@phnx-labs/agents-cli 1.20.0 → 1.20.3

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 (105) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +4 -4
  3. package/dist/commands/cli.js +3 -3
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +24 -7
  6. package/dist/commands/exec.js +36 -16
  7. package/dist/commands/feedback.d.ts +7 -0
  8. package/dist/commands/feedback.js +89 -0
  9. package/dist/commands/helper.d.ts +12 -0
  10. package/dist/commands/helper.js +87 -0
  11. package/dist/commands/hooks.js +86 -7
  12. package/dist/commands/mcp.js +166 -10
  13. package/dist/commands/packages.js +196 -27
  14. package/dist/commands/permissions.js +21 -6
  15. package/dist/commands/profiles.d.ts +8 -0
  16. package/dist/commands/profiles.js +117 -4
  17. package/dist/commands/pull.js +4 -4
  18. package/dist/commands/routines.js +6 -6
  19. package/dist/commands/rules.js +8 -4
  20. package/dist/commands/secrets-migrate.d.ts +24 -0
  21. package/dist/commands/secrets-migrate.js +198 -0
  22. package/dist/commands/secrets-sync.d.ts +11 -0
  23. package/dist/commands/secrets-sync.js +155 -0
  24. package/dist/commands/secrets.js +74 -39
  25. package/dist/commands/skills.js +22 -5
  26. package/dist/commands/subagents.js +69 -49
  27. package/dist/commands/teams.js +48 -10
  28. package/dist/commands/utils.d.ts +33 -0
  29. package/dist/commands/utils.js +139 -0
  30. package/dist/commands/versions.js +4 -4
  31. package/dist/commands/view.d.ts +6 -0
  32. package/dist/commands/view.js +164 -8
  33. package/dist/commands/workflows.js +29 -6
  34. package/dist/index.js +4 -0
  35. package/dist/lib/acp/client.js +6 -1
  36. package/dist/lib/agents.d.ts +4 -0
  37. package/dist/lib/agents.js +18 -14
  38. package/dist/lib/auto-pull-worker.js +18 -1
  39. package/dist/lib/browser/chrome.js +4 -0
  40. package/dist/lib/browser/drivers/ssh.js +1 -1
  41. package/dist/lib/browser/profiles.d.ts +3 -3
  42. package/dist/lib/browser/profiles.js +3 -3
  43. package/dist/lib/browser/service.js +19 -0
  44. package/dist/lib/browser/types.d.ts +4 -4
  45. package/dist/lib/cli-resources.d.ts +36 -8
  46. package/dist/lib/cli-resources.js +268 -46
  47. package/dist/lib/cloud/factory.d.ts +1 -1
  48. package/dist/lib/cloud/factory.js +1 -1
  49. package/dist/lib/events.d.ts +16 -2
  50. package/dist/lib/events.js +33 -2
  51. package/dist/lib/exec.d.ts +39 -11
  52. package/dist/lib/exec.js +90 -31
  53. package/dist/lib/help.js +11 -5
  54. package/dist/lib/hooks/cache.d.ts +38 -0
  55. package/dist/lib/hooks/cache.js +242 -0
  56. package/dist/lib/hooks/profile.d.ts +33 -0
  57. package/dist/lib/hooks/profile.js +129 -0
  58. package/dist/lib/hooks.d.ts +0 -10
  59. package/dist/lib/hooks.js +68 -15
  60. package/dist/lib/mcp.d.ts +15 -0
  61. package/dist/lib/mcp.js +40 -0
  62. package/dist/lib/permissions.d.ts +13 -0
  63. package/dist/lib/permissions.js +51 -1
  64. package/dist/lib/plugins.js +15 -1
  65. package/dist/lib/profiles-presets.d.ts +26 -0
  66. package/dist/lib/profiles-presets.js +187 -8
  67. package/dist/lib/profiles.d.ts +34 -0
  68. package/dist/lib/profiles.js +112 -1
  69. package/dist/lib/routines-format.d.ts +17 -5
  70. package/dist/lib/routines-format.js +37 -16
  71. package/dist/lib/routines.d.ts +1 -1
  72. package/dist/lib/routines.js +2 -2
  73. package/dist/lib/runner.js +64 -10
  74. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  75. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  76. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  77. package/dist/lib/secrets/bundles.d.ts +18 -22
  78. package/dist/lib/secrets/bundles.js +75 -99
  79. package/dist/lib/secrets/index.d.ts +51 -27
  80. package/dist/lib/secrets/index.js +147 -156
  81. package/dist/lib/secrets/install-helper.d.ts +45 -0
  82. package/dist/lib/secrets/install-helper.js +165 -0
  83. package/dist/lib/secrets/linux.js +4 -4
  84. package/dist/lib/secrets/sync.d.ts +56 -0
  85. package/dist/lib/secrets/sync.js +180 -0
  86. package/dist/lib/session/render.js +4 -4
  87. package/dist/lib/session/types.d.ts +1 -1
  88. package/dist/lib/shims.d.ts +4 -1
  89. package/dist/lib/shims.js +5 -35
  90. package/dist/lib/state.d.ts +14 -1
  91. package/dist/lib/state.js +49 -5
  92. package/dist/lib/teams/agents.d.ts +5 -4
  93. package/dist/lib/teams/agents.js +47 -21
  94. package/dist/lib/teams/api.d.ts +2 -1
  95. package/dist/lib/teams/api.js +4 -3
  96. package/dist/lib/types.d.ts +57 -1
  97. package/dist/lib/types.js +2 -0
  98. package/dist/lib/usage.d.ts +27 -2
  99. package/dist/lib/usage.js +100 -17
  100. package/dist/lib/versions.d.ts +35 -1
  101. package/dist/lib/versions.js +267 -64
  102. package/package.json +9 -8
  103. package/scripts/install-helper.js +97 -0
  104. package/scripts/postinstall.js +16 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
