@phnx-labs/agents-cli 1.20.71 → 1.20.73

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 (238) hide show
  1. package/CHANGELOG.md +325 -1
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/commands.js +2 -0
  11. package/dist/commands/doctor.js +8 -4
  12. package/dist/commands/events.d.ts +11 -10
  13. package/dist/commands/events.js +30 -21
  14. package/dist/commands/exec.d.ts +34 -0
  15. package/dist/commands/exec.js +337 -39
  16. package/dist/commands/feed.d.ts +13 -0
  17. package/dist/commands/feed.js +130 -29
  18. package/dist/commands/hq.d.ts +2 -0
  19. package/dist/commands/hq.js +58 -0
  20. package/dist/commands/lease.d.ts +19 -0
  21. package/dist/commands/lease.js +138 -11
  22. package/dist/commands/login.d.ts +2 -0
  23. package/dist/commands/login.js +123 -0
  24. package/dist/commands/logs.js +39 -10
  25. package/dist/commands/mcp.js +2 -0
  26. package/dist/commands/mine.d.ts +27 -0
  27. package/dist/commands/mine.js +207 -0
  28. package/dist/commands/monitors.js +79 -49
  29. package/dist/commands/plugins.js +1 -0
  30. package/dist/commands/profiles.js +158 -0
  31. package/dist/commands/repo.js +2 -60
  32. package/dist/commands/resource-view.d.ts +2 -0
  33. package/dist/commands/resource-view.js +8 -0
  34. package/dist/commands/resources.d.ts +5 -0
  35. package/dist/commands/resources.js +95 -0
  36. package/dist/commands/routines.d.ts +2 -0
  37. package/dist/commands/routines.js +139 -37
  38. package/dist/commands/secrets.js +234 -47
  39. package/dist/commands/send.d.ts +15 -0
  40. package/dist/commands/send.js +62 -0
  41. package/dist/commands/sessions-tail.d.ts +1 -0
  42. package/dist/commands/sessions-tail.js +20 -9
  43. package/dist/commands/sessions.d.ts +6 -0
  44. package/dist/commands/sessions.js +26 -3
  45. package/dist/commands/setup-mine.d.ts +18 -0
  46. package/dist/commands/setup-mine.js +106 -0
  47. package/dist/commands/setup-share.js +2 -2
  48. package/dist/commands/setup.js +3 -1
  49. package/dist/commands/share.d.ts +8 -1
  50. package/dist/commands/share.js +122 -37
  51. package/dist/commands/skills.js +2 -0
  52. package/dist/commands/ssh.d.ts +9 -0
  53. package/dist/commands/ssh.js +196 -26
  54. package/dist/commands/status.js +8 -2
  55. package/dist/commands/subagents.js +3 -1
  56. package/dist/commands/sync.js +1 -1
  57. package/dist/commands/teams.js +11 -2
  58. package/dist/commands/uninstall.d.ts +11 -0
  59. package/dist/commands/uninstall.js +158 -0
  60. package/dist/commands/utils.d.ts +29 -0
  61. package/dist/commands/utils.js +24 -0
  62. package/dist/commands/versions.js +120 -11
  63. package/dist/index.js +83 -12
  64. package/dist/lib/activity.d.ts +124 -0
  65. package/dist/lib/activity.js +542 -0
  66. package/dist/lib/agents.js +16 -5
  67. package/dist/lib/artifact-actions.d.ts +1 -1
  68. package/dist/lib/artifact-actions.js +1 -1
  69. package/dist/lib/ask-classifier.d.ts +2 -1
  70. package/dist/lib/ask-classifier.js +3 -3
  71. package/dist/lib/auth-health.d.ts +32 -4
  72. package/dist/lib/auth-health.js +40 -1
  73. package/dist/lib/auto-pull.js +8 -1
  74. package/dist/lib/brand.d.ts +40 -0
  75. package/dist/lib/brand.js +122 -0
  76. package/dist/lib/browser/drivers/ssh.js +4 -7
  77. package/dist/lib/browser/service.js +6 -8
  78. package/dist/lib/channels/providers/index.d.ts +2 -0
  79. package/dist/lib/channels/providers/index.js +20 -0
  80. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  81. package/dist/lib/channels/providers/mailbox.js +26 -0
  82. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  83. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  84. package/dist/lib/channels/providers/rush.d.ts +6 -0
  85. package/dist/lib/channels/providers/rush.js +55 -0
  86. package/dist/lib/channels/registry.d.ts +42 -0
  87. package/dist/lib/channels/registry.js +20 -0
  88. package/dist/lib/channels/resolve.d.ts +11 -0
  89. package/dist/lib/channels/resolve.js +12 -0
  90. package/dist/lib/cli-resources.d.ts +18 -0
  91. package/dist/lib/cli-resources.js +53 -4
  92. package/dist/lib/cloud/codex.js +6 -3
  93. package/dist/lib/cloud/factory.d.ts +1 -0
  94. package/dist/lib/cloud/factory.js +10 -4
  95. package/dist/lib/cloud/rush.d.ts +3 -1
  96. package/dist/lib/cloud/rush.js +6 -1
  97. package/dist/lib/cloud/types.d.ts +2 -0
  98. package/dist/lib/codex-home.d.ts +35 -0
  99. package/dist/lib/codex-home.js +136 -0
  100. package/dist/lib/crabbox/cli.d.ts +44 -0
  101. package/dist/lib/crabbox/cli.js +120 -20
  102. package/dist/lib/crabbox/lease.d.ts +52 -1
  103. package/dist/lib/crabbox/lease.js +117 -16
  104. package/dist/lib/crabbox/progress.d.ts +31 -0
  105. package/dist/lib/crabbox/progress.js +76 -0
  106. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  107. package/dist/lib/crabbox/runtimes.js +33 -8
  108. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  109. package/dist/lib/crabbox/setup-copy.js +127 -0
  110. package/dist/lib/daemon.d.ts +14 -5
  111. package/dist/lib/daemon.js +52 -9
  112. package/dist/lib/drive-sync.js +6 -3
  113. package/dist/lib/event-stream.d.ts +36 -0
  114. package/dist/lib/event-stream.js +68 -0
  115. package/dist/lib/events.d.ts +1 -1
  116. package/dist/lib/exec.d.ts +11 -0
  117. package/dist/lib/exec.js +23 -2
  118. package/dist/lib/feed-policy.d.ts +1 -1
  119. package/dist/lib/feed-policy.js +14 -9
  120. package/dist/lib/feed-ranking.d.ts +32 -0
  121. package/dist/lib/feed-ranking.js +224 -0
  122. package/dist/lib/feed.d.ts +35 -2
  123. package/dist/lib/feed.js +66 -3
  124. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  125. package/dist/lib/fleet/auth-sync.js +92 -0
  126. package/dist/lib/fleet/remote-login.d.ts +187 -0
  127. package/dist/lib/fleet/remote-login.js +556 -0
  128. package/dist/lib/format.d.ts +30 -3
  129. package/dist/lib/format.js +34 -5
  130. package/dist/lib/fs-atomic.d.ts +7 -2
  131. package/dist/lib/fs-atomic.js +8 -12
  132. package/dist/lib/git.d.ts +16 -0
  133. package/dist/lib/git.js +79 -2
  134. package/dist/lib/heal.js +11 -3
  135. package/dist/lib/hosts/logs.d.ts +12 -0
  136. package/dist/lib/hosts/logs.js +18 -0
  137. package/dist/lib/hosts/progress.d.ts +28 -10
  138. package/dist/lib/hosts/progress.js +79 -22
  139. package/dist/lib/hosts/remote-cmd.js +4 -0
  140. package/dist/lib/hq/floor.d.ts +87 -0
  141. package/dist/lib/hq/floor.js +226 -0
  142. package/dist/lib/menubar/install-menubar.js +14 -20
  143. package/dist/lib/notify.d.ts +1 -0
  144. package/dist/lib/notify.js +3 -3
  145. package/dist/lib/open-url.d.ts +2 -0
  146. package/dist/lib/open-url.js +19 -0
  147. package/dist/lib/openclaw-keychain.d.ts +56 -0
  148. package/dist/lib/openclaw-keychain.js +236 -0
  149. package/dist/lib/overdue.js +10 -0
  150. package/dist/lib/permissions.d.ts +44 -1
  151. package/dist/lib/permissions.js +284 -7
  152. package/dist/lib/project-launch.d.ts +6 -12
  153. package/dist/lib/project-launch.js +13 -228
  154. package/dist/lib/project-resources.d.ts +7 -0
  155. package/dist/lib/project-resources.js +291 -0
  156. package/dist/lib/pty-client.d.ts +27 -0
  157. package/dist/lib/pty-client.js +136 -10
  158. package/dist/lib/refresh.js +14 -10
  159. package/dist/lib/resource-profiles.d.ts +26 -0
  160. package/dist/lib/resource-profiles.js +157 -0
  161. package/dist/lib/resources/permissions.js +7 -1
  162. package/dist/lib/resources/types.d.ts +1 -1
  163. package/dist/lib/resources.d.ts +1 -1
  164. package/dist/lib/resources.js +32 -3
  165. package/dist/lib/routines.d.ts +11 -0
  166. package/dist/lib/routines.js +56 -15
  167. package/dist/lib/runner.d.ts +1 -0
  168. package/dist/lib/runner.js +66 -5
  169. package/dist/lib/secrets/bundles.d.ts +16 -3
  170. package/dist/lib/secrets/bundles.js +206 -37
  171. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  172. package/dist/lib/secrets/icloud-import.js +9 -6
  173. package/dist/lib/secrets/mcp.js +1 -1
  174. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  175. package/dist/lib/secrets/vault-age-helper.js +34 -0
  176. package/dist/lib/secrets/vault.d.ts +49 -0
  177. package/dist/lib/secrets/vault.js +397 -0
  178. package/dist/lib/self-heal/checks/path.js +3 -1
  179. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  180. package/dist/lib/self-heal/checks/shims.js +19 -11
  181. package/dist/lib/session/cloud.d.ts +2 -2
  182. package/dist/lib/session/cloud.js +2 -2
  183. package/dist/lib/session/db.d.ts +4 -0
  184. package/dist/lib/session/db.js +44 -7
  185. package/dist/lib/session/discover.d.ts +2 -0
  186. package/dist/lib/session/discover.js +114 -3
  187. package/dist/lib/session/stream-render.d.ts +3 -0
  188. package/dist/lib/session/stream-render.js +130 -0
  189. package/dist/lib/session/types.d.ts +6 -0
  190. package/dist/lib/share/analytics.d.ts +13 -0
  191. package/dist/lib/share/analytics.js +45 -0
  192. package/dist/lib/share/config.d.ts +19 -5
  193. package/dist/lib/share/config.js +44 -8
  194. package/dist/lib/share/provision.d.ts +58 -6
  195. package/dist/lib/share/provision.js +97 -22
  196. package/dist/lib/share/publish.d.ts +36 -13
  197. package/dist/lib/share/publish.js +55 -8
  198. package/dist/lib/share/worker-template.js +83 -17
  199. package/dist/lib/shims.d.ts +36 -0
  200. package/dist/lib/shims.js +120 -16
  201. package/dist/lib/ssh-exec.d.ts +14 -0
  202. package/dist/lib/ssh-exec.js +57 -0
  203. package/dist/lib/staleness/detectors/hooks.js +25 -1
  204. package/dist/lib/staleness/detectors/permissions.js +66 -0
  205. package/dist/lib/staleness/detectors/workflows.js +20 -0
  206. package/dist/lib/staleness/writers/hooks.js +58 -4
  207. package/dist/lib/staleness/writers/permissions.js +2 -2
  208. package/dist/lib/staleness/writers/skills.js +1 -1
  209. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  210. package/dist/lib/staleness/writers/sources.js +68 -1
  211. package/dist/lib/star-nudge.d.ts +45 -0
  212. package/dist/lib/star-nudge.js +91 -0
  213. package/dist/lib/startup/command-registry.d.ts +7 -1
  214. package/dist/lib/startup/command-registry.js +19 -3
  215. package/dist/lib/state.d.ts +2 -0
  216. package/dist/lib/state.js +3 -0
  217. package/dist/lib/subagents-registry.d.ts +2 -0
  218. package/dist/lib/subagents-registry.js +3 -0
  219. package/dist/lib/teams/parsers.js +214 -6
  220. package/dist/lib/teams/supervisor.d.ts +7 -0
  221. package/dist/lib/teams/supervisor.js +2 -1
  222. package/dist/lib/template.d.ts +1 -1
  223. package/dist/lib/template.js +1 -1
  224. package/dist/lib/triggers/webhook.js +36 -3
  225. package/dist/lib/types.d.ts +73 -0
  226. package/dist/lib/uninstall.d.ts +84 -0
  227. package/dist/lib/uninstall.js +347 -0
  228. package/dist/lib/usage.d.ts +13 -1
  229. package/dist/lib/usage.js +32 -14
  230. package/dist/lib/version.d.ts +40 -0
  231. package/dist/lib/version.js +94 -16
  232. package/dist/lib/versions.d.ts +27 -0
  233. package/dist/lib/versions.js +248 -79
  234. package/dist/lib/workflows.d.ts +2 -0
  235. package/dist/lib/workflows.js +88 -0
  236. package/package.json +2 -1
  237. package/dist/commands/daemon.d.ts +0 -10
  238. package/dist/commands/daemon.js +0 -121
