@vm0/cli 9.20.2 → 9.22.0

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 (2) hide show
  1. package/index.js +587 -341
  2. package/package.json +2 -1
package/index.js CHANGED
@@ -1,5 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/instrument.ts
4
+ import * as Sentry from "@sentry/node";
5
+ import * as os from "os";
6
+ var DSN = process.env.SENTRY_DSN ?? "https://268d9b4cd051531805af76a5b3934dca@o4510583739777024.ingest.us.sentry.io/4510832047947776";
7
+ var OPERATIONAL_ERROR_PATTERNS = [
8
+ // Authentication errors (user needs to login)
9
+ /not authenticated/i,
10
+ // Resource not found (user typo or deleted resource)
11
+ /not found/i,
12
+ /agent not found/i,
13
+ /version not found/i,
14
+ /checkpoint not found/i,
15
+ /session not found/i,
16
+ // File errors (user provided wrong path)
17
+ /file not found/i,
18
+ /environment file not found/i,
19
+ // Validation errors (user input issues)
20
+ /invalid format/i,
21
+ /invalid.*config/i,
22
+ // Rate limiting (expected operational condition)
23
+ /rate limit/i,
24
+ /concurrent run limit/i,
25
+ // Network issues (transient, not bugs)
26
+ /network error/i,
27
+ /network issue/i,
28
+ /connection refused/i,
29
+ /timeout/i,
30
+ /ECONNREFUSED/i,
31
+ /ETIMEDOUT/i,
32
+ // Permission/access errors (operational, not bugs)
33
+ /forbidden/i,
34
+ /access denied/i
35
+ ];
36
+ function isOperationalError(error) {
37
+ if (!(error instanceof Error)) {
38
+ return false;
39
+ }
40
+ const message = error.message;
41
+ return OPERATIONAL_ERROR_PATTERNS.some((pattern) => pattern.test(message));
42
+ }
43
+ if (DSN) {
44
+ Sentry.init({
45
+ dsn: DSN,
46
+ sendDefaultPii: false,
47
+ tracesSampleRate: 0,
48
+ shutdownTimeout: 500,
49
+ initialScope: {
50
+ tags: {
51
+ app: "cli"
52
+ }
53
+ },
54
+ // Filter out operational errors - only send programmer errors (bugs)
55
+ beforeSend(event, hint) {
56
+ const error = hint.originalException;
57
+ if (isOperationalError(error)) {
58
+ return null;
59
+ }
60
+ return event;
61
+ }
62
+ });
63
+ Sentry.setContext("cli", {
64
+ version: "9.22.0",
65
+ command: process.argv.slice(2).join(" ")
66
+ });
67
+ Sentry.setContext("runtime", {
68
+ node_version: process.version,
69
+ os_platform: os.platform(),
70
+ os_release: os.release()
71
+ });
72
+ }
73
+
3
74
  // src/index.ts
4
75
  import { Command as Command68 } from "commander";
5
76
 
