@odla-ai/cli 0.27.7 → 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 +18 -2
- package/REQUIREMENTS.md +5 -0
- 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 +2 -1
- package/skills/odla/references/agent-identity.md +9 -2
- package/skills/odla/references/build.md +11 -1
- package/skills/odla/references/co-owners.md +11 -13
- package/dist/chunk-MZSU4YQL.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -243,17 +243,24 @@ function handshakeFile(cfg) {
|
|
|
243
243
|
return join(dirname2(cfg.local.tokenFile), "handshake.local.json");
|
|
244
244
|
}
|
|
245
245
|
var RESUME_MARGIN_MS = 5e3;
|
|
246
|
-
function readPendingHandshake(path, platform, email) {
|
|
246
|
+
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
247
247
|
const pending = readJsonFile(path);
|
|
248
248
|
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
249
249
|
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
250
250
|
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
251
|
+
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
251
252
|
return {
|
|
252
253
|
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
253
254
|
...pending,
|
|
254
255
|
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
255
256
|
};
|
|
256
257
|
}
|
|
258
|
+
function grantCovers(stored, required) {
|
|
259
|
+
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
260
|
+
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
261
|
+
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
257
264
|
function writePendingHandshake(path, pending) {
|
|
258
265
|
writePrivateJson(path, pending);
|
|
259
266
|
}
|
|
@@ -284,7 +291,7 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
|
|
|
284
291
|
}
|
|
285
292
|
|
|
286
293
|
// src/token.ts
|
|
287
|
-
async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
294
|
+
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
288
295
|
if (options.token) return options.token;
|
|
289
296
|
const audience = platformAudience(cfg.platformUrl);
|
|
290
297
|
if (process5.env.ODLA_DEV_TOKEN) {
|
|
@@ -296,8 +303,10 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
296
303
|
}
|
|
297
304
|
return process5.env.ODLA_DEV_TOKEN;
|
|
298
305
|
}
|
|
306
|
+
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
307
|
+
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
299
308
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
300
|
-
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
309
|
+
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
301
310
|
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
302
311
|
return cached.token;
|
|
303
312
|
}
|
|
@@ -308,17 +317,30 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
|
|
|
308
317
|
out,
|
|
309
318
|
audience,
|
|
310
319
|
email: handshakeEmail(options.email, cached?.platform === audience ? cached.email : void 0),
|
|
311
|
-
pendingFile: handshakeFile(cfg)
|
|
320
|
+
pendingFile: handshakeFile(cfg),
|
|
321
|
+
grantIntent
|
|
312
322
|
};
|
|
313
323
|
const waitMs = handshakeWaitMs(options.wait);
|
|
314
324
|
const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
|
|
315
325
|
clearPendingHandshake(ctx.pendingFile);
|
|
316
|
-
writePrivateJson(cfg.local.tokenFile, {
|
|
326
|
+
writePrivateJson(cfg.local.tokenFile, {
|
|
327
|
+
platform: audience,
|
|
328
|
+
email: ctx.email,
|
|
329
|
+
projectIds: grantIntent.projectIds,
|
|
330
|
+
optionalProjectCapabilities,
|
|
331
|
+
token,
|
|
332
|
+
expiresAt
|
|
333
|
+
});
|
|
317
334
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
318
335
|
return token;
|
|
319
336
|
}
|
|
320
337
|
async function resumePendingHandshake(ctx, waitMs) {
|
|
321
|
-
const pending = readPendingHandshake(
|
|
338
|
+
const pending = readPendingHandshake(
|
|
339
|
+
ctx.pendingFile,
|
|
340
|
+
ctx.audience,
|
|
341
|
+
ctx.email,
|
|
342
|
+
ctx.grantIntent
|
|
343
|
+
);
|
|
322
344
|
if (!pending) return null;
|
|
323
345
|
ctx.out.error("");
|
|
324
346
|
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
@@ -363,6 +385,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
363
385
|
label: `${ctx.cfg.app.id} provisioner`,
|
|
364
386
|
agentHandle: projectAgentHandle(ctx.cfg.app.id),
|
|
365
387
|
projectIds: [ctx.cfg.app.id],
|
|
388
|
+
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities,
|
|
366
389
|
fetch: ctx.doFetch,
|
|
367
390
|
waitMs,
|
|
368
391
|
onCode: async ({ userCode, deviceCode, expiresIn, interval }) => {
|
|
@@ -374,7 +397,9 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
374
397
|
deviceCode,
|
|
375
398
|
approvalUrl,
|
|
376
399
|
interval,
|
|
377
|
-
expiresAt: Date.now() + expiresIn * 1e3
|
|
400
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
401
|
+
projectIds: ctx.grantIntent.projectIds,
|
|
402
|
+
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
378
403
|
};
|
|
379
404
|
writePendingHandshake(ctx.pendingFile, started);
|
|
380
405
|
await presentHandshakeApproval(ctx.out, {
|
|
@@ -397,6 +422,12 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
397
422
|
stopReminder?.();
|
|
398
423
|
}
|
|
399
424
|
}
|
|
425
|
+
function cachedGrantCovers(cached, required) {
|
|
426
|
+
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
427
|
+
return required.projectIds.every((id) => cached.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
428
|
+
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
400
431
|
function projectAgentHandle(appId) {
|
|
401
432
|
const candidate = /^[a-z]/.test(appId) ? appId : `app-${appId}`;
|
|
402
433
|
if (candidate.length <= 32) return candidate;
|
|
@@ -1769,7 +1800,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
1769
1800
|
if (res.ok || res.status === 404) return;
|
|
1770
1801
|
if (res.status === 403) {
|
|
1771
1802
|
throw new Error(
|
|
1772
|
-
`${env}:
|
|
1803
|
+
`${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`
|
|
1773
1804
|
);
|
|
1774
1805
|
}
|
|
1775
1806
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
|
|
@@ -7404,7 +7435,9 @@ async function provision(options) {
|
|
|
7404
7435
|
out.log("secrets: Wrangler config and login preflight passed");
|
|
7405
7436
|
}
|
|
7406
7437
|
const doFetch = options.fetch ?? fetch;
|
|
7407
|
-
const token = await getDeveloperToken(cfg, options, doFetch, out
|
|
7438
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
7439
|
+
optionalProjectCapabilities: ["app.manage"]
|
|
7440
|
+
});
|
|
7408
7441
|
const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
7409
7442
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
7410
7443
|
if (existing) {
|
|
@@ -13166,4 +13199,4 @@ export {
|
|
|
13166
13199
|
exitCodeFor,
|
|
13167
13200
|
runCli
|
|
13168
13201
|
};
|
|
13169
|
-
//# sourceMappingURL=chunk-
|
|
13202
|
+
//# sourceMappingURL=chunk-LT56MKDK.js.map
|