agent-relay-plugin 0.4.13 → 0.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/hooks/approval-gate.sh +131 -0
- package/hooks/hooks.json +22 -0
- package/hooks/poll-inbox.sh +88 -7
- package/hooks/relay-monitor.sh +9 -1
- package/hooks/session-end.sh +1 -0
- package/hooks/set-status.sh +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent Relay plugin — approval gate for PreToolUse and PermissionDenied hooks.
|
|
3
|
+
# Enforces AGENT_RELAY_APPROVAL policy: open (default), guarded, read-only.
|
|
4
|
+
|
|
5
|
+
MODE="${AGENT_RELAY_APPROVAL:-open}"
|
|
6
|
+
[ "$MODE" = "open" ] && exit 0
|
|
7
|
+
|
|
8
|
+
input=$(cat)
|
|
9
|
+
event=$(printf '%s' "$input" | jq -r '.hook_event_name')
|
|
10
|
+
|
|
11
|
+
# --- PermissionDenied: allow retry so the model adapts ---
|
|
12
|
+
if [ "$event" = "PermissionDenied" ]; then
|
|
13
|
+
jq -n '{hookSpecificOutput:{hookEventName:"PermissionDenied",retry:true}}'
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
tool=$(printf '%s' "$input" | jq -r '.tool_name')
|
|
18
|
+
|
|
19
|
+
# --- Always allowed: read-only and orchestration tools ---
|
|
20
|
+
case "$tool" in
|
|
21
|
+
Read|Grep|Glob|WebFetch|WebSearch|Agent|Monitor|ToolSearch|AskUserQuestion|Skill|ScheduleWakeup|CronList|EnterPlanMode|ExitPlanMode)
|
|
22
|
+
exit 0 ;;
|
|
23
|
+
Task*|*McpResource*)
|
|
24
|
+
exit 0 ;;
|
|
25
|
+
mcp__*)
|
|
26
|
+
exit 0 ;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
deny() {
|
|
30
|
+
jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'
|
|
31
|
+
exit 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# --- read-only: deny mutation tools ---
|
|
35
|
+
if [ "$MODE" = "read-only" ]; then
|
|
36
|
+
case "$tool" in
|
|
37
|
+
Write|Edit|NotebookEdit|CronCreate|CronDelete|PushNotification|RemoteTrigger|EnterWorktree|ExitWorktree)
|
|
38
|
+
deny "${tool} blocked by read-only policy" ;;
|
|
39
|
+
esac
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# --- Bash command analysis ---
|
|
43
|
+
if [ "$tool" = "Bash" ]; then
|
|
44
|
+
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""')
|
|
45
|
+
|
|
46
|
+
if [ "$MODE" = "guarded" ]; then
|
|
47
|
+
# Denylist: block destructive patterns anywhere in the command
|
|
48
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*(rm|rmdir)\b'; then
|
|
49
|
+
deny "rm/rmdir blocked by guarded policy"
|
|
50
|
+
fi
|
|
51
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*mv\b'; then
|
|
52
|
+
deny "mv blocked by guarded policy (use cp instead)"
|
|
53
|
+
fi
|
|
54
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*(chmod|chown|chattr)\b'; then
|
|
55
|
+
deny "Permission change blocked by guarded policy"
|
|
56
|
+
fi
|
|
57
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*(kill|pkill|killall)\b'; then
|
|
58
|
+
deny "Process signal blocked by guarded policy"
|
|
59
|
+
fi
|
|
60
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*(dd|mkfs|truncate|shred)\b'; then
|
|
61
|
+
deny "Destructive command blocked by guarded policy"
|
|
62
|
+
fi
|
|
63
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|]\s*)\s*sudo\b'; then
|
|
64
|
+
deny "sudo blocked by guarded policy"
|
|
65
|
+
fi
|
|
66
|
+
if printf '%s' "$cmd" | grep -qE 'git\s+(reset\s+--hard|push\s+.*(-f\b|--force)|clean\s+-[a-z]*f)'; then
|
|
67
|
+
deny "Destructive git operation blocked by guarded policy"
|
|
68
|
+
fi
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ "$MODE" = "read-only" ]; then
|
|
73
|
+
# Check for file-writing redirects (allow >/dev/null and fd redirects like 2>&1)
|
|
74
|
+
redirect_cleaned=$(printf '%s' "$cmd" | sed 's|[0-9]*>[[:space:]]*/dev/null||g; s|[0-9]*>&[0-9]*||g')
|
|
75
|
+
if printf '%s' "$redirect_cleaned" | grep -qE '>>|[^a-zA-Z0-9_]>|^>'; then
|
|
76
|
+
deny "Output redirection blocked by read-only policy"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Allowlist: check first command word
|
|
80
|
+
first_word=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//' | awk '{print $1}')
|
|
81
|
+
|
|
82
|
+
case "$first_word" in
|
|
83
|
+
# Filesystem reads
|
|
84
|
+
ls|cat|head|tail|less|more|file|stat|tree|du|df|readlink|realpath|basename|dirname)
|
|
85
|
+
exit 0 ;;
|
|
86
|
+
# Text processing
|
|
87
|
+
wc|sort|uniq|diff|comm|tr|cut|paste|column|fmt|fold|tac|rev|nl|seq|xargs)
|
|
88
|
+
exit 0 ;;
|
|
89
|
+
# Search
|
|
90
|
+
find|fd|grep|egrep|fgrep|rg|ag|ast-grep)
|
|
91
|
+
exit 0 ;;
|
|
92
|
+
# Shell builtins / info
|
|
93
|
+
echo|printf|pwd|which|whoami|date|env|printenv|uname|id|hostname|type|true|false|test|\[)
|
|
94
|
+
exit 0 ;;
|
|
95
|
+
# System info
|
|
96
|
+
ps|top|htop|uptime|free|lsof|netstat|ss|ip|ifconfig)
|
|
97
|
+
exit 0 ;;
|
|
98
|
+
# Data processing
|
|
99
|
+
jq|yq|bc|expr|base64|xxd|hexdump|od|strings|md5sum|sha256sum|sha1sum|shasum)
|
|
100
|
+
exit 0 ;;
|
|
101
|
+
# Network reads
|
|
102
|
+
curl|wget|dig|nslookup|ping|traceroute|ssh)
|
|
103
|
+
exit 0 ;;
|
|
104
|
+
# Git — subcommand check
|
|
105
|
+
git)
|
|
106
|
+
git_sub=$(printf '%s' "$cmd" | sed 's/^[[:space:]]*git[[:space:]]*//' | awk '{print $1}')
|
|
107
|
+
case "$git_sub" in
|
|
108
|
+
status|log|diff|show|branch|remote|tag|rev-parse|config|shortlog|reflog|blame|describe|ls-files|ls-tree|cat-file|rev-list|for-each-ref|name-rev|merge-base|version)
|
|
109
|
+
exit 0 ;;
|
|
110
|
+
stash)
|
|
111
|
+
stash_action=$(printf '%s' "$cmd" | sed 's/.*stash[[:space:]]*//' | awk '{print $1}')
|
|
112
|
+
case "$stash_action" in
|
|
113
|
+
list|show|"") exit 0 ;;
|
|
114
|
+
*) deny "git stash ${stash_action} blocked by read-only policy" ;;
|
|
115
|
+
esac ;;
|
|
116
|
+
*)
|
|
117
|
+
deny "git ${git_sub} blocked by read-only policy" ;;
|
|
118
|
+
esac ;;
|
|
119
|
+
# Everything else denied
|
|
120
|
+
*)
|
|
121
|
+
deny "Command '${first_word}' not in read-only allowlist" ;;
|
|
122
|
+
esac
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Unknown tools: guarded → allow, read-only → deny
|
|
127
|
+
if [ "$MODE" = "read-only" ]; then
|
|
128
|
+
deny "${tool} not in read-only allowlist"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
exit 0
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/approval-gate.sh\"",
|
|
9
|
+
"timeout": 5
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PermissionDenied": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/approval-gate.sh\"",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
3
25
|
"UserPromptSubmit": [
|
|
4
26
|
{
|
|
5
27
|
"hooks": [
|
package/hooks/poll-inbox.sh
CHANGED
|
@@ -18,12 +18,64 @@ fi
|
|
|
18
18
|
|
|
19
19
|
# PID file so session-start.sh can find and kill us on /clear or /compact.
|
|
20
20
|
pid_file="/tmp/agent-relay-poll-${AGENT_ID}.pid"
|
|
21
|
+
status_file="/tmp/agent-relay-status-${AGENT_ID}.state"
|
|
21
22
|
cleanup() { rm -f "$pid_file"; }
|
|
22
23
|
trap cleanup EXIT
|
|
23
24
|
echo $$ > "$pid_file"
|
|
24
25
|
|
|
26
|
+
fetch_cursor() {
|
|
27
|
+
local cursor
|
|
28
|
+
cursor=$(curl -s --fail "${auth_header_args[@]}" "${RELAY_URL}/api/messages/cursor" 2>/dev/null | jq -r '.latestId // empty' 2>/dev/null) || return 1
|
|
29
|
+
case "$cursor" in
|
|
30
|
+
''|*[!0-9]*) return 1 ;;
|
|
31
|
+
*) printf '%s\n' "$cursor" ;;
|
|
32
|
+
esac
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
current_status() {
|
|
36
|
+
local status
|
|
37
|
+
status=$(head -1 "$status_file" 2>/dev/null || true)
|
|
38
|
+
case "$status" in
|
|
39
|
+
online|idle|busy|offline) printf '%s\n' "$status" ;;
|
|
40
|
+
*) printf 'idle\n' ;;
|
|
41
|
+
esac
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
re_register() {
|
|
45
|
+
local machine rig project caps_csv caps_json approval reg_code
|
|
46
|
+
machine=$(hostname)
|
|
47
|
+
project=$(basename "${PWD:-unknown}")
|
|
48
|
+
if [ -n "$CLAUDE_RIG_NAME" ]; then rig="$CLAUDE_RIG_NAME"
|
|
49
|
+
elif [ -n "$CLAUDE_CONFIG_DIR" ]; then rig=$(basename "$CLAUDE_CONFIG_DIR")
|
|
50
|
+
else rig="default"; fi
|
|
51
|
+
caps_csv="${AGENT_RELAY_CAPS:-chat}"
|
|
52
|
+
caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
|
|
53
|
+
approval="${AGENT_RELAY_APPROVAL:-open}"
|
|
54
|
+
|
|
55
|
+
reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" "${auth_header_args[@]}" \
|
|
56
|
+
-H 'Content-Type: application/json' \
|
|
57
|
+
-d "$(jq -n \
|
|
58
|
+
--arg id "$AGENT_ID" \
|
|
59
|
+
--arg name "$project ($rig @ $machine)" \
|
|
60
|
+
--arg machine "$machine" \
|
|
61
|
+
--arg rig "$rig" \
|
|
62
|
+
--argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
|
|
63
|
+
--argjson caps "$caps_json" \
|
|
64
|
+
--argjson meta "$(jq -n --arg cwd "$PWD" --arg approval "$approval" '{cwd: $cwd, approvalMode: $approval}')" \
|
|
65
|
+
'{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
|
|
66
|
+
)" 2>/dev/null)
|
|
67
|
+
|
|
68
|
+
[ "$reg_code" = "200" ] || [ "$reg_code" = "201" ]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
set_status() {
|
|
72
|
+
printf '%s\n' "$1" > "$status_file"
|
|
73
|
+
curl -s -o /dev/null -X PATCH "${RELAY_URL}/api/agents/${AGENT_ID}/status" "${auth_header_args[@]}" \
|
|
74
|
+
-H 'Content-Type: application/json' -d "{\"status\":\"$1\"}" 2>/dev/null
|
|
75
|
+
}
|
|
76
|
+
|
|
25
77
|
# Bootstrap cursor at current max so we only see messages arriving after session start.
|
|
26
|
-
since_id=$(
|
|
78
|
+
since_id=$(fetch_cursor || echo 0)
|
|
27
79
|
|
|
28
80
|
fail_streak=0
|
|
29
81
|
marked_ready=false
|
|
@@ -31,15 +83,24 @@ while true; do
|
|
|
31
83
|
hb_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${auth_header_args[@]}" \
|
|
32
84
|
"${RELAY_URL}/api/agents/${AGENT_ID}/heartbeat" 2>/dev/null)
|
|
33
85
|
|
|
34
|
-
# Agent was
|
|
86
|
+
# Agent was pruned — re-register instead of exiting.
|
|
35
87
|
if [ "$hb_code" = "404" ]; then
|
|
36
|
-
|
|
88
|
+
if re_register; then
|
|
89
|
+
if since_id=$(fetch_cursor); then
|
|
90
|
+
marked_ready=false
|
|
91
|
+
hb_code="200"
|
|
92
|
+
else
|
|
93
|
+
hb_code="000"
|
|
94
|
+
fi
|
|
95
|
+
else
|
|
96
|
+
hb_code="000"
|
|
97
|
+
fi
|
|
37
98
|
fi
|
|
38
99
|
|
|
39
100
|
msgs=$(curl -s --fail "${auth_header_args[@]}" "${RELAY_URL}/api/messages?for=${AGENT_ID}&sinceId=${since_id}&unread=true" 2>/dev/null)
|
|
40
101
|
poll_rc=$?
|
|
41
102
|
|
|
42
|
-
if [ "$hb_code" != "200" ] || [ $poll_rc -ne 0 ]; then
|
|
103
|
+
if [ "$hb_code" != "200" ] && [ "$hb_code" != "404" ] || [ $poll_rc -ne 0 ]; then
|
|
43
104
|
fail_streak=$((fail_streak + 1))
|
|
44
105
|
case "$fail_streak" in
|
|
45
106
|
1) delay=2 ;;
|
|
@@ -53,23 +114,43 @@ while true; do
|
|
|
53
114
|
sleep "$delay"
|
|
54
115
|
continue
|
|
55
116
|
fi
|
|
117
|
+
|
|
118
|
+
# Recovery from failure streak — re-assert status so dashboard shows correct state.
|
|
119
|
+
if [ "$fail_streak" -gt 0 ]; then
|
|
120
|
+
set_status "$(current_status)"
|
|
121
|
+
fi
|
|
56
122
|
fail_streak=0
|
|
57
123
|
|
|
58
124
|
if [ "$marked_ready" = "false" ]; then
|
|
59
125
|
curl -s -o /dev/null -X PATCH "${RELAY_URL}/api/agents/${AGENT_ID}/ready" "${auth_header_args[@]}" \
|
|
60
126
|
-H 'Content-Type: application/json' -d '{"ready":true}' 2>/dev/null
|
|
127
|
+
set_status "idle"
|
|
61
128
|
marked_ready=true
|
|
62
129
|
fi
|
|
63
130
|
|
|
64
131
|
count=$(echo "$msgs" | jq 'length' 2>/dev/null || echo 0)
|
|
65
132
|
if [ "$count" -gt 0 ] 2>/dev/null && [ "$count" != "0" ]; then
|
|
66
|
-
echo "$msgs" | jq -
|
|
67
|
-
|
|
68
|
-
|
|
133
|
+
echo "$msgs" | jq -c '.[]' | while IFS= read -r msg; do
|
|
134
|
+
mid=$(printf '%s' "$msg" | jq -r '.id')
|
|
135
|
+
claimable=$(printf '%s' "$msg" | jq -r '.claimable // false')
|
|
136
|
+
|
|
137
|
+
if [ "$claimable" = "true" ]; then
|
|
138
|
+
claim_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
|
|
139
|
+
"${RELAY_URL}/api/messages/${mid}/claim" "${auth_header_args[@]}" \
|
|
140
|
+
-H 'Content-Type: application/json' \
|
|
141
|
+
-d "{\"agentId\":\"${AGENT_ID}\"}" 2>/dev/null)
|
|
142
|
+
if [ "$claim_code" != "200" ]; then
|
|
143
|
+
continue
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
printf '%s' "$msg" | jq -r 'if .type == "system" then "⚠ SYSTEM [msg:\(.id)]: \(.body)" else "[msg:\(.id)] \(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)" end'
|
|
148
|
+
|
|
69
149
|
curl -s -X PATCH "${RELAY_URL}/api/messages/${mid}" "${auth_header_args[@]}" \
|
|
70
150
|
-H 'Content-Type: application/json' \
|
|
71
151
|
-d "{\"readBy\":\"${AGENT_ID}\"}" > /dev/null 2>&1 || true
|
|
72
152
|
done
|
|
153
|
+
since_id=$(echo "$msgs" | jq '[.[].id] | max')
|
|
73
154
|
fi
|
|
74
155
|
|
|
75
156
|
sleep "$INTERVAL"
|
package/hooks/relay-monitor.sh
CHANGED
|
@@ -53,6 +53,7 @@ printf '%s\n' "$agent_id" > "$instance_state"
|
|
|
53
53
|
# --- Register agent ---
|
|
54
54
|
caps_csv="${AGENT_RELAY_CAPS:-chat}"
|
|
55
55
|
caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
|
|
56
|
+
approval="${AGENT_RELAY_APPROVAL:-open}"
|
|
56
57
|
|
|
57
58
|
reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" "${auth_header_args[@]}" \
|
|
58
59
|
-H 'Content-Type: application/json' \
|
|
@@ -63,7 +64,7 @@ reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agen
|
|
|
63
64
|
--arg rig "$rig" \
|
|
64
65
|
--argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
|
|
65
66
|
--argjson caps "$caps_json" \
|
|
66
|
-
--argjson meta "$(jq -n --arg cwd "$PWD" '{cwd: $cwd}')" \
|
|
67
|
+
--argjson meta "$(jq -n --arg cwd "$PWD" --arg approval "$approval" '{cwd: $cwd, approvalMode: $approval}')" \
|
|
67
68
|
'{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
|
|
68
69
|
)" 2>/dev/null)
|
|
69
70
|
|
|
@@ -81,9 +82,16 @@ elif [ "$server_version" != "$PLUGIN_VERSION" ]; then
|
|
|
81
82
|
fi
|
|
82
83
|
|
|
83
84
|
# --- Output context (first stdout = notification injected into conversation) ---
|
|
85
|
+
approval_note=""
|
|
86
|
+
case "$approval" in
|
|
87
|
+
guarded) approval_note=" — destructive ops (rm, mv, chmod, kill, force-push) are blocked" ;;
|
|
88
|
+
read-only) approval_note=" — observe/analyze/report only, no file writes or mutation commands" ;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
84
91
|
cat <<CONTEXT
|
|
85
92
|
Agent Relay active. Your agent ID: ${agent_id}
|
|
86
93
|
Relay URL: ${RELAY_URL} | Server: ${server_version:-unknown} | Plugin: ${PLUGIN_VERSION}
|
|
94
|
+
Approval mode: ${approval}${approval_note}
|
|
87
95
|
|
|
88
96
|
To send a message:
|
|
89
97
|
curl -s -X POST ${RELAY_URL}/api/messages${auth_header_example} -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE"}'
|
package/hooks/session-end.sh
CHANGED
|
@@ -12,6 +12,7 @@ instance_state="/tmp/agent-relay-instance-${PPID}.state"
|
|
|
12
12
|
if [ -f "$instance_state" ]; then
|
|
13
13
|
agent_id=$(head -1 "$instance_state" 2>/dev/null)
|
|
14
14
|
if [ -n "$agent_id" ]; then
|
|
15
|
+
printf 'offline\n' > "/tmp/agent-relay-status-${agent_id}.state"
|
|
15
16
|
curl -s -X PATCH "${RELAY_URL}/api/agents/${agent_id}/status" "${auth_header_args[@]}" \
|
|
16
17
|
-H 'Content-Type: application/json' \
|
|
17
18
|
-d '{"status":"offline"}' \
|
package/hooks/set-status.sh
CHANGED
|
@@ -24,6 +24,8 @@ if [ -z "$agent_id" ]; then
|
|
|
24
24
|
exit 0
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
|
+
printf '%s\n' "$status" > "/tmp/agent-relay-status-${agent_id}.state"
|
|
28
|
+
|
|
27
29
|
curl -s -o /dev/null -X PATCH "${RELAY_URL}/api/agents/${agent_id}/status" "${auth_header_args[@]}" \
|
|
28
30
|
-H 'Content-Type: application/json' \
|
|
29
31
|
-d "{\"status\":\"${status}\"}" \
|
package/package.json
CHANGED