@odla-ai/cli 0.27.15 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -509,7 +509,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
509
509
  const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
510
510
  const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
511
511
  const cached = readJsonFile(cfg.local.tokenFile);
512
- if (!grantRequest.forceReview) {
512
+ if (!grantRequest.forceReview && !grantRequest.freshLogin) {
513
513
  if (options.token) return options.token;
514
514
  if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
515
515
  const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
@@ -526,9 +526,9 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
526
526
  }
527
527
  } else {
528
528
  if (options.token) {
529
- throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
529
+ throw new Error(grantRequest.forceReview ? "--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached" : "a fresh authorization cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
530
530
  }
531
- out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
531
+ out.error(grantRequest.forceReview ? `auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"` : `auth: requesting a fresh agent sign-in for exact project "${cfg.app.id}"`);
532
532
  }
533
533
  const ctx = {
534
534
  cfg,
@@ -541,6 +541,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
541
541
  grantIntent
542
542
  };
543
543
  const waitMs = handshakeWaitMs(options.wait);
544
+ if (grantRequest.freshLogin) clearPendingHandshake(ctx.pendingFile);
544
545
  const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
545
546
  clearPendingHandshake(ctx.pendingFile);
546
547
  writePrivateJson(cfg.local.tokenFile, {
@@ -663,8 +664,15 @@ function stillPending(pending, email) {
663
664
  }
664
665
  function handshakeEmail(value2, cached) {
665
666
  const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
667
+ if (/@users\.noreply\.github\.com$/i.test(email)) {
668
+ throw new Error(
669
+ `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
670
+ );
671
+ }
666
672
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
667
- throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
673
+ throw new Error(
674
+ "a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL; use the signed-in email shown in odla Studio, not git or GitHub identity"
675
+ );
668
676
  }
669
677
  return email;
670
678
  }
@@ -1958,6 +1966,223 @@ var init_admin_command = __esm({
1958
1966
  }
1959
1967
  });
1960
1968
 
1969
+ // src/whoami-command.ts
1970
+ function principalKind(value2, machine) {
1971
+ return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
1972
+ }
1973
+ function credentialKind(value2, machine, scopes) {
1974
+ if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
1975
+ if (machine) return "machine";
1976
+ if (scopes.length) return "device";
1977
+ return "unknown";
1978
+ }
1979
+ function managerOf(value2) {
1980
+ if (!value2 || typeof value2 !== "object") return null;
1981
+ const row = value2;
1982
+ const principalId = text(row.principalId);
1983
+ if (!principalId) return null;
1984
+ return {
1985
+ principalId,
1986
+ displayName: text(row.displayName) ?? "Unnamed member",
1987
+ handle: text(row.handle) ?? ""
1988
+ };
1989
+ }
1990
+ function unnamedPrincipal(kind) {
1991
+ if (kind === "agent") return "Unnamed agent";
1992
+ if (kind === "service") return "Unnamed service";
1993
+ return "Unnamed member";
1994
+ }
1995
+ async function fetchIdentity(platformUrl, token, doFetch) {
1996
+ const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
1997
+ headers: { authorization: `Bearer ${token}` }
1998
+ });
1999
+ if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
2000
+ const body = await res.json();
2001
+ const developerId = text(body.developerId) ?? "";
2002
+ const machine = body.machine === true;
2003
+ const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
2004
+ const principalId = text(body.principalId) ?? developerId;
2005
+ const email = text(body.email);
2006
+ const kind = principalKind(body.principalKind, machine);
2007
+ const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
2008
+ const handle = text(body.handle) ?? "";
2009
+ const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
2010
+ return {
2011
+ developerId,
2012
+ principalId,
2013
+ principalKind: kind,
2014
+ displayName,
2015
+ handle,
2016
+ manager: managerOf(body.manager),
2017
+ credential: {
2018
+ id: text(credential2.id),
2019
+ kind: credentialKind(credential2.kind, machine, scopes)
2020
+ },
2021
+ email,
2022
+ admin: body.admin === true,
2023
+ machine,
2024
+ scopes
2025
+ };
2026
+ }
2027
+ function credentialLabel(identity) {
2028
+ if (identity.credential.kind === "machine") return "machine (platform admin secret)";
2029
+ if (identity.credential.kind === "device")
2030
+ return identity.scopes.length ? "device (scoped)" : "device";
2031
+ if (identity.credential.kind === "clerk") return "clerk";
2032
+ return "unknown (legacy server)";
2033
+ }
2034
+ function namedPrincipal(identity) {
2035
+ return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
2036
+ }
2037
+ function namedManager(manager) {
2038
+ return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
2039
+ }
2040
+ function accountableOwner(identity) {
2041
+ if (identity.principalKind === "agent" && identity.manager) {
2042
+ return namedManager(identity.manager);
2043
+ }
2044
+ if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
2045
+ return namedPrincipal(identity);
2046
+ }
2047
+ return identity.email ?? "Unnamed member";
2048
+ }
2049
+ async function whoamiCommand(parsed, deps = {}) {
2050
+ assertArgs(
2051
+ parsed,
2052
+ ["config", "context", "platform", "token", "email", "json", "open"],
2053
+ 1
2054
+ );
2055
+ const out = deps.stdout ?? console;
2056
+ const doFetch = deps.fetch ?? fetch;
2057
+ const { cfg } = await resolveOperatorContext(parsed, {
2058
+ allowMissingConfig: true
2059
+ });
2060
+ const token = await getDeveloperToken(
2061
+ cfg,
2062
+ {
2063
+ configPath: cfg.configPath,
2064
+ token: stringOpt(parsed.options.token),
2065
+ email: stringOpt(parsed.options.email),
2066
+ // As above: never suppress the browser for an ordinary sign-in.
2067
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
2068
+ openApprovalUrl: deps.openUrl
2069
+ },
2070
+ doFetch,
2071
+ out
2072
+ );
2073
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
2074
+ if (parsed.options.json === true) {
2075
+ out.log(JSON.stringify(identity, null, 2));
2076
+ return;
2077
+ }
2078
+ out.log(`platform: ${cfg.platformUrl}`);
2079
+ out.log(`principal: ${namedPrincipal(identity)}`);
2080
+ out.log(`principal id: ${identity.principalId}`);
2081
+ out.log(`kind: ${identity.principalKind}`);
2082
+ if (identity.principalKind === "agent")
2083
+ out.log(
2084
+ `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
2085
+ );
2086
+ out.log(`owner: ${accountableOwner(identity)}`);
2087
+ out.log(`owner id: ${identity.developerId}`);
2088
+ out.log(`email: ${identity.email ?? "(none)"}`);
2089
+ out.log(`credential: ${credentialLabel(identity)}`);
2090
+ if (identity.credential.id)
2091
+ out.log(`credential id: ${identity.credential.id}`);
2092
+ out.log(`admin: ${identity.admin ? "yes" : "no"}`);
2093
+ if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
2094
+ if (!identity.admin) {
2095
+ if (identity.scopes.includes("platform:runbook:write")) {
2096
+ out.log("\nThis exact scope can read and edit all platform runbook content.");
2097
+ out.log("The device token is not ambient admin and cannot change visibility.");
2098
+ } else {
2099
+ out.log("\nYou can read operator-visible platform runbooks but not edit them.");
2100
+ out.log("Use a platform-admin-approved runbook write request to edit.");
2101
+ }
2102
+ }
2103
+ }
2104
+ var text;
2105
+ var init_whoami_command = __esm({
2106
+ "src/whoami-command.ts"() {
2107
+ "use strict";
2108
+ init_cjs_shims();
2109
+ init_argv();
2110
+ init_operator_context();
2111
+ init_token();
2112
+ text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
2113
+ }
2114
+ });
2115
+
2116
+ // src/auth-command.ts
2117
+ async function authCommand(parsed, deps = {}) {
2118
+ assertArgs(parsed, [
2119
+ "config",
2120
+ "context",
2121
+ "platform",
2122
+ "app",
2123
+ "email",
2124
+ "open",
2125
+ "wait",
2126
+ "json"
2127
+ ], 2);
2128
+ const action2 = parsed.positionals[1] ?? "login";
2129
+ if (action2 !== "login") {
2130
+ throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2131
+ }
2132
+ const context = await resolveOperatorContext(parsed, {
2133
+ allowMissingConfig: true,
2134
+ requireApp: true
2135
+ });
2136
+ const { cfg } = context;
2137
+ const out = deps.stdout ?? console;
2138
+ const doFetch = deps.fetch ?? fetch;
2139
+ const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
2140
+ if (!email) {
2141
+ throw new Error(
2142
+ "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
2143
+ );
2144
+ }
2145
+ const token = await getDeveloperToken(
2146
+ cfg,
2147
+ {
2148
+ configPath: cfg.configPath,
2149
+ email,
2150
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
2151
+ wait: numberOpt(parsed.options.wait, "--wait"),
2152
+ openApprovalUrl: deps.openUrl
2153
+ },
2154
+ doFetch,
2155
+ out,
2156
+ { freshLogin: true }
2157
+ );
2158
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
2159
+ if (parsed.options.json === true) {
2160
+ out.log(JSON.stringify({
2161
+ principalId: identity.principalId,
2162
+ displayName: identity.displayName,
2163
+ handle: identity.handle,
2164
+ email: identity.email,
2165
+ appId: cfg.app.id
2166
+ }, null, 2));
2167
+ return;
2168
+ }
2169
+ const handle = identity.handle ? ` (@${identity.handle})` : "";
2170
+ out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
2171
+ out.log(`odla account: ${identity.email ?? "not returned"}`);
2172
+ }
2173
+ var import_node_process10;
2174
+ var init_auth_command = __esm({
2175
+ "src/auth-command.ts"() {
2176
+ "use strict";
2177
+ init_cjs_shims();
2178
+ import_node_process10 = __toESM(require("process"), 1);
2179
+ init_argv();
2180
+ init_operator_context();
2181
+ init_token();
2182
+ init_whoami_command();
2183
+ }
2184
+ });
2185
+
1961
2186
  // src/tenant.ts
1962
2187
  function resolveEnv(cfg, requested) {
1963
2188
  const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
@@ -3035,6 +3260,71 @@ var init_capabilities = __esm({
3035
3260
  }
3036
3261
  });
3037
3262
 
3263
+ // src/ai-models.ts
3264
+ async function aiModels(options = {}) {
3265
+ const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
3266
+ const env = options.env ?? cfg.envs[0] ?? "dev";
3267
+ if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
3268
+ const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
3269
+ url.searchParams.set("env", env);
3270
+ const response2 = await (options.fetch ?? fetch)(url);
3271
+ if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
3272
+ const body = await response2.json();
3273
+ if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
3274
+ const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
3275
+ const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
3276
+ let models;
3277
+ if (mode === "hosted") {
3278
+ if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
3279
+ if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
3280
+ throw new Error("platform returned an invalid hosted AI model catalog");
3281
+ }
3282
+ models = body.ai.models;
3283
+ } else {
3284
+ const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
3285
+ if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
3286
+ models = Object.values(import_ai.DEFAULT_CATALOG).filter((model) => model.provider === provider);
3287
+ }
3288
+ if (options.provider) models = models.filter((model) => model.provider === options.provider);
3289
+ models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
3290
+ const out = options.stdout ?? console;
3291
+ if (options.json) {
3292
+ out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
3293
+ return;
3294
+ }
3295
+ out.log("provider model default capabilities");
3296
+ for (const model of models) {
3297
+ out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
3298
+ }
3299
+ }
3300
+ function capabilityList(model) {
3301
+ return [
3302
+ model.capabilities.imageIn ? "image" : "",
3303
+ model.capabilities.audioIn ? "audio" : "",
3304
+ model.capabilities.documentIn ? "document" : "",
3305
+ model.capabilities.toolUse ? "tools" : "",
3306
+ model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
3307
+ model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
3308
+ ].filter(Boolean).join(",");
3309
+ }
3310
+ function isModelSpec(value2) {
3311
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
3312
+ const model = value2;
3313
+ return typeof model.id === "string" && typeof model.nativeId === "string" && (model.provider === "anthropic" || model.provider === "openai" || model.provider === "google") && Boolean(model.capabilities) && typeof model.capabilities === "object";
3314
+ }
3315
+ async function safeText4(response2) {
3316
+ return (await response2.text().catch(() => "request failed")).slice(0, 300);
3317
+ }
3318
+ var import_ai;
3319
+ var init_ai_models = __esm({
3320
+ "src/ai-models.ts"() {
3321
+ "use strict";
3322
+ init_cjs_shims();
3323
+ import_ai = require("@odla-ai/ai");
3324
+ init_config();
3325
+ }
3326
+ });
3327
+
3038
3328
  // src/config-operation-error.ts
3039
3329
  var ConfigOperationCommandError;
3040
3330
  var init_config_operation_error = __esm({
@@ -3223,7 +3513,7 @@ var init_config_operation_validate = __esm({
3223
3513
 
3224
3514
  // src/provision-helpers.ts
3225
3515
  function defaultSecretName(provider) {
3226
- const names = import_ai.DEFAULT_SECRET_NAMES;
3516
+ const names = import_ai2.DEFAULT_SECRET_NAMES;
3227
3517
  return names[provider] ?? `${provider}_api_key`;
3228
3518
  }
3229
3519
  async function assertTenantAdminAccess(doFetch, cfg, env, token) {
@@ -3233,7 +3523,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3233
3523
  });
3234
3524
  if (res.ok || res.status === 404) return;
3235
3525
  if (res.status === 403) {
3236
- const detail = await safeText4(res);
3526
+ const detail = await safeText5(res);
3237
3527
  const code = errorCode(detail);
3238
3528
  if (code === "human_session_required") {
3239
3529
  throw new Error(
@@ -3249,7 +3539,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3249
3539
  `${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
3250
3540
  );
3251
3541
  }
3252
- throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
3542
+ throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
3253
3543
  }
3254
3544
  function errorCode(text2) {
3255
3545
  try {
@@ -3265,7 +3555,7 @@ async function postJson(doFetch, url, bearer, body) {
3265
3555
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
3266
3556
  body: JSON.stringify(body)
3267
3557
  });
3268
- if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText4(res)}`);
3558
+ if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
3269
3559
  }
3270
3560
  function normalizeClerkConfig(value2) {
3271
3561
  if (!value2) return null;
@@ -3278,19 +3568,19 @@ function normalizeClerkConfig(value2) {
3278
3568
  const publishableKey = envValue(cfg.publishableKey);
3279
3569
  return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
3280
3570
  }
3281
- async function safeText4(res) {
3571
+ async function safeText5(res) {
3282
3572
  try {
3283
3573
  return redactSecrets((await res.text()).slice(0, 500));
3284
3574
  } catch {
3285
3575
  return "";
3286
3576
  }
3287
3577
  }
3288
- var import_ai, import_apps4;
3578
+ var import_ai2, import_apps4;
3289
3579
  var init_provision_helpers = __esm({
3290
3580
  "src/provision-helpers.ts"() {
3291
3581
  "use strict";
3292
3582
  init_cjs_shims();
3293
- import_ai = require("@odla-ai/ai");
3583
+ import_ai2 = require("@odla-ai/ai");
3294
3584
  import_apps4 = require("@odla-ai/apps");
3295
3585
  init_config();
3296
3586
  init_redact();
@@ -4745,7 +5035,7 @@ async function secretsSet(options) {
4745
5035
  optionalProjectCapabilities: ["app.manage"]
4746
5036
  });
4747
5037
  try {
4748
- await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
5038
+ await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
4749
5039
  } catch (err) {
4750
5040
  throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
4751
5041
  }
@@ -4790,12 +5080,12 @@ async function resolveVaultWrite(options) {
4790
5080
  const value2 = await secretInputValue(options, "secret");
4791
5081
  return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
4792
5082
  }
4793
- var import_ai2, import_apps10, PROD_ENV_NAMES2;
5083
+ var import_ai3, import_apps10, PROD_ENV_NAMES2;
4794
5084
  var init_secrets_set = __esm({
4795
5085
  "src/secrets-set.ts"() {
4796
5086
  "use strict";
4797
5087
  init_cjs_shims();
4798
- import_ai2 = require("@odla-ai/ai");
5088
+ import_ai3 = require("@odla-ai/ai");
4799
5089
  import_apps10 = require("@odla-ai/apps");
4800
5090
  init_config();
4801
5091
  init_redact();
@@ -4852,6 +5142,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
4852
5142
  and file bugs when you notice them. The conventions and the full command set are
4853
5143
  in \`.agents/skills/odla/references/pm.md\`.
4854
5144
 
5145
+ Use the human's signed-in odla account email for device authorization; never
5146
+ infer it from git config, commit metadata, or GitHub. If authorization is not
5147
+ already active, run
5148
+ \`npx @odla-ai/cli auth login --app <appId> --email <odla-account>\` and open
5149
+ the exact Studio URL it prints. Never file an odla project or product defect in
5150
+ GitHub Issues: run \`npx @odla-ai/cli bug report --app <appId> ...\` so the bug
5151
+ lands in odla PM with the rest of the project's goals, tasks, and decisions.
5152
+
4855
5153
  The setup runbooks and their references are installed in this repository, pinned
4856
5154
  to this CLI version. Use them as your setup context.
4857
5155
 
@@ -5207,7 +5505,7 @@ async function getJson(doFetch, url, bearer) {
5207
5505
  const res = await doFetch(url, {
5208
5506
  headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
5209
5507
  });
5210
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
5508
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
5211
5509
  return res.json();
5212
5510
  }
5213
5511
  async function postJson2(doFetch, url, bearer, body) {
@@ -5216,7 +5514,7 @@ async function postJson2(doFetch, url, bearer, body) {
5216
5514
  headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
5217
5515
  body: JSON.stringify(body)
5218
5516
  });
5219
- if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText5(res)}`);
5517
+ if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
5220
5518
  return res.json();
5221
5519
  }
5222
5520
  function publicConfigUrl(platformUrl, appId, env) {
@@ -5224,7 +5522,7 @@ function publicConfigUrl(platformUrl, appId, env) {
5224
5522
  url.searchParams.set("env", env);
5225
5523
  return url.toString();
5226
5524
  }
5227
- async function safeText5(res) {
5525
+ async function safeText6(res) {
5228
5526
  try {
5229
5527
  return redactSecrets((await res.text()).slice(0, 500));
5230
5528
  } catch {
@@ -5291,6 +5589,20 @@ async function secretsCommand(parsed, deps) {
5291
5589
  });
5292
5590
  }
5293
5591
  async function projectCommand(command, parsed, deps) {
5592
+ if (command === "ai") {
5593
+ const sub = parsed.positionals[1];
5594
+ if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
5595
+ assertArgs(parsed, ["config", "env", "provider", "json"], 2);
5596
+ await aiModels({
5597
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
5598
+ env: stringOpt(parsed.options.env),
5599
+ provider: stringOpt(parsed.options.provider),
5600
+ json: parsed.options.json === true,
5601
+ fetch: deps.fetch,
5602
+ stdout: deps.stdout
5603
+ });
5604
+ return true;
5605
+ }
5294
5606
  if (command === "config") {
5295
5607
  const sub = parsed.positionals[1];
5296
5608
  if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
@@ -5413,6 +5725,7 @@ var init_cli_project = __esm({
5413
5725
  init_cjs_shims();
5414
5726
  init_argv();
5415
5727
  init_capabilities();
5728
+ init_ai_models();
5416
5729
  init_config_operation_command();
5417
5730
  init_config_reconcile_command();
5418
5731
  init_doctor();
@@ -9023,7 +9336,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
9023
9336
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
9024
9337
  const source = clean3(
9025
9338
  stringOpt(parsed.options.token)
9026
- ) ? "flag" : clean3(import_node_process10.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9339
+ ) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9027
9340
  return {
9028
9341
  source,
9029
9342
  cacheFile: context.cfg.local.tokenFile,
@@ -9034,12 +9347,12 @@ function clean3(value2) {
9034
9347
  const normalized = value2?.trim();
9035
9348
  return normalized || void 0;
9036
9349
  }
9037
- var import_node_process10;
9350
+ var import_node_process11;
9038
9351
  var init_operator_credentials = __esm({
9039
9352
  "src/operator-credentials.ts"() {
9040
9353
  "use strict";
9041
9354
  init_cjs_shims();
9042
- import_node_process10 = __toESM(require("process"), 1);
9355
+ import_node_process11 = __toESM(require("process"), 1);
9043
9356
  init_argv();
9044
9357
  init_local();
9045
9358
  }
@@ -9205,9 +9518,11 @@ Start here:
9205
9518
  step it made wrong.
9206
9519
 
9207
9520
  Usage:
9521
+ odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
9208
9522
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
9209
9523
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
9210
9524
  odla-ai doctor [--config odla.config.mjs]
9525
+ odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
9211
9526
  odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
9212
9527
  odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
9213
9528
  odla-ai operations get <operation-id> [--json]
@@ -9240,6 +9555,7 @@ Usage:
9240
9555
  odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
9241
9556
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
9242
9557
  odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
9558
+ odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
9243
9559
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
9244
9560
  odla-ai pm <goal|task|decision|bug> ref <id> [--json]
9245
9561
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
@@ -9321,6 +9637,9 @@ function printHelp(output = console) {
9321
9637
  output.log(`odla-ai
9322
9638
  ${USAGE_SECTION}
9323
9639
  Commands:
9640
+ auth Start a fresh, exact-project agent authorization in the browser.
9641
+ The email is the signed-in odla account, never git or GitHub
9642
+ identity. The approval screen confirms the agent name first.
9324
9643
  agent Inspect durable agent wakeups and explicitly requeue a
9325
9644
  dead-lettered job; JSON output is stable for remote operators.
9326
9645
  runbook odla's operational procedures, stored in the database and read at
@@ -9388,6 +9707,8 @@ Commands:
9388
9707
  "app". Entities: goal (alias conformance), task (alias kanban),
9389
9708
  decision, bug. Status changes and comments post to each item's
9390
9709
  @odla-ai/chat discussion thread.
9710
+ bug Intent-first alias for PM bugs. "bug report" writes to
9711
+ odla PM; odla product defects do not belong in GitHub Issues.
9391
9712
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
9392
9713
  one group per project, topics with replies, @-mentions of people,
9393
9714
  agents, PM items, and projects. Built for unattended use \u2014 post a
@@ -9445,8 +9766,12 @@ Safety:
9445
9766
  wait for approval, and re-run to collect.
9446
9767
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
9447
9768
  The email is a non-secret identity hint: never provide a password or session
9448
- token. The matching account must already exist, be signed in, explicitly
9769
+ token. It is the email shown by the signed-in odla account \u2014 never infer it
9770
+ from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
9449
9771
  review the exact code, and finish any current request before claiming another.
9772
+ Use "auth login --app <id> --email <odla-account>" when an outside agent needs
9773
+ a deliberate fresh request; it ignores cached credentials and opens the same
9774
+ focused authorization sequence used by every first-time command.
9450
9775
  If provision reports that the current agent principal has no live app.manage
9451
9776
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
9452
9777
  the local cache, prints and opens a fresh exact-project owner-review URL, then
@@ -9983,7 +10308,8 @@ async function buildContext(parsed, deps) {
9983
10308
  configPath: cfg.configPath,
9984
10309
  token: stringOpt(parsed.options.token),
9985
10310
  email: stringOpt(parsed.options.email),
9986
- open: false
10311
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10312
+ openApprovalUrl: deps.openUrl
9987
10313
  },
9988
10314
  doFetch,
9989
10315
  out
@@ -10063,7 +10389,8 @@ var init_discuss_command = __esm({
10063
10389
  "jsonl",
10064
10390
  "mutation-id",
10065
10391
  "platform",
10066
- "context"
10392
+ "context",
10393
+ "open"
10067
10394
  ];
10068
10395
  }
10069
10396
  });
@@ -10650,7 +10977,13 @@ async function buildContext2(parsed, deps) {
10650
10977
  const out = deps.stdout ?? console;
10651
10978
  const token = await getDeveloperToken(
10652
10979
  cfg,
10653
- { configPath: cfg.configPath, token: stringOpt(parsed.options.token), email: stringOpt(parsed.options.email), open: false },
10980
+ {
10981
+ configPath: cfg.configPath,
10982
+ token: stringOpt(parsed.options.token),
10983
+ email: stringOpt(parsed.options.email),
10984
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10985
+ openApprovalUrl: deps.openUrl
10986
+ },
10654
10987
  doFetch,
10655
10988
  out
10656
10989
  );
@@ -10748,7 +11081,7 @@ var init_pm_command = __esm({
10748
11081
  decision: "decision",
10749
11082
  bug: "bug"
10750
11083
  };
10751
- COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
11084
+ COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
10752
11085
  ACTION_OPTIONS = {
10753
11086
  list: ["app", "q", "limit", "offset"],
10754
11087
  add: ["app", "title", "mutation-id"],
@@ -11204,7 +11537,8 @@ async function o11yCommand(parsed, deps = {}) {
11204
11537
  "json",
11205
11538
  "app",
11206
11539
  "env",
11207
- "minutes"
11540
+ "minutes",
11541
+ "open"
11208
11542
  ],
11209
11543
  2
11210
11544
  );
@@ -11231,7 +11565,8 @@ async function o11yCommand(parsed, deps = {}) {
11231
11565
  configPath: cfg.configPath,
11232
11566
  token: stringOpt(parsed.options.token),
11233
11567
  email: stringOpt(parsed.options.email),
11234
- open: false
11568
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
11569
+ openApprovalUrl: deps.openUrl
11235
11570
  },
11236
11571
  doFetch,
11237
11572
  out
@@ -11468,14 +11803,14 @@ async function mintDbKey(opts, tenantId) {
11468
11803
  appId: tenantId
11469
11804
  })
11470
11805
  });
11471
- if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText6(created)}`);
11806
+ if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
11472
11807
  res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
11473
11808
  method: "POST",
11474
11809
  headers,
11475
11810
  body: "{}"
11476
11811
  });
11477
11812
  }
11478
- if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText6(res)}`);
11813
+ if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
11479
11814
  const body = await res.json();
11480
11815
  if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
11481
11816
  return body.key;
@@ -11491,12 +11826,12 @@ async function issueO11yToken(opts) {
11491
11826
  `o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
11492
11827
  );
11493
11828
  }
11494
- if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText6(res)}`);
11829
+ if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
11495
11830
  const body = await res.json();
11496
11831
  if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
11497
11832
  return body.token;
11498
11833
  }
11499
- async function safeText6(res) {
11834
+ async function safeText7(res) {
11500
11835
  try {
11501
11836
  return redactSecrets((await res.text()).slice(0, 500));
11502
11837
  } catch {
@@ -11685,10 +12020,10 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11685
12020
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
11686
12021
  }
11687
12022
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
11688
- const key = import_node_process11.default.env[cfg.ai.keyEnv];
12023
+ const key = import_node_process12.default.env[cfg.ai.keyEnv];
11689
12024
  if (key) {
11690
12025
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
11691
- await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
12026
+ await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
11692
12027
  out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
11693
12028
  } else {
11694
12029
  out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
@@ -11724,14 +12059,14 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11724
12059
  }
11725
12060
  }
11726
12061
  }
11727
- var import_apps12, import_ai3, import_node_process11;
12062
+ var import_apps12, import_ai4, import_node_process12;
11728
12063
  var init_provision = __esm({
11729
12064
  "src/provision.ts"() {
11730
12065
  "use strict";
11731
12066
  init_cjs_shims();
11732
12067
  import_apps12 = require("@odla-ai/apps");
11733
- import_ai3 = require("@odla-ai/ai");
11734
- import_node_process11 = __toESM(require("process"), 1);
12068
+ import_ai4 = require("@odla-ai/ai");
12069
+ import_node_process12 = __toESM(require("process"), 1);
11735
12070
  init_config();
11736
12071
  init_calendar();
11737
12072
  init_calendar_errors();
@@ -11819,6 +12154,7 @@ var init_surface = __esm({
11819
12154
  };
11820
12155
  COMMAND_SURFACE = {
11821
12156
  agent: { jobs: {}, retry: {} },
12157
+ ai: { models: {} },
11822
12158
  admin: {
11823
12159
  ai: {
11824
12160
  show: {},
@@ -11841,7 +12177,9 @@ var init_surface = __esm({
11841
12177
  promote: {},
11842
12178
  owners: { list: {}, add: {}, remove: {} }
11843
12179
  },
12180
+ auth: { login: {} },
11844
12181
  brand: { design: { unpack: {} } },
12182
+ bug: { create: {}, list: {}, report: {} },
11845
12183
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
11846
12184
  capabilities: {},
11847
12185
  code: { connect: {} },
@@ -11913,7 +12251,7 @@ var init_surface = __esm({
11913
12251
 
11914
12252
  // src/record.ts
11915
12253
  function recordInvocation(parsed) {
11916
- const file = import_node_process12.default.env.ODLA_CLI_RECORD;
12254
+ const file = import_node_process13.default.env.ODLA_CLI_RECORD;
11917
12255
  if (!file) return;
11918
12256
  try {
11919
12257
  const entry = {
@@ -11926,13 +12264,13 @@ function recordInvocation(parsed) {
11926
12264
  } catch {
11927
12265
  }
11928
12266
  }
11929
- var import_node_fs17, import_node_process12;
12267
+ var import_node_fs17, import_node_process13;
11930
12268
  var init_record = __esm({
11931
12269
  "src/record.ts"() {
11932
12270
  "use strict";
11933
12271
  init_cjs_shims();
11934
12272
  import_node_fs17 = require("fs");
11935
- import_node_process12 = __toESM(require("process"), 1);
12273
+ import_node_process13 = __toESM(require("process"), 1);
11936
12274
  init_surface();
11937
12275
  }
11938
12276
  });
@@ -12591,7 +12929,7 @@ var init_runbook_search_command = __esm({
12591
12929
  });
12592
12930
 
12593
12931
  // src/runbook-editor.ts
12594
- function resolveEditor(env = import_node_process13.default.env) {
12932
+ function resolveEditor(env = import_node_process14.default.env) {
12595
12933
  for (const name of EDITOR_ENV) {
12596
12934
  const value2 = env[name];
12597
12935
  if (value2 && value2.trim()) return value2.trim();
@@ -12605,8 +12943,8 @@ function defaultRun(command, path) {
12605
12943
  return result.status ?? 0;
12606
12944
  }
12607
12945
  function editText(initial, slug, deps = {}) {
12608
- const env = deps.env ?? import_node_process13.default.env;
12609
- const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
12946
+ const env = deps.env ?? import_node_process14.default.env;
12947
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
12610
12948
  const editor = resolveEditor(env);
12611
12949
  if (!editor)
12612
12950
  throw new Error(
@@ -12626,7 +12964,7 @@ function editText(initial, slug, deps = {}) {
12626
12964
  (0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
12627
12965
  }
12628
12966
  }
12629
- var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19, import_node_process13, EDITOR_ENV, defaultRunOrInjected;
12967
+ var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
12630
12968
  var init_runbook_editor = __esm({
12631
12969
  "src/runbook-editor.ts"() {
12632
12970
  "use strict";
@@ -12635,7 +12973,7 @@ var init_runbook_editor = __esm({
12635
12973
  import_node_fs21 = require("fs");
12636
12974
  import_node_os5 = require("os");
12637
12975
  import_node_path19 = require("path");
12638
- import_node_process13 = __toESM(require("process"), 1);
12976
+ import_node_process14 = __toESM(require("process"), 1);
12639
12977
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
12640
12978
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
12641
12979
  }
@@ -12663,152 +13001,6 @@ var init_runbook_edit_flow = __esm({
12663
13001
  }
12664
13002
  });
12665
13003
 
12666
- // src/whoami-command.ts
12667
- function principalKind(value2, machine) {
12668
- return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
12669
- }
12670
- function credentialKind(value2, machine, scopes) {
12671
- if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
12672
- if (machine) return "machine";
12673
- if (scopes.length) return "device";
12674
- return "unknown";
12675
- }
12676
- function managerOf(value2) {
12677
- if (!value2 || typeof value2 !== "object") return null;
12678
- const row = value2;
12679
- const principalId = text(row.principalId);
12680
- if (!principalId) return null;
12681
- return {
12682
- principalId,
12683
- displayName: text(row.displayName) ?? "Unnamed member",
12684
- handle: text(row.handle) ?? ""
12685
- };
12686
- }
12687
- function unnamedPrincipal(kind) {
12688
- if (kind === "agent") return "Unnamed agent";
12689
- if (kind === "service") return "Unnamed service";
12690
- return "Unnamed member";
12691
- }
12692
- async function fetchIdentity(platformUrl, token, doFetch) {
12693
- const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
12694
- headers: { authorization: `Bearer ${token}` }
12695
- });
12696
- if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
12697
- const body = await res.json();
12698
- const developerId = text(body.developerId) ?? "";
12699
- const machine = body.machine === true;
12700
- const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
12701
- const principalId = text(body.principalId) ?? developerId;
12702
- const email = text(body.email);
12703
- const kind = principalKind(body.principalKind, machine);
12704
- const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
12705
- const handle = text(body.handle) ?? "";
12706
- const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
12707
- return {
12708
- developerId,
12709
- principalId,
12710
- principalKind: kind,
12711
- displayName,
12712
- handle,
12713
- manager: managerOf(body.manager),
12714
- credential: {
12715
- id: text(credential2.id),
12716
- kind: credentialKind(credential2.kind, machine, scopes)
12717
- },
12718
- email,
12719
- admin: body.admin === true,
12720
- machine,
12721
- scopes
12722
- };
12723
- }
12724
- function credentialLabel(identity) {
12725
- if (identity.credential.kind === "machine") return "machine (platform admin secret)";
12726
- if (identity.credential.kind === "device")
12727
- return identity.scopes.length ? "device (scoped)" : "device";
12728
- if (identity.credential.kind === "clerk") return "clerk";
12729
- return "unknown (legacy server)";
12730
- }
12731
- function namedPrincipal(identity) {
12732
- return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
12733
- }
12734
- function namedManager(manager) {
12735
- return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
12736
- }
12737
- function accountableOwner(identity) {
12738
- if (identity.principalKind === "agent" && identity.manager) {
12739
- return namedManager(identity.manager);
12740
- }
12741
- if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
12742
- return namedPrincipal(identity);
12743
- }
12744
- return identity.email ?? "Unnamed member";
12745
- }
12746
- async function whoamiCommand(parsed, deps = {}) {
12747
- assertArgs(
12748
- parsed,
12749
- ["config", "context", "platform", "token", "email", "json"],
12750
- 1
12751
- );
12752
- const out = deps.stdout ?? console;
12753
- const doFetch = deps.fetch ?? fetch;
12754
- const { cfg } = await resolveOperatorContext(parsed, {
12755
- allowMissingConfig: true
12756
- });
12757
- const token = await getDeveloperToken(
12758
- cfg,
12759
- {
12760
- configPath: cfg.configPath,
12761
- token: stringOpt(parsed.options.token),
12762
- email: stringOpt(parsed.options.email),
12763
- // As above: never suppress the browser for an ordinary sign-in.
12764
- open: void 0
12765
- },
12766
- doFetch,
12767
- out
12768
- );
12769
- const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
12770
- if (parsed.options.json === true) {
12771
- out.log(JSON.stringify(identity, null, 2));
12772
- return;
12773
- }
12774
- out.log(`platform: ${cfg.platformUrl}`);
12775
- out.log(`principal: ${namedPrincipal(identity)}`);
12776
- out.log(`principal id: ${identity.principalId}`);
12777
- out.log(`kind: ${identity.principalKind}`);
12778
- if (identity.principalKind === "agent")
12779
- out.log(
12780
- `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
12781
- );
12782
- out.log(`owner: ${accountableOwner(identity)}`);
12783
- out.log(`owner id: ${identity.developerId}`);
12784
- out.log(`email: ${identity.email ?? "(none)"}`);
12785
- out.log(`credential: ${credentialLabel(identity)}`);
12786
- if (identity.credential.id)
12787
- out.log(`credential id: ${identity.credential.id}`);
12788
- out.log(`admin: ${identity.admin ? "yes" : "no"}`);
12789
- if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
12790
- if (!identity.admin) {
12791
- if (identity.scopes.includes("platform:runbook:write")) {
12792
- out.log("\nThis exact scope can read and edit all platform runbook content.");
12793
- out.log("The device token is not ambient admin and cannot change visibility.");
12794
- } else {
12795
- out.log("\nYou can read operator-visible platform runbooks but not edit them.");
12796
- out.log("Use a platform-admin-approved runbook write request to edit.");
12797
- }
12798
- }
12799
- }
12800
- var text;
12801
- var init_whoami_command = __esm({
12802
- "src/whoami-command.ts"() {
12803
- "use strict";
12804
- init_cjs_shims();
12805
- init_argv();
12806
- init_operator_context();
12807
- init_token();
12808
- text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
12809
- }
12810
- });
12811
-
12812
13004
  // src/runbook-command.ts
12813
13005
  function requireSlug(slug, action2) {
12814
13006
  if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
@@ -13781,6 +13973,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13781
13973
  await whoamiCommand(parsed, runtime);
13782
13974
  return;
13783
13975
  }
13976
+ if (command === "auth") {
13977
+ await authCommand(parsed, runtime);
13978
+ return;
13979
+ }
13784
13980
  if (command === "context") {
13785
13981
  await contextCommand(parsed, runtime);
13786
13982
  return;
@@ -13821,6 +14017,15 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13821
14017
  await pmCommand(parsed, runtime);
13822
14018
  return;
13823
14019
  }
14020
+ if (command === "bug") {
14021
+ const action2 = parsed.positionals[1] ?? "list";
14022
+ const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
14023
+ await pmCommand({
14024
+ ...parsed,
14025
+ positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
14026
+ }, runtime);
14027
+ return;
14028
+ }
13824
14029
  if (command === "discuss") {
13825
14030
  await discussCommand(parsed, runtime);
13826
14031
  return;
@@ -13907,6 +14112,7 @@ var init_cli = __esm({
13907
14112
  "use strict";
13908
14113
  init_cjs_shims();
13909
14114
  init_admin_command();
14115
+ init_auth_command();
13910
14116
  init_agent_command();
13911
14117
  init_app_lifecycle();
13912
14118
  init_brand_command();