agentradar-cli 0.3.5 → 0.3.7

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.
@@ -48,6 +48,7 @@ set -a
48
48
  # shellcheck source=/dev/null
49
49
  . "${ENV_FILE}"
50
50
  set +a
51
+ [ "${2:-}" = "--dry-run" ] && AR_DRY_RUN=1
51
52
 
52
53
  # --- 2. Validate + defaults ---------------------------------------------------
53
54
  : "${AGENT_NAME:?AGENT_NAME required in env}"
@@ -66,6 +67,11 @@ CLAUDE_BIN="${CLAUDE_BIN:-claude}"
66
67
  TMUX_BIN="${TMUX_BIN:-tmux}"
67
68
  CLAUDE_EXTRA_FLAGS="${CLAUDE_EXTRA_FLAGS:-}" # e.g. "--chrome" (Spielberg/Sam); empty (Liza)
68
69
  PRE_LAUNCH_HOOK="${PRE_LAUNCH_HOOK:-}" # optional per-agent script run once before claude
70
+ PRE_LAUNCH_HOOK_REQUIRED="${PRE_LAUNCH_HOOK_REQUIRED:-0}" # 1: a failing or missing hook aborts the launch; the loop retries (e.g. wait for the gateway)
71
+ AR_LAUNCH_ARGS_FILE="${AR_LAUNCH_ARGS_FILE:-}" # extra launch args, one VERBATIM arg per line (`@file:<path>` = that file's whole content as one arg)
72
+ AR_PROMPT_WATCH_SEC="${AR_PROMPT_WATCH_SEC:-180}" # how long to answer startup prompts (dev channels, Chrome intro, resume summary)
73
+ AR_READY_PORTS="${AR_READY_PORTS:-}" # e.g. "8769 8770": none up after the watch = deaf session, kill the pane so it is recreated
74
+ AR_DRY_RUN="${AR_DRY_RUN:-0}" # 1 (or a 2nd argument --dry-run): print the exact argv and env, start nothing
69
75
  BROWSER_APP="${BROWSER_APP:-Google Chrome}" # #150: app to ensure-up for --chrome agents (macOS)
70
76
  BROWSER_READY_MAX_SEC="${BROWSER_READY_MAX_SEC:-20}" # #150: bounded wait for the browser proc on cold boot
71
77
  BROWSER_READY_SETTLE_SEC="${BROWSER_READY_SETTLE_SEC:-3}" # #150: settle for extension/native-host after cold launch
@@ -149,6 +155,9 @@ else
149
155
  # SC2086: CLAUDE_EXTRA_FLAGS is intentionally UNQUOTED so "--chrome" (etc.) word-splits into flags.
150
156
  # shellcheck disable=SC2086
151
157
  _launch_flags=( ${CLAUDE_EXTRA_FLAGS} --strict-mcp-config --mcp-config="${MCP_CONFIG}" )
158
+ # AR_MCP_STRICT=0 (written by `agentradar join`): AgentRadar joins the agent's own MCPs and channels instead of
159
+ # replacing them (Vadym 13444). The default stays strict: hosted and vendor-fleet agents still get only $MCP_CONFIG.
160
+ [ "${AR_MCP_STRICT:-1}" = 0 ] && _launch_flags=( ${CLAUDE_EXTRA_FLAGS} --mcp-config="${MCP_CONFIG}" )
152
161
  # `agentradar join` enables the agent-guard plugin for this session only, through a settings file.
153
162
  [ -n "${CLAUDE_SETTINGS:-}" ] && _launch_flags+=( --settings "${CLAUDE_SETTINGS}" )
154
163
  # R764 + Vadym 10342: the AgentRadar channel (the stdio MCP `agentradar` in $MCP_CONFIG) pushes work in; no feeder types.
@@ -161,6 +170,24 @@ else
161
170
  fi
162
171
  AGENT_PID_PATTERN="${AGENT_PID_PATTERN:-${_agent_pid_pattern_default}}"
163
172
 
173
+ # The launcher for a session that already has its own launch command (the secretary: model pin, plugin dirs, channels,
174
+ # --append-system-prompt): its flags go here verbatim, after ours. CLAUDE_EXTRA_FLAGS word-splits and cannot carry them.
175
+ AR_ARGS_CHROME=""
176
+ if [ -n "${AR_LAUNCH_ARGS_FILE}" ]; then
177
+ [ -f "${AR_LAUNCH_ARGS_FILE}" ] || die "AR_LAUNCH_ARGS_FILE not found: ${AR_LAUNCH_ARGS_FILE}"
178
+ while IFS= read -r _a || [ -n "${_a}" ]; do
179
+ case "${_a}" in
180
+ ''|'#'*) continue ;;
181
+ '@file:'*) [ -f "${_a#@file:}" ] || die "AR_LAUNCH_ARGS_FILE names a missing file: ${_a#@file:}"; _a="$(cat "${_a#@file:}")" ;;
182
+ esac
183
+ [ "${_a}" = "--chrome" ] && AR_ARGS_CHROME="--chrome"
184
+ _launch_flags+=( "${_a}" )
185
+ done < "${AR_LAUNCH_ARGS_FILE}"
186
+ fi
187
+ if [ "${PRE_LAUNCH_HOOK_REQUIRED}" = 1 ] && [ ! -x "${PRE_LAUNCH_HOOK}" ]; then
188
+ die "PRE_LAUNCH_HOOK_REQUIRED=1 but PRE_LAUNCH_HOOK is not an executable file: '${PRE_LAUNCH_HOOK}'"
189
+ fi
190
+
164
191
  # Optional per-agent TOOL credentials — a vendor brain's OWN MCP-tool API keys (e.g. an SEO agent's
165
192
  # WEBFLOW_TOKEN / PERPLEXITY_API_KEY). These are ADDED to the scrubbed launch env so the agent's own
166
193
  # tool servers (declared in its --mcp-config) get their creds, WITHOUT widening the daemon's
@@ -228,6 +255,24 @@ command -v "${AGENT_BIN}" >/dev/null 2>&1 || die "${AR_AGENT_HARNESS} CLI not fo
228
255
  [ -f "${PANE_LIVE_SH}" ] || die "pane liveness check not found: ${PANE_LIVE_SH}"
229
256
  [ -d "${AR_BASE}" ] || die "AR_BASE dir not found: ${AR_BASE}"
230
257
 
258
+ # --- 2a-bis. Dry run: the exact launch, printed, nothing started or written. Secret values show only their length.
259
+ # Same argv/env as the launch below: `env -i` allowlist + claude cfg + tool-env entries, then the resume args and flags.
260
+ if [ "${AR_DRY_RUN}" = 1 ]; then
261
+ # redact by (case-insensitive) name or by value shape: a secret in a name we did not foresee still shows only its length
262
+ _show() { local _n; _n=$(printf %s "$1" | tr a-z A-Z); case "${_n}" in *TOKEN*|*KEY*|*SECRET*|*BEARER*|*PASS*|*AUTH*|*COOKIE*|*CRED*|PAT|*_PAT|PAT_*|*_PAT_*) printf 'ENV %s=<redacted:%s chars>\n' "$1" "${#2}"; return ;; esac
263
+ case "$2" in *://*:*@*|*[Bb]earer\ *) printf 'ENV %s=<redacted:%s chars>\n' "$1" "${#2}" ;; *) printf 'ENV %s=%s\n' "$1" "$2" ;; esac; }
264
+ _showarg() { case "$1" in *[Bb]earer*|*://*:*@*|*[Tt]oken=*|*[Ss]ecret=*|*[Kk]ey=*) printf 'ARGV <redacted:%s chars>\n' "${#1}" ;; *) printf 'ARGV %q\n' "$1" ;; esac; }
265
+ printf 'DRY_RUN session=%s cwd=%s harness=%s feed=%s resume=%s fresh=%s\n' "${TMUX_SESSION}" "${AR_BASE}" "${AR_AGENT_HARNESS}" "${AR_FEED}" "${AR_RESUME_FLAGS}" "${AR_FRESH_FLAGS}"
266
+ for _k in HOME PATH USER LOGNAME SHELL LANG LC_ALL TMPDIR NODE_OPTIONS CODEX_HOME; do _show "${_k}" "${!_k:-}"; done
267
+ _show TERM "${AR_AGENT_TERM:-tmux-256color}"; _show COLORTERM "${AR_AGENT_COLORTERM:-truecolor}"; _show LC_CTYPE "${AR_AGENT_LC_CTYPE:-en_US.UTF-8}"
268
+ _show DISABLE_AUTOUPDATER 1; _show CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF 1
269
+ for _kv in ${_claude_cfg[@]+"${_claude_cfg[@]}"} ${TOOL_ENV_ARGS[@]+"${TOOL_ENV_ARGS[@]}"}; do _show "${_kv%%=*}" "${_kv#*=}"; done
270
+ printf 'ARGV %q\n' "${AGENT_BIN}"
271
+ # shellcheck disable=SC2086 # the resume args word-split in the pane wrapper too
272
+ for _a in ${AR_RESUME_FLAGS} ${_launch_flags[@]+"${_launch_flags[@]}"}; do _showarg "${_a}"; done
273
+ exit 0
274
+ fi
275
+
231
276
  # --- 2b. Credential hygiene (#152): secrets to 0600 files, OUT of argv+env ------
232
277
  # The AWP bearer must not linger in any long-lived process's ARGV (`ps aux`) or ENVIRONMENT
233
278
  # (`ps eww`), where a co-resident process on the box could read it. Stage it into a per-agent 0600
@@ -253,7 +298,7 @@ if [ "${#TOOL_ENV_ARGS[@]}" -gt 0 ]; then
253
298
  TOOL_ENV_ARGS=() # #166: no longer passed inline on the env -i line
254
299
  fi
255
300
 
256
- session_exists() { "${TMUX_BIN}" has-session -t "${TMUX_SESSION}" 2>/dev/null; }
301
+ session_exists() { "${TMUX_BIN}" has-session -t "=${TMUX_SESSION}" 2>/dev/null; }
257
302
  pane_alive() {
258
303
  TMUX_BIN="${TMUX_BIN}" TMUX_SESSION="${TMUX_SESSION}" AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" \
259
304
  /bin/sh "${PANE_LIVE_SH}"
@@ -266,7 +311,7 @@ pane_alive() {
266
311
  # Overridable via BROWSER_APP (default "Google Chrome"). macOS-only; documented no-op elsewhere (on Linux,
267
312
  # launch the browser in your runner env / at login). Belt-and-suspenders alongside a Login-Items entry.
268
313
  ensure_browser() {
269
- case " ${CLAUDE_EXTRA_FLAGS} " in
314
+ case " ${CLAUDE_EXTRA_FLAGS} ${AR_ARGS_CHROME} " in
270
315
  *" --chrome "*) : ;; # --chrome as a WHOLE space-bounded token (won't false-match e.g. --chromeless)
271
316
  *) return 0 ;; # not a --chrome agent → nothing to do
272
317
  esac
@@ -302,7 +347,7 @@ ensure_browser() {
302
347
  # True when one identical `API Error: 400` line (digits normalised: the message index moves) fills the pane
303
348
  # scrollback AR_API400_LOOP_MAX times. ponytail: only the last 200 lines are read, so a very slow loop could slip by.
304
349
  api_error_loop() {
305
- "${TMUX_BIN}" capture-pane -p -J -t "${TMUX_SESSION}" -S -200 2>/dev/null \
350
+ "${TMUX_BIN}" capture-pane -p -J -t "=${TMUX_SESSION}:" -S -200 2>/dev/null \
306
351
  | grep -o 'API Error: 400 .*' | sed -E 's/[0-9]+/N/g' | sort | uniq -c | sort -rn \
307
352
  | awk -v m="${AR_API400_LOOP_MAX}" 'NR==1{ok=($1>=m)} END{exit !ok}'
308
353
  }
@@ -321,7 +366,7 @@ ensure_agent() {
321
366
  fi
322
367
  if session_exists && ! pane_alive; then
323
368
  log "session '${TMUX_SESSION}' exists but claude pane is dead — recreating"
324
- "${TMUX_BIN}" kill-session -t "${TMUX_SESSION}" 2>/dev/null || true
369
+ "${TMUX_BIN}" kill-session -t "=${TMUX_SESSION}" 2>/dev/null || true
325
370
  fi
326
371
  [ -n "${AR_GUARD_MARKER}" ] && rm -f "${AR_GUARD_MARKER}" # this launch must prove the guard afresh
327
372
  # Retire-legacy step 1 (Vadym 11054): Codex starts only with AgentRadar's guard. Its SessionStart hook does not
@@ -347,7 +392,10 @@ ensure_agent() {
347
392
  ensure_browser # #150: bring up the browser first for --chrome agents (idempotent, no-op otherwise)
348
393
  if [ -n "${PRE_LAUNCH_HOOK}" ] && [ -x "${PRE_LAUNCH_HOOK}" ]; then
349
394
  log "running pre-launch hook: ${PRE_LAUNCH_HOOK}"
350
- "${PRE_LAUNCH_HOOK}" || log "WARN pre-launch hook exited non-zero (continuing)"
395
+ if ! "${PRE_LAUNCH_HOOK}"; then
396
+ [ "${PRE_LAUNCH_HOOK_REQUIRED}" = 1 ] && { log "pre-launch hook failed and is required — not launching"; return 1; }
397
+ log "WARN pre-launch hook exited non-zero (continuing)"
398
+ fi
351
399
  fi
352
400
  # SCRUBBED launch: env -i with a strict allowlist so the agent inherits nothing from this daemon
353
401
  # (AWP_BEARER, AWP_MCP_URL, etc. never reach the pane). CONNECTOR ISOLATION is enforced by
@@ -428,19 +476,37 @@ ensure_agent() {
428
476
  return 1
429
477
  }
430
478
 
431
- # A pushed session starts on Claude Code's "Loading development channels" warning, and nobody sits at an unattended
432
- # pane to confirm it. The channel is this machine's own installed CLI (`agentradar channel <runner>`), so the
433
- # service confirms it, once per launch, only while that exact warning is on screen.
479
+ # A pushed session starts on Claude Code's "Loading development channels" warning ("I am using this for local
480
+ # development" / "Exit"), once per development channel, and nobody sits at an unattended pane to confirm it. A launcher
481
+ # that carries its own launch args (AR_LAUNCH_ARGS_FILE) names those channels itself, so it answers every one of them,
482
+ # however late, plus the Chrome intro and the "Resume from summary" modal, for AR_PROMPT_WATCH_SEC (same strings and
483
+ # keys as the secretary's watchdog). Then, if AR_READY_PORTS names channel ports and none is listening, the session is
484
+ # deaf: kill the pane so the monitor loop recreates it.
434
485
  accept_dev_channels() {
435
- [ "${AR_FEED}" = "push" ] || return 0
436
- for _ in $(seq 1 30); do
437
- if "${TMUX_BIN}" capture-pane -p -t "=${TMUX_SESSION}:" 2>/dev/null | grep -q "Loading development channels"; then
438
- "${TMUX_BIN}" send-keys -t "=${TMUX_SESSION}:" Enter
439
- log "confirmed Claude Code's development-channels warning for the AgentRadar channel"
440
- return 0
486
+ { [ "${AR_FEED}" = "push" ] || [ -n "${AR_LAUNCH_ARGS_FILE}" ]; } || return 0
487
+ local _pane _now _t0 _last=0 _chrome=0 _resume=0 _p _up=0 _presses=0
488
+ _t0=$(date +%s)
489
+ while _now=$(date +%s); [ $(( _now - _t0 )) -lt "${AR_PROMPT_WATCH_SEC}" ]; do
490
+ _pane="$("${TMUX_BIN}" capture-pane -p -t "=${TMUX_SESSION}:" 2>/dev/null || true)"
491
+ if [ "${_presses}" -lt 6 ] && [ $(( _now - _last )) -ge 2 ] && { grep -q "I am using this for local development" <<< "${_pane}" || grep -q "Loading development channels" <<< "${_pane}"; }; then
492
+ "${TMUX_BIN}" send-keys -t "=${TMUX_SESSION}:" 1 Enter
493
+ _last=${_now}; _presses=$(( _presses + 1 )) # cap: text the agent itself prints must not type into its conversation forever
494
+ log "confirmed Claude Code's development-channels warning for a launch channel"
495
+ fi
496
+ if [ "${_chrome}" = 0 ] && grep -q "Claude in Chrome (Beta)" <<< "${_pane}"; then
497
+ "${TMUX_BIN}" send-keys -t "=${TMUX_SESSION}:" Enter; _chrome=1; log "dismissed the Claude-in-Chrome intro"
498
+ fi
499
+ if [ "${_resume}" = 0 ] && grep -q "Resume from summary" <<< "${_pane}"; then
500
+ "${TMUX_BIN}" send-keys -t "=${TMUX_SESSION}:" Enter; _resume=1; log "took the default of the resume-from-summary modal"
441
501
  fi
442
502
  sleep 1
443
503
  done
504
+ [ -n "${AR_READY_PORTS}" ] || return 0
505
+ for _p in ${AR_READY_PORTS}; do (exec 3<>"/dev/tcp/127.0.0.1/${_p}") 2>/dev/null && _up=1; done
506
+ if [ "${_up}" = 0 ]; then
507
+ log "WARNING: no channel port of '${AR_READY_PORTS}' is up after ${AR_PROMPT_WATCH_SEC}s: killing the pane so it is recreated"
508
+ "${TMUX_BIN}" kill-session -t "=${TMUX_SESSION}" 2>/dev/null || true # "=": exact match, never a prefix (join-req-1 killed join-req-12)
509
+ fi
444
510
  }
445
511
 
446
512
  # --- 4. Run: ensure agent, run feeder in foreground, monitor both -------------
@@ -525,7 +591,7 @@ while true; do
525
591
  elif [ "${AR_ATTACH:-}" != "1" ] && [ $(( $(date +%s) - ${_LAST_HEAL:-0} )) -ge "${AR_API400_COOLDOWN_SEC}" ] && api_error_loop; then
526
592
  log "agent pane is looping on the same API 400 — replacing it with a FRESH session"
527
593
  _LAST_HEAL=$(date +%s); _FORCE_FRESH=1; _heal=1
528
- "${TMUX_BIN}" kill-session -t "${TMUX_SESSION}" 2>/dev/null || true
594
+ "${TMUX_BIN}" kill-session -t "=${TMUX_SESSION}" 2>/dev/null || true
529
595
  fi
530
596
  if [ "${_heal}" = 1 ]; then
531
597
  if ensure_agent; then
@@ -351,6 +351,11 @@ class TerminalBridge {
351
351
  case "detach":
352
352
  this.stopPipe();
353
353
  break;
354
+ case "clear":
355
+ // An engagement ended or changed: drop the scrollback so the next client's snapshot (capture-pane -S)
356
+ // cannot replay the previous one's. The visible screen of the live session is not touched.
357
+ tmux(["clear-history", "-t", TMUX_SESSION]);
358
+ break;
354
359
  case "input":
355
360
  if (typeof obj.data === "string") tmux(["send-keys", "-t", TMUX_SESSION, "-l", obj.data]);
356
361
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentradar-cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -19,9 +19,9 @@
19
19
  "dist/sync.js",
20
20
  "dist/vault-import.js",
21
21
  "dist/update.js",
22
- "dist/commands.js",
23
22
  "dist/vault-clone.js",
24
23
  "dist/secrets.js",
24
+ "dist/admin.js",
25
25
  "dist/vendor-runner/**",
26
26
  "dist/agentradar-vendor/SKILL.md",
27
27
  "dist/agent-guard",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "scripts": {
35
- "build": "tsc && mkdir -p dist/agentradar-vendor dist/vendor-runner/supervisor && cp ../../skills/agentradar-vendor/SKILL.md dist/agentradar-vendor/SKILL.md && cp ../../templates/vendor-runner/feeder.mjs ../../templates/vendor-runner/pane-live.sh ../../templates/vendor-runner/session-frame.mjs ../../templates/vendor-runner/terminal-bridge.mjs dist/vendor-runner/ && cp ../../templates/vendor-runner/supervisor/supervise-agent.sh ../../templates/vendor-runner/supervisor/install-agent.sh ../../templates/vendor-runner/supervisor/com.agentradar.agent.plist.template ../../templates/vendor-runner/supervisor/agentradar-agent@.service.template dist/vendor-runner/supervisor/ && rm -rf dist/agent-guard && cp -R ../../templates/agent-guard dist/agent-guard && rm dist/agent-guard/scripts/*.test.mjs && chmod +x dist/index.js dist/vendor-runner/pane-live.sh dist/vendor-runner/supervisor/supervise-agent.sh dist/vendor-runner/supervisor/install-agent.sh",
35
+ "build": "tsc && mkdir -p dist/agentradar-vendor dist/vendor-runner/supervisor && cp ../../skills/agentradar-vendor/SKILL.md dist/agentradar-vendor/SKILL.md && cp ../../templates/vendor-runner/feeder.mjs ../../templates/vendor-runner/pane-live.sh ../../templates/vendor-runner/session-frame.mjs ../../templates/vendor-runner/skill-zip.mjs ../../templates/vendor-runner/terminal-bridge.mjs dist/vendor-runner/ && cp ../../templates/vendor-runner/supervisor/supervise-agent.sh ../../templates/vendor-runner/supervisor/install-agent.sh ../../templates/vendor-runner/supervisor/com.agentradar.agent.plist.template ../../templates/vendor-runner/supervisor/agentradar-agent@.service.template dist/vendor-runner/supervisor/ && rm -rf dist/agent-guard && cp -R ../../templates/agent-guard dist/agent-guard && rm dist/agent-guard/scripts/*.test.mjs && chmod +x dist/index.js dist/vendor-runner/pane-live.sh dist/vendor-runner/supervisor/supervise-agent.sh dist/vendor-runner/supervisor/install-agent.sh",
36
36
  "clean": "rm -rf dist",
37
37
  "prepack": "pnpm run build",
38
38
  "typecheck": "tsc --noEmit",
package/dist/commands.js DELETED
@@ -1,74 +0,0 @@
1
- /**
2
- * Vadym 9970 (CLI-FIRST): every agent-facing MCP tool is also a CLI command, `agentradar run <runner-id> <noun> <verb>`,
3
- * in AgentRadar terms. The tree is derived from the server's own tools/list at call time, so it cannot fall behind
4
- * the MCP; this file only says how a tool name reads as a noun and a verb.
5
- */
6
- /** Nouns a tool name is grouped under, most specific first. A tool takes the first one its name contains. */
7
- const NOUNS = [
8
- "inbox", "thread", "message", "task", "metric", "quota", "vault", "memory", "skill", "escalation", "incident",
9
- "karma", "wallet", "spend", "budget", "usage", "browser", "email", "telegram", "slack", "discord", "whatsapp",
10
- "github", "model", "outcome", "role", "org_chart", "org", "project", "integration", "channel", "peer",
11
- "subordinate", "secret", "service", "onboarding", "intervention", "marketplace", "hire", "egress", "feedback",
12
- "contact", "placement", "mcp", "tool", "join_link", "handoff", "selftest", "readiness", "pane", "brief",
13
- "teammate", "approval", "agent",
14
- ];
15
- const PLURAL = {
16
- messages: "message", tasks: "task", metrics: "metric", vaults: "vault", skills: "skill", escalations: "escalation",
17
- threads: "thread", channels: "channel", peers: "peer", subordinates: "subordinate", integrations: "integration",
18
- secrets: "secret", agents: "agent", interventions: "intervention", events: "event", retrospectives: "retrospective",
19
- };
20
- /** Where the rule above would put a tool somewhere an agent would not look for it. */
21
- const OVERRIDES = {
22
- next_event: ["inbox", "next"],
23
- mark_messages_read: ["inbox", "mark-read"],
24
- search_history: ["thread", "history"],
25
- reply: ["thread", "reply"],
26
- read_history: ["thread", "read"],
27
- mark_read: ["thread", "mark-read"],
28
- expect_reply: ["thread", "expect-reply"],
29
- clear_reply_allowance: ["thread", "clear-reply-allowance"],
30
- request_reply_clearance: ["thread", "request-reply-clearance"],
31
- github_thread: ["github", "thread"],
32
- post_escalation_discussion_message: ["escalation", "post-message"],
33
- read_escalation_discussion: ["escalation", "read-discussion"],
34
- get_self: ["agent", "self"],
35
- get_my_context: ["agent", "context"],
36
- liveness_ack: ["agent", "liveness-ack"],
37
- remember: ["memory", "remember"],
38
- recall: ["memory", "recall"],
39
- my_skills: ["skill", "mine"],
40
- list_my_threads: ["thread", "list"],
41
- list_my_metrics: ["metric", "list"],
42
- read_my_usage: ["usage", "read"],
43
- // #496 (APPROVAL-ASK): the comment thread on an approval, and the decision it acknowledges.
44
- approval_comments: ["approval", "comments"],
45
- approval_comment: ["approval", "comment"],
46
- acknowledge_decision: ["approval", "acknowledge"],
47
- };
48
- /** One-release aliases: still on the MCP, never given a command of their own. */
49
- export const DEPRECATED_TOOLS = new Set(["acknowledge_messages"]);
50
- /** `<noun> <verb>` for a tool name. */
51
- export function commandFor(tool) {
52
- const fixed = OVERRIDES[tool];
53
- if (fixed)
54
- return fixed;
55
- // Plurals fold only to FIND the noun (list_tasks is a task command); the verb keeps the tool's own words, so
56
- // slack_list_channels reads "list-channels" as slack_list_users reads "list-users".
57
- const raw = tool.split("_");
58
- const words = raw.map((word) => PLURAL[word] ?? word);
59
- for (const noun of NOUNS) {
60
- const parts = noun.split("_");
61
- for (let i = 0; i + parts.length <= words.length; i++) {
62
- if (parts.every((part, k) => words[i + k] === part)) {
63
- const rest = [...raw.slice(0, i), ...raw.slice(i + parts.length)];
64
- return [noun.replace(/_/g, "-"), rest.join("-") || "get"];
65
- }
66
- }
67
- }
68
- return ["agent", raw.join("-")];
69
- }
70
- /** The tool a `<noun> <verb>` names among the server's tools, or undefined. */
71
- export function toolFor(noun, verb, tools) {
72
- return tools.find((tool) => !DEPRECATED_TOOLS.has(tool) && commandFor(tool).join(" ") === `${noun} ${verb}`);
73
- }
74
- //# sourceMappingURL=commands.js.map