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