@phnx-labs/agents-cli 1.20.4 → 1.20.6

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 (207) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +49 -18
  3. package/dist/commands/browser.js +31 -4
  4. package/dist/commands/cli.js +1 -1
  5. package/dist/commands/cloud.js +1 -1
  6. package/dist/commands/commands.js +2 -0
  7. package/dist/commands/computer.js +10 -2
  8. package/dist/commands/defaults.d.ts +7 -0
  9. package/dist/commands/defaults.js +89 -0
  10. package/dist/commands/doctor.js +1 -1
  11. package/dist/commands/exec.js +73 -19
  12. package/dist/commands/hooks.js +6 -6
  13. package/dist/commands/inspect.d.ts +26 -0
  14. package/dist/commands/inspect.js +590 -0
  15. package/dist/commands/mcp.js +17 -16
  16. package/dist/commands/models.js +1 -1
  17. package/dist/commands/packages.js +6 -4
  18. package/dist/commands/permissions.js +13 -12
  19. package/dist/commands/plugins.d.ts +13 -0
  20. package/dist/commands/plugins.js +100 -11
  21. package/dist/commands/prune.js +3 -2
  22. package/dist/commands/pull.d.ts +12 -5
  23. package/dist/commands/pull.js +26 -422
  24. package/dist/commands/push.d.ts +14 -0
  25. package/dist/commands/push.js +30 -0
  26. package/dist/commands/repo.d.ts +1 -1
  27. package/dist/commands/repo.js +155 -112
  28. package/dist/commands/resource-view.d.ts +2 -0
  29. package/dist/commands/resource-view.js +12 -3
  30. package/dist/commands/routines.js +32 -7
  31. package/dist/commands/rules.js +4 -4
  32. package/dist/commands/secrets.js +46 -9
  33. package/dist/commands/sessions.js +1 -0
  34. package/dist/commands/setup.d.ts +3 -3
  35. package/dist/commands/setup.js +17 -17
  36. package/dist/commands/skills.js +6 -5
  37. package/dist/commands/subagents.js +5 -4
  38. package/dist/commands/sync.d.ts +18 -5
  39. package/dist/commands/sync.js +251 -65
  40. package/dist/commands/teams.js +109 -11
  41. package/dist/commands/tmux.d.ts +25 -0
  42. package/dist/commands/tmux.js +415 -0
  43. package/dist/commands/trash.d.ts +2 -2
  44. package/dist/commands/trash.js +1 -1
  45. package/dist/commands/versions.js +2 -2
  46. package/dist/commands/view.d.ts +12 -1
  47. package/dist/commands/view.js +128 -40
  48. package/dist/commands/workflows.js +4 -3
  49. package/dist/commands/worktree.d.ts +4 -5
  50. package/dist/commands/worktree.js +4 -4
  51. package/dist/index.js +106 -41
  52. package/dist/lib/agents.d.ts +23 -10
  53. package/dist/lib/agents.js +88 -25
  54. package/dist/lib/auto-pull-worker.d.ts +1 -1
  55. package/dist/lib/auto-pull-worker.js +2 -2
  56. package/dist/lib/auto-pull.d.ts +1 -1
  57. package/dist/lib/auto-pull.js +1 -1
  58. package/dist/lib/beta.d.ts +1 -1
  59. package/dist/lib/beta.js +1 -1
  60. package/dist/lib/browser/chrome.d.ts +10 -0
  61. package/dist/lib/browser/chrome.js +84 -3
  62. package/dist/lib/capabilities.js +2 -0
  63. package/dist/lib/commands.d.ts +28 -1
  64. package/dist/lib/commands.js +125 -20
  65. package/dist/lib/doctor-diff.js +2 -2
  66. package/dist/lib/exec.d.ts +14 -0
  67. package/dist/lib/exec.js +59 -5
  68. package/dist/lib/fuzzy.d.ts +12 -2
  69. package/dist/lib/fuzzy.js +29 -4
  70. package/dist/lib/git.js +8 -1
  71. package/dist/lib/hooks.d.ts +2 -2
  72. package/dist/lib/hooks.js +97 -10
  73. package/dist/lib/mcp.js +32 -2
  74. package/dist/lib/migrate.d.ts +51 -0
  75. package/dist/lib/migrate.js +233 -5
  76. package/dist/lib/models.js +62 -15
  77. package/dist/lib/permissions.d.ts +59 -2
  78. package/dist/lib/permissions.js +299 -7
  79. package/dist/lib/plugin-marketplace.d.ts +98 -40
  80. package/dist/lib/plugin-marketplace.js +196 -93
  81. package/dist/lib/plugins.d.ts +21 -4
  82. package/dist/lib/plugins.js +130 -49
  83. package/dist/lib/profiles-presets.js +12 -12
  84. package/dist/lib/project-launch.d.ts +70 -0
  85. package/dist/lib/project-launch.js +404 -0
  86. package/dist/lib/pty-client.js +1 -1
  87. package/dist/lib/pty-server.d.ts +1 -1
  88. package/dist/lib/pty-server.js +8 -5
  89. package/dist/lib/refresh.d.ts +26 -0
  90. package/dist/lib/refresh.js +315 -0
  91. package/dist/lib/resource-patterns.d.ts +1 -1
  92. package/dist/lib/resource-patterns.js +1 -1
  93. package/dist/lib/resources/commands.js +2 -2
  94. package/dist/lib/resources/hooks.d.ts +1 -1
  95. package/dist/lib/resources/hooks.js +1 -1
  96. package/dist/lib/resources/mcp.d.ts +1 -1
  97. package/dist/lib/resources/mcp.js +5 -6
  98. package/dist/lib/resources/permissions.js +5 -2
  99. package/dist/lib/resources/rules.js +3 -2
  100. package/dist/lib/resources/skills.js +3 -2
  101. package/dist/lib/resources/types.d.ts +1 -1
  102. package/dist/lib/resources.d.ts +2 -0
  103. package/dist/lib/resources.js +4 -3
  104. package/dist/lib/rotate.d.ts +1 -1
  105. package/dist/lib/rotate.js +7 -19
  106. package/dist/lib/routines.d.ts +16 -4
  107. package/dist/lib/routines.js +67 -17
  108. package/dist/lib/rules/compile.js +22 -10
  109. package/dist/lib/rules/rules.js +3 -3
  110. package/dist/lib/run-config.d.ts +9 -0
  111. package/dist/lib/run-config.js +35 -0
  112. package/dist/lib/run-defaults.d.ts +42 -0
  113. package/dist/lib/run-defaults.js +180 -0
  114. package/dist/lib/runner.js +16 -3
  115. package/dist/lib/scheduler.js +15 -1
  116. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  117. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  118. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  119. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  120. package/dist/lib/secrets/install-helper.d.ts +11 -3
  121. package/dist/lib/secrets/install-helper.js +48 -6
  122. package/dist/lib/secrets/linux.d.ts +56 -9
  123. package/dist/lib/secrets/linux.js +327 -59
  124. package/dist/lib/session/db.js +15 -2
  125. package/dist/lib/session/discover.js +118 -3
  126. package/dist/lib/session/parse.js +3 -0
  127. package/dist/lib/session/types.d.ts +1 -1
  128. package/dist/lib/session/types.js +1 -1
  129. package/dist/lib/shims.d.ts +18 -9
  130. package/dist/lib/shims.js +133 -50
  131. package/dist/lib/skills.d.ts +1 -1
  132. package/dist/lib/skills.js +10 -9
  133. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/commands.js +46 -0
  135. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/hooks.js +44 -0
  137. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  138. package/dist/lib/staleness/detectors/mcp.js +31 -0
  139. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/permissions.js +201 -0
  141. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  142. package/dist/lib/staleness/detectors/plugins.js +23 -0
  143. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  144. package/dist/lib/staleness/detectors/rules.js +34 -0
  145. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  146. package/dist/lib/staleness/detectors/skills.js +71 -0
  147. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  148. package/dist/lib/staleness/detectors/subagents.js +50 -0
  149. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  150. package/dist/lib/staleness/detectors/types.js +1 -0
  151. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  152. package/dist/lib/staleness/detectors/workflows.js +28 -0
  153. package/dist/lib/staleness/registry.d.ts +26 -0
  154. package/dist/lib/staleness/registry.js +123 -0
  155. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  156. package/dist/lib/staleness/writers/commands.js +111 -0
  157. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  158. package/dist/lib/staleness/writers/hooks.js +47 -0
  159. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  160. package/dist/lib/staleness/writers/kinds.js +15 -0
  161. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  162. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  163. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  164. package/dist/lib/staleness/writers/mcp.js +19 -0
  165. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  166. package/dist/lib/staleness/writers/permissions.js +26 -0
  167. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  168. package/dist/lib/staleness/writers/plugins.js +31 -0
  169. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  170. package/dist/lib/staleness/writers/rules.js +55 -0
  171. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  172. package/dist/lib/staleness/writers/skills.js +81 -0
  173. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  174. package/dist/lib/staleness/writers/sources.js +72 -0
  175. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  176. package/dist/lib/staleness/writers/subagents.js +53 -0
  177. package/dist/lib/staleness/writers/types.d.ts +36 -0
  178. package/dist/lib/staleness/writers/types.js +1 -0
  179. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  180. package/dist/lib/staleness/writers/workflows.js +31 -0
  181. package/dist/lib/state.d.ts +34 -11
  182. package/dist/lib/state.js +58 -13
  183. package/dist/lib/subagents.d.ts +0 -2
  184. package/dist/lib/subagents.js +6 -6
  185. package/dist/lib/teams/agents.js +1 -1
  186. package/dist/lib/teams/api.d.ts +67 -0
  187. package/dist/lib/teams/api.js +78 -0
  188. package/dist/lib/teams/parsers.d.ts +1 -1
  189. package/dist/lib/tmux/binary.d.ts +67 -0
  190. package/dist/lib/tmux/binary.js +141 -0
  191. package/dist/lib/tmux/index.d.ts +8 -0
  192. package/dist/lib/tmux/index.js +8 -0
  193. package/dist/lib/tmux/paths.d.ts +17 -0
  194. package/dist/lib/tmux/paths.js +30 -0
  195. package/dist/lib/tmux/session.d.ts +122 -0
  196. package/dist/lib/tmux/session.js +305 -0
  197. package/dist/lib/types.d.ts +73 -13
  198. package/dist/lib/types.js +1 -1
  199. package/dist/lib/usage.js +1 -1
  200. package/dist/lib/versions.d.ts +4 -4
  201. package/dist/lib/versions.js +138 -496
  202. package/dist/lib/workflows.d.ts +2 -4
  203. package/dist/lib/workflows.js +3 -4
  204. package/package.json +6 -3
  205. package/scripts/postinstall.js +16 -63
  206. package/dist/commands/status.d.ts +0 -9
  207. package/dist/commands/status.js +0 -25
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Launch-time project compile. Invoked by the agent shim's hot path (via
3
+ * `agents sync --launch`) between version resolve and binary exec.
4
+ *
5
+ * Three responsibilities, all skip-fast when there's nothing to do:
6
+ *
7
+ * 1. Compile project rules from `<cwd>/.agents/rules/` into `<cwd>/AGENTS.md`
8
+ * (+ per-agent symlinks). Delegates to compileRulesForProject, which is
9
+ * the same helper management-side `agents sync` uses.
10
+ *
11
+ * 2. Mirror project resources from `<cwd>/.agents/{subagents,commands,skills}`
12
+ * into the agent's workspace-local discovery dirs (`<cwd>/.claude/agents/`,
13
+ * `<cwd>/.claude/commands/`, `<cwd>/.claude/skills/`). File-level symlinks
14
+ * so edits in the source dir are live without re-sync. Skips any dest
15
+ * entry that already exists and isn't one of our symlinks (don't-clobber).
16
+ * v1: claude-only. Other agents have varying workspace conventions (amp
17
+ * uses `~/.config/amp`, antigravity uses `~/.gemini/antigravity-cli`,
18
+ * codex/gemini/cursor lack subagent support entirely) — they're follow-up
19
+ * material. NOTE: `.mcp.json` is intentionally NOT auto-symlinked from
20
+ * the launch path — that's a supply-chain surface (cloning a hostile
21
+ * repo would auto-register an attacker MCP server). Belongs to full
22
+ * `agents sync` with explicit opt-in, not the hot path.
23
+ *
24
+ * 3. Synthesize four scope-grouped plugin marketplaces under the version's
25
+ * `<versionHome>/.{agent}/plugins/marketplaces/` (for plugin-capable agents):
26
+ * - agents-cli ← ~/.agents/plugins/* (user scope, legacy name)
27
+ * - agents-system ← ~/.agents/.system/plugins/*
28
+ * - extras-<alias> ← ~/.agents-<alias>/plugins/* (per enabled extra)
29
+ * - agents-project ← <cwd>/.agents/plugins/*
30
+ * Each plugin is copied in (skip-fast via mtime cache), the marketplace
31
+ * catalog is rewritten only when contents change, and the marketplace is
32
+ * registered in known_marketplaces.json. Upstream marketplaces like
33
+ * "claude-plugins-official" are left untouched. Project- and extras-
34
+ * scope plugins do NOT auto-enable exec surfaces (.mcp.json, hooks, bin/,
35
+ * scripts/) — user must explicitly `agents plugins enable` them.
36
+ *
37
+ * Heavy work (version-home reconciliation, hook registration, MCP merging)
38
+ * stays in `agents sync` without --launch and is NOT touched here. The
39
+ * launch path is filesystem-only and skip-fast: sub-50ms when no source
40
+ * has changed, scales linearly only with newly-modified plugins on the
41
+ * change path.
42
+ */
43
+ import * as crypto from 'crypto';
44
+ import * as fs from 'fs';
45
+ import * as os from 'os';
46
+ import * as path from 'path';
47
+ import { supports } from './capabilities.js';
48
+ import { getEnabledExtraRepos, getExtraPluginsDir, getPluginsDir, getProjectAgentsDir, getProjectPluginsDir, getSystemPluginsDir, } from './state.js';
49
+ import { getVersionHomePath } from './versions.js';
50
+ import { compileRulesForProject } from './rules/compile.js';
51
+ import { discoverPluginsInDir, hasPluginExecSurfaces, inspectPluginCapabilities } from './plugins.js';
52
+ import { MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, addPluginToSettings, copyPluginToMarketplace, marketplaceNameFor, marketplaceRoot, pluginInstallDir, registerMarketplace, removePluginFromSettings, syncMarketplaceManifest, } from './plugin-marketplace.js';
53
+ /**
54
+ * Run the launch-time project compile. Safe to call on every agent launch:
55
+ * each step is idempotent and skips when its inputs are missing.
56
+ *
57
+ * After a successful run, touches the shim-side skip-fast sentinel at
58
+ * `~/.agents/.cache/launch-sync/<agent>@<version>@<projectslug>` so the next
59
+ * shim invocation can skip the node spawn entirely when no source dir is
60
+ * newer than the sentinel (shim schema v17+).
61
+ */
62
+ export function runLaunchSync(opts) {
63
+ const result = {
64
+ rulesCompiled: false,
65
+ workspaceLinks: 0,
66
+ workspaceSkipped: [],
67
+ marketplaces: {},
68
+ };
69
+ // Step 1: project rules
70
+ try {
71
+ const r = compileRulesForProject(opts.cwd);
72
+ result.rulesCompiled = r.compiled;
73
+ }
74
+ catch {
75
+ // Don't fail launch on a malformed project rules.yaml.
76
+ }
77
+ // Step 2: workspace resource mirror
78
+ const mirror = mirrorWorkspaceResources(opts.cwd, opts.agent);
79
+ result.workspaceLinks = mirror.links;
80
+ result.workspaceSkipped = mirror.skipped;
81
+ // Step 3: scoped plugin marketplaces
82
+ result.marketplaces = synthesizeScopedMarketplaces(opts.agent, opts.version, opts.cwd);
83
+ // Touch the shim's skip-fast sentinel. Best-effort — if this fails the
84
+ // shim just won't skip on the next launch, which is correct fallback.
85
+ touchLaunchSentinel(opts.agent, opts.version, opts.cwd);
86
+ return result;
87
+ }
88
+ /**
89
+ * Path of the shim's skip-fast sentinel for this (agent, version, cwd) tuple.
90
+ * Must match the SHIM-SIDE format in src/lib/shims.ts (PROJECT_SLUG derivation):
91
+ * slug = PWD with `/` → `_` and ` ` → `_`
92
+ *
93
+ * Cache leak note: this dir accumulates one zero-byte file per
94
+ * (agent, version, project) tuple ever launched. Disk impact is negligible
95
+ * (inodes only). A periodic GC belongs in `agents prune` — follow-up.
96
+ */
97
+ function launchSentinelPath(agent, version, cwd) {
98
+ const slug = cwd.replace(/\//g, '_').replace(/ /g, '_');
99
+ // Prefer $HOME (respects test overrides + matches bash's $HOME expansion in
100
+ // the shim), fall back to os.homedir() so the lookup never resolves to '/'
101
+ // if HOME is somehow unset.
102
+ const home = process.env.HOME || os.homedir();
103
+ return path.join(home, '.agents', '.cache', 'launch-sync', `${agent}@${version}@${slug}`);
104
+ }
105
+ function touchLaunchSentinel(agent, version, cwd) {
106
+ try {
107
+ const sentinel = launchSentinelPath(agent, version, cwd);
108
+ fs.mkdirSync(path.dirname(sentinel), { recursive: true });
109
+ // Empty content — purely an mtime carrier for the shim's `[ -nt ]` compare.
110
+ fs.writeFileSync(sentinel, '');
111
+ }
112
+ catch {
113
+ // best-effort
114
+ }
115
+ }
116
+ const CLAUDE_MIRROR_PLANS = [
117
+ { srcSubdir: 'subagents', destSubdir: 'agents', entriesAreDirs: false },
118
+ { srcSubdir: 'commands', destSubdir: 'commands', entriesAreDirs: false },
119
+ { srcSubdir: 'skills', destSubdir: 'skills', entriesAreDirs: true },
120
+ ];
121
+ function mirrorWorkspaceResources(cwd, agent) {
122
+ // v1: claude-only. Other agents have workspace conventions we haven't
123
+ // mapped (amp: ~/.config/amp; antigravity: ~/.gemini/antigravity-cli;
124
+ // codex/gemini/cursor: no subagent support per capabilities). Adding them
125
+ // requires per-agent workspaceDirName + capability gates — follow-up.
126
+ if (agent !== 'claude')
127
+ return { links: 0, skipped: [] };
128
+ const projectAgentsDir = getProjectAgentsDir(cwd);
129
+ if (!projectAgentsDir)
130
+ return { links: 0, skipped: [] };
131
+ const agentWorkspaceDir = path.join(cwd, '.claude');
132
+ const projectAgentsResolved = (() => {
133
+ try {
134
+ return fs.realpathSync(projectAgentsDir);
135
+ }
136
+ catch {
137
+ return path.resolve(projectAgentsDir);
138
+ }
139
+ })();
140
+ let links = 0;
141
+ const skipped = [];
142
+ // Mirror subagents / commands / skills. mcp.json is intentionally excluded
143
+ // — see header doc, it's a supply-chain surface.
144
+ for (const plan of CLAUDE_MIRROR_PLANS) {
145
+ const srcDir = path.join(projectAgentsDir, plan.srcSubdir);
146
+ if (!fs.existsSync(srcDir))
147
+ continue;
148
+ const destDir = path.join(agentWorkspaceDir, plan.destSubdir);
149
+ fs.mkdirSync(destDir, { recursive: true });
150
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (entry.name.startsWith('.'))
153
+ continue;
154
+ if (plan.entriesAreDirs && !entry.isDirectory())
155
+ continue;
156
+ if (!plan.entriesAreDirs && !entry.isFile() && !entry.isSymbolicLink())
157
+ continue;
158
+ const srcPath = path.join(srcDir, entry.name);
159
+ const destPath = path.join(destDir, entry.name);
160
+ if (replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved)) {
161
+ links += 1;
162
+ }
163
+ else {
164
+ skipped.push(path.relative(cwd, destPath));
165
+ }
166
+ }
167
+ }
168
+ return { links, skipped };
169
+ }
170
+ /**
171
+ * Create or refresh a symlink at `destPath` pointing at `srcPath`. Returns
172
+ * true if we wrote (or already had) the link, false if we skipped because
173
+ * the destination is user-owned (regular file, directory, or symlink pointing
174
+ * outside the project's `.agents/` tree, or a dangling symlink — treated as
175
+ * user state we don't yet understand).
176
+ *
177
+ * Skip-fast: if the destination is already a symlink resolving to the
178
+ * project-agents tree AND its target matches srcPath, no write happens.
179
+ */
180
+ function replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved) {
181
+ let destLstat = null;
182
+ try {
183
+ destLstat = fs.lstatSync(destPath);
184
+ }
185
+ catch { /* missing — write fresh */ }
186
+ if (destLstat) {
187
+ if (!destLstat.isSymbolicLink()) {
188
+ return false;
189
+ }
190
+ let destTargetReal = null;
191
+ try {
192
+ destTargetReal = fs.realpathSync(destPath);
193
+ }
194
+ catch { /* dangling */ }
195
+ // Dangling symlink → user-owned in-progress state; do not clobber.
196
+ if (destTargetReal === null) {
197
+ return false;
198
+ }
199
+ if (destTargetReal !== projectAgentsResolved && !destTargetReal.startsWith(projectAgentsResolved + path.sep)) {
200
+ return false;
201
+ }
202
+ let srcReal = null;
203
+ try {
204
+ srcReal = fs.realpathSync(srcPath);
205
+ }
206
+ catch { /* src vanished mid-launch */ }
207
+ if (srcReal !== null && destTargetReal === srcReal) {
208
+ return true; // already correct
209
+ }
210
+ try {
211
+ fs.unlinkSync(destPath);
212
+ }
213
+ catch { /* ignore */ }
214
+ }
215
+ try {
216
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
217
+ fs.symlinkSync(srcPath, destPath);
218
+ }
219
+ catch {
220
+ return false;
221
+ }
222
+ return true;
223
+ }
224
+ function makeScope(spec, pluginsDir, autoEnableExecSurfaces, precedence) {
225
+ return { spec, marketplaceName: marketplaceNameFor(spec), pluginsDir, autoEnableExecSurfaces, precedence };
226
+ }
227
+ function collectPluginScopes(cwd) {
228
+ const scopes = [];
229
+ // Precedence: project > extras > user > system. Same direction the rules
230
+ // composition uses (project layer shadows base layers).
231
+ scopes.push(makeScope({ kind: 'system', root: getSystemPluginsDir() }, getSystemPluginsDir(), true, 0));
232
+ scopes.push(makeScope({ kind: 'user' }, getPluginsDir(), true, 1));
233
+ for (const extra of getEnabledExtraRepos()) {
234
+ const root = getExtraPluginsDir(extra.alias);
235
+ scopes.push(makeScope({ kind: 'extra', alias: extra.alias, root }, root, false, 2));
236
+ }
237
+ const projectPluginsDir = getProjectPluginsDir(cwd);
238
+ if (projectPluginsDir) {
239
+ scopes.push(makeScope({ kind: 'project', root: projectPluginsDir }, projectPluginsDir, false, 3));
240
+ }
241
+ return scopes;
242
+ }
243
+ function synthesizeScopedMarketplaces(agent, version, cwd) {
244
+ const result = {};
245
+ if (!supports(agent, 'plugins', version).ok)
246
+ return result;
247
+ let versionHome;
248
+ try {
249
+ versionHome = getVersionHomePath(agent, version);
250
+ }
251
+ catch {
252
+ return result;
253
+ }
254
+ if (!fs.existsSync(versionHome))
255
+ return result;
256
+ // First pass: resolve cross-scope plugin name collisions by precedence.
257
+ // For each plugin name, the scope with the highest precedence wins; the
258
+ // plugin is installed only into that scope's marketplace.
259
+ const winner = new Map();
260
+ for (const scope of collectPluginScopes(cwd)) {
261
+ if (!fs.existsSync(scope.pluginsDir))
262
+ continue;
263
+ for (const plugin of discoverPluginsInDir(scope.pluginsDir)) {
264
+ const existing = winner.get(plugin.name);
265
+ if (!existing || scope.precedence > existing.scope.precedence) {
266
+ winner.set(plugin.name, { scope, plugin });
267
+ }
268
+ }
269
+ }
270
+ if (winner.size === 0)
271
+ return result;
272
+ // Group winners by their winning scope and synthesize one marketplace per
273
+ // scope. Skip-fast: scope hash sentinel short-circuits unchanged scopes.
274
+ const byScope = new Map();
275
+ for (const { scope, plugin } of winner.values()) {
276
+ let bucket = byScope.get(scope.marketplaceName);
277
+ if (!bucket) {
278
+ bucket = { scope, plugins: [] };
279
+ byScope.set(scope.marketplaceName, bucket);
280
+ }
281
+ bucket.plugins.push(plugin);
282
+ }
283
+ for (const { scope, plugins } of byScope.values()) {
284
+ plugins.sort((a, b) => a.name.localeCompare(b.name));
285
+ const installed = installScope(agent, versionHome, scope, plugins);
286
+ if (installed.length > 0)
287
+ result[scope.marketplaceName] = installed;
288
+ }
289
+ // Sweep any orphaned `<plugin>@*` keys whose plugin name is now owned by a
290
+ // different scope (e.g. plugin moved from user to project). Without this,
291
+ // the OLD scope's enabledPlugins key stays set forever, double-enabling.
292
+ pruneLosingScopeEnables(agent, versionHome, winner);
293
+ return result;
294
+ }
295
+ function installScope(agent, versionHome, scope, plugins) {
296
+ const newHash = computeScopeHash(plugins);
297
+ const sentinelPath = path.join(marketplaceRoot(scope.spec, agent, versionHome), '.agents-launch-sync');
298
+ const existingHash = readScopeHash(sentinelPath);
299
+ if (existingHash === newHash) {
300
+ // Nothing changed since last launch — fast path. Verify the manifest
301
+ // dir still exists; if a user blew it away, force a re-sync.
302
+ if (fs.existsSync(path.dirname(sentinelPath))) {
303
+ return plugins.map((p) => p.name);
304
+ }
305
+ }
306
+ const installed = [];
307
+ for (const plugin of plugins) {
308
+ try {
309
+ copyPluginToMarketplace(plugin, scope.spec, agent, versionHome);
310
+ installed.push(plugin.name);
311
+ }
312
+ catch {
313
+ // Individual plugin copy failure — keep going on the others.
314
+ }
315
+ }
316
+ if (installed.length === 0)
317
+ return [];
318
+ syncMarketplaceManifest(scope.spec, agent, versionHome);
319
+ registerMarketplace(scope.spec, agent, versionHome);
320
+ // Enable each plugin in settings unless the scope withholds auto-enable for
321
+ // exec surfaces (project + extras) AND the plugin actually ships any.
322
+ for (const plugin of plugins) {
323
+ if (!installed.includes(plugin.name))
324
+ continue;
325
+ if (!scope.autoEnableExecSurfaces && hasPluginExecSurfaces(inspectPluginCapabilities(plugin.root)))
326
+ continue;
327
+ addPluginToSettings(plugin.name, scope.marketplaceName, agent, versionHome);
328
+ }
329
+ writeScopeHash(sentinelPath, newHash);
330
+ return installed;
331
+ }
332
+ function pruneLosingScopeEnables(agent, versionHome, winner) {
333
+ const ourScopeNames = new Set([
334
+ SYSTEM_MARKETPLACE_NAME,
335
+ MARKETPLACE_NAME,
336
+ PROJECT_MARKETPLACE_NAME,
337
+ ...getEnabledExtraRepos().map((e) => marketplaceNameFor({ kind: 'extra', alias: e.alias, root: getExtraPluginsDir(e.alias) })),
338
+ ]);
339
+ for (const [name, { scope: winningScope }] of winner) {
340
+ for (const candidateScope of ourScopeNames) {
341
+ if (candidateScope === winningScope.marketplaceName)
342
+ continue;
343
+ removePluginFromSettings(name, candidateScope, agent, versionHome);
344
+ }
345
+ }
346
+ }
347
+ /**
348
+ * Hash the plugin set so we can skip-fast when nothing changed since the
349
+ * last launch. Includes the source path (catches moves), the .claude-plugin/
350
+ * plugin.json content (catches metadata edits), and the file-mtime+size
351
+ * fingerprint of every file in the plugin (catches code edits).
352
+ */
353
+ function computeScopeHash(plugins) {
354
+ const hash = crypto.createHash('sha256');
355
+ for (const plugin of [...plugins].sort((a, b) => a.name.localeCompare(b.name))) {
356
+ hash.update(`${plugin.name}\0${plugin.root}\0`);
357
+ fingerprintDir(plugin.root, hash);
358
+ hash.update('\0SEP\0');
359
+ }
360
+ return hash.digest('hex');
361
+ }
362
+ function fingerprintDir(dir, hash) {
363
+ let entries;
364
+ try {
365
+ entries = fs.readdirSync(dir, { withFileTypes: true });
366
+ }
367
+ catch {
368
+ return;
369
+ }
370
+ entries.sort((a, b) => a.name.localeCompare(b.name));
371
+ for (const entry of entries) {
372
+ if (entry.name === 'node_modules' || entry.name === '.git')
373
+ continue;
374
+ const abs = path.join(dir, entry.name);
375
+ if (entry.isDirectory()) {
376
+ hash.update(`D ${entry.name}\n`);
377
+ fingerprintDir(abs, hash);
378
+ }
379
+ else {
380
+ try {
381
+ const stat = fs.lstatSync(abs);
382
+ hash.update(`F ${entry.name} ${stat.size} ${stat.mtimeMs}\n`);
383
+ }
384
+ catch { /* race during launch — skip */ }
385
+ }
386
+ }
387
+ }
388
+ function readScopeHash(sentinelPath) {
389
+ try {
390
+ return fs.readFileSync(sentinelPath, 'utf-8').trim();
391
+ }
392
+ catch {
393
+ return null;
394
+ }
395
+ }
396
+ function writeScopeHash(sentinelPath, hash) {
397
+ try {
398
+ fs.mkdirSync(path.dirname(sentinelPath), { recursive: true });
399
+ fs.writeFileSync(sentinelPath, hash + '\n');
400
+ }
401
+ catch { /* best-effort; missing sentinel just means next launch does full work */ }
402
+ }
403
+ // Re-export for the test's structural assertions; not used internally.
404
+ export { pluginInstallDir };
@@ -57,7 +57,7 @@ async function ensureServer() {
57
57
  }
