@odla-ai/cli 0.27.8 → 0.27.9
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/README.md +7 -1
- package/dist/bin.cjs +42 -9
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MZSU4YQL.js → chunk-LT56MKDK.js} +43 -10
- package/dist/chunk-LT56MKDK.js.map +1 -0
- package/dist/index.cjs +42 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +1 -1
- package/skills/odla/references/agent-identity.md +5 -4
- package/skills/odla/references/build.md +7 -6
- package/skills/odla/references/co-owners.md +11 -13
- package/dist/chunk-MZSU4YQL.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -337,17 +337,24 @@ function handshakeFile(cfg) {
|
|
|
337
337
|
return (0, import_node_path2.join)((0, import_node_path2.dirname)(cfg.local.tokenFile), "handshake.local.json");
|
|
338
338
|
}
|
|
339
339
|
var RESUME_MARGIN_MS = 5e3;
|
|
340
|
-
function readPendingHandshake(path, platform, email) {
|
|
340
|
+
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
341
341
|
const pending = readJsonFile(path);
|
|
342
342
|
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
343
343
|
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
344
344
|
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
345
|
+
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
345
346
|
return {
|
|
346
347
|
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
347
348
|
...pending,
|
|
348
349
|
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
349
350
|
};
|
|
350
351
|
}
|
|
352
|
+
function grantCovers(stored, required) {
|
|
353
|
+
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
354
|
+
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
355
|
+
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
351
358
|
function writePendingHandshake(path, pending) {
|
|
352
359
|
writePrivateJson(path, pending);
|
|
353
360
|
}
|
|
@@ -378,7 +385,7 @@ function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default
|
|
|
378
385
|
}
|
|
379
386
|
|
|
380
387
|
// src/token.ts
|
|
381
|
-
async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
388
|
+
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
382
389
|
if (options.token) return options.token;
|
|
383
390
|
const audience = platformAudience(cfg.platformUrl);
|
|
384
391
|
if (import_node_process4.default.env.ODLA_DEV_TOKEN) {
|
|
@@ -390,8 +397,10 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
390
397
|
}
|
|
391
398
|
return import_node_process4.default.env.ODLA_DEV_TOKEN;
|
|
392
399
|
}
|
|
400
|
+
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
401
|
+
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
393
402
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
394
|
-
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
403
|
+
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
395
404
|
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
396
405
|
return cached.token;
|
|
397
406
|
}
|
|
@@ -402,17 +411,30 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
402
411
|
out,
|
|
403
412
|
audience,
|
|
404
413
|
email: handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0),
|
|
405
|
-
pendingFile: handshakeFile(cfg)
|
|
414
|
+
pendingFile: handshakeFile(cfg),
|
|
415
|
+
grantIntent
|
|
406
416
|
};
|
|
407
417
|
const waitMs = handshakeWaitMs(options.wait);
|
|
408
418
|
const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
|
|
409
419
|
clearPendingHandshake(ctx.pendingFile);
|
|
410
|
-
writePrivateJson(cfg.local.tokenFile, {
|
|
420
|
+
writePrivateJson(cfg.local.tokenFile, {
|
|
421
|
+
platform: audience,
|
|
422
|
+
email: ctx.email,
|
|
423
|
+
projectIds: grantIntent.projectIds,
|
|
424
|
+
optionalProjectCapabilities,
|
|
425
|
+
token,
|
|
426
|
+
expiresAt
|
|
427
|
+
});
|
|
411
428
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
412
429
|
return token;
|
|
413
430
|
}
|
|
414
431
|
async function resumePendingHandshake(ctx, waitMs) {
|
|
415
|
-
const pending = readPendingHandshake(
|
|
432
|
+
const pending = readPendingHandshake(
|
|
433
|
+
ctx.pendingFile,
|
|
434
|
+
ctx.audience,
|
|
435
|
+
ctx.email,
|
|
436
|
+
ctx.grantIntent
|
|
437
|
+
);
|
|
416
438
|
if (!pending) return null;
|
|
417
439
|
ctx.out.error("");
|
|
418
440
|
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
@@ -457,6 +479,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
457
479
|
label: `${ctx.cfg.app.id} provisioner`,
|
|
458
480
|
agentHandle: projectAgentHandle(ctx.cfg.app.id),
|
|
459
481
|
projectIds: [ctx.cfg.app.id],
|
|
482
|
+
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
|
|
460
483
|
fetch: ctx.doFetch,
|
|
461
484
|
waitMs,
|
|
462
485
|
onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
|
|
@@ -468,7 +491,9 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
468
491
|
deviceCode,
|
|
469
492
|
approvalUrl,
|
|
470
493
|
interval,
|
|
471
|
-
expiresAt: Date.now() + expiresIn * 1e3
|
|
494
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
495
|
+
projectIds: ctx.grantIntent.projectIds,
|
|
496
|
+
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
472
497
|
};
|
|
473
498
|
writePendingHandshake(ctx.pendingFile, started);
|
|
474
499
|
await presentHandshakeApproval(ctx.out, {
|
|
@@ -491,6 +516,12 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
491
516
|
stopReminder?.();
|
|
492
517
|
}
|
|
493
518
|
}
|
|
519
|
+
function cachedGrantCovers(cached, required) {
|
|
520
|
+
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
521
|
+
return required.projectIds.every((id) => cached.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
522
|
+
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
523
|
+
);
|
|
524
|
+
}
|
|
494
525
|
function projectAgentHandle(appId) {
|
|
495
526
|
const candidate = /^[a-z]/.test(appId) ? appId : `app-${appId}`;
|
|
496
527
|
if (candidate.length <= 32) return candidate;
|
|
@@ -3154,7 +3185,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3154
3185
|
if (res.ok || res.status === 404) return;
|
|
3155
3186
|
if (res.status === 403) {
|
|
3156
3187
|
throw new Error(
|
|
3157
|
-
`${env}:
|
|
3188
|
+
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; re-run provision with a fresh owner-approved provision handshake. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
|
|
3158
3189
|
);
|
|
3159
3190
|
}
|
|
3160
3191
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
|
|
@@ -10991,7 +11022,9 @@ async function provision(options) {
|
|
|
10991
11022
|
out.log("secrets: Wrangler config and login preflight passed");
|
|
10992
11023
|
}
|
|
10993
11024
|
const doFetch = options.fetch ?? fetch;
|
|
10994
|
-
const token = await getDeveloperToken(cfg, options, doFetch, out
|
|
11025
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
11026
|
+
optionalProjectCapabilities: ["app.manage"]
|
|
11027
|
+
});
|
|
10995
11028
|
const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
10996
11029
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
10997
11030
|
if (existing) {
|