@@ -11,8 +11,10 @@ import { fileURLToPath } from 'url';
11
11
  import * as path from 'path';
12
12
  import { getSocketPath, getPtyLogPath, isPtyServerRunning } from './pty-server.js';
13
13
  import { backgroundSpawnOptions } from './platform/process.js';
14
+ import { BUN_VIRTUAL_ROOT } from './cli-entry.js';
14
15
  const CONNECT_TIMEOUT_MS = 5000;
15
16
  const RESPONSE_TIMEOUT_MS = 30000;
17
+ const START_TIMEOUT_MS = 5000;
16
18
  const IS_WINDOWS = process.platform === 'win32';
17
19
  /**
18
20
  * Send a request to the PTY server and return the response.
@@ -36,18 +38,44 @@ async function ensureServer() {
36
38
  // Find the entry point to spawn the server
37
39
  const { bin, args } = getServerSpawnArgs();
38
40
  const logPath = getPtyLogPath();
39
- const logFd = fs.openSync(logPath, 'a');
40
- const child = spawn(bin, args, {
41
- stdio: ['ignore', logFd, logFd],
42
- ...backgroundSpawnOptions({ fdStdio: true }),
41
+ let logFd;
42
+ try {
43
+ logFd = fs.openSync(logPath, 'a');
44
+ }
45
+ catch (err) {
46
+ const message = err instanceof Error ? err.message : String(err);
47
+ throw new Error([
48
+ 'PTY server failed to start before spawning.',
49
+ `Spawned: ${formatCommand({ bin, args })}`,
50
+ `Log: ${logPath}`,
51
+ `Log open error: ${message}`,
52
+ ].join('\n'));
53
+ }
54
+ let child;
55
+ try {
56
+ child = spawn(bin, args, {
57
+ stdio: ['ignore', logFd, logFd],
58
+ ...backgroundSpawnOptions({ fdStdio: true }),
59
+ });
60
+ }
61
+ finally {
62
+ fs.closeSync(logFd);
63
+ }
64
+ let childExit;
65
+ let childError;
66
+ let lastReadinessError;
67
+ child.once('exit', (code, signal) => {
68
+ childExit = { code, signal };
69
+ });
70
+ child.once('error', (err) => {
71
+ childError = err;
43
72
  });
44
73
  child.unref();
45
- fs.closeSync(logFd);
46
74
  // Wait for the server to become reachable. On Unix the socket file appearing is
47
75
  // a cheap readiness signal; on Windows the named pipe is not a filesystem object
48
76
  // (fs.existsSync always returns false), so we just attempt the ping directly.
49
77
  const socketPath = getSocketPath();
50
- const deadline = Date.now() + 5000;
78
+ const deadline = Date.now() + START_TIMEOUT_MS;
51
79
  while (Date.now() < deadline) {
52
80
  if (IS_WINDOWS || fs.existsSync(socketPath)) {
53
81
  // Verify we can connect
@@ -55,15 +83,50 @@ async function ensureServer() {
55
83
  await sendRequest({ action: 'ping' });
56
84
  return;
57
85
  }
58
- catch {
59
- // Not ready yet
86
+ catch (err) {
87
+ lastReadinessError = err instanceof Error ? err : new Error(String(err));
60
88
  }
61
89
  }
90
+ if (childError || childExit)
91
+ break;
62
92
  await new Promise(r => setTimeout(r, 100));
63
93
  }
64
- throw new Error('PTY server failed to start within 5 seconds. Check ~/.agents/.system/helpers/pty/logs.jsonl');
94
+ throw new Error(buildPtyStartFailureMessage({
95
+ timeoutMs: START_TIMEOUT_MS,
96
+ spawn: { bin, args },
97
+ exit: childExit,
98
+ spawnError: childError,
99
+ lastReadinessError,
100
+ logPath,
101
+ }));
65
102
  }
66
- function getServerSpawnArgs() {
103
+ export function getServerSpawnArgs(options = {}) {
104
+ const isStandaloneExecutable = options.isStandaloneExecutable
105
+ ?? isBunStandaloneExecutable();
106
+ if (isStandaloneExecutable) {
107
+ // A Bun `--compile` standalone binary cannot `require()` a native addon, so
108
+ // running the PTY sidecar AS this binary fails to load node-pty's pty.node
109
+ // (fatal on macOS; see #315). Prefer a real `node` running the dist/index.js
110
+ // that ships beside the binary — there node-pty loads from the on-disk
111
+ // prebuild. We can't do `<binary> dist/index.js` (the standalone misparses a
112
+ // script arg as a subcommand), so a genuine `node` is required. Fall back to
113
+ // the binary itself only when no node / no dist is found (a bare standalone
114
+ // install with no Node — PTY may then be unavailable, but nothing else can
115
+ // load the addon anyway).
116
+ const node = (options.resolveNode ?? resolveNodeExecutable)();
117
+ const exists = options.fileExists ?? fs.existsSync;
118
+ const execPath = options.execPath ?? process.execPath;
119
+ if (node) {
120
+ try {
121
+ const distIndex = path.join(path.dirname(execPath), '..', 'index.js');
122
+ if (exists(distIndex)) {
123
+ return { bin: node, args: [distIndex, 'pty', '_server'] };
124
+ }
125
+ }
126
+ catch { }
127
+ }
128
+ return { bin: process.execPath, args: ['pty', '_server'] };
129
+ }
67
130
  // Prefer the dist/index.js from the same installation as this code.
68
131
  // This avoids version mismatch when a globally installed `agents` is older.
69
132
  try {
@@ -86,6 +149,69 @@ function getServerSpawnArgs() {
86
149
  catch { }
87
150
  return { bin: 'agents', args: ['pty', '_server'] };
88
151
  }
152
+ export function isBunStandaloneExecutable(moduleUrl = import.meta.url) {
153
+ return BUN_VIRTUAL_ROOT.test(moduleUrl);
154
+ }
155
+ /**
156
+ * Locate a real `node` on PATH for running the sidecar's dist/index.js from a
157
+ * Bun standalone binary (which cannot itself load native addons). Returns
158
+ * undefined when no node is found — the caller then falls back to the binary.
159
+ */
160
+ function resolveNodeExecutable() {
161
+ try {
162
+ const lookup = IS_WINDOWS ? 'where node' : 'which node';
163
+ const node = execSync(lookup, { encoding: 'utf-8' }).split(/\r?\n/)[0].trim();
164
+ if (node && fs.existsSync(node))
165
+ return node;
166
+ }
167
+ catch { }
168
+ return undefined;
169
+ }
170
+ export function readRecentLogLines(logPath, maxLines = 20) {
171
+ try {
172
+ if (!fs.existsSync(logPath))
173
+ return [];
174
+ return fs.readFileSync(logPath, 'utf-8')
175
+ .split(/\r?\n/)
176
+ .filter(line => line.trim().length > 0)
177
+ .slice(-maxLines);
178
+ }
179
+ catch (err) {
180
+ const message = err instanceof Error ? err.message : String(err);
181
+ return [`Unable to read PTY server log: ${message}`];
182
+ }
183
+ }
184
+ export function buildPtyStartFailureMessage(details) {
185
+ const lines = [
186
+ `PTY server failed to start within ${Math.round(details.timeoutMs / 1000)} seconds.`,
187
+ `Spawned: ${formatCommand(details.spawn)}`,
188
+ ];
189
+ if (details.spawnError) {
190
+ lines.push(`Spawn error: ${details.spawnError.message}`);
191
+ }
192
+ if (details.exit) {
193
+ const reason = details.exit.signal
194
+ ? `signal ${details.exit.signal}`
195
+ : `code ${details.exit.code ?? 'unknown'}`;
196
+ lines.push(`PTY server process exited with ${reason} before listening.`);
197
+ }
198
+ if (details.lastReadinessError) {
199
+ lines.push(`Last readiness error: ${details.lastReadinessError.message}`);
200
+ }
201
+ lines.push(`Log: ${details.logPath}`);
202
+ const logLines = readRecentLogLines(details.logPath);
203
+ if (logLines.length > 0) {
204
+ lines.push('Recent PTY server log:');
205
+ lines.push(...logLines.map(line => ` ${line}`));
206
+ }
207
+ else {
208
+ lines.push('No PTY server log output was written.');
209
+ }
210
+ return lines.join('\n');
211
+ }
212
+ function formatCommand(spawnArgs) {
213
+ return [spawnArgs.bin, ...spawnArgs.args].map((part) => JSON.stringify(part)).join(' ');
214
+ }
89
215
  /**
90
216
  * Send a JSON request over the unix socket and return the parsed response.
91
217
  */
