@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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-3WVVHH3Y.js";
4
+ } from "./chunk-STJCWZJU.js";
5
5
  import {
6
6
  exitCodeFor
7
7
  } from "./chunk-UKLSRQ5J.js";
@@ -9,4 +9,4 @@ export {
9
9
  exitCodeFor,
10
10
  runCli
11
11
  };
12
- //# sourceMappingURL=cli-DCSVQAZ6.js.map
12
+ //# sourceMappingURL=cli-2WZIFXA6.js.map
package/dist/index.cjs CHANGED
@@ -391,7 +391,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
391
391
  const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
392
392
  const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
393
393
  const cached = readJsonFile(cfg.local.tokenFile);
394
- if (!grantRequest.forceReview) {
394
+ if (!grantRequest.forceReview && !grantRequest.freshLogin) {
395
395
  if (options.token) return options.token;
396
396
  if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
397
397
  const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
@@ -408,9 +408,9 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
408
408
  }
409
409
  } else {
410
410
  if (options.token) {
411
- throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
411
+ 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");
412
412
  }
413
- out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
413
+ 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}"`);
414
414
  }
415
415
  const ctx = {
416
416
  cfg,
@@ -423,6 +423,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
423
423
  grantIntent
424
424
  };
425
425
  const waitMs = handshakeWaitMs(options.wait);
426
+ if (grantRequest.freshLogin) clearPendingHandshake(ctx.pendingFile);
426
427
  const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
427
428
  clearPendingHandshake(ctx.pendingFile);