@@ -2092,26 +2163,26 @@ var c13 = initContract();
2092
2163
  var MODEL_PROVIDER_TYPES = {
2093
2164
  "claude-code-oauth-token": {
2094
2165
  framework: "claude-code",
2095
- credentialName: "CLAUDE_CODE_OAUTH_TOKEN",
2166
+ secretName: "CLAUDE_CODE_OAUTH_TOKEN",
2096
2167
  label: "Claude Code (OAuth Token)",
2097
- credentialLabel: "OAuth token",
2168
+ secretLabel: "OAuth token",
2098
2169
  helpText: "To get your OAuth token, run: claude setup-token\n(Requires Claude Pro or Max subscription)"
2099
2170
  },
2100
2171
  "anthropic-api-key": {
2101
2172
  framework: "claude-code",
2102
- credentialName: "ANTHROPIC_API_KEY",
2173
+ secretName: "ANTHROPIC_API_KEY",
2103
2174
  label: "Anthropic API Key",
2104
- credentialLabel: "API key",
2175
+ secretLabel: "API key",
2105
2176
  helpText: "Get your API key at: https://console.anthropic.com/settings/keys"
2106
2177
  },
2107
2178
  "openrouter-api-key": {
2108
2179
  framework: "claude-code",
2109
- credentialName: "OPENROUTER_API_KEY",
2180
+ secretName: "OPENROUTER_API_KEY",
2110
2181
  label: "OpenRouter",
2111
- credentialLabel: "API key",
2182
+ secretLabel: "API key",
2112
2183
  helpText: "Get your API key at: https://openrouter.ai/settings/keys",
2113
2184
  environmentMapping: {
2114
- ANTHROPIC_AUTH_TOKEN: "$credential",
2185
+ ANTHROPIC_AUTH_TOKEN: "$secret",
2115
2186
  ANTHROPIC_BASE_URL: "https://openrouter.ai/api",
2116
2187
  ANTHROPIC_API_KEY: "",
2117
2188
  ANTHROPIC_MODEL: "$model",
@@ -2129,12 +2200,12 @@ var MODEL_PROVIDER_TYPES = {
2129
2200
  },
2130
2201
  "moonshot-api-key": {
2131
2202
  framework: "claude-code",
2132
- credentialName: "MOONSHOT_API_KEY",
2203
+ secretName: "MOONSHOT_API_KEY",
2133
2204
  label: "Moonshot (Kimi)",
2134
- credentialLabel: "API key",
2205
+ secretLabel: "API key",
2135
2206
  helpText: "Get your API key at: https://platform.moonshot.ai/console/api-keys",
2136
2207
  environmentMapping: {
2137
- ANTHROPIC_AUTH_TOKEN: "$credential",
2208
+ ANTHROPIC_AUTH_TOKEN: "$secret",
2138
2209
  ANTHROPIC_BASE_URL: "https://api.moonshot.ai/anthropic",
2139
2210
  ANTHROPIC_MODEL: "$model",
2140
2211
  ANTHROPIC_DEFAULT_OPUS_MODEL: "$model",
@@ -2151,12 +2222,12 @@ var MODEL_PROVIDER_TYPES = {
2151
2222
  },
2152
2223
  "minimax-api-key": {
2153
2224
  framework: "claude-code",
2154
- credentialName: "MINIMAX_API_KEY",
2225
+ secretName: "MINIMAX_API_KEY",
2155
2226
  label: "MiniMax",
2156
- credentialLabel: "API key",
2227
+ secretLabel: "API key",
2157
2228
  helpText: "Get your API key at: https://platform.minimax.io/user-center/basic-information/interface-key",
2158
2229
  environmentMapping: {
2159
- ANTHROPIC_AUTH_TOKEN: "$credential",
2230
+ ANTHROPIC_AUTH_TOKEN: "$secret",
2160
2231
  ANTHROPIC_BASE_URL: "https://api.minimax.io/anthropic",
2161
2232
  ANTHROPIC_MODEL: "$model",
2162
2233
  ANTHROPIC_DEFAULT_OPUS_MODEL: "$model",
@@ -2171,12 +2242,12 @@ var MODEL_PROVIDER_TYPES = {
2171
2242
  },
2172
2243
  "deepseek-api-key": {
2173
2244
  framework: "claude-code",
2174
- credentialName: "DEEPSEEK_API_KEY",
2245
+ secretName: "DEEPSEEK_API_KEY",
2175
2246
  label: "DeepSeek",
2176
- credentialLabel: "API key",
2247
+ secretLabel: "API key",
2177
2248
  helpText: "Get your API key at: https://platform.deepseek.com/api_keys",
2178
2249
  environmentMapping: {
2179
- ANTHROPIC_AUTH_TOKEN: "$credential",
2250
+ ANTHROPIC_AUTH_TOKEN: "$secret",
2180
2251
  ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
2181
2252
  ANTHROPIC_MODEL: "$model",
2182
2253
  ANTHROPIC_DEFAULT_OPUS_MODEL: "$model",
@@ -2191,12 +2262,12 @@ var MODEL_PROVIDER_TYPES = {
2191
2262
  },
2192
2263
  "zai-api-key": {
2193
2264
  framework: "claude-code",
2194
- credentialName: "ZAI_API_KEY",
2265
+ secretName: "ZAI_API_KEY",
2195
2266
  label: "Z.AI (GLM)",
2196
- credentialLabel: "API key",
2267
+ secretLabel: "API key",
2197
2268
  helpText: "Get your API key at: https://z.ai/model-api",
2198
2269
  environmentMapping: {
2199
- ANTHROPIC_AUTH_TOKEN: "$credential",
2270
+ ANTHROPIC_AUTH_TOKEN: "$secret",
2200
2271
  ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
2201
2272
  ANTHROPIC_MODEL: "$model",
2202
2273
  ANTHROPIC_DEFAULT_OPUS_MODEL: "$model",
@@ -2216,7 +2287,7 @@ var MODEL_PROVIDER_TYPES = {
2216
2287
  "api-key": {
2217
2288
  label: "API Key",
2218
2289
  helpText: "Use an Azure Foundry API key for authentication",
2219
- credentials: {
2290
+ secrets: {
2220
2291
  ANTHROPIC_FOUNDRY_API_KEY: {
2221
2292
  label: "ANTHROPIC_FOUNDRY_API_KEY",
2222
2293
  required: true,
@@ -2234,8 +2305,8 @@ var MODEL_PROVIDER_TYPES = {
2234
2305
  defaultAuthMethod: "api-key",
2235
2306
  environmentMapping: {
2236
2307
  CLAUDE_CODE_USE_FOUNDRY: "1",
2237
- ANTHROPIC_FOUNDRY_API_KEY: "$credentials.ANTHROPIC_FOUNDRY_API_KEY",
2238
- ANTHROPIC_FOUNDRY_RESOURCE: "$credentials.ANTHROPIC_FOUNDRY_RESOURCE",
2308
+ ANTHROPIC_FOUNDRY_API_KEY: "$secrets.ANTHROPIC_FOUNDRY_API_KEY",
2309
+ ANTHROPIC_FOUNDRY_RESOURCE: "$secrets.ANTHROPIC_FOUNDRY_RESOURCE",
2239
2310
  ANTHROPIC_MODEL: "$model"
2240
2311
  },
2241
2312
  models: [],
@@ -2246,12 +2317,12 @@ var MODEL_PROVIDER_TYPES = {
2246
2317
  "aws-bedrock": {
2247
2318
  framework: "claude-code",
2248
2319
  label: "AWS Bedrock",
2249
- helpText: "Run Claude on AWS Bedrock.\nSetup guide: https://docs.anthropic.com/en/docs/claude-code/bedrock",
2320
+ helpText: "Run Claude on AWS Bedrock.\nSetup guide: https://code.claude.com/docs/en/amazon-bedrock",
2250
2321
  authMethods: {
2251
2322
  "api-key": {
2252
2323
  label: "Bedrock API Key",
2253
2324
  helpText: "Use a Bedrock API key for authentication",
2254
- credentials: {
2325
+ secrets: {
2255
2326
  AWS_BEARER_TOKEN_BEDROCK: {
2256
2327
  label: "AWS_BEARER_TOKEN_BEDROCK",
2257
2328
  required: true,
@@ -2267,8 +2338,8 @@ var MODEL_PROVIDER_TYPES = {
2267
2338
  },
2268
2339
  "access-keys": {
2269
2340
  label: "IAM Access Keys",
2270
- helpText: "Use IAM access key credentials",
2271
- credentials: {
2341
+ helpText: "Use IAM access key secrets",
2342
+ secrets: {
2272
2343
  AWS_ACCESS_KEY_ID: {
2273
2344
  label: "AWS_ACCESS_KEY_ID",
2274
2345
  required: true,
@@ -2282,7 +2353,7 @@ var MODEL_PROVIDER_TYPES = {
2282
2353
  AWS_SESSION_TOKEN: {
2283
2354
  label: "AWS_SESSION_TOKEN",
2284
2355
  required: false,
2285
- helpText: "Optional, for temporary credentials"
2356
+ helpText: "Optional, for temporary secrets"
2286
2357
  },
2287
2358
  AWS_REGION: {
2288
2359
  label: "AWS_REGION",
@@ -2296,11 +2367,11 @@ var MODEL_PROVIDER_TYPES = {
2296
2367
  defaultAuthMethod: "api-key",
2297
2368
  environmentMapping: {
2298
2369
  CLAUDE_CODE_USE_BEDROCK: "1",
2299
- AWS_REGION: "$credentials.AWS_REGION",
2300
- AWS_BEARER_TOKEN_BEDROCK: "$credentials.AWS_BEARER_TOKEN_BEDROCK",
2301
- AWS_ACCESS_KEY_ID: "$credentials.AWS_ACCESS_KEY_ID",
2302
- AWS_SECRET_ACCESS_KEY: "$credentials.AWS_SECRET_ACCESS_KEY",
2303
- AWS_SESSION_TOKEN: "$credentials.AWS_SESSION_TOKEN",
2370
+ AWS_REGION: "$secrets.AWS_REGION",
2371
+ AWS_BEARER_TOKEN_BEDROCK: "$secrets.AWS_BEARER_TOKEN_BEDROCK",
2372
+ AWS_ACCESS_KEY_ID: "$secrets.AWS_ACCESS_KEY_ID",
2373
+ AWS_SECRET_ACCESS_KEY: "$secrets.AWS_SECRET_ACCESS_KEY",
2374
+ AWS_SESSION_TOKEN: "$secrets.AWS_SESSION_TOKEN",
2304
2375
  ANTHROPIC_MODEL: "$model"
2305
2376
  },
2306
2377
  models: [],
@@ -2333,13 +2404,13 @@ function getDefaultAuthMethod(type) {
2333
2404
  const config = MODEL_PROVIDER_TYPES[type];
2334
2405
  return "defaultAuthMethod" in config ? config.defaultAuthMethod : void 0;
2335
2406
  }
2336
- function getCredentialsForAuthMethod(type, authMethod) {
2407
+ function getSecretsForAuthMethod(type, authMethod) {
2337
2408
  const authMethods = getAuthMethodsForType(type);
2338
2409
  if (!authMethods || !(authMethod in authMethods)) {
2339
2410
  return void 0;
2340
2411
  }
2341
2412
  const method = authMethods[authMethod];
2342
- return method?.credentials;
2413
+ return method?.secrets;
2343
2414
  }
2344
2415
  function getModels(type) {
2345
2416
  const config = MODEL_PROVIDER_TYPES[type];
@@ -2365,11 +2436,11 @@ var modelProviderResponseSchema = z16.object({
2365
2436
  id: z16.string().uuid(),
2366
2437
  type: modelProviderTypeSchema,
2367
2438
  framework: modelProviderFrameworkSchema,
2368
- credentialName: z16.string().nullable(),
2369
- // Legacy single-credential (deprecated for multi-auth)
2439
+ secretName: z16.string().nullable(),
2440
+ // Legacy single-secret (deprecated for multi-auth)
2370
2441
  authMethod: z16.string().nullable(),
2371
2442
  // For multi-auth providers
2372
- credentialNames: z16.array(z16.string()).nullable(),
2443
+ secretNames: z16.array(z16.string()).nullable(),
2373
2444
  // For multi-auth providers
2374
2445
  isDefault: z16.boolean(),
2375
2446
  selectedModel: z16.string().nullable(),
@@ -2381,23 +2452,21 @@ var modelProviderListResponseSchema = z16.object({
2381
2452
  });
2382
2453
  var upsertModelProviderRequestSchema = z16.object({
2383
2454
  type: modelProviderTypeSchema,
2384
- credential: z16.string().min(1).optional(),
2385
- // Legacy single credential
2455
+ secret: z16.string().min(1).optional(),
2456
+ // Legacy single secret
2386
2457
  authMethod: z16.string().optional(),
2387
2458
  // For multi-auth providers
2388
- credentials: z16.record(z16.string(), z16.string()).optional(),
2459
+ secrets: z16.record(z16.string(), z16.string()).optional(),
2389
2460
  // For multi-auth providers
2390
- convert: z16.boolean().optional(),
2391
2461
  selectedModel: z16.string().optional()
2392
2462
  });
2393
2463
  var upsertModelProviderResponseSchema = z16.object({
2394
2464
  provider: modelProviderResponseSchema,
2395
2465
  created: z16.boolean()
2396
2466
  });
2397
- var checkCredentialResponseSchema = z16.object({
2467
+ var checkSecretResponseSchema = z16.object({
2398
2468
  exists: z16.boolean(),
2399
- credentialName: z16.string(),
2400
- currentType: z16.enum(["user", "model-provider"]).optional()
2469
+ secretName: z16.string()
2401
2470
  });
2402
2471
  var modelProvidersMainContract = c13.router({
2403
2472
  list: {
@@ -2436,11 +2505,11 @@ var modelProvidersCheckContract = c13.router({
2436
2505
  type: modelProviderTypeSchema
2437
2506
  }),
2438
2507
  responses: {
2439
- 200: checkCredentialResponseSchema,
2508
+ 200: checkSecretResponseSchema,
2440
2509
  401: apiErrorSchema,
2441
2510
  500: apiErrorSchema
2442
2511
  },
2443
- summary: "Check if credential exists for a model provider type"
2512
+ summary: "Check if secret exists for a model provider type"
2444
2513
  }
2445
2514
  });
2446
2515
  var modelProvidersByTypeContract = c13.router({
@@ -2476,7 +2545,7 @@ var modelProvidersConvertContract = c13.router({
2476
2545
  404: apiErrorSchema,
2477
2546
  500: apiErrorSchema
2478
2547
  },
2479
- summary: "Convert existing user credential to model provider"
2548
+ summary: "Convert existing user secret to model provider"
2480
2549
  }
2481
2550
  });
2482
2551
  var modelProvidersSetDefaultContract = c13.router({
@@ -3627,13 +3696,14 @@ function getSkillStorageName(fullPath) {
3627
3696
 
3628
3697
  // ../../packages/core/src/github-url.ts
3629
3698
  function parseGitHubTreeUrl(url) {
3630
- const fullPathMatch = url.match(/^https:\/\/github\.com\/(.+)$/);
3699
+ const normalizedUrl = url.replace(/\/+$/, "");
3700
+ const fullPathMatch = normalizedUrl.match(/^https:\/\/github\.com\/(.+)$/);
3631
3701
  if (!fullPathMatch) {
3632
3702
  return null;
3633
3703
  }
3634
3704
  const fullPath = fullPathMatch[1];
3635
3705
  const regex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/;
3636
- const match = url.match(regex);
3706
+ const match = normalizedUrl.match(regex);
3637
3707
  if (!match) {
3638
3708
  return null;
3639
3709
  }
@@ -3649,6 +3719,39 @@ function parseGitHubTreeUrl(url) {
3649
3719
  fullPath
3650
3720
  };
3651
3721
  }
3722
+ function parseGitHubUrl(url) {
3723
+ const normalizedUrl = url.replace(/\/+$/, "");
3724
+ const fullPathMatch = normalizedUrl.match(/^https:\/\/github\.com\/(.+)$/);
3725
+ if (!fullPathMatch) {
3726
+ return null;
3727
+ }
3728
+ const fullPath = fullPathMatch[1];
3729
+ const plainMatch = normalizedUrl.match(
3730
+ /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/
3731
+ );
3732
+ if (plainMatch) {
3733
+ return {
3734
+ owner: plainMatch[1],
3735
+ repo: plainMatch[2],
3736
+ branch: null,
3737
+ path: null,
3738
+ fullPath
3739
+ };
3740
+ }
3741
+ const treeMatch = normalizedUrl.match(
3742
+ /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.+))?$/
3743
+ );
3744
+ if (treeMatch) {
3745
+ return {
3746
+ owner: treeMatch[1],
3747
+ repo: treeMatch[2],
3748
+ branch: treeMatch[3],
3749
+ path: treeMatch[4] ?? null,
3750
+ fullPath
3751
+ };
3752
+ }
3753
+ return null;
3754
+ }
3652
3755
 
3653
3756
  // ../../packages/core/src/frameworks.ts
3654
3757
  var SUPPORTED_FRAMEWORKS = ["claude-code", "codex"];
@@ -3710,9 +3813,10 @@ var FEATURE_SWITCHES = {
3710
3813
 
3711
3814
  // src/lib/api/core/client-factory.ts
3712
3815
  var ApiRequestError = class extends Error {
3713
- constructor(message, code) {
3816
+ constructor(message, code, status) {
3714
3817
  super(message);
3715
3818
  this.code = code;
3819
+ this.status = status;
3716
3820
  this.name = "ApiRequestError";
3717
3821
  }
3718
3822
  };
@@ -3742,11 +3846,27 @@ async function getClientConfig() {
3742
3846
  const baseHeaders = await getHeaders();
3743
3847
  return { baseUrl, baseHeaders, jsonQuery: true };
3744
3848
  }
3745
- function handleError(result, defaultMessage) {
3849
+ function handleError(result, defaultMessage, options) {
3850
+ if (!options?.useServerMessage) {
3851
+ if (result.status === 401) {
3852
+ throw new ApiRequestError(
3853
+ "Not authenticated. Run: vm0 auth login",
3854
+ "UNAUTHORIZED",
3855
+ 401
3856
+ );
3857
+ }
3858
+ if (result.status === 403) {
3859
+ throw new ApiRequestError(
3860
+ "An unexpected network issue occurred",
3861
+ "FORBIDDEN",
3862
+ 403
3863
+ );
3864
+ }
3865
+ }
3746
3866
  const errorBody = result.body;
3747
3867
  const message = errorBody.error?.message || defaultMessage;
3748
3868
  const code = errorBody.error?.code || "UNKNOWN";
3749
- throw new ApiRequestError(message, code);
3869
+ throw new ApiRequestError(message, code, result.status);
3750
3870
  }
3751
3871
 
3752
3872
  // src/lib/api/core/http.ts
@@ -3931,7 +4051,7 @@ async function getScope() {
3931
4051
  if (result.status === 200) {
3932
4052
  return result.body;
3933
4053
  }
3934
- handleError(result, "Failed to get scope");
4054
+ handleError(result, "Failed to get scope", { useServerMessage: true });
3935
4055
  }
3936
4056
  async function createScope(body) {
3937
4057
  const config = await getClientConfig();
@@ -3940,7 +4060,7 @@ async function createScope(body) {
3940
4060
  if (result.status === 201) {
3941
4061
  return result.body;
3942
4062
  }
3943
- handleError(result, "Failed to create scope");
4063
+ handleError(result, "Failed to create scope", { useServerMessage: true });
3944
4064
  }
3945
4065
  async function updateScope(body) {
3946
4066
  const config = await getClientConfig();
@@ -3949,7 +4069,7 @@ async function updateScope(body) {
3949
4069
  if (result.status === 200) {
3950
4070
  return result.body;
3951
4071
  }
3952
- handleError(result, "Failed to update scope");
4072
+ handleError(result, "Failed to update scope", { useServerMessage: true });
3953
4073
  }
3954
4074
 
3955
4075
  // src/lib/api/domains/storages.ts
@@ -4187,7 +4307,7 @@ async function upsertModelProvider(body) {
4187
4307
  }
4188
4308
  handleError(result, "Failed to set model provider");
4189
4309
  }
4190
- async function checkModelProviderCredential(type) {
4310
+ async function checkModelProviderSecret(type) {
4191
4311
  const config = await getClientConfig();
4192
4312
  const client = initClient9(modelProvidersCheckContract, config);
4193
4313
  const result = await client.check({
@@ -4196,7 +4316,7 @@ async function checkModelProviderCredential(type) {
4196
4316
  if (result.status === 200) {
4197
4317
  return result.body;
4198
4318
  }
4199
- handleError(result, "Failed to check credential");
4319
+ handleError(result, "Failed to check secret");
4200
4320
  }
4201
4321
  async function deleteModelProvider(type) {
4202
4322
  const config = await getClientConfig();
@@ -4209,17 +4329,6 @@ async function deleteModelProvider(type) {
4209
4329
  }
4210
4330
  handleError(result, `Model provider "${type}" not found`);
4211
4331
  }
4212
- async function convertModelProviderCredential(type) {
4213
- const config = await getClientConfig();
4214
- const client = initClient9(modelProvidersConvertContract, config);
4215
- const result = await client.convert({
4216
- params: { type }
4217
- });
4218
- if (result.status === 200) {
4219
- return result.body;
4220
- }
4221
- handleError(result, "Failed to convert credential");
4222
- }
4223
4332
  async function setModelProviderDefault(type) {
4224
4333
  const config = await getClientConfig();
4225
4334
  const client = initClient9(modelProvidersSetDefaultContract, config);
@@ -4450,7 +4559,7 @@ function validateAgentCompose(config) {
4450
4559
  // src/lib/domain/github-skills.ts
4451
4560
  import * as fs from "fs/promises";
4452
4561
  import * as path from "path";
4453
- import * as os from "os";
4562
+ import * as os2 from "os";
4454
4563
  import { exec } from "child_process";
4455
4564
  import { promisify } from "util";
4456
4565
  import { parse as parseYaml } from "yaml";
@@ -4470,7 +4579,7 @@ function getSkillStorageName2(parsed) {
4470
4579
  async function downloadGitHubSkill(parsed, destDir) {
4471
4580
  const repoUrl = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
4472
4581
  const skillDir = path.join(destDir, parsed.skillName);
4473
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "vm0-skill-"));
4582
+ const tempDir = await fs.mkdtemp(path.join(os2.tmpdir(), "vm0-skill-"));
4474
4583
  try {
4475
4584
  await execAsync(`git init`, { cwd: tempDir });
4476
4585
  await execAsync(`git remote add origin "${repoUrl}"`, { cwd: tempDir });
@@ -4489,10 +4598,41 @@ async function downloadGitHubSkill(parsed, destDir) {
4489
4598
  await fs.rm(tempDir, { recursive: true, force: true });
4490
4599
  }
4491
4600
  }
4601
+ async function getDefaultBranch(owner, repo) {
4602
+ const repoUrl = `https://github.com/${owner}/${repo}.git`;
4603
+ try {
4604
+ const { stdout } = await execAsync(
4605
+ `git ls-remote --symref "${repoUrl}" HEAD`
4606
+ );
4607
+ const match = stdout.match(/ref: refs\/heads\/([^\s]+)/);
4608
+ if (!match) {
4609
+ throw new Error(
4610
+ `Could not determine default branch for ${owner}/${repo}`
4611
+ );
4612
+ }
4613
+ return match[1];
4614
+ } catch (error) {
4615
+ const message = error instanceof Error ? error.message : String(error);
4616
+ if (message.includes("not found") || message.includes("Repository not found")) {
4617
+ throw new Error(`Repository not found: ${owner}/${repo}`);
4618
+ }
4619
+ if (message.includes("Authentication failed") || message.includes("could not read Username")) {
4620
+ throw new Error(
4621
+ `Cannot access repository ${owner}/${repo}. Is it private?`
4622
+ );
4623
+ }
4624
+ throw error;
4625
+ }
4626
+ }
4492
4627
  async function downloadGitHubDirectory(url) {
4493
- const parsed = parseGitHubTreeUrl2(url);
4628
+ const parsed = parseGitHubUrl(url);
4629
+ if (!parsed) {
4630
+ throw new Error(
4631
+ `Invalid GitHub URL: ${url}. Expected format: https://github.com/{owner}/{repo}[/tree/{branch}[/path]]`
4632
+ );
4633
+ }
4494
4634
  const repoUrl = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
4495
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "vm0-github-"));
4635
+ const tempDir = await fs.mkdtemp(path.join(os2.tmpdir(), "vm0-github-"));
4496
4636
  try {
4497
4637
  try {
4498
4638
  await execAsync("git --version");
@@ -4501,13 +4641,15 @@ async function downloadGitHubDirectory(url) {
4501
4641
  "git command not found. Please install git to use GitHub URLs."
4502
4642
  );
4503
4643
  }
4644
+ const branch = parsed.branch ?? await getDefaultBranch(parsed.owner, parsed.repo);
4504
4645
  await execAsync(`git init`, { cwd: tempDir });
4505
4646
  await execAsync(`git remote add origin "${repoUrl}"`, { cwd: tempDir });
4506
4647
  await execAsync(`git config core.sparseCheckout true`, { cwd: tempDir });
4648
+ const sparsePattern = parsed.path ?? "/*";
4507
4649
  const sparseFile = path.join(tempDir, ".git", "info", "sparse-checkout");
4508
- await fs.writeFile(sparseFile, parsed.path + "\n");
4650
+ await fs.writeFile(sparseFile, sparsePattern + "\n");
4509
4651
  try {
4510
- await execAsync(`git fetch --depth 1 origin "${parsed.branch}"`, {
4652
+ await execAsync(`git fetch --depth 1 origin "${branch}"`, {
4511
4653
  cwd: tempDir
4512
4654
  });
4513
4655
  } catch (error) {
@@ -4516,15 +4658,14 @@ async function downloadGitHubDirectory(url) {
4516
4658
  throw new Error(`Cannot access repository. Is it private? URL: ${url}`);
4517
4659
  }
4518
4660
  if (message.includes("couldn't find remote ref")) {
4519
- throw new Error(
4520
- `Branch "${parsed.branch}" not found in repository: ${url}`
4521
- );
4661
+ throw new Error(`Branch "${branch}" not found in repository: ${url}`);
4522
4662
  }
4523
4663
  throw error;
4524
4664
  }
4525
- await execAsync(`git checkout "${parsed.branch}"`, { cwd: tempDir });
4665
+ await execAsync(`git checkout "${branch}"`, { cwd: tempDir });
4666
+ const downloadedDir = parsed.path ? path.join(tempDir, parsed.path) : tempDir;
4526
4667
  return {
4527
- dir: path.join(tempDir, parsed.path),
4668
+ dir: downloadedDir,
4528
4669
  tempRoot: tempDir
4529
4670
  };
4530
4671
  } catch (error) {
@@ -4577,13 +4718,13 @@ async function readSkillFrontmatter(skillDir) {
4577
4718
  // src/lib/storage/system-storage.ts
4578
4719
  import * as fs4 from "fs/promises";
4579
4720
  import * as path4 from "path";
4580
- import * as os3 from "os";
4721
+ import * as os4 from "os";
4581
4722
 
4582
4723
  // src/lib/storage/direct-upload.ts
4583
4724
  import { createHash } from "crypto";
4584
4725
  import * as fs3 from "fs";
4585
4726
  import * as path3 from "path";
4586
- import * as os2 from "os";
4727
+ import * as os3 from "os";
4587
4728
  import * as tar2 from "tar";
4588
4729
 
4589
4730
  // src/lib/utils/file-utils.ts
@@ -4746,7 +4887,7 @@ async function collectFileMetadata(cwd, files, onProgress) {
4746
4887
  return fileEntries;
4747
4888
  }
4748
4889
  async function createArchive(cwd, files) {
4749
- const tmpDir = fs3.mkdtempSync(path3.join(os2.tmpdir(), "vm0-"));
4890
+ const tmpDir = fs3.mkdtempSync(path3.join(os3.tmpdir(), "vm0-"));
4750
4891
  const tarPath = path3.join(tmpDir, "archive.tar.gz");
4751
4892
  try {
4752
4893
  const relativePaths = files.map((file) => path3.relative(cwd, file));
@@ -4903,7 +5044,7 @@ async function uploadInstructions(agentName, instructionsFilePath, basePath, fra
4903
5044
  const storageName = getInstructionsStorageName(agentName.toLowerCase());
4904
5045
  const absolutePath = path4.isAbsolute(instructionsFilePath) ? instructionsFilePath : path4.join(basePath, instructionsFilePath);
4905
5046
  const content = await fs4.readFile(absolutePath, "utf8");
4906
- const tmpDir = await fs4.mkdtemp(path4.join(os3.tmpdir(), "vm0-instructions-"));
5047
+ const tmpDir = await fs4.mkdtemp(path4.join(os4.tmpdir(), "vm0-instructions-"));
4907
5048
  const instructionsDir = path4.join(tmpDir, "instructions");
4908
5049
  await fs4.mkdir(instructionsDir);
4909
5050
  const filename = getInstructionsFilename(framework);
@@ -4922,7 +5063,7 @@ async function uploadInstructions(agentName, instructionsFilePath, basePath, fra
4922
5063
  async function uploadSkill(skillUrl) {
4923
5064
  const parsed = parseGitHubTreeUrl2(skillUrl);
4924
5065
  const storageName = getSkillStorageName2(parsed);
4925
- const tmpDir = await fs4.mkdtemp(path4.join(os3.tmpdir(), "vm0-skill-"));
5066
+ const tmpDir = await fs4.mkdtemp(path4.join(os4.tmpdir(), "vm0-skill-"));
4926
5067
  try {
4927
5068
  const skillDir = await downloadGitHubSkill(parsed, tmpDir);
4928
5069
  await validateSkillDirectory(skillDir);
@@ -5207,17 +5348,23 @@ async function silentUpgradeAfterCommand(currentVersion) {
5207
5348
 
5208
5349
  // src/commands/compose/index.ts
5209
5350
  var DEFAULT_CONFIG_FILE = "vm0.yaml";
5210
- function isGitHubTreeUrl(input) {
5211
- return input.startsWith("https://github.com/") && input.includes("/tree/");
5351
+ function isGitHubUrl(input) {
5352
+ return /^https:\/\/github\.com\/[^/]+\/[^/]+/.test(input);
5212
5353
  }
5213
5354
  function getSecretsFromComposeContent(content) {
5214
5355
  const refs = extractVariableReferences(content);
5215
5356
  const grouped = groupVariablesBySource(refs);
5216
5357
  return new Set(grouped.secrets.map((r) => r.name));
5217
5358
  }
5218
- async function loadAndValidateConfig(configFile) {
5359
+ async function loadAndValidateConfig(configFile, porcelainMode) {
5219
5360
  if (!existsSync4(configFile)) {
5220
- console.error(chalk4.red(`\u2717 Config file not found: ${configFile}`));
5361
+ if (porcelainMode) {
5362
+ console.log(
5363
+ JSON.stringify({ error: `Config file not found: ${configFile}` })
5364
+ );
5365
+ } else {
5366
+ console.error(chalk4.red(`\u2717 Config file not found: ${configFile}`));
5367
+ }
5221
5368
  process.exit(1);
5222
5369
  }
5223
5370
  const content = await readFile4(configFile, "utf8");
@@ -5225,15 +5372,22 @@ async function loadAndValidateConfig(configFile) {
5225
5372
  try {
5226
5373
  config = parseYaml2(content);
5227
5374
  } catch (error) {
5228
- console.error(chalk4.red("\u2717 Invalid YAML format"));
5229
- if (error instanceof Error) {
5230
- console.error(chalk4.dim(` ${error.message}`));
5375
+ const message = error instanceof Error ? error.message : "Unknown error";
5376
+ if (porcelainMode) {
5377
+ console.log(JSON.stringify({ error: `Invalid YAML format: ${message}` }));
5378
+ } else {
5379
+ console.error(chalk4.red("\u2717 Invalid YAML format"));
5380
+ console.error(chalk4.dim(` ${message}`));
5231
5381
  }
5232
5382
  process.exit(1);
5233
5383
  }
5234
5384
  const validation = validateAgentCompose(config);
5235
5385
  if (!validation.valid) {
5236
- console.error(chalk4.red(`\u2717 ${validation.error}`));
5386
+ if (porcelainMode) {
5387
+ console.log(JSON.stringify({ error: validation.error }));
5388
+ } else {
5389
+ console.error(chalk4.red(`\u2717 ${validation.error}`));
5390
+ }
5237
5391
  process.exit(1);
5238
5392
  }
5239
5393
  const cfg = config;
@@ -5269,33 +5423,43 @@ function checkLegacyImageFormat(config) {
5269
5423
  }
5270
5424
  }
5271
5425
  }
5272
- async function uploadAssets(agentName, agent, basePath) {
5426
+ async function uploadAssets(agentName, agent, basePath, porcelainMode) {
5273
5427
  if (agent.instructions) {
5274
- console.log(`Uploading instructions: ${agent.instructions}`);
5428
+ if (!porcelainMode) {
5429
+ console.log(`Uploading instructions: ${agent.instructions}`);
5430
+ }
5275
5431
  const result = await uploadInstructions(
5276
5432
  agentName,
5277
5433
  agent.instructions,
5278
5434
  basePath,
5279
5435
  agent.framework
5280
5436
  );
5281
- console.log(
5282
- chalk4.green(
5283
- `\u2713 Instructions ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.versionId.slice(0, 8)}`
5284
- )
5285
- );
5437
+ if (!porcelainMode) {
5438
+ console.log(
5439
+ chalk4.green(
5440
+ `\u2713 Instructions ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.versionId.slice(0, 8)}`
5441
+ )
5442
+ );
5443
+ }
5286
5444
  }
5287
5445
  const skillResults = [];
5288
5446
  if (agent.skills && Array.isArray(agent.skills)) {
5289
- console.log(`Uploading ${agent.skills.length} skill(s)...`);
5447
+ if (!porcelainMode) {
5448
+ console.log(`Uploading ${agent.skills.length} skill(s)...`);
5449
+ }
5290
5450
  for (const skillUrl of agent.skills) {
5291
- console.log(chalk4.dim(` Downloading: ${skillUrl}`));
5451
+ if (!porcelainMode) {
5452
+ console.log(chalk4.dim(` Downloading: ${skillUrl}`));
5453
+ }
5292
5454
  const result = await uploadSkill(skillUrl);
5293
5455
  skillResults.push(result);
5294
- console.log(
5295
- chalk4.green(
5296
- ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.skillName} (${result.versionId.slice(0, 8)})`
5297
- )
5298
- );
5456
+ if (!porcelainMode) {
5457
+ console.log(
5458
+ chalk4.green(
5459
+ ` \u2713 Skill ${result.action === "deduplicated" ? "(unchanged)" : "uploaded"}: ${result.skillName} (${result.versionId.slice(0, 8)})`
5460
+ )
5461
+ );
5462
+ }
5299
5463
  }
5300
5464
  }
5301
5465
  return skillResults;
@@ -5341,36 +5505,48 @@ async function displayAndConfirmVariables(variables, options) {
5341
5505
  if (newSecrets.length === 0 && newVars.length === 0) {
5342
5506
  return true;
5343
5507
  }
5344
- console.log();
5345
- console.log(
5346
- chalk4.bold("Skills require the following environment variables:")
5347
- );
5348
- console.log();
5349
- if (newSecrets.length > 0) {
5350
- console.log(chalk4.cyan(" Secrets:"));
5351
- for (const [name, skills] of newSecrets) {
5352
- const isNew = trulyNewSecrets.includes(name);
5353
- const newMarker = isNew ? chalk4.yellow(" (new)") : "";
5354
- console.log(` ${name.padEnd(24)}${newMarker} <- ${skills.join(", ")}`);
5508
+ if (!options.porcelain) {
5509
+ console.log();
5510
+ console.log(
5511
+ chalk4.bold("Skills require the following environment variables:")
5512
+ );
5513
+ console.log();
5514
+ if (newSecrets.length > 0) {
5515
+ console.log(chalk4.cyan(" Secrets:"));
5516
+ for (const [name, skills] of newSecrets) {
5517
+ const isNew = trulyNewSecrets.includes(name);
5518
+ const newMarker = isNew ? chalk4.yellow(" (new)") : "";
5519
+ console.log(
5520
+ ` ${name.padEnd(24)}${newMarker} <- ${skills.join(", ")}`
5521
+ );
5522
+ }
5355
5523
  }
5356
- }
5357
- if (newVars.length > 0) {
5358
- console.log(chalk4.cyan(" Vars:"));
5359
- for (const [name, skills] of newVars) {
5360
- console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
5524
+ if (newVars.length > 0) {
5525
+ console.log(chalk4.cyan(" Vars:"));
5526
+ for (const [name, skills] of newVars) {
5527
+ console.log(` ${name.padEnd(24)} <- ${skills.join(", ")}`);
5528
+ }
5361
5529
  }
5530
+ console.log();
5362
5531
  }
5363
- console.log();
5364
5532
  if (trulyNewSecrets.length > 0 && !options.yes) {
5365
5533
  if (!isInteractive()) {
5366
- console.error(
5367
- chalk4.red(`\u2717 New secrets detected: ${trulyNewSecrets.join(", ")}`)
5368
- );
5369
- console.error(
5370
- chalk4.dim(
5371
- " Use --yes flag to approve new secrets in non-interactive mode."
5372
- )
5373
- );
5534
+ if (options.porcelain) {
5535
+ console.log(
5536
+ JSON.stringify({
5537
+ error: `New secrets detected: ${trulyNewSecrets.join(", ")}. Use --yes flag to approve.`
5538
+ })
5539
+ );
5540
+ } else {
5541
+ console.error(
5542
+ chalk4.red(`\u2717 New secrets detected: ${trulyNewSecrets.join(", ")}`)
5543
+ );
5544
+ console.error(
5545
+ chalk4.dim(
5546
+ " Use --yes flag to approve new secrets in non-interactive mode."
5547
+ )
5548
+ );
5549
+ }
5374
5550
  process.exit(1);
5375
5551
  }
5376
5552
  const confirmed = await promptConfirm(
@@ -5378,7 +5554,9 @@ async function displayAndConfirmVariables(variables, options) {
5378
5554
  true
5379
5555
  );
5380
5556
  if (!confirmed) {
5381
- console.log(chalk4.yellow("Compose cancelled"));
5557
+ if (!options.porcelain) {
5558
+ console.log(chalk4.yellow("Compose cancelled"));
5559
+ }
5382
5560
  return false;
5383
5561
  }
5384
5562
  }
@@ -5406,57 +5584,94 @@ async function finalizeCompose(config, agent, variables, options) {
5406
5584
  process.exit(0);
5407
5585
  }
5408
5586
  mergeSkillVariables(agent, variables);
5409
- console.log("Uploading compose...");
5587
+ if (!options.porcelain) {
5588
+ console.log("Uploading compose...");
5589
+ }
5410
5590
  const response = await createOrUpdateCompose({ content: config });
5411
5591
  const scopeResponse = await getScope();
5412
5592
  const shortVersionId = response.versionId.slice(0, 8);
5413
5593
  const displayName = `${scopeResponse.slug}/${response.name}`;
5414
- if (response.action === "created") {
5415
- console.log(chalk4.green(`\u2713 Compose created: ${displayName}`));
5416
- } else {
5417
- console.log(chalk4.green(`\u2713 Compose version exists: ${displayName}`));
5594
+ const result = {
5595
+ composeId: response.composeId,
5596
+ composeName: response.name,
5597
+ versionId: response.versionId,
5598
+ action: response.action,
5599
+ displayName
5600
+ };
5601
+ if (!options.porcelain) {
5602
+ if (response.action === "created") {
5603
+ console.log(chalk4.green(`\u2713 Compose created: ${displayName}`));
5604
+ } else {
5605
+ console.log(chalk4.green(`\u2713 Compose version exists: ${displayName}`));
5606
+ }
5607
+ console.log(chalk4.dim(` Version: ${shortVersionId}`));
5608
+ console.log();
5609
+ console.log(" Run your agent:");
5610
+ console.log(
5611
+ chalk4.cyan(
5612
+ ` vm0 run ${displayName}:${shortVersionId} --artifact-name <artifact> "your prompt"`
5613
+ )
5614
+ );
5418
5615
  }
5419
- console.log(chalk4.dim(` Version: ${shortVersionId}`));
5420
- console.log();
5421
- console.log(" Run your agent:");
5422
- console.log(
5423
- chalk4.cyan(
5424
- ` vm0 run ${displayName}:${shortVersionId} --artifact-name <artifact> "your prompt"`
5425
- )
5426
- );
5427
5616
  if (options.autoUpdate !== false) {
5428
- await silentUpgradeAfterCommand("9.20.2");
5617
+ await silentUpgradeAfterCommand("9.22.0");
5429
5618
  }
5619
+ return result;
5430
5620
  }
5431
5621
  async function handleGitHubCompose(url, options) {
5432
- console.log(`Downloading from GitHub: ${url}`);
5622
+ if (!options.porcelain) {
5623
+ console.log(`Downloading from GitHub: ${url}`);
5624
+ }
5433
5625
  const { dir: downloadedDir, tempRoot } = await downloadGitHubDirectory(url);
5434
5626
  const configFile = join6(downloadedDir, "vm0.yaml");
5435
5627
  try {
5436
5628
  if (!existsSync4(configFile)) {
5437
- console.error(chalk4.red(`\u2717 vm0.yaml not found in the GitHub directory`));
5438
- console.error(chalk4.dim(` URL: ${url}`));
5629
+ if (options.porcelain) {
5630
+ console.log(
5631
+ JSON.stringify({
5632
+ error: "vm0.yaml not found in the GitHub directory"
5633
+ })
5634
+ );
5635
+ } else {
5636
+ console.error(
5637
+ chalk4.red(`\u2717 vm0.yaml not found in the GitHub directory`)
5638
+ );
5639
+ console.error(chalk4.dim(` URL: ${url}`));
5640
+ }
5439
5641
  process.exit(1);
5440
5642
  }
5441
- const { config, agentName, agent, basePath } = await loadAndValidateConfig(configFile);
5643
+ const { config, agentName, agent, basePath } = await loadAndValidateConfig(
5644
+ configFile,
5645
+ options.porcelain
5646
+ );
5442
5647
  const existingCompose = await getComposeByName(agentName);
5443
5648
  if (existingCompose) {
5444
- console.log();
5445
- console.log(
5446
- chalk4.yellow(`\u26A0 An agent named "${agentName}" already exists.`)
5447
- );
5649
+ if (!options.porcelain) {
5650
+ console.log();
5651
+ console.log(
5652
+ chalk4.yellow(`\u26A0 An agent named "${agentName}" already exists.`)
5653
+ );
5654
+ }
5448
5655
  if (!isInteractive()) {
5449
5656
  if (!options.yes) {
5450
- console.error(
5451
- chalk4.red(
5452
- `\u2717 Cannot overwrite existing agent in non-interactive mode`
5453
- )
5454
- );
5455
- console.error(
5456
- chalk4.dim(
5457
- ` Use --yes flag to confirm overwriting the existing agent.`
5458
- )
5459
- );
5657
+ if (options.porcelain) {
5658
+ console.log(
5659
+ JSON.stringify({
5660
+ error: "Cannot overwrite existing agent in non-interactive mode"
5661
+ })
5662
+ );
5663
+ } else {
5664
+ console.error(
5665
+ chalk4.red(
5666
+ `\u2717 Cannot overwrite existing agent in non-interactive mode`
5667
+ )
5668
+ );
5669
+ console.error(
5670
+ chalk4.dim(
5671
+ ` Use --yes flag to confirm overwriting the existing agent.`
5672
+ )
5673
+ );
5674
+ }
5460
5675
  process.exit(1);
5461
5676
  }
5462
5677
  } else {
@@ -5465,31 +5680,48 @@ async function handleGitHubCompose(url, options) {
5465
5680
  false
5466
5681
  );
5467
5682
  if (!confirmed) {
5468
- console.log(chalk4.yellow("Compose cancelled."));
5683
+ if (!options.porcelain) {
5684
+ console.log(chalk4.yellow("Compose cancelled."));
5685
+ }
5469
5686
  process.exit(0);
5470
5687
  }
5471
5688
  }
5472
5689
  }
5473
5690
  if (hasVolumes(config)) {
5474
- console.error(
5475
- chalk4.red(`\u2717 Volumes are not supported for GitHub URL compose`)
5476
- );
5477
- console.error(
5478
- chalk4.dim(
5479
- ` Clone the repository locally and run: vm0 compose ./path/to/vm0.yaml`
5480
- )
5481
- );
5691
+ if (options.porcelain) {
5692
+ console.log(
5693
+ JSON.stringify({
5694
+ error: "Volumes are not supported for GitHub URL compose"
5695
+ })
5696
+ );
5697
+ } else {
5698
+ console.error(
5699
+ chalk4.red(`\u2717 Volumes are not supported for GitHub URL compose`)
5700
+ );
5701
+ console.error(
5702
+ chalk4.dim(
5703
+ ` Clone the repository locally and run: vm0 compose ./path/to/vm0.yaml`
5704
+ )
5705
+ );
5706
+ }
5482
5707
  process.exit(1);
5483
5708
  }
5484
- checkLegacyImageFormat(config);
5485
- const skillResults = await uploadAssets(agentName, agent, basePath);
5709
+ if (!options.porcelain) {
5710
+ checkLegacyImageFormat(config);
5711
+ }
5712
+ const skillResults = await uploadAssets(
5713
+ agentName,
5714
+ agent,
5715
+ basePath,
5716
+ options.porcelain
5717
+ );
5486
5718
  const environment = agent.environment || {};
5487
5719
  const variables = await collectSkillVariables(
5488
5720
  skillResults,
5489
5721
  environment,
5490
5722
  agentName
5491
5723
  );
5492
- await finalizeCompose(config, agent, variables, options);
5724
+ return await finalizeCompose(config, agent, variables, options);
5493
5725
  } finally {
5494
5726
  await rm3(tempRoot, { recursive: true, force: true });
5495
5727
  }
@@ -5500,42 +5732,73 @@ var composeCommand = new Command7().name("compose").description("Create or updat
5500
5732
  ).option("-y, --yes", "Skip confirmation prompts for skill requirements").option(
5501
5733
  "--experimental-shared-compose",
5502
5734
  "Enable GitHub URL compose (experimental)"
5735
+ ).option(
5736
+ "--porcelain",
5737
+ "Output stable JSON for scripts (suppresses interactive output)"
5503
5738
  ).addOption(new Option("--no-auto-update").hideHelp()).action(
5504
5739
  async (configFile, options) => {
5505
5740
  const resolvedConfigFile = configFile ?? DEFAULT_CONFIG_FILE;
5741
+ if (options.porcelain) {
5742
+ options.yes = true;
5743
+ options.autoUpdate = false;
5744
+ }
5506
5745
  try {
5507
- if (isGitHubTreeUrl(resolvedConfigFile)) {
5746
+ let result;
5747
+ if (isGitHubUrl(resolvedConfigFile)) {
5508
5748
  if (!options.experimentalSharedCompose) {
5509
- console.error(
5510
- chalk4.red(
5511
- "\u2717 Composing shared agents requires --experimental-shared-compose flag"
5512
- )
5513
- );
5514
- console.error();
5515
- console.error(
5516
- chalk4.dim(
5517
- " Composing agents from other users carries security risks."
5518
- )
5519
- );
5520
- console.error(
5521
- chalk4.dim(" Only compose agents from users you trust.")
5522
- );
5749
+ if (options.porcelain) {
5750
+ console.log(
5751
+ JSON.stringify({
5752
+ error: "Composing shared agents requires --experimental-shared-compose flag"
5753
+ })
5754
+ );
5755
+ } else {
5756
+ console.error(
5757
+ chalk4.red(
5758
+ "\u2717 Composing shared agents requires --experimental-shared-compose flag"
5759
+ )
5760
+ );
5761
+ console.error();
5762
+ console.error(
5763
+ chalk4.dim(
5764
+ " Composing agents from other users carries security risks."
5765
+ )
5766
+ );
5767
+ console.error(
5768
+ chalk4.dim(" Only compose agents from users you trust.")
5769
+ );
5770
+ }
5523
5771
  process.exit(1);
5524
5772
  }
5525
- await handleGitHubCompose(resolvedConfigFile, options);
5773
+ result = await handleGitHubCompose(resolvedConfigFile, options);
5526
5774
  } else {
5527
- const { config, agentName, agent, basePath } = await loadAndValidateConfig(resolvedConfigFile);
5528
- checkLegacyImageFormat(config);
5529
- const skillResults = await uploadAssets(agentName, agent, basePath);
5775
+ const { config, agentName, agent, basePath } = await loadAndValidateConfig(resolvedConfigFile, options.porcelain);
5776
+ if (!options.porcelain) {
5777
+ checkLegacyImageFormat(config);
5778
+ }
5779
+ const skillResults = await uploadAssets(
5780
+ agentName,
5781
+ agent,
5782
+ basePath,
5783
+ options.porcelain
5784
+ );
5530
5785
  const environment = agent.environment || {};
5531
5786
  const variables = await collectSkillVariables(
5532
5787
  skillResults,
5533
5788
  environment,
5534
5789
  agentName
5535
5790
  );
5536
- await finalizeCompose(config, agent, variables, options);
5791
+ result = await finalizeCompose(config, agent, variables, options);
5792
+ }
5793
+ if (options.porcelain) {
5794
+ console.log(JSON.stringify(result));
5537
5795
  }
5538
5796
  } catch (error) {
5797
+ if (options.porcelain) {
5798
+ const message = error instanceof Error ? error.message : "An unexpected error occurred";
5799
+ console.log(JSON.stringify({ error: message }));
5800
+ process.exit(1);
5801
+ }
5539
5802
  if (error instanceof Error) {
5540
5803
  if (error.message.includes("Not authenticated")) {
5541
5804
  console.error(
@@ -7793,7 +8056,7 @@ var mainRunCommand = new Command8().name("run").description("Run an agent").argu
7793
8056
  }
7794
8057
  showNextSteps(result);
7795
8058
  if (options.autoUpdate !== false) {
7796
- await silentUpgradeAfterCommand("9.20.2");
8059
+ await silentUpgradeAfterCommand("9.22.0");
7797
8060
  }
7798
8061
  } catch (error) {
7799
8062
  handleRunError(error, identifier);
@@ -8238,7 +8501,7 @@ import { Command as Command15 } from "commander";
8238
8501
  import chalk17 from "chalk";
8239
8502
  import path7 from "path";
8240
8503
  import * as fs6 from "fs";
8241
- import * as os4 from "os";
8504
+ import * as os5 from "os";
8242
8505
  import * as tar3 from "tar";
8243
8506
 
8244
8507
  // src/lib/storage/pull-utils.ts
@@ -8290,7 +8553,7 @@ var pullCommand = new Command15().name("pull").description("Pull cloud files to
8290
8553
  const arrayBuffer = await s3Response.arrayBuffer();
8291
8554
  const tarBuffer = Buffer.from(arrayBuffer);
8292
8555
  console.log(chalk17.green(`\u2713 Downloaded ${formatBytes(tarBuffer.length)}`));
8293
- const tmpDir = fs6.mkdtempSync(path7.join(os4.tmpdir(), "vm0-"));
8556
+ const tmpDir = fs6.mkdtempSync(path7.join(os5.tmpdir(), "vm0-"));
8294
8557
  const tarPath = path7.join(tmpDir, "volume.tar.gz");
8295
8558
  await fs6.promises.writeFile(tarPath, tarBuffer);
8296
8559
  console.log(chalk17.dim("Syncing local files..."));
@@ -8439,7 +8702,7 @@ import chalk21 from "chalk";
8439
8702
  import chalk20 from "chalk";
8440
8703
  import path8 from "path";
8441
8704
  import * as fs7 from "fs";
8442
- import * as os5 from "os";
8705
+ import * as os6 from "os";
8443
8706
  import * as tar4 from "tar";
8444
8707
  async function cloneStorage(name, type, destination, options = {}) {
8445
8708
  const typeLabel = type === "artifact" ? "artifact" : "volume";
@@ -8479,7 +8742,7 @@ async function cloneStorage(name, type, destination, options = {}) {
8479
8742
  const arrayBuffer = await s3Response.arrayBuffer();
8480
8743
  const tarBuffer = Buffer.from(arrayBuffer);
8481
8744
  console.log(chalk20.green(`\u2713 Downloaded ${formatBytes(tarBuffer.length)}`));
8482
- const tmpDir = fs7.mkdtempSync(path8.join(os5.tmpdir(), "vm0-clone-"));
8745
+ const tmpDir = fs7.mkdtempSync(path8.join(os6.tmpdir(), "vm0-clone-"));
8483
8746
  const tarPath = path8.join(tmpDir, "archive.tar.gz");
8484
8747
  await fs7.promises.writeFile(tarPath, tarBuffer);
8485
8748
  const files = await listTarFiles(tarPath);
@@ -8680,7 +8943,7 @@ import { Command as Command22 } from "commander";
8680
8943
  import chalk24 from "chalk";
8681
8944
  import path10 from "path";
8682
8945
  import * as fs8 from "fs";
8683
- import * as os6 from "os";
8946
+ import * as os7 from "os";
8684
8947
  import * as tar5 from "tar";
8685
8948
  var pullCommand2 = new Command22().name("pull").description("Pull cloud artifact to local directory").argument("[versionId]", "Version ID to pull (default: latest)").action(async (versionId) => {
8686
8949
  try {
@@ -8727,7 +8990,7 @@ var pullCommand2 = new Command22().name("pull").description("Pull cloud artifact
8727
8990
  const arrayBuffer = await s3Response.arrayBuffer();
8728
8991
  const tarBuffer = Buffer.from(arrayBuffer);
8729
8992
  console.log(chalk24.green(`\u2713 Downloaded ${formatBytes(tarBuffer.length)}`));
8730
- const tmpDir = fs8.mkdtempSync(path10.join(os6.tmpdir(), "vm0-"));
8993
+ const tmpDir = fs8.mkdtempSync(path10.join(os7.tmpdir(), "vm0-"));
8731
8994
  const tarPath = path10.join(tmpDir, "artifact.tar.gz");
8732
8995
  await fs8.promises.writeFile(tarPath, tarBuffer);
8733
8996
  console.log(chalk24.dim("Syncing local files..."));
@@ -9300,7 +9563,7 @@ var cookAction = new Command27().name("cook").description("Quick start: prepare,
9300
9563
  ).option("-y, --yes", "Skip confirmation prompts").option("-v, --verbose", "Show full tool inputs and outputs").addOption(new Option5("--debug-no-mock-claude").hideHelp()).addOption(new Option5("--no-auto-update").hideHelp()).action(
9301
9564
  async (prompt, options) => {
9302
9565
  if (options.autoUpdate !== false) {
9303
- const shouldExit = await checkAndUpgrade("9.20.2", prompt);
9566
+ const shouldExit = await checkAndUpgrade("9.22.0", prompt);
9304
9567
  if (shouldExit) {
9305
9568
  process.exit(0);
9306
9569
  }
@@ -10049,7 +10312,7 @@ import chalk38 from "chalk";
10049
10312
  // src/lib/domain/source-derivation.ts
10050
10313
  import * as fs9 from "fs/promises";
10051
10314
  import * as path14 from "path";
10052
- import * as os7 from "os";
10315
+ import * as os8 from "os";
10053
10316
  async function fetchSkillFrontmatter(skillUrl, tempDir) {
10054
10317
  try {
10055
10318
  const parsed = parseGitHubTreeUrl2(skillUrl);
@@ -10089,7 +10352,7 @@ async function deriveAgentVariableSources(agent, options) {
10089
10352
  };
10090
10353
  }
10091
10354
  const tempDir = await fs9.mkdtemp(
10092
- path14.join(os7.tmpdir(), "vm0-source-derivation-")
10355
+ path14.join(os8.tmpdir(), "vm0-source-derivation-")
10093
10356
  );
10094
10357
  try {
10095
10358
  const skillResults = await Promise.all(
@@ -11840,7 +12103,7 @@ var listCommand6 = new Command53().name("list").alias("ls").description("List al
11840
12103
  console.log(chalk53.dim("No secrets found"));
11841
12104
  console.log();
11842
12105
  console.log("To add a secret:");
11843
- console.log(chalk53.cyan(" vm0 secret set MY_API_KEY <value>"));
12106
+ console.log(chalk53.cyan(" vm0 secret set MY_API_KEY --body <value>"));
11844
12107
  return;
11845
12108
  }
11846
12109
  console.log(chalk53.bold("Secrets:"));
@@ -11874,9 +12137,32 @@ var listCommand6 = new Command53().name("list").alias("ls").description("List al
11874
12137
  // src/commands/secret/set.ts
11875
12138
  import { Command as Command54 } from "commander";
11876
12139
  import chalk54 from "chalk";
11877
- var setCommand2 = new Command54().name("set").description("Create or update a secret").argument("<name>", "Secret name (uppercase, e.g., MY_API_KEY)").argument("<value>", "Secret value").option("-d, --description <description>", "Optional description").action(
11878
- async (name, value, options) => {
12140
+ var setCommand2 = new Command54().name("set").description("Create or update a secret").argument("<name>", "Secret name (uppercase, e.g., MY_API_KEY)").option(
12141
+ "-b, --body <value>",
12142
+ "Secret value (required in non-interactive mode)"
12143
+ ).option("-d, --description <description>", "Optional description").action(
12144
+ async (name, options) => {
11879
12145
  try {
12146
+ let value;
12147
+ if (options.body !== void 0) {
12148
+ value = options.body;
12149
+ } else if (isInteractive()) {
12150
+ const prompted = await promptPassword("Enter secret value:");
12151
+ if (prompted === void 0) {
12152
+ process.exit(0);
12153
+ }
12154
+ value = prompted;
12155
+ } else {
12156
+ console.error(
12157
+ chalk54.red("\u2717 --body is required in non-interactive mode")
12158
+ );
12159
+ console.log();
12160
+ console.log("Usage:");
12161
+ console.log(
12162
+ chalk54.cyan(` vm0 secret set ${name} --body "your-secret-value"`)
12163
+ );
12164
+ process.exit(1);
12165
+ }
11880
12166
  const secret = await setSecret({
11881
12167
  name,
11882
12168
  value,
@@ -12203,63 +12489,61 @@ function validateAuthMethod(type, authMethodStr) {
12203
12489
  }
12204
12490
  return authMethodStr;
12205
12491
  }
12206
- function parseCredentials(type, authMethod, credentialArgs) {
12207
- const credentialsConfig = getCredentialsForAuthMethod(type, authMethod);
12208
- if (!credentialsConfig) {
12492
+ function parseSecrets(type, authMethod, secretArgs) {
12493
+ const secretsConfig = getSecretsForAuthMethod(type, authMethod);
12494
+ if (!secretsConfig) {
12209
12495
  console.error(chalk60.red(`\u2717 Invalid auth method "${authMethod}"`));
12210
12496
  process.exit(1);
12211
12497
  }
12212
- const credentialNames = Object.keys(credentialsConfig);
12213
- const firstArg = credentialArgs[0];
12214
- if (credentialArgs.length === 1 && firstArg && !firstArg.includes("=")) {
12215
- if (credentialNames.length !== 1) {
12498
+ const secretNames = Object.keys(secretsConfig);
12499
+ const firstArg = secretArgs[0];
12500
+ if (secretArgs.length === 1 && firstArg && !firstArg.includes("=")) {
12501
+ if (secretNames.length !== 1) {
12216
12502
  console.error(
12217
- chalk60.red(
12218
- "\u2717 Must use KEY=VALUE format for multi-credential auth methods"
12219
- )
12503
+ chalk60.red("\u2717 Must use KEY=VALUE format for multi-secret auth methods")
12220
12504
  );
12221
12505
  console.log();
12222
- console.log("Required credentials:");
12223
- for (const [name, fieldConfig] of Object.entries(credentialsConfig)) {
12506
+ console.log("Required secrets:");
12507
+ for (const [name, fieldConfig] of Object.entries(secretsConfig)) {
12224
12508
  const requiredNote = fieldConfig.required ? " (required)" : "";
12225
12509
  console.log(` ${chalk60.cyan(name)}${requiredNote}`);
12226
12510
  }
12227
12511
  process.exit(1);
12228
12512
  }
12229
- const firstCredentialName = credentialNames[0];
12230
- if (!firstCredentialName) {
12231
- console.error(chalk60.red("\u2717 No credentials defined for this auth method"));
12513
+ const firstSecretName = secretNames[0];
12514
+ if (!firstSecretName) {
12515
+ console.error(chalk60.red("\u2717 No secrets defined for this auth method"));
12232
12516
  process.exit(1);
12233
12517
  }
12234
- return { [firstCredentialName]: firstArg };
12518
+ return { [firstSecretName]: firstArg };
12235
12519
  }
12236
- const credentials = {};
12237
- for (const arg of credentialArgs) {
12520
+ const secrets = {};
12521
+ for (const arg of secretArgs) {
12238
12522
  const eqIndex = arg.indexOf("=");
12239
12523
  if (eqIndex === -1) {
12240
- console.error(chalk60.red(`\u2717 Invalid credential format "${arg}"`));
12524
+ console.error(chalk60.red(`\u2717 Invalid secret format "${arg}"`));
12241
12525
  console.log();
12242
12526
  console.log("Use KEY=VALUE format (e.g., AWS_REGION=us-east-1)");
12243
12527
  process.exit(1);
12244
12528
  }
12245
12529
  const key = arg.slice(0, eqIndex);
12246
12530
  const value = arg.slice(eqIndex + 1);
12247
- credentials[key] = value;
12531
+ secrets[key] = value;
12248
12532
  }
12249
- return credentials;
12533
+ return secrets;
12250
12534
  }
12251
- function validateCredentials(type, authMethod, credentials) {
12252
- const credentialsConfig = getCredentialsForAuthMethod(type, authMethod);
12253
- if (!credentialsConfig) {
12535
+ function validateSecrets(type, authMethod, secrets) {
12536
+ const secretsConfig = getSecretsForAuthMethod(type, authMethod);
12537
+ if (!secretsConfig) {
12254
12538
  console.error(chalk60.red(`\u2717 Invalid auth method "${authMethod}"`));
12255
12539
  process.exit(1);
12256
12540
  }
12257
- for (const [name, fieldConfig] of Object.entries(credentialsConfig)) {
12258
- if (fieldConfig.required && !credentials[name]) {
12259
- console.error(chalk60.red(`\u2717 Missing required credential: ${name}`));
12541
+ for (const [name, fieldConfig] of Object.entries(secretsConfig)) {
12542
+ if (fieldConfig.required && !secrets[name]) {
12543
+ console.error(chalk60.red(`\u2717 Missing required secret: ${name}`));
12260
12544
  console.log();
12261
- console.log("Required credentials:");
12262
- for (const [n, fc] of Object.entries(credentialsConfig)) {
12545
+ console.log("Required secrets:");
12546
+ for (const [n, fc] of Object.entries(secretsConfig)) {
12263
12547
  if (fc.required) {
12264
12548
  console.log(` ${chalk60.cyan(n)} - ${fc.label}`);
12265
12549
  }
@@ -12267,12 +12551,12 @@ function validateCredentials(type, authMethod, credentials) {
12267
12551
  process.exit(1);
12268
12552
  }
12269
12553
  }
12270
- for (const name of Object.keys(credentials)) {
12271
- if (!(name in credentialsConfig)) {
12272
- console.error(chalk60.red(`\u2717 Unknown credential: ${name}`));
12554
+ for (const name of Object.keys(secrets)) {
12555
+ if (!(name in secretsConfig)) {
12556
+ console.error(chalk60.red(`\u2717 Unknown secret: ${name}`));
12273
12557
  console.log();
12274
- console.log("Valid credentials:");
12275
- for (const [n, fc] of Object.entries(credentialsConfig)) {
12558
+ console.log("Valid secrets:");
12559
+ for (const [n, fc] of Object.entries(secretsConfig)) {
12276
12560
  const requiredNote = fc.required ? " (required)" : " (optional)";
12277
12561
  console.log(` ${chalk60.cyan(n)}${requiredNote}`);
12278
12562
  }
@@ -12321,37 +12605,37 @@ function handleNonInteractiveMode(options) {
12321
12605
  console.log("Example:");
12322
12606
  console.log(
12323
12607
  chalk60.cyan(
12324
- ` vm0 model-provider setup --type ${type} --auth-method ${authMethodNames[0]} --credential KEY=VALUE`
12608
+ ` vm0 model-provider setup --type ${type} --auth-method ${authMethodNames[0]} --secret KEY=VALUE`
12325
12609
  )
12326
12610
  );
12327
12611
  process.exit(1);
12328
12612
  }
12329
12613
  }
12330
- const credentials = parseCredentials(type, authMethod, options.credential);
12331
- validateCredentials(type, authMethod, credentials);
12614
+ const secrets = parseSecrets(type, authMethod, options.secret);
12615
+ validateSecrets(type, authMethod, secrets);
12332
12616
  return {
12333
12617
  type,
12334
12618
  authMethod,
12335
- credentials,
12619
+ secrets,
12336
12620
  selectedModel,
12337
12621
  isInteractiveMode: false
12338
12622
  };
12339
12623
  }
12340
- const credentialArgs = options.credential;
12341
- const firstArg = credentialArgs[0];
12624
+ const secretArgs = options.secret;
12625
+ const firstArg = secretArgs[0];
12342
12626
  if (!firstArg) {
12343
- console.error(chalk60.red("\u2717 Credential is required"));
12627
+ console.error(chalk60.red("\u2717 Secret is required"));
12344
12628
  process.exit(1);
12345
12629
  }
12346
- let credential;
12630
+ let secret;
12347
12631
  if (firstArg.includes("=")) {
12348
- credential = firstArg.slice(firstArg.indexOf("=") + 1);
12632
+ secret = firstArg.slice(firstArg.indexOf("=") + 1);
12349
12633
  } else {
12350
- credential = firstArg;
12634
+ secret = firstArg;
12351
12635
  }
12352
12636
  return {
12353
12637
  type,
12354
- credential,
12638
+ secret,
12355
12639
  selectedModel,
12356
12640
  isInteractiveMode: false
12357
12641
  };
@@ -12425,29 +12709,29 @@ async function promptForAuthMethod(type) {
12425
12709
  );
12426
12710
  return response.authMethod;
12427
12711
  }
12428
- function isSecretCredential(name) {
12712
+ function isSensitiveSecret(name) {
12429
12713
  const nonSecretPatterns = ["REGION", "ENDPOINT", "URL"];
12430
12714
  return !nonSecretPatterns.some(
12431
12715
  (pattern) => name.toUpperCase().includes(pattern)
12432
12716
  );
12433
12717
  }
12434
- async function promptForCredentials(type, authMethod) {
12435
- const credentialsConfig = getCredentialsForAuthMethod(type, authMethod);
12436
- if (!credentialsConfig) {
12718
+ async function promptForSecrets(type, authMethod) {
12719
+ const secretsConfig = getSecretsForAuthMethod(type, authMethod);
12720
+ if (!secretsConfig) {
12437
12721
  console.error(chalk60.red(`\u2717 Invalid auth method "${authMethod}"`));
12438
12722
  process.exit(1);
12439
12723
  }
12440
- const credentials = {};
12441
- for (const [name, fieldConfig] of Object.entries(credentialsConfig)) {
12724
+ const secrets = {};
12725
+ for (const [name, fieldConfig] of Object.entries(secretsConfig)) {
12442
12726
  if (fieldConfig.helpText) {
12443
12727
  console.log(chalk60.dim(fieldConfig.helpText));
12444
12728
  }
12445
- const isSecret = isSecretCredential(name);
12729
+ const isSensitive = isSensitiveSecret(name);
12446
12730
  const placeholder = "placeholder" in fieldConfig ? fieldConfig.placeholder : "";
12447
12731
  if (fieldConfig.required) {
12448
12732
  const response = await prompts2(
12449
12733
  {
12450
- type: isSecret ? "password" : "text",
12734
+ type: isSensitive ? "password" : "text",
12451
12735
  name: "value",
12452
12736
  message: `${fieldConfig.label}:`,
12453
12737
  initial: placeholder ? "" : void 0,
@@ -12455,11 +12739,11 @@ async function promptForCredentials(type, authMethod) {
12455
12739
  },
12456
12740
  { onCancel: () => process.exit(0) }
12457
12741
  );
12458
- credentials[name] = response.value;
12742
+ secrets[name] = response.value;
12459
12743
  } else {
12460
12744
  const response = await prompts2(
12461
12745
  {
12462
- type: isSecret ? "password" : "text",
12746
+ type: isSensitive ? "password" : "text",
12463
12747
  name: "value",
12464
12748
  message: `${fieldConfig.label} (optional):`
12465
12749
  },
@@ -12467,11 +12751,11 @@ async function promptForCredentials(type, authMethod) {
12467
12751
  );
12468
12752
  const value = response.value;
12469
12753
  if (value && value.trim()) {
12470
- credentials[name] = value.trim();
12754
+ secrets[name] = value.trim();
12471
12755
  }
12472
12756
  }
12473
12757
  }
12474
- return credentials;
12758
+ return secrets;
12475
12759
  }
12476
12760
  async function handleInteractiveMode() {
12477
12761
  if (!isInteractive()) {
@@ -12479,9 +12763,7 @@ async function handleInteractiveMode() {
12479
12763
  console.log();
12480
12764
  console.log("Use non-interactive mode:");
12481
12765
  console.log(
12482
- chalk60.cyan(
12483
- ' vm0 model-provider setup --type <type> --credential "<value>"'
12484
- )
12766
+ chalk60.cyan(' vm0 model-provider setup --type <type> --secret "<value>"')
12485
12767
  );
12486
12768
  process.exit(1);
12487
12769
  }
@@ -12514,32 +12796,8 @@ async function handleInteractiveMode() {
12514
12796
  { onCancel: () => process.exit(0) }
12515
12797
  );
12516
12798
  const type = typeResponse.type;
12517
- const checkResult = await checkModelProviderCredential(type);
12518
- if (checkResult.exists && checkResult.currentType === "user") {
12519
- const convertResponse = await prompts2(
12520
- {
12521
- type: "confirm",
12522
- name: "convert",
12523
- message: `Credential "${checkResult.credentialName}" already exists. Convert to model provider?`,
12524
- initial: true
12525
- },
12526
- { onCancel: () => process.exit(0) }
12527
- );
12528
- if (convertResponse.convert) {
12529
- const provider = await convertModelProviderCredential(type);
12530
- const defaultNote = provider.isDefault ? ` (default for ${provider.framework})` : "";
12531
- console.log(
12532
- chalk60.green(
12533
- `\u2713 Converted "${checkResult.credentialName}" to model provider${defaultNote}`
12534
- )
12535
- );
12536
- await promptSetAsDefault(type, provider.framework, provider.isDefault);
12537
- return null;
12538
- }
12539
- console.log(chalk60.dim("Aborted"));
12540
- process.exit(0);
12541
- }
12542
- if (checkResult.exists && checkResult.currentType === "model-provider") {
12799
+ const checkResult = await checkModelProviderSecret(type);
12800
+ if (checkResult.exists) {
12543
12801
  console.log();
12544
12802
  console.log(`"${type}" is already configured.`);
12545
12803
  console.log();
@@ -12549,8 +12807,8 @@ async function handleInteractiveMode() {
12549
12807
  name: "action",
12550
12808
  message: "",
12551
12809
  choices: [
12552
- { title: "Keep existing credential", value: "keep" },
12553
- { title: "Update credential", value: "update" }
12810
+ { title: "Keep existing secret", value: "keep" },
12811
+ { title: "Update secret", value: "update" }
12554
12812
  ]
12555
12813
  },
12556
12814
  { onCancel: () => process.exit(0) }
@@ -12559,7 +12817,7 @@ async function handleInteractiveMode() {
12559
12817
  const selectedModel2 = await promptForModelSelection(type);
12560
12818
  return {
12561
12819
  type,
12562
- keepExistingCredential: true,
12820
+ keepExistingSecret: true,
12563
12821
  selectedModel: selectedModel2,
12564
12822
  isInteractiveMode: true
12565
12823
  };
@@ -12571,38 +12829,33 @@ async function handleInteractiveMode() {
12571
12829
  console.log();
12572
12830
  if (hasAuthMethods(type)) {
12573
12831
  const authMethod = await promptForAuthMethod(type);
12574
- const credentials = await promptForCredentials(type, authMethod);
12832
+ const secrets = await promptForSecrets(type, authMethod);
12575
12833
  const selectedModel2 = await promptForModelSelection(type);
12576
12834
  return {
12577
12835
  type,
12578
12836
  authMethod,
12579
- credentials,
12837
+ secrets,
12580
12838
  selectedModel: selectedModel2,
12581
12839
  isInteractiveMode: true
12582
12840
  };
12583
12841
  }
12584
- const credentialLabel = "credentialLabel" in config ? config.credentialLabel : "credential";
12585
- const credentialResponse = await prompts2(
12842
+ const secretLabel = "secretLabel" in config ? config.secretLabel : "secret";
12843
+ const secretResponse = await prompts2(
12586
12844
  {
12587
12845
  type: "password",
12588
- name: "credential",
12589
- message: `Enter your ${credentialLabel}:`,
12590
- validate: (value) => value.length > 0 || `${credentialLabel} is required`
12846
+ name: "secret",
12847
+ message: `Enter your ${secretLabel}:`,
12848
+ validate: (value) => value.length > 0 || `${secretLabel} is required`
12591
12849
  },
12592
12850
  { onCancel: () => process.exit(0) }
12593
12851
  );
12594
- const credential = credentialResponse.credential;
12852
+ const secret = secretResponse.secret;
12595
12853
  const selectedModel = await promptForModelSelection(type);
12596
- return { type, credential, selectedModel, isInteractiveMode: true };
12854
+ return { type, secret, selectedModel, isInteractiveMode: true };
12597
12855
  }
12598
12856
  function handleSetupError2(error) {
12599
12857
  if (error instanceof Error) {
12600
- if (error.message.includes("already exists")) {
12601
- console.error(chalk60.red(`\u2717 ${error.message}`));
12602
- console.log();
12603
- console.log("To convert the existing credential, run:");
12604
- console.log(chalk60.cyan(" vm0 model-provider setup --convert"));
12605
- } else if (error.message.includes("Not authenticated")) {
12858
+ if (error.message.includes("Not authenticated")) {
12606
12859
  console.error(chalk60.red("\u2717 Not authenticated. Run: vm0 auth login"));
12607
12860
  } else {
12608
12861
  console.error(chalk60.red(`\u2717 ${error.message}`));
@@ -12628,34 +12881,31 @@ async function promptSetAsDefault(type, framework, isDefault) {
12628
12881
  console.log(chalk60.green(`\u2713 Default for ${framework} set to "${type}"`));
12629
12882
  }
12630
12883
  }
12631
- function collectCredentials(value, previous) {
12884
+ function collectSecrets(value, previous) {
12632
12885
  return previous.concat([value]);
12633
12886
  }
12634
12887
  var setupCommand2 = new Command62().name("setup").description("Configure a model provider").option("-t, --type <type>", "Provider type (for non-interactive mode)").option(
12635
- "-c, --credential <value>",
12636
- "Credential value (can be used multiple times, supports VALUE or KEY=VALUE format)",
12637
- collectCredentials,
12888
+ "-s, --secret <value>",
12889
+ "Secret value (can be used multiple times, supports VALUE or KEY=VALUE format)",
12890
+ collectSecrets,
12638
12891
  []
12639
12892
  ).option(
12640
12893
  "-a, --auth-method <method>",
12641
12894
  "Auth method (required for multi-auth providers like aws-bedrock)"
12642
- ).option("-m, --model <model>", "Model selection (for non-interactive mode)").option("--convert", "Convert existing user credential to model provider").action(
12895
+ ).option("-m, --model <model>", "Model selection (for non-interactive mode)").action(
12643
12896
  async (options) => {
12644
12897
  try {
12645
12898
  let input;
12646
- const shouldConvert = options.convert ?? false;
12647
- const credentialArgs = options.credential ?? [];
12648
- if (options.type && credentialArgs.length > 0) {
12899
+ const secretArgs = options.secret ?? [];
12900
+ if (options.type && secretArgs.length > 0) {
12649
12901
  input = handleNonInteractiveMode({
12650
12902
  type: options.type,
12651
- credential: credentialArgs,
12903
+ secret: secretArgs,
12652
12904
  authMethod: options.authMethod,
12653
12905
  model: options.model
12654
12906
  });
12655
- } else if (options.type || credentialArgs.length > 0) {
12656
- console.error(
12657
- chalk60.red("\u2717 Both --type and --credential are required")
12658
- );
12907
+ } else if (options.type || secretArgs.length > 0) {
12908
+ console.error(chalk60.red("\u2717 Both --type and --secret are required"));
12659
12909
  process.exit(1);
12660
12910
  } else {
12661
12911
  const result = await handleInteractiveMode();
@@ -12664,7 +12914,7 @@ var setupCommand2 = new Command62().name("setup").description("Configure a model
12664
12914
  }
12665
12915
  input = result;
12666
12916
  }
12667
- if (input.keepExistingCredential) {
12917
+ if (input.keepExistingSecret) {
12668
12918
  const provider2 = await updateModelProviderModel(
12669
12919
  input.type,
12670
12920
  input.selectedModel
@@ -12693,10 +12943,9 @@ var setupCommand2 = new Command62().name("setup").description("Configure a model
12693
12943
  }
12694
12944
  const { provider, created } = await upsertModelProvider({
12695
12945
  type: input.type,
12696
- credential: input.credential,
12946
+ secret: input.secret,
12697
12947
  authMethod: input.authMethod,
12698
- credentials: input.credentials,
12699
- convert: shouldConvert,
12948
+ secrets: input.secrets,
12700
12949
  selectedModel: input.selectedModel
12701
12950
  });
12702
12951
  const action = created ? "created" : "updated";
@@ -13030,17 +13279,16 @@ function getProviderChoices() {
13030
13279
  type,
13031
13280
  label: config.label,
13032
13281
  helpText: config.helpText,
13033
- credentialLabel: "credentialLabel" in config ? config.credentialLabel : "",
13282
+ secretLabel: "secretLabel" in config ? config.secretLabel : "",
13034
13283
  models: getModels(type),
13035
13284
  defaultModel: getDefaultModel(type)
13036
13285
  };
13037
13286
  });
13038
13287
  }
13039
- async function setupModelProvider(type, credential, options) {
13288
+ async function setupModelProvider(type, secret, options) {
13040
13289
  const response = await upsertModelProvider({
13041
13290
  type,
13042
- credential,
13043
- convert: options?.convert,
13291
+ secret,
13044
13292
  selectedModel: options?.selectedModel
13045
13293
  });
13046
13294
  return {
@@ -13231,12 +13479,10 @@ async function handleModelProvider(ctx) {
13231
13479
  step.detail(chalk66.dim(line));
13232
13480
  }
13233
13481
  }
13234
- const credential = await step.prompt(
13235
- () => promptPassword(
13236
- `Enter your ${selectedChoice?.credentialLabel ?? "credential"}:`
13237
- )
13482
+ const secret = await step.prompt(
13483
+ () => promptPassword(`Enter your ${selectedChoice?.secretLabel ?? "secret"}:`)
13238
13484
  );
13239
- if (!credential) {
13485
+ if (!secret) {
13240
13486
  console.log(chalk66.dim("Cancelled"));
13241
13487
  process.exit(0);
13242
13488
  }
@@ -13261,7 +13507,7 @@ async function handleModelProvider(ctx) {
13261
13507
  }
13262
13508
  selectedModel = modelSelection === "" ? void 0 : modelSelection;
13263
13509
  }
13264
- const result = await setupModelProvider(providerType, credential, {
13510
+ const result = await setupModelProvider(providerType, secret, {
13265
13511
  selectedModel
13266
13512
  });
13267
13513
  const modelNote = result.provider.selectedModel ? ` with model: ${result.provider.selectedModel}` : "";
@@ -13415,7 +13661,7 @@ var setupClaudeCommand = new Command67().name("setup-claude").description("Insta
13415
13661
 
13416
13662
  // src/index.ts
13417
13663
  var program = new Command68();
13418
- program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.20.2");
13664
+ program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.22.0");
13419
13665
  program.addCommand(authCommand);
13420
13666
  program.addCommand(infoCommand);
13421
13667
  program.addCommand(composeCommand);