@odla-ai/cli 0.27.17 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, {
@@ -598,11 +599,12 @@ async function resumePendingHandshake(ctx, waitMs) {
598
599
  async function freshHandshake(ctx, waitMs) {
599
600
  let started;
600
601
  let stopReminder;
602
+ const provisioning = ctx.grantIntent.optionalProjectCapabilities.includes("app.manage");
601
603
  try {
602
604
  return await (0, import_db.requestToken)({
603
605
  endpoint: ctx.cfg.platformUrl,
604
606
  email: ctx.email,
605
- label: `${ctx.cfg.app.id} provisioner`,
607
+ label: `${ctx.cfg.app.id} ${provisioning ? "provisioner" : "agent"}`,
606
608
  agentHandle: projectAgentHandle(ctx.cfg.app.id),
607
609
  projectIds: [ctx.cfg.app.id],
608
610
  optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
@@ -663,8 +665,15 @@ function stillPending(pending, email) {
663
665
  }
664
666
  function handshakeEmail(value2, cached) {
665
667
  const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
668
+ if (/@users\.noreply\.github\.com$/i.test(email)) {
669
+ throw new Error(
670
+ `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
671
+ );
672
+ }
666
673
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
667
- throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
674
+ throw new Error(
675
+ "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"
676
+ );
668
677
  }
669
678
  return email;
670
679
  }
@@ -1958,6 +1967,223 @@ var init_admin_command = __esm({
1958
1967
  }
1959
1968
  });
1960
1969
 
1970
+ // src/whoami-command.ts
1971
+ function principalKind(value2, machine) {
1972
+ return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
1973
+ }
1974
+ function credentialKind(value2, machine, scopes) {
1975
+ if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
1976
+ if (machine) return "machine";
1977
+ if (scopes.length) return "device";
1978
+ return "unknown";
1979
+ }
1980
+ function managerOf(value2) {
1981
+ if (!value2 || typeof value2 !== "object") return null;
1982
+ const row = value2;
1983
+ const principalId = text(row.principalId);
1984
+ if (!principalId) return null;
1985
+ return {
1986
+ principalId,
1987
+ displayName: text(row.displayName) ?? "Unnamed member",
1988
+ handle: text(row.handle) ?? ""
1989
+ };
1990
+ }
1991
+ function unnamedPrincipal(kind) {
1992
+ if (kind === "agent") return "Unnamed agent";
1993
+ if (kind === "service") return "Unnamed service";
1994
+ return "Unnamed member";
1995
+ }
1996
+ async function fetchIdentity(platformUrl, token, doFetch) {
1997
+ const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
1998
+ headers: { authorization: `Bearer ${token}` }
1999
+ });
2000
+ if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
2001
+ const body = await res.json();
2002
+ const developerId = text(body.developerId) ?? "";
2003
+ const machine = body.machine === true;
2004
+ const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
2005
+ const principalId = text(body.principalId) ?? developerId;
2006
+ const email = text(body.email);
2007
+ const kind = principalKind(body.principalKind, machine);
2008
+ const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
2009
+ const handle = text(body.handle) ?? "";
2010
+ const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
2011
+ return {
2012
+ developerId,
2013
+ principalId,
2014
+ principalKind: kind,
2015
+ displayName,
2016
+ handle,
2017
+ manager: managerOf(body.manager),
2018
+ credential: {
2019
+ id: text(credential2.id),
2020
+ kind: credentialKind(credential2.kind, machine, scopes)
2021
+ },
2022
+ email,
2023
+ admin: body.admin === true,
2024
+ machine,
2025
+ scopes
2026
+ };
2027
+ }
2028
+ function credentialLabel(identity) {
2029
+ if (identity.credential.kind === "machine") return "machine (platform admin secret)";
2030
+ if (identity.credential.kind === "device")
2031
+ return identity.scopes.length ? "device (scoped)" : "device";
2032
+ if (identity.credential.kind === "clerk") return "clerk";
2033
+ return "unknown (legacy server)";
2034
+ }
2035
+ function namedPrincipal(identity) {
2036
+ return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
2037
+ }
2038
+ function namedManager(manager) {
2039
+ return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
2040
+ }
2041
+ function accountableOwner(identity) {
2042
+ if (identity.principalKind === "agent" && identity.manager) {
2043
+ return namedManager(identity.manager);
2044
+ }
2045
+ if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
2046
+ return namedPrincipal(identity);
2047
+ }
2048
+ return identity.email ?? "Unnamed member";
2049
+ }
2050
+ async function whoamiCommand(parsed, deps = {}) {
2051
+ assertArgs(
2052
+ parsed,
2053
+ ["config", "context", "platform", "token", "email", "json", "open"],
2054
+ 1
2055
+ );
2056
+ const out = deps.stdout ?? console;
2057
+ const doFetch = deps.fetch ?? fetch;
2058
+ const { cfg } = await resolveOperatorContext(parsed, {
2059
+ allowMissingConfig: true
2060
+ });
2061
+ const token = await getDeveloperToken(
2062
+ cfg,
2063
+ {
2064
+ configPath: cfg.configPath,
2065
+ token: stringOpt(parsed.options.token),
2066
+ email: stringOpt(parsed.options.email),
2067
+ // As above: never suppress the browser for an ordinary sign-in.
2068
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
2069
+ openApprovalUrl: deps.openUrl
2070
+ },
2071
+ doFetch,
2072
+ out
2073
+ );
2074
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
2075
+ if (parsed.options.json === true) {
2076
+ out.log(JSON.stringify(identity, null, 2));
2077
+ return;
2078
+ }
2079
+ out.log(`platform: ${cfg.platformUrl}`);
2080
+ out.log(`principal: ${namedPrincipal(identity)}`);
2081
+ out.log(`principal id: ${identity.principalId}`);
2082
+ out.log(`kind: ${identity.principalKind}`);
2083
+ if (identity.principalKind === "agent")
2084
+ out.log(
2085
+ `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
2086
+ );
2087
+ out.log(`owner: ${accountableOwner(identity)}`);
2088
+ out.log(`owner id: ${identity.developerId}`);
2089
+ out.log(`email: ${identity.email ?? "(none)"}`);
2090
+ out.log(`credential: ${credentialLabel(identity)}`);
2091
+ if (identity.credential.id)
2092
+ out.log(`credential id: ${identity.credential.id}`);
2093
+ out.log(`admin: ${identity.admin ? "yes" : "no"}`);
2094
+ if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
2095
+ if (!identity.admin) {
2096
+ if (identity.scopes.includes("platform:runbook:write")) {
2097
+ out.log("\nThis exact scope can read and edit all platform runbook content.");
2098
+ out.log("The device token is not ambient admin and cannot change visibility.");
2099
+ } else {
2100
+ out.log("\nYou can read operator-visible platform runbooks but not edit them.");
2101
+ out.log("Use a platform-admin-approved runbook write request to edit.");
2102
+ }
2103
+ }
2104
+ }
2105
+ var text;
2106
+ var init_whoami_command = __esm({
2107
+ "src/whoami-command.ts"() {
2108
+ "use strict";
2109
+ init_cjs_shims();
2110
+ init_argv();
2111
+ init_operator_context();
2112
+ init_token();
2113
+ text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
2114
+ }
2115
+ });
2116
+
2117
+ // src/auth-command.ts
2118
+ async function authCommand(parsed, deps = {}) {
2119
+ assertArgs(parsed, [
2120
+ "config",
2121
+ "context",
2122
+ "platform",
2123
+ "app",
2124
+ "email",
2125
+ "open",
2126
+ "wait",
2127
+ "json"
2128
+ ], 2);
2129
+ const action2 = parsed.positionals[1] ?? "login";
2130
+ if (action2 !== "login") {
2131
+ throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2132
+ }
2133
+ const context = await resolveOperatorContext(parsed, {
2134
+ allowMissingConfig: true,
2135
+ requireApp: true
2136
+ });
2137
+ const { cfg } = context;
2138
+ const out = deps.stdout ?? console;
2139
+ const doFetch = deps.fetch ?? fetch;
2140
+ const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
2141
+ if (!email) {
2142
+ throw new Error(
2143
+ "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
2144
+ );
2145
+ }
2146
+ const token = await getDeveloperToken(
2147
+ cfg,
2148
+ {
2149
+ configPath: cfg.configPath,
2150
+ email,
2151
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
2152
+ wait: numberOpt(parsed.options.wait, "--wait"),
2153
+ openApprovalUrl: deps.openUrl
2154
+ },
2155
+ doFetch,
2156
+ out,
2157
+ { freshLogin: true }
2158
+ );
2159
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
2160
+ if (parsed.options.json === true) {
2161
+ out.log(JSON.stringify({
2162
+ principalId: identity.principalId,
2163
+ displayName: identity.displayName,
2164
+ handle: identity.handle,
2165
+ email: identity.email,
2166
+ appId: cfg.app.id
2167
+ }, null, 2));
2168
+ return;
2169
+ }
2170
+ const handle = identity.handle ? ` (@${identity.handle})` : "";
2171
+ out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
2172
+ out.log(`odla account: ${identity.email ?? "not returned"}`);
2173
+ }
2174
+ var import_node_process10;
2175
+ var init_auth_command = __esm({
2176
+ "src/auth-command.ts"() {
2177
+ "use strict";
2178
+ init_cjs_shims();
2179
+ import_node_process10 = __toESM(require("process"), 1);
2180
+ init_argv();
2181
+ init_operator_context();
2182
+ init_token();
2183
+ init_whoami_command();
2184
+ }
2185
+ });
2186
+
1961
2187
  // src/tenant.ts
1962
2188
  function resolveEnv(cfg, requested) {
1963
2189
  const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
@@ -4917,6 +5143,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
4917
5143
  and file bugs when you notice them. The conventions and the full command set are
4918
5144
  in \`.agents/skills/odla/references/pm.md\`.
4919
5145
 
5146
+ Use the human's signed-in odla account email for device authorization; never
5147
+ infer it from git config, commit metadata, or GitHub. If authorization is not
5148
+ already active, run
5149
+ \`npx @odla-ai/cli auth login --app <appId> --email <odla-account>\` and open
5150
+ the exact Studio URL it prints. Never file an odla project or product defect in
5151
+ GitHub Issues: run \`npx @odla-ai/cli bug report --app <appId> ...\` so the bug
5152
+ lands in odla PM with the rest of the project's goals, tasks, and decisions.
5153
+
4920
5154
  The setup runbooks and their references are installed in this repository, pinned
4921
5155
  to this CLI version. Use them as your setup context.
4922
5156
 
@@ -9103,7 +9337,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
9103
9337
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
9104
9338
  const source = clean3(
9105
9339
  stringOpt(parsed.options.token)
9106
- ) ? "flag" : clean3(import_node_process10.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9340
+ ) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
9107
9341
  return {
9108
9342
  source,
9109
9343
  cacheFile: context.cfg.local.tokenFile,
@@ -9114,12 +9348,12 @@ function clean3(value2) {
9114
9348
  const normalized = value2?.trim();
9115
9349
  return normalized || void 0;
9116
9350
  }
9117
- var import_node_process10;
9351
+ var import_node_process11;
9118
9352
  var init_operator_credentials = __esm({
9119
9353
  "src/operator-credentials.ts"() {
9120
9354
  "use strict";
9121
9355
  init_cjs_shims();
9122
- import_node_process10 = __toESM(require("process"), 1);
9356
+ import_node_process11 = __toESM(require("process"), 1);
9123
9357
  init_argv();
9124
9358
  init_local();
9125
9359
  }
@@ -9285,6 +9519,7 @@ Start here:
9285
9519
  step it made wrong.
9286
9520
 
9287
9521
  Usage:
9522
+ odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
9288
9523
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
9289
9524
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
9290
9525
  odla-ai doctor [--config odla.config.mjs]
@@ -9321,6 +9556,7 @@ Usage:
9321
9556
  odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
9322
9557
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
9323
9558
  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]
9559
+ odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
9324
9560
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
9325
9561
  odla-ai pm <goal|task|decision|bug> ref <id> [--json]
9326
9562
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
@@ -9402,6 +9638,9 @@ function printHelp(output = console) {
9402
9638
  output.log(`odla-ai
9403
9639
  ${USAGE_SECTION}
9404
9640
  Commands:
9641
+ auth Start a fresh, exact-project agent authorization in the browser.
9642
+ The email is the signed-in odla account, never git or GitHub
9643
+ identity. The approval screen confirms the agent name first.
9405
9644
  agent Inspect durable agent wakeups and explicitly requeue a
9406
9645
  dead-lettered job; JSON output is stable for remote operators.
9407
9646
  runbook odla's operational procedures, stored in the database and read at
@@ -9469,6 +9708,8 @@ Commands:
9469
9708
  "app". Entities: goal (alias conformance), task (alias kanban),
9470
9709
  decision, bug. Status changes and comments post to each item's
9471
9710
  @odla-ai/chat discussion thread.
9711
+ bug Intent-first alias for PM bugs. "bug report" writes to
9712
+ odla PM; odla product defects do not belong in GitHub Issues.
9472
9713
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
9473
9714
  one group per project, topics with replies, @-mentions of people,
9474
9715
  agents, PM items, and projects. Built for unattended use \u2014 post a
@@ -9526,8 +9767,12 @@ Safety:
9526
9767
  wait for approval, and re-run to collect.
9527
9768
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
9528
9769
  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
9770
+ token. It is the email shown by the signed-in odla account \u2014 never infer it
9771
+ from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
9530
9772
  review the exact code, and finish any current request before claiming another.
9773
+ Use "auth login --app <id> --email <odla-account>" when an outside agent needs
9774
+ a deliberate fresh request; it ignores cached credentials and opens the same
9775
+ focused authorization sequence used by every first-time command.
9531
9776
  If provision reports that the current agent principal has no live app.manage
9532
9777
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
9533
9778
  the local cache, prints and opens a fresh exact-project owner-review URL, then
@@ -10064,7 +10309,8 @@ async function buildContext(parsed, deps) {
10064
10309
  configPath: cfg.configPath,
10065
10310
  token: stringOpt(parsed.options.token),
10066
10311
  email: stringOpt(parsed.options.email),
10067
- open: false
10312
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10313
+ openApprovalUrl: deps.openUrl
10068
10314
  },
10069
10315
  doFetch,
10070
10316
  out
@@ -10144,7 +10390,8 @@ var init_discuss_command = __esm({
10144
10390
  "jsonl",
10145
10391
  "mutation-id",
10146
10392
  "platform",
10147
- "context"
10393
+ "context",
10394
+ "open"
10148
10395
  ];
10149
10396
  }
10150
10397
  });
@@ -10731,7 +10978,13 @@ async function buildContext2(parsed, deps) {
10731
10978
  const out = deps.stdout ?? console;
10732
10979
  const token = await getDeveloperToken(
10733
10980
  cfg,
10734
- { configPath: cfg.configPath, token: stringOpt(parsed.options.token), email: stringOpt(parsed.options.email), open: false },
10981
+ {
10982
+ configPath: cfg.configPath,
10983
+ token: stringOpt(parsed.options.token),
10984
+ email: stringOpt(parsed.options.email),
10985
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10986
+ openApprovalUrl: deps.openUrl
10987
+ },
10735
10988
  doFetch,
10736
10989
  out
10737
10990
  );
@@ -10829,7 +11082,7 @@ var init_pm_command = __esm({
10829
11082
  decision: "decision",
10830
11083
  bug: "bug"
10831
11084
  };
10832
- COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
11085
+ COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
10833
11086
  ACTION_OPTIONS = {
10834
11087
  list: ["app", "q", "limit", "offset"],
10835
11088
  add: ["app", "title", "mutation-id"],
@@ -11285,7 +11538,8 @@ async function o11yCommand(parsed, deps = {}) {
11285
11538
  "json",
11286
11539
  "app",
11287
11540
  "env",
11288
- "minutes"
11541
+ "minutes",
11542
+ "open"
11289
11543
  ],
11290
11544
  2
11291
11545
  );
@@ -11312,7 +11566,8 @@ async function o11yCommand(parsed, deps = {}) {
11312
11566
  configPath: cfg.configPath,
11313
11567
  token: stringOpt(parsed.options.token),
11314
11568
  email: stringOpt(parsed.options.email),
11315
- open: false
11569
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
11570
+ openApprovalUrl: deps.openUrl
11316
11571
  },
11317
11572
  doFetch,
11318
11573
  out
@@ -11766,7 +12021,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11766
12021
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
11767
12022
  }
11768
12023
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
11769
- const key = import_node_process11.default.env[cfg.ai.keyEnv];
12024
+ const key = import_node_process12.default.env[cfg.ai.keyEnv];
11770
12025
  if (key) {
11771
12026
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
11772
12027
  await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -11805,14 +12060,14 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
11805
12060
  }
11806
12061
  }
11807
12062
  }
11808
- var import_apps12, import_ai4, import_node_process11;
12063
+ var import_apps12, import_ai4, import_node_process12;
11809
12064
  var init_provision = __esm({
11810
12065
  "src/provision.ts"() {
11811
12066
  "use strict";
11812
12067
  init_cjs_shims();
11813
12068
  import_apps12 = require("@odla-ai/apps");
11814
12069
  import_ai4 = require("@odla-ai/ai");
11815
- import_node_process11 = __toESM(require("process"), 1);
12070
+ import_node_process12 = __toESM(require("process"), 1);
11816
12071
  init_config();
11817
12072
  init_calendar();
11818
12073
  init_calendar_errors();
@@ -11923,7 +12178,9 @@ var init_surface = __esm({
11923
12178
  promote: {},
11924
12179
  owners: { list: {}, add: {}, remove: {} }
11925
12180
  },
12181
+ auth: { login: {} },
11926
12182
  brand: { design: { unpack: {} } },
12183
+ bug: { create: {}, list: {}, report: {} },
11927
12184
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
11928
12185
  capabilities: {},
11929
12186
  code: { connect: {} },
@@ -11995,7 +12252,7 @@ var init_surface = __esm({
11995
12252
 
11996
12253
  // src/record.ts
11997
12254
  function recordInvocation(parsed) {
11998
- const file = import_node_process12.default.env.ODLA_CLI_RECORD;
12255
+ const file = import_node_process13.default.env.ODLA_CLI_RECORD;
11999
12256
  if (!file) return;
12000
12257
  try {
12001
12258
  const entry = {
@@ -12008,13 +12265,13 @@ function recordInvocation(parsed) {
12008
12265
  } catch {
12009
12266
  }
12010
12267
  }
12011
- var import_node_fs17, import_node_process12;
12268
+ var import_node_fs17, import_node_process13;
12012
12269
  var init_record = __esm({
12013
12270
  "src/record.ts"() {
12014
12271
  "use strict";
12015
12272
  init_cjs_shims();
12016
12273
  import_node_fs17 = require("fs");
12017
- import_node_process12 = __toESM(require("process"), 1);
12274
+ import_node_process13 = __toESM(require("process"), 1);
12018
12275
  init_surface();
12019
12276
  }
12020
12277
  });
@@ -12673,7 +12930,7 @@ var init_runbook_search_command = __esm({
12673
12930
  });
12674
12931
 
12675
12932
  // src/runbook-editor.ts
12676
- function resolveEditor(env = import_node_process13.default.env) {
12933
+ function resolveEditor(env = import_node_process14.default.env) {
12677
12934
  for (const name of EDITOR_ENV) {
12678
12935
  const value2 = env[name];
12679
12936
  if (value2 && value2.trim()) return value2.trim();
@@ -12687,8 +12944,8 @@ function defaultRun(command, path) {
12687
12944
  return result.status ?? 0;
12688
12945
  }
12689
12946
  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));
12947
+ const env = deps.env ?? import_node_process14.default.env;
12948
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
12692
12949
  const editor = resolveEditor(env);
12693
12950
  if (!editor)
12694
12951
  throw new Error(
@@ -12708,7 +12965,7 @@ function editText(initial, slug, deps = {}) {
12708
12965
  (0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
12709
12966
  }
12710
12967
  }
12711
- var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19, import_node_process13, EDITOR_ENV, defaultRunOrInjected;
12968
+ var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path19, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
12712
12969
  var init_runbook_editor = __esm({
12713
12970
  "src/runbook-editor.ts"() {
12714
12971
  "use strict";
@@ -12717,7 +12974,7 @@ var init_runbook_editor = __esm({
12717
12974
  import_node_fs21 = require("fs");
12718
12975
  import_node_os5 = require("os");
12719
12976
  import_node_path19 = require("path");
12720
- import_node_process13 = __toESM(require("process"), 1);
12977
+ import_node_process14 = __toESM(require("process"), 1);
12721
12978
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
12722
12979
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
12723
12980
  }
@@ -12745,152 +13002,6 @@ var init_runbook_edit_flow = __esm({
12745
13002
  }
12746
13003
  });
12747
13004
 
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
13005
  // src/runbook-command.ts
12895
13006
  function requireSlug(slug, action2) {
12896
13007
  if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
@@ -13863,6 +13974,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13863
13974
  await whoamiCommand(parsed, runtime);
13864
13975
  return;
13865
13976
  }
13977
+ if (command === "auth") {
13978
+ await authCommand(parsed, runtime);
13979
+ return;
13980
+ }
13866
13981
  if (command === "context") {
13867
13982
  await contextCommand(parsed, runtime);
13868
13983
  return;
@@ -13903,6 +14018,15 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
13903
14018
  await pmCommand(parsed, runtime);
13904
14019
  return;
13905
14020
  }
14021
+ if (command === "bug") {
14022
+ const action2 = parsed.positionals[1] ?? "list";
14023
+ const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
14024
+ await pmCommand({
14025
+ ...parsed,
14026
+ positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
14027
+ }, runtime);
14028
+ return;
14029
+ }
13906
14030
  if (command === "discuss") {
13907
14031
  await discussCommand(parsed, runtime);
13908
14032
  return;
@@ -13989,6 +14113,7 @@ var init_cli = __esm({
13989
14113
  "use strict";
13990
14114
  init_cjs_shims();
13991
14115
  init_admin_command();
14116
+ init_auth_command();
13992
14117
  init_agent_command();
13993
14118
  init_app_lifecycle();
13994
14119
  init_brand_command();