428
429
  writePrivateJson(cfg.local.tokenFile, {
@@ -480,11 +481,12 @@ async function resumePendingHandshake(ctx, waitMs) {
480
481
  async function freshHandshake(ctx, waitMs) {
481
482
  let started;
482
483
  let stopReminder;
484
+ const provisioning = ctx.grantIntent.optionalProjectCapabilities.includes("app.manage");
483
485
  try {
484
486
  return await (0, import_db.requestToken)({
485
487
  endpoint: ctx.cfg.platformUrl,
486
488
  email: ctx.email,
487
- label: `${ctx.cfg.app.id} provisioner`,
489
+ label: `${ctx.cfg.app.id} ${provisioning ? "provisioner" : "agent"}`,
488
490
  agentHandle: projectAgentHandle(ctx.cfg.app.id),
489
491
  projectIds: [ctx.cfg.app.id],
490
492
  optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
@@ -545,8 +547,15 @@ function stillPending(pending, email) {
545
547
  }
546
548
  function handshakeEmail(value2, cached) {
547
549
  const email = (value2 ?? import_node_process4.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
550
+ if (/@users\.noreply\.github\.com$/i.test(email)) {
551
+ throw new Error(
552
+ `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
553
+ );
554
+ }
548
555
  if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
549
- throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
556
+ throw new Error(
557
+ "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"
558
+ );
550
559
  }
551
560
  return email;
552
561
  }
@@ -1714,6 +1723,204 @@ async function adminCommand(parsed, deps = {}) {
1714
1723
  });
1715
1724
  }
1716
1725
 
1726
+ // src/auth-command.ts
1727
+ var import_node_process10 = __toESM(require("process"), 1);
1728
+
1729
+ // src/whoami-command.ts
1730
+ var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
1731
+ function principalKind(value2, machine) {
1732
+ return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
1733
+ }
1734
+ function credentialKind(value2, machine, scopes) {
1735
+ if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
1736
+ if (machine) return "machine";
1737
+ if (scopes.length) return "device";
1738
+ return "unknown";
1739
+ }
1740
+ function managerOf(value2) {
1741
+ if (!value2 || typeof value2 !== "object") return null;
1742
+ const row = value2;
1743
+ const principalId = text(row.principalId);
1744
+ if (!principalId) return null;
1745
+ return {
1746
+ principalId,
1747
+ displayName: text(row.displayName) ?? "Unnamed member",
1748
+ handle: text(row.handle) ?? ""
1749
+ };
1750
+ }
1751
+ function unnamedPrincipal(kind) {
1752
+ if (kind === "agent") return "Unnamed agent";
1753
+ if (kind === "service") return "Unnamed service";
1754
+ return "Unnamed member";
1755
+ }
1756
+ async function fetchIdentity(platformUrl, token, doFetch) {
1757
+ const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
1758
+ headers: { authorization: `Bearer ${token}` }
1759
+ });
1760
+ if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
1761
+ const body = await res.json();
1762
+ const developerId = text(body.developerId) ?? "";
1763
+ const machine = body.machine === true;
1764
+ const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
1765
+ const principalId = text(body.principalId) ?? developerId;
1766
+ const email = text(body.email);
1767
+ const kind = principalKind(body.principalKind, machine);
1768
+ const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
1769
+ const handle = text(body.handle) ?? "";
1770
+ const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
1771
+ return {
1772
+ developerId,
1773
+ principalId,
1774
+ principalKind: kind,
1775
+ displayName,
1776
+ handle,
1777
+ manager: managerOf(body.manager),
1778
+ credential: {
1779
+ id: text(credential2.id),
1780
+ kind: credentialKind(credential2.kind, machine, scopes)
1781
+ },
1782
+ email,
1783
+ admin: body.admin === true,
1784
+ machine,
1785
+ scopes
1786
+ };
1787
+ }
1788
+ function credentialLabel(identity) {
1789
+ if (identity.credential.kind === "machine") return "machine (platform admin secret)";
1790
+ if (identity.credential.kind === "device")
1791
+ return identity.scopes.length ? "device (scoped)" : "device";
1792
+ if (identity.credential.kind === "clerk") return "clerk";
1793
+ return "unknown (legacy server)";
1794
+ }
1795
+ function namedPrincipal(identity) {
1796
+ return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
1797
+ }
1798
+ function namedManager(manager) {
1799
+ return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
1800
+ }
1801
+ function accountableOwner(identity) {
1802
+ if (identity.principalKind === "agent" && identity.manager) {
1803
+ return namedManager(identity.manager);
1804
+ }
1805
+ if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
1806
+ return namedPrincipal(identity);
1807
+ }
1808
+ return identity.email ?? "Unnamed member";
1809
+ }
1810
+ async function whoamiCommand(parsed, deps = {}) {
1811
+ assertArgs(
1812
+ parsed,
1813
+ ["config", "context", "platform", "token", "email", "json", "open"],
1814
+ 1
1815
+ );
1816
+ const out = deps.stdout ?? console;
1817
+ const doFetch = deps.fetch ?? fetch;
1818
+ const { cfg } = await resolveOperatorContext(parsed, {
1819
+ allowMissingConfig: true
1820
+ });
1821
+ const token = await getDeveloperToken(
1822
+ cfg,
1823
+ {
1824
+ configPath: cfg.configPath,
1825
+ token: stringOpt(parsed.options.token),
1826
+ email: stringOpt(parsed.options.email),
1827
+ // As above: never suppress the browser for an ordinary sign-in.
1828
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1829
+ openApprovalUrl: deps.openUrl
1830
+ },
1831
+ doFetch,
1832
+ out
1833
+ );
1834
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
1835
+ if (parsed.options.json === true) {
1836
+ out.log(JSON.stringify(identity, null, 2));
1837
+ return;
1838
+ }
1839
+ out.log(`platform: ${cfg.platformUrl}`);
1840
+ out.log(`principal: ${namedPrincipal(identity)}`);
1841
+ out.log(`principal id: ${identity.principalId}`);
1842
+ out.log(`kind: ${identity.principalKind}`);
1843
+ if (identity.principalKind === "agent")
1844
+ out.log(
1845
+ `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
1846
+ );
1847
+ out.log(`owner: ${accountableOwner(identity)}`);
1848
+ out.log(`owner id: ${identity.developerId}`);
1849
+ out.log(`email: ${identity.email ?? "(none)"}`);
1850
+ out.log(`credential: ${credentialLabel(identity)}`);
1851
+ if (identity.credential.id)
1852
+ out.log(`credential id: ${identity.credential.id}`);
1853
+ out.log(`admin: ${identity.admin ? "yes" : "no"}`);
1854
+ if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
1855
+ if (!identity.admin) {
1856
+ if (identity.scopes.includes("platform:runbook:write")) {
1857
+ out.log("\nThis exact scope can read and edit all platform runbook content.");
1858
+ out.log("The device token is not ambient admin and cannot change visibility.");
1859
+ } else {
1860
+ out.log("\nYou can read operator-visible platform runbooks but not edit them.");
1861
+ out.log("Use a platform-admin-approved runbook write request to edit.");
1862
+ }
1863
+ }
1864
+ }
1865
+
1866
+ // src/auth-command.ts
1867
+ async function authCommand(parsed, deps = {}) {
1868
+ assertArgs(parsed, [
1869
+ "config",
1870
+ "context",
1871
+ "platform",
1872
+ "app",
1873
+ "email",
1874
+ "open",
1875
+ "wait",
1876
+ "json"
1877
+ ], 2);
1878
+ const action2 = parsed.positionals[1] ?? "login";
1879
+ if (action2 !== "login") {
1880
+ throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
1881
+ }
1882
+ const context = await resolveOperatorContext(parsed, {
1883
+ allowMissingConfig: true,
1884
+ requireApp: true
1885
+ });
1886
+ const { cfg } = context;
1887
+ const out = deps.stdout ?? console;
1888
+ const doFetch = deps.fetch ?? fetch;
1889
+ const email = stringOpt(parsed.options.email) ?? import_node_process10.default.env.ODLA_USER_EMAIL?.trim();
1890
+ if (!email) {
1891
+ throw new Error(
1892
+ "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
1893
+ );
1894
+ }
1895
+ const token = await getDeveloperToken(
1896
+ cfg,
1897
+ {
1898
+ configPath: cfg.configPath,
1899
+ email,
1900
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
1901
+ wait: numberOpt(parsed.options.wait, "--wait"),
1902
+ openApprovalUrl: deps.openUrl
1903
+ },
1904
+ doFetch,
1905
+ out,
1906
+ { freshLogin: true }
1907
+ );
1908
+ const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
1909
+ if (parsed.options.json === true) {
1910
+ out.log(JSON.stringify({
1911
+ principalId: identity.principalId,
1912
+ displayName: identity.displayName,
1913
+ handle: identity.handle,
1914
+ email: identity.email,
1915
+ appId: cfg.app.id
1916
+ }, null, 2));
1917
+ return;
1918
+ }
1919
+ const handle = identity.handle ? ` (@${identity.handle})` : "";
1920
+ out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
1921
+ out.log(`odla account: ${identity.email ?? "not returned"}`);
1922
+ }
1923
+
1717
1924
  // src/tenant.ts
1718
1925
  var import_apps2 = require("@odla-ai/apps");
1719
1926
  function resolveEnv(cfg, requested) {
@@ -4402,6 +4609,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
4402
4609
  and file bugs when you notice them. The conventions and the full command set are
4403
4610
  in \`.agents/skills/odla/references/pm.md\`.
4404
4611
 
4612
+ Use the human's signed-in odla account email for device authorization; never
4613
+ infer it from git config, commit metadata, or GitHub. If authorization is not
4614
+ already active, run
4615
+ \`npx @odla-ai/cli auth login --app <appId> --email <odla-account>\` and open
4616
+ the exact Studio URL it prints. Never file an odla project or product defect in
4617
+ GitHub Issues: run \`npx @odla-ai/cli bug report --app <appId> ...\` so the bug
4618
+ lands in odla PM with the rest of the project's goals, tasks, and decisions.
4619
+
4405
4620
  The setup runbooks and their references are installed in this repository, pinned
4406
4621
  to this CLI version. Use them as your setup context.
4407
4622
 
@@ -8467,13 +8682,13 @@ async function codeCommand(parsed, dependencies) {
8467
8682
  }
8468
8683
 
8469
8684
  // src/operator-credentials.ts
8470
- var import_node_process10 = __toESM(require("process"), 1);
8685
+ var import_node_process11 = __toESM(require("process"), 1);
8471
8686
  function developerTokenStatus(context, parsed, now = Date.now()) {
8472
8687
  const cached = readJsonFile(context.cfg.local.tokenFile);
8473
8688
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
8474
8689
  const source = clean3(
8475
8690
  stringOpt(parsed.options.token)
8476
- ) ? "flag" : clean3(import_node_process10.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8691
+ ) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
8477
8692
  return {
8478
8693
  source,
8479
8694
  cacheFile: context.cfg.local.tokenFile,
@@ -8630,6 +8845,7 @@ Start here:
8630
8845
  step it made wrong.
8631
8846
 
8632
8847
  Usage:
8848
+ odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
8633
8849
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8634
8850
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
8635
8851
  odla-ai doctor [--config odla.config.mjs]
@@ -8666,6 +8882,7 @@ Usage:
8666
8882
  odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
8667
8883
  odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
8668
8884
  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]
8885
+ odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
8669
8886
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
8670
8887
  odla-ai pm <goal|task|decision|bug> ref <id> [--json]
8671
8888
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
@@ -8745,6 +8962,9 @@ function printHelp(output = console) {
8745
8962
  output.log(`odla-ai
8746
8963
  ${USAGE_SECTION}
8747
8964
  Commands:
8965
+ auth Start a fresh, exact-project agent authorization in the browser.
8966
+ The email is the signed-in odla account, never git or GitHub
8967
+ identity. The approval screen confirms the agent name first.
8748
8968
  agent Inspect durable agent wakeups and explicitly requeue a
8749
8969
  dead-lettered job; JSON output is stable for remote operators.
8750
8970
  runbook odla's operational procedures, stored in the database and read at
@@ -8812,6 +9032,8 @@ Commands:
8812
9032
  "app". Entities: goal (alias conformance), task (alias kanban),
8813
9033
  decision, bug. Status changes and comments post to each item's
8814
9034
  @odla-ai/chat discussion thread.
9035
+ bug Intent-first alias for PM bugs. "bug report" writes to
9036
+ odla PM; odla product defects do not belong in GitHub Issues.
8815
9037
  discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
8816
9038
  one group per project, topics with replies, @-mentions of people,
8817
9039
  agents, PM items, and projects. Built for unattended use \u2014 post a
@@ -8869,8 +9091,12 @@ Safety:
8869
9091
  wait for approval, and re-run to collect.
8870
9092
  A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
8871
9093
  The email is a non-secret identity hint: never provide a password or session
8872
- token. The matching account must already exist, be signed in, explicitly
9094
+ token. It is the email shown by the signed-in odla account \u2014 never infer it
9095
+ from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
8873
9096
  review the exact code, and finish any current request before claiming another.
9097
+ Use "auth login --app <id> --email <odla-account>" when an outside agent needs
9098
+ a deliberate fresh request; it ignores cached credentials and opens the same
9099
+ focused authorization sequence used by every first-time command.
8874
9100
  If provision reports that the current agent principal has no live app.manage
8875
9101
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
8876
9102
  the local cache, prints and opens a fresh exact-project owner-review URL, then
@@ -9364,7 +9590,8 @@ var ALLOWED = [
9364
9590
  "jsonl",
9365
9591
  "mutation-id",
9366
9592
  "platform",
9367
- "context"
9593
+ "context",
9594
+ "open"
9368
9595
  ];
9369
9596
  function requireId(id, action2) {
9370
9597
  if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
@@ -9383,7 +9610,8 @@ async function buildContext(parsed, deps) {
9383
9610
  configPath: cfg.configPath,
9384
9611
  token: stringOpt(parsed.options.token),
9385
9612
  email: stringOpt(parsed.options.email),
9386
- open: false
9613
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
9614
+ openApprovalUrl: deps.openUrl
9387
9615
  },
9388
9616
  doFetch,
9389
9617
  out
@@ -9958,7 +10186,7 @@ var ALIASES = {
9958
10186
  decision: "decision",
9959
10187
  bug: "bug"
9960
10188
  };
9961
- var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
10189
+ var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
9962
10190
  var ACTION_OPTIONS = {
9963
10191
  list: ["app", "q", "limit", "offset"],
9964
10192
  add: ["app", "title", "mutation-id"],
@@ -10022,7 +10250,13 @@ async function buildContext2(parsed, deps) {
10022
10250
  const out = deps.stdout ?? console;
10023
10251
  const token = await getDeveloperToken(
10024
10252
  cfg,
10025
- { configPath: cfg.configPath, token: stringOpt(parsed.options.token), email: stringOpt(parsed.options.email), open: false },
10253
+ {
10254
+ configPath: cfg.configPath,
10255
+ token: stringOpt(parsed.options.token),
10256
+ email: stringOpt(parsed.options.email),
10257
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10258
+ openApprovalUrl: deps.openUrl
10259
+ },
10026
10260
  doFetch,
10027
10261
  out
10028
10262
  );
@@ -10486,7 +10720,8 @@ async function o11yCommand(parsed, deps = {}) {
10486
10720
  "json",
10487
10721
  "app",
10488
10722
  "env",
10489
- "minutes"
10723
+ "minutes",
10724
+ "open"
10490
10725
  ],
10491
10726
  2
10492
10727
  );
@@ -10513,7 +10748,8 @@ async function o11yCommand(parsed, deps = {}) {
10513
10748
  configPath: cfg.configPath,
10514
10749
  token: stringOpt(parsed.options.token),
10515
10750
  email: stringOpt(parsed.options.email),
10516
- open: false
10751
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
10752
+ openApprovalUrl: deps.openUrl
10517
10753
  },
10518
10754
  doFetch,
10519
10755
  out
@@ -10624,7 +10860,7 @@ async function read2(url, headers, doFetch) {
10624
10860
  // src/provision.ts
10625
10861
  var import_apps12 = require("@odla-ai/apps");
10626
10862
  var import_ai4 = require("@odla-ai/ai");
10627
- var import_node_process11 = __toESM(require("process"), 1);
10863
+ var import_node_process12 = __toESM(require("process"), 1);
10628
10864
 
10629
10865
  // src/integration-provision.ts
10630
10866
  var import_db3 = require("@odla-ai/db");
@@ -10944,7 +11180,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
10944
11180
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
10945
11181
  }
10946
11182
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
10947
- const key = import_node_process11.default.env[cfg.ai.keyEnv];
11183
+ const key = import_node_process12.default.env[cfg.ai.keyEnv];
10948
11184
  if (key) {
10949
11185
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
10950
11186
  await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -10986,7 +11222,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
10986
11222
 
10987
11223
  // src/record.ts
10988
11224
  var import_node_fs15 = require("fs");
10989
- var import_node_process12 = __toESM(require("process"), 1);
11225
+ var import_node_process13 = __toESM(require("process"), 1);
10990
11226
 
10991
11227
  // src/surface.ts
10992
11228
  var PM_ACTIONS = {
@@ -11043,7 +11279,9 @@ var COMMAND_SURFACE = {
11043
11279
  promote: {},
11044
11280
  owners: { list: {}, add: {}, remove: {} }
11045
11281
  },
11282
+ auth: { login: {} },
11046
11283
  brand: { design: { unpack: {} } },
11284
+ bug: { create: {}, list: {}, report: {} },
11047
11285
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
11048
11286
  capabilities: {},
11049
11287
  code: { connect: {} },
@@ -11158,7 +11396,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
11158
11396
 
11159
11397
  // src/record.ts
11160
11398
  function recordInvocation(parsed) {
11161
- const file = import_node_process12.default.env.ODLA_CLI_RECORD;
11399
+ const file = import_node_process13.default.env.ODLA_CLI_RECORD;
11162
11400
  if (!file) return;
11163
11401
  try {
11164
11402
  const entry = {
@@ -11830,9 +12068,9 @@ var import_node_child_process8 = require("child_process");
11830
12068
  var import_node_fs19 = require("fs");
11831
12069
  var import_node_os5 = require("os");
11832
12070
  var import_node_path18 = require("path");
11833
- var import_node_process13 = __toESM(require("process"), 1);
12071
+ var import_node_process14 = __toESM(require("process"), 1);
11834
12072
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
11835
- function resolveEditor(env = import_node_process13.default.env) {
12073
+ function resolveEditor(env = import_node_process14.default.env) {
11836
12074
  for (const name of EDITOR_ENV) {
11837
12075
  const value2 = env[name];
11838
12076
  if (value2 && value2.trim()) return value2.trim();
@@ -11846,8 +12084,8 @@ function defaultRun(command, path) {
11846
12084
  return result.status ?? 0;
11847
12085
  }
11848
12086
  function editText(initial, slug, deps = {}) {
11849
- const env = deps.env ?? import_node_process13.default.env;
11850
- const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
12087
+ const env = deps.env ?? import_node_process14.default.env;
12088
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
11851
12089
  const editor = resolveEditor(env);
11852
12090
  if (!editor)
11853
12091
  throw new Error(
@@ -11883,142 +12121,6 @@ async function editRunbook(ctx, slug, deps = {}) {
11883
12121
  return body === null ? null : { body, expectedVersion: found.version };
11884
12122
  }
11885
12123
 
11886
- // src/whoami-command.ts
11887
- var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
11888
- function principalKind(value2, machine) {
11889
- return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
11890
- }
11891
- function credentialKind(value2, machine, scopes) {
11892
- if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
11893
- if (machine) return "machine";
11894
- if (scopes.length) return "device";
11895
- return "unknown";
11896
- }
11897
- function managerOf(value2) {
11898
- if (!value2 || typeof value2 !== "object") return null;
11899
- const row = value2;
11900
- const principalId = text(row.principalId);
11901
- if (!principalId) return null;
11902
- return {
11903
- principalId,
11904
- displayName: text(row.displayName) ?? "Unnamed member",
11905
- handle: text(row.handle) ?? ""
11906
- };
11907
- }
11908
- function unnamedPrincipal(kind) {
11909
- if (kind === "agent") return "Unnamed agent";
11910
- if (kind === "service") return "Unnamed service";
11911
- return "Unnamed member";
11912
- }
11913
- async function fetchIdentity(platformUrl, token, doFetch) {
11914
- const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
11915
- headers: { authorization: `Bearer ${token}` }
11916
- });
11917
- if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
11918
- const body = await res.json();
11919
- const developerId = text(body.developerId) ?? "";
11920
- const machine = body.machine === true;
11921
- const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
11922
- const principalId = text(body.principalId) ?? developerId;
11923
- const email = text(body.email);
11924
- const kind = principalKind(body.principalKind, machine);
11925
- const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
11926
- const handle = text(body.handle) ?? "";
11927
- const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
11928
- return {
11929
- developerId,
11930
- principalId,
11931
- principalKind: kind,
11932
- displayName,
11933
- handle,
11934
- manager: managerOf(body.manager),
11935
- credential: {
11936
- id: text(credential2.id),
11937
- kind: credentialKind(credential2.kind, machine, scopes)
11938
- },
11939
- email,
11940
- admin: body.admin === true,
11941
- machine,
11942
- scopes
11943
- };
11944
- }
11945
- function credentialLabel(identity) {
11946
- if (identity.credential.kind === "machine") return "machine (platform admin secret)";
11947
- if (identity.credential.kind === "device")
11948
- return identity.scopes.length ? "device (scoped)" : "device";
11949
- if (identity.credential.kind === "clerk") return "clerk";
11950
- return "unknown (legacy server)";
11951
- }
11952
- function namedPrincipal(identity) {
11953
- return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
11954
- }
11955
- function namedManager(manager) {
11956
- return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
11957
- }
11958
- function accountableOwner(identity) {
11959
- if (identity.principalKind === "agent" && identity.manager) {
11960
- return namedManager(identity.manager);
11961
- }
11962
- if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
11963
- return namedPrincipal(identity);
11964
- }
11965
- return identity.email ?? "Unnamed member";
11966
- }
11967
- async function whoamiCommand(parsed, deps = {}) {
11968
- assertArgs(
11969
- parsed,
11970
- ["config", "context", "platform", "token", "email", "json"],
11971
- 1
11972
- );
11973
- const out = deps.stdout ?? console;
11974
- const doFetch = deps.fetch ?? fetch;
11975
- const { cfg } = await resolveOperatorContext(parsed, {
11976
- allowMissingConfig: true
11977
- });
11978
- const token = await getDeveloperToken(
11979
- cfg,
11980
- {
11981
- configPath: cfg.configPath,
11982
- token: stringOpt(parsed.options.token),
11983
- email: stringOpt(parsed.options.email),
11984
- // As above: never suppress the browser for an ordinary sign-in.
11985
- open: void 0
11986
- },
11987
- doFetch,
11988
- out
11989
- );
11990
- const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
11991
- if (parsed.options.json === true) {
11992
- out.log(JSON.stringify(identity, null, 2));
11993
- return;
11994
- }
11995
- out.log(`platform: ${cfg.platformUrl}`);
11996
- out.log(`principal: ${namedPrincipal(identity)}`);
11997
- out.log(`principal id: ${identity.principalId}`);
11998
- out.log(`kind: ${identity.principalKind}`);
11999
- if (identity.principalKind === "agent")
12000
- out.log(
12001
- `manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
12002
- );
12003
- out.log(`owner: ${accountableOwner(identity)}`);
12004
- out.log(`owner id: ${identity.developerId}`);
12005
- out.log(`email: ${identity.email ?? "(none)"}`);
12006
- out.log(`credential: ${credentialLabel(identity)}`);
12007
- if (identity.credential.id)
12008
- out.log(`credential id: ${identity.credential.id}`);
12009
- out.log(`admin: ${identity.admin ? "yes" : "no"}`);
12010
- if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
12011
- if (!identity.admin) {
12012
- if (identity.scopes.includes("platform:runbook:write")) {
12013
- out.log("\nThis exact scope can read and edit all platform runbook content.");
12014
- out.log("The device token is not ambient admin and cannot change visibility.");
12015
- } else {
12016
- out.log("\nYou can read operator-visible platform runbooks but not edit them.");
12017
- out.log("Use a platform-admin-approved runbook write request to edit.");
12018
- }
12019
- }
12020
- }
12021
-
12022
12124
  // src/runbook-command.ts
12023
12125
  var ALLOWED2 = [
12024
12126
  "config",
@@ -12914,6 +13016,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
12914
13016
  await whoamiCommand(parsed, runtime);
12915
13017
  return;
12916
13018
  }
13019
+ if (command === "auth") {
13020
+ await authCommand(parsed, runtime);
13021
+ return;
13022
+ }
12917
13023
  if (command === "context") {
12918
13024
  await contextCommand(parsed, runtime);
12919
13025
  return;
@@ -12954,6 +13060,15 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
12954
13060
  await pmCommand(parsed, runtime);
12955
13061
  return;
12956
13062
  }
13063
+ if (command === "bug") {
13064
+ const action2 = parsed.positionals[1] ?? "list";
13065
+ const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
13066
+ await pmCommand({
13067
+ ...parsed,
13068
+ positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
13069
+ }, runtime);
13070
+ return;
13071
+ }
12957
13072
  if (command === "discuss") {
12958
13073
  await discussCommand(parsed, runtime);
12959
13074
  return;