@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.js
CHANGED
|
@@ -174,7 +174,7 @@ function absoluteEntryPath(entryPath) {
|
|
|
174
174
|
|
|
175
175
|
// src/bin.ts
|
|
176
176
|
var argv = process.argv.slice(2);
|
|
177
|
-
requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-
|
|
177
|
+
requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-LRJBESE6.js")).runCli()).catch((err) => {
|
|
178
178
|
console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
|
|
179
179
|
process.exitCode = exitCodeFor(err);
|
|
180
180
|
});
|
|
@@ -208,6 +208,11 @@ import { homedir as homedir2 } from "os";
|
|
|
208
208
|
import { dirname as dirname2, join as join3 } from "path";
|
|
209
209
|
import process6 from "process";
|
|
210
210
|
function odlaHome(env = process6.env) {
|
|
211
|
+
if (env.VITEST && !env.ODLA_HOME) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
"ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
211
216
|
return env.ODLA_HOME ?? join3(env.HOME ?? homedir2(), ".odla");
|
|
212
217
|
}
|
|
213
218
|
function odlaHomePath(segments, env = process6.env) {
|
|
@@ -233,15 +238,15 @@ function pmContextFile(env) {
|
|
|
233
238
|
}
|
|
234
239
|
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
235
240
|
if (!existsSync2(legacyPath) || legacyPath === machinePath) return false;
|
|
236
|
-
const
|
|
237
|
-
if (!
|
|
241
|
+
const superseded2 = existsSync2(machinePath);
|
|
242
|
+
if (!superseded2) {
|
|
238
243
|
mkdirSync(dirname2(machinePath), { recursive: true });
|
|
239
244
|
copyFileSync(legacyPath, machinePath);
|
|
240
245
|
chmodSync(machinePath, 384);
|
|
241
246
|
}
|
|
242
247
|
rmSync2(legacyPath, { force: true });
|
|
243
248
|
out?.error(
|
|
244
|
-
|
|
249
|
+
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`
|
|
245
250
|
);
|
|
246
251
|
return true;
|
|
247
252
|
}
|
|
@@ -378,7 +383,7 @@ function displayPath(path, rootDir = process.cwd()) {
|
|
|
378
383
|
}
|
|
379
384
|
|
|
380
385
|
// src/auth-guidance.ts
|
|
381
|
-
var ENROL_EVERYTHING = "npx odla-ai device enroll --
|
|
386
|
+
var ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
|
|
382
387
|
var ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
|
|
383
388
|
function machineAuthState(audience, env = process7.env) {
|
|
384
389
|
const device = readDeviceCredential(audience, env);
|
|
@@ -404,6 +409,20 @@ function scopeInterruptionNotice(scope, state2) {
|
|
|
404
409
|
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."
|
|
405
410
|
].join("\n");
|
|
406
411
|
}
|
|
412
|
+
function narrowEnrollmentNotice(envelope) {
|
|
413
|
+
const everyApp = envelope.appIds.includes("*");
|
|
414
|
+
if (everyApp && envelope.capabilities.length > 0 && envelope.scopes.length > 0) return null;
|
|
415
|
+
const missing = [
|
|
416
|
+
everyApp ? null : `only ${envelope.appIds.length} app${envelope.appIds.length === 1 ? "" : "s"} \u2014 a new one will need a new approval`,
|
|
417
|
+
envelope.capabilities.length ? null : "no optional capabilities \u2014 app.manage, crm.read and code.session are not included",
|
|
418
|
+
envelope.scopes.length ? null : "no platform scopes \u2014 runbook edits, config plans and host connect will each ask again"
|
|
419
|
+
].filter(Boolean);
|
|
420
|
+
return [
|
|
421
|
+
`odla: this is a NARROW enrollment: ${missing.join("; ")}.`,
|
|
422
|
+
" That is a fine choice if you meant it. If you did not, drop the flags:",
|
|
423
|
+
` ${ENROL_EVERYTHING}`
|
|
424
|
+
].join("\n");
|
|
425
|
+
}
|
|
407
426
|
function lapseNotice(state2, now = Date.now()) {
|
|
408
427
|
if (!state2.enrolled || !state2.lapsesAt) return null;
|
|
409
428
|
const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
|
|
@@ -2281,6 +2300,11 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2281
2300
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
2282
2301
|
);
|
|
2283
2302
|
}
|
|
2303
|
+
if (!machineAuthState(platformAudience(cfg.platformUrl)).enrolled) {
|
|
2304
|
+
out.error("odla: this machine is not enrolled, so this approval buys one project until it lapses.");
|
|
2305
|
+
out.error(` For one approval that covers every app you own, with no repeats:
|
|
2306
|
+
${ENROL_EVERYTHING}`);
|
|
2307
|
+
}
|
|
2284
2308
|
const token = await getDeveloperToken(
|
|
2285
2309
|
cfg,
|
|
2286
2310
|
{
|
|
@@ -10446,13 +10470,16 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
10446
10470
|
// src/help-usage.ts
|
|
10447
10471
|
var AUTH_SECTION = `
|
|
10448
10472
|
Enrol this machine once, then stop asking:
|
|
10449
|
-
npx odla-ai device enroll --
|
|
10450
|
-
One browser approval
|
|
10451
|
-
own
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10473
|
+
npx odla-ai device enroll --no-open --wait 600
|
|
10474
|
+
One browser approval, and no flags to remember: this covers every app you
|
|
10475
|
+
own \u2014 including apps you create later \u2014 with every capability that
|
|
10476
|
+
approval can carry. Afterwards every worktree on this machine mints its
|
|
10477
|
+
own short-lived credentials with nobody's attention. The window rolls
|
|
10478
|
+
forward each time you use it, so continuous work never interrupts anyone;
|
|
10479
|
+
only a real gap does. Give the human the printed /studio?code= URL, keep
|
|
10480
|
+
the process alive, and wait on it.
|
|
10481
|
+
Narrow it deliberately with --app <id> or --capability <c>; the CLI then
|
|
10482
|
+
says what that gave up.
|
|
10456
10483
|
|
|
10457
10484
|
npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
|
|
10458
10485
|
The same thing across all of odla, for weeks. Needs a platform
|
|
@@ -10595,7 +10622,7 @@ Usage:
|
|
|
10595
10622
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10596
10623
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10597
10624
|
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]
|
|
10598
|
-
odla-ai device enroll [--
|
|
10625
|
+
odla-ai device enroll [--app <id>[,<id>...]|--all-apps] [--capability all|<c>[,<c>...]] [--platform-wide]
|
|
10599
10626
|
[--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
|
|
10600
10627
|
odla-ai device list [--email <odla-account>] [--json]
|
|
10601
10628
|
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
@@ -10721,11 +10748,13 @@ Commands:
|
|
|
10721
10748
|
enrollment in the browser; from then on EVERY worktree on this
|
|
10722
10749
|
machine mints its own short-lived credentials with nobody's
|
|
10723
10750
|
attention, until the device is revoked or goes unused.
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10751
|
+
With no flags it covers every app you own, now and later, with
|
|
10752
|
+
every capability that approval can carry \u2014 so creating an app
|
|
10753
|
+
costs no new approval, and a capability nobody thought to name is
|
|
10754
|
+
not a 403 next week. "--app" or "--capability" narrow it
|
|
10755
|
+
deliberately, and the CLI says what that gave up.
|
|
10756
|
+
"--platform-wide" is the administrator's version, across all of
|
|
10757
|
+
odla.
|
|
10729
10758
|
The expiry is a GAP, not a clock: each use rolls it forward, so
|
|
10730
10759
|
only going quiet brings a human back into the loop \u2014 which is
|
|
10731
10760
|
where anything that changed can be explained.
|
|
@@ -13765,10 +13794,17 @@ function advisoryCollectingFetch(inner, sink) {
|
|
|
13765
13794
|
return response2;
|
|
13766
13795
|
});
|
|
13767
13796
|
}
|
|
13797
|
+
var superseded = /* @__PURE__ */ new Set();
|
|
13798
|
+
function supersedeAdvisory(code) {
|
|
13799
|
+
superseded.add(code);
|
|
13800
|
+
}
|
|
13768
13801
|
function renderAdvisories(out, advisories, env = process.env) {
|
|
13802
|
+
const retracted = new Set(superseded);
|
|
13803
|
+
superseded.clear();
|
|
13769
13804
|
if (env.ODLA_NO_ADVISORIES) return;
|
|
13770
13805
|
const seen = /* @__PURE__ */ new Set();
|
|
13771
13806
|
for (const advisory of advisories) {
|
|
13807
|
+
if (retracted.has(advisory.code)) continue;
|
|
13772
13808
|
const key = `${advisory.code}:${advisory.message}`;
|
|
13773
13809
|
if (seen.has(key)) continue;
|
|
13774
13810
|
seen.add(key);
|
|
@@ -13836,11 +13872,18 @@ async function deviceCommand(parsed, deps) {
|
|
|
13836
13872
|
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
13837
13873
|
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
13838
13874
|
const platformWide = parsed.options["platform-wide"] === true;
|
|
13839
|
-
const
|
|
13875
|
+
const narrowed = stringOpt(parsed.options.app) !== void 0 || stringOpt(parsed.options.capability) !== void 0;
|
|
13876
|
+
const apps = platformWide || parsed.options["all-apps"] === true || !narrowed ? [ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
13840
13877
|
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
|
|
13841
13878
|
const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
|
|
13842
13879
|
const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
13843
|
-
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide);
|
|
13880
|
+
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide, narrowed);
|
|
13881
|
+
const narrowNotice = narrowEnrollmentNotice({
|
|
13882
|
+
appIds: apps,
|
|
13883
|
+
capabilities: capabilities ?? [],
|
|
13884
|
+
scopes: scopes ?? []
|
|
13885
|
+
});
|
|
13886
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
13844
13887
|
const token = await scopedToken2(
|
|
13845
13888
|
parsed,
|
|
13846
13889
|
deps,
|
|
@@ -13876,8 +13919,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13876
13919
|
}, null, 2));
|
|
13877
13920
|
chmodSync3(path, 384);
|
|
13878
13921
|
rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
|
|
13922
|
+
supersedeAdvisory("credential.expiring");
|
|
13879
13923
|
const reach = body.device.appIds.includes(ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
|
|
13880
13924
|
out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
|
|
13925
|
+
if (narrowNotice) out.error(narrowNotice);
|
|
13881
13926
|
out.error(
|
|
13882
13927
|
"device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
|
|
13883
13928
|
);
|
|
@@ -13896,9 +13941,9 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13896
13941
|
}, null, 2));
|
|
13897
13942
|
}
|
|
13898
13943
|
}
|
|
13899
|
-
function requestedEnvelope(parsed, platformWide) {
|
|
13944
|
+
function requestedEnvelope(parsed, platformWide, narrowed) {
|
|
13900
13945
|
const raw = stringOpt(parsed.options.capability);
|
|
13901
|
-
const everything = platformWide || raw?.trim().toLowerCase() === "all";
|
|
13946
|
+
const everything = platformWide || !narrowed || raw?.trim().toLowerCase() === "all";
|
|
13902
13947
|
if (everything) {
|
|
13903
13948
|
return {
|
|
13904
13949
|
capabilities: [...OPTIONAL_AGENT_PROJECT_CAPABILITIES],
|
|
@@ -15747,4 +15792,4 @@ export {
|
|
|
15747
15792
|
isTerminalHostedSecurityStatus,
|
|
15748
15793
|
runCli
|
|
15749
15794
|
};
|
|
15750
|
-
//# sourceMappingURL=chunk-
|
|
15795
|
+
//# sourceMappingURL=chunk-2IHE5U3M.js.map
|