@odla-ai/cli 0.35.3 → 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 +408 -231
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-PYR73XBD.js → chunk-DBZQIMES.js} +292 -136
- package/dist/chunk-DBZQIMES.js.map +1 -0
- package/dist/{cli-ZBNA7J4T.js → cli-BN6WLH5O.js} +2 -2
- package/dist/index.cjs +360 -204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PYR73XBD.js.map +0 -1
- /package/dist/{cli-ZBNA7J4T.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";
|
|
@@ -172,13 +172,49 @@ function explainRejectedCredential(error) {
|
|
|
172
172
|
].join("\n");
|
|
173
173
|
}
|
|
174
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
|
+
|
|
175
211
|
// src/local.ts
|
|
176
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
212
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
177
213
|
import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
|
|
178
214
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
179
215
|
function readJsonFile(path) {
|
|
180
216
|
try {
|
|
181
|
-
return JSON.parse(
|
|
217
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
182
218
|
} catch {
|
|
183
219
|
return null;
|
|
184
220
|
}
|
|
@@ -188,10 +224,10 @@ function writePrivateJson(path, value2) {
|
|
|
188
224
|
`);
|
|
189
225
|
}
|
|
190
226
|
function readCredentials(path) {
|
|
191
|
-
if (!
|
|
227
|
+
if (!existsSync2(path)) return null;
|
|
192
228
|
let value2;
|
|
193
229
|
try {
|
|
194
|
-
value2 = JSON.parse(
|
|
230
|
+
value2 = JSON.parse(readFileSync2(path, "utf8"));
|
|
195
231
|
} catch {
|
|
196
232
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
197
233
|
}
|
|
@@ -222,7 +258,7 @@ function mergeCredential(current, update) {
|
|
|
222
258
|
}
|
|
223
259
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
224
260
|
const path = resolve(rootDir, ".gitignore");
|
|
225
|
-
const existing =
|
|
261
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
226
262
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
|
|
227
263
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
228
264
|
const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
|
|
@@ -256,7 +292,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
256
292
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
257
293
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
258
294
|
}
|
|
259
|
-
const existing =
|
|
295
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
260
296
|
const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
|
|
261
297
|
while (retained.at(-1) === "") retained.pop();
|
|
262
298
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -306,14 +342,20 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
306
342
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
307
343
|
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
308
344
|
if (options.token) return options.token;
|
|
309
|
-
if (
|
|
310
|
-
const declared =
|
|
345
|
+
if (process6.env.ODLA_DEV_TOKEN) {
|
|
346
|
+
const declared = process6.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
311
347
|
if (declared) {
|
|
312
348
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
313
349
|
} else if (audience !== "https://odla.ai") {
|
|
314
350
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
315
351
|
}
|
|
316
|
-
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;
|
|
317
359
|
}
|
|
318
360
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
319
361
|
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
@@ -418,7 +460,7 @@ function stillPending(pending, email) {
|
|
|
418
460
|
);
|
|
419
461
|
}
|
|
420
462
|
function handshakeEmail(value2, cached) {
|
|
421
|
-
const email = (value2 ??
|
|
463
|
+
const email = (value2 ?? process6.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
422
464
|
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
423
465
|
throw new Error(
|
|
424
466
|
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
@@ -449,12 +491,12 @@ function platformAudience(value2) {
|
|
|
449
491
|
}
|
|
450
492
|
|
|
451
493
|
// src/secret-input.ts
|
|
452
|
-
import
|
|
494
|
+
import process7 from "process";
|
|
453
495
|
var MAX_BYTES = 64 * 1024;
|
|
454
496
|
async function secretInputValue(options, kind = "credential") {
|
|
455
497
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
456
498
|
let value2;
|
|
457
|
-
if (options.fromEnv) value2 =
|
|
499
|
+
if (options.fromEnv) value2 = process7.env[options.fromEnv];
|
|
458
500
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
459
501
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
460
502
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -462,7 +504,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
462
504
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
463
505
|
return value2;
|
|
464
506
|
}
|
|
465
|
-
async function readSecretStream(kind, stream =
|
|
507
|
+
async function readSecretStream(kind, stream = process7.stdin) {
|
|
466
508
|
let value2 = "";
|
|
467
509
|
for await (const chunk of stream) {
|
|
468
510
|
value2 += String(chunk);
|
|
@@ -472,9 +514,9 @@ async function readSecretStream(kind, stream = process6.stdin) {
|
|
|
472
514
|
}
|
|
473
515
|
|
|
474
516
|
// src/admin-ai-auth.ts
|
|
475
|
-
import { existsSync as
|
|
476
|
-
import { join as
|
|
477
|
-
import
|
|
517
|
+
import { existsSync as existsSync3 } from "fs";
|
|
518
|
+
import { join as join3 } from "path";
|
|
519
|
+
import process8 from "process";
|
|
478
520
|
import { requestToken as requestToken2 } from "@odla-ai/db";
|
|
479
521
|
async function getScopedPlatformToken(options) {
|
|
480
522
|
return resolveAdminPlatformToken(options);
|
|
@@ -482,7 +524,7 @@ async function getScopedPlatformToken(options) {
|
|
|
482
524
|
async function resolveAdminPlatformToken(options) {
|
|
483
525
|
const audience = platformAudience(options.platform);
|
|
484
526
|
if (options.token) return options.token;
|
|
485
|
-
const fromEnv =
|
|
527
|
+
const fromEnv = process8.env.ODLA_ADMIN_TOKEN;
|
|
486
528
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
487
529
|
return scopedToken(
|
|
488
530
|
audience,
|
|
@@ -494,7 +536,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
494
536
|
}
|
|
495
537
|
function audienceBoundEnvToken(token, platform) {
|
|
496
538
|
const audience = platformAudience(platform);
|
|
497
|
-
const declared =
|
|
539
|
+
const declared = process8.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
498
540
|
if (declared) {
|
|
499
541
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
500
542
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -507,6 +549,7 @@ var SCOPE_PURPOSE = {
|
|
|
507
549
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
508
550
|
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
509
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",
|
|
510
553
|
"platform:ai:policy:write": "change System AI model routing",
|
|
511
554
|
"platform:ai:policy:read": "read System AI model routing",
|
|
512
555
|
"platform:ai:credential:write": "replace a stored AI provider key",
|
|
@@ -519,8 +562,8 @@ var SCOPE_PURPOSE = {
|
|
|
519
562
|
};
|
|
520
563
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
521
564
|
const audience = platformAudience(platform);
|
|
522
|
-
const rootDir = options.rootDir ??
|
|
523
|
-
const tokenFile = options.tokenFile ??
|
|
565
|
+
const rootDir = options.rootDir ?? process8.cwd();
|
|
566
|
+
const tokenFile = options.tokenFile ?? join3(rootDir, ".odla/admin-token.local.json");
|
|
524
567
|
const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
|
|
525
568
|
const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
|
|
526
569
|
if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
@@ -547,7 +590,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
547
590
|
if (options.cache !== false) {
|
|
548
591
|
const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
|
|
549
592
|
tokens[scope] = { token, expiresAt };
|
|
550
|
-
if (
|
|
593
|
+
if (existsSync3(join3(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
|
|
551
594
|
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
552
595
|
out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
553
596
|
} else {
|
|
@@ -724,7 +767,7 @@ function isRecord2(value2) {
|
|
|
724
767
|
|
|
725
768
|
// src/admin-ai.ts
|
|
726
769
|
async function adminAi(options) {
|
|
727
|
-
const platform = platformAudience(options.platform ??
|
|
770
|
+
const platform = platformAudience(options.platform ?? process9.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
728
771
|
const doFetch = options.fetch ?? fetch;
|
|
729
772
|
const out = options.stdout ?? console;
|
|
730
773
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -979,12 +1022,12 @@ function addOption(options, name, value2) {
|
|
|
979
1022
|
}
|
|
980
1023
|
|
|
981
1024
|
// src/operator-context.ts
|
|
982
|
-
import { existsSync as
|
|
983
|
-
import { join as
|
|
984
|
-
import
|
|
1025
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1026
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
1027
|
+
import process11 from "process";
|
|
985
1028
|
|
|
986
1029
|
// src/config.ts
|
|
987
|
-
import { existsSync as
|
|
1030
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
988
1031
|
import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
989
1032
|
import { pathToFileURL } from "url";
|
|
990
1033
|
import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
|
|
@@ -1391,7 +1434,7 @@ var configImportSerial = 0;
|
|
|
1391
1434
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1392
1435
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1393
1436
|
const resolved = resolve2(configPath);
|
|
1394
|
-
if (!
|
|
1437
|
+
if (!existsSync4(resolved)) {
|
|
1395
1438
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
1396
1439
|
}
|
|
1397
1440
|
const raw = await loadConfigModule(resolved);
|
|
@@ -1426,7 +1469,7 @@ async function resolveDataExport(cfg, value2, names) {
|
|
|
1426
1469
|
if (typeof value2 !== "string") return value2;
|
|
1427
1470
|
const target = isAbsolute2(value2) ? value2 : resolve2(cfg.rootDir, value2);
|
|
1428
1471
|
if (target.endsWith(".json")) {
|
|
1429
|
-
return JSON.parse(
|
|
1472
|
+
return JSON.parse(readFileSync3(target, "utf8"));
|
|
1430
1473
|
}
|
|
1431
1474
|
const mod = await import(pathToFileURL(target).href);
|
|
1432
1475
|
for (const name of names) {
|
|
@@ -1502,7 +1545,7 @@ function validId2(value2) {
|
|
|
1502
1545
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1503
1546
|
}
|
|
1504
1547
|
async function loadConfigModule(path) {
|
|
1505
|
-
if (path.endsWith(".json")) return JSON.parse(
|
|
1548
|
+
if (path.endsWith(".json")) return JSON.parse(readFileSync3(path, "utf8"));
|
|
1506
1549
|
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1507
1550
|
const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
|
|
1508
1551
|
const value2 = mod.default ?? mod.config;
|
|
@@ -1517,18 +1560,18 @@ function unique3(values) {
|
|
|
1517
1560
|
}
|
|
1518
1561
|
|
|
1519
1562
|
// src/operator-profiles.ts
|
|
1520
|
-
import { existsSync as
|
|
1521
|
-
import { homedir } from "os";
|
|
1522
|
-
import { dirname as dirname4, join as
|
|
1523
|
-
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";
|
|
1524
1567
|
function operatorProfileFile() {
|
|
1525
1568
|
return resolve3(
|
|
1526
|
-
clean(
|
|
1569
|
+
clean(process10.env.ODLA_CONTEXT_FILE) ?? join4(homedir2(), ".odla", "contexts.json")
|
|
1527
1570
|
);
|
|
1528
1571
|
}
|
|
1529
1572
|
function resolveOperatorProfile(parsed) {
|
|
1530
1573
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1531
|
-
const fromEnvironment = clean(
|
|
1574
|
+
const fromEnvironment = clean(process10.env.ODLA_CONTEXT);
|
|
1532
1575
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1533
1576
|
const file = operatorProfileFile();
|
|
1534
1577
|
if (!name) {
|
|
@@ -1568,10 +1611,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
1568
1611
|
return true;
|
|
1569
1612
|
}
|
|
1570
1613
|
function operatorCredentialFiles(selection) {
|
|
1571
|
-
const base = selection.name ?
|
|
1614
|
+
const base = selection.name ? join4(dirname4(selection.file), "profiles", selection.name) : join4(homedir2(), ".odla");
|
|
1572
1615
|
return {
|
|
1573
|
-
developer:
|
|
1574
|
-
scoped:
|
|
1616
|
+
developer: join4(base, "dev-token.json"),
|
|
1617
|
+
scoped: join4(base, "admin-token.local.json")
|
|
1575
1618
|
};
|
|
1576
1619
|
}
|
|
1577
1620
|
function assertOperatorName(value2, label) {
|
|
@@ -1582,10 +1625,10 @@ function assertOperatorName(value2, label) {
|
|
|
1582
1625
|
}
|
|
1583
1626
|
}
|
|
1584
1627
|
function readOperatorProfiles(file) {
|
|
1585
|
-
if (!
|
|
1628
|
+
if (!existsSync5(file)) return emptyProfiles();
|
|
1586
1629
|
let raw;
|
|
1587
1630
|
try {
|
|
1588
|
-
raw = JSON.parse(
|
|
1631
|
+
raw = JSON.parse(readFileSync4(file, "utf8"));
|
|
1589
1632
|
} catch {
|
|
1590
1633
|
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
1591
1634
|
}
|
|
@@ -1651,19 +1694,19 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1651
1694
|
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
1652
1695
|
const configPath = resolve4(configArgument);
|
|
1653
1696
|
const explicitConfig = parsed.options.config !== void 0;
|
|
1654
|
-
const hasConfig =
|
|
1697
|
+
const hasConfig = existsSync6(configPath);
|
|
1655
1698
|
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
1656
1699
|
await loadProjectConfig(configArgument);
|
|
1657
1700
|
}
|
|
1658
1701
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1659
1702
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1660
|
-
const platformEnvironment = clean2(
|
|
1703
|
+
const platformEnvironment = clean2(process11.env.ODLA_PLATFORM_URL);
|
|
1661
1704
|
const platformValue = platformAudience(
|
|
1662
1705
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1663
1706
|
);
|
|
1664
1707
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1665
1708
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1666
|
-
const appEnvironment = clean2(
|
|
1709
|
+
const appEnvironment = clean2(process11.env.ODLA_APP_ID);
|
|
1667
1710
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1668
1711
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1669
1712
|
if (appValue) assertOperatorName(appValue, "app");
|
|
@@ -1673,16 +1716,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1673
1716
|
);
|
|
1674
1717
|
}
|
|
1675
1718
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1676
|
-
const envEnvironment = clean2(
|
|
1719
|
+
const envEnvironment = clean2(process11.env.ODLA_ENV);
|
|
1677
1720
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1678
1721
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1679
1722
|
if (environmentValue) {
|
|
1680
1723
|
assertOperatorName(environmentValue, "environment");
|
|
1681
1724
|
}
|
|
1682
|
-
const rootDir = loaded?.rootDir ??
|
|
1725
|
+
const rootDir = loaded?.rootDir ?? process11.cwd();
|
|
1683
1726
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1684
|
-
const tokenFile = clean2(
|
|
1685
|
-
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;
|
|
1686
1729
|
const cfg = loaded ? {
|
|
1687
1730
|
...loaded,
|
|
1688
1731
|
platformUrl: platformValue,
|
|
@@ -1704,8 +1747,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1704
1747
|
services: [],
|
|
1705
1748
|
local: {
|
|
1706
1749
|
tokenFile,
|
|
1707
|
-
credentialsFile:
|
|
1708
|
-
devVarsFile:
|
|
1750
|
+
credentialsFile: join5(rootDir, ".odla", "credentials.local.json"),
|
|
1751
|
+
devVarsFile: join5(rootDir, ".dev.vars"),
|
|
1709
1752
|
gitignore: true
|
|
1710
1753
|
}
|
|
1711
1754
|
};
|
|
@@ -1802,7 +1845,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1802
1845
|
}
|
|
1803
1846
|
|
|
1804
1847
|
// src/auth-command.ts
|
|
1805
|
-
import
|
|
1848
|
+
import process12 from "process";
|
|
1806
1849
|
|
|
1807
1850
|
// src/whoami-command.ts
|
|
1808
1851
|
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
@@ -1964,7 +2007,7 @@ async function authCommand(parsed, deps = {}) {
|
|
|
1964
2007
|
const { cfg } = context;
|
|
1965
2008
|
const out = deps.stdout ?? console;
|
|
1966
2009
|
const doFetch = deps.fetch ?? fetch;
|
|
1967
|
-
const email = stringOpt(parsed.options.email) ??
|
|
2010
|
+
const email = stringOpt(parsed.options.email) ?? process12.env.ODLA_USER_EMAIL?.trim();
|
|
1968
2011
|
if (!email) {
|
|
1969
2012
|
throw new Error(
|
|
1970
2013
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
@@ -2117,7 +2160,7 @@ async function appExport(options) {
|
|
|
2117
2160
|
}
|
|
2118
2161
|
|
|
2119
2162
|
// src/app-import.ts
|
|
2120
|
-
import { readFileSync as
|
|
2163
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2121
2164
|
import {
|
|
2122
2165
|
buildImportOps,
|
|
2123
2166
|
parseImport,
|
|
@@ -2139,7 +2182,7 @@ async function appImport(options) {
|
|
|
2139
2182
|
const out = options.stdout ?? console;
|
|
2140
2183
|
const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
|
|
2141
2184
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2142
|
-
const text3 = options.file === "-" ? (options.readStdin ?? (() =>
|
|
2185
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
|
|
2143
2186
|
const { format, sources } = parseImport(text3, options.ns);
|
|
2144
2187
|
if (format === "namespace-map" && options.ns) {
|
|
2145
2188
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -3010,7 +3053,7 @@ import {
|
|
|
3010
3053
|
AppsError,
|
|
3011
3054
|
createAppsClient
|
|
3012
3055
|
} from "@odla-ai/apps";
|
|
3013
|
-
import { join as
|
|
3056
|
+
import { join as join6 } from "path";
|
|
3014
3057
|
|
|
3015
3058
|
// src/config-operation-error.ts
|
|
3016
3059
|
var ConfigOperationCommandError = class extends Error {
|
|
@@ -3026,7 +3069,7 @@ var ConfigOperationCommandError = class extends Error {
|
|
|
3026
3069
|
import {
|
|
3027
3070
|
appServiceDefinition as appServiceDefinition2
|
|
3028
3071
|
} from "@odla-ai/apps";
|
|
3029
|
-
import { readFileSync as
|
|
3072
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3030
3073
|
|
|
3031
3074
|
// src/config-reconcile-digest.ts
|
|
3032
3075
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -3062,7 +3105,7 @@ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
|
3062
3105
|
function readPlan(path) {
|
|
3063
3106
|
let value2;
|
|
3064
3107
|
try {
|
|
3065
|
-
const raw =
|
|
3108
|
+
const raw = readFileSync6(path, "utf8");
|
|
3066
3109
|
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
3067
3110
|
value2 = JSON.parse(raw);
|
|
3068
3111
|
} catch (error) {
|
|
@@ -3435,7 +3478,7 @@ async function operationClient(cfg, options, purpose) {
|
|
|
3435
3478
|
platform: cfg.platformUrl,
|
|
3436
3479
|
scope: "app:config:write",
|
|
3437
3480
|
token: options.token,
|
|
3438
|
-
tokenFile:
|
|
3481
|
+
tokenFile: join6(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3439
3482
|
rootDir: cfg.rootDir,
|
|
3440
3483
|
email: options.email,
|
|
3441
3484
|
open: options.open,
|
|
@@ -3490,7 +3533,7 @@ function record4(value2) {
|
|
|
3490
3533
|
|
|
3491
3534
|
// src/config-reconcile-command.ts
|
|
3492
3535
|
import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
|
|
3493
|
-
import { join as
|
|
3536
|
+
import { join as join7 } from "path";
|
|
3494
3537
|
|
|
3495
3538
|
// src/config-reconcile.ts
|
|
3496
3539
|
import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
|
|
@@ -3786,7 +3829,7 @@ async function inspectConfig(options) {
|
|
|
3786
3829
|
platform: cfg.platformUrl,
|
|
3787
3830
|
scope: "app:config:read",
|
|
3788
3831
|
token: options.token,
|
|
3789
|
-
tokenFile:
|
|
3832
|
+
tokenFile: join7(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3790
3833
|
rootDir: cfg.rootDir,
|
|
3791
3834
|
email: options.email,
|
|
3792
3835
|
open: options.open,
|
|
@@ -3918,13 +3961,13 @@ function quoteArg2(value2) {
|
|
|
3918
3961
|
|
|
3919
3962
|
// src/doctor-checks.ts
|
|
3920
3963
|
import { execFileSync } from "child_process";
|
|
3921
|
-
import { existsSync as
|
|
3922
|
-
import { join as
|
|
3964
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
3965
|
+
import { join as join9, resolve as resolve6 } from "path";
|
|
3923
3966
|
|
|
3924
3967
|
// src/wrangler.ts
|
|
3925
3968
|
import { spawn as spawn2 } from "child_process";
|
|
3926
|
-
import { existsSync as
|
|
3927
|
-
import { join as
|
|
3969
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3970
|
+
import { join as join8 } from "path";
|
|
3928
3971
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3929
3972
|
const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3930
3973
|
let stdout = "";
|
|
@@ -3938,15 +3981,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3938
3981
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3939
3982
|
function findWranglerConfig(rootDir) {
|
|
3940
3983
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3941
|
-
const path =
|
|
3942
|
-
if (
|
|
3984
|
+
const path = join8(rootDir, name);
|
|
3985
|
+
if (existsSync7(path)) return path;
|
|
3943
3986
|
}
|
|
3944
3987
|
return null;
|
|
3945
3988
|
}
|
|
3946
3989
|
function readWranglerConfig(path) {
|
|
3947
3990
|
if (path.endsWith(".toml")) return null;
|
|
3948
3991
|
try {
|
|
3949
|
-
return JSON.parse(stripJsonComments(
|
|
3992
|
+
return JSON.parse(stripJsonComments(readFileSync7(path, "utf8")));
|
|
3950
3993
|
} catch {
|
|
3951
3994
|
return null;
|
|
3952
3995
|
}
|
|
@@ -4099,7 +4142,7 @@ function wranglerWarnings(rootDir) {
|
|
|
4099
4142
|
const dir = resolve6(rootDir, assets.directory);
|
|
4100
4143
|
if (dir === resolve6(rootDir)) {
|
|
4101
4144
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4102
|
-
} else if (
|
|
4145
|
+
} else if (existsSync8(join9(dir, "node_modules"))) {
|
|
4103
4146
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4104
4147
|
}
|
|
4105
4148
|
}
|
|
@@ -4135,12 +4178,12 @@ function o11yProjectWarnings(rootDir) {
|
|
|
4135
4178
|
return warnings;
|
|
4136
4179
|
}
|
|
4137
4180
|
const main = typeof config.main === "string" ? resolve6(rootDir, config.main) : null;
|
|
4138
|
-
if (!main || !
|
|
4181
|
+
if (!main || !existsSync8(main)) {
|
|
4139
4182
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
4140
4183
|
} else {
|
|
4141
4184
|
let source = "";
|
|
4142
4185
|
try {
|
|
4143
|
-
source =
|
|
4186
|
+
source = readFileSync8(main, "utf8");
|
|
4144
4187
|
} catch {
|
|
4145
4188
|
}
|
|
4146
4189
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -4164,7 +4207,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4164
4207
|
}
|
|
4165
4208
|
function readPackageJson(rootDir) {
|
|
4166
4209
|
try {
|
|
4167
|
-
return JSON.parse(
|
|
4210
|
+
return JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf8"));
|
|
4168
4211
|
} catch {
|
|
4169
4212
|
return null;
|
|
4170
4213
|
}
|
|
@@ -4458,14 +4501,14 @@ function harnessOption(value2, flag) {
|
|
|
4458
4501
|
}
|
|
4459
4502
|
|
|
4460
4503
|
// src/init.ts
|
|
4461
|
-
import { existsSync as
|
|
4504
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4462
4505
|
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
4463
4506
|
import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
|
|
4464
4507
|
function initProject(options) {
|
|
4465
4508
|
const out = options.stdout ?? console;
|
|
4466
4509
|
const rootDir = resolve7(options.rootDir ?? process.cwd());
|
|
4467
4510
|
const configPath = resolve7(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4468
|
-
if (
|
|
4511
|
+
if (existsSync9(configPath) && !options.force) {
|
|
4469
4512
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
4470
4513
|
}
|
|
4471
4514
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -4493,7 +4536,7 @@ function initProject(options) {
|
|
|
4493
4536
|
out.log("updated .gitignore for local odla credentials");
|
|
4494
4537
|
}
|
|
4495
4538
|
function writeIfMissing(path, text3) {
|
|
4496
|
-
if (
|
|
4539
|
+
if (existsSync9(path)) return;
|
|
4497
4540
|
writeFileSync2(path, text3);
|
|
4498
4541
|
}
|
|
4499
4542
|
function configTemplate(input) {
|
|
@@ -4790,9 +4833,9 @@ function printReport(report5, out) {
|
|
|
4790
4833
|
}
|
|
4791
4834
|
|
|
4792
4835
|
// src/skill.ts
|
|
4793
|
-
import { existsSync as
|
|
4794
|
-
import { homedir as
|
|
4795
|
-
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";
|
|
4796
4839
|
import { fileURLToPath } from "url";
|
|
4797
4840
|
|
|
4798
4841
|
// src/skill-adapters.ts
|
|
@@ -4892,7 +4935,7 @@ function installSkill(options = {}) {
|
|
|
4892
4935
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
4893
4936
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
4894
4937
|
const root = resolve8(options.dir ?? process.cwd());
|
|
4895
|
-
const home = resolve8(options.homeDir ??
|
|
4938
|
+
const home = resolve8(options.homeDir ?? homedir3());
|
|
4896
4939
|
const plans = /* @__PURE__ */ new Map();
|
|
4897
4940
|
const targets = /* @__PURE__ */ new Map();
|
|
4898
4941
|
const rememberTarget = (harness, target) => {
|
|
@@ -4906,12 +4949,12 @@ function installSkill(options = {}) {
|
|
|
4906
4949
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4907
4950
|
};
|
|
4908
4951
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4909
|
-
for (const rel of files) plan(
|
|
4952
|
+
for (const rel of files) plan(join10(targetDir2, rel), readFileSync9(join10(sourceDir, rel), "utf8"), false, boundary);
|
|
4910
4953
|
};
|
|
4911
4954
|
let targetDir;
|
|
4912
4955
|
if (options.global) {
|
|
4913
|
-
const claudeRoot =
|
|
4914
|
-
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");
|
|
4915
4958
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4916
4959
|
for (const harness of harnesses) {
|
|
4917
4960
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -4919,35 +4962,35 @@ function installSkill(options = {}) {
|
|
|
4919
4962
|
rememberTarget(harness, skillRoot);
|
|
4920
4963
|
}
|
|
4921
4964
|
} else {
|
|
4922
|
-
const sharedRoot =
|
|
4965
|
+
const sharedRoot = join10(root, ".agents", "skills");
|
|
4923
4966
|
planSkillTree(sharedRoot);
|
|
4924
|
-
const claudeRoot =
|
|
4967
|
+
const claudeRoot = join10(root, ".claude", "skills");
|
|
4925
4968
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4926
4969
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4927
4970
|
if (harnesses.includes("claude")) {
|
|
4928
4971
|
for (const skill of skillNames(files)) {
|
|
4929
|
-
const canonical2 =
|
|
4930
|
-
plan(
|
|
4972
|
+
const canonical2 = readFileSync9(join10(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4973
|
+
plan(join10(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
4931
4974
|
}
|
|
4932
4975
|
rememberTarget("claude", claudeRoot);
|
|
4933
4976
|
}
|
|
4934
4977
|
if (harnesses.includes("cursor")) {
|
|
4935
|
-
const cursorRule =
|
|
4978
|
+
const cursorRule = join10(root, ".cursor", "rules", "odla.mdc");
|
|
4936
4979
|
plan(cursorRule, CURSOR_RULE);
|
|
4937
4980
|
rememberTarget("cursor", cursorRule);
|
|
4938
4981
|
}
|
|
4939
4982
|
if (harnesses.includes("agents")) {
|
|
4940
|
-
const agentsFile =
|
|
4983
|
+
const agentsFile = join10(root, "AGENTS.md");
|
|
4941
4984
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4942
4985
|
rememberTarget("agents", agentsFile);
|
|
4943
4986
|
}
|
|
4944
4987
|
if (harnesses.includes("copilot")) {
|
|
4945
|
-
const copilotFile =
|
|
4988
|
+
const copilotFile = join10(root, ".github", "copilot-instructions.md");
|
|
4946
4989
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4947
4990
|
rememberTarget("copilot", copilotFile);
|
|
4948
4991
|
}
|
|
4949
4992
|
if (harnesses.includes("gemini")) {
|
|
4950
|
-
const geminiFile =
|
|
4993
|
+
const geminiFile = join10(root, "GEMINI.md");
|
|
4951
4994
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4952
4995
|
rememberTarget("gemini", geminiFile);
|
|
4953
4996
|
}
|
|
@@ -4961,11 +5004,11 @@ function installSkill(options = {}) {
|
|
|
4961
5004
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4962
5005
|
continue;
|
|
4963
5006
|
}
|
|
4964
|
-
if (!
|
|
5007
|
+
if (!existsSync10(file.target)) {
|
|
4965
5008
|
writtenPaths.add(file.target);
|
|
4966
5009
|
continue;
|
|
4967
5010
|
}
|
|
4968
|
-
const current =
|
|
5011
|
+
const current = readFileSync9(file.target, "utf8");
|
|
4969
5012
|
if (current === file.content) {
|
|
4970
5013
|
unchangedPaths.add(file.target);
|
|
4971
5014
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4982,7 +5025,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4982
5025
|
);
|
|
4983
5026
|
}
|
|
4984
5027
|
for (const file of plans.values()) {
|
|
4985
|
-
if (!
|
|
5028
|
+
if (!existsSync10(file.target) || readFileSync9(file.target, "utf8") !== file.content) {
|
|
4986
5029
|
mkdirSync3(dirname7(file.target), { recursive: true });
|
|
4987
5030
|
writeFileSync3(file.target, file.content);
|
|
4988
5031
|
}
|
|
@@ -5025,9 +5068,9 @@ function normalizeHarnesses(values, global) {
|
|
|
5025
5068
|
function managedFileContent(path, block, force, boundary) {
|
|
5026
5069
|
const symlink = symlinkedComponent(boundary, path);
|
|
5027
5070
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
5028
|
-
if (!
|
|
5071
|
+
if (!existsSync10(path)) return `${block}
|
|
5029
5072
|
`;
|
|
5030
|
-
const current =
|
|
5073
|
+
const current = readFileSync9(path, "utf8");
|
|
5031
5074
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
5032
5075
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
5033
5076
|
const startAt = current.indexOf(start);
|
|
@@ -5054,7 +5097,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
5054
5097
|
}
|
|
5055
5098
|
let current = boundary;
|
|
5056
5099
|
for (const part of rel.split(sep).filter(Boolean)) {
|
|
5057
|
-
current =
|
|
5100
|
+
current = join10(current, part);
|
|
5058
5101
|
try {
|
|
5059
5102
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
5060
5103
|
} catch (error) {
|
|
@@ -5067,11 +5110,11 @@ function skillNames(files) {
|
|
|
5067
5110
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
5068
5111
|
}
|
|
5069
5112
|
function listFiles(dir) {
|
|
5070
|
-
if (!
|
|
5113
|
+
if (!existsSync10(dir)) return [];
|
|
5071
5114
|
const results = [];
|
|
5072
5115
|
const walk = (current) => {
|
|
5073
5116
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
5074
|
-
const path =
|
|
5117
|
+
const path = join10(current, entry.name);
|
|
5075
5118
|
if (entry.isDirectory()) walk(path);
|
|
5076
5119
|
else results.push(relative2(dir, path));
|
|
5077
5120
|
}
|
|
@@ -5428,7 +5471,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5428
5471
|
}
|
|
5429
5472
|
|
|
5430
5473
|
// src/code-connect.ts
|
|
5431
|
-
import { existsSync as
|
|
5474
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5432
5475
|
import { cpus, hostname, totalmem } from "os";
|
|
5433
5476
|
import { resolve as resolve11 } from "path";
|
|
5434
5477
|
|
|
@@ -5439,7 +5482,7 @@ var HARNESS_PROTOCOL_VERSION = 1;
|
|
|
5439
5482
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
5440
5483
|
import { constants } from "fs";
|
|
5441
5484
|
import { access } from "fs/promises";
|
|
5442
|
-
import { delimiter, join as
|
|
5485
|
+
import { delimiter, join as join11 } from "path";
|
|
5443
5486
|
import { getgid, getuid } from "process";
|
|
5444
5487
|
import { mkdir as mkdir2, mkdtemp, realpath, rm, writeFile as writeFile2 } from "fs/promises";
|
|
5445
5488
|
import { tmpdir } from "os";
|
|
@@ -5457,7 +5500,7 @@ function assertPinnedImage(image) {
|
|
|
5457
5500
|
async function commandAvailable(engine) {
|
|
5458
5501
|
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
5459
5502
|
try {
|
|
5460
|
-
await access(
|
|
5503
|
+
await access(join11(directory, engine), constants.X_OK);
|
|
5461
5504
|
return true;
|
|
5462
5505
|
} catch {
|
|
5463
5506
|
}
|
|
@@ -6155,7 +6198,7 @@ import { randomUUID } from "crypto";
|
|
|
6155
6198
|
import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
|
|
6156
6199
|
import { createReadStream } from "fs";
|
|
6157
6200
|
import { lstat as lstat22 } from "fs/promises";
|
|
6158
|
-
import { join as
|
|
6201
|
+
import { join as join13 } from "path";
|
|
6159
6202
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
6160
6203
|
import { tmpdir as tmpdir3 } from "os";
|
|
6161
6204
|
import { dirname as dirname9, join as join23, resolve as resolve32, sep as sep23 } from "path";
|
|
@@ -6542,8 +6585,8 @@ function rollup(graph, kind, options = {}) {
|
|
|
6542
6585
|
for (const node of nodesOfKind(graph, kind)) {
|
|
6543
6586
|
if (options.prefix && !node.name.startsWith(options.prefix)) continue;
|
|
6544
6587
|
const key = node.name.split(separator).slice(0, depth).join(separator);
|
|
6545
|
-
const
|
|
6546
|
-
if (
|
|
6588
|
+
const list3 = groups.get(key);
|
|
6589
|
+
if (list3) list3.push(node);
|
|
6547
6590
|
else groups.set(key, [node]);
|
|
6548
6591
|
}
|
|
6549
6592
|
return [...groups].map(([prefix, nodes]) => ({
|
|
@@ -6558,7 +6601,7 @@ function dirname8(path) {
|
|
|
6558
6601
|
const at = path.lastIndexOf("/");
|
|
6559
6602
|
return at <= 0 ? "." : path.slice(0, at);
|
|
6560
6603
|
}
|
|
6561
|
-
function
|
|
6604
|
+
function join12(base, specifier) {
|
|
6562
6605
|
const parts = [];
|
|
6563
6606
|
const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
|
|
6564
6607
|
for (const segment of segments) {
|
|
@@ -6582,7 +6625,7 @@ var BARE_IMPORT = /^\s*import\s*["']([^"']+)["']/gm;
|
|
|
6582
6625
|
var isSourcePath = (path) => SOURCE.test(path);
|
|
6583
6626
|
function resolveImport(fromPath, specifier, known) {
|
|
6584
6627
|
if (!specifier.startsWith(".")) return null;
|
|
6585
|
-
const base =
|
|
6628
|
+
const base = join12(dirname8(fromPath), specifier);
|
|
6586
6629
|
const candidates = [
|
|
6587
6630
|
base,
|
|
6588
6631
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -7385,7 +7428,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
7385
7428
|
const receipts = [];
|
|
7386
7429
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
7387
7430
|
try {
|
|
7388
|
-
const path =
|
|
7431
|
+
const path = join13(workspaceDir, artifact.path);
|
|
7389
7432
|
const info = await lstat22(path);
|
|
7390
7433
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
7391
7434
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
@@ -9481,7 +9524,7 @@ var CODE_BUILD_RECIPES = Object.freeze([{
|
|
|
9481
9524
|
async function codeConnect(options) {
|
|
9482
9525
|
const cwd = options.cwd ?? process.cwd();
|
|
9483
9526
|
const configPath = resolve11(cwd, options.configPath);
|
|
9484
|
-
const cfg =
|
|
9527
|
+
const cfg = existsSync11(configPath) ? await loadProjectConfig(configPath) : null;
|
|
9485
9528
|
const requestedAppId = options.appId?.trim();
|
|
9486
9529
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
9487
9530
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -9915,13 +9958,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
9915
9958
|
}
|
|
9916
9959
|
|
|
9917
9960
|
// src/operator-credentials.ts
|
|
9918
|
-
import
|
|
9961
|
+
import process13 from "process";
|
|
9919
9962
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
9920
9963
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
9921
9964
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
9922
9965
|
const source = clean3(
|
|
9923
9966
|
stringOpt(parsed.options.token)
|
|
9924
|
-
) ? "flag" : clean3(
|
|
9967
|
+
) ? "flag" : clean3(process13.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
9925
9968
|
return {
|
|
9926
9969
|
source,
|
|
9927
9970
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -10243,6 +10286,9 @@ Usage:
|
|
|
10243
10286
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10244
10287
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10245
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]
|
|
10246
10292
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
10247
10293
|
odla-ai credentials revoke <receipt-id> [--config odla.config.mjs] [--json]
|
|
10248
10294
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--runtime] [--email <odla-account>] [--no-open]
|
|
@@ -10348,6 +10394,10 @@ Commands:
|
|
|
10348
10394
|
stable status, incident, and report JSON to agents and CI.
|
|
10349
10395
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10350
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.
|
|
10351
10401
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
10352
10402
|
"provision --live --yes" initializes only the live instance of
|
|
10353
10403
|
an existing sandbox app and enables every configured service;
|
|
@@ -12630,7 +12680,7 @@ function percent(value2) {
|
|
|
12630
12680
|
// src/provision.ts
|
|
12631
12681
|
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
|
|
12632
12682
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
12633
|
-
import
|
|
12683
|
+
import process14 from "process";
|
|
12634
12684
|
|
|
12635
12685
|
// src/integration-provision.ts
|
|
12636
12686
|
import { uuidv7 } from "@odla-ai/db";
|
|
@@ -13066,7 +13116,7 @@ async function provision(options) {
|
|
|
13066
13116
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
13067
13117
|
}
|
|
13068
13118
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
13069
|
-
const key =
|
|
13119
|
+
const key = process14.env[cfg.ai.keyEnv];
|
|
13070
13120
|
if (key) {
|
|
13071
13121
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
13072
13122
|
await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -13108,7 +13158,7 @@ async function provision(options) {
|
|
|
13108
13158
|
|
|
13109
13159
|
// src/record.ts
|
|
13110
13160
|
import { appendFileSync } from "fs";
|
|
13111
|
-
import
|
|
13161
|
+
import process15 from "process";
|
|
13112
13162
|
|
|
13113
13163
|
// src/surface.ts
|
|
13114
13164
|
var PM_ACTIONS = {
|
|
@@ -13178,6 +13228,7 @@ var COMMAND_SURFACE = {
|
|
|
13178
13228
|
config: { diff: {}, plan: {}, apply: {} },
|
|
13179
13229
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
13180
13230
|
credentials: { list: {}, revoke: {} },
|
|
13231
|
+
device: { enroll: {}, list: {}, revoke: {} },
|
|
13181
13232
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
13182
13233
|
discuss: {
|
|
13183
13234
|
groups: {},
|
|
@@ -13289,7 +13340,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
13289
13340
|
|
|
13290
13341
|
// src/record.ts
|
|
13291
13342
|
function recordInvocation(parsed) {
|
|
13292
|
-
const file =
|
|
13343
|
+
const file = process15.env.ODLA_CLI_RECORD;
|
|
13293
13344
|
if (!file) return;
|
|
13294
13345
|
try {
|
|
13295
13346
|
const entry = {
|
|
@@ -13326,8 +13377,109 @@ function renderAdvisories(out, advisories, env = process.env) {
|
|
|
13326
13377
|
}
|
|
13327
13378
|
}
|
|
13328
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
|
+
|
|
13329
13481
|
// src/runbook-actions.ts
|
|
13330
|
-
import { readFileSync as
|
|
13482
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
13331
13483
|
var PLATFORM_SCOPE = "$platform";
|
|
13332
13484
|
async function call(ctx, method, path, body) {
|
|
13333
13485
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
@@ -13374,7 +13526,7 @@ async function bySlug(ctx, slug) {
|
|
|
13374
13526
|
function readBody(file, inline) {
|
|
13375
13527
|
if (inline !== void 0) return inline;
|
|
13376
13528
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
13377
|
-
return
|
|
13529
|
+
return readFileSync10(file === "-" ? 0 : file, "utf8");
|
|
13378
13530
|
}
|
|
13379
13531
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
13380
13532
|
async function runbookList(ctx, all, query) {
|
|
@@ -13466,8 +13618,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
13466
13618
|
}
|
|
13467
13619
|
|
|
13468
13620
|
// src/runbook-import.ts
|
|
13469
|
-
import { readFileSync as
|
|
13470
|
-
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";
|
|
13471
13623
|
function parseRunbook(text3, slug) {
|
|
13472
13624
|
let rest = text3;
|
|
13473
13625
|
const meta = {};
|
|
@@ -13497,7 +13649,7 @@ function readRunbookDir(dir) {
|
|
|
13497
13649
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
13498
13650
|
return files.map((file) => {
|
|
13499
13651
|
const slug = basename2(file, ".md");
|
|
13500
|
-
const parsed = parseRunbook(
|
|
13652
|
+
const parsed = parseRunbook(readFileSync11(join14(dir, file), "utf8"), slug);
|
|
13501
13653
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
13502
13654
|
});
|
|
13503
13655
|
}
|
|
@@ -13570,8 +13722,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
13570
13722
|
|
|
13571
13723
|
// src/runbook-impact.ts
|
|
13572
13724
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13573
|
-
import { existsSync as
|
|
13574
|
-
import { join as
|
|
13725
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
13726
|
+
import { join as join15 } from "path";
|
|
13575
13727
|
|
|
13576
13728
|
// src/runbook-impact-scan.ts
|
|
13577
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$]*)/;
|
|
@@ -13740,10 +13892,10 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
|
|
|
13740
13892
|
}
|
|
13741
13893
|
function manifestLabeller(root) {
|
|
13742
13894
|
return (workspace) => {
|
|
13743
|
-
const manifest =
|
|
13744
|
-
if (!
|
|
13895
|
+
const manifest = join15(root, workspace, "package.json");
|
|
13896
|
+
if (!existsSync12(manifest)) return void 0;
|
|
13745
13897
|
try {
|
|
13746
|
-
const name = JSON.parse(
|
|
13898
|
+
const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
|
|
13747
13899
|
return typeof name === "string" ? name : void 0;
|
|
13748
13900
|
} catch {
|
|
13749
13901
|
return void 0;
|
|
@@ -13810,7 +13962,7 @@ function report4(ctx, impacts) {
|
|
|
13810
13962
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
13811
13963
|
const cwd = deps.cwd ?? process.cwd();
|
|
13812
13964
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
13813
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
13965
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join15(cwd, path), "utf8"));
|
|
13814
13966
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
13815
13967
|
if (!surfaces.length) {
|
|
13816
13968
|
return ctx.out.log(
|
|
@@ -13943,12 +14095,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
13943
14095
|
|
|
13944
14096
|
// src/runbook-editor.ts
|
|
13945
14097
|
import { spawnSync } from "child_process";
|
|
13946
|
-
import { mkdtempSync, readFileSync as
|
|
14098
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
13947
14099
|
import { tmpdir as tmpdir4 } from "os";
|
|
13948
|
-
import { join as
|
|
13949
|
-
import
|
|
14100
|
+
import { join as join16 } from "path";
|
|
14101
|
+
import process17 from "process";
|
|
13950
14102
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
13951
|
-
function resolveEditor(env =
|
|
14103
|
+
function resolveEditor(env = process17.env) {
|
|
13952
14104
|
for (const name of EDITOR_ENV) {
|
|
13953
14105
|
const value2 = env[name];
|
|
13954
14106
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -13962,8 +14114,8 @@ function defaultRun(command, path) {
|
|
|
13962
14114
|
return result.status ?? 0;
|
|
13963
14115
|
}
|
|
13964
14116
|
function editText(initial, slug, deps = {}) {
|
|
13965
|
-
const env = deps.env ??
|
|
13966
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
14117
|
+
const env = deps.env ?? process17.env;
|
|
14118
|
+
const interactive = deps.interactive ?? (() => Boolean(process17.stdin.isTTY));
|
|
13967
14119
|
const editor = resolveEditor(env);
|
|
13968
14120
|
if (!editor)
|
|
13969
14121
|
throw new Error(
|
|
@@ -13971,13 +14123,13 @@ function editText(initial, slug, deps = {}) {
|
|
|
13971
14123
|
);
|
|
13972
14124
|
if (!interactive())
|
|
13973
14125
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
13974
|
-
const dir = mkdtempSync(
|
|
13975
|
-
const file =
|
|
14126
|
+
const dir = mkdtempSync(join16(tmpdir4(), "odla-runbook-"));
|
|
14127
|
+
const file = join16(dir, `${slug}.md`);
|
|
13976
14128
|
try {
|
|
13977
|
-
|
|
14129
|
+
writeFileSync5(file, initial, { mode: 384 });
|
|
13978
14130
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
13979
14131
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
13980
|
-
const edited =
|
|
14132
|
+
const edited = readFileSync13(file, "utf8");
|
|
13981
14133
|
return edited === initial ? null : edited;
|
|
13982
14134
|
} finally {
|
|
13983
14135
|
rmSync3(dir, { recursive: true, force: true });
|
|
@@ -14914,6 +15066,10 @@ async function dispatchCli(argv, dependencies) {
|
|
|
14914
15066
|
await contextCommand(parsed, runtime);
|
|
14915
15067
|
return;
|
|
14916
15068
|
}
|
|
15069
|
+
if (command === "device") {
|
|
15070
|
+
await deviceCommand(parsed, runtime);
|
|
15071
|
+
return;
|
|
15072
|
+
}
|
|
14917
15073
|
if (command === "credentials") {
|
|
14918
15074
|
await credentialCommand(parsed, runtime);
|
|
14919
15075
|
return;
|
|
@@ -15109,4 +15265,4 @@ export {
|
|
|
15109
15265
|
isTerminalHostedSecurityStatus,
|
|
15110
15266
|
runCli
|
|
15111
15267
|
};
|
|
15112
|
-
//# sourceMappingURL=chunk-
|
|
15268
|
+
//# sourceMappingURL=chunk-DBZQIMES.js.map
|