@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
@@ -32,6 +32,7 @@ export interface JobConfig {
32
32
  config?: Record<string, unknown>;
33
33
  version?: string;
34
34
  runOnce?: boolean;
35
+ endAt?: string;
35
36
  }
36
37
  /** Metadata for a single job execution, persisted as JSON in the run directory. */
37
38
  export interface RunMeta {
@@ -45,10 +46,19 @@ export interface RunMeta {
45
46
  completedAt: string | null;
46
47
  exitCode: number | null;
47
48
  }
48
- /** List all job configs from ~/.agents/routines/. */
49
- export declare function listJobs(): JobConfig[];
50
- /** Read a single job config by name. Returns null if not found. */
51
- export declare function readJob(name: string): JobConfig | null;
49
+ /**
50
+ * List all job configs, scanning project > user routine dirs.
51
+ * Project routines (`<project>/.agents/routines/`) shadow user routines of the
52
+ * same name. Project discovery is opt-in via `cwd`; the daemon (which calls
53
+ * `listJobs()` with no argument) only sees user routines.
54
+ */
55
+ export declare function listJobs(cwd?: string): JobConfig[];
56
+ /**
57
+ * Read a single job config by name, checking project > user.
58
+ * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
59
+ * only resolve user routines.
60
+ */
61
+ export declare function readJob(name: string, cwd?: string): JobConfig | null;
52
62
  /** Write a job config to disk, omitting fields that match defaults. */
53
63
  export declare function writeJob(config: JobConfig): void;
54
64
  /** Delete a job config file by name. Returns true if the file existed. */
@@ -57,6 +67,8 @@ export declare function deleteJob(name: string): boolean;
57
67
  export declare function setJobEnabled(name: string, enabled: boolean): void;
58
68
  /** Validate a partial job config, returning a list of human-readable errors. */
59
69
  export declare function validateJob(config: Partial<JobConfig>): string[];
70
+ /** True when a job's endAt has already elapsed. False when endAt is unset or in the future. */
71
+ export declare function isPastEndAt(config: Pick<JobConfig, 'endAt'>, now?: Date): boolean;
60
72
  /** Expand built-in and user-defined template variables in a job's prompt string. */
61
73
  export declare function resolveJobPrompt(config: JobConfig): string;
62
74
  /** Parse a human-readable timeout string (e.g. "10m", "2h", "1h30m", "3d", "1w") into milliseconds.
@@ -10,7 +10,7 @@ import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as yaml from 'yaml';
12
12
  import { Cron } from 'croner';
13
- import { getRoutinesDir, getRunsDir, ensureAgentsDir } from './state.js';
13
+ import { getRoutinesDir, getRunsDir, ensureAgentsDir, getProjectRoutinesDir } from './state.js';
14
14
  import { safeJoin } from './paths.js';
15
15
  import { ALL_AGENT_IDS } from './agents.js';
16
16
  /** Default values applied to every job config when fields are omitted. */
@@ -20,29 +20,59 @@ const JOB_DEFAULTS = {
20
20
  timeout: '10m',
21
21
  enabled: true,
22
22
  };
23
- /** List all job configs from ~/.agents/routines/. */
24
- export function listJobs() {
23
+ /**
24
+ * List all job configs, scanning project > user routine dirs.
25
+ * Project routines (`<project>/.agents/routines/`) shadow user routines of the
26
+ * same name. Project discovery is opt-in via `cwd`; the daemon (which calls
27
+ * `listJobs()` with no argument) only sees user routines.
28
+ */
29
+ export function listJobs(cwd) {
25
30
  ensureAgentsDir();
26
- const jobsDir = getRoutinesDir();
27
- if (!fs.existsSync(jobsDir))
28
- return [];
29
- const files = fs.readdirSync(jobsDir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
31
+ const seen = new Set();
30
32
  const jobs = [];
31
- for (const file of files) {
32
- const job = readJobFile(path.join(jobsDir, file));
33
- if (job)
33
+ const dirs = [];
34
+ if (cwd) {
35
+ const projectDir = getProjectRoutinesDir(cwd);
36
+ if (projectDir)
37
+ dirs.push(projectDir);
38
+ }
39
+ dirs.push(getRoutinesDir());
40
+ for (const dir of dirs) {
41
+ if (!fs.existsSync(dir))
42
+ continue;
43
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
44
+ for (const file of files) {
45
+ const job = readJobFile(path.join(dir, file));
46
+ if (!job)
47
+ continue;
48
+ if (seen.has(job.name))
49
+ continue;
50
+ seen.add(job.name);
34
51
  jobs.push(job);
52
+ }
35
53
  }
36
54
  return jobs;
37
55
  }
38
- /** Read a single job config by name. Returns null if not found. */
39
- export function readJob(name) {
56
+ /**
57
+ * Read a single job config by name, checking project > user.
58
+ * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
59
+ * only resolve user routines.
60
+ */
61
+ export function readJob(name, cwd) {
40
62
  ensureAgentsDir();
41
- const jobsDir = getRoutinesDir();
42
- for (const ext of ['.yml', '.yaml']) {
43
- const filePath = safeJoin(jobsDir, name + ext);
44
- if (fs.existsSync(filePath)) {
45
- return readJobFile(filePath);
63
+ const dirs = [];
64
+ if (cwd) {
65
+ const projectDir = getProjectRoutinesDir(cwd);
66
+ if (projectDir)
67
+ dirs.push(projectDir);
68
+ }
69
+ dirs.push(getRoutinesDir());
70
+ for (const dir of dirs) {
71
+ for (const ext of ['.yml', '.yaml']) {
72
+ const filePath = safeJoin(dir, name + ext);
73
+ if (fs.existsSync(filePath)) {
74
+ return readJobFile(filePath);
75
+ }
46
76
  }
47
77
  }
48
78
  return null;
@@ -147,8 +177,28 @@ export function validateJob(config) {
147
177
  if (config.timeout && !parseTimeout(config.timeout)) {
148
178
  errors.push('timeout must be like 10m, 2h, 3d, 1w (max 1w)');
149
179
  }
180
+ if (config.endAt !== undefined) {
181
+ if (typeof config.endAt !== 'string' || !isParseableDate(config.endAt)) {
182
+ errors.push('endAt must be a parseable ISO 8601 / RFC3339 timestamp (e.g., 2026-12-31T23:59:00Z)');
183
+ }
184
+ }
150
185
  return errors;
151
186
  }
187
+ function isParseableDate(value) {
188
+ if (!value.trim())
189
+ return false;
190
+ const ts = Date.parse(value);
191
+ return Number.isFinite(ts);
192
+ }
193
+ /** True when a job's endAt has already elapsed. False when endAt is unset or in the future. */
194
+ export function isPastEndAt(config, now = new Date()) {
195
+ if (!config.endAt)
196
+ return false;
197
+ const end = Date.parse(config.endAt);
198
+ if (!Number.isFinite(end))
199
+ return false;
200
+ return now.getTime() >= end;
201
+ }
152
202
  /** Expand built-in and user-defined template variables in a job's prompt string. */
153
203
  export function resolveJobPrompt(config) {
154
204
  const now = new Date();
@@ -10,8 +10,8 @@ import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as os from 'os';
12
12
  import * as crypto from 'crypto';
13
- import { AGENTS } from '../agents.js';
14
- import { getResolvedRulesDir, getVersionsDir } from '../state.js';
13
+ import { AGENTS, agentConfigDirName } from '../agents.js';
14
+ import { getVersionsDir } from '../state.js';
15
15
  import { composeRules, composeRulesFromState } from './compose.js';
16
16
  // Match `@path` preceded by start-of-string or whitespace. This avoids
17
17
  // matching emails ("foo@bar.com") and the middle of words. The leading
@@ -97,7 +97,7 @@ export function supportsRulesImports(agentId) {
97
97
  function getCompiledRulesPath(agentId, version) {
98
98
  const agentConfig = AGENTS[agentId];
99
99
  const versionHome = path.join(getVersionsDir(), agentId, version, 'home');
100
- return path.join(versionHome, `.${agentId}`, agentConfig.instructionsFile);
100
+ return path.join(versionHome, agentConfigDirName(agentId), agentConfig.instructionsFile);
101
101
  }
102
102
  function getManifestPath(compiledPath) {
103
103
  return compiledPath + '.manifest.json';
@@ -151,14 +151,24 @@ export function compileRulesForAgent(agentId, version) {
151
151
  if (supportsRulesImports(agentId)) {
152
152
  return { compiled: false, compiledPath: '', sources: 0 };
153
153
  }
154
- const rulesDir = getResolvedRulesDir();
155
- const sourceAgents = path.join(rulesDir, 'AGENTS.md');
156
- if (!fs.existsSync(sourceAgents)) {
154
+ // Route through the layered composer (project > user > extras > system).
155
+ // The previous implementation read only `<systemRules>/AGENTS.md` and
156
+ // inlined its @-imports — that dropped user/extras/project subrules
157
+ // entirely for @-import-incapable agents (Cursor, older Codex), so
158
+ // updates to ~/.agents/rules/subrules/ never reached the version home.
159
+ // composeRulesFromState already returns the concatenated content with
160
+ // every fragment resolved across layers; we just need to record the
161
+ // composed source list for staleness detection.
162
+ let composed;
163
+ try {
164
+ composed = composeRulesFromState({ preset: undefined });
165
+ }
166
+ catch {
167
+ // No rules.yaml in any layer, or the default preset is missing — leave
168
+ // the version home untouched (matches the previous file-missing branch).
157
169
  return { compiled: false, compiledPath: '', sources: 0 };
158
170
  }
159
- const rootContent = fs.readFileSync(sourceAgents, 'utf8');
160
- const { content, sources } = resolveImports(rootContent, rulesDir);
161
- const newContent = COMPILED_HEADER + content;
171
+ const newContent = COMPILED_HEADER + composed.content;
162
172
  const compiledPath = getCompiledRulesPath(agentId, version);
163
173
  fs.mkdirSync(path.dirname(compiledPath), { recursive: true });
164
174
  const existing = fs.existsSync(compiledPath) ? fs.readFileSync(compiledPath, 'utf8') : null;
@@ -166,7 +176,9 @@ export function compileRulesForAgent(agentId, version) {
166
176
  return { compiled: false, compiledPath, sources: 0 };
167
177
  }
168
178
  fs.writeFileSync(compiledPath, newContent);
169
- const allSources = [sourceAgents, ...sources];
179
+ // Track every concrete subrule file the composer included as a source for
180
+ // staleness. composeRulesFromState exposes sourcePath on each ComposedSubrule.
181
+ const allSources = composed.subrules.map(s => s.sourcePath);
170
182
  const manifest = {
171
183
  compiledAt: new Date().toISOString(),
172
184
  sources: allSources.map(p => {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
- import { AGENTS, ALL_AGENT_IDS } from '../agents.js';
11
+ import { AGENTS, ALL_AGENT_IDS, agentConfigDirName } from '../agents.js';
12
12
  import { getResolvedRulesDir, getUserRulesDir, getProjectAgentsDir } from '../state.js';
13
13
  import { getEffectiveHome } from '../versions.js';
14
14
  /**
@@ -65,7 +65,7 @@ function normalizeContent(content) {
65
65
  */
66
66
  function getUserConfigDir(agentId) {
67
67
  const home = getEffectiveHome(agentId);
68
- return path.join(home, `.${agentId}`);
68
+ return path.join(home, agentConfigDirName(agentId));
69
69
  }
70
70
  export function getInstructionsPath(agentId, scope, cwd = process.cwd()) {
71
71
  const agent = AGENTS[agentId];
@@ -211,7 +211,7 @@ export function listInstalledInstructionsWithScope(agentId, cwd = process.cwd(),
211
211
  const agent = AGENTS[agentId];
212
212
  // User-scoped instructions (version-aware when home is provided)
213
213
  const home = options?.home || getEffectiveHome(agentId);
214
- const userConfigDir = path.join(home, `.${agentId}`);
214
+ const userConfigDir = path.join(home, agentConfigDirName(agentId));
215
215
  const userPath = path.join(userConfigDir, agent.instructionsFile);
216
216
  results.push({
217
217
  agentId,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Project-local `run:` config discovery.
3
+ *
4
+ * The user/system `agents.yaml` is read through state.ts. Project-local
5
+ * agents.yaml files are discovered from the current working directory upward.
6
+ */
7
+ import type { RunConfig } from './types.js';
8
+ /** Return project-local run configs from nearest directory upward. */
9
+ export declare function getProjectRunConfigs(startPath?: string): RunConfig[];
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Project-local `run:` config discovery.
3
+ *
4
+ * The user/system `agents.yaml` is read through state.ts. Project-local
5
+ * agents.yaml files are discovered from the current working directory upward.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as yaml from 'yaml';
10
+ import { getUserAgentsDir } from './state.js';
11
+ function isRecord(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
14
+ /** Return project-local run configs from nearest directory upward. */
15
+ export function getProjectRunConfigs(startPath = process.cwd()) {
16
+ const configs = [];
17
+ let dir = path.resolve(startPath);
18
+ const userAgentsYaml = path.join(getUserAgentsDir(), 'agents.yaml');
19
+ while (dir !== path.dirname(dir)) {
20
+ const manifestPath = path.join(dir, 'agents.yaml');
21
+ if (manifestPath !== userAgentsYaml && fs.existsSync(manifestPath)) {
22
+ try {
23
+ const parsed = yaml.parse(fs.readFileSync(manifestPath, 'utf-8'));
24
+ if (isRecord(parsed?.run)) {
25
+ configs.push(parsed.run);
26
+ }
27
+ }
28
+ catch {
29
+ // Ignore malformed project config and keep walking.
30
+ }
31
+ }
32
+ dir = path.dirname(dir);
33
+ }
34
+ return configs;
35
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Selector-based defaults for `agents run`.
3
+ *
4
+ * Stored under agents.yaml:
5
+ *
6
+ * run:
7
+ * defaults:
8
+ * "claude:*":
9
+ * mode: auto
10
+ * model: opus
11
+ * "claude:2.1.45":
12
+ * mode: plan
13
+ */
14
+ import type { AgentId, Mode, RunConfig, RunDefaults } from './types.js';
15
+ export interface ParsedRunDefaultSelector {
16
+ agent: AgentId;
17
+ version: string;
18
+ selector: string;
19
+ }
20
+ export interface ResolvedRunDefaults extends RunDefaults {
21
+ sources: {
22
+ mode?: string;
23
+ model?: string;
24
+ };
25
+ }
26
+ export interface RunDefaultEntry {
27
+ selector: string;
28
+ defaults: RunDefaults;
29
+ }
30
+ type RunDefaultsInput = {
31
+ mode?: unknown;
32
+ model?: unknown;
33
+ };
34
+ export declare function normalizeRunDefaultMode(input: string): Mode;
35
+ export declare function parseRunDefaultSelector(input: string): ParsedRunDefaultSelector;
36
+ export declare function resolveRunDefaultsFromConfig(runConfig: RunConfig | undefined, agent: AgentId, version?: string | null): ResolvedRunDefaults;
37
+ export declare function resolveRunDefaultsFromConfigs(runConfigs: Array<RunConfig | undefined>, agent: AgentId, version?: string | null): ResolvedRunDefaults;
38
+ export declare function resolveRunDefaults(agent: AgentId, version?: string | null, startPath?: string): ResolvedRunDefaults;
39
+ export declare function listRunDefaults(): RunDefaultEntry[];
40
+ export declare function setRunDefault(selectorInput: string, defaultsInput: RunDefaultsInput): RunDefaultEntry;
41
+ export declare function unsetRunDefault(selectorInput: string): boolean;
42
+ export {};
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Selector-based defaults for `agents run`.
3
+ *
4
+ * Stored under agents.yaml:
5
+ *
6
+ * run:
7
+ * defaults:
8
+ * "claude:*":
9
+ * mode: auto
10
+ * model: opus
11
+ * "claude:2.1.45":
12
+ * mode: plan
13
+ */
14
+ import { ALL_MODES } from './types.js';
15
+ import { AGENTS } from './agents.js';
16
+ import { readMeta, updateMeta } from './state.js';
17
+ import { getProjectRunConfigs } from './run-config.js';
18
+ const VERSION_RE = /^(?:\*|latest|(?!.*\.\.)[A-Za-z0-9._+-]{1,64})$/;
19
+ function isAgentId(value) {
20
+ return value in AGENTS;
21
+ }
22
+ export function normalizeRunDefaultMode(input) {
23
+ const mode = input.trim().toLowerCase();
24
+ if (mode === 'full')
25
+ return 'skip';
26
+ if (ALL_MODES.includes(mode))
27
+ return mode;
28
+ throw new Error(`Invalid mode '${input}'. Use one of: ${ALL_MODES.join(', ')} (or 'full' as an alias for 'skip').`);
29
+ }
30
+ function normalizeRunDefaults(defaults, selector) {
31
+ const out = {};
32
+ if (defaults.mode !== undefined) {
33
+ if (typeof defaults.mode !== 'string') {
34
+ throw new Error(`Invalid mode in run.defaults.${selector}: expected a string.`);
35
+ }
36
+ out.mode = normalizeRunDefaultMode(defaults.mode);
37
+ }
38
+ if (defaults.model !== undefined) {
39
+ if (typeof defaults.model !== 'string' || defaults.model.trim() === '') {
40
+ throw new Error(`Invalid model in run.defaults.${selector}: expected a non-empty string.`);
41
+ }
42
+ out.model = defaults.model.trim();
43
+ }
44
+ return out;
45
+ }
46
+ export function parseRunDefaultSelector(input) {
47
+ const raw = input.trim();
48
+ if (!raw)
49
+ throw new Error('Selector is required. Use <agent>:<version>, <agent>@<version>, or <agent>:*.');
50
+ let agentPart;
51
+ let versionPart;
52
+ if (raw.includes('@')) {
53
+ const parts = raw.split('@');
54
+ if (parts.length !== 2)
55
+ throw new Error(`Invalid selector '${input}'. Use <agent>@<version>.`);
56
+ [agentPart, versionPart] = parts;
57
+ }
58
+ else if (raw.includes(':')) {
59
+ const idx = raw.indexOf(':');
60
+ agentPart = raw.slice(0, idx);
61
+ versionPart = raw.slice(idx + 1);
62
+ }
63
+ else {
64
+ agentPart = raw;
65
+ versionPart = '*';
66
+ }
67
+ const agent = agentPart.toLowerCase();
68
+ if (!isAgentId(agent)) {
69
+ throw new Error(`Invalid agent '${agentPart}'. Available agents: ${Object.keys(AGENTS).join(', ')}.`);
70
+ }
71
+ if (!VERSION_RE.test(versionPart)) {
72
+ throw new Error(`Invalid selector version '${versionPart}'. Use *, latest, or [A-Za-z0-9._+-]{1,64}.`);
73
+ }
74
+ return {
75
+ agent,
76
+ version: versionPart,
77
+ selector: `${agent}:${versionPart}`,
78
+ };
79
+ }
80
+ function sortedDefaults(defaults) {
81
+ return Object.fromEntries(Object.entries(defaults).sort(([a], [b]) => a.localeCompare(b)));
82
+ }
83
+ export function resolveRunDefaultsFromConfig(runConfig, agent, version) {
84
+ const defaults = runConfig?.defaults ?? {};
85
+ const wildcardSelector = `${agent}:*`;
86
+ const exactSelector = version ? `${agent}:${version}` : null;
87
+ const resolved = { sources: {} };
88
+ const wildcard = defaults[wildcardSelector]
89
+ ? normalizeRunDefaults(defaults[wildcardSelector], wildcardSelector)
90
+ : null;
91
+ if (wildcard?.mode) {
92
+ resolved.mode = wildcard.mode;
93
+ resolved.sources.mode = wildcardSelector;
94
+ }
95
+ if (wildcard?.model) {
96
+ resolved.model = wildcard.model;
97
+ resolved.sources.model = wildcardSelector;
98
+ }
99
+ if (exactSelector && defaults[exactSelector]) {
100
+ const exact = normalizeRunDefaults(defaults[exactSelector], exactSelector);
101
+ if (exact.mode) {
102
+ resolved.mode = exact.mode;
103
+ resolved.sources.mode = exactSelector;
104
+ }
105
+ if (exact.model) {
106
+ resolved.model = exact.model;
107
+ resolved.sources.model = exactSelector;
108
+ }
109
+ }
110
+ return resolved;
111
+ }
112
+ export function resolveRunDefaultsFromConfigs(runConfigs, agent, version) {
113
+ const resolved = { sources: {} };
114
+ for (const runConfig of runConfigs) {
115
+ const next = resolveRunDefaultsFromConfig(runConfig, agent, version);
116
+ if (next.mode) {
117
+ resolved.mode = next.mode;
118
+ resolved.sources.mode = next.sources.mode;
119
+ }
120
+ if (next.model) {
121
+ resolved.model = next.model;
122
+ resolved.sources.model = next.sources.model;
123
+ }
124
+ }
125
+ return resolved;
126
+ }
127
+ export function resolveRunDefaults(agent, version, startPath = process.cwd()) {
128
+ const projectRunConfigs = getProjectRunConfigs(startPath).reverse();
129
+ return resolveRunDefaultsFromConfigs([readMeta().run, ...projectRunConfigs], agent, version);
130
+ }
131
+ export function listRunDefaults() {
132
+ const defaults = readMeta().run?.defaults ?? {};
133
+ return Object.entries(defaults)
134
+ .sort(([a], [b]) => a.localeCompare(b))
135
+ .map(([selector, value]) => ({
136
+ selector,
137
+ defaults: normalizeRunDefaults(value, selector),
138
+ }));
139
+ }
140
+ export function setRunDefault(selectorInput, defaultsInput) {
141
+ const parsed = parseRunDefaultSelector(selectorInput);
142
+ const defaults = normalizeRunDefaults(defaultsInput, parsed.selector);
143
+ if (!defaults.mode && !defaults.model) {
144
+ throw new Error('Set at least one default: --mode <mode> or --model <model>.');
145
+ }
146
+ updateMeta((meta) => {
147
+ const run = { ...(meta.run ?? {}) };
148
+ const currentDefaults = { ...(run.defaults ?? {}) };
149
+ currentDefaults[parsed.selector] = {
150
+ ...(currentDefaults[parsed.selector] ?? {}),
151
+ ...defaults,
152
+ };
153
+ run.defaults = sortedDefaults(currentDefaults);
154
+ return { ...meta, run };
155
+ });
156
+ return {
157
+ selector: parsed.selector,
158
+ defaults: {
159
+ ...(readMeta().run?.defaults?.[parsed.selector] ?? {}),
160
+ },
161
+ };
162
+ }
163
+ export function unsetRunDefault(selectorInput) {
164
+ const parsed = parseRunDefaultSelector(selectorInput);
165
+ let removed = false;
166
+ updateMeta((meta) => {
167
+ const run = { ...(meta.run ?? {}) };
168
+ const currentDefaults = { ...(run.defaults ?? {}) };
169
+ removed = Object.prototype.hasOwnProperty.call(currentDefaults, parsed.selector);
170
+ delete currentDefaults[parsed.selector];
171
+ if (Object.keys(currentDefaults).length > 0) {
172
+ run.defaults = sortedDefaults(currentDefaults);
173
+ }
174
+ else {
175
+ delete run.defaults;
176
+ }
177
+ return { ...meta, run };
178
+ });
179
+ return removed;
180
+ }
@@ -21,6 +21,7 @@ const AGENT_COMMANDS = {
21
21
  claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
22
22
  codex: ['codex', 'exec', '--sandbox', 'workspace-write', '{prompt}', '--json'],
23
23
  gemini: ['gemini', '{prompt}', '--output-format', 'stream-json'],
24
+ kimi: ['kimi', '--prompt', '{prompt}', '--output-format', 'stream-json'],
24
25
  };
25
26
  /** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
26
27
  export function buildJobCommand(config, resolvedPrompt) {
@@ -72,14 +73,14 @@ export function buildJobCommand(config, resolvedPrompt) {
72
73
  }
73
74
  if (config.agent === 'codex') {
74
75
  if (mode === 'edit') {
75
- cmd.push('--full-auto');
76
+ cmd.push('--dangerously-bypass-approvals-and-sandbox');
76
77
  }
77
78
  else if (mode === 'skip') {
78
- // Remove sandbox restriction, just --full-auto
79
+ // Remove sandbox restriction, just --dangerously-bypass-approvals-and-sandbox
79
80
  const sbIndex = cmd.indexOf('--sandbox');
80
81
  if (sbIndex !== -1)
81
82
  cmd.splice(sbIndex, 2);
82
- cmd.push('--full-auto');
83
+ cmd.push('--dangerously-bypass-approvals-and-sandbox');
83
84
  }
84
85
  appendModelAndReasoning(cmd, config);
85
86
  }
@@ -92,6 +93,18 @@ export function buildJobCommand(config, resolvedPrompt) {
92
93
  }
93
94
  appendModelAndReasoning(cmd, config);
94
95
  }
96
+ if (config.agent === 'kimi') {
97
+ if (mode === 'plan') {
98
+ cmd.push('--plan');
99
+ }
100
+ else if (mode === 'auto') {
101
+ cmd.push('--auto');
102
+ }
103
+ else if (mode === 'skip') {
104
+ cmd.push('--yolo');
105
+ }
106
+ appendModelAndReasoning(cmd, config);
107
+ }
95
108
  return cmd;
96
109
  }
97
110
  /**
@@ -6,7 +6,7 @@
6
6
  * on startup and reloads them on SIGHUP.
7
7
  */
8
8
  import { Cron } from 'croner';
9
- import { listJobs, deleteJob } from './routines.js';
9
+ import { listJobs, deleteJob, isPastEndAt, setJobEnabled } from './routines.js';
10
10
  /** In-memory cron scheduler that triggers a callback when jobs fire. */
11
11
  export class JobScheduler {
12
12
  jobs = new Map();
@@ -32,6 +32,20 @@ export class JobScheduler {
32
32
  if (config.timezone)
33
33
  cronOptions.timezone = config.timezone;
34
34
  const cron = new Cron(config.schedule, cronOptions, async () => {
35
+ // endAt: once the configured end time has passed, auto-disable and stop
36
+ // firing. We persist enabled=false to disk so the next daemon reload
37
+ // doesn't re-schedule, and unschedule in-memory so this cron stops.
38
+ if (isPastEndAt(config)) {
39
+ this.unschedule(config.name);
40
+ try {
41
+ setJobEnabled(config.name, false);
42
+ }
43
+ catch (err) {
44
+ console.error(`Job '${config.name}' endAt auto-disable failed:`, err.message);
45
+ }
46
+ console.log(`Job '${config.name}' reached endAt (${config.endAt}); auto-disabled.`);
47
+ return;
48
+ }
35
49
  try {
36
50
  await this.onTrigger(config);
37
51
  }
@@ -5,7 +5,15 @@
5
5
  <key>files</key>
6
6
  <dict/>
7
7
  <key>files2</key>
8
- <dict/>
8
+ <dict>
9
+ <key>embedded.provisionprofile</key>
10
+ <dict>
11
+ <key>hash2</key>
12
+ <data>
13
+ 2vfA/eR3dTYgNc/fXhdADUPkp5tRIepPzE3FCLfDx4w=
14
+ </data>
15
+ </dict>
16
+ </dict>
9
17
  <key>rules</key>
10
18
  <dict>
11
19
  <key>^Resources/</key>
@@ -12,10 +12,14 @@
12
12
  * modules in `src/lib/secrets/` must import `getKeychainHelperPath()` rather
13
13
  * than recomputing it.
14
14
  */
15
+ /** Redirect the install root (test only). Returns the previous override so callers can restore. */
16
+ export declare function setInstallRootForTest(dir: string | null): string | null;
15
17
  /**
16
18
  * Idempotent install. Copies the bundled `.app` to the stable user path. Skips
17
- * if the destination already exists and `codesign --verify` passes, unless
18
- * `forceReinstall=true`.
19
+ * if the destination already exists, `codesign --verify` passes, AND the
20
+ * installed executable matches the bundled one byte-for-byte — a valid
21
+ * signature alone is not enough, because an outdated helper signs clean too.
22
+ * `forceReinstall=true` skips all checks and always copies.
19
23
  *
20
24
  * Notarization is checked via `spctl --assess` after install — a failure is
21
25
  * logged as a warning but does NOT throw. Notarization checks require network
@@ -27,7 +31,11 @@ export declare function ensureKeychainHelperInstalled(opts?: {
27
31
  }): void;
28
32
  /**
29
33
  * Return the absolute path to the helper executable. If the installed bundle
30
- * is missing, performs a lazy install first.
34
+ * is missing, or is stale relative to the bundled source helper, performs a
35
+ * lazy (re)install first. The staleness check is what lets an upgraded CLI
36
+ * replace a helper a previous version installed — `agents helper install`
37
+ * never runs on `npm i -g`, so this call site is the only one every machine
38
+ * is guaranteed to pass through.
31
39
  *
32
40
  * Throws on non-darwin.
33
41
  */