@phnx-labs/agents-cli 1.20.63 → 1.20.65

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.
Files changed (197) hide show
  1. package/CHANGELOG.md +56 -3
  2. package/README.md +46 -2
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.d.ts +12 -0
  5. package/dist/commands/apply.js +274 -0
  6. package/dist/commands/browser.js +2 -2
  7. package/dist/commands/cloud.js +32 -2
  8. package/dist/commands/doctor.js +4 -1
  9. package/dist/commands/exec.js +149 -77
  10. package/dist/commands/feed.d.ts +4 -0
  11. package/dist/commands/feed.js +52 -19
  12. package/dist/commands/hosts.js +44 -6
  13. package/dist/commands/lease.d.ts +23 -0
  14. package/dist/commands/lease.js +201 -0
  15. package/dist/commands/mailboxes.d.ts +20 -0
  16. package/dist/commands/mailboxes.js +390 -0
  17. package/dist/commands/mcp.js +55 -5
  18. package/dist/commands/monitors.d.ts +12 -0
  19. package/dist/commands/monitors.js +740 -0
  20. package/dist/commands/output.js +2 -2
  21. package/dist/commands/routines.js +43 -16
  22. package/dist/commands/secrets.d.ts +16 -0
  23. package/dist/commands/secrets.js +215 -64
  24. package/dist/commands/serve.js +31 -0
  25. package/dist/commands/sessions-export.d.ts +2 -0
  26. package/dist/commands/sessions-export.js +284 -0
  27. package/dist/commands/sessions-import.d.ts +2 -0
  28. package/dist/commands/sessions-import.js +230 -0
  29. package/dist/commands/sessions.d.ts +16 -0
  30. package/dist/commands/sessions.js +40 -6
  31. package/dist/commands/ssh.js +143 -5
  32. package/dist/commands/usage.d.ts +2 -0
  33. package/dist/commands/usage.js +7 -2
  34. package/dist/commands/versions.js +7 -3
  35. package/dist/commands/view.d.ts +27 -1
  36. package/dist/commands/view.js +32 -9
  37. package/dist/commands/webhook.js +10 -2
  38. package/dist/index.js +35 -14
  39. package/dist/lib/agents.d.ts +36 -0
  40. package/dist/lib/agents.js +80 -20
  41. package/dist/lib/auto-dispatch-provider.js +7 -2
  42. package/dist/lib/auto-dispatch.d.ts +3 -0
  43. package/dist/lib/auto-dispatch.js +3 -0
  44. package/dist/lib/browser/chrome.js +2 -2
  45. package/dist/lib/browser/drivers/ssh.js +19 -2
  46. package/dist/lib/cloud/antigravity.js +2 -2
  47. package/dist/lib/cloud/host.d.ts +59 -0
  48. package/dist/lib/cloud/host.js +224 -0
  49. package/dist/lib/cloud/registry.js +4 -0
  50. package/dist/lib/cloud/types.d.ts +6 -4
  51. package/dist/lib/comms-render.d.ts +37 -0
  52. package/dist/lib/comms-render.js +89 -0
  53. package/dist/lib/computer-rpc.js +3 -1
  54. package/dist/lib/crabbox/cli.d.ts +72 -0
  55. package/dist/lib/crabbox/cli.js +162 -9
  56. package/dist/lib/crabbox/runtimes.d.ts +13 -0
  57. package/dist/lib/crabbox/runtimes.js +35 -2
  58. package/dist/lib/daemon.d.ts +20 -4
  59. package/dist/lib/daemon.js +68 -20
  60. package/dist/lib/devices/fleet.d.ts +3 -2
  61. package/dist/lib/devices/fleet.js +9 -0
  62. package/dist/lib/devices/health.d.ts +77 -0
  63. package/dist/lib/devices/health.js +186 -0
  64. package/dist/lib/devices/registry.d.ts +15 -0
  65. package/dist/lib/devices/registry.js +9 -0
  66. package/dist/lib/exec.d.ts +19 -2
  67. package/dist/lib/exec.js +41 -13
  68. package/dist/lib/fleet/apply.d.ts +63 -0
  69. package/dist/lib/fleet/apply.js +214 -0
  70. package/dist/lib/fleet/auth-sync.d.ts +67 -0
  71. package/dist/lib/fleet/auth-sync.js +142 -0
  72. package/dist/lib/fleet/manifest.d.ts +29 -0
  73. package/dist/lib/fleet/manifest.js +127 -0
  74. package/dist/lib/fleet/types.d.ts +129 -0
  75. package/dist/lib/fleet/types.js +13 -0
  76. package/dist/lib/git.d.ts +27 -0
  77. package/dist/lib/git.js +34 -2
  78. package/dist/lib/hosts/dispatch.d.ts +29 -8
  79. package/dist/lib/hosts/dispatch.js +46 -18
  80. package/dist/lib/hosts/passthrough.js +2 -0
  81. package/dist/lib/hosts/providers/devices.d.ts +27 -0
  82. package/dist/lib/hosts/providers/devices.js +98 -0
  83. package/dist/lib/hosts/registry.d.ts +10 -16
  84. package/dist/lib/hosts/registry.js +17 -50
  85. package/dist/lib/hosts/remote-cmd.d.ts +23 -0
  86. package/dist/lib/hosts/remote-cmd.js +71 -0
  87. package/dist/lib/hosts/run-target.d.ts +84 -0
  88. package/dist/lib/hosts/run-target.js +99 -0
  89. package/dist/lib/hosts/types.d.ts +23 -5
  90. package/dist/lib/hosts/types.js +22 -4
  91. package/dist/lib/linear-autoclose.d.ts +30 -0
  92. package/dist/lib/linear-autoclose.js +22 -0
  93. package/dist/lib/mailbox.d.ts +39 -0
  94. package/dist/lib/mailbox.js +112 -0
  95. package/dist/lib/mcp.d.ts +27 -1
  96. package/dist/lib/mcp.js +126 -12
  97. package/dist/lib/monitors/config.d.ts +161 -0
  98. package/dist/lib/monitors/config.js +372 -0
  99. package/dist/lib/monitors/dispatch.d.ts +28 -0
  100. package/dist/lib/monitors/dispatch.js +91 -0
  101. package/dist/lib/monitors/engine.d.ts +61 -0
  102. package/dist/lib/monitors/engine.js +201 -0
  103. package/dist/lib/monitors/sources/command.d.ts +11 -0
  104. package/dist/lib/monitors/sources/command.js +31 -0
  105. package/dist/lib/monitors/sources/device.d.ts +13 -0
  106. package/dist/lib/monitors/sources/device.js +35 -0
  107. package/dist/lib/monitors/sources/file.d.ts +14 -0
  108. package/dist/lib/monitors/sources/file.js +57 -0
  109. package/dist/lib/monitors/sources/http.d.ts +10 -0
  110. package/dist/lib/monitors/sources/http.js +34 -0
  111. package/dist/lib/monitors/sources/index.d.ts +14 -0
  112. package/dist/lib/monitors/sources/index.js +31 -0
  113. package/dist/lib/monitors/sources/poll.d.ts +9 -0
  114. package/dist/lib/monitors/sources/poll.js +9 -0
  115. package/dist/lib/monitors/sources/types.d.ts +18 -0
  116. package/dist/lib/monitors/sources/types.js +9 -0
  117. package/dist/lib/monitors/sources/webhook.d.ts +23 -0
  118. package/dist/lib/monitors/sources/webhook.js +47 -0
  119. package/dist/lib/monitors/sources/ws.d.ts +14 -0
  120. package/dist/lib/monitors/sources/ws.js +45 -0
  121. package/dist/lib/monitors/state.d.ts +69 -0
  122. package/dist/lib/monitors/state.js +144 -0
  123. package/dist/lib/paths.d.ts +13 -0
  124. package/dist/lib/paths.js +26 -4
  125. package/dist/lib/platform/exec.d.ts +16 -0
  126. package/dist/lib/platform/exec.js +17 -0
  127. package/dist/lib/plugins.js +101 -2
  128. package/dist/lib/redact.d.ts +14 -1
  129. package/dist/lib/redact.js +47 -1
  130. package/dist/lib/remote-agents-json.js +7 -1
  131. package/dist/lib/rotate.d.ts +6 -3
  132. package/dist/lib/rotate.js +0 -1
  133. package/dist/lib/routines.d.ts +37 -2
  134. package/dist/lib/routines.js +54 -12
  135. package/dist/lib/runner.d.ts +1 -0
  136. package/dist/lib/runner.js +348 -13
  137. package/dist/lib/sandbox.d.ts +9 -1
  138. package/dist/lib/sandbox.js +11 -2
  139. package/dist/lib/secrets/agent.d.ts +48 -10
  140. package/dist/lib/secrets/agent.js +123 -15
  141. package/dist/lib/secrets/bundles.d.ts +26 -0
  142. package/dist/lib/secrets/bundles.js +59 -8
  143. package/dist/lib/secrets/mcp.js +4 -2
  144. package/dist/lib/secrets/remote.d.ts +17 -0
  145. package/dist/lib/secrets/remote.js +40 -0
  146. package/dist/lib/self-update.d.ts +20 -0
  147. package/dist/lib/self-update.js +54 -1
  148. package/dist/lib/serve/control.d.ts +95 -0
  149. package/dist/lib/serve/control.js +260 -0
  150. package/dist/lib/serve/server.d.ts +35 -1
  151. package/dist/lib/serve/server.js +106 -76
  152. package/dist/lib/serve/stream.d.ts +43 -0
  153. package/dist/lib/serve/stream.js +116 -0
  154. package/dist/lib/serve/token.d.ts +35 -0
  155. package/dist/lib/serve/token.js +85 -0
  156. package/dist/lib/session/bundle.d.ts +164 -0
  157. package/dist/lib/session/bundle.js +200 -0
  158. package/dist/lib/session/remote-bundle.d.ts +12 -0
  159. package/dist/lib/session/remote-bundle.js +61 -0
  160. package/dist/lib/session/remote-list.js +5 -1
  161. package/dist/lib/session/state.d.ts +7 -25
  162. package/dist/lib/session/state.js +16 -6
  163. package/dist/lib/session/sync/agents.d.ts +54 -6
  164. package/dist/lib/session/sync/agents.js +0 -0
  165. package/dist/lib/session/sync/config.js +8 -2
  166. package/dist/lib/session/sync/manifest.d.ts +14 -3
  167. package/dist/lib/session/sync/manifest.js +4 -0
  168. package/dist/lib/session/sync/sync.d.ts +23 -2
  169. package/dist/lib/session/sync/sync.js +177 -74
  170. package/dist/lib/session/types.d.ts +30 -0
  171. package/dist/lib/ssh-tunnel.d.ts +19 -1
  172. package/dist/lib/ssh-tunnel.js +99 -8
  173. package/dist/lib/staleness/detectors/subagents.d.ts +5 -0
  174. package/dist/lib/staleness/detectors/subagents.js +5 -192
  175. package/dist/lib/staleness/writers/subagents.d.ts +10 -0
  176. package/dist/lib/staleness/writers/subagents.js +11 -102
  177. package/dist/lib/startup/command-registry.d.ts +4 -0
  178. package/dist/lib/startup/command-registry.js +9 -0
  179. package/dist/lib/state.d.ts +5 -0
  180. package/dist/lib/state.js +12 -0
  181. package/dist/lib/subagents-registry.d.ts +85 -0
  182. package/dist/lib/subagents-registry.js +393 -0
  183. package/dist/lib/subagents.d.ts +8 -8
  184. package/dist/lib/subagents.js +32 -663
  185. package/dist/lib/sync-umbrella.d.ts +1 -0
  186. package/dist/lib/sync-umbrella.js +14 -3
  187. package/dist/lib/tmux/session.d.ts +7 -0
  188. package/dist/lib/tmux/session.js +3 -1
  189. package/dist/lib/triggers/webhook.d.ts +18 -0
  190. package/dist/lib/triggers/webhook.js +105 -0
  191. package/dist/lib/types.d.ts +35 -1
  192. package/dist/lib/usage.d.ts +42 -3
  193. package/dist/lib/usage.js +163 -21
  194. package/dist/lib/versions.js +14 -11
  195. package/dist/lib/workflows.d.ts +20 -0
  196. package/dist/lib/workflows.js +24 -0
  197. package/package.json +2 -1
