@odla-ai/cli 0.25.11 → 0.25.17

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/dist/index.cjs CHANGED
@@ -304,7 +304,7 @@ var import_node_process2 = __toESM(require("process"), 1);
304
304
  async function openUrl(url, options = {}) {
305
305
  const command = openerFor(options.platform ?? import_node_process2.default.platform);
306
306
  const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
307
- await new Promise((resolve11, reject) => {
307
+ await new Promise((resolve12, reject) => {
308
308
  const child = doSpawn(command.cmd, [...command.args, url], {
309
309
  stdio: "ignore",
310
310
  detached: true
@@ -312,7 +312,7 @@ async function openUrl(url, options = {}) {
312
312
  child.once("error", reject);
313
313
  child.once("spawn", () => {
314
314
  child.unref();
315
- resolve11();
315
+ resolve12();
316
316
  });
317
317
  });
318
318
  }
@@ -2246,8 +2246,8 @@ function credential(value) {
2246
2246
  // src/calendar-poll.ts
2247
2247
  async function waitForCalendarPoll(milliseconds, signal) {
2248
2248
  if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
2249
- await new Promise((resolve11, reject) => {
2250
- const timer = setTimeout(resolve11, milliseconds);
2249
+ await new Promise((resolve12, reject) => {
2250
+ const timer = setTimeout(resolve12, milliseconds);
2251
2251
  signal?.addEventListener("abort", () => {
2252
2252
  clearTimeout(timer);
2253
2253
  reject(signal.reason ?? new Error("calendar connection aborted"));
@@ -2448,7 +2448,8 @@ var CAPABILITIES = {
2448
2448
  "compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
2449
2449
  "apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
2450
2450
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2451
- "read one versioned o11y status envelope spanning application RED, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, and a bounded machine verdict",
2451
+ "save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
2452
+ "read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
2452
2453
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2453
2454
  "run app-attributed hosted security discovery and independent validation without provider keys",
2454
2455
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2470,7 +2471,7 @@ var CAPABILITIES = {
2470
2471
  "approve GitHub App repository access separately from redacted-snippet disclosure"
2471
2472
  ],
2472
2473
  studio: [
2473
- "view application telemetry, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
2474
+ "view application telemetry grouped by exact Worker version, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
2474
2475
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
2475
2476
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
2476
2477
  "perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
@@ -5476,21 +5477,21 @@ async function verifyCodeCandidate(input) {
5476
5477
  const recipes = [];
5477
5478
  const logs = [];
5478
5479
  for (const recipe2 of policy.recipes) {
5479
- const clean = await stageWorkspace(staged.workspaceDir, limits);
5480
+ const clean4 = await stageWorkspace(staged.workspaceDir, limits);
5480
5481
  try {
5481
- if (await digestStagedWorkspace(clean.workspaceDir, limits) !== sourceDigest) {
5482
+ if (await digestStagedWorkspace(clean4.workspaceDir, limits) !== sourceDigest) {
5482
5483
  throw new TypeError("clean verifier source changed before execution");
5483
5484
  }
5484
5485
  const result = checkedResult(await input.recipeExecutor.run({
5485
- workspaceDir: clean.workspaceDir,
5486
+ workspaceDir: clean4.workspaceDir,
5486
5487
  recipe: recipe2,
5487
5488
  signal: input.signal
5488
5489
  }), recipe2.maxOutputBytes);
5489
- const artifacts = await inspectArtifacts(clean.workspaceDir, recipe2);
5490
+ const artifacts = await inspectArtifacts(clean4.workspaceDir, recipe2);
5490
5491
  recipes.push(recipeReceipt(recipe2, result, artifacts));
5491
5492
  logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
5492
5493
  } finally {
5493
- await clean.cleanup();
5494
+ await clean4.cleanup();
5494
5495
  }
5495
5496
  }
5496
5497
  const fields = {
@@ -6631,8 +6632,12 @@ Usage:
6631
6632
  odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
6632
6633
  odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6633
6634
  odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6634
- odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
6635
- odla-ai whoami [--json]
6635
+ odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
6636
+ odla-ai context list [--json]
6637
+ odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
6638
+ odla-ai context remove <name> --yes [--json]
6639
+ odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
6640
+ odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
6636
6641
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
6637
6642
  odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
6638
6643
  odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
@@ -6697,6 +6702,11 @@ Commands:
6697
6702
  whoami Report who this terminal is authenticated as, and whether it holds
6698
6703
  platform admin. A handshake-minted device token is never admin,
6699
6704
  however the human who approved it is configured.
6705
+ context Explain selected config, platform, app, environment, and
6706
+ developer-token provenance without printing credentials or
6707
+ starting a device handshake. Operator work can run outside a
6708
+ project checkout with explicit flags or ODLA_* environment
6709
+ variables.
6700
6710
  setup Install offline odla runbooks for common coding-agent harnesses.
6701
6711
  init Create a generic odla.config.mjs plus starter schema/rules files.
6702
6712
  doctor Validate and summarize the project config without network calls.
@@ -6768,6 +6778,15 @@ Safety:
6768
6778
  Provision caches the approved developer token and service credentials under
6769
6779
  .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
6770
6780
  preflights Wrangler before any shown-once issuance or destructive rotation.
6781
+ Projectless PM, Discussions, o11y, runbook, and identity commands use
6782
+ --platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
6783
+ ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
6784
+ select it explicitly with --context or ODLA_CONTEXT. Each named context gets
6785
+ isolated developer and scoped-token caches. Override their locations with
6786
+ ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
6787
+ the metadata file. Flags and specific ODLA_* scope variables beat a selected
6788
+ context, which beats project config. There is no ambient current context.
6789
+ "context show" reports only provenance and cache state and never authenticates.
6771
6790
  Provision opens the approval page in your browser automatically whenever the
6772
6791
  machine can show one, including agent-driven runs; only CI, SSH, and
6773
6792
  display-less hosts skip it. Use --open to force or --no-open to suppress.
@@ -6885,8 +6904,8 @@ function hostedPollTimeout(value = 10 * 6e4) {
6885
6904
  }
6886
6905
  async function waitForHostedPoll(milliseconds, signal) {
6887
6906
  if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
6888
- await new Promise((resolve11, reject) => {
6889
- const timer = setTimeout(resolve11, milliseconds);
6907
+ await new Promise((resolve12, reject) => {
6908
+ const timer = setTimeout(resolve12, milliseconds);
6890
6909
  signal?.addEventListener("abort", () => {
6891
6910
  clearTimeout(timer);
6892
6911
  reject(signal.reason ?? new DOMException("aborted", "AbortError"));
@@ -7396,6 +7415,384 @@ async function codeCommand(parsed, dependencies) {
7396
7415
  });
7397
7416
  }
7398
7417
 
7418
+ // src/operator-credentials.ts
7419
+ var import_node_process7 = __toESM(require("process"), 1);
7420
+ function developerTokenStatus(context, parsed, now = Date.now()) {
7421
+ const cached = readJsonFile(context.cfg.local.tokenFile);
7422
+ const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
7423
+ const source = clean(
7424
+ stringOpt(parsed.options.token)
7425
+ ) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
7426
+ return {
7427
+ source,
7428
+ cacheFile: context.cfg.local.tokenFile,
7429
+ cacheStatus
7430
+ };
7431
+ }
7432
+ function clean(value) {
7433
+ const normalized = value?.trim();
7434
+ return normalized || void 0;
7435
+ }
7436
+
7437
+ // src/operator-context.ts
7438
+ var import_node_fs14 = require("fs");
7439
+ var import_node_path12 = require("path");
7440
+ var import_node_process9 = __toESM(require("process"), 1);
7441
+
7442
+ // src/operator-profiles.ts
7443
+ var import_node_fs13 = require("fs");
7444
+ var import_node_os4 = require("os");
7445
+ var import_node_path11 = require("path");
7446
+ var import_node_process8 = __toESM(require("process"), 1);
7447
+ function operatorProfileFile() {
7448
+ return (0, import_node_path11.resolve)(
7449
+ clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
7450
+ );
7451
+ }
7452
+ function resolveOperatorProfile(parsed) {
7453
+ const fromFlag = clean2(stringOpt(parsed.options.context));
7454
+ const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
7455
+ const name = fromFlag ?? fromEnvironment ?? null;
7456
+ const file = operatorProfileFile();
7457
+ if (!name) {
7458
+ return { name: null, source: "unresolved", file, value: null };
7459
+ }
7460
+ assertOperatorName(name, "context");
7461
+ const profiles = readOperatorProfiles(file);
7462
+ const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
7463
+ if (!value) {
7464
+ throw new Error(
7465
+ `operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
7466
+ );
7467
+ }
7468
+ return {
7469
+ name,
7470
+ source: fromFlag ? "flag" : "environment",
7471
+ file,
7472
+ value
7473
+ };
7474
+ }
7475
+ function listOperatorProfiles(file = operatorProfileFile()) {
7476
+ return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
7477
+ }
7478
+ function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
7479
+ assertOperatorName(name, "context");
7480
+ const normalized = validateProfile(profile, `operator context "${name}"`);
7481
+ const profiles = readOperatorProfiles(file);
7482
+ profiles[name] = normalized;
7483
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7484
+ }
7485
+ function removeOperatorProfile(name, file = operatorProfileFile()) {
7486
+ assertOperatorName(name, "context");
7487
+ const profiles = readOperatorProfiles(file);
7488
+ if (!Object.hasOwn(profiles, name)) return false;
7489
+ delete profiles[name];
7490
+ writePrivateJson(file, { schemaVersion: 1, profiles });
7491
+ return true;
7492
+ }
7493
+ function operatorCredentialFiles(selection) {
7494
+ const base = selection.name ? (0, import_node_path11.join)((0, import_node_path11.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla");
7495
+ return {
7496
+ developer: (0, import_node_path11.join)(base, "dev-token.json"),
7497
+ scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
7498
+ };
7499
+ }
7500
+ function assertOperatorName(value, label) {
7501
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
7502
+ throw new Error(
7503
+ `${label} must contain lowercase letters, numbers, and hyphens`
7504
+ );
7505
+ }
7506
+ }
7507
+ function readOperatorProfiles(file) {
7508
+ if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
7509
+ let raw;
7510
+ try {
7511
+ raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
7512
+ } catch {
7513
+ throw new Error(`operator context file ${file} is not valid JSON`);
7514
+ }
7515
+ if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
7516
+ throw new Error(`operator context file ${file} has an invalid shape`);
7517
+ }
7518
+ const unknown = Object.keys(raw).filter(
7519
+ (key) => !["schemaVersion", "profiles"].includes(key)
7520
+ );
7521
+ if (unknown.length > 0) {
7522
+ throw new Error(
7523
+ `operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
7524
+ );
7525
+ }
7526
+ const profiles = emptyProfiles();
7527
+ for (const [name, value] of Object.entries(
7528
+ raw.profiles
7529
+ )) {
7530
+ assertOperatorName(name, "context");
7531
+ profiles[name] = validateProfile(value, `operator context "${name}"`);
7532
+ }
7533
+ return profiles;
7534
+ }
7535
+ function emptyProfiles() {
7536
+ return /* @__PURE__ */ Object.create(null);
7537
+ }
7538
+ function validateProfile(value, label) {
7539
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7540
+ throw new Error(`${label} has an invalid shape`);
7541
+ }
7542
+ const unknown = Object.keys(value).filter(
7543
+ (key) => !["platform", "app", "environment"].includes(key)
7544
+ );
7545
+ if (unknown.length > 0) {
7546
+ throw new Error(
7547
+ `${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
7548
+ );
7549
+ }
7550
+ const candidate = value;
7551
+ if (typeof candidate.platform !== "string") {
7552
+ throw new Error(`${label} needs an absolute platform URL`);
7553
+ }
7554
+ const platform = platformAudience(candidate.platform);
7555
+ const app = clean2(candidate.app);
7556
+ const environment2 = clean2(candidate.environment);
7557
+ if (app) assertOperatorName(app, "app");
7558
+ if (environment2) assertOperatorName(environment2, "environment");
7559
+ return {
7560
+ platform,
7561
+ ...app ? { app } : {},
7562
+ ...environment2 ? { environment: environment2 } : {}
7563
+ };
7564
+ }
7565
+ function clean2(value) {
7566
+ const normalized = value?.trim();
7567
+ return normalized || void 0;
7568
+ }
7569
+
7570
+ // src/operator-context.ts
7571
+ var DEFAULT_PLATFORM2 = "https://odla.ai";
7572
+ async function resolveOperatorContext(parsed, options = {}) {
7573
+ const profile = resolveOperatorProfile(parsed);
7574
+ const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
7575
+ const configPath = (0, import_node_path12.resolve)(configArgument);
7576
+ const explicitConfig = parsed.options.config !== void 0;
7577
+ const hasConfig = (0, import_node_fs14.existsSync)(configPath);
7578
+ if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
7579
+ await loadProjectConfig(configArgument);
7580
+ }
7581
+ const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
7582
+ const platformFlag = clean3(stringOpt(parsed.options.platform));
7583
+ const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
7584
+ const platformValue = platformAudience(
7585
+ platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
7586
+ );
7587
+ const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
7588
+ const appFlag = clean3(stringOpt(parsed.options.app));
7589
+ const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
7590
+ const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
7591
+ const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
7592
+ if (appValue) assertOperatorName(appValue, "app");
7593
+ if (options.requireApp && !appValue) {
7594
+ throw new Error(
7595
+ "app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
7596
+ );
7597
+ }
7598
+ const envFlag = clean3(stringOpt(parsed.options.env));
7599
+ const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
7600
+ const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
7601
+ const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
7602
+ if (environmentValue) {
7603
+ assertOperatorName(environmentValue, "environment");
7604
+ }
7605
+ const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
7606
+ const profileCredentials = operatorCredentialFiles(profile);
7607
+ const tokenFile = clean3(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
7608
+ const scopedTokenFile = clean3(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path12.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
7609
+ const cfg = loaded ? {
7610
+ ...loaded,
7611
+ platformUrl: platformValue,
7612
+ app: appValue ? {
7613
+ id: appValue,
7614
+ name: appValue === loaded.app.id ? loaded.app.name : appValue
7615
+ } : loaded.app,
7616
+ local: { ...loaded.local, tokenFile }
7617
+ } : {
7618
+ configPath,
7619
+ rootDir,
7620
+ platformUrl: platformValue,
7621
+ dbEndpoint: platformValue,
7622
+ app: {
7623
+ id: appValue ?? "operator",
7624
+ name: appValue ?? "Operator"
7625
+ },
7626
+ envs: environmentValue ? [environmentValue] : [],
7627
+ services: [],
7628
+ local: {
7629
+ tokenFile,
7630
+ credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
7631
+ devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
7632
+ gitignore: true
7633
+ }
7634
+ };
7635
+ return {
7636
+ cfg,
7637
+ profile: {
7638
+ name: profile.name,
7639
+ source: profile.source,
7640
+ file: profile.file
7641
+ },
7642
+ config: {
7643
+ path: configPath,
7644
+ status: loaded ? "loaded" : "absent",
7645
+ explicit: explicitConfig
7646
+ },
7647
+ platform: { value: platformValue, source: platformSource },
7648
+ app: { value: appValue, source: appSource },
7649
+ environment: {
7650
+ value: environmentValue,
7651
+ source: environmentSource
7652
+ },
7653
+ credentials: {
7654
+ developerTokenFile: tokenFile,
7655
+ scopedTokenFile
7656
+ }
7657
+ };
7658
+ }
7659
+ function clean3(value) {
7660
+ const normalized = value?.trim();
7661
+ return normalized || void 0;
7662
+ }
7663
+
7664
+ // src/context-command.ts
7665
+ async function contextCommand(parsed, deps = {}) {
7666
+ assertArgs(
7667
+ parsed,
7668
+ [
7669
+ "config",
7670
+ "context",
7671
+ "platform",
7672
+ "app",
7673
+ "env",
7674
+ "token",
7675
+ "json",
7676
+ "yes"
7677
+ ],
7678
+ 3
7679
+ );
7680
+ const action = parsed.positionals[1];
7681
+ const out = deps.stdout ?? console;
7682
+ if (action === "list") {
7683
+ const profiles = listOperatorProfiles();
7684
+ if (parsed.options.json === true) {
7685
+ out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
7686
+ return;
7687
+ }
7688
+ if (profiles.length === 0) {
7689
+ out.log("No named operator contexts.");
7690
+ return;
7691
+ }
7692
+ out.log("name platform app environment");
7693
+ for (const profile of profiles) {
7694
+ out.log(
7695
+ `${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
7696
+ );
7697
+ }
7698
+ return;
7699
+ }
7700
+ if (action === "save") {
7701
+ if (parsed.options.token !== void 0) {
7702
+ throw new Error(
7703
+ "context save does not accept --token; contexts store scope metadata only"
7704
+ );
7705
+ }
7706
+ const name = requireName(parsed);
7707
+ const context2 = await resolveOperatorContext(parsed, {
7708
+ allowMissingConfig: true
7709
+ });
7710
+ const profile = {
7711
+ platform: context2.platform.value,
7712
+ ...context2.app.value ? { app: context2.app.value } : {},
7713
+ ...context2.environment.value ? { environment: context2.environment.value } : {}
7714
+ };
7715
+ saveOperatorProfile(name, profile, context2.profile.file);
7716
+ if (parsed.options.json === true) {
7717
+ out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
7718
+ } else {
7719
+ out.log(`saved operator context "${name}" (${profile.platform})`);
7720
+ }
7721
+ return;
7722
+ }
7723
+ if (action === "remove") {
7724
+ const name = requireName(parsed);
7725
+ if (parsed.options.yes !== true) {
7726
+ throw new Error(`context remove "${name}" requires --yes`);
7727
+ }
7728
+ const removed = removeOperatorProfile(name);
7729
+ const result2 = {
7730
+ schemaVersion: 1,
7731
+ name,
7732
+ removed,
7733
+ credentialCachesRemoved: false,
7734
+ grantsRevoked: false
7735
+ };
7736
+ if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
7737
+ else {
7738
+ const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
7739
+ out.log(`${status}; credential caches were not deleted or revoked`);
7740
+ }
7741
+ return;
7742
+ }
7743
+ if (action !== "show") {
7744
+ throw new Error(
7745
+ `unknown context action "${action ?? ""}". Try show|list|save|remove.`
7746
+ );
7747
+ }
7748
+ const context = await resolveOperatorContext(parsed, {
7749
+ allowMissingConfig: true,
7750
+ defaultEnvironment: "prod"
7751
+ });
7752
+ const token = developerTokenStatus(context, parsed);
7753
+ const result = {
7754
+ schemaVersion: 1,
7755
+ profile: context.profile,
7756
+ config: context.config,
7757
+ platform: context.platform,
7758
+ app: context.app,
7759
+ environment: context.environment,
7760
+ authentication: {
7761
+ developerToken: token,
7762
+ scopedTokenCacheFile: context.credentials.scopedTokenFile,
7763
+ handshakeStarted: false
7764
+ }
7765
+ };
7766
+ if (parsed.options.json === true) {
7767
+ out.log(JSON.stringify(result, null, 2));
7768
+ return;
7769
+ }
7770
+ out.log(`config: ${context.config.status} (${context.config.path})`);
7771
+ out.log(
7772
+ `context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
7773
+ );
7774
+ out.log(
7775
+ `platform: ${context.platform.value} (${context.platform.source})`
7776
+ );
7777
+ out.log(
7778
+ `app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
7779
+ );
7780
+ out.log(
7781
+ `env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
7782
+ );
7783
+ out.log(
7784
+ `auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
7785
+ );
7786
+ out.log("handshake: not started");
7787
+ }
7788
+ function requireName(parsed) {
7789
+ const name = parsed.positionals[2]?.trim();
7790
+ if (!name) {
7791
+ throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
7792
+ }
7793
+ return name;
7794
+ }
7795
+
7399
7796
  // src/discuss-actions.ts
7400
7797
  var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
7401
7798
  async function request(ctx, method, path, body) {
@@ -7447,8 +7844,9 @@ async function discussGroups(ctx) {
7447
7844
  }
7448
7845
  async function discussList(ctx, parsed) {
7449
7846
  const query = new URLSearchParams();
7847
+ const app = stringOpt(parsed.options.app) ?? ctx.appId;
7848
+ if (app) query.set("app", app);
7450
7849
  for (const [flag, param] of [
7451
- ["app", "app"],
7452
7850
  ["q", "q"],
7453
7851
  ["state", "state"],
7454
7852
  ["limit", "limit"],
@@ -7524,7 +7922,7 @@ function renderRead(ctx, topic, posts) {
7524
7922
  }
7525
7923
  }
7526
7924
  async function discussPost(ctx, parsed) {
7527
- const appId = stringOpt(parsed.options.app);
7925
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
7528
7926
  if (!appId) throw new Error("discuss post needs --app <appId>");
7529
7927
  const subject = stringOpt(parsed.options.subject);
7530
7928
  if (!subject) throw new Error('discuss post needs --subject "\u2026"');
@@ -7556,7 +7954,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
7556
7954
  }
7557
7955
  async function discussWho(ctx, parsed) {
7558
7956
  const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
7559
- const app = stringOpt(parsed.options.app);
7957
+ const app = stringOpt(parsed.options.app) ?? ctx.appId;
7560
7958
  if (app) query.set("app", app);
7561
7959
  const kinds = stringOpt(parsed.options.kinds);
7562
7960
  if (kinds) query.set("kinds", kinds);
@@ -7660,14 +8058,14 @@ function jsonl(ctx, parsed, value) {
7660
8058
  }
7661
8059
  async function discussWatch(ctx, topicId, parsed) {
7662
8060
  if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
7663
- const sleep = ctx.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
8061
+ const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
7664
8062
  const now = ctx.now ?? Date.now;
7665
8063
  const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
7666
8064
  const timeoutSeconds = numberOpt2(parsed, "timeout");
7667
8065
  const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
7668
8066
  const by = stringOpt(parsed.options.by);
7669
8067
  const self = stringOpt(parsed.options.self);
7670
- const app = stringOpt(parsed.options.app);
8068
+ const app = stringOpt(parsed.options.app) ?? ctx.appId;
7671
8069
  let cursor = stringOpt(parsed.options.cursor);
7672
8070
  let firstSuccess = true;
7673
8071
  let consecutiveFailures = 0;
@@ -7812,14 +8210,19 @@ var ALLOWED = [
7812
8210
  "timeout",
7813
8211
  "cursor",
7814
8212
  "jsonl",
7815
- "mutation-id"
8213
+ "mutation-id",
8214
+ "platform",
8215
+ "context"
7816
8216
  ];
7817
8217
  function requireId(id, action) {
7818
8218
  if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
7819
8219
  return id;
7820
8220
  }
7821
8221
  async function buildContext(parsed, deps) {
7822
- const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
8222
+ const context = await resolveOperatorContext(parsed, {
8223
+ allowMissingConfig: true
8224
+ });
8225
+ const { cfg } = context;
7823
8226
  const doFetch = deps.fetch ?? fetch;
7824
8227
  const out = deps.stdout ?? console;
7825
8228
  const token = await getDeveloperToken(
@@ -7833,7 +8236,16 @@ async function buildContext(parsed, deps) {
7833
8236
  doFetch,
7834
8237
  out
7835
8238
  );
7836
- return { platformUrl: cfg.platformUrl, token, doFetch, out, json: parsed.options.json === true };
8239
+ return {
8240
+ platformUrl: cfg.platformUrl,
8241
+ token,
8242
+ doFetch,
8243
+ out,
8244
+ json: parsed.options.json === true,
8245
+ // Preserve Discussions' existing cross-project default for config-backed
8246
+ // use while allowing remote agents to scope via ODLA_APP_ID or a profile.
8247
+ appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
8248
+ };
7837
8249
  }
7838
8250
  async function discussCommand(parsed, deps = {}) {
7839
8251
  assertArgs(parsed, ALLOWED, 3);
@@ -7945,7 +8357,7 @@ async function pmList(ctx, entity, parsed) {
7945
8357
  assignee: "assigneeId",
7946
8358
  decision: "decisionId"
7947
8359
  };
7948
- const app = stringOpt(parsed.options.app);
8360
+ const app = stringOpt(parsed.options.app) ?? ctx.appId;
7949
8361
  if (app) q.set("app", app);
7950
8362
  for (const [flag, param] of Object.entries(filters)) {
7951
8363
  const v = stringOpt(parsed.options[flag]);
@@ -7964,7 +8376,7 @@ async function pmList(ctx, entity, parsed) {
7964
8376
  });
7965
8377
  }
7966
8378
  async function pmAdd(ctx, entity, parsed) {
7967
- const appId = stringOpt(parsed.options.app);
8379
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
7968
8380
  if (!appId) throw new Error("pm add needs --app <appId>");
7969
8381
  const input = collectEntityFields(entity, parsed, false);
7970
8382
  if (!input.title) throw new Error("pm add needs --title <title>");
@@ -8015,7 +8427,7 @@ async function allRecords(ctx, entity, appId) {
8015
8427
  }
8016
8428
  }
8017
8429
  async function pmHandoff(ctx, parsed) {
8018
- const appId = stringOpt(parsed.options.app);
8430
+ const appId = stringOpt(parsed.options.app) ?? ctx.appId;
8019
8431
  if (!appId) throw new Error("pm handoff needs --app <appId>");
8020
8432
  const [goals, tasks, bugs] = await Promise.all([
8021
8433
  allRecords(ctx, "goal", appId),
@@ -8110,14 +8522,19 @@ var ALLOWED2 = [
8110
8522
  "limit",
8111
8523
  "offset",
8112
8524
  "q",
8113
- "mutation-id"
8525
+ "mutation-id",
8526
+ "platform",
8527
+ "context"
8114
8528
  ];
8115
8529
  function requireId2(id, action) {
8116
8530
  if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
8117
8531
  return id;
8118
8532
  }
8119
8533
  async function buildContext2(parsed, deps) {
8120
- const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
8534
+ const context = await resolveOperatorContext(parsed, {
8535
+ allowMissingConfig: true
8536
+ });
8537
+ const { cfg } = context;
8121
8538
  const doFetch = deps.fetch ?? fetch;
8122
8539
  const out = deps.stdout ?? console;
8123
8540
  const token = await getDeveloperToken(
@@ -8126,7 +8543,16 @@ async function buildContext2(parsed, deps) {
8126
8543
  doFetch,
8127
8544
  out
8128
8545
  );
8129
- return { platformUrl: cfg.platformUrl, token, doFetch, out, json: parsed.options.json === true };
8546
+ return {
8547
+ platformUrl: cfg.platformUrl,
8548
+ token,
8549
+ doFetch,
8550
+ out,
8551
+ json: parsed.options.json === true,
8552
+ // Preserve PM's existing cross-project default when a config is present;
8553
+ // only an explicit remote-agent environment or profile selects an app.
8554
+ appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
8555
+ };
8130
8556
  }
8131
8557
  async function pmCommand(parsed, deps = {}) {
8132
8558
  const word = parsed.positionals[1] ?? "";
@@ -8175,6 +8601,20 @@ function statusVerdict(reads) {
8175
8601
  sourceHttp(reasons, "canary", reads.canary);
8176
8602
  sourceHttp(reasons, "collector", reads.collector);
8177
8603
  sourceHttp(reasons, "provider", reads.provider);
8604
+ if (reads.providerCapacity) {
8605
+ sourceHttp(
8606
+ reasons,
8607
+ "provider",
8608
+ reads.providerCapacity,
8609
+ "provider_capacity_"
8610
+ );
8611
+ }
8612
+ sourceHttp(
8613
+ reasons,
8614
+ "provider",
8615
+ reads.providerReconciliation,
8616
+ "provider_reconciliation_"
8617
+ );
8178
8618
  sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
8179
8619
  const liveStatus = String(reads.liveSync.body.status ?? "");
8180
8620
  if (liveStatus === "partial") {
@@ -8247,6 +8687,17 @@ function statusVerdict(reads) {
8247
8687
  severity: "degraded"
8248
8688
  });
8249
8689
  }
8690
+ const reconciliationStatus = String(
8691
+ reads.providerReconciliation.body.status ?? ""
8692
+ );
8693
+ if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
8694
+ const reconciliationReason = reads.providerReconciliation.body.reason;
8695
+ reasons.push({
8696
+ source: "provider",
8697
+ code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
8698
+ severity: "degraded"
8699
+ });
8700
+ }
8250
8701
  const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
8251
8702
  if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
8252
8703
  reasons.push({
@@ -8283,11 +8734,138 @@ function sourceHttp(reasons, source, read3, prefix = "") {
8283
8734
  });
8284
8735
  }
8285
8736
 
8737
+ // src/o11y-output.ts
8738
+ function printO11yStatus(status, out) {
8739
+ out.log(
8740
+ `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8741
+ );
8742
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
8743
+ const requests = routes.reduce(
8744
+ (total, row) => total + numeric3(row.requests),
8745
+ 0
8746
+ );
8747
+ const errors = routes.reduce(
8748
+ (total, row) => total + numeric3(row.errors),
8749
+ 0
8750
+ );
8751
+ out.log(
8752
+ `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8753
+ );
8754
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record6) : [];
8755
+ out.log(
8756
+ `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
8757
+ (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
8758
+ ).join(", ") : "none observed"}`
8759
+ );
8760
+ out.log(liveSyncLine(status.liveSync));
8761
+ const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8762
+ out.log(
8763
+ `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8764
+ );
8765
+ const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8766
+ const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
8767
+ out.log(
8768
+ `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8769
+ );
8770
+ const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8771
+ const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
8772
+ const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
8773
+ out.log(
8774
+ `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
8775
+ );
8776
+ for (const line of providerCapacityLines(status.providerCapacity)) {
8777
+ out.log(line);
8778
+ }
8779
+ const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
8780
+ const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
8781
+ const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
8782
+ out.log(
8783
+ `request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
8784
+ );
8785
+ const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
8786
+ const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
8787
+ out.log(
8788
+ `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
8789
+ );
8790
+ out.log(
8791
+ `verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
8792
+ );
8793
+ }
8794
+ function providerCapacityLines(read3) {
8795
+ const resources = record6(read3.body.resources) ? read3.body.resources : {};
8796
+ const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
8797
+ const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
8798
+ const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
8799
+ const d1 = record6(resources.d1) ? resources.d1 : {};
8800
+ const d1Activity = record6(d1.activity) ? d1.activity : {};
8801
+ const d1Storage = record6(d1.storage) ? d1.storage : {};
8802
+ const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
8803
+ const r2 = record6(resources.r2) ? resources.r2 : {};
8804
+ const r2Operations = record6(r2.operations) ? r2.operations : {};
8805
+ const r2Storage = record6(r2.storage) ? r2.storage : {};
8806
+ const status = String(
8807
+ read3.body.status ?? read3.body.error ?? "unavailable"
8808
+ );
8809
+ return [
8810
+ `cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
8811
+ `cloudflare-d1 ${read3.httpStatus} ${status} ${optionalBytes(d1Storage.databaseSizeBytes)} storage ${numeric3(d1Activity.readQueries) + numeric3(d1Activity.writeQueries)} queries ${numeric3(d1Activity.rowsRead)}/${numeric3(d1Activity.rowsWritten)} rows read/written ${optionalNumeric(d1Latency.worstDatabaseHourP99Ms)} worst database-hour p99 ${optionalAgeSeconds(d1Activity.ageSeconds)} activity evidence age`,
8812
+ `cloudflare-r2 ${read3.httpStatus} ${status} ${optionalBytes(r2Storage.storedBytes)} storage ${numeric3(r2Storage.objectCount)} objects ${numeric3(r2Operations.requests)} operations ${numeric3(r2Operations.userErrorRequests) + numeric3(r2Operations.internalErrorRequests)} provider errors ${optionalAgeSeconds(r2Operations.ageSeconds)} operation evidence age`
8813
+ ];
8814
+ }
8815
+ function liveSyncLine(read3) {
8816
+ const performance = record6(read3.body.performance) ? read3.body.performance : {};
8817
+ const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
8818
+ return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
8819
+ }
8820
+ function record6(value) {
8821
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8822
+ }
8823
+ function numeric3(value) {
8824
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
8825
+ }
8826
+ function optionalNumeric(value) {
8827
+ return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
8828
+ }
8829
+ function optionalAge(value) {
8830
+ if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
8831
+ return `${Math.max(0, Math.round(value / 1e3))}s`;
8832
+ }
8833
+ function optionalPercent(value) {
8834
+ return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
8835
+ }
8836
+ function optionalBytes(value) {
8837
+ if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
8838
+ if (value >= 1024 * 1024 * 1024) {
8839
+ return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
8840
+ }
8841
+ if (value >= 1024 * 1024) {
8842
+ return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
8843
+ }
8844
+ if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
8845
+ return `${Math.round(value)} B`;
8846
+ }
8847
+ function optionalCount(value, unit) {
8848
+ return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
8849
+ }
8850
+ function optionalAgeSeconds(value) {
8851
+ return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
8852
+ }
8853
+
8286
8854
  // src/o11y-command.ts
8287
8855
  async function o11yCommand(parsed, deps = {}) {
8288
8856
  assertArgs(
8289
8857
  parsed,
8290
- ["config", "token", "email", "json", "app", "env", "minutes"],
8858
+ [
8859
+ "config",
8860
+ "context",
8861
+ "platform",
8862
+ "token",
8863
+ "email",
8864
+ "json",
8865
+ "app",
8866
+ "env",
8867
+ "minutes"
8868
+ ],
8291
8869
  2
8292
8870
  );
8293
8871
  const action = parsed.positionals[1];
@@ -8299,9 +8877,12 @@ async function o11yCommand(parsed, deps = {}) {
8299
8877
  const minutes = statusMinutes(
8300
8878
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
8301
8879
  );
8302
- const cfg = await loadProjectConfig(
8303
- stringOpt(parsed.options.config) ?? "odla.config.mjs"
8304
- );
8880
+ const context = await resolveOperatorContext(parsed, {
8881
+ allowMissingConfig: true,
8882
+ defaultEnvironment: "prod",
8883
+ requireApp: true
8884
+ });
8885
+ const cfg = context.cfg;
8305
8886
  const doFetch = deps.fetch ?? fetch;
8306
8887
  const out = deps.stdout ?? console;
8307
8888
  const token = await getDeveloperToken(
@@ -8315,13 +8896,30 @@ async function o11yCommand(parsed, deps = {}) {
8315
8896
  doFetch,
8316
8897
  out
8317
8898
  );
8318
- const appId = stringOpt(parsed.options.app)?.trim() || cfg.app.id;
8319
- const env = stringOpt(parsed.options.env)?.trim() || "prod";
8899
+ const appId = context.app.value;
8900
+ const env = context.environment.value;
8320
8901
  const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
8321
8902
  const query = new URLSearchParams({ env, minutes: String(minutes) });
8322
8903
  const headers = { authorization: `Bearer ${token}` };
8323
- const [application, liveSync, canary, collector, provider, providerHistory] = await Promise.all([
8904
+ const versionsQuery = new URLSearchParams({
8905
+ env,
8906
+ minutes: String(minutes),
8907
+ dimension: "version",
8908
+ metric: "requests"
8909
+ });
8910
+ const [
8911
+ application,
8912
+ applicationVersions,
8913
+ liveSync,
8914
+ canary,
8915
+ collector,
8916
+ provider,
8917
+ providerCapacity,
8918
+ providerReconciliation,
8919
+ providerHistory
8920
+ ] = await Promise.all([
8324
8921
  read2(`${base}/metrics/red?${query}`, headers, doFetch),
8922
+ read2(`${base}/explore?${versionsQuery}`, headers, doFetch),
8325
8923
  read2(
8326
8924
  `${base}/live-sync/health?${query}`,
8327
8925
  headers,
@@ -8334,6 +8932,19 @@ async function o11yCommand(parsed, deps = {}) {
8334
8932
  ),
8335
8933
  read2(`${base}/self-health?${query}`, headers, doFetch),
8336
8934
  read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
8935
+ appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
8936
+ httpStatus: 204,
8937
+ body: {
8938
+ status: "not_applicable",
8939
+ scope: "cloudflare_account",
8940
+ reason: "account-scoped Durable Object capacity is exposed only through odla-db"
8941
+ }
8942
+ }),
8943
+ read2(
8944
+ `${base}/provider/cloudflare/reconciliation?${query}`,
8945
+ headers,
8946
+ doFetch
8947
+ ),
8337
8948
  read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
8338
8949
  ]);
8339
8950
  const verdict = statusVerdict({
@@ -8342,25 +8953,30 @@ async function o11yCommand(parsed, deps = {}) {
8342
8953
  canary,
8343
8954
  collector,
8344
8955
  provider,
8956
+ providerCapacity,
8957
+ providerReconciliation,
8345
8958
  providerHistory
8346
8959
  });
8347
8960
  const status = {
8348
- schemaVersion: 5,
8961
+ schemaVersion: 9,
8349
8962
  scope: { appId, env, minutes },
8350
8963
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
8351
8964
  verdict,
8352
8965
  application,
8966
+ applicationVersions,
8353
8967
  liveSync,
8354
8968
  canary,
8355
8969
  collector,
8356
8970
  provider,
8971
+ providerCapacity,
8972
+ providerReconciliation,
8357
8973
  providerHistory
8358
8974
  };
8359
8975
  if (parsed.options.json === true) {
8360
8976
  out.log(JSON.stringify(status, null, 2));
8361
8977
  return;
8362
8978
  }
8363
- printStatus2(status, out);
8979
+ printO11yStatus(status, out);
8364
8980
  }
8365
8981
  function statusMinutes(value) {
8366
8982
  if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
@@ -8382,70 +8998,11 @@ async function read2(url, headers, doFetch) {
8382
8998
  }
8383
8999
  return { httpStatus: response2.status, body };
8384
9000
  }
8385
- function printStatus2(status, out) {
8386
- out.log(
8387
- `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8388
- );
8389
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
8390
- const requests = routes.reduce(
8391
- (total, row) => total + numeric3(row.requests),
8392
- 0
8393
- );
8394
- const errors = routes.reduce(
8395
- (total, row) => total + numeric3(row.errors),
8396
- 0
8397
- );
8398
- out.log(
8399
- `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8400
- );
8401
- out.log(
8402
- liveSyncLine(status.liveSync)
8403
- );
8404
- const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8405
- out.log(
8406
- `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8407
- );
8408
- const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8409
- const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
8410
- out.log(
8411
- `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8412
- );
8413
- const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8414
- out.log(
8415
- `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
8416
- );
8417
- const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
8418
- const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
8419
- out.log(
8420
- `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
8421
- );
8422
- out.log(
8423
- `verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
8424
- );
8425
- }
8426
- function liveSyncLine(read3) {
8427
- const performance = record6(read3.body.performance) ? read3.body.performance : {};
8428
- const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
8429
- return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
8430
- }
8431
- function record6(value) {
8432
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8433
- }
8434
- function numeric3(value) {
8435
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
8436
- }
8437
- function optionalNumeric(value) {
8438
- return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
8439
- }
8440
- function optionalAge(value) {
8441
- if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
8442
- return `${Math.max(0, Math.round(value / 1e3))}s`;
8443
- }
8444
9001
 
8445
9002
  // src/provision.ts
8446
9003
  var import_apps5 = require("@odla-ai/apps");
8447
9004
  var import_ai3 = require("@odla-ai/ai");
8448
- var import_node_process7 = __toESM(require("process"), 1);
9005
+ var import_node_process10 = __toESM(require("process"), 1);
8449
9006
 
8450
9007
  // src/integration-provision.ts
8451
9008
  var import_db3 = require("@odla-ai/db");
@@ -8804,7 +9361,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
8804
9361
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
8805
9362
  }
8806
9363
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
8807
- const key = import_node_process7.default.env[cfg.ai.keyEnv];
9364
+ const key = import_node_process10.default.env[cfg.ai.keyEnv];
8808
9365
  if (key) {
8809
9366
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
8810
9367
  await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -8845,8 +9402,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
8845
9402
  }
8846
9403
 
8847
9404
  // src/record.ts
8848
- var import_node_fs13 = require("fs");
8849
- var import_node_process8 = __toESM(require("process"), 1);
9405
+ var import_node_fs15 = require("fs");
9406
+ var import_node_process11 = __toESM(require("process"), 1);
8850
9407
 
8851
9408
  // src/surface.ts
8852
9409
  var PM_ACTIONS = {
@@ -8894,6 +9451,7 @@ var COMMAND_SURFACE = {
8894
9451
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
8895
9452
  capabilities: {},
8896
9453
  code: { connect: {} },
9454
+ context: { show: {}, list: {}, save: {}, remove: {} },
8897
9455
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
8898
9456
  discuss: {
8899
9457
  groups: {},
@@ -8997,7 +9555,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
8997
9555
 
8998
9556
  // src/record.ts
8999
9557
  function recordInvocation(parsed) {
9000
- const file = import_node_process8.default.env.ODLA_CLI_RECORD;
9558
+ const file = import_node_process11.default.env.ODLA_CLI_RECORD;
9001
9559
  if (!file) return;
9002
9560
  try {
9003
9561
  const entry = {
@@ -9005,19 +9563,14 @@ function recordInvocation(parsed) {
9005
9563
  options: Object.keys(parsed.options).sort()
9006
9564
  };
9007
9565
  if (!entry.path.length) return;
9008
- (0, import_node_fs13.appendFileSync)(file, `${JSON.stringify(entry)}
9566
+ (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
9009
9567
  `);
9010
9568
  } catch {
9011
9569
  }
9012
9570
  }
9013
9571
 
9014
- // src/runbook-command.ts
9015
- var import_node_fs18 = require("fs");
9016
- var import_node_path14 = require("path");
9017
- var import_node_process10 = __toESM(require("process"), 1);
9018
-
9019
9572
  // src/runbook-actions.ts
9020
- var import_node_fs14 = require("fs");
9573
+ var import_node_fs16 = require("fs");
9021
9574
 
9022
9575
  // src/runbook-requires.ts
9023
9576
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -9102,7 +9655,7 @@ async function bySlug(ctx, slug) {
9102
9655
  function readBody(file, inline) {
9103
9656
  if (inline !== void 0) return inline;
9104
9657
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
9105
- return (0, import_node_fs14.readFileSync)(file === "-" ? 0 : file, "utf8");
9658
+ return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
9106
9659
  }
9107
9660
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
9108
9661
  async function runbookList(ctx, all, query) {
@@ -9189,8 +9742,8 @@ async function runbookRemove(ctx, slug) {
9189
9742
  }
9190
9743
 
9191
9744
  // src/runbook-import.ts
9192
- var import_node_fs15 = require("fs");
9193
- var import_node_path11 = require("path");
9745
+ var import_node_fs17 = require("fs");
9746
+ var import_node_path13 = require("path");
9194
9747
  function parseRunbook(text, slug) {
9195
9748
  let rest = text;
9196
9749
  const meta = {};
@@ -9215,12 +9768,12 @@ function parseRunbook(text, slug) {
9215
9768
  };
9216
9769
  }
9217
9770
  function readRunbookDir(dir) {
9218
- if (!(0, import_node_fs15.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
9219
- const files = (0, import_node_fs15.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
9771
+ if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
9772
+ const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
9220
9773
  if (!files.length) throw new Error(`no .md files in ${dir}`);
9221
9774
  return files.map((file) => {
9222
- const slug = (0, import_node_path11.basename)(file, ".md");
9223
- const parsed = parseRunbook((0, import_node_fs15.readFileSync)((0, import_node_path11.join)(dir, file), "utf8"), slug);
9775
+ const slug = (0, import_node_path13.basename)(file, ".md");
9776
+ const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
9224
9777
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
9225
9778
  });
9226
9779
  }
@@ -9292,8 +9845,8 @@ async function upsert(ctx, r, visibility) {
9292
9845
 
9293
9846
  // src/runbook-impact.ts
9294
9847
  var import_node_child_process7 = require("child_process");
9295
- var import_node_fs16 = require("fs");
9296
- var import_node_path12 = require("path");
9848
+ var import_node_fs18 = require("fs");
9849
+ var import_node_path14 = require("path");
9297
9850
 
9298
9851
  // src/runbook-impact-scan.ts
9299
9852
  var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
@@ -9462,10 +10015,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
9462
10015
  }
9463
10016
  function manifestLabeller(root) {
9464
10017
  return (workspace) => {
9465
- const manifest = (0, import_node_path12.join)(root, workspace, "package.json");
9466
- if (!(0, import_node_fs16.existsSync)(manifest)) return void 0;
10018
+ const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
10019
+ if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
9467
10020
  try {
9468
- const name = JSON.parse((0, import_node_fs16.readFileSync)(manifest, "utf8")).name;
10021
+ const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
9469
10022
  return typeof name === "string" ? name : void 0;
9470
10023
  } catch {
9471
10024
  return void 0;
@@ -9532,7 +10085,7 @@ function report3(ctx, impacts) {
9532
10085
  async function runbookImpact(ctx, options, deps = {}) {
9533
10086
  const cwd = deps.cwd ?? process.cwd();
9534
10087
  const runGit = deps.runGit ?? gitRunner(cwd);
9535
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs16.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
10088
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
9536
10089
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
9537
10090
  if (!surfaces.length) {
9538
10091
  return ctx.out.log(
@@ -9665,12 +10218,12 @@ async function runbookComment(ctx, slug, body) {
9665
10218
 
9666
10219
  // src/runbook-editor.ts
9667
10220
  var import_node_child_process8 = require("child_process");
9668
- var import_node_fs17 = require("fs");
9669
- var import_node_os4 = require("os");
9670
- var import_node_path13 = require("path");
9671
- var import_node_process9 = __toESM(require("process"), 1);
10221
+ var import_node_fs19 = require("fs");
10222
+ var import_node_os5 = require("os");
10223
+ var import_node_path15 = require("path");
10224
+ var import_node_process12 = __toESM(require("process"), 1);
9672
10225
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
9673
- function resolveEditor(env = import_node_process9.default.env) {
10226
+ function resolveEditor(env = import_node_process12.default.env) {
9674
10227
  for (const name of EDITOR_ENV) {
9675
10228
  const value = env[name];
9676
10229
  if (value && value.trim()) return value.trim();
@@ -9684,8 +10237,8 @@ function defaultRun(command, path) {
9684
10237
  return result.status ?? 0;
9685
10238
  }
9686
10239
  function editText(initial, slug, deps = {}) {
9687
- const env = deps.env ?? import_node_process9.default.env;
9688
- const interactive = deps.interactive ?? (() => Boolean(import_node_process9.default.stdin.isTTY));
10240
+ const env = deps.env ?? import_node_process12.default.env;
10241
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process12.default.stdin.isTTY));
9689
10242
  const editor = resolveEditor(env);
9690
10243
  if (!editor)
9691
10244
  throw new Error(
@@ -9693,16 +10246,16 @@ function editText(initial, slug, deps = {}) {
9693
10246
  );
9694
10247
  if (!interactive())
9695
10248
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
9696
- const dir = (0, import_node_fs17.mkdtempSync)((0, import_node_path13.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
9697
- const file = (0, import_node_path13.join)(dir, `${slug}.md`);
10249
+ const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10250
+ const file = (0, import_node_path15.join)(dir, `${slug}.md`);
9698
10251
  try {
9699
- (0, import_node_fs17.writeFileSync)(file, initial, { mode: 384 });
10252
+ (0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
9700
10253
  const code = defaultRunOrInjected(deps)(editor, file);
9701
10254
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
9702
- const edited = (0, import_node_fs17.readFileSync)(file, "utf8");
10255
+ const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
9703
10256
  return edited === initial ? null : edited;
9704
10257
  } finally {
9705
- (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
10258
+ (0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
9706
10259
  }
9707
10260
  }
9708
10261
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -9740,10 +10293,16 @@ function credentialKind(identity) {
9740
10293
  return identity.scopes.length ? "device token (scoped)" : "device token or session";
9741
10294
  }
9742
10295
  async function whoamiCommand(parsed, deps = {}) {
9743
- assertArgs(parsed, ["config", "token", "email", "json"], 1);
10296
+ assertArgs(
10297
+ parsed,
10298
+ ["config", "context", "platform", "token", "email", "json"],
10299
+ 1
10300
+ );
9744
10301
  const out = deps.stdout ?? console;
9745
10302
  const doFetch = deps.fetch ?? fetch;
9746
- const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
10303
+ const { cfg } = await resolveOperatorContext(parsed, {
10304
+ allowMissingConfig: true
10305
+ });
9747
10306
  const token = await getDeveloperToken(
9748
10307
  cfg,
9749
10308
  {
@@ -9792,34 +10351,24 @@ var ALLOWED3 = [
9792
10351
  "dry-run",
9793
10352
  "limit",
9794
10353
  "base",
9795
- "requires"
10354
+ "requires",
10355
+ "platform",
10356
+ "context"
9796
10357
  ];
9797
10358
  function requireSlug(slug, action) {
9798
10359
  if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
9799
10360
  return slug;
9800
10361
  }
9801
10362
  var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
9802
- var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
9803
- async function loadOrDefaultConfig(configPath, action, appId) {
9804
- const projectScoped = appId !== void 0;
9805
- if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs18.existsSync)((0, import_node_path14.resolve)(configPath))) {
9806
- const rootDir = import_node_process10.default.cwd();
9807
- return {
9808
- configPath,
9809
- platformUrl: import_node_process10.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
9810
- rootDir,
9811
- app: { id: "", name: "" },
9812
- local: { tokenFile: (0, import_node_path14.join)(rootDir, ".odla/dev-token.json") }
9813
- };
9814
- }
9815
- return loadProjectConfig(configPath);
9816
- }
9817
10363
  async function buildContext3(parsed, deps, action) {
9818
- const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
9819
- const cfg = await loadOrDefaultConfig(configPath, action, stringOpt(parsed.options.app));
10364
+ const appIdOption = stringOpt(parsed.options.app);
10365
+ const context = await resolveOperatorContext(parsed, {
10366
+ allowMissingConfig: true
10367
+ });
10368
+ const { cfg } = context;
9820
10369
  const doFetch = deps.fetch ?? fetch;
9821
10370
  const out = deps.stdout ?? console;
9822
- const appId = stringOpt(parsed.options.app) ?? PLATFORM_SCOPE;
10371
+ const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
9823
10372
  const dryRun = parsed.options["dry-run"] === true;
9824
10373
  if (action === "import" && dryRun) {
9825
10374
  return {
@@ -9840,8 +10389,10 @@ async function buildContext3(parsed, deps, action) {
9840
10389
  fetch: doFetch,
9841
10390
  stdout: out,
9842
10391
  openApprovalUrl: deps.openUrl,
9843
- // Anchor the grant cache to this project, not the shell's cwd.
9844
- rootDir: cfg.rootDir
10392
+ // A project, named context, or the global operator context owns the
10393
+ // exact-scope cache; it never follows an arbitrary shell directory.
10394
+ rootDir: cfg.rootDir,
10395
+ tokenFile: context.credentials.scopedTokenFile
9845
10396
  }) : await getDeveloperToken(
9846
10397
  cfg,
9847
10398
  {
@@ -10116,7 +10667,7 @@ function hostedSeverity(value, flag) {
10116
10667
  var import_security2 = require("@odla-ai/security");
10117
10668
 
10118
10669
  // src/security.ts
10119
- var import_node_path15 = require("path");
10670
+ var import_node_path16 = require("path");
10120
10671
  var import_security = require("@odla-ai/security");
10121
10672
  var import_node3 = require("@odla-ai/security/node");
10122
10673
  async function runHostedSecurity(options) {
@@ -10128,9 +10679,9 @@ async function runHostedSecurity(options) {
10128
10679
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
10129
10680
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
10130
10681
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
10131
- const target = (0, import_node_path15.resolve)(options.target ?? cfg?.rootDir ?? ".");
10132
- const output = (0, import_node_path15.resolve)(options.out ?? (0, import_node_path15.resolve)(target, ".odla/security/hosted"));
10133
- const outputRelative = (0, import_node_path15.relative)(target, output).split(import_node_path15.sep).join("/");
10682
+ const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
10683
+ const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
10684
+ const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
10134
10685
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
10135
10686
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
10136
10687
  const tokenRequest = {
@@ -10142,7 +10693,7 @@ async function runHostedSecurity(options) {
10142
10693
  };
10143
10694
  const token = await injectedToken(options, tokenRequest);
10144
10695
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
10145
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path15.isAbsolute)(outputRelative) ? [outputRelative] : []
10696
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
10146
10697
  });
10147
10698
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
10148
10699
  platform,
@@ -10160,7 +10711,7 @@ async function runHostedSecurity(options) {
10160
10711
  });
10161
10712
  const harness = (0, import_security.createSecurityHarness)({
10162
10713
  profile,
10163
- store: new import_node3.FileRunStore((0, import_node_path15.resolve)(output, "state")),
10714
+ store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
10164
10715
  discoveryReasoner: hosted.discoveryReasoner,
10165
10716
  validationReasoner: hosted.validationReasoner,
10166
10717
  policy: {
@@ -10184,7 +10735,7 @@ async function runHostedSecurity(options) {
10184
10735
  function selectEnv(requested, declared, configPath, rootDir) {
10185
10736
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
10186
10737
  if (!env || !declared.includes(env)) {
10187
- const shown = (0, import_node_path15.relative)(rootDir, configPath) || configPath;
10738
+ const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
10188
10739
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
10189
10740
  }
10190
10741
  return env;
@@ -10213,7 +10764,7 @@ function printSummary(out, appId, env, run, report4, output) {
10213
10764
  out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
10214
10765
  if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
10215
10766
  out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
10216
- out.log(` report: ${(0, import_node_path15.resolve)(output, "REPORT.md")}`);
10767
+ out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
10217
10768
  }
10218
10769
  function formatBudget(usage) {
10219
10770
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
@@ -10693,6 +11244,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
10693
11244
  await whoamiCommand(parsed, runtime);
10694
11245
  return;
10695
11246
  }
11247
+ if (command === "context") {
11248
+ await contextCommand(parsed, runtime);
11249
+ return;
11250
+ }
10696
11251
  if (command === "runbook") {
10697
11252
  await runbookCommand(parsed, runtime);
10698
11253
  return;