@odla-ai/cli 0.30.0 → 0.30.1
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 +19 -6
- package/dist/bin.cjs +96 -148
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-PPSXHZJI.js → chunk-7V7WVTDT.js} +71 -123
- package/dist/chunk-7V7WVTDT.js.map +1 -0
- package/dist/{cli-CKBUVTJM.js → cli-SZUWA6EF.js} +2 -2
- package/dist/index.cjs +84 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +6 -4
- package/dist/chunk-PPSXHZJI.js.map +0 -1
- /package/dist/{cli-CKBUVTJM.js.map → cli-SZUWA6EF.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-7V7WVTDT.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-SZUWA6EF.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -203,17 +203,45 @@ function handshakeUrl(platformUrl, userCode) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// src/handshake-state.ts
|
|
206
|
-
var
|
|
207
|
-
var
|
|
206
|
+
var import_node_fs = require("fs");
|
|
207
|
+
var import_node_path = require("path");
|
|
208
208
|
var import_node_process3 = __toESM(require("process"), 1);
|
|
209
|
+
function handshakeFile(cfg) {
|
|
210
|
+
return (0, import_node_path.join)((0, import_node_path.dirname)(cfg.local.tokenFile), "handshake.local.json");
|
|
211
|
+
}
|
|
212
|
+
function clearPendingHandshake(path) {
|
|
213
|
+
(0, import_node_fs.rmSync)(path, { force: true });
|
|
214
|
+
}
|
|
215
|
+
function minutesLeft(expiresAt) {
|
|
216
|
+
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
217
|
+
}
|
|
218
|
+
function approvalHint(pending) {
|
|
219
|
+
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
220
|
+
}
|
|
221
|
+
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
222
|
+
const timer = setInterval(() => {
|
|
223
|
+
for (const line of reminderLines({
|
|
224
|
+
userCode: pending.userCode,
|
|
225
|
+
approvalUrl: pending.approvalUrl,
|
|
226
|
+
minutesLeft: minutesLeft(pending.expiresAt)
|
|
227
|
+
}))
|
|
228
|
+
out.log(line);
|
|
229
|
+
}, periodMs);
|
|
230
|
+
timer.unref?.();
|
|
231
|
+
return () => clearInterval(timer);
|
|
232
|
+
}
|
|
233
|
+
function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
|
|
234
|
+
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
235
|
+
return interactive ? void 0 : 9e4;
|
|
236
|
+
}
|
|
209
237
|
|
|
210
238
|
// src/local.ts
|
|
211
|
-
var
|
|
212
|
-
var
|
|
239
|
+
var import_node_fs2 = require("fs");
|
|
240
|
+
var import_node_path2 = require("path");
|
|
213
241
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
214
242
|
function readJsonFile(path) {
|
|
215
243
|
try {
|
|
216
|
-
return JSON.parse((0,
|
|
244
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
217
245
|
} catch {
|
|
218
246
|
return null;
|
|
219
247
|
}
|
|
@@ -223,10 +251,10 @@ function writePrivateJson(path, value2) {
|
|
|
223
251
|
`);
|
|
224
252
|
}
|
|
225
253
|
function readCredentials(path) {
|
|
226
|
-
if (!(0,
|
|
254
|
+
if (!(0, import_node_fs2.existsSync)(path)) return null;
|
|
227
255
|
let value2;
|
|
228
256
|
try {
|
|
229
|
-
value2 = JSON.parse((0,
|
|
257
|
+
value2 = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
230
258
|
} catch {
|
|
231
259
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
232
260
|
}
|
|
@@ -256,14 +284,14 @@ function mergeCredential(current, update) {
|
|
|
256
284
|
return next;
|
|
257
285
|
}
|
|
258
286
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
259
|
-
const path = (0,
|
|
260
|
-
const existing = (0,
|
|
287
|
+
const path = (0, import_node_path2.resolve)(rootDir, ".gitignore");
|
|
288
|
+
const existing = (0, import_node_fs2.existsSync)(path) ? (0, import_node_fs2.readFileSync)(path, "utf8") : "";
|
|
261
289
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line) => !!line);
|
|
262
290
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
263
291
|
const missing = wanted.filter((line) => !existing.split(/\r?\n/).includes(line));
|
|
264
292
|
if (missing.length === 0) return;
|
|
265
293
|
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
266
|
-
(0,
|
|
294
|
+
(0, import_node_fs2.writeFileSync)(path, `${existing}${prefix}${missing.join("\n")}
|
|
267
295
|
`);
|
|
268
296
|
}
|
|
269
297
|
function o11yDevVars(cfg) {
|
|
@@ -277,7 +305,7 @@ function o11yDevVars(cfg) {
|
|
|
277
305
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
278
306
|
if (!requested) return null;
|
|
279
307
|
if (requested === true) return cfg.local.devVarsFile;
|
|
280
|
-
return (0,
|
|
308
|
+
return (0, import_node_path2.resolve)((0, import_node_path2.dirname)(cfg.configPath), requested);
|
|
281
309
|
}
|
|
282
310
|
function writeDevVars(path, credentials, env, o11y) {
|
|
283
311
|
const entry = credentials.envs[env];
|
|
@@ -291,7 +319,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
291
319
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
292
320
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
293
321
|
}
|
|
294
|
-
const existing = (0,
|
|
322
|
+
const existing = (0, import_node_fs2.existsSync)(path) ? (0, import_node_fs2.readFileSync)(path, "utf8") : "";
|
|
295
323
|
const retained = existing.split(/\r?\n/).filter((line) => !isManagedDevVar(line));
|
|
296
324
|
while (retained.at(-1) === "") retained.pop();
|
|
297
325
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -317,74 +345,22 @@ function isManagedDevVar(line) {
|
|
|
317
345
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
318
346
|
}
|
|
319
347
|
function writePrivateText(path, text2) {
|
|
320
|
-
(0,
|
|
348
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(path), { recursive: true });
|
|
321
349
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
322
|
-
(0,
|
|
323
|
-
(0,
|
|
324
|
-
(0,
|
|
350
|
+
(0, import_node_fs2.writeFileSync)(temporary, text2, { mode: 384 });
|
|
351
|
+
(0, import_node_fs2.chmodSync)(temporary, 384);
|
|
352
|
+
(0, import_node_fs2.renameSync)(temporary, path);
|
|
325
353
|
}
|
|
326
354
|
function gitignoreEntry(rootDir, path) {
|
|
327
|
-
const rel = (0,
|
|
328
|
-
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0,
|
|
355
|
+
const rel = (0, import_node_path2.relative)((0, import_node_path2.resolve)(rootDir), (0, import_node_path2.resolve)(path));
|
|
356
|
+
if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path2.isAbsolute)(rel)) return null;
|
|
329
357
|
return rel.replaceAll("\\", "/");
|
|
330
358
|
}
|
|
331
359
|
function displayPath(path, rootDir = process.cwd()) {
|
|
332
|
-
const rel = (0,
|
|
360
|
+
const rel = (0, import_node_path2.relative)(rootDir, path);
|
|
333
361
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
334
362
|
}
|
|
335
363
|
|
|
336
|
-
// src/handshake-state.ts
|
|
337
|
-
function handshakeFile(cfg) {
|
|
338
|
-
return (0, import_node_path2.join)((0, import_node_path2.dirname)(cfg.local.tokenFile), "handshake.local.json");
|
|
339
|
-
}
|
|
340
|
-
var RESUME_MARGIN_MS = 5e3;
|
|
341
|
-
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
342
|
-
const pending = readJsonFile(path);
|
|
343
|
-
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
344
|
-
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
345
|
-
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
346
|
-
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
347
|
-
return {
|
|
348
|
-
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
349
|
-
...pending,
|
|
350
|
-
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
function grantCovers(stored, required) {
|
|
354
|
-
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
355
|
-
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
356
|
-
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
function writePendingHandshake(path, pending) {
|
|
360
|
-
writePrivateJson(path, pending);
|
|
361
|
-
}
|
|
362
|
-
function clearPendingHandshake(path) {
|
|
363
|
-
(0, import_node_fs2.rmSync)(path, { force: true });
|
|
364
|
-
}
|
|
365
|
-
function minutesLeft(expiresAt) {
|
|
366
|
-
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
367
|
-
}
|
|
368
|
-
function approvalHint(pending) {
|
|
369
|
-
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
370
|
-
}
|
|
371
|
-
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
372
|
-
const timer = setInterval(() => {
|
|
373
|
-
for (const line of reminderLines({
|
|
374
|
-
userCode: pending.userCode,
|
|
375
|
-
approvalUrl: pending.approvalUrl,
|
|
376
|
-
minutesLeft: minutesLeft(pending.expiresAt)
|
|
377
|
-
}))
|
|
378
|
-
out.log(line);
|
|
379
|
-
}, periodMs);
|
|
380
|
-
timer.unref?.();
|
|
381
|
-
return () => clearInterval(timer);
|
|
382
|
-
}
|
|
383
|
-
function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
|
|
384
|
-
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
385
|
-
return interactive ? void 0 : 9e4;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
364
|
// src/token.ts
|
|
389
365
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
390
366
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -423,8 +399,8 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
423
399
|
grantIntent
|
|
424
400
|
};
|
|
425
401
|
const waitMs = handshakeWaitMs(options.wait);
|
|
426
|
-
|
|
427
|
-
const { token, expiresAt } = await
|
|
402
|
+
clearPendingHandshake(ctx.pendingFile);
|
|
403
|
+
const { token, expiresAt } = await freshHandshake(ctx, waitMs);
|
|
428
404
|
clearPendingHandshake(ctx.pendingFile);
|
|
429
405
|
writePrivateJson(cfg.local.tokenFile, {
|
|
430
406
|
platform: audience,
|
|
@@ -437,47 +413,6 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
437
413
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
438
414
|
return token;
|
|
439
415
|
}
|
|
440
|
-
async function resumePendingHandshake(ctx, waitMs) {
|
|
441
|
-
const pending = readPendingHandshake(
|
|
442
|
-
ctx.pendingFile,
|
|
443
|
-
ctx.audience,
|
|
444
|
-
ctx.email,
|
|
445
|
-
ctx.grantIntent
|
|
446
|
-
);
|
|
447
|
-
if (!pending) return null;
|
|
448
|
-
ctx.out.error("");
|
|
449
|
-
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
450
|
-
await presentHandshakeApproval(ctx.out, {
|
|
451
|
-
userCode: pending.userCode,
|
|
452
|
-
approvalUrl: pending.approvalUrl,
|
|
453
|
-
minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
|
|
454
|
-
purpose: `sign this terminal in as ${ctx.email}`
|
|
455
|
-
}, ctx.options);
|
|
456
|
-
ctx.out.error("");
|
|
457
|
-
const stopReminder = approvalReminder(ctx.out, pending);
|
|
458
|
-
try {
|
|
459
|
-
return await (0, import_db.collectToken)({
|
|
460
|
-
endpoint: ctx.cfg.platformUrl,
|
|
461
|
-
deviceCode: pending.deviceCode,
|
|
462
|
-
expiresAt: pending.expiresAt,
|
|
463
|
-
interval: pending.interval,
|
|
464
|
-
waitMs,
|
|
465
|
-
fetch: ctx.doFetch
|
|
466
|
-
});
|
|
467
|
-
} catch (err) {
|
|
468
|
-
const code = err instanceof import_db.OdlaError ? err.code : void 0;
|
|
469
|
-
if (code === "handshake_pending") throw stillPending(pending, ctx.email);
|
|
470
|
-
if (code === "handshake_expired" || code === "handshake_timeout") {
|
|
471
|
-
clearPendingHandshake(ctx.pendingFile);
|
|
472
|
-
ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
|
|
473
|
-
return null;
|
|
474
|
-
}
|
|
475
|
-
if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
|
|
476
|
-
throw err;
|
|
477
|
-
} finally {
|
|
478
|
-
stopReminder();
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
416
|
async function freshHandshake(ctx, waitMs) {
|
|
482
417
|
let started;
|
|
483
418
|
let stopReminder;
|
|
@@ -505,7 +440,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
505
440
|
projectIds: ctx.grantIntent.projectIds,
|
|
506
441
|
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
507
442
|
};
|
|
508
|
-
writePendingHandshake(ctx.pendingFile, started);
|
|
509
443
|
await presentHandshakeApproval(ctx.out, {
|
|
510
444
|
userCode,
|
|
511
445
|
approvalUrl,
|
|
@@ -541,7 +475,7 @@ function projectAgentHandle(appId) {
|
|
|
541
475
|
function stillPending(pending, email) {
|
|
542
476
|
return new import_db.OdlaError(
|
|
543
477
|
"handshake_pending",
|
|
544
|
-
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}
|
|
478
|
+
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}; if this command exits, re-run it to request a new code`,
|
|
545
479
|
{ retryable: true }
|
|
546
480
|
);
|
|
547
481
|
}
|
|
@@ -4911,28 +4845,31 @@ async function smoke(options) {
|
|
|
4911
4845
|
const out = options.stdout ?? console;
|
|
4912
4846
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4913
4847
|
const env = resolveEnv(cfg, options.env);
|
|
4914
|
-
const
|
|
4915
|
-
|
|
4848
|
+
const runtime = options.runtime === true;
|
|
4849
|
+
const credentials = runtime ? null : readCredentials(cfg.local.credentialsFile);
|
|
4850
|
+
if (!runtime && !credentials) {
|
|
4916
4851
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
4917
4852
|
}
|
|
4918
|
-
if (credentials.appId !== cfg.app.id) {
|
|
4853
|
+
if (credentials && credentials.appId !== cfg.app.id) {
|
|
4919
4854
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
4920
4855
|
}
|
|
4921
|
-
const entry = credentials
|
|
4922
|
-
if (!entry?.tenantId) {
|
|
4856
|
+
const entry = credentials?.envs[env];
|
|
4857
|
+
if (!runtime && !entry?.tenantId) {
|
|
4923
4858
|
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4924
4859
|
}
|
|
4925
4860
|
const hasDb = cfg.services.includes("db");
|
|
4926
4861
|
const hasO11y = cfg.services.includes("o11y");
|
|
4927
|
-
if (hasDb && !entry
|
|
4862
|
+
if (!runtime && hasDb && !entry?.dbKey) {
|
|
4928
4863
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4929
4864
|
}
|
|
4930
|
-
if (hasO11y && !entry
|
|
4865
|
+
if (!runtime && hasO11y && !entry?.o11yToken) {
|
|
4931
4866
|
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4932
4867
|
}
|
|
4933
4868
|
const doFetch = options.fetch ?? fetch;
|
|
4869
|
+
const tenantId = entry?.tenantId ?? resolveTenant(cfg, env).tenant;
|
|
4934
4870
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
4935
|
-
out.log(`
|
|
4871
|
+
out.log(` mode: ${runtime ? "runtime (Worker-held credentials)" : "local credentials"}`);
|
|
4872
|
+
out.log(` tenant: ${tenantId}`);
|
|
4936
4873
|
const publicConfig = await getJson(doFetch, publicConfigUrl(cfg.platformUrl, cfg.app.id, env), void 0);
|
|
4937
4874
|
out.log(` public-config: ok`);
|
|
4938
4875
|
if (cfg.services.includes("ai") && cfg.ai?.provider) {
|
|
@@ -4946,7 +4883,7 @@ async function smoke(options) {
|
|
|
4946
4883
|
if (mode !== "hosted") throw new Error(`ai mode mismatch: expected "hosted", public-config has "${String(mode ?? "none")}"`);
|
|
4947
4884
|
out.log(" ai: hosted");
|
|
4948
4885
|
}
|
|
4949
|
-
if (hasO11y) out.log(` o11y: credentials present`);
|
|
4886
|
+
if (hasO11y) out.log(runtime ? ` o11y: Worker-held credential` : ` o11y: credentials present`);
|
|
4950
4887
|
if (cfg.services.includes("calendar")) {
|
|
4951
4888
|
const token = await getDeveloperToken(
|
|
4952
4889
|
cfg,
|
|
@@ -4965,10 +4902,10 @@ async function smoke(options) {
|
|
|
4965
4902
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
4966
4903
|
}
|
|
4967
4904
|
const database = await resolveDatabaseConfig(cfg);
|
|
4968
|
-
if (hasDb) {
|
|
4905
|
+
if (hasDb && !runtime) {
|
|
4969
4906
|
const expectedSchema = database.schema;
|
|
4970
4907
|
const expectedEntities = serializedEntities(expectedSchema);
|
|
4971
|
-
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4908
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, entry.dbKey);
|
|
4972
4909
|
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
4973
4910
|
const liveEntities = serializedEntities(liveSchema);
|
|
4974
4911
|
if (expectedEntities.length) {
|
|
@@ -4978,7 +4915,7 @@ async function smoke(options) {
|
|
|
4978
4915
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
4979
4916
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
4980
4917
|
if (aggregateEntity) {
|
|
4981
|
-
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4918
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/aggregate`, entry.dbKey, {
|
|
4982
4919
|
ns: aggregateEntity,
|
|
4983
4920
|
aggregate: { count: true }
|
|
4984
4921
|
});
|
|
@@ -4987,9 +4924,20 @@ async function smoke(options) {
|
|
|
4987
4924
|
} else {
|
|
4988
4925
|
out.log(` aggregate: skipped (schema has no entities)`);
|
|
4989
4926
|
}
|
|
4927
|
+
} else if (hasDb) {
|
|
4928
|
+
out.log(` db: Worker-held credential (direct schema/aggregate skipped)`);
|
|
4990
4929
|
} else {
|
|
4991
4930
|
out.log(` db: skipped (not enabled)`);
|
|
4992
4931
|
}
|
|
4932
|
+
if (runtime) {
|
|
4933
|
+
const link = cfg.links?.[env];
|
|
4934
|
+
if (!link) throw new Error(`runtime smoke requires links.${env} in odla.config.mjs`);
|
|
4935
|
+
const res = await doFetch(link, { redirect: "manual" });
|
|
4936
|
+
if (res.status < 200 || res.status >= 500) {
|
|
4937
|
+
throw new Error(`runtime target ${new URL(link).origin} returned ${res.status}`);
|
|
4938
|
+
}
|
|
4939
|
+
out.log(` runtime target: ${res.status}`);
|
|
4940
|
+
}
|
|
4993
4941
|
const probes = database.integrations.flatMap(
|
|
4994
4942
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
4995
4943
|
);
|
|
@@ -5192,10 +5140,11 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5192
5140
|
return true;
|
|
5193
5141
|
}
|
|
5194
5142
|
if (command === "smoke") {
|
|
5195
|
-
assertArgs(parsed, ["config", "env", "token", "email", "open"], 1);
|
|
5143
|
+
assertArgs(parsed, ["config", "env", "runtime", "token", "email", "open"], 1);
|
|
5196
5144
|
await smoke({
|
|
5197
5145
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5198
5146
|
env: stringOpt(parsed.options.env),
|
|
5147
|
+
runtime: parsed.options.runtime === true,
|
|
5199
5148
|
token: stringOpt(parsed.options.token),
|
|
5200
5149
|
email: stringOpt(parsed.options.email),
|
|
5201
5150
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
@@ -9038,7 +8987,7 @@ Usage:
|
|
|
9038
8987
|
odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9039
8988
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
9040
8989
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
9041
|
-
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
8990
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
9042
8991
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9043
8992
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
9044
8993
|
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
@@ -9140,7 +9089,7 @@ Commands:
|
|
|
9140
9089
|
"provision --live --yes" initializes only the live instance of
|
|
9141
9090
|
an existing sandbox app and enables every configured service;
|
|
9142
9091
|
no edit to the dev-first envs list is required.
|
|
9143
|
-
smoke Verify
|
|
9092
|
+
smoke Verify local service access, or use --runtime for a Worker-held credential deployment.
|
|
9144
9093
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
9145
9094
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
9146
9095
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
@@ -9180,12 +9129,11 @@ Safety:
|
|
|
9180
9129
|
attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
|
|
9181
9130
|
shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
|
|
9182
9131
|
agents with browser control must open that exact URL immediately; otherwise
|
|
9183
|
-
they must give it to the human verbatim. A
|
|
9184
|
-
|
|
9185
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
9132
|
+
they must give it to the human verbatim. A device code remains only in the
|
|
9133
|
+
running process. Outside an interactive terminal the wait is capped (90s by
|
|
9186
9134
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
9187
|
-
with code 75
|
|
9188
|
-
|
|
9135
|
+
with code 75. Rerunning always requests and opens a new code; older clients'
|
|
9136
|
+
persisted pending state is discarded.
|
|
9189
9137
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
9190
9138
|
The email is a non-secret identity hint: never provide a password or session
|
|
9191
9139
|
token. It is the email shown by the signed-in odla account \u2014 never infer it
|