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