@phnx-labs/agents-cli 1.20.4 → 1.20.5

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 (190) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +48 -17
  3. package/dist/commands/cli.js +1 -1
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +2 -0
  6. package/dist/commands/doctor.js +1 -1
  7. package/dist/commands/exec.js +52 -16
  8. package/dist/commands/hooks.js +6 -6
  9. package/dist/commands/inspect.d.ts +26 -0
  10. package/dist/commands/inspect.js +590 -0
  11. package/dist/commands/mcp.js +17 -16
  12. package/dist/commands/models.js +1 -1
  13. package/dist/commands/packages.js +6 -4
  14. package/dist/commands/permissions.js +13 -12
  15. package/dist/commands/plugins.d.ts +13 -0
  16. package/dist/commands/plugins.js +100 -11
  17. package/dist/commands/prune.js +3 -2
  18. package/dist/commands/pull.d.ts +12 -5
  19. package/dist/commands/pull.js +26 -422
  20. package/dist/commands/push.d.ts +14 -0
  21. package/dist/commands/push.js +30 -0
  22. package/dist/commands/repo.d.ts +1 -1
  23. package/dist/commands/repo.js +155 -112
  24. package/dist/commands/resource-view.d.ts +2 -0
  25. package/dist/commands/resource-view.js +12 -3
  26. package/dist/commands/routines.js +32 -7
  27. package/dist/commands/rules.js +1 -1
  28. package/dist/commands/sessions.js +1 -0
  29. package/dist/commands/setup.d.ts +3 -3
  30. package/dist/commands/setup.js +15 -15
  31. package/dist/commands/skills.js +6 -5
  32. package/dist/commands/subagents.js +5 -4
  33. package/dist/commands/sync.d.ts +18 -5
  34. package/dist/commands/sync.js +251 -65
  35. package/dist/commands/teams.js +1 -0
  36. package/dist/commands/tmux.d.ts +25 -0
  37. package/dist/commands/tmux.js +415 -0
  38. package/dist/commands/trash.d.ts +2 -2
  39. package/dist/commands/trash.js +1 -1
  40. package/dist/commands/versions.js +2 -2
  41. package/dist/commands/view.js +9 -4
  42. package/dist/commands/workflows.js +4 -3
  43. package/dist/commands/worktree.d.ts +4 -5
  44. package/dist/commands/worktree.js +4 -4
  45. package/dist/index.js +68 -20
  46. package/dist/lib/agents.d.ts +19 -10
  47. package/dist/lib/agents.js +79 -25
  48. package/dist/lib/auto-pull-worker.d.ts +1 -1
  49. package/dist/lib/auto-pull-worker.js +2 -2
  50. package/dist/lib/auto-pull.d.ts +1 -1
  51. package/dist/lib/auto-pull.js +1 -1
  52. package/dist/lib/beta.d.ts +1 -1
  53. package/dist/lib/beta.js +1 -1
  54. package/dist/lib/capabilities.js +2 -0
  55. package/dist/lib/commands.d.ts +28 -1
  56. package/dist/lib/commands.js +125 -20
  57. package/dist/lib/doctor-diff.js +2 -2
  58. package/dist/lib/exec.d.ts +14 -0
  59. package/dist/lib/exec.js +39 -5
  60. package/dist/lib/fuzzy.d.ts +12 -2
  61. package/dist/lib/fuzzy.js +29 -4
  62. package/dist/lib/git.js +8 -1
  63. package/dist/lib/hooks.d.ts +2 -2
  64. package/dist/lib/hooks.js +97 -10
  65. package/dist/lib/mcp.js +32 -2
  66. package/dist/lib/migrate.d.ts +51 -0
  67. package/dist/lib/migrate.js +227 -1
  68. package/dist/lib/models.js +62 -15
  69. package/dist/lib/permissions.d.ts +36 -2
  70. package/dist/lib/permissions.js +217 -7
  71. package/dist/lib/plugin-marketplace.d.ts +98 -40
  72. package/dist/lib/plugin-marketplace.js +196 -93
  73. package/dist/lib/plugins.d.ts +21 -4
  74. package/dist/lib/plugins.js +130 -49
  75. package/dist/lib/profiles-presets.js +12 -12
  76. package/dist/lib/project-launch.d.ts +65 -0
  77. package/dist/lib/project-launch.js +367 -0
  78. package/dist/lib/pty-client.js +1 -1
  79. package/dist/lib/pty-server.d.ts +1 -1
  80. package/dist/lib/pty-server.js +1 -1
  81. package/dist/lib/refresh.d.ts +26 -0
  82. package/dist/lib/refresh.js +315 -0
  83. package/dist/lib/resource-patterns.d.ts +1 -1
  84. package/dist/lib/resource-patterns.js +1 -1
  85. package/dist/lib/resources/commands.js +2 -2
  86. package/dist/lib/resources/hooks.d.ts +1 -1
  87. package/dist/lib/resources/hooks.js +1 -1
  88. package/dist/lib/resources/mcp.d.ts +1 -1
  89. package/dist/lib/resources/mcp.js +5 -6
  90. package/dist/lib/resources/permissions.js +5 -2
  91. package/dist/lib/resources/rules.js +3 -2
  92. package/dist/lib/resources/skills.js +3 -2
  93. package/dist/lib/resources/types.d.ts +1 -1
  94. package/dist/lib/resources.js +2 -2
  95. package/dist/lib/rotate.d.ts +1 -1
  96. package/dist/lib/rotate.js +1 -1
  97. package/dist/lib/routines.d.ts +16 -4
  98. package/dist/lib/routines.js +67 -17
  99. package/dist/lib/rules/compile.js +22 -10
  100. package/dist/lib/rules/rules.js +3 -3
  101. package/dist/lib/runner.js +16 -3
  102. package/dist/lib/scheduler.js +15 -1
  103. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  104. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  107. package/dist/lib/secrets/linux.d.ts +44 -9
  108. package/dist/lib/secrets/linux.js +302 -48
  109. package/dist/lib/session/db.js +15 -2
  110. package/dist/lib/session/discover.js +118 -3
  111. package/dist/lib/session/parse.js +3 -0
  112. package/dist/lib/session/types.d.ts +1 -1
  113. package/dist/lib/session/types.js +1 -1
  114. package/dist/lib/shims.d.ts +10 -9
  115. package/dist/lib/shims.js +101 -50
  116. package/dist/lib/skills.d.ts +1 -1
  117. package/dist/lib/skills.js +10 -9
  118. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  119. package/dist/lib/staleness/detectors/commands.js +46 -0
  120. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  121. package/dist/lib/staleness/detectors/hooks.js +44 -0
  122. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  123. package/dist/lib/staleness/detectors/mcp.js +31 -0
  124. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  125. package/dist/lib/staleness/detectors/permissions.js +201 -0
  126. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  127. package/dist/lib/staleness/detectors/plugins.js +23 -0
  128. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  129. package/dist/lib/staleness/detectors/rules.js +34 -0
  130. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  131. package/dist/lib/staleness/detectors/skills.js +71 -0
  132. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  133. package/dist/lib/staleness/detectors/subagents.js +50 -0
  134. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  135. package/dist/lib/staleness/detectors/types.js +1 -0
  136. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  137. package/dist/lib/staleness/detectors/workflows.js +28 -0
  138. package/dist/lib/staleness/registry.d.ts +26 -0
  139. package/dist/lib/staleness/registry.js +123 -0
  140. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  141. package/dist/lib/staleness/writers/commands.js +111 -0
  142. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  143. package/dist/lib/staleness/writers/hooks.js +47 -0
  144. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  145. package/dist/lib/staleness/writers/kinds.js +15 -0
  146. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  147. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  148. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  149. package/dist/lib/staleness/writers/mcp.js +19 -0
  150. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  151. package/dist/lib/staleness/writers/permissions.js +26 -0
  152. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  153. package/dist/lib/staleness/writers/plugins.js +31 -0
  154. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  155. package/dist/lib/staleness/writers/rules.js +55 -0
  156. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  157. package/dist/lib/staleness/writers/skills.js +81 -0
  158. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  159. package/dist/lib/staleness/writers/sources.js +72 -0
  160. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  161. package/dist/lib/staleness/writers/subagents.js +53 -0
  162. package/dist/lib/staleness/writers/types.d.ts +36 -0
  163. package/dist/lib/staleness/writers/types.js +1 -0
  164. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  165. package/dist/lib/staleness/writers/workflows.js +31 -0
  166. package/dist/lib/state.d.ts +34 -11
  167. package/dist/lib/state.js +58 -13
  168. package/dist/lib/subagents.d.ts +0 -2
  169. package/dist/lib/subagents.js +6 -6
  170. package/dist/lib/teams/agents.js +1 -1
  171. package/dist/lib/teams/parsers.d.ts +1 -1
  172. package/dist/lib/tmux/binary.d.ts +67 -0
  173. package/dist/lib/tmux/binary.js +141 -0
  174. package/dist/lib/tmux/index.d.ts +8 -0
  175. package/dist/lib/tmux/index.js +8 -0
  176. package/dist/lib/tmux/paths.d.ts +17 -0
  177. package/dist/lib/tmux/paths.js +30 -0
  178. package/dist/lib/tmux/session.d.ts +122 -0
  179. package/dist/lib/tmux/session.js +305 -0
  180. package/dist/lib/types.d.ts +58 -7
  181. package/dist/lib/types.js +1 -1
  182. package/dist/lib/usage.js +1 -1
  183. package/dist/lib/versions.d.ts +4 -4
  184. package/dist/lib/versions.js +135 -493
  185. package/dist/lib/workflows.d.ts +2 -4
  186. package/dist/lib/workflows.js +3 -4
  187. package/package.json +2 -2
  188. package/scripts/postinstall.js +16 -63
  189. package/dist/commands/status.d.ts +0 -9
  190. package/dist/commands/status.js +0 -25
