@odla-ai/cli 0.30.0 → 0.30.2
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 +24 -7
- package/dist/bin.cjs +154 -155
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-PPSXHZJI.js → chunk-3HFNKDZV.js} +120 -129
- package/dist/chunk-3HFNKDZV.js.map +1 -0
- package/dist/{cli-CKBUVTJM.js → cli-BGSX5Z4C.js} +2 -2
- package/dist/index.cjs +133 -142
- 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/skills/odla/references/pm-work-intake.md +11 -2
- package/skills/odla/references/pm.md +35 -0
- package/dist/chunk-PPSXHZJI.js.map +0 -1
- /package/dist/{cli-CKBUVTJM.js.map → cli-BGSX5Z4C.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-BGSX5Z4C.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
|
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import process8 from "process";
|
|
14
14
|
|
|
15
15
|
// src/token.ts
|
|
16
|
-
import {
|
|
16
|
+
import { OdlaError, requestToken } from "@odla-ai/db";
|
|
17
17
|
import { createHash } from "crypto";
|
|
18
18
|
import process5 from "process";
|
|
19
19
|
|
|
@@ -117,12 +117,40 @@ function handshakeUrl(platformUrl, userCode) {
|
|
|
117
117
|
|
|
118
118
|
// src/handshake-state.ts
|
|
119
119
|
import { rmSync } from "fs";
|
|
120
|
-
import { dirname
|
|
120
|
+
import { dirname, join } from "path";
|
|
121
121
|
import process4 from "process";
|
|
122
|
+
function handshakeFile(cfg) {
|
|
123
|
+
return join(dirname(cfg.local.tokenFile), "handshake.local.json");
|
|
124
|
+
}
|
|
125
|
+
function clearPendingHandshake(path) {
|
|
126
|
+
rmSync(path, { force: true });
|
|
127
|
+
}
|
|
128
|
+
function minutesLeft(expiresAt) {
|
|
129
|
+
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
130
|
+
}
|
|
131
|
+
function approvalHint(pending) {
|
|
132
|
+
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
133
|
+
}
|
|
134
|
+
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
135
|
+
const timer = setInterval(() => {
|
|
136
|
+
for (const line of reminderLines({
|
|
137
|
+
userCode: pending.userCode,
|
|
138
|
+
approvalUrl: pending.approvalUrl,
|
|
139
|
+
minutesLeft: minutesLeft(pending.expiresAt)
|
|
140
|
+
}))
|
|
141
|
+
out.log(line);
|
|
142
|
+
}, periodMs);
|
|
143
|
+
timer.unref?.();
|
|
144
|
+
return () => clearInterval(timer);
|
|
145
|
+
}
|
|
146
|
+
function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === true) {
|
|
147
|
+
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
148
|
+
return interactive ? void 0 : 9e4;
|
|
149
|
+
}
|
|
122
150
|
|
|
123
151
|
// src/local.ts
|
|
124
152
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
125
|
-
import { dirname, isAbsolute, relative, resolve } from "path";
|
|
153
|
+
import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
|
|
126
154
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
127
155
|
function readJsonFile(path) {
|
|
128
156
|
try {
|
|
@@ -190,7 +218,7 @@ function o11yDevVars(cfg) {
|
|
|
190
218
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
191
219
|
if (!requested) return null;
|
|
192
220
|
if (requested === true) return cfg.local.devVarsFile;
|
|
193
|
-
return resolve(
|
|
221
|
+
return resolve(dirname2(cfg.configPath), requested);
|
|
194
222
|
}
|
|
195
223
|
function writeDevVars(path, credentials, env, o11y) {
|
|
196
224
|
const entry = credentials.envs[env];
|
|
@@ -230,7 +258,7 @@ function isManagedDevVar(line) {
|
|
|
230
258
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
231
259
|
}
|
|
232
260
|
function writePrivateText(path, text2) {
|
|
233
|
-
mkdirSync(
|
|
261
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
234
262
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
235
263
|
writeFileSync(temporary, text2, { mode: 384 });
|
|
236
264
|
chmodSync(temporary, 384);
|
|
@@ -246,58 +274,6 @@ function displayPath(path, rootDir = process.cwd()) {
|
|
|
246
274
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
247
275
|
}
|
|
248
276
|
|
|
249
|
-
// src/handshake-state.ts
|
|
250
|
-
function handshakeFile(cfg) {
|
|
251
|
-
return join(dirname2(cfg.local.tokenFile), "handshake.local.json");
|
|
252
|
-
}
|
|
253
|
-
var RESUME_MARGIN_MS = 5e3;
|
|
254
|
-
function readPendingHandshake(path, platform, email, requiredGrant) {
|
|
255
|
-
const pending = readJsonFile(path);
|
|
256
|
-
if (!pending || pending.platform !== platform || pending.email !== email) return null;
|
|
257
|
-
if (typeof pending.userCode !== "string" || typeof pending.deviceCode !== "string") return null;
|
|
258
|
-
if (typeof pending.expiresAt !== "number" || pending.expiresAt <= Date.now() + RESUME_MARGIN_MS) return null;
|
|
259
|
-
if (requiredGrant && !grantCovers(pending, requiredGrant)) return null;
|
|
260
|
-
return {
|
|
261
|
-
interval: typeof pending.interval === "number" ? pending.interval : 3,
|
|
262
|
-
...pending,
|
|
263
|
-
approvalUrl: handshakeUrl(platform, pending.userCode)
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function grantCovers(stored, required) {
|
|
267
|
-
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
268
|
-
return required.projectIds.every((id) => stored.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
|
|
269
|
-
(capability) => stored.optionalProjectCapabilities?.includes(capability)
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
function writePendingHandshake(path, pending) {
|
|
273
|
-
writePrivateJson(path, pending);
|
|
274
|
-
}
|
|
275
|
-
function clearPendingHandshake(path) {
|
|
276
|
-
rmSync(path, { force: true });
|
|
277
|
-
}
|
|
278
|
-
function minutesLeft(expiresAt) {
|
|
279
|
-
return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
|
|
280
|
-
}
|
|
281
|
-
function approvalHint(pending) {
|
|
282
|
-
return `approve code ${pending.userCode} at ${pending.approvalUrl} (${minutesLeft(pending.expiresAt)}m left)`;
|
|
283
|
-
}
|
|
284
|
-
function approvalReminder(out, pending, periodMs = 3e4) {
|
|
285
|
-
const timer = setInterval(() => {
|
|
286
|
-
for (const line of reminderLines({
|
|
287
|
-
userCode: pending.userCode,
|
|
288
|
-
approvalUrl: pending.approvalUrl,
|
|
289
|
-
minutesLeft: minutesLeft(pending.expiresAt)
|
|
290
|
-
}))
|
|
291
|
-
out.log(line);
|
|
292
|
-
}, periodMs);
|
|
293
|
-
timer.unref?.();
|
|
294
|
-
return () => clearInterval(timer);
|
|
295
|
-
}
|
|
296
|
-
function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === true) {
|
|
297
|
-
if (waitSeconds !== void 0) return waitSeconds * 1e3;
|
|
298
|
-
return interactive ? void 0 : 9e4;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
277
|
// src/token.ts
|
|
302
278
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
303
279
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -336,8 +312,8 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
336
312
|
grantIntent
|
|
337
313
|
};
|
|
338
314
|
const waitMs = handshakeWaitMs(options.wait);
|
|
339
|
-
|
|
340
|
-
const { token, expiresAt } = await
|
|
315
|
+
clearPendingHandshake(ctx.pendingFile);
|
|
316
|
+
const { token, expiresAt } = await freshHandshake(ctx, waitMs);
|
|
341
317
|
clearPendingHandshake(ctx.pendingFile);
|
|
342
318
|
writePrivateJson(cfg.local.tokenFile, {
|
|
343
319
|
platform: audience,
|
|
@@ -350,47 +326,6 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
350
326
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
351
327
|
return token;
|
|
352
328
|
}
|
|
353
|
-
async function resumePendingHandshake(ctx, waitMs) {
|
|
354
|
-
const pending = readPendingHandshake(
|
|
355
|
-
ctx.pendingFile,
|
|
356
|
-
ctx.audience,
|
|
357
|
-
ctx.email,
|
|
358
|
-
ctx.grantIntent
|
|
359
|
-
);
|
|
360
|
-
if (!pending) return null;
|
|
361
|
-
ctx.out.error("");
|
|
362
|
-
ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
|
|
363
|
-
await presentHandshakeApproval(ctx.out, {
|
|
364
|
-
userCode: pending.userCode,
|
|
365
|
-
approvalUrl: pending.approvalUrl,
|
|
366
|
-
minutesLeft: Math.max(1, Math.floor((pending.expiresAt - Date.now()) / 6e4)),
|
|
367
|
-
purpose: `sign this terminal in as ${ctx.email}`
|
|
368
|
-
}, ctx.options);
|
|
369
|
-
ctx.out.error("");
|
|
370
|
-
const stopReminder = approvalReminder(ctx.out, pending);
|
|
371
|
-
try {
|
|
372
|
-
return await collectToken({
|
|
373
|
-
endpoint: ctx.cfg.platformUrl,
|
|
374
|
-
deviceCode: pending.deviceCode,
|
|
375
|
-
expiresAt: pending.expiresAt,
|
|
376
|
-
interval: pending.interval,
|
|
377
|
-
waitMs,
|
|
378
|
-
fetch: ctx.doFetch
|
|
379
|
-
});
|
|
380
|
-
} catch (err) {
|
|
381
|
-
const code = err instanceof OdlaError ? err.code : void 0;
|
|
382
|
-
if (code === "handshake_pending") throw stillPending(pending, ctx.email);
|
|
383
|
-
if (code === "handshake_expired" || code === "handshake_timeout") {
|
|
384
|
-
clearPendingHandshake(ctx.pendingFile);
|
|
385
|
-
ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
|
|
389
|
-
throw err;
|
|
390
|
-
} finally {
|
|
391
|
-
stopReminder();
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
329
|
async function freshHandshake(ctx, waitMs) {
|
|
395
330
|
let started;
|
|
396
331
|
let stopReminder;
|
|
@@ -418,7 +353,6 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
418
353
|
projectIds: ctx.grantIntent.projectIds,
|
|
419
354
|
optionalProjectCapabilities: ctx.grantIntent.optionalProjectCapabilities
|
|
420
355
|
};
|
|
421
|
-
writePendingHandshake(ctx.pendingFile, started);
|
|
422
356
|
await presentHandshakeApproval(ctx.out, {
|
|
423
357
|
userCode,
|
|
424
358
|
approvalUrl,
|
|
@@ -454,7 +388,7 @@ function projectAgentHandle(appId) {
|
|
|
454
388
|
function stillPending(pending, email) {
|
|
455
389
|
return new OdlaError(
|
|
456
390
|
"handshake_pending",
|
|
457
|
-
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}
|
|
391
|
+
`handshake still pending \u2014 ask ${email} to ${approvalHint(pending)}; if this command exits, re-run it to request a new code`,
|
|
458
392
|
{ retryable: true }
|
|
459
393
|
);
|
|
460
394
|
}
|
|
@@ -4781,28 +4715,31 @@ async function smoke(options) {
|
|
|
4781
4715
|
const out = options.stdout ?? console;
|
|
4782
4716
|
const cfg = await loadProjectConfig(options.configPath);
|
|
4783
4717
|
const env = resolveEnv(cfg, options.env);
|
|
4784
|
-
const
|
|
4785
|
-
|
|
4718
|
+
const runtime = options.runtime === true;
|
|
4719
|
+
const credentials = runtime ? null : readCredentials(cfg.local.credentialsFile);
|
|
4720
|
+
if (!runtime && !credentials) {
|
|
4786
4721
|
throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
|
|
4787
4722
|
}
|
|
4788
|
-
if (credentials.appId !== cfg.app.id) {
|
|
4723
|
+
if (credentials && credentials.appId !== cfg.app.id) {
|
|
4789
4724
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
4790
4725
|
}
|
|
4791
|
-
const entry = credentials
|
|
4792
|
-
if (!entry?.tenantId) {
|
|
4726
|
+
const entry = credentials?.envs[env];
|
|
4727
|
+
if (!runtime && !entry?.tenantId) {
|
|
4793
4728
|
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4794
4729
|
}
|
|
4795
4730
|
const hasDb = cfg.services.includes("db");
|
|
4796
4731
|
const hasO11y = cfg.services.includes("o11y");
|
|
4797
|
-
if (hasDb && !entry
|
|
4732
|
+
if (!runtime && hasDb && !entry?.dbKey) {
|
|
4798
4733
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4799
4734
|
}
|
|
4800
|
-
if (hasO11y && !entry
|
|
4735
|
+
if (!runtime && hasO11y && !entry?.o11yToken) {
|
|
4801
4736
|
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
4802
4737
|
}
|
|
4803
4738
|
const doFetch = options.fetch ?? fetch;
|
|
4739
|
+
const tenantId = entry?.tenantId ?? resolveTenant(cfg, env).tenant;
|
|
4804
4740
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
4805
|
-
out.log(`
|
|
4741
|
+
out.log(` mode: ${runtime ? "runtime (Worker-held credentials)" : "local credentials"}`);
|
|
4742
|
+
out.log(` tenant: ${tenantId}`);
|
|
4806
4743
|
const publicConfig = await getJson(doFetch, publicConfigUrl(cfg.platformUrl, cfg.app.id, env), void 0);
|
|
4807
4744
|
out.log(` public-config: ok`);
|
|
4808
4745
|
if (cfg.services.includes("ai") && cfg.ai?.provider) {
|
|
@@ -4816,7 +4753,7 @@ async function smoke(options) {
|
|
|
4816
4753
|
if (mode !== "hosted") throw new Error(`ai mode mismatch: expected "hosted", public-config has "${String(mode ?? "none")}"`);
|
|
4817
4754
|
out.log(" ai: hosted");
|
|
4818
4755
|
}
|
|
4819
|
-
if (hasO11y) out.log(` o11y: credentials present`);
|
|
4756
|
+
if (hasO11y) out.log(runtime ? ` o11y: Worker-held credential` : ` o11y: credentials present`);
|
|
4820
4757
|
if (cfg.services.includes("calendar")) {
|
|
4821
4758
|
const token = await getDeveloperToken(
|
|
4822
4759
|
cfg,
|
|
@@ -4835,10 +4772,10 @@ async function smoke(options) {
|
|
|
4835
4772
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
4836
4773
|
}
|
|
4837
4774
|
const database = await resolveDatabaseConfig(cfg);
|
|
4838
|
-
if (hasDb) {
|
|
4775
|
+
if (hasDb && !runtime) {
|
|
4839
4776
|
const expectedSchema = database.schema;
|
|
4840
4777
|
const expectedEntities = serializedEntities(expectedSchema);
|
|
4841
|
-
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4778
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, entry.dbKey);
|
|
4842
4779
|
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
4843
4780
|
const liveEntities = serializedEntities(liveSchema);
|
|
4844
4781
|
if (expectedEntities.length) {
|
|
@@ -4848,7 +4785,7 @@ async function smoke(options) {
|
|
|
4848
4785
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
4849
4786
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
4850
4787
|
if (aggregateEntity) {
|
|
4851
|
-
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(
|
|
4788
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/aggregate`, entry.dbKey, {
|
|
4852
4789
|
ns: aggregateEntity,
|
|
4853
4790
|
aggregate: { count: true }
|
|
4854
4791
|
});
|
|
@@ -4857,9 +4794,20 @@ async function smoke(options) {
|
|
|
4857
4794
|
} else {
|
|
4858
4795
|
out.log(` aggregate: skipped (schema has no entities)`);
|
|
4859
4796
|
}
|
|
4797
|
+
} else if (hasDb) {
|
|
4798
|
+
out.log(` db: Worker-held credential (direct schema/aggregate skipped)`);
|
|
4860
4799
|
} else {
|
|
4861
4800
|
out.log(` db: skipped (not enabled)`);
|
|
4862
4801
|
}
|
|
4802
|
+
if (runtime) {
|
|
4803
|
+
const link = cfg.links?.[env];
|
|
4804
|
+
if (!link) throw new Error(`runtime smoke requires links.${env} in odla.config.mjs`);
|
|
4805
|
+
const res = await doFetch(link, { redirect: "manual" });
|
|
4806
|
+
if (res.status < 200 || res.status >= 500) {
|
|
4807
|
+
throw new Error(`runtime target ${new URL(link).origin} returned ${res.status}`);
|
|
4808
|
+
}
|
|
4809
|
+
out.log(` runtime target: ${res.status}`);
|
|
4810
|
+
}
|
|
4863
4811
|
const probes = database.integrations.flatMap(
|
|
4864
4812
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
4865
4813
|
);
|
|
@@ -5062,10 +5010,11 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5062
5010
|
return true;
|
|
5063
5011
|
}
|
|
5064
5012
|
if (command === "smoke") {
|
|
5065
|
-
assertArgs(parsed, ["config", "env", "token", "email", "open"], 1);
|
|
5013
|
+
assertArgs(parsed, ["config", "env", "runtime", "token", "email", "open"], 1);
|
|
5066
5014
|
await smoke({
|
|
5067
5015
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5068
5016
|
env: stringOpt(parsed.options.env),
|
|
5017
|
+
runtime: parsed.options.runtime === true,
|
|
5069
5018
|
token: stringOpt(parsed.options.token),
|
|
5070
5019
|
email: stringOpt(parsed.options.email),
|
|
5071
5020
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
@@ -8840,6 +8789,7 @@ Usage:
|
|
|
8840
8789
|
odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
|
|
8841
8790
|
odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
|
|
8842
8791
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
8792
|
+
odla-ai pm <goal|task|decision|bug> link <id> [--json]
|
|
8843
8793
|
odla-ai pm <goal|task|decision|bug> ref <id> [--json]
|
|
8844
8794
|
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
8845
8795
|
odla-ai pm task set <id> [--title <t>|--column <backlog|ready|doing|review|done>|--rank <n>|--goal <id>|--no-goal|--alignment-decision <id>|--no-alignment-decision|--execution <human|agent|either>|--assignee <id>|--no-assignee|--description <text>|--body <text>|--acceptance <text>|--no-acceptance|--due <epoch-ms>|--no-due|--expected-revision <n>] [--mutation-id <id>] [--json]
|
|
@@ -8908,7 +8858,7 @@ Usage:
|
|
|
8908
8858
|
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]
|
|
8909
8859
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
8910
8860
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
8911
|
-
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
8861
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
8912
8862
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8913
8863
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
8914
8864
|
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
@@ -9010,7 +8960,7 @@ Commands:
|
|
|
9010
8960
|
"provision --live --yes" initializes only the live instance of
|
|
9011
8961
|
an existing sandbox app and enables every configured service;
|
|
9012
8962
|
no edit to the dev-first envs list is required.
|
|
9013
|
-
smoke Verify
|
|
8963
|
+
smoke Verify local service access, or use --runtime for a Worker-held credential deployment.
|
|
9014
8964
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
9015
8965
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
9016
8966
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
@@ -9050,12 +9000,11 @@ Safety:
|
|
|
9050
9000
|
attempts to open it \u2014 including in CI, SSH, display-less, and agent-driven
|
|
9051
9001
|
shells. Only --no-open suppresses the attempt. Browser launch is best-effort:
|
|
9052
9002
|
agents with browser control must open that exact URL immediately; otherwise
|
|
9053
|
-
they must give it to the human verbatim. A
|
|
9054
|
-
|
|
9055
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
9003
|
+
they must give it to the human verbatim. A device code remains only in the
|
|
9004
|
+
running process. Outside an interactive terminal the wait is capped (90s by
|
|
9056
9005
|
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
9057
|
-
with code 75
|
|
9058
|
-
|
|
9006
|
+
with code 75. Rerunning always requests and opens a new code; older clients'
|
|
9007
|
+
persisted pending state is discarded.
|
|
9059
9008
|
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
9060
9009
|
The email is a non-secret identity hint: never provide a password or session
|
|
9061
9010
|
token. It is the email shown by the signed-in odla account \u2014 never infer it
|
|
@@ -9706,9 +9655,25 @@ function referenceMarkup(entity, record10) {
|
|
|
9706
9655
|
const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
|
|
9707
9656
|
return `@[${label}](pm:${entity}/${record10.id})`;
|
|
9708
9657
|
}
|
|
9658
|
+
var STUDIO_SECTION = {
|
|
9659
|
+
goal: "goals",
|
|
9660
|
+
task: "board",
|
|
9661
|
+
decision: "decisions",
|
|
9662
|
+
bug: "bugs"
|
|
9663
|
+
};
|
|
9664
|
+
function studioRecordUrl(ctx, entity, id) {
|
|
9665
|
+
return new URL(
|
|
9666
|
+
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id)}`,
|
|
9667
|
+
ctx.platformUrl
|
|
9668
|
+
).href;
|
|
9669
|
+
}
|
|
9670
|
+
function studioRecordLink(ctx, entity, record10) {
|
|
9671
|
+
const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
|
|
9672
|
+
return `[${label}](${studioRecordUrl(ctx, entity, record10.id)})`;
|
|
9673
|
+
}
|
|
9709
9674
|
function printRecord(ctx, entity, record10) {
|
|
9710
9675
|
ctx.out.log(
|
|
9711
|
-
`${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${
|
|
9676
|
+
`${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${studioRecordLink(ctx, entity, record10)}`
|
|
9712
9677
|
);
|
|
9713
9678
|
}
|
|
9714
9679
|
function emit2(ctx, value2, human) {
|
|
@@ -9758,7 +9723,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9758
9723
|
input,
|
|
9759
9724
|
mutationId: writeMutationId2(parsed)
|
|
9760
9725
|
});
|
|
9761
|
-
|
|
9726
|
+
const record10 = { id: res.id, appId, title: String(input.title) };
|
|
9727
|
+
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record10)}`));
|
|
9762
9728
|
}
|
|
9763
9729
|
async function pmGet(ctx, entity, id) {
|
|
9764
9730
|
const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
@@ -9783,7 +9749,10 @@ async function pmSet(ctx, entity, id, parsed) {
|
|
|
9783
9749
|
patch: patch2,
|
|
9784
9750
|
mutationId: writeMutationId2(parsed)
|
|
9785
9751
|
});
|
|
9786
|
-
emit2(ctx, res, () =>
|
|
9752
|
+
emit2(ctx, res, () => {
|
|
9753
|
+
if (!res.record) return ctx.out.log(`updated ${entity} ${id}`);
|
|
9754
|
+
ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
|
|
9755
|
+
});
|
|
9787
9756
|
}
|
|
9788
9757
|
async function pmDone(ctx, entity, id, parsed) {
|
|
9789
9758
|
const decisionId = stringOpt(parsed.options.decision);
|
|
@@ -9793,7 +9762,11 @@ async function pmDone(ctx, entity, id, parsed) {
|
|
|
9793
9762
|
patch: patch2,
|
|
9794
9763
|
mutationId: writeMutationId2(parsed)
|
|
9795
9764
|
});
|
|
9796
|
-
emit2(ctx, res, () =>
|
|
9765
|
+
emit2(ctx, res, () => {
|
|
9766
|
+
const label = res.record ? studioRecordLink(ctx, entity, res.record) : id;
|
|
9767
|
+
const state2 = res.record ? statusCol(entity, res.record) : "done";
|
|
9768
|
+
ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
|
|
9769
|
+
});
|
|
9797
9770
|
}
|
|
9798
9771
|
async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
9799
9772
|
const rawRevision = stringOpt(parsed.options["expected-revision"]);
|
|
@@ -9822,7 +9795,8 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
9822
9795
|
);
|
|
9823
9796
|
emit2(ctx, res, () => {
|
|
9824
9797
|
const state2 = res.record ? statusCol("task", res.record) : action2;
|
|
9825
|
-
|
|
9798
|
+
const label = res.record ? studioRecordLink(ctx, "task", res.record) : id;
|
|
9799
|
+
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
9826
9800
|
});
|
|
9827
9801
|
}
|
|
9828
9802
|
async function allRecords(ctx, entity, appId) {
|
|
@@ -9919,6 +9893,20 @@ async function pmRemove(ctx, entity, id) {
|
|
|
9919
9893
|
ctx.out.log(`deleted ${entity} ${id}`);
|
|
9920
9894
|
}
|
|
9921
9895
|
|
|
9896
|
+
// src/pm-links.ts
|
|
9897
|
+
async function pmLink(ctx, entity, id) {
|
|
9898
|
+
const { record: record10 } = await pmRequest(
|
|
9899
|
+
ctx,
|
|
9900
|
+
"GET",
|
|
9901
|
+
`/${entity}/${encodeURIComponent(id)}`
|
|
9902
|
+
);
|
|
9903
|
+
const url = studioRecordUrl(ctx, entity, record10.id);
|
|
9904
|
+
const markdown = studioRecordLink(ctx, entity, record10);
|
|
9905
|
+
emit2(ctx, { kind: entity, id: record10.id, label: record10.title ?? "", url, markdown }, () => {
|
|
9906
|
+
ctx.out.log(markdown);
|
|
9907
|
+
});
|
|
9908
|
+
}
|
|
9909
|
+
|
|
9922
9910
|
// src/pm-comments.ts
|
|
9923
9911
|
async function pmComment(ctx, entity, id, parsed) {
|
|
9924
9912
|
const body = stringOpt(parsed.options.body);
|
|
@@ -10166,6 +10154,7 @@ var ACTION_OPTIONS = {
|
|
|
10166
10154
|
ready: ["goal", "alignment-decision", "execution", "description", "desc", "body", "acceptance", "expected-revision", "mutation-id"],
|
|
10167
10155
|
claim: ["expected-revision", "mutation-id"],
|
|
10168
10156
|
release: ["expected-revision", "mutation-id"],
|
|
10157
|
+
link: [],
|
|
10169
10158
|
ref: []
|
|
10170
10159
|
};
|
|
10171
10160
|
var ENTITY_OPTIONS = {
|
|
@@ -10270,7 +10259,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
10270
10259
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
10271
10260
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
10272
10261
|
const action2 = canonicalAction(requestedAction);
|
|
10273
|
-
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
10262
|
+
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|rm.`);
|
|
10274
10263
|
assertArgs(parsed, allowedOptions(entity, action2), 4);
|
|
10275
10264
|
if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
|
|
10276
10265
|
throw new Error(`pm ${action2} is only valid for tasks`);
|
|
@@ -10294,6 +10283,8 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
10294
10283
|
return pmComments(ctx, entity, requireId2(id, action2));
|
|
10295
10284
|
case "rm":
|
|
10296
10285
|
return pmRemove(ctx, entity, requireId2(id, action2));
|
|
10286
|
+
case "link":
|
|
10287
|
+
return pmLink(ctx, entity, requireId2(id, action2));
|
|
10297
10288
|
case "ref":
|
|
10298
10289
|
return pmReference(ctx, entity, requireId2(id, action2));
|
|
10299
10290
|
case "ready":
|
|
@@ -13246,4 +13237,4 @@ export {
|
|
|
13246
13237
|
isTerminalHostedSecurityStatus,
|
|
13247
13238
|
runCli
|
|
13248
13239
|
};
|
|
13249
|
-
//# sourceMappingURL=chunk-
|
|
13240
|
+
//# sourceMappingURL=chunk-3HFNKDZV.js.map
|