@odla-ai/cli 0.27.15 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/bin.cjs +396 -190
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MR5QXX3B.js → chunk-HCNESRIA.js} +357 -169
- package/dist/chunk-HCNESRIA.js.map +1 -0
- package/dist/{cli-2RZFZRRT.js → cli-DDOFM47D.js} +2 -2
- package/dist/index.cjs +363 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +3 -1
- package/dist/runtime/pi-agent.js +15 -314577
- package/package.json +2 -2
- package/dist/chunk-MR5QXX3B.js.map +0 -1
- /package/dist/{cli-2RZFZRRT.js.map → cli-DDOFM47D.js.map} +0 -0
|
@@ -304,7 +304,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
304
304
|
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
305
305
|
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
306
306
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
307
|
-
if (!grantRequest.forceReview) {
|
|
307
|
+
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
308
308
|
if (options.token) return options.token;
|
|
309
309
|
if (process5.env.ODLA_DEV_TOKEN) {
|
|
310
310
|
const declared = process5.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
@@ -321,9 +321,9 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
321
321
|
}
|
|
322
322
|
} else {
|
|
323
323
|
if (options.token) {
|
|
324
|
-
throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
|
|
324
|
+
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");
|
|
325
325
|
}
|
|
326
|
-
out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
|
|
326
|
+
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}"`);
|
|
327
327
|
}
|
|
328
328
|
const ctx = {
|
|
329
329
|
cfg,
|
|
@@ -336,6 +336,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
336
336
|
grantIntent
|
|
337
337
|
};
|
|
338
338
|
const waitMs = handshakeWaitMs(options.wait);
|
|
339
|
+
if (grantRequest.freshLogin) clearPendingHandshake(ctx.pendingFile);
|
|
339
340
|
const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
|
|
340
341
|
clearPendingHandshake(ctx.pendingFile);
|
|
341
342
|
writePrivateJson(cfg.local.tokenFile, {
|
|
@@ -458,8 +459,15 @@ function stillPending(pending, email) {
|
|
|
458
459
|
}
|
|
459
460
|
function handshakeEmail(value2, cached) {
|
|
460
461
|
const email = (value2 ?? process5.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
462
|
+
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
461
467
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
462
|
-
throw new Error(
|
|
468
|
+
throw new Error(
|
|
469
|
+
"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"
|
|
470
|
+
);
|
|
463
471
|
}
|
|
464
472
|
return email;
|
|
465
473
|
}
|
|
@@ -1627,6 +1635,204 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1627
1635
|
});
|
|
1628
1636
|
}
|
|
1629
1637
|
|
|
1638
|
+
// src/auth-command.ts
|
|
1639
|
+
import process11 from "process";
|
|
1640
|
+
|
|
1641
|
+
// src/whoami-command.ts
|
|
1642
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
1643
|
+
function principalKind(value2, machine) {
|
|
1644
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
1645
|
+
}
|
|
1646
|
+
function credentialKind(value2, machine, scopes) {
|
|
1647
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
1648
|
+
if (machine) return "machine";
|
|
1649
|
+
if (scopes.length) return "device";
|
|
1650
|
+
return "unknown";
|
|
1651
|
+
}
|
|
1652
|
+
function managerOf(value2) {
|
|
1653
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
1654
|
+
const row = value2;
|
|
1655
|
+
const principalId = text(row.principalId);
|
|
1656
|
+
if (!principalId) return null;
|
|
1657
|
+
return {
|
|
1658
|
+
principalId,
|
|
1659
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
1660
|
+
handle: text(row.handle) ?? ""
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function unnamedPrincipal(kind) {
|
|
1664
|
+
if (kind === "agent") return "Unnamed agent";
|
|
1665
|
+
if (kind === "service") return "Unnamed service";
|
|
1666
|
+
return "Unnamed member";
|
|
1667
|
+
}
|
|
1668
|
+
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
1669
|
+
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
1670
|
+
headers: { authorization: `Bearer ${token}` }
|
|
1671
|
+
});
|
|
1672
|
+
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
1673
|
+
const body = await res.json();
|
|
1674
|
+
const developerId = text(body.developerId) ?? "";
|
|
1675
|
+
const machine = body.machine === true;
|
|
1676
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
1677
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
1678
|
+
const email = text(body.email);
|
|
1679
|
+
const kind = principalKind(body.principalKind, machine);
|
|
1680
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
1681
|
+
const handle = text(body.handle) ?? "";
|
|
1682
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
1683
|
+
return {
|
|
1684
|
+
developerId,
|
|
1685
|
+
principalId,
|
|
1686
|
+
principalKind: kind,
|
|
1687
|
+
displayName,
|
|
1688
|
+
handle,
|
|
1689
|
+
manager: managerOf(body.manager),
|
|
1690
|
+
credential: {
|
|
1691
|
+
id: text(credential2.id),
|
|
1692
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
1693
|
+
},
|
|
1694
|
+
email,
|
|
1695
|
+
admin: body.admin === true,
|
|
1696
|
+
machine,
|
|
1697
|
+
scopes
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
function credentialLabel(identity) {
|
|
1701
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
1702
|
+
if (identity.credential.kind === "device")
|
|
1703
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
1704
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
1705
|
+
return "unknown (legacy server)";
|
|
1706
|
+
}
|
|
1707
|
+
function namedPrincipal(identity) {
|
|
1708
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
1709
|
+
}
|
|
1710
|
+
function namedManager(manager) {
|
|
1711
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
1712
|
+
}
|
|
1713
|
+
function accountableOwner(identity) {
|
|
1714
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
1715
|
+
return namedManager(identity.manager);
|
|
1716
|
+
}
|
|
1717
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
1718
|
+
return namedPrincipal(identity);
|
|
1719
|
+
}
|
|
1720
|
+
return identity.email ?? "Unnamed member";
|
|
1721
|
+
}
|
|
1722
|
+
async function whoamiCommand(parsed, deps = {}) {
|
|
1723
|
+
assertArgs(
|
|
1724
|
+
parsed,
|
|
1725
|
+
["config", "context", "platform", "token", "email", "json", "open"],
|
|
1726
|
+
1
|
|
1727
|
+
);
|
|
1728
|
+
const out = deps.stdout ?? console;
|
|
1729
|
+
const doFetch = deps.fetch ?? fetch;
|
|
1730
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
1731
|
+
allowMissingConfig: true
|
|
1732
|
+
});
|
|
1733
|
+
const token = await getDeveloperToken(
|
|
1734
|
+
cfg,
|
|
1735
|
+
{
|
|
1736
|
+
configPath: cfg.configPath,
|
|
1737
|
+
token: stringOpt(parsed.options.token),
|
|
1738
|
+
email: stringOpt(parsed.options.email),
|
|
1739
|
+
// As above: never suppress the browser for an ordinary sign-in.
|
|
1740
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1741
|
+
openApprovalUrl: deps.openUrl
|
|
1742
|
+
},
|
|
1743
|
+
doFetch,
|
|
1744
|
+
out
|
|
1745
|
+
);
|
|
1746
|
+
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
1747
|
+
if (parsed.options.json === true) {
|
|
1748
|
+
out.log(JSON.stringify(identity, null, 2));
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
out.log(`platform: ${cfg.platformUrl}`);
|
|
1752
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
1753
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
1754
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
1755
|
+
if (identity.principalKind === "agent")
|
|
1756
|
+
out.log(
|
|
1757
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
1758
|
+
);
|
|
1759
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
1760
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
1761
|
+
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
1762
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
1763
|
+
if (identity.credential.id)
|
|
1764
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
1765
|
+
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
1766
|
+
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
1767
|
+
if (!identity.admin) {
|
|
1768
|
+
if (identity.scopes.includes("platform:runbook:write")) {
|
|
1769
|
+
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
1770
|
+
out.log("The device token is not ambient admin and cannot change visibility.");
|
|
1771
|
+
} else {
|
|
1772
|
+
out.log("\nYou can read operator-visible platform runbooks but not edit them.");
|
|
1773
|
+
out.log("Use a platform-admin-approved runbook write request to edit.");
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// src/auth-command.ts
|
|
1779
|
+
async function authCommand(parsed, deps = {}) {
|
|
1780
|
+
assertArgs(parsed, [
|
|
1781
|
+
"config",
|
|
1782
|
+
"context",
|
|
1783
|
+
"platform",
|
|
1784
|
+
"app",
|
|
1785
|
+
"email",
|
|
1786
|
+
"open",
|
|
1787
|
+
"wait",
|
|
1788
|
+
"json"
|
|
1789
|
+
], 2);
|
|
1790
|
+
const action2 = parsed.positionals[1] ?? "login";
|
|
1791
|
+
if (action2 !== "login") {
|
|
1792
|
+
throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
|
|
1793
|
+
}
|
|
1794
|
+
const context = await resolveOperatorContext(parsed, {
|
|
1795
|
+
allowMissingConfig: true,
|
|
1796
|
+
requireApp: true
|
|
1797
|
+
});
|
|
1798
|
+
const { cfg } = context;
|
|
1799
|
+
const out = deps.stdout ?? console;
|
|
1800
|
+
const doFetch = deps.fetch ?? fetch;
|
|
1801
|
+
const email = stringOpt(parsed.options.email) ?? process11.env.ODLA_USER_EMAIL?.trim();
|
|
1802
|
+
if (!email) {
|
|
1803
|
+
throw new Error(
|
|
1804
|
+
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
const token = await getDeveloperToken(
|
|
1808
|
+
cfg,
|
|
1809
|
+
{
|
|
1810
|
+
configPath: cfg.configPath,
|
|
1811
|
+
email,
|
|
1812
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1813
|
+
wait: numberOpt(parsed.options.wait, "--wait"),
|
|
1814
|
+
openApprovalUrl: deps.openUrl
|
|
1815
|
+
},
|
|
1816
|
+
doFetch,
|
|
1817
|
+
out,
|
|
1818
|
+
{ freshLogin: true }
|
|
1819
|
+
);
|
|
1820
|
+
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
1821
|
+
if (parsed.options.json === true) {
|
|
1822
|
+
out.log(JSON.stringify({
|
|
1823
|
+
principalId: identity.principalId,
|
|
1824
|
+
displayName: identity.displayName,
|
|
1825
|
+
handle: identity.handle,
|
|
1826
|
+
email: identity.email,
|
|
1827
|
+
appId: cfg.app.id
|
|
1828
|
+
}, null, 2));
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
const handle = identity.handle ? ` (@${identity.handle})` : "";
|
|
1832
|
+
out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
|
|
1833
|
+
out.log(`odla account: ${identity.email ?? "not returned"}`);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1630
1836
|
// src/tenant.ts
|
|
1631
1837
|
import { tenantIdFor } from "@odla-ai/apps";
|
|
1632
1838
|
function resolveEnv(cfg, requested) {
|
|
@@ -2577,6 +2783,63 @@ function printGroup(out, heading, items) {
|
|
|
2577
2783
|
out.log("");
|
|
2578
2784
|
}
|
|
2579
2785
|
|
|
2786
|
+
// src/ai-models.ts
|
|
2787
|
+
import { DEFAULT_CATALOG } from "@odla-ai/ai";
|
|
2788
|
+
async function aiModels(options = {}) {
|
|
2789
|
+
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
2790
|
+
const env = options.env ?? cfg.envs[0] ?? "dev";
|
|
2791
|
+
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
2792
|
+
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
2793
|
+
url.searchParams.set("env", env);
|
|
2794
|
+
const response2 = await (options.fetch ?? fetch)(url);
|
|
2795
|
+
if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
|
|
2796
|
+
const body = await response2.json();
|
|
2797
|
+
if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
|
|
2798
|
+
const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
|
|
2799
|
+
const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
|
|
2800
|
+
let models;
|
|
2801
|
+
if (mode === "hosted") {
|
|
2802
|
+
if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
|
|
2803
|
+
if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
|
|
2804
|
+
throw new Error("platform returned an invalid hosted AI model catalog");
|
|
2805
|
+
}
|
|
2806
|
+
models = body.ai.models;
|
|
2807
|
+
} else {
|
|
2808
|
+
const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
|
|
2809
|
+
if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
|
|
2810
|
+
models = Object.values(DEFAULT_CATALOG).filter((model) => model.provider === provider);
|
|
2811
|
+
}
|
|
2812
|
+
if (options.provider) models = models.filter((model) => model.provider === options.provider);
|
|
2813
|
+
models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
|
|
2814
|
+
const out = options.stdout ?? console;
|
|
2815
|
+
if (options.json) {
|
|
2816
|
+
out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
out.log("provider model default capabilities");
|
|
2820
|
+
for (const model of models) {
|
|
2821
|
+
out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function capabilityList(model) {
|
|
2825
|
+
return [
|
|
2826
|
+
model.capabilities.imageIn ? "image" : "",
|
|
2827
|
+
model.capabilities.audioIn ? "audio" : "",
|
|
2828
|
+
model.capabilities.documentIn ? "document" : "",
|
|
2829
|
+
model.capabilities.toolUse ? "tools" : "",
|
|
2830
|
+
model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
|
|
2831
|
+
model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
|
|
2832
|
+
].filter(Boolean).join(",");
|
|
2833
|
+
}
|
|
2834
|
+
function isModelSpec(value2) {
|
|
2835
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
|
|
2836
|
+
const model = value2;
|
|
2837
|
+
return typeof model.id === "string" && typeof model.nativeId === "string" && (model.provider === "anthropic" || model.provider === "openai" || model.provider === "google") && Boolean(model.capabilities) && typeof model.capabilities === "object";
|
|
2838
|
+
}
|
|
2839
|
+
async function safeText4(response2) {
|
|
2840
|
+
return (await response2.text().catch(() => "request failed")).slice(0, 300);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2580
2843
|
// src/config-operation-command.ts
|
|
2581
2844
|
import {
|
|
2582
2845
|
AppsError,
|
|
@@ -2768,7 +3031,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2768
3031
|
});
|
|
2769
3032
|
if (res.ok || res.status === 404) return;
|
|
2770
3033
|
if (res.status === 403) {
|
|
2771
|
-
const detail = await
|
|
3034
|
+
const detail = await safeText5(res);
|
|
2772
3035
|
const code = errorCode(detail);
|
|
2773
3036
|
if (code === "human_session_required") {
|
|
2774
3037
|
throw new Error(
|
|
@@ -2784,7 +3047,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2784
3047
|
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
|
|
2785
3048
|
);
|
|
2786
3049
|
}
|
|
2787
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await
|
|
3050
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
2788
3051
|
}
|
|
2789
3052
|
function errorCode(text2) {
|
|
2790
3053
|
try {
|
|
@@ -2800,7 +3063,7 @@ async function postJson(doFetch, url, bearer, body) {
|
|
|
2800
3063
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
2801
3064
|
body: JSON.stringify(body)
|
|
2802
3065
|
});
|
|
2803
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await
|
|
3066
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
2804
3067
|
}
|
|
2805
3068
|
function normalizeClerkConfig(value2) {
|
|
2806
3069
|
if (!value2) return null;
|
|
@@ -2813,7 +3076,7 @@ function normalizeClerkConfig(value2) {
|
|
|
2813
3076
|
const publishableKey = envValue(cfg.publishableKey);
|
|
2814
3077
|
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
2815
3078
|
}
|
|
2816
|
-
async function
|
|
3079
|
+
async function safeText5(res) {
|
|
2817
3080
|
try {
|
|
2818
3081
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
2819
3082
|
} catch {
|
|
@@ -4215,6 +4478,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
|
|
|
4215
4478
|
and file bugs when you notice them. The conventions and the full command set are
|
|
4216
4479
|
in \`.agents/skills/odla/references/pm.md\`.
|
|
4217
4480
|
|
|
4481
|
+
Use the human's signed-in odla account email for device authorization; never
|
|
4482
|
+
infer it from git config, commit metadata, or GitHub. If authorization is not
|
|
4483
|
+
already active, run
|
|
4484
|
+
\`npx @odla-ai/cli auth login --app <appId> --email <odla-account>\` and open
|
|
4485
|
+
the exact Studio URL it prints. Never file an odla project or product defect in
|
|
4486
|
+
GitHub Issues: run \`npx @odla-ai/cli bug report --app <appId> ...\` so the bug
|
|
4487
|
+
lands in odla PM with the rest of the project's goals, tasks, and decisions.
|
|
4488
|
+
|
|
4218
4489
|
The setup runbooks and their references are installed in this repository, pinned
|
|
4219
4490
|
to this CLI version. Use them as your setup context.
|
|
4220
4491
|
|
|
@@ -4583,7 +4854,7 @@ async function getJson(doFetch, url, bearer) {
|
|
|
4583
4854
|
const res = await doFetch(url, {
|
|
4584
4855
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
4585
4856
|
});
|
|
4586
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4857
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4587
4858
|
return res.json();
|
|
4588
4859
|
}
|
|
4589
4860
|
async function postJson2(doFetch, url, bearer, body) {
|
|
@@ -4592,7 +4863,7 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
4592
4863
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
4593
4864
|
body: JSON.stringify(body)
|
|
4594
4865
|
});
|
|
4595
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4866
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4596
4867
|
return res.json();
|
|
4597
4868
|
}
|
|
4598
4869
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -4600,7 +4871,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
4600
4871
|
url.searchParams.set("env", env);
|
|
4601
4872
|
return url.toString();
|
|
4602
4873
|
}
|
|
4603
|
-
async function
|
|
4874
|
+
async function safeText6(res) {
|
|
4604
4875
|
try {
|
|
4605
4876
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
4606
4877
|
} catch {
|
|
@@ -4656,6 +4927,20 @@ async function secretsCommand(parsed, deps) {
|
|
|
4656
4927
|
});
|
|
4657
4928
|
}
|
|
4658
4929
|
async function projectCommand(command, parsed, deps) {
|
|
4930
|
+
if (command === "ai") {
|
|
4931
|
+
const sub = parsed.positionals[1];
|
|
4932
|
+
if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
|
|
4933
|
+
assertArgs(parsed, ["config", "env", "provider", "json"], 2);
|
|
4934
|
+
await aiModels({
|
|
4935
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4936
|
+
env: stringOpt(parsed.options.env),
|
|
4937
|
+
provider: stringOpt(parsed.options.provider),
|
|
4938
|
+
json: parsed.options.json === true,
|
|
4939
|
+
fetch: deps.fetch,
|
|
4940
|
+
stdout: deps.stdout
|
|
4941
|
+
});
|
|
4942
|
+
return true;
|
|
4943
|
+
}
|
|
4659
4944
|
if (command === "config") {
|
|
4660
4945
|
const sub = parsed.positionals[1];
|
|
4661
4946
|
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
@@ -8266,13 +8551,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8266
8551
|
}
|
|
8267
8552
|
|
|
8268
8553
|
// src/operator-credentials.ts
|
|
8269
|
-
import
|
|
8554
|
+
import process12 from "process";
|
|
8270
8555
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8271
8556
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8272
8557
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8273
8558
|
const source = clean3(
|
|
8274
8559
|
stringOpt(parsed.options.token)
|
|
8275
|
-
) ? "flag" : clean3(
|
|
8560
|
+
) ? "flag" : clean3(process12.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8276
8561
|
return {
|
|
8277
8562
|
source,
|
|
8278
8563
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -8429,9 +8714,11 @@ Start here:
|
|
|
8429
8714
|
step it made wrong.
|
|
8430
8715
|
|
|
8431
8716
|
Usage:
|
|
8717
|
+
odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
|
|
8432
8718
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8433
8719
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
8434
8720
|
odla-ai doctor [--config odla.config.mjs]
|
|
8721
|
+
odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
|
|
8435
8722
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8436
8723
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8437
8724
|
odla-ai operations get <operation-id> [--json]
|
|
@@ -8464,6 +8751,7 @@ Usage:
|
|
|
8464
8751
|
odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
8465
8752
|
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
8466
8753
|
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]
|
|
8754
|
+
odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
|
|
8467
8755
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
8468
8756
|
odla-ai pm <goal|task|decision|bug> ref <id> [--json]
|
|
8469
8757
|
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
@@ -8543,6 +8831,9 @@ function printHelp(output = console) {
|
|
|
8543
8831
|
output.log(`odla-ai
|
|
8544
8832
|
${USAGE_SECTION}
|
|
8545
8833
|
Commands:
|
|
8834
|
+
auth Start a fresh, exact-project agent authorization in the browser.
|
|
8835
|
+
The email is the signed-in odla account, never git or GitHub
|
|
8836
|
+
identity. The approval screen confirms the agent name first.
|
|
8546
8837
|
agent Inspect durable agent wakeups and explicitly requeue a
|
|
8547
8838
|
dead-lettered job; JSON output is stable for remote operators.
|
|
8548
8839
|
runbook odla's operational procedures, stored in the database and read at
|
|
@@ -8610,6 +8901,8 @@ Commands:
|
|
|
8610
8901
|
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
8611
8902
|
decision, bug. Status changes and comments post to each item's
|
|
8612
8903
|
@odla-ai/chat discussion thread.
|
|
8904
|
+
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
8905
|
+
odla PM; odla product defects do not belong in GitHub Issues.
|
|
8613
8906
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
8614
8907
|
one group per project, topics with replies, @-mentions of people,
|
|
8615
8908
|
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
@@ -8667,8 +8960,12 @@ Safety:
|
|
|
8667
8960
|
wait for approval, and re-run to collect.
|
|
8668
8961
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8669
8962
|
The email is a non-secret identity hint: never provide a password or session
|
|
8670
|
-
token.
|
|
8963
|
+
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
8964
|
+
from git config, a commit author, or GitHub. The matching account must already exist, be signed in, explicitly
|
|
8671
8965
|
review the exact code, and finish any current request before claiming another.
|
|
8966
|
+
Use "auth login --app <id> --email <odla-account>" when an outside agent needs
|
|
8967
|
+
a deliberate fresh request; it ignores cached credentials and opens the same
|
|
8968
|
+
focused authorization sequence used by every first-time command.
|
|
8672
8969
|
If provision reports that the current agent principal has no live app.manage
|
|
8673
8970
|
grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
|
|
8674
8971
|
the local cache, prints and opens a fresh exact-project owner-review URL, then
|
|
@@ -9162,7 +9459,8 @@ var ALLOWED = [
|
|
|
9162
9459
|
"jsonl",
|
|
9163
9460
|
"mutation-id",
|
|
9164
9461
|
"platform",
|
|
9165
|
-
"context"
|
|
9462
|
+
"context",
|
|
9463
|
+
"open"
|
|
9166
9464
|
];
|
|
9167
9465
|
function requireId(id, action2) {
|
|
9168
9466
|
if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
@@ -9181,7 +9479,8 @@ async function buildContext(parsed, deps) {
|
|
|
9181
9479
|
configPath: cfg.configPath,
|
|
9182
9480
|
token: stringOpt(parsed.options.token),
|
|
9183
9481
|
email: stringOpt(parsed.options.email),
|
|
9184
|
-
open: false
|
|
9482
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9483
|
+
openApprovalUrl: deps.openUrl
|
|
9185
9484
|
},
|
|
9186
9485
|
doFetch,
|
|
9187
9486
|
out
|
|
@@ -9756,7 +10055,7 @@ var ALIASES = {
|
|
|
9756
10055
|
decision: "decision",
|
|
9757
10056
|
bug: "bug"
|
|
9758
10057
|
};
|
|
9759
|
-
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
10058
|
+
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
|
|
9760
10059
|
var ACTION_OPTIONS = {
|
|
9761
10060
|
list: ["app", "q", "limit", "offset"],
|
|
9762
10061
|
add: ["app", "title", "mutation-id"],
|
|
@@ -9820,7 +10119,13 @@ async function buildContext2(parsed, deps) {
|
|
|
9820
10119
|
const out = deps.stdout ?? console;
|
|
9821
10120
|
const token = await getDeveloperToken(
|
|
9822
10121
|
cfg,
|
|
9823
|
-
{
|
|
10122
|
+
{
|
|
10123
|
+
configPath: cfg.configPath,
|
|
10124
|
+
token: stringOpt(parsed.options.token),
|
|
10125
|
+
email: stringOpt(parsed.options.email),
|
|
10126
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10127
|
+
openApprovalUrl: deps.openUrl
|
|
10128
|
+
},
|
|
9824
10129
|
doFetch,
|
|
9825
10130
|
out
|
|
9826
10131
|
);
|
|
@@ -10284,7 +10589,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
10284
10589
|
"json",
|
|
10285
10590
|
"app",
|
|
10286
10591
|
"env",
|
|
10287
|
-
"minutes"
|
|
10592
|
+
"minutes",
|
|
10593
|
+
"open"
|
|
10288
10594
|
],
|
|
10289
10595
|
2
|
|
10290
10596
|
);
|
|
@@ -10311,7 +10617,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
10311
10617
|
configPath: cfg.configPath,
|
|
10312
10618
|
token: stringOpt(parsed.options.token),
|
|
10313
10619
|
email: stringOpt(parsed.options.email),
|
|
10314
|
-
open: false
|
|
10620
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10621
|
+
openApprovalUrl: deps.openUrl
|
|
10315
10622
|
},
|
|
10316
10623
|
doFetch,
|
|
10317
10624
|
out
|
|
@@ -10422,7 +10729,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10422
10729
|
// src/provision.ts
|
|
10423
10730
|
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor5 } from "@odla-ai/apps";
|
|
10424
10731
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
10425
|
-
import
|
|
10732
|
+
import process13 from "process";
|
|
10426
10733
|
|
|
10427
10734
|
// src/integration-provision.ts
|
|
10428
10735
|
import { uuidv7 } from "@odla-ai/db";
|
|
@@ -10535,14 +10842,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
10535
10842
|
appId: tenantId
|
|
10536
10843
|
})
|
|
10537
10844
|
});
|
|
10538
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
10845
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
|
|
10539
10846
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
10540
10847
|
method: "POST",
|
|
10541
10848
|
headers,
|
|
10542
10849
|
body: "{}"
|
|
10543
10850
|
});
|
|
10544
10851
|
}
|
|
10545
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
10852
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10546
10853
|
const body = await res.json();
|
|
10547
10854
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
10548
10855
|
return body.key;
|
|
@@ -10558,12 +10865,12 @@ async function issueO11yToken(opts) {
|
|
|
10558
10865
|
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
|
|
10559
10866
|
);
|
|
10560
10867
|
}
|
|
10561
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
10868
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10562
10869
|
const body = await res.json();
|
|
10563
10870
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
10564
10871
|
return body.token;
|
|
10565
10872
|
}
|
|
10566
|
-
async function
|
|
10873
|
+
async function safeText7(res) {
|
|
10567
10874
|
try {
|
|
10568
10875
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
10569
10876
|
} catch {
|
|
@@ -10742,7 +11049,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10742
11049
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10743
11050
|
}
|
|
10744
11051
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10745
|
-
const key =
|
|
11052
|
+
const key = process13.env[cfg.ai.keyEnv];
|
|
10746
11053
|
if (key) {
|
|
10747
11054
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10748
11055
|
await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10784,7 +11091,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10784
11091
|
|
|
10785
11092
|
// src/record.ts
|
|
10786
11093
|
import { appendFileSync } from "fs";
|
|
10787
|
-
import
|
|
11094
|
+
import process14 from "process";
|
|
10788
11095
|
|
|
10789
11096
|
// src/surface.ts
|
|
10790
11097
|
var PM_ACTIONS = {
|
|
@@ -10818,6 +11125,7 @@ var PM_ENTITIES = {
|
|
|
10818
11125
|
};
|
|
10819
11126
|
var COMMAND_SURFACE = {
|
|
10820
11127
|
agent: { jobs: {}, retry: {} },
|
|
11128
|
+
ai: { models: {} },
|
|
10821
11129
|
admin: {
|
|
10822
11130
|
ai: {
|
|
10823
11131
|
show: {},
|
|
@@ -10840,7 +11148,9 @@ var COMMAND_SURFACE = {
|
|
|
10840
11148
|
promote: {},
|
|
10841
11149
|
owners: { list: {}, add: {}, remove: {} }
|
|
10842
11150
|
},
|
|
11151
|
+
auth: { login: {} },
|
|
10843
11152
|
brand: { design: { unpack: {} } },
|
|
11153
|
+
bug: { create: {}, list: {}, report: {} },
|
|
10844
11154
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10845
11155
|
capabilities: {},
|
|
10846
11156
|
code: { connect: {} },
|
|
@@ -10955,7 +11265,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
10955
11265
|
|
|
10956
11266
|
// src/record.ts
|
|
10957
11267
|
function recordInvocation(parsed) {
|
|
10958
|
-
const file =
|
|
11268
|
+
const file = process14.env.ODLA_CLI_RECORD;
|
|
10959
11269
|
if (!file) return;
|
|
10960
11270
|
try {
|
|
10961
11271
|
const entry = {
|
|
@@ -11578,9 +11888,9 @@ import { spawnSync } from "child_process";
|
|
|
11578
11888
|
import { mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
11579
11889
|
import { tmpdir as tmpdir5 } from "os";
|
|
11580
11890
|
import { join as join15 } from "path";
|
|
11581
|
-
import
|
|
11891
|
+
import process15 from "process";
|
|
11582
11892
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11583
|
-
function resolveEditor(env =
|
|
11893
|
+
function resolveEditor(env = process15.env) {
|
|
11584
11894
|
for (const name of EDITOR_ENV) {
|
|
11585
11895
|
const value2 = env[name];
|
|
11586
11896
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11594,8 +11904,8 @@ function defaultRun(command, path) {
|
|
|
11594
11904
|
return result.status ?? 0;
|
|
11595
11905
|
}
|
|
11596
11906
|
function editText(initial, slug, deps = {}) {
|
|
11597
|
-
const env = deps.env ??
|
|
11598
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11907
|
+
const env = deps.env ?? process15.env;
|
|
11908
|
+
const interactive = deps.interactive ?? (() => Boolean(process15.stdin.isTTY));
|
|
11599
11909
|
const editor = resolveEditor(env);
|
|
11600
11910
|
if (!editor)
|
|
11601
11911
|
throw new Error(
|
|
@@ -11631,142 +11941,6 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11631
11941
|
return body === null ? null : { body, expectedVersion: found.version };
|
|
11632
11942
|
}
|
|
11633
11943
|
|
|
11634
|
-
// src/whoami-command.ts
|
|
11635
|
-
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11636
|
-
function principalKind(value2, machine) {
|
|
11637
|
-
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11638
|
-
}
|
|
11639
|
-
function credentialKind(value2, machine, scopes) {
|
|
11640
|
-
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11641
|
-
if (machine) return "machine";
|
|
11642
|
-
if (scopes.length) return "device";
|
|
11643
|
-
return "unknown";
|
|
11644
|
-
}
|
|
11645
|
-
function managerOf(value2) {
|
|
11646
|
-
if (!value2 || typeof value2 !== "object") return null;
|
|
11647
|
-
const row = value2;
|
|
11648
|
-
const principalId = text(row.principalId);
|
|
11649
|
-
if (!principalId) return null;
|
|
11650
|
-
return {
|
|
11651
|
-
principalId,
|
|
11652
|
-
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11653
|
-
handle: text(row.handle) ?? ""
|
|
11654
|
-
};
|
|
11655
|
-
}
|
|
11656
|
-
function unnamedPrincipal(kind) {
|
|
11657
|
-
if (kind === "agent") return "Unnamed agent";
|
|
11658
|
-
if (kind === "service") return "Unnamed service";
|
|
11659
|
-
return "Unnamed member";
|
|
11660
|
-
}
|
|
11661
|
-
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11662
|
-
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11663
|
-
headers: { authorization: `Bearer ${token}` }
|
|
11664
|
-
});
|
|
11665
|
-
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11666
|
-
const body = await res.json();
|
|
11667
|
-
const developerId = text(body.developerId) ?? "";
|
|
11668
|
-
const machine = body.machine === true;
|
|
11669
|
-
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11670
|
-
const principalId = text(body.principalId) ?? developerId;
|
|
11671
|
-
const email = text(body.email);
|
|
11672
|
-
const kind = principalKind(body.principalKind, machine);
|
|
11673
|
-
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11674
|
-
const handle = text(body.handle) ?? "";
|
|
11675
|
-
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11676
|
-
return {
|
|
11677
|
-
developerId,
|
|
11678
|
-
principalId,
|
|
11679
|
-
principalKind: kind,
|
|
11680
|
-
displayName,
|
|
11681
|
-
handle,
|
|
11682
|
-
manager: managerOf(body.manager),
|
|
11683
|
-
credential: {
|
|
11684
|
-
id: text(credential2.id),
|
|
11685
|
-
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11686
|
-
},
|
|
11687
|
-
email,
|
|
11688
|
-
admin: body.admin === true,
|
|
11689
|
-
machine,
|
|
11690
|
-
scopes
|
|
11691
|
-
};
|
|
11692
|
-
}
|
|
11693
|
-
function credentialLabel(identity) {
|
|
11694
|
-
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11695
|
-
if (identity.credential.kind === "device")
|
|
11696
|
-
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11697
|
-
if (identity.credential.kind === "clerk") return "clerk";
|
|
11698
|
-
return "unknown (legacy server)";
|
|
11699
|
-
}
|
|
11700
|
-
function namedPrincipal(identity) {
|
|
11701
|
-
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11702
|
-
}
|
|
11703
|
-
function namedManager(manager) {
|
|
11704
|
-
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11705
|
-
}
|
|
11706
|
-
function accountableOwner(identity) {
|
|
11707
|
-
if (identity.principalKind === "agent" && identity.manager) {
|
|
11708
|
-
return namedManager(identity.manager);
|
|
11709
|
-
}
|
|
11710
|
-
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11711
|
-
return namedPrincipal(identity);
|
|
11712
|
-
}
|
|
11713
|
-
return identity.email ?? "Unnamed member";
|
|
11714
|
-
}
|
|
11715
|
-
async function whoamiCommand(parsed, deps = {}) {
|
|
11716
|
-
assertArgs(
|
|
11717
|
-
parsed,
|
|
11718
|
-
["config", "context", "platform", "token", "email", "json"],
|
|
11719
|
-
1
|
|
11720
|
-
);
|
|
11721
|
-
const out = deps.stdout ?? console;
|
|
11722
|
-
const doFetch = deps.fetch ?? fetch;
|
|
11723
|
-
const { cfg } = await resolveOperatorContext(parsed, {
|
|
11724
|
-
allowMissingConfig: true
|
|
11725
|
-
});
|
|
11726
|
-
const token = await getDeveloperToken(
|
|
11727
|
-
cfg,
|
|
11728
|
-
{
|
|
11729
|
-
configPath: cfg.configPath,
|
|
11730
|
-
token: stringOpt(parsed.options.token),
|
|
11731
|
-
email: stringOpt(parsed.options.email),
|
|
11732
|
-
// As above: never suppress the browser for an ordinary sign-in.
|
|
11733
|
-
open: void 0
|
|
11734
|
-
},
|
|
11735
|
-
doFetch,
|
|
11736
|
-
out
|
|
11737
|
-
);
|
|
11738
|
-
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
11739
|
-
if (parsed.options.json === true) {
|
|
11740
|
-
out.log(JSON.stringify(identity, null, 2));
|
|
11741
|
-
return;
|
|
11742
|
-
}
|
|
11743
|
-
out.log(`platform: ${cfg.platformUrl}`);
|
|
11744
|
-
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11745
|
-
out.log(`principal id: ${identity.principalId}`);
|
|
11746
|
-
out.log(`kind: ${identity.principalKind}`);
|
|
11747
|
-
if (identity.principalKind === "agent")
|
|
11748
|
-
out.log(
|
|
11749
|
-
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11750
|
-
);
|
|
11751
|
-
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11752
|
-
out.log(`owner id: ${identity.developerId}`);
|
|
11753
|
-
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11754
|
-
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11755
|
-
if (identity.credential.id)
|
|
11756
|
-
out.log(`credential id: ${identity.credential.id}`);
|
|
11757
|
-
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11758
|
-
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11759
|
-
if (!identity.admin) {
|
|
11760
|
-
if (identity.scopes.includes("platform:runbook:write")) {
|
|
11761
|
-
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
11762
|
-
out.log("The device token is not ambient admin and cannot change visibility.");
|
|
11763
|
-
} else {
|
|
11764
|
-
out.log("\nYou can read operator-visible platform runbooks but not edit them.");
|
|
11765
|
-
out.log("Use a platform-admin-approved runbook write request to edit.");
|
|
11766
|
-
}
|
|
11767
|
-
}
|
|
11768
|
-
}
|
|
11769
|
-
|
|
11770
11944
|
// src/runbook-command.ts
|
|
11771
11945
|
var ALLOWED2 = [
|
|
11772
11946
|
"config",
|
|
@@ -12658,6 +12832,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
12658
12832
|
await whoamiCommand(parsed, runtime);
|
|
12659
12833
|
return;
|
|
12660
12834
|
}
|
|
12835
|
+
if (command === "auth") {
|
|
12836
|
+
await authCommand(parsed, runtime);
|
|
12837
|
+
return;
|
|
12838
|
+
}
|
|
12661
12839
|
if (command === "context") {
|
|
12662
12840
|
await contextCommand(parsed, runtime);
|
|
12663
12841
|
return;
|
|
@@ -12698,6 +12876,15 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
12698
12876
|
await pmCommand(parsed, runtime);
|
|
12699
12877
|
return;
|
|
12700
12878
|
}
|
|
12879
|
+
if (command === "bug") {
|
|
12880
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
12881
|
+
const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
12882
|
+
await pmCommand({
|
|
12883
|
+
...parsed,
|
|
12884
|
+
positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
|
|
12885
|
+
}, runtime);
|
|
12886
|
+
return;
|
|
12887
|
+
}
|
|
12701
12888
|
if (command === "discuss") {
|
|
12702
12889
|
await discussCommand(parsed, runtime);
|
|
12703
12890
|
return;
|
|
@@ -12793,6 +12980,7 @@ export {
|
|
|
12793
12980
|
calendarDisconnect,
|
|
12794
12981
|
CAPABILITIES,
|
|
12795
12982
|
printCapabilities,
|
|
12983
|
+
aiModels,
|
|
12796
12984
|
ConfigOperationCommandError,
|
|
12797
12985
|
desiredRegistryState,
|
|
12798
12986
|
configApply,
|
|
@@ -12837,4 +13025,4 @@ export {
|
|
|
12837
13025
|
isTerminalHostedSecurityStatus,
|
|
12838
13026
|
runCli
|
|
12839
13027
|
};
|
|
12840
|
-
//# sourceMappingURL=chunk-
|
|
13028
|
+
//# sourceMappingURL=chunk-HCNESRIA.js.map
|