@@ -91,41 +91,220 @@ export const PRESETS = [
91
91
  // ----- xAI Grok Build CLI (native host) -----
92
92
  {
93
93
  name: 'grok-fast',
94
- description: 'xAI Grok Build CLI — fast tier. Native grok host, no OpenRouter wrapper.',
94
+ description: 'xAI Grok Build CLI — fast tier. Optimized for speed and low-latency coding tasks.',
95
95
  provider: 'xai',
96
96
  host: 'grok',
97
97
  authEnvVar: 'XAI_API_KEY',
98
98
  signupUrl: 'https://console.x.ai',
99
99
  env: {
100
- // TODO: confirm model id (docs.x.ai/build/models, May 2026)
101
- GROK_MODEL: 'grok-build-fast',
100
+ GROK_MODEL: 'grok-build-0.1',
102
101
  },
103
102
  },
104
103
  {
105
104
  name: 'grok-heavy',
106
- description: 'xAI Grok Build CLI — heavy tier (SuperGrok). Native grok host.',
105
+ description: 'xAI Grok Build CLI — flagship tier (Grok 4.3). Best for complex reasoning and large context windows.',
107
106
  provider: 'xai',
108
107
  host: 'grok',
109
108
  authEnvVar: 'XAI_API_KEY',
110
109
  signupUrl: 'https://console.x.ai',
111
110
  env: {
112
- // TODO: confirm model id (docs.x.ai/build/models, May 2026)
113
- GROK_MODEL: 'grok-build',
111
+ GROK_MODEL: 'grok-4.3',
114
112
  },
115
113
  },
116
114
  // ----- Google Antigravity CLI (native host) -----
117
115
  {
118
116
  name: 'agy',
119
- description: 'Google Antigravity CLI default. Auth via Google OAuth or ANTIGRAVITY_API_KEY.',
117
+ description: 'Google Antigravity CLI default (gemini-3.5-flash). Optimized for speed and large context.',
120
118
  provider: 'google',
121
119
  host: 'antigravity',
122
120
  authEnvVar: 'ANTIGRAVITY_API_KEY',
123
121
  signupUrl: 'https://antigravity.google',
124
122
  env: {
125
- // TODO: confirm model id — antigravity defaults are managed by the CLI itself
123
+ // Antigravity defaults to gemini-3.5-flash as of June 2026
126
124
  },
127
125
  },
126
+ // ----- Direct Providers -----
127
+ {
128
+ name: 'anthropic',
129
+ description: 'Anthropic direct API — standard Claude Code experience with your own API key.',
130
+ provider: 'anthropic',
131
+ host: 'claude',
132
+ authEnvVar: 'ANTHROPIC_API_KEY',
133
+ signupUrl: 'https://console.anthropic.com',
134
+ env: {
135
+ ANTHROPIC_MODEL: 'claude-3-5-sonnet-latest',
136
+ ANTHROPIC_SMALL_FAST_MODEL: 'claude-3-5-haiku-latest',
137
+ },
138
+ },
139
+ // ----- Gateway / enterprise / self-hosted -----
140
+ {
141
+ name: 'proxy',
142
+ description: 'Generic local proxy / gateway — points at a local router (CCR, LiteLLM) or internal corporate inference endpoint.',
143
+ provider: 'proxy',
144
+ host: 'claude',
145
+ authEnvVar: 'ANTHROPIC_AUTH_TOKEN',
146
+ authOptional: true,
147
+ env: {
148
+ API_TIMEOUT_MS: '600000',
149
+ },
150
+ vars: [
151
+ {
152
+ envVar: 'ANTHROPIC_BASE_URL',
153
+ prompt: 'Gateway base URL',
154
+ default: 'http://127.0.0.1:3456',
155
+ },
156
+ {
157
+ envVar: 'ANTHROPIC_MODEL',
158
+ prompt: 'Model ID',
159
+ default: 'claude-3-5-sonnet-latest',
160
+ },
161
+ ],
162
+ },
163
+ {
164
+ name: 'truefoundry',
165
+ description: 'TrueFoundry AI Gateway routing to Anthropic-compatible backends (often Bedrock). Strips experimental headers + disables prompt caching to satisfy Bedrock validation.',
166
+ provider: 'truefoundry',
167
+ host: 'claude',
168
+ authEnvVar: 'ANTHROPIC_AUTH_TOKEN',
169
+ signupUrl: 'https://www.truefoundry.com',
170
+ docPath: 'truefoundry',
171
+ env: {
172
+ CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: '1',
173
+ CLAUDE_CODE_ATTRIBUTION_HEADER: '0',
174
+ DISABLE_PROMPT_CACHING: '1',
175
+ API_TIMEOUT_MS: '600000',
176
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1',
177
+ },
178
+ vars: [
179
+ {
180
+ envVar: 'ANTHROPIC_BASE_URL',
181
+ prompt: 'TrueFoundry gateway base URL',
182
+ hint: 'e.g. https://<tenant>.truefoundry.cloud/api/llm',
183
+ },
184
+ {
185
+ envVar: 'ANTHROPIC_MODEL',
186
+ prompt: 'Model ID',
187
+ hint: 'provider-account/model-id',
188
+ },
189
+ ],
190
+ },
191
+ {
192
+ name: 'bedrock',
193
+ description: 'AWS Bedrock — Claude Code native Bedrock mode. Uses the standard AWS SDK credential chain (SSO, IAM roles, env). Set AWS_BEARER_TOKEN_BEDROCK only if your gateway requires a static token.',
194
+ provider: 'bedrock',
195
+ host: 'claude',
196
+ authEnvVar: 'AWS_BEARER_TOKEN_BEDROCK',
197
+ authOptional: true,
198
+ signupUrl: 'https://aws.amazon.com/bedrock/',
199
+ env: {
200
+ CLAUDE_CODE_USE_BEDROCK: '1',
201
+ DISABLE_PROMPT_CACHING: '1',
202
+ },
203
+ vars: [
204
+ {
205
+ envVar: 'AWS_REGION',
206
+ prompt: 'AWS region',
207
+ default: 'us-east-1',
208
+ },
209
+ ],
210
+ },
211
+ {
212
+ name: 'vertex',
213
+ description: 'Google Vertex AI — Claude Code native Vertex mode.',
214
+ provider: 'vertex',
215
+ host: 'claude',
216
+ authEnvVar: 'GOOGLE_APPLICATION_CREDENTIALS',
217
+ signupUrl: 'https://cloud.google.com/vertex-ai',
218
+ env: {
219
+ CLAUDE_CODE_USE_VERTEX: '1',
220
+ },
221
+ vars: [
222
+ {
223
+ envVar: 'CLOUD_ML_REGION',
224
+ prompt: 'Vertex region',
225
+ default: 'us-east5',
226
+ },
227
+ {
228
+ envVar: 'ANTHROPIC_VERTEX_PROJECT_ID',
229
+ prompt: 'GCP project ID',
230
+ },
231
+ ],
232
+ },
233
+ {
234
+ name: 'foundry',
235
+ description: 'Microsoft Azure AI Foundry — Anthropic models hosted on Azure. Distinct from TrueFoundry.',
236
+ provider: 'foundry',
237
+ host: 'claude',
238
+ authEnvVar: 'ANTHROPIC_FOUNDRY_API_KEY',
239
+ signupUrl: 'https://ai.azure.com',
240
+ env: {
241
+ CLAUDE_CODE_USE_FOUNDRY: '1',
242
+ },
243
+ vars: [
244
+ {
245
+ envVar: 'ANTHROPIC_FOUNDRY_BASE_URL',
246
+ prompt: 'Azure AI Foundry base URL',
247
+ hint: '<resource>.services.ai.azure.com/anthropic',
248
+ },
249
+ ],
250
+ },
251
+ {
252
+ name: 'litellm',
253
+ description: 'LiteLLM proxy in Anthropic-compatible mode.',
254
+ provider: 'litellm',
255
+ host: 'claude',
256
+ authEnvVar: 'ANTHROPIC_AUTH_TOKEN',
257
+ env: {
258
+ API_TIMEOUT_MS: '600000',
259
+ },
260
+ vars: [
261
+ { envVar: 'ANTHROPIC_BASE_URL', prompt: 'LiteLLM base URL' },
262
+ { envVar: 'ANTHROPIC_MODEL', prompt: 'Model ID' },
263
+ ],
264
+ },
265
+ {
266
+ name: 'vllm',
267
+ description: 'Self-hosted vLLM with native Anthropic-compatible endpoint.',
268
+ provider: 'vllm',
269
+ host: 'claude',
270
+ authEnvVar: 'ANTHROPIC_AUTH_TOKEN',
271
+ env: {
272
+ API_TIMEOUT_MS: '600000',
273
+ },
274
+ vars: [
275
+ {
276
+ envVar: 'ANTHROPIC_BASE_URL',
277
+ prompt: 'vLLM base URL',
278
+ default: 'http://127.0.0.1:8000',
279
+ },
280
+ { envVar: 'ANTHROPIC_MODEL', prompt: 'Model ID' },
281
+ ],
282
+ },
283
+ {
284
+ name: 'ollama',
285
+ description: 'Local Ollama via Codex CLI (OpenAI-compatible). Codex host because Anthropic translation through CCR/LiteLLM drops tool_use.',
286
+ provider: 'ollama',
287
+ host: 'codex',
288
+ authEnvVar: 'OPENAI_API_KEY',
289
+ env: {},
290
+ vars: [
291
+ {
292
+ envVar: 'OPENAI_BASE_URL',
293
+ prompt: 'Ollama base URL',
294
+ default: 'http://127.0.0.1:11434/v1',
295
+ },
296
+ {
297
+ envVar: 'OPENAI_MODEL',
298
+ prompt: 'Model ID',
299
+ default: 'qwen3-coder:30b',
300
+ },
301
+ ],
302
+ },
128
303
  ];
