@odla-ai/cli 0.25.20 → 0.25.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/REQUIREMENTS.md +6 -0
- package/dist/bin.cjs +1719 -1090
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-ONETPJFW.js → chunk-2AFAXLKE.js} +1907 -1276
- package/dist/chunk-2AFAXLKE.js.map +1 -0
- package/dist/index.cjs +1727 -1090
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +169 -6
- package/dist/index.d.ts +169 -6
- package/dist/index.js +9 -1
- package/package.json +1 -1
- package/dist/chunk-ONETPJFW.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -43,7 +43,9 @@ function approvalLines(prompt) {
|
|
|
43
43
|
lines.push("");
|
|
44
44
|
lines.push(` ${prompt.approvalUrl}`);
|
|
45
45
|
lines.push("");
|
|
46
|
-
lines.push(
|
|
46
|
+
lines.push(
|
|
47
|
+
` ${prompt.approver ?? "The matching signed-in odla account"} opens that URL, checks the code matches, and approves.`
|
|
48
|
+
);
|
|
47
49
|
lines.push(" Opening the link without approving does nothing; this command waits until they do.");
|
|
48
50
|
if (prompt.browserAttempted) {
|
|
49
51
|
lines.push(" A browser launch was attempted, but it is best-effort and may have shown no tab.");
|
|
@@ -82,22 +84,22 @@ function readJsonFile(path) {
|
|
|
82
84
|
return null;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
|
-
function writePrivateJson(path,
|
|
86
|
-
writePrivateText(path, `${JSON.stringify(
|
|
87
|
+
function writePrivateJson(path, value2) {
|
|
88
|
+
writePrivateText(path, `${JSON.stringify(value2, null, 2)}
|
|
87
89
|
`);
|
|
88
90
|
}
|
|
89
91
|
function readCredentials(path) {
|
|
90
92
|
if (!(0, import_node_fs.existsSync)(path)) return null;
|
|
91
|
-
let
|
|
93
|
+
let value2;
|
|
92
94
|
try {
|
|
93
|
-
|
|
95
|
+
value2 = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
94
96
|
} catch {
|
|
95
97
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
96
98
|
}
|
|
97
|
-
if (!
|
|
99
|
+
if (!value2 || typeof value2 !== "object" || typeof value2.appId !== "string" || !value2.envs || typeof value2.envs !== "object") {
|
|
98
100
|
throw new Error(`credentials file ${path} has an invalid shape; fix or remove it before provisioning`);
|
|
99
101
|
}
|
|
100
|
-
return
|
|
102
|
+
return value2;
|
|
101
103
|
}
|
|
102
104
|
function mergeCredential(current, update) {
|
|
103
105
|
const next = current ?? {
|
|
@@ -392,8 +394,8 @@ function stillPending(pending, email) {
|
|
|
392
394
|
{ retryable: true }
|
|
393
395
|
);
|
|
394
396
|
}
|
|
395
|
-
function handshakeEmail(
|
|
396
|
-
const email = (
|
|
397
|
+
function handshakeEmail(value2, cached) {
|
|
398
|
+
const email = (value2 ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
397
399
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
398
400
|
throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
|
|
399
401
|
}
|
|
@@ -417,10 +419,10 @@ function handshakeUrl(platformUrl, userCode) {
|
|
|
417
419
|
url.searchParams.set("code", userCode);
|
|
418
420
|
return url.toString();
|
|
419
421
|
}
|
|
420
|
-
function platformAudience(
|
|
422
|
+
function platformAudience(value2) {
|
|
421
423
|
let url;
|
|
422
424
|
try {
|
|
423
|
-
url = new URL(
|
|
425
|
+
url = new URL(value2);
|
|
424
426
|
} catch {
|
|
425
427
|
throw new Error("platform must be an absolute URL");
|
|
426
428
|
}
|
|
@@ -439,22 +441,22 @@ var import_node_process4 = __toESM(require("process"), 1);
|
|
|
439
441
|
var MAX_BYTES = 64 * 1024;
|
|
440
442
|
async function secretInputValue(options, kind = "credential") {
|
|
441
443
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
442
|
-
let
|
|
443
|
-
if (options.fromEnv)
|
|
444
|
-
else if (options.stdin)
|
|
444
|
+
let value2;
|
|
445
|
+
if (options.fromEnv) value2 = import_node_process4.default.env[options.fromEnv];
|
|
446
|
+
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
445
447
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
446
|
-
|
|
447
|
-
if (!
|
|
448
|
-
if (new TextEncoder().encode(
|
|
449
|
-
return
|
|
448
|
+
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
449
|
+
if (!value2) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
|
|
450
|
+
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
451
|
+
return value2;
|
|
450
452
|
}
|
|
451
453
|
async function readSecretStream(kind, stream = import_node_process4.default.stdin) {
|
|
452
|
-
let
|
|
454
|
+
let value2 = "";
|
|
453
455
|
for await (const chunk of stream) {
|
|
454
|
-
|
|
455
|
-
if (
|
|
456
|
+
value2 += String(chunk);
|
|
457
|
+
if (value2.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
456
458
|
}
|
|
457
|
-
return
|
|
459
|
+
return value2;
|
|
458
460
|
}
|
|
459
461
|
|
|
460
462
|
// src/admin-ai-auth.ts
|
|
@@ -489,6 +491,8 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
489
491
|
return token;
|
|
490
492
|
}
|
|
491
493
|
var SCOPE_PURPOSE = {
|
|
494
|
+
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
495
|
+
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
492
496
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
493
497
|
"platform:ai:policy:write": "change System AI model routing",
|
|
494
498
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -525,6 +529,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
525
529
|
approvalUrl,
|
|
526
530
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
527
531
|
purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
|
|
532
|
+
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
|
|
528
533
|
browserAttempted: browser.open,
|
|
529
534
|
browserSkipped: browser.reason
|
|
530
535
|
});
|
|
@@ -592,13 +597,13 @@ function apiError(status, body) {
|
|
|
592
597
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
593
598
|
return `read System AI admin changes failed (${status}): ${message2}`;
|
|
594
599
|
}
|
|
595
|
-
function timestamp(
|
|
596
|
-
if (typeof
|
|
597
|
-
const date = new Date(
|
|
600
|
+
function timestamp(value2) {
|
|
601
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
|
|
602
|
+
const date = new Date(value2);
|
|
598
603
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
599
604
|
}
|
|
600
|
-
function isRecord(
|
|
601
|
-
return Boolean(
|
|
605
|
+
function isRecord(value2) {
|
|
606
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
602
607
|
}
|
|
603
608
|
|
|
604
609
|
// src/admin-ai-policy.ts
|
|
@@ -608,11 +613,11 @@ var SYSTEM_AI_PURPOSES = [
|
|
|
608
613
|
"security.validation",
|
|
609
614
|
"runbook.answer"
|
|
610
615
|
];
|
|
611
|
-
function requireSystemAiPurpose(
|
|
612
|
-
if (!SYSTEM_AI_PURPOSES.includes(
|
|
616
|
+
function requireSystemAiPurpose(value2) {
|
|
617
|
+
if (!SYSTEM_AI_PURPOSES.includes(value2)) {
|
|
613
618
|
throw new Error(`purpose must be one of: ${SYSTEM_AI_PURPOSES.join(", ")}`);
|
|
614
619
|
}
|
|
615
|
-
return
|
|
620
|
+
return value2;
|
|
616
621
|
}
|
|
617
622
|
|
|
618
623
|
// src/admin-ai-usage.ts
|
|
@@ -634,11 +639,11 @@ async function readAdminAiUsage(request2) {
|
|
|
634
639
|
if (request2.json) request2.stdout.log(JSON.stringify(body, null, 2));
|
|
635
640
|
else printUsage(body, request2.stdout);
|
|
636
641
|
}
|
|
637
|
-
function usageLimit(
|
|
638
|
-
if (!Number.isSafeInteger(
|
|
642
|
+
function usageLimit(value2) {
|
|
643
|
+
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
639
644
|
throw new Error("usage limit must be an integer from 1 to 500");
|
|
640
645
|
}
|
|
641
|
-
return
|
|
646
|
+
return value2;
|
|
642
647
|
}
|
|
643
648
|
function printUsage(body, out) {
|
|
644
649
|
const aggregates = isRecord2(body) && isRecord2(body.aggregates) && Array.isArray(body.aggregates.byStatus) ? body.aggregates.byStatus.filter(isRecord2) : [];
|
|
@@ -674,15 +679,15 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
674
679
|
].join(" "));
|
|
675
680
|
}
|
|
676
681
|
}
|
|
677
|
-
function numeric(
|
|
678
|
-
return typeof
|
|
682
|
+
function numeric(value2) {
|
|
683
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
679
684
|
}
|
|
680
|
-
function formatMicrousd(
|
|
681
|
-
return typeof
|
|
685
|
+
function formatMicrousd(value2) {
|
|
686
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `$${(value2 / 1e6).toFixed(6)}` : "unpriced";
|
|
682
687
|
}
|
|
683
|
-
function timestamp2(
|
|
684
|
-
if (typeof
|
|
685
|
-
const date = new Date(
|
|
688
|
+
function timestamp2(value2) {
|
|
689
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
|
|
690
|
+
const date = new Date(value2);
|
|
686
691
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
687
692
|
}
|
|
688
693
|
async function responseBody2(res) {
|
|
@@ -694,13 +699,13 @@ async function responseBody2(res) {
|
|
|
694
699
|
return { message: text.slice(0, 300) };
|
|
695
700
|
}
|
|
696
701
|
}
|
|
697
|
-
function apiError2(
|
|
702
|
+
function apiError2(action2, status, body) {
|
|
698
703
|
const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
|
|
699
704
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
700
|
-
return `${
|
|
705
|
+
return `${action2} failed (${status}): ${message2}`;
|
|
701
706
|
}
|
|
702
|
-
function isRecord2(
|
|
703
|
-
return Boolean(
|
|
707
|
+
function isRecord2(value2) {
|
|
708
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
704
709
|
}
|
|
705
710
|
|
|
706
711
|
// src/admin-ai.ts
|
|
@@ -745,11 +750,11 @@ async function adminAi(options) {
|
|
|
745
750
|
}
|
|
746
751
|
if (options.action === "credential-set") {
|
|
747
752
|
const provider = requireProvider(options.credentialProvider);
|
|
748
|
-
const
|
|
753
|
+
const value2 = await secretInputValue(options);
|
|
749
754
|
const res2 = await doFetch(`${platform}/registry/platform/ai-credentials/${encodeURIComponent(provider)}`, {
|
|
750
755
|
method: "PUT",
|
|
751
756
|
headers,
|
|
752
|
-
body: JSON.stringify({ value })
|
|
757
|
+
body: JSON.stringify({ value: value2 })
|
|
753
758
|
});
|
|
754
759
|
const body2 = await responseBody3(res2);
|
|
755
760
|
if (!res2.ok) throw new Error(apiError3(`store ${provider} platform credential`, res2.status, body2));
|
|
@@ -849,11 +854,11 @@ async function adminAi(options) {
|
|
|
849
854
|
const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
|
|
850
855
|
out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
|
|
851
856
|
}
|
|
852
|
-
function requireProvider(
|
|
853
|
-
if (
|
|
857
|
+
function requireProvider(value2) {
|
|
858
|
+
if (value2 !== "anthropic" && value2 !== "openai" && value2 !== "google") {
|
|
854
859
|
throw new Error("provider must be one of: anthropic, openai, google");
|
|
855
860
|
}
|
|
856
|
-
return
|
|
861
|
+
return value2;
|
|
857
862
|
}
|
|
858
863
|
function policyArray(body) {
|
|
859
864
|
if (isRecord3(body) && Array.isArray(body.policies)) return body.policies.filter(isRecord3);
|
|
@@ -864,7 +869,7 @@ function catalogModels(body) {
|
|
|
864
869
|
if (!isRecord3(body) || !isRecord3(body.catalog) || !Array.isArray(body.catalog.models)) {
|
|
865
870
|
throw new Error("platform returned an invalid System AI model catalog");
|
|
866
871
|
}
|
|
867
|
-
return body.catalog.models.filter((
|
|
872
|
+
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
868
873
|
}
|
|
869
874
|
async function responseBody3(res) {
|
|
870
875
|
const text = await res.text();
|
|
@@ -875,13 +880,13 @@ async function responseBody3(res) {
|
|
|
875
880
|
return { message: text.slice(0, 300) };
|
|
876
881
|
}
|
|
877
882
|
}
|
|
878
|
-
function apiError3(
|
|
883
|
+
function apiError3(action2, status, body) {
|
|
879
884
|
const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
|
|
880
885
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
881
|
-
return `${
|
|
886
|
+
return `${action2} failed (${status}): ${message2}`;
|
|
882
887
|
}
|
|
883
|
-
function isRecord3(
|
|
884
|
-
return Boolean(
|
|
888
|
+
function isRecord3(value2) {
|
|
889
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
885
890
|
}
|
|
886
891
|
|
|
887
892
|
// src/argv.ts
|
|
@@ -905,10 +910,10 @@ function parseArgv(argv) {
|
|
|
905
910
|
addOption(options, rawName, arg.slice(eq + 1));
|
|
906
911
|
continue;
|
|
907
912
|
}
|
|
908
|
-
const
|
|
909
|
-
if (
|
|
913
|
+
const value2 = argv[i + 1];
|
|
914
|
+
if (value2 !== void 0 && !value2.startsWith("--")) {
|
|
910
915
|
i++;
|
|
911
|
-
addOption(options, rawName,
|
|
916
|
+
addOption(options, rawName, value2);
|
|
912
917
|
} else {
|
|
913
918
|
addOption(options, rawName, true);
|
|
914
919
|
}
|
|
@@ -924,39 +929,39 @@ function assertArgs(parsed, allowedOptions2, maxPositionals) {
|
|
|
924
929
|
throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
|
|
925
930
|
}
|
|
926
931
|
}
|
|
927
|
-
function requiredString(
|
|
928
|
-
const result = stringOpt(
|
|
932
|
+
function requiredString(value2, name) {
|
|
933
|
+
const result = stringOpt(value2);
|
|
929
934
|
if (!result) throw new Error(`${name} is required`);
|
|
930
935
|
return result;
|
|
931
936
|
}
|
|
932
|
-
function stringOpt(
|
|
933
|
-
if (typeof
|
|
934
|
-
if (Array.isArray(
|
|
937
|
+
function stringOpt(value2) {
|
|
938
|
+
if (typeof value2 === "string") return value2;
|
|
939
|
+
if (Array.isArray(value2)) return value2[value2.length - 1];
|
|
935
940
|
return void 0;
|
|
936
941
|
}
|
|
937
|
-
function listOpt(
|
|
938
|
-
if (
|
|
939
|
-
const values = Array.isArray(
|
|
942
|
+
function listOpt(value2) {
|
|
943
|
+
if (value2 === void 0 || typeof value2 === "boolean") return void 0;
|
|
944
|
+
const values = Array.isArray(value2) ? value2 : [value2];
|
|
940
945
|
return values.flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
941
946
|
}
|
|
942
|
-
function boolOpt(
|
|
943
|
-
if (typeof
|
|
944
|
-
if (typeof
|
|
945
|
-
if (
|
|
947
|
+
function boolOpt(value2) {
|
|
948
|
+
if (typeof value2 === "boolean") return value2;
|
|
949
|
+
if (typeof value2 === "string" && (value2 === "true" || value2 === "false")) return value2 === "true";
|
|
950
|
+
if (value2 === void 0) return void 0;
|
|
946
951
|
throw new Error("boolean option must be true or false");
|
|
947
952
|
}
|
|
948
|
-
function numberOpt(
|
|
949
|
-
if (
|
|
950
|
-
const raw = stringOpt(
|
|
953
|
+
function numberOpt(value2, flag) {
|
|
954
|
+
if (value2 === void 0) return void 0;
|
|
955
|
+
const raw = stringOpt(value2);
|
|
951
956
|
const parsed = raw === void 0 ? Number.NaN : Number(raw);
|
|
952
957
|
if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${flag} requires a positive integer`);
|
|
953
958
|
return parsed;
|
|
954
959
|
}
|
|
955
|
-
function addOption(options, name,
|
|
960
|
+
function addOption(options, name, value2) {
|
|
956
961
|
const current = options[name];
|
|
957
|
-
if (current === void 0) options[name] =
|
|
958
|
-
else if (Array.isArray(current)) current.push(String(
|
|
959
|
-
else options[name] = [String(current), String(
|
|
962
|
+
if (current === void 0) options[name] = value2;
|
|
963
|
+
else if (Array.isArray(current)) current.push(String(value2));
|
|
964
|
+
else options[name] = [String(current), String(value2)];
|
|
960
965
|
}
|
|
961
966
|
|
|
962
967
|
// src/operator-context.ts
|
|
@@ -1023,17 +1028,17 @@ function validateProbes(integration, at) {
|
|
|
1023
1028
|
}
|
|
1024
1029
|
}
|
|
1025
1030
|
}
|
|
1026
|
-
function isRecord4(
|
|
1027
|
-
return
|
|
1031
|
+
function isRecord4(value2) {
|
|
1032
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1028
1033
|
}
|
|
1029
|
-
function safeText(
|
|
1030
|
-
return typeof
|
|
1034
|
+
function safeText(value2, max) {
|
|
1035
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1031
1036
|
}
|
|
1032
|
-
function safeProbePath(
|
|
1033
|
-
return typeof
|
|
1037
|
+
function safeProbePath(value2) {
|
|
1038
|
+
return typeof value2 === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value2);
|
|
1034
1039
|
}
|
|
1035
|
-
function validId(
|
|
1036
|
-
return typeof
|
|
1040
|
+
function validId(value2) {
|
|
1041
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1037
1042
|
}
|
|
1038
1043
|
function unique(values) {
|
|
1039
1044
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -1075,10 +1080,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
|
1075
1080
|
local
|
|
1076
1081
|
};
|
|
1077
1082
|
}
|
|
1078
|
-
async function resolveDataExport(cfg,
|
|
1079
|
-
if (
|
|
1080
|
-
if (typeof
|
|
1081
|
-
const target = (0, import_node_path4.isAbsolute)(
|
|
1083
|
+
async function resolveDataExport(cfg, value2, names) {
|
|
1084
|
+
if (value2 === void 0 || value2 === null || value2 === false) return void 0;
|
|
1085
|
+
if (typeof value2 !== "string") return value2;
|
|
1086
|
+
const target = (0, import_node_path4.isAbsolute)(value2) ? value2 : (0, import_node_path4.resolve)(cfg.rootDir, value2);
|
|
1082
1087
|
if (target.endsWith(".json")) {
|
|
1083
1088
|
return JSON.parse((0, import_node_fs4.readFileSync)(target, "utf8"));
|
|
1084
1089
|
}
|
|
@@ -1087,7 +1092,7 @@ async function resolveDataExport(cfg, value, names) {
|
|
|
1087
1092
|
if (mod[name] !== void 0) return mod[name];
|
|
1088
1093
|
}
|
|
1089
1094
|
if (mod.default !== void 0) return mod.default;
|
|
1090
|
-
throw new Error(`${
|
|
1095
|
+
throw new Error(`${value2} did not export ${names.join(", ")} or default`);
|
|
1091
1096
|
}
|
|
1092
1097
|
function buildPlan(cfg) {
|
|
1093
1098
|
const integrationSchema = cfg.integrations?.some((integration) => integration.schema) ?? false;
|
|
@@ -1121,9 +1126,9 @@ function calendarServiceConfig(cfg, env) {
|
|
|
1121
1126
|
};
|
|
1122
1127
|
}
|
|
1123
1128
|
function calendarBookingPageUrl(cfg, env) {
|
|
1124
|
-
const
|
|
1125
|
-
if (
|
|
1126
|
-
return new URL(
|
|
1129
|
+
const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
|
|
1130
|
+
if (value2 === void 0 || value2 === null) return value2;
|
|
1131
|
+
return new URL(value2).toString();
|
|
1127
1132
|
}
|
|
1128
1133
|
function rulesFromSchema(schema) {
|
|
1129
1134
|
const entities = serializedEntities(schema);
|
|
@@ -1137,10 +1142,10 @@ function serializedEntities(schema) {
|
|
|
1137
1142
|
if (!entities || typeof entities !== "object" || Array.isArray(entities)) return [];
|
|
1138
1143
|
return Object.keys(entities);
|
|
1139
1144
|
}
|
|
1140
|
-
function envValue(
|
|
1141
|
-
if (!
|
|
1142
|
-
if (
|
|
1143
|
-
return
|
|
1145
|
+
function envValue(value2) {
|
|
1146
|
+
if (!value2) return void 0;
|
|
1147
|
+
if (value2.startsWith("$")) return process.env[value2.slice(1)];
|
|
1148
|
+
return value2;
|
|
1144
1149
|
}
|
|
1145
1150
|
function validateRawConfig(raw, path) {
|
|
1146
1151
|
if (!raw || typeof raw !== "object") throw new Error(`${path} must export an object`);
|
|
@@ -1196,8 +1201,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1196
1201
|
if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1197
1202
|
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
|
|
1198
1203
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1199
|
-
for (const [env,
|
|
1200
|
-
if (!safeText2(
|
|
1204
|
+
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1205
|
+
if (!safeText2(value2, 1024)) {
|
|
1201
1206
|
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1202
1207
|
}
|
|
1203
1208
|
}
|
|
@@ -1206,8 +1211,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1206
1211
|
if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1207
1212
|
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
|
|
1208
1213
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1209
|
-
for (const [env,
|
|
1210
|
-
if (
|
|
1214
|
+
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1215
|
+
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
1211
1216
|
throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
|
|
1212
1217
|
}
|
|
1213
1218
|
}
|
|
@@ -1226,37 +1231,37 @@ function validateServices(services, path) {
|
|
|
1226
1231
|
}
|
|
1227
1232
|
}
|
|
1228
1233
|
}
|
|
1229
|
-
function assertOnly(
|
|
1230
|
-
const extra = Object.keys(
|
|
1234
|
+
function assertOnly(value2, allowed, label) {
|
|
1235
|
+
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1231
1236
|
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1232
1237
|
}
|
|
1233
|
-
function isRecord5(
|
|
1234
|
-
return
|
|
1238
|
+
function isRecord5(value2) {
|
|
1239
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1235
1240
|
}
|
|
1236
|
-
function safeText2(
|
|
1237
|
-
return typeof
|
|
1241
|
+
function safeText2(value2, max) {
|
|
1242
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1238
1243
|
}
|
|
1239
|
-
function safeHttpsUrl(
|
|
1240
|
-
if (typeof
|
|
1244
|
+
function safeHttpsUrl(value2) {
|
|
1245
|
+
if (typeof value2 !== "string" || value2.length > 2048) return false;
|
|
1241
1246
|
try {
|
|
1242
|
-
const url = new URL(
|
|
1247
|
+
const url = new URL(value2);
|
|
1243
1248
|
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
1244
1249
|
} catch {
|
|
1245
1250
|
return false;
|
|
1246
1251
|
}
|
|
1247
1252
|
}
|
|
1248
|
-
function validId2(
|
|
1249
|
-
return typeof
|
|
1253
|
+
function validId2(value2) {
|
|
1254
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1250
1255
|
}
|
|
1251
1256
|
async function loadConfigModule(path) {
|
|
1252
1257
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1253
1258
|
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
|
|
1254
|
-
const
|
|
1255
|
-
if (typeof
|
|
1256
|
-
return
|
|
1259
|
+
const value2 = mod.default ?? mod.config;
|
|
1260
|
+
if (typeof value2 === "function") return await value2();
|
|
1261
|
+
return value2;
|
|
1257
1262
|
}
|
|
1258
|
-
function trimSlash(
|
|
1259
|
-
return
|
|
1263
|
+
function trimSlash(value2) {
|
|
1264
|
+
return value2.replace(/\/+$/, "");
|
|
1260
1265
|
}
|
|
1261
1266
|
function unique2(values) {
|
|
1262
1267
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -1282,8 +1287,8 @@ function resolveOperatorProfile(parsed) {
|
|
|
1282
1287
|
}
|
|
1283
1288
|
assertOperatorName(name, "context");
|
|
1284
1289
|
const profiles = readOperatorProfiles(file);
|
|
1285
|
-
const
|
|
1286
|
-
if (!
|
|
1290
|
+
const value2 = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
1291
|
+
if (!value2) {
|
|
1287
1292
|
throw new Error(
|
|
1288
1293
|
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
1289
1294
|
);
|
|
@@ -1292,7 +1297,7 @@ function resolveOperatorProfile(parsed) {
|
|
|
1292
1297
|
name,
|
|
1293
1298
|
source: fromFlag ? "flag" : "environment",
|
|
1294
1299
|
file,
|
|
1295
|
-
value
|
|
1300
|
+
value: value2
|
|
1296
1301
|
};
|
|
1297
1302
|
}
|
|
1298
1303
|
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
@@ -1320,8 +1325,8 @@ function operatorCredentialFiles(selection) {
|
|
|
1320
1325
|
scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
|
|
1321
1326
|
};
|
|
1322
1327
|
}
|
|
1323
|
-
function assertOperatorName(
|
|
1324
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(
|
|
1328
|
+
function assertOperatorName(value2, label) {
|
|
1329
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value2)) {
|
|
1325
1330
|
throw new Error(
|
|
1326
1331
|
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
1327
1332
|
);
|
|
@@ -1347,22 +1352,22 @@ function readOperatorProfiles(file) {
|
|
|
1347
1352
|
);
|
|
1348
1353
|
}
|
|
1349
1354
|
const profiles = emptyProfiles();
|
|
1350
|
-
for (const [name,
|
|
1355
|
+
for (const [name, value2] of Object.entries(
|
|
1351
1356
|
raw.profiles
|
|
1352
1357
|
)) {
|
|
1353
1358
|
assertOperatorName(name, "context");
|
|
1354
|
-
profiles[name] = validateProfile(
|
|
1359
|
+
profiles[name] = validateProfile(value2, `operator context "${name}"`);
|
|
1355
1360
|
}
|
|
1356
1361
|
return profiles;
|
|
1357
1362
|
}
|
|
1358
1363
|
function emptyProfiles() {
|
|
1359
1364
|
return /* @__PURE__ */ Object.create(null);
|
|
1360
1365
|
}
|
|
1361
|
-
function validateProfile(
|
|
1362
|
-
if (!
|
|
1366
|
+
function validateProfile(value2, label) {
|
|
1367
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
|
|
1363
1368
|
throw new Error(`${label} has an invalid shape`);
|
|
1364
1369
|
}
|
|
1365
|
-
const unknown = Object.keys(
|
|
1370
|
+
const unknown = Object.keys(value2).filter(
|
|
1366
1371
|
(key) => !["platform", "app", "environment"].includes(key)
|
|
1367
1372
|
);
|
|
1368
1373
|
if (unknown.length > 0) {
|
|
@@ -1370,7 +1375,7 @@ function validateProfile(value, label) {
|
|
|
1370
1375
|
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
1371
1376
|
);
|
|
1372
1377
|
}
|
|
1373
|
-
const candidate =
|
|
1378
|
+
const candidate = value2;
|
|
1374
1379
|
if (typeof candidate.platform !== "string") {
|
|
1375
1380
|
throw new Error(`${label} needs an absolute platform URL`);
|
|
1376
1381
|
}
|
|
@@ -1385,8 +1390,8 @@ function validateProfile(value, label) {
|
|
|
1385
1390
|
...environment2 ? { environment: environment2 } : {}
|
|
1386
1391
|
};
|
|
1387
1392
|
}
|
|
1388
|
-
function clean(
|
|
1389
|
-
const normalized =
|
|
1393
|
+
function clean(value2) {
|
|
1394
|
+
const normalized = value2?.trim();
|
|
1390
1395
|
return normalized || void 0;
|
|
1391
1396
|
}
|
|
1392
1397
|
|
|
@@ -1479,8 +1484,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1479
1484
|
}
|
|
1480
1485
|
};
|
|
1481
1486
|
}
|
|
1482
|
-
function clean2(
|
|
1483
|
-
const normalized =
|
|
1487
|
+
function clean2(value2) {
|
|
1488
|
+
const normalized = value2?.trim();
|
|
1484
1489
|
return normalized || void 0;
|
|
1485
1490
|
}
|
|
1486
1491
|
|
|
@@ -1502,21 +1507,21 @@ var SET_OPTIONS = [
|
|
|
1502
1507
|
];
|
|
1503
1508
|
async function adminCommand(parsed, deps = {}) {
|
|
1504
1509
|
const area = parsed.positionals[1];
|
|
1505
|
-
const
|
|
1506
|
-
const credentialSet =
|
|
1507
|
-
const credentials =
|
|
1508
|
-
const models =
|
|
1509
|
-
const usage =
|
|
1510
|
-
const audit =
|
|
1511
|
-
if (area !== "ai" ||
|
|
1510
|
+
const action2 = parsed.positionals[2];
|
|
1511
|
+
const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
|
|
1512
|
+
const credentials = action2 === "credentials";
|
|
1513
|
+
const models = action2 === "models";
|
|
1514
|
+
const usage = action2 === "usage";
|
|
1515
|
+
const audit = action2 === "audit";
|
|
1516
|
+
if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
|
|
1512
1517
|
throw new Error('unknown admin command. Try "odla-ai admin ai show".');
|
|
1513
1518
|
}
|
|
1514
|
-
const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] :
|
|
1515
|
-
assertArgs(parsed, allowed, credentialSet ? 5 :
|
|
1519
|
+
const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] : action2 === "set" ? SET_OPTIONS : models ? [...JSON_OPTIONS, "provider"] : usage ? [...JSON_OPTIONS, "app-id", "env", "run-id", "limit"] : audit ? [...JSON_OPTIONS, "limit"] : JSON_OPTIONS;
|
|
1520
|
+
assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
|
|
1516
1521
|
const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
1517
1522
|
await adminAi({
|
|
1518
|
-
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" :
|
|
1519
|
-
purpose:
|
|
1523
|
+
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action2,
|
|
1524
|
+
purpose: action2 === "set" ? parsed.positionals[3] : void 0,
|
|
1520
1525
|
provider: stringOpt(parsed.options.provider),
|
|
1521
1526
|
model: stringOpt(parsed.options.model),
|
|
1522
1527
|
enabled: boolOpt(parsed.options.enabled),
|
|
@@ -1573,12 +1578,12 @@ function bothTenants(cfg) {
|
|
|
1573
1578
|
|
|
1574
1579
|
// src/agent-command.ts
|
|
1575
1580
|
async function agentCommand(parsed, deps = {}) {
|
|
1576
|
-
const
|
|
1577
|
-
if (
|
|
1578
|
-
throw new Error(`unknown agent action "${
|
|
1581
|
+
const action2 = parsed.positionals[1];
|
|
1582
|
+
if (action2 !== "jobs" && action2 !== "retry") {
|
|
1583
|
+
throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
1579
1584
|
}
|
|
1580
|
-
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"],
|
|
1581
|
-
if (
|
|
1585
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action2 === "jobs" ? 2 : 3);
|
|
1586
|
+
if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
1582
1587
|
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
1583
1588
|
}
|
|
1584
1589
|
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
@@ -1598,7 +1603,7 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1598
1603
|
);
|
|
1599
1604
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1600
1605
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1601
|
-
if (
|
|
1606
|
+
if (action2 === "retry") {
|
|
1602
1607
|
const id = parsed.positionals[2];
|
|
1603
1608
|
const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
|
|
1604
1609
|
const body2 = await readJson(res2);
|
|
@@ -2034,7 +2039,7 @@ async function copyFiles(cfg, token, route2, doFetch, out) {
|
|
|
2034
2039
|
}
|
|
2035
2040
|
|
|
2036
2041
|
// src/app-lifecycle.ts
|
|
2037
|
-
async function lifecycleCall(
|
|
2042
|
+
async function lifecycleCall(action2, options) {
|
|
2038
2043
|
const cfg = await loadProjectConfig(options.configPath);
|
|
2039
2044
|
const out = options.stdout ?? console;
|
|
2040
2045
|
const doFetch = options.fetch ?? fetch;
|
|
@@ -2044,13 +2049,13 @@ async function lifecycleCall(action, options) {
|
|
|
2044
2049
|
doFetch,
|
|
2045
2050
|
out
|
|
2046
2051
|
);
|
|
2047
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${
|
|
2052
|
+
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
|
|
2048
2053
|
method: "POST",
|
|
2049
2054
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
|
|
2050
2055
|
});
|
|
2051
2056
|
const body = await res.json().catch(() => ({}));
|
|
2052
2057
|
if (!res.ok || !body.ok) {
|
|
2053
|
-
throw new Error(`${
|
|
2058
|
+
throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
|
|
2054
2059
|
}
|
|
2055
2060
|
return body;
|
|
2056
2061
|
}
|
|
@@ -2191,44 +2196,44 @@ var REPLACEMENTS = [
|
|
|
2191
2196
|
[/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
|
|
2192
2197
|
[/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
|
|
2193
2198
|
];
|
|
2194
|
-
function redactSecrets(
|
|
2195
|
-
let result =
|
|
2199
|
+
function redactSecrets(value2) {
|
|
2200
|
+
let result = value2;
|
|
2196
2201
|
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
2197
2202
|
return result;
|
|
2198
2203
|
}
|
|
2199
2204
|
function redactingOutput(output) {
|
|
2200
|
-
const redactArgs = (args) => args.map((
|
|
2205
|
+
const redactArgs = (args) => args.map((value2) => redactOutputValue(value2, /* @__PURE__ */ new WeakMap()));
|
|
2201
2206
|
return {
|
|
2202
2207
|
log: (...args) => output.log(...redactArgs(args)),
|
|
2203
2208
|
error: (...args) => output.error(...redactArgs(args))
|
|
2204
2209
|
};
|
|
2205
2210
|
}
|
|
2206
|
-
function redactOutputValue(
|
|
2207
|
-
if (typeof
|
|
2208
|
-
if (
|
|
2209
|
-
const redacted = new Error(redactSecrets(
|
|
2210
|
-
redacted.name =
|
|
2211
|
-
if (
|
|
2211
|
+
function redactOutputValue(value2, seen) {
|
|
2212
|
+
if (typeof value2 === "string") return redactSecrets(value2);
|
|
2213
|
+
if (value2 instanceof Error) {
|
|
2214
|
+
const redacted = new Error(redactSecrets(value2.message));
|
|
2215
|
+
redacted.name = value2.name;
|
|
2216
|
+
if (value2.stack) redacted.stack = redactSecrets(value2.stack);
|
|
2212
2217
|
return redacted;
|
|
2213
2218
|
}
|
|
2214
|
-
if (!
|
|
2215
|
-
const prior = seen.get(
|
|
2219
|
+
if (!value2 || typeof value2 !== "object") return value2;
|
|
2220
|
+
const prior = seen.get(value2);
|
|
2216
2221
|
if (prior) return prior;
|
|
2217
|
-
if (Array.isArray(
|
|
2222
|
+
if (Array.isArray(value2)) {
|
|
2218
2223
|
const next2 = [];
|
|
2219
|
-
seen.set(
|
|
2220
|
-
for (const item of
|
|
2224
|
+
seen.set(value2, next2);
|
|
2225
|
+
for (const item of value2) next2.push(redactOutputValue(item, seen));
|
|
2221
2226
|
return next2;
|
|
2222
2227
|
}
|
|
2223
|
-
const prototype = Object.getPrototypeOf(
|
|
2224
|
-
if (prototype !== Object.prototype && prototype !== null) return
|
|
2228
|
+
const prototype = Object.getPrototypeOf(value2);
|
|
2229
|
+
if (prototype !== Object.prototype && prototype !== null) return value2;
|
|
2225
2230
|
const next = {};
|
|
2226
|
-
seen.set(
|
|
2227
|
-
for (const [key, item] of Object.entries(
|
|
2231
|
+
seen.set(value2, next);
|
|
2232
|
+
for (const [key, item] of Object.entries(value2)) next[key] = redactOutputValue(item, seen);
|
|
2228
2233
|
return next;
|
|
2229
2234
|
}
|
|
2230
|
-
function looksSecret(
|
|
2231
|
-
return redactSecrets(
|
|
2235
|
+
function looksSecret(value2) {
|
|
2236
|
+
return redactSecrets(value2) !== value2 || value2.includes("-----BEGIN");
|
|
2232
2237
|
}
|
|
2233
2238
|
|
|
2234
2239
|
// src/calendar-http.ts
|
|
@@ -2245,9 +2250,9 @@ async function readCalendarStatus(ctx) {
|
|
|
2245
2250
|
}
|
|
2246
2251
|
async function discoverGoogleCalendars(ctx) {
|
|
2247
2252
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2248
|
-
const
|
|
2249
|
-
if (!
|
|
2250
|
-
return
|
|
2253
|
+
const value2 = record(raw);
|
|
2254
|
+
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2255
|
+
return value2.calendars.map((item, index) => {
|
|
2251
2256
|
const calendar = record(item);
|
|
2252
2257
|
const id = textField(calendar?.id, 1024);
|
|
2253
2258
|
if (!calendar || !id) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
@@ -2269,10 +2274,10 @@ async function applyCalendarSettings(ctx, bookingPageUrl) {
|
|
|
2269
2274
|
}
|
|
2270
2275
|
async function startCalendarConnection(ctx) {
|
|
2271
2276
|
const raw = await calendarJson(ctx, "/google/connect", { method: "POST", body: "{}" });
|
|
2272
|
-
const
|
|
2273
|
-
const attemptId = textField(
|
|
2274
|
-
const consentUrl = textField(
|
|
2275
|
-
const expiresAt = timestamp3(
|
|
2277
|
+
const value2 = wrapped(raw, "attempt");
|
|
2278
|
+
const attemptId = textField(value2.attemptId, 180);
|
|
2279
|
+
const consentUrl = textField(value2.consentUrl, 4096);
|
|
2280
|
+
const expiresAt = timestamp3(value2.expiresAt);
|
|
2276
2281
|
if (!attemptId || !consentUrl || expiresAt === void 0) throw new Error("calendar connect returned an invalid attempt");
|
|
2277
2282
|
return { attemptId, consentUrl, expiresAt };
|
|
2278
2283
|
}
|
|
@@ -2290,42 +2295,42 @@ async function requestCalendarDisconnect(ctx) {
|
|
|
2290
2295
|
}
|
|
2291
2296
|
function parseCalendarStatus(raw, env) {
|
|
2292
2297
|
const outer = wrapped(raw, "calendar");
|
|
2293
|
-
const
|
|
2294
|
-
const connection = record(
|
|
2295
|
-
const config = record(
|
|
2298
|
+
const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
|
|
2299
|
+
const connection = record(value2.connection) ?? {};
|
|
2300
|
+
const config = record(value2.config) ?? record(outer.config) ?? {};
|
|
2296
2301
|
const googleConfig = record(config.google) ?? config;
|
|
2297
|
-
const stateValue = calendarState(
|
|
2302
|
+
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2298
2303
|
if (!stateValue) {
|
|
2299
2304
|
throw new Error("calendar status returned an invalid connection state");
|
|
2300
2305
|
}
|
|
2301
|
-
const providerValue =
|
|
2306
|
+
const providerValue = value2.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
|
|
2302
2307
|
if (providerValue !== void 0 && providerValue !== null && providerValue !== "google") {
|
|
2303
2308
|
throw new Error("calendar status returned an unsupported provider");
|
|
2304
2309
|
}
|
|
2305
|
-
const accessValue =
|
|
2310
|
+
const accessValue = value2.access ?? connection.access ?? config.access ?? googleConfig.access;
|
|
2306
2311
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2307
2312
|
throw new Error("calendar status returned unsupported access");
|
|
2308
2313
|
}
|
|
2309
|
-
const errorValue = record(
|
|
2310
|
-
const errorCode = textField(
|
|
2311
|
-
const bookingPageValue = Object.hasOwn(
|
|
2312
|
-
const connected = typeof (
|
|
2313
|
-
const bookingCalendarId = textField(
|
|
2314
|
+
const errorValue = record(value2.error) ?? record(connection.error);
|
|
2315
|
+
const errorCode = textField(value2.lastErrorCode, 128);
|
|
2316
|
+
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2317
|
+
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
2318
|
+
const bookingCalendarId = textField(value2.bookingCalendarId ?? config.bookingCalendarId, 1024);
|
|
2314
2319
|
return {
|
|
2315
2320
|
env,
|
|
2316
|
-
enabled: typeof
|
|
2321
|
+
enabled: typeof value2.enabled === "boolean" ? value2.enabled : true,
|
|
2317
2322
|
connected,
|
|
2318
|
-
writable: typeof
|
|
2323
|
+
writable: typeof value2.writable === "boolean" ? value2.writable : stateValue === "healthy",
|
|
2319
2324
|
provider: providerValue === "google" ? "google" : null,
|
|
2320
2325
|
status: stateValue,
|
|
2321
2326
|
...accessValue !== void 0 ? { access: "book" } : {},
|
|
2322
2327
|
...bookingCalendarId ? { bookingCalendarId } : {},
|
|
2323
2328
|
calendars: calendarIds(
|
|
2324
|
-
|
|
2329
|
+
value2.availabilityCalendars ?? config.availabilityCalendars ?? value2.configuredCalendars ?? value2.calendars ?? config.calendars ?? googleConfig.calendars
|
|
2325
2330
|
),
|
|
2326
2331
|
...optionalNullableUrl("bookingPageUrl", bookingPageValue),
|
|
2327
|
-
grantedScopes: stringList(
|
|
2328
|
-
...optionalText("attemptId",
|
|
2332
|
+
grantedScopes: stringList(value2.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
|
|
2333
|
+
...optionalText("attemptId", value2.attemptId ?? connection.attemptId, 180),
|
|
2329
2334
|
...errorValue || errorCode ? { error: {
|
|
2330
2335
|
...textField(errorValue?.code, 128) ? { code: textField(errorValue?.code, 128) } : {},
|
|
2331
2336
|
...textField(errorValue?.message, 500) ? { message: textField(errorValue?.message, 500) } : {},
|
|
@@ -2333,9 +2338,9 @@ function parseCalendarStatus(raw, env) {
|
|
|
2333
2338
|
} } : {}
|
|
2334
2339
|
};
|
|
2335
2340
|
}
|
|
2336
|
-
function calendarState(
|
|
2337
|
-
if (typeof
|
|
2338
|
-
if (CALENDAR_STATES.includes(
|
|
2341
|
+
function calendarState(value2) {
|
|
2342
|
+
if (typeof value2 !== "string") return null;
|
|
2343
|
+
if (CALENDAR_STATES.includes(value2)) return value2;
|
|
2339
2344
|
const legacy = {
|
|
2340
2345
|
disabled: "not_connected",
|
|
2341
2346
|
disconnected: "disconnected",
|
|
@@ -2346,7 +2351,7 @@ function calendarState(value) {
|
|
|
2346
2351
|
ready: "healthy",
|
|
2347
2352
|
error: "failed"
|
|
2348
2353
|
};
|
|
2349
|
-
return legacy[
|
|
2354
|
+
return legacy[value2] ?? null;
|
|
2350
2355
|
}
|
|
2351
2356
|
async function calendarJson(ctx, suffix, init) {
|
|
2352
2357
|
const platform = platformAudience(ctx.platform);
|
|
@@ -2380,50 +2385,50 @@ function wrapped(raw, key) {
|
|
|
2380
2385
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2381
2386
|
return record(outer[key]) ?? outer;
|
|
2382
2387
|
}
|
|
2383
|
-
function record(
|
|
2384
|
-
return
|
|
2388
|
+
function record(value2) {
|
|
2389
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2385
2390
|
}
|
|
2386
|
-
function textField(
|
|
2387
|
-
return typeof
|
|
2391
|
+
function textField(value2, max) {
|
|
2392
|
+
return typeof value2 === "string" && value2.length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2) ? value2 : void 0;
|
|
2388
2393
|
}
|
|
2389
|
-
function stringList(
|
|
2390
|
-
return Array.isArray(
|
|
2394
|
+
function stringList(value2) {
|
|
2395
|
+
return Array.isArray(value2) ? [...new Set(value2.filter((item) => !!textField(item, 4096)))] : [];
|
|
2391
2396
|
}
|
|
2392
|
-
function calendarIds(
|
|
2393
|
-
if (!Array.isArray(
|
|
2394
|
-
return [...new Set(
|
|
2397
|
+
function calendarIds(value2) {
|
|
2398
|
+
if (!Array.isArray(value2)) return [];
|
|
2399
|
+
return [...new Set(value2.flatMap((item) => {
|
|
2395
2400
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2396
2401
|
const calendar = record(item);
|
|
2397
2402
|
const id = textField(calendar?.id, 4096);
|
|
2398
2403
|
return id && calendar?.selected !== false ? [id] : [];
|
|
2399
2404
|
}))];
|
|
2400
2405
|
}
|
|
2401
|
-
function timestamp3(
|
|
2402
|
-
if (typeof
|
|
2403
|
-
if (typeof
|
|
2404
|
-
const parsed = Date.parse(
|
|
2406
|
+
function timestamp3(value2) {
|
|
2407
|
+
if (typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0) return value2;
|
|
2408
|
+
if (typeof value2 === "string") {
|
|
2409
|
+
const parsed = Date.parse(value2);
|
|
2405
2410
|
if (Number.isFinite(parsed)) return parsed;
|
|
2406
2411
|
}
|
|
2407
2412
|
return void 0;
|
|
2408
2413
|
}
|
|
2409
|
-
function optionalText(key,
|
|
2410
|
-
const parsed = textField(
|
|
2414
|
+
function optionalText(key, value2, max) {
|
|
2415
|
+
const parsed = textField(value2, max);
|
|
2411
2416
|
return parsed ? { [key]: parsed } : {};
|
|
2412
2417
|
}
|
|
2413
|
-
function optionalNullableUrl(key,
|
|
2414
|
-
if (
|
|
2415
|
-
const parsed = textField(
|
|
2418
|
+
function optionalNullableUrl(key, value2) {
|
|
2419
|
+
if (value2 === null) return { [key]: null };
|
|
2420
|
+
const parsed = textField(value2, 4096);
|
|
2416
2421
|
return parsed ? { [key]: parsed } : {};
|
|
2417
2422
|
}
|
|
2418
|
-
function identifier(
|
|
2419
|
-
if (typeof
|
|
2420
|
-
return
|
|
2423
|
+
function identifier(value2, name) {
|
|
2424
|
+
if (typeof value2 !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value2)) throw new Error(`${name} is invalid`);
|
|
2425
|
+
return value2;
|
|
2421
2426
|
}
|
|
2422
|
-
function credential(
|
|
2423
|
-
if (typeof
|
|
2427
|
+
function credential(value2) {
|
|
2428
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
2424
2429
|
throw new Error("calendar requires an odla developer token");
|
|
2425
2430
|
}
|
|
2426
|
-
return
|
|
2431
|
+
return value2;
|
|
2427
2432
|
}
|
|
2428
2433
|
|
|
2429
2434
|
// src/calendar-poll.ts
|
|
@@ -2584,11 +2589,11 @@ async function connectWithContext(ctx, options) {
|
|
|
2584
2589
|
}
|
|
2585
2590
|
throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
|
|
2586
2591
|
}
|
|
2587
|
-
function trustedCalendarConsentUrl(platform,
|
|
2592
|
+
function trustedCalendarConsentUrl(platform, value2) {
|
|
2588
2593
|
const origin = platformAudience(platform);
|
|
2589
2594
|
let url;
|
|
2590
2595
|
try {
|
|
2591
|
-
url =
|
|
2596
|
+
url = value2.startsWith("/") ? new URL(value2, origin) : new URL(value2);
|
|
2592
2597
|
} catch {
|
|
2593
2598
|
throw new Error("odla.ai returned an invalid calendar consent URL");
|
|
2594
2599
|
}
|
|
@@ -2599,12 +2604,12 @@ function trustedCalendarConsentUrl(platform, value) {
|
|
|
2599
2604
|
}
|
|
2600
2605
|
return url.toString();
|
|
2601
2606
|
}
|
|
2602
|
-
function pollTimeout(
|
|
2603
|
-
if (!Number.isSafeInteger(
|
|
2604
|
-
return
|
|
2607
|
+
function pollTimeout(value2 = 10 * 6e4) {
|
|
2608
|
+
if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
|
|
2609
|
+
return value2;
|
|
2605
2610
|
}
|
|
2606
|
-
function productionConsent(env, yes,
|
|
2607
|
-
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${
|
|
2611
|
+
function productionConsent(env, yes, action2) {
|
|
2612
|
+
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action2} for "${env}" without --yes`);
|
|
2608
2613
|
}
|
|
2609
2614
|
function printStatus(status, json, out) {
|
|
2610
2615
|
if (json) {
|
|
@@ -2633,6 +2638,8 @@ var CAPABILITIES = {
|
|
|
2633
2638
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2634
2639
|
"save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
|
|
2635
2640
|
"read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
|
|
2641
|
+
"read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
|
|
2642
|
+
"compare one project's checked-in Registry intent with live owner-visible state and freeze a secret-free plan digest through an exact app:config:read capability",
|
|
2636
2643
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2637
2644
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2638
2645
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2679,15 +2686,542 @@ function printGroup(out, heading, items) {
|
|
|
2679
2686
|
out.log("");
|
|
2680
2687
|
}
|
|
2681
2688
|
|
|
2689
|
+
// src/config-reconcile-command.ts
|
|
2690
|
+
var import_apps6 = require("@odla-ai/apps");
|
|
2691
|
+
var import_node_path7 = require("path");
|
|
2692
|
+
|
|
2693
|
+
// src/config-reconcile-digest.ts
|
|
2694
|
+
var import_node_crypto = require("crypto");
|
|
2695
|
+
function canonicalJson(value2) {
|
|
2696
|
+
return JSON.stringify(canonicalValue(value2));
|
|
2697
|
+
}
|
|
2698
|
+
function configDigest(value2) {
|
|
2699
|
+
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2700
|
+
}
|
|
2701
|
+
function canonicalValue(value2) {
|
|
2702
|
+
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2703
|
+
if (value2 && typeof value2 === "object") {
|
|
2704
|
+
return Object.fromEntries(
|
|
2705
|
+
Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
return value2;
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// src/config-reconcile-desired.ts
|
|
2712
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2713
|
+
|
|
2714
|
+
// src/provision-helpers.ts
|
|
2715
|
+
var import_ai = require("@odla-ai/ai");
|
|
2716
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2717
|
+
function defaultSecretName(provider) {
|
|
2718
|
+
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2719
|
+
return names[provider] ?? `${provider}_api_key`;
|
|
2720
|
+
}
|
|
2721
|
+
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2722
|
+
const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
|
|
2723
|
+
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2724
|
+
headers: { authorization: `Bearer ${token}` }
|
|
2725
|
+
});
|
|
2726
|
+
if (res.ok || res.status === 404) return;
|
|
2727
|
+
if (res.status === 403) {
|
|
2728
|
+
throw new Error(
|
|
2729
|
+
`${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText3(res)}`);
|
|
2733
|
+
}
|
|
2734
|
+
async function postJson(doFetch, url, bearer, body) {
|
|
2735
|
+
const res = await doFetch(url, {
|
|
2736
|
+
method: "POST",
|
|
2737
|
+
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
2738
|
+
body: JSON.stringify(body)
|
|
2739
|
+
});
|
|
2740
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText3(res)}`);
|
|
2741
|
+
}
|
|
2742
|
+
function normalizeClerkConfig(value2) {
|
|
2743
|
+
if (!value2) return null;
|
|
2744
|
+
if (typeof value2 === "string") {
|
|
2745
|
+
const publishableKey2 = envValue(value2);
|
|
2746
|
+
return publishableKey2 ? { publishableKey: publishableKey2 } : null;
|
|
2747
|
+
}
|
|
2748
|
+
if (typeof value2 !== "object") return null;
|
|
2749
|
+
const cfg = value2;
|
|
2750
|
+
const publishableKey = envValue(cfg.publishableKey);
|
|
2751
|
+
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
2752
|
+
}
|
|
2753
|
+
async function safeText3(res) {
|
|
2754
|
+
try {
|
|
2755
|
+
return redactSecrets((await res.text()).slice(0, 500));
|
|
2756
|
+
} catch {
|
|
2757
|
+
return "";
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// src/config-reconcile-desired.ts
|
|
2762
|
+
function desiredRegistryState(cfg) {
|
|
2763
|
+
const environments = {};
|
|
2764
|
+
const services = (0, import_apps4.orderAppServices)(cfg.services);
|
|
2765
|
+
for (const env of cfg.envs) {
|
|
2766
|
+
const desiredServices = {};
|
|
2767
|
+
for (const service of services) {
|
|
2768
|
+
desiredServices[service] = {
|
|
2769
|
+
enabled: true,
|
|
2770
|
+
...managedServiceConfig(cfg, env, service)
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
const authoredAuth = cfg.auth?.clerk && Object.hasOwn(cfg.auth.clerk, env) ? cfg.auth.clerk[env] : void 0;
|
|
2774
|
+
const auth = normalizeClerkConfig(authoredAuth);
|
|
2775
|
+
if (authoredAuth && !auth) {
|
|
2776
|
+
throw new Error(`auth.clerk.${env} could not resolve its publishable key`);
|
|
2777
|
+
}
|
|
2778
|
+
const linkManaged = !!cfg.links && Object.hasOwn(cfg.links, env);
|
|
2779
|
+
environments[env] = {
|
|
2780
|
+
services: desiredServices,
|
|
2781
|
+
...auth ? { auth } : {},
|
|
2782
|
+
...linkManaged ? { link: cfg.links?.[env] ?? null } : {}
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
return {
|
|
2786
|
+
appId: cfg.app.id,
|
|
2787
|
+
name: cfg.app.name,
|
|
2788
|
+
environments
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
function managedServiceConfig(cfg, env, service) {
|
|
2792
|
+
if (service === "ai" && cfg.ai?.provider) {
|
|
2793
|
+
return {
|
|
2794
|
+
config: {
|
|
2795
|
+
provider: cfg.ai.provider,
|
|
2796
|
+
...cfg.ai.model ? { model: cfg.ai.model } : {}
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
if (service === "calendar") return { config: calendarServiceConfig(cfg, env) };
|
|
2801
|
+
return {};
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// src/config-reconcile.ts
|
|
2805
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2806
|
+
|
|
2807
|
+
// src/config-reconcile-values.ts
|
|
2808
|
+
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
2809
|
+
return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
|
|
2810
|
+
}
|
|
2811
|
+
function action(kind, path, before, after, risk, applySupport, reason) {
|
|
2812
|
+
return { kind, path, before, after, risk, requiresApproval: false, applySupport, reason };
|
|
2813
|
+
}
|
|
2814
|
+
function scopedAction(kind, path, env, service, before, after, reason, destructive = false) {
|
|
2815
|
+
const production = env === "prod" || env === "production";
|
|
2816
|
+
return {
|
|
2817
|
+
...action(
|
|
2818
|
+
kind,
|
|
2819
|
+
path,
|
|
2820
|
+
before,
|
|
2821
|
+
after,
|
|
2822
|
+
destructive ? "high" : production ? "medium" : "low",
|
|
2823
|
+
destructive ? "studio" : "provision",
|
|
2824
|
+
reason
|
|
2825
|
+
),
|
|
2826
|
+
env,
|
|
2827
|
+
...service ? { service } : {},
|
|
2828
|
+
requiresApproval: production || destructive
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function observedProjection(app) {
|
|
2832
|
+
return {
|
|
2833
|
+
appId: app.appId,
|
|
2834
|
+
name: app.name,
|
|
2835
|
+
archivedAt: app.archivedAt,
|
|
2836
|
+
environments: Object.fromEntries(
|
|
2837
|
+
Object.entries(app.environments).sort(([left], [right]) => left.localeCompare(right)).map(([env, services]) => [
|
|
2838
|
+
env,
|
|
2839
|
+
Object.fromEntries(Object.entries(services).sort(([left], [right]) => left.localeCompare(right)))
|
|
2840
|
+
])
|
|
2841
|
+
),
|
|
2842
|
+
auth: app.auth,
|
|
2843
|
+
links: app.links
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function projectConfig(value2, keys) {
|
|
2847
|
+
return Object.fromEntries(keys.filter((key) => value2[key] !== void 0).map((key) => [key, value2[key]]));
|
|
2848
|
+
}
|
|
2849
|
+
function same(left, right) {
|
|
2850
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
2851
|
+
}
|
|
2852
|
+
function actionId(actionValue) {
|
|
2853
|
+
return `action-${configDigest(actionValue).slice("sha256:".length, "sha256:".length + 16)}`;
|
|
2854
|
+
}
|
|
2855
|
+
function quoteArg(value2) {
|
|
2856
|
+
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// src/config-reconcile.ts
|
|
2860
|
+
var COVERAGE = {
|
|
2861
|
+
included: [
|
|
2862
|
+
"app display name",
|
|
2863
|
+
"environment service enablement",
|
|
2864
|
+
"AI provider/model and calendar booking service configuration",
|
|
2865
|
+
"auth configuration when explicitly declared",
|
|
2866
|
+
"deployment link when explicitly declared"
|
|
2867
|
+
],
|
|
2868
|
+
excluded: [
|
|
2869
|
+
{ path: "db.schema", reason: "owned by the database data plane" },
|
|
2870
|
+
{ path: "db.rules", reason: "owned by the database data plane" },
|
|
2871
|
+
{ path: "integrations.seeds", reason: "guarded data-plane writes are not Registry state" },
|
|
2872
|
+
{ path: "credentials", reason: "shown-once credentials are intentionally local" },
|
|
2873
|
+
{ path: "secrets", reason: "vault and Worker secrets are write-only" },
|
|
2874
|
+
{ path: "calendar.connection", reason: "OAuth connection state is provider-owned" },
|
|
2875
|
+
{ path: "service.config.opaque", reason: "controller-assigned fields remain service-owned" }
|
|
2876
|
+
]
|
|
2877
|
+
};
|
|
2878
|
+
function reconcileConfig(input) {
|
|
2879
|
+
const desiredSource = { kind: "project_config", location: input.configPath };
|
|
2880
|
+
const observedSource = {
|
|
2881
|
+
kind: "registry",
|
|
2882
|
+
location: `${input.platformUrl}/registry/apps/${encodeURIComponent(input.desired.appId)}`
|
|
2883
|
+
};
|
|
2884
|
+
const differences = [];
|
|
2885
|
+
const actions = [];
|
|
2886
|
+
const add = (difference2, action2) => {
|
|
2887
|
+
differences.push(difference2);
|
|
2888
|
+
if (action2) actions.push({ ...action2, id: actionId(action2) });
|
|
2889
|
+
};
|
|
2890
|
+
compareApp(input.desired, input.observed, desiredSource, observedSource, add);
|
|
2891
|
+
compareEnvironments(input.desired, input.observed, desiredSource, observedSource, add);
|
|
2892
|
+
const desiredRevision = configDigest(input.desired);
|
|
2893
|
+
const observedRevision = input.observed ? configDigest(observedProjection(input.observed)) : null;
|
|
2894
|
+
const different = differences.filter((entry) => entry.status === "different").length;
|
|
2895
|
+
const unmanaged = differences.length - different;
|
|
2896
|
+
return {
|
|
2897
|
+
generatedAt: input.generatedAt,
|
|
2898
|
+
scope: {
|
|
2899
|
+
appId: input.desired.appId,
|
|
2900
|
+
platformUrl: input.platformUrl,
|
|
2901
|
+
environments: Object.keys(input.desired.environments).sort()
|
|
2902
|
+
},
|
|
2903
|
+
sources: { desired: desiredSource, observed: observedSource },
|
|
2904
|
+
desiredRevision,
|
|
2905
|
+
observedRevision,
|
|
2906
|
+
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2907
|
+
summary: {
|
|
2908
|
+
differences: different,
|
|
2909
|
+
unmanaged,
|
|
2910
|
+
actions: actions.length,
|
|
2911
|
+
productionActions: actions.filter((action2) => action2.env === "prod" || action2.env === "production").length,
|
|
2912
|
+
highRiskActions: actions.filter((action2) => action2.risk === "high").length
|
|
2913
|
+
},
|
|
2914
|
+
coverage: COVERAGE,
|
|
2915
|
+
differences,
|
|
2916
|
+
actions
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
function compareApp(desired, observed, desiredSource, observedSource, add) {
|
|
2920
|
+
if (!observed) {
|
|
2921
|
+
const path = "app";
|
|
2922
|
+
add(
|
|
2923
|
+
difference(path, desired, null, "different", "the app is absent from Registry", desiredSource, observedSource),
|
|
2924
|
+
action(
|
|
2925
|
+
"create_app",
|
|
2926
|
+
path,
|
|
2927
|
+
null,
|
|
2928
|
+
{ appId: desired.appId, name: desired.name },
|
|
2929
|
+
"low",
|
|
2930
|
+
"provision",
|
|
2931
|
+
"create the repository-declared app"
|
|
2932
|
+
)
|
|
2933
|
+
);
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (observed.name !== desired.name) {
|
|
2937
|
+
const path = "app.name";
|
|
2938
|
+
add(
|
|
2939
|
+
difference(path, desired.name, observed.name, "different", "the checked-in display name differs", desiredSource, observedSource),
|
|
2940
|
+
{
|
|
2941
|
+
...action("rename_app", path, observed.name, desired.name, "low", "command", "make Registry match the checked-in display name"),
|
|
2942
|
+
command: `odla-ai app rename ${quoteArg(desired.name)} --config ${quoteArg(desiredSource.location)}`
|
|
2943
|
+
}
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
function compareEnvironments(desired, observed, desiredSource, observedSource, add) {
|
|
2948
|
+
const envs = /* @__PURE__ */ new Set([
|
|
2949
|
+
...Object.keys(desired.environments),
|
|
2950
|
+
...Object.keys(observed?.environments ?? {}).filter((env) => Object.values(observed?.environments[env] ?? {}).some((service) => service.enabled))
|
|
2951
|
+
]);
|
|
2952
|
+
for (const env of [...envs].sort()) {
|
|
2953
|
+
compareServices(env, desired, observed, desiredSource, observedSource, add);
|
|
2954
|
+
compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
2958
|
+
const wanted = desired.environments[env]?.services ?? {};
|
|
2959
|
+
const live = observed?.environments[env] ?? {};
|
|
2960
|
+
const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
|
|
2961
|
+
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
2962
|
+
for (const service of services) {
|
|
2963
|
+
const next = wanted[service];
|
|
2964
|
+
const current = live[service];
|
|
2965
|
+
const path = `environments.${env}.services.${service}`;
|
|
2966
|
+
if (next?.enabled && !current?.enabled) {
|
|
2967
|
+
add(
|
|
2968
|
+
difference(path, next, current ?? null, "different", "the repository enables a service Registry does not", desiredSource, observedSource, env, service),
|
|
2969
|
+
scopedAction("enable_service", path, env, service, current ?? null, next, "enable the repository-declared service")
|
|
2970
|
+
);
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
if (next?.enabled && current?.enabled && next.config) {
|
|
2974
|
+
const projected = projectConfig(current.config, Object.keys(next.config));
|
|
2975
|
+
if (!same(next.config, projected)) {
|
|
2976
|
+
add(
|
|
2977
|
+
difference(`${path}.config`, next.config, projected, "different", "repository-managed service settings differ", desiredSource, observedSource, env, service),
|
|
2978
|
+
scopedAction(
|
|
2979
|
+
"configure_service",
|
|
2980
|
+
`${path}.config`,
|
|
2981
|
+
env,
|
|
2982
|
+
service,
|
|
2983
|
+
projected,
|
|
2984
|
+
next.config,
|
|
2985
|
+
"apply the repository-managed service settings"
|
|
2986
|
+
)
|
|
2987
|
+
);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
for (const service of [...services].reverse()) {
|
|
2992
|
+
const next = wanted[service];
|
|
2993
|
+
const current = live[service];
|
|
2994
|
+
if (next?.enabled || !current?.enabled) continue;
|
|
2995
|
+
const path = `environments.${env}.services.${service}`;
|
|
2996
|
+
add(
|
|
2997
|
+
difference(path, null, { enabled: true }, "different", "Registry enables a service absent from repository intent", desiredSource, observedSource, env, service),
|
|
2998
|
+
scopedAction(
|
|
2999
|
+
"disable_service",
|
|
3000
|
+
path,
|
|
3001
|
+
env,
|
|
3002
|
+
service,
|
|
3003
|
+
{ enabled: true },
|
|
3004
|
+
{ enabled: false },
|
|
3005
|
+
"disablement is destructive to availability and requires an explicit Studio review",
|
|
3006
|
+
true
|
|
3007
|
+
)
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add) {
|
|
3012
|
+
const wanted = desired.environments[env];
|
|
3013
|
+
const liveAuth = observed?.auth[env] ?? null;
|
|
3014
|
+
if (wanted?.auth) {
|
|
3015
|
+
const projected = liveAuth ? projectConfig(liveAuth, Object.keys(wanted.auth)) : null;
|
|
3016
|
+
if (!same(wanted.auth, projected)) {
|
|
3017
|
+
const path = `environments.${env}.auth`;
|
|
3018
|
+
add(
|
|
3019
|
+
difference(path, wanted.auth, projected, "different", "explicit repository auth settings differ", desiredSource, observedSource, env),
|
|
3020
|
+
scopedAction("set_auth", path, env, void 0, projected, wanted.auth, "apply explicit repository auth settings")
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
} else if (liveAuth) {
|
|
3024
|
+
add(difference(
|
|
3025
|
+
`environments.${env}.auth`,
|
|
3026
|
+
null,
|
|
3027
|
+
projectConfig(liveAuth, ["publishableKey", "audience", "mode"]),
|
|
3028
|
+
"unmanaged",
|
|
3029
|
+
"live auth exists but this project config does not manage it",
|
|
3030
|
+
desiredSource,
|
|
3031
|
+
observedSource,
|
|
3032
|
+
env
|
|
3033
|
+
));
|
|
3034
|
+
}
|
|
3035
|
+
const managesLink = wanted && Object.hasOwn(wanted, "link");
|
|
3036
|
+
const liveLink = observed?.links[env] ?? null;
|
|
3037
|
+
if (managesLink && wanted.link !== liveLink) {
|
|
3038
|
+
const path = `environments.${env}.link`;
|
|
3039
|
+
add(
|
|
3040
|
+
difference(path, wanted.link ?? null, liveLink, "different", "explicit repository deployment link differs", desiredSource, observedSource, env),
|
|
3041
|
+
scopedAction("set_link", path, env, void 0, liveLink, wanted.link ?? null, "apply the repository deployment link")
|
|
3042
|
+
);
|
|
3043
|
+
} else if (!managesLink && liveLink) {
|
|
3044
|
+
add(difference(
|
|
3045
|
+
`environments.${env}.link`,
|
|
3046
|
+
null,
|
|
3047
|
+
liveLink,
|
|
3048
|
+
"unmanaged",
|
|
3049
|
+
"a live deployment link exists but this project config does not manage it",
|
|
3050
|
+
desiredSource,
|
|
3051
|
+
observedSource,
|
|
3052
|
+
env
|
|
3053
|
+
));
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
// src/config-reconcile-command.ts
|
|
3058
|
+
async function configDiff(options) {
|
|
3059
|
+
const reconciliation = await inspectConfig(options);
|
|
3060
|
+
const { actions: _actions, ...read3 } = reconciliation;
|
|
3061
|
+
const document2 = {
|
|
3062
|
+
schemaVersion: "odla.config-diff/v1",
|
|
3063
|
+
...read3,
|
|
3064
|
+
nextActions: diffNextActions(reconciliation, options.configPath)
|
|
3065
|
+
};
|
|
3066
|
+
printDiff(document2, options);
|
|
3067
|
+
return document2;
|
|
3068
|
+
}
|
|
3069
|
+
async function configPlan(options) {
|
|
3070
|
+
const reconciliation = await inspectConfig(options);
|
|
3071
|
+
const planDigest = configDigest({
|
|
3072
|
+
schemaVersion: "odla.config-plan/v1",
|
|
3073
|
+
desiredRevision: reconciliation.desiredRevision,
|
|
3074
|
+
observedRevision: reconciliation.observedRevision,
|
|
3075
|
+
actions: reconciliation.actions
|
|
3076
|
+
});
|
|
3077
|
+
const document2 = {
|
|
3078
|
+
schemaVersion: "odla.config-plan/v1",
|
|
3079
|
+
...reconciliation,
|
|
3080
|
+
planDigest,
|
|
3081
|
+
apply: {
|
|
3082
|
+
supported: false,
|
|
3083
|
+
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3084
|
+
},
|
|
3085
|
+
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3086
|
+
};
|
|
3087
|
+
printPlan2(document2, options);
|
|
3088
|
+
return document2;
|
|
3089
|
+
}
|
|
3090
|
+
async function inspectConfig(options) {
|
|
3091
|
+
const out = options.stdout ?? console;
|
|
3092
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3093
|
+
const doFetch = options.fetch ?? fetch;
|
|
3094
|
+
const token = await resolveAdminPlatformToken({
|
|
3095
|
+
platform: cfg.platformUrl,
|
|
3096
|
+
scope: "app:config:read",
|
|
3097
|
+
token: options.token,
|
|
3098
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3099
|
+
rootDir: cfg.rootDir,
|
|
3100
|
+
email: options.email,
|
|
3101
|
+
open: options.open,
|
|
3102
|
+
fetch: doFetch,
|
|
3103
|
+
stdout: out,
|
|
3104
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3105
|
+
label: `odla CLI (${cfg.app.id} config read)`
|
|
3106
|
+
});
|
|
3107
|
+
const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3108
|
+
const observed = await client.resolveApp(cfg.app.id);
|
|
3109
|
+
return reconcileConfig({
|
|
3110
|
+
desired: desiredRegistryState(cfg),
|
|
3111
|
+
observed,
|
|
3112
|
+
configPath: cfg.configPath,
|
|
3113
|
+
platformUrl: cfg.platformUrl,
|
|
3114
|
+
generatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
function printDiff(document2, options) {
|
|
3118
|
+
const out = options.stdout ?? console;
|
|
3119
|
+
if (options.json) {
|
|
3120
|
+
out.log(JSON.stringify(document2, null, 2));
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
printHeader(out, "diff", document2);
|
|
3124
|
+
printDifferences(out, document2);
|
|
3125
|
+
printNext(out, document2.nextActions);
|
|
3126
|
+
}
|
|
3127
|
+
function printPlan2(document2, options) {
|
|
3128
|
+
const out = options.stdout ?? console;
|
|
3129
|
+
if (options.json) {
|
|
3130
|
+
out.log(JSON.stringify(document2, null, 2));
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
printHeader(out, "plan", document2);
|
|
3134
|
+
for (const entry of document2.actions) {
|
|
3135
|
+
const scope = [entry.env, entry.service].filter(Boolean).join("/");
|
|
3136
|
+
out.log(
|
|
3137
|
+
` ${entry.id} ${entry.kind}${scope ? ` (${scope})` : ""} ${entry.risk}${entry.requiresApproval ? ", approval required" : ""}`
|
|
3138
|
+
);
|
|
3139
|
+
out.log(` ${entry.reason}`);
|
|
3140
|
+
}
|
|
3141
|
+
if (!document2.actions.length) out.log(" no managed changes");
|
|
3142
|
+
out.log(`plan digest: ${document2.planDigest}`);
|
|
3143
|
+
out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
|
|
3144
|
+
printNext(out, document2.nextActions);
|
|
3145
|
+
}
|
|
3146
|
+
function printHeader(out, kind, document2) {
|
|
3147
|
+
out.log(`config ${kind}: ${document2.scope.appId} \u2014 ${document2.status}`);
|
|
3148
|
+
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3149
|
+
out.log(`desired: ${document2.desiredRevision}`);
|
|
3150
|
+
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3151
|
+
out.log(
|
|
3152
|
+
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
function printDifferences(out, document2) {
|
|
3156
|
+
for (const entry of document2.differences) {
|
|
3157
|
+
out.log(` ${entry.status} ${entry.path} ${entry.reason}`);
|
|
3158
|
+
}
|
|
3159
|
+
if (!document2.differences.length) out.log(" managed Registry configuration is in sync");
|
|
3160
|
+
}
|
|
3161
|
+
function printNext(out, next) {
|
|
3162
|
+
if (!next.length) return;
|
|
3163
|
+
out.log("next:");
|
|
3164
|
+
for (const item of next) out.log(` ${item.command}
|
|
3165
|
+
${item.description}`);
|
|
3166
|
+
}
|
|
3167
|
+
function diffNextActions(reconciliation, configPath) {
|
|
3168
|
+
if (reconciliation.status === "in_sync") {
|
|
3169
|
+
return [{
|
|
3170
|
+
code: "verify_runtime",
|
|
3171
|
+
command: `odla-ai smoke --config ${quoteArg2(configPath)}`,
|
|
3172
|
+
description: "Registry intent matches; verify the service-owned data planes separately."
|
|
3173
|
+
}];
|
|
3174
|
+
}
|
|
3175
|
+
return [{
|
|
3176
|
+
code: "freeze_plan",
|
|
3177
|
+
command: `odla-ai config plan --config ${quoteArg2(configPath)} --json`,
|
|
3178
|
+
description: "Freeze the current desired and observed revisions into a reviewable action plan."
|
|
3179
|
+
}];
|
|
3180
|
+
}
|
|
3181
|
+
function planNextActions(reconciliation, configPath) {
|
|
3182
|
+
const next = [];
|
|
3183
|
+
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3184
|
+
next.push({
|
|
3185
|
+
code: "review_provision",
|
|
3186
|
+
command: `odla-ai provision --config ${quoteArg2(configPath)} --dry-run`,
|
|
3187
|
+
description: "Review the existing provisioner's service/auth/link work before applying it."
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
for (const action2 of reconciliation.actions.filter((entry) => entry.command)) {
|
|
3191
|
+
if (!next.some((entry) => entry.command === action2.command)) {
|
|
3192
|
+
next.push({ code: action2.kind, command: action2.command, description: action2.reason });
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3196
|
+
const { appId, platformUrl } = reconciliation.scope;
|
|
3197
|
+
next.push({
|
|
3198
|
+
code: "review_destructive",
|
|
3199
|
+
command: `${platformUrl}/studio/apps/${encodeURIComponent(appId)}/settings`,
|
|
3200
|
+
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3204
|
+
next.push({
|
|
3205
|
+
code: "declare_or_accept_runtime_state",
|
|
3206
|
+
command: `${reconciliation.scope.platformUrl}/studio/apps/${encodeURIComponent(reconciliation.scope.appId)}/settings`,
|
|
3207
|
+
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
return next;
|
|
3211
|
+
}
|
|
3212
|
+
function quoteArg2(value2) {
|
|
3213
|
+
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
2682
3216
|
// src/doctor-checks.ts
|
|
2683
3217
|
var import_node_child_process3 = require("child_process");
|
|
2684
3218
|
var import_node_fs10 = require("fs");
|
|
2685
|
-
var
|
|
3219
|
+
var import_node_path9 = require("path");
|
|
2686
3220
|
|
|
2687
3221
|
// src/wrangler.ts
|
|
2688
3222
|
var import_node_child_process2 = require("child_process");
|
|
2689
3223
|
var import_node_fs9 = require("fs");
|
|
2690
|
-
var
|
|
3224
|
+
var import_node_path8 = require("path");
|
|
2691
3225
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
2692
3226
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2693
3227
|
let stdout = "";
|
|
@@ -2701,7 +3235,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
2701
3235
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
2702
3236
|
function findWranglerConfig(rootDir) {
|
|
2703
3237
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
2704
|
-
const path = (0,
|
|
3238
|
+
const path = (0, import_node_path8.join)(rootDir, name);
|
|
2705
3239
|
if ((0, import_node_fs9.existsSync)(path)) return path;
|
|
2706
3240
|
}
|
|
2707
3241
|
return null;
|
|
@@ -2767,11 +3301,11 @@ function lintRules(rules, entities, publicRead) {
|
|
|
2767
3301
|
const warnings = [];
|
|
2768
3302
|
if (!rules) return warnings;
|
|
2769
3303
|
for (const [ns, actions] of Object.entries(rules)) {
|
|
2770
|
-
for (const [
|
|
3304
|
+
for (const [action2, expr] of Object.entries(actions)) {
|
|
2771
3305
|
if (typeof expr !== "string" || expr.trim() !== "true") continue;
|
|
2772
|
-
if (
|
|
3306
|
+
if (action2 === "view" && publicRead.includes(ns)) continue;
|
|
2773
3307
|
warnings.push(
|
|
2774
|
-
|
|
3308
|
+
action2 === "view" ? `rules.${ns}.view is "true" \u2014 public read; add "${ns}" to db.publicRead if intended` : `rules.${ns}.${action2} is "true" \u2014 any client can ${action2} ${ns} rows`
|
|
2775
3309
|
);
|
|
2776
3310
|
}
|
|
2777
3311
|
}
|
|
@@ -2807,17 +3341,17 @@ function wranglerWarnings(rootDir) {
|
|
|
2807
3341
|
for (const { label, block } of blocks) {
|
|
2808
3342
|
const assets = block.assets;
|
|
2809
3343
|
if (assets?.directory) {
|
|
2810
|
-
const dir = (0,
|
|
2811
|
-
if (dir === (0,
|
|
3344
|
+
const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
|
|
3345
|
+
if (dir === (0, import_node_path9.resolve)(rootDir)) {
|
|
2812
3346
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
2813
|
-
} else if ((0, import_node_fs10.existsSync)((0,
|
|
3347
|
+
} else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
|
|
2814
3348
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
2815
3349
|
}
|
|
2816
3350
|
}
|
|
2817
3351
|
const vars = block.vars;
|
|
2818
3352
|
if (vars && typeof vars === "object") {
|
|
2819
|
-
for (const [name,
|
|
2820
|
-
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof
|
|
3353
|
+
for (const [name, value2] of Object.entries(vars)) {
|
|
3354
|
+
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
|
|
2821
3355
|
warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
|
|
2822
3356
|
}
|
|
2823
3357
|
}
|
|
@@ -2845,7 +3379,7 @@ function o11yProjectWarnings(rootDir) {
|
|
|
2845
3379
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
2846
3380
|
return warnings;
|
|
2847
3381
|
}
|
|
2848
|
-
const main = typeof config.main === "string" ? (0,
|
|
3382
|
+
const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
|
|
2849
3383
|
if (!main || !(0, import_node_fs10.existsSync)(main)) {
|
|
2850
3384
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
2851
3385
|
} else {
|
|
@@ -2875,7 +3409,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
2875
3409
|
}
|
|
2876
3410
|
function readPackageJson(rootDir) {
|
|
2877
3411
|
try {
|
|
2878
|
-
return JSON.parse((0, import_node_fs10.readFileSync)((0,
|
|
3412
|
+
return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
|
|
2879
3413
|
} catch {
|
|
2880
3414
|
return null;
|
|
2881
3415
|
}
|
|
@@ -2931,8 +3465,8 @@ function integrationWarnings(integrations, schema, rules) {
|
|
|
2931
3465
|
warnings.push(`integration "${integration.id}" has no rules for "${ns}"`);
|
|
2932
3466
|
continue;
|
|
2933
3467
|
}
|
|
2934
|
-
for (const
|
|
2935
|
-
if (rule[
|
|
3468
|
+
for (const action2 of ["view", "create", "update", "delete"]) {
|
|
3469
|
+
if (rule[action2] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action2}" is missing`);
|
|
2936
3470
|
}
|
|
2937
3471
|
}
|
|
2938
3472
|
for (const seed of integration.seeds ?? []) {
|
|
@@ -2985,27 +3519,27 @@ function isUniqueAttr(schema, ns, attr) {
|
|
|
2985
3519
|
const definition = entity.attrs[attr];
|
|
2986
3520
|
return isRecord6(definition) && definition.unique === true;
|
|
2987
3521
|
}
|
|
2988
|
-
function normalizeSchema(
|
|
2989
|
-
if (
|
|
2990
|
-
if (!isRecord6(
|
|
3522
|
+
function normalizeSchema(value2) {
|
|
3523
|
+
if (value2 === void 0 || value2 === null) return { entities: {}, links: {} };
|
|
3524
|
+
if (!isRecord6(value2) || !isRecord6(value2.entities)) {
|
|
2991
3525
|
throw new Error("db schema must be a serialized schema object with an entities map");
|
|
2992
3526
|
}
|
|
2993
|
-
if (
|
|
3527
|
+
if (value2.links !== void 0 && !isRecord6(value2.links)) throw new Error("db schema links must be an object");
|
|
2994
3528
|
return {
|
|
2995
|
-
entities: { ...
|
|
2996
|
-
links: { ...
|
|
3529
|
+
entities: { ...value2.entities },
|
|
3530
|
+
links: { ...value2.links ?? {} }
|
|
2997
3531
|
};
|
|
2998
3532
|
}
|
|
2999
3533
|
function mergeMap(target, fragment, label) {
|
|
3000
|
-
for (const [name,
|
|
3001
|
-
if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name],
|
|
3534
|
+
for (const [name, value2] of Object.entries(fragment)) {
|
|
3535
|
+
if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value2)) {
|
|
3002
3536
|
throw new Error(`${label} "${name}" conflicts with an existing definition`);
|
|
3003
3537
|
}
|
|
3004
|
-
target[name] =
|
|
3538
|
+
target[name] = value2;
|
|
3005
3539
|
}
|
|
3006
3540
|
}
|
|
3007
|
-
function isRecord6(
|
|
3008
|
-
return
|
|
3541
|
+
function isRecord6(value2) {
|
|
3542
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
3009
3543
|
}
|
|
3010
3544
|
|
|
3011
3545
|
// src/doctor.ts
|
|
@@ -3046,9 +3580,9 @@ async function doctor(options) {
|
|
|
3046
3580
|
warnings.push(...integrationWarnings(database.integrations, schema, rules));
|
|
3047
3581
|
if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
|
|
3048
3582
|
if (cfg.auth?.clerk) {
|
|
3049
|
-
for (const [env,
|
|
3050
|
-
if (typeof
|
|
3051
|
-
warnings.push(`auth.clerk.${env} references unset env var ${
|
|
3583
|
+
for (const [env, value2] of Object.entries(cfg.auth.clerk)) {
|
|
3584
|
+
if (typeof value2 === "string" && value2.startsWith("$") && !process.env[value2.slice(1)]) {
|
|
3585
|
+
warnings.push(`auth.clerk.${env} references unset env var ${value2}`);
|
|
3052
3586
|
}
|
|
3053
3587
|
}
|
|
3054
3588
|
}
|
|
@@ -3090,10 +3624,10 @@ function harnessList(parsed) {
|
|
|
3090
3624
|
];
|
|
3091
3625
|
return selected.length ? selected : ["all"];
|
|
3092
3626
|
}
|
|
3093
|
-
function harnessOption(
|
|
3094
|
-
if (
|
|
3095
|
-
if (typeof
|
|
3096
|
-
const raw = Array.isArray(
|
|
3627
|
+
function harnessOption(value2, flag) {
|
|
3628
|
+
if (value2 === void 0) return [];
|
|
3629
|
+
if (typeof value2 === "boolean") throw new Error(`${flag} requires a harness name`);
|
|
3630
|
+
const raw = Array.isArray(value2) ? value2 : [value2];
|
|
3097
3631
|
const parts = raw.flatMap((item) => item.split(","));
|
|
3098
3632
|
if (parts.some((item) => !item.trim())) {
|
|
3099
3633
|
throw new Error(`${flag} requires a harness name`);
|
|
@@ -3103,12 +3637,12 @@ function harnessOption(value, flag) {
|
|
|
3103
3637
|
|
|
3104
3638
|
// src/init.ts
|
|
3105
3639
|
var import_node_fs11 = require("fs");
|
|
3106
|
-
var
|
|
3107
|
-
var
|
|
3640
|
+
var import_node_path10 = require("path");
|
|
3641
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
3108
3642
|
function initProject(options) {
|
|
3109
3643
|
const out = options.stdout ?? console;
|
|
3110
|
-
const rootDir = (0,
|
|
3111
|
-
const configPath = (0,
|
|
3644
|
+
const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
|
|
3645
|
+
const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
3112
3646
|
if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
|
|
3113
3647
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3114
3648
|
}
|
|
@@ -3118,19 +3652,19 @@ function initProject(options) {
|
|
|
3118
3652
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3119
3653
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3120
3654
|
for (const service of services) {
|
|
3121
|
-
const definition = (0,
|
|
3122
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
3655
|
+
const definition = (0, import_apps7.appServiceDefinition)(service);
|
|
3656
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
|
|
3123
3657
|
for (const dependency of definition.requires) {
|
|
3124
3658
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3125
3659
|
}
|
|
3126
3660
|
}
|
|
3127
3661
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3128
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3129
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3130
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3662
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
|
|
3663
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
3664
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
|
|
3131
3665
|
(0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
3132
|
-
writeIfMissing((0,
|
|
3133
|
-
writeIfMissing((0,
|
|
3666
|
+
writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
3667
|
+
writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3134
3668
|
ensureGitignore(rootDir);
|
|
3135
3669
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3136
3670
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
@@ -3312,8 +3846,8 @@ function assertWranglerConfig(cfg) {
|
|
|
3312
3846
|
}
|
|
3313
3847
|
|
|
3314
3848
|
// src/secrets-set.ts
|
|
3315
|
-
var
|
|
3316
|
-
var
|
|
3849
|
+
var import_ai2 = require("@odla-ai/ai");
|
|
3850
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3317
3851
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3318
3852
|
async function secretsSet(options) {
|
|
3319
3853
|
const name = (options.name ?? "").trim();
|
|
@@ -3321,38 +3855,38 @@ async function secretsSet(options) {
|
|
|
3321
3855
|
if (name.startsWith("$")) {
|
|
3322
3856
|
throw new Error('"$"-prefixed vault names are platform-reserved; for the Clerk secret key use "odla-ai secrets set-clerk-key"');
|
|
3323
3857
|
}
|
|
3324
|
-
const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
|
|
3858
|
+
const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
|
|
3325
3859
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
3326
3860
|
try {
|
|
3327
|
-
await (0,
|
|
3861
|
+
await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
3328
3862
|
} catch (err) {
|
|
3329
|
-
throw new Error(scrubValue(err instanceof Error ? err.message : String(err),
|
|
3863
|
+
throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
|
|
3330
3864
|
}
|
|
3331
3865
|
out.log(`${name} stored in the ${tenantId} vault (write-only; the value was never echoed and cannot be read back here)`);
|
|
3332
3866
|
}
|
|
3333
3867
|
async function secretsSetClerkKey(options) {
|
|
3334
|
-
const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
|
|
3335
|
-
if (!
|
|
3336
|
-
if (
|
|
3868
|
+
const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
|
|
3869
|
+
if (!value2.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
|
|
3870
|
+
if (value2.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
|
|
3337
3871
|
throw new Error(`refusing to store an sk_test_ Clerk key for "${options.env}" \u2014 use the production instance's sk_live_ key`);
|
|
3338
3872
|
}
|
|
3339
|
-
if (
|
|
3873
|
+
if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
3340
3874
|
throw new Error(`refusing to store an sk_live_ Clerk key for "${options.env}" without --yes (live users would sync into a non-prod tenant)`);
|
|
3341
3875
|
}
|
|
3342
3876
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
3343
3877
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
|
|
3344
3878
|
method: "POST",
|
|
3345
3879
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
3346
|
-
body: JSON.stringify({ value })
|
|
3880
|
+
body: JSON.stringify({ value: value2 })
|
|
3347
3881
|
});
|
|
3348
3882
|
if (!res.ok) {
|
|
3349
|
-
const text = scrubValue((await res.text().catch(() => "")).slice(0, 300),
|
|
3883
|
+
const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
3350
3884
|
throw new Error(`store Clerk secret key failed (${res.status}): ${text || "request failed"}`);
|
|
3351
3885
|
}
|
|
3352
3886
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
3353
3887
|
}
|
|
3354
|
-
function scrubValue(text,
|
|
3355
|
-
return redactSecrets(text).split(
|
|
3888
|
+
function scrubValue(text, value2) {
|
|
3889
|
+
return redactSecrets(text).split(value2).join("[value redacted]");
|
|
3356
3890
|
}
|
|
3357
3891
|
async function resolveVaultWrite(options) {
|
|
3358
3892
|
const out = options.stdout ?? console;
|
|
@@ -3364,14 +3898,14 @@ async function resolveVaultWrite(options) {
|
|
|
3364
3898
|
if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
3365
3899
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3366
3900
|
}
|
|
3367
|
-
const
|
|
3368
|
-
return { cfg, tenantId: (0,
|
|
3901
|
+
const value2 = await secretInputValue(options, "secret");
|
|
3902
|
+
return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3369
3903
|
}
|
|
3370
3904
|
|
|
3371
3905
|
// src/skill.ts
|
|
3372
3906
|
var import_node_fs12 = require("fs");
|
|
3373
3907
|
var import_node_os2 = require("os");
|
|
3374
|
-
var
|
|
3908
|
+
var import_node_path11 = require("path");
|
|
3375
3909
|
var import_node_url2 = require("url");
|
|
3376
3910
|
|
|
3377
3911
|
// src/skill-adapters.ts
|
|
@@ -3450,8 +3984,8 @@ function installSkill(options = {}) {
|
|
|
3450
3984
|
const files = listFiles(sourceDir);
|
|
3451
3985
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
3452
3986
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
3453
|
-
const root = (0,
|
|
3454
|
-
const home = (0,
|
|
3987
|
+
const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
|
|
3988
|
+
const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
3455
3989
|
const plans = /* @__PURE__ */ new Map();
|
|
3456
3990
|
const targets = /* @__PURE__ */ new Map();
|
|
3457
3991
|
const rememberTarget = (harness, target) => {
|
|
@@ -3465,48 +3999,48 @@ function installSkill(options = {}) {
|
|
|
3465
3999
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
3466
4000
|
};
|
|
3467
4001
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
3468
|
-
for (const rel of files) plan((0,
|
|
4002
|
+
for (const rel of files) plan((0, import_node_path11.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
3469
4003
|
};
|
|
3470
4004
|
let targetDir;
|
|
3471
4005
|
if (options.global) {
|
|
3472
|
-
const claudeRoot = (0,
|
|
3473
|
-
const codexRoot = (0,
|
|
4006
|
+
const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
|
|
4007
|
+
const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
|
|
3474
4008
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
3475
4009
|
for (const harness of harnesses) {
|
|
3476
4010
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
3477
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4011
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
|
|
3478
4012
|
rememberTarget(harness, skillRoot);
|
|
3479
4013
|
}
|
|
3480
4014
|
} else {
|
|
3481
|
-
const sharedRoot = (0,
|
|
4015
|
+
const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
|
|
3482
4016
|
planSkillTree(sharedRoot);
|
|
3483
|
-
const claudeRoot = (0,
|
|
4017
|
+
const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
|
|
3484
4018
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
3485
4019
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
3486
4020
|
if (harnesses.includes("claude")) {
|
|
3487
4021
|
for (const skill of skillNames(files)) {
|
|
3488
|
-
const canonical = (0, import_node_fs12.readFileSync)((0,
|
|
3489
|
-
plan((0,
|
|
4022
|
+
const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4023
|
+
plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
3490
4024
|
}
|
|
3491
4025
|
rememberTarget("claude", claudeRoot);
|
|
3492
4026
|
}
|
|
3493
4027
|
if (harnesses.includes("cursor")) {
|
|
3494
|
-
const cursorRule = (0,
|
|
4028
|
+
const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
|
|
3495
4029
|
plan(cursorRule, CURSOR_RULE);
|
|
3496
4030
|
rememberTarget("cursor", cursorRule);
|
|
3497
4031
|
}
|
|
3498
4032
|
if (harnesses.includes("agents")) {
|
|
3499
|
-
const agentsFile = (0,
|
|
4033
|
+
const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
|
|
3500
4034
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3501
4035
|
rememberTarget("agents", agentsFile);
|
|
3502
4036
|
}
|
|
3503
4037
|
if (harnesses.includes("copilot")) {
|
|
3504
|
-
const copilotFile = (0,
|
|
4038
|
+
const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
|
|
3505
4039
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3506
4040
|
rememberTarget("copilot", copilotFile);
|
|
3507
4041
|
}
|
|
3508
4042
|
if (harnesses.includes("gemini")) {
|
|
3509
|
-
const geminiFile = (0,
|
|
4043
|
+
const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
|
|
3510
4044
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3511
4045
|
rememberTarget("gemini", geminiFile);
|
|
3512
4046
|
}
|
|
@@ -3542,7 +4076,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3542
4076
|
}
|
|
3543
4077
|
for (const file of plans.values()) {
|
|
3544
4078
|
if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
|
|
3545
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
4079
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
|
|
3546
4080
|
(0, import_node_fs12.writeFileSync)(file.target, file.content);
|
|
3547
4081
|
}
|
|
3548
4082
|
}
|
|
@@ -3562,7 +4096,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3562
4096
|
};
|
|
3563
4097
|
}
|
|
3564
4098
|
function pathsUnder(root, paths) {
|
|
3565
|
-
return [...paths].map((path) => (0,
|
|
4099
|
+
return [...paths].map((path) => (0, import_node_path11.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path11.sep}`) && !(0, import_node_path11.isAbsolute)(path)).sort();
|
|
3566
4100
|
}
|
|
3567
4101
|
function normalizeHarnesses(values, global) {
|
|
3568
4102
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -3607,13 +4141,13 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
3607
4141
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
3608
4142
|
}
|
|
3609
4143
|
function symlinkedComponent(boundary, target) {
|
|
3610
|
-
const rel = (0,
|
|
3611
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4144
|
+
const rel = (0, import_node_path11.relative)(boundary, target);
|
|
4145
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
|
|
3612
4146
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
3613
4147
|
}
|
|
3614
4148
|
let current = boundary;
|
|
3615
|
-
for (const part of rel.split(
|
|
3616
|
-
current = (0,
|
|
4149
|
+
for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
|
|
4150
|
+
current = (0, import_node_path11.join)(current, part);
|
|
3617
4151
|
try {
|
|
3618
4152
|
if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
|
|
3619
4153
|
} catch (error) {
|
|
@@ -3630,9 +4164,9 @@ function listFiles(dir) {
|
|
|
3630
4164
|
const results = [];
|
|
3631
4165
|
const walk = (current) => {
|
|
3632
4166
|
for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
|
|
3633
|
-
const path = (0,
|
|
4167
|
+
const path = (0, import_node_path11.join)(current, entry.name);
|
|
3634
4168
|
if (entry.isDirectory()) walk(path);
|
|
3635
|
-
else results.push((0,
|
|
4169
|
+
else results.push((0, import_node_path11.relative)(dir, path));
|
|
3636
4170
|
}
|
|
3637
4171
|
};
|
|
3638
4172
|
walk(dir);
|
|
@@ -3708,7 +4242,7 @@ async function smoke(options) {
|
|
|
3708
4242
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
3709
4243
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
3710
4244
|
if (aggregateEntity) {
|
|
3711
|
-
const aggregate = await
|
|
4245
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
|
|
3712
4246
|
ns: aggregateEntity,
|
|
3713
4247
|
aggregate: { count: true }
|
|
3714
4248
|
});
|
|
@@ -3752,16 +4286,16 @@ async function getJson(doFetch, url, bearer) {
|
|
|
3752
4286
|
const res = await doFetch(url, {
|
|
3753
4287
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
3754
4288
|
});
|
|
3755
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4289
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
|
|
3756
4290
|
return res.json();
|
|
3757
4291
|
}
|
|
3758
|
-
async function
|
|
4292
|
+
async function postJson2(doFetch, url, bearer, body) {
|
|
3759
4293
|
const res = await doFetch(url, {
|
|
3760
4294
|
method: "POST",
|
|
3761
4295
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
3762
4296
|
body: JSON.stringify(body)
|
|
3763
4297
|
});
|
|
3764
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4298
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
|
|
3765
4299
|
return res.json();
|
|
3766
4300
|
}
|
|
3767
4301
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -3769,7 +4303,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
3769
4303
|
url.searchParams.set("env", env);
|
|
3770
4304
|
return url.toString();
|
|
3771
4305
|
}
|
|
3772
|
-
async function
|
|
4306
|
+
async function safeText4(res) {
|
|
3773
4307
|
try {
|
|
3774
4308
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
3775
4309
|
} catch {
|
|
@@ -3825,6 +4359,26 @@ async function secretsCommand(parsed, deps) {
|
|
|
3825
4359
|
});
|
|
3826
4360
|
}
|
|
3827
4361
|
async function projectCommand(command, parsed, deps) {
|
|
4362
|
+
if (command === "config") {
|
|
4363
|
+
const sub = parsed.positionals[1];
|
|
4364
|
+
if (sub !== "diff" && sub !== "plan") {
|
|
4365
|
+
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4366
|
+
}
|
|
4367
|
+
assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
|
|
4368
|
+
const options = {
|
|
4369
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4370
|
+
token: stringOpt(parsed.options.token),
|
|
4371
|
+
email: stringOpt(parsed.options.email),
|
|
4372
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4373
|
+
json: parsed.options.json === true,
|
|
4374
|
+
fetch: deps.fetch,
|
|
4375
|
+
openApprovalUrl: deps.openUrl,
|
|
4376
|
+
stdout: deps.stdout
|
|
4377
|
+
};
|
|
4378
|
+
if (sub === "diff") await configDiff(options);
|
|
4379
|
+
else await configPlan(options);
|
|
4380
|
+
return true;
|
|
4381
|
+
}
|
|
3828
4382
|
if (command === "init") {
|
|
3829
4383
|
assertArgs(parsed, ["app-id", "name", "config", "env", "services", "ai-provider", "force"], 1);
|
|
3830
4384
|
initProject({
|
|
@@ -3885,7 +4439,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
3885
4439
|
// src/code-connect.ts
|
|
3886
4440
|
var import_node_fs14 = require("fs");
|
|
3887
4441
|
var import_node_os4 = require("os");
|
|
3888
|
-
var
|
|
4442
|
+
var import_node_path13 = require("path");
|
|
3889
4443
|
|
|
3890
4444
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
3891
4445
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -3895,24 +4449,24 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
3895
4449
|
var HarnessProtocolError = class extends Error {
|
|
3896
4450
|
name = "HarnessProtocolError";
|
|
3897
4451
|
};
|
|
3898
|
-
function record2(
|
|
3899
|
-
return
|
|
4452
|
+
function record2(value2) {
|
|
4453
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
3900
4454
|
}
|
|
3901
|
-
function boundedText(
|
|
3902
|
-
if (typeof
|
|
4455
|
+
function boundedText(value2, label, max) {
|
|
4456
|
+
if (typeof value2 !== "string" || !value2 || value2.length > max || CONTROL.test(value2)) {
|
|
3903
4457
|
throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
|
|
3904
4458
|
}
|
|
3905
|
-
return
|
|
4459
|
+
return value2;
|
|
3906
4460
|
}
|
|
3907
4461
|
function parseAgentOutput(line) {
|
|
3908
4462
|
if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
|
|
3909
|
-
let
|
|
4463
|
+
let value2;
|
|
3910
4464
|
try {
|
|
3911
|
-
|
|
4465
|
+
value2 = JSON.parse(line);
|
|
3912
4466
|
} catch {
|
|
3913
4467
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
3914
4468
|
}
|
|
3915
|
-
const message2 = record2(
|
|
4469
|
+
const message2 = record2(value2);
|
|
3916
4470
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
3917
4471
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
3918
4472
|
}
|
|
@@ -4221,7 +4775,7 @@ async function gitOutput(cwd, args, maxBytes) {
|
|
|
4221
4775
|
else stdout.push(chunk);
|
|
4222
4776
|
});
|
|
4223
4777
|
child.stderr.on("data", (chunk) => {
|
|
4224
|
-
if (stderr.reduce((sum,
|
|
4778
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4225
4779
|
});
|
|
4226
4780
|
const code = await new Promise((accept, reject) => {
|
|
4227
4781
|
child.once("error", reject);
|
|
@@ -4242,7 +4796,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
4242
4796
|
else stdout.push(chunk);
|
|
4243
4797
|
});
|
|
4244
4798
|
child.stderr.on("data", (chunk) => {
|
|
4245
|
-
if (stderr.reduce((sum,
|
|
4799
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4246
4800
|
});
|
|
4247
4801
|
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
4248
4802
|
`);
|
|
@@ -4280,8 +4834,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4280
4834
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4281
4835
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4282
4836
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4283
|
-
const entries = inventory.flatMap((
|
|
4284
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
4837
|
+
const entries = inventory.flatMap((record8) => {
|
|
4838
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
|
|
4285
4839
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4286
4840
|
});
|
|
4287
4841
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -4356,7 +4910,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
4356
4910
|
else stdout.push(chunk);
|
|
4357
4911
|
});
|
|
4358
4912
|
child.stderr.on("data", (chunk) => {
|
|
4359
|
-
if (stderr.reduce((sum,
|
|
4913
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4360
4914
|
});
|
|
4361
4915
|
const code = await new Promise((accept, reject) => {
|
|
4362
4916
|
child.once("error", reject);
|
|
@@ -4416,7 +4970,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
4416
4970
|
else stdout.push(chunk);
|
|
4417
4971
|
});
|
|
4418
4972
|
child.stderr.on("data", (chunk) => {
|
|
4419
|
-
if (stderr.reduce((sum,
|
|
4973
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4420
4974
|
});
|
|
4421
4975
|
const code = await new Promise((accept, reject) => {
|
|
4422
4976
|
child.once("error", reject);
|
|
@@ -4503,34 +5057,34 @@ var CamelError = class extends Error {
|
|
|
4503
5057
|
};
|
|
4504
5058
|
|
|
4505
5059
|
// ../camel/dist/chunk-L5DYU2E2.js
|
|
4506
|
-
function
|
|
4507
|
-
return JSON.stringify(normalize(
|
|
5060
|
+
function canonicalJson2(value2) {
|
|
5061
|
+
return JSON.stringify(normalize(value2));
|
|
4508
5062
|
}
|
|
4509
|
-
async function sha256Hex(
|
|
4510
|
-
const bytes = typeof
|
|
5063
|
+
async function sha256Hex(value2) {
|
|
5064
|
+
const bytes = typeof value2 === "string" ? new TextEncoder().encode(value2) : value2;
|
|
4511
5065
|
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
4512
5066
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4513
5067
|
}
|
|
4514
|
-
function utf8Length(
|
|
4515
|
-
if (typeof
|
|
4516
|
-
if (
|
|
5068
|
+
function utf8Length(value2) {
|
|
5069
|
+
if (typeof value2 === "string") return new TextEncoder().encode(value2).byteLength;
|
|
5070
|
+
if (value2 instanceof Uint8Array) return value2.byteLength;
|
|
4517
5071
|
try {
|
|
4518
|
-
return new TextEncoder().encode(
|
|
5072
|
+
return new TextEncoder().encode(canonicalJson2(value2)).byteLength;
|
|
4519
5073
|
} catch {
|
|
4520
5074
|
throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
|
|
4521
5075
|
}
|
|
4522
5076
|
}
|
|
4523
|
-
function normalize(
|
|
4524
|
-
if (
|
|
4525
|
-
if (typeof
|
|
4526
|
-
if (!Number.isFinite(
|
|
4527
|
-
return
|
|
5077
|
+
function normalize(value2) {
|
|
5078
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
5079
|
+
if (typeof value2 === "number") {
|
|
5080
|
+
if (!Number.isFinite(value2)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
|
|
5081
|
+
return value2;
|
|
4528
5082
|
}
|
|
4529
|
-
if (Array.isArray(
|
|
4530
|
-
if (
|
|
4531
|
-
if (typeof
|
|
4532
|
-
const
|
|
4533
|
-
return Object.fromEntries(Object.keys(
|
|
5083
|
+
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5084
|
+
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5085
|
+
if (typeof value2 === "object") {
|
|
5086
|
+
const record8 = value2;
|
|
5087
|
+
return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
|
|
4534
5088
|
}
|
|
4535
5089
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
4536
5090
|
}
|
|
@@ -4539,10 +5093,10 @@ function canRead(readers, readerId) {
|
|
|
4539
5093
|
}
|
|
4540
5094
|
function dependenciesOf(values, influence = "data") {
|
|
4541
5095
|
const result = [];
|
|
4542
|
-
for (const
|
|
4543
|
-
result.push(...
|
|
4544
|
-
for (const ref of
|
|
4545
|
-
result.push({ ref, influence, promptSafetyAtUse:
|
|
5096
|
+
for (const value2 of values) {
|
|
5097
|
+
result.push(...value2.label.dependencies);
|
|
5098
|
+
for (const ref of value2.label.provenance) {
|
|
5099
|
+
result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
|
|
4546
5100
|
}
|
|
4547
5101
|
}
|
|
4548
5102
|
const unique3 = /* @__PURE__ */ new Map();
|
|
@@ -4553,32 +5107,32 @@ function dependenciesOf(values, influence = "data") {
|
|
|
4553
5107
|
// ../camel/dist/chunk-S7EVNA2U.js
|
|
4554
5108
|
var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
|
|
4555
5109
|
var authenticCamelValues = /* @__PURE__ */ new WeakSet();
|
|
4556
|
-
function isCamelValue(
|
|
4557
|
-
return typeof
|
|
5110
|
+
function isCamelValue(value2) {
|
|
5111
|
+
return typeof value2 === "object" && value2 !== null && authenticCamelValues.has(value2);
|
|
4558
5112
|
}
|
|
4559
|
-
function isSafe(
|
|
4560
|
-
return isCamelValue(
|
|
5113
|
+
function isSafe(value2) {
|
|
5114
|
+
return isCamelValue(value2) && value2.label.promptSafety === "safe";
|
|
4561
5115
|
}
|
|
4562
|
-
function isUnsafe(
|
|
4563
|
-
return isCamelValue(
|
|
5116
|
+
function isUnsafe(value2) {
|
|
5117
|
+
return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
|
|
4564
5118
|
}
|
|
4565
|
-
function createSafeInternal(
|
|
4566
|
-
return createValue(
|
|
5119
|
+
function createSafeInternal(value2, safeBasis, metadata2) {
|
|
5120
|
+
return createValue(value2, {
|
|
4567
5121
|
schemaVersion: 1,
|
|
4568
5122
|
promptSafety: "safe",
|
|
4569
5123
|
safeBasis,
|
|
4570
5124
|
...copyMetadata(metadata2)
|
|
4571
5125
|
});
|
|
4572
5126
|
}
|
|
4573
|
-
function createUnsafeInternal(
|
|
4574
|
-
return createValue(
|
|
5127
|
+
function createUnsafeInternal(value2, metadata2) {
|
|
5128
|
+
return createValue(value2, {
|
|
4575
5129
|
schemaVersion: 1,
|
|
4576
5130
|
promptSafety: "unsafe",
|
|
4577
5131
|
...copyMetadata(metadata2)
|
|
4578
5132
|
});
|
|
4579
5133
|
}
|
|
4580
|
-
function createValue(
|
|
4581
|
-
const result = { value, label: Object.freeze(label) };
|
|
5134
|
+
function createValue(value2, label) {
|
|
5135
|
+
const result = { value: value2, label: Object.freeze(label) };
|
|
4582
5136
|
Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
|
|
4583
5137
|
authenticCamelValues.add(result);
|
|
4584
5138
|
return Object.freeze(result);
|
|
@@ -4641,7 +5195,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
4641
5195
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
4642
5196
|
outcome: fields.outcome
|
|
4643
5197
|
};
|
|
4644
|
-
return `sha256:${await sha256Hex(
|
|
5198
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
4645
5199
|
}
|
|
4646
5200
|
function validate(fields) {
|
|
4647
5201
|
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST.test(fields.trustedBaseDigest) || !DIGEST.test(fields.patchDigest) || !DIGEST.test(fields.candidateDigest) || !DIGEST.test(fields.sourceDigest) || !DIGEST.test(fields.policyDigest) || !DIGEST.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
|
|
@@ -4679,13 +5233,13 @@ async function createCodePortableCheckpoint(input) {
|
|
|
4679
5233
|
const state2 = normalizeState(input.state);
|
|
4680
5234
|
validateBaseAndPatch(input.baseCommitSha, input.patch);
|
|
4681
5235
|
const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
|
|
4682
|
-
const stateDigest = `sha256:${await sha256Hex(
|
|
5236
|
+
const stateDigest = `sha256:${await sha256Hex(canonicalJson2(state2))}`;
|
|
4683
5237
|
const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state: state2, stateDigest };
|
|
4684
|
-
const checkpointDigest = `sha256:${await sha256Hex(
|
|
5238
|
+
const checkpointDigest = `sha256:${await sha256Hex(canonicalJson2(fields))}`;
|
|
4685
5239
|
return freeze({ ...fields, patch: input.patch, checkpointDigest });
|
|
4686
5240
|
}
|
|
4687
|
-
async function verifyCodePortableCheckpoint(
|
|
4688
|
-
const input = object(
|
|
5241
|
+
async function verifyCodePortableCheckpoint(value2) {
|
|
5242
|
+
const input = object(value2, "checkpoint");
|
|
4689
5243
|
exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
|
|
4690
5244
|
if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
|
|
4691
5245
|
throw invalid2("Portable checkpoint fields are malformed.");
|
|
@@ -4700,8 +5254,8 @@ async function verifyCodePortableCheckpoint(value) {
|
|
|
4700
5254
|
}
|
|
4701
5255
|
return created;
|
|
4702
5256
|
}
|
|
4703
|
-
function normalizeState(
|
|
4704
|
-
const state2 = object(
|
|
5257
|
+
function normalizeState(value2) {
|
|
5258
|
+
const state2 = object(value2, "state");
|
|
4705
5259
|
exact2(state2, [
|
|
4706
5260
|
"planCursor",
|
|
4707
5261
|
"conversationRefs",
|
|
@@ -4759,30 +5313,30 @@ function validateBaseAndPatch(baseCommitSha, patch2) {
|
|
|
4759
5313
|
throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
|
|
4760
5314
|
}
|
|
4761
5315
|
}
|
|
4762
|
-
function strings(
|
|
4763
|
-
if (!Array.isArray(
|
|
5316
|
+
function strings(value2, name, pattern, maximum, sort) {
|
|
5317
|
+
if (!Array.isArray(value2) || value2.length > maximum || value2.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value2).size !== value2.length) {
|
|
4764
5318
|
throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
|
|
4765
5319
|
}
|
|
4766
|
-
const result = [...
|
|
5320
|
+
const result = [...value2];
|
|
4767
5321
|
return sort ? result.sort() : result;
|
|
4768
5322
|
}
|
|
4769
|
-
function object(
|
|
4770
|
-
if (!
|
|
4771
|
-
return
|
|
5323
|
+
function object(value2, name) {
|
|
5324
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw invalid2(`Portable ${name} must be an object.`);
|
|
5325
|
+
return value2;
|
|
4772
5326
|
}
|
|
4773
|
-
function exact2(
|
|
4774
|
-
if (Object.keys(
|
|
5327
|
+
function exact2(value2, keys) {
|
|
5328
|
+
if (Object.keys(value2).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value2))) {
|
|
4775
5329
|
throw invalid2("Portable checkpoint contains missing or unsupported fields.");
|
|
4776
5330
|
}
|
|
4777
5331
|
}
|
|
4778
|
-
function freeze(
|
|
5332
|
+
function freeze(value2) {
|
|
4779
5333
|
return Object.freeze({
|
|
4780
|
-
...
|
|
5334
|
+
...value2,
|
|
4781
5335
|
state: Object.freeze({
|
|
4782
|
-
...
|
|
4783
|
-
conversationRefs: Object.freeze([...
|
|
4784
|
-
completedEffects: Object.freeze(
|
|
4785
|
-
unresolvedApprovals: Object.freeze([...
|
|
5336
|
+
...value2.state,
|
|
5337
|
+
conversationRefs: Object.freeze([...value2.state.conversationRefs]),
|
|
5338
|
+
completedEffects: Object.freeze(value2.state.completedEffects.map((item) => Object.freeze({ ...item }))),
|
|
5339
|
+
unresolvedApprovals: Object.freeze([...value2.state.unresolvedApprovals])
|
|
4786
5340
|
})
|
|
4787
5341
|
});
|
|
4788
5342
|
}
|
|
@@ -4799,7 +5353,7 @@ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
|
|
|
4799
5353
|
commitSha: snapshot.commitSha,
|
|
4800
5354
|
files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
|
|
4801
5355
|
};
|
|
4802
|
-
return `sha256:${await sha256Hex(
|
|
5356
|
+
return `sha256:${await sha256Hex(canonicalJson2(normalized))}`;
|
|
4803
5357
|
}
|
|
4804
5358
|
function validateSnapshot(snapshot, limits) {
|
|
4805
5359
|
if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
|
|
@@ -4841,10 +5395,10 @@ var import_path9 = require("path");
|
|
|
4841
5395
|
|
|
4842
5396
|
// ../camel/dist/chunk-LAXU2AVK.js
|
|
4843
5397
|
function conversionPolicyDigest(policy) {
|
|
4844
|
-
return sha256Hex(
|
|
5398
|
+
return sha256Hex(canonicalJson2(policy));
|
|
4845
5399
|
}
|
|
4846
5400
|
function registeredIdRegistryDigest(values) {
|
|
4847
|
-
return sha256Hex(
|
|
5401
|
+
return sha256Hex(canonicalJson2(values));
|
|
4848
5402
|
}
|
|
4849
5403
|
async function createConversionRegistry(config) {
|
|
4850
5404
|
const policies = /* @__PURE__ */ new Map();
|
|
@@ -4873,61 +5427,61 @@ async function createConversionRegistry(config) {
|
|
|
4873
5427
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
4874
5428
|
return policy;
|
|
4875
5429
|
};
|
|
4876
|
-
const emit3 = (source, policy,
|
|
5430
|
+
const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
4877
5431
|
const operations = Object.freeze({
|
|
4878
|
-
boolean: async (
|
|
4879
|
-
const policy = checked(
|
|
4880
|
-
return emit3(
|
|
5432
|
+
boolean: async (value2, id) => {
|
|
5433
|
+
const policy = checked(value2, id, "boolean");
|
|
5434
|
+
return emit3(value2, policy, requireBoolean(value2.value));
|
|
4881
5435
|
},
|
|
4882
|
-
integer: async (
|
|
4883
|
-
const policy = checked(
|
|
4884
|
-
return emit3(
|
|
5436
|
+
integer: async (value2, id) => {
|
|
5437
|
+
const policy = checked(value2, id, "integer");
|
|
5438
|
+
return emit3(value2, policy, boundedInteger(value2.value, policy.output));
|
|
4885
5439
|
},
|
|
4886
|
-
finiteNumber: async (
|
|
4887
|
-
const policy = checked(
|
|
4888
|
-
return emit3(
|
|
5440
|
+
finiteNumber: async (value2, id) => {
|
|
5441
|
+
const policy = checked(value2, id, "finite_number");
|
|
5442
|
+
return emit3(value2, policy, boundedNumber(value2.value, policy.output));
|
|
4889
5443
|
},
|
|
4890
|
-
enum: async (
|
|
4891
|
-
const policy = checked(
|
|
4892
|
-
return emit3(
|
|
5444
|
+
enum: async (value2, id) => {
|
|
5445
|
+
const policy = checked(value2, id, "enum");
|
|
5446
|
+
return emit3(value2, policy, enumMember(value2.value, policy.output));
|
|
4893
5447
|
},
|
|
4894
|
-
date: async (
|
|
4895
|
-
const policy = checked(
|
|
4896
|
-
return emit3(
|
|
5448
|
+
date: async (value2, id) => {
|
|
5449
|
+
const policy = checked(value2, id, "date");
|
|
5450
|
+
return emit3(value2, policy, canonicalDate(value2.value, policy.output));
|
|
4897
5451
|
},
|
|
4898
|
-
registeredId: async (
|
|
4899
|
-
const policy = checked(
|
|
5452
|
+
registeredId: async (value2, id) => {
|
|
5453
|
+
const policy = checked(value2, id, "registered_id");
|
|
4900
5454
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
4901
|
-
const output = typeof
|
|
5455
|
+
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
4902
5456
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
4903
|
-
return emit3(
|
|
5457
|
+
return emit3(value2, policy, output);
|
|
4904
5458
|
},
|
|
4905
|
-
digest: async (
|
|
4906
|
-
const policy = checked(
|
|
4907
|
-
if (!(
|
|
4908
|
-
return emit3(
|
|
5459
|
+
digest: async (value2, id) => {
|
|
5460
|
+
const policy = checked(value2, id, "digest");
|
|
5461
|
+
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
5462
|
+
return emit3(value2, policy, await sha256Hex(value2.value));
|
|
4909
5463
|
},
|
|
4910
|
-
measure: async (
|
|
4911
|
-
const policy = checked(
|
|
4912
|
-
const measured = measure(
|
|
4913
|
-
return emit3(
|
|
5464
|
+
measure: async (value2, metric, id) => {
|
|
5465
|
+
const policy = checked(value2, id, "integer");
|
|
5466
|
+
const measured = measure(value2.value, metric);
|
|
5467
|
+
return emit3(value2, policy, boundedInteger(measured, policy.output));
|
|
4914
5468
|
},
|
|
4915
|
-
test: async (
|
|
4916
|
-
const policy = checked(
|
|
5469
|
+
test: async (value2, predicateId, id) => {
|
|
5470
|
+
const policy = checked(value2, id, "boolean");
|
|
4917
5471
|
const predicate = config.predicates?.[predicateId];
|
|
4918
5472
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
4919
|
-
return emit3(
|
|
5473
|
+
return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
4920
5474
|
}
|
|
4921
5475
|
});
|
|
4922
5476
|
return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
|
|
4923
5477
|
}
|
|
4924
|
-
async function convert(source, policy,
|
|
5478
|
+
async function convert(source, policy, value2, counts) {
|
|
4925
5479
|
const sourceKey = sourceIdentity(source);
|
|
4926
5480
|
const countKey = `${policy.conversionId}\0${sourceKey}`;
|
|
4927
5481
|
const count = counts.get(countKey) ?? 0;
|
|
4928
5482
|
if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
|
|
4929
5483
|
counts.set(countKey, count + 1);
|
|
4930
|
-
return createSafeInternal(
|
|
5484
|
+
return createSafeInternal(value2, "atomic_conversion", {
|
|
4931
5485
|
readers: source.label.readers,
|
|
4932
5486
|
provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
|
|
4933
5487
|
dependencies: dependenciesOf([source])
|
|
@@ -4939,49 +5493,49 @@ function validatePolicyShape(policy) {
|
|
|
4939
5493
|
const output = policy.output;
|
|
4940
5494
|
if ((output.kind === "integer" || output.kind === "finite_number") && (!Number.isFinite(output.minimum) || !Number.isFinite(output.maximum) || output.minimum > output.maximum)) throw new CamelError("state_conflict", "Numeric conversion bounds are invalid.");
|
|
4941
5495
|
if (output.kind === "finite_number" && (!Number.isSafeInteger(output.maximumDecimalPlaces) || output.maximumDecimalPlaces < 0 || output.maximumDecimalPlaces > 15)) throw new CamelError("state_conflict", "Decimal-place bound is invalid.");
|
|
4942
|
-
if (output.kind === "enum" && (output.values.length === 0 || output.values.some((
|
|
5496
|
+
if (output.kind === "enum" && (output.values.length === 0 || output.values.some((value2) => typeof value2 !== "string" || !value2) || new Set(output.values).size !== output.values.length)) throw new CamelError("state_conflict", "Enum conversion members must be unique and non-empty.");
|
|
4943
5497
|
if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
|
|
4944
5498
|
if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
|
|
4945
5499
|
if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
|
|
4946
5500
|
}
|
|
4947
|
-
function requireBoolean(
|
|
4948
|
-
if (typeof
|
|
4949
|
-
return
|
|
5501
|
+
function requireBoolean(value2) {
|
|
5502
|
+
if (typeof value2 !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
|
|
5503
|
+
return value2;
|
|
4950
5504
|
}
|
|
4951
|
-
function boundedInteger(
|
|
4952
|
-
if (spec.kind !== "integer" || typeof
|
|
4953
|
-
return
|
|
5505
|
+
function boundedInteger(value2, spec) {
|
|
5506
|
+
if (spec.kind !== "integer" || typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Integer conversion rejected the structured value.");
|
|
5507
|
+
return value2;
|
|
4954
5508
|
}
|
|
4955
|
-
function boundedNumber(
|
|
4956
|
-
if (spec.kind !== "finite_number" || typeof
|
|
4957
|
-
const text = String(
|
|
5509
|
+
function boundedNumber(value2, spec) {
|
|
5510
|
+
if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
|
|
5511
|
+
const text = String(value2);
|
|
4958
5512
|
if (/e/i.test(text) || (text.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
4959
|
-
return
|
|
5513
|
+
return value2;
|
|
4960
5514
|
}
|
|
4961
|
-
function enumMember(
|
|
4962
|
-
if (spec.kind !== "enum" || typeof
|
|
4963
|
-
const member = spec.caseSensitive ? spec.values.find((item) => item ===
|
|
5515
|
+
function enumMember(value2, spec) {
|
|
5516
|
+
if (spec.kind !== "enum" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
|
|
5517
|
+
const member = spec.caseSensitive ? spec.values.find((item) => item === value2) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value2.toLocaleLowerCase("en-US"));
|
|
4964
5518
|
if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
|
|
4965
5519
|
return member;
|
|
4966
5520
|
}
|
|
4967
|
-
function canonicalDate(
|
|
4968
|
-
if (spec.kind !== "date" || typeof
|
|
5521
|
+
function canonicalDate(value2, spec) {
|
|
5522
|
+
if (spec.kind !== "date" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
|
|
4969
5523
|
const pattern = spec.format === "date" ? /^\d{4}-\d{2}-\d{2}$/ : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
4970
|
-
const instant = Date.parse(spec.format === "date" ? `${
|
|
4971
|
-
if (!pattern.test(
|
|
4972
|
-
if (spec.earliest &&
|
|
4973
|
-
return
|
|
4974
|
-
}
|
|
4975
|
-
function measure(
|
|
4976
|
-
if (metric === "byte_length" && (typeof
|
|
4977
|
-
if (metric === "codepoint_length" && typeof
|
|
4978
|
-
if (metric === "item_count" && Array.isArray(
|
|
5524
|
+
const instant = Date.parse(spec.format === "date" ? `${value2}T00:00:00Z` : value2);
|
|
5525
|
+
if (!pattern.test(value2) || !Number.isFinite(instant) || spec.format === "date" && new Date(instant).toISOString().slice(0, 10) !== value2) throw new CamelError("conversion_rejected", "Date conversion rejected a non-canonical value.");
|
|
5526
|
+
if (spec.earliest && value2 < spec.earliest || spec.latest && value2 > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
|
|
5527
|
+
return value2;
|
|
5528
|
+
}
|
|
5529
|
+
function measure(value2, metric) {
|
|
5530
|
+
if (metric === "byte_length" && (typeof value2 === "string" || value2 instanceof Uint8Array)) return utf8Length(value2);
|
|
5531
|
+
if (metric === "codepoint_length" && typeof value2 === "string") return [...value2].length;
|
|
5532
|
+
if (metric === "item_count" && Array.isArray(value2)) return value2.length;
|
|
4979
5533
|
throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
|
|
4980
5534
|
}
|
|
4981
|
-
function evaluatePredicate(
|
|
4982
|
-
if (predicate.kind === "has_fields") return typeof
|
|
4983
|
-
if (predicate.kind === "registered_id") return typeof
|
|
4984
|
-
const actual =
|
|
5535
|
+
function evaluatePredicate(value2, predicate, registries) {
|
|
5536
|
+
if (predicate.kind === "has_fields") return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && predicate.fields.every((field) => Object.hasOwn(value2, field));
|
|
5537
|
+
if (predicate.kind === "registered_id") return typeof value2 === "string" && registries?.[predicate.registryId]?.values[value2] !== void 0;
|
|
5538
|
+
const actual = value2 === null ? "null" : Array.isArray(value2) ? "array" : typeof value2;
|
|
4985
5539
|
return actual === predicate.valueType;
|
|
4986
5540
|
}
|
|
4987
5541
|
function sourceIdentity(source) {
|
|
@@ -5004,17 +5558,17 @@ function createCamelIngress(constants2 = []) {
|
|
|
5004
5558
|
byId.set(item.id, item);
|
|
5005
5559
|
}
|
|
5006
5560
|
const ingress = {
|
|
5007
|
-
userInstruction: (
|
|
5008
|
-
systemPolicy: (
|
|
5561
|
+
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
5562
|
+
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
5009
5563
|
control: (id) => {
|
|
5010
5564
|
const item = byId.get(id);
|
|
5011
5565
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
5012
5566
|
return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
|
|
5013
5567
|
},
|
|
5014
|
-
external: (
|
|
5015
|
-
quarantinedOutput: (
|
|
5568
|
+
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
5569
|
+
quarantinedOutput: (value2, input) => {
|
|
5016
5570
|
if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
|
|
5017
|
-
return createUnsafeInternal(
|
|
5571
|
+
return createUnsafeInternal(value2, {
|
|
5018
5572
|
readers: input.readers,
|
|
5019
5573
|
dependencies: input.dependencies,
|
|
5020
5574
|
provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
|
|
@@ -5023,15 +5577,15 @@ function createCamelIngress(constants2 = []) {
|
|
|
5023
5577
|
};
|
|
5024
5578
|
return Object.freeze(ingress);
|
|
5025
5579
|
}
|
|
5026
|
-
function assertNoUnsafeConstant(
|
|
5027
|
-
if (typeof
|
|
5028
|
-
seen.add(
|
|
5029
|
-
if (isCamelValue(
|
|
5030
|
-
if (isUnsafe(
|
|
5031
|
-
assertNoUnsafeConstant(
|
|
5580
|
+
function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
5581
|
+
if (typeof value2 !== "object" || value2 === null || seen.has(value2)) return;
|
|
5582
|
+
seen.add(value2);
|
|
5583
|
+
if (isCamelValue(value2)) {
|
|
5584
|
+
if (isUnsafe(value2)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
|
|
5585
|
+
assertNoUnsafeConstant(value2.value, seen);
|
|
5032
5586
|
return;
|
|
5033
5587
|
}
|
|
5034
|
-
for (const child of Object.values(
|
|
5588
|
+
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
5035
5589
|
}
|
|
5036
5590
|
function metadata(kind, id, readers) {
|
|
5037
5591
|
if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
@@ -5045,10 +5599,10 @@ function createEffectPolicy(config = {}) {
|
|
|
5045
5599
|
return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
|
|
5046
5600
|
}
|
|
5047
5601
|
function destinationRegistryDigest(values) {
|
|
5048
|
-
return sha256Hex(
|
|
5602
|
+
return sha256Hex(canonicalJson2([...values].sort()));
|
|
5049
5603
|
}
|
|
5050
5604
|
async function evaluate(input, registries, approvals) {
|
|
5051
|
-
const actionDigest = await sha256Hex(
|
|
5605
|
+
const actionDigest = await sha256Hex(canonicalJson2(input));
|
|
5052
5606
|
const decisionId = `decision_${actionDigest.slice(0, 24)}`;
|
|
5053
5607
|
const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
|
|
5054
5608
|
if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
|
|
@@ -5062,7 +5616,7 @@ async function evaluate(input, registries, approvals) {
|
|
|
5062
5616
|
if (invalid3) return deny(invalid3);
|
|
5063
5617
|
if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
|
|
5064
5618
|
}
|
|
5065
|
-
if (input.controlDependencies.some((
|
|
5619
|
+
if (input.controlDependencies.some((value2) => !isSafe(value2) && !confined.has(value2))) return deny("unsafe_control_dependency");
|
|
5066
5620
|
if (confined.size > 0) {
|
|
5067
5621
|
const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
|
|
5068
5622
|
const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
|
|
@@ -5089,29 +5643,29 @@ async function validateArgument(path, arg, tool, registries) {
|
|
|
5089
5643
|
if (!isSafe(arg.value)) return "unsafe_destination_argument";
|
|
5090
5644
|
if (!isControlOwned(arg.value)) return "destination_not_control_owned";
|
|
5091
5645
|
const registry = registries[arg.registryId];
|
|
5092
|
-
const validValues = registry && registry.values.length > 0 && registry.values.every((
|
|
5646
|
+
const validValues = registry && registry.values.length > 0 && registry.values.every((value2) => typeof value2 === "string" && value2.length > 0) && new Set(registry.values).size === registry.values.length;
|
|
5093
5647
|
if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
|
|
5094
5648
|
}
|
|
5095
5649
|
if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
|
|
5096
5650
|
return void 0;
|
|
5097
5651
|
}
|
|
5098
|
-
function isControlOwned(
|
|
5099
|
-
return
|
|
5652
|
+
function isControlOwned(value2) {
|
|
5653
|
+
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
5100
5654
|
}
|
|
5101
5655
|
function copyRegistries(registries) {
|
|
5102
5656
|
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
5103
5657
|
}
|
|
5104
|
-
function validateUnsafeSelector(path,
|
|
5658
|
+
function validateUnsafeSelector(path, value2, tool) {
|
|
5105
5659
|
const policy = tool.unsafeSelectorPolicy;
|
|
5106
5660
|
if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
|
|
5107
5661
|
if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
|
|
5108
5662
|
if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
|
|
5109
|
-
if (utf8Length(
|
|
5110
|
-
if (typeof
|
|
5663
|
+
if (utf8Length(value2.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
|
|
5664
|
+
if (typeof value2.value === "string" && looksLikeDestination(value2.value)) return "unsafe_selector_is_destination";
|
|
5111
5665
|
return void 0;
|
|
5112
5666
|
}
|
|
5113
|
-
function looksLikeDestination(
|
|
5114
|
-
const text =
|
|
5667
|
+
function looksLikeDestination(value2) {
|
|
5668
|
+
const text = value2.trim();
|
|
5115
5669
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
5116
5670
|
}
|
|
5117
5671
|
|
|
@@ -5197,9 +5751,9 @@ var CodeRuntimeReconciler = class {
|
|
|
5197
5751
|
}
|
|
5198
5752
|
}
|
|
5199
5753
|
};
|
|
5200
|
-
function retryableControlFailure(
|
|
5201
|
-
if (!
|
|
5202
|
-
const failure =
|
|
5754
|
+
function retryableControlFailure(value2) {
|
|
5755
|
+
if (!value2 || typeof value2 !== "object") return false;
|
|
5756
|
+
const failure = value2;
|
|
5203
5757
|
if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
|
|
5204
5758
|
return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
|
|
5205
5759
|
}
|
|
@@ -5251,16 +5805,16 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5251
5805
|
if (options.signal?.aborted) throw cause;
|
|
5252
5806
|
throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
|
|
5253
5807
|
}
|
|
5254
|
-
const
|
|
5808
|
+
const value2 = await response2.json().catch(() => null);
|
|
5255
5809
|
if (!response2.ok) {
|
|
5256
|
-
const problem = record3(record3(
|
|
5810
|
+
const problem = record3(record3(value2)?.error);
|
|
5257
5811
|
throw new CodeRuntimeControlError(
|
|
5258
5812
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5259
5813
|
response2.status,
|
|
5260
5814
|
typeof problem?.code === "string" ? problem.code : void 0
|
|
5261
5815
|
);
|
|
5262
5816
|
}
|
|
5263
|
-
return
|
|
5817
|
+
return value2;
|
|
5264
5818
|
};
|
|
5265
5819
|
return {
|
|
5266
5820
|
heartbeat: async (version, capabilities) => {
|
|
@@ -5275,15 +5829,15 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5275
5829
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5276
5830
|
),
|
|
5277
5831
|
infer: async (sessionId, inference) => {
|
|
5278
|
-
const
|
|
5832
|
+
const value2 = record3(await call2(
|
|
5279
5833
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5280
5834
|
inference,
|
|
5281
5835
|
modelRequestTimeoutMs
|
|
5282
5836
|
));
|
|
5283
|
-
if (!
|
|
5837
|
+
if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
|
|
5284
5838
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5285
5839
|
}
|
|
5286
|
-
return
|
|
5840
|
+
return value2;
|
|
5287
5841
|
},
|
|
5288
5842
|
review: async (sessionId, review) => parseReview(
|
|
5289
5843
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
|
|
@@ -5308,8 +5862,8 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5308
5862
|
}
|
|
5309
5863
|
};
|
|
5310
5864
|
}
|
|
5311
|
-
function validatedEndpoint(
|
|
5312
|
-
const endpoint =
|
|
5865
|
+
function validatedEndpoint(value2) {
|
|
5866
|
+
const endpoint = value2.replace(/\/+$/, "");
|
|
5313
5867
|
let url;
|
|
5314
5868
|
try {
|
|
5315
5869
|
url = new URL(endpoint);
|
|
@@ -5322,9 +5876,9 @@ function validatedEndpoint(value) {
|
|
|
5322
5876
|
}
|
|
5323
5877
|
return endpoint;
|
|
5324
5878
|
}
|
|
5325
|
-
function validSessionId(
|
|
5326
|
-
if (!/^csess_[0-9a-f]{32}$/.test(
|
|
5327
|
-
return
|
|
5879
|
+
function validSessionId(value2) {
|
|
5880
|
+
if (!/^csess_[0-9a-f]{32}$/.test(value2)) throw new TypeError("invalid Code session id");
|
|
5881
|
+
return value2;
|
|
5328
5882
|
}
|
|
5329
5883
|
function validateHeartbeat(version, capabilities) {
|
|
5330
5884
|
if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
|
|
@@ -5337,8 +5891,8 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5337
5891
|
throw new TypeError("runtime resources must be positive integers");
|
|
5338
5892
|
}
|
|
5339
5893
|
}
|
|
5340
|
-
function parseSnapshot(
|
|
5341
|
-
const root = record3(
|
|
5894
|
+
function parseSnapshot(value2) {
|
|
5895
|
+
const root = record3(value2);
|
|
5342
5896
|
const host = record3(root?.host);
|
|
5343
5897
|
if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
|
|
5344
5898
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
@@ -5363,11 +5917,11 @@ function parseSnapshot(value) {
|
|
|
5363
5917
|
});
|
|
5364
5918
|
return { host, bindings, commands };
|
|
5365
5919
|
}
|
|
5366
|
-
async function parseSource(
|
|
5367
|
-
const snapshot = record3(record3(
|
|
5920
|
+
async function parseSource(value2) {
|
|
5921
|
+
const snapshot = record3(record3(value2)?.snapshot);
|
|
5368
5922
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5369
|
-
const files = snapshot.files.map((
|
|
5370
|
-
const file = record3(
|
|
5923
|
+
const files = snapshot.files.map((value22) => {
|
|
5924
|
+
const file = record3(value22);
|
|
5371
5925
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5372
5926
|
return { path: file.path, content: file.content };
|
|
5373
5927
|
});
|
|
@@ -5394,19 +5948,19 @@ async function parseSource(value) {
|
|
|
5394
5948
|
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
5395
5949
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5396
5950
|
}
|
|
5397
|
-
function parseReview(
|
|
5398
|
-
const review = record3(record3(
|
|
5951
|
+
function parseReview(value2) {
|
|
5952
|
+
const review = record3(record3(value2)?.review);
|
|
5399
5953
|
if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
|
|
5400
5954
|
return review;
|
|
5401
5955
|
}
|
|
5402
|
-
function parseCandidate(
|
|
5403
|
-
const candidate = record3(record3(
|
|
5956
|
+
function parseCandidate(value2) {
|
|
5957
|
+
const candidate = record3(record3(value2)?.candidate);
|
|
5404
5958
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5405
5959
|
throw invalid("candidate");
|
|
5406
5960
|
}
|
|
5407
5961
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5408
5962
|
}
|
|
5409
|
-
var record3 = (
|
|
5963
|
+
var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5410
5964
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5411
5965
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5412
5966
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -5440,8 +5994,8 @@ function validateCodePatch(patch2, maxBytes) {
|
|
|
5440
5994
|
if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
5441
5995
|
return paths;
|
|
5442
5996
|
}
|
|
5443
|
-
function validHeaderPath(
|
|
5444
|
-
return
|
|
5997
|
+
function validHeaderPath(value2, path, prefix) {
|
|
5998
|
+
return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
|
|
5445
5999
|
}
|
|
5446
6000
|
function validateRelativePath(path) {
|
|
5447
6001
|
const parts = path.split("/");
|
|
@@ -5587,8 +6141,8 @@ function assertCodeBuildRecipe(recipe2) {
|
|
|
5587
6141
|
throw new TypeError("build recipe is malformed or exceeds its control bounds");
|
|
5588
6142
|
}
|
|
5589
6143
|
}
|
|
5590
|
-
function parseMemory(
|
|
5591
|
-
const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(
|
|
6144
|
+
function parseMemory(value2) {
|
|
6145
|
+
const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value2.toLowerCase());
|
|
5592
6146
|
if (!match?.[1] || !match[2]) return 0;
|
|
5593
6147
|
const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
|
|
5594
6148
|
return Number(match[1]) * scale;
|
|
@@ -5823,14 +6377,14 @@ function normalizedRecipe(recipe2) {
|
|
|
5823
6377
|
expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
|
|
5824
6378
|
};
|
|
5825
6379
|
}
|
|
5826
|
-
function digestJson(
|
|
5827
|
-
return digestBytes(JSON.stringify(
|
|
6380
|
+
function digestJson(value2) {
|
|
6381
|
+
return digestBytes(JSON.stringify(value2));
|
|
5828
6382
|
}
|
|
5829
|
-
function digestBytes(
|
|
5830
|
-
return `sha256:${(0, import_crypto3.createHash)("sha256").update(
|
|
6383
|
+
function digestBytes(value2) {
|
|
6384
|
+
return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
|
|
5831
6385
|
}
|
|
5832
|
-
function integer(
|
|
5833
|
-
return Number.isSafeInteger(
|
|
6386
|
+
function integer(value2, minimum, maximum) {
|
|
6387
|
+
return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
|
|
5834
6388
|
}
|
|
5835
6389
|
async function prepareRuntimeCheckpoint(input) {
|
|
5836
6390
|
const patch2 = await input.workspace.patch(256 * 1024);
|
|
@@ -5883,7 +6437,7 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
5883
6437
|
});
|
|
5884
6438
|
return { checkpoint, verification, review, note };
|
|
5885
6439
|
}
|
|
5886
|
-
var message = (
|
|
6440
|
+
var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
|
|
5887
6441
|
var CodeRuntimeCheckpointManager = class {
|
|
5888
6442
|
constructor(options) {
|
|
5889
6443
|
this.options = options;
|
|
@@ -6077,7 +6631,7 @@ async function conversionPolicy(id, output) {
|
|
|
6077
6631
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
6078
6632
|
}
|
|
6079
6633
|
async function registeredPolicy(id, registryId, values) {
|
|
6080
|
-
const mapping = Object.fromEntries(values.map((
|
|
6634
|
+
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
6081
6635
|
return conversionPolicy(id, {
|
|
6082
6636
|
kind: "registered_id",
|
|
6083
6637
|
registryId,
|
|
@@ -6086,7 +6640,7 @@ async function registeredPolicy(id, registryId, values) {
|
|
|
6086
6640
|
}
|
|
6087
6641
|
async function conversionRegistry(policies, values) {
|
|
6088
6642
|
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
|
|
6089
|
-
const mapping = Object.fromEntries(entries.map((
|
|
6643
|
+
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
6090
6644
|
return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
6091
6645
|
})));
|
|
6092
6646
|
return createConversionRegistry({ policies, registeredIds });
|
|
@@ -6110,8 +6664,8 @@ async function environment(input, options, tool) {
|
|
|
6110
6664
|
};
|
|
6111
6665
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
6112
6666
|
}
|
|
6113
|
-
function unsafe(base,
|
|
6114
|
-
return base.ingress.quarantinedOutput(
|
|
6667
|
+
function unsafe(base, value2, field) {
|
|
6668
|
+
return base.ingress.quarantinedOutput(value2, {
|
|
6115
6669
|
readers: base.reader.label.readers,
|
|
6116
6670
|
runId: `${base.runId}:${field}`
|
|
6117
6671
|
});
|
|
@@ -6191,14 +6745,14 @@ async function read(context, request2, options, policy) {
|
|
|
6191
6745
|
}
|
|
6192
6746
|
async function patch(context, request2, options, policy) {
|
|
6193
6747
|
exactKeys(request2.input, ["patch"]);
|
|
6194
|
-
const
|
|
6195
|
-
const paths = validateCodePatch(
|
|
6748
|
+
const value2 = stringField(request2.input, "patch");
|
|
6749
|
+
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
6196
6750
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
6197
6751
|
throw new TypeError("patch targets a read-only reference source");
|
|
6198
6752
|
}
|
|
6199
|
-
const allowed = await policy.patch(policyContext(context, request2, options, { patch:
|
|
6753
|
+
const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
|
|
6200
6754
|
if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
|
|
6201
|
-
await applyCodePatch(context.workspaceDir,
|
|
6755
|
+
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
6202
6756
|
return response(request2, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
6203
6757
|
}
|
|
6204
6758
|
async function recipe(context, request2, options, recipes, policy) {
|
|
@@ -6289,14 +6843,14 @@ function exactKeys(input, allowed) {
|
|
|
6289
6843
|
if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
|
|
6290
6844
|
}
|
|
6291
6845
|
function stringField(input, name) {
|
|
6292
|
-
const
|
|
6293
|
-
if (typeof
|
|
6294
|
-
return
|
|
6846
|
+
const value2 = input[name];
|
|
6847
|
+
if (typeof value2 !== "string" || !value2) throw new TypeError(`${name} must be a non-empty string`);
|
|
6848
|
+
return value2;
|
|
6295
6849
|
}
|
|
6296
|
-
function optionalInteger(
|
|
6297
|
-
if (
|
|
6298
|
-
if (!Number.isSafeInteger(
|
|
6299
|
-
return
|
|
6850
|
+
function optionalInteger(value2) {
|
|
6851
|
+
if (value2 === void 0) return void 0;
|
|
6852
|
+
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
6853
|
+
return value2;
|
|
6300
6854
|
}
|
|
6301
6855
|
function response(request2, ok, content2, details) {
|
|
6302
6856
|
return { requestId: request2.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
@@ -6342,9 +6896,9 @@ function codeLocalSource(payload) {
|
|
|
6342
6896
|
return source;
|
|
6343
6897
|
}
|
|
6344
6898
|
function codeCheckpointPayload(payload) {
|
|
6345
|
-
const
|
|
6346
|
-
if (!
|
|
6347
|
-
return
|
|
6899
|
+
const value2 = payload.checkpoint;
|
|
6900
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new TypeError("resume checkpoint is missing");
|
|
6901
|
+
return value2;
|
|
6348
6902
|
}
|
|
6349
6903
|
function fakeCodeLease(command, metadata2) {
|
|
6350
6904
|
return {
|
|
@@ -6368,7 +6922,7 @@ function fakeCodeLease(command, metadata2) {
|
|
|
6368
6922
|
}
|
|
6369
6923
|
};
|
|
6370
6924
|
}
|
|
6371
|
-
var record22 = (
|
|
6925
|
+
var record22 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6372
6926
|
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
6373
6927
|
async function prepareRuntimeLocalSource(input) {
|
|
6374
6928
|
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
@@ -6458,24 +7012,24 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
|
6458
7012
|
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
6459
7013
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
6460
7014
|
}
|
|
6461
|
-
var digestRuntimeValue = (
|
|
6462
|
-
var runtimeErrorMessage = (
|
|
6463
|
-
var runtimeRecord = (
|
|
6464
|
-
var safeRuntimeJson = (
|
|
7015
|
+
var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
|
|
7016
|
+
var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
|
|
7017
|
+
var runtimeRecord = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7018
|
+
var safeRuntimeJson = (value2) => {
|
|
6465
7019
|
try {
|
|
6466
|
-
return JSON.stringify(
|
|
7020
|
+
return JSON.stringify(value2).slice(0, 1e4);
|
|
6467
7021
|
} catch {
|
|
6468
7022
|
return "[event]";
|
|
6469
7023
|
}
|
|
6470
7024
|
};
|
|
6471
|
-
function runtimeResultText(
|
|
6472
|
-
const record32 = runtimeRecord(
|
|
7025
|
+
function runtimeResultText(value2) {
|
|
7026
|
+
const record32 = runtimeRecord(value2);
|
|
6473
7027
|
if (record32 && typeof record32.text === "string") return record32.text.slice(0, 2e4);
|
|
6474
7028
|
if (record32 && typeof record32.error === "string") return `Pi failed: ${record32.error.slice(0, 19989)}`;
|
|
6475
7029
|
return null;
|
|
6476
7030
|
}
|
|
6477
|
-
function runtimeResultError(
|
|
6478
|
-
const record32 = runtimeRecord(
|
|
7031
|
+
function runtimeResultError(value2) {
|
|
7032
|
+
const record32 = runtimeRecord(value2);
|
|
6479
7033
|
return record32 && typeof record32.error === "string" && record32.error.trim() ? record32.error.trim().slice(0, 2e3) : null;
|
|
6480
7034
|
}
|
|
6481
7035
|
var CodePiRuntimeEngine = class {
|
|
@@ -6749,14 +7303,14 @@ var CodePiRuntimeEngine = class {
|
|
|
6749
7303
|
this.#active.delete(command.sessionId);
|
|
6750
7304
|
return result;
|
|
6751
7305
|
}
|
|
6752
|
-
async #failure(command, active,
|
|
6753
|
-
active.failure =
|
|
7306
|
+
async #failure(command, active, value2) {
|
|
7307
|
+
active.failure = value2.slice(0, 2e3);
|
|
6754
7308
|
if (active.acknowledged) {
|
|
6755
7309
|
await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
|
|
6756
7310
|
}
|
|
6757
7311
|
}
|
|
6758
|
-
async #diagnostic(command, active,
|
|
6759
|
-
const detail =
|
|
7312
|
+
async #diagnostic(command, active, value2) {
|
|
7313
|
+
const detail = value2.trim().slice(0, 2e3) || "Pi runtime failed";
|
|
6760
7314
|
this.options.onDiagnostic?.(detail);
|
|
6761
7315
|
await this.#event(
|
|
6762
7316
|
command,
|
|
@@ -6769,265 +7323,19 @@ var CodePiRuntimeEngine = class {
|
|
|
6769
7323
|
}
|
|
6770
7324
|
};
|
|
6771
7325
|
|
|
6772
|
-
// src/
|
|
7326
|
+
// src/version.ts
|
|
6773
7327
|
var import_node_fs13 = require("fs");
|
|
6774
7328
|
function cliVersion() {
|
|
6775
7329
|
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
6776
7330
|
return pkg.version ?? "unknown";
|
|
6777
7331
|
}
|
|
6778
|
-
function printHelp(output = console) {
|
|
6779
|
-
output.log(`odla-ai
|
|
6780
7332
|
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
working from memory: runbooks are edited
|
|
6785
|
-
live, so your training data is out of date
|
|
6786
|
-
and this is not.
|
|
6787
|
-
odla-ai runbook impact After a change: which runbooks describe the
|
|
6788
|
-
code you just touched, so you can fix any
|
|
6789
|
-
step it made wrong.
|
|
6790
|
-
|
|
6791
|
-
Usage:
|
|
6792
|
-
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
6793
|
-
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
6794
|
-
odla-ai doctor [--config odla.config.mjs]
|
|
6795
|
-
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
6796
|
-
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
6797
|
-
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
6798
|
-
odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
|
|
6799
|
-
odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
|
|
6800
|
-
odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6801
|
-
odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
|
|
6802
|
-
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
6803
|
-
odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
6804
|
-
odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
6805
|
-
odla-ai app promote [--dry-run] [--json] --yes
|
|
6806
|
-
odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6807
|
-
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6808
|
-
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6809
|
-
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6810
|
-
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6811
|
-
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6812
|
-
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6813
|
-
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6814
|
-
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
6815
|
-
odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
6816
|
-
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
6817
|
-
odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
|
|
6818
|
-
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6819
|
-
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
6820
|
-
odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
|
|
6821
|
-
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
6822
|
-
odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
|
|
6823
|
-
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
6824
|
-
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
6825
|
-
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
6826
|
-
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6827
|
-
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
6828
|
-
odla-ai pm handoff --app <id> [--json]
|
|
6829
|
-
odla-ai discuss groups [--json]
|
|
6830
|
-
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
6831
|
-
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
6832
|
-
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
6833
|
-
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
6834
|
-
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
6835
|
-
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
6836
|
-
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6837
|
-
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6838
|
-
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6839
|
-
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6840
|
-
odla-ai context list [--json]
|
|
6841
|
-
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6842
|
-
odla-ai context remove <name> --yes [--json]
|
|
6843
|
-
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6844
|
-
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6845
|
-
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6846
|
-
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6847
|
-
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
6848
|
-
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
6849
|
-
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
6850
|
-
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
6851
|
-
odla-ai runbook get <slug> [--app <id>]
|
|
6852
|
-
odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
|
|
6853
|
-
odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
|
|
6854
|
-
odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
|
|
6855
|
-
odla-ai runbook publish <slug> [--app <id>]
|
|
6856
|
-
odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
|
|
6857
|
-
odla-ai runbook archive <slug> [--app <id>]
|
|
6858
|
-
odla-ai runbook history <slug> [--app <id>] [--json]
|
|
6859
|
-
odla-ai runbook revert <slug> --version <n> [--app <id>]
|
|
6860
|
-
odla-ai runbook rm <slug> [--app <id>]
|
|
6861
|
-
odla-ai capabilities [--json]
|
|
6862
|
-
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
6863
|
-
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
6864
|
-
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
6865
|
-
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
6866
|
-
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
6867
|
-
odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
|
|
6868
|
-
--validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
|
|
6869
|
-
odla-ai admin ai credentials [--context <name>] [--json]
|
|
6870
|
-
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
6871
|
-
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
6872
|
-
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
6873
|
-
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
6874
|
-
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
6875
|
-
odla-ai security plan [--env dev] [--json]
|
|
6876
|
-
odla-ai security sources [--env dev] [--json]
|
|
6877
|
-
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
6878
|
-
odla-ai security status <job-id> [--follow] [--json]
|
|
6879
|
-
odla-ai security report <job-id> [--json]
|
|
6880
|
-
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
6881
|
-
odla-ai security run [target] --self --ack-redacted-source
|
|
6882
|
-
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
6883
|
-
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
6884
|
-
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
6885
|
-
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
6886
|
-
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
6887
|
-
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
6888
|
-
odla-ai version
|
|
6889
|
-
|
|
6890
|
-
Commands:
|
|
6891
|
-
agent Inspect durable agent wakeups and explicitly requeue a
|
|
6892
|
-
dead-lettered job; JSON output is stable for remote operators.
|
|
6893
|
-
runbook odla's operational procedures, stored in the database and read at
|
|
6894
|
-
the moment they are followed. "ask" gives a written, cited answer;
|
|
6895
|
-
"search" the passages behind it; "get" the whole document.
|
|
6896
|
-
"impact" diffs your working tree against a base ref and names the
|
|
6897
|
-
runbooks that describe what you changed \u2014 run it after touching a
|
|
6898
|
-
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
6899
|
-
checks the corpus the other way: every odla-ai command the
|
|
6900
|
-
runbooks name is held against this CLI's real command surface,
|
|
6901
|
-
and against the minimum versions each runbook declares. A wrong step
|
|
6902
|
-
is fixed with "edit", which takes effect immediately: a runbook is
|
|
6903
|
-
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
6904
|
-
does and what it guarantees is JSDoc, rendered per package at
|
|
6905
|
-
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
6906
|
-
a question usually needs both.
|
|
6907
|
-
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6908
|
-
platform admin. A handshake-minted device token is never admin,
|
|
6909
|
-
however the human who approved it is configured.
|
|
6910
|
-
context Explain selected config, platform, app, environment, and
|
|
6911
|
-
developer-token provenance without printing credentials or
|
|
6912
|
-
starting a device handshake. Operator work can run outside a
|
|
6913
|
-
project checkout with explicit flags or ODLA_* environment
|
|
6914
|
-
variables.
|
|
6915
|
-
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6916
|
-
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6917
|
-
doctor Validate and summarize the project config without network calls.
|
|
6918
|
-
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
6919
|
-
app Archive (suspend, data retained), restore, export, import, or
|
|
6920
|
-
manage the co-owners of the app. Archiving takes every
|
|
6921
|
-
environment's data plane down until restored; permanent deletion
|
|
6922
|
-
has NO CLI \u2014 it requires a signed-in owner in Studio, and no
|
|
6923
|
-
machine or agent credential can purge. "app export" downloads a
|
|
6924
|
-
portable gzipped-JSONL snapshot of one database (--fresh takes a
|
|
6925
|
-
new snapshot first) \u2014 your data is always yours to take.
|
|
6926
|
-
"app import" upserts rows back in from JSON, JSONL, or a
|
|
6927
|
-
{namespace: rows} map (the shape a query returns); it writes only
|
|
6928
|
-
with --yes, so the bare command is a dry run, and it refuses to
|
|
6929
|
-
guess an id mode that would duplicate rows on a re-run.
|
|
6930
|
-
"app refresh-sandbox" replaces the sandbox with a copy of live
|
|
6931
|
-
(the routine dev loop); "app go-live" copies the sandbox into an
|
|
6932
|
-
EMPTY live database, once, at launch; "app promote" pushes only
|
|
6933
|
-
schema, rules and gates up afterwards, leaving live's rows alone.
|
|
6934
|
-
Each prints its plan and writes nothing without --yes, so the
|
|
6935
|
-
bare command is the dry run. Clerk config, triggers, allowlists,
|
|
6936
|
-
secrets and API keys never travel; identity rows stay behind
|
|
6937
|
-
unless --include-identity.
|
|
6938
|
-
"app rename <name>" changes only the display name humans read;
|
|
6939
|
-
the app id is permanent (tenants, URLs and keys embed it), so a
|
|
6940
|
-
rename never re-provisions anything or invalidates a credential.
|
|
6941
|
-
"app owners add <email>" grants a signed-up odla member the SAME
|
|
6942
|
-
full access as you; they then run their own "provision" to mint
|
|
6943
|
-
their own credentials for the shared db (the live one included)
|
|
6944
|
-
\u2014 no secret is ever copied between people.
|
|
6945
|
-
capabilities Show what the CLI automates vs agent edits and human checkpoints.
|
|
6946
|
-
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
6947
|
-
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
6948
|
-
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
6949
|
-
pm Project management (via @odla-ai/pm) shared across the apps you
|
|
6950
|
-
co-own: conformance goals, kanban tasks, decisions, and bugs. With
|
|
6951
|
-
no --app, "list" spans every co-owned project (the cross-project
|
|
6952
|
-
view); with --app it scopes to one. Same device-grant auth as
|
|
6953
|
-
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
6954
|
-
decision, bug. Status changes and comments post to each item's
|
|
6955
|
-
@odla-ai/chat discussion thread.
|
|
6956
|
-
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
6957
|
-
one group per project, topics with replies, @-mentions of people,
|
|
6958
|
-
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
6959
|
-
question, then "watch" it until someone answers. "watch" exits 75
|
|
6960
|
-
(not 1) when it times out with nothing new, so a script can tell
|
|
6961
|
-
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
6962
|
-
up the exact @[Label](kind/id) markup for a mention.
|
|
6963
|
-
o11y Read one stable status envelope for application RED, current
|
|
6964
|
-
live-sync load/freshness, the protected commit-to-visible
|
|
6965
|
-
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
6966
|
-
runtime metrics, and a machine verdict.
|
|
6967
|
-
--json keeps auth progress on stderr for unattended agents.
|
|
6968
|
-
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
6969
|
-
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
6970
|
-
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
6971
|
-
copilot, gemini, or agents (repeatable or comma-separated).
|
|
6972
|
-
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
6973
|
-
stdin; set stores a tenant-vault secret and set-clerk-key the
|
|
6974
|
-
reserved Clerk secret key, write-only from stdin or an env var.
|
|
6975
|
-
version Print the CLI version.
|
|
6976
|
-
|
|
6977
|
-
Safety:
|
|
6978
|
-
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
6979
|
-
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
6980
|
-
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
6981
|
-
the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
|
|
6982
|
-
provision the live database; use --dry-run first to inspect the resolved plan.
|
|
6983
|
-
Provision caches the approved developer token and service credentials under
|
|
6984
|
-
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6985
|
-
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6986
|
-
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6987
|
-
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6988
|
-
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6989
|
-
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6990
|
-
isolated developer and scoped-token caches. Override their locations with
|
|
6991
|
-
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6992
|
-
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6993
|
-
context, which beats project config. There is no ambient current context.
|
|
6994
|
-
"context show" reports only provenance and cache state and never authenticates.
|
|
6995
|
-
Provision opens the approval page in your browser automatically whenever the
|
|
6996
|
-
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6997
|
-
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
6998
|
-
Browser launch is best-effort: the printed approval URL is authoritative and
|
|
6999
|
-
agents must relay it to the human verbatim. A started handshake is persisted
|
|
7000
|
-
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
7001
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
7002
|
-
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
7003
|
-
with code 75: relay the URL, wait for approval, and re-run to collect.
|
|
7004
|
-
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
7005
|
-
The email is a non-secret identity hint: never provide a password or session
|
|
7006
|
-
token. The matching account must already exist, be signed in, explicitly
|
|
7007
|
-
review the exact code, and finish any current request before claiming another.
|
|
7008
|
-
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
7009
|
-
odla.config.mjs may select the app explicitly but is not required. Code host
|
|
7010
|
-
approval and credential hashes live in odla-ai/db. The host
|
|
7011
|
-
credential is never written under .odla/; it exists only in the foreground
|
|
7012
|
-
"code connect" process and is rotated by the next approved connection.
|
|
7013
|
-
Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
|
|
7014
|
-
OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
|
|
7015
|
-
GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
|
|
7016
|
-
for a PAT or provider key. GitHub read access is separate from the explicit
|
|
7017
|
-
--ack-redacted-source consent and the exact --plan-digest printed by security
|
|
7018
|
-
plan are required before bounded snippets reach System AI.
|
|
7019
|
-
Run security plan first to inspect the admin-selected providers, models,
|
|
7020
|
-
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
7021
|
-
and digest that binds consent to that exact plan.
|
|
7022
|
-
`);
|
|
7023
|
-
}
|
|
7024
|
-
|
|
7025
|
-
// src/security-hosted-github.ts
|
|
7026
|
-
var import_node_child_process4 = require("child_process");
|
|
7027
|
-
var import_node_util2 = require("util");
|
|
7333
|
+
// src/security-hosted-github.ts
|
|
7334
|
+
var import_node_child_process4 = require("child_process");
|
|
7335
|
+
var import_node_util2 = require("util");
|
|
7028
7336
|
|
|
7029
7337
|
// src/security-hosted-request.ts
|
|
7030
|
-
async function requestHostedSecurityJson(options, path, init,
|
|
7338
|
+
async function requestHostedSecurityJson(options, path, init, action2) {
|
|
7031
7339
|
const platform = platformAudience(options.platform);
|
|
7032
7340
|
const token = hostedSecurityCredential(options.token);
|
|
7033
7341
|
const requestInit = {
|
|
@@ -7047,35 +7355,35 @@ async function requestHostedSecurityJson(options, path, init, action) {
|
|
|
7047
7355
|
if (!response2.ok) {
|
|
7048
7356
|
const code = optionalHostedText(body.error?.code, "error code", 128);
|
|
7049
7357
|
const message2 = optionalHostedText(body.error?.message, "error message", 300);
|
|
7050
|
-
throw new Error(`${
|
|
7358
|
+
throw new Error(`${action2} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
|
|
7051
7359
|
}
|
|
7052
7360
|
return body;
|
|
7053
7361
|
}
|
|
7054
|
-
function hostedIdentifier(
|
|
7055
|
-
const result = optionalHostedText(
|
|
7362
|
+
function hostedIdentifier(value2, name) {
|
|
7363
|
+
const result = optionalHostedText(value2, name, 180);
|
|
7056
7364
|
if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
|
|
7057
7365
|
throw new Error(`${name} is invalid`);
|
|
7058
7366
|
}
|
|
7059
7367
|
return result;
|
|
7060
7368
|
}
|
|
7061
|
-
function positivePolicyVersion(
|
|
7062
|
-
if (!Number.isSafeInteger(
|
|
7369
|
+
function positivePolicyVersion(value2, name) {
|
|
7370
|
+
if (!Number.isSafeInteger(value2) || value2 < 1) {
|
|
7063
7371
|
throw new Error(`${name} is invalid`);
|
|
7064
7372
|
}
|
|
7065
|
-
return
|
|
7373
|
+
return value2;
|
|
7066
7374
|
}
|
|
7067
|
-
function optionalHostedText(
|
|
7068
|
-
if (
|
|
7069
|
-
if (typeof
|
|
7375
|
+
function optionalHostedText(value2, name, maxLength) {
|
|
7376
|
+
if (value2 === void 0 || value2 === null || value2 === "") return void 0;
|
|
7377
|
+
if (typeof value2 !== "string" || value2.length > maxLength || /[\u0000-\u001f\u007f]/.test(value2)) {
|
|
7070
7378
|
throw new Error(`${name} is invalid`);
|
|
7071
7379
|
}
|
|
7072
|
-
return
|
|
7380
|
+
return value2;
|
|
7073
7381
|
}
|
|
7074
|
-
function githubRepositoryName(
|
|
7075
|
-
if (typeof
|
|
7382
|
+
function githubRepositoryName(value2) {
|
|
7383
|
+
if (typeof value2 !== "string" || value2.length > 201) {
|
|
7076
7384
|
throw new Error("repository must be owner/name");
|
|
7077
7385
|
}
|
|
7078
|
-
const parts =
|
|
7386
|
+
const parts = value2.split("/");
|
|
7079
7387
|
const owner = parts[0] ?? "";
|
|
7080
7388
|
const name = (parts[1] ?? "").replace(/\.git$/i, "");
|
|
7081
7389
|
if (parts.length !== 2 || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,98}[A-Za-z0-9])?$/.test(owner) || !/^[A-Za-z0-9_.-]{1,100}$/.test(name) || name === "." || name === "..") {
|
|
@@ -7083,10 +7391,10 @@ function githubRepositoryName(value) {
|
|
|
7083
7391
|
}
|
|
7084
7392
|
return `${owner}/${name}`;
|
|
7085
7393
|
}
|
|
7086
|
-
function trustedGitHubInstallUrl(
|
|
7394
|
+
function trustedGitHubInstallUrl(value2) {
|
|
7087
7395
|
let url;
|
|
7088
7396
|
try {
|
|
7089
|
-
url = new URL(
|
|
7397
|
+
url = new URL(value2);
|
|
7090
7398
|
} catch {
|
|
7091
7399
|
throw new Error("odla.ai returned an invalid GitHub installation URL");
|
|
7092
7400
|
}
|
|
@@ -7095,17 +7403,17 @@ function trustedGitHubInstallUrl(value) {
|
|
|
7095
7403
|
}
|
|
7096
7404
|
return url.toString();
|
|
7097
7405
|
}
|
|
7098
|
-
function hostedPollInterval(
|
|
7099
|
-
if (!Number.isSafeInteger(
|
|
7406
|
+
function hostedPollInterval(value2 = 2e3) {
|
|
7407
|
+
if (!Number.isSafeInteger(value2) || value2 < 100 || value2 > 3e4) {
|
|
7100
7408
|
throw new Error("poll interval must be 100-30000ms");
|
|
7101
7409
|
}
|
|
7102
|
-
return
|
|
7410
|
+
return value2;
|
|
7103
7411
|
}
|
|
7104
|
-
function hostedPollTimeout(
|
|
7105
|
-
if (!Number.isSafeInteger(
|
|
7412
|
+
function hostedPollTimeout(value2 = 10 * 6e4) {
|
|
7413
|
+
if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) {
|
|
7106
7414
|
throw new Error("poll timeout must be 1000-3600000ms");
|
|
7107
7415
|
}
|
|
7108
|
-
return
|
|
7416
|
+
return value2;
|
|
7109
7417
|
}
|
|
7110
7418
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
7111
7419
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
@@ -7117,16 +7425,16 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
7117
7425
|
}, { once: true });
|
|
7118
7426
|
});
|
|
7119
7427
|
}
|
|
7120
|
-
function isValidHostedSecurityPlan(
|
|
7121
|
-
if (!
|
|
7428
|
+
function isValidHostedSecurityPlan(value2, env) {
|
|
7429
|
+
if (!value2 || value2.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value2.planDigest) || value2.consentContract !== "odla.hosted-security-consent.v1" || value2.reportProjection !== "odla.hosted-security-report.v1" || value2.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value2.promptBundle !== "string" || value2.promptBundle.length < 1 || value2.promptBundle.length > 100 || typeof value2.ready !== "boolean" || typeof value2.independent !== "boolean" || value2.sourceDisclosure !== "redacted" || value2.targetExecution !== false || !Number.isSafeInteger(value2.reportRetentionDays) || value2.reportRetentionDays < 1) return false;
|
|
7122
7430
|
const validRoute = (route2, purpose) => !!route2 && route2.purpose === purpose && typeof route2.enabled === "boolean" && typeof route2.credentialReady === "boolean" && typeof route2.provider === "string" && route2.provider.length > 0 && route2.provider.length <= 100 && typeof route2.model === "string" && route2.model.length > 0 && route2.model.length <= 200 && Number.isSafeInteger(route2.policyVersion) && route2.policyVersion >= 1 && Number.isSafeInteger(route2.maxCallsPerRun) && route2.maxCallsPerRun >= 1 && Number.isSafeInteger(route2.maxInputBytes) && route2.maxInputBytes >= 1 && Number.isSafeInteger(route2.maxOutputTokens) && route2.maxOutputTokens >= 1;
|
|
7123
|
-
return validRoute(
|
|
7431
|
+
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
7124
7432
|
}
|
|
7125
|
-
function hostedSecurityCredential(
|
|
7126
|
-
if (typeof
|
|
7433
|
+
function hostedSecurityCredential(value2) {
|
|
7434
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
7127
7435
|
throw new Error("Hosted security requires an injected odla developer token");
|
|
7128
7436
|
}
|
|
7129
|
-
return
|
|
7437
|
+
return value2;
|
|
7130
7438
|
}
|
|
7131
7439
|
|
|
7132
7440
|
// src/security-hosted-github.ts
|
|
@@ -7238,7 +7546,7 @@ async function defaultReadOrigin(cwd) {
|
|
|
7238
7546
|
|
|
7239
7547
|
// src/code-local-source.ts
|
|
7240
7548
|
var import_node_child_process5 = require("child_process");
|
|
7241
|
-
var
|
|
7549
|
+
var import_node_crypto2 = require("crypto");
|
|
7242
7550
|
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
7243
7551
|
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
7244
7552
|
const headCommitSha = await readHead(cwd);
|
|
@@ -7279,25 +7587,25 @@ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
|
7279
7587
|
}
|
|
7280
7588
|
}
|
|
7281
7589
|
async function readGitHead(cwd) {
|
|
7282
|
-
const
|
|
7590
|
+
const value2 = await new Promise((accept, reject) => {
|
|
7283
7591
|
(0, import_node_child_process5.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
7284
7592
|
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
7285
7593
|
else accept(stdout.trim());
|
|
7286
7594
|
});
|
|
7287
7595
|
});
|
|
7288
|
-
if (!/^[0-9a-f]{40}$/.test(
|
|
7289
|
-
return
|
|
7596
|
+
if (!/^[0-9a-f]{40}$/.test(value2)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
7597
|
+
return value2;
|
|
7290
7598
|
}
|
|
7291
|
-
function digestText(
|
|
7292
|
-
return `sha256:${(0,
|
|
7599
|
+
function digestText(value2) {
|
|
7600
|
+
return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(value2).digest("hex")}`;
|
|
7293
7601
|
}
|
|
7294
7602
|
|
|
7295
7603
|
// src/code-images.ts
|
|
7296
7604
|
var import_node_child_process6 = require("child_process");
|
|
7297
|
-
var
|
|
7605
|
+
var import_node_crypto3 = require("crypto");
|
|
7298
7606
|
var import_promises10 = require("fs/promises");
|
|
7299
7607
|
var import_node_os3 = require("os");
|
|
7300
|
-
var
|
|
7608
|
+
var import_node_path12 = require("path");
|
|
7301
7609
|
var import_node_url3 = require("url");
|
|
7302
7610
|
|
|
7303
7611
|
// src/code-runtime-config.ts
|
|
@@ -7380,13 +7688,13 @@ async function embeddedPiImageName() {
|
|
|
7380
7688
|
const bundle = await (0, import_promises10.readFile)(embeddedPiAssetPath()).catch(() => {
|
|
7381
7689
|
throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
|
|
7382
7690
|
});
|
|
7383
|
-
return `odla-ai/pi-agent:embedded-sha256-${(0,
|
|
7691
|
+
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7384
7692
|
}
|
|
7385
7693
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7386
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
7694
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7387
7695
|
try {
|
|
7388
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7389
|
-
await (0, import_promises10.writeFile)((0,
|
|
7696
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
|
|
7697
|
+
await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
|
|
7390
7698
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7391
7699
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7392
7700
|
"WORKDIR /workspace",
|
|
@@ -7402,7 +7710,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7402
7710
|
// src/code-connect.ts
|
|
7403
7711
|
async function codeConnect(options) {
|
|
7404
7712
|
const cwd = options.cwd ?? process.cwd();
|
|
7405
|
-
const configPath = (0,
|
|
7713
|
+
const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
|
|
7406
7714
|
const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7407
7715
|
const requestedAppId = options.appId?.trim();
|
|
7408
7716
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
@@ -7560,8 +7868,8 @@ async function runCodeRuntime(input) {
|
|
|
7560
7868
|
for (const [name, handler] of handlers) process.removeListener(name, handler);
|
|
7561
7869
|
}
|
|
7562
7870
|
}
|
|
7563
|
-
function parseConnection(
|
|
7564
|
-
const root = record4(
|
|
7871
|
+
function parseConnection(value2, appId, appEnv) {
|
|
7872
|
+
const root = record4(value2);
|
|
7565
7873
|
const host = record4(root?.host);
|
|
7566
7874
|
const offer = record4(root?.offer);
|
|
7567
7875
|
const binding = record4(root?.binding);
|
|
@@ -7570,12 +7878,12 @@ function parseConnection(value, appId, appEnv) {
|
|
|
7570
7878
|
}
|
|
7571
7879
|
return root;
|
|
7572
7880
|
}
|
|
7573
|
-
function apiFailure(
|
|
7574
|
-
const message2 = record4(record4(
|
|
7575
|
-
return `${
|
|
7881
|
+
function apiFailure(action2, status, value2) {
|
|
7882
|
+
const message2 = record4(record4(value2)?.error)?.message;
|
|
7883
|
+
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7576
7884
|
}
|
|
7577
|
-
function record4(
|
|
7578
|
-
return
|
|
7885
|
+
function record4(value2) {
|
|
7886
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7579
7887
|
}
|
|
7580
7888
|
|
|
7581
7889
|
// src/code-command.ts
|
|
@@ -7634,8 +7942,8 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
|
7634
7942
|
cacheStatus
|
|
7635
7943
|
};
|
|
7636
7944
|
}
|
|
7637
|
-
function clean3(
|
|
7638
|
-
const normalized =
|
|
7945
|
+
function clean3(value2) {
|
|
7946
|
+
const normalized = value2?.trim();
|
|
7639
7947
|
return normalized || void 0;
|
|
7640
7948
|
}
|
|
7641
7949
|
|
|
@@ -7655,9 +7963,9 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7655
7963
|
],
|
|
7656
7964
|
3
|
|
7657
7965
|
);
|
|
7658
|
-
const
|
|
7966
|
+
const action2 = parsed.positionals[1];
|
|
7659
7967
|
const out = deps.stdout ?? console;
|
|
7660
|
-
if (
|
|
7968
|
+
if (action2 === "list") {
|
|
7661
7969
|
const profiles = listOperatorProfiles();
|
|
7662
7970
|
if (parsed.options.json === true) {
|
|
7663
7971
|
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
@@ -7675,7 +7983,7 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7675
7983
|
}
|
|
7676
7984
|
return;
|
|
7677
7985
|
}
|
|
7678
|
-
if (
|
|
7986
|
+
if (action2 === "save") {
|
|
7679
7987
|
if (parsed.options.token !== void 0) {
|
|
7680
7988
|
throw new Error(
|
|
7681
7989
|
"context save does not accept --token; contexts store scope metadata only"
|
|
@@ -7698,7 +8006,7 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7698
8006
|
}
|
|
7699
8007
|
return;
|
|
7700
8008
|
}
|
|
7701
|
-
if (
|
|
8009
|
+
if (action2 === "remove") {
|
|
7702
8010
|
const name = requireName(parsed);
|
|
7703
8011
|
if (parsed.options.yes !== true) {
|
|
7704
8012
|
throw new Error(`context remove "${name}" requires --yes`);
|
|
@@ -7718,9 +8026,9 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7718
8026
|
}
|
|
7719
8027
|
return;
|
|
7720
8028
|
}
|
|
7721
|
-
if (
|
|
8029
|
+
if (action2 !== "show") {
|
|
7722
8030
|
throw new Error(
|
|
7723
|
-
`unknown context action "${
|
|
8031
|
+
`unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
|
|
7724
8032
|
);
|
|
7725
8033
|
}
|
|
7726
8034
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -7771,6 +8079,261 @@ function requireName(parsed) {
|
|
|
7771
8079
|
return name;
|
|
7772
8080
|
}
|
|
7773
8081
|
|
|
8082
|
+
// src/help.ts
|
|
8083
|
+
function printHelp(output = console) {
|
|
8084
|
+
output.log(`odla-ai
|
|
8085
|
+
|
|
8086
|
+
Start here:
|
|
8087
|
+
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
8088
|
+
runbooks. Ask BEFORE searching the web or
|
|
8089
|
+
working from memory: runbooks are edited
|
|
8090
|
+
live, so your training data is out of date
|
|
8091
|
+
and this is not.
|
|
8092
|
+
odla-ai runbook impact After a change: which runbooks describe the
|
|
8093
|
+
code you just touched, so you can fix any
|
|
8094
|
+
step it made wrong.
|
|
8095
|
+
|
|
8096
|
+
Usage:
|
|
8097
|
+
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8098
|
+
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8099
|
+
odla-ai doctor [--config odla.config.mjs]
|
|
8100
|
+
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8101
|
+
odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8102
|
+
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8103
|
+
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8104
|
+
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
8105
|
+
odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
|
|
8106
|
+
odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
|
|
8107
|
+
odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8108
|
+
odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
|
|
8109
|
+
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
8110
|
+
odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
8111
|
+
odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
8112
|
+
odla-ai app promote [--dry-run] [--json] --yes
|
|
8113
|
+
odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8114
|
+
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8115
|
+
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
8116
|
+
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
8117
|
+
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8118
|
+
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8119
|
+
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8120
|
+
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8121
|
+
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
8122
|
+
odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
8123
|
+
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
8124
|
+
odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
|
|
8125
|
+
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
8126
|
+
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
8127
|
+
odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
|
|
8128
|
+
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
8129
|
+
odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
|
|
8130
|
+
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
8131
|
+
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
8132
|
+
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
8133
|
+
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
8134
|
+
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
8135
|
+
odla-ai pm handoff --app <id> [--json]
|
|
8136
|
+
odla-ai discuss groups [--json]
|
|
8137
|
+
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
8138
|
+
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
8139
|
+
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
8140
|
+
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
8141
|
+
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
8142
|
+
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
8143
|
+
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
8144
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
8145
|
+
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
8146
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
8147
|
+
odla-ai context list [--json]
|
|
8148
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
8149
|
+
odla-ai context remove <name> --yes [--json]
|
|
8150
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8151
|
+
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8152
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8153
|
+
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8154
|
+
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
8155
|
+
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
8156
|
+
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
8157
|
+
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
8158
|
+
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
8159
|
+
odla-ai runbook get <slug> [--app <id>]
|
|
8160
|
+
odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
|
|
8161
|
+
odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
|
|
8162
|
+
odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
|
|
8163
|
+
odla-ai runbook publish <slug> [--app <id>]
|
|
8164
|
+
odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
|
|
8165
|
+
odla-ai runbook archive <slug> [--app <id>]
|
|
8166
|
+
odla-ai runbook history <slug> [--app <id>] [--json]
|
|
8167
|
+
odla-ai runbook revert <slug> --version <n> [--app <id>]
|
|
8168
|
+
odla-ai runbook rm <slug> [--app <id>]
|
|
8169
|
+
odla-ai capabilities [--json]
|
|
8170
|
+
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
8171
|
+
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8172
|
+
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
8173
|
+
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
8174
|
+
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
8175
|
+
odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
|
|
8176
|
+
--validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
|
|
8177
|
+
odla-ai admin ai credentials [--context <name>] [--json]
|
|
8178
|
+
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
8179
|
+
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
8180
|
+
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
8181
|
+
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
8182
|
+
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
8183
|
+
odla-ai security plan [--env dev] [--json]
|
|
8184
|
+
odla-ai security sources [--env dev] [--json]
|
|
8185
|
+
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
8186
|
+
odla-ai security status <job-id> [--follow] [--json]
|
|
8187
|
+
odla-ai security report <job-id> [--json]
|
|
8188
|
+
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
8189
|
+
odla-ai security run [target] --self --ack-redacted-source
|
|
8190
|
+
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
8191
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
8192
|
+
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8193
|
+
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
8194
|
+
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
8195
|
+
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
8196
|
+
odla-ai version
|
|
8197
|
+
|
|
8198
|
+
Commands:
|
|
8199
|
+
agent Inspect durable agent wakeups and explicitly requeue a
|
|
8200
|
+
dead-lettered job; JSON output is stable for remote operators.
|
|
8201
|
+
runbook odla's operational procedures, stored in the database and read at
|
|
8202
|
+
the moment they are followed. "ask" gives a written, cited answer;
|
|
8203
|
+
"search" the passages behind it; "get" the whole document.
|
|
8204
|
+
"impact" diffs your working tree against a base ref and names the
|
|
8205
|
+
runbooks that describe what you changed \u2014 run it after touching a
|
|
8206
|
+
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
8207
|
+
checks the corpus the other way: every odla-ai command the
|
|
8208
|
+
runbooks name is held against this CLI's real command surface,
|
|
8209
|
+
and against the minimum versions each runbook declares. A wrong step
|
|
8210
|
+
is fixed with "edit", which takes effect immediately: a runbook is
|
|
8211
|
+
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
8212
|
+
does and what it guarantees is JSDoc, rendered per package at
|
|
8213
|
+
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8214
|
+
a question usually needs both.
|
|
8215
|
+
whoami Report who this terminal is authenticated as, and whether it holds
|
|
8216
|
+
platform admin. A handshake-minted device token is never admin,
|
|
8217
|
+
however the human who approved it is configured.
|
|
8218
|
+
context Explain selected config, platform, app, environment, and
|
|
8219
|
+
developer-token provenance without printing credentials or
|
|
8220
|
+
starting a device handshake. Operator work can run outside a
|
|
8221
|
+
project checkout with explicit flags or ODLA_* environment
|
|
8222
|
+
variables.
|
|
8223
|
+
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8224
|
+
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8225
|
+
doctor Validate and summarize the project config without network calls.
|
|
8226
|
+
config Read-only diff and revision-bound plan for checked-in Registry
|
|
8227
|
+
intent; runtime-owned fields and excluded coverage stay explicit.
|
|
8228
|
+
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8229
|
+
app Archive (suspend, data retained), restore, export, import, or
|
|
8230
|
+
manage the co-owners of the app. Archiving takes every
|
|
8231
|
+
environment's data plane down until restored; permanent deletion
|
|
8232
|
+
has NO CLI \u2014 it requires a signed-in owner in Studio, and no
|
|
8233
|
+
machine or agent credential can purge. "app export" downloads a
|
|
8234
|
+
portable gzipped-JSONL snapshot of one database (--fresh takes a
|
|
8235
|
+
new snapshot first) \u2014 your data is always yours to take.
|
|
8236
|
+
"app import" upserts rows back in from JSON, JSONL, or a
|
|
8237
|
+
{namespace: rows} map (the shape a query returns); it writes only
|
|
8238
|
+
with --yes, so the bare command is a dry run, and it refuses to
|
|
8239
|
+
guess an id mode that would duplicate rows on a re-run.
|
|
8240
|
+
"app refresh-sandbox" replaces the sandbox with a copy of live
|
|
8241
|
+
(the routine dev loop); "app go-live" copies the sandbox into an
|
|
8242
|
+
EMPTY live database, once, at launch; "app promote" pushes only
|
|
8243
|
+
schema, rules and gates up afterwards, leaving live's rows alone.
|
|
8244
|
+
Each prints its plan and writes nothing without --yes, so the
|
|
8245
|
+
bare command is the dry run. Clerk config, triggers, allowlists,
|
|
8246
|
+
secrets and API keys never travel; identity rows stay behind
|
|
8247
|
+
unless --include-identity.
|
|
8248
|
+
"app rename <name>" changes only the display name humans read;
|
|
8249
|
+
the app id is permanent (tenants, URLs and keys embed it), so a
|
|
8250
|
+
rename never re-provisions anything or invalidates a credential.
|
|
8251
|
+
"app owners add <email>" grants a signed-up odla member the SAME
|
|
8252
|
+
full access as you; they then run their own "provision" to mint
|
|
8253
|
+
their own credentials for the shared db (the live one included)
|
|
8254
|
+
\u2014 no secret is ever copied between people.
|
|
8255
|
+
capabilities Show what the CLI automates vs agent edits and human checkpoints.
|
|
8256
|
+
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
8257
|
+
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
8258
|
+
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
8259
|
+
pm Project management (via @odla-ai/pm) shared across the apps you
|
|
8260
|
+
co-own: conformance goals, kanban tasks, decisions, and bugs. With
|
|
8261
|
+
no --app, "list" spans every co-owned project (the cross-project
|
|
8262
|
+
view); with --app it scopes to one. Same device-grant auth as
|
|
8263
|
+
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
8264
|
+
decision, bug. Status changes and comments post to each item's
|
|
8265
|
+
@odla-ai/chat discussion thread.
|
|
8266
|
+
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
8267
|
+
one group per project, topics with replies, @-mentions of people,
|
|
8268
|
+
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
8269
|
+
question, then "watch" it until someone answers. "watch" exits 75
|
|
8270
|
+
(not 1) when it times out with nothing new, so a script can tell
|
|
8271
|
+
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
8272
|
+
up the exact @[Label](kind/id) markup for a mention.
|
|
8273
|
+
o11y Read one stable status envelope for application RED, current
|
|
8274
|
+
live-sync load/freshness, the protected commit-to-visible
|
|
8275
|
+
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
8276
|
+
runtime metrics, and a machine verdict.
|
|
8277
|
+
--json keeps auth progress on stderr for unattended agents.
|
|
8278
|
+
platform Read canonical fleet health, releases, provider load/freshness,
|
|
8279
|
+
explicit unknowns, and next actions through a read-only grant.
|
|
8280
|
+
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
8281
|
+
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
8282
|
+
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
8283
|
+
copilot, gemini, or agents (repeatable or comma-separated).
|
|
8284
|
+
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
8285
|
+
stdin; set stores a tenant-vault secret and set-clerk-key the
|
|
8286
|
+
reserved Clerk secret key, write-only from stdin or an env var.
|
|
8287
|
+
version Print the CLI version.
|
|
8288
|
+
|
|
8289
|
+
Safety:
|
|
8290
|
+
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
8291
|
+
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
8292
|
+
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
8293
|
+
the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
|
|
8294
|
+
provision the live database; use --dry-run first to inspect the resolved plan.
|
|
8295
|
+
Provision caches the approved developer token and service credentials under
|
|
8296
|
+
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
8297
|
+
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
8298
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
8299
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
8300
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
8301
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
8302
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
8303
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
8304
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
8305
|
+
context, which beats project config. There is no ambient current context.
|
|
8306
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
8307
|
+
Provision opens the approval page in your browser automatically whenever the
|
|
8308
|
+
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
8309
|
+
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
8310
|
+
Browser launch is best-effort: the printed approval URL is authoritative and
|
|
8311
|
+
agents must relay it to the human verbatim. A started handshake is persisted
|
|
8312
|
+
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
8313
|
+
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
8314
|
+
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
8315
|
+
with code 75: relay the URL, wait for approval, and re-run to collect.
|
|
8316
|
+
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8317
|
+
The email is a non-secret identity hint: never provide a password or session
|
|
8318
|
+
token. The matching account must already exist, be signed in, explicitly
|
|
8319
|
+
review the exact code, and finish any current request before claiming another.
|
|
8320
|
+
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
8321
|
+
odla.config.mjs may select the app explicitly but is not required. Code host
|
|
8322
|
+
approval and credential hashes live in odla-ai/db. The host
|
|
8323
|
+
credential is never written under .odla/; it exists only in the foreground
|
|
8324
|
+
"code connect" process and is rotated by the next approved connection.
|
|
8325
|
+
Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
|
|
8326
|
+
OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
|
|
8327
|
+
GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
|
|
8328
|
+
for a PAT or provider key. GitHub read access is separate from the explicit
|
|
8329
|
+
--ack-redacted-source consent and the exact --plan-digest printed by security
|
|
8330
|
+
plan are required before bounded snippets reach System AI.
|
|
8331
|
+
Run security plan first to inspect the admin-selected providers, models,
|
|
8332
|
+
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
8333
|
+
and digest that binds consent to that exact plan.
|
|
8334
|
+
`);
|
|
8335
|
+
}
|
|
8336
|
+
|
|
7774
8337
|
// src/discuss-actions.ts
|
|
7775
8338
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7776
8339
|
async function request(ctx, method, path, body) {
|
|
@@ -7784,8 +8347,8 @@ async function request(ctx, method, path, body) {
|
|
|
7784
8347
|
throw new Error(`discuss ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
|
|
7785
8348
|
return data;
|
|
7786
8349
|
}
|
|
7787
|
-
function emit(ctx,
|
|
7788
|
-
if (ctx.json) ctx.out.log(JSON.stringify(
|
|
8350
|
+
function emit(ctx, value2, human) {
|
|
8351
|
+
if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
|
|
7789
8352
|
else human();
|
|
7790
8353
|
}
|
|
7791
8354
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
@@ -7830,8 +8393,8 @@ async function discussList(ctx, parsed) {
|
|
|
7830
8393
|
["limit", "limit"],
|
|
7831
8394
|
["offset", "offset"]
|
|
7832
8395
|
]) {
|
|
7833
|
-
const
|
|
7834
|
-
if (
|
|
8396
|
+
const value2 = stringOpt(parsed.options[flag]);
|
|
8397
|
+
if (value2) query.set(param, value2);
|
|
7835
8398
|
}
|
|
7836
8399
|
const qs = query.toString();
|
|
7837
8400
|
const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
|
|
@@ -8028,11 +8591,11 @@ var MAX_CONSECUTIVE_FAILURES = 5;
|
|
|
8028
8591
|
var MAX_BACKOFF_MS = 3e4;
|
|
8029
8592
|
function numberOpt2(parsed, flag, fallback) {
|
|
8030
8593
|
const raw = stringOpt(parsed.options[flag]);
|
|
8031
|
-
const
|
|
8032
|
-
return Number.isFinite(
|
|
8594
|
+
const value2 = raw == null ? NaN : Number(raw);
|
|
8595
|
+
return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
|
|
8033
8596
|
}
|
|
8034
|
-
function jsonl(ctx, parsed,
|
|
8035
|
-
if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...
|
|
8597
|
+
function jsonl(ctx, parsed, value2) {
|
|
8598
|
+
if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
|
|
8036
8599
|
}
|
|
8037
8600
|
async function discussWatch(ctx, topicId, parsed) {
|
|
8038
8601
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
@@ -8192,8 +8755,8 @@ var ALLOWED = [
|
|
|
8192
8755
|
"platform",
|
|
8193
8756
|
"context"
|
|
8194
8757
|
];
|
|
8195
|
-
function requireId(id,
|
|
8196
|
-
if (!id) throw new Error(`"discuss ${
|
|
8758
|
+
function requireId(id, action2) {
|
|
8759
|
+
if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
8197
8760
|
return id;
|
|
8198
8761
|
}
|
|
8199
8762
|
async function buildContext(parsed, deps) {
|
|
@@ -8227,11 +8790,11 @@ async function buildContext(parsed, deps) {
|
|
|
8227
8790
|
}
|
|
8228
8791
|
async function discussCommand(parsed, deps = {}) {
|
|
8229
8792
|
assertArgs(parsed, ALLOWED, 3);
|
|
8230
|
-
const
|
|
8793
|
+
const action2 = parsed.positionals[1];
|
|
8231
8794
|
const id = parsed.positionals[2];
|
|
8232
|
-
if (!
|
|
8795
|
+
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
8233
8796
|
const ctx = await buildContext(parsed, deps);
|
|
8234
|
-
switch (
|
|
8797
|
+
switch (action2) {
|
|
8235
8798
|
case "groups":
|
|
8236
8799
|
return discussGroups(ctx);
|
|
8237
8800
|
case "list":
|
|
@@ -8253,7 +8816,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
8253
8816
|
return;
|
|
8254
8817
|
}
|
|
8255
8818
|
default:
|
|
8256
|
-
throw new Error(`unknown discuss action "${
|
|
8819
|
+
throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
|
|
8257
8820
|
}
|
|
8258
8821
|
}
|
|
8259
8822
|
|
|
@@ -8294,13 +8857,13 @@ async function pmRequest(ctx, method, path, body) {
|
|
|
8294
8857
|
function collectFields(parsed, allowClear) {
|
|
8295
8858
|
const out = {};
|
|
8296
8859
|
for (const [flag, spec] of Object.entries(FIELD_MAP)) {
|
|
8297
|
-
const
|
|
8298
|
-
if (
|
|
8299
|
-
if (
|
|
8860
|
+
const value2 = parsed.options[flag];
|
|
8861
|
+
if (value2 === void 0 || value2 === true) continue;
|
|
8862
|
+
if (value2 === false) {
|
|
8300
8863
|
if (allowClear) out[spec.key] = null;
|
|
8301
8864
|
continue;
|
|
8302
8865
|
}
|
|
8303
|
-
const text = stringOpt(
|
|
8866
|
+
const text = stringOpt(value2);
|
|
8304
8867
|
out[spec.key] = spec.num ? Number(text) : text;
|
|
8305
8868
|
}
|
|
8306
8869
|
return out;
|
|
@@ -8321,8 +8884,8 @@ function statusCol(entity, r) {
|
|
|
8321
8884
|
function printRecord(ctx, entity, r) {
|
|
8322
8885
|
ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
|
|
8323
8886
|
}
|
|
8324
|
-
function emit2(ctx,
|
|
8325
|
-
if (ctx.json) ctx.out.log(JSON.stringify(
|
|
8887
|
+
function emit2(ctx, value2, human) {
|
|
8888
|
+
if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
|
|
8326
8889
|
else human();
|
|
8327
8890
|
}
|
|
8328
8891
|
async function pmList(ctx, entity, parsed) {
|
|
@@ -8368,8 +8931,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8368
8931
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8369
8932
|
}
|
|
8370
8933
|
async function pmGet(ctx, entity, id) {
|
|
8371
|
-
const { record:
|
|
8372
|
-
emit2(ctx,
|
|
8934
|
+
const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
8935
|
+
emit2(ctx, record8, () => printRecord(ctx, entity, record8));
|
|
8373
8936
|
}
|
|
8374
8937
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8375
8938
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -8414,9 +8977,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8414
8977
|
]);
|
|
8415
8978
|
const handoff = {
|
|
8416
8979
|
appId,
|
|
8417
|
-
unmetGoals: goals.filter((
|
|
8418
|
-
activeTasks: tasks.filter((
|
|
8419
|
-
openBugs: bugs.filter((
|
|
8980
|
+
unmetGoals: goals.filter((record8) => record8.status !== "met"),
|
|
8981
|
+
activeTasks: tasks.filter((record8) => record8.column !== "done"),
|
|
8982
|
+
openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
|
|
8420
8983
|
};
|
|
8421
8984
|
const result = {
|
|
8422
8985
|
...handoff,
|
|
@@ -8435,10 +8998,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8435
8998
|
]) {
|
|
8436
8999
|
ctx.out.log(`${label}:`);
|
|
8437
9000
|
if (!records.length) ctx.out.log("- (none)");
|
|
8438
|
-
else for (const
|
|
9001
|
+
else for (const record8 of records) printRecord(
|
|
8439
9002
|
ctx,
|
|
8440
9003
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
8441
|
-
|
|
9004
|
+
record8
|
|
8442
9005
|
);
|
|
8443
9006
|
}
|
|
8444
9007
|
});
|
|
@@ -8514,18 +9077,18 @@ var ENTITY_OPTIONS = {
|
|
|
8514
9077
|
done: ["decision"]
|
|
8515
9078
|
}
|
|
8516
9079
|
};
|
|
8517
|
-
function canonicalAction(
|
|
8518
|
-
if (
|
|
8519
|
-
if (
|
|
8520
|
-
if (
|
|
8521
|
-
return
|
|
8522
|
-
}
|
|
8523
|
-
function allowedOptions(entity,
|
|
8524
|
-
const entityOptions =
|
|
8525
|
-
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[
|
|
8526
|
-
}
|
|
8527
|
-
function requireId2(id,
|
|
8528
|
-
if (!id) throw new Error(`"pm ... ${
|
|
9080
|
+
function canonicalAction(action2) {
|
|
9081
|
+
if (action2 === "create") return "add";
|
|
9082
|
+
if (action2 === "update" || action2 === "status" || action2 === "move") return "set";
|
|
9083
|
+
if (action2 === "delete") return "rm";
|
|
9084
|
+
return action2 in ACTION_OPTIONS ? action2 : null;
|
|
9085
|
+
}
|
|
9086
|
+
function allowedOptions(entity, action2) {
|
|
9087
|
+
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
9088
|
+
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
9089
|
+
}
|
|
9090
|
+
function requireId2(id, action2) {
|
|
9091
|
+
if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
8529
9092
|
return id;
|
|
8530
9093
|
}
|
|
8531
9094
|
async function buildContext2(parsed, deps) {
|
|
@@ -8561,29 +9124,136 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8561
9124
|
const entity = ALIASES[word];
|
|
8562
9125
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8563
9126
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
8564
|
-
const
|
|
8565
|
-
if (!
|
|
8566
|
-
assertArgs(parsed, allowedOptions(entity,
|
|
9127
|
+
const action2 = canonicalAction(requestedAction);
|
|
9128
|
+
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
9129
|
+
assertArgs(parsed, allowedOptions(entity, action2), 4);
|
|
8567
9130
|
const ctx = await buildContext2(parsed, deps);
|
|
8568
9131
|
const id = parsed.positionals[3];
|
|
8569
|
-
switch (
|
|
9132
|
+
switch (action2) {
|
|
8570
9133
|
case "list":
|
|
8571
9134
|
return pmList(ctx, entity, parsed);
|
|
8572
9135
|
case "add":
|
|
8573
9136
|
return pmAdd(ctx, entity, parsed);
|
|
8574
9137
|
case "get":
|
|
8575
|
-
return pmGet(ctx, entity, requireId2(id,
|
|
9138
|
+
return pmGet(ctx, entity, requireId2(id, action2));
|
|
8576
9139
|
case "set":
|
|
8577
|
-
return pmSet(ctx, entity, requireId2(id,
|
|
9140
|
+
return pmSet(ctx, entity, requireId2(id, action2), parsed);
|
|
8578
9141
|
case "done":
|
|
8579
|
-
return pmDone(ctx, entity, requireId2(id,
|
|
9142
|
+
return pmDone(ctx, entity, requireId2(id, action2), parsed);
|
|
8580
9143
|
case "comment":
|
|
8581
|
-
return pmComment(ctx, entity, requireId2(id,
|
|
9144
|
+
return pmComment(ctx, entity, requireId2(id, action2), parsed);
|
|
8582
9145
|
case "comments":
|
|
8583
|
-
return pmComments(ctx, entity, requireId2(id,
|
|
9146
|
+
return pmComments(ctx, entity, requireId2(id, action2));
|
|
8584
9147
|
case "rm":
|
|
8585
|
-
return pmRemove(ctx, entity, requireId2(id,
|
|
9148
|
+
return pmRemove(ctx, entity, requireId2(id, action2));
|
|
9149
|
+
}
|
|
9150
|
+
}
|
|
9151
|
+
|
|
9152
|
+
// src/platform-output.ts
|
|
9153
|
+
function printPlatformStatus(status, out) {
|
|
9154
|
+
out.log(`platform ${status.verdict.status} ${status.observedAt}`);
|
|
9155
|
+
out.log(
|
|
9156
|
+
`fleet ${status.summary.healthy}/${status.summary.services} healthy ${status.summary.required} required ${status.summary.unreachable} unreachable ${status.summary.notConfigured} not configured`
|
|
9157
|
+
);
|
|
9158
|
+
out.log(`catalog ${status.catalog.revision}`);
|
|
9159
|
+
out.log(
|
|
9160
|
+
"service required health probe ms version provider age requests errors cpu p99 us wall p99 us"
|
|
9161
|
+
);
|
|
9162
|
+
for (const service of status.services) {
|
|
9163
|
+
const metrics = service.provider.metrics;
|
|
9164
|
+
out.log([
|
|
9165
|
+
service.id,
|
|
9166
|
+
service.required ? "yes" : "no",
|
|
9167
|
+
service.health.status,
|
|
9168
|
+
value(service.health.latencyMs),
|
|
9169
|
+
service.release.observedVersionId ?? "unknown",
|
|
9170
|
+
service.provider.status,
|
|
9171
|
+
age(service.provider.ageMs),
|
|
9172
|
+
value(metrics?.requests),
|
|
9173
|
+
value(metrics?.errors),
|
|
9174
|
+
value(metrics?.cpuTimeP99),
|
|
9175
|
+
value(metrics?.wallTimeP99)
|
|
9176
|
+
].join(" "));
|
|
9177
|
+
}
|
|
9178
|
+
if (status.verdict.reasons.length) {
|
|
9179
|
+
out.log(`reasons ${status.verdict.reasons.join(", ")}`);
|
|
9180
|
+
}
|
|
9181
|
+
for (const action2 of status.nextActions) {
|
|
9182
|
+
out.log(`next ${action2.service} ${action2.code} ${action2.message}`);
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
function value(input) {
|
|
9186
|
+
return input === null || input === void 0 ? "unknown" : String(input);
|
|
9187
|
+
}
|
|
9188
|
+
function age(input) {
|
|
9189
|
+
if (input === null) return "unknown";
|
|
9190
|
+
if (input < 1e3) return `${input}ms`;
|
|
9191
|
+
if (input < 6e4) return `${Math.round(input / 1e3)}s`;
|
|
9192
|
+
return `${Math.round(input / 6e4)}m`;
|
|
9193
|
+
}
|
|
9194
|
+
|
|
9195
|
+
// src/platform-command.ts
|
|
9196
|
+
async function platformCommand(parsed, deps = {}) {
|
|
9197
|
+
assertArgs(
|
|
9198
|
+
parsed,
|
|
9199
|
+
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9200
|
+
2
|
|
9201
|
+
);
|
|
9202
|
+
const action2 = parsed.positionals[1];
|
|
9203
|
+
if (action2 !== "status") {
|
|
9204
|
+
throw new Error(
|
|
9205
|
+
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9206
|
+
);
|
|
9207
|
+
}
|
|
9208
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9209
|
+
allowMissingConfig: true
|
|
9210
|
+
});
|
|
9211
|
+
const platform = context.platform.value;
|
|
9212
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9213
|
+
const out = deps.stdout ?? console;
|
|
9214
|
+
const token = await resolveAdminPlatformToken({
|
|
9215
|
+
platform,
|
|
9216
|
+
scope: "platform:status:read",
|
|
9217
|
+
token: stringOpt(parsed.options.token),
|
|
9218
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9219
|
+
email: stringOpt(parsed.options.email),
|
|
9220
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9221
|
+
fetch: doFetch,
|
|
9222
|
+
stdout: out,
|
|
9223
|
+
openApprovalUrl: deps.openUrl,
|
|
9224
|
+
label: "odla CLI (platform fleet status)"
|
|
9225
|
+
});
|
|
9226
|
+
const response2 = await doFetch(`${platform}/registry/platform/status`, {
|
|
9227
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9228
|
+
});
|
|
9229
|
+
const body = await response2.json().catch(() => null);
|
|
9230
|
+
if (!response2.ok) {
|
|
9231
|
+
throw new Error(
|
|
9232
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage(body)}`
|
|
9233
|
+
);
|
|
9234
|
+
}
|
|
9235
|
+
if (!isPlatformStatus(body)) {
|
|
9236
|
+
throw new Error("platform returned an invalid odla.platform-status/v1 envelope");
|
|
8586
9237
|
}
|
|
9238
|
+
if (parsed.options.json === true) {
|
|
9239
|
+
out.log(JSON.stringify(body, null, 2));
|
|
9240
|
+
} else {
|
|
9241
|
+
printPlatformStatus(body, out);
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9244
|
+
function isPlatformStatus(value2) {
|
|
9245
|
+
if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9246
|
+
if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9247
|
+
if (!record5(value2.catalog) || !record5(value2.summary)) return false;
|
|
9248
|
+
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9249
|
+
}
|
|
9250
|
+
function apiMessage(value2) {
|
|
9251
|
+
if (!record5(value2)) return "request failed";
|
|
9252
|
+
const error = record5(value2.error) ? value2.error : value2;
|
|
9253
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9254
|
+
}
|
|
9255
|
+
function record5(value2) {
|
|
9256
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
8587
9257
|
}
|
|
8588
9258
|
|
|
8589
9259
|
// src/o11y-verdict.ts
|
|
@@ -8623,7 +9293,7 @@ function statusVerdict(reads) {
|
|
|
8623
9293
|
severity: "degraded"
|
|
8624
9294
|
});
|
|
8625
9295
|
}
|
|
8626
|
-
const performance =
|
|
9296
|
+
const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
8627
9297
|
if (performance?.status === "unavailable") {
|
|
8628
9298
|
reasons.push({
|
|
8629
9299
|
source: "liveSync",
|
|
@@ -8704,11 +9374,11 @@ function statusVerdict(reads) {
|
|
|
8704
9374
|
reasons
|
|
8705
9375
|
};
|
|
8706
9376
|
}
|
|
8707
|
-
function
|
|
8708
|
-
return Boolean(
|
|
9377
|
+
function record6(value2) {
|
|
9378
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
8709
9379
|
}
|
|
8710
|
-
function numeric2(
|
|
8711
|
-
return typeof
|
|
9380
|
+
function numeric2(value2) {
|
|
9381
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
8712
9382
|
}
|
|
8713
9383
|
function collectorReason(read3, fallback) {
|
|
8714
9384
|
const reasons = read3.body.reasons;
|
|
@@ -8732,7 +9402,7 @@ function printO11yStatus(status, out) {
|
|
|
8732
9402
|
out.log(
|
|
8733
9403
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
8734
9404
|
);
|
|
8735
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
9405
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
|
|
8736
9406
|
const requests = routes.reduce(
|
|
8737
9407
|
(total, row) => total + numeric3(row.requests),
|
|
8738
9408
|
0
|
|
@@ -8744,39 +9414,39 @@ function printO11yStatus(status, out) {
|
|
|
8744
9414
|
out.log(
|
|
8745
9415
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
8746
9416
|
);
|
|
8747
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
9417
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
|
|
8748
9418
|
out.log(
|
|
8749
9419
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
8750
9420
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
8751
9421
|
).join(", ") : "none observed"}`
|
|
8752
9422
|
);
|
|
8753
9423
|
out.log(liveSyncLine(status.liveSync));
|
|
8754
|
-
const canaryDurations =
|
|
9424
|
+
const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
8755
9425
|
out.log(
|
|
8756
9426
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
8757
9427
|
);
|
|
8758
|
-
const collectorIngest =
|
|
8759
|
-
const collectorStorage =
|
|
9428
|
+
const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9429
|
+
const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
8760
9430
|
out.log(
|
|
8761
9431
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8762
9432
|
);
|
|
8763
|
-
const providerMetrics =
|
|
8764
|
-
const providerCapacity =
|
|
8765
|
-
const workerMemory =
|
|
9433
|
+
const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9434
|
+
const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9435
|
+
const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
8766
9436
|
out.log(
|
|
8767
9437
|
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
|
|
8768
9438
|
);
|
|
8769
9439
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
8770
9440
|
out.log(line);
|
|
8771
9441
|
}
|
|
8772
|
-
const coverage =
|
|
8773
|
-
const coverageCounts =
|
|
8774
|
-
const coverageBudget =
|
|
9442
|
+
const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9443
|
+
const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9444
|
+
const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
8775
9445
|
out.log(
|
|
8776
9446
|
`request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
|
|
8777
9447
|
);
|
|
8778
9448
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
8779
|
-
const providerFreshness =
|
|
9449
|
+
const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
8780
9450
|
out.log(
|
|
8781
9451
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
8782
9452
|
);
|
|
@@ -8785,17 +9455,17 @@ function printO11yStatus(status, out) {
|
|
|
8785
9455
|
);
|
|
8786
9456
|
}
|
|
8787
9457
|
function providerCapacityLines(read3) {
|
|
8788
|
-
const resources =
|
|
8789
|
-
const durableObjects =
|
|
8790
|
-
const periodic =
|
|
8791
|
-
const storage =
|
|
8792
|
-
const d1 =
|
|
8793
|
-
const d1Activity =
|
|
8794
|
-
const d1Storage =
|
|
8795
|
-
const d1Latency =
|
|
8796
|
-
const r2 =
|
|
8797
|
-
const r2Operations =
|
|
8798
|
-
const r2Storage =
|
|
9458
|
+
const resources = record7(read3.body.resources) ? read3.body.resources : {};
|
|
9459
|
+
const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
|
|
9460
|
+
const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9461
|
+
const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9462
|
+
const d1 = record7(resources.d1) ? resources.d1 : {};
|
|
9463
|
+
const d1Activity = record7(d1.activity) ? d1.activity : {};
|
|
9464
|
+
const d1Storage = record7(d1.storage) ? d1.storage : {};
|
|
9465
|
+
const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
|
|
9466
|
+
const r2 = record7(resources.r2) ? resources.r2 : {};
|
|
9467
|
+
const r2Operations = record7(r2.operations) ? r2.operations : {};
|
|
9468
|
+
const r2Storage = record7(r2.storage) ? r2.storage : {};
|
|
8799
9469
|
const status = String(
|
|
8800
9470
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
8801
9471
|
);
|
|
@@ -8806,42 +9476,42 @@ function providerCapacityLines(read3) {
|
|
|
8806
9476
|
];
|
|
8807
9477
|
}
|
|
8808
9478
|
function liveSyncLine(read3) {
|
|
8809
|
-
const performance =
|
|
8810
|
-
const commitToSend =
|
|
9479
|
+
const performance = record7(read3.body.performance) ? read3.body.performance : {};
|
|
9480
|
+
const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
|
|
8811
9481
|
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
8812
9482
|
}
|
|
8813
|
-
function
|
|
8814
|
-
return Boolean(
|
|
9483
|
+
function record7(value2) {
|
|
9484
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
8815
9485
|
}
|
|
8816
|
-
function numeric3(
|
|
8817
|
-
return typeof
|
|
9486
|
+
function numeric3(value2) {
|
|
9487
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
8818
9488
|
}
|
|
8819
|
-
function optionalNumeric(
|
|
8820
|
-
return typeof
|
|
9489
|
+
function optionalNumeric(value2) {
|
|
9490
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ms` : "\u2014";
|
|
8821
9491
|
}
|
|
8822
|
-
function optionalAge(
|
|
8823
|
-
if (typeof
|
|
8824
|
-
return `${Math.max(0, Math.round(
|
|
9492
|
+
function optionalAge(value2) {
|
|
9493
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return "unknown";
|
|
9494
|
+
return `${Math.max(0, Math.round(value2 / 1e3))}s`;
|
|
8825
9495
|
}
|
|
8826
|
-
function optionalPercent(
|
|
8827
|
-
return typeof
|
|
9496
|
+
function optionalPercent(value2) {
|
|
9497
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.round(value2 * 100)}%` : "\u2014";
|
|
8828
9498
|
}
|
|
8829
|
-
function optionalBytes(
|
|
8830
|
-
if (typeof
|
|
8831
|
-
if (
|
|
8832
|
-
return `${(
|
|
9499
|
+
function optionalBytes(value2) {
|
|
9500
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return "\u2014";
|
|
9501
|
+
if (value2 >= 1024 * 1024 * 1024) {
|
|
9502
|
+
return `${(value2 / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8833
9503
|
}
|
|
8834
|
-
if (
|
|
8835
|
-
return `${(
|
|
9504
|
+
if (value2 >= 1024 * 1024) {
|
|
9505
|
+
return `${(value2 / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8836
9506
|
}
|
|
8837
|
-
if (
|
|
8838
|
-
return `${Math.round(
|
|
9507
|
+
if (value2 >= 1024) return `${(value2 / 1024).toFixed(1)} KiB`;
|
|
9508
|
+
return `${Math.round(value2)} B`;
|
|
8839
9509
|
}
|
|
8840
|
-
function optionalCount(
|
|
8841
|
-
return typeof
|
|
9510
|
+
function optionalCount(value2, unit) {
|
|
9511
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ${unit}` : `\u2014 ${unit}`;
|
|
8842
9512
|
}
|
|
8843
|
-
function optionalAgeSeconds(
|
|
8844
|
-
return typeof
|
|
9513
|
+
function optionalAgeSeconds(value2) {
|
|
9514
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.max(0, Math.round(value2))}s` : "unknown";
|
|
8845
9515
|
}
|
|
8846
9516
|
|
|
8847
9517
|
// src/o11y-command.ts
|
|
@@ -8861,10 +9531,10 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8861
9531
|
],
|
|
8862
9532
|
2
|
|
8863
9533
|
);
|
|
8864
|
-
const
|
|
8865
|
-
if (
|
|
9534
|
+
const action2 = parsed.positionals[1];
|
|
9535
|
+
if (action2 !== "status") {
|
|
8866
9536
|
throw new Error(
|
|
8867
|
-
`unknown o11y action "${
|
|
9537
|
+
`unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
|
|
8868
9538
|
);
|
|
8869
9539
|
}
|
|
8870
9540
|
const minutes = statusMinutes(
|
|
@@ -8971,11 +9641,11 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8971
9641
|
}
|
|
8972
9642
|
printO11yStatus(status, out);
|
|
8973
9643
|
}
|
|
8974
|
-
function statusMinutes(
|
|
8975
|
-
if (!Number.isSafeInteger(
|
|
9644
|
+
function statusMinutes(value2) {
|
|
9645
|
+
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 7 * 24 * 60) {
|
|
8976
9646
|
throw new Error("--minutes must be an integer from 1 to 10080");
|
|
8977
9647
|
}
|
|
8978
|
-
return
|
|
9648
|
+
return value2;
|
|
8979
9649
|
}
|
|
8980
9650
|
async function read2(url, headers, doFetch) {
|
|
8981
9651
|
const response2 = await doFetch(url, { headers });
|
|
@@ -8983,8 +9653,8 @@ async function read2(url, headers, doFetch) {
|
|
|
8983
9653
|
let body = {};
|
|
8984
9654
|
if (text) {
|
|
8985
9655
|
try {
|
|
8986
|
-
const
|
|
8987
|
-
body =
|
|
9656
|
+
const value2 = JSON.parse(text);
|
|
9657
|
+
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
8988
9658
|
} catch {
|
|
8989
9659
|
body = { message: text.slice(0, 300) };
|
|
8990
9660
|
}
|
|
@@ -8993,7 +9663,7 @@ async function read2(url, headers, doFetch) {
|
|
|
8993
9663
|
}
|
|
8994
9664
|
|
|
8995
9665
|
// src/provision.ts
|
|
8996
|
-
var
|
|
9666
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
8997
9667
|
var import_ai3 = require("@odla-ai/ai");
|
|
8998
9668
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
8999
9669
|
|
|
@@ -9003,7 +9673,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9003
9673
|
const base = `${endpoint}/app/${encodeURIComponent(tenantId)}`;
|
|
9004
9674
|
for (const integration of integrations) {
|
|
9005
9675
|
for (const seed of integration.seeds ?? []) {
|
|
9006
|
-
const payload = await
|
|
9676
|
+
const payload = await postJson3(doFetch, `${base}/query`, dbKey, {
|
|
9007
9677
|
query: { [seed.ns]: { $: { where: { [seed.key.attr]: seed.key.value }, limit: 1 } } }
|
|
9008
9678
|
});
|
|
9009
9679
|
const rows = isRecord7(payload) && isRecord7(payload.result) ? payload.result[seed.ns] : void 0;
|
|
@@ -9014,7 +9684,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9014
9684
|
out.log(`${env}: integration ${integration.id} seed ${seed.id} already exists`);
|
|
9015
9685
|
continue;
|
|
9016
9686
|
}
|
|
9017
|
-
await
|
|
9687
|
+
await postJson3(doFetch, `${base}/transact`, dbKey, {
|
|
9018
9688
|
mutationId: `integration:${integration.id}:seed:${seed.id}`,
|
|
9019
9689
|
ops: [{
|
|
9020
9690
|
t: "update",
|
|
@@ -9029,7 +9699,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9029
9699
|
}
|
|
9030
9700
|
}
|
|
9031
9701
|
}
|
|
9032
|
-
async function
|
|
9702
|
+
async function postJson3(doFetch, url, bearer, body) {
|
|
9033
9703
|
const res = await doFetch(url, {
|
|
9034
9704
|
method: "POST",
|
|
9035
9705
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
@@ -9038,8 +9708,8 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
9038
9708
|
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await responseText(res)}`);
|
|
9039
9709
|
return res.json().catch(() => ({}));
|
|
9040
9710
|
}
|
|
9041
|
-
function isRecord7(
|
|
9042
|
-
return
|
|
9711
|
+
function isRecord7(value2) {
|
|
9712
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
9043
9713
|
}
|
|
9044
9714
|
async function responseText(res) {
|
|
9045
9715
|
try {
|
|
@@ -9050,9 +9720,9 @@ async function responseText(res) {
|
|
|
9050
9720
|
}
|
|
9051
9721
|
|
|
9052
9722
|
// src/provision-credentials.ts
|
|
9053
|
-
var
|
|
9723
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
9054
9724
|
async function provisionEnvCredentials(opts) {
|
|
9055
|
-
const tenantId = (0,
|
|
9725
|
+
const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9056
9726
|
const prior = opts.credentials?.envs[opts.env];
|
|
9057
9727
|
let credentials = opts.credentials;
|
|
9058
9728
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9108,14 +9778,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
9108
9778
|
appId: tenantId
|
|
9109
9779
|
})
|
|
9110
9780
|
});
|
|
9111
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
9781
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText5(created)}`);
|
|
9112
9782
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
9113
9783
|
method: "POST",
|
|
9114
9784
|
headers,
|
|
9115
9785
|
body: "{}"
|
|
9116
9786
|
});
|
|
9117
9787
|
}
|
|
9118
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
9788
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9119
9789
|
const body = await res.json();
|
|
9120
9790
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
9121
9791
|
return body.key;
|
|
@@ -9131,58 +9801,11 @@ async function issueO11yToken(opts) {
|
|
|
9131
9801
|
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
|
|
9132
9802
|
);
|
|
9133
9803
|
}
|
|
9134
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
9804
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9135
9805
|
const body = await res.json();
|
|
9136
9806
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
9137
9807
|
return body.token;
|
|
9138
9808
|
}
|
|
9139
|
-
async function safeText4(res) {
|
|
9140
|
-
try {
|
|
9141
|
-
return redactSecrets((await res.text()).slice(0, 500));
|
|
9142
|
-
} catch {
|
|
9143
|
-
return "";
|
|
9144
|
-
}
|
|
9145
|
-
}
|
|
9146
|
-
|
|
9147
|
-
// src/provision-helpers.ts
|
|
9148
|
-
var import_ai2 = require("@odla-ai/ai");
|
|
9149
|
-
var import_apps6 = require("@odla-ai/apps");
|
|
9150
|
-
function defaultSecretName(provider) {
|
|
9151
|
-
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
9152
|
-
return names[provider] ?? `${provider}_api_key`;
|
|
9153
|
-
}
|
|
9154
|
-
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
9155
|
-
const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
|
|
9156
|
-
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
9157
|
-
headers: { authorization: `Bearer ${token}` }
|
|
9158
|
-
});
|
|
9159
|
-
if (res.ok || res.status === 404) return;
|
|
9160
|
-
if (res.status === 403) {
|
|
9161
|
-
throw new Error(
|
|
9162
|
-
`${env}: you are not an owner of "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; ask an existing owner to run "odla-ai app owners add <your-email>", then re-run provision`
|
|
9163
|
-
);
|
|
9164
|
-
}
|
|
9165
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9166
|
-
}
|
|
9167
|
-
async function postJson3(doFetch, url, bearer, body) {
|
|
9168
|
-
const res = await doFetch(url, {
|
|
9169
|
-
method: "POST",
|
|
9170
|
-
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
9171
|
-
body: JSON.stringify(body)
|
|
9172
|
-
});
|
|
9173
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
9174
|
-
}
|
|
9175
|
-
function normalizeClerkConfig(value) {
|
|
9176
|
-
if (!value) return null;
|
|
9177
|
-
if (typeof value === "string") {
|
|
9178
|
-
const publishableKey2 = envValue(value);
|
|
9179
|
-
return publishableKey2 ? { publishableKey: publishableKey2 } : null;
|
|
9180
|
-
}
|
|
9181
|
-
if (typeof value !== "object") return null;
|
|
9182
|
-
const cfg = value;
|
|
9183
|
-
const publishableKey = envValue(cfg.publishableKey);
|
|
9184
|
-
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
9185
|
-
}
|
|
9186
9809
|
async function safeText5(res) {
|
|
9187
9810
|
try {
|
|
9188
9811
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
@@ -9261,7 +9884,7 @@ async function provision(options) {
|
|
|
9261
9884
|
}
|
|
9262
9885
|
const doFetch = options.fetch ?? fetch;
|
|
9263
9886
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9264
|
-
const apps = (0,
|
|
9887
|
+
const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9265
9888
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9266
9889
|
if (existing) {
|
|
9267
9890
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9272,7 +9895,7 @@ async function provision(options) {
|
|
|
9272
9895
|
for (const env of cfg.envs) {
|
|
9273
9896
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9274
9897
|
}
|
|
9275
|
-
const serviceOrder = (0,
|
|
9898
|
+
const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
|
|
9276
9899
|
for (const env of cfg.envs) {
|
|
9277
9900
|
for (const service of serviceOrder) {
|
|
9278
9901
|
if (service === "ai") {
|
|
@@ -9305,7 +9928,7 @@ async function provision(options) {
|
|
|
9305
9928
|
}
|
|
9306
9929
|
}
|
|
9307
9930
|
for (const env of cfg.envs) {
|
|
9308
|
-
const tenantId = (0,
|
|
9931
|
+
const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
|
|
9309
9932
|
credentials = await provisionEnvCredentials({
|
|
9310
9933
|
cfg,
|
|
9311
9934
|
env,
|
|
@@ -9338,11 +9961,11 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
9338
9961
|
}
|
|
9339
9962
|
}
|
|
9340
9963
|
if (schema && dbKey) {
|
|
9341
|
-
await
|
|
9964
|
+
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
9342
9965
|
out.log(`${env}: schema pushed`);
|
|
9343
9966
|
}
|
|
9344
9967
|
if (rules) {
|
|
9345
|
-
await
|
|
9968
|
+
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
|
|
9346
9969
|
out.log(`${env}: rules pushed (${Object.keys(rules).length} namespaces)`);
|
|
9347
9970
|
}
|
|
9348
9971
|
if (dbKey) {
|
|
@@ -9439,6 +10062,7 @@ var COMMAND_SURFACE = {
|
|
|
9439
10062
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
9440
10063
|
capabilities: {},
|
|
9441
10064
|
code: { connect: {} },
|
|
10065
|
+
config: { diff: {}, plan: {} },
|
|
9442
10066
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
9443
10067
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
9444
10068
|
discuss: {
|
|
@@ -9456,6 +10080,7 @@ var COMMAND_SURFACE = {
|
|
|
9456
10080
|
help: {},
|
|
9457
10081
|
init: {},
|
|
9458
10082
|
o11y: { status: {} },
|
|
10083
|
+
platform: { status: {} },
|
|
9459
10084
|
pm: {
|
|
9460
10085
|
...PM_ENTITIES,
|
|
9461
10086
|
handoff: {}
|
|
@@ -9539,7 +10164,7 @@ function recordInvocation(parsed) {
|
|
|
9539
10164
|
try {
|
|
9540
10165
|
const entry = {
|
|
9541
10166
|
path: invocationPath(parsed.positionals),
|
|
9542
|
-
options: Object.entries(parsed.options).map(([name,
|
|
10167
|
+
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
9543
10168
|
};
|
|
9544
10169
|
if (!entry.path.length) return;
|
|
9545
10170
|
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
@@ -9553,10 +10178,10 @@ var import_node_fs16 = require("fs");
|
|
|
9553
10178
|
|
|
9554
10179
|
// src/runbook-requires.ts
|
|
9555
10180
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
9556
|
-
function parseRequires(
|
|
9557
|
-
if (!
|
|
10181
|
+
function parseRequires(value2) {
|
|
10182
|
+
if (!value2) return [];
|
|
9558
10183
|
const out = [];
|
|
9559
|
-
for (const token of
|
|
10184
|
+
for (const token of value2.split(/[\s,]+/).filter(Boolean)) {
|
|
9560
10185
|
const match = SPEC.exec(token);
|
|
9561
10186
|
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
9562
10187
|
}
|
|
@@ -9722,7 +10347,7 @@ async function runbookRemove(ctx, slug) {
|
|
|
9722
10347
|
|
|
9723
10348
|
// src/runbook-import.ts
|
|
9724
10349
|
var import_node_fs17 = require("fs");
|
|
9725
|
-
var
|
|
10350
|
+
var import_node_path14 = require("path");
|
|
9726
10351
|
function parseRunbook(text, slug) {
|
|
9727
10352
|
let rest = text;
|
|
9728
10353
|
const meta = {};
|
|
@@ -9732,9 +10357,9 @@ function parseRunbook(text, slug) {
|
|
|
9732
10357
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
9733
10358
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
9734
10359
|
if (!pair) continue;
|
|
9735
|
-
const
|
|
9736
|
-
if (pair[1] === "summary") meta.summary =
|
|
9737
|
-
if (pair[1] === "tags") meta.tags =
|
|
10360
|
+
const value2 = pair[2].trim().replace(/^["']|["']$/g, "");
|
|
10361
|
+
if (pair[1] === "summary") meta.summary = value2;
|
|
10362
|
+
if (pair[1] === "tags") meta.tags = value2.replace(/^\[|\]$/g, "").trim();
|
|
9738
10363
|
}
|
|
9739
10364
|
}
|
|
9740
10365
|
const lines = rest.split("\n");
|
|
@@ -9751,8 +10376,8 @@ function readRunbookDir(dir) {
|
|
|
9751
10376
|
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9752
10377
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9753
10378
|
return files.map((file) => {
|
|
9754
|
-
const slug = (0,
|
|
9755
|
-
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0,
|
|
10379
|
+
const slug = (0, import_node_path14.basename)(file, ".md");
|
|
10380
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
|
|
9756
10381
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9757
10382
|
});
|
|
9758
10383
|
}
|
|
@@ -9825,7 +10450,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
9825
10450
|
// src/runbook-impact.ts
|
|
9826
10451
|
var import_node_child_process7 = require("child_process");
|
|
9827
10452
|
var import_node_fs18 = require("fs");
|
|
9828
|
-
var
|
|
10453
|
+
var import_node_path15 = require("path");
|
|
9829
10454
|
|
|
9830
10455
|
// src/runbook-impact-scan.ts
|
|
9831
10456
|
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$]*)/;
|
|
@@ -9851,7 +10476,7 @@ var NOISE = /* @__PURE__ */ new Set([
|
|
|
9851
10476
|
"and",
|
|
9852
10477
|
"for"
|
|
9853
10478
|
]);
|
|
9854
|
-
var words = (
|
|
10479
|
+
var words = (value2) => value2.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
|
|
9855
10480
|
function namedExports(clause) {
|
|
9856
10481
|
return clause.split(",").map((part) => part.trim().split(/\s+as\s+/)[0]?.trim() ?? "").filter((name) => /^[A-Za-z_$][\w$]*$/.test(name) && name !== "type");
|
|
9857
10482
|
}
|
|
@@ -9994,7 +10619,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9994
10619
|
}
|
|
9995
10620
|
function manifestLabeller(root) {
|
|
9996
10621
|
return (workspace) => {
|
|
9997
|
-
const manifest = (0,
|
|
10622
|
+
const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
|
|
9998
10623
|
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
9999
10624
|
try {
|
|
10000
10625
|
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
@@ -10064,7 +10689,7 @@ function report3(ctx, impacts) {
|
|
|
10064
10689
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10065
10690
|
const cwd = deps.cwd ?? process.cwd();
|
|
10066
10691
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10067
|
-
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0,
|
|
10692
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
|
|
10068
10693
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10069
10694
|
if (!surfaces.length) {
|
|
10070
10695
|
return ctx.out.log(
|
|
@@ -10199,13 +10824,13 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10199
10824
|
var import_node_child_process8 = require("child_process");
|
|
10200
10825
|
var import_node_fs19 = require("fs");
|
|
10201
10826
|
var import_node_os5 = require("os");
|
|
10202
|
-
var
|
|
10827
|
+
var import_node_path16 = require("path");
|
|
10203
10828
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
10204
10829
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10205
10830
|
function resolveEditor(env = import_node_process12.default.env) {
|
|
10206
10831
|
for (const name of EDITOR_ENV) {
|
|
10207
|
-
const
|
|
10208
|
-
if (
|
|
10832
|
+
const value2 = env[name];
|
|
10833
|
+
if (value2 && value2.trim()) return value2.trim();
|
|
10209
10834
|
}
|
|
10210
10835
|
return null;
|
|
10211
10836
|
}
|
|
@@ -10225,8 +10850,8 @@ function editText(initial, slug, deps = {}) {
|
|
|
10225
10850
|
);
|
|
10226
10851
|
if (!interactive())
|
|
10227
10852
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10228
|
-
const dir = (0, import_node_fs19.mkdtempSync)((0,
|
|
10229
|
-
const file = (0,
|
|
10853
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
10854
|
+
const file = (0, import_node_path16.join)(dir, `${slug}.md`);
|
|
10230
10855
|
try {
|
|
10231
10856
|
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
10232
10857
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
@@ -10334,12 +10959,12 @@ var ALLOWED2 = [
|
|
|
10334
10959
|
"platform",
|
|
10335
10960
|
"context"
|
|
10336
10961
|
];
|
|
10337
|
-
function requireSlug(slug,
|
|
10338
|
-
if (!slug) throw new Error(`"runbook ${
|
|
10962
|
+
function requireSlug(slug, action2) {
|
|
10963
|
+
if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
|
|
10339
10964
|
return slug;
|
|
10340
10965
|
}
|
|
10341
10966
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
10342
|
-
async function buildContext3(parsed, deps,
|
|
10967
|
+
async function buildContext3(parsed, deps, action2) {
|
|
10343
10968
|
const appIdOption = stringOpt(parsed.options.app);
|
|
10344
10969
|
const context = await resolveOperatorContext(parsed, {
|
|
10345
10970
|
allowMissingConfig: true
|
|
@@ -10349,7 +10974,7 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10349
10974
|
const out = deps.stdout ?? console;
|
|
10350
10975
|
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
10351
10976
|
const dryRun = parsed.options["dry-run"] === true;
|
|
10352
|
-
if (
|
|
10977
|
+
if (action2 === "import" && dryRun) {
|
|
10353
10978
|
return {
|
|
10354
10979
|
platformUrl: cfg.platformUrl,
|
|
10355
10980
|
token: "",
|
|
@@ -10359,12 +10984,12 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10359
10984
|
appId
|
|
10360
10985
|
};
|
|
10361
10986
|
}
|
|
10362
|
-
const needsCapability = WRITES.has(
|
|
10987
|
+
const needsCapability = WRITES.has(action2) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
|
|
10363
10988
|
const token = needsCapability ? await getScopedPlatformToken({
|
|
10364
10989
|
platform: cfg.platformUrl,
|
|
10365
10990
|
scope: "platform:runbook:write",
|
|
10366
10991
|
email: stringOpt(parsed.options.email),
|
|
10367
|
-
label: `odla CLI (runbook ${
|
|
10992
|
+
label: `odla CLI (runbook ${action2})`,
|
|
10368
10993
|
fetch: doFetch,
|
|
10369
10994
|
stdout: out,
|
|
10370
10995
|
openApprovalUrl: deps.openUrl,
|
|
@@ -10399,11 +11024,11 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10399
11024
|
};
|
|
10400
11025
|
}
|
|
10401
11026
|
async function runbookCommand(parsed, deps = {}) {
|
|
10402
|
-
const
|
|
10403
|
-
assertArgs(parsed, ALLOWED2,
|
|
10404
|
-
const ctx = await buildContext3(parsed, deps,
|
|
11027
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
11028
|
+
assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
|
|
11029
|
+
const ctx = await buildContext3(parsed, deps, action2);
|
|
10405
11030
|
const slug = parsed.positionals[2];
|
|
10406
|
-
switch (
|
|
11031
|
+
switch (action2) {
|
|
10407
11032
|
case "list":
|
|
10408
11033
|
return runbookList(ctx, parsed.options.all === true, stringOpt(parsed.options.q));
|
|
10409
11034
|
case "ask": {
|
|
@@ -10470,9 +11095,9 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
10470
11095
|
});
|
|
10471
11096
|
}
|
|
10472
11097
|
case "visibility": {
|
|
10473
|
-
const
|
|
10474
|
-
if (!
|
|
10475
|
-
return runbookVisibility(ctx, requireSlug(slug, "visibility"),
|
|
11098
|
+
const value2 = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
|
|
11099
|
+
if (!value2) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
|
|
11100
|
+
return runbookVisibility(ctx, requireSlug(slug, "visibility"), value2);
|
|
10476
11101
|
}
|
|
10477
11102
|
case "publish":
|
|
10478
11103
|
return runbookStatus(ctx, requireSlug(slug, "publish"), "published");
|
|
@@ -10488,7 +11113,7 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
10488
11113
|
case "rm":
|
|
10489
11114
|
return runbookRemove(ctx, requireSlug(slug, "rm"));
|
|
10490
11115
|
default:
|
|
10491
|
-
throw new Error(`unknown runbook action "${
|
|
11116
|
+
throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
|
|
10492
11117
|
}
|
|
10493
11118
|
}
|
|
10494
11119
|
|
|
@@ -10528,13 +11153,13 @@ async function interactiveConfirmation(message2, dependencies) {
|
|
|
10528
11153
|
}
|
|
10529
11154
|
}
|
|
10530
11155
|
function requiredSecurityPositional(parsed, index, label) {
|
|
10531
|
-
const
|
|
10532
|
-
if (!
|
|
10533
|
-
return
|
|
11156
|
+
const value2 = parsed.positionals[index];
|
|
11157
|
+
if (!value2) throw new Error(`${label} is required`);
|
|
11158
|
+
return value2;
|
|
10534
11159
|
}
|
|
10535
|
-
function securityProfile(
|
|
10536
|
-
if (
|
|
10537
|
-
if (
|
|
11160
|
+
function securityProfile(value2) {
|
|
11161
|
+
if (value2 === void 0) return void 0;
|
|
11162
|
+
if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
|
|
10538
11163
|
throw new Error("--profile must be odla, cloudflare-app, or generic");
|
|
10539
11164
|
}
|
|
10540
11165
|
|
|
@@ -10635,9 +11260,9 @@ function routeLabel(route2) {
|
|
|
10635
11260
|
return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
|
|
10636
11261
|
}
|
|
10637
11262
|
var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
|
|
10638
|
-
function hostedSeverity(
|
|
10639
|
-
if (HOSTED_SEVERITIES.includes(
|
|
10640
|
-
return
|
|
11263
|
+
function hostedSeverity(value2, flag) {
|
|
11264
|
+
if (HOSTED_SEVERITIES.includes(value2)) {
|
|
11265
|
+
return value2;
|
|
10641
11266
|
}
|
|
10642
11267
|
throw new Error(`${flag} must be informational, low, medium, high, or critical`);
|
|
10643
11268
|
}
|
|
@@ -10646,7 +11271,7 @@ function hostedSeverity(value, flag) {
|
|
|
10646
11271
|
var import_security2 = require("@odla-ai/security");
|
|
10647
11272
|
|
|
10648
11273
|
// src/security.ts
|
|
10649
|
-
var
|
|
11274
|
+
var import_node_path17 = require("path");
|
|
10650
11275
|
var import_security = require("@odla-ai/security");
|
|
10651
11276
|
var import_node3 = require("@odla-ai/security/node");
|
|
10652
11277
|
async function runHostedSecurity(options) {
|
|
@@ -10658,9 +11283,9 @@ async function runHostedSecurity(options) {
|
|
|
10658
11283
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
10659
11284
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
10660
11285
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
10661
|
-
const target = (0,
|
|
10662
|
-
const output = (0,
|
|
10663
|
-
const outputRelative = (0,
|
|
11286
|
+
const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
11287
|
+
const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
|
|
11288
|
+
const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
|
|
10664
11289
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
10665
11290
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
10666
11291
|
const tokenRequest = {
|
|
@@ -10672,7 +11297,7 @@ async function runHostedSecurity(options) {
|
|
|
10672
11297
|
};
|
|
10673
11298
|
const token = await injectedToken(options, tokenRequest);
|
|
10674
11299
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
10675
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
11300
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
10676
11301
|
});
|
|
10677
11302
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
10678
11303
|
platform,
|
|
@@ -10690,7 +11315,7 @@ async function runHostedSecurity(options) {
|
|
|
10690
11315
|
});
|
|
10691
11316
|
const harness = (0, import_security.createSecurityHarness)({
|
|
10692
11317
|
profile,
|
|
10693
|
-
store: new import_node3.FileRunStore((0,
|
|
11318
|
+
store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
|
|
10694
11319
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
10695
11320
|
validationReasoner: hosted.validationReasoner,
|
|
10696
11321
|
policy: {
|
|
@@ -10714,19 +11339,19 @@ async function runHostedSecurity(options) {
|
|
|
10714
11339
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
10715
11340
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
10716
11341
|
if (!env || !declared.includes(env)) {
|
|
10717
|
-
const shown = (0,
|
|
11342
|
+
const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
|
|
10718
11343
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
10719
11344
|
}
|
|
10720
11345
|
return env;
|
|
10721
11346
|
}
|
|
10722
11347
|
async function injectedToken(options, request2) {
|
|
10723
|
-
const
|
|
10724
|
-
if (typeof
|
|
11348
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
|
|
11349
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
10725
11350
|
throw new Error(
|
|
10726
11351
|
request2.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
10727
11352
|
);
|
|
10728
11353
|
}
|
|
10729
|
-
return
|
|
11354
|
+
return value2;
|
|
10730
11355
|
}
|
|
10731
11356
|
function profileFor(name, maxHuntTasks) {
|
|
10732
11357
|
const profile = name === "odla" ? (0, import_security.odlaProfile)() : name === "cloudflare-app" ? (0, import_security.cloudflareAppProfile)() : name === "generic" ? (0, import_security.genericProfile)() : void 0;
|
|
@@ -10743,7 +11368,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
10743
11368
|
out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
10744
11369
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
10745
11370
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
10746
|
-
out.log(` report: ${(0,
|
|
11371
|
+
out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
|
|
10747
11372
|
}
|
|
10748
11373
|
function formatBudget(usage) {
|
|
10749
11374
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -10772,8 +11397,8 @@ async function getHostedSecurityIntent(options) {
|
|
|
10772
11397
|
}
|
|
10773
11398
|
return response2;
|
|
10774
11399
|
}
|
|
10775
|
-
function isValidIntent(
|
|
10776
|
-
return !!
|
|
11400
|
+
function isValidIntent(value2, expected) {
|
|
11401
|
+
return !!value2 && value2.executionContract === "odla.hosted-security-execution-consent.v1" && /^sha256:[a-f0-9]{64}$/.test(value2.executionDigest) && /^sha256:[a-f0-9]{64}$/.test(value2.planDigest) && value2.appId === expected.appId && value2.env === expected.env && value2.sourceId === expected.sourceId && typeof value2.repository === "string" && value2.repository.length > 2 && value2.repository.length <= 201 && typeof value2.defaultBranch === "string" && value2.defaultBranch.length > 0 && value2.defaultBranch.length <= 255 && typeof value2.requestedRef === "string" && value2.requestedRef.length > 0 && value2.requestedRef.length <= 255 && !/[\u0000-\u001f\u007f]/.test(value2.requestedRef) && ["odla", "cloudflare-app", "generic"].includes(value2.profile) && value2.sourceDisclosure === "redacted";
|
|
10777
11402
|
}
|
|
10778
11403
|
|
|
10779
11404
|
// src/security-hosted-jobs.ts
|
|
@@ -10888,17 +11513,17 @@ function hostedJobPath(appIdInput, jobIdInput) {
|
|
|
10888
11513
|
const jobId = hostedIdentifier(jobIdInput, "jobId");
|
|
10889
11514
|
return `/registry/apps/${encodeURIComponent(appId)}/security/jobs/${encodeURIComponent(jobId)}`;
|
|
10890
11515
|
}
|
|
10891
|
-
function securityPlanDigest(
|
|
10892
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(
|
|
11516
|
+
function securityPlanDigest(value2) {
|
|
11517
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
|
|
10893
11518
|
throw new Error("expected plan digest must be a sha256 digest from security plan");
|
|
10894
11519
|
}
|
|
10895
|
-
return
|
|
11520
|
+
return value2;
|
|
10896
11521
|
}
|
|
10897
|
-
function securityExecutionDigest(
|
|
10898
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(
|
|
11522
|
+
function securityExecutionDigest(value2) {
|
|
11523
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
|
|
10899
11524
|
throw new Error("expected execution digest must be a sha256 digest from security intent");
|
|
10900
11525
|
}
|
|
10901
|
-
return
|
|
11526
|
+
return value2;
|
|
10902
11527
|
}
|
|
10903
11528
|
|
|
10904
11529
|
// src/security-run-command.ts
|
|
@@ -10962,7 +11587,7 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
|
|
|
10962
11587
|
...context,
|
|
10963
11588
|
jobId: job.jobId,
|
|
10964
11589
|
wait: dependencies.pollWait,
|
|
10965
|
-
onUpdate: parsed.options.json === true ? void 0 : (
|
|
11590
|
+
onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
|
|
10966
11591
|
}) : job;
|
|
10967
11592
|
if (!follow) {
|
|
10968
11593
|
if (parsed.options.json === true) {
|
|
@@ -11062,9 +11687,9 @@ function enforceLocalGate(report4, parsed) {
|
|
|
11062
11687
|
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
|
|
11063
11688
|
}
|
|
11064
11689
|
}
|
|
11065
|
-
function severityOpt(
|
|
11066
|
-
if (["informational", "low", "medium", "high", "critical"].includes(
|
|
11067
|
-
return
|
|
11690
|
+
function severityOpt(value2, flag) {
|
|
11691
|
+
if (["informational", "low", "medium", "high", "critical"].includes(value2)) {
|
|
11692
|
+
return value2;
|
|
11068
11693
|
}
|
|
11069
11694
|
throw new Error(`${flag} must be informational, low, medium, high, or critical`);
|
|
11070
11695
|
}
|
|
@@ -11109,8 +11734,8 @@ async function securityCommand(parsed, dependencies) {
|
|
|
11109
11734
|
else await runLocalSecurityCommand(parsed, dependencies);
|
|
11110
11735
|
}
|
|
11111
11736
|
async function githubSecurityCommand(parsed, dependencies) {
|
|
11112
|
-
const
|
|
11113
|
-
if (
|
|
11737
|
+
const action2 = parsed.positionals[2];
|
|
11738
|
+
if (action2 === "disconnect") {
|
|
11114
11739
|
assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
|
|
11115
11740
|
const context2 = await hostedSecurityContext(parsed, dependencies);
|
|
11116
11741
|
const sourceId = requiredString(parsed.options.source, "--source");
|
|
@@ -11125,7 +11750,7 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
11125
11750
|
context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
|
|
11126
11751
|
return;
|
|
11127
11752
|
}
|
|
11128
|
-
if (
|
|
11753
|
+
if (action2 !== "connect") {
|
|
11129
11754
|
throw new Error('unknown security github command. Try "odla-ai security github connect".');
|
|
11130
11755
|
}
|
|
11131
11756
|
assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
|
|
@@ -11172,7 +11797,7 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11172
11797
|
...context,
|
|
11173
11798
|
jobId,
|
|
11174
11799
|
wait: dependencies.pollWait,
|
|
11175
|
-
onUpdate: parsed.options.json === true ? void 0 : (
|
|
11800
|
+
onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
|
|
11176
11801
|
}) : await getHostedSecurityJob({ ...context, jobId });
|
|
11177
11802
|
if (parsed.options.json === true) context.stdout.log(JSON.stringify(job, null, 2));
|
|
11178
11803
|
else if (parsed.options.follow !== true) {
|
|
@@ -11256,6 +11881,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
11256
11881
|
await o11yCommand(parsed, runtime);
|
|
11257
11882
|
return;
|
|
11258
11883
|
}
|
|
11884
|
+
if (command === "platform") {
|
|
11885
|
+
await platformCommand(parsed, runtime);
|
|
11886
|
+
return;
|
|
11887
|
+
}
|
|
11259
11888
|
if (command === "provision") {
|
|
11260
11889
|
await provisionCommand(parsed, runtime);
|
|
11261
11890
|
return;
|