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