@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
package/dist/bin.cjs
CHANGED
|
@@ -403,6 +403,11 @@ var init_device_session = __esm({
|
|
|
403
403
|
|
|
404
404
|
// src/odla-home.ts
|
|
405
405
|
function odlaHome(env = import_node_process5.default.env) {
|
|
406
|
+
if (env.VITEST && !env.ODLA_HOME) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
406
411
|
return env.ODLA_HOME ?? (0, import_node_path4.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla");
|
|
407
412
|
}
|
|
408
413
|
function odlaHomePath(segments, env = import_node_process5.default.env) {
|
|
@@ -428,15 +433,15 @@ function pmContextFile(env) {
|
|
|
428
433
|
}
|
|
429
434
|
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
430
435
|
if (!(0, import_node_fs6.existsSync)(legacyPath) || legacyPath === machinePath) return false;
|
|
431
|
-
const
|
|
432
|
-
if (!
|
|
436
|
+
const superseded2 = (0, import_node_fs6.existsSync)(machinePath);
|
|
437
|
+
if (!superseded2) {
|
|
433
438
|
(0, import_node_fs6.mkdirSync)((0, import_node_path4.dirname)(machinePath), { recursive: true });
|
|
434
439
|
(0, import_node_fs6.copyFileSync)(legacyPath, machinePath);
|
|
435
440
|
(0, import_node_fs6.chmodSync)(machinePath, 384);
|
|
436
441
|
}
|
|
437
442
|
(0, import_node_fs6.rmSync)(legacyPath, { force: true });
|
|
438
443
|
out?.error(
|
|
439
|
-
|
|
444
|
+
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`
|
|
440
445
|
);
|
|
441
446
|
return true;
|
|
442
447
|
}
|
|
@@ -615,6 +620,20 @@ function scopeInterruptionNotice(scope, state2) {
|
|
|
615
620
|
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."
|
|
616
621
|
].join("\n");
|
|
617
622
|
}
|
|
623
|
+
function narrowEnrollmentNotice(envelope) {
|
|
624
|
+
const everyApp = envelope.appIds.includes("*");
|
|
625
|
+
if (everyApp && envelope.capabilities.length > 0 && envelope.scopes.length > 0) return null;
|
|
626
|
+
const missing = [
|
|
627
|
+
everyApp ? null : `only ${envelope.appIds.length} app${envelope.appIds.length === 1 ? "" : "s"} \u2014 a new one will need a new approval`,
|
|
628
|
+
envelope.capabilities.length ? null : "no optional capabilities \u2014 app.manage, crm.read and code.session are not included",
|
|
629
|
+
envelope.scopes.length ? null : "no platform scopes \u2014 runbook edits, config plans and host connect will each ask again"
|
|
630
|
+
].filter(Boolean);
|
|
631
|
+
return [
|
|
632
|
+
`odla: this is a NARROW enrollment: ${missing.join("; ")}.`,
|
|
633
|
+
" That is a fine choice if you meant it. If you did not, drop the flags:",
|
|
634
|
+
` ${ENROL_EVERYTHING}`
|
|
635
|
+
].join("\n");
|
|
636
|
+
}
|
|
618
637
|
function lapseNotice(state2, now = Date.now()) {
|
|
619
638
|
if (!state2.enrolled || !state2.lapsesAt) return null;
|
|
620
639
|
const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
|
|
@@ -630,7 +649,7 @@ var init_auth_guidance = __esm({
|
|
|
630
649
|
init_device_session();
|
|
631
650
|
init_odla_home();
|
|
632
651
|
init_local();
|
|
633
|
-
ENROL_EVERYTHING = "npx odla-ai device enroll --
|
|
652
|
+
ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
|
|
634
653
|
ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
|
|
635
654
|
}
|
|
636
655
|
});
|
|
@@ -2705,6 +2724,11 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2705
2724
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
2706
2725
|
);
|
|
2707
2726
|
}
|
|
2727
|
+
if (!machineAuthState(platformAudience(cfg.platformUrl)).enrolled) {
|
|
2728
|
+
out.error("odla: this machine is not enrolled, so this approval buys one project until it lapses.");
|
|
2729
|
+
out.error(` For one approval that covers every app you own, with no repeats:
|
|
2730
|
+
${ENROL_EVERYTHING}`);
|
|
2731
|
+
}
|
|
2708
2732
|
const token = await getDeveloperToken(
|
|
2709
2733
|
cfg,
|
|
2710
2734
|
{
|
|
@@ -2742,6 +2766,7 @@ var init_auth_command = __esm({
|
|
|
2742
2766
|
init_argv();
|
|
2743
2767
|
init_operator_context();
|
|
2744
2768
|
init_token();
|
|
2769
|
+
init_auth_guidance();
|
|
2745
2770
|
init_whoami_command();
|
|
2746
2771
|
}
|
|
2747
2772
|
});
|
|
@@ -11397,13 +11422,16 @@ var init_help_usage = __esm({
|
|
|
11397
11422
|
init_cjs_shims();
|
|
11398
11423
|
AUTH_SECTION = `
|
|
11399
11424
|
Enrol this machine once, then stop asking:
|
|
11400
|
-
npx odla-ai device enroll --
|
|
11401
|
-
One browser approval
|
|
11402
|
-
own
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11425
|
+
npx odla-ai device enroll --no-open --wait 600
|
|
11426
|
+
One browser approval, and no flags to remember: this covers every app you
|
|
11427
|
+
own \u2014 including apps you create later \u2014 with every capability that
|
|
11428
|
+
approval can carry. Afterwards every worktree on this machine mints its
|
|
11429
|
+
own short-lived credentials with nobody's attention. The window rolls
|
|
11430
|
+
forward each time you use it, so continuous work never interrupts anyone;
|
|
11431
|
+
only a real gap does. Give the human the printed /studio?code= URL, keep
|
|
11432
|
+
the process alive, and wait on it.
|
|
11433
|
+
Narrow it deliberately with --app <id> or --capability <c>; the CLI then
|
|
11434
|
+
says what that gave up.
|
|
11407
11435
|
|
|
11408
11436
|
npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
|
|
11409
11437
|
The same thing across all of odla, for weeks. Needs a platform
|
|
@@ -11546,7 +11574,7 @@ Usage:
|
|
|
11546
11574
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
11547
11575
|
odla-ai security run [target] --self --ack-redacted-source
|
|
11548
11576
|
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]
|
|
11549
|
-
odla-ai device enroll [--
|
|
11577
|
+
odla-ai device enroll [--app <id>[,<id>...]|--all-apps] [--capability all|<c>[,<c>...]] [--platform-wide]
|
|
11550
11578
|
[--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
|
|
11551
11579
|
odla-ai device list [--email <odla-account>] [--json]
|
|
11552
11580
|
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
@@ -11674,11 +11702,13 @@ Commands:
|
|
|
11674
11702
|
enrollment in the browser; from then on EVERY worktree on this
|
|
11675
11703
|
machine mints its own short-lived credentials with nobody's
|
|
11676
11704
|
attention, until the device is revoked or goes unused.
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
|
|
11705
|
+
With no flags it covers every app you own, now and later, with
|
|
11706
|
+
every capability that approval can carry \u2014 so creating an app
|
|
11707
|
+
costs no new approval, and a capability nobody thought to name is
|
|
11708
|
+
not a 403 next week. "--app" or "--capability" narrow it
|
|
11709
|
+
deliberately, and the CLI says what that gave up.
|
|
11710
|
+
"--platform-wide" is the administrator's version, across all of
|
|
11711
|
+
odla.
|
|
11682
11712
|
The expiry is a GAP, not a clock: each use rolls it forward, so
|
|
11683
11713
|
only going quiet brings a human back into the loop \u2014 which is
|
|
11684
11714
|
where anything that changed can be explained.
|
|
@@ -14988,22 +15018,29 @@ function advisoryCollectingFetch(inner, sink) {
|
|
|
14988
15018
|
return response2;
|
|
14989
15019
|
});
|
|
14990
15020
|
}
|
|
15021
|
+
function supersedeAdvisory(code) {
|
|
15022
|
+
superseded.add(code);
|
|
15023
|
+
}
|
|
14991
15024
|
function renderAdvisories(out, advisories, env = process.env) {
|
|
15025
|
+
const retracted = new Set(superseded);
|
|
15026
|
+
superseded.clear();
|
|
14992
15027
|
if (env.ODLA_NO_ADVISORIES) return;
|
|
14993
15028
|
const seen = /* @__PURE__ */ new Set();
|
|
14994
15029
|
for (const advisory of advisories) {
|
|
15030
|
+
if (retracted.has(advisory.code)) continue;
|
|
14995
15031
|
const key = `${advisory.code}:${advisory.message}`;
|
|
14996
15032
|
if (seen.has(key)) continue;
|
|
14997
15033
|
seen.add(key);
|
|
14998
15034
|
out.error((0, import_apps14.formatAdvisory)(advisory));
|
|
14999
15035
|
}
|
|
15000
15036
|
}
|
|
15001
|
-
var import_apps14;
|
|
15037
|
+
var import_apps14, superseded;
|
|
15002
15038
|
var init_advisory_output = __esm({
|
|
15003
15039
|
"src/advisory-output.ts"() {
|
|
15004
15040
|
"use strict";
|
|
15005
15041
|
init_cjs_shims();
|
|
15006
15042
|
import_apps14 = require("@odla-ai/apps");
|
|
15043
|
+
superseded = /* @__PURE__ */ new Set();
|
|
15007
15044
|
}
|
|
15008
15045
|
});
|
|
15009
15046
|
|
|
@@ -15063,11 +15100,18 @@ async function deviceCommand(parsed, deps) {
|
|
|
15063
15100
|
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
15064
15101
|
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
15065
15102
|
const platformWide = parsed.options["platform-wide"] === true;
|
|
15066
|
-
const
|
|
15103
|
+
const narrowed = stringOpt(parsed.options.app) !== void 0 || stringOpt(parsed.options.capability) !== void 0;
|
|
15104
|
+
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);
|
|
15067
15105
|
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
|
|
15068
15106
|
const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
|
|
15069
15107
|
const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
15070
|
-
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide);
|
|
15108
|
+
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide, narrowed);
|
|
15109
|
+
const narrowNotice = narrowEnrollmentNotice({
|
|
15110
|
+
appIds: apps,
|
|
15111
|
+
capabilities: capabilities ?? [],
|
|
15112
|
+
scopes: scopes ?? []
|
|
15113
|
+
});
|
|
15114
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
15071
15115
|
const token = await scopedToken2(
|
|
15072
15116
|
parsed,
|
|
15073
15117
|
deps,
|
|
@@ -15103,8 +15147,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
15103
15147
|
}, null, 2));
|
|
15104
15148
|
(0, import_node_fs21.chmodSync)(path, 384);
|
|
15105
15149
|
rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
|
|
15150
|
+
supersedeAdvisory("credential.expiring");
|
|
15106
15151
|
const reach = body.device.appIds.includes(import_db4.ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
|
|
15107
15152
|
out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
|
|
15153
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
15108
15154
|
out.error(
|
|
15109
15155
|
"device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
|
|
15110
15156
|
);
|
|
@@ -15123,9 +15169,9 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
15123
15169
|
}, null, 2));
|
|
15124
15170
|
}
|
|
15125
15171
|
}
|
|
15126
|
-
function requestedEnvelope(parsed, platformWide) {
|
|
15172
|
+
function requestedEnvelope(parsed, platformWide, narrowed) {
|
|
15127
15173
|
const raw = stringOpt(parsed.options.capability);
|
|
15128
|
-
const everything = platformWide || raw?.trim().toLowerCase() === "all";
|
|
15174
|
+
const everything = platformWide || !narrowed || raw?.trim().toLowerCase() === "all";
|
|
15129
15175
|
if (everything) {
|
|
15130
15176
|
return {
|
|
15131
15177
|
capabilities: [...import_db4.OPTIONAL_AGENT_PROJECT_CAPABILITIES],
|
|
@@ -15209,6 +15255,8 @@ var init_device_command = __esm({
|
|
|
15209
15255
|
import_db4 = require("@odla-ai/db");
|
|
15210
15256
|
init_device_ttl();
|
|
15211
15257
|
init_machine_identity();
|
|
15258
|
+
init_auth_guidance();
|
|
15259
|
+
init_advisory_output();
|
|
15212
15260
|
import_node_fs21 = require("fs");
|
|
15213
15261
|
import_node_path19 = require("path");
|
|
15214
15262
|
import_node_process19 = __toESM(require("process"), 1);
|