@@ -16,7 +16,7 @@ import { capableAgents } from './capabilities.js';
16
16
  import { AGENTS, ALL_AGENT_IDS, getAllCliStates, registerMcpToTargets, agentLabel, } from './agents.js';
17
17
  import { readManifest, MANIFEST_FILENAME } from './manifest.js';
18
18
  import { getUserAgentsDir } from './state.js';
19
- import { installVersion, listInstalledVersions, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from './versions.js';
19
+ import { installVersion, listInstalledVersions, isVersionIsolated, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from './versions.js';
20
20
  import { listCliStatus, installCli, describeMethod, describeCheck, selectInstallMethod, } from './cli-resources.js';
21
21
  import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from './shims.js';
22
22
  import { parseHookManifest, registerHooksToSettings } from './hooks.js';
@@ -133,13 +133,9 @@ export async function refresh(options = {}) {
133
133
  actuallySynced.plugins.length > 0;
134
134
  try {
135
135
  let selection;
136
+ let forceFullSync = false;
136
137
  if (skipPrompts) {
137
- if (!hasAnySynced || hasNewResources(newResources, agentId)) {
138
- selection = {
139
- commands: 'all', skills: 'all', hooks: 'all', memory: 'all',
140
- mcp: 'all', permissions: 'all', subagents: 'all', plugins: 'all',
141
- };
142
- }
138
+ forceFullSync = true;
143
139
  }
144
140
  else if (!hasAnySynced) {
145
141
  console.log(chalk.yellow(`\n${agentLabel(agentId)}@${defaultVer} has no synced resources.`));
@@ -153,8 +149,11 @@ export async function refresh(options = {}) {
153
149
  if (userSelection)
154
150
  selection = userSelection;
155
151
  }
156
- if (selection && Object.keys(selection).length > 0) {
157
- const syncResult = syncResourcesToVersion(agentId, defaultVer, selection);
152
+ else {
153
+ forceFullSync = true;
154
+ }
155
+ if (forceFullSync || (selection && Object.keys(selection).length > 0)) {
156
+ const syncResult = syncResourcesToVersion(agentId, defaultVer, selection, forceFullSync ? { force: true } : undefined);
158
157
  const synced = [];
159
158
  if (syncResult.commands)
160
159
  synced.push('commands');
@@ -231,7 +230,12 @@ export async function refresh(options = {}) {
231
230
  }
232
231
  const selectedVersions = [];
233
232
  for (const agentId of agentsNeedingDefault) {
234
- const versions = listInstalledVersions(agentId);
233
+ // Isolated copies are not default-eligible — `agents use` refuses them and
234
+ // setting one here would also switch the config symlink, pointing the user's
235
+ // real ~/.<agent> at an isolated home. Keep them out of the picker entirely.
236
+ const versions = listInstalledVersions(agentId).filter((v) => !isVersionIsolated(agentId, v));
237
+ if (versions.length === 0)
238
+ continue;
235
239
  const agent = AGENTS[agentId];
236
240
  const shouldSwitch = await select({
237
241
  message: `${agentLabel(agent.id)} has no default version. Set one now?`,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Top-level resource profiles.
3
+ *
4
+ * These are distinct from model-provider run profiles in lib/profiles.ts. A
5
+ * resource profile is a global mode switch stored in agents.yaml that filters
6
+ * the resolved DotAgents resource view and secrets bundles.
7
+ */
8
+ import type { ResourceProfilePreset } from './types.js';
9
+ export type ProfiledResourceKind = 'commands' | 'skills' | 'hooks' | 'subagents' | 'plugins' | 'workflows' | 'permissions' | 'mcp' | 'memory' | 'secrets';
10
+ export type PatternedProfileKind = Exclude<ProfiledResourceKind, 'memory' | 'secrets'>;
11
+ export interface ActiveResourceProfile {
12
+ name: string;
13
+ preset: ResourceProfilePreset;
14
+ }
15
+ export declare function validateResourceProfileName(name: string): void;
16
+ export declare function listResourceProfileNames(): string[];
17
+ export declare function getResourceProfilePreset(name: string): ResourceProfilePreset | null;
18
+ export declare function getActiveResourceProfileName(): string | null;
19
+ export declare function getActiveResourceProfile(): ActiveResourceProfile | null;
20
+ export declare function upsertResourceProfilePreset(name: string, preset: ResourceProfilePreset): void;
21
+ export declare function setActiveResourceProfile(name: string | null): void;
22
+ export declare function patternedProfileKinds(): PatternedProfileKind[];
23
+ export declare function activeRulesPreset(): string | null;
24
+ export declare function filterNamesForActiveResourceProfile(kind: ProfiledResourceKind, names: string[], sourceMap?: Map<string, string>): string[];
25
+ export declare function isNameActiveInResourceProfile(kind: ProfiledResourceKind, name: string, source?: string): boolean;
26
+ export declare function assertNameActiveInResourceProfile(kind: ProfiledResourceKind, name: string): void;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Top-level resource profiles.
3
+ *
4
+ * These are distinct from model-provider run profiles in lib/profiles.ts. A
5
+ * resource profile is a global mode switch stored in agents.yaml that filters
6
+ * the resolved DotAgents resource view and secrets bundles.
7
+ */
8
+ import { readMeta, updateMeta } from './state.js';
9
+ import { brandProfileName } from './brand.js';
10
+ const PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-_]{0,48}$/i;
11
+ const PATTERNED_KINDS = [
12
+ 'commands',
13
+ 'skills',
14
+ 'hooks',
15
+ 'subagents',
16
+ 'plugins',
17
+ 'workflows',
18
+ 'permissions',
19
+ 'mcp',
20
+ ];
21
+ export function validateResourceProfileName(name) {
22
+ if (!PROFILE_NAME_PATTERN.test(name)) {
23
+ throw new Error(`Invalid profile name '${name}'. Use letters, digits, dash, underscore (max 48 chars).`);
24
+ }
25
+ }
26
+ export function listResourceProfileNames() {
27
+ return Object.keys(readMeta().profiles?.presets ?? {}).sort((a, b) => a.localeCompare(b));
28
+ }
29
+ export function getResourceProfilePreset(name) {
30
+ validateResourceProfileName(name);
31
+ return readMeta().profiles?.presets?.[name] ?? null;
32
+ }
33
+ export function getActiveResourceProfileName() {
34
+ // A white-label brand pins its own profile; when running under a brand
35
+ // (AGENTS_BRAND set) that preset wins over the global `profiles.active`, so
36
+ // every resource filter that keys off the active profile becomes brand-scoped.
37
+ // See lib/brand.ts.
38
+ const branded = brandProfileName();
39
+ if (branded)
40
+ return branded;
41
+ return readMeta().profiles?.active ?? null;
42
+ }
43
+ export function getActiveResourceProfile() {
44
+ const meta = readMeta();
45
+ const name = getActiveResourceProfileName();
46
+ if (!name)
47
+ return null;
48
+ const preset = meta.profiles?.presets?.[name];
49
+ return preset ? { name, preset } : null;
50
+ }
51
+ export function upsertResourceProfilePreset(name, preset) {
52
+ validateResourceProfileName(name);
53
+ updateMeta((meta) => ({
54
+ ...meta,
55
+ profiles: {
56
+ ...meta.profiles,
57
+ presets: {
58
+ ...(meta.profiles?.presets ?? {}),
59
+ [name]: preset,
60
+ },
61
+ },
62
+ }));
63
+ }
64
+ export function setActiveResourceProfile(name) {
65
+ if (name) {
66
+ validateResourceProfileName(name);
67
+ const preset = getResourceProfilePreset(name);
68
+ if (!preset)
69
+ throw new Error(`Profile '${name}' not found.`);
70
+ }
71
+ updateMeta((meta) => ({
72
+ ...meta,
73
+ profiles: {
74
+ ...meta.profiles,
75
+ active: name || undefined,
76
+ },
77
+ }));
78
+ }
79
+ export function patternedProfileKinds() {
80
+ return [...PATTERNED_KINDS];
81
+ }
82
+ function plainPatternMatches(pattern, name) {
83
+ if (pattern === '*')
84
+ return true;
85
+ if (!pattern.includes('*'))
86
+ return pattern === name;
87
+ const escaped = pattern
88
+ .split('*')
89
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
90
+ .join('.*');
91
+ return new RegExp(`^${escaped}$`).test(name);
92
+ }
93
+ function filterByPlainPatterns(names, patterns) {
94
+ const include = patterns.filter((p) => !p.startsWith('!'));
95
+ const exclude = patterns.filter((p) => p.startsWith('!')).map((p) => p.slice(1));
96
+ return names.filter((name) => include.some((p) => plainPatternMatches(p, name)) &&
97
+ !exclude.some((p) => plainPatternMatches(p, name)));
98
+ }
99
+ function sourcePatternMatches(pattern, name, sourceMap) {
100
+ const colon = pattern.indexOf(':');
101
+ if (colon === -1)
102
+ return false;
103
+ const source = pattern.slice(0, colon);
104
+ const rawName = pattern.slice(colon + 1);
105
+ const actualSource = sourceMap.get(name);
106
+ if (actualSource !== source)
107
+ return false;
108
+ const patternNames = rawName === '*' ? ['*'] : rawName.split(',').map((n) => n.trim()).filter(Boolean);
109
+ return patternNames.some((patternName) => plainPatternMatches(patternName, name));
110
+ }
111
+ function expandProfilePatterns(names, patterns, sourceMap) {
112
+ if (sourceMap) {
113
+ const included = new Set();
114
+ const excluded = new Set();
115
+ for (const pattern of patterns) {
116
+ const negate = pattern.startsWith('!');
117
+ const raw = negate ? pattern.slice(1) : pattern;
118
+ const target = negate ? excluded : included;
119
+ const matches = raw.includes(':')
120
+ ? names.filter((name) => sourcePatternMatches(raw, name, sourceMap))
121
+ : names.filter((name) => plainPatternMatches(raw, name));
122
+ for (const name of matches)
123
+ target.add(name);
124
+ }
125
+ return names.filter((name) => included.has(name) && !excluded.has(name));
126
+ }
127
+ return filterByPlainPatterns(names, patterns);
128
+ }
129
+ export function activeRulesPreset() {
130
+ const preset = getActiveResourceProfile()?.preset;
131
+ return preset?.rules ?? preset?.rulesPreset ?? null;
132
+ }
133
+ export function filterNamesForActiveResourceProfile(kind, names, sourceMap) {
134
+ const profile = getActiveResourceProfile();
135
+ if (!profile)
136
+ return names;
137
+ if (kind === 'memory') {
138
+ const rules = activeRulesPreset();
139
+ return rules ? names.filter((name) => name === rules) : names;
140
+ }
141
+ const patterns = profile.preset[kind];
142
+ if (!patterns)
143
+ return names;
144
+ return expandProfilePatterns(names, patterns, sourceMap);
145
+ }
146
+ export function isNameActiveInResourceProfile(kind, name, source) {
147
+ const sourceMap = source ? new Map([[name, source]]) : undefined;
148
+ return filterNamesForActiveResourceProfile(kind, [name], sourceMap).includes(name);
149
+ }
150
+ export function assertNameActiveInResourceProfile(kind, name) {
151
+ const active = getActiveResourceProfile();
152
+ if (!active)
153
+ return;
154
+ if (isNameActiveInResourceProfile(kind, name))
155
+ return;
156
+ throw new Error(`${kind === 'secrets' ? 'Secrets bundle' : 'Resource'} '${name}' is not active in profile '${active.name}'.`);
157
+ }
@@ -77,6 +77,12 @@ function getAgentConfigPath(agent, versionHome) {
77
77
  return path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
78
78
  case 'openclaw':
79
79
  return path.join(versionHome, '.openclaw', 'openclaw.json');
80
+ case 'copilot':
81
+ return path.join(versionHome, '.copilot', 'permissions-config.json');
82
+ case 'forge':
83
+ return path.join(versionHome, '.forge', 'permissions.yaml');
84
+ case 'hermes':
85
+ return path.join(versionHome, '.hermes', 'config.yaml');
80
86
  case 'goose':
81
87
  return path.join(versionHome, '.config', 'goose', 'permission.yaml');
82
88
  default:
@@ -173,7 +179,7 @@ export const PermissionsHandler = {
173
179
  merged = mergePermissionSets(merged, r.item);
174
180
  }
175
181
  // Apply to the agent's config file
176
- applyPermissionsToVersion(agent, merged, versionHome, true);
182
+ applyPermissionsToVersion(agent, merged, versionHome, true, cwd);
177
183
  },
178
184
  /**
179
185
  * Permissions use YAML format.
@@ -5,7 +5,7 @@
5
5
  * - Union: All resources from all layers are combined
6
6
  * - Override on name conflict: Higher layer wins (project > user > system)
7
7
  */
8
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
8
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
9
9
  export type Layer = 'system' | 'user' | 'project';
10
10
  export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';
11
11
  /** A resolved resource with its origin layer. */
@@ -5,7 +5,7 @@
5
5
  import type { AgentId } from './types.js';
6
6
  import { type SkillParseError } from './skills.js';
7
7
  /** Resource kind — matches the subdirectory name under each repo root. */
8
- export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'cli' | 'permissions' | 'subagents' | 'profiles' | 'secrets';
8
+ export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'cli' | 'permissions' | 'subagents' | 'workflows' | 'profiles' | 'secrets';
9
9
  /** A resource resolved with its origin. */
10
10
  export interface ResolvedResource {
11
11
  name: string;
@@ -14,6 +14,27 @@ import { listMcpServerConfigs } from './mcp.js';
14
14
  import { WorkflowsHandler } from './resources/workflows.js';
15
15
  import { isCapable } from './capabilities.js';
16
16
  import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from './state.js';
17
+ import { isNameActiveInResourceProfile } from './resource-profiles.js';
18
+ function profiledKind(kind) {
19
+ switch (kind) {
20
+ case 'commands':
21
+ case 'skills':
22
+ case 'hooks':
23
+ case 'mcp':
24
+ case 'permissions':
25
+ case 'subagents':
26
+ case 'secrets':
27
+ return kind;
28
+ case 'rules':
29
+ return 'memory';
30
+ default:
31
+ return null;
32
+ }
33
+ }
34
+ function resourceIsActive(kind, name, source) {
35
+ const activeKind = profiledKind(kind);
36
+ return activeKind ? isNameActiveInResourceProfile(activeKind, name, source) : true;
37
+ }
17
38
  /**
18
39
  * Resolve a single resource by kind + name using project > user > system precedence.
19
40
  * For file-based resources the path ends in `.md`, `.yaml`, or `.yml` as appropriate.
@@ -36,13 +57,19 @@ export function resolveResource(kind, name, cwd) {
36
57
  // Try exact name (for directories like skills/subagents)
37
58
  const exactPath = path.join(dir, name);
38
59
  if (fs.existsSync(exactPath)) {
39
- return { name, path: exactPath, source };
60
+ if (resourceIsActive(kind, name, source)) {
61
+ return { name, path: exactPath, source };
62
+ }
63
+ continue;
40
64
  }
41
65
  // Try with common file extensions
42
66
  for (const ext of ['.md', '.yaml', '.yml']) {
43
67
  const withExt = exactPath + ext;
44
68
  if (fs.existsSync(withExt)) {
45
- return { name, path: withExt, source };
69
+ if (resourceIsActive(kind, name, source)) {
70
+ return { name, path: withExt, source };
71
+ }
72
+ continue;
46
73
  }
47
74
  }
48
75
  }
@@ -80,6 +107,8 @@ export function listResources(kind, cwd) {
80
107
  const rawName = entry.name.replace(/\.(md|yaml|yml)$/, '');
81
108
  if (seen.has(rawName))
82
109
  continue;
110
+ if (!resourceIsActive(kind, rawName, source))
111
+ continue;
83
112
  seen.add(rawName);
84
113
  results.push({
85
114
  name: rawName,
@@ -162,7 +191,7 @@ export function getAgentResources(agentId, options = {}) {
162
191
  hooks.push({ name: hook.name, path: hook.path, scope: hook.scope });
163
192
  }
164
193
  }
165
- // Workflows (claude only)
194
+ // Workflows
166
195
  const workflows = [];
167
196
  if (isCapable(agentId, 'workflows')) {
168
197
  for (const w of WorkflowsHandler.listAll(agentId, cwd)) {
@@ -41,6 +41,10 @@ export interface GithubJobTrigger {
41
41
  repo?: string;
42
42
  /** git branch (ref short name) — when set, only payloads for this branch match. */
43
43
  branch?: string;
44
+ /** GitHub webhook action, e.g. `opened`, `synchronize`, or `labeled`. */
45
+ action?: string;
46
+ /** Required GitHub label name. For `pull_request.labeled`, this is the added label. */
47
+ label?: string;
44
48
  }
45
49
  export interface LinearJobTrigger {
46
50
  type: 'linear_event';
@@ -184,6 +188,10 @@ export declare function checkJobDeviceEligibility(config: Pick<JobConfig, 'name'
184
188
  * Higher layers shadow lower ones of the same name (first-seen wins): a project
185
189
  * routine shadows a user routine, and a user routine shadows a built-in system
186
190
  * routine (`~/.agents/.system/routines/`, shipped via gh:phnx-labs/.agents-system).
191
+ * When a same-name project routine wins for inspection, the user-layer
192
+ * `devices` allowlist is overlaid only if the project routine does not declare
193
+ * its own allowlist, so CWD project discovery cannot hide an operational fleet
194
+ * pin or erase a project-authored one.
187
195
  * Project discovery is opt-in via `cwd`; the daemon (which calls `listJobs()`
188
196
  * with no argument) sees user + system routines, so a built-in routine fires for
189
197
  * every install unless the user overrides or disables it by name.
@@ -191,6 +199,9 @@ export declare function checkJobDeviceEligibility(config: Pick<JobConfig, 'name'
191
199
  export declare function listJobs(cwd?: string): JobConfig[];
192
200
  /**
193
201
  * Read a single job config by name, checking project > user > system.
202
+ * Same-name project routines keep the user-layer `devices` allowlist only when
203
+ * the project routine does not declare its own allowlist, for the same reason
204
+ * as listJobs().
194
205
  * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
195
206
  * resolve user + system routines (a user routine of the same name shadows a
196
207
  * built-in system routine).