package/dist/lib/state.js CHANGED
@@ -83,12 +83,19 @@ const HISTORY_DIR = path.join(USER_AGENTS_DIR, '.history');
83
83
  const CACHE_DIR = path.join(USER_AGENTS_DIR, '.cache');
84
84
  // Top-level user dirs (config/definitions only — runtime moves into .history/.cache).
85
85
  const ROUTINES_DIR = path.join(USER_AGENTS_DIR, 'routines');
86
+ // Monitor definitions (event-triggered watchers). Sibling of ROUTINES_DIR: a
87
+ // monitor is a routine whose trigger is a watched source instead of a clock.
88
+ const MONITORS_DIR = path.join(USER_AGENTS_DIR, 'monitors');
86
89
  const TEAMS_DIR = path.join(USER_AGENTS_DIR, 'teams');
87
90
  // History bucket (durable).
88
91
  const SESSIONS_DIR = path.join(HISTORY_DIR, 'sessions');
89
92
  const SESSIONS_DB_PATH = path.join(SESSIONS_DIR, 'sessions.db');
90
93
  const VERSIONS_DIR = path.join(HISTORY_DIR, 'versions');
91
94
  const RUNS_DIR = path.join(HISTORY_DIR, 'runs');
95
+ // Durable per-monitor state-diff store + fire history (last-seen value/hash,
96
+ // fires/<id>/). Sibling of RUNS_DIR — the native diff store that replaces the
97
+ // hand-rolled markdown memory files monitors used to need.
98
+ const MONITORS_HISTORY_DIR = path.join(HISTORY_DIR, 'monitors');
92
99
  const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
