@odla-ai/cli 0.38.2 → 0.39.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 +685 -298
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-HETCZVFB.js → chunk-PJ3RATDD.js} +560 -221
- package/dist/chunk-PJ3RATDD.js.map +1 -0
- package/dist/{cli-LGYDXY5R.js → cli-ALS5OZPB.js} +2 -2
- package/dist/index.cjs +594 -260
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/skills/odla/SKILL.md +50 -11
- package/skills/odla/references/agent-identity.md +94 -111
- package/dist/chunk-HETCZVFB.js.map +0 -1
- /package/dist/{cli-LGYDXY5R.js.map → cli-ALS5OZPB.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 process13 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 process10 from "process";
|
|
19
19
|
|
|
20
20
|
// src/handshake-approval.ts
|
|
21
21
|
import process3 from "process";
|
|
@@ -150,27 +150,10 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// src/cached-credential.ts
|
|
153
|
-
import { rmSync as
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
}
|
|
153
|
+
import { rmSync as rmSync3 } from "fs";
|
|
154
|
+
|
|
155
|
+
// src/auth-guidance.ts
|
|
156
|
+
import process7 from "process";
|
|
174
157
|
|
|
175
158
|
// src/device-session.ts
|
|
176
159
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -206,12 +189,71 @@ async function mintDeviceSession(platformUrl, credential2, doFetch) {
|
|
|
206
189
|
`device session failed: ${detail} (${response2.status})` + (revocable ? " \u2014 if this machine's enrollment was revoked or has expired, enroll it again in Studio" : "")
|
|
207
190
|
);
|
|
208
191
|
}
|
|
209
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
token: body.token,
|
|
194
|
+
expiresAt: body.expiresAt ?? Date.now(),
|
|
195
|
+
// Absent from a registry that predates rolling expiry and scoped devices.
|
|
196
|
+
// Left undefined rather than defaulted, so a caller can tell "the platform
|
|
197
|
+
// did not say" from "the platform said none".
|
|
198
|
+
...typeof body.deviceExpiresAt === "number" ? { deviceExpiresAt: body.deviceExpiresAt } : {},
|
|
199
|
+
...Array.isArray(body.appIds) ? { appIds: body.appIds } : {},
|
|
200
|
+
...Array.isArray(body.capabilities) ? { capabilities: body.capabilities } : {},
|
|
201
|
+
...Array.isArray(body.scopes) ? { scopes: body.scopes } : {}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/odla-home.ts
|
|
206
|
+
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, rmSync as rmSync2 } from "fs";
|
|
207
|
+
import { homedir as homedir2 } from "os";
|
|
208
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
209
|
+
import process6 from "process";
|
|
210
|
+
function odlaHome(env = process6.env) {
|
|
211
|
+
return env.ODLA_HOME ?? join3(env.HOME ?? homedir2(), ".odla");
|
|
212
|
+
}
|
|
213
|
+
function odlaHomePath(segments, env = process6.env) {
|
|
214
|
+
return join3(odlaHome(env), ...segments);
|
|
215
|
+
}
|
|
216
|
+
function identityFile(env) {
|
|
217
|
+
return odlaHomePath(["identity.json"], env);
|
|
218
|
+
}
|
|
219
|
+
function deviceSessionFile(env) {
|
|
220
|
+
return odlaHomePath(["session.json"], env);
|
|
221
|
+
}
|
|
222
|
+
function appTokenFile(appId, env) {
|
|
223
|
+
return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
|
|
224
|
+
}
|
|
225
|
+
function appCredentialsFile(appId, env) {
|
|
226
|
+
return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
|
|
227
|
+
}
|
|
228
|
+
function scopedTokenFile(env) {
|
|
229
|
+
return odlaHomePath(["admin-token.local.json"], env);
|
|
230
|
+
}
|
|
231
|
+
function pmContextFile(env) {
|
|
232
|
+
return odlaHomePath(["pm-context.json"], env);
|
|
233
|
+
}
|
|
234
|
+
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
235
|
+
if (!existsSync2(legacyPath) || legacyPath === machinePath) return false;
|
|
236
|
+
const superseded = existsSync2(machinePath);
|
|
237
|
+
if (!superseded) {
|
|
238
|
+
mkdirSync(dirname2(machinePath), { recursive: true });
|
|
239
|
+
copyFileSync(legacyPath, machinePath);
|
|
240
|
+
chmodSync(machinePath, 384);
|
|
241
|
+
}
|
|
242
|
+
rmSync2(legacyPath, { force: true });
|
|
243
|
+
out?.error(
|
|
244
|
+
superseded ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
|
|
245
|
+
);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
function safeSegment(value2) {
|
|
249
|
+
const clean4 = value2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
|
|
250
|
+
if (!clean4) throw new Error(`"${value2}" is not a usable app id`);
|
|
251
|
+
return clean4;
|
|
210
252
|
}
|
|
211
253
|
|
|
212
254
|
// src/local.ts
|
|
213
|
-
import { chmodSync, existsSync as
|
|
214
|
-
import { dirname as
|
|
255
|
+
import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
256
|
+
import { dirname as dirname3, isAbsolute, relative, resolve } from "path";
|
|
215
257
|
var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
|
|
216
258
|
function readJsonFile(path) {
|
|
217
259
|
try {
|
|
@@ -225,7 +267,7 @@ function writePrivateJson(path, value2) {
|
|
|
225
267
|
`);
|
|
226
268
|
}
|
|
227
269
|
function readCredentials(path) {
|
|
228
|
-
if (!
|
|
270
|
+
if (!existsSync3(path)) return null;
|
|
229
271
|
let value2;
|
|
230
272
|
try {
|
|
231
273
|
value2 = JSON.parse(readFileSync2(path, "utf8"));
|
|
@@ -259,7 +301,7 @@ function mergeCredential(current, update) {
|
|
|
259
301
|
}
|
|
260
302
|
function ensureGitignore(rootDir, localPaths = []) {
|
|
261
303
|
const path = resolve(rootDir, ".gitignore");
|
|
262
|
-
const existing =
|
|
304
|
+
const existing = existsSync3(path) ? readFileSync2(path, "utf8") : "";
|
|
263
305
|
const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
|
|
264
306
|
const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
|
|
265
307
|
const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
|
|
@@ -279,7 +321,7 @@ function o11yDevVars(cfg) {
|
|
|
279
321
|
function resolveWriteDevVarsTarget(cfg, requested) {
|
|
280
322
|
if (!requested) return null;
|
|
281
323
|
if (requested === true) return cfg.local.devVarsFile;
|
|
282
|
-
return resolve(
|
|
324
|
+
return resolve(dirname3(cfg.configPath), requested);
|
|
283
325
|
}
|
|
284
326
|
function writeDevVars(path, credentials, env, o11y) {
|
|
285
327
|
const entry = credentials.envs[env];
|
|
@@ -293,7 +335,7 @@ function writeDevVars(path, credentials, env, o11y) {
|
|
|
293
335
|
if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
|
|
294
336
|
if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
|
|
295
337
|
}
|
|
296
|
-
const existing =
|
|
338
|
+
const existing = existsSync3(path) ? readFileSync2(path, "utf8") : "";
|
|
297
339
|
const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
|
|
298
340
|
while (retained.at(-1) === "") retained.pop();
|
|
299
341
|
const prefix = retained.length ? `${retained.join("\n")}
|
|
@@ -319,10 +361,10 @@ function isManagedDevVar(line2) {
|
|
|
319
361
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
320
362
|
}
|
|
321
363
|
function writePrivateText(path, text3) {
|
|
322
|
-
|
|
364
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
323
365
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
324
366
|
writeFileSync(temporary, text3, { mode: 384 });
|
|
325
|
-
|
|
367
|
+
chmodSync2(temporary, 384);
|
|
326
368
|
renameSync(temporary, path);
|
|
327
369
|
}
|
|
328
370
|
function gitignoreEntry(rootDir, path) {
|
|
@@ -335,6 +377,94 @@ function displayPath(path, rootDir = process.cwd()) {
|
|
|
335
377
|
return rel && !rel.startsWith("..") ? rel : path;
|
|
336
378
|
}
|
|
337
379
|
|
|
380
|
+
// src/auth-guidance.ts
|
|
381
|
+
var ENROL_EVERYTHING = "npx odla-ai device enroll --all-apps --capability all --no-open --wait 600";
|
|
382
|
+
var ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
|
|
383
|
+
function machineAuthState(audience, env = process7.env) {
|
|
384
|
+
const device = readDeviceCredential(audience, env);
|
|
385
|
+
if (!device) return { enrolled: false };
|
|
386
|
+
const session = readJsonFile(deviceSessionFile(env));
|
|
387
|
+
const current = session?.platform === audience && session.deviceId === device.deviceId ? session : void 0;
|
|
388
|
+
return {
|
|
389
|
+
enrolled: true,
|
|
390
|
+
...device.name ? { deviceName: device.name } : {},
|
|
391
|
+
...current?.appIds ? { appIds: current.appIds } : {},
|
|
392
|
+
...current?.capabilities ? { capabilities: current.capabilities } : {},
|
|
393
|
+
...current?.scopes ? { scopes: current.scopes } : {},
|
|
394
|
+
...current?.deviceExpiresAt ? { lapsesAt: current.deviceExpiresAt } : {}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function scopeInterruptionNotice(scope, state2) {
|
|
398
|
+
const platformScope = scope.startsWith("platform:");
|
|
399
|
+
const reason = !state2.enrolled ? "this machine is not enrolled, so every privileged command needs its own browser approval" : `this machine is enrolled but its approval did not include "${scope}"`;
|
|
400
|
+
return [
|
|
401
|
+
`odla: ${reason}.`,
|
|
402
|
+
` Approve this one now, then end the interruptions with:`,
|
|
403
|
+
` ${platformScope ? ENROL_PLATFORM_WIDE : ENROL_EVERYTHING}`,
|
|
404
|
+
platformScope ? " A platform scope needs an administrator's approval; an app owner's cannot carry it." : " One approval, every app you own, and it rolls forward while you keep working."
|
|
405
|
+
].join("\n");
|
|
406
|
+
}
|
|
407
|
+
function lapseNotice(state2, now = Date.now()) {
|
|
408
|
+
if (!state2.enrolled || !state2.lapsesAt) return null;
|
|
409
|
+
const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
|
|
410
|
+
if (days < 0) return "this machine's enrollment has lapsed; the next command will ask for approval";
|
|
411
|
+
return `idle for ${days} more day${days === 1 ? "" : "s"} before this machine needs approving again (using it resets the clock)`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/cached-credential.ts
|
|
415
|
+
var noted = null;
|
|
416
|
+
function noteCachedCredential(tokenFile) {
|
|
417
|
+
noted = tokenFile;
|
|
418
|
+
}
|
|
419
|
+
function isCredentialRejection(error) {
|
|
420
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
421
|
+
return /\((401|403)\)\s*$/.test(message2.trim());
|
|
422
|
+
}
|
|
423
|
+
function explainRejectedCredential(error) {
|
|
424
|
+
const tokenFile = noted;
|
|
425
|
+
if (!tokenFile || !isCredentialRejection(error)) return null;
|
|
426
|
+
noted = null;
|
|
427
|
+
rmSync3(tokenFile, { force: true });
|
|
428
|
+
return [
|
|
429
|
+
"auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
|
|
430
|
+
" The usual cause is a newer sign-in for this account: collecting a handshake retires the",
|
|
431
|
+
" principal's other collected credentials, so a second machine supersedes this one.",
|
|
432
|
+
` Discarded ${tokenFile}; re-run this command to request a fresh approval.`,
|
|
433
|
+
` To stop needing one: ${ENROL_EVERYTHING}`
|
|
434
|
+
].join("\n");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/device-session-cache.ts
|
|
438
|
+
import process8 from "process";
|
|
439
|
+
var SKEW_MS = 6e4;
|
|
440
|
+
async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = process8.env) {
|
|
441
|
+
const path = deviceSessionFile(env);
|
|
442
|
+
const cached = readJsonFile(path);
|
|
443
|
+
if (cached?.token && cached.platform === audience && cached.deviceId === credential2.deviceId && (cached.expiresAt ?? 0) > Date.now() + SKEW_MS) return cached;
|
|
444
|
+
const minted = await mintDeviceSession(platformUrl, credential2, doFetch);
|
|
445
|
+
const session = {
|
|
446
|
+
...minted,
|
|
447
|
+
platform: audience,
|
|
448
|
+
...credential2.deviceId ? { deviceId: credential2.deviceId } : {}
|
|
449
|
+
};
|
|
450
|
+
writePrivateJson(path, session);
|
|
451
|
+
return session;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/machine-identity.ts
|
|
455
|
+
import process9 from "process";
|
|
456
|
+
function readMachineIdentity(audience, env = process9.env) {
|
|
457
|
+
const stored = readJsonFile(identityFile(env));
|
|
458
|
+
if (!stored || typeof stored.email !== "string" || !stored.email) return null;
|
|
459
|
+
return stored.platform === audience ? { platform: audience, email: stored.email } : null;
|
|
460
|
+
}
|
|
461
|
+
function rememberMachineIdentity(audience, email, env = process9.env) {
|
|
462
|
+
if (!email) return;
|
|
463
|
+
const existing = readMachineIdentity(audience, env);
|
|
464
|
+
if (existing?.email === email) return;
|
|
465
|
+
writePrivateJson(identityFile(env), { platform: audience, email });
|
|
466
|
+
}
|
|
467
|
+
|
|
338
468
|
// src/token.ts
|
|
339
469
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
340
470
|
const audience = platformAudience(cfg.platformUrl);
|
|
@@ -343,19 +473,19 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
343
473
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
344
474
|
if (!grantRequest.forceReview && !grantRequest.freshLogin) {
|
|
345
475
|
if (options.token) return options.token;
|
|
346
|
-
if (
|
|
347
|
-
const declared =
|
|
476
|
+
if (process10.env.ODLA_DEV_TOKEN) {
|
|
477
|
+
const declared = process10.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
348
478
|
if (declared) {
|
|
349
479
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
350
480
|
} else if (audience !== "https://odla.ai") {
|
|
351
481
|
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
352
482
|
}
|
|
353
|
-
return
|
|
483
|
+
return process10.env.ODLA_DEV_TOKEN;
|
|
354
484
|
}
|
|
355
485
|
const device = readDeviceCredential(audience);
|
|
356
486
|
if (device) {
|
|
357
|
-
const session = await
|
|
358
|
-
out.error(`auth: session
|
|
487
|
+
const session = await deviceSessionToken(cfg.platformUrl, audience, device, doFetch);
|
|
488
|
+
out.error(`auth: session held by this enrolled device (${displayPath(deviceCredentialPath(), cfg.rootDir)})`);
|
|
359
489
|
return session.token;
|
|
360
490
|
}
|
|
361
491
|
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
@@ -375,7 +505,10 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
375
505
|
doFetch,
|
|
376
506
|
out,
|
|
377
507
|
audience,
|
|
378
|
-
email: handshakeEmail(
|
|
508
|
+
email: handshakeEmail(
|
|
509
|
+
options.email,
|
|
510
|
+
(cached?.platform === audience ? cached.email : void 0) ?? readMachineIdentity(audience)?.email
|
|
511
|
+
),
|
|
379
512
|
pendingFile: handshakeFile(cfg),
|
|
380
513
|
grantIntent
|
|
381
514
|
};
|
|
@@ -392,6 +525,7 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
|
|
|
392
525
|
expiresAt
|
|
393
526
|
});
|
|
394
527
|
out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
528
|
+
rememberMachineIdentity(audience, ctx.email);
|
|
395
529
|
return token;
|
|
396
530
|
}
|
|
397
531
|
async function freshHandshake(ctx, waitMs) {
|
|
@@ -461,7 +595,7 @@ function stillPending(pending, email) {
|
|
|
461
595
|
);
|
|
462
596
|
}
|
|
463
597
|
function handshakeEmail(value2, cached) {
|
|
464
|
-
const email = (value2 ??
|
|
598
|
+
const email = (value2 ?? process10.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
465
599
|
if (/@users\.noreply\.github\.com$/i.test(email)) {
|
|
466
600
|
throw new Error(
|
|
467
601
|
`"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
|
|
@@ -492,12 +626,12 @@ function platformAudience(value2) {
|
|
|
492
626
|
}
|
|
493
627
|
|
|
494
628
|
// src/secret-input.ts
|
|
495
|
-
import
|
|
629
|
+
import process11 from "process";
|
|
496
630
|
var MAX_BYTES = 64 * 1024;
|
|
497
631
|
async function secretInputValue(options, kind = "credential") {
|
|
498
632
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
499
633
|
let value2;
|
|
500
|
-
if (options.fromEnv) value2 =
|
|
634
|
+
if (options.fromEnv) value2 = process11.env[options.fromEnv];
|
|
501
635
|
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
502
636
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
503
637
|
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
@@ -505,7 +639,7 @@ async function secretInputValue(options, kind = "credential") {
|
|
|
505
639
|
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
506
640
|
return value2;
|
|
507
641
|
}
|
|
508
|
-
async function readSecretStream(kind, stream =
|
|
642
|
+
async function readSecretStream(kind, stream = process11.stdin) {
|
|
509
643
|
let value2 = "";
|
|
510
644
|
for await (const chunk of stream) {
|
|
511
645
|
value2 += String(chunk);
|
|
@@ -515,9 +649,8 @@ async function readSecretStream(kind, stream = process7.stdin) {
|
|
|
515
649
|
}
|
|
516
650
|
|
|
517
651
|
// src/admin-ai-auth.ts
|
|
518
|
-
import {
|
|
519
|
-
import
|
|
520
|
-
import process8 from "process";
|
|
652
|
+
import { join as join4 } from "path";
|
|
653
|
+
import process12 from "process";
|
|
521
654
|
import { requestToken as requestToken2 } from "@odla-ai/db";
|
|
522
655
|
async function getScopedPlatformToken(options) {
|
|
523
656
|
return resolveAdminPlatformToken(options);
|
|
@@ -525,7 +658,7 @@ async function getScopedPlatformToken(options) {
|
|
|
525
658
|
async function resolveAdminPlatformToken(options) {
|
|
526
659
|
const audience = platformAudience(options.platform);
|
|
527
660
|
if (options.token) return options.token;
|
|
528
|
-
const fromEnv =
|
|
661
|
+
const fromEnv = process12.env.ODLA_ADMIN_TOKEN;
|
|
529
662
|
if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
|
|
530
663
|
return scopedToken(
|
|
531
664
|
audience,
|
|
@@ -537,7 +670,7 @@ async function resolveAdminPlatformToken(options) {
|
|
|
537
670
|
}
|
|
538
671
|
function audienceBoundEnvToken(token, platform) {
|
|
539
672
|
const audience = platformAudience(platform);
|
|
540
|
-
const declared =
|
|
673
|
+
const declared = process12.env.ODLA_ADMIN_TOKEN_AUDIENCE;
|
|
541
674
|
if (declared) {
|
|
542
675
|
if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
|
|
543
676
|
} else if (audience !== "https://odla.ai") {
|
|
@@ -564,15 +697,28 @@ var SCOPE_PURPOSE = {
|
|
|
564
697
|
};
|
|
565
698
|
async function scopedToken(platform, scope, options, doFetch, out) {
|
|
566
699
|
const audience = platformAudience(platform);
|
|
567
|
-
const rootDir = options.rootDir ??
|
|
568
|
-
const tokenFile = options.tokenFile ??
|
|
700
|
+
const rootDir = options.rootDir ?? process12.cwd();
|
|
701
|
+
const tokenFile = options.tokenFile ?? scopedTokenFile();
|
|
702
|
+
adoptRepoLocalCache(join4(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
|
|
703
|
+
const device = readDeviceCredential(audience);
|
|
704
|
+
if (device && options.cache !== false) {
|
|
705
|
+
const session = await deviceSessionToken(platform, audience, device, doFetch);
|
|
706
|
+
if (session.scopes?.includes(scope)) {
|
|
707
|
+
out.error(`auth: ${scope} held by this enrolled device`);
|
|
708
|
+
return session.token;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
569
711
|
const cache2 = options.cache === false ? null : readJsonFile(tokenFile);
|
|
570
712
|
const cached = cache2?.platform === audience ? cache2.tokens?.[scope] : void 0;
|
|
571
713
|
if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
|
|
572
714
|
out.error(`auth: using cached ${scope} grant (${tokenFile})`);
|
|
573
715
|
return cached.token;
|
|
574
716
|
}
|
|
575
|
-
|
|
717
|
+
out.error(scopeInterruptionNotice(scope, machineAuthState(audience)));
|
|
718
|
+
const email = handshakeEmail(
|
|
719
|
+
options.email,
|
|
720
|
+
(cache2?.platform === audience ? cache2.email : void 0) ?? readMachineIdentity(audience)?.email
|
|
721
|
+
);
|
|
576
722
|
const { token, expiresAt } = await requestToken2({
|
|
577
723
|
endpoint: audience,
|
|
578
724
|
email,
|
|
@@ -592,8 +738,8 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
592
738
|
if (options.cache !== false) {
|
|
593
739
|
const tokens = cache2?.platform === audience ? { ...cache2.tokens ?? {} } : {};
|
|
594
740
|
tokens[scope] = { token, expiresAt };
|
|
595
|
-
if (existsSync3(join3(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
|
|
596
741
|
writePrivateJson(tokenFile, { platform: audience, email, tokens });
|
|
742
|
+
rememberMachineIdentity(audience, email);
|
|
597
743
|
out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
|
|
598
744
|
} else {
|
|
599
745
|
out.error(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
|
|
@@ -769,7 +915,7 @@ function isRecord2(value2) {
|
|
|
769
915
|
|
|
770
916
|
// src/admin-ai.ts
|
|
771
917
|
async function adminAi(options) {
|
|
772
|
-
const platform = platformAudience(options.platform ??
|
|
918
|
+
const platform = platformAudience(options.platform ?? process13.env.ODLA_PLATFORM ?? "https://odla.ai");
|
|
773
919
|
const doFetch = options.fetch ?? fetch;
|
|
774
920
|
const out = options.stdout ?? console;
|
|
775
921
|
const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
|
|
@@ -1085,12 +1231,12 @@ async function adminSpend(parsed, ctx) {
|
|
|
1085
1231
|
|
|
1086
1232
|
// src/operator-context.ts
|
|
1087
1233
|
import { existsSync as existsSync6 } from "fs";
|
|
1088
|
-
import { join as
|
|
1089
|
-
import
|
|
1234
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
1235
|
+
import process15 from "process";
|
|
1090
1236
|
|
|
1091
1237
|
// src/config.ts
|
|
1092
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1093
|
-
import { dirname as
|
|
1238
|
+
import { existsSync as existsSync4, rmSync as rmSync4, readFileSync as readFileSync3 } from "fs";
|
|
1239
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1094
1240
|
import { pathToFileURL } from "url";
|
|
1095
1241
|
import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
|
|
1096
1242
|
|
|
@@ -1494,13 +1640,17 @@ var DEFAULT_ENVS = ["dev"];
|
|
|
1494
1640
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1495
1641
|
var configImportSerial = 0;
|
|
1496
1642
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1643
|
+
var stderr = { error: (message2) => {
|
|
1644
|
+
process.stderr.write(`${message2}
|
|
1645
|
+
`);
|
|
1646
|
+
} };
|
|
1497
1647
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1498
1648
|
const resolved = resolve2(configPath);
|
|
1499
1649
|
if (!existsSync4(resolved)) {
|
|
1500
1650
|
throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
|
|
1501
1651
|
}
|
|
1502
1652
|
const raw = await loadConfigModule(resolved);
|
|
1503
|
-
const rootDir =
|
|
1653
|
+
const rootDir = dirname4(resolved);
|
|
1504
1654
|
validateRawConfig(raw, resolved);
|
|
1505
1655
|
const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
|
|
1506
1656
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
@@ -1510,11 +1660,14 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1510
1660
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1511
1661
|
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1512
1662
|
const local = {
|
|
1513
|
-
tokenFile: resolve2(rootDir, raw.local
|
|
1514
|
-
credentialsFile: resolve2(rootDir, raw.local
|
|
1663
|
+
tokenFile: raw.local?.tokenFile ? resolve2(rootDir, raw.local.tokenFile) : appTokenFile(raw.app.id),
|
|
1664
|
+
credentialsFile: raw.local?.credentialsFile ? resolve2(rootDir, raw.local.credentialsFile) : appCredentialsFile(raw.app.id),
|
|
1515
1665
|
devVarsFile: resolve2(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
|
|
1516
1666
|
gitignore: raw.local?.gitignore ?? true
|
|
1517
1667
|
};
|
|
1668
|
+
adoptRepoLocalCache(resolve2(rootDir, ".odla/dev-token.json"), local.tokenFile, stderr);
|
|
1669
|
+
adoptRepoLocalCache(resolve2(rootDir, ".odla/credentials.local.json"), local.credentialsFile, stderr);
|
|
1670
|
+
rmSync4(resolve2(rootDir, ".odla/handshake.local.json"), { force: true });
|
|
1518
1671
|
return {
|
|
1519
1672
|
...raw,
|
|
1520
1673
|
configPath: resolved,
|
|
@@ -1623,17 +1776,17 @@ function unique3(values) {
|
|
|
1623
1776
|
|
|
1624
1777
|
// src/operator-profiles.ts
|
|
1625
1778
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1626
|
-
import { homedir as
|
|
1627
|
-
import { dirname as
|
|
1628
|
-
import
|
|
1779
|
+
import { homedir as homedir3 } from "os";
|
|
1780
|
+
import { dirname as dirname5, join as join5, resolve as resolve3 } from "path";
|
|
1781
|
+
import process14 from "process";
|
|
1629
1782
|
function operatorProfileFile() {
|
|
1630
1783
|
return resolve3(
|
|
1631
|
-
clean(
|
|
1784
|
+
clean(process14.env.ODLA_CONTEXT_FILE) ?? join5(homedir3(), ".odla", "contexts.json")
|
|
1632
1785
|
);
|
|
1633
1786
|
}
|
|
1634
1787
|
function resolveOperatorProfile(parsed) {
|
|
1635
1788
|
const fromFlag = clean(stringOpt(parsed.options.context));
|
|
1636
|
-
const fromEnvironment = clean(
|
|
1789
|
+
const fromEnvironment = clean(process14.env.ODLA_CONTEXT);
|
|
1637
1790
|
const name = fromFlag ?? fromEnvironment ?? null;
|
|
1638
1791
|
const file = operatorProfileFile();
|
|
1639
1792
|
if (!name) {
|
|
@@ -1673,10 +1826,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
1673
1826
|
return true;
|
|
1674
1827
|
}
|
|
1675
1828
|
function operatorCredentialFiles(selection) {
|
|
1676
|
-
const base = selection.name ?
|
|
1829
|
+
const base = selection.name ? join5(dirname5(selection.file), "profiles", selection.name) : join5(homedir3(), ".odla");
|
|
1677
1830
|
return {
|
|
1678
|
-
developer:
|
|
1679
|
-
scoped:
|
|
1831
|
+
developer: join5(base, "dev-token.json"),
|
|
1832
|
+
scoped: join5(base, "admin-token.local.json")
|
|
1680
1833
|
};
|
|
1681
1834
|
}
|
|
1682
1835
|
function assertOperatorName(value2, label) {
|
|
@@ -1762,13 +1915,13 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1762
1915
|
}
|
|
1763
1916
|
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
1764
1917
|
const platformFlag = clean2(stringOpt(parsed.options.platform));
|
|
1765
|
-
const platformEnvironment = clean2(
|
|
1918
|
+
const platformEnvironment = clean2(process15.env.ODLA_PLATFORM_URL);
|
|
1766
1919
|
const platformValue = platformAudience(
|
|
1767
1920
|
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
1768
1921
|
);
|
|
1769
1922
|
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
1770
1923
|
const appFlag = clean2(stringOpt(parsed.options.app));
|
|
1771
|
-
const appEnvironment = clean2(
|
|
1924
|
+
const appEnvironment = clean2(process15.env.ODLA_APP_ID);
|
|
1772
1925
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1773
1926
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1774
1927
|
if (appValue) {
|
|
@@ -1782,16 +1935,16 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1782
1935
|
);
|
|
1783
1936
|
}
|
|
1784
1937
|
const envFlag = clean2(stringOpt(parsed.options.env));
|
|
1785
|
-
const envEnvironment = clean2(
|
|
1938
|
+
const envEnvironment = clean2(process15.env.ODLA_ENV);
|
|
1786
1939
|
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
1787
1940
|
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
1788
1941
|
if (environmentValue) {
|
|
1789
1942
|
assertOperatorName(environmentValue, "environment");
|
|
1790
1943
|
}
|
|
1791
|
-
const rootDir = loaded?.rootDir ??
|
|
1944
|
+
const rootDir = loaded?.rootDir ?? process15.cwd();
|
|
1792
1945
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
1793
|
-
const tokenFile = clean2(
|
|
1794
|
-
const
|
|
1946
|
+
const tokenFile = clean2(process15.env.ODLA_DEV_TOKEN_FILE) ? resolve4(process15.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
1947
|
+
const scopedTokenFile2 = clean2(process15.env.ODLA_ADMIN_TOKEN_FILE) ? resolve4(process15.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join6(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
1795
1948
|
const cfg = loaded ? {
|
|
1796
1949
|
...loaded,
|
|
1797
1950
|
platformUrl: platformValue,
|
|
@@ -1813,8 +1966,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1813
1966
|
services: [],
|
|
1814
1967
|
local: {
|
|
1815
1968
|
tokenFile,
|
|
1816
|
-
credentialsFile:
|
|
1817
|
-
devVarsFile:
|
|
1969
|
+
credentialsFile: join6(rootDir, ".odla", "credentials.local.json"),
|
|
1970
|
+
devVarsFile: join6(rootDir, ".dev.vars"),
|
|
1818
1971
|
gitignore: true
|
|
1819
1972
|
}
|
|
1820
1973
|
};
|
|
@@ -1838,7 +1991,7 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1838
1991
|
},
|
|
1839
1992
|
credentials: {
|
|
1840
1993
|
developerTokenFile: tokenFile,
|
|
1841
|
-
scopedTokenFile
|
|
1994
|
+
scopedTokenFile: scopedTokenFile2
|
|
1842
1995
|
}
|
|
1843
1996
|
};
|
|
1844
1997
|
}
|
|
@@ -1936,7 +2089,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1936
2089
|
}
|
|
1937
2090
|
|
|
1938
2091
|
// src/auth-command.ts
|
|
1939
|
-
import
|
|
2092
|
+
import process16 from "process";
|
|
1940
2093
|
|
|
1941
2094
|
// src/whoami-command.ts
|
|
1942
2095
|
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
@@ -2072,6 +2225,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2072
2225
|
} else {
|
|
2073
2226
|
out.log("projects: (none \u2014 every pm and discuss call will be refused)");
|
|
2074
2227
|
}
|
|
2228
|
+
printMachineBlock(cfg.platformUrl, out);
|
|
2075
2229
|
if (!identity.admin) {
|
|
2076
2230
|
if (identity.scopes.includes("platform:runbook:write")) {
|
|
2077
2231
|
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
@@ -2082,6 +2236,21 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2082
2236
|
}
|
|
2083
2237
|
}
|
|
2084
2238
|
}
|
|
2239
|
+
function printMachineBlock(platformUrl, out) {
|
|
2240
|
+
const state2 = machineAuthState(platformAudience(platformUrl));
|
|
2241
|
+
if (!state2.enrolled) {
|
|
2242
|
+
out.log("\nmachine: not enrolled \u2014 every privileged command needs its own browser approval.");
|
|
2243
|
+
out.log(` End that with:
|
|
2244
|
+
${ENROL_EVERYTHING}`);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const reach = state2.appIds?.includes("*") ? "every app you own" : state2.appIds?.join(", ");
|
|
2248
|
+
out.log(`
|
|
2249
|
+
machine: enrolled${state2.deviceName ? ` as "${state2.deviceName}"` : ""}${reach ? ` for ${reach}` : ""}`);
|
|
2250
|
+
if (state2.scopes?.length) out.log(` carrying ${state2.scopes.join(", ")}`);
|
|
2251
|
+
const lapse = lapseNotice(state2);
|
|
2252
|
+
if (lapse) out.log(` ${lapse}`);
|
|
2253
|
+
}
|
|
2085
2254
|
|
|
2086
2255
|
// src/auth-command.ts
|
|
2087
2256
|
async function authCommand(parsed, deps = {}) {
|
|
@@ -2106,7 +2275,7 @@ async function authCommand(parsed, deps = {}) {
|
|
|
2106
2275
|
const { cfg } = context;
|
|
2107
2276
|
const out = deps.stdout ?? console;
|
|
2108
2277
|
const doFetch = deps.fetch ?? fetch;
|
|
2109
|
-
const email = stringOpt(parsed.options.email) ??
|
|
2278
|
+
const email = stringOpt(parsed.options.email) ?? process16.env.ODLA_USER_EMAIL?.trim();
|
|
2110
2279
|
if (!email) {
|
|
2111
2280
|
throw new Error(
|
|
2112
2281
|
"auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
|
|
@@ -2471,7 +2640,7 @@ async function appCommand(parsed, dependencies = {}) {
|
|
|
2471
2640
|
|
|
2472
2641
|
// src/brand-command.ts
|
|
2473
2642
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2474
|
-
import { dirname as
|
|
2643
|
+
import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
2475
2644
|
|
|
2476
2645
|
// src/brand-design-unpack.ts
|
|
2477
2646
|
import { gunzipSync } from "zlib";
|
|
@@ -2586,7 +2755,7 @@ async function readBundle(source, deps) {
|
|
|
2586
2755
|
async function writeAll(result, outDir) {
|
|
2587
2756
|
for (const file of result.files) {
|
|
2588
2757
|
const target = resolve5(outDir, file.path);
|
|
2589
|
-
await mkdir(
|
|
2758
|
+
await mkdir(dirname6(target), { recursive: true });
|
|
2590
2759
|
await writeFile(target, file.bytes);
|
|
2591
2760
|
}
|
|
2592
2761
|
}
|
|
@@ -3152,7 +3321,7 @@ import {
|
|
|
3152
3321
|
AppsError,
|
|
3153
3322
|
createAppsClient
|
|
3154
3323
|
} from "@odla-ai/apps";
|
|
3155
|
-
import { join as
|
|
3324
|
+
import { join as join7 } from "path";
|
|
3156
3325
|
|
|
3157
3326
|
// src/config-operation-error.ts
|
|
3158
3327
|
var ConfigOperationCommandError = class extends Error {
|
|
@@ -3577,7 +3746,7 @@ async function operationClient(cfg, options, purpose) {
|
|
|
3577
3746
|
platform: cfg.platformUrl,
|
|
3578
3747
|
scope: "app:config:write",
|
|
3579
3748
|
token: options.token,
|
|
3580
|
-
tokenFile:
|
|
3749
|
+
tokenFile: join7(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3581
3750
|
rootDir: cfg.rootDir,
|
|
3582
3751
|
email: options.email,
|
|
3583
3752
|
open: options.open,
|
|
@@ -3632,7 +3801,7 @@ function record4(value2) {
|
|
|
3632
3801
|
|
|
3633
3802
|
// src/config-reconcile-command.ts
|
|
3634
3803
|
import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
|
|
3635
|
-
import { join as
|
|
3804
|
+
import { join as join8 } from "path";
|
|
3636
3805
|
|
|
3637
3806
|
// src/config-reconcile.ts
|
|
3638
3807
|
import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
|
|
@@ -3928,7 +4097,7 @@ async function inspectConfig(options) {
|
|
|
3928
4097
|
platform: cfg.platformUrl,
|
|
3929
4098
|
scope: "app:config:read",
|
|
3930
4099
|
token: options.token,
|
|
3931
|
-
tokenFile:
|
|
4100
|
+
tokenFile: join8(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3932
4101
|
rootDir: cfg.rootDir,
|
|
3933
4102
|
email: options.email,
|
|
3934
4103
|
open: options.open,
|
|
@@ -4061,26 +4230,26 @@ function quoteArg2(value2) {
|
|
|
4061
4230
|
// src/doctor-checks.ts
|
|
4062
4231
|
import { execFileSync } from "child_process";
|
|
4063
4232
|
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
4064
|
-
import { join as
|
|
4233
|
+
import { join as join10, resolve as resolve6 } from "path";
|
|
4065
4234
|
|
|
4066
4235
|
// src/wrangler.ts
|
|
4067
4236
|
import { spawn as spawn2 } from "child_process";
|
|
4068
4237
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
4069
|
-
import { join as
|
|
4238
|
+
import { join as join9 } from "path";
|
|
4070
4239
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
4071
4240
|
const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
4072
4241
|
let stdout = "";
|
|
4073
|
-
let
|
|
4242
|
+
let stderr2 = "";
|
|
4074
4243
|
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
4075
|
-
child.stderr.on("data", (chunk) =>
|
|
4244
|
+
child.stderr.on("data", (chunk) => stderr2 += chunk.toString());
|
|
4076
4245
|
child.on("error", reject);
|
|
4077
|
-
child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
|
|
4246
|
+
child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr: stderr2 }));
|
|
4078
4247
|
child.stdin.end(opts?.input ?? "");
|
|
4079
4248
|
});
|
|
4080
4249
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
4081
4250
|
function findWranglerConfig(rootDir) {
|
|
4082
4251
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
4083
|
-
const path =
|
|
4252
|
+
const path = join9(rootDir, name);
|
|
4084
4253
|
if (existsSync7(path)) return path;
|
|
4085
4254
|
}
|
|
4086
4255
|
return null;
|
|
@@ -4231,21 +4400,21 @@ function wranglerWarnings(rootDir) {
|
|
|
4231
4400
|
const blocks = [{ label: "", block: config }];
|
|
4232
4401
|
const envs = config.env;
|
|
4233
4402
|
if (envs && typeof envs === "object") {
|
|
4234
|
-
for (const [name,
|
|
4235
|
-
if (
|
|
4403
|
+
for (const [name, block2] of Object.entries(envs)) {
|
|
4404
|
+
if (block2 && typeof block2 === "object") blocks.push({ label: `env.${name}.`, block: block2 });
|
|
4236
4405
|
}
|
|
4237
4406
|
}
|
|
4238
|
-
for (const { label, block } of blocks) {
|
|
4239
|
-
const assets =
|
|
4407
|
+
for (const { label, block: block2 } of blocks) {
|
|
4408
|
+
const assets = block2.assets;
|
|
4240
4409
|
if (assets?.directory) {
|
|
4241
4410
|
const dir = resolve6(rootDir, assets.directory);
|
|
4242
4411
|
if (dir === resolve6(rootDir)) {
|
|
4243
4412
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4244
|
-
} else if (existsSync8(
|
|
4413
|
+
} else if (existsSync8(join10(dir, "node_modules"))) {
|
|
4245
4414
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4246
4415
|
}
|
|
4247
4416
|
}
|
|
4248
|
-
const vars =
|
|
4417
|
+
const vars = block2.vars;
|
|
4249
4418
|
if (vars && typeof vars === "object") {
|
|
4250
4419
|
for (const [name, value2] of Object.entries(vars)) {
|
|
4251
4420
|
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
|
|
@@ -4306,7 +4475,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4306
4475
|
}
|
|
4307
4476
|
function readPackageJson(rootDir) {
|
|
4308
4477
|
try {
|
|
4309
|
-
return JSON.parse(readFileSync8(
|
|
4478
|
+
return JSON.parse(readFileSync8(join10(rootDir, "package.json"), "utf8"));
|
|
4310
4479
|
} catch {
|
|
4311
4480
|
return null;
|
|
4312
4481
|
}
|
|
@@ -4600,8 +4769,8 @@ function harnessOption(value2, flag) {
|
|
|
4600
4769
|
}
|
|
4601
4770
|
|
|
4602
4771
|
// src/init.ts
|
|
4603
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
4604
|
-
import { dirname as
|
|
4772
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4773
|
+
import { dirname as dirname7, resolve as resolve7 } from "path";
|
|
4605
4774
|
import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
|
|
4606
4775
|
function initProject(options) {
|
|
4607
4776
|
const out = options.stdout ?? console;
|
|
@@ -4623,9 +4792,9 @@ function initProject(options) {
|
|
|
4623
4792
|
}
|
|
4624
4793
|
}
|
|
4625
4794
|
const aiProvider = options.aiProvider;
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4795
|
+
mkdirSync3(dirname7(configPath), { recursive: true });
|
|
4796
|
+
mkdirSync3(resolve7(rootDir, "src/odla"), { recursive: true });
|
|
4797
|
+
mkdirSync3(resolve7(rootDir, ".odla"), { recursive: true });
|
|
4629
4798
|
writeFileSync2(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4630
4799
|
writeIfMissing(resolve7(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4631
4800
|
writeIfMissing(resolve7(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
@@ -4695,8 +4864,10 @@ ${calendar}
|
|
|
4695
4864
|
// prod: "https://example.com",
|
|
4696
4865
|
},
|
|
4697
4866
|
local: {
|
|
4698
|
-
|
|
4699
|
-
|
|
4867
|
+
// Credentials live in ~/.odla, per machine, so every worktree of this app
|
|
4868
|
+
// shares one approval instead of asking for its own. Pinning tokenFile or
|
|
4869
|
+
// credentialsFile here still works and still overrides that \u2014 it just puts
|
|
4870
|
+
// this checkout back on its own island.
|
|
4700
4871
|
devVarsFile: ".dev.vars",
|
|
4701
4872
|
},
|
|
4702
4873
|
};
|
|
@@ -4932,9 +5103,9 @@ function printReport(report5, out) {
|
|
|
4932
5103
|
}
|
|
4933
5104
|
|
|
4934
5105
|
// src/skill.ts
|
|
4935
|
-
import { existsSync as existsSync10, lstatSync, mkdirSync as
|
|
4936
|
-
import { homedir as
|
|
4937
|
-
import { dirname as
|
|
5106
|
+
import { existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5107
|
+
import { homedir as homedir4 } from "os";
|
|
5108
|
+
import { dirname as dirname8, isAbsolute as isAbsolute3, join as join11, relative as relative2, resolve as resolve8, sep } from "path";
|
|
4938
5109
|
import { fileURLToPath } from "url";
|
|
4939
5110
|
|
|
4940
5111
|
// src/skill-adapters.ts
|
|
@@ -5034,7 +5205,7 @@ function installSkill(options = {}) {
|
|
|
5034
5205
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
5035
5206
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
5036
5207
|
const root = resolve8(options.dir ?? process.cwd());
|
|
5037
|
-
const home = resolve8(options.homeDir ??
|
|
5208
|
+
const home = resolve8(options.homeDir ?? homedir4());
|
|
5038
5209
|
const plans = /* @__PURE__ */ new Map();
|
|
5039
5210
|
const targets = /* @__PURE__ */ new Map();
|
|
5040
5211
|
const rememberTarget = (harness, target) => {
|
|
@@ -5048,48 +5219,48 @@ function installSkill(options = {}) {
|
|
|
5048
5219
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
5049
5220
|
};
|
|
5050
5221
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
5051
|
-
for (const rel of files) plan(
|
|
5222
|
+
for (const rel of files) plan(join11(targetDir2, rel), readFileSync9(join11(sourceDir, rel), "utf8"), false, boundary);
|
|
5052
5223
|
};
|
|
5053
5224
|
let targetDir;
|
|
5054
5225
|
if (options.global) {
|
|
5055
|
-
const claudeRoot =
|
|
5056
|
-
const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
5226
|
+
const claudeRoot = join11(home, ".claude", "skills");
|
|
5227
|
+
const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ?? join11(home, ".codex"), "skills");
|
|
5057
5228
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
5058
5229
|
for (const harness of harnesses) {
|
|
5059
5230
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
5060
|
-
planSkillTree(skillRoot, harness === "claude" ? home :
|
|
5231
|
+
planSkillTree(skillRoot, harness === "claude" ? home : dirname8(dirname8(codexRoot)));
|
|
5061
5232
|
rememberTarget(harness, skillRoot);
|
|
5062
5233
|
}
|
|
5063
5234
|
} else {
|
|
5064
|
-
const sharedRoot =
|
|
5235
|
+
const sharedRoot = join11(root, ".agents", "skills");
|
|
5065
5236
|
planSkillTree(sharedRoot);
|
|
5066
|
-
const claudeRoot =
|
|
5237
|
+
const claudeRoot = join11(root, ".claude", "skills");
|
|
5067
5238
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
5068
5239
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5069
5240
|
if (harnesses.includes("claude")) {
|
|
5070
5241
|
for (const skill of skillNames(files)) {
|
|
5071
|
-
const canonical2 = readFileSync9(
|
|
5072
|
-
plan(
|
|
5242
|
+
const canonical2 = readFileSync9(join11(sourceDir, skill, "SKILL.md"), "utf8");
|
|
5243
|
+
plan(join11(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
5073
5244
|
}
|
|
5074
5245
|
rememberTarget("claude", claudeRoot);
|
|
5075
5246
|
}
|
|
5076
5247
|
if (harnesses.includes("cursor")) {
|
|
5077
|
-
const cursorRule =
|
|
5248
|
+
const cursorRule = join11(root, ".cursor", "rules", "odla.mdc");
|
|
5078
5249
|
plan(cursorRule, CURSOR_RULE);
|
|
5079
5250
|
rememberTarget("cursor", cursorRule);
|
|
5080
5251
|
}
|
|
5081
5252
|
if (harnesses.includes("agents")) {
|
|
5082
|
-
const agentsFile =
|
|
5253
|
+
const agentsFile = join11(root, "AGENTS.md");
|
|
5083
5254
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5084
5255
|
rememberTarget("agents", agentsFile);
|
|
5085
5256
|
}
|
|
5086
5257
|
if (harnesses.includes("copilot")) {
|
|
5087
|
-
const copilotFile =
|
|
5258
|
+
const copilotFile = join11(root, ".github", "copilot-instructions.md");
|
|
5088
5259
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5089
5260
|
rememberTarget("copilot", copilotFile);
|
|
5090
5261
|
}
|
|
5091
5262
|
if (harnesses.includes("gemini")) {
|
|
5092
|
-
const geminiFile =
|
|
5263
|
+
const geminiFile = join11(root, "GEMINI.md");
|
|
5093
5264
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
5094
5265
|
rememberTarget("gemini", geminiFile);
|
|
5095
5266
|
}
|
|
@@ -5125,7 +5296,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5125
5296
|
}
|
|
5126
5297
|
for (const file of plans.values()) {
|
|
5127
5298
|
if (!existsSync10(file.target) || readFileSync9(file.target, "utf8") !== file.content) {
|
|
5128
|
-
|
|
5299
|
+
mkdirSync4(dirname8(file.target), { recursive: true });
|
|
5129
5300
|
writeFileSync3(file.target, file.content);
|
|
5130
5301
|
}
|
|
5131
5302
|
}
|
|
@@ -5164,10 +5335,10 @@ function normalizeHarnesses(values, global) {
|
|
|
5164
5335
|
}
|
|
5165
5336
|
return expanded;
|
|
5166
5337
|
}
|
|
5167
|
-
function managedFileContent(path,
|
|
5338
|
+
function managedFileContent(path, block2, force, boundary) {
|
|
5168
5339
|
const symlink = symlinkedComponent(boundary, path);
|
|
5169
5340
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
5170
|
-
if (!existsSync10(path)) return `${
|
|
5341
|
+
if (!existsSync10(path)) return `${block2}
|
|
5171
5342
|
`;
|
|
5172
5343
|
const current = readFileSync9(path, "utf8");
|
|
5173
5344
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
@@ -5179,15 +5350,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
5179
5350
|
}
|
|
5180
5351
|
if (startAt === -1) {
|
|
5181
5352
|
const separator = current.length === 0 || current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
5182
|
-
return `${current}${separator}${
|
|
5353
|
+
return `${current}${separator}${block2}
|
|
5183
5354
|
`;
|
|
5184
5355
|
}
|
|
5185
5356
|
const afterEnd = endAt + end.length;
|
|
5186
5357
|
const existing = current.slice(startAt, afterEnd);
|
|
5187
|
-
if (existing !==
|
|
5358
|
+
if (existing !== block2 && !force) {
|
|
5188
5359
|
throw new Error(`odla-managed section modified locally in ${path}; re-run with --force to replace that section`);
|
|
5189
5360
|
}
|
|
5190
|
-
return `${current.slice(0, startAt)}${
|
|
5361
|
+
return `${current.slice(0, startAt)}${block2}${current.slice(afterEnd)}`;
|
|
5191
5362
|
}
|
|
5192
5363
|
function symlinkedComponent(boundary, target) {
|
|
5193
5364
|
const rel = relative2(boundary, target);
|
|
@@ -5196,7 +5367,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
5196
5367
|
}
|
|
5197
5368
|
let current = boundary;
|
|
5198
5369
|
for (const part of rel.split(sep).filter(Boolean)) {
|
|
5199
|
-
current =
|
|
5370
|
+
current = join11(current, part);
|
|
5200
5371
|
try {
|
|
5201
5372
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
5202
5373
|
} catch (error) {
|
|
@@ -5213,7 +5384,7 @@ function listFiles(dir) {
|
|
|
5213
5384
|
const results = [];
|
|
5214
5385
|
const walk = (current) => {
|
|
5215
5386
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
5216
|
-
const path =
|
|
5387
|
+
const path = join11(current, entry.name);
|
|
5217
5388
|
if (entry.isDirectory()) walk(path);
|
|
5218
5389
|
else results.push(relative2(dir, path));
|
|
5219
5390
|
}
|
|
@@ -5581,7 +5752,7 @@ var HARNESS_PROTOCOL_VERSION = 1;
|
|
|
5581
5752
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
5582
5753
|
import { constants } from "fs";
|
|
5583
5754
|
import { access } from "fs/promises";
|
|
5584
|
-
import { delimiter, join as
|
|
5755
|
+
import { delimiter, join as join12 } from "path";
|
|
5585
5756
|
import { getgid, getuid } from "process";
|
|
5586
5757
|
import { mkdir as mkdir2, mkdtemp, realpath, rm, writeFile as writeFile2 } from "fs/promises";
|
|
5587
5758
|
import { tmpdir } from "os";
|
|
@@ -5599,7 +5770,7 @@ function assertPinnedImage(image) {
|
|
|
5599
5770
|
async function commandAvailable(engine) {
|
|
5600
5771
|
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
5601
5772
|
try {
|
|
5602
|
-
await access(
|
|
5773
|
+
await access(join12(directory, engine), constants.X_OK);
|
|
5603
5774
|
return true;
|
|
5604
5775
|
} catch {
|
|
5605
5776
|
}
|
|
@@ -5678,7 +5849,7 @@ function allowedWorkspacePath(relativePath) {
|
|
|
5678
5849
|
async function gitOutput(cwd, args, maxBytes) {
|
|
5679
5850
|
const child = spawn22("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
5680
5851
|
const stdout = [];
|
|
5681
|
-
const
|
|
5852
|
+
const stderr2 = [];
|
|
5682
5853
|
let bytes = 0;
|
|
5683
5854
|
child.stdout.on("data", (chunk) => {
|
|
5684
5855
|
bytes += chunk.byteLength;
|
|
@@ -5686,20 +5857,20 @@ async function gitOutput(cwd, args, maxBytes) {
|
|
|
5686
5857
|
else stdout.push(chunk);
|
|
5687
5858
|
});
|
|
5688
5859
|
child.stderr.on("data", (chunk) => {
|
|
5689
|
-
if (
|
|
5860
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
5690
5861
|
});
|
|
5691
5862
|
const code = await new Promise((accept, reject) => {
|
|
5692
5863
|
child.once("error", reject);
|
|
5693
5864
|
child.once("exit", accept);
|
|
5694
5865
|
});
|
|
5695
5866
|
if (bytes > maxBytes) throw new Error(`git output exceeds ${maxBytes} bytes`);
|
|
5696
|
-
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(
|
|
5867
|
+
if (code !== 0) throw new Error(`git command failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
5697
5868
|
return Buffer.concat(stdout);
|
|
5698
5869
|
}
|
|
5699
5870
|
async function gitBlobs(cwd, entries, maxBytes) {
|
|
5700
5871
|
const child = spawn22("git", ["cat-file", "--batch"], { cwd, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
5701
5872
|
const stdout = [];
|
|
5702
|
-
const
|
|
5873
|
+
const stderr2 = [];
|
|
5703
5874
|
let bytes = 0;
|
|
5704
5875
|
child.stdout.on("data", (chunk) => {
|
|
5705
5876
|
bytes += chunk.byteLength;
|
|
@@ -5707,7 +5878,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
5707
5878
|
else stdout.push(chunk);
|
|
5708
5879
|
});
|
|
5709
5880
|
child.stderr.on("data", (chunk) => {
|
|
5710
|
-
if (
|
|
5881
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
5711
5882
|
});
|
|
5712
5883
|
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
5713
5884
|
`);
|
|
@@ -5716,7 +5887,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
5716
5887
|
child.once("exit", accept);
|
|
5717
5888
|
});
|
|
5718
5889
|
if (bytes > maxBytes + entries.length * 100) throw new Error(`Git tree exceeds ${maxBytes} bytes`);
|
|
5719
|
-
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(
|
|
5890
|
+
if (code !== 0) throw new Error(`git object read failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
5720
5891
|
const output = Buffer.concat(stdout);
|
|
5721
5892
|
const blobs = [];
|
|
5722
5893
|
let offset = 0;
|
|
@@ -5813,7 +5984,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5813
5984
|
shell: false
|
|
5814
5985
|
});
|
|
5815
5986
|
const stdout = [];
|
|
5816
|
-
const
|
|
5987
|
+
const stderr2 = [];
|
|
5817
5988
|
let outputBytes = 0;
|
|
5818
5989
|
child.stdout.on("data", (chunk) => {
|
|
5819
5990
|
outputBytes += chunk.byteLength;
|
|
@@ -5821,14 +5992,14 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5821
5992
|
else stdout.push(chunk);
|
|
5822
5993
|
});
|
|
5823
5994
|
child.stderr.on("data", (chunk) => {
|
|
5824
|
-
if (
|
|
5995
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
5825
5996
|
});
|
|
5826
5997
|
const code = await new Promise((accept, reject) => {
|
|
5827
5998
|
child.once("error", reject);
|
|
5828
5999
|
child.once("exit", accept);
|
|
5829
6000
|
});
|
|
5830
6001
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
5831
|
-
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(
|
|
6002
|
+
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
5832
6003
|
const paths = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
5833
6004
|
if (paths.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
5834
6005
|
const root = resolve22(sourceDir);
|
|
@@ -5873,7 +6044,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
5873
6044
|
"workspace"
|
|
5874
6045
|
], { cwd: root, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
5875
6046
|
const stdout = [];
|
|
5876
|
-
const
|
|
6047
|
+
const stderr2 = [];
|
|
5877
6048
|
let bytes = 0;
|
|
5878
6049
|
child.stdout.on("data", (chunk) => {
|
|
5879
6050
|
bytes += chunk.byteLength;
|
|
@@ -5881,7 +6052,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
5881
6052
|
else stdout.push(chunk);
|
|
5882
6053
|
});
|
|
5883
6054
|
child.stderr.on("data", (chunk) => {
|
|
5884
|
-
if (
|
|
6055
|
+
if (stderr2.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr2.push(chunk);
|
|
5885
6056
|
});
|
|
5886
6057
|
const code = await new Promise((accept, reject) => {
|
|
5887
6058
|
child.once("error", reject);
|
|
@@ -5889,7 +6060,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
5889
6060
|
});
|
|
5890
6061
|
if (bytes > maxBytes) throw new Error(`patch exceeds ${maxBytes} bytes`);
|
|
5891
6062
|
if (code !== 0 && code !== 1) {
|
|
5892
|
-
throw new Error(`git diff failed: ${Buffer.concat(
|
|
6063
|
+
throw new Error(`git diff failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
5893
6064
|
}
|
|
5894
6065
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("a/workspace/", "a/").replaceAll("b/baseline/", "b/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
5895
6066
|
}
|
|
@@ -6297,10 +6468,10 @@ import { randomUUID } from "crypto";
|
|
|
6297
6468
|
import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
|
|
6298
6469
|
import { createReadStream } from "fs";
|
|
6299
6470
|
import { lstat as lstat22 } from "fs/promises";
|
|
6300
|
-
import { join as
|
|
6471
|
+
import { join as join14 } from "path";
|
|
6301
6472
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
6302
6473
|
import { tmpdir as tmpdir3 } from "os";
|
|
6303
|
-
import { dirname as
|
|
6474
|
+
import { dirname as dirname10, join as join23, resolve as resolve32, sep as sep23 } from "path";
|
|
6304
6475
|
import {
|
|
6305
6476
|
keepRecentExchanges,
|
|
6306
6477
|
runAgent
|
|
@@ -6696,11 +6867,11 @@ function rollup(graph, kind, options = {}) {
|
|
|
6696
6867
|
}
|
|
6697
6868
|
|
|
6698
6869
|
// ../graph/dist/code/index.js
|
|
6699
|
-
function
|
|
6870
|
+
function dirname9(path) {
|
|
6700
6871
|
const at = path.lastIndexOf("/");
|
|
6701
6872
|
return at <= 0 ? "." : path.slice(0, at);
|
|
6702
6873
|
}
|
|
6703
|
-
function
|
|
6874
|
+
function join13(base, specifier) {
|
|
6704
6875
|
const parts = [];
|
|
6705
6876
|
const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
|
|
6706
6877
|
for (const segment of segments) {
|
|
@@ -6724,7 +6895,7 @@ var BARE_IMPORT = /^\s*import\s*["']([^"']+)["']/gm;
|
|
|
6724
6895
|
var isSourcePath = (path) => SOURCE.test(path);
|
|
6725
6896
|
function resolveImport(fromPath, specifier, known) {
|
|
6726
6897
|
if (!specifier.startsWith(".")) return null;
|
|
6727
|
-
const base =
|
|
6898
|
+
const base = join13(dirname9(fromPath), specifier);
|
|
6728
6899
|
const candidates = [
|
|
6729
6900
|
base,
|
|
6730
6901
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -7255,13 +7426,13 @@ function gitApply(cwd, patch2, check) {
|
|
|
7255
7426
|
stdio: ["pipe", "ignore", "pipe"],
|
|
7256
7427
|
env: { PATH: process.env.PATH ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" }
|
|
7257
7428
|
});
|
|
7258
|
-
let
|
|
7429
|
+
let stderr2 = "";
|
|
7259
7430
|
child.stderr.setEncoding("utf8");
|
|
7260
7431
|
child.stderr.on("data", (text3) => {
|
|
7261
|
-
if (
|
|
7432
|
+
if (stderr2.length < 4e3) stderr2 += text3.slice(0, 4e3);
|
|
7262
7433
|
});
|
|
7263
7434
|
child.once("error", reject);
|
|
7264
|
-
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2,
|
|
7435
|
+
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
|
|
7265
7436
|
child.stdin.end(patch2);
|
|
7266
7437
|
});
|
|
7267
7438
|
}
|
|
@@ -7374,7 +7545,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
7374
7545
|
const started = Date.now();
|
|
7375
7546
|
const child = spawn23(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
7376
7547
|
const stdout = [];
|
|
7377
|
-
const
|
|
7548
|
+
const stderr2 = [];
|
|
7378
7549
|
let bytes = 0;
|
|
7379
7550
|
let outputLimitExceeded = false;
|
|
7380
7551
|
let timedOut = false;
|
|
@@ -7395,7 +7566,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
7395
7566
|
else target.push(chunk);
|
|
7396
7567
|
};
|
|
7397
7568
|
child.stdout.on("data", collect(stdout));
|
|
7398
|
-
child.stderr.on("data", collect(
|
|
7569
|
+
child.stderr.on("data", collect(stderr2));
|
|
7399
7570
|
const abort = () => stop("abort");
|
|
7400
7571
|
signal?.addEventListener("abort", abort, { once: true });
|
|
7401
7572
|
if (signal?.aborted) abort();
|
|
@@ -7411,7 +7582,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
7411
7582
|
accept({
|
|
7412
7583
|
exitCode: code ?? 1,
|
|
7413
7584
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
7414
|
-
stderr: Buffer.concat(
|
|
7585
|
+
stderr: Buffer.concat(stderr2).toString("utf8"),
|
|
7415
7586
|
durationMs: Date.now() - started,
|
|
7416
7587
|
outputLimitExceeded,
|
|
7417
7588
|
timedOut
|
|
@@ -7527,7 +7698,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
7527
7698
|
const receipts = [];
|
|
7528
7699
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
7529
7700
|
try {
|
|
7530
|
-
const path =
|
|
7701
|
+
const path = join14(workspaceDir, artifact.path);
|
|
7531
7702
|
const info = await lstat22(path);
|
|
7532
7703
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
7533
7704
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
@@ -7563,11 +7734,11 @@ function checkedResult(result, maximumOutputBytes) {
|
|
|
7563
7734
|
}
|
|
7564
7735
|
function boundedLogs(result, maximum) {
|
|
7565
7736
|
const stdout = Buffer.from(result.stdout);
|
|
7566
|
-
const
|
|
7737
|
+
const stderr2 = Buffer.from(result.stderr);
|
|
7567
7738
|
const first = stdout.subarray(0, maximum);
|
|
7568
7739
|
return {
|
|
7569
7740
|
stdout: first.toString("utf8"),
|
|
7570
|
-
stderr:
|
|
7741
|
+
stderr: stderr2.subarray(0, Math.max(0, maximum - first.byteLength)).toString("utf8")
|
|
7571
7742
|
};
|
|
7572
7743
|
}
|
|
7573
7744
|
function digestPolicy(policy) {
|
|
@@ -7813,7 +7984,7 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
|
|
|
7813
7984
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
7814
7985
|
const target = resolve32(sourceDir, file.path);
|
|
7815
7986
|
if (!target.startsWith(`${resolve32(sourceDir)}${sep23}`)) throw new TypeError("Code source path escapes its root");
|
|
7816
|
-
await mkdir3(
|
|
7987
|
+
await mkdir3(dirname10(target), { recursive: true });
|
|
7817
7988
|
await writeFile3(target, file.content, { flag: "wx", mode: 420 });
|
|
7818
7989
|
}
|
|
7819
7990
|
for (const reference of snapshot.references ?? []) {
|
|
@@ -7828,7 +7999,7 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
|
|
|
7828
7999
|
if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
|
|
7829
8000
|
const target = resolve32(sourceDir, path);
|
|
7830
8001
|
if (!target.startsWith(`${resolve32(sourceDir)}${sep23}`)) throw new TypeError("Code reference path escapes its root");
|
|
7831
|
-
await mkdir3(
|
|
8002
|
+
await mkdir3(dirname10(target), { recursive: true });
|
|
7832
8003
|
await writeFile3(target, file.content, { flag: "wx", mode: 292 });
|
|
7833
8004
|
}
|
|
7834
8005
|
}
|
|
@@ -7855,7 +8026,7 @@ async function attachCodeRuntimeReferences(workspace, references) {
|
|
|
7855
8026
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
7856
8027
|
const target = resolve32(root, path);
|
|
7857
8028
|
if (!target.startsWith(`${resolve32(root)}${sep23}`)) throw new TypeError("Code reference path escapes its root");
|
|
7858
|
-
await mkdir3(
|
|
8029
|
+
await mkdir3(dirname10(target), { recursive: true });
|
|
7859
8030
|
await writeFile3(target, file.content, { flag: "wx", mode: 292 });
|
|
7860
8031
|
}
|
|
7861
8032
|
}
|
|
@@ -9677,7 +9848,8 @@ async function codeConnect(options) {
|
|
|
9677
9848
|
);
|
|
9678
9849
|
try {
|
|
9679
9850
|
const descriptor2 = localSource.descriptor;
|
|
9680
|
-
const
|
|
9851
|
+
const device = options.getToken ? null : readDeviceCredential(platform);
|
|
9852
|
+
const authorization = device ? (await mintDeviceSession(platform, device, doFetch)).token : await (options.getToken ?? getScopedPlatformToken)({
|
|
9681
9853
|
platform,
|
|
9682
9854
|
scope: "app:code:host:connect",
|
|
9683
9855
|
email: options.email,
|
|
@@ -9691,7 +9863,7 @@ async function codeConnect(options) {
|
|
|
9691
9863
|
const target = appId ? { appId } : { repository };
|
|
9692
9864
|
const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
|
|
9693
9865
|
method: "POST",
|
|
9694
|
-
headers: { authorization: `Bearer ${
|
|
9866
|
+
headers: { authorization: `Bearer ${authorization}`, "content-type": "application/json" },
|
|
9695
9867
|
body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
|
|
9696
9868
|
redirect: "error",
|
|
9697
9869
|
signal: options.signal
|
|
@@ -10072,13 +10244,13 @@ async function codeCommand(parsed, dependencies) {
|
|
|
10072
10244
|
}
|
|
10073
10245
|
|
|
10074
10246
|
// src/operator-credentials.ts
|
|
10075
|
-
import
|
|
10247
|
+
import process17 from "process";
|
|
10076
10248
|
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
10077
10249
|
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
10078
10250
|
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
10079
10251
|
const source = clean3(
|
|
10080
10252
|
stringOpt(parsed.options.token)
|
|
10081
|
-
) ? "flag" : clean3(
|
|
10253
|
+
) ? "flag" : clean3(process17.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
10082
10254
|
return {
|
|
10083
10255
|
source,
|
|
10084
10256
|
cacheFile: context.cfg.local.tokenFile,
|
|
@@ -10272,6 +10444,27 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
10272
10444
|
}
|
|
10273
10445
|
|
|
10274
10446
|
// src/help-usage.ts
|
|
10447
|
+
var AUTH_SECTION = `
|
|
10448
|
+
Enrol this machine once, then stop asking:
|
|
10449
|
+
npx odla-ai device enroll --all-apps --capability all --no-open --wait 600
|
|
10450
|
+
One browser approval. Afterwards every worktree on this machine mints its
|
|
10451
|
+
own short-lived credentials with nobody's attention, for every app you
|
|
10452
|
+
own \u2014 including apps you create later. The window rolls forward each time
|
|
10453
|
+
you use it, so continuous work never interrupts anyone; only a real gap
|
|
10454
|
+
does. Give the human the printed /studio?code= URL, keep the process
|
|
10455
|
+
alive, and wait on it.
|
|
10456
|
+
|
|
10457
|
+
npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
|
|
10458
|
+
The same thing across all of odla, for weeks. Needs a platform
|
|
10459
|
+
administrator's approval \u2014 an app owner's cannot carry platform scopes.
|
|
10460
|
+
|
|
10461
|
+
npx odla-ai whoami what this machine holds and when it lapses
|
|
10462
|
+
npx odla-ai device list every machine you have enrolled
|
|
10463
|
+
|
|
10464
|
+
Enrollment is the only human decision here. Revoking a machine, purging an app,
|
|
10465
|
+
transferring ownership, and rotating credentials still need a signed-in human in
|
|
10466
|
+
Studio, and no machine credential can do them however wide its approval was.
|
|
10467
|
+
`;
|
|
10275
10468
|
var USAGE_SECTION = `
|
|
10276
10469
|
Start here:
|
|
10277
10470
|
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
@@ -10310,7 +10503,7 @@ Usage:
|
|
|
10310
10503
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
10311
10504
|
odla-ai pm project list [--app <product-id>] [--status <s>] [--json]
|
|
10312
10505
|
odla-ai pm project add --app <product-id> --name <name> [--description <text>] [--json]
|
|
10313
|
-
odla-ai pm project use <project-id> [--json] [saved
|
|
10506
|
+
odla-ai pm project use <project-id> [--json] [saved for this app, on this machine]
|
|
10314
10507
|
odla-ai pm goal list [--app <id>] [--project <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
10315
10508
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
10316
10509
|
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
@@ -10402,7 +10595,8 @@ Usage:
|
|
|
10402
10595
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
10403
10596
|
odla-ai security run [target] --self --ack-redacted-source
|
|
10404
10597
|
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]
|
|
10405
|
-
odla-ai device enroll [--app <id>[,<id>...]] [--
|
|
10598
|
+
odla-ai device enroll [--all-apps|--app <id>[,<id>...]] [--capability all|<c>[,<c>...]] [--platform-wide]
|
|
10599
|
+
[--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
|
|
10406
10600
|
odla-ai device list [--email <odla-account>] [--json]
|
|
10407
10601
|
odla-ai device revoke <device-id> [--email <odla-account>] [--json]
|
|
10408
10602
|
odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
|
|
@@ -10416,8 +10610,11 @@ Usage:
|
|
|
10416
10610
|
|
|
10417
10611
|
// src/help.ts
|
|
10418
10612
|
function printHelp(output = console) {
|
|
10419
|
-
output.log(
|
|
10420
|
-
|
|
10613
|
+
output.log(helpText());
|
|
10614
|
+
}
|
|
10615
|
+
function helpText() {
|
|
10616
|
+
return `odla-ai
|
|
10617
|
+
${AUTH_SECTION}${USAGE_SECTION}
|
|
10421
10618
|
Commands:
|
|
10422
10619
|
auth Start a fresh, exact-project agent authorization for human review.
|
|
10423
10620
|
The email is the signed-in odla account, never git or GitHub
|
|
@@ -10490,8 +10687,9 @@ Commands:
|
|
|
10490
10687
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
10491
10688
|
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
10492
10689
|
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
10493
|
-
"pm project list|add|use"
|
|
10494
|
-
|
|
10690
|
+
"pm project list|add|use" selects a project for this app on this
|
|
10691
|
+
machine \u2014 every worktree shares it \u2014 or pass --app/--project
|
|
10692
|
+
explicitly. Same device-grant auth as "app".
|
|
10495
10693
|
Status changes and comments post to each item's @odla-ai/chat
|
|
10496
10694
|
discussion thread.
|
|
10497
10695
|
NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
|
|
@@ -10520,9 +10718,20 @@ Commands:
|
|
|
10520
10718
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10521
10719
|
explicit unknowns, and next actions through a read-only grant.
|
|
10522
10720
|
device Enrol THIS machine once, then stop asking. A human approves the
|
|
10523
|
-
enrollment in the browser; from then on
|
|
10524
|
-
own short-lived credentials
|
|
10525
|
-
attention, until the device
|
|
10721
|
+
enrollment in the browser; from then on EVERY worktree on this
|
|
10722
|
+
machine mints its own short-lived credentials with nobody's
|
|
10723
|
+
attention, until the device is revoked or goes unused.
|
|
10724
|
+
"--all-apps" covers every app you own, now and later, so creating
|
|
10725
|
+
an app costs no new approval. "--capability all" takes everything
|
|
10726
|
+
that approval is allowed to carry, so a capability you did not
|
|
10727
|
+
think to name is not a 403 next week. "--platform-wide" is the
|
|
10728
|
+
administrator's version, across all of odla.
|
|
10729
|
+
The expiry is a GAP, not a clock: each use rolls it forward, so
|
|
10730
|
+
only going quiet brings a human back into the loop \u2014 which is
|
|
10731
|
+
where anything that changed can be explained.
|
|
10732
|
+
"device list" shows what each machine holds and when it lapses;
|
|
10733
|
+
revoking one takes down every credential it ever minted, and is
|
|
10734
|
+
deliberately a signed-in human's decision in Studio.
|
|
10526
10735
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
10527
10736
|
"provision --live --yes" initializes only the live instance of
|
|
10528
10737
|
an existing sandbox app and enables every configured service;
|
|
@@ -10592,10 +10801,12 @@ Safety:
|
|
|
10592
10801
|
release. A confirmed stale client stops with a safe npx rerun command; a
|
|
10593
10802
|
workspace-linked client also identifies the worktree that must be updated.
|
|
10594
10803
|
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
10595
|
-
odla.config.mjs may select the app explicitly but is not required.
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
"code connect"
|
|
10804
|
+
odla.config.mjs may select the app explicitly but is not required. With an
|
|
10805
|
+
enrolled code.session device, the Studio repository selection authorizes the
|
|
10806
|
+
host's first connection and reconnects without another human approval. Without
|
|
10807
|
+
an enrolled device, "code connect" falls back to the reviewed handshake. Host
|
|
10808
|
+
credential hashes live in odla-ai/db; the credential itself is never written
|
|
10809
|
+
under .odla/, exists only in the foreground process, and rotates on reconnect.
|
|
10599
10810
|
"code repository bind" takes owner/name and resolves the two GitHub integers the
|
|
10600
10811
|
bind route wants across every installation you have, refusing an ambiguous match
|
|
10601
10812
|
rather than choosing one \u2014 the same repository name under two organizations is
|
|
@@ -10616,7 +10827,46 @@ Safety:
|
|
|
10616
10827
|
Run security plan first to inspect the admin-selected providers, models,
|
|
10617
10828
|
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
10618
10829
|
and digest that binds consent to that exact plan.
|
|
10619
|
-
|
|
10830
|
+
`;
|
|
10831
|
+
}
|
|
10832
|
+
|
|
10833
|
+
// src/help-command.ts
|
|
10834
|
+
function printCommandHelp(command, output = console) {
|
|
10835
|
+
const lines = helpText().split("\n");
|
|
10836
|
+
const usage = allBlocks(lines, new RegExp(`^ odla-ai ${escapeRe(command)}(\\s|$)`));
|
|
10837
|
+
const prose = block(lines, (line2) => new RegExp(`^ ${escapeRe(command)}\\s\\s+\\S`).test(line2));
|
|
10838
|
+
if (usage.length === 0 && prose.length === 0) {
|
|
10839
|
+
output.log(`odla-ai: no command "${command}". Run "odla-ai help" for all of them.`);
|
|
10840
|
+
return;
|
|
10841
|
+
}
|
|
10842
|
+
output.log([
|
|
10843
|
+
...prose.length ? [prose.join("\n"), ""] : [],
|
|
10844
|
+
...usage.length ? ["Usage:", ...usage, ""] : [],
|
|
10845
|
+
AUTH_SECTION.trimEnd()
|
|
10846
|
+
].join("\n"));
|
|
10847
|
+
}
|
|
10848
|
+
function allBlocks(lines, pattern) {
|
|
10849
|
+
const out = [];
|
|
10850
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10851
|
+
if (!pattern.test(lines[i])) continue;
|
|
10852
|
+
out.push(...block(lines.slice(i), (line2) => line2 === lines[i]));
|
|
10853
|
+
}
|
|
10854
|
+
return out;
|
|
10855
|
+
}
|
|
10856
|
+
function block(lines, starts) {
|
|
10857
|
+
const first = lines.findIndex(starts);
|
|
10858
|
+
if (first === -1) return [];
|
|
10859
|
+
const indent = lines[first].length - lines[first].trimStart().length;
|
|
10860
|
+
const out = [lines[first]];
|
|
10861
|
+
for (const line2 of lines.slice(first + 1)) {
|
|
10862
|
+
if (!line2.trim()) break;
|
|
10863
|
+
if (line2.length - line2.trimStart().length <= indent) break;
|
|
10864
|
+
out.push(line2);
|
|
10865
|
+
}
|
|
10866
|
+
return out;
|
|
10867
|
+
}
|
|
10868
|
+
function escapeRe(value2) {
|
|
10869
|
+
return value2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10620
10870
|
}
|
|
10621
10871
|
|
|
10622
10872
|
// src/discuss-principals.ts
|
|
@@ -11773,14 +12023,36 @@ async function pmWatch(ctx, parsed) {
|
|
|
11773
12023
|
}
|
|
11774
12024
|
|
|
11775
12025
|
// src/pm-project-context.ts
|
|
12026
|
+
import { existsSync as existsSync12, rmSync as rmSync5 } from "fs";
|
|
11776
12027
|
import { resolve as resolve12 } from "path";
|
|
11777
|
-
var pmProjectContextFile = (
|
|
12028
|
+
var pmProjectContextFile = () => pmContextFile();
|
|
11778
12029
|
function readPmProjectContext(rootDir) {
|
|
11779
|
-
|
|
11780
|
-
|
|
12030
|
+
adoptLegacySelection(rootDir);
|
|
12031
|
+
const entries = Object.values(readSelections()).filter(isSelection);
|
|
12032
|
+
return entries.sort((a, b) => b.selectedAt.localeCompare(a.selectedAt))[0] ?? null;
|
|
11781
12033
|
}
|
|
11782
12034
|
function writePmProjectContext(rootDir, value2) {
|
|
11783
|
-
|
|
12035
|
+
adoptLegacySelection(rootDir);
|
|
12036
|
+
writePrivateJson(pmProjectContextFile(), {
|
|
12037
|
+
...readSelections(),
|
|
12038
|
+
[value2.appId]: { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12039
|
+
});
|
|
12040
|
+
}
|
|
12041
|
+
function adoptLegacySelection(rootDir) {
|
|
12042
|
+
const legacy = resolve12(rootDir, ".odla", "pm-project.local.json");
|
|
12043
|
+
if (!existsSync12(legacy)) return;
|
|
12044
|
+
const previous = readJsonFile(legacy);
|
|
12045
|
+
rmSync5(legacy, { force: true });
|
|
12046
|
+
if (!isSelection(previous)) return;
|
|
12047
|
+
const selections = readSelections();
|
|
12048
|
+
if (selections[previous.appId]) return;
|
|
12049
|
+
writePrivateJson(pmProjectContextFile(), { ...selections, [previous.appId]: previous });
|
|
12050
|
+
}
|
|
12051
|
+
function readSelections() {
|
|
12052
|
+
return readJsonFile(pmProjectContextFile()) ?? {};
|
|
12053
|
+
}
|
|
12054
|
+
function isSelection(value2) {
|
|
12055
|
+
return !!value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" && typeof value2.selectedAt === "string";
|
|
11784
12056
|
}
|
|
11785
12057
|
|
|
11786
12058
|
// src/pm-project-actions.ts
|
|
@@ -12805,7 +13077,7 @@ function percent(value2) {
|
|
|
12805
13077
|
// src/provision.ts
|
|
12806
13078
|
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
|
|
12807
13079
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
12808
|
-
import
|
|
13080
|
+
import process18 from "process";
|
|
12809
13081
|
|
|
12810
13082
|
// src/integration-provision.ts
|
|
12811
13083
|
import { uuidv7 } from "@odla-ai/db";
|
|
@@ -13243,7 +13515,7 @@ async function provision(options) {
|
|
|
13243
13515
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
13244
13516
|
}
|
|
13245
13517
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
13246
|
-
const key =
|
|
13518
|
+
const key = process18.env[cfg.ai.keyEnv];
|
|
13247
13519
|
if (key) {
|
|
13248
13520
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
13249
13521
|
await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -13285,7 +13557,7 @@ async function provision(options) {
|
|
|
13285
13557
|
|
|
13286
13558
|
// src/record.ts
|
|
13287
13559
|
import { appendFileSync } from "fs";
|
|
13288
|
-
import
|
|
13560
|
+
import process19 from "process";
|
|
13289
13561
|
|
|
13290
13562
|
// src/surface.ts
|
|
13291
13563
|
var PM_ACTIONS = {
|
|
@@ -13467,7 +13739,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
13467
13739
|
|
|
13468
13740
|
// src/record.ts
|
|
13469
13741
|
function recordInvocation(parsed) {
|
|
13470
|
-
const file =
|
|
13742
|
+
const file = process19.env.ODLA_CLI_RECORD;
|
|
13471
13743
|
if (!file) return;
|
|
13472
13744
|
try {
|
|
13473
13745
|
const entry = {
|
|
@@ -13504,6 +13776,14 @@ function renderAdvisories(out, advisories, env = process.env) {
|
|
|
13504
13776
|
}
|
|
13505
13777
|
}
|
|
13506
13778
|
|
|
13779
|
+
// src/device-command.ts
|
|
13780
|
+
import {
|
|
13781
|
+
ADMIN_DEVICE_SCOPES,
|
|
13782
|
+
ALL_OWNED_APPS,
|
|
13783
|
+
OPTIONAL_AGENT_PROJECT_CAPABILITIES,
|
|
13784
|
+
OWNER_DEVICE_SCOPES
|
|
13785
|
+
} from "@odla-ai/db";
|
|
13786
|
+
|
|
13507
13787
|
// src/device-ttl.ts
|
|
13508
13788
|
var OWNER_DEVICE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
13509
13789
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -13523,10 +13803,26 @@ function parseDeviceTtl(raw) {
|
|
|
13523
13803
|
}
|
|
13524
13804
|
|
|
13525
13805
|
// src/device-command.ts
|
|
13526
|
-
import { chmodSync as
|
|
13527
|
-
import { dirname as
|
|
13528
|
-
import
|
|
13806
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
13807
|
+
import { dirname as dirname11 } from "path";
|
|
13808
|
+
import process20 from "process";
|
|
13529
13809
|
async function deviceCommand(parsed, deps) {
|
|
13810
|
+
assertArgs(parsed, [
|
|
13811
|
+
"app",
|
|
13812
|
+
"all-apps",
|
|
13813
|
+
"platform-wide",
|
|
13814
|
+
"name",
|
|
13815
|
+
"capability",
|
|
13816
|
+
"device-ttl",
|
|
13817
|
+
"email",
|
|
13818
|
+
"open",
|
|
13819
|
+
"json",
|
|
13820
|
+
"config",
|
|
13821
|
+
"token",
|
|
13822
|
+
"context",
|
|
13823
|
+
"platform",
|
|
13824
|
+
"wait"
|
|
13825
|
+
], 3);
|
|
13530
13826
|
const action2 = parsed.positionals[1] ?? "";
|
|
13531
13827
|
const out = deps.stdout ?? console;
|
|
13532
13828
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -13539,10 +13835,12 @@ async function deviceCommand(parsed, deps) {
|
|
|
13539
13835
|
}
|
|
13540
13836
|
async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
13541
13837
|
const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
|
|
13542
|
-
const
|
|
13543
|
-
|
|
13838
|
+
const platformWide = parsed.options["platform-wide"] === true;
|
|
13839
|
+
const apps = platformWide || parsed.options["all-apps"] === true ? [ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
|
|
13840
|
+
if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
|
|
13544
13841
|
const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
|
|
13545
|
-
const extended = deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
13842
|
+
const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
|
|
13843
|
+
const { capabilities, scopes } = requestedEnvelope(parsed, platformWide);
|
|
13546
13844
|
const token = await scopedToken2(
|
|
13547
13845
|
parsed,
|
|
13548
13846
|
deps,
|
|
@@ -13557,9 +13855,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13557
13855
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
13558
13856
|
body: JSON.stringify({
|
|
13559
13857
|
name,
|
|
13560
|
-
platform:
|
|
13858
|
+
platform: process20.platform,
|
|
13561
13859
|
appIds: apps,
|
|
13562
|
-
...
|
|
13860
|
+
...capabilities ? { capabilities } : {},
|
|
13861
|
+
...scopes ? { scopes } : {},
|
|
13563
13862
|
...deviceTtlMs === void 0 ? {} : { deviceTtlMs }
|
|
13564
13863
|
})
|
|
13565
13864
|
});
|
|
@@ -13568,20 +13867,53 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13568
13867
|
throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
|
|
13569
13868
|
}
|
|
13570
13869
|
const path = deviceCredentialPath();
|
|
13571
|
-
|
|
13870
|
+
mkdirSync5(dirname11(path), { recursive: true });
|
|
13572
13871
|
writeFileSync4(path, JSON.stringify({
|
|
13573
13872
|
token: body.token,
|
|
13574
13873
|
platform: cfg.platformUrl.replace(/\/$/, ""),
|
|
13575
13874
|
deviceId: body.device.deviceId,
|
|
13576
13875
|
name
|
|
13577
13876
|
}, null, 2));
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13877
|
+
chmodSync3(path, 384);
|
|
13878
|
+
rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
|
|
13879
|
+
const reach = body.device.appIds.includes(ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
|
|
13880
|
+
out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
|
|
13881
|
+
out.error(
|
|
13882
|
+
"device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
|
|
13883
|
+
);
|
|
13884
|
+
out.error(
|
|
13885
|
+
`device: and the clock resets each time you use it. Going quiet for ${describeWindow(body.device.expiresAt)} is what ends it.`
|
|
13886
|
+
);
|
|
13581
13887
|
if (json) {
|
|
13582
|
-
out.log(JSON.stringify({
|
|
13888
|
+
out.log(JSON.stringify({
|
|
13889
|
+
deviceId: body.device.deviceId,
|
|
13890
|
+
name,
|
|
13891
|
+
appIds: body.device.appIds,
|
|
13892
|
+
capabilities: body.device.capabilities ?? [],
|
|
13893
|
+
scopes: body.device.scopes ?? [],
|
|
13894
|
+
expiresAt: body.device.expiresAt,
|
|
13895
|
+
hardExpiresAt: body.device.hardExpiresAt ?? null
|
|
13896
|
+
}, null, 2));
|
|
13583
13897
|
}
|
|
13584
13898
|
}
|
|
13899
|
+
function requestedEnvelope(parsed, platformWide) {
|
|
13900
|
+
const raw = stringOpt(parsed.options.capability);
|
|
13901
|
+
const everything = platformWide || raw?.trim().toLowerCase() === "all";
|
|
13902
|
+
if (everything) {
|
|
13903
|
+
return {
|
|
13904
|
+
capabilities: [...OPTIONAL_AGENT_PROJECT_CAPABILITIES],
|
|
13905
|
+
scopes: platformWide ? [...ADMIN_DEVICE_SCOPES] : [...OWNER_DEVICE_SCOPES]
|
|
13906
|
+
};
|
|
13907
|
+
}
|
|
13908
|
+
const named = raw?.split(",").map((c) => c.trim()).filter(Boolean);
|
|
13909
|
+
return named?.length ? { capabilities: named } : {};
|
|
13910
|
+
}
|
|
13911
|
+
function describeWindow(expiresAt, now = Date.now()) {
|
|
13912
|
+
const days = Math.max(1, Math.round((expiresAt - now) / (24 * 60 * 60 * 1e3)));
|
|
13913
|
+
if (days >= 365) return `${Math.round(days / 365)} year${days >= 730 ? "s" : ""}`;
|
|
13914
|
+
if (days % 7 === 0) return `${days / 7} week${days > 7 ? "s" : ""}`;
|
|
13915
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
13916
|
+
}
|
|
13585
13917
|
async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
13586
13918
|
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device list)");
|
|
13587
13919
|
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
|
|
@@ -13595,12 +13927,17 @@ async function list2(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13595
13927
|
if (body.devices.length === 0) return out.log("no enrolled devices");
|
|
13596
13928
|
for (const device of body.devices) {
|
|
13597
13929
|
const state2 = device.revokedAt ? "revoked" : device.expiresAt <= Date.now() ? "expired" : "active";
|
|
13598
|
-
|
|
13930
|
+
const reach = device.appIds.includes("*") ? "every app you own" : device.appIds.join(", ");
|
|
13931
|
+
const gap = state2 === "active" ? ` idle ${describeWindow(device.expiresAt)} left` : "";
|
|
13932
|
+
out.log(`${device.deviceId} ${state2.padEnd(7)} ${device.name} [${reach}]${gap}`);
|
|
13599
13933
|
}
|
|
13600
13934
|
}
|
|
13601
13935
|
async function revoke(parsed, deps, cfg, doFetch, out, json) {
|
|
13602
13936
|
const deviceId = parsed.positionals[2];
|
|
13603
13937
|
if (!deviceId) throw new Error("device revoke needs the device id from `odla-ai device list`");
|
|
13938
|
+
out.error(
|
|
13939
|
+
`device: revoking is a signed-in human's decision; if this is refused, open ${cfg.platformUrl}/studio and revoke it there.`
|
|
13940
|
+
);
|
|
13604
13941
|
const token = await scopedToken2(parsed, deps, cfg, doFetch, out, "odla CLI (device revoke)");
|
|
13605
13942
|
const response2 = await doFetch(`${cfg.platformUrl}/registry/devices/${encodeURIComponent(deviceId)}/revoke`, {
|
|
13606
13943
|
method: "POST",
|
|
@@ -13619,7 +13956,7 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
|
|
|
13619
13956
|
// A device is granted the apps named in ONE approval, so --app is a list here.
|
|
13620
13957
|
allowAppList: true
|
|
13621
13958
|
});
|
|
13622
|
-
const
|
|
13959
|
+
const scopedTokenFile2 = credentials.scopedTokenFile;
|
|
13623
13960
|
return getScopedPlatformToken({
|
|
13624
13961
|
platform: cfg.platformUrl,
|
|
13625
13962
|
scope,
|
|
@@ -13630,12 +13967,12 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
|
|
|
13630
13967
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
13631
13968
|
openApprovalUrl: deps.openUrl,
|
|
13632
13969
|
rootDir: cfg.rootDir,
|
|
13633
|
-
tokenFile:
|
|
13970
|
+
tokenFile: scopedTokenFile2,
|
|
13634
13971
|
...stringOpt(parsed.options.token) ? { token: stringOpt(parsed.options.token) } : {}
|
|
13635
13972
|
});
|
|
13636
13973
|
}
|
|
13637
13974
|
function defaultDeviceName() {
|
|
13638
|
-
return `${
|
|
13975
|
+
return `${process20.env.HOSTNAME ?? process20.env.HOST ?? "machine"}-${process20.platform}`;
|
|
13639
13976
|
}
|
|
13640
13977
|
|
|
13641
13978
|
// src/runbook-actions.ts
|
|
@@ -13779,7 +14116,7 @@ async function runbookRemove(ctx, slug) {
|
|
|
13779
14116
|
|
|
13780
14117
|
// src/runbook-import.ts
|
|
13781
14118
|
import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
13782
|
-
import { basename as basename2, join as
|
|
14119
|
+
import { basename as basename2, join as join15 } from "path";
|
|
13783
14120
|
function parseRunbook(text3, slug) {
|
|
13784
14121
|
let rest = text3;
|
|
13785
14122
|
const meta = {};
|
|
@@ -13809,7 +14146,7 @@ function readRunbookDir(dir) {
|
|
|
13809
14146
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
13810
14147
|
return files.map((file) => {
|
|
13811
14148
|
const slug = basename2(file, ".md");
|
|
13812
|
-
const parsed = parseRunbook(readFileSync11(
|
|
14149
|
+
const parsed = parseRunbook(readFileSync11(join15(dir, file), "utf8"), slug);
|
|
13813
14150
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
13814
14151
|
});
|
|
13815
14152
|
}
|
|
@@ -13877,8 +14214,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
13877
14214
|
|
|
13878
14215
|
// src/runbook-impact.ts
|
|
13879
14216
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13880
|
-
import { existsSync as
|
|
13881
|
-
import { join as
|
|
14217
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
14218
|
+
import { join as join16 } from "path";
|
|
13882
14219
|
|
|
13883
14220
|
// src/runbook-impact-scan.ts
|
|
13884
14221
|
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$]*)/;
|
|
@@ -14047,8 +14384,8 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
|
|
|
14047
14384
|
}
|
|
14048
14385
|
function manifestLabeller(root) {
|
|
14049
14386
|
return (workspace) => {
|
|
14050
|
-
const manifest =
|
|
14051
|
-
if (!
|
|
14387
|
+
const manifest = join16(root, workspace, "package.json");
|
|
14388
|
+
if (!existsSync13(manifest)) return void 0;
|
|
14052
14389
|
try {
|
|
14053
14390
|
const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
|
|
14054
14391
|
return typeof name === "string" ? name : void 0;
|
|
@@ -14117,7 +14454,7 @@ function report4(ctx, impacts) {
|
|
|
14117
14454
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
14118
14455
|
const cwd = deps.cwd ?? process.cwd();
|
|
14119
14456
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
14120
|
-
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(
|
|
14457
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join16(cwd, path), "utf8"));
|
|
14121
14458
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
14122
14459
|
if (!surfaces.length) {
|
|
14123
14460
|
return ctx.out.log(
|
|
@@ -14244,12 +14581,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
14244
14581
|
|
|
14245
14582
|
// src/runbook-editor.ts
|
|
14246
14583
|
import { spawnSync } from "child_process";
|
|
14247
|
-
import { mkdtempSync, readFileSync as readFileSync13, rmSync as
|
|
14584
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
14248
14585
|
import { tmpdir as tmpdir4 } from "os";
|
|
14249
|
-
import { join as
|
|
14250
|
-
import
|
|
14586
|
+
import { join as join17 } from "path";
|
|
14587
|
+
import process21 from "process";
|
|
14251
14588
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
14252
|
-
function resolveEditor(env =
|
|
14589
|
+
function resolveEditor(env = process21.env) {
|
|
14253
14590
|
for (const name of EDITOR_ENV) {
|
|
14254
14591
|
const value2 = env[name];
|
|
14255
14592
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -14263,8 +14600,8 @@ function defaultRun(command, path) {
|
|
|
14263
14600
|
return result.status ?? 0;
|
|
14264
14601
|
}
|
|
14265
14602
|
function editText(initial, slug, deps = {}) {
|
|
14266
|
-
const env = deps.env ??
|
|
14267
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
14603
|
+
const env = deps.env ?? process21.env;
|
|
14604
|
+
const interactive = deps.interactive ?? (() => Boolean(process21.stdin.isTTY));
|
|
14268
14605
|
const editor = resolveEditor(env);
|
|
14269
14606
|
if (!editor)
|
|
14270
14607
|
throw new Error(
|
|
@@ -14272,8 +14609,8 @@ function editText(initial, slug, deps = {}) {
|
|
|
14272
14609
|
);
|
|
14273
14610
|
if (!interactive())
|
|
14274
14611
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
14275
|
-
const dir = mkdtempSync(
|
|
14276
|
-
const file =
|
|
14612
|
+
const dir = mkdtempSync(join17(tmpdir4(), "odla-runbook-"));
|
|
14613
|
+
const file = join17(dir, `${slug}.md`);
|
|
14277
14614
|
try {
|
|
14278
14615
|
writeFileSync5(file, initial, { mode: 384 });
|
|
14279
14616
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
@@ -14281,7 +14618,7 @@ function editText(initial, slug, deps = {}) {
|
|
|
14281
14618
|
const edited = readFileSync13(file, "utf8");
|
|
14282
14619
|
return edited === initial ? null : edited;
|
|
14283
14620
|
} finally {
|
|
14284
|
-
|
|
14621
|
+
rmSync6(dir, { recursive: true, force: true });
|
|
14285
14622
|
}
|
|
14286
14623
|
}
|
|
14287
14624
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -15193,8 +15530,10 @@ async function dispatchCli(argv, dependencies) {
|
|
|
15193
15530
|
return;
|
|
15194
15531
|
}
|
|
15195
15532
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
15196
|
-
assertArgs(parsed, ["help"],
|
|
15197
|
-
|
|
15533
|
+
assertArgs(parsed, ["help"], 2);
|
|
15534
|
+
const topic = parsed.positionals[1];
|
|
15535
|
+
if (topic) printCommandHelp(topic, runtime.stdout);
|
|
15536
|
+
else printHelp(runtime.stdout);
|
|
15198
15537
|
return;
|
|
15199
15538
|
}
|
|
15200
15539
|
if (command === "whoami") {
|
|
@@ -15408,4 +15747,4 @@ export {
|
|
|
15408
15747
|
isTerminalHostedSecurityStatus,
|
|
15409
15748
|
runCli
|
|
15410
15749
|
};
|
|
15411
|
-
//# sourceMappingURL=chunk-
|
|
15750
|
+
//# sourceMappingURL=chunk-PJ3RATDD.js.map
|