@@ -0,0 +1,367 @@
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 path from 'path';
46
+ import { supports } from './capabilities.js';
47
+ import { getEnabledExtraRepos, getExtraPluginsDir, getPluginsDir, getProjectAgentsDir, getProjectPluginsDir, getSystemPluginsDir, } from './state.js';
48
+ import { getVersionHomePath } from './versions.js';
49
+ import { compileRulesForProject } from './rules/compile.js';
50
+ import { discoverPluginsInDir, hasPluginExecSurfaces, inspectPluginCapabilities } from './plugins.js';
51
+ import { MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, addPluginToSettings, copyPluginToMarketplace, marketplaceNameFor, marketplaceRoot, pluginInstallDir, registerMarketplace, removePluginFromSettings, syncMarketplaceManifest, } from './plugin-marketplace.js';
52
+ /**
53
+ * Run the launch-time project compile. Safe to call on every agent launch:
54
+ * each step is idempotent and skips when its inputs are missing.
55
+ */
56
+ export function runLaunchSync(opts) {
57
+ const result = {
58
+ rulesCompiled: false,
59
+ workspaceLinks: 0,
60
+ workspaceSkipped: [],
61
+ marketplaces: {},
62
+ };
63
+ // Step 1: project rules
64
+ try {
65
+ const r = compileRulesForProject(opts.cwd);
66
+ result.rulesCompiled = r.compiled;
67
+ }
68
+ catch {
69
+ // Don't fail launch on a malformed project rules.yaml.
70
+ }
71
+ // Step 2: workspace resource mirror
72
+ const mirror = mirrorWorkspaceResources(opts.cwd, opts.agent);
73
+ result.workspaceLinks = mirror.links;
74
+ result.workspaceSkipped = mirror.skipped;
75
+ // Step 3: scoped plugin marketplaces
76
+ result.marketplaces = synthesizeScopedMarketplaces(opts.agent, opts.version, opts.cwd);
77
+ return result;
78
+ }
79
+ const CLAUDE_MIRROR_PLANS = [
80
+ { srcSubdir: 'subagents', destSubdir: 'agents', entriesAreDirs: false },
81
+ { srcSubdir: 'commands', destSubdir: 'commands', entriesAreDirs: false },
82
+ { srcSubdir: 'skills', destSubdir: 'skills', entriesAreDirs: true },
83
+ ];
84
+ function mirrorWorkspaceResources(cwd, agent) {
85
+ // v1: claude-only. Other agents have workspace conventions we haven't
86
+ // mapped (amp: ~/.config/amp; antigravity: ~/.gemini/antigravity-cli;
87
+ // codex/gemini/cursor: no subagent support per capabilities). Adding them
88
+ // requires per-agent workspaceDirName + capability gates — follow-up.
89
+ if (agent !== 'claude')
90
+ return { links: 0, skipped: [] };
91
+ const projectAgentsDir = getProjectAgentsDir(cwd);
92
+ if (!projectAgentsDir)
93
+ return { links: 0, skipped: [] };
94
+ const agentWorkspaceDir = path.join(cwd, '.claude');
95
+ const projectAgentsResolved = (() => {
96
+ try {
97
+ return fs.realpathSync(projectAgentsDir);
98
+ }
99
+ catch {
100
+ return path.resolve(projectAgentsDir);
101
+ }
102
+ })();
103
+ let links = 0;
104
+ const skipped = [];
105
+ // Mirror subagents / commands / skills. mcp.json is intentionally excluded
106
+ // — see header doc, it's a supply-chain surface.
107
+ for (const plan of CLAUDE_MIRROR_PLANS) {
108
+ const srcDir = path.join(projectAgentsDir, plan.srcSubdir);
109
+ if (!fs.existsSync(srcDir))
110
+ continue;
111
+ const destDir = path.join(agentWorkspaceDir, plan.destSubdir);
112
+ fs.mkdirSync(destDir, { recursive: true });
113
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ if (entry.name.startsWith('.'))
116
+ continue;
117
+ if (plan.entriesAreDirs && !entry.isDirectory())
118
+ continue;
119
+ if (!plan.entriesAreDirs && !entry.isFile() && !entry.isSymbolicLink())
120
+ continue;
121
+ const srcPath = path.join(srcDir, entry.name);
122
+ const destPath = path.join(destDir, entry.name);
123
+ if (replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved)) {
124
+ links += 1;
125
+ }
126
+ else {
127
+ skipped.push(path.relative(cwd, destPath));
128
+ }
129
+ }
130
+ }
131
+ return { links, skipped };
132
+ }
133
+ /**
134
+ * Create or refresh a symlink at `destPath` pointing at `srcPath`. Returns
135
+ * true if we wrote (or already had) the link, false if we skipped because
136
+ * the destination is user-owned (regular file, directory, or symlink pointing
137
+ * outside the project's `.agents/` tree, or a dangling symlink — treated as
138
+ * user state we don't yet understand).
139
+ *
140
+ * Skip-fast: if the destination is already a symlink resolving to the
141
+ * project-agents tree AND its target matches srcPath, no write happens.
142
+ */
143
+ function replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved) {
144
+ let destLstat = null;
145
+ try {
146
+ destLstat = fs.lstatSync(destPath);
147
+ }
148
+ catch { /* missing — write fresh */ }
149
+ if (destLstat) {
150
+ if (!destLstat.isSymbolicLink()) {
151
+ return false;
152
+ }
153
+ let destTargetReal = null;
154
+ try {
155
+ destTargetReal = fs.realpathSync(destPath);
156
+ }
157
+ catch { /* dangling */ }
158
+ // Dangling symlink → user-owned in-progress state; do not clobber.
159
+ if (destTargetReal === null) {
160
+ return false;
161
+ }
162
+ if (destTargetReal !== projectAgentsResolved && !destTargetReal.startsWith(projectAgentsResolved + path.sep)) {
163
+ return false;
164
+ }
165
+ let srcReal = null;
166
+ try {
167
+ srcReal = fs.realpathSync(srcPath);
168
+ }
169
+ catch { /* src vanished mid-launch */ }
170
+ if (srcReal !== null && destTargetReal === srcReal) {
171
+ return true; // already correct
172
+ }
173
+ try {
174
+ fs.unlinkSync(destPath);
175
+ }
176
+ catch { /* ignore */ }
177
+ }
178
+ try {
179
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
180
+ fs.symlinkSync(srcPath, destPath);
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ return true;
186
+ }
187
+ function makeScope(spec, pluginsDir, autoEnableExecSurfaces, precedence) {
188
+ return { spec, marketplaceName: marketplaceNameFor(spec), pluginsDir, autoEnableExecSurfaces, precedence };
189
+ }
190
+ function collectPluginScopes(cwd) {
191
+ const scopes = [];
192
+ // Precedence: project > extras > user > system. Same direction the rules
193
+ // composition uses (project layer shadows base layers).
194
+ scopes.push(makeScope({ kind: 'system', root: getSystemPluginsDir() }, getSystemPluginsDir(), true, 0));
195
+ scopes.push(makeScope({ kind: 'user' }, getPluginsDir(), true, 1));
196
+ for (const extra of getEnabledExtraRepos()) {
197
+ const root = getExtraPluginsDir(extra.alias);
198
+ scopes.push(makeScope({ kind: 'extra', alias: extra.alias, root }, root, false, 2));
199
+ }
200
+ const projectPluginsDir = getProjectPluginsDir(cwd);
201
+ if (projectPluginsDir) {
202
+ scopes.push(makeScope({ kind: 'project', root: projectPluginsDir }, projectPluginsDir, false, 3));
203
+ }
204
+ return scopes;
205
+ }
206
+ function synthesizeScopedMarketplaces(agent, version, cwd) {
207
+ const result = {};
208
+ if (!supports(agent, 'plugins', version).ok)
209
+ return result;
210
+ let versionHome;
211
+ try {
212
+ versionHome = getVersionHomePath(agent, version);
213
+ }
214
+ catch {
215
+ return result;
216
+ }
217
+ if (!fs.existsSync(versionHome))
218
+ return result;
219
+ // First pass: resolve cross-scope plugin name collisions by precedence.
220
+ // For each plugin name, the scope with the highest precedence wins; the
221
+ // plugin is installed only into that scope's marketplace.
222
+ const winner = new Map();
223
+ for (const scope of collectPluginScopes(cwd)) {
224
+ if (!fs.existsSync(scope.pluginsDir))
225
+ continue;
226
+ for (const plugin of discoverPluginsInDir(scope.pluginsDir)) {
227
+ const existing = winner.get(plugin.name);
228
+ if (!existing || scope.precedence > existing.scope.precedence) {
229
+ winner.set(plugin.name, { scope, plugin });
230
+ }
231
+ }
232
+ }
233
+ if (winner.size === 0)
234
+ return result;
235
+ // Group winners by their winning scope and synthesize one marketplace per
236
+ // scope. Skip-fast: scope hash sentinel short-circuits unchanged scopes.
237
+ const byScope = new Map();
238
+ for (const { scope, plugin } of winner.values()) {
239
+ let bucket = byScope.get(scope.marketplaceName);
240
+ if (!bucket) {
241
+ bucket = { scope, plugins: [] };
242
+ byScope.set(scope.marketplaceName, bucket);
243
+ }
244
+ bucket.plugins.push(plugin);
245
+ }
246
+ for (const { scope, plugins } of byScope.values()) {
247
+ plugins.sort((a, b) => a.name.localeCompare(b.name));
248
+ const installed = installScope(agent, versionHome, scope, plugins);
249
+ if (installed.length > 0)
250
+ result[scope.marketplaceName] = installed;
251
+ }
252
+ // Sweep any orphaned `<plugin>@*` keys whose plugin name is now owned by a
253
+ // different scope (e.g. plugin moved from user to project). Without this,
254
+ // the OLD scope's enabledPlugins key stays set forever, double-enabling.
255
+ pruneLosingScopeEnables(agent, versionHome, winner);
256
+ return result;
257
+ }
258
+ function installScope(agent, versionHome, scope, plugins) {
259
+ const newHash = computeScopeHash(plugins);
260
+ const sentinelPath = path.join(marketplaceRoot(scope.spec, agent, versionHome), '.agents-launch-sync');
261
+ const existingHash = readScopeHash(sentinelPath);
262
+ if (existingHash === newHash) {
263
+ // Nothing changed since last launch — fast path. Verify the manifest
264
+ // dir still exists; if a user blew it away, force a re-sync.
265
+ if (fs.existsSync(path.dirname(sentinelPath))) {
266
+ return plugins.map((p) => p.name);
267
+ }
268
+ }
269
+ const installed = [];
270
+ for (const plugin of plugins) {
271
+ try {
272
+ copyPluginToMarketplace(plugin, scope.spec, agent, versionHome);
273
+ installed.push(plugin.name);
274
+ }
275
+ catch {
276
+ // Individual plugin copy failure — keep going on the others.
277
+ }
278
+ }
279
+ if (installed.length === 0)
280
+ return [];
281
+ syncMarketplaceManifest(scope.spec, agent, versionHome);
282
+ registerMarketplace(scope.spec, agent, versionHome);
283
+ // Enable each plugin in settings unless the scope withholds auto-enable for
284
+ // exec surfaces (project + extras) AND the plugin actually ships any.
285
+ for (const plugin of plugins) {
286
+ if (!installed.includes(plugin.name))
287
+ continue;
288
+ if (!scope.autoEnableExecSurfaces && hasPluginExecSurfaces(inspectPluginCapabilities(plugin.root)))
289
+ continue;
290
+ addPluginToSettings(plugin.name, scope.marketplaceName, agent, versionHome);
291
+ }
292
+ writeScopeHash(sentinelPath, newHash);
293
+ return installed;
294
+ }
295
+ function pruneLosingScopeEnables(agent, versionHome, winner) {
296
+ const ourScopeNames = new Set([
297
+ SYSTEM_MARKETPLACE_NAME,
298
+ MARKETPLACE_NAME,
299
+ PROJECT_MARKETPLACE_NAME,
300
+ ...getEnabledExtraRepos().map((e) => marketplaceNameFor({ kind: 'extra', alias: e.alias, root: getExtraPluginsDir(e.alias) })),
301
+ ]);
302
+ for (const [name, { scope: winningScope }] of winner) {
303
+ for (const candidateScope of ourScopeNames) {
304
+ if (candidateScope === winningScope.marketplaceName)
305
+ continue;
306
+ removePluginFromSettings(name, candidateScope, agent, versionHome);
307
+ }
308
+ }
309
+ }
310
+ /**
311
+ * Hash the plugin set so we can skip-fast when nothing changed since the
312
+ * last launch. Includes the source path (catches moves), the .claude-plugin/
313
+ * plugin.json content (catches metadata edits), and the file-mtime+size
314
+ * fingerprint of every file in the plugin (catches code edits).
315
+ */
316
+ function computeScopeHash(plugins) {
317
+ const hash = crypto.createHash('sha256');
318
+ for (const plugin of [...plugins].sort((a, b) => a.name.localeCompare(b.name))) {
319
+ hash.update(`${plugin.name}\0${plugin.root}\0`);
320
+ fingerprintDir(plugin.root, hash);
321
+ hash.update('\0SEP\0');
322
+ }
323
+ return hash.digest('hex');
324
+ }
325
+ function fingerprintDir(dir, hash) {
326
+ let entries;
327
+ try {
328
+ entries = fs.readdirSync(dir, { withFileTypes: true });
329
+ }
330
+ catch {
331
+ return;
332
+ }
333
+ entries.sort((a, b) => a.name.localeCompare(b.name));
334
+ for (const entry of entries) {
335
+ if (entry.name === 'node_modules' || entry.name === '.git')
336
+ continue;
337
+ const abs = path.join(dir, entry.name);
338
+ if (entry.isDirectory()) {
339
+ hash.update(`D ${entry.name}\n`);
340
+ fingerprintDir(abs, hash);
341
+ }
342
+ else {
343
+ try {
344
+ const stat = fs.lstatSync(abs);
345
+ hash.update(`F ${entry.name} ${stat.size} ${stat.mtimeMs}\n`);
346
+ }
347
+ catch { /* race during launch — skip */ }
348
+ }
349
+ }
350
+ }
351
+ function readScopeHash(sentinelPath) {
352
+ try {
353
+ return fs.readFileSync(sentinelPath, 'utf-8').trim();
354
+ }
355
+ catch {
356
+ return null;
357
+ }
358
+ }
359
+ function writeScopeHash(sentinelPath, hash) {
360
+ try {
361
+ fs.mkdirSync(path.dirname(sentinelPath), { recursive: true });
362
+ fs.writeFileSync(sentinelPath, hash + '\n');
363
+ }
364
+ catch { /* best-effort; missing sentinel just means next launch does full work */ }
365
+ }
366
+ // Re-export for the test's structural assertions; not used internally.
367
+ 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';
@@ -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>;