93
100
  const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
94
101
  const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
@@ -324,6 +331,11 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
324
331
  }
325
332
  /** Path to routine execution logs (~/.agents/.history/runs/). */
326
333
  export function getRunsDir() { return RUNS_DIR; }
334
+ /** Path to monitor YAML definitions (~/.agents/monitors/). */
335
+ export function getMonitorsDir() { return MONITORS_DIR; }
336
+ /** Path to the durable per-monitor state-diff store + fire history
337
+ * (~/.agents/.history/monitors/). */
338
+ export function getMonitorsHistoryDir() { return MONITORS_HISTORY_DIR; }
327
339
  /** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
328
340
  export function getMailboxRootDir() { return MAILBOX_DIR; }
329
341
  /** Root for open-block feed records (~/.agents/.history/feed/). */
@@ -0,0 +1,85 @@
1
+ import type { AgentId, InstalledSubagent, SubagentFrontmatter } from './types.js';
2
+ /** A path an installed subagent occupies, tagged for removal/trash handling. */
3
+ export interface OccupiedEntry {
4
+ path: string;
5
+ kind: 'file' | 'dir';
6
+ }
7
+ /** Parsed metadata for one installed subagent (drives the rich listing). */
8
+ export interface SubagentMeta {
9
+ frontmatter: SubagentFrontmatter;
10
+ files: string[];
11
+ /** Primary on-disk path for the listing's `path` field (a file or a dir, per layout). */
12
+ path: string;
13
+ }
14
+ /**
15
+ * The complete on-disk contract for one agent's subagents. Every operation is
16
+ * expressed here so the engine below never branches on the agent id.
17
+ */
18
+ export interface SubagentTarget {
19
+ /** Absolute container dir under a home root (a version home or an agent home). */
20
+ dir(home: string): string;
21
+ /** Materialize central subagent `sub` into container `dir`. Throws on fs error. */
22
+ write(dir: string, sub: {
23
+ name: string;
24
+ path: string;
25
+ }): void;
26
+ /** Installed subagent names in `dir` (detector + orphan diff). */
27
+ names(dir: string): string[];
28
+ /** On-disk paths subagent `name` occupies (for removal / soft-delete). */
29
+ occupied(dir: string, name: string): OccupiedEntry[];
30
+ /** Rich metadata for `name`; `null` skips it from the listing. */
31
+ read(dir: string, name: string): SubagentMeta | null;
32
+ /** Optional post-sync pass over the just-synced subagents (Kimi's parent index). */
33
+ finalize?(dir: string, synced: Array<{
34
+ name: string;
35
+ path: string;
36
+ }>): void;
37
+ }
38
+ /**
39
+ * Copy every file in `src` into `dest` (created if missing), applying
40
+ * `rename` (source filename -> target filename) on the way. Directories in
41
+ * `src` are skipped -- subagents are flat file sets. Throws on fs error.
42
+ */
43
+ export declare function copyDirWithRename(src: string, dest: string, rename?: Record<string, string>): void;
44
+ /**
45
+ * Single source of truth for how each subagents-capable agent stores subagents.
46
+ * The keys MUST match `capableAgents('subagents')` (the `subagents` flag in
47
+ * `agents.ts`): the capability flag is the version gate, this table is the shape.
48
+ */
49
+ export declare const SUBAGENT_TARGETS: Partial<Record<AgentId, SubagentTarget>>;
50
+ /** The registry entry for `agent`, or undefined if it stores no subagents. */
51
+ export declare function subagentTarget(agent: AgentId): SubagentTarget | undefined;
52
+ /**
53
+ * Materialize central subagent `sub` into `home` for `agent`. Returns whether a
54
+ * write happened (false when the agent has no registry entry). Throws only on
55
+ * unexpected fs errors -- bulk callers wrap per-item.
56
+ */
57
+ export declare function writeSubagentToHome(agent: AgentId, home: string, sub: {
58
+ name: string;
59
+ path: string;
60
+ }): boolean;
61
+ /** Installed subagent names for `agent` under `home` (detector + orphan diff). */
62
+ export declare function listInstalledSubagentNames(agent: AgentId, home: string): string[];
63
+ /**
64
+ * Rich listing of subagents installed for `agent` under `home`, with parsed
65
+ * metadata. Enumerates names, then reads each -- entries whose metadata is
66
+ * unreadable (per the target's reader) are dropped.
67
+ */
68
+ export declare function listInstalledSubagentsRich(agent: AgentId, home: string): InstalledSubagent[];
69
+ /**
70
+ * Remove subagent `name` for `agent` from `home` (hard delete). No-op success
71
+ * when the agent has no registry entry or nothing is installed.
72
+ */
73
+ export declare function removeSubagentFromHome(agent: AgentId, home: string, name: string): {
74
+ success: boolean;
75
+ error?: string;
76
+ };
77
+ /**
78
+ * Soft-delete subagent `name` for `agent` from `home` into `trashDir`, stamping
79
+ * each moved entry. Files land as `<basename>.<stamp>`, directories as
80
+ * `<stamp>/`. No-op success when nothing is installed.
81
+ */
82
+ export declare function trashSubagentFromHome(agent: AgentId, home: string, name: string, trashDir: string, stamp: string): {
83
+ success: boolean;
84
+ error?: string;
85
+ };
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Declarative subagent-target registry.
3
+ *
4
+ * Each subagents-capable agent gets ONE table entry (`SUBAGENT_TARGETS`)
5
+ * describing how a central subagent (`~/.agents/subagents/<name>/`) is
6
+ * materialized into that agent's home, how it is enumerated, and where it lives
7
+ * on disk. Generic install / list / detect / orphan / remove logic iterates the
8
+ * table instead of the near-identical `else if (agent === '...')` chains that
9
+ * used to be copy-pasted across `subagents.ts`, the staleness writer, and the
10
+ * staleness detector -- roughly O(agents x operations) arms.
11
+ *
12
+ * Adding a *standard* integration is now one line here (plus the `subagents`
13
+ * capability flag in `agents.ts`, the version gate). Three layout builders cover
14
+ * every current agent, so most entries are a single call:
15
+ *
16
+ * - `flatFile` one `<name><ext>` file, body from a `transform` fn.
17
+ * (claude, gemini, grok, droid, codex, opencode, copilot,
18
+ * cursor, forge, kiro, goose)
19
+ * - `dirFile` a `<name>/` directory holding one generated `<file>`.
20
+ * (antigravity: `<name>/agent.md`)
21
+ * - `dirCopy` copy the whole source directory to `<name>/`, applying
22
+ * renames, detected by a `marker` file. (openclaw)
23
+ *
24
+ * Genuinely-bespoke agents keep an explicit handler in the same table -- Kimi
25
+ * writes two files per subagent plus a managed parent index, so it is a hand
26
+ * -written `SubagentTarget` rather than a builder call.
27
+ *
28
+ * The per-agent `transform`/metadata parsers are the escape hatch: they live in
29
+ * `subagents.ts` and are referenced by the table, so the generic engine has zero
30
+ * per-agent branches. See the integration tiers in `docs/subagents.md`.
31
+ */
32
+ import * as fs from 'fs';
33
+ import * as path from 'path';
34
+ import * as yaml from 'yaml';
35
+ import { safeJoin } from './paths.js';
36
+ import { parseSubagentFrontmatter, transformSubagentForClaude, transformSubagentForCodex, transformSubagentForCopilot, transformSubagentForCursor, transformSubagentForDroid, transformSubagentForForge, transformSubagentForGoose, transformSubagentForKiro, transformSubagentForOpenCode, transformSubagentForAntigravity, writeKimiSubagentFiles, buildKimiSubagentsParentYaml, KIMI_SUBAGENTS_PARENT_FILE, } from './subagents.js';
37
+ // ── metadata readers (the per-format escape hatch) ───────────────────────────
38
+ /** Frontmatter, skipping files that lack a valid block (claude/gemini/grok/droid/codex). */
39
+ function metaFrontmatterSkip(filePath) {
40
+ return parseSubagentFrontmatter(filePath);
41
+ }
42
+ /** Frontmatter, falling back to an empty description (opencode/copilot/cursor/forge). */
43
+ function metaFrontmatterFallback(filePath, name) {
44
+ return parseSubagentFrontmatter(filePath) ?? { name, description: '' };
45
+ }
46
+ /** Kiro custom-agent JSON: read name/description/model; skip on parse error. */
47
+ function metaJson(filePath, name) {
48
+ try {
49
+ const cfg = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
50
+ return { name: cfg.name || name, description: cfg.description || '', model: cfg.model };
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ /** Goose recipe YAML: title -> name, description; skip on parse error. */
57
+ function metaGooseYaml(filePath, name) {
58
+ try {
59
+ const recipe = yaml.parse(fs.readFileSync(filePath, 'utf-8'));
60
+ return { name: recipe?.title || name, description: recipe?.description || '' };
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ // ── shared fs primitive ──────────────────────────────────────────────────────
67
+ /**
68
+ * Copy every file in `src` into `dest` (created if missing), applying
69
+ * `rename` (source filename -> target filename) on the way. Directories in
70
+ * `src` are skipped -- subagents are flat file sets. Throws on fs error.
71
+ */
72
+ export function copyDirWithRename(src, dest, rename) {
73
+ if (!fs.existsSync(dest)) {
74
+ fs.mkdirSync(dest, { recursive: true });
75
+ }
76
+ for (const file of fs.readdirSync(src)) {
77
+ const sourcePath = path.join(src, file);
78
+ if (!fs.statSync(sourcePath).isFile())
79
+ continue;
80
+ const targetName = rename?.[file] ?? file;
81
+ fs.copyFileSync(sourcePath, path.join(dest, targetName));
82
+ }
83
+ }
84
+ // ── layout builders ──────────────────────────────────────────────────────────
85
+ /** One flattened `<name><ext>` file per subagent under `subdir`. */
86
+ function flatFile(opts) {
87
+ const readMeta = opts.readMeta ?? metaFrontmatterSkip;
88
+ return {
89
+ dir: (home) => path.join(home, ...opts.subdir),
90
+ write(dir, sub) {
91
+ fs.mkdirSync(dir, { recursive: true });
92
+ fs.writeFileSync(safeJoin(dir, `${sub.name}${opts.ext}`), opts.transform(sub.path));
93
+ },
94
+ names(dir) {
95
+ if (!fs.existsSync(dir))
96
+ return [];
97
+ return fs
98
+ .readdirSync(dir)
99
+ .filter((f) => f.endsWith(opts.ext))
100
+ .map((f) => f.slice(0, -opts.ext.length));
101
+ },
102
+ occupied(dir, name) {
103
+ return [{ path: safeJoin(dir, `${name}${opts.ext}`), kind: 'file' }];
104
+ },
105
+ read(dir, name) {
106
+ const filePath = path.join(dir, `${name}${opts.ext}`);
107
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
108
+ return null;
109
+ const frontmatter = readMeta(filePath, name);
110
+ if (!frontmatter)
111
+ return null;
112
+ return { frontmatter, files: [`${name}${opts.ext}`], path: filePath };
113
+ },
114
+ };
115
+ }
116
+ /** A `<name>/` directory holding one generated `<file>` per subagent. */
117
+ function dirFile(opts) {
118
+ const readMeta = opts.readMeta ?? ((p, name) => metaFrontmatterFallback(p, name));
119
+ return {
120
+ dir: (home) => path.join(home, ...opts.subdir),
121
+ write(dir, sub) {
122
+ const target = safeJoin(dir, sub.name);
123
+ fs.mkdirSync(target, { recursive: true });
124
+ fs.writeFileSync(safeJoin(target, opts.file), opts.transform(sub.path));
125
+ },
126
+ names(dir) {
127
+ if (!fs.existsSync(dir))
128
+ return [];
129
+ return fs
130
+ .readdirSync(dir, { withFileTypes: true })
131
+ .filter((e) => e.isDirectory() && fs.existsSync(path.join(dir, e.name, opts.file)))
132
+ .map((e) => e.name);
133
+ },
134
+ occupied(dir, name) {
135
+ return [{ path: safeJoin(dir, name), kind: 'dir' }];
136
+ },
137
+ read(dir, name) {
138
+ const filePath = path.join(dir, name, opts.file);
139
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
140
+ return null;
141
+ const frontmatter = readMeta(filePath, name);
142
+ if (!frontmatter)
143
+ return null;
144
+ return { frontmatter, files: [opts.file], path: filePath };
145
+ },
146
+ };
147
+ }
148
+ /** Copy the whole source directory to `<name>/`, detected by `marker`. */
149
+ function dirCopy(opts) {
150
+ return {
151
+ dir: (home) => path.join(home, ...opts.subdir),
152
+ write(dir, sub) {
153
+ copyDirWithRename(sub.path, safeJoin(dir, sub.name), opts.rename);
154
+ },
155
+ names(dir) {
156
+ if (!fs.existsSync(dir))
157
+ return [];
158
+ return fs
159
+ .readdirSync(dir, { withFileTypes: true })
160
+ .filter((d) => d.isDirectory() && fs.existsSync(path.join(dir, d.name, opts.marker)))
161
+ .map((d) => d.name);
162
+ },
163
+ occupied(dir, name) {
164
+ return [{ path: safeJoin(dir, name), kind: 'dir' }];
165
+ },
166
+ read(dir, name) {
167
+ const markerPath = path.join(dir, name, opts.marker);
168
+ if (!fs.existsSync(markerPath))
169
+ return null;
170
+ // The marker may lack frontmatter; fall back to the first content line.
171
+ let frontmatter = { name, description: '' };
172
+ const parsed = parseSubagentFrontmatter(markerPath);
173
+ if (parsed) {
174
+ frontmatter = parsed;
175
+ }
176
+ else {
177
+ const content = fs.readFileSync(markerPath, 'utf-8');
178
+ const firstLine = content.split('\n').find((l) => l.trim() && !l.startsWith('#'));
179
+ frontmatter.description = firstLine?.slice(0, 80) || `${name}`;
180
+ }
181
+ const subagentDir = path.join(dir, name);
182
+ const files = fs
183
+ .readdirSync(subagentDir)
184
+ .filter((f) => f.endsWith('.md'))
185
+ .sort();
186
+ return { frontmatter, files, path: subagentDir };
187
+ },
188
+ };
189
+ }
190
+ /**
191
+ * Kimi (genuinely bespoke): each subagent is a `<name>.yaml` + sibling
192
+ * `<name>.system.md`, and a managed `_agents-cli.yaml` parent lists them all for
193
+ * `kimi --agent-file`. The parent index is a cross-item concern, so it is built
194
+ * in `finalize`, not per-item.
195
+ */
196
+ const kimiTarget = {
197
+ dir: (home) => path.join(home, '.kimi-code', 'agents'),
198
+ write(dir, sub) {
199
+ writeKimiSubagentFiles(dir, sub.path, sub.name);
200
+ },
201
+ names(dir) {
202
+ if (!fs.existsSync(dir))
203
+ return [];
204
+ // The parent is `_agents-cli.yaml` (underscore-prefixed, reserved).
205
+ return fs
206
+ .readdirSync(dir)
207
+ .filter((f) => f.endsWith('.yaml') && !f.startsWith('_'))
208
+ .map((f) => f.slice(0, -'.yaml'.length));
209
+ },
210
+ occupied(dir, name) {
211
+ return [
212
+ { path: safeJoin(dir, `${name}.yaml`), kind: 'file' },
213
+ { path: safeJoin(dir, `${name}.system.md`), kind: 'file' },
214
+ ];
215
+ },
216
+ read(dir, name) {
217
+ const yamlPath = path.join(dir, `${name}.yaml`);
218
+ if (!fs.existsSync(yamlPath) || !fs.statSync(yamlPath).isFile())
219
+ return null;
220
+ let description = '';
221
+ try {
222
+ const parsed = yaml.parse(fs.readFileSync(yamlPath, 'utf-8'));
223
+ description = parsed?.agent?.description ?? '';
224
+ }
225
+ catch {
226
+ /* leave description empty */
227
+ }
228
+ const files = [`${name}.yaml`];
229
+ const promptFile = `${name}.system.md`;
230
+ if (fs.existsSync(path.join(dir, promptFile)))
231
+ files.push(promptFile);
232
+ return { frontmatter: { name, description }, files, path: yamlPath };
233
+ },
234
+ finalize(dir, synced) {
235
+ const entries = synced.map((sub) => {
236
+ const fm = parseSubagentFrontmatter(path.join(sub.path, 'AGENT.md'));
237
+ return { name: sub.name, description: fm?.description ?? sub.name, relativePath: `./${sub.name}.yaml` };
238
+ });
239
+ fs.writeFileSync(safeJoin(dir, KIMI_SUBAGENTS_PARENT_FILE), buildKimiSubagentsParentYaml(entries));
240
+ },
241
+ };
242
+ // ── the registry ─────────────────────────────────────────────────────────────
243
+ /**
244
+ * Single source of truth for how each subagents-capable agent stores subagents.
245
+ * The keys MUST match `capableAgents('subagents')` (the `subagents` flag in
246
+ * `agents.ts`): the capability flag is the version gate, this table is the shape.
247
+ */
248
+ export const SUBAGENT_TARGETS = {
249
+ // Tier 1 -- flat markdown, Claude-compatible flatten.
250
+ claude: flatFile({ subdir: ['.claude', 'agents'], ext: '.md', transform: transformSubagentForClaude }),
251
+ gemini: flatFile({ subdir: ['.gemini', 'agents'], ext: '.md', transform: transformSubagentForClaude }),
252
+ grok: flatFile({ subdir: ['.grok', 'agents'], ext: '.md', transform: transformSubagentForClaude }),
253
+ droid: flatFile({ subdir: ['.factory', 'droids'], ext: '.md', transform: transformSubagentForDroid }),
254
+ // Bespoke frontmatter/format, still one flat file.
255
+ codex: flatFile({ subdir: ['.codex', 'agents'], ext: '.toml', transform: transformSubagentForCodex }),
256
+ opencode: flatFile({
257
+ subdir: ['.config', 'opencode', 'agents'],
258
+ ext: '.md',
259
+ transform: transformSubagentForOpenCode,
260
+ readMeta: metaFrontmatterFallback,
261
+ }),
262
+ copilot: flatFile({
263
+ subdir: ['.copilot', 'agents'],
264
+ ext: '.agent.md',
265
+ transform: transformSubagentForCopilot,
266
+ readMeta: metaFrontmatterFallback,
267
+ }),
268
+ cursor: flatFile({
269
+ subdir: ['.cursor', 'agents'],
270
+ ext: '.md',
271
+ transform: transformSubagentForCursor,
272
+ readMeta: metaFrontmatterFallback,
273
+ }),
274
+ forge: flatFile({
275
+ subdir: ['.forge', 'agents'],
276
+ ext: '.md',
277
+ transform: transformSubagentForForge,
278
+ readMeta: metaFrontmatterFallback,
279
+ }),
280
+ kiro: flatFile({
281
+ subdir: ['.kiro', 'agents'],
282
+ ext: '.json',
283
+ transform: transformSubagentForKiro,
284
+ readMeta: metaJson,
285
+ }),
286
+ goose: flatFile({
287
+ subdir: ['.config', 'goose', 'agents'],
288
+ ext: '.yaml',
289
+ transform: transformSubagentForGoose,
290
+ readMeta: metaGooseYaml,
291
+ }),
292
+ // Directory layouts.
293
+ antigravity: dirFile({
294
+ subdir: ['.gemini', 'config', 'agents'],
295
+ file: 'agent.md',
296
+ transform: transformSubagentForAntigravity,
297
+ }),
298
+ openclaw: dirCopy({ subdir: ['.openclaw'], marker: 'AGENTS.md', rename: { 'AGENT.md': 'AGENTS.md' } }),
299
+ // Bespoke multi-file + parent index.
300
+ kimi: kimiTarget,
301
+ };
302
+ /** The registry entry for `agent`, or undefined if it stores no subagents. */
303
+ export function subagentTarget(agent) {
304
+ return SUBAGENT_TARGETS[agent];
305
+ }
306
+ // ── generic engine (zero per-agent branches) ─────────────────────────────────
307
+ /**
308
+ * Materialize central subagent `sub` into `home` for `agent`. Returns whether a
309
+ * write happened (false when the agent has no registry entry). Throws only on
310
+ * unexpected fs errors -- bulk callers wrap per-item.
311
+ */
312
+ export function writeSubagentToHome(agent, home, sub) {
313
+ const target = SUBAGENT_TARGETS[agent];
314
+ if (!target)
315
+ return false;
316
+ target.write(target.dir(home), sub);
317
+ return true;
318
+ }
319
+ /** Installed subagent names for `agent` under `home` (detector + orphan diff). */
320
+ export function listInstalledSubagentNames(agent, home) {
321
+ const target = SUBAGENT_TARGETS[agent];
322
+ if (!target)
323
+ return [];
324
+ return target.names(target.dir(home));
325
+ }
326
+ /**
327
+ * Rich listing of subagents installed for `agent` under `home`, with parsed
328
+ * metadata. Enumerates names, then reads each -- entries whose metadata is
329
+ * unreadable (per the target's reader) are dropped.
330
+ */
331
+ export function listInstalledSubagentsRich(agent, home) {
332
+ const target = SUBAGENT_TARGETS[agent];
333
+ if (!target)
334
+ return [];
335
+ const dir = target.dir(home);
336
+ const out = [];
337
+ for (const name of target.names(dir)) {
338
+ const meta = target.read(dir, name);
339
+ if (!meta)
340
+ continue;
341
+ out.push({ name, path: meta.path, files: meta.files, frontmatter: meta.frontmatter });
342
+ }
343
+ return out;
344
+ }
345
+ /**
346
+ * Remove subagent `name` for `agent` from `home` (hard delete). No-op success
347
+ * when the agent has no registry entry or nothing is installed.
348
+ */
349
+ export function removeSubagentFromHome(agent, home, name) {
350
+ const target = SUBAGENT_TARGETS[agent];
351
+ if (!target)
352
+ return { success: true };
353
+ try {
354
+ for (const entry of target.occupied(target.dir(home), name)) {
355
+ if (!fs.existsSync(entry.path))
356
+ continue;
357
+ if (entry.kind === 'dir')
358
+ fs.rmSync(entry.path, { recursive: true, force: true });
359
+ else
360
+ fs.unlinkSync(entry.path);
361
+ }
362
+ return { success: true };
363
+ }
364
+ catch (err) {
365
+ return { success: false, error: String(err) };
366
+ }
367
+ }
368
+ /**
369
+ * Soft-delete subagent `name` for `agent` from `home` into `trashDir`, stamping
370
+ * each moved entry. Files land as `<basename>.<stamp>`, directories as
371
+ * `<stamp>/`. No-op success when nothing is installed.
372
+ */
373
+ export function trashSubagentFromHome(agent, home, name, trashDir, stamp) {
374
+ const target = SUBAGENT_TARGETS[agent];
375
+ if (!target)
376
+ return { success: true };
377
+ try {
378
+ const present = target.occupied(target.dir(home), name).filter((e) => fs.existsSync(e.path));
379
+ if (present.length > 0) {
380
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
381
+ for (const entry of present) {
382
+ const dest = entry.kind === 'dir'
383
+ ? path.join(trashDir, stamp)
384
+ : path.join(trashDir, `${path.basename(entry.path)}.${stamp}`);
385
+ fs.renameSync(entry.path, dest);
386
+ }
387
+ }
388
+ return { success: true };
389
+ }
390
+ catch (err) {
391
+ return { success: false, error: err.message };
392
+ }
393
+ }
@@ -67,7 +67,7 @@ export declare function transformSubagentForDroid(subagentDir: string): string;
67
67
  * sections, `color` dropped), so this is an alias of transformSubagentForDroid.
68
68
  * See GitHub docs for custom agents.
69
69
  */
70
- export declare const transformSubagentForCopilot: typeof transformSubagentForDroid;
70
+ export declare function transformSubagentForCopilot(subagentDir: string): string;
71
71
  /**
72
72
  * Transform a subagent into a Cursor CLI custom subagent `.md` file.
73
73
  *
@@ -78,7 +78,7 @@ export declare const transformSubagentForCopilot: typeof transformSubagentForDro
78
78
  * an alias of transformSubagentForDroid, same as Copilot.
79
79
  * See https://cursor.com/docs/subagents.
80
80
  */
81
- export declare const transformSubagentForCursor: typeof transformSubagentForDroid;
81
+ export declare function transformSubagentForCursor(subagentDir: string): string;
82
82
  /**
83
83
  * Transform a subagent into a ForgeCode custom subagent `.md` file.
84
84
  *
@@ -88,7 +88,7 @@ export declare const transformSubagentForCursor: typeof transformSubagentForDroi
88
88
  * no `color` field, so this is an alias of transformSubagentForDroid, same as
89
89
  * Copilot/Cursor. See https://forgecode.dev/docs/agent-definition-guide/.
90
90
  */
91
- export declare const transformSubagentForForge: typeof transformSubagentForDroid;
91
+ export declare function transformSubagentForForge(subagentDir: string): string;
92
92
  /**
93
93
  * Transform a subagent into Antigravity's custom-agent markdown shape.
94
94
  *
@@ -197,11 +197,11 @@ export declare function removeSubagentFromAgent(subagentName: string, agent: Age
197
197
  */
198
198
  export declare function subagentContentMatches(installedDir: string, sourceDir: string): boolean;
199
199
  /**
200
- * List subagents installed to a specific agent's home
201
- * Claude/Gemini/Grok: scans ~/.{agent}/agents/{name}.md
202
- * Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
203
- * Kiro: scans ~/.kiro/agents/{name}.json
204
- * OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
200
+ * List subagents installed to a specific agent's home.
201
+ *
202
+ * Layout (target dir, file/dir shape, metadata reader) is declared once per
203
+ * agent in the subagent registry; this is a thin wrapper over the generic
204
+ * enumerator so a new agent needs no arm here -- just a registry entry.
205
205
  */
206
206
  export declare function listSubagentsForAgent(agentId: AgentId, home: string): InstalledSubagent[];
207
207
  export interface VersionSubagentDiff {