304
+ /** Split a preset into static env vars and prompts needed from the user. */
305
+ export function expandPreset(p) {
306
+ return { static: { ...p.env }, prompts: p.vars ?? [] };
307
+ }
129
308
  /** Look up a preset by name (case-sensitive). */
130
309
  export function getPreset(name) {
131
310
  return PRESETS.find((p) => p.name === name);
@@ -23,8 +23,24 @@ export interface Profile {
23
23
  preset?: string;
24
24
  provider?: string;
25
25
  }
26
+ /**
27
+ * Stable, machine-readable summary used by `agents view` and `--json`.
28
+ * `agent` is the underlying harness (claude/codex/...) so consumers can
29
+ * group profiles under installed agents without reparsing host strings.
30
+ */
31
+ export interface ProfileSummary {
32
+ name: string;
33
+ agent: AgentId;
34
+ host: string;
35
+ provider: string;
36
+ model: string;
37
+ auth: string;
38
+ path: string;
39
+ }
26
40
  /** Get the directory where profile YAML files are stored. */
27
41
  export declare function getProfilesDir(): string;
42
+ /** Return the on-disk YAML path for a profile name. */
43
+ export declare function getProfilePath(name: string): string;
28
44
  /** Validate a profile name against the allowed pattern. Throws on invalid input. */
29
45
  export declare function validateProfileName(name: string): void;
30
46
  /** Check whether a profile YAML file exists on disk. */
@@ -37,6 +53,24 @@ export declare function writeProfile(profile: Profile): void;
37
53
  export declare function deleteProfile(name: string): boolean;
38
54
  /** List all valid profiles, sorted by name. Malformed files are silently skipped. */
39
55
  export declare function listProfiles(): Profile[];
56
+ /** Format the host harness and optional pinned version for display. */
57
+ export declare function profileHostLabel(profile: Profile): string;
58
+ /** Return the configured provider name, deriving it from the shared keychain item when needed. */
59
+ export declare function profileProviderLabel(profile: Profile): string;
60
+ /** Return the configured model env value for display. */
61
+ export declare function profileModelLabel(profile: Profile): string;
62
+ /**
63
+ * Build a non-secret auth identity/status label for list surfaces.
64
+ *
65
+ * - Inline JWT in env: decode locally and show email / preferred_username / sub.
66
+ * - Inline opaque token in env: masked prefix/suffix (user explicitly stored it
67
+ * in the YAML, so they accept the leak in their own output).
68
+ * - Keychain-backed auth: provider + "stored" or "missing" (non-prompting).
69
+ * - No auth at all: provider only.
70
+ */
71
+ export declare function profileAuthLabel(profile: Profile): string;
72
+ /** Build a stable, machine-readable summary for list and view surfaces. */
73
+ export declare function profileSummary(profile: Profile): ProfileSummary;
40
74
  /**
41
75
  * Build a profile from a preset. The keychain item is shared across all
42
76
  * profiles that point at the same provider, so adding kimi + deepseek prompts
@@ -9,7 +9,7 @@ import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import * as yaml from 'yaml';
11
11
  import { getUserAgentsDir } from './state.js';
12
- import { getKeychainToken, keychainItemName } from './secrets/profiles.js';
12
+ import { getKeychainToken, hasKeychainToken, keychainItemName } from './secrets/profiles.js';
13
13
  import { getPreset } from './profiles-presets.js';
14
14
  const PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-_]{0,48}$/i;
15
15
  /** Get the directory where profile YAML files are stored. */
@@ -19,6 +19,11 @@ export function getProfilesDir() {
19
19
  function profilePath(name) {
20
20
  return path.join(getProfilesDir(), `${name}.yml`);
21
21
  }
22
+ /** Return the on-disk YAML path for a profile name. */
23
+ export function getProfilePath(name) {
24
+ validateProfileName(name);
25
+ return profilePath(name);
26
+ }
22
27
  /** Validate a profile name against the allowed pattern. Throws on invalid input. */
23
28
  export function validateProfileName(name) {
24
29
  if (!PROFILE_NAME_PATTERN.test(name)) {
@@ -89,6 +94,112 @@ export function listProfiles() {
89
94
  }
90
95
  return profiles.sort((a, b) => a.name.localeCompare(b.name));
91
96
  }
97
+ /** Format the host harness and optional pinned version for display. */
98
+ export function profileHostLabel(profile) {
99
+ return profile.host.version ? `${profile.host.agent}@${profile.host.version}` : profile.host.agent;
100
+ }
101
+ /** Return the configured provider name, deriving it from the shared keychain item when needed. */
102
+ export function profileProviderLabel(profile) {
103
+ return profile.provider || profile.auth?.keychainItem?.split('.')[1] || '-';
104
+ }
105
+ const MODEL_ENV_KEYS = [
106
+ 'ANTHROPIC_MODEL',
107
+ 'ANTHROPIC_SMALL_FAST_MODEL',
108
+ 'OPENAI_MODEL',
109
+ 'GEMINI_MODEL',
110
+ 'GROK_MODEL',
111
+ ];
112
+ /** Return the configured model env value for display. */
113
+ export function profileModelLabel(profile) {
114
+ for (const key of MODEL_ENV_KEYS) {
115
+ const value = profile.env[key];
116
+ if (value)
117
+ return value;
118
+ }
119
+ for (const [key, value] of Object.entries(profile.env)) {
120
+ if ((key === 'MODEL' || key.endsWith('_MODEL')) && value)
121
+ return value;
122
+ }
123
+ return '-';
124
+ }
125
+ function decodeJwtPayload(token) {
126
+ const parts = token.split('.');
127
+ if (parts.length < 2)
128
+ return null;
129
+ try {
130
+ const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');
131
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
132
+ const decoded = Buffer.from(padded, 'base64').toString('utf-8');
133
+ const parsed = JSON.parse(decoded);
134
+ return parsed && typeof parsed === 'object' ? parsed : null;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ function maskToken(token) {
141
+ if (token.length <= 12)
142
+ return `${token.slice(0, 3)}...${token.slice(-2)}`;
143
+ return `${token.slice(0, 6)}...${token.slice(-4)}`;
144
+ }
145
+ const INLINE_AUTH_KEYS = [
146
+ 'ANTHROPIC_AUTH_TOKEN',
147
+ 'ANTHROPIC_API_KEY',
148
+ 'OPENAI_API_KEY',
149
+ 'GEMINI_API_KEY',
150
+ 'GOOGLE_API_KEY',
151
+ 'XAI_API_KEY',
152
+ ];
153
+ function inlineAuthToken(profile) {
154
+ if (profile.auth?.envVar && profile.env[profile.auth.envVar]) {
155
+ return profile.env[profile.auth.envVar];
156
+ }
157
+ for (const key of INLINE_AUTH_KEYS) {
158
+ const value = profile.env[key];
159
+ if (value)
160
+ return value;
161
+ }
162
+ return undefined;
163
+ }
164
+ /**
165
+ * Build a non-secret auth identity/status label for list surfaces.
166
+ *
167
+ * - Inline JWT in env: decode locally and show email / preferred_username / sub.
168
+ * - Inline opaque token in env: masked prefix/suffix (user explicitly stored it
169
+ * in the YAML, so they accept the leak in their own output).
170
+ * - Keychain-backed auth: provider + "stored" or "missing" (non-prompting).
171
+ * - No auth at all: provider only.
172
+ */
173
+ export function profileAuthLabel(profile) {
174
+ const provider = profileProviderLabel(profile);
175
+ const token = inlineAuthToken(profile);
176
+ if (token) {
177
+ const payload = decodeJwtPayload(token);
178
+ const identity = payload?.email ||
179
+ payload?.preferred_username ||
180
+ payload?.username ||
181
+ payload?.sub;
182
+ if (typeof identity === 'string')
183
+ return `${provider} ${identity}`;
184
+ return `${provider} ${maskToken(token)}`;
185
+ }
186
+ if (profile.auth) {
187
+ return `${provider} ${hasKeychainToken(profile.auth.keychainItem) ? 'stored' : 'missing'}`;
188
+ }
189
+ return provider;
190
+ }
191
+ /** Build a stable, machine-readable summary for list and view surfaces. */
192
+ export function profileSummary(profile) {
193
+ return {
194
+ name: profile.name,
195
+ agent: profile.host.agent,
196
+ host: profileHostLabel(profile),
197
+ provider: profileProviderLabel(profile),
198
+ model: profileModelLabel(profile),
199
+ auth: profileAuthLabel(profile),
200
+ path: getProfilePath(profile.name),
201
+ };
202
+ }
92
203
  /**
93
204
  * Build a profile from a preset. The keychain item is shared across all
94
205
  * profiles that point at the same provider, so adding kimi + deepseek prompts
@@ -20,16 +20,28 @@ export declare function humanizeCron(expr: string, _tz?: string): string;
20
20
  * - further out → 'Jun 15, 9:00 AM'
21
21
  */
22
22
  export declare function humanizeNextRun(date: Date | null, now: Date, tz?: string): string;
23
+ /**
24
+ * Maximum display length for a repo cell. Display strings longer than this
25
+ * are truncated with an ellipsis so column alignment is preserved.
26
+ * Consumers that render the column should use this constant as the column width.
27
+ */
28
+ export declare const REPO_DISPLAY_MAX = 24;
23
29
  /**
24
30
  * Parse a repo string into a display label and an optional hyperlink target.
25
31
  *
26
32
  * Rules:
27
- * - undefined / empty → display '-', href null
28
- * - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
29
- * - 'https://...' or 'http://…' → display hostname+path, href the URL verbatim
30
- * - anything else → display raw string, href null
33
+ * - null / undefined / empty / non-string → display '-', href null
34
+ * - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
35
+ * - 'https://...' or 'http://...' → display hostname+path, href the URL verbatim
36
+ * - anything else → display raw string, href null
37
+ *
38
+ * The display string is truncated to REPO_DISPLAY_MAX characters (with a
39
+ * trailing '…') when it would otherwise exceed the column width. The href
40
+ * is always the full untruncated URL so hyperlinks remain functional.
41
+ *
42
+ * NEVER throws — mirrors the contract of humanizeCron.
31
43
  */
32
- export declare function formatRepoLink(repo: string | undefined): {
44
+ export declare function formatRepoLink(repo: unknown): {
33
45
  display: string;
34
46
  href: string | null;
35
47
  };
@@ -136,38 +136,59 @@ export function humanizeNextRun(date, now, tz) {
136
136
  // ---------------------------------------------------------------------------
137
137
  // formatRepoLink
138
138
  // ---------------------------------------------------------------------------
139
+ /**
140
+ * Maximum display length for a repo cell. Display strings longer than this
141
+ * are truncated with an ellipsis so column alignment is preserved.
142
+ * Consumers that render the column should use this constant as the column width.
143
+ */
144
+ export const REPO_DISPLAY_MAX = 24;
139
145
  /**
140
146
  * Parse a repo string into a display label and an optional hyperlink target.
141
147
  *
142
148
  * Rules:
143
- * - undefined / empty → display '-', href null
144
- * - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
145
- * - 'https://...' or 'http://…' → display hostname+path, href the URL verbatim
146
- * - anything else → display raw string, href null
149
+ * - null / undefined / empty / non-string → display '-', href null
150
+ * - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
151
+ * - 'https://...' or 'http://...' → display hostname+path, href the URL verbatim
152
+ * - anything else → display raw string, href null
153
+ *
154
+ * The display string is truncated to REPO_DISPLAY_MAX characters (with a
155
+ * trailing '…') when it would otherwise exceed the column width. The href
156
+ * is always the full untruncated URL so hyperlinks remain functional.
157
+ *
158
+ * NEVER throws — mirrors the contract of humanizeCron.
147
159
  */
148
160
  export function formatRepoLink(repo) {
149
- if (!repo || repo.trim() === '') {
161
+ if (repo == null || typeof repo !== 'string' || repo.trim() === '') {
150
162
  return { display: '-', href: null };
151
163
  }
152
164
  const trimmed = repo.trim();
165
+ let display;
166
+ let href;
153
167
  // Absolute URL
154
168
  if (trimmed.startsWith('https://') || trimmed.startsWith('http://')) {
155
169
  try {
156
170
  const url = new URL(trimmed);
157
- const display = url.hostname + url.pathname.replace(/\/$/, '');
158
- return { display, href: trimmed };
171
+ display = url.hostname + url.pathname.replace(/\/$/, '');
172
+ href = trimmed;
159
173
  }
160
174
  catch {
161
- return { display: trimmed, href: null };
175
+ display = trimmed;
176
+ href = null;
162
177
  }
163
178
  }
164
- // GitHub shorthand: owner/name (exactly one slash, no scheme, no extra slashes)
165
- if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(trimmed)) {
166
- return {
167
- display: trimmed,
168
- href: `https://github.com/${trimmed}/pulls`,
169
- };
179
+ else if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(trimmed)) {
180
+ // GitHub shorthand: owner/name (exactly one slash, no scheme, no extra slashes)
181
+ display = trimmed;
182
+ href = `https://github.com/${trimmed}/pulls`;
183
+ }
184
+ else {
185
+ // Anything else: plain text, no link
186
+ display = trimmed;
187
+ href = null;
188
+ }
189
+ // Truncate display to column width; href stays untruncated.
190
+ if (display.length > REPO_DISPLAY_MAX) {
191
+ display = display.slice(0, REPO_DISPLAY_MAX - 1) + '…';
170
192
  }
171
- // Anything else: plain text, no link
172
- return { display: trimmed, href: null };
193
+ return { display, href };
173
194
  }
@@ -19,7 +19,7 @@ export interface JobConfig {
19
19
  schedule: string;
20
20
  agent: AgentId;
21
21
  workflow?: string;
22
- mode: 'plan' | 'edit' | 'full';
22
+ mode: 'plan' | 'edit' | 'auto' | 'skip' | 'full';
23
23
  effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
24
24
  timeout: string;
25
25
  enabled: boolean;
@@ -135,8 +135,8 @@ export function validateJob(config) {
135
135
  errors.push('workflow must be a lowercase alphanumeric name (hyphens and underscores allowed, e.g. autodev)');
136
136
  }
137
137
  }
138
- if (config.mode && !['plan', 'edit', 'full'].includes(config.mode)) {
139
- errors.push('mode must be plan, edit, or full');
138
+ if (config.mode && !['plan', 'edit', 'auto', 'skip', 'full'].includes(config.mode)) {
139
+ errors.push("mode must be plan, edit, auto, or skip ('full' accepted as alias for skip)");
140
140
  }
141
141
  if (config.effort && !['low', 'medium', 'high', 'xhigh', 'max', 'auto'].includes(config.effort)) {
142
142
  errors.push('effort must be low, medium, high, xhigh, max, or auto');