@odla-ai/cli 0.39.0 → 0.40.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 +70 -22
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-PJ3RATDD.js → chunk-2IHE5U3M.js} +67 -22
- package/dist/chunk-2IHE5U3M.js.map +1 -0
- package/dist/{cli-ALS5OZPB.js → cli-LRJBESE6.js} +2 -2
- package/dist/index.cjs +66 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +6 -5
- package/skills/odla/references/agent-identity.md +1 -1
- package/dist/chunk-PJ3RATDD.js.map +0 -1
- /package/dist/{cli-ALS5OZPB.js.map → cli-LRJBESE6.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-2IHE5U3M.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-LRJBESE6.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -295,6 +295,11 @@ var import_node_os2 = require("os");
|
|
|
295
295
|
var import_node_path3 = require("path");
|
|
296
296
|
var import_node_process5 = __toESM(require("process"), 1);
|
|
297
297
|
function odlaHome(env = import_node_process5.default.env) {
|
|
298
|
+
if (env.VITEST && !env.ODLA_HOME) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
298
303
|
return env.ODLA_HOME ?? (0, import_node_path3.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla");
|
|
299
304
|
}
|
|
300
305
|
function odlaHomePath(segments, env = import_node_process5.default.env) {
|
|
@@ -320,15 +325,15 @@ function pmContextFile(env) {
|
|
|
320
325
|
}
|
|
321
326
|
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
322
327
|
if (!(0, import_node_fs3.existsSync)(legacyPath) || legacyPath === machinePath) return false;
|
|
323
|
-
const
|
|
324
|
-
if (!
|
|
328
|
+
const superseded2 = (0, import_node_fs3.existsSync)(machinePath);
|
|
329
|
+
if (!superseded2) {
|
|
325
330
|
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(machinePath), { recursive: true });
|
|
326
331
|
(0, import_node_fs3.copyFileSync)(legacyPath, machinePath);
|
|
327
332
|
(0, import_node_fs3.chmodSync)(machinePath, 384);
|
|
328
333
|
}
|
|
329
334
|
(0, import_node_fs3.rmSync)(legacyPath, { force: true });
|
|
330
335
|
out?.error(
|
|
331
|
-
|
|
336
|
+
superseded2 ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
|
|
332
337
|
);
|
|
333
338
|
return true;
|
|
334
339
|
}
|
|
@@ -465,7 +470,7 @@ function displayPath(path, rootDir = process.cwd()) {
|
|
|
465
470
|
}
|
|
466
471
|
|
|
467
472
|
// src/auth-guidance.ts
|
|
468
|
-
var ENROL_EVERYTHING = "npx odla-ai device enroll --
|
|
473
|
+
var ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
|
|
469
474
|
var ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
|
|
470
475
|
function machineAuthState(audience, env = import_node_process6.default.env) {
|
|
471
476
|
const device = readDeviceCredential(audience, env);
|
|
@@ -491,6 +496,20 @@ function scopeInterruptionNotice(scope, state2) {
|
|
|
491
496
|
platformScope ? " A platform scope needs an administrator's approval; an app owner's cannot carry it." : " One approval, every app you own, and it rolls forward while you keep working."
|
|
492
497
|
].join("\n");
|
|
493
498
|
}
|
|
499
|
+
function narrowEnrollmentNotice(envelope) {
|
|
500
|
+
const everyApp = envelope.appIds.includes("*");
|
|
501
|
+
if (everyApp && envelope.capabilities.length > 0 && envelope.scopes.length > 0) return null;
|
|
502
|
+
const missing = [
|
|
503
|
+
everyApp ? null : `only ${envelope.appIds.length} app${envelope.appIds.length === 1 ? "" : "s"} \u2014 a new one will need a new approval`,
|
|
504
|
+
envelope.capabilities.length ? null : "no optional capabilities \u2014 app.manage, crm.read and code.session are not included",
|
|
505
|
+
envelope.scopes.length ? null : "no platform scopes \u2014 runbook edits, config plans and host connect will each ask again"
|
|
506
|
+
].filter(Boolean);
|
|
507
|
+
return [
|
|
508
|
+
`odla: this is a NARROW enrollment: ${missing.join("; ")}.`,
|
|
509
|
+
" That is a fine choice if you meant it. If you did not, drop the flags:",
|
|
510
|
+
` ${ENROL_EVERYTHING}`
|
|
511
|
+
].join("\n");
|
|
512
|
+
}
|
|
494
513
|
function lapseNotice(state2, now = Date.now()) {
|
|
495
514
|
if (!state2.enrolled || !state2.lapsesAt) return null;
|
|
496
515
|
const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
|
|
@@ -2368,6 +2387,11 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2368
2387
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
2369
2388
|
);
|
|
2370
2389
|
}
|
|
2390
|
+
if (!machineAuthState(platformAudience(cfg.platformUrl)).enrolled) {
|
|
2391
|
+
out.error("odla: this machine is not enrolled, so this approval buys one project until it lapses.");
|
|
2392
|
+
out.error(` For one approval that covers every app you own, with no repeats:
|
|
2393
|
+
${ENROL_EVERYTHING}`);
|
|
2394
|
+
}
|
|
2371
2395
|
const token = await getDeveloperToken(
|
|
2372
2396
|
cfg,
|
|
2373
2397
|
{
|
|
@@ -10573,13 +10597,16 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
10573
10597
|
// src/help-usage.ts
|
|
10574
10598
|
var AUTH_SECTION = `
|
|
10575
10599
|
Enrol this machine once, then stop asking:
|
|
10576
|
-
npx odla-ai device enroll --
|
|
10577
|
-
One browser approval
|
|
10578
|
-
own
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10600
|
+
npx odla-ai device enroll --no-open --wait 600
|
|
10601
|
+
One browser approval, and no flags to remember: this covers every app you
|
|
10602
|
+
own \u2014 including apps you create later \u2014 with every capability that
|
|
10603
|
+
approval can carry. Afterwards every worktree on this machine mints its
|
|
10604
|
+
own short-lived credentials with nobody's attention. The window rolls
|
|
10605
|
+
forward each time you use it, so continuous work never interrupts anyone;
|
|
10606
|
+
only a real gap does. Give the human the printed /studio?code= URL, keep
|
|
10607
|
+
the process alive, and wait on it.
|
|
10608
|
+
Narrow it deliberately with --app <id> or --capability <c>; the CLI then
|
|
10609
|
+
says what that gave up.
|
|
10583
10610
|
|
|
10584
10611
|
npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
|
|
10585
10612
|
The same thing across all of odla, for weeks. Needs a platform
|
|
@@ -10722,7 +10749,7 @@ Usage:
|
|
|
10722
10749
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10723
10750
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10724
10751
|
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--no-open] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
10725
|
-
odla-ai device enroll [--
|
|
10752
|
+
odla-ai device enroll [--app <id>[,<id>...]|--all-apps] [--capability all|<c>[,<c>...]] [--platform-wide]
|
|
10726
10753
|
[--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
|
|
10727
10754
|
odla-ai device list [--email <odla-account>] [--json]
|
|
10728
10755
|
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
@@ -10848,11 +10875,13 @@ Commands:
|
|
|
10848
10875
|
enrollment in the browser; from then on EVERY worktree on this
|
|
10849
10876
|
machine mints its own short-lived credentials with nobody's
|
|
10850
10877
|
attention, until the device is revoked or goes unused.
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10878
|
+
With no flags it covers every app you own, now and later, with
|
|
10879
|
+
every capability that approval can carry \u2014 so creating an app
|
|
10880
|
+
costs no new approval, and a capability nobody thought to name is
|
|
10881
|
+
not a 403 next week. "--app" or "--capability" narrow it
|
|
10882
|
+
deliberately, and the CLI says what that gave up.
|
|
10883
|
+
"--platform-wide" is the administrator's version, across all of
|
|
10884
|
+
odla.
|
|
10856
10885
|
The expiry is a GAP, not a clock: each use rolls it forward, so
|
|
10857
10886
|
only going quiet brings a human back into the loop \u2014 which is
|
|
10858
10887
|
where anything that changed can be explained.
|
|
@@ -13892,10 +13921,17 @@ function advisoryCollectingFetch(inner, sink) {
|
|
|
13892
13921
|
return response2;
|
|
13893
13922
|
});
|
|
13894
13923
|
}
|
|
13924
|
+
var superseded = /* @__PURE__ */ new Set();
|
|
13925
|
+
function supersedeAdvisory(code) {
|
|
13926
|
+
superseded.add(code);
|
|
13927
|
+
}
|
|
13895
13928
|
function renderAdvisories(out, advisories, env = process.env) {
|
|
13929
|
+
const retracted = new Set(superseded);
|
|
13930
|
+
superseded.clear();
|
|
13896
13931
|
if (env.ODLA_NO_ADVISORIES) return;
|
|
13897
13932
|
const seen = /* @__PURE__ */ new Set();
|
|
13898
13933
|
for (const advisory of advisories) {
|
|
13934
|
+
if (retracted.has(advisory.code)) continue;
|
|
13899
13935
|
const key = `${advisory.code}:${advisory.message}`;
|
|
13900
13936
|
if (seen.has(key)) continue;
|
|
13901
13937
|
seen.add(key);
|
|
@@ -13958,11 +13994,18 @@ async function deviceCommand(parsed, deps) {
|
|
|
13958
13994
|
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
13959
13995
|
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
13960
13996
|
const platformWide = parsed.options["platform-wide"] === true;
|
|
13961
|
-
const
|
|
13997
|
+
const narrowed = stringOpt(parsed.options.app) !== void 0 || stringOpt(parsed.options.capability) !== void 0;
|
|
13998
|
+
const apps = platformWide || parsed.options["all-apps"] === true || !narrowed ? [import_db4.ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
13962
13999
|
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
|
|
13963
14000
|
const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
|
|
13964
14001
|
const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
13965
|
-
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide);
|
|
14002
|
+
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide, narrowed);
|
|
14003
|
+
const narrowNotice = narrowEnrollmentNotice({
|
|
14004
|
+
appIds: apps,
|
|
14005
|
+
capabilities: capabilities ?? [],
|
|
14006
|
+
scopes: scopes ?? []
|
|
14007
|
+
});
|
|
14008
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
13966
14009
|
const token = await scopedToken2(
|
|
13967
14010
|
parsed,
|
|
13968
14011
|
deps,
|
|
@@ -13998,8 +14041,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13998
14041
|
}, null, 2));
|
|
13999
14042
|
(0, import_node_fs19.chmodSync)(path, 384);
|
|
14000
14043
|
rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
|
|
14044
|
+
supersedeAdvisory("credential.expiring");
|
|
14001
14045
|
const reach = body.device.appIds.includes(import_db4.ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
|
|
14002
14046
|
out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
|
|
14047
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
14003
14048
|
out.error(
|
|
14004
14049
|
"device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
|
|
14005
14050
|
);
|
|
@@ -14018,9 +14063,9 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
14018
14063
|
}, null, 2));
|
|
14019
14064
|
}
|
|
14020
14065
|
}
|
|
14021
|
-
function requestedEnvelope(parsed, platformWide) {
|
|
14066
|
+
function requestedEnvelope(parsed, platformWide, narrowed) {
|
|
14022
14067
|
const raw = stringOpt(parsed.options.capability);
|
|
14023
|
-
const everything = platformWide || raw?.trim().toLowerCase() === "all";
|
|
14068
|
+
const everything = platformWide || !narrowed || raw?.trim().toLowerCase() === "all";
|
|
14024
14069
|
if (everything) {
|
|
14025
14070
|
return {
|
|
14026
14071
|
capabilities: [...import_db4.OPTIONAL_AGENT_PROJECT_CAPABILITIES],
|