feat(skills): add self-config skill
safe config editing with secret redaction - config.sh redacted|summary - config-patch.sh start|'... jq patch..'|commit|diff|sort|status|show|rollback|reset|backup - service.sh restart|restart-auto-rollback|confirm
This commit is contained in:
parent
25f26f305b
commit
ac71769b70
4 changed files with 549 additions and 0 deletions
89
unsupported/workspace/skills/self-config/SKILL.md
Normal file
89
unsupported/workspace/skills/self-config/SKILL.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
name: self-config
|
||||
description: Allows the agent to safely update its own configuration files
|
||||
---
|
||||
|
||||
# Self-Config Skill Capabilities
|
||||
|
||||
1. Presentation of the `config.json` with secrets redacted.
|
||||
2. Staging of a `config.new.json` with utilities for patching, sorting, diffing, and validation.
|
||||
3. Safe service restarts with auto-rollback timers.
|
||||
|
||||
## Tools used
|
||||
- `scripts/config.sh`: Base utility for presenting redacted JSON files.
|
||||
- `scripts/config-patch.sh`: Tools for iterative patching of a staged `config.new.json` file.
|
||||
- `scripts/service.sh`: Manages service restarts with auto-rollback.
|
||||
- `jq`: Used internally for JSON manipulation.
|
||||
|
||||
## View Current Configuration
|
||||
|
||||
### Redacted (Full)
|
||||
```bash
|
||||
scripts/config.sh redacted
|
||||
```
|
||||
|
||||
### Summary (Filtered)
|
||||
Shows only configured models, agents, enabled tools, devices, and heartbeat settings.
|
||||
```bash
|
||||
scripts/config.sh summary
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow: Safe Configuration Update
|
||||
|
||||
### 1. Start a Session
|
||||
Initialize the staging environment. This redacts sensitive keys (tokens, passwords) into a separate hidden file so they aren't exposed in plain text during editing.
|
||||
```bash
|
||||
scripts/config-patch.sh start
|
||||
```
|
||||
|
||||
### 2. Apply Patches
|
||||
Apply `jq` filters to the **staged** file. You can run this multiple times.
|
||||
```bash
|
||||
scripts/config-patch.sh '.path.to.key = "new_value"'
|
||||
```
|
||||
|
||||
### 3. Review Changes
|
||||
Review the diff between the original and the staged (redacted) version.
|
||||
```bash
|
||||
scripts/config-patch.sh diff
|
||||
```
|
||||
|
||||
### 4. View Staged Config
|
||||
```bash
|
||||
scripts/config-patch.sh config
|
||||
# OR
|
||||
scripts/config-patch.sh summary
|
||||
```
|
||||
|
||||
### 5. Sort Keys (Optional)
|
||||
Sort the keys in the staged file alphabetically.
|
||||
```bash
|
||||
scripts/config-patch.sh sort
|
||||
```
|
||||
|
||||
### 6. Reset (Abort)
|
||||
If you make a mistake *before* switching, clear the staging files.
|
||||
```bash
|
||||
scripts/config-patch.sh reset
|
||||
```
|
||||
|
||||
### 7. Switch & Test (The "Hot" Update)
|
||||
Commit the patch, restart the service, and create a timer that will rollback
|
||||
unless 'confirm' action is performed. Default is 120 seconds.
|
||||
|
||||
```bash
|
||||
TIMEOUT=300 scripts/service.sh restart-auto-rollback
|
||||
```
|
||||
|
||||
### 8. Confirm
|
||||
If the agent is still working and the changes are correct, confirm the update to remove the rollback marker.
|
||||
```bash
|
||||
scripts/service.sh confirm
|
||||
```
|
||||
|
||||
## Safety Rules
|
||||
1. **Always** use `start` before applying patches.
|
||||
2. **Never** manually edit the `.secrets.json` file.
|
||||
3. **Always** confirm your changes within the time limit after a `switch`.
|
||||
162
unsupported/workspace/skills/self-config/scripts/config-patch.sh
Executable file
162
unsupported/workspace/skills/self-config/scripts/config-patch.sh
Executable file
|
|
@ -0,0 +1,162 @@
|
|||
#!/bin/bash
|
||||
|
||||
# config-patch: A tool for iterative JSON patching with high-security secret handling.
|
||||
|
||||
# Determine script directory for relocatable operation
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Source shared config and functions
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
|
||||
COMMAND="${1:-help}"
|
||||
|
||||
case "$COMMAND" in
|
||||
|
||||
start|sort)
|
||||
|
||||
[ -f "$SECRETS_FILE" ] && { echo "Error: Session active. Commit or reset first."; exit 1; }
|
||||
[ ! -f "$PICOCLAW_CONFIG" ] && { echo "Error: $PICOCLAW_CONFIG not found."; exit 1; }
|
||||
|
||||
redact_secrets || exit 1
|
||||
|
||||
# On success - Move results final locations (trap will clean if script crashes before this)
|
||||
# Clear <file>_TMP so trap doesn't try to delete final files
|
||||
|
||||
mv "$CONFIG_TMP" "$STAGING_FILE"
|
||||
CONFIG_TMP=""
|
||||
mv "$SECRETS_TMP" "$SECRETS_FILE"
|
||||
SECRETS_TMP=""
|
||||
|
||||
echo "Start: Redacted staging file created. Secrets mapped to $SECRETS_FILE"
|
||||
|
||||
;;&
|
||||
|
||||
start)
|
||||
exit 0
|
||||
;;
|
||||
|
||||
sort)
|
||||
|
||||
{ rm "$STAGING_FILE" ; jq -S . > "$STAGING_FILE"; } < "$STAGING_FILE"
|
||||
|
||||
echo "Sorted: keys in $STAGING_FILE"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
commit)
|
||||
[ ! -f "$STAGING_FILE" ] && { echo "Error: No staged changes."; exit 1; }
|
||||
[ ! -f "$SECRETS_FILE" ] && { echo "Error: No secrets file found."; exit 1; }
|
||||
|
||||
# 3. Restoration & Validation
|
||||
FINAL_FILE=$(mktemp)
|
||||
|
||||
# Restore secrets: replace path placeholders with stored values
|
||||
# Keys in secrets file are the paths (e.g., "SECRET:agents.defaults.model")
|
||||
jq --slurpfile secrets "$SECRETS_FILE" 'walk(if type == "string" and $secrets[0][.] != null then $secrets[0][.] else . end)' "$STAGING_FILE" > "$FINAL_FILE"
|
||||
|
||||
# Ensure no placeholders survived (means they weren't in the map)
|
||||
# Check if any string key in secrets map still exists in output
|
||||
while IFS= read -r key; do
|
||||
if grep -q "\"$key\"" "$FINAL_FILE"; then
|
||||
echo "Error: Placeholder '$key' was not restored. Check for typos in secrets map."
|
||||
exit 1
|
||||
fi
|
||||
done < <(jq -r 'keys[]' "$SECRETS_FILE")
|
||||
|
||||
# 4. JSON Integrity Check
|
||||
jq . "$FINAL_FILE" > /dev/null 2>&1 || { echo "Error: Invalid JSON output. Aborting."; exit 1; }
|
||||
|
||||
# 5. Backup & Swap (unredacted config)
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp "$PICOCLAW_CONFIG" "$BACKUP_DIR/${BASE}_${TIMESTAMP}.json"
|
||||
|
||||
mv "$FINAL_FILE" "$PICOCLAW_CONFIG"
|
||||
rm -f "$STAGING_FILE" "$SECRETS_FILE"
|
||||
echo "Committed successfully. Backup: $BACKUP_DIR/${BASE}_${TIMESTAMP}.json"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
status)
|
||||
echo "Target: $PICOCLAW_CONFIG"
|
||||
[ -f "$STAGING_FILE" ] && echo "Staging: Active ($STAGING_FILE)" || echo "Staging: None"
|
||||
[ -f "$SECRETS_FILE" ] && echo "Secrets: $(jq 'keys | length' "$SECRETS_FILE") tracked" || echo "Secrets: None"
|
||||
;;
|
||||
|
||||
summary)
|
||||
# Output a summary of the staged config with only configured models, agents, enabled tools, devices, and heartbeat
|
||||
|
||||
summarize "$STAGING_FILE"
|
||||
|
||||
exit 0
|
||||
;;
|
||||
|
||||
show|redacted|config)
|
||||
# Output the full staged config
|
||||
|
||||
cat "$STAGING_FILE"
|
||||
;;
|
||||
|
||||
diff)
|
||||
[ ! -f "$STAGING_FILE" ] && { echo "Error: No staged changes to diff."; exit 1; }
|
||||
|
||||
redact_secrets
|
||||
diff -u "$CONFIG_TMP" "$STAGING_FILE"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
rollback)
|
||||
# Restore the most recent backup
|
||||
LATEST=$(ls -t "$BACKUP_DIR"/${BASE}_*.json 2>/dev/null | head -n 1)
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo "Error: No backups found for $PICOCLAW_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LATEST_BASE=$(basename "$LATEST" .json)
|
||||
|
||||
cp "$LATEST" "$PICOCLAW_CONFIG"
|
||||
|
||||
echo "Rollback: Restored $PICOCLAW_CONFIG from $LATEST"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
reset)
|
||||
rm -f "$STAGING_FILE" "$SECRETS_FILE"
|
||||
echo "Session cleared."
|
||||
exit 0
|
||||
;;
|
||||
|
||||
help)
|
||||
echo "Usage: $0 <command> [config_file]"
|
||||
echo "Commands:"
|
||||
echo " start - Create staging file with redacted secrets"
|
||||
echo " sort - Sort JSON keys alphabetically"
|
||||
echo " diff - Show staged changes"
|
||||
echo " commit - Apply staged changes to config"
|
||||
echo " reset - Clear staging (discard changes)"
|
||||
echo " rollback - Restore from last backup"
|
||||
echo " status - Show current state"
|
||||
echo " summary - Show summary of the staged config with unused models/chatconfigs filtered"
|
||||
echo " config - Show the staged config"
|
||||
echo " <jq expr> - Apply inline jq patch (e.g. '.agents.model=\"gpt-4\"')"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
# Generic Patching
|
||||
redact_secrets
|
||||
if [ -f "$STAGING_FILE" ]; then
|
||||
SOURCE="$STAGING_FILE"
|
||||
else
|
||||
SOURCE="$PICOCLAW_CONFIG"
|
||||
fi
|
||||
TMP=$(mktemp)
|
||||
if jq "$COMMAND" "$SOURCE" > "$TMP"; then
|
||||
mv "$TMP" "$STAGING_FILE"
|
||||
echo "Applied patch to $STAGING_FILE"
|
||||
else
|
||||
rm -f "$TMP"; exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
157
unsupported/workspace/skills/self-config/scripts/config.sh
Executable file
157
unsupported/workspace/skills/self-config/scripts/config.sh
Executable file
|
|
@ -0,0 +1,157 @@
|
|||
#!/bin/bash
|
||||
|
||||
# config: A tool for presenting config in insecure channels.
|
||||
|
||||
# Input validation functions
|
||||
assert_is_identifier() {
|
||||
if ! echo "$1" | grep -qE '^[a-zA-Z_][a-zA-Z0-9_-]*$'; then
|
||||
echo "Error: $2" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_is_file_path() {
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: $2" >&2
|
||||
return 1
|
||||
fi
|
||||
local dir
|
||||
dir=$(dirname "$1")
|
||||
if [ ! -d "$dir" ] && [ "$dir" != "." ]; then
|
||||
echo "Error: Directory does not exist: $dir" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_is_safe() {
|
||||
# Only check the command argument, not the entire command line
|
||||
local cmd="$1"
|
||||
if echo "$cmd" | grep -qE '[\$\`\\]'; then
|
||||
echo "Error: $2 - Command contains dangerous characters" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
COMMAND="${1:-help}"
|
||||
|
||||
PICOCLAW_CONFIG="${2:-}"
|
||||
PICOCLAW_CONFIG="${PICOCLAW_CONFIG:-$HOME/.picoclaw/config.json}"
|
||||
|
||||
assert_is_safe "$COMMAND" "Command must be a valid identifier" || exit 1
|
||||
assert_is_file_path "$PICOCLAW_CONFIG" "Config file path must be valid" || exit 1
|
||||
|
||||
# Setup paths
|
||||
BASE=$(basename "$PICOCLAW_CONFIG" .json)
|
||||
DIR=$(dirname "$PICOCLAW_CONFIG")
|
||||
STAGING_FILE="$DIR/$BASE.new.json"
|
||||
SECRETS_FILE="$DIR/.$BASE.secrets.json"
|
||||
BACKUP_DIR="$DIR/.config_backups"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Temp file for staging (cleaned up on script exit)
|
||||
CONFIG_TMP=""
|
||||
SECRETS_TMP=""
|
||||
FINAL_FILE=""
|
||||
cleanup() { rm -f "$CONFIG_TMP" "$FINAL_FILE" "$SECRETS_TMP"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
SECRET_PATTERN="key|pass|secret|token|auth|credential|sid|sk-"
|
||||
EXCLUDE_PATTERN="max_tokens|auth_method|enable_auth|auth_type"
|
||||
|
||||
# Shared function: extract secrets if not already done
|
||||
redact_secrets() {
|
||||
|
||||
# Check if original has any secrets
|
||||
paths=$(jq -c "paths(scalars) | select(.[-1] | tostring | ascii_downcase | (test(\"$SECRET_PATTERN\"; \"i\") and (test(\"$EXCLUDE_PATTERN\"; \"i\") | not)))" "$PICOCLAW_CONFIG" 2>/dev/null) || return 0
|
||||
|
||||
# Create staging file in temp (global var for trap cleanup)
|
||||
CONFIG_TMP=$(mktemp)
|
||||
cp "$PICOCLAW_CONFIG" "$CONFIG_TMP"
|
||||
|
||||
# Extract secrets directly to final location
|
||||
SECRETS_TMP=$(mktemp)
|
||||
echo "{}" > "$SECRETS_TMP"
|
||||
chmod 600 "$SECRETS_TMP"
|
||||
|
||||
while read -r p; do
|
||||
[ -z "$p" ] && continue
|
||||
ID="SECRET:$(echo "$p" | jq -r '. | map(tostring) | map(if . | test("^[0-9]+$") then "[" + . + "]" else "." + . end) | join("") | ltrimstr(".")')"
|
||||
VAL=$(jq -r "getpath($p)" "$PICOCLAW_CONFIG")
|
||||
|
||||
# Add to secrets file
|
||||
{ rm -f "$SECRETS_TMP" ; jq --arg id "$ID" --arg val "$VAL" '. + {($id): $val}' > "$SECRETS_TMP"; } < "$SECRETS_TMP"
|
||||
|
||||
# Replace with placeholder in staging
|
||||
{ rm -f "$CONFIG_TMP" ; jq --argjson p "$p" --arg id "$ID" 'setpath($p; $id)' > "${CONFIG_TMP}"; } < "$CONFIG_TMP"
|
||||
|
||||
done <<< "$paths"
|
||||
}
|
||||
|
||||
|
||||
# Output a summary with only configured models, agents, enabled tools, devices, and heartbeat
|
||||
|
||||
summarize()
|
||||
{
|
||||
jq '
|
||||
def referenced_models:
|
||||
[.. | objects | if has("model") then .model elif has("model_name") then .model_name else null end // empty] | unique;
|
||||
|
||||
def used_model_ids:
|
||||
referenced_models;
|
||||
|
||||
def is_configured:
|
||||
. != null and . != {} and . != [];
|
||||
|
||||
def filter_tools:
|
||||
(walk(
|
||||
if type == "object" then
|
||||
if .enabled == false then
|
||||
null
|
||||
else
|
||||
. as $obj | reduce keys_unsorted[] as $k ({}; . + {($k): ($obj[$k] | filter_tools)})
|
||||
end
|
||||
else
|
||||
.
|
||||
end
|
||||
) | if type == "object" then with_entries(select(.value != null)) else . end);
|
||||
|
||||
. * {
|
||||
model_list: (.model_list // []) | map(select(.model_name as $id | used_model_ids | contains([$id]))),
|
||||
agents: (.agents // {}) | map_values(select(is_configured))
|
||||
} | .channels = ((.channels // {}) | to_entries | map(select(.value.enabled == true)) | from_entries)
|
||||
| .tools = ((.tools // {}) | filter_tools)
|
||||
| .providers = ((.providers // {} | to_entries | map(select(.value.api_key | gsub("^\\s+"; "") | gsub("\\s+$"; "") != "")) | from_entries))
|
||||
| .devices = (if (.devices.enabled // false) then .devices else null end)
|
||||
| .heartbeat = (if (.heartbeat.enabled // false) then .heartbeat else null end)
|
||||
' "${1}" | jq 'with_entries(select(.value != null))'
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
case "$COMMAND" in
|
||||
|
||||
redacted)
|
||||
|
||||
redact_secrets
|
||||
cat "$CONFIG_TMP"
|
||||
;;
|
||||
|
||||
summary)
|
||||
# Output a summary with only configured models, agents, enabled tools, devices, and heartbeat
|
||||
|
||||
redact_secrets
|
||||
summarize "$CONFIG_TMP"
|
||||
|
||||
exit 0
|
||||
;;
|
||||
|
||||
help)
|
||||
echo "Usage: $0 <command> [config_file]"
|
||||
echo "Commands:"
|
||||
echo " redacted - Show config (default: PICOCLAW_CONFIG)"
|
||||
echo " summary - Show config with unused models/chatconfigs filtered"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
esac
|
||||
fi
|
||||
141
unsupported/workspace/skills/self-config/scripts/service.sh
Normal file
141
unsupported/workspace/skills/self-config/scripts/service.sh
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#!/bin/bash
|
||||
|
||||
# service.sh - Service management with auto-rollback support
|
||||
# A "dead man's switch" for configuration changes.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Validate identifier (alphanumeric, dash, underscore)
|
||||
assert_is_identifier() {
|
||||
if ! echo "$1" | grep -qE '^[a-zA-Z_][a-zA-Z0-9_-]*$'; then
|
||||
echo "Error: $2" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_is_number ()
|
||||
{
|
||||
# Use grep for robust, portable POSIX regex matching.
|
||||
if ! echo "$1" | grep -qE '^[0-9]+$';
|
||||
then
|
||||
echo "Error: $2" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
SERVICE_NAME="${PICOCLAW_SERVICE_NAME:-picoclaw}"
|
||||
|
||||
COMMAND="${1:-help}"
|
||||
assert_is_identifier "$COMMAND" "Action must be a valid identifier" || exit 1
|
||||
|
||||
SERVICE="${2:-$SERVICE_NAME}"
|
||||
assert_is_identifier "$SERVICE" "Service name must be a valid identifier" || exit 1
|
||||
|
||||
CONFIG="${3:-${PICOCLAW_CONFIG:-$HOME/.picoclaw/config.json}}"
|
||||
|
||||
[[ -n "$CONFIG" ]] && ! [[ -f "$CONFIG" ]] && echo "Expected file" && exit 1
|
||||
|
||||
case "$COMMAND" in
|
||||
|
||||
restart)
|
||||
|
||||
echo "Restarting service: $SERVICE"
|
||||
systemctl --user restart "$SERVICE"
|
||||
echo "Done."
|
||||
;;
|
||||
|
||||
_rollback)
|
||||
|
||||
# Check if marker file exists (auto-rollback was set)
|
||||
MARKER="${CONFIG}-PENDING-ROLLBACK"
|
||||
|
||||
if [ ! -f "$MARKER" ]; then
|
||||
# No marker = nothing to rollback = success
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Marker exists - perform rollback
|
||||
rm -f "$MARKER"
|
||||
|
||||
"$SCRIPT_DIR/config.sh" rollback "$CONFIG"
|
||||
|
||||
echo "[ROLLBACK] Restarting service: $SERVICE"
|
||||
systemctl --user restart "$SERVICE" || true
|
||||
echo "Rollback complete."
|
||||
|
||||
;;
|
||||
|
||||
restart-auto-rollback)
|
||||
|
||||
TIMEOUT="${TIMEOUT:-}"
|
||||
TIMEOUT="${4:-120}"
|
||||
assert_is_number "$TIMEOUT" "Timer must be number of seconds" || exit 1
|
||||
|
||||
# Create marker file
|
||||
MARKER="${CONFIG}-PENDING-ROLLBACK"
|
||||
|
||||
# Restart service first
|
||||
echo "Restarting service: $SERVICE"
|
||||
systemctl --user restart "$SERVICE"
|
||||
|
||||
echo "Auto-rollback armed for $SERVICE (timeout: ${TIMEOUT}s)"
|
||||
echo "Marker: $MARKER"
|
||||
|
||||
# Start background timer
|
||||
(
|
||||
sleep "$TIMEOUT"
|
||||
if [ -f "$MARKER" ]; then
|
||||
echo "[TIMEOUT] Auto-rollback triggered for $SERVICE"
|
||||
bash "$0" _rollback "$SERVICE" "$CONFIG"
|
||||
fi
|
||||
) &
|
||||
TIMER_PID=$!
|
||||
|
||||
{
|
||||
echo "Timer PID: $TIMER_PID"
|
||||
echo "----------------------------------------------------"
|
||||
echo "Service will ROLLBACK in $TIMEOUT seconds if not confirmed."
|
||||
echo "To confirm: service.sh confirm $SERVICE"
|
||||
echo "----------------------------------------------------"
|
||||
} | tee "$MARKER"
|
||||
|
||||
;;
|
||||
|
||||
confirm)
|
||||
|
||||
MARKER="${CONFIG}-PENDING-ROLLBACK"
|
||||
|
||||
if [ -n "$MARKER" ] && [ -f "$MARKER" ]; then
|
||||
rm -f "$MARKER"
|
||||
echo "Rollback cancelled. Changes confirmed."
|
||||
else
|
||||
echo "No pending rollback to confirm."
|
||||
fi
|
||||
|
||||
;;
|
||||
|
||||
help)
|
||||
echo "Usage: $0 <command> [args...]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " restart [service] Restart service (default: \$PICOCLAW_SERVICE_NAME or picoclaw)"
|
||||
echo " auto-rollback [service] [config] [timeout]"
|
||||
echo " Arm rollback timer, restart service"
|
||||
echo " confirm [service] [config] Cancel pending rollback"
|
||||
echo " _rollback [service] [config]"
|
||||
echo " Internal: check marker, rollback if present"
|
||||
echo ""
|
||||
echo "Environment:"
|
||||
echo " PICOCLAW_SERVICE_NAME Default service name (default: picoclaw)"
|
||||
echo " PICOCLAW_CONFIG Default config file (default: picoclaw)"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown command: $COMMAND"
|
||||
echo "Run '$0 help' for usage."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Loading…
Add table
Reference in a new issue