58
58
  await new Promise(r => setTimeout(r, 100));
59
59
  }
60
- throw new Error('PTY server failed to start within 5 seconds. Check ~/.agents-system/helpers/pty/logs.jsonl');
60
+ throw new Error('PTY server failed to start within 5 seconds. Check ~/.agents/.system/helpers/pty/logs.jsonl');
61
61
  }
62
62
  function getServerSpawnArgs() {
63
63
  // Prefer the dist/index.js from the same installation as this code.
@@ -6,7 +6,7 @@
6
6
  * across multiple CLI invocations. Each session holds a real PTY (via node-pty)
7
7
  * and a headless terminal emulator (via @xterm/headless) for screen rendering.
8
8
  *
9
- * Protocol: newline-delimited JSON over ~/.agents-system/pty.sock
9
+ * Protocol: newline-delimited JSON over ~/.agents/.system/pty.sock
10
10
  */
11
11
  /**
12
12
  * Capture a stable identifier for a process at the moment it was started.
@@ -6,7 +6,7 @@
6
6
  * across multiple CLI invocations. Each session holds a real PTY (via node-pty)
7
7
  * and a headless terminal emulator (via @xterm/headless) for screen rendering.
8
8
  *
9
- * Protocol: newline-delimited JSON over ~/.agents-system/pty.sock
9
+ * Protocol: newline-delimited JSON over ~/.agents/.system/pty.sock
10
10
  */
11
11
  import * as net from 'net';
12
12
  import * as fs from 'fs';
@@ -155,14 +155,17 @@ export async function runPtyServer() {
155
155
  let nodePty;
156
156
  let XtermTerminal;
157
157
  try {
158
- nodePty = await import('node-pty');
158
+ // The Homebridge multiarch fork of node-pty: API-identical (same 1.x N-API
159
+ // codebase) but ships prebuilt binaries for Linux glibc + musl, x64 + arm64
160
+ // (plus macOS/Windows), so no compiler is needed on Linux/Alpine/arm64.
161
+ nodePty = await import('@homebridge/node-pty-prebuilt-multiarch');
159
162
  // Handle ESM default export
160
163
  if (nodePty.default?.spawn)
161
164
  nodePty = nodePty.default;
162
165
  // Ensure spawn-helper is executable (bun install doesn't set +x on prebuilds)
163
166
  try {
164
167
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
165
- const ptyBase = path.resolve(__dirname, '..', '..', 'node_modules', 'node-pty');
168
+ const ptyBase = path.resolve(__dirname, '..', '..', 'node_modules', '@homebridge', 'node-pty-prebuilt-multiarch');
166
169
  const helpers = [
167
170
  path.join(ptyBase, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper'),
168
171
  path.join(ptyBase, 'build', 'Release', 'spawn-helper'),
@@ -176,8 +179,8 @@ export async function runPtyServer() {
176
179
  catch { }
177
180
  }
178
181
  catch (err) {
179
- console.error('node-pty is required for PTY support.');
180
- console.error('Install: cd ' + '~/agents-cli && bun add node-pty');
182
+ console.error('node-pty (@homebridge/node-pty-prebuilt-multiarch) is required for PTY support.');
183
+ console.error('Install: bun add @homebridge/node-pty-prebuilt-multiarch');
181
184
  process.exit(1);
182
185
  }
183
186
  try {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Materialization helpers — install manifest CLIs, register MCP servers,
3
+ * sync resources into installed version homes, register hooks, add shims to
4
+ * PATH, prompt for missing default versions, install declared host-CLIs.
5
+ *
6
+ * Used by `agents repo refresh` (user-facing) and any other caller that needs
7
+ * to re-derive local state from declared configuration. Does NOT do any git
8
+ * operations — that lives in `agents repo pull`.
9
+ */
10
+ import type { AgentId } from './types.js';
11
+ export interface RefreshOptions {
12
+ /** Limit operations to a single agent (claude/codex/etc). Default: all installed. */
13
+ agentFilter?: AgentId;
14
+ /** Auto-sync everything and skip interactive prompts. */
15
+ skipPrompts?: boolean;
16
+ /** Skip CLI version install/upgrade from agents.yaml. */
17
+ skipClis?: boolean;
18
+ }
19
+ /**
20
+ * Re-materialize local state from declared configuration: install CLI versions,
21
+ * register MCP servers, sync resources to version homes, register hooks, add
22
+ * shims to PATH, prompt for missing defaults, install declared host-CLIs.
23
+ *
24
+ * Idempotent — safe to run repeatedly. No network operations.
25
+ */
26
+ export declare function refresh(options?: RefreshOptions): Promise<void>;