@odla-ai/cli 0.27.17 → 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]);
@@ -4917,6 +5142,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
4917
5142
  and file bugs when you notice them. The conventions and the full command set are
4918
5143
  in \`.agents/skills/odla/references/pm.md\`.
4919
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
+
4920
5153
  The setup runbooks and their references are installed in this repository, pinned
4921
5154
  to this CLI version. Use them as your setup context.
4922
5155
 
@@ -9103,7 +9336,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
9103
9336
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
9104
9337
  const source = clean3(
9105
9338
  stringOpt(parsed.options.token)
9106
- ) ? "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";
9107
9340
  return {
9108
9341
  source,
9109
9342
  cacheFile: context.cfg.local.tokenFile,
@@ -9114,12 +9347,12 @@ function clean3(value2) {
9114
9347
  const normalized = value2?.trim();
9115
9348
  return normalized || void 0;
9116
9349
  }
9117
- var import_node_process10;
9350
+ var import_node_process11;
9118
9351
  var init_operator_credentials = __esm({
9119
9352
  "src/operator-credentials.ts"() {
9120
9353
  "use strict";
9121
9354
  init_cjs_shims();
9122
- import_node_process10 = __toESM(require("process"), 1);
9355
+ import_node_process11 = __toESM(require("process"), 1);
9123
9356
  init_argv();
9124
9357
  init_local();
9125
9358
  }
@@ -9285,6 +9518,7 @@ Start here:
9285
9518
  step it made wrong.
9286
9519
 
9287
9520
  Usage:
9521
+ odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
9288
9522
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
9289
9523
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
9290
9524
  odla-ai doctor [--config odla.config.mjs]
@@ -9321,6 +9555,7 @@ Usage:
9321
9555
  odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
9322
9556
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
9323
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]
9324
9559
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
9325
9560
  odla-ai pm <goal|task|decision|bug> ref <id> [--json]
9326
9561
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
@@ -9402,6 +9637,9 @@ function printHelp(output = console) {
9402
9637
  output.log(`odla-ai
9403
9638
  ${USAGE_SECTION}
9404
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.
9405
9643
  agent Inspect durable agent wakeups and explicitly requeue a
9406
9644
  dead-lettered job; JSON output is stable for remote operators.
9407
9645
  runbook odla's operational procedures, stored in the database and read at
@@ -9469,6 +9707,8 @@ Commands:
9469
9707
  "app". Entities: goal (alias conformance), task (alias kanban),
9470
9708
  decision, bug. Status changes and comments post to each item's
9471
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.
9472
9712
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
9473
9713
  one group per project, topics with replies, @-mentions of people,
9474
9714
  agents, PM items, and projects. Built for unattended use \u2014 post a
@@ -9526,8 +9766,12 @@ Safety:
9526
9766
  wait for approval, and re-run to collect.
9527
9767
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
9528
9768
  The email is a non-secret identity hint: never provide a password or session
9529
- 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
9530
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.
9531
9775
  If provision reports that the current agent principal has no live app.manage
9532
9776
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
9533
9777
  the local cache, prints and opens a fresh exact-project owner-review URL, then
@@ -10064,7 +10308,8 @@ async function buildContext(parsed, deps) {
10064
10308
  configPath: cfg.configPath,
10065
10309
  token: stringOpt(parsed.options.token),
10066
10310
  email: stringOpt(parsed.options.email),
10067
- open: false
10311
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10312
+ openApprovalUrl: deps.openUrl
10068
10313
  },
10069
10314
  doFetch,
10070
10315
  out
@@ -10144,7 +10389,8 @@ var init_discuss_command = __esm({
10144
10389
  "jsonl",
10145
10390
  "mutation-id",
10146
10391
  "platform",
10147
- "context"
10392
+ "context",
10393
+ "open"
10148
10394
  ];
10149
10395
  }
10150
10396
  });
@@ -10731,7 +10977,13 @@ async function buildContext2(parsed, deps) {
10731
10977
  const out = deps.stdout ?? console;
10732
10978
  const token = await getDeveloperToken(
10733
10979
  cfg,
10734
- { 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
+ },
10735
10987
  doFetch,
10736
10988
  out
10737
10989
  );
@@ -10829,7 +11081,7 @@ var init_pm_command = __esm({
10829
11081
  decision: "decision",
10830
11082
  bug: "bug"
10831
11083
  };
10832
- COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
11084
+ COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
10833
11085
  ACTION_OPTIONS = {
10834
11086
  list: ["app", "q", "limit", "offset"],
10835
11087
  add: ["app", "title", "mutation-id"],
@@ -11285,7 +11537,8 @@ async function o11yCommand(parsed, deps = {}) {
11285
11537
  "json",
11286
11538
  "app",
11287
11539
  "env",
11288
- "minutes"
11540
+ "minutes",
11541
+ "open"
11289
11542
  ],
11290
11543
  2
11291
11544
  );
@@ -11312,7 +11565,8 @@ async function o11yCommand(parsed, deps = {}) {
11312
11565
  configPath: cfg.configPath,
11313
11566
  token: stringOpt(parsed.options.token),
11314
11567
  email: stringOpt(parsed.options.email),
11315
- open: false
11568
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
11569
+ openApprovalUrl: deps.openUrl
11316
11570
  },
11317
11571
  doFetch,
11318
11572
  out
@@ -11766,7 +12020,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11766
12020
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
11767
12021
  }
11768
12022
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
11769
- const key = import_node_process11.default.env[cfg.ai.keyEnv];
12023
+ const key = import_node_process12.default.env[cfg.ai.keyEnv];
11770
12024
  if (key) {
11771
12025
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
11772
12026
  await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -11805,14 +12059,14 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11805
12059
  }
11806
12060
  }
11807
12061
  }
11808
- var import_apps12, import_ai4, import_node_process11;
12062
+ var import_apps12, import_ai4, import_node_process12;
11809
12063
  var init_provision = __esm({
11810
12064
  "src/provision.ts"() {
11811
12065
  "use strict";
11812
12066
  init_cjs_shims();
11813
12067
  import_apps12 = require("@odla-ai/apps");
11814
12068
  import_ai4 = require("@odla-ai/ai");
11815
- import_node_process11 = __toESM(require("process"), 1);
12069
+ import_node_process12 = __toESM(require("process"), 1);
11816
12070
  init_config();
11817
12071
  init_calendar();
11818
12072
  init_calendar_errors();
@@ -11923,7 +12177,9 @@ var init_surface = __esm({
11923
12177
  promote: {},
11924
12178
  owners: { list: {}, add: {}, remove: {} }
11925
12179
  },
12180
+ auth: { login: {} },
11926
12181
  brand: { design: { unpack: {} } },
12182
+ bug: { create: {}, list: {}, report: {} },
11927
12183
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
11928
12184
  capabilities: {},
11929
12185
  code: { connect: {} },
@@ -11995,7 +12251,7 @@ var init_surface = __esm({
11995
12251
 
11996
12252
  // src/record.ts
11997
12253
  function recordInvocation(parsed) {
11998
- const file = import_node_process12.default.env.ODLA_CLI_RECORD;
12254
+ const file = import_node_process13.default.env.ODLA_CLI_RECORD;
11999
12255
  if (!file) return;
12000
12256
  try {
12001
12257
  const entry = {
@@ -12008,13 +12264,13 @@ function recordInvocation(parsed) {
12008
12264
  } catch {
12009
12265
  }
12010
12266
  }
12011
- var import_node_fs17, import_node_process12;
12267
+ var import_node_fs17, import_node_process13;
12012
12268
  var init_record = __esm({
12013
12269
  "src/record.ts"() {
12014
12270
  "use strict";
12015
12271
  init_cjs_shims();
12016
12272
  import_node_fs17 = require("fs");
12017
- import_node_process12 = __toESM(require("process"), 1);
12273
+ import_node_process13 = __toESM(require("process"), 1);
12018
12274
  init_surface();
12019
12275
  }
12020
12276
  });
@@ -12673,7 +12929,7 @@ var init_runbook_search_command = __esm({
12673
12929
  });
12674
12930
 
12675
12931
  // src/runbook-editor.ts
12676
- function resolveEditor(env = import_node_process13.default.env) {
12932
+ function resolveEditor(env = import_node_process14.default.env) {
12677
12933
  for (const name of EDITOR_ENV) {
12678
12934
  const value2 = env[name];
12679
12935
  if (value2 && value2.trim()) return value2.trim();
@@ -12687,8 +12943,8 @@ function defaultRun(command, path) {
12687
12943
  return result.status ?? 0;
12688
12944
  }
12689
12945
  function editText(initial, slug, deps = {}) {
12690
- const env = deps.env ?? import_node_process13.default.env;
12691
- 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));
12692
12948
  const editor = resolveEditor(env);
12693
12949
  if (!editor)
12694
12950
  throw new Error(
@@ -12708,7 +12964,7 @@ function editText(initial, slug, deps = {}) {
12708
12964
  (0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
12709
12965
  }
12710
12966
  }
12711
- 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;
12712
12968
  var init_runbook_editor = __esm({
12713
12969
  "src/runbook-editor.ts"() {
12714
12970
  "use strict";
@@ -12717,7 +12973,7 @@ var init_runbook_editor = __esm({
12717
12973
  import_node_fs21 = require("fs");
12718
12974
  import_node_os5 = require("os");
12719
12975
  import_node_path19 = require("path");
12720
- import_node_process13 = __toESM(require("process"), 1);
12976
+ import_node_process14 = __toESM(require("process"), 1);
12721
12977
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
12722
12978
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
12723
12979
  }
@@ -12745,152 +13001,6 @@ var init_runbook_edit_flow = __esm({
12745
13001
  }
12746
13002
  });
12747
13003
 
12748
- // src/whoami-command.ts
12749
- function principalKind(value2, machine) {
12750
- return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
12751
- }
12752
- function credentialKind(value2, machine, scopes) {
12753
- if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
12754
- if (machine) return "machine";
12755
- if (scopes.length) return "device";
12756
- return "unknown";
12757
- }
12758
- function managerOf(value2) {
12759
- if (!value2 || typeof value2 !== "object") return null;
12760
- const row = value2;
12761
- const principalId = text(row.principalId);
12762
- if (!principalId) return null;
12763
- return {
12764
- principalId,
12765
- displayName: text(row.displayName) ?? "Unnamed member",
12766
- handle: text(row.handle) ?? ""
12767
- };
12768
- }
12769
- function unnamedPrincipal(kind) {
12770
- if (kind === "agent") return "Unnamed agent";
12771
- if (kind === "service") return "Unnamed service";
12772
- return "Unnamed member";
12773
- }
12774
- async function fetchIdentity(platformUrl, token, doFetch) {
12775
- const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
12776
- headers: { authorization: `Bearer ${token}` }
12777
- });
12778
- if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
12779
- const body = await res.json();
12780
- const developerId = text(body.developerId) ?? "";
12781
- const machine = body.machine === true;
12782
- const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
12783
- const principalId = text(body.principalId) ?? developerId;
12784
- const email = text(body.email);
12785
- const kind = principalKind(body.principalKind, machine);
12786
- const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
12787
- const handle = text(body.handle) ?? "";
12788
- const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
12789
- return {
12790
- developerId,
12791
- principalId,
12792
- principalKind: kind,
12793
- displayName,
12794
- handle,
12795
- manager: managerOf(body.manager),
12796
- credential: {
12797
- id: text(credential2.id),
12798
- kind: credentialKind(credential2.kind, machine, scopes)
12799
- },
12800
- email,
12801
- admin: body.admin === true,
12802
- machine,
12803
- scopes
12804
- };
12805
- }
12806
- function credentialLabel(identity) {
12807
- if (identity.credential.kind === "machine") return "machine (platform admin secret)";
12808
- if (identity.credential.kind === "device")
12809
- return identity.scopes.length ? "device (scoped)" : "device";
12810
- if (identity.credential.kind === "clerk") return "clerk";
12811
- return "unknown (legacy server)";
12812
- }
12813
- function namedPrincipal(identity) {
12814
- return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
12815
- }
12816
- function namedManager(manager) {
12817
- return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
12818
- }
12819
- function accountableOwner(identity) {
12820
- if (identity.principalKind === "agent" && identity.manager) {
12821
- return namedManager(identity.manager);
12822
- }
12823
- if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
12824
- return namedPrincipal(identity);
12825
- }
12826
- return identity.email ?? "Unnamed member";
12827
- }
12828
- async function whoamiCommand(parsed, deps = {}) {
12829
- assertArgs(
12830
- parsed,
12831
- ["config", "context", "platform", "token", "email", "json"],
12832
- 1
12833
- );
12834
- const out = deps.stdout ?? console;
12835
- const doFetch = deps.fetch ?? fetch;
12836
- const { cfg } = await resolveOperatorContext(parsed, {
12837
- allowMissingConfig: true
12838
- });
12839
- const token = await getDeveloperToken(
12840
- cfg,
12841
- {
12842
- configPath: cfg.configPath,
12843
- token: stringOpt(parsed.options.token),
12844
- email: stringOpt(parsed.options.email),
12845
- // As above: never suppress the browser for an ordinary sign-in.
12846
- open: void 0
12847
- },
12848
- doFetch,
12849
- out
12850
- );
12851
- const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
12852
- if (parsed.options.json === true) {
12853
- out.log(JSON.stringify(identity, null, 2));
12854
- return;
12855
- }
12856
- out.log(`platform: ${cfg.platformUrl}`);
12857
- out.log(`principal: ${namedPrincipal(identity)}`);
12858
- out.log(`principal id: ${identity.principalId}`);
12859
- out.log(`kind: ${identity.principalKind}`);
12860
- if (identity.principalKind === "agent")
12861
- out.log(
12862
- `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
12863
- );
12864
- out.log(`owner: ${accountableOwner(identity)}`);
12865
- out.log(`owner id: ${identity.developerId}`);
12866
- out.log(`email: ${identity.email ?? "(none)"}`);
12867
- out.log(`credential: ${credentialLabel(identity)}`);
12868
- if (identity.credential.id)
12869
- out.log(`credential id: ${identity.credential.id}`);
12870
- out.log(`admin: ${identity.admin ? "yes" : "no"}`);
12871
- if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
12872
- if (!identity.admin) {
12873
- if (identity.scopes.includes("platform:runbook:write")) {
12874
- out.log("\nThis exact scope can read and edit all platform runbook content.");
12875
- out.log("The device token is not ambient admin and cannot change visibility.");
12876
- } else {
12877
- out.log("\nYou can read operator-visible platform runbooks but not edit them.");
12878
- out.log("Use a platform-admin-approved runbook write request to edit.");
12879
- }
12880
- }
12881
- }
12882
- var text;
12883
- var init_whoami_command = __esm({
12884
- "src/whoami-command.ts"() {
12885
- "use strict";
12886
- init_cjs_shims();
12887
- init_argv();
12888
- init_operator_context();
12889
- init_token();
12890
- text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
12891
- }
12892
- });
12893
-
12894
13004
  // src/runbook-command.ts
12895
13005
  function requireSlug(slug, action2) {
12896
13006
  if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
@@ -13863,6 +13973,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13863
13973
  await whoamiCommand(parsed, runtime);
13864
13974
  return;
13865
13975
  }
13976
+ if (command === "auth") {
13977
+ await authCommand(parsed, runtime);
13978
+ return;
13979
+ }
13866
13980
  if (command === "context") {
13867
13981
  await contextCommand(parsed, runtime);
13868
13982
  return;
@@ -13903,6 +14017,15 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13903
14017
  await pmCommand(parsed, runtime);
13904
14018
  return;
13905
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
+ }
13906
14029
  if (command === "discuss") {
13907
14030
  await discussCommand(parsed, runtime);
13908
14031
  return;
@@ -13989,6 +14112,7 @@ var init_cli = __esm({
13989
14112
  "use strict";
13990
14113
  init_cjs_shims();
13991
14114
  init_admin_command();
14115
+ init_auth_command();
13992
14116
  init_agent_command();
13993
14117
  init_app_lifecycle();
13994
14118
  init_brand_command();