@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
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
|
|
42
42
|
acceptedAfter: () => acceptedAfter,
|
|
43
43
|
adminAi: () => adminAi,
|
|
44
|
+
aiModels: () => aiModels,
|
|
44
45
|
calendarBookingPageUrl: () => calendarBookingPageUrl,
|
|
45
46
|
calendarCalendars: () => calendarCalendars,
|
|
46
47
|
calendarConnect: () => calendarConnect,
|
|
@@ -390,7 +391,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
390
391
|
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
391
392
|
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
392
393
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
393
|
-
if (!grantRequest.forceReview) {
|
|
394
|
+
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
394
395
|
if (options.token) return options.token;
|
|
395
396
|
if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
|
|
396
397
|
const declared = import_node_process4.default.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
@@ -407,9 +408,9 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
407
408
|
}
|
|
408
409
|
} else {
|
|
409
410
|
if (options.token) {
|
|
410
|
-
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");
|
|
411
412
|
}
|
|
412
|
-
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}"`);
|
|
413
414
|
}
|
|
414
415
|
const ctx = {
|
|
415
416
|
cfg,
|
|
@@ -422,6 +423,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
422
423
|
grantIntent
|
|
423
424
|
};
|
|
424
425
|
const waitMs = handshakeWaitMs(options.wait);
|
|
426
|
+
if (grantRequest.freshLogin) clearPendingHandshake(ctx.pendingFile);
|
|
425
427
|
const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
|
|
426
428
|
clearPendingHandshake(ctx.pendingFile);
|
|
427
429
|
writePrivateJson(cfg.local.tokenFile, {
|
|
@@ -544,8 +546,15 @@ function stillPending(pending, email) {
|
|
|
544
546
|
}
|
|
545
547
|
function handshakeEmail(value2, cached) {
|
|
546
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
|
+
}
|
|
547
554
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
548
|
-
throw new Error(
|
|
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
|
+
);
|
|
549
558
|
}
|
|
550
559
|
return email;
|
|
551
560
|
}
|
|
@@ -1713,6 +1722,204 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1713
1722
|
});
|
|
1714
1723
|
}
|
|
1715
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
|
+
|
|
1716
1923
|
// src/tenant.ts
|
|
1717
1924
|
var import_apps2 = require("@odla-ai/apps");
|
|
1718
1925
|
function resolveEnv(cfg, requested) {
|
|
@@ -2704,6 +2911,63 @@ function printGroup(out, heading, items) {
|
|
|
2704
2911
|
out.log("");
|
|
2705
2912
|
}
|
|
2706
2913
|
|
|
2914
|
+
// src/ai-models.ts
|
|
2915
|
+
var import_ai = require("@odla-ai/ai");
|
|
2916
|
+
async function aiModels(options = {}) {
|
|
2917
|
+
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
2918
|
+
const env = options.env ?? cfg.envs[0] ?? "dev";
|
|
2919
|
+
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
2920
|
+
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
2921
|
+
url.searchParams.set("env", env);
|
|
2922
|
+
const response2 = await (options.fetch ?? fetch)(url);
|
|
2923
|
+
if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
|
|
2924
|
+
const body = await response2.json();
|
|
2925
|
+
if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
|
|
2926
|
+
const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
|
|
2927
|
+
const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
|
|
2928
|
+
let models;
|
|
2929
|
+
if (mode === "hosted") {
|
|
2930
|
+
if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
|
|
2931
|
+
if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
|
|
2932
|
+
throw new Error("platform returned an invalid hosted AI model catalog");
|
|
2933
|
+
}
|
|
2934
|
+
models = body.ai.models;
|
|
2935
|
+
} else {
|
|
2936
|
+
const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
|
|
2937
|
+
if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
|
|
2938
|
+
models = Object.values(import_ai.DEFAULT_CATALOG).filter((model) => model.provider === provider);
|
|
2939
|
+
}
|
|
2940
|
+
if (options.provider) models = models.filter((model) => model.provider === options.provider);
|
|
2941
|
+
models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
|
|
2942
|
+
const out = options.stdout ?? console;
|
|
2943
|
+
if (options.json) {
|
|
2944
|
+
out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
out.log("provider model default capabilities");
|
|
2948
|
+
for (const model of models) {
|
|
2949
|
+
out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
function capabilityList(model) {
|
|
2953
|
+
return [
|
|
2954
|
+
model.capabilities.imageIn ? "image" : "",
|
|
2955
|
+
model.capabilities.audioIn ? "audio" : "",
|
|
2956
|
+
model.capabilities.documentIn ? "document" : "",
|
|
2957
|
+
model.capabilities.toolUse ? "tools" : "",
|
|
2958
|
+
model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
|
|
2959
|
+
model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
|
|
2960
|
+
].filter(Boolean).join(",");
|
|
2961
|
+
}
|
|
2962
|
+
function isModelSpec(value2) {
|
|
2963
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
|
|
2964
|
+
const model = value2;
|
|
2965
|
+
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";
|
|
2966
|
+
}
|
|
2967
|
+
async function safeText4(response2) {
|
|
2968
|
+
return (await response2.text().catch(() => "request failed")).slice(0, 300);
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2707
2971
|
// src/config-operation-command.ts
|
|
2708
2972
|
var import_apps6 = require("@odla-ai/apps");
|
|
2709
2973
|
var import_node_path8 = require("path");
|
|
@@ -2884,10 +3148,10 @@ function record2(value2) {
|
|
|
2884
3148
|
var import_apps5 = require("@odla-ai/apps");
|
|
2885
3149
|
|
|
2886
3150
|
// src/provision-helpers.ts
|
|
2887
|
-
var
|
|
3151
|
+
var import_ai2 = require("@odla-ai/ai");
|
|
2888
3152
|
var import_apps4 = require("@odla-ai/apps");
|
|
2889
3153
|
function defaultSecretName(provider) {
|
|
2890
|
-
const names =
|
|
3154
|
+
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
2891
3155
|
return names[provider] ?? `${provider}_api_key`;
|
|
2892
3156
|
}
|
|
2893
3157
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
@@ -2897,7 +3161,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2897
3161
|
});
|
|
2898
3162
|
if (res.ok || res.status === 404) return;
|
|
2899
3163
|
if (res.status === 403) {
|
|
2900
|
-
const detail = await
|
|
3164
|
+
const detail = await safeText5(res);
|
|
2901
3165
|
const code = errorCode(detail);
|
|
2902
3166
|
if (code === "human_session_required") {
|
|
2903
3167
|
throw new Error(
|
|
@@ -2913,7 +3177,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2913
3177
|
`${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`
|
|
2914
3178
|
);
|
|
2915
3179
|
}
|
|
2916
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await
|
|
3180
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
2917
3181
|
}
|
|
2918
3182
|
function errorCode(text2) {
|
|
2919
3183
|
try {
|
|
@@ -2929,7 +3193,7 @@ async function postJson(doFetch, url, bearer, body) {
|
|
|
2929
3193
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
2930
3194
|
body: JSON.stringify(body)
|
|
2931
3195
|
});
|
|
2932
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await
|
|
3196
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
2933
3197
|
}
|
|
2934
3198
|
function normalizeClerkConfig(value2) {
|
|
2935
3199
|
if (!value2) return null;
|
|
@@ -2942,7 +3206,7 @@ function normalizeClerkConfig(value2) {
|
|
|
2942
3206
|
const publishableKey = envValue(cfg.publishableKey);
|
|
2943
3207
|
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
2944
3208
|
}
|
|
2945
|
-
async function
|
|
3209
|
+
async function safeText5(res) {
|
|
2946
3210
|
try {
|
|
2947
3211
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
2948
3212
|
} catch {
|
|
@@ -4263,7 +4527,7 @@ function assertWranglerConfig(cfg) {
|
|
|
4263
4527
|
}
|
|
4264
4528
|
|
|
4265
4529
|
// src/secrets-set.ts
|
|
4266
|
-
var
|
|
4530
|
+
var import_ai3 = require("@odla-ai/ai");
|
|
4267
4531
|
var import_apps10 = require("@odla-ai/apps");
|
|
4268
4532
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
4269
4533
|
async function secretsSet(options) {
|
|
@@ -4277,7 +4541,7 @@ async function secretsSet(options) {
|
|
|
4277
4541
|
optionalProjectCapabilities: ["app.manage"]
|
|
4278
4542
|
});
|
|
4279
4543
|
try {
|
|
4280
|
-
await (0,
|
|
4544
|
+
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
4281
4545
|
} catch (err) {
|
|
4282
4546
|
throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
|
|
4283
4547
|
}
|
|
@@ -4344,6 +4608,14 @@ and atomically claim a refined Ready task. Record decisions when you make them
|
|
|
4344
4608
|
and file bugs when you notice them. The conventions and the full command set are
|
|
4345
4609
|
in \`.agents/skills/odla/references/pm.md\`.
|
|
4346
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
|
+
|
|
4347
4619
|
The setup runbooks and their references are installed in this repository, pinned
|
|
4348
4620
|
to this CLI version. Use them as your setup context.
|
|
4349
4621
|
|
|
@@ -4712,7 +4984,7 @@ async function getJson(doFetch, url, bearer) {
|
|
|
4712
4984
|
const res = await doFetch(url, {
|
|
4713
4985
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
4714
4986
|
});
|
|
4715
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4987
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4716
4988
|
return res.json();
|
|
4717
4989
|
}
|
|
4718
4990
|
async function postJson2(doFetch, url, bearer, body) {
|
|
@@ -4721,7 +4993,7 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
4721
4993
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
4722
4994
|
body: JSON.stringify(body)
|
|
4723
4995
|
});
|
|
4724
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4996
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4725
4997
|
return res.json();
|
|
4726
4998
|
}
|
|
4727
4999
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -4729,7 +5001,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
4729
5001
|
url.searchParams.set("env", env);
|
|
4730
5002
|
return url.toString();
|
|
4731
5003
|
}
|
|
4732
|
-
async function
|
|
5004
|
+
async function safeText6(res) {
|
|
4733
5005
|
try {
|
|
4734
5006
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
4735
5007
|
} catch {
|
|
@@ -4785,6 +5057,20 @@ async function secretsCommand(parsed, deps) {
|
|
|
4785
5057
|
});
|
|
4786
5058
|
}
|
|
4787
5059
|
async function projectCommand(command, parsed, deps) {
|
|
5060
|
+
if (command === "ai") {
|
|
5061
|
+
const sub = parsed.positionals[1];
|
|
5062
|
+
if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
|
|
5063
|
+
assertArgs(parsed, ["config", "env", "provider", "json"], 2);
|
|
5064
|
+
await aiModels({
|
|
5065
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5066
|
+
env: stringOpt(parsed.options.env),
|
|
5067
|
+
provider: stringOpt(parsed.options.provider),
|
|
5068
|
+
json: parsed.options.json === true,
|
|
5069
|
+
fetch: deps.fetch,
|
|
5070
|
+
stdout: deps.stdout
|
|
5071
|
+
});
|
|
5072
|
+
return true;
|
|
5073
|
+
}
|
|
4788
5074
|
if (command === "config") {
|
|
4789
5075
|
const sub = parsed.positionals[1];
|
|
4790
5076
|
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
@@ -8395,13 +8681,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8395
8681
|
}
|
|
8396
8682
|
|
|
8397
8683
|
// src/operator-credentials.ts
|
|
8398
|
-
var
|
|
8684
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
8399
8685
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8400
8686
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8401
8687
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8402
8688
|
const source = clean3(
|
|
8403
8689
|
stringOpt(parsed.options.token)
|
|
8404
|
-
) ? "flag" : clean3(
|
|
8690
|
+
) ? "flag" : clean3(import_node_process11.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8405
8691
|
return {
|
|
8406
8692
|
source,
|
|
8407
8693
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -8558,9 +8844,11 @@ Start here:
|
|
|
8558
8844
|
step it made wrong.
|
|
8559
8845
|
|
|
8560
8846
|
Usage:
|
|
8847
|
+
odla-ai auth login --app <id> --email <odla-account> [--platform https://odla.ai] [--no-open] [--wait <seconds>] [--json]
|
|
8561
8848
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8562
8849
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
8563
8850
|
odla-ai doctor [--config odla.config.mjs]
|
|
8851
|
+
odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
|
|
8564
8852
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8565
8853
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8566
8854
|
odla-ai operations get <operation-id> [--json]
|
|
@@ -8593,6 +8881,7 @@ Usage:
|
|
|
8593
8881
|
odla-ai pm task release <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
8594
8882
|
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
8595
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]
|
|
8596
8885
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
8597
8886
|
odla-ai pm <goal|task|decision|bug> ref <id> [--json]
|
|
8598
8887
|
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
@@ -8672,6 +8961,9 @@ function printHelp(output = console) {
|
|
|
8672
8961
|
output.log(`odla-ai
|
|
8673
8962
|
${USAGE_SECTION}
|
|
8674
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.
|
|
8675
8967
|
agent Inspect durable agent wakeups and explicitly requeue a
|
|
8676
8968
|
dead-lettered job; JSON output is stable for remote operators.
|
|
8677
8969
|
runbook odla's operational procedures, stored in the database and read at
|
|
@@ -8739,6 +9031,8 @@ Commands:
|
|
|
8739
9031
|
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
8740
9032
|
decision, bug. Status changes and comments post to each item's
|
|
8741
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.
|
|
8742
9036
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
8743
9037
|
one group per project, topics with replies, @-mentions of people,
|
|
8744
9038
|
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
@@ -8796,8 +9090,12 @@ Safety:
|
|
|
8796
9090
|
wait for approval, and re-run to collect.
|
|
8797
9091
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8798
9092
|
The email is a non-secret identity hint: never provide a password or session
|
|
8799
|
-
token.
|
|
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
|
|
8800
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.
|
|
8801
9099
|
If provision reports that the current agent principal has no live app.manage
|
|
8802
9100
|
grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
|
|
8803
9101
|
the local cache, prints and opens a fresh exact-project owner-review URL, then
|
|
@@ -9291,7 +9589,8 @@ var ALLOWED = [
|
|
|
9291
9589
|
"jsonl",
|
|
9292
9590
|
"mutation-id",
|
|
9293
9591
|
"platform",
|
|
9294
|
-
"context"
|
|
9592
|
+
"context",
|
|
9593
|
+
"open"
|
|
9295
9594
|
];
|
|
9296
9595
|
function requireId(id, action2) {
|
|
9297
9596
|
if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
@@ -9310,7 +9609,8 @@ async function buildContext(parsed, deps) {
|
|
|
9310
9609
|
configPath: cfg.configPath,
|
|
9311
9610
|
token: stringOpt(parsed.options.token),
|
|
9312
9611
|
email: stringOpt(parsed.options.email),
|
|
9313
|
-
open: false
|
|
9612
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9613
|
+
openApprovalUrl: deps.openUrl
|
|
9314
9614
|
},
|
|
9315
9615
|
doFetch,
|
|
9316
9616
|
out
|
|
@@ -9885,7 +10185,7 @@ var ALIASES = {
|
|
|
9885
10185
|
decision: "decision",
|
|
9886
10186
|
bug: "bug"
|
|
9887
10187
|
};
|
|
9888
|
-
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
10188
|
+
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
|
|
9889
10189
|
var ACTION_OPTIONS = {
|
|
9890
10190
|
list: ["app", "q", "limit", "offset"],
|
|
9891
10191
|
add: ["app", "title", "mutation-id"],
|
|
@@ -9949,7 +10249,13 @@ async function buildContext2(parsed, deps) {
|
|
|
9949
10249
|
const out = deps.stdout ?? console;
|
|
9950
10250
|
const token = await getDeveloperToken(
|
|
9951
10251
|
cfg,
|
|
9952
|
-
{
|
|
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
|
+
},
|
|
9953
10259
|
doFetch,
|
|
9954
10260
|
out
|
|
9955
10261
|
);
|
|
@@ -10413,7 +10719,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
10413
10719
|
"json",
|
|
10414
10720
|
"app",
|
|
10415
10721
|
"env",
|
|
10416
|
-
"minutes"
|
|
10722
|
+
"minutes",
|
|
10723
|
+
"open"
|
|
10417
10724
|
],
|
|
10418
10725
|
2
|
|
10419
10726
|
);
|
|
@@ -10440,7 +10747,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
10440
10747
|
configPath: cfg.configPath,
|
|
10441
10748
|
token: stringOpt(parsed.options.token),
|
|
10442
10749
|
email: stringOpt(parsed.options.email),
|
|
10443
|
-
open: false
|
|
10750
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10751
|
+
openApprovalUrl: deps.openUrl
|
|
10444
10752
|
},
|
|
10445
10753
|
doFetch,
|
|
10446
10754
|
out
|
|
@@ -10550,8 +10858,8 @@ async function read2(url, headers, doFetch) {
|
|
|
10550
10858
|
|
|
10551
10859
|
// src/provision.ts
|
|
10552
10860
|
var import_apps12 = require("@odla-ai/apps");
|
|
10553
|
-
var
|
|
10554
|
-
var
|
|
10861
|
+
var import_ai4 = require("@odla-ai/ai");
|
|
10862
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10555
10863
|
|
|
10556
10864
|
// src/integration-provision.ts
|
|
10557
10865
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -10664,14 +10972,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
10664
10972
|
appId: tenantId
|
|
10665
10973
|
})
|
|
10666
10974
|
});
|
|
10667
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
10975
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
|
|
10668
10976
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
10669
10977
|
method: "POST",
|
|
10670
10978
|
headers,
|
|
10671
10979
|
body: "{}"
|
|
10672
10980
|
});
|
|
10673
10981
|
}
|
|
10674
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
10982
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10675
10983
|
const body = await res.json();
|
|
10676
10984
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
10677
10985
|
return body.key;
|
|
@@ -10687,12 +10995,12 @@ async function issueO11yToken(opts) {
|
|
|
10687
10995
|
`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`
|
|
10688
10996
|
);
|
|
10689
10997
|
}
|
|
10690
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
10998
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10691
10999
|
const body = await res.json();
|
|
10692
11000
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
10693
11001
|
return body.token;
|
|
10694
11002
|
}
|
|
10695
|
-
async function
|
|
11003
|
+
async function safeText7(res) {
|
|
10696
11004
|
try {
|
|
10697
11005
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
10698
11006
|
} catch {
|
|
@@ -10871,10 +11179,10 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10871
11179
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10872
11180
|
}
|
|
10873
11181
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10874
|
-
const key =
|
|
11182
|
+
const key = import_node_process12.default.env[cfg.ai.keyEnv];
|
|
10875
11183
|
if (key) {
|
|
10876
11184
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10877
|
-
await (0,
|
|
11185
|
+
await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
10878
11186
|
out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
|
|
10879
11187
|
} else {
|
|
10880
11188
|
out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
|
|
@@ -10913,7 +11221,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10913
11221
|
|
|
10914
11222
|
// src/record.ts
|
|
10915
11223
|
var import_node_fs15 = require("fs");
|
|
10916
|
-
var
|
|
11224
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
10917
11225
|
|
|
10918
11226
|
// src/surface.ts
|
|
10919
11227
|
var PM_ACTIONS = {
|
|
@@ -10947,6 +11255,7 @@ var PM_ENTITIES = {
|
|
|
10947
11255
|
};
|
|
10948
11256
|
var COMMAND_SURFACE = {
|
|
10949
11257
|
agent: { jobs: {}, retry: {} },
|
|
11258
|
+
ai: { models: {} },
|
|
10950
11259
|
admin: {
|
|
10951
11260
|
ai: {
|
|
10952
11261
|
show: {},
|
|
@@ -10969,7 +11278,9 @@ var COMMAND_SURFACE = {
|
|
|
10969
11278
|
promote: {},
|
|
10970
11279
|
owners: { list: {}, add: {}, remove: {} }
|
|
10971
11280
|
},
|
|
11281
|
+
auth: { login: {} },
|
|
10972
11282
|
brand: { design: { unpack: {} } },
|
|
11283
|
+
bug: { create: {}, list: {}, report: {} },
|
|
10973
11284
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10974
11285
|
capabilities: {},
|
|
10975
11286
|
code: { connect: {} },
|
|
@@ -11084,7 +11395,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
11084
11395
|
|
|
11085
11396
|
// src/record.ts
|
|
11086
11397
|
function recordInvocation(parsed) {
|
|
11087
|
-
const file =
|
|
11398
|
+
const file = import_node_process13.default.env.ODLA_CLI_RECORD;
|
|
11088
11399
|
if (!file) return;
|
|
11089
11400
|
try {
|
|
11090
11401
|
const entry = {
|
|
@@ -11756,9 +12067,9 @@ var import_node_child_process8 = require("child_process");
|
|
|
11756
12067
|
var import_node_fs19 = require("fs");
|
|
11757
12068
|
var import_node_os5 = require("os");
|
|
11758
12069
|
var import_node_path18 = require("path");
|
|
11759
|
-
var
|
|
12070
|
+
var import_node_process14 = __toESM(require("process"), 1);
|
|
11760
12071
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11761
|
-
function resolveEditor(env =
|
|
12072
|
+
function resolveEditor(env = import_node_process14.default.env) {
|
|
11762
12073
|
for (const name of EDITOR_ENV) {
|
|
11763
12074
|
const value2 = env[name];
|
|
11764
12075
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11772,8 +12083,8 @@ function defaultRun(command, path) {
|
|
|
11772
12083
|
return result.status ?? 0;
|
|
11773
12084
|
}
|
|
11774
12085
|
function editText(initial, slug, deps = {}) {
|
|
11775
|
-
const env = deps.env ??
|
|
11776
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
12086
|
+
const env = deps.env ?? import_node_process14.default.env;
|
|
12087
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process14.default.stdin.isTTY));
|
|
11777
12088
|
const editor = resolveEditor(env);
|
|
11778
12089
|
if (!editor)
|
|
11779
12090
|
throw new Error(
|
|
@@ -11809,142 +12120,6 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11809
12120
|
return body === null ? null : { body, expectedVersion: found.version };
|
|
11810
12121
|
}
|
|
11811
12122
|
|
|
11812
|
-
// src/whoami-command.ts
|
|
11813
|
-
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11814
|
-
function principalKind(value2, machine) {
|
|
11815
|
-
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11816
|
-
}
|
|
11817
|
-
function credentialKind(value2, machine, scopes) {
|
|
11818
|
-
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11819
|
-
if (machine) return "machine";
|
|
11820
|
-
if (scopes.length) return "device";
|
|
11821
|
-
return "unknown";
|
|
11822
|
-
}
|
|
11823
|
-
function managerOf(value2) {
|
|
11824
|
-
if (!value2 || typeof value2 !== "object") return null;
|
|
11825
|
-
const row = value2;
|
|
11826
|
-
const principalId = text(row.principalId);
|
|
11827
|
-
if (!principalId) return null;
|
|
11828
|
-
return {
|
|
11829
|
-
principalId,
|
|
11830
|
-
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11831
|
-
handle: text(row.handle) ?? ""
|
|
11832
|
-
};
|
|
11833
|
-
}
|
|
11834
|
-
function unnamedPrincipal(kind) {
|
|
11835
|
-
if (kind === "agent") return "Unnamed agent";
|
|
11836
|
-
if (kind === "service") return "Unnamed service";
|
|
11837
|
-
return "Unnamed member";
|
|
11838
|
-
}
|
|
11839
|
-
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11840
|
-
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11841
|
-
headers: { authorization: `Bearer ${token}` }
|
|
11842
|
-
});
|
|
11843
|
-
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11844
|
-
const body = await res.json();
|
|
11845
|
-
const developerId = text(body.developerId) ?? "";
|
|
11846
|
-
const machine = body.machine === true;
|
|
11847
|
-
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11848
|
-
const principalId = text(body.principalId) ?? developerId;
|
|
11849
|
-
const email = text(body.email);
|
|
11850
|
-
const kind = principalKind(body.principalKind, machine);
|
|
11851
|
-
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11852
|
-
const handle = text(body.handle) ?? "";
|
|
11853
|
-
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11854
|
-
return {
|
|
11855
|
-
developerId,
|
|
11856
|
-
principalId,
|
|
11857
|
-
principalKind: kind,
|
|
11858
|
-
displayName,
|
|
11859
|
-
handle,
|
|
11860
|
-
manager: managerOf(body.manager),
|
|
11861
|
-
credential: {
|
|
11862
|
-
id: text(credential2.id),
|
|
11863
|
-
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11864
|
-
},
|
|
11865
|
-
email,
|
|
11866
|
-
admin: body.admin === true,
|
|
11867
|
-
machine,
|
|
11868
|
-
scopes
|
|
11869
|
-
};
|
|
11870
|
-
}
|
|
11871
|
-
function credentialLabel(identity) {
|
|
11872
|
-
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11873
|
-
if (identity.credential.kind === "device")
|
|
11874
|
-
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11875
|
-
if (identity.credential.kind === "clerk") return "clerk";
|
|
11876
|
-
return "unknown (legacy server)";
|
|
11877
|
-
}
|
|
11878
|
-
function namedPrincipal(identity) {
|
|
11879
|
-
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11880
|
-
}
|
|
11881
|
-
function namedManager(manager) {
|
|
11882
|
-
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11883
|
-
}
|
|
11884
|
-
function accountableOwner(identity) {
|
|
11885
|
-
if (identity.principalKind === "agent" && identity.manager) {
|
|
11886
|
-
return namedManager(identity.manager);
|
|
11887
|
-
}
|
|
11888
|
-
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11889
|
-
return namedPrincipal(identity);
|
|
11890
|
-
}
|
|
11891
|
-
return identity.email ?? "Unnamed member";
|
|
11892
|
-
}
|
|
11893
|
-
async function whoamiCommand(parsed, deps = {}) {
|
|
11894
|
-
assertArgs(
|
|
11895
|
-
parsed,
|
|
11896
|
-
["config", "context", "platform", "token", "email", "json"],
|
|
11897
|
-
1
|
|
11898
|
-
);
|
|
11899
|
-
const out = deps.stdout ?? console;
|
|
11900
|
-
const doFetch = deps.fetch ?? fetch;
|
|
11901
|
-
const { cfg } = await resolveOperatorContext(parsed, {
|
|
11902
|
-
allowMissingConfig: true
|
|
11903
|
-
});
|
|
11904
|
-
const token = await getDeveloperToken(
|
|
11905
|
-
cfg,
|
|
11906
|
-
{
|
|
11907
|
-
configPath: cfg.configPath,
|
|
11908
|
-
token: stringOpt(parsed.options.token),
|
|
11909
|
-
email: stringOpt(parsed.options.email),
|
|
11910
|
-
// As above: never suppress the browser for an ordinary sign-in.
|
|
11911
|
-
open: void 0
|
|
11912
|
-
},
|
|
11913
|
-
doFetch,
|
|
11914
|
-
out
|
|
11915
|
-
);
|
|
11916
|
-
const identity = await fetchIdentity(cfg.platformUrl, token, doFetch);
|
|
11917
|
-
if (parsed.options.json === true) {
|
|
11918
|
-
out.log(JSON.stringify(identity, null, 2));
|
|
11919
|
-
return;
|
|
11920
|
-
}
|
|
11921
|
-
out.log(`platform: ${cfg.platformUrl}`);
|
|
11922
|
-
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11923
|
-
out.log(`principal id: ${identity.principalId}`);
|
|
11924
|
-
out.log(`kind: ${identity.principalKind}`);
|
|
11925
|
-
if (identity.principalKind === "agent")
|
|
11926
|
-
out.log(
|
|
11927
|
-
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11928
|
-
);
|
|
11929
|
-
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11930
|
-
out.log(`owner id: ${identity.developerId}`);
|
|
11931
|
-
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11932
|
-
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11933
|
-
if (identity.credential.id)
|
|
11934
|
-
out.log(`credential id: ${identity.credential.id}`);
|
|
11935
|
-
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11936
|
-
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11937
|
-
if (!identity.admin) {
|
|
11938
|
-
if (identity.scopes.includes("platform:runbook:write")) {
|
|
11939
|
-
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
11940
|
-
out.log("The device token is not ambient admin and cannot change visibility.");
|
|
11941
|
-
} else {
|
|
11942
|
-
out.log("\nYou can read operator-visible platform runbooks but not edit them.");
|
|
11943
|
-
out.log("Use a platform-admin-approved runbook write request to edit.");
|
|
11944
|
-
}
|
|
11945
|
-
}
|
|
11946
|
-
}
|
|
11947
|
-
|
|
11948
12123
|
// src/runbook-command.ts
|
|
11949
12124
|
var ALLOWED2 = [
|
|
11950
12125
|
"config",
|
|
@@ -12840,6 +13015,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
12840
13015
|
await whoamiCommand(parsed, runtime);
|
|
12841
13016
|
return;
|
|
12842
13017
|
}
|
|
13018
|
+
if (command === "auth") {
|
|
13019
|
+
await authCommand(parsed, runtime);
|
|
13020
|
+
return;
|
|
13021
|
+
}
|
|
12843
13022
|
if (command === "context") {
|
|
12844
13023
|
await contextCommand(parsed, runtime);
|
|
12845
13024
|
return;
|
|
@@ -12880,6 +13059,15 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
12880
13059
|
await pmCommand(parsed, runtime);
|
|
12881
13060
|
return;
|
|
12882
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
|
+
}
|
|
12883
13071
|
if (command === "discuss") {
|
|
12884
13072
|
await discussCommand(parsed, runtime);
|
|
12885
13073
|
return;
|
|
@@ -12973,6 +13161,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12973
13161
|
SYSTEM_AI_PURPOSES,
|
|
12974
13162
|
acceptedAfter,
|
|
12975
13163
|
adminAi,
|
|
13164
|
+
aiModels,
|
|
12976
13165
|
calendarBookingPageUrl,
|
|
12977
13166
|
calendarCalendars,
|
|
12978
13167
|
calendarConnect,
|