@vm0/cli 9.113.4 → 9.114.1

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.
package/index.js CHANGED
@@ -70,7 +70,7 @@ import {
70
70
  source_default,
71
71
  volumeConfigSchema,
72
72
  withErrorHandler
73
- } from "./chunk-2TQ7ATTO.js";
73
+ } from "./chunk-KFF4XQ5R.js";
74
74
 
75
75
  // src/index.ts
76
76
  init_esm_shims();
@@ -463,7 +463,7 @@ function getConfigPath() {
463
463
  return join(homedir(), ".vm0", "config.json");
464
464
  }
465
465
  var infoCommand = new Command().name("info").description("Display environment and debug information").action(async () => {
466
- console.log(source_default.bold(`VM0 CLI v${"9.113.4"}`));
466
+ console.log(source_default.bold(`VM0 CLI v${"9.114.1"}`));
467
467
  console.log();
468
468
  const config = await loadConfig();
469
469
  const hasEnvToken = !!process.env.VM0_TOKEN;
@@ -4492,7 +4492,7 @@ var composeCommand = new Command().name("compose").description("Create or update
4492
4492
  options.autoUpdate = false;
4493
4493
  }
4494
4494
  if (options.autoUpdate !== false) {
4495
- await startSilentUpgrade("9.113.4");
4495
+ await startSilentUpgrade("9.114.1");
4496
4496
  }
4497
4497
  try {
4498
4498
  let result;
@@ -4572,7 +4572,7 @@ var mainRunCommand = new Command().name("run").description("Run an agent").argum
4572
4572
  withErrorHandler(
4573
4573
  async (identifier, prompt, options) => {
4574
4574
  if (options.autoUpdate !== false) {
4575
- await startSilentUpgrade("9.113.4");
4575
+ await startSilentUpgrade("9.114.1");
4576
4576
  }
4577
4577
  const { name, version } = parseIdentifier(identifier);
4578
4578
  let composeId;
@@ -6313,7 +6313,7 @@ var cookAction = new Command().name("cook").description("Quick start: prepare, c
6313
6313
  withErrorHandler(
6314
6314
  async (prompt, options) => {
6315
6315
  if (options.autoUpdate !== false) {
6316
- const shouldExit = await checkAndUpgrade("9.113.4", prompt);
6316
+ const shouldExit = await checkAndUpgrade("9.114.1", prompt);
6317
6317
  if (shouldExit) {
6318
6318
  process.exit(0);
6319
6319
  }
@@ -7080,13 +7080,13 @@ var upgradeCommand = new Command().name("upgrade").description("Upgrade vm0 CLI
7080
7080
  if (latestVersion === null) {
7081
7081
  throw new Error("Could not check for updates. Please try again later.");
7082
7082
  }
7083
- if (latestVersion === "9.113.4") {
7084
- console.log(source_default.green(`\u2713 Already up to date (${"9.113.4"})`));
7083
+ if (latestVersion === "9.114.1") {
7084
+ console.log(source_default.green(`\u2713 Already up to date (${"9.114.1"})`));
7085
7085
  return;
7086
7086
  }
7087
7087
  console.log(
7088
7088
  source_default.yellow(
7089
- `Current version: ${"9.113.4"} -> Latest version: ${latestVersion}`
7089
+ `Current version: ${"9.114.1"} -> Latest version: ${latestVersion}`
7090
7090
  )
7091
7091
  );
7092
7092
  console.log();
@@ -7113,7 +7113,7 @@ var upgradeCommand = new Command().name("upgrade").description("Upgrade vm0 CLI
7113
7113
  const success = await performUpgrade(packageManager);
7114
7114
  if (success) {
7115
7115
  console.log(
7116
- source_default.green(`\u2713 Upgraded from ${"9.113.4"} to ${latestVersion}`)
7116
+ source_default.green(`\u2713 Upgraded from ${"9.114.1"} to ${latestVersion}`)
7117
7117
  );
7118
7118
  return;
7119
7119
  }
@@ -7180,7 +7180,7 @@ var whoamiCommand = new Command().name("whoami").description("Show current ident
7180
7180
 
7181
7181
  // src/index.ts
7182
7182
  var program = new Command();
7183
- program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.113.4");
7183
+ program.name("vm0").description("VM0 CLI - Build and run agents with natural language").version("9.114.1");
7184
7184
  program.addCommand(authCommand);
7185
7185
  program.addCommand(infoCommand);
7186
7186
  program.addCommand(composeCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "9.113.4",
3
+ "version": "9.114.1",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",
package/zero.js CHANGED
@@ -38,6 +38,7 @@ import {
38
38
  deployZeroSchedule,
39
39
  disableZeroSchedule,
40
40
  enableZeroSchedule,
41
+ extractSecretNamesFromApis,
41
42
  findMatchingPermissions,
42
43
  getActiveOrg,
43
44
  getApiUrl,
@@ -45,6 +46,7 @@ import {
45
46
  getBaseUrl,
46
47
  getComputerUseHost,
47
48
  getConnectorDerivedNames,
49
+ getConnectorEnvironmentMapping,
48
50
  getConnectorFirewall,
49
51
  getConnectorTypeForSecretName,
50
52
  getCustomModelPlaceholder,
@@ -67,6 +69,7 @@ import {
67
69
  getZeroOrgMembers,
68
70
  getZeroRun,
69
71
  getZeroRunAgentEvents,
72
+ getZeroRunContext,
70
73
  getZeroUserPreferences,
71
74
  hasAuthMethods,
72
75
  hasModelSelection,
@@ -127,7 +130,7 @@ import {
127
130
  upsertZeroOrgModelProvider,
128
131
  withErrorHandler,
129
132
  zeroAgentCustomSkillNameSchema
130
- } from "./chunk-2TQ7ATTO.js";
133
+ } from "./chunk-KFF4XQ5R.js";
131
134
 
132
135
  // src/zero.ts
133
136
  init_esm_shims();
@@ -2183,7 +2186,7 @@ var zeroConnectorCommand = new Command().name("connector").description("Check or
2183
2186
  // src/commands/zero/doctor/index.ts
2184
2187
  init_esm_shims();
2185
2188
 
2186
- // src/commands/zero/doctor/missing-token.ts
2189
+ // src/commands/zero/doctor/check-connector.ts
2187
2190
  init_esm_shims();
2188
2191
 
2189
2192
  // src/commands/zero/doctor/platform-url.ts
@@ -2206,80 +2209,403 @@ async function getPlatformOrigin() {
2206
2209
  return toPlatformUrl(apiUrl).origin;
2207
2210
  }
2208
2211
 
2209
- // src/commands/zero/doctor/missing-token.ts
2210
- var missingTokenCommand = new Command().name("missing-token").description(
2211
- "Diagnose a missing token and find the connector that provides it"
2212
- ).argument("<token-name>", "The environment variable / token name to look up").addHelpText(
2212
+ // src/commands/zero/doctor/check-connector.ts
2213
+ function resolveConnectorFromUrl(url) {
2214
+ const allTypes = Object.keys(CONNECTOR_TYPES);
2215
+ const normalized = url.endsWith("/") ? url.slice(0, -1) : url;
2216
+ let bestMatch = null;
2217
+ for (const type of allTypes) {
2218
+ if (!isFirewallConnectorType(type)) continue;
2219
+ const config = getConnectorFirewall(type);
2220
+ for (const api of config.apis) {
2221
+ const base = api.base.endsWith("/") ? api.base.slice(0, -1) : api.base;
2222
+ if (normalized === base || normalized.startsWith(base + "/")) {
2223
+ if (!bestMatch || base.length > bestMatch.base.length) {
2224
+ bestMatch = { connectorType: type, base, config };
2225
+ }
2226
+ }
2227
+ }
2228
+ }
2229
+ if (!bestMatch) return null;
2230
+ const mapping = getConnectorEnvironmentMapping(
2231
+ bestMatch.connectorType
2232
+ );
2233
+ const envName = Object.keys(mapping)[0];
2234
+ if (!envName) return null;
2235
+ const relativePath = normalized === bestMatch.base ? "/" : normalized.slice(bestMatch.base.length);
2236
+ return {
2237
+ connectorType: bestMatch.connectorType,
2238
+ envName,
2239
+ matchedBase: bestMatch.base,
2240
+ relativePath
2241
+ };
2242
+ }
2243
+ function checkEnvVariable(ctx) {
2244
+ console.log("## Step 1: Sandbox environment variable");
2245
+ console.log("");
2246
+ const envPresent = Boolean(process.env[ctx.envName]);
2247
+ console.log(
2248
+ `Checking process.env.${ctx.envName}: ${envPresent ? "present" : "not present"}`
2249
+ );
2250
+ if (envPresent) {
2251
+ console.log(
2252
+ "A placeholder value is present in the sandbox environment. This value is not the real credential \u2014 it is a stand-in that gets replaced at the network boundary when requests are sent to registered base URLs."
2253
+ );
2254
+ } else {
2255
+ console.log(
2256
+ "No value found for this environment variable. Note: credential replacement at the network boundary is independent of this variable \u2014 the proxy injects auth headers based on the destination URL, not the presence of this env var."
2257
+ );
2258
+ }
2259
+ console.log("");
2260
+ return envPresent;
2261
+ }
2262
+ async function checkConnectorStatus(ctx) {
2263
+ console.log("## Step 2: Connector configuration");
2264
+ console.log("");
2265
+ console.log(
2266
+ "A Connector holds the real credentials (OAuth tokens or API keys) for an external service. These credentials are never injected into the sandbox. Instead, when the sandbox sends an HTTP request to a base URL registered by the Connector, the network boundary intercepts the request and replaces the auth headers with real credentials. For this to work, three conditions must be met:"
2267
+ );
2268
+ console.log("");
2269
+ const [connector, enabledTypes] = await Promise.all([
2270
+ getZeroConnector(ctx.connectorType),
2271
+ ctx.agentId ? getZeroAgentUserConnectors(ctx.agentId) : Promise.resolve(null)
2272
+ ]);
2273
+ const isConnected = connector !== null;
2274
+ const isExpired = connector?.needsReconnect === true;
2275
+ const hasPermission = enabledTypes !== null && enabledTypes.includes(ctx.connectorType);
2276
+ console.log(
2277
+ `### 2a: Connector status (user must configure via OAuth login or API key)`
2278
+ );
2279
+ console.log("");
2280
+ if (!isConnected) {
2281
+ const connectUrl = ctx.agentId ? `${ctx.platformOrigin}/connectors/${ctx.connectorType}/connect?agentId=${ctx.agentId}` : `${ctx.platformOrigin}/connectors/${ctx.connectorType}/connect`;
2282
+ console.log(`The ${ctx.label} connector is not connected.`);
2283
+ console.log(`Connect it at: [Connect ${ctx.label}](${connectUrl})`);
2284
+ } else if (isExpired) {
2285
+ const url = `${ctx.platformOrigin}/connectors`;
2286
+ console.log(
2287
+ `The ${ctx.label} connector is connected but has expired and needs to be reconnected.`
2288
+ );
2289
+ console.log(`Reconnect it at: [Reconnect ${ctx.label}](${url})`);
2290
+ } else {
2291
+ console.log(`The ${ctx.label} connector is connected and active.`);
2292
+ }
2293
+ console.log("");
2294
+ console.log(
2295
+ `### 2b: Agent authorization (user must authorize agent to use this connector)`
2296
+ );
2297
+ console.log("");
2298
+ if (!ctx.agentId) {
2299
+ console.log("ZERO_AGENT_ID is not set \u2014 cannot check agent authorization.");
2300
+ } else if (!hasPermission) {
2301
+ const url = `${ctx.platformOrigin}/connectors/${ctx.connectorType}/authorize?agentId=${ctx.agentId}`;
2302
+ console.log(
2303
+ `The ${ctx.label} connector is not authorized for this agent (${ctx.agentId}).`
2304
+ );
2305
+ console.log(`Authorize it at: [Authorize ${ctx.label}](${url})`);
2306
+ } else {
2307
+ console.log(`The ${ctx.label} connector is authorized for this agent.`);
2308
+ }
2309
+ console.log("");
2310
+ return { isConnected, isExpired, hasPermission };
2311
+ }
2312
+ async function checkConnectorDomains(ctx) {
2313
+ console.log(
2314
+ `### 2c: Registered base URLs (credential replacement only applies to URLs matching these prefixes)`
2315
+ );
2316
+ console.log("");
2317
+ const payload = decodeZeroTokenPayload();
2318
+ const runId = payload?.runId;
2319
+ if (!runId) {
2320
+ console.log(
2321
+ "Cannot determine run ID from ZERO_TOKEN \u2014 skipping base URL check."
2322
+ );
2323
+ console.log("");
2324
+ return null;
2325
+ }
2326
+ const runContext = await getZeroRunContext(runId);
2327
+ printConnectorDomains(ctx, runContext);
2328
+ console.log("");
2329
+ return runContext.networkPolicies;
2330
+ }
2331
+ function printConnectorDomains(ctx, runContext) {
2332
+ const matchingEntry = runContext.firewalls.find((fw) => {
2333
+ return fw.ref === ctx.connectorType;
2334
+ });
2335
+ if (!matchingEntry) {
2336
+ console.log(
2337
+ `No configuration found for the ${ctx.label} connector in this run.`
2338
+ );
2339
+ console.log(
2340
+ "This means no base URLs are registered for credential replacement for this connector."
2341
+ );
2342
+ return;
2343
+ }
2344
+ console.log(
2345
+ `The ${ctx.label} connector is configured for this run with the following base URLs:`
2346
+ );
2347
+ for (const api of matchingEntry.apis) {
2348
+ console.log(` - ${api.base}`);
2349
+ }
2350
+ console.log("");
2351
+ console.log(
2352
+ "When the sandbox sends an HTTP request matching one of these URL prefixes, the network boundary intercepts the request and injects real credentials into the auth headers."
2353
+ );
2354
+ if (isFirewallConnectorType(ctx.connectorType)) {
2355
+ const firewallConfig = getConnectorFirewall(ctx.connectorType);
2356
+ const secretNames = extractSecretNamesFromApis(firewallConfig.apis);
2357
+ if (secretNames.length > 0) {
2358
+ console.log(`Credentials resolved from: ${secretNames.join(", ")}`);
2359
+ }
2360
+ }
2361
+ }
2362
+ function checkPermissionPolicy(connectorType, label, permissionName, networkPolicies) {
2363
+ console.log("## Step 3: Permission policy check");
2364
+ console.log("");
2365
+ console.log(
2366
+ `Checking permission: "${permissionName}" for the ${label} connector.`
2367
+ );
2368
+ console.log(
2369
+ `Beyond credential replacement, the ${label} connector enforces permission policies on each API path. A request either matches a named permission or falls through to the unknown-endpoint policy.`
2370
+ );
2371
+ console.log("");
2372
+ if (!networkPolicies) {
2373
+ console.log(
2374
+ "Network policies are not available for this run \u2014 cannot check permission status."
2375
+ );
2376
+ console.log("");
2377
+ return;
2378
+ }
2379
+ const connectorPolicies = networkPolicies[connectorType];
2380
+ if (!connectorPolicies) {
2381
+ console.log(
2382
+ `No policy entry found for the ${label} connector in this run's network policies.`
2383
+ );
2384
+ console.log(
2385
+ "When a connector has no policy entry, all requests are fully permissive (allowed)."
2386
+ );
2387
+ console.log("");
2388
+ return;
2389
+ }
2390
+ console.log(`Permission policies for the ${label} connector:`);
2391
+ console.log(` allow list: [${connectorPolicies.allow.join(", ")}]`);
2392
+ console.log(` deny list: [${connectorPolicies.deny.join(", ")}]`);
2393
+ console.log(` unknown endpoint policy: ${connectorPolicies.unknownPolicy}`);
2394
+ console.log("");
2395
+ const isInAllow = connectorPolicies.allow.includes(permissionName);
2396
+ const isInDeny = connectorPolicies.deny.includes(permissionName);
2397
+ if (isInAllow) {
2398
+ console.log(
2399
+ `Result: "${permissionName}" is in the allow list. Requests matching this permission are allowed.`
2400
+ );
2401
+ } else if (isInDeny) {
2402
+ console.log(
2403
+ `Result: "${permissionName}" is in the deny list. Requests matching this permission are denied.`
2404
+ );
2405
+ } else {
2406
+ console.log(
2407
+ `Result: "${permissionName}" is not in any permission list. It will be handled by the unknown endpoint policy: ${connectorPolicies.unknownPolicy}.`
2408
+ );
2409
+ }
2410
+ console.log("");
2411
+ }
2412
+ function resolvePermissionFromUrl(connectorType, label, method, relativePath, matchedBase, networkPolicies) {
2413
+ console.log("## Step 3: Permission policy check (auto-detected from URL)");
2414
+ console.log("");
2415
+ console.log(
2416
+ `Matching ${method} ${relativePath} (relative to base URL ${matchedBase}) against the ${label} connector's permission rules.`
2417
+ );
2418
+ console.log("");
2419
+ if (!isFirewallConnectorType(connectorType)) {
2420
+ console.log(
2421
+ `The ${label} connector does not have permission rules defined.`
2422
+ );
2423
+ console.log("");
2424
+ return;
2425
+ }
2426
+ const config = getConnectorFirewall(connectorType);
2427
+ const matchedPermissions = findMatchingPermissions(
2428
+ method,
2429
+ relativePath,
2430
+ config
2431
+ );
2432
+ if (matchedPermissions.length === 0) {
2433
+ console.log(
2434
+ `No named permission matches ${method} ${relativePath}. This request falls through to the unknown-endpoint policy.`
2435
+ );
2436
+ } else {
2437
+ console.log(`Matched permissions: [${matchedPermissions.join(", ")}]`);
2438
+ }
2439
+ console.log("");
2440
+ if (!networkPolicies) {
2441
+ console.log(
2442
+ "Network policies are not available for this run \u2014 cannot check allow/deny status."
2443
+ );
2444
+ console.log("");
2445
+ return;
2446
+ }
2447
+ const connectorPolicies = networkPolicies[connectorType];
2448
+ if (!connectorPolicies) {
2449
+ console.log(
2450
+ `No policy entry found for the ${label} connector. All requests are fully permissive (allowed).`
2451
+ );
2452
+ console.log("");
2453
+ return;
2454
+ }
2455
+ console.log(`Permission policies for the ${label} connector:`);
2456
+ console.log(` allow list: [${connectorPolicies.allow.join(", ")}]`);
2457
+ console.log(` deny list: [${connectorPolicies.deny.join(", ")}]`);
2458
+ console.log(` unknown endpoint policy: ${connectorPolicies.unknownPolicy}`);
2459
+ console.log("");
2460
+ if (matchedPermissions.length === 0) {
2461
+ console.log(
2462
+ `Result: No permission matched. The unknown endpoint policy applies: ${connectorPolicies.unknownPolicy}.`
2463
+ );
2464
+ } else {
2465
+ for (const perm of matchedPermissions) {
2466
+ const isInAllow = connectorPolicies.allow.includes(perm);
2467
+ const isInDeny = connectorPolicies.deny.includes(perm);
2468
+ if (isInAllow) {
2469
+ console.log(`Result: "${perm}" is in the allow list \u2014 allowed.`);
2470
+ } else if (isInDeny) {
2471
+ console.log(`Result: "${perm}" is in the deny list \u2014 denied.`);
2472
+ } else {
2473
+ console.log(
2474
+ `Result: "${perm}" is not in any list \u2014 falls through to unknown endpoint policy: ${connectorPolicies.unknownPolicy}.`
2475
+ );
2476
+ }
2477
+ }
2478
+ }
2479
+ console.log("");
2480
+ }
2481
+ var checkConnectorCommand = new Command().name("check-connector").description(
2482
+ "Diagnose connector health: environment variable, connector configuration, and permission policies"
2483
+ ).addOption(
2484
+ new Option(
2485
+ "--env-name <ENV_NAME>",
2486
+ "The environment variable name to check (e.g. GITHUB_TOKEN)"
2487
+ )
2488
+ ).addOption(
2489
+ new Option(
2490
+ "--url <URL>",
2491
+ "A full URL to diagnose \u2014 auto-detects the connector, env var, and permission (e.g. https://api.github.com/repos/owner/repo)"
2492
+ )
2493
+ ).addOption(
2494
+ new Option(
2495
+ "--method <METHOD>",
2496
+ "HTTP method to use when matching permissions with --url (default: GET)"
2497
+ ).default("GET")
2498
+ ).addOption(
2499
+ new Option(
2500
+ "--check-permission <name>",
2501
+ "Check whether a specific permission is allowed or denied (e.g. contents:read)"
2502
+ )
2503
+ ).addHelpText(
2213
2504
  "after",
2214
2505
  `
2215
2506
  Examples:
2216
- zero doctor missing-token GITHUB_TOKEN
2217
- zero doctor missing-token LINEAR_API_KEY
2218
- zero doctor missing-token NOTION_TOKEN
2219
-
2220
- Notes:
2221
- - Outputs which connector provides the token and a URL for the user to connect it
2222
- - Use this to guide the user when a required token is not available in the sandbox`
2507
+ zero doctor check-connector --env-name GITHUB_TOKEN
2508
+ zero doctor check-connector --url https://api.github.com/repos/owner/repo
2509
+ zero doctor check-connector --url https://slack.com/api/chat.postMessage --method POST
2510
+ zero doctor check-connector --env-name SLACK_TOKEN --check-permission chat:write
2511
+
2512
+ How connectors work:
2513
+ A Connector holds the real credentials for an external service. These credentials
2514
+ are never injected into the sandbox. Instead, when the sandbox sends an HTTP
2515
+ request to a base URL registered by the Connector, the network boundary intercepts
2516
+ the request and replaces the auth headers with real credentials.
2517
+
2518
+ This command checks each part of that pipeline and reports what it finds.`
2223
2519
  ).action(
2224
- withErrorHandler(async (tokenName) => {
2225
- const connectorType = getConnectorTypeForSecretName(tokenName);
2226
- if (!connectorType) {
2520
+ withErrorHandler(async (opts) => {
2521
+ if (!opts.envName && !opts.url) {
2227
2522
  throw new Error(
2228
- `Unknown token: ${tokenName} \u2014 not managed by any connector`
2523
+ "Either --env-name or --url is required. Use --help for usage."
2229
2524
  );
2230
2525
  }
2526
+ let envName;
2527
+ let connectorType;
2528
+ let urlLookup = null;
2529
+ if (opts.url) {
2530
+ urlLookup = resolveConnectorFromUrl(opts.url);
2531
+ if (!urlLookup) {
2532
+ throw new Error(
2533
+ `No connector found for URL: ${opts.url} \u2014 no registered base URL matches this URL`
2534
+ );
2535
+ }
2536
+ connectorType = urlLookup.connectorType;
2537
+ envName = opts.envName ?? urlLookup.envName;
2538
+ console.log(
2539
+ `URL ${opts.url} matches the ${CONNECTOR_TYPES[connectorType].label} connector (type: ${connectorType}).`
2540
+ );
2541
+ console.log(` Matched base URL: ${urlLookup.matchedBase}`);
2542
+ console.log(` Relative path: ${urlLookup.relativePath}`);
2543
+ console.log(` Environment var: ${envName}`);
2544
+ } else {
2545
+ connectorType = getConnectorTypeForSecretName(
2546
+ envName = opts.envName
2547
+ );
2548
+ if (!connectorType) {
2549
+ throw new Error(
2550
+ `Unknown environment variable: ${envName} \u2014 not managed by any connector`
2551
+ );
2552
+ }
2553
+ console.log(
2554
+ `${envName} is managed by the ${CONNECTOR_TYPES[connectorType].label} connector (type: ${connectorType}).`
2555
+ );
2556
+ }
2557
+ console.log("");
2231
2558
  const { label } = CONNECTOR_TYPES[connectorType];
2232
2559
  const apiUrl = await getApiUrl();
2233
2560
  const platformUrl = toPlatformUrl(apiUrl);
2234
- const agentId = process.env.ZERO_AGENT_ID;
2235
- const tokenPresent = Boolean(process.env[tokenName]);
2236
- console.log(
2237
- `${tokenName} is provided by the ${label} connector. Sandbox env: ${tokenPresent ? "present" : "not present"}.`
2238
- );
2239
- const [connector, enabledTypes] = await Promise.all([
2240
- getZeroConnector(connectorType).catch(() => {
2241
- return null;
2242
- }),
2243
- agentId ? getZeroAgentUserConnectors(agentId).catch(() => {
2244
- return null;
2245
- }) : Promise.resolve(null)
2246
- ]);
2247
- const isConnected = connector !== null;
2248
- const hasPermission = enabledTypes !== null && enabledTypes.includes(connectorType);
2249
- const rediagnoseHint = `Important: if ${tokenName} is still missing after the user takes action, run \`zero doctor missing-token ${tokenName}\` again to re-diagnose instead of assuming the status.`;
2250
- if (!isConnected) {
2251
- const connectUrl = agentId ? `${platformUrl.origin}/connectors/${connectorType}/connect?agentId=${agentId}` : `${platformUrl.origin}/connectors/${connectorType}/connect`;
2561
+ const ctx = {
2562
+ envName,
2563
+ connectorType,
2564
+ label,
2565
+ platformOrigin: platformUrl.origin,
2566
+ agentId: process.env.ZERO_AGENT_ID || void 0
2567
+ };
2568
+ checkEnvVariable(ctx);
2569
+ const { isConnected, isExpired, hasPermission } = await checkConnectorStatus(ctx);
2570
+ const networkPolicies = await checkConnectorDomains(ctx);
2571
+ if (isConnected && !isExpired && hasPermission) {
2252
2572
  console.log(
2253
- `The ${label} connector is not connected. Ask the user to connect it at: [Connect ${label}](${connectUrl})
2254
- ${rediagnoseHint}`
2573
+ `Steps 1-2 summary: The ${label} connector is connected, active, and authorized. Outbound requests to the registered base URLs will have credentials injected at the network boundary.`
2255
2574
  );
2256
- return;
2257
2575
  }
2258
- const issues = [];
2259
- if (connector.needsReconnect) {
2260
- const url = `${platformUrl.origin}/connectors`;
2261
- issues.push(
2262
- `The ${label} connector has expired and needs to be reconnected. Ask the user to reconnect it at: [Reconnect ${label}](${url})`
2576
+ console.log("");
2577
+ if (urlLookup) {
2578
+ resolvePermissionFromUrl(
2579
+ connectorType,
2580
+ label,
2581
+ opts.method,
2582
+ urlLookup.relativePath,
2583
+ urlLookup.matchedBase,
2584
+ networkPolicies
2263
2585
  );
2264
- }
2265
- if (!hasPermission) {
2266
- const url = agentId ? `${platformUrl.origin}/connectors/${connectorType}/authorize?agentId=${agentId}` : `${platformUrl.origin}/connectors`;
2267
- issues.push(
2268
- `The ${label} connector is not authorized for this agent. Ask the user to enable it at: [Authorize ${label}](${url})`
2586
+ } else if (opts.checkPermission) {
2587
+ checkPermissionPolicy(
2588
+ connectorType,
2589
+ label,
2590
+ opts.checkPermission,
2591
+ networkPolicies
2269
2592
  );
2270
2593
  }
2271
- if (issues.length > 0) {
2272
- for (const issue of issues) {
2273
- console.log(issue);
2594
+ const args = [];
2595
+ if (opts.url) {
2596
+ args.push(`--url ${opts.url}`);
2597
+ if (opts.method !== "GET") {
2598
+ args.push(`--method ${opts.method}`);
2274
2599
  }
2275
- console.log(rediagnoseHint);
2276
2600
  } else {
2277
- const url = `${platformUrl.origin}/connectors`;
2278
- console.log(
2279
- `The ${label} connector is connected and authorized, but the token is still missing. Ask VM0 developer to resolve this issue. Connector status: [Check ${label} status](${url})
2280
- ${rediagnoseHint}`
2281
- );
2601
+ args.push(`--env-name ${envName}`);
2602
+ }
2603
+ if (opts.checkPermission) {
2604
+ args.push(`--check-permission ${opts.checkPermission}`);
2282
2605
  }
2606
+ console.log(
2607
+ `To re-diagnose after changes, run: zero doctor check-connector ${args.join(" ")}`
2608
+ );
2283
2609
  })
2284
2610
  );
2285
2611
 
@@ -2509,11 +2835,13 @@ Notes:
2509
2835
  );
2510
2836
 
2511
2837
  // src/commands/zero/doctor/index.ts
2512
- var zeroDoctorCommand = new Command().name("doctor").description("Diagnose runtime issues (missing tokens, permission denials)").addCommand(missingTokenCommand).addCommand(permissionDenyCommand).addCommand(permissionChangeCommand).addHelpText(
2838
+ var zeroDoctorCommand = new Command().name("doctor").description("Diagnose runtime issues (connector health, permission denials)").addCommand(checkConnectorCommand).addCommand(permissionDenyCommand).addCommand(permissionChangeCommand).addHelpText(
2513
2839
  "after",
2514
2840
  `
2515
2841
  Examples:
2516
- Missing an API key? zero doctor missing-token GITHUB_TOKEN
2842
+ Check a connector? zero doctor check-connector --env-name GITHUB_TOKEN
2843
+ Check a URL? zero doctor check-connector --url https://api.github.com/repos/owner/repo
2844
+ Check with permission? zero doctor check-connector --env-name SLACK_TOKEN --check-permission chat:write
2517
2845
  Permission denied? zero doctor permission-deny github --method GET --path /repos/owner/repo
2518
2846
  Change a permission? zero doctor permission-change github --permission contents:read --enable
2519
2847
 
@@ -6011,11 +6339,11 @@ function registerZeroCommands(prog, commands) {
6011
6339
  var program = new Command();
6012
6340
  program.name("zero").description(
6013
6341
  "Zero CLI \u2014 interact with the zero platform from inside the sandbox"
6014
- ).version("9.113.4").addHelpText(
6342
+ ).version("9.114.1").addHelpText(
6015
6343
  "after",
6016
6344
  `
6017
6345
  Examples:
6018
- Missing a token? zero doctor missing-token <TOKEN_NAME>
6346
+ Check a connector? zero doctor check-connector --env-name <ENV_NAME>
6019
6347
  Send a Slack message? zero slack message send --help
6020
6348
  Set up a schedule? zero schedule setup --help
6021
6349
  Update yourself? zero agent --help