@odla-ai/cli 0.35.1 → 0.36.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 +489 -231
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-SRL2TN24.js → chunk-DBZQIMES.js} +357 -137
- package/dist/chunk-DBZQIMES.js.map +1 -0
- package/dist/{cli-HS35QLXR.js → cli-BN6WLH5O.js} +2 -2
- package/dist/index.cjs +424 -204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-SRL2TN24.js.map +0 -1
- /package/dist/{cli-HS35QLXR.js.map → cli-BN6WLH5O.js.map} +0 -0
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
} from "./chunk-UKLSRQ5J.js";
|
|
11
11
|
|
|
12
12
|
// src/admin-ai.ts
|
|
13
|
-
import
|
|
13
|
+
import process9 from "process";
|
|
14
14
|
|
|
15
15
|
// src/token.ts
|
|
16
16
|
import { OdlaError, requestToken } from "@odla-ai/db";
|
|
17
17
|
import { createHash } from "crypto";
|
|
18
|
-
import
|
|
18
|
+
import process6 from "process";
|
|
19
19
|
|
|
20
20
|
// src/handshake-approval.ts
|
|
21
21
|
import process3 from "process";
|
|
@@ -149,13 +149,72 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
|
|
|
149
149
|
return interactive ? void 0 : 9e4;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
// src/cached-credential.ts
|
|
153
|
+
import { rmSync as rmSync2 } from "fs";
|
|
154
|
+
var noted = null;
|
|
155
|
+
function noteCachedCredential(tokenFile) {
|
|
156
|
+
noted = tokenFile;
|
|
157
|
+
}
|
|
158
|
+
function isCredentialRejection(error) {
|
|
159
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
160
|
+
return /\((401|403)\)\s*$/.test(message2.trim());
|
|
161
|
+
}
|
|
162
|
+
function explainRejectedCredential(error) {
|
|
163
|
+
const tokenFile = noted;
|
|
164
|
+
if (!tokenFile || !isCredentialRejection(error)) return null;
|
|
165
|
+
noted = null;
|
|
166
|
+
rmSync2(tokenFile, { force: true });
|
|
167
|
+
return [
|
|
168
|
+
"auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
|
|
169
|
+
" The usual cause is a newer sign-in for this project: collecting a handshake retires the",
|
|
170
|
+
" principal's other credentials, so a second terminal or worktree supersedes this one.",
|
|
171
|
+
` Discarded ${tokenFile}; re-run this command to request a fresh approval.`
|
|
172
|
+
].join("\n");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/device-session.ts
|
|
176
|
+
import { existsSync, readFileSync } from "fs";
|
|
177
|
+
import { homedir } from "os";
|
|
178
|
+
import { join as join2 } from "path";
|
|
179
|
+
import process5 from "process";
|
|
180
|
+
function deviceCredentialPath(env = process5.env) {
|
|
181
|
+
return env.ODLA_DEVICE_CREDENTIAL ?? join2(env.HOME ?? homedir(), ".odla", "device.json");
|
|
182
|
+
}
|
|
183
|
+
function readDeviceCredential(platform, env = process5.env) {
|
|
184
|
+
const path = deviceCredentialPath(env);
|
|
185
|
+
if (!existsSync(path)) return null;
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
188
|
+
if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
|
|
189
|
+
if (parsed.platform !== platform) return null;
|
|
190
|
+
return { ...parsed, token: parsed.token, platform: parsed.platform };
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function mintDeviceSession(platformUrl, credential2, doFetch) {
|
|
196
|
+
const response2 = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/devices/session`, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { authorization: `Bearer ${credential2.token}`, "content-type": "application/json" },
|
|
199
|
+
body: "{}"
|
|
200
|
+
});
|
|
201
|
+
const body = await response2.json().catch(() => ({}));
|
|
202
|
+
if (!response2.ok || typeof body.token !== "string") {
|
|
203
|
+
const detail = body.error?.message ?? `registry returned ${response2.status}`;
|
|
204
|
+
throw new Error(
|
|
205
|
+
`device session failed: ${detail} (${response2.status}) \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return { token: body.token, expiresAt: body.expiresAt ?? Date.now() };
|
|
209
|
+
}
|
|
210
|
+
|
|
152
211
|
// src/local.ts
|
|
153
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
212
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
154
213
|
import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
|
|
155
214
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
156
215
|
function readJsonFile(path) {
|
|
157
216
|
try {
|
|
158
|
-
return JSON.parse(
|
|
217
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
159
218
|
} catch {
|
|
160
219
|
return null;
|
|
161
220
|
}
|
|
@@ -165,10 +224,10 @@ function writePrivateJson(path, value2) {
|
|
|
165
224
|
`);
|
|
166
225
|
}
|
|
167
226
|
function readCredentials(path) {
|
|
168
|
-
if (!
|
|
227
|
+
if (!existsSync2(path)) return null;
|
|
169
228
|
let value2;
|
|
170
229
|
try {
|
|
171
|
-
value2 = JSON.parse(
|
|
230
|
+
value2 = JSON.parse(readFileSync2(path, "utf8"));
|
|
172
231
|
} catch {
|
|
173
232
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
174
233
|
}
|
|
@@ -199,7 +258,7 @@ function mergeCredential(current, update) {
|
|
|
199
258
|
}
|
|
200
259
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
201
260
|
const path = resolve(rootDir, ".gitignore");
|
|
202
|
-
const existing =
|
|
261
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
203
262
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
|
|
204
263
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
205
264
|
const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
|
|
@@ -233,7 +292,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
233
292
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
234
293
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
235
294
|
}
|
|
236
|
-
const existing =
|
|
295
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
237
296
|
const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
|
|
238
297
|
while (retained.at(-1) === "") retained.pop();
|
|
239
298
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -283,17 +342,24 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
283
342
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
284
343
|
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
285
344
|
if (options.token) return options.token;
|
|
286
|
-
if (
|
|
287
|
-
const declared =
|
|
345
|
+
if (process6.env.ODLA_DEV_TOKEN) {
|
|
346
|
+
const declared = process6.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
288
347
|
if (declared) {
|
|
289
348
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
290
349
|
} else if (audience !== "https://odla.ai") {
|
|
291
350
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
292
351
|
}
|
|
293
|
-
return
|
|
352
|
+
return process6.env.ODLA_DEV_TOKEN;
|
|
353
|
+
}
|
|
354
|
+
const device = readDeviceCredential(audience);
|
|
355
|
+
if (device) {
|
|
356
|
+
const session = await mintDeviceSession(cfg.platformUrl, device, doFetch);
|
|
357
|
+
out.error(`auth: session minted by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
|
|
358
|
+
return session.token;
|
|
294
359
|
}
|
|
295
360
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
296
361
|
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
362
|
+
noteCachedCredential(cfg.local.tokenFile);
|
|
297
363
|
return cached.token;
|
|
298
364
|
}
|
|
299
365
|
} else {
|
|
@@ -394,7 +460,7 @@ function stillPending(pending, email) {
|
|
|
394
460
|
);
|
|
395
461
|
}
|
|
396
462
|
function handshakeEmail(value2, cached) {
|
|
397
|
-
const email = (value2 ??
|
|
463
|
+
const email = (value2 ?? process6.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
398
464
|
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
399
465
|
throw new Error(
|
|
400
466
|
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
@@ -425,12 +491,12 @@ function platformAudience(value2) {
|
|
|
425
491
|
}
|
|
426
492
|
|
|
427
493
|
// src/secret-input.ts
|
|
428
|
-
import
|
|
494
|
+
import process7 from "process";
|
|
429
495
|
var MAX_BYTES = 64 * 1024;
|
|
430
496
|
async function secretInputValue(options, kind = "credential") {
|
|
431
497
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
432
498
|
let value2;
|
|
433
|
-
if (options.fromEnv) value2 =
|
|
499
|
+
if (options.fromEnv) value2 = process7.env[options.fromEnv];
|
|
434
500
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
435
501
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
436
502
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -438,7 +504,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
438
504
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
439
505
|
return value2;
|
|
440
506
|
}
|
|
441
|
-
async function readSecretStream(kind, stream =
|
|
507
|
+
async function readSecretStream(kind, stream = process7.stdin) {
|
|
442
508
|
let value2 = "";
|
|
443
509
|
for await (const chunk of stream) {
|
|
444
510
|
value2 += String(chunk);
|
|
@@ -448,9 +514,9 @@ async function readSecretStream(kind, stream = process6.stdin) {
|
|
|
448
514
|
}
|
|
449
515
|
|
|
450
516
|
// src/admin-ai-auth.ts
|
|
451
|
-
import { existsSync as
|
|
452
|
-
import { join as
|
|
453
|
-
import
|
|
517
|
+
import { existsSync as existsSync3 } from "fs";
|
|
518
|
+
import { join as join3 } from "path";
|
|
519
|
+
import process8 from "process";
|
|
454
520
|
import { requestToken as requestToken2 } from "@odla-ai/db";
|
|
455
521
|
async function getScopedPlatformToken(options) {
|
|
456
522
|
return resolveAdminPlatformToken(options);
|
|
@@ -458,7 +524,7 @@ async function getScopedPlatformToken(options) {
|
|
|
458
524
|
async function resolveAdminPlatformToken(options) {
|
|
459
525
|
const audience = platformAudience(options.platform);
|
|
460
526
|
if (options.token) return options.token;
|
|
461
|
-
const fromEnv =
|
|
527
|
+
const fromEnv = process8.env.ODLA_ADMIN_TOKEN;
|
|
462
528
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
463
529
|
return scopedToken(
|
|
464
530
|
audience,
|
|
@@ -470,7 +536,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
470
536
|
}
|
|
471
537
|
function audienceBoundEnvToken(token, platform) {
|
|
472
538
|
const audience = platformAudience(platform);
|
|
473
|
-
const declared =
|
|
539
|
+
const declared = process8.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
474
540
|
if (declared) {
|
|
475
541
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
476
542
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -483,6 +549,7 @@ var SCOPE_PURPOSE = {
|
|
|
483
549
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
484
550
|
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
485
551
|
"platform:runbook:write": "read and edit all of odla's operational runbooks, including admin-visible content",
|
|
552
|
+
"app:device:enroll": "enrol this machine so it can mint its own short-lived credentials without asking you again",
|
|
486
553
|
"platform:ai:policy:write": "change System AI model routing",
|
|
487
554
|
"platform:ai:policy:read": "read System AI model routing",
|
|
488
555
|
"platform:ai:credential:write": "replace a stored AI provider key",
|
|
@@ -495,8 +562,8 @@ var SCOPE_PURPOSE = {
|
|
|
495
562
|
};
|
|
496
563
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
497
564
|
const audience = platformAudience(platform);
|
|
498
|
-
const rootDir = options.rootDir ??
|
|
499
|
-
const tokenFile = options.tokenFile ??
|
|
565
|
+
const rootDir = options.rootDir ?? process8.cwd();
|
|
566
|
+
const tokenFile = options.tokenFile ?? join3(rootDir, ".odla/admin-token.local.json");
|
|
500
567
|
const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
|
|
501
568
|
const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
|
|
502
569
|
if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
@@ -523,7 +590,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
523
590
|
if (options.cache !== false) {
|
|
524
591
|
const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
|
|
525
592
|
tokens[scope] = { token, expiresAt };
|
|
526
|
-
if (
|
|
593
|
+
if (existsSync3(join3(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
|
|
527
594
|
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
528
595
|
out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
529
596
|
} else {
|
|
@@ -700,7 +767,7 @@ function isRecord2(value2) {
|
|
|
700
767
|
|
|
701
768
|
// src/admin-ai.ts
|
|
702
769
|
async function adminAi(options) {
|
|
703
|
-
const platform = platformAudience(options.platform ??
|
|
770
|
+
const platform = platformAudience(options.platform ?? process9.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
704
771
|
const doFetch = options.fetch ?? fetch;
|
|
705
772
|
const out = options.stdout ?? console;
|
|
706
773
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -955,12 +1022,12 @@ function addOption(options, name, value2) {
|
|
|
955
1022
|
}
|
|
956
1023
|
|
|
957
1024
|
// src/operator-context.ts
|
|
958
|
-
import { existsSync as
|
|
959
|
-
import { join as
|
|
960
|
-
import
|
|
1025
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1026
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
1027
|
+
import process11 from "process";
|
|
961
1028
|
|
|
962
1029
|
// src/config.ts
|
|
963
|
-
import { existsSync as
|
|
1030
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
964
1031
|
import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
965
1032
|
import { pathToFileURL } from "url";
|
|
966
1033
|
import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
|
|
@@ -1367,7 +1434,7 @@ var configImportSerial = 0;
|
|
|
1367
1434
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1368
1435
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1369
1436
|
const resolved = resolve2(configPath);
|
|
1370
|
-
if (!
|
|
1437
|
+
if (!existsSync4(resolved)) {
|
|
1371
1438
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
1372
1439
|
}
|
|
1373
1440
|
const raw = await loadConfigModule(resolved);
|
|
@@ -1402,7 +1469,7 @@ async function resolveDataExport(cfg, value2, names) {
|
|
|
1402
1469
|
if (typeof value2 !== "string") return value2;
|
|
1403
1470
|
const target = isAbsolute2(value2) ? value2 : resolve2(cfg.rootDir, value2);
|
|
1404
1471
|
if (target.endsWith(".json")) {
|
|
1405
|
-
return JSON.parse(
|
|
1472
|
+
return JSON.parse(readFileSync3(target, "utf8"));
|
|
1406
1473
|
}
|
|
1407
1474
|
const mod = await import(pathToFileURL(target).href);
|
|
1408
1475
|
for (const name of names) {
|
|
@@ -1478,7 +1545,7 @@ function validId2(value2) {
|
|
|
1478
1545
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1479
1546
|
}
|
|
1480
1547
|
async function loadConfigModule(path) {
|
|
1481
|
-
if (path.endsWith(".json")) return JSON.parse(
|
|
1548
|
+
if (path.endsWith(".json")) return JSON.parse(readFileSync3(path, "utf8"));
|
|
1482
1549
|
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1483
1550
|
const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
|
|
1484
1551
|
const value2 = mod.default ?? mod.config;
|
|
@@ -1493,18 +1560,18 @@ function unique3(values) {
|
|
|
1493
1560
|
}
|
|
1494
1561
|
|
|
1495
1562
|
// src/operator-profiles.ts
|
|
1496
|
-
import { existsSync as
|
|
1497
|
-
import { homedir } from "os";
|
|
1498
|
-
import { dirname as dirname4, join as
|
|
1499
|
-
import
|
|
1563
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1564
|
+
import { homedir as homedir2 } from "os";
|
|
1565
|
+
import { dirname as dirname4, join as join4, resolve as resolve3 } from "path";
|
|
1566
|
+
import process10 from "process";
|
|
1500
1567
|
function operatorProfileFile() {
|
|
1501
1568
|
return resolve3(
|
|
1502
|
-
clean(
|
|
1569
|
+
clean(process10.env.ODLA_CONTEXT_FILE) ?? join4(homedir2(), ".odla", "contexts.json")
|
|
1503
1570
|
);
|
|
1504
1571
|
}
|
|
1505
1572
|
function resolveOperatorProfile(parsed) {
|
|
1506
1573
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1507
|
-
const fromEnvironment = clean(
|
|
1574
|
+
const fromEnvironment = clean(process10.env.ODLA_CONTEXT);
|
|
1508
1575
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1509
1576
|
const file = operatorProfileFile();
|
|
1510
1577
|
if (!name) {
|
|
@@ -1544,10 +1611,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
1544
1611
|
return true;
|
|
1545
1612
|
}
|
|
1546
1613
|
function operatorCredentialFiles(selection) {
|
|
1547
|
-
const base = selection.name ?
|
|
1614
|
+
const base = selection.name ? join4(dirname4(selection.file), "profiles", selection.name) : join4(homedir2(), ".odla");
|
|
1548
1615
|
return {
|
|
1549
|
-
developer:
|
|
1550
|
-
scoped:
|
|
1616
|
+
developer: join4(base, "dev-token.json"),
|
|
1617
|
+
scoped: join4(base, "admin-token.local.json")
|
|
1551
1618
|
};
|
|
1552
1619
|
}
|
|
1553
1620
|
function assertOperatorName(value2, label) {
|
|
@@ -1558,10 +1625,10 @@ function assertOperatorName(value2, label) {
|
|
|
1558
1625
|
}
|
|
1559
1626
|
}
|
|
1560
1627
|
function readOperatorProfiles(file) {
|
|
1561
|
-
if (!
|
|
1628
|
+
if (!existsSync5(file)) return emptyProfiles();
|
|
1562
1629
|
let raw;
|
|
1563
1630
|
try {
|
|
1564
|
-
raw = JSON.parse(
|
|
1631
|
+
raw = JSON.parse(readFileSync4(file, "utf8"));
|
|
1565
1632
|
} catch {
|
|
1566
1633
|
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
1567
1634
|
}
|
|
@@ -1627,19 +1694,19 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1627
1694
|
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
1628
1695
|
const configPath = resolve4(configArgument);
|
|
1629
1696
|
const explicitConfig = parsed.options.config !== void 0;
|
|
1630
|
-
const hasConfig =
|
|
1697
|
+
const hasConfig = existsSync6(configPath);
|
|
1631
1698
|
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
1632
1699
|
await loadProjectConfig(configArgument);
|
|
1633
1700
|
}
|
|
1634
1701
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1635
1702
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1636
|
-
const platformEnvironment = clean2(
|
|
1703
|
+
const platformEnvironment = clean2(process11.env.ODLA_PLATFORM_URL);
|
|
1637
1704
|
const platformValue = platformAudience(
|
|
1638
1705
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1639
1706
|
);
|
|
1640
1707
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1641
1708
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1642
|
-
const appEnvironment = clean2(
|
|
1709
|
+
const appEnvironment = clean2(process11.env.ODLA_APP_ID);
|
|
1643
1710
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1644
1711
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1645
1712
|
if (appValue) assertOperatorName(appValue, "app");
|
|
@@ -1649,16 +1716,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1649
1716
|
);
|
|
1650
1717
|
}
|
|
1651
1718
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1652
|
-
const envEnvironment = clean2(
|
|
1719
|
+
const envEnvironment = clean2(process11.env.ODLA_ENV);
|
|
1653
1720
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1654
1721
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1655
1722
|
if (environmentValue) {
|
|
1656
1723
|
assertOperatorName(environmentValue, "environment");
|
|
1657
1724
|
}
|
|
1658
|
-
const rootDir = loaded?.rootDir ??
|
|
1725
|
+
const rootDir = loaded?.rootDir ?? process11.cwd();
|
|
1659
1726
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1660
|
-
const tokenFile = clean2(
|
|
1661
|
-
const scopedTokenFile = clean2(
|
|
1727
|
+
const tokenFile = clean2(process11.env.ODLA_DEV_TOKEN_FILE) ? resolve4(process11.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
1728
|
+
const scopedTokenFile = clean2(process11.env.ODLA_ADMIN_TOKEN_FILE) ? resolve4(process11.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join5(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
1662
1729
|
const cfg = loaded ? {
|
|
1663
1730
|
...loaded,
|
|
1664
1731
|
platformUrl: platformValue,
|
|
@@ -1680,8 +1747,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1680
1747
|
services: [],
|
|
1681
1748
|
local: {
|
|
1682
1749
|
tokenFile,
|
|
1683
|
-
credentialsFile:
|
|
1684
|
-
devVarsFile:
|
|
1750
|
+
credentialsFile: join5(rootDir, ".odla", "credentials.local.json"),
|
|
1751
|
+
devVarsFile: join5(rootDir, ".dev.vars"),
|
|
1685
1752
|
gitignore: true
|
|
1686
1753
|
}
|
|
1687
1754
|
};
|
|
@@ -1778,7 +1845,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1778
1845
|
}
|
|
1779
1846
|
|
|
1780
1847
|
// src/auth-command.ts
|
|
1781
|
-
import
|
|
1848
|
+
import process12 from "process";
|
|
1782
1849
|
|
|
1783
1850
|
// src/whoami-command.ts
|
|
1784
1851
|
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
@@ -1940,7 +2007,7 @@ async function authCommand(parsed, deps = {}) {
|
|
|
1940
2007
|
const { cfg } = context;
|
|
1941
2008
|
const out = deps.stdout ?? console;
|
|
1942
2009
|
const doFetch = deps.fetch ?? fetch;
|
|
1943
|
-
const email = stringOpt(parsed.options.email) ??
|
|
2010
|
+
const email = stringOpt(parsed.options.email) ?? process12.env.ODLA_USER_EMAIL?.trim();
|
|
1944
2011
|
if (!email) {
|
|
1945
2012
|
throw new Error(
|
|
1946
2013
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
@@ -2093,7 +2160,7 @@ async function appExport(options) {
|
|
|
2093
2160
|
}
|
|
2094
2161
|
|
|
2095
2162
|
// src/app-import.ts
|
|
2096
|
-
import { readFileSync as
|
|
2163
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2097
2164
|
import {
|
|
2098
2165
|
buildImportOps,
|
|
2099
2166
|
parseImport,
|
|
@@ -2115,7 +2182,7 @@ async function appImport(options) {
|
|
|
2115
2182
|
const out = options.stdout ?? console;
|
|
2116
2183
|
const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
|
|
2117
2184
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2118
|
-
const text3 = options.file === "-" ? (options.readStdin ?? (() =>
|
|
2185
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
|
|
2119
2186
|
const { format, sources } = parseImport(text3, options.ns);
|
|
2120
2187
|
if (format === "namespace-map" && options.ns) {
|
|
2121
2188
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -2986,7 +3053,7 @@ import {
|
|
|
2986
3053
|
AppsError,
|
|
2987
3054
|
createAppsClient
|
|
2988
3055
|
} from "@odla-ai/apps";
|
|
2989
|
-
import { join as
|
|
3056
|
+
import { join as join6 } from "path";
|
|
2990
3057
|
|
|
2991
3058
|
// src/config-operation-error.ts
|
|
2992
3059
|
var ConfigOperationCommandError = class extends Error {
|
|
@@ -3002,7 +3069,7 @@ var ConfigOperationCommandError = class extends Error {
|
|
|
3002
3069
|
import {
|
|
3003
3070
|
appServiceDefinition as appServiceDefinition2
|
|
3004
3071
|
} from "@odla-ai/apps";
|
|
3005
|
-
import { readFileSync as
|
|
3072
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3006
3073
|
|
|
3007
3074
|
// src/config-reconcile-digest.ts
|
|
3008
3075
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -3038,7 +3105,7 @@ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
|
3038
3105
|
function readPlan(path) {
|
|
3039
3106
|
let value2;
|
|
3040
3107
|
try {
|
|
3041
|
-
const raw =
|
|
3108
|
+
const raw = readFileSync6(path, "utf8");
|
|
3042
3109
|
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
3043
3110
|
value2 = JSON.parse(raw);
|
|
3044
3111
|
} catch (error) {
|
|
@@ -3411,7 +3478,7 @@ async function operationClient(cfg, options, purpose) {
|
|
|
3411
3478
|
platform: cfg.platformUrl,
|
|
3412
3479
|
scope: "app:config:write",
|
|
3413
3480
|
token: options.token,
|
|
3414
|
-
tokenFile:
|
|
3481
|
+
tokenFile: join6(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3415
3482
|
rootDir: cfg.rootDir,
|
|
3416
3483
|
email: options.email,
|
|
3417
3484
|
open: options.open,
|
|
@@ -3466,7 +3533,7 @@ function record4(value2) {
|
|
|
3466
3533
|
|
|
3467
3534
|
// src/config-reconcile-command.ts
|
|
3468
3535
|
import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
|
|
3469
|
-
import { join as
|
|
3536
|
+
import { join as join7 } from "path";
|
|
3470
3537
|
|
|
3471
3538
|
// src/config-reconcile.ts
|
|
3472
3539
|
import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
|
|
@@ -3762,7 +3829,7 @@ async function inspectConfig(options) {
|
|
|
3762
3829
|
platform: cfg.platformUrl,
|
|
3763
3830
|
scope: "app:config:read",
|
|
3764
3831
|
token: options.token,
|
|
3765
|
-
tokenFile:
|
|
3832
|
+
tokenFile: join7(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3766
3833
|
rootDir: cfg.rootDir,
|
|
3767
3834
|
email: options.email,
|
|
3768
3835
|
open: options.open,
|
|
@@ -3894,13 +3961,13 @@ function quoteArg2(value2) {
|
|
|
3894
3961
|
|
|
3895
3962
|
// src/doctor-checks.ts
|
|
3896
3963
|
import { execFileSync } from "child_process";
|
|
3897
|
-
import { existsSync as
|
|
3898
|
-
import { join as
|
|
3964
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
3965
|
+
import { join as join9, resolve as resolve6 } from "path";
|
|
3899
3966
|
|
|
3900
3967
|
// src/wrangler.ts
|
|
3901
3968
|
import { spawn as spawn2 } from "child_process";
|
|
3902
|
-
import { existsSync as
|
|
3903
|
-
import { join as
|
|
3969
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3970
|
+
import { join as join8 } from "path";
|
|
3904
3971
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3905
3972
|
const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3906
3973
|
let stdout = "";
|
|
@@ -3914,15 +3981,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3914
3981
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3915
3982
|
function findWranglerConfig(rootDir) {
|
|
3916
3983
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3917
|
-
const path =
|
|
3918
|
-
if (
|
|
3984
|
+
const path = join8(rootDir, name);
|
|
3985
|
+
if (existsSync7(path)) return path;
|
|
3919
3986
|
}
|
|
3920
3987
|
return null;
|
|
3921
3988
|
}
|
|
3922
3989
|
function readWranglerConfig(path) {
|
|
3923
3990
|
if (path.endsWith(".toml")) return null;
|
|
3924
3991
|
try {
|
|
3925
|
-
return JSON.parse(stripJsonComments(
|
|
3992
|
+
return JSON.parse(stripJsonComments(readFileSync7(path, "utf8")));
|
|
3926
3993
|
} catch {
|
|
3927
3994
|
return null;
|
|
3928
3995
|
}
|
|
@@ -4075,7 +4142,7 @@ function wranglerWarnings(rootDir) {
|
|
|
4075
4142
|
const dir = resolve6(rootDir, assets.directory);
|
|
4076
4143
|
if (dir === resolve6(rootDir)) {
|
|
4077
4144
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4078
|
-
} else if (
|
|
4145
|
+
} else if (existsSync8(join9(dir, "node_modules"))) {
|
|
4079
4146
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4080
4147
|
}
|
|
4081
4148
|
}
|
|
@@ -4111,12 +4178,12 @@ function o11yProjectWarnings(rootDir) {
|
|
|
4111
4178
|
return warnings;
|
|
4112
4179
|
}
|
|
4113
4180
|
const main = typeof config.main === "string" ? resolve6(rootDir, config.main) : null;
|
|
4114
|
-
if (!main || !
|
|
4181
|
+
if (!main || !existsSync8(main)) {
|
|
4115
4182
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
4116
4183
|
} else {
|
|
4117
4184
|
let source = "";
|
|
4118
4185
|
try {
|
|
4119
|
-
source =
|
|
4186
|
+
source = readFileSync8(main, "utf8");
|
|
4120
4187
|
} catch {
|
|
4121
4188
|
}
|
|
4122
4189
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -4140,7 +4207,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4140
4207
|
}
|
|
4141
4208
|
function readPackageJson(rootDir) {
|
|
4142
4209
|
try {
|
|
4143
|
-
return JSON.parse(
|
|
4210
|
+
return JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf8"));
|
|
4144
4211
|
} catch {
|
|
4145
4212
|
return null;
|
|
4146
4213
|
}
|
|
@@ -4434,14 +4501,14 @@ function harnessOption(value2, flag) {
|
|
|
4434
4501
|
}
|
|
4435
4502
|
|
|
4436
4503
|
// src/init.ts
|
|
4437
|
-
import { existsSync as
|
|
4504
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4438
4505
|
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
4439
4506
|
import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
|
|
4440
4507
|
function initProject(options) {
|
|
4441
4508
|
const out = options.stdout ?? console;
|
|
4442
4509
|
const rootDir = resolve7(options.rootDir ?? process.cwd());
|
|
4443
4510
|
const configPath = resolve7(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4444
|
-
if (
|
|
4511
|
+
if (existsSync9(configPath) && !options.force) {
|
|
4445
4512
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
4446
4513
|
}
|
|
4447
4514
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -4469,7 +4536,7 @@ function initProject(options) {
|
|
|
4469
4536
|
out.log("updated .gitignore for local odla credentials");
|
|
4470
4537
|
}
|
|
4471
4538
|
function writeIfMissing(path, text3) {
|
|
4472
|
-
if (
|
|
4539
|
+
if (existsSync9(path)) return;
|
|
4473
4540
|
writeFileSync2(path, text3);
|
|
4474
4541
|
}
|
|
4475
4542
|
function configTemplate(input) {
|
|
@@ -4766,9 +4833,9 @@ function printReport(report5, out) {
|
|
|
4766
4833
|
}
|
|
4767
4834
|
|
|
4768
4835
|
// src/skill.ts
|
|
4769
|
-
import { existsSync as
|
|
4770
|
-
import { homedir as
|
|
4771
|
-
import { dirname as dirname7, isAbsolute as isAbsolute3, join as
|
|
4836
|
+
import { existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
4837
|
+
import { homedir as homedir3 } from "os";
|
|
4838
|
+
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join10, relative as relative2, resolve as resolve8, sep } from "path";
|
|
4772
4839
|
import { fileURLToPath } from "url";
|
|
4773
4840
|
|
|
4774
4841
|
// src/skill-adapters.ts
|
|
@@ -4868,7 +4935,7 @@ function installSkill(options = {}) {
|
|
|
4868
4935
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
4869
4936
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
4870
4937
|
const root = resolve8(options.dir ?? process.cwd());
|
|
4871
|
-
const home = resolve8(options.homeDir ??
|
|
4938
|
+
const home = resolve8(options.homeDir ?? homedir3());
|
|
4872
4939
|
const plans = /* @__PURE__ */ new Map();
|
|
4873
4940
|
const targets = /* @__PURE__ */ new Map();
|
|
4874
4941
|
const rememberTarget = (harness, target) => {
|
|
@@ -4882,12 +4949,12 @@ function installSkill(options = {}) {
|
|
|
4882
4949
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4883
4950
|
};
|
|
4884
4951
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4885
|
-
for (const rel of files) plan(
|
|
4952
|
+
for (const rel of files) plan(join10(targetDir2, rel), readFileSync9(join10(sourceDir, rel), "utf8"), false, boundary);
|
|
4886
4953
|
};
|
|
4887
4954
|
let targetDir;
|
|
4888
4955
|
if (options.global) {
|
|
4889
|
-
const claudeRoot =
|
|
4890
|
-
const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
4956
|
+
const claudeRoot = join10(home, ".claude", "skills");
|
|
4957
|
+
const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ?? join10(home, ".codex"), "skills");
|
|
4891
4958
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4892
4959
|
for (const harness of harnesses) {
|
|
4893
4960
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -4895,35 +4962,35 @@ function installSkill(options = {}) {
|
|
|
4895
4962
|
rememberTarget(harness, skillRoot);
|
|
4896
4963
|
}
|
|
4897
4964
|
} else {
|
|
4898
|
-
const sharedRoot =
|
|
4965
|
+
const sharedRoot = join10(root, ".agents", "skills");
|
|
4899
4966
|
planSkillTree(sharedRoot);
|
|
4900
|
-
const claudeRoot =
|
|
4967
|
+
const claudeRoot = join10(root, ".claude", "skills");
|
|
4901
4968
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4902
4969
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4903
4970
|
if (harnesses.includes("claude")) {
|
|
4904
4971
|
for (const skill of skillNames(files)) {
|
|
4905
|
-
const canonical2 =
|
|
4906
|
-
plan(
|
|
4972
|
+
const canonical2 = readFileSync9(join10(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4973
|
+
plan(join10(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
4907
4974
|
}
|
|
4908
4975
|
rememberTarget("claude", claudeRoot);
|
|
4909
4976
|
}
|
|
4910
4977
|
if (harnesses.includes("cursor")) {
|
|
4911
|
-
const cursorRule =
|
|
4978
|
+
const cursorRule = join10(root, ".cursor", "rules", "odla.mdc");
|
|
4912
4979
|
plan(cursorRule, CURSOR_RULE);
|
|
4913
4980
|
rememberTarget("cursor", cursorRule);
|
|
4914
4981
|
}
|
|
4915
4982
|
if (harnesses.includes("agents")) {
|
|
4916
|
-
const agentsFile =
|
|
4983
|
+
const agentsFile = join10(root, "AGENTS.md");
|
|
4917
4984
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4918
4985
|
rememberTarget("agents", agentsFile);
|
|
4919
4986
|
}
|
|
4920
4987
|
if (harnesses.includes("copilot")) {
|
|
4921
|
-
const copilotFile =
|
|
4988
|
+
const copilotFile = join10(root, ".github", "copilot-instructions.md");
|
|
4922
4989
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4923
4990
|
rememberTarget("copilot", copilotFile);
|
|
4924
4991
|
}
|
|
4925
4992
|
if (harnesses.includes("gemini")) {
|
|
4926
|
-
const geminiFile =
|
|
4993
|
+
const geminiFile = join10(root, "GEMINI.md");
|
|
4927
4994
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4928
4995
|
rememberTarget("gemini", geminiFile);
|
|
4929
4996
|
}
|
|
@@ -4937,11 +5004,11 @@ function installSkill(options = {}) {
|
|
|
4937
5004
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4938
5005
|
continue;
|
|
4939
5006
|
}
|
|
4940
|
-
if (!
|
|
5007
|
+
if (!existsSync10(file.target)) {
|
|
4941
5008
|
writtenPaths.add(file.target);
|
|
4942
5009
|
continue;
|
|
4943
5010
|
}
|
|
4944
|
-
const current =
|
|
5011
|
+
const current = readFileSync9(file.target, "utf8");
|
|
4945
5012
|
if (current === file.content) {
|
|
4946
5013
|
unchangedPaths.add(file.target);
|
|
4947
5014
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4958,7 +5025,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4958
5025
|
);
|
|
4959
5026
|
}
|
|
4960
5027
|
for (const file of plans.values()) {
|
|
4961
|
-
if (!
|
|
5028
|
+
if (!existsSync10(file.target) || readFileSync9(file.target, "utf8") !== file.content) {
|
|
4962
5029
|
mkdirSync3(dirname7(file.target), { recursive: true });
|
|
4963
5030
|
writeFileSync3(file.target, file.content);
|
|
4964
5031
|
}
|
|
@@ -5001,9 +5068,9 @@ function normalizeHarnesses(values, global) {
|
|
|
5001
5068
|
function managedFileContent(path, block, force, boundary) {
|
|
5002
5069
|
const symlink = symlinkedComponent(boundary, path);
|
|
5003
5070
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
5004
|
-
if (!
|
|
5071
|
+
if (!existsSync10(path)) return `${block}
|
|
5005
5072
|
`;
|
|
5006
|
-
const current =
|
|
5073
|
+
const current = readFileSync9(path, "utf8");
|
|
5007
5074
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
5008
5075
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
5009
5076
|
const startAt = current.indexOf(start);
|
|
@@ -5030,7 +5097,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
5030
5097
|
}
|
|
5031
5098
|
let current = boundary;
|
|
5032
5099
|
for (const part of rel.split(sep).filter(Boolean)) {
|
|
5033
|
-
current =
|
|
5100
|
+
current = join10(current, part);
|
|
5034
5101
|
try {
|
|
5035
5102
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
5036
5103
|
} catch (error) {
|
|
@@ -5043,11 +5110,11 @@ function skillNames(files) {
|
|
|
5043
5110
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
5044
5111
|
}
|
|
5045
5112
|
function listFiles(dir) {
|
|
5046
|
-
if (!
|
|
5113
|
+
if (!existsSync10(dir)) return [];
|
|
5047
5114
|
const results = [];
|
|
5048
5115
|
const walk = (current) => {
|
|
5049
5116
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
5050
|
-
const path =
|
|
5117
|
+
const path = join10(current, entry.name);
|
|
5051
5118
|
if (entry.isDirectory()) walk(path);
|
|
5052
5119
|
else results.push(relative2(dir, path));
|
|
5053
5120
|
}
|
|
@@ -5404,7 +5471,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5404
5471
|
}
|
|
5405
5472
|
|
|
5406
5473
|
// src/code-connect.ts
|
|
5407
|
-
import { existsSync as
|
|
5474
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5408
5475
|
import { cpus, hostname, totalmem } from "os";
|
|
5409
5476
|
import { resolve as resolve11 } from "path";
|
|
5410
5477
|
|
|
@@ -5415,7 +5482,7 @@ var HARNESS_PROTOCOL_VERSION = 1;
|
|
|
5415
5482
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
5416
5483
|
import { constants } from "fs";
|
|
5417
5484
|
import { access } from "fs/promises";
|
|
5418
|
-
import { delimiter, join as
|
|
5485
|
+
import { delimiter, join as join11 } from "path";
|
|
5419
5486
|
import { getgid, getuid } from "process";
|
|
5420
5487
|
import { mkdir as mkdir2, mkdtemp, realpath, rm, writeFile as writeFile2 } from "fs/promises";
|
|
5421
5488
|
import { tmpdir } from "os";
|
|
@@ -5433,7 +5500,7 @@ function assertPinnedImage(image) {
|
|
|
5433
5500
|
async function commandAvailable(engine) {
|
|
5434
5501
|
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
5435
5502
|
try {
|
|
5436
|
-
await access(
|
|
5503
|
+
await access(join11(directory, engine), constants.X_OK);
|
|
5437
5504
|
return true;
|
|
5438
5505
|
} catch {
|
|
5439
5506
|
}
|
|
@@ -6131,7 +6198,7 @@ import { randomUUID } from "crypto";
|
|
|
6131
6198
|
import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
|
|
6132
6199
|
import { createReadStream } from "fs";
|
|
6133
6200
|
import { lstat as lstat22 } from "fs/promises";
|
|
6134
|
-
import { join as
|
|
6201
|
+
import { join as join13 } from "path";
|
|
6135
6202
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
6136
6203
|
import { tmpdir as tmpdir3 } from "os";
|
|
6137
6204
|
import { dirname as dirname9, join as join23, resolve as resolve32, sep as sep23 } from "path";
|
|
@@ -6518,8 +6585,8 @@ function rollup(graph, kind, options = {}) {
|
|
|
6518
6585
|
for (const node of nodesOfKind(graph, kind)) {
|
|
6519
6586
|
if (options.prefix && !node.name.startsWith(options.prefix)) continue;
|
|
6520
6587
|
const key = node.name.split(separator).slice(0, depth).join(separator);
|
|
6521
|
-
const
|
|
6522
|
-
if (
|
|
6588
|
+
const list3 = groups.get(key);
|
|
6589
|
+
if (list3) list3.push(node);
|
|
6523
6590
|
else groups.set(key, [node]);
|
|
6524
6591
|
}
|
|
6525
6592
|
return [...groups].map(([prefix, nodes]) => ({
|
|
@@ -6534,7 +6601,7 @@ function dirname8(path) {
|
|
|
6534
6601
|
const at = path.lastIndexOf("/");
|
|
6535
6602
|
return at <= 0 ? "." : path.slice(0, at);
|
|
6536
6603
|
}
|
|
6537
|
-
function
|
|
6604
|
+
function join12(base, specifier) {
|
|
6538
6605
|
const parts = [];
|
|
6539
6606
|
const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
|
|
6540
6607
|
for (const segment of segments) {
|
|
@@ -6558,7 +6625,7 @@ var BARE_IMPORT = /^\s*import\s*["']([^"']+)["']/gm;
|
|
|
6558
6625
|
var isSourcePath = (path) => SOURCE.test(path);
|
|
6559
6626
|
function resolveImport(fromPath, specifier, known) {
|
|
6560
6627
|
if (!specifier.startsWith(".")) return null;
|
|
6561
|
-
const base =
|
|
6628
|
+
const base = join12(dirname8(fromPath), specifier);
|
|
6562
6629
|
const candidates = [
|
|
6563
6630
|
base,
|
|
6564
6631
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -7361,7 +7428,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
7361
7428
|
const receipts = [];
|
|
7362
7429
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
7363
7430
|
try {
|
|
7364
|
-
const path =
|
|
7431
|
+
const path = join13(workspaceDir, artifact.path);
|
|
7365
7432
|
const info = await lstat22(path);
|
|
7366
7433
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
7367
7434
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
@@ -9457,7 +9524,7 @@ var CODE_BUILD_RECIPES = Object.freeze([{
|
|
|
9457
9524
|
async function codeConnect(options) {
|
|
9458
9525
|
const cwd = options.cwd ?? process.cwd();
|
|
9459
9526
|
const configPath = resolve11(cwd, options.configPath);
|
|
9460
|
-
const cfg =
|
|
9527
|
+
const cfg = existsSync11(configPath) ? await loadProjectConfig(configPath) : null;
|
|
9461
9528
|
const requestedAppId = options.appId?.trim();
|
|
9462
9529
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
9463
9530
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -9891,13 +9958,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
9891
9958
|
}
|
|
9892
9959
|
|
|
9893
9960
|
// src/operator-credentials.ts
|
|
9894
|
-
import
|
|
9961
|
+
import process13 from "process";
|
|
9895
9962
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
9896
9963
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
9897
9964
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
9898
9965
|
const source = clean3(
|
|
9899
9966
|
stringOpt(parsed.options.token)
|
|
9900
|
-
) ? "flag" : clean3(
|
|
9967
|
+
) ? "flag" : clean3(process13.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
9901
9968
|
return {
|
|
9902
9969
|
source,
|
|
9903
9970
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -10219,6 +10286,9 @@ Usage:
|
|
|
10219
10286
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10220
10287
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10221
10288
|
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]
|
|
10289
|
+
odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--email <odla-account>] [--no-open] [--json]
|
|
10290
|
+
odla-ai device list [--email <odla-account>] [--json]
|
|
10291
|
+
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
10222
10292
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
10223
10293
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
10224
10294
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
@@ -10324,6 +10394,10 @@ Commands:
|
|
|
10324
10394
|
stable status, incident, and report JSON to agents and CI.
|
|
10325
10395
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10326
10396
|
explicit unknowns, and next actions through a read-only grant.
|
|
10397
|
+
device Enrol THIS machine once, then stop asking. A human approves the
|
|
10398
|
+
enrollment in the browser; from then on this terminal mints its
|
|
10399
|
+
own short-lived credentials for the named projects with nobody's
|
|
10400
|
+
attention, until the device expires or is revoked.
|
|
10327
10401
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
10328
10402
|
"provision --live --yes" initializes only the live instance of
|
|
10329
10403
|
an existing sandbox app and enables every configured service;
|
|
@@ -12606,7 +12680,7 @@ function percent(value2) {
|
|
|
12606
12680
|
// src/provision.ts
|
|
12607
12681
|
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
|
|
12608
12682
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
12609
|
-
import
|
|
12683
|
+
import process14 from "process";
|
|
12610
12684
|
|
|
12611
12685
|
// src/integration-provision.ts
|
|
12612
12686
|
import { uuidv7 } from "@odla-ai/db";
|
|
@@ -13042,7 +13116,7 @@ async function provision(options) {
|
|
|
13042
13116
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
13043
13117
|
}
|
|
13044
13118
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
13045
|
-
const key =
|
|
13119
|
+
const key = process14.env[cfg.ai.keyEnv];
|
|
13046
13120
|
if (key) {
|
|
13047
13121
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
13048
13122
|
await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -13084,7 +13158,7 @@ async function provision(options) {
|
|
|
13084
13158
|
|
|
13085
13159
|
// src/record.ts
|
|
13086
13160
|
import { appendFileSync } from "fs";
|
|
13087
|
-
import
|
|
13161
|
+
import process15 from "process";
|
|
13088
13162
|
|
|
13089
13163
|
// src/surface.ts
|
|
13090
13164
|
var PM_ACTIONS = {
|
|
@@ -13154,6 +13228,7 @@ var COMMAND_SURFACE = {
|
|
|
13154
13228
|
config: { diff: {}, plan: {}, apply: {} },
|
|
13155
13229
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
13156
13230
|
credentials: { list: {}, revoke: {} },
|
|
13231
|
+
device: { enroll: {}, list: {}, revoke: {} },
|
|
13157
13232
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
13158
13233
|
discuss: {
|
|
13159
13234
|
groups: {},
|
|
@@ -13265,7 +13340,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
13265
13340
|
|
|
13266
13341
|
// src/record.ts
|
|
13267
13342
|
function recordInvocation(parsed) {
|
|
13268
|
-
const file =
|
|
13343
|
+
const file = process15.env.ODLA_CLI_RECORD;
|
|
13269
13344
|
if (!file) return;
|
|
13270
13345
|
try {
|
|
13271
13346
|
const entry = {
|
|
@@ -13279,8 +13354,132 @@ function recordInvocation(parsed) {
|
|
|
13279
13354
|
}
|
|
13280
13355
|
}
|
|
13281
13356
|
|
|
13357
|
+
// src/advisory-output.ts
|
|
13358
|
+
import { formatAdvisory, parseAdvisories } from "@odla-ai/apps";
|
|
13359
|
+
function advisoryCollectingFetch(inner, sink) {
|
|
13360
|
+
return (async (input, init) => {
|
|
13361
|
+
const response2 = await inner(input, init);
|
|
13362
|
+
try {
|
|
13363
|
+
sink.push(...parseAdvisories(response2));
|
|
13364
|
+
} catch {
|
|
13365
|
+
}
|
|
13366
|
+
return response2;
|
|
13367
|
+
});
|
|
13368
|
+
}
|
|
13369
|
+
function renderAdvisories(out, advisories, env = process.env) {
|
|
13370
|
+
if (env.ODLA_NO_ADVISORIES) return;
|
|
13371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13372
|
+
for (const advisory of advisories) {
|
|
13373
|
+
const key = `${advisory.code}:${advisory.message}`;
|
|
13374
|
+
if (seen.has(key)) continue;
|
|
13375
|
+
seen.add(key);
|
|
13376
|
+
out.error(formatAdvisory(advisory));
|
|
13377
|
+
}
|
|
13378
|
+
}
|
|
13379
|
+
|
|
13380
|
+
// src/device-command.ts
|
|
13381
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
13382
|
+
import { dirname as dirname10 } from "path";
|
|
13383
|
+
import process16 from "process";
|
|
13384
|
+
async function deviceCommand(parsed, deps) {
|
|
13385
|
+
const action2 = parsed.positionals[1] ?? "";
|
|
13386
|
+
const out = deps.stdout ?? console;
|
|
13387
|
+
const doFetch = deps.fetch ?? fetch;
|
|
13388
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
|
|
13389
|
+
const json = parsed.options.json === true;
|
|
13390
|
+
if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
|
|
13391
|
+
if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
|
|
13392
|
+
if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
|
|
13393
|
+
throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
|
|
13394
|
+
}
|
|
13395
|
+
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
13396
|
+
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
13397
|
+
const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
13398
|
+
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026]");
|
|
13399
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, `odla CLI (enroll ${name})`);
|
|
13400
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
|
|
13401
|
+
method: "POST",
|
|
13402
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
13403
|
+
body: JSON.stringify({
|
|
13404
|
+
name,
|
|
13405
|
+
platform: process16.platform,
|
|
13406
|
+
appIds: apps,
|
|
13407
|
+
...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
|
|
13408
|
+
})
|
|
13409
|
+
});
|
|
13410
|
+
const body = await response2.json().catch(() => ({}));
|
|
13411
|
+
if (!response2.ok || !body.token || !body.device) {
|
|
13412
|
+
throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13413
|
+
}
|
|
13414
|
+
const path = deviceCredentialPath();
|
|
13415
|
+
mkdirSync4(dirname10(path), { recursive: true });
|
|
13416
|
+
writeFileSync4(path, JSON.stringify({
|
|
13417
|
+
token: body.token,
|
|
13418
|
+
platform: cfg.platformUrl.replace(/\/$/, ""),
|
|
13419
|
+
deviceId: body.device.deviceId,
|
|
13420
|
+
name
|
|
13421
|
+
}, null, 2));
|
|
13422
|
+
chmodSync2(path, 384);
|
|
13423
|
+
out.error(`device: enrolled "${name}" for ${body.device.appIds.join(", ")}; credential written to ${path}`);
|
|
13424
|
+
out.error("device: this terminal will mint its own credentials from now on \u2014 no further approvals.");
|
|
13425
|
+
if (json) {
|
|
13426
|
+
out.log(JSON.stringify({ deviceId: body.device.deviceId, name, appIds: body.device.appIds, expiresAt: body.device.expiresAt }, null, 2));
|
|
13427
|
+
}
|
|
13428
|
+
}
|
|
13429
|
+
async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
13430
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
|
|
13431
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
|
|
13432
|
+
headers: { authorization: `Bearer ${token}` }
|
|
13433
|
+
});
|
|
13434
|
+
const body = await response2.json().catch(() => ({}));
|
|
13435
|
+
if (!response2.ok || !body.devices) {
|
|
13436
|
+
throw new Error(`device list failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13437
|
+
}
|
|
13438
|
+
if (json) return out.log(JSON.stringify(body.devices, null, 2));
|
|
13439
|
+
if (body.devices.length === 0) return out.log("no enrolled devices");
|
|
13440
|
+
for (const device of body.devices) {
|
|
13441
|
+
const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
|
|
13442
|
+
out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${device.appIds.join(", ")}]`);
|
|
13443
|
+
}
|
|
13444
|
+
}
|
|
13445
|
+
async function revoke(parsed, deps, cfg, doFetch, out, json) {
|
|
13446
|
+
const deviceId = parsed.positionals[2];
|
|
13447
|
+
if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
|
|
13448
|
+
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
|
|
13449
|
+
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
|
|
13450
|
+
method: "POST",
|
|
13451
|
+
headers: { authorization: `Bearer ${token}` }
|
|
13452
|
+
});
|
|
13453
|
+
if (!response2.ok) {
|
|
13454
|
+
const body = await response2.json().catch(() => ({}));
|
|
13455
|
+
throw new Error(`device revoke failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13456
|
+
}
|
|
13457
|
+
out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
|
|
13458
|
+
if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
|
|
13459
|
+
}
|
|
13460
|
+
async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
|
|
13461
|
+
const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
13462
|
+
const scopedTokenFile = credentials.scopedTokenFile;
|
|
13463
|
+
return getScopedPlatformToken({
|
|
13464
|
+
platform: cfg.platformUrl,
|
|
13465
|
+
scope: "app:device:enroll",
|
|
13466
|
+
email: stringOpt(parsed.options.email),
|
|
13467
|
+
label,
|
|
13468
|
+
fetch: doFetch,
|
|
13469
|
+
stdout: out,
|
|
13470
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
13471
|
+
openApprovalUrl: deps.openUrl,
|
|
13472
|
+
rootDir: cfg.rootDir,
|
|
13473
|
+
tokenFile: scopedTokenFile,
|
|
13474
|
+
...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
|
|
13475
|
+
});
|
|
13476
|
+
}
|
|
13477
|
+
function defaultDeviceName() {
|
|
13478
|
+
return `${process16.env.HOSTNAME ?? process16.env.HOST ?? "machine"}-${process16.platform}`;
|
|
13479
|
+
}
|
|
13480
|
+
|
|
13282
13481
|
// src/runbook-actions.ts
|
|
13283
|
-
import { readFileSync as
|
|
13482
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13284
13483
|
var PLATFORM_SCOPE = "$platform";
|
|
13285
13484
|
async function call(ctx, method, path, body) {
|
|
13286
13485
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
@@ -13327,7 +13526,7 @@ async function bySlug(ctx, slug) {
|
|
|
13327
13526
|
function readBody(file, inline) {
|
|
13328
13527
|
if (inline !== void 0) return inline;
|
|
13329
13528
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
13330
|
-
return
|
|
13529
|
+
return readFileSync10(file === "-" ? 0 : file, "utf8");
|
|
13331
13530
|
}
|
|
13332
13531
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
13333
13532
|
async function runbookList(ctx, all, query) {
|
|
@@ -13419,8 +13618,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
13419
13618
|
}
|
|
13420
13619
|
|
|
13421
13620
|
// src/runbook-import.ts
|
|
13422
|
-
import { readFileSync as
|
|
13423
|
-
import { basename as basename2, join as
|
|
13621
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
13622
|
+
import { basename as basename2, join as join14 } from "path";
|
|
13424
13623
|
function parseRunbook(text3, slug) {
|
|
13425
13624
|
let rest = text3;
|
|
13426
13625
|
const meta = {};
|
|
@@ -13450,7 +13649,7 @@ function readRunbookDir(dir) {
|
|
|
13450
13649
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
13451
13650
|
return files.map((file) => {
|
|
13452
13651
|
const slug = basename2(file, ".md");
|
|
13453
|
-
const parsed = parseRunbook(
|
|
13652
|
+
const parsed = parseRunbook(readFileSync11(join14(dir, file), "utf8"), slug);
|
|
13454
13653
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
13455
13654
|
});
|
|
13456
13655
|
}
|
|
@@ -13523,8 +13722,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
13523
13722
|
|
|
13524
13723
|
// src/runbook-impact.ts
|
|
13525
13724
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13526
|
-
import { existsSync as
|
|
13527
|
-
import { join as
|
|
13725
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
13726
|
+
import { join as join15 } from "path";
|
|
13528
13727
|
|
|
13529
13728
|
// src/runbook-impact-scan.ts
|
|
13530
13729
|
var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
@@ -13693,10 +13892,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
|
|
|
13693
13892
|
}
|
|
13694
13893
|
function manifestLabeller(root) {
|
|
13695
13894
|
return (workspace) => {
|
|
13696
|
-
const manifest =
|
|
13697
|
-
if (!
|
|
13895
|
+
const manifest = join15(root, workspace, "package.json");
|
|
13896
|
+
if (!existsSync12(manifest)) return void 0;
|
|
13698
13897
|
try {
|
|
13699
|
-
const name = JSON.parse(
|
|
13898
|
+
const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
|
|
13700
13899
|
return typeof name === "string" ? name : void 0;
|
|
13701
13900
|
} catch {
|
|
13702
13901
|
return void 0;
|
|
@@ -13763,7 +13962,7 @@ function report4(ctx, impacts) {
|
|
|
13763
13962
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
13764
13963
|
const cwd = deps.cwd ?? process.cwd();
|
|
13765
13964
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
13766
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
13965
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join15(cwd, path), "utf8"));
|
|
13767
13966
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
13768
13967
|
if (!surfaces.length) {
|
|
13769
13968
|
return ctx.out.log(
|
|
@@ -13896,12 +14095,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
13896
14095
|
|
|
13897
14096
|
// src/runbook-editor.ts
|
|
13898
14097
|
import { spawnSync } from "child_process";
|
|
13899
|
-
import { mkdtempSync, readFileSync as
|
|
14098
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
13900
14099
|
import { tmpdir as tmpdir4 } from "os";
|
|
13901
|
-
import { join as
|
|
13902
|
-
import
|
|
14100
|
+
import { join as join16 } from "path";
|
|
14101
|
+
import process17 from "process";
|
|
13903
14102
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
13904
|
-
function resolveEditor(env =
|
|
14103
|
+
function resolveEditor(env = process17.env) {
|
|
13905
14104
|
for (const name of EDITOR_ENV) {
|
|
13906
14105
|
const value2 = env[name];
|
|
13907
14106
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -13915,8 +14114,8 @@ function defaultRun(command, path) {
|
|
|
13915
14114
|
return result.status ?? 0;
|
|
13916
14115
|
}
|
|
13917
14116
|
function editText(initial, slug, deps = {}) {
|
|
13918
|
-
const env = deps.env ??
|
|
13919
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
14117
|
+
const env = deps.env ?? process17.env;
|
|
14118
|
+
const interactive = deps.interactive ?? (() => Boolean(process17.stdin.isTTY));
|
|
13920
14119
|
const editor = resolveEditor(env);
|
|
13921
14120
|
if (!editor)
|
|
13922
14121
|
throw new Error(
|
|
@@ -13924,16 +14123,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
13924
14123
|
);
|
|
13925
14124
|
if (!interactive())
|
|
13926
14125
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
13927
|
-
const dir = mkdtempSync(
|
|
13928
|
-
const file =
|
|
14126
|
+
const dir = mkdtempSync(join16(tmpdir4(), "odla-runbook-"));
|
|
14127
|
+
const file = join16(dir, `${slug}.md`);
|
|
13929
14128
|
try {
|
|
13930
|
-
|
|
14129
|
+
writeFileSync5(file, initial, { mode: 384 });
|
|
13931
14130
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
13932
14131
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
13933
|
-
const edited =
|
|
14132
|
+
const edited = readFileSync13(file, "utf8");
|
|
13934
14133
|
return edited === initial ? null : edited;
|
|
13935
14134
|
} finally {
|
|
13936
|
-
|
|
14135
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
13937
14136
|
}
|
|
13938
14137
|
}
|
|
13939
14138
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -14821,6 +15020,23 @@ async function securityStatus(parsed, dependencies) {
|
|
|
14821
15020
|
|
|
14822
15021
|
// src/cli.ts
|
|
14823
15022
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
15023
|
+
const out = redactingOutput(dependencies.stdout ?? console);
|
|
15024
|
+
const advisories = [];
|
|
15025
|
+
const withAdvisoryReader = {
|
|
15026
|
+
...dependencies,
|
|
15027
|
+
fetch: advisoryCollectingFetch(dependencies.fetch ?? fetch, advisories)
|
|
15028
|
+
};
|
|
15029
|
+
try {
|
|
15030
|
+
return await dispatchCli(argv, withAdvisoryReader);
|
|
15031
|
+
} catch (error) {
|
|
15032
|
+
const explanation = explainRejectedCredential(error);
|
|
15033
|
+
if (explanation) out.error(explanation);
|
|
15034
|
+
throw error;
|
|
15035
|
+
} finally {
|
|
15036
|
+
renderAdvisories(out, advisories);
|
|
15037
|
+
}
|
|
15038
|
+
}
|
|
15039
|
+
async function dispatchCli(argv, dependencies) {
|
|
14824
15040
|
const runtime = {
|
|
14825
15041
|
...dependencies,
|
|
14826
15042
|
stdout: redactingOutput(dependencies.stdout ?? console)
|
|
@@ -14850,6 +15066,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14850
15066
|
await contextCommand(parsed, runtime);
|
|
14851
15067
|
return;
|
|
14852
15068
|
}
|
|
15069
|
+
if (command === "device") {
|
|
15070
|
+
await deviceCommand(parsed, runtime);
|
|
15071
|
+
return;
|
|
15072
|
+
}
|
|
14853
15073
|
if (command === "credentials") {
|
|
14854
15074
|
await credentialCommand(parsed, runtime);
|
|
14855
15075
|
return;
|
|
@@ -15045,4 +15265,4 @@ export {
|
|
|
15045
15265
|
isTerminalHostedSecurityStatus,
|
|
15046
15266
|
runCli
|
|
15047
15267
|
};
|
|
15048
|
-
//# sourceMappingURL=chunk-
|
|
15268
|
+
//# sourceMappingURL=chunk-DBZQIMES.js.map
|