@phnx-labs/agents-cli 1.20.60 → 1.20.62
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/CHANGELOG.md +18 -2
- package/README.md +1 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +10 -1
- package/dist/commands/routines.js +2 -0
- package/dist/lib/agents.js +129 -11
- package/dist/lib/commands.js +29 -0
- package/dist/lib/convert.d.ts +11 -0
- package/dist/lib/convert.js +22 -0
- package/dist/lib/doctor-diff.js +17 -1
- package/dist/lib/goose-commands.d.ts +41 -0
- package/dist/lib/goose-commands.js +176 -0
- package/dist/lib/hooks.js +134 -0
- package/dist/lib/permissions.d.ts +15 -0
- package/dist/lib/permissions.js +99 -0
- package/dist/lib/plugins.d.ts +20 -0
- package/dist/lib/plugins.js +118 -0
- package/dist/lib/resources/permissions.js +2 -0
- package/dist/lib/routines.d.ts +7 -0
- package/dist/lib/routines.js +18 -0
- package/dist/lib/runner.d.ts +7 -0
- package/dist/lib/runner.js +34 -6
- package/dist/lib/session/active.d.ts +8 -1
- package/dist/lib/session/active.js +1 -0
- package/dist/lib/session/parse.js +12 -0
- package/dist/lib/session/state.d.ts +33 -0
- package/dist/lib/session/state.js +40 -0
- package/dist/lib/staleness/detectors/permissions.js +23 -0
- package/dist/lib/staleness/detectors/subagents.js +36 -0
- package/dist/lib/staleness/detectors/workflows.js +29 -0
- package/dist/lib/staleness/writers/commands.js +7 -0
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/staleness/writers/subagents.js +22 -3
- package/dist/lib/subagents.d.ts +32 -0
- package/dist/lib/subagents.js +238 -0
- package/dist/lib/tmux/session.d.ts +13 -7
- package/dist/lib/tmux/session.js +23 -8
- package/dist/lib/versions.js +72 -0
- package/dist/lib/workflows.d.ts +9 -0
- package/dist/lib/workflows.js +84 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,16 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 1.20.
|
|
5
|
+
## 1.20.62
|
|
6
|
+
|
|
7
|
+
- **Wire Goose commands support (RUSH-1572).** `agents` now syncs slash commands to Goose as recipe YAML files under `~/.config/goose/commands/<name>.yaml`, each registered in `~/.config/goose/config.yaml` under a `slash_commands: [{ command, recipe_path }]` array (Goose has no native slash-command file format — a slash command IS a recipe). The command recipes live in a dir distinct from the workflow recipes dir (`~/.config/goose/recipes/`) so the workflow detector never treats a command recipe as a workflow. Registration is a read-modify-write that preserves every other `config.yaml` key (`mcp_servers`, `extensions`, …) and other `slash_commands` entries, and removal soft-deletes the recipe + unregisters the entry. Flip Goose's `commands` capability and add `goose` branches to install/list/match/remove, the staleness commands writer, and doctor-diff, backed by a new `goose-commands.ts` module + a `markdownToGooseRecipe` converter. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/goose-commands.ts`, `apps/cli/src/lib/commands.ts`, `apps/cli/src/lib/convert.ts`, `apps/cli/src/lib/staleness/writers/commands.ts`, `apps/cli/src/lib/doctor-diff.ts`.
|
|
8
|
+
- **Fix: Hermes plugin sync no longer disables a plugin the user explicitly enabled.** The plugin install path (RUSH-1688) unconditionally forced `plugins.enabled` to the exec-surface trust verdict on every sync. An ordinary un-flagged background re-sync computes `enable=false` for a plugin with hooks/tools, so it stripped that plugin from the `~/.hermes/config.yaml` allowlist — clobbering a plugin the user deliberately enabled with `--allow-exec-surfaces`. The install path now enables only when trusted and never down-toggles (matching the marketplace flow's add-if-trusted semantics); removal still unregisters explicitly. Source: `apps/cli/src/lib/plugins.ts`.
|
|
9
|
+
- **Wire Goose subagents support (RUSH-1573).** `agents` now syncs subagents to Goose as recipe YAML files under `~/.config/goose/agents/<name>.yaml` — Goose has no dedicated subagent format, so a named subagent IS a recipe (goose auto-discovers `~/.config/goose/agents/` and delegates to them by name in autonomous mode). `transformSubagentForGoose` emits the same recipe schema agents-cli already uses for Goose workflow recipes (`version`/`title`/`description`/`instructions`/`prompt`, plus optional `settings.goose_model`). Flip Goose's `subagents` capability and wire the install/remove/list/orphan/version-remove branches plus the staleness subagents writer and detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
10
|
+
|
|
11
|
+
- **Wire ForgeCode commands and subagents support (RUSH-1689, RUSH-1690).** `agents` now syncs slash commands to ForgeCode as Markdown files under `~/.forge/commands/<name>.md` (previously ForgeCode had `commands: false` and received commands only as skills), and named subagents as Markdown-with-frontmatter definitions under `~/.forge/agents/<name>.md` (same `color`-less shape as Droid/Copilot/Cursor, so `transformSubagentForForge` aliases `transformSubagentForDroid`). Flip ForgeCode's `commands`/`subagents` capabilities, set `commandsDir`, add the subagent transform plus install/remove/list/orphan/version-remove branches, and register the subagents writer + detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
12
|
+
- **Wire allowlist (permissions) support for OpenClaw (RUSH-1570).** OpenClaw gates at TOOL granularity only, so permission sync maps just **blanket** (whole-tool) rules into `~/.openclaw/openclaw.json` `tools.alsoAllow` (allow) / `tools.deny` (deny): `bash → exec`, `read → read`, `write`/`edit → write`, `webfetch → web_fetch`, `websearch → web_search`. Sub-command/path/domain rules (`Bash(git:*)`, `Write(secrets/**)`, `WebFetch(domain:x)`) have no tool-level equivalent and are skipped — coarse-mapping a specific deny to a whole tool would wrongly gate every use of that tool. The absolute `tools.allow` list is never touched, and all other keys (`mcp`, `exec`, `agents`, …) are preserved on read-modify-write. Flip OpenClaw's `allowlist: true`, add `convertToOpenClawFormat` + the `openclaw` branch in `applyPermissionsToVersion`, register the config path and staleness detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/resources/permissions.ts`, `apps/cli/src/lib/staleness/detectors/permissions.ts`.
|
|
13
|
+
- **Wire subagents support for Cursor CLI (RUSH-1388).** cursor-agent loads custom subagents as Markdown with YAML frontmatter under `~/.cursor/agents/*.md` (project-scoped `.cursor/agents/` also supported natively), same shape as Claude/Droid/Copilot minus the `color` field, gated at `>= 2026.1.22` (cursor-agent's CalVer build tag for Cursor 2.4). Flip Cursor's `subagents`, add `transformSubagentForCursor` (alias of `transformSubagentForDroid`), and wire the install/remove, list, orphan-detection, writer, and detector paths. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
14
|
+
- **Wire hooks support for Hermes (RUSH-1687).** `agents` now registers central hooks into Hermes Agent's `~/.hermes/config.yaml` under a `hooks:` block (YAML, ≥ 0.11.0). The registrar read-modify-writes that shared config so sibling keys like `mcp_servers` survive, maps canonical events to Hermes' snake_case lifecycle names (`SessionStart→on_session_start`, `SessionEnd→on_session_end`, `PreToolUse→pre_tool_call`, `PostToolUse→post_tool_call`, `SubagentStop→subagent_stop`, `UserPromptSubmit→pre_llm_call`, `Stop→on_session_finalize`), and clamps each hook's timeout to 300s (default 60s). Managed entries are re-synced idempotently while user-authored hooks are preserved. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/hooks.ts`, `apps/cli/src/lib/staleness/writers/hooks.ts`.
|
|
6
15
|
|
|
16
|
+
## 1.20.61
|
|
17
|
+
|
|
18
|
+
- **Detect OpenCode sign-in state so `agents view` stops mislabeling a logged-in install as "not signed in."** `getAccountInfo` had no `opencode` case, so it fell through to `signedIn: false` and every row printed "(not signed in — run opencode to log in)" even with a live login. It now reads OpenCode's `auth.json` (`$XDG_DATA_HOME/opencode/auth.json`, defaulting to `~/.local/share/opencode/auth.json` on every platform — `xdg-basedir` does not special-case macOS), validates each provider entry against its `oauth`/`api`/`wellknown` credential shape, and reports the account as signed in with the non-secret provider ids surfaced as the account label (e.g. `id:muse-spark`). Credential secrets (`access`/`refresh`/`key`/`token`) are only inspected for presence — never read into any display or JSON output. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/agents.test.ts`.
|
|
19
|
+
- **Wire Antigravity workflows support (RUSH-1580).** `agents` now syncs workflows to Antigravity as markdown files with the required `description` frontmatter plus an `agents_workflow` ownership marker, invocable as `/<name>` slash commands. Antigravity workflows are the one non-version-isolated target: `agy` scans a single shared, HOME-global `~/.gemini/config/global_workflows/` at startup (a real home directory, never symlinked per version — verified via strace of `agy`), so the writer and detector both resolve that shared dir for every installed version instead of a per-version home. Gated at `>= 1.0.6`. The ownership marker prevents overwriting or removing user-authored workflows of the same name. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/staleness/detectors/workflows.ts`.
|
|
20
|
+
|
|
21
|
+
## 1.20.60
|
|
7
22
|
- **Fix Goose skill sync status for its native central-storage path.** `agents skills list goose@<version>` now reports skills under `~/.agents/skills/` as installed instead of falsely requiring a per-version `.config/goose/skills/` copy that Goose never reads. Source: `apps/cli/src/lib/skills.ts`.
|
|
8
23
|
- **Correct the documented `auto` and ACP `skip` semantics.** The README and bundled `run` skill now distinguish Kimi's interactive `--auto` from its already-auto-approved headless `-p` path, document Droid's native `--auto high`, and explain that ACP `skip` prefers `allow_always` but falls back to the first permission option offered by the server. Documentation only; runtime behavior is unchanged. Source: `README.md`, `skills/run/SKILL.md`.
|
|
9
|
-
- **Wire Antigravity subagents and Kimi workflow sync (RUSH-1548, RUSH-1581).** Antigravity now receives subagents as custom-agent Markdown under `~/.gemini/config/agents/<name>/agent.md` with the `>= 1.0.16` version gate enforced during sync. Kimi receives workflows as managed `type: flow` skills under `.kimi-code/skills/<name>/SKILL.md`, using the canonical slug as the flow name and an `agents_workflow` marker so native user-owned flows are not overwritten or removed. Antigravity workflows
|
|
24
|
+
- **Wire Antigravity subagents and Kimi workflow sync (RUSH-1548, RUSH-1581).** Antigravity now receives subagents as custom-agent Markdown under `~/.gemini/config/agents/<name>/agent.md` with the `>= 1.0.16` version gate enforced during sync. Kimi receives workflows as managed `type: flow` skills under `.kimi-code/skills/<name>/SKILL.md`, using the canonical slug as the flow name and an `agents_workflow` marker so native user-owned flows are not overwritten or removed. Antigravity workflows are wired separately in RUSH-1580 (they target a shared HOME-global dir, not a version home). Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/workflows.ts`.
|
|
10
25
|
- **Release retries rebuild the exact merged, CI-tested release tree even after `main` advances.** A registry/auth failure after the release PR merged previously stranded that version because the catch-up guard required the release merge to remain current `main`. The local release script now validates the original PR head and full green matrix, verifies the SHA-pinned keychain helper, rebuilds the unpinned menu-bar helper from historical source in a detached temporary worktree, rejects mismatched remote tags, and tags/publishes that exact merge without including later commits. Source: `apps/cli/scripts/release.sh`.
|
|
11
26
|
- **Wire Gemini plugins/subagents and Goose workflows/allowlists.** Gemini now syncs plugin bundles as Gemini extensions (`.gemini/extensions/<name>/gemini-extension.json`) from CLI 0.8.0+ and subagents as `.gemini/agents/*.md` from CLI 0.36.0+. Goose now syncs workflows as recipe/subrecipe YAML under `.config/goose/recipes/` and permission groups into `.config/goose/permission.yaml`. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/plugins.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/permissions.ts`. (RUSH-1568, RUSH-1569, RUSH-1574, RUSH-1582)
|
|
12
27
|
- **Wire Gemini permissions/allowlist support (RUSH-1567).** Gemini permission groups now sync Bash allow/deny rules into `.gemini/settings.json` as `tools.core` / `tools.exclude` entries with per-command `ShellTool(...)` patterns; non-Bash canonical permissions remain unsupported by Gemini's native tool grammar and are skipped. Flip `allowlist: true`, register the permission writer/detector through the capability table, and replace the dormant legacy `tools.allowed` serializer. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/resources/permissions.ts`, `apps/cli/src/lib/staleness/detectors/permissions.ts`.
|
|
13
28
|
- **Fix: `agents run <agent>@<version> --host <host>` now forwards the version pin and most run flags to the remote host.** Previously the `--host` branch stripped `@version` and ignored `--strategy`, `--effort`, `--add-dir`, `--json`, `--verbose`, `--timeout`, `--yes`, and `--acp`, so the remote host applied its own defaults. The local CLI now parses `agent@version` verbatim, normalizes `--strategy`/`--balanced`, makes `--add-dir` paths remote-portable, and forwards all of these flags to the remote `agents run` invocation. `--add-dir` portability uses the same `~`/`$HOME` re-rooting that `--cwd` already uses, so a Linux remote resolves home paths against its own `/home/<user>`. Source: `apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/src/lib/hosts/dispatch.test.ts`.
|
|
14
29
|
- **Fix: routine edits no longer rewrite the whole YAML file, so `~/.agents` stays clean and `agents repo pull` can sync routines across the fleet.** `writeJob` previously re-emitted the entire document via `yaml.stringify` on every mutation (pause/resume, `routines devices --set`, add), restyling untouched scalars — unquoting `schedule`, re-wrapping the folded `prompt` block — which left the git-backed user repo perpetually dirty. That made cross-device `agents repo pull` refuse ("uncommitted changes"), so a `devices:` pin set on one machine never reached the others and `Devices: all` routines kept firing on every box. A new `serializeJob` edits only the changed keys via the YAML Document API, preserving byte-for-byte formatting of untouched nodes; new/unparseable/non-mapping files fall back to canonical stringify. Source: `apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/__tests__/routines.serialize.test.ts`.
|
|
30
|
+
- **`agents sessions --active --json` now carries live plan progress (RUSH-1380).** The state engine parses the latest `TodoWrite` off the transcript tail into `ActiveSession.todos` (`{ items: [{ content, status, activeForm? }], done, total, activeForm }`), and the live preview verb reads `Plan N/M: <current step>` instead of a bare "TodoWrite". This lets consumers (the Factory Floor) show an N/M pill + checklist for every session — including remote / device-dispatched agents that have no local tool-call stream. Source: `apps/cli/src/lib/session/state.ts` (`extractTodoProgress`, `inferActivity`), `apps/cli/src/lib/session/active.ts` (`ActiveSession.todos`, `applyState`), `apps/cli/src/lib/session/parse.ts` (`summarizeToolUse`).
|
|
15
31
|
|
|
16
32
|
## 1.20.59
|
|
17
33
|
|
package/README.md
CHANGED
|
@@ -855,7 +855,7 @@ Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/age
|
|
|
855
855
|
| Grok Build | yes | yes | yes | yes | yes | skills ($name) | yes | no | `AGENTS.md` | no |
|
|
856
856
|
| OpenClaw | yes | yes | yes | no | yes | gateway | yes | yes | `workspace/AGENTS.md` | no |
|
|
857
857
|
| Cursor | yes | no | yes | no | yes | yes | no | no | `.cursorrules` | no |
|
|
858
|
-
| OpenCode | yes | no | yes |
|
|
858
|
+
| OpenCode | yes | no | yes | >= 1.1.1 | yes | yes | no | no | `AGENTS.md` | no |
|
|
859
859
|
| Copilot | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
|
|
860
860
|
| Amp | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
|
|
861
861
|
| Kiro | yes | no | yes | >= 2.8.0 | yes | yes | no | >= 1.23.0 | `AGENTS.md` | no |
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/exec.js
CHANGED
|
@@ -1081,8 +1081,17 @@ export function registerRunCommand(program) {
|
|
|
1081
1081
|
if (nativeResume(agent)) {
|
|
1082
1082
|
resumeNative = true;
|
|
1083
1083
|
resumeSessionId = session.id;
|
|
1084
|
+
// Native `--resume` (claude/codex) resolves the transcript relative to the
|
|
1085
|
+
// working directory (projects/<cwd-hash>/). The session may have been started
|
|
1086
|
+
// in a different directory than we're standing in now — most importantly when a
|
|
1087
|
+
// routine daemon fires `agents run --resume` from its own cwd. Spawn from the
|
|
1088
|
+
// session's ORIGIN cwd so the resume actually finds it; otherwise the agent
|
|
1089
|
+
// exits "No conversation found with session ID". Honor an explicit --cwd only if
|
|
1090
|
+
// the caller passed one (they're overriding on purpose).
|
|
1091
|
+
if (!options.cwd && session.cwd)
|
|
1092
|
+
options.cwd = session.cwd;
|
|
1084
1093
|
if (!options.quiet)
|
|
1085
|
-
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''}\n`));
|
|
1094
|
+
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''}${!options.cwd || options.cwd === session.cwd ? ` in ${session.cwd ?? cwd}` : ''}\n`));
|
|
1086
1095
|
}
|
|
1087
1096
|
else {
|
|
1088
1097
|
// Tier-2: launch fresh with a /continue <id> first message; the agent
|
|
@@ -347,6 +347,7 @@ export function registerRoutinesCommands(program) {
|
|
|
347
347
|
.option('--at <time>', 'One-shot mode: run once at this time (e.g., "14:30" or "2026-02-24 09:00"), then disable')
|
|
348
348
|
.option('--end-at <iso>', 'Stop firing on or after this ISO 8601 timestamp (e.g., "2026-12-31T23:59:00Z"); routine auto-disables.')
|
|
349
349
|
.option('--disabled', 'Create the routine but keep it paused (enable later with resume)')
|
|
350
|
+
.option('--resume <sessionId>', 'At fire time, resume this existing session id (via `agents run <agent> --resume`) instead of starting fresh — the actual session reopens with full context and the prompt becomes its next turn. Powers self-scheduled wake-ups (e.g. /hibernate). Requires --agent claude or codex; runs un-sandboxed (the session store lives in the real home, not the job overlay).')
|
|
350
351
|
.action(async (nameOrPath, options) => {
|
|
351
352
|
// Check if inline mode (has flags) or file mode
|
|
352
353
|
const hasInlineFlags = options.schedule || options.agent || options.workflow || options.prompt || options.at;
|
|
@@ -406,6 +407,7 @@ export function registerRoutinesCommands(program) {
|
|
|
406
407
|
...(devices ? { devices } : {}),
|
|
407
408
|
...(runOnce ? { runOnce: true } : {}),
|
|
408
409
|
...(options.endAt ? { endAt: options.endAt } : {}),
|
|
410
|
+
...(options.resume ? { resume: options.resume } : {}),
|
|
409
411
|
};
|
|
410
412
|
const errors = validateJob(config);
|
|
411
413
|
if (errors.length > 0) {
|
package/dist/lib/agents.js
CHANGED
|
@@ -291,7 +291,15 @@ export const AGENTS = {
|
|
|
291
291
|
format: 'markdown',
|
|
292
292
|
variableSyntax: '$ARGUMENTS',
|
|
293
293
|
supportsHooks: true,
|
|
294
|
-
|
|
294
|
+
// Subagents: `.cursor/agents/<name>.md` (project) or `~/.cursor/agents/<name>.md`
|
|
295
|
+
// (user), Markdown with YAML frontmatter (name, description, model, readonly,
|
|
296
|
+
// is_background — no `color`). Shipped in cursor-agent CLI 2026.01 (Cursor 2.4,
|
|
297
|
+
// 2026-01-22); cursor-agent uses CalVer build tags (e.g. 2025.11.25-<hash>), so
|
|
298
|
+
// gate at `>= 2026.1.22`. The `agents sync` path enforces this (versions.ts skips
|
|
299
|
+
// + warns for pre-2.4 installs); the direct `subagents add --agents cursor` path
|
|
300
|
+
// writes unconditionally, same as the other since-gated agents.
|
|
301
|
+
// See transformSubagentForCursor / https://cursor.com/docs/subagents.
|
|
302
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '2026.1.22' }, rules: { file: '.cursorrules' }, workflows: false, memory: false, modes: ['edit', 'skip'] }, // allowlist: ~/.cursor/cli-config.json
|
|
295
303
|
},
|
|
296
304
|
opencode: {
|
|
297
305
|
id: 'opencode',
|
|
@@ -329,7 +337,11 @@ export const AGENTS = {
|
|
|
329
337
|
format: 'markdown',
|
|
330
338
|
variableSyntax: '{{ARGUMENTS}}',
|
|
331
339
|
supportsHooks: true,
|
|
332
|
-
|
|
340
|
+
// allowlist: maps blanket (whole-tool) rules to ~/.openclaw/openclaw.json
|
|
341
|
+
// tools.alsoAllow (allow) / tools.deny (deny). OpenClaw gates at tool
|
|
342
|
+
// granularity only, so sub-command/path/domain patterns are skipped.
|
|
343
|
+
// OpenClaw is self-updating (no pinned since), so `true` is correct.
|
|
344
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: false, memory: true, modes: ['plan', 'edit', 'skip'] },
|
|
333
345
|
},
|
|
334
346
|
copilot: {
|
|
335
347
|
id: 'copilot',
|
|
@@ -421,7 +433,12 @@ export const AGENTS = {
|
|
|
421
433
|
// Plugins: Open Plugins under ~/.agents/plugins/<name>/ (same layout as
|
|
422
434
|
// agents-cli source). Version isolation copies into versionHome/.agents/plugins/.
|
|
423
435
|
// Workflows sync as Goose recipe YAML; permissions sync to permission.yaml.
|
|
424
|
-
|
|
436
|
+
// Commands: a Goose slash command is a recipe YAML under
|
|
437
|
+
// ~/.config/goose/commands/<name>.yaml, registered in ~/.config/goose/config.yaml
|
|
438
|
+
// under `slash_commands: [{ command, recipe_path }]` (see goose-commands.ts).
|
|
439
|
+
// Subagents: recipe YAML named agents under ~/.config/goose/agents/<name>.yaml
|
|
440
|
+
// (goose auto-discovers and delegates to them by name in autonomous mode).
|
|
441
|
+
capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: { since: '1.25.0' }, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: true, memory: false, modes: ['edit'] },
|
|
425
442
|
},
|
|
426
443
|
// Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
|
|
427
444
|
// configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
|
|
@@ -450,7 +467,7 @@ export const AGENTS = {
|
|
|
450
467
|
variableSyntax: '{{args}}',
|
|
451
468
|
supportsHooks: true,
|
|
452
469
|
cloudProvider: 'antigravity',
|
|
453
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '1.0.16' }, rules: { file: 'AGENTS.md' }, workflows:
|
|
470
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '1.0.16' }, rules: { file: 'AGENTS.md' }, workflows: { since: '1.0.6' }, memory: false, modes: ['edit', 'skip'], rulesImports: false }, // workflows: markdown files in the shared, HOME-global ~/.gemini/config/global_workflows/ (agy scans it at startup; not version-isolated — see workflows.ts), invoked as /<name> slash commands
|
|
454
471
|
},
|
|
455
472
|
// xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
|
|
456
473
|
// first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
|
|
@@ -610,16 +627,25 @@ export const AGENTS = {
|
|
|
610
627
|
instructionsFile: 'MEMORY.md',
|
|
611
628
|
format: 'markdown',
|
|
612
629
|
variableSyntax: '$ARGUMENTS',
|
|
613
|
-
supportsHooks:
|
|
630
|
+
supportsHooks: true,
|
|
631
|
+
// Plugins: Hermes loads plugins from a flat `~/.hermes/plugins/<name>/` dir
|
|
632
|
+
// with a `plugin.yaml` manifest; a plugin only loads once its name is in the
|
|
633
|
+
// `plugins.enabled` allowlist in `~/.hermes/config.yaml` (deny-list
|
|
634
|
+
// `plugins.disabled` wins). Not the Claude marketplace layout, so it installs
|
|
635
|
+
// via a flat-copy branch (mirrors goose) plus a YAML allowlist toggle.
|
|
636
|
+
// See https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins
|
|
614
637
|
capabilities: {
|
|
615
|
-
hooks
|
|
638
|
+
// Lifecycle hooks land in ~/.hermes/config.yaml under a `hooks:` block
|
|
639
|
+
// (YAML, shared with `mcp_servers`); gated to Hermes ≥ 0.11.0 which
|
|
640
|
+
// introduced the configurable hook runner.
|
|
641
|
+
hooks: { since: '0.11.0' },
|
|
616
642
|
mcp: true,
|
|
617
643
|
mcpHttp: true,
|
|
618
644
|
mcpHeaders: false,
|
|
619
645
|
allowlist: false,
|
|
620
646
|
skills: true,
|
|
621
647
|
commands: false,
|
|
622
|
-
plugins:
|
|
648
|
+
plugins: true,
|
|
623
649
|
subagents: false,
|
|
624
650
|
rules: { file: 'MEMORY.md' },
|
|
625
651
|
workflows: false,
|
|
@@ -638,14 +664,20 @@ export const AGENTS = {
|
|
|
638
664
|
npmPackage: '',
|
|
639
665
|
installScript: 'curl -fsSL https://forgecode.dev/cli | sh',
|
|
640
666
|
configDir: path.join(HOME, '.forge'),
|
|
641
|
-
commandsDir: '',
|
|
642
|
-
commandsSubdir: '',
|
|
667
|
+
commandsDir: path.join(HOME, '.forge', 'commands'),
|
|
668
|
+
commandsSubdir: 'commands',
|
|
643
669
|
skillsDir: path.join(HOME, '.forge', 'skills'),
|
|
644
670
|
hooksDir: 'hooks',
|
|
645
671
|
instructionsFile: 'AGENTS.md',
|
|
646
672
|
format: 'markdown',
|
|
647
673
|
variableSyntax: '$ARGUMENTS',
|
|
648
674
|
supportsHooks: false,
|
|
675
|
+
// Commands: ForgeCode reads Markdown slash commands from `~/.forge/commands/<name>.md`
|
|
676
|
+
// (also the shared `~/.agents/commands/`); the filename is the command name.
|
|
677
|
+
// Subagents: named `.md` agent definitions with YAML frontmatter under
|
|
678
|
+
// `~/.forge/agents/<name>.md` — same Markdown+frontmatter shape as Droid/Copilot
|
|
679
|
+
// (no `color` field), so transformSubagentForForge aliases transformSubagentForDroid.
|
|
680
|
+
// See https://forgecode.dev/docs/commands/ and /docs/agent-definition-guide/.
|
|
649
681
|
capabilities: {
|
|
650
682
|
hooks: false,
|
|
651
683
|
mcp: true,
|
|
@@ -653,9 +685,9 @@ export const AGENTS = {
|
|
|
653
685
|
mcpHeaders: false,
|
|
654
686
|
allowlist: false,
|
|
655
687
|
skills: true,
|
|
656
|
-
commands:
|
|
688
|
+
commands: true,
|
|
657
689
|
plugins: false,
|
|
658
|
-
subagents:
|
|
690
|
+
subagents: true,
|
|
659
691
|
rules: { file: 'AGENTS.md' },
|
|
660
692
|
workflows: false,
|
|
661
693
|
memory: false,
|
|
@@ -1024,6 +1056,64 @@ async function antigravityKeychainSignedIn() {
|
|
|
1024
1056
|
}
|
|
1025
1057
|
return cachedAgyKeychainSignedIn;
|
|
1026
1058
|
}
|
|
1059
|
+
/**
|
|
1060
|
+
* OpenCode (sst/opencode) stores provider credentials in a single JSON file at
|
|
1061
|
+
* `$XDG_DATA_HOME/opencode/auth.json`, defaulting to
|
|
1062
|
+
* `~/.local/share/opencode/auth.json` on EVERY platform — its `xdg-basedir`
|
|
1063
|
+
* dependency does not special-case macOS, so there is no
|
|
1064
|
+
* `~/Library/Application Support` variant. The path is account-global (not
|
|
1065
|
+
* per-version), matching how `session/discover.ts` already resolves
|
|
1066
|
+
* `~/.local/share/opencode/opencode.db`.
|
|
1067
|
+
*
|
|
1068
|
+
* Resolution order, first existing wins:
|
|
1069
|
+
* 1. `<base>/.local/share/opencode/auth.json` — the passed per-version home.
|
|
1070
|
+
* This is primarily a test hook (suites write a hermetic auth file under a
|
|
1071
|
+
* temp home) but also covers any relocated install.
|
|
1072
|
+
* 2. `$XDG_DATA_HOME/opencode/auth.json` — an explicit XDG override, exactly
|
|
1073
|
+
* what OpenCode itself honours.
|
|
1074
|
+
* 3. `<realHome>/.local/share/opencode/auth.json` — the active default, under
|
|
1075
|
+
* `AGENTS_REAL_HOME` or `os.homedir()`, so every installed version reflects
|
|
1076
|
+
* the one account-global login (same fallback shape as
|
|
1077
|
+
* resolveAccountCredentialPath).
|
|
1078
|
+
* Returns the first existing path, or null. Never throws.
|
|
1079
|
+
*/
|
|
1080
|
+
function resolveOpenCodeAuthPath(base) {
|
|
1081
|
+
const candidates = [path.join(base, '.local', 'share', 'opencode', 'auth.json')];
|
|
1082
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
1083
|
+
if (xdgData)
|
|
1084
|
+
candidates.push(path.join(xdgData, 'opencode', 'auth.json'));
|
|
1085
|
+
const realHome = process.env.AGENTS_REAL_HOME || os.homedir();
|
|
1086
|
+
candidates.push(path.join(realHome, '.local', 'share', 'opencode', 'auth.json'));
|
|
1087
|
+
for (const candidate of candidates) {
|
|
1088
|
+
try {
|
|
1089
|
+
if (fs.existsSync(candidate))
|
|
1090
|
+
return candidate;
|
|
1091
|
+
}
|
|
1092
|
+
catch { /* unreadable */ }
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Validate one OpenCode auth.json entry against its discriminated union
|
|
1098
|
+
* (`type: 'oauth' | 'api' | 'wellknown'`) and confirm the credential actually
|
|
1099
|
+
* carries its required secret field(s) non-empty. This guards against a
|
|
1100
|
+
* corrupt/half-written entry reading as signed-in — the same "must have a real
|
|
1101
|
+
* credential" floor grok/antigravity apply. We only INSPECT the shape here; the
|
|
1102
|
+
* secret values (`access`/`refresh`/`key`/`token`) are never read out or
|
|
1103
|
+
* surfaced anywhere.
|
|
1104
|
+
*/
|
|
1105
|
+
function isValidOpenCodeCredential(value) {
|
|
1106
|
+
if (!value || typeof value !== 'object')
|
|
1107
|
+
return false;
|
|
1108
|
+
const cred = value;
|
|
1109
|
+
const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
|
|
1110
|
+
switch (cred.type) {
|
|
1111
|
+
case 'oauth': return nonEmpty(cred.access) || nonEmpty(cred.refresh);
|
|
1112
|
+
case 'api': return nonEmpty(cred.key);
|
|
1113
|
+
case 'wellknown': return nonEmpty(cred.key) && nonEmpty(cred.token);
|
|
1114
|
+
default: return false;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1027
1117
|
export async function getAccountInfo(agentId, home) {
|
|
1028
1118
|
const base = home || os.homedir();
|
|
1029
1119
|
const empty = {
|
|
@@ -1253,6 +1343,34 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1253
1343
|
return { ...empty, lastActive };
|
|
1254
1344
|
return { ...empty, signedIn: true, lastActive };
|
|
1255
1345
|
}
|
|
1346
|
+
case 'opencode': {
|
|
1347
|
+
// OpenCode's auth.json is a record keyed by provider id ->
|
|
1348
|
+
// { type: 'oauth'|'api'|'wellknown', ...secret fields }. There is no
|
|
1349
|
+
// email/identity claim to surface, so — like antigravity/kimi — we
|
|
1350
|
+
// report signed-in state plus the NON-SECRET provider metadata (which
|
|
1351
|
+
// provider ids hold a valid credential) and never read the tokens/keys
|
|
1352
|
+
// themselves. The user's complaint was the row read "not signed in"
|
|
1353
|
+
// despite a live login; a valid provider entry now shows e.g.
|
|
1354
|
+
// "id:muse-spark" so they can see exactly which provider is configured.
|
|
1355
|
+
const authPath = resolveOpenCodeAuthPath(base);
|
|
1356
|
+
if (!authPath)
|
|
1357
|
+
return { ...empty, lastActive };
|
|
1358
|
+
const data = JSON.parse(await fs.promises.readFile(authPath, 'utf-8'));
|
|
1359
|
+
if (!data || typeof data !== 'object')
|
|
1360
|
+
return { ...empty, lastActive };
|
|
1361
|
+
const providers = Object.entries(data)
|
|
1362
|
+
.filter(([, cred]) => isValidOpenCodeCredential(cred))
|
|
1363
|
+
.map(([id]) => id)
|
|
1364
|
+
.sort();
|
|
1365
|
+
if (providers.length === 0)
|
|
1366
|
+
return { ...empty, lastActive };
|
|
1367
|
+
// Provider ids are config keys (e.g. "anthropic", "muse-spark"), not
|
|
1368
|
+
// secrets. Join them into a stable, human-readable account label +
|
|
1369
|
+
// identity key for usage dedup.
|
|
1370
|
+
const accountId = providers.join('+');
|
|
1371
|
+
const accountKey = buildIdentityKey(agentId, [['providers', accountId]]);
|
|
1372
|
+
return { ...empty, signedIn: true, accountId, accountKey, lastActive };
|
|
1373
|
+
}
|
|
1256
1374
|
default:
|
|
1257
1375
|
return { ...empty, lastActive };
|
|
1258
1376
|
}
|
package/dist/lib/commands.js
CHANGED
|
@@ -15,6 +15,7 @@ import { markdownToToml } from './convert.js';
|
|
|
15
15
|
import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir, getTrashCommandsDir } from './state.js';
|
|
16
16
|
import { getEffectiveHome, getVersionHomePath, listInstalledVersions, resolveVersion } from './versions.js';
|
|
17
17
|
import { commandSkillMatches, installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldInstallCommandAsSkill, } from './command-skills.js';
|
|
18
|
+
import { installGooseCommandToVersion, listGooseCommandsInVersion, gooseCommandMatches, removeGooseCommandFromVersion, } from './goose-commands.js';
|
|
18
19
|
function compareVersions(a, b) {
|
|
19
20
|
const aParts = a.split('.').map((n) => parseInt(n, 10) || 0);
|
|
20
21
|
const bParts = b.split('.').map((n) => parseInt(n, 10) || 0);
|
|
@@ -209,6 +210,19 @@ export function installCommand(sourcePath, agentId, commandName, method = 'symli
|
|
|
209
210
|
const agent = AGENTS[agentId];
|
|
210
211
|
ensureCommandsDir(agentId);
|
|
211
212
|
const home = getEffectiveHome(agentId);
|
|
213
|
+
// Goose: a slash command is a recipe YAML registered in config.yaml, not a
|
|
214
|
+
// native command file under commandsSubdir.
|
|
215
|
+
if (agentId === 'goose') {
|
|
216
|
+
const result = installGooseCommandToVersion(home, commandName, sourcePath);
|
|
217
|
+
if (!result.success) {
|
|
218
|
+
return { path: '', method: 'copy', error: result.error, warnings: validation.warnings };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
path: path.join(home, '.config', 'goose', 'commands', `${commandName}.yaml`),
|
|
222
|
+
method: 'copy',
|
|
223
|
+
warnings: validation.warnings,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
212
226
|
const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
|
|
213
227
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
214
228
|
const ext = agent.format === 'toml' ? '.toml' : '.md';
|
|
@@ -248,6 +262,9 @@ export function listCommandsInVersionHome(agent, version) {
|
|
|
248
262
|
if (shouldInstallCommandAsSkill(agent, version)) {
|
|
249
263
|
return listCommandSkillsInVersion(agentDir);
|
|
250
264
|
}
|
|
265
|
+
if (agent === 'goose') {
|
|
266
|
+
return listGooseCommandsInVersion(versionHome);
|
|
267
|
+
}
|
|
251
268
|
const dir = getVersionCommandsDir(agent, version);
|
|
252
269
|
if (!fs.existsSync(dir))
|
|
253
270
|
return [];
|
|
@@ -270,6 +287,9 @@ function versionCommandMatches(agent, version, commandName) {
|
|
|
270
287
|
if (shouldInstallCommandAsSkill(agent, version)) {
|
|
271
288
|
return commandSkillMatches(agentDir, commandName, sourcePath);
|
|
272
289
|
}
|
|
290
|
+
if (agent === 'goose') {
|
|
291
|
+
return gooseCommandMatches(versionHome, commandName, sourcePath);
|
|
292
|
+
}
|
|
273
293
|
const agentConfig = AGENTS[agent];
|
|
274
294
|
const ext = agentConfig.format === 'toml' ? '.toml' : '.md';
|
|
275
295
|
const installedPath = path.join(getVersionCommandsDir(agent, version), `${commandName}${ext}`);
|
|
@@ -357,6 +377,11 @@ export function installCommandToVersion(agent, version, commandName, method = 'c
|
|
|
357
377
|
...getEnabledExtraRepos().map((repo) => path.join(repo.dir, 'skills')),
|
|
358
378
|
]);
|
|
359
379
|
}
|
|
380
|
+
// Goose: a slash command is a recipe YAML registered in config.yaml, not a
|
|
381
|
+
// native command file. Write the recipe + slash_commands entry.
|
|
382
|
+
if (agent === 'goose') {
|
|
383
|
+
return installGooseCommandToVersion(versionHome, commandName, sourcePath);
|
|
384
|
+
}
|
|
360
385
|
const agentConfig = AGENTS[agent];
|
|
361
386
|
const commandsDir = getVersionCommandsDir(agent, version);
|
|
362
387
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
@@ -392,6 +417,10 @@ export function removeCommandFromVersion(agent, version, commandName) {
|
|
|
392
417
|
if (shouldInstallCommandAsSkill(agent, version)) {
|
|
393
418
|
return removeCommandSkillFromVersion(agentDir, commandName);
|
|
394
419
|
}
|
|
420
|
+
if (agent === 'goose') {
|
|
421
|
+
const trashDir = path.join(getTrashCommandsDir(), agent, version, commandName);
|
|
422
|
+
return removeGooseCommandFromVersion(versionHome, commandName, trashDir);
|
|
423
|
+
}
|
|
395
424
|
const ext = AGENTS[agent].format === 'toml' ? '.toml' : '.md';
|
|
396
425
|
const targetPath = path.join(getVersionCommandsDir(agent, version), `${commandName}${ext}`);
|
|
397
426
|
if (!fs.existsSync(targetPath) && !fs.lstatSync(targetPath, { throwIfNoEntry: false })) {
|
package/dist/lib/convert.d.ts
CHANGED
|
@@ -16,5 +16,16 @@ export declare function parseMarkdownFrontmatter(content: string): {
|
|
|
16
16
|
};
|
|
17
17
|
/** Convert a Markdown command file to Gemini's TOML format, translating $ARGUMENTS to {{args}}. */
|
|
18
18
|
export declare function markdownToToml(skillName: string, markdown: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Convert a Markdown command file to a Goose recipe YAML object.
|
|
21
|
+
*
|
|
22
|
+
* Goose has no native slash-command file format — a slash command is a recipe
|
|
23
|
+
* (registered in `config.yaml` under `slash_commands`). The recipe schema matches
|
|
24
|
+
* the one agents-cli already emits for Goose workflow/subagent recipes:
|
|
25
|
+
* `version`, `title`, `description`, `instructions`, `prompt`. The Markdown body
|
|
26
|
+
* (with `$ARGUMENTS` preserved) becomes both `instructions` and `prompt`.
|
|
27
|
+
* Returns a plain object so the caller can `yaml.stringify` it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function markdownToGooseRecipe(commandName: string, markdown: string): Record<string, unknown>;
|
|
19
30
|
/** Convert a Gemini TOML command file back to Markdown format, translating {{args}} to $ARGUMENTS. */
|
|
20
31
|
export declare function tomlToMarkdown(toml: string): string;
|
package/dist/lib/convert.js
CHANGED
|
@@ -40,6 +40,28 @@ export function markdownToToml(skillName, markdown) {
|
|
|
40
40
|
];
|
|
41
41
|
return lines.join('\n');
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a Markdown command file to a Goose recipe YAML object.
|
|
45
|
+
*
|
|
46
|
+
* Goose has no native slash-command file format — a slash command is a recipe
|
|
47
|
+
* (registered in `config.yaml` under `slash_commands`). The recipe schema matches
|
|
48
|
+
* the one agents-cli already emits for Goose workflow/subagent recipes:
|
|
49
|
+
* `version`, `title`, `description`, `instructions`, `prompt`. The Markdown body
|
|
50
|
+
* (with `$ARGUMENTS` preserved) becomes both `instructions` and `prompt`.
|
|
51
|
+
* Returns a plain object so the caller can `yaml.stringify` it.
|
|
52
|
+
*/
|
|
53
|
+
export function markdownToGooseRecipe(commandName, markdown) {
|
|
54
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(markdown);
|
|
55
|
+
const description = frontmatter.description || `Run ${commandName} command`;
|
|
56
|
+
const prompt = body.trim() || description;
|
|
57
|
+
return {
|
|
58
|
+
version: '1.0.0',
|
|
59
|
+
title: commandName,
|
|
60
|
+
description,
|
|
61
|
+
instructions: prompt,
|
|
62
|
+
prompt,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
43
65
|
/** Convert a Gemini TOML command file back to Markdown format, translating {{args}} to $ARGUMENTS. */
|
|
44
66
|
export function tomlToMarkdown(toml) {
|
|
45
67
|
const nameMatch = toml.match(/name\s*=\s*"([^"]+)"/);
|
package/dist/lib/doctor-diff.js
CHANGED
|
@@ -30,6 +30,7 @@ import { markdownToToml } from './convert.js';
|
|
|
30
30
|
import { resolveImports, supportsRulesImports } from './rules/compile.js';
|
|
31
31
|
import { listCommandsInVersionHome, getVersionCommandsDir } from './commands.js';
|
|
32
32
|
import { shouldInstallCommandAsSkill, commandSkillMatches, commandSkillName } from './command-skills.js';
|
|
33
|
+
import { gooseCommandMatches, gooseCommandsDir } from './goose-commands.js';
|
|
33
34
|
import { supports } from './capabilities.js';
|
|
34
35
|
import { listSkillsInVersionHome, getVersionSkillsDir } from './skills.js';
|
|
35
36
|
import { listHooksInVersionHome, listHookEntriesFromDir } from './hooks.js';
|
|
@@ -141,6 +142,19 @@ function diffCommands(agent, version, cwd, excludeProject = false) {
|
|
|
141
142
|
});
|
|
142
143
|
continue;
|
|
143
144
|
}
|
|
145
|
+
if (agent === 'goose') {
|
|
146
|
+
// Compare against the installed Goose recipe YAML + slash_commands registration.
|
|
147
|
+
const matches = gooseCommandMatches(getVersionHomePath(agent, version), name, src.path);
|
|
148
|
+
rows.push({
|
|
149
|
+
kind: 'commands',
|
|
150
|
+
name,
|
|
151
|
+
status: matches ? 'ok' : 'diff',
|
|
152
|
+
source: src.layer,
|
|
153
|
+
sourcePath: src.path,
|
|
154
|
+
homePath: path.join(gooseCommandsDir(getVersionHomePath(agent, version)), `${name}.yaml`),
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
144
158
|
const homePath = path.join(homeDir, `${name}${ext}`);
|
|
145
159
|
const installedContent = readSafe(homePath);
|
|
146
160
|
const sourceContent = readSafe(src.path);
|
|
@@ -164,7 +178,9 @@ function diffCommands(agent, version, cwd, excludeProject = false) {
|
|
|
164
178
|
continue;
|
|
165
179
|
const extraHome = asSkill
|
|
166
180
|
? path.join(agentDir, 'skills', commandSkillName(name), 'SKILL.md')
|
|
167
|
-
:
|
|
181
|
+
: agent === 'goose'
|
|
182
|
+
? path.join(gooseCommandsDir(getVersionHomePath(agent, version)), `${name}.yaml`)
|
|
183
|
+
: path.join(homeDir, `${name}${ext}`);
|
|
168
184
|
rows.push({ kind: 'commands', name, status: 'extra', homePath: extraHome });
|
|
169
185
|
}
|
|
170
186
|
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goose slash-command install/remove/list/compare.
|
|
3
|
+
*
|
|
4
|
+
* Goose has no native slash-command file format — a slash command is a recipe
|
|
5
|
+
* YAML file registered in `~/.config/goose/config.yaml` under a `slash_commands`
|
|
6
|
+
* array: `[{ command: "<name>", recipe_path: "<abs path to recipe.yaml>" }]`.
|
|
7
|
+
* (See goose-docs.ai context-engineering/slash-commands.)
|
|
8
|
+
*
|
|
9
|
+
* agents-cli writes each command's recipe to `<versionHome>/.config/goose/commands/
|
|
10
|
+
* <name>.yaml` — a dir distinct from the workflow recipes dir
|
|
11
|
+
* (`.config/goose/recipes/`) so the workflow detector never treats a command
|
|
12
|
+
* recipe as a workflow — and registers/unregisters the `slash_commands` entry in
|
|
13
|
+
* `config.yaml` via a read-modify-write that preserves every other key
|
|
14
|
+
* (`mcp_servers`, `extensions`, …). Under agents-cli version isolation HOME is the
|
|
15
|
+
* version home, so both files live under it and the absolute `recipe_path`
|
|
16
|
+
* resolves correctly at goose runtime.
|
|
17
|
+
*/
|
|
18
|
+
/** Directory holding Goose slash-command recipe YAML files in a version home. */
|
|
19
|
+
export declare function gooseCommandsDir(versionHome: string): string;
|
|
20
|
+
/** Path to the Goose config.yaml (holds the `slash_commands` registry) in a version home. */
|
|
21
|
+
export declare function gooseCommandConfigPath(versionHome: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Install a command into a Goose version home: write its recipe YAML and register
|
|
24
|
+
* the `slash_commands` entry in config.yaml.
|
|
25
|
+
*/
|
|
26
|
+
export declare function installGooseCommandToVersion(versionHome: string, commandName: string, sourcePath: string): {
|
|
27
|
+
success: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
};
|
|
30
|
+
/** List Goose command names (recipe files) installed in a version home. */
|
|
31
|
+
export declare function listGooseCommandsInVersion(versionHome: string): string[];
|
|
32
|
+
/** Whether an installed Goose command recipe matches the central Markdown source. */
|
|
33
|
+
export declare function gooseCommandMatches(versionHome: string, commandName: string, sourcePath: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Remove a Goose command from a version home: soft-delete the recipe to `trashDir`
|
|
36
|
+
* (when provided) and unregister its `slash_commands` entry.
|
|
37
|
+
*/
|
|
38
|
+
export declare function removeGooseCommandFromVersion(versionHome: string, commandName: string, trashDir?: string): {
|
|
39
|
+
success: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
};
|