@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/index.cjs
CHANGED
|
@@ -47,8 +47,11 @@ __export(index_exports, {
|
|
|
47
47
|
calendarServiceConfig: () => calendarServiceConfig,
|
|
48
48
|
calendarStatus: () => calendarStatus,
|
|
49
49
|
codeConnect: () => codeConnect,
|
|
50
|
+
configDiff: () => configDiff,
|
|
51
|
+
configPlan: () => configPlan,
|
|
50
52
|
connectGitHubSecuritySource: () => connectGitHubSecuritySource,
|
|
51
53
|
describeProblem: () => describeProblem,
|
|
54
|
+
desiredRegistryState: () => desiredRegistryState,
|
|
52
55
|
disconnectGitHubSecuritySource: () => disconnectGitHubSecuritySource,
|
|
53
56
|
doctor: () => doctor,
|
|
54
57
|
exitCodeFor: () => exitCodeFor,
|
|
@@ -68,6 +71,7 @@ __export(index_exports, {
|
|
|
68
71
|
prepareCodeImages: () => prepareCodeImages,
|
|
69
72
|
printCapabilities: () => printCapabilities,
|
|
70
73
|
provision: () => provision,
|
|
74
|
+
reconcileConfig: () => reconcileConfig,
|
|
71
75
|
redactSecrets: () => redactSecrets,
|
|
72
76
|
repositoryFromGitRemote: () => repositoryFromGitRemote,
|
|
73
77
|
runCli: () => runCli,
|
|
@@ -103,7 +107,9 @@ function approvalLines(prompt) {
|
|
|
103
107
|
lines.push("");
|
|
104
108
|
lines.push(` ${prompt.approvalUrl}`);
|
|
105
109
|
lines.push("");
|
|
106
|
-
lines.push(
|
|
110
|
+
lines.push(
|
|
111
|
+
` ${prompt.approver ?? "The matching signed-in odla account"} opens that URL, checks the code matches, and approves.`
|
|
112
|
+
);
|
|
107
113
|
lines.push(" Opening the link without approving does nothing; this command waits until they do.");
|
|
108
114
|
if (prompt.browserAttempted) {
|
|
109
115
|
lines.push(" A browser launch was attempted, but it is best-effort and may have shown no tab.");
|
|
@@ -142,22 +148,22 @@ function readJsonFile(path) {
|
|
|
142
148
|
return null;
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
|
-
function writePrivateJson(path,
|
|
146
|
-
writePrivateText(path, `${JSON.stringify(
|
|
151
|
+
function writePrivateJson(path, value2) {
|
|
152
|
+
writePrivateText(path, `${JSON.stringify(value2, null, 2)}
|
|
147
153
|
`);
|
|
148
154
|
}
|
|
149
155
|
function readCredentials(path) {
|
|
150
156
|
if (!(0, import_node_fs.existsSync)(path)) return null;
|
|
151
|
-
let
|
|
157
|
+
let value2;
|
|
152
158
|
try {
|
|
153
|
-
|
|
159
|
+
value2 = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
154
160
|
} catch {
|
|
155
161
|
throw new Error(`credentials file ${path} is not valid JSON; fix or remove it before provisioning`);
|
|
156
162
|
}
|
|
157
|
-
if (!
|
|
163
|
+
if (!value2 || typeof value2 !== "object" || typeof value2.appId !== "string" || !value2.envs || typeof value2.envs !== "object") {
|
|
158
164
|
throw new Error(`credentials file ${path} has an invalid shape; fix or remove it before provisioning`);
|
|
159
165
|
}
|
|
160
|
-
return
|
|
166
|
+
return value2;
|
|
161
167
|
}
|
|
162
168
|
function mergeCredential(current, update) {
|
|
163
169
|
const next = current ?? {
|
|
@@ -452,8 +458,8 @@ function stillPending(pending, email) {
|
|
|
452
458
|
{ retryable: true }
|
|
453
459
|
);
|
|
454
460
|
}
|
|
455
|
-
function handshakeEmail(
|
|
456
|
-
const email = (
|
|
461
|
+
function handshakeEmail(value2, cached) {
|
|
462
|
+
const email = (value2 ?? import_node_process3.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
|
|
457
463
|
if (email.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
458
464
|
throw new Error("a fresh odla handshake requires --email <account> or ODLA_USER_EMAIL");
|
|
459
465
|
}
|
|
@@ -477,10 +483,10 @@ function handshakeUrl(platformUrl, userCode) {
|
|
|
477
483
|
url.searchParams.set("code", userCode);
|
|
478
484
|
return url.toString();
|
|
479
485
|
}
|
|
480
|
-
function platformAudience(
|
|
486
|
+
function platformAudience(value2) {
|
|
481
487
|
let url;
|
|
482
488
|
try {
|
|
483
|
-
url = new URL(
|
|
489
|
+
url = new URL(value2);
|
|
484
490
|
} catch {
|
|
485
491
|
throw new Error("platform must be an absolute URL");
|
|
486
492
|
}
|
|
@@ -499,22 +505,22 @@ var import_node_process4 = __toESM(require("process"), 1);
|
|
|
499
505
|
var MAX_BYTES = 64 * 1024;
|
|
500
506
|
async function secretInputValue(options, kind = "credential") {
|
|
501
507
|
if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
|
|
502
|
-
let
|
|
503
|
-
if (options.fromEnv)
|
|
504
|
-
else if (options.stdin)
|
|
508
|
+
let value2;
|
|
509
|
+
if (options.fromEnv) value2 = import_node_process4.default.env[options.fromEnv];
|
|
510
|
+
else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
|
|
505
511
|
else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
|
|
506
|
-
|
|
507
|
-
if (!
|
|
508
|
-
if (new TextEncoder().encode(
|
|
509
|
-
return
|
|
512
|
+
value2 = value2?.replace(/[\r\n]+$/, "");
|
|
513
|
+
if (!value2) throw new Error(options.fromEnv ? `environment variable ${options.fromEnv} is empty or unset` : `stdin ${kind} is empty`);
|
|
514
|
+
if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
515
|
+
return value2;
|
|
510
516
|
}
|
|
511
517
|
async function readSecretStream(kind, stream = import_node_process4.default.stdin) {
|
|
512
|
-
let
|
|
518
|
+
let value2 = "";
|
|
513
519
|
for await (const chunk of stream) {
|
|
514
|
-
|
|
515
|
-
if (
|
|
520
|
+
value2 += String(chunk);
|
|
521
|
+
if (value2.length > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
|
|
516
522
|
}
|
|
517
|
-
return
|
|
523
|
+
return value2;
|
|
518
524
|
}
|
|
519
525
|
|
|
520
526
|
// src/admin-ai-auth.ts
|
|
@@ -549,6 +555,8 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
549
555
|
return token;
|
|
550
556
|
}
|
|
551
557
|
var SCOPE_PURPOSE = {
|
|
558
|
+
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
559
|
+
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
552
560
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
553
561
|
"platform:ai:policy:write": "change System AI model routing",
|
|
554
562
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -585,6 +593,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
585
593
|
approvalUrl,
|
|
586
594
|
minutesLeft: Math.floor(expiresIn / 60),
|
|
587
595
|
purpose: SCOPE_PURPOSE[scope] ?? `use ${scope}`,
|
|
596
|
+
approver: scope.startsWith("app:") ? "A signed-in app owner" : "A signed-in odla platform admin",
|
|
588
597
|
browserAttempted: browser.open,
|
|
589
598
|
browserSkipped: browser.reason
|
|
590
599
|
});
|
|
@@ -652,13 +661,13 @@ function apiError(status, body) {
|
|
|
652
661
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
653
662
|
return `read System AI admin changes failed (${status}): ${message2}`;
|
|
654
663
|
}
|
|
655
|
-
function timestamp(
|
|
656
|
-
if (typeof
|
|
657
|
-
const date = new Date(
|
|
664
|
+
function timestamp(value2) {
|
|
665
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
|
|
666
|
+
const date = new Date(value2);
|
|
658
667
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
659
668
|
}
|
|
660
|
-
function isRecord(
|
|
661
|
-
return Boolean(
|
|
669
|
+
function isRecord(value2) {
|
|
670
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
662
671
|
}
|
|
663
672
|
|
|
664
673
|
// src/admin-ai-policy.ts
|
|
@@ -668,11 +677,11 @@ var SYSTEM_AI_PURPOSES = [
|
|
|
668
677
|
"security.validation",
|
|
669
678
|
"runbook.answer"
|
|
670
679
|
];
|
|
671
|
-
function requireSystemAiPurpose(
|
|
672
|
-
if (!SYSTEM_AI_PURPOSES.includes(
|
|
680
|
+
function requireSystemAiPurpose(value2) {
|
|
681
|
+
if (!SYSTEM_AI_PURPOSES.includes(value2)) {
|
|
673
682
|
throw new Error(`purpose must be one of: ${SYSTEM_AI_PURPOSES.join(", ")}`);
|
|
674
683
|
}
|
|
675
|
-
return
|
|
684
|
+
return value2;
|
|
676
685
|
}
|
|
677
686
|
|
|
678
687
|
// src/admin-ai-usage.ts
|
|
@@ -694,11 +703,11 @@ async function readAdminAiUsage(request2) {
|
|
|
694
703
|
if (request2.json) request2.stdout.log(JSON.stringify(body, null, 2));
|
|
695
704
|
else printUsage(body, request2.stdout);
|
|
696
705
|
}
|
|
697
|
-
function usageLimit(
|
|
698
|
-
if (!Number.isSafeInteger(
|
|
706
|
+
function usageLimit(value2) {
|
|
707
|
+
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
699
708
|
throw new Error("usage limit must be an integer from 1 to 500");
|
|
700
709
|
}
|
|
701
|
-
return
|
|
710
|
+
return value2;
|
|
702
711
|
}
|
|
703
712
|
function printUsage(body, out) {
|
|
704
713
|
const aggregates = isRecord2(body) && isRecord2(body.aggregates) && Array.isArray(body.aggregates.byStatus) ? body.aggregates.byStatus.filter(isRecord2) : [];
|
|
@@ -734,15 +743,15 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
734
743
|
].join(" "));
|
|
735
744
|
}
|
|
736
745
|
}
|
|
737
|
-
function numeric(
|
|
738
|
-
return typeof
|
|
746
|
+
function numeric(value2) {
|
|
747
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
739
748
|
}
|
|
740
|
-
function formatMicrousd(
|
|
741
|
-
return typeof
|
|
749
|
+
function formatMicrousd(value2) {
|
|
750
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `$${(value2 / 1e6).toFixed(6)}` : "unpriced";
|
|
742
751
|
}
|
|
743
|
-
function timestamp2(
|
|
744
|
-
if (typeof
|
|
745
|
-
const date = new Date(
|
|
752
|
+
function timestamp2(value2) {
|
|
753
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return String(value2 ?? "");
|
|
754
|
+
const date = new Date(value2);
|
|
746
755
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
747
756
|
}
|
|
748
757
|
async function responseBody2(res) {
|
|
@@ -754,13 +763,13 @@ async function responseBody2(res) {
|
|
|
754
763
|
return { message: text.slice(0, 300) };
|
|
755
764
|
}
|
|
756
765
|
}
|
|
757
|
-
function apiError2(
|
|
766
|
+
function apiError2(action2, status, body) {
|
|
758
767
|
const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
|
|
759
768
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
760
|
-
return `${
|
|
769
|
+
return `${action2} failed (${status}): ${message2}`;
|
|
761
770
|
}
|
|
762
|
-
function isRecord2(
|
|
763
|
-
return Boolean(
|
|
771
|
+
function isRecord2(value2) {
|
|
772
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
764
773
|
}
|
|
765
774
|
|
|
766
775
|
// src/admin-ai.ts
|
|
@@ -805,11 +814,11 @@ async function adminAi(options) {
|
|
|
805
814
|
}
|
|
806
815
|
if (options.action === "credential-set") {
|
|
807
816
|
const provider = requireProvider(options.credentialProvider);
|
|
808
|
-
const
|
|
817
|
+
const value2 = await secretInputValue(options);
|
|
809
818
|
const res2 = await doFetch(`${platform}/registry/platform/ai-credentials/${encodeURIComponent(provider)}`, {
|
|
810
819
|
method: "PUT",
|
|
811
820
|
headers,
|
|
812
|
-
body: JSON.stringify({ value })
|
|
821
|
+
body: JSON.stringify({ value: value2 })
|
|
813
822
|
});
|
|
814
823
|
const body2 = await responseBody3(res2);
|
|
815
824
|
if (!res2.ok) throw new Error(apiError3(`store ${provider} platform credential`, res2.status, body2));
|
|
@@ -909,11 +918,11 @@ async function adminAi(options) {
|
|
|
909
918
|
const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
|
|
910
919
|
out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
|
|
911
920
|
}
|
|
912
|
-
function requireProvider(
|
|
913
|
-
if (
|
|
921
|
+
function requireProvider(value2) {
|
|
922
|
+
if (value2 !== "anthropic" && value2 !== "openai" && value2 !== "google") {
|
|
914
923
|
throw new Error("provider must be one of: anthropic, openai, google");
|
|
915
924
|
}
|
|
916
|
-
return
|
|
925
|
+
return value2;
|
|
917
926
|
}
|
|
918
927
|
function policyArray(body) {
|
|
919
928
|
if (isRecord3(body) && Array.isArray(body.policies)) return body.policies.filter(isRecord3);
|
|
@@ -924,7 +933,7 @@ function catalogModels(body) {
|
|
|
924
933
|
if (!isRecord3(body) || !isRecord3(body.catalog) || !Array.isArray(body.catalog.models)) {
|
|
925
934
|
throw new Error("platform returned an invalid System AI model catalog");
|
|
926
935
|
}
|
|
927
|
-
return body.catalog.models.filter((
|
|
936
|
+
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
928
937
|
}
|
|
929
938
|
async function responseBody3(res) {
|
|
930
939
|
const text = await res.text();
|
|
@@ -935,13 +944,13 @@ async function responseBody3(res) {
|
|
|
935
944
|
return { message: text.slice(0, 300) };
|
|
936
945
|
}
|
|
937
946
|
}
|
|
938
|
-
function apiError3(
|
|
947
|
+
function apiError3(action2, status, body) {
|
|
939
948
|
const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
|
|
940
949
|
const message2 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
|
|
941
|
-
return `${
|
|
950
|
+
return `${action2} failed (${status}): ${message2}`;
|
|
942
951
|
}
|
|
943
|
-
function isRecord3(
|
|
944
|
-
return Boolean(
|
|
952
|
+
function isRecord3(value2) {
|
|
953
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
945
954
|
}
|
|
946
955
|
|
|
947
956
|
// src/argv.ts
|
|
@@ -965,10 +974,10 @@ function parseArgv(argv) {
|
|
|
965
974
|
addOption(options, rawName, arg.slice(eq + 1));
|
|
966
975
|
continue;
|
|
967
976
|
}
|
|
968
|
-
const
|
|
969
|
-
if (
|
|
977
|
+
const value2 = argv[i + 1];
|
|
978
|
+
if (value2 !== void 0 && !value2.startsWith("--")) {
|
|
970
979
|
i++;
|
|
971
|
-
addOption(options, rawName,
|
|
980
|
+
addOption(options, rawName, value2);
|
|
972
981
|
} else {
|
|
973
982
|
addOption(options, rawName, true);
|
|
974
983
|
}
|
|
@@ -984,39 +993,39 @@ function assertArgs(parsed, allowedOptions2, maxPositionals) {
|
|
|
984
993
|
throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
|
|
985
994
|
}
|
|
986
995
|
}
|
|
987
|
-
function requiredString(
|
|
988
|
-
const result = stringOpt(
|
|
996
|
+
function requiredString(value2, name) {
|
|
997
|
+
const result = stringOpt(value2);
|
|
989
998
|
if (!result) throw new Error(`${name} is required`);
|
|
990
999
|
return result;
|
|
991
1000
|
}
|
|
992
|
-
function stringOpt(
|
|
993
|
-
if (typeof
|
|
994
|
-
if (Array.isArray(
|
|
1001
|
+
function stringOpt(value2) {
|
|
1002
|
+
if (typeof value2 === "string") return value2;
|
|
1003
|
+
if (Array.isArray(value2)) return value2[value2.length - 1];
|
|
995
1004
|
return void 0;
|
|
996
1005
|
}
|
|
997
|
-
function listOpt(
|
|
998
|
-
if (
|
|
999
|
-
const values = Array.isArray(
|
|
1006
|
+
function listOpt(value2) {
|
|
1007
|
+
if (value2 === void 0 || typeof value2 === "boolean") return void 0;
|
|
1008
|
+
const values = Array.isArray(value2) ? value2 : [value2];
|
|
1000
1009
|
return values.flatMap((item) => item.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
1001
1010
|
}
|
|
1002
|
-
function boolOpt(
|
|
1003
|
-
if (typeof
|
|
1004
|
-
if (typeof
|
|
1005
|
-
if (
|
|
1011
|
+
function boolOpt(value2) {
|
|
1012
|
+
if (typeof value2 === "boolean") return value2;
|
|
1013
|
+
if (typeof value2 === "string" && (value2 === "true" || value2 === "false")) return value2 === "true";
|
|
1014
|
+
if (value2 === void 0) return void 0;
|
|
1006
1015
|
throw new Error("boolean option must be true or false");
|
|
1007
1016
|
}
|
|
1008
|
-
function numberOpt(
|
|
1009
|
-
if (
|
|
1010
|
-
const raw = stringOpt(
|
|
1017
|
+
function numberOpt(value2, flag) {
|
|
1018
|
+
if (value2 === void 0) return void 0;
|
|
1019
|
+
const raw = stringOpt(value2);
|
|
1011
1020
|
const parsed = raw === void 0 ? Number.NaN : Number(raw);
|
|
1012
1021
|
if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${flag} requires a positive integer`);
|
|
1013
1022
|
return parsed;
|
|
1014
1023
|
}
|
|
1015
|
-
function addOption(options, name,
|
|
1024
|
+
function addOption(options, name, value2) {
|
|
1016
1025
|
const current = options[name];
|
|
1017
|
-
if (current === void 0) options[name] =
|
|
1018
|
-
else if (Array.isArray(current)) current.push(String(
|
|
1019
|
-
else options[name] = [String(current), String(
|
|
1026
|
+
if (current === void 0) options[name] = value2;
|
|
1027
|
+
else if (Array.isArray(current)) current.push(String(value2));
|
|
1028
|
+
else options[name] = [String(current), String(value2)];
|
|
1020
1029
|
}
|
|
1021
1030
|
|
|
1022
1031
|
// src/operator-context.ts
|
|
@@ -1083,17 +1092,17 @@ function validateProbes(integration, at) {
|
|
|
1083
1092
|
}
|
|
1084
1093
|
}
|
|
1085
1094
|
}
|
|
1086
|
-
function isRecord4(
|
|
1087
|
-
return
|
|
1095
|
+
function isRecord4(value2) {
|
|
1096
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1088
1097
|
}
|
|
1089
|
-
function safeText(
|
|
1090
|
-
return typeof
|
|
1098
|
+
function safeText(value2, max) {
|
|
1099
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1091
1100
|
}
|
|
1092
|
-
function safeProbePath(
|
|
1093
|
-
return typeof
|
|
1101
|
+
function safeProbePath(value2) {
|
|
1102
|
+
return typeof value2 === "string" && /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/.test(value2);
|
|
1094
1103
|
}
|
|
1095
|
-
function validId(
|
|
1096
|
-
return typeof
|
|
1104
|
+
function validId(value2) {
|
|
1105
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1097
1106
|
}
|
|
1098
1107
|
function unique(values) {
|
|
1099
1108
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -1135,10 +1144,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
|
1135
1144
|
local
|
|
1136
1145
|
};
|
|
1137
1146
|
}
|
|
1138
|
-
async function resolveDataExport(cfg,
|
|
1139
|
-
if (
|
|
1140
|
-
if (typeof
|
|
1141
|
-
const target = (0, import_node_path4.isAbsolute)(
|
|
1147
|
+
async function resolveDataExport(cfg, value2, names) {
|
|
1148
|
+
if (value2 === void 0 || value2 === null || value2 === false) return void 0;
|
|
1149
|
+
if (typeof value2 !== "string") return value2;
|
|
1150
|
+
const target = (0, import_node_path4.isAbsolute)(value2) ? value2 : (0, import_node_path4.resolve)(cfg.rootDir, value2);
|
|
1142
1151
|
if (target.endsWith(".json")) {
|
|
1143
1152
|
return JSON.parse((0, import_node_fs4.readFileSync)(target, "utf8"));
|
|
1144
1153
|
}
|
|
@@ -1147,7 +1156,7 @@ async function resolveDataExport(cfg, value, names) {
|
|
|
1147
1156
|
if (mod[name] !== void 0) return mod[name];
|
|
1148
1157
|
}
|
|
1149
1158
|
if (mod.default !== void 0) return mod.default;
|
|
1150
|
-
throw new Error(`${
|
|
1159
|
+
throw new Error(`${value2} did not export ${names.join(", ")} or default`);
|
|
1151
1160
|
}
|
|
1152
1161
|
function buildPlan(cfg) {
|
|
1153
1162
|
const integrationSchema = cfg.integrations?.some((integration) => integration.schema) ?? false;
|
|
@@ -1181,9 +1190,9 @@ function calendarServiceConfig(cfg, env) {
|
|
|
1181
1190
|
};
|
|
1182
1191
|
}
|
|
1183
1192
|
function calendarBookingPageUrl(cfg, env) {
|
|
1184
|
-
const
|
|
1185
|
-
if (
|
|
1186
|
-
return new URL(
|
|
1193
|
+
const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
|
|
1194
|
+
if (value2 === void 0 || value2 === null) return value2;
|
|
1195
|
+
return new URL(value2).toString();
|
|
1187
1196
|
}
|
|
1188
1197
|
function rulesFromSchema(schema) {
|
|
1189
1198
|
const entities = serializedEntities(schema);
|
|
@@ -1197,10 +1206,10 @@ function serializedEntities(schema) {
|
|
|
1197
1206
|
if (!entities || typeof entities !== "object" || Array.isArray(entities)) return [];
|
|
1198
1207
|
return Object.keys(entities);
|
|
1199
1208
|
}
|
|
1200
|
-
function envValue(
|
|
1201
|
-
if (!
|
|
1202
|
-
if (
|
|
1203
|
-
return
|
|
1209
|
+
function envValue(value2) {
|
|
1210
|
+
if (!value2) return void 0;
|
|
1211
|
+
if (value2.startsWith("$")) return process.env[value2.slice(1)];
|
|
1212
|
+
return value2;
|
|
1204
1213
|
}
|
|
1205
1214
|
function validateRawConfig(raw, path) {
|
|
1206
1215
|
if (!raw || typeof raw !== "object") throw new Error(`${path} must export an object`);
|
|
@@ -1256,8 +1265,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1256
1265
|
if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1257
1266
|
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
|
|
1258
1267
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1259
|
-
for (const [env,
|
|
1260
|
-
if (!safeText2(
|
|
1268
|
+
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1269
|
+
if (!safeText2(value2, 1024)) {
|
|
1261
1270
|
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1262
1271
|
}
|
|
1263
1272
|
}
|
|
@@ -1266,8 +1275,8 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1266
1275
|
if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1267
1276
|
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
|
|
1268
1277
|
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1269
|
-
for (const [env,
|
|
1270
|
-
if (
|
|
1278
|
+
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1279
|
+
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
1271
1280
|
throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
|
|
1272
1281
|
}
|
|
1273
1282
|
}
|
|
@@ -1286,37 +1295,37 @@ function validateServices(services, path) {
|
|
|
1286
1295
|
}
|
|
1287
1296
|
}
|
|
1288
1297
|
}
|
|
1289
|
-
function assertOnly(
|
|
1290
|
-
const extra = Object.keys(
|
|
1298
|
+
function assertOnly(value2, allowed, label) {
|
|
1299
|
+
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1291
1300
|
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1292
1301
|
}
|
|
1293
|
-
function isRecord5(
|
|
1294
|
-
return
|
|
1302
|
+
function isRecord5(value2) {
|
|
1303
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1295
1304
|
}
|
|
1296
|
-
function safeText2(
|
|
1297
|
-
return typeof
|
|
1305
|
+
function safeText2(value2, max) {
|
|
1306
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1298
1307
|
}
|
|
1299
|
-
function safeHttpsUrl(
|
|
1300
|
-
if (typeof
|
|
1308
|
+
function safeHttpsUrl(value2) {
|
|
1309
|
+
if (typeof value2 !== "string" || value2.length > 2048) return false;
|
|
1301
1310
|
try {
|
|
1302
|
-
const url = new URL(
|
|
1311
|
+
const url = new URL(value2);
|
|
1303
1312
|
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
1304
1313
|
} catch {
|
|
1305
1314
|
return false;
|
|
1306
1315
|
}
|
|
1307
1316
|
}
|
|
1308
|
-
function validId2(
|
|
1309
|
-
return typeof
|
|
1317
|
+
function validId2(value2) {
|
|
1318
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1310
1319
|
}
|
|
1311
1320
|
async function loadConfigModule(path) {
|
|
1312
1321
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1313
1322
|
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
|
|
1314
|
-
const
|
|
1315
|
-
if (typeof
|
|
1316
|
-
return
|
|
1323
|
+
const value2 = mod.default ?? mod.config;
|
|
1324
|
+
if (typeof value2 === "function") return await value2();
|
|
1325
|
+
return value2;
|
|
1317
1326
|
}
|
|
1318
|
-
function trimSlash(
|
|
1319
|
-
return
|
|
1327
|
+
function trimSlash(value2) {
|
|
1328
|
+
return value2.replace(/\/+$/, "");
|
|
1320
1329
|
}
|
|
1321
1330
|
function unique2(values) {
|
|
1322
1331
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -1342,8 +1351,8 @@ function resolveOperatorProfile(parsed) {
|
|
|
1342
1351
|
}
|
|
1343
1352
|
assertOperatorName(name, "context");
|
|
1344
1353
|
const profiles = readOperatorProfiles(file);
|
|
1345
|
-
const
|
|
1346
|
-
if (!
|
|
1354
|
+
const value2 = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
1355
|
+
if (!value2) {
|
|
1347
1356
|
throw new Error(
|
|
1348
1357
|
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
1349
1358
|
);
|
|
@@ -1352,7 +1361,7 @@ function resolveOperatorProfile(parsed) {
|
|
|
1352
1361
|
name,
|
|
1353
1362
|
source: fromFlag ? "flag" : "environment",
|
|
1354
1363
|
file,
|
|
1355
|
-
value
|
|
1364
|
+
value: value2
|
|
1356
1365
|
};
|
|
1357
1366
|
}
|
|
1358
1367
|
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
@@ -1380,8 +1389,8 @@ function operatorCredentialFiles(selection) {
|
|
|
1380
1389
|
scoped: (0, import_node_path5.join)(base, "admin-token.local.json")
|
|
1381
1390
|
};
|
|
1382
1391
|
}
|
|
1383
|
-
function assertOperatorName(
|
|
1384
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(
|
|
1392
|
+
function assertOperatorName(value2, label) {
|
|
1393
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value2)) {
|
|
1385
1394
|
throw new Error(
|
|
1386
1395
|
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
1387
1396
|
);
|
|
@@ -1407,22 +1416,22 @@ function readOperatorProfiles(file) {
|
|
|
1407
1416
|
);
|
|
1408
1417
|
}
|
|
1409
1418
|
const profiles = emptyProfiles();
|
|
1410
|
-
for (const [name,
|
|
1419
|
+
for (const [name, value2] of Object.entries(
|
|
1411
1420
|
raw.profiles
|
|
1412
1421
|
)) {
|
|
1413
1422
|
assertOperatorName(name, "context");
|
|
1414
|
-
profiles[name] = validateProfile(
|
|
1423
|
+
profiles[name] = validateProfile(value2, `operator context "${name}"`);
|
|
1415
1424
|
}
|
|
1416
1425
|
return profiles;
|
|
1417
1426
|
}
|
|
1418
1427
|
function emptyProfiles() {
|
|
1419
1428
|
return /* @__PURE__ */ Object.create(null);
|
|
1420
1429
|
}
|
|
1421
|
-
function validateProfile(
|
|
1422
|
-
if (!
|
|
1430
|
+
function validateProfile(value2, label) {
|
|
1431
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) {
|
|
1423
1432
|
throw new Error(`${label} has an invalid shape`);
|
|
1424
1433
|
}
|
|
1425
|
-
const unknown = Object.keys(
|
|
1434
|
+
const unknown = Object.keys(value2).filter(
|
|
1426
1435
|
(key) => !["platform", "app", "environment"].includes(key)
|
|
1427
1436
|
);
|
|
1428
1437
|
if (unknown.length > 0) {
|
|
@@ -1430,7 +1439,7 @@ function validateProfile(value, label) {
|
|
|
1430
1439
|
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
1431
1440
|
);
|
|
1432
1441
|
}
|
|
1433
|
-
const candidate =
|
|
1442
|
+
const candidate = value2;
|
|
1434
1443
|
if (typeof candidate.platform !== "string") {
|
|
1435
1444
|
throw new Error(`${label} needs an absolute platform URL`);
|
|
1436
1445
|
}
|
|
@@ -1445,8 +1454,8 @@ function validateProfile(value, label) {
|
|
|
1445
1454
|
...environment2 ? { environment: environment2 } : {}
|
|
1446
1455
|
};
|
|
1447
1456
|
}
|
|
1448
|
-
function clean(
|
|
1449
|
-
const normalized =
|
|
1457
|
+
function clean(value2) {
|
|
1458
|
+
const normalized = value2?.trim();
|
|
1450
1459
|
return normalized || void 0;
|
|
1451
1460
|
}
|
|
1452
1461
|
|
|
@@ -1539,8 +1548,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1539
1548
|
}
|
|
1540
1549
|
};
|
|
1541
1550
|
}
|
|
1542
|
-
function clean2(
|
|
1543
|
-
const normalized =
|
|
1551
|
+
function clean2(value2) {
|
|
1552
|
+
const normalized = value2?.trim();
|
|
1544
1553
|
return normalized || void 0;
|
|
1545
1554
|
}
|
|
1546
1555
|
|
|
@@ -1562,21 +1571,21 @@ var SET_OPTIONS = [
|
|
|
1562
1571
|
];
|
|
1563
1572
|
async function adminCommand(parsed, deps = {}) {
|
|
1564
1573
|
const area = parsed.positionals[1];
|
|
1565
|
-
const
|
|
1566
|
-
const credentialSet =
|
|
1567
|
-
const credentials =
|
|
1568
|
-
const models =
|
|
1569
|
-
const usage =
|
|
1570
|
-
const audit =
|
|
1571
|
-
if (area !== "ai" ||
|
|
1574
|
+
const action2 = parsed.positionals[2];
|
|
1575
|
+
const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
|
|
1576
|
+
const credentials = action2 === "credentials";
|
|
1577
|
+
const models = action2 === "models";
|
|
1578
|
+
const usage = action2 === "usage";
|
|
1579
|
+
const audit = action2 === "audit";
|
|
1580
|
+
if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
|
|
1572
1581
|
throw new Error('unknown admin command. Try "odla-ai admin ai show".');
|
|
1573
1582
|
}
|
|
1574
|
-
const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] :
|
|
1575
|
-
assertArgs(parsed, allowed, credentialSet ? 5 :
|
|
1583
|
+
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;
|
|
1584
|
+
assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
|
|
1576
1585
|
const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
1577
1586
|
await adminAi({
|
|
1578
|
-
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" :
|
|
1579
|
-
purpose:
|
|
1587
|
+
action: credentialSet ? "credential-set" : credentials ? "credentials" : models ? "models" : usage ? "usage" : audit ? "audit" : action2,
|
|
1588
|
+
purpose: action2 === "set" ? parsed.positionals[3] : void 0,
|
|
1580
1589
|
provider: stringOpt(parsed.options.provider),
|
|
1581
1590
|
model: stringOpt(parsed.options.model),
|
|
1582
1591
|
enabled: boolOpt(parsed.options.enabled),
|
|
@@ -1633,12 +1642,12 @@ function bothTenants(cfg) {
|
|
|
1633
1642
|
|
|
1634
1643
|
// src/agent-command.ts
|
|
1635
1644
|
async function agentCommand(parsed, deps = {}) {
|
|
1636
|
-
const
|
|
1637
|
-
if (
|
|
1638
|
-
throw new Error(`unknown agent action "${
|
|
1645
|
+
const action2 = parsed.positionals[1];
|
|
1646
|
+
if (action2 !== "jobs" && action2 !== "retry") {
|
|
1647
|
+
throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
1639
1648
|
}
|
|
1640
|
-
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"],
|
|
1641
|
-
if (
|
|
1649
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action2 === "jobs" ? 2 : 3);
|
|
1650
|
+
if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
1642
1651
|
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
1643
1652
|
}
|
|
1644
1653
|
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
@@ -1658,7 +1667,7 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1658
1667
|
);
|
|
1659
1668
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1660
1669
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1661
|
-
if (
|
|
1670
|
+
if (action2 === "retry") {
|
|
1662
1671
|
const id = parsed.positionals[2];
|
|
1663
1672
|
const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
|
|
1664
1673
|
const body2 = await readJson(res2);
|
|
@@ -2094,7 +2103,7 @@ async function copyFiles(cfg, token, route2, doFetch, out) {
|
|
|
2094
2103
|
}
|
|
2095
2104
|
|
|
2096
2105
|
// src/app-lifecycle.ts
|
|
2097
|
-
async function lifecycleCall(
|
|
2106
|
+
async function lifecycleCall(action2, options) {
|
|
2098
2107
|
const cfg = await loadProjectConfig(options.configPath);
|
|
2099
2108
|
const out = options.stdout ?? console;
|
|
2100
2109
|
const doFetch = options.fetch ?? fetch;
|
|
@@ -2104,13 +2113,13 @@ async function lifecycleCall(action, options) {
|
|
|
2104
2113
|
doFetch,
|
|
2105
2114
|
out
|
|
2106
2115
|
);
|
|
2107
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${
|
|
2116
|
+
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
|
|
2108
2117
|
method: "POST",
|
|
2109
2118
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
|
|
2110
2119
|
});
|
|
2111
2120
|
const body = await res.json().catch(() => ({}));
|
|
2112
2121
|
if (!res.ok || !body.ok) {
|
|
2113
|
-
throw new Error(`${
|
|
2122
|
+
throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
|
|
2114
2123
|
}
|
|
2115
2124
|
return body;
|
|
2116
2125
|
}
|
|
@@ -2251,44 +2260,44 @@ var REPLACEMENTS = [
|
|
|
2251
2260
|
[/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
|
|
2252
2261
|
[/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
|
|
2253
2262
|
];
|
|
2254
|
-
function redactSecrets(
|
|
2255
|
-
let result =
|
|
2263
|
+
function redactSecrets(value2) {
|
|
2264
|
+
let result = value2;
|
|
2256
2265
|
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
2257
2266
|
return result;
|
|
2258
2267
|
}
|
|
2259
2268
|
function redactingOutput(output) {
|
|
2260
|
-
const redactArgs = (args) => args.map((
|
|
2269
|
+
const redactArgs = (args) => args.map((value2) => redactOutputValue(value2, /* @__PURE__ */ new WeakMap()));
|
|
2261
2270
|
return {
|
|
2262
2271
|
log: (...args) => output.log(...redactArgs(args)),
|
|
2263
2272
|
error: (...args) => output.error(...redactArgs(args))
|
|
2264
2273
|
};
|
|
2265
2274
|
}
|
|
2266
|
-
function redactOutputValue(
|
|
2267
|
-
if (typeof
|
|
2268
|
-
if (
|
|
2269
|
-
const redacted = new Error(redactSecrets(
|
|
2270
|
-
redacted.name =
|
|
2271
|
-
if (
|
|
2275
|
+
function redactOutputValue(value2, seen) {
|
|
2276
|
+
if (typeof value2 === "string") return redactSecrets(value2);
|
|
2277
|
+
if (value2 instanceof Error) {
|
|
2278
|
+
const redacted = new Error(redactSecrets(value2.message));
|
|
2279
|
+
redacted.name = value2.name;
|
|
2280
|
+
if (value2.stack) redacted.stack = redactSecrets(value2.stack);
|
|
2272
2281
|
return redacted;
|
|
2273
2282
|
}
|
|
2274
|
-
if (!
|
|
2275
|
-
const prior = seen.get(
|
|
2283
|
+
if (!value2 || typeof value2 !== "object") return value2;
|
|
2284
|
+
const prior = seen.get(value2);
|
|
2276
2285
|
if (prior) return prior;
|
|
2277
|
-
if (Array.isArray(
|
|
2286
|
+
if (Array.isArray(value2)) {
|
|
2278
2287
|
const next2 = [];
|
|
2279
|
-
seen.set(
|
|
2280
|
-
for (const item of
|
|
2288
|
+
seen.set(value2, next2);
|
|
2289
|
+
for (const item of value2) next2.push(redactOutputValue(item, seen));
|
|
2281
2290
|
return next2;
|
|
2282
2291
|
}
|
|
2283
|
-
const prototype = Object.getPrototypeOf(
|
|
2284
|
-
if (prototype !== Object.prototype && prototype !== null) return
|
|
2292
|
+
const prototype = Object.getPrototypeOf(value2);
|
|
2293
|
+
if (prototype !== Object.prototype && prototype !== null) return value2;
|
|
2285
2294
|
const next = {};
|
|
2286
|
-
seen.set(
|
|
2287
|
-
for (const [key, item] of Object.entries(
|
|
2295
|
+
seen.set(value2, next);
|
|
2296
|
+
for (const [key, item] of Object.entries(value2)) next[key] = redactOutputValue(item, seen);
|
|
2288
2297
|
return next;
|
|
2289
2298
|
}
|
|
2290
|
-
function looksSecret(
|
|
2291
|
-
return redactSecrets(
|
|
2299
|
+
function looksSecret(value2) {
|
|
2300
|
+
return redactSecrets(value2) !== value2 || value2.includes("-----BEGIN");
|
|
2292
2301
|
}
|
|
2293
2302
|
|
|
2294
2303
|
// src/calendar-http.ts
|
|
@@ -2305,9 +2314,9 @@ async function readCalendarStatus(ctx) {
|
|
|
2305
2314
|
}
|
|
2306
2315
|
async function discoverGoogleCalendars(ctx) {
|
|
2307
2316
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2308
|
-
const
|
|
2309
|
-
if (!
|
|
2310
|
-
return
|
|
2317
|
+
const value2 = record(raw);
|
|
2318
|
+
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2319
|
+
return value2.calendars.map((item, index) => {
|
|
2311
2320
|
const calendar = record(item);
|
|
2312
2321
|
const id = textField(calendar?.id, 1024);
|
|
2313
2322
|
if (!calendar || !id) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
@@ -2329,10 +2338,10 @@ async function applyCalendarSettings(ctx, bookingPageUrl) {
|
|
|
2329
2338
|
}
|
|
2330
2339
|
async function startCalendarConnection(ctx) {
|
|
2331
2340
|
const raw = await calendarJson(ctx, "/google/connect", { method: "POST", body: "{}" });
|
|
2332
|
-
const
|
|
2333
|
-
const attemptId = textField(
|
|
2334
|
-
const consentUrl = textField(
|
|
2335
|
-
const expiresAt = timestamp3(
|
|
2341
|
+
const value2 = wrapped(raw, "attempt");
|
|
2342
|
+
const attemptId = textField(value2.attemptId, 180);
|
|
2343
|
+
const consentUrl = textField(value2.consentUrl, 4096);
|
|
2344
|
+
const expiresAt = timestamp3(value2.expiresAt);
|
|
2336
2345
|
if (!attemptId || !consentUrl || expiresAt === void 0) throw new Error("calendar connect returned an invalid attempt");
|
|
2337
2346
|
return { attemptId, consentUrl, expiresAt };
|
|
2338
2347
|
}
|
|
@@ -2350,42 +2359,42 @@ async function requestCalendarDisconnect(ctx) {
|
|
|
2350
2359
|
}
|
|
2351
2360
|
function parseCalendarStatus(raw, env) {
|
|
2352
2361
|
const outer = wrapped(raw, "calendar");
|
|
2353
|
-
const
|
|
2354
|
-
const connection = record(
|
|
2355
|
-
const config = record(
|
|
2362
|
+
const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
|
|
2363
|
+
const connection = record(value2.connection) ?? {};
|
|
2364
|
+
const config = record(value2.config) ?? record(outer.config) ?? {};
|
|
2356
2365
|
const googleConfig = record(config.google) ?? config;
|
|
2357
|
-
const stateValue = calendarState(
|
|
2366
|
+
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2358
2367
|
if (!stateValue) {
|
|
2359
2368
|
throw new Error("calendar status returned an invalid connection state");
|
|
2360
2369
|
}
|
|
2361
|
-
const providerValue =
|
|
2370
|
+
const providerValue = value2.provider ?? connection.provider ?? config.provider ?? (config.google ? "google" : void 0);
|
|
2362
2371
|
if (providerValue !== void 0 && providerValue !== null && providerValue !== "google") {
|
|
2363
2372
|
throw new Error("calendar status returned an unsupported provider");
|
|
2364
2373
|
}
|
|
2365
|
-
const accessValue =
|
|
2374
|
+
const accessValue = value2.access ?? connection.access ?? config.access ?? googleConfig.access;
|
|
2366
2375
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2367
2376
|
throw new Error("calendar status returned unsupported access");
|
|
2368
2377
|
}
|
|
2369
|
-
const errorValue = record(
|
|
2370
|
-
const errorCode = textField(
|
|
2371
|
-
const bookingPageValue = Object.hasOwn(
|
|
2372
|
-
const connected = typeof (
|
|
2373
|
-
const bookingCalendarId = textField(
|
|
2378
|
+
const errorValue = record(value2.error) ?? record(connection.error);
|
|
2379
|
+
const errorCode = textField(value2.lastErrorCode, 128);
|
|
2380
|
+
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2381
|
+
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
2382
|
+
const bookingCalendarId = textField(value2.bookingCalendarId ?? config.bookingCalendarId, 1024);
|
|
2374
2383
|
return {
|
|
2375
2384
|
env,
|
|
2376
|
-
enabled: typeof
|
|
2385
|
+
enabled: typeof value2.enabled === "boolean" ? value2.enabled : true,
|
|
2377
2386
|
connected,
|
|
2378
|
-
writable: typeof
|
|
2387
|
+
writable: typeof value2.writable === "boolean" ? value2.writable : stateValue === "healthy",
|
|
2379
2388
|
provider: providerValue === "google" ? "google" : null,
|
|
2380
2389
|
status: stateValue,
|
|
2381
2390
|
...accessValue !== void 0 ? { access: "book" } : {},
|
|
2382
2391
|
...bookingCalendarId ? { bookingCalendarId } : {},
|
|
2383
2392
|
calendars: calendarIds(
|
|
2384
|
-
|
|
2393
|
+
value2.availabilityCalendars ?? config.availabilityCalendars ?? value2.configuredCalendars ?? value2.calendars ?? config.calendars ?? googleConfig.calendars
|
|
2385
2394
|
),
|
|
2386
2395
|
...optionalNullableUrl("bookingPageUrl", bookingPageValue),
|
|
2387
|
-
grantedScopes: stringList(
|
|
2388
|
-
...optionalText("attemptId",
|
|
2396
|
+
grantedScopes: stringList(value2.grantedScopes ?? connection.grantedScopes ?? connection.scopes),
|
|
2397
|
+
...optionalText("attemptId", value2.attemptId ?? connection.attemptId, 180),
|
|
2389
2398
|
...errorValue || errorCode ? { error: {
|
|
2390
2399
|
...textField(errorValue?.code, 128) ? { code: textField(errorValue?.code, 128) } : {},
|
|
2391
2400
|
...textField(errorValue?.message, 500) ? { message: textField(errorValue?.message, 500) } : {},
|
|
@@ -2393,9 +2402,9 @@ function parseCalendarStatus(raw, env) {
|
|
|
2393
2402
|
} } : {}
|
|
2394
2403
|
};
|
|
2395
2404
|
}
|
|
2396
|
-
function calendarState(
|
|
2397
|
-
if (typeof
|
|
2398
|
-
if (CALENDAR_STATES.includes(
|
|
2405
|
+
function calendarState(value2) {
|
|
2406
|
+
if (typeof value2 !== "string") return null;
|
|
2407
|
+
if (CALENDAR_STATES.includes(value2)) return value2;
|
|
2399
2408
|
const legacy = {
|
|
2400
2409
|
disabled: "not_connected",
|
|
2401
2410
|
disconnected: "disconnected",
|
|
@@ -2406,7 +2415,7 @@ function calendarState(value) {
|
|
|
2406
2415
|
ready: "healthy",
|
|
2407
2416
|
error: "failed"
|
|
2408
2417
|
};
|
|
2409
|
-
return legacy[
|
|
2418
|
+
return legacy[value2] ?? null;
|
|
2410
2419
|
}
|
|
2411
2420
|
async function calendarJson(ctx, suffix, init) {
|
|
2412
2421
|
const platform = platformAudience(ctx.platform);
|
|
@@ -2440,50 +2449,50 @@ function wrapped(raw, key) {
|
|
|
2440
2449
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2441
2450
|
return record(outer[key]) ?? outer;
|
|
2442
2451
|
}
|
|
2443
|
-
function record(
|
|
2444
|
-
return
|
|
2452
|
+
function record(value2) {
|
|
2453
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2445
2454
|
}
|
|
2446
|
-
function textField(
|
|
2447
|
-
return typeof
|
|
2455
|
+
function textField(value2, max) {
|
|
2456
|
+
return typeof value2 === "string" && value2.length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2) ? value2 : void 0;
|
|
2448
2457
|
}
|
|
2449
|
-
function stringList(
|
|
2450
|
-
return Array.isArray(
|
|
2458
|
+
function stringList(value2) {
|
|
2459
|
+
return Array.isArray(value2) ? [...new Set(value2.filter((item) => !!textField(item, 4096)))] : [];
|
|
2451
2460
|
}
|
|
2452
|
-
function calendarIds(
|
|
2453
|
-
if (!Array.isArray(
|
|
2454
|
-
return [...new Set(
|
|
2461
|
+
function calendarIds(value2) {
|
|
2462
|
+
if (!Array.isArray(value2)) return [];
|
|
2463
|
+
return [...new Set(value2.flatMap((item) => {
|
|
2455
2464
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2456
2465
|
const calendar = record(item);
|
|
2457
2466
|
const id = textField(calendar?.id, 4096);
|
|
2458
2467
|
return id && calendar?.selected !== false ? [id] : [];
|
|
2459
2468
|
}))];
|
|
2460
2469
|
}
|
|
2461
|
-
function timestamp3(
|
|
2462
|
-
if (typeof
|
|
2463
|
-
if (typeof
|
|
2464
|
-
const parsed = Date.parse(
|
|
2470
|
+
function timestamp3(value2) {
|
|
2471
|
+
if (typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0) return value2;
|
|
2472
|
+
if (typeof value2 === "string") {
|
|
2473
|
+
const parsed = Date.parse(value2);
|
|
2465
2474
|
if (Number.isFinite(parsed)) return parsed;
|
|
2466
2475
|
}
|
|
2467
2476
|
return void 0;
|
|
2468
2477
|
}
|
|
2469
|
-
function optionalText(key,
|
|
2470
|
-
const parsed = textField(
|
|
2478
|
+
function optionalText(key, value2, max) {
|
|
2479
|
+
const parsed = textField(value2, max);
|
|
2471
2480
|
return parsed ? { [key]: parsed } : {};
|
|
2472
2481
|
}
|
|
2473
|
-
function optionalNullableUrl(key,
|
|
2474
|
-
if (
|
|
2475
|
-
const parsed = textField(
|
|
2482
|
+
function optionalNullableUrl(key, value2) {
|
|
2483
|
+
if (value2 === null) return { [key]: null };
|
|
2484
|
+
const parsed = textField(value2, 4096);
|
|
2476
2485
|
return parsed ? { [key]: parsed } : {};
|
|
2477
2486
|
}
|
|
2478
|
-
function identifier(
|
|
2479
|
-
if (typeof
|
|
2480
|
-
return
|
|
2487
|
+
function identifier(value2, name) {
|
|
2488
|
+
if (typeof value2 !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,179}$/.test(value2)) throw new Error(`${name} is invalid`);
|
|
2489
|
+
return value2;
|
|
2481
2490
|
}
|
|
2482
|
-
function credential(
|
|
2483
|
-
if (typeof
|
|
2491
|
+
function credential(value2) {
|
|
2492
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
2484
2493
|
throw new Error("calendar requires an odla developer token");
|
|
2485
2494
|
}
|
|
2486
|
-
return
|
|
2495
|
+
return value2;
|
|
2487
2496
|
}
|
|
2488
2497
|
|
|
2489
2498
|
// src/calendar-poll.ts
|
|
@@ -2644,11 +2653,11 @@ async function connectWithContext(ctx, options) {
|
|
|
2644
2653
|
}
|
|
2645
2654
|
throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
|
|
2646
2655
|
}
|
|
2647
|
-
function trustedCalendarConsentUrl(platform,
|
|
2656
|
+
function trustedCalendarConsentUrl(platform, value2) {
|
|
2648
2657
|
const origin = platformAudience(platform);
|
|
2649
2658
|
let url;
|
|
2650
2659
|
try {
|
|
2651
|
-
url =
|
|
2660
|
+
url = value2.startsWith("/") ? new URL(value2, origin) : new URL(value2);
|
|
2652
2661
|
} catch {
|
|
2653
2662
|
throw new Error("odla.ai returned an invalid calendar consent URL");
|
|
2654
2663
|
}
|
|
@@ -2659,12 +2668,12 @@ function trustedCalendarConsentUrl(platform, value) {
|
|
|
2659
2668
|
}
|
|
2660
2669
|
return url.toString();
|
|
2661
2670
|
}
|
|
2662
|
-
function pollTimeout(
|
|
2663
|
-
if (!Number.isSafeInteger(
|
|
2664
|
-
return
|
|
2671
|
+
function pollTimeout(value2 = 10 * 6e4) {
|
|
2672
|
+
if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) throw new Error("calendar poll timeout must be 1000-3600000ms");
|
|
2673
|
+
return value2;
|
|
2665
2674
|
}
|
|
2666
|
-
function productionConsent(env, yes,
|
|
2667
|
-
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${
|
|
2675
|
+
function productionConsent(env, yes, action2) {
|
|
2676
|
+
if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action2} for "${env}" without --yes`);
|
|
2668
2677
|
}
|
|
2669
2678
|
function printStatus(status, json, out) {
|
|
2670
2679
|
if (json) {
|
|
@@ -2693,6 +2702,8 @@ var CAPABILITIES = {
|
|
|
2693
2702
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2694
2703
|
"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",
|
|
2695
2704
|
"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",
|
|
2705
|
+
"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",
|
|
2706
|
+
"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",
|
|
2696
2707
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2697
2708
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2698
2709
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2739,15 +2750,542 @@ function printGroup(out, heading, items) {
|
|
|
2739
2750
|
out.log("");
|
|
2740
2751
|
}
|
|
2741
2752
|
|
|
2753
|
+
// src/config-reconcile-command.ts
|
|
2754
|
+
var import_apps6 = require("@odla-ai/apps");
|
|
2755
|
+
var import_node_path7 = require("path");
|
|
2756
|
+
|
|
2757
|
+
// src/config-reconcile-digest.ts
|
|
2758
|
+
var import_node_crypto = require("crypto");
|
|
2759
|
+
function canonicalJson(value2) {
|
|
2760
|
+
return JSON.stringify(canonicalValue(value2));
|
|
2761
|
+
}
|
|
2762
|
+
function configDigest(value2) {
|
|
2763
|
+
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2764
|
+
}
|
|
2765
|
+
function canonicalValue(value2) {
|
|
2766
|
+
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2767
|
+
if (value2 && typeof value2 === "object") {
|
|
2768
|
+
return Object.fromEntries(
|
|
2769
|
+
Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
return value2;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// src/config-reconcile-desired.ts
|
|
2776
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2777
|
+
|
|
2778
|
+
// src/provision-helpers.ts
|
|
2779
|
+
var import_ai = require("@odla-ai/ai");
|
|
2780
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2781
|
+
function defaultSecretName(provider) {
|
|
2782
|
+
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2783
|
+
return names[provider] ?? `${provider}_api_key`;
|
|
2784
|
+
}
|
|
2785
|
+
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2786
|
+
const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
|
|
2787
|
+
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2788
|
+
headers: { authorization: `Bearer ${token}` }
|
|
2789
|
+
});
|
|
2790
|
+
if (res.ok || res.status === 404) return;
|
|
2791
|
+
if (res.status === 403) {
|
|
2792
|
+
throw new Error(
|
|
2793
|
+
`${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`
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText3(res)}`);
|
|
2797
|
+
}
|
|
2798
|
+
async function postJson(doFetch, url, bearer, body) {
|
|
2799
|
+
const res = await doFetch(url, {
|
|
2800
|
+
method: "POST",
|
|
2801
|
+
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
2802
|
+
body: JSON.stringify(body)
|
|
2803
|
+
});
|
|
2804
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText3(res)}`);
|
|
2805
|
+
}
|
|
2806
|
+
function normalizeClerkConfig(value2) {
|
|
2807
|
+
if (!value2) return null;
|
|
2808
|
+
if (typeof value2 === "string") {
|
|
2809
|
+
const publishableKey2 = envValue(value2);
|
|
2810
|
+
return publishableKey2 ? { publishableKey: publishableKey2 } : null;
|
|
2811
|
+
}
|
|
2812
|
+
if (typeof value2 !== "object") return null;
|
|
2813
|
+
const cfg = value2;
|
|
2814
|
+
const publishableKey = envValue(cfg.publishableKey);
|
|
2815
|
+
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
2816
|
+
}
|
|
2817
|
+
async function safeText3(res) {
|
|
2818
|
+
try {
|
|
2819
|
+
return redactSecrets((await res.text()).slice(0, 500));
|
|
2820
|
+
} catch {
|
|
2821
|
+
return "";
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
// src/config-reconcile-desired.ts
|
|
2826
|
+
function desiredRegistryState(cfg) {
|
|
2827
|
+
const environments = {};
|
|
2828
|
+
const services = (0, import_apps4.orderAppServices)(cfg.services);
|
|
2829
|
+
for (const env of cfg.envs) {
|
|
2830
|
+
const desiredServices = {};
|
|
2831
|
+
for (const service of services) {
|
|
2832
|
+
desiredServices[service] = {
|
|
2833
|
+
enabled: true,
|
|
2834
|
+
...managedServiceConfig(cfg, env, service)
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
const authoredAuth = cfg.auth?.clerk && Object.hasOwn(cfg.auth.clerk, env) ? cfg.auth.clerk[env] : void 0;
|
|
2838
|
+
const auth = normalizeClerkConfig(authoredAuth);
|
|
2839
|
+
if (authoredAuth && !auth) {
|
|
2840
|
+
throw new Error(`auth.clerk.${env} could not resolve its publishable key`);
|
|
2841
|
+
}
|
|
2842
|
+
const linkManaged = !!cfg.links && Object.hasOwn(cfg.links, env);
|
|
2843
|
+
environments[env] = {
|
|
2844
|
+
services: desiredServices,
|
|
2845
|
+
...auth ? { auth } : {},
|
|
2846
|
+
...linkManaged ? { link: cfg.links?.[env] ?? null } : {}
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
return {
|
|
2850
|
+
appId: cfg.app.id,
|
|
2851
|
+
name: cfg.app.name,
|
|
2852
|
+
environments
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
function managedServiceConfig(cfg, env, service) {
|
|
2856
|
+
if (service === "ai" && cfg.ai?.provider) {
|
|
2857
|
+
return {
|
|
2858
|
+
config: {
|
|
2859
|
+
provider: cfg.ai.provider,
|
|
2860
|
+
...cfg.ai.model ? { model: cfg.ai.model } : {}
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
if (service === "calendar") return { config: calendarServiceConfig(cfg, env) };
|
|
2865
|
+
return {};
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
// src/config-reconcile.ts
|
|
2869
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2870
|
+
|
|
2871
|
+
// src/config-reconcile-values.ts
|
|
2872
|
+
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
2873
|
+
return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
|
|
2874
|
+
}
|
|
2875
|
+
function action(kind, path, before, after, risk, applySupport, reason) {
|
|
2876
|
+
return { kind, path, before, after, risk, requiresApproval: false, applySupport, reason };
|
|
2877
|
+
}
|
|
2878
|
+
function scopedAction(kind, path, env, service, before, after, reason, destructive = false) {
|
|
2879
|
+
const production = env === "prod" || env === "production";
|
|
2880
|
+
return {
|
|
2881
|
+
...action(
|
|
2882
|
+
kind,
|
|
2883
|
+
path,
|
|
2884
|
+
before,
|
|
2885
|
+
after,
|
|
2886
|
+
destructive ? "high" : production ? "medium" : "low",
|
|
2887
|
+
destructive ? "studio" : "provision",
|
|
2888
|
+
reason
|
|
2889
|
+
),
|
|
2890
|
+
env,
|
|
2891
|
+
...service ? { service } : {},
|
|
2892
|
+
requiresApproval: production || destructive
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
function observedProjection(app) {
|
|
2896
|
+
return {
|
|
2897
|
+
appId: app.appId,
|
|
2898
|
+
name: app.name,
|
|
2899
|
+
archivedAt: app.archivedAt,
|
|
2900
|
+
environments: Object.fromEntries(
|
|
2901
|
+
Object.entries(app.environments).sort(([left], [right]) => left.localeCompare(right)).map(([env, services]) => [
|
|
2902
|
+
env,
|
|
2903
|
+
Object.fromEntries(Object.entries(services).sort(([left], [right]) => left.localeCompare(right)))
|
|
2904
|
+
])
|
|
2905
|
+
),
|
|
2906
|
+
auth: app.auth,
|
|
2907
|
+
links: app.links
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
function projectConfig(value2, keys) {
|
|
2911
|
+
return Object.fromEntries(keys.filter((key) => value2[key] !== void 0).map((key) => [key, value2[key]]));
|
|
2912
|
+
}
|
|
2913
|
+
function same(left, right) {
|
|
2914
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
2915
|
+
}
|
|
2916
|
+
function actionId(actionValue) {
|
|
2917
|
+
return `action-${configDigest(actionValue).slice("sha256:".length, "sha256:".length + 16)}`;
|
|
2918
|
+
}
|
|
2919
|
+
function quoteArg(value2) {
|
|
2920
|
+
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
// src/config-reconcile.ts
|
|
2924
|
+
var COVERAGE = {
|
|
2925
|
+
included: [
|
|
2926
|
+
"app display name",
|
|
2927
|
+
"environment service enablement",
|
|
2928
|
+
"AI provider/model and calendar booking service configuration",
|
|
2929
|
+
"auth configuration when explicitly declared",
|
|
2930
|
+
"deployment link when explicitly declared"
|
|
2931
|
+
],
|
|
2932
|
+
excluded: [
|
|
2933
|
+
{ path: "db.schema", reason: "owned by the database data plane" },
|
|
2934
|
+
{ path: "db.rules", reason: "owned by the database data plane" },
|
|
2935
|
+
{ path: "integrations.seeds", reason: "guarded data-plane writes are not Registry state" },
|
|
2936
|
+
{ path: "credentials", reason: "shown-once credentials are intentionally local" },
|
|
2937
|
+
{ path: "secrets", reason: "vault and Worker secrets are write-only" },
|
|
2938
|
+
{ path: "calendar.connection", reason: "OAuth connection state is provider-owned" },
|
|
2939
|
+
{ path: "service.config.opaque", reason: "controller-assigned fields remain service-owned" }
|
|
2940
|
+
]
|
|
2941
|
+
};
|
|
2942
|
+
function reconcileConfig(input) {
|
|
2943
|
+
const desiredSource = { kind: "project_config", location: input.configPath };
|
|
2944
|
+
const observedSource = {
|
|
2945
|
+
kind: "registry",
|
|
2946
|
+
location: `${input.platformUrl}/registry/apps/${encodeURIComponent(input.desired.appId)}`
|
|
2947
|
+
};
|
|
2948
|
+
const differences = [];
|
|
2949
|
+
const actions = [];
|
|
2950
|
+
const add = (difference2, action2) => {
|
|
2951
|
+
differences.push(difference2);
|
|
2952
|
+
if (action2) actions.push({ ...action2, id: actionId(action2) });
|
|
2953
|
+
};
|
|
2954
|
+
compareApp(input.desired, input.observed, desiredSource, observedSource, add);
|
|
2955
|
+
compareEnvironments(input.desired, input.observed, desiredSource, observedSource, add);
|
|
2956
|
+
const desiredRevision = configDigest(input.desired);
|
|
2957
|
+
const observedRevision = input.observed ? configDigest(observedProjection(input.observed)) : null;
|
|
2958
|
+
const different = differences.filter((entry) => entry.status === "different").length;
|
|
2959
|
+
const unmanaged = differences.length - different;
|
|
2960
|
+
return {
|
|
2961
|
+
generatedAt: input.generatedAt,
|
|
2962
|
+
scope: {
|
|
2963
|
+
appId: input.desired.appId,
|
|
2964
|
+
platformUrl: input.platformUrl,
|
|
2965
|
+
environments: Object.keys(input.desired.environments).sort()
|
|
2966
|
+
},
|
|
2967
|
+
sources: { desired: desiredSource, observed: observedSource },
|
|
2968
|
+
desiredRevision,
|
|
2969
|
+
observedRevision,
|
|
2970
|
+
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2971
|
+
summary: {
|
|
2972
|
+
differences: different,
|
|
2973
|
+
unmanaged,
|
|
2974
|
+
actions: actions.length,
|
|
2975
|
+
productionActions: actions.filter((action2) => action2.env === "prod" || action2.env === "production").length,
|
|
2976
|
+
highRiskActions: actions.filter((action2) => action2.risk === "high").length
|
|
2977
|
+
},
|
|
2978
|
+
coverage: COVERAGE,
|
|
2979
|
+
differences,
|
|
2980
|
+
actions
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
function compareApp(desired, observed, desiredSource, observedSource, add) {
|
|
2984
|
+
if (!observed) {
|
|
2985
|
+
const path = "app";
|
|
2986
|
+
add(
|
|
2987
|
+
difference(path, desired, null, "different", "the app is absent from Registry", desiredSource, observedSource),
|
|
2988
|
+
action(
|
|
2989
|
+
"create_app",
|
|
2990
|
+
path,
|
|
2991
|
+
null,
|
|
2992
|
+
{ appId: desired.appId, name: desired.name },
|
|
2993
|
+
"low",
|
|
2994
|
+
"provision",
|
|
2995
|
+
"create the repository-declared app"
|
|
2996
|
+
)
|
|
2997
|
+
);
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
if (observed.name !== desired.name) {
|
|
3001
|
+
const path = "app.name";
|
|
3002
|
+
add(
|
|
3003
|
+
difference(path, desired.name, observed.name, "different", "the checked-in display name differs", desiredSource, observedSource),
|
|
3004
|
+
{
|
|
3005
|
+
...action("rename_app", path, observed.name, desired.name, "low", "command", "make Registry match the checked-in display name"),
|
|
3006
|
+
command: `odla-ai app rename ${quoteArg(desired.name)} --config ${quoteArg(desiredSource.location)}`
|
|
3007
|
+
}
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function compareEnvironments(desired, observed, desiredSource, observedSource, add) {
|
|
3012
|
+
const envs = /* @__PURE__ */ new Set([
|
|
3013
|
+
...Object.keys(desired.environments),
|
|
3014
|
+
...Object.keys(observed?.environments ?? {}).filter((env) => Object.values(observed?.environments[env] ?? {}).some((service) => service.enabled))
|
|
3015
|
+
]);
|
|
3016
|
+
for (const env of [...envs].sort()) {
|
|
3017
|
+
compareServices(env, desired, observed, desiredSource, observedSource, add);
|
|
3018
|
+
compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
3022
|
+
const wanted = desired.environments[env]?.services ?? {};
|
|
3023
|
+
const live = observed?.environments[env] ?? {};
|
|
3024
|
+
const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
|
|
3025
|
+
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
3026
|
+
for (const service of services) {
|
|
3027
|
+
const next = wanted[service];
|
|
3028
|
+
const current = live[service];
|
|
3029
|
+
const path = `environments.${env}.services.${service}`;
|
|
3030
|
+
if (next?.enabled && !current?.enabled) {
|
|
3031
|
+
add(
|
|
3032
|
+
difference(path, next, current ?? null, "different", "the repository enables a service Registry does not", desiredSource, observedSource, env, service),
|
|
3033
|
+
scopedAction("enable_service", path, env, service, current ?? null, next, "enable the repository-declared service")
|
|
3034
|
+
);
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
if (next?.enabled && current?.enabled && next.config) {
|
|
3038
|
+
const projected = projectConfig(current.config, Object.keys(next.config));
|
|
3039
|
+
if (!same(next.config, projected)) {
|
|
3040
|
+
add(
|
|
3041
|
+
difference(`${path}.config`, next.config, projected, "different", "repository-managed service settings differ", desiredSource, observedSource, env, service),
|
|
3042
|
+
scopedAction(
|
|
3043
|
+
"configure_service",
|
|
3044
|
+
`${path}.config`,
|
|
3045
|
+
env,
|
|
3046
|
+
service,
|
|
3047
|
+
projected,
|
|
3048
|
+
next.config,
|
|
3049
|
+
"apply the repository-managed service settings"
|
|
3050
|
+
)
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
for (const service of [...services].reverse()) {
|
|
3056
|
+
const next = wanted[service];
|
|
3057
|
+
const current = live[service];
|
|
3058
|
+
if (next?.enabled || !current?.enabled) continue;
|
|
3059
|
+
const path = `environments.${env}.services.${service}`;
|
|
3060
|
+
add(
|
|
3061
|
+
difference(path, null, { enabled: true }, "different", "Registry enables a service absent from repository intent", desiredSource, observedSource, env, service),
|
|
3062
|
+
scopedAction(
|
|
3063
|
+
"disable_service",
|
|
3064
|
+
path,
|
|
3065
|
+
env,
|
|
3066
|
+
service,
|
|
3067
|
+
{ enabled: true },
|
|
3068
|
+
{ enabled: false },
|
|
3069
|
+
"disablement is destructive to availability and requires an explicit Studio review",
|
|
3070
|
+
true
|
|
3071
|
+
)
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
function compareOptionalEnvironmentState(env, desired, observed, desiredSource, observedSource, add) {
|
|
3076
|
+
const wanted = desired.environments[env];
|
|
3077
|
+
const liveAuth = observed?.auth[env] ?? null;
|
|
3078
|
+
if (wanted?.auth) {
|
|
3079
|
+
const projected = liveAuth ? projectConfig(liveAuth, Object.keys(wanted.auth)) : null;
|
|
3080
|
+
if (!same(wanted.auth, projected)) {
|
|
3081
|
+
const path = `environments.${env}.auth`;
|
|
3082
|
+
add(
|
|
3083
|
+
difference(path, wanted.auth, projected, "different", "explicit repository auth settings differ", desiredSource, observedSource, env),
|
|
3084
|
+
scopedAction("set_auth", path, env, void 0, projected, wanted.auth, "apply explicit repository auth settings")
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
} else if (liveAuth) {
|
|
3088
|
+
add(difference(
|
|
3089
|
+
`environments.${env}.auth`,
|
|
3090
|
+
null,
|
|
3091
|
+
projectConfig(liveAuth, ["publishableKey", "audience", "mode"]),
|
|
3092
|
+
"unmanaged",
|
|
3093
|
+
"live auth exists but this project config does not manage it",
|
|
3094
|
+
desiredSource,
|
|
3095
|
+
observedSource,
|
|
3096
|
+
env
|
|
3097
|
+
));
|
|
3098
|
+
}
|
|
3099
|
+
const managesLink = wanted && Object.hasOwn(wanted, "link");
|
|
3100
|
+
const liveLink = observed?.links[env] ?? null;
|
|
3101
|
+
if (managesLink && wanted.link !== liveLink) {
|
|
3102
|
+
const path = `environments.${env}.link`;
|
|
3103
|
+
add(
|
|
3104
|
+
difference(path, wanted.link ?? null, liveLink, "different", "explicit repository deployment link differs", desiredSource, observedSource, env),
|
|
3105
|
+
scopedAction("set_link", path, env, void 0, liveLink, wanted.link ?? null, "apply the repository deployment link")
|
|
3106
|
+
);
|
|
3107
|
+
} else if (!managesLink && liveLink) {
|
|
3108
|
+
add(difference(
|
|
3109
|
+
`environments.${env}.link`,
|
|
3110
|
+
null,
|
|
3111
|
+
liveLink,
|
|
3112
|
+
"unmanaged",
|
|
3113
|
+
"a live deployment link exists but this project config does not manage it",
|
|
3114
|
+
desiredSource,
|
|
3115
|
+
observedSource,
|
|
3116
|
+
env
|
|
3117
|
+
));
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// src/config-reconcile-command.ts
|
|
3122
|
+
async function configDiff(options) {
|
|
3123
|
+
const reconciliation = await inspectConfig(options);
|
|
3124
|
+
const { actions: _actions, ...read3 } = reconciliation;
|
|
3125
|
+
const document2 = {
|
|
3126
|
+
schemaVersion: "odla.config-diff/v1",
|
|
3127
|
+
...read3,
|
|
3128
|
+
nextActions: diffNextActions(reconciliation, options.configPath)
|
|
3129
|
+
};
|
|
3130
|
+
printDiff(document2, options);
|
|
3131
|
+
return document2;
|
|
3132
|
+
}
|
|
3133
|
+
async function configPlan(options) {
|
|
3134
|
+
const reconciliation = await inspectConfig(options);
|
|
3135
|
+
const planDigest = configDigest({
|
|
3136
|
+
schemaVersion: "odla.config-plan/v1",
|
|
3137
|
+
desiredRevision: reconciliation.desiredRevision,
|
|
3138
|
+
observedRevision: reconciliation.observedRevision,
|
|
3139
|
+
actions: reconciliation.actions
|
|
3140
|
+
});
|
|
3141
|
+
const document2 = {
|
|
3142
|
+
schemaVersion: "odla.config-plan/v1",
|
|
3143
|
+
...reconciliation,
|
|
3144
|
+
planDigest,
|
|
3145
|
+
apply: {
|
|
3146
|
+
supported: false,
|
|
3147
|
+
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3148
|
+
},
|
|
3149
|
+
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3150
|
+
};
|
|
3151
|
+
printPlan2(document2, options);
|
|
3152
|
+
return document2;
|
|
3153
|
+
}
|
|
3154
|
+
async function inspectConfig(options) {
|
|
3155
|
+
const out = options.stdout ?? console;
|
|
3156
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3157
|
+
const doFetch = options.fetch ?? fetch;
|
|
3158
|
+
const token = await resolveAdminPlatformToken({
|
|
3159
|
+
platform: cfg.platformUrl,
|
|
3160
|
+
scope: "app:config:read",
|
|
3161
|
+
token: options.token,
|
|
3162
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3163
|
+
rootDir: cfg.rootDir,
|
|
3164
|
+
email: options.email,
|
|
3165
|
+
open: options.open,
|
|
3166
|
+
fetch: doFetch,
|
|
3167
|
+
stdout: out,
|
|
3168
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3169
|
+
label: `odla CLI (${cfg.app.id} config read)`
|
|
3170
|
+
});
|
|
3171
|
+
const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3172
|
+
const observed = await client.resolveApp(cfg.app.id);
|
|
3173
|
+
return reconcileConfig({
|
|
3174
|
+
desired: desiredRegistryState(cfg),
|
|
3175
|
+
observed,
|
|
3176
|
+
configPath: cfg.configPath,
|
|
3177
|
+
platformUrl: cfg.platformUrl,
|
|
3178
|
+
generatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
function printDiff(document2, options) {
|
|
3182
|
+
const out = options.stdout ?? console;
|
|
3183
|
+
if (options.json) {
|
|
3184
|
+
out.log(JSON.stringify(document2, null, 2));
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
printHeader(out, "diff", document2);
|
|
3188
|
+
printDifferences(out, document2);
|
|
3189
|
+
printNext(out, document2.nextActions);
|
|
3190
|
+
}
|
|
3191
|
+
function printPlan2(document2, options) {
|
|
3192
|
+
const out = options.stdout ?? console;
|
|
3193
|
+
if (options.json) {
|
|
3194
|
+
out.log(JSON.stringify(document2, null, 2));
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3197
|
+
printHeader(out, "plan", document2);
|
|
3198
|
+
for (const entry of document2.actions) {
|
|
3199
|
+
const scope = [entry.env, entry.service].filter(Boolean).join("/");
|
|
3200
|
+
out.log(
|
|
3201
|
+
` ${entry.id} ${entry.kind}${scope ? ` (${scope})` : ""} ${entry.risk}${entry.requiresApproval ? ", approval required" : ""}`
|
|
3202
|
+
);
|
|
3203
|
+
out.log(` ${entry.reason}`);
|
|
3204
|
+
}
|
|
3205
|
+
if (!document2.actions.length) out.log(" no managed changes");
|
|
3206
|
+
out.log(`plan digest: ${document2.planDigest}`);
|
|
3207
|
+
out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
|
|
3208
|
+
printNext(out, document2.nextActions);
|
|
3209
|
+
}
|
|
3210
|
+
function printHeader(out, kind, document2) {
|
|
3211
|
+
out.log(`config ${kind}: ${document2.scope.appId} \u2014 ${document2.status}`);
|
|
3212
|
+
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3213
|
+
out.log(`desired: ${document2.desiredRevision}`);
|
|
3214
|
+
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3215
|
+
out.log(
|
|
3216
|
+
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
function printDifferences(out, document2) {
|
|
3220
|
+
for (const entry of document2.differences) {
|
|
3221
|
+
out.log(` ${entry.status} ${entry.path} ${entry.reason}`);
|
|
3222
|
+
}
|
|
3223
|
+
if (!document2.differences.length) out.log(" managed Registry configuration is in sync");
|
|
3224
|
+
}
|
|
3225
|
+
function printNext(out, next) {
|
|
3226
|
+
if (!next.length) return;
|
|
3227
|
+
out.log("next:");
|
|
3228
|
+
for (const item of next) out.log(` ${item.command}
|
|
3229
|
+
${item.description}`);
|
|
3230
|
+
}
|
|
3231
|
+
function diffNextActions(reconciliation, configPath) {
|
|
3232
|
+
if (reconciliation.status === "in_sync") {
|
|
3233
|
+
return [{
|
|
3234
|
+
code: "verify_runtime",
|
|
3235
|
+
command: `odla-ai smoke --config ${quoteArg2(configPath)}`,
|
|
3236
|
+
description: "Registry intent matches; verify the service-owned data planes separately."
|
|
3237
|
+
}];
|
|
3238
|
+
}
|
|
3239
|
+
return [{
|
|
3240
|
+
code: "freeze_plan",
|
|
3241
|
+
command: `odla-ai config plan --config ${quoteArg2(configPath)} --json`,
|
|
3242
|
+
description: "Freeze the current desired and observed revisions into a reviewable action plan."
|
|
3243
|
+
}];
|
|
3244
|
+
}
|
|
3245
|
+
function planNextActions(reconciliation, configPath) {
|
|
3246
|
+
const next = [];
|
|
3247
|
+
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3248
|
+
next.push({
|
|
3249
|
+
code: "review_provision",
|
|
3250
|
+
command: `odla-ai provision --config ${quoteArg2(configPath)} --dry-run`,
|
|
3251
|
+
description: "Review the existing provisioner's service/auth/link work before applying it."
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
for (const action2 of reconciliation.actions.filter((entry) => entry.command)) {
|
|
3255
|
+
if (!next.some((entry) => entry.command === action2.command)) {
|
|
3256
|
+
next.push({ code: action2.kind, command: action2.command, description: action2.reason });
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3260
|
+
const { appId, platformUrl } = reconciliation.scope;
|
|
3261
|
+
next.push({
|
|
3262
|
+
code: "review_destructive",
|
|
3263
|
+
command: `${platformUrl}/studio/apps/${encodeURIComponent(appId)}/settings`,
|
|
3264
|
+
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3268
|
+
next.push({
|
|
3269
|
+
code: "declare_or_accept_runtime_state",
|
|
3270
|
+
command: `${reconciliation.scope.platformUrl}/studio/apps/${encodeURIComponent(reconciliation.scope.appId)}/settings`,
|
|
3271
|
+
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
return next;
|
|
3275
|
+
}
|
|
3276
|
+
function quoteArg2(value2) {
|
|
3277
|
+
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
2742
3280
|
// src/doctor-checks.ts
|
|
2743
3281
|
var import_node_child_process3 = require("child_process");
|
|
2744
3282
|
var import_node_fs10 = require("fs");
|
|
2745
|
-
var
|
|
3283
|
+
var import_node_path9 = require("path");
|
|
2746
3284
|
|
|
2747
3285
|
// src/wrangler.ts
|
|
2748
3286
|
var import_node_child_process2 = require("child_process");
|
|
2749
3287
|
var import_node_fs9 = require("fs");
|
|
2750
|
-
var
|
|
3288
|
+
var import_node_path8 = require("path");
|
|
2751
3289
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
2752
3290
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2753
3291
|
let stdout = "";
|
|
@@ -2761,7 +3299,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
2761
3299
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
2762
3300
|
function findWranglerConfig(rootDir) {
|
|
2763
3301
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
2764
|
-
const path = (0,
|
|
3302
|
+
const path = (0, import_node_path8.join)(rootDir, name);
|
|
2765
3303
|
if ((0, import_node_fs9.existsSync)(path)) return path;
|
|
2766
3304
|
}
|
|
2767
3305
|
return null;
|
|
@@ -2827,11 +3365,11 @@ function lintRules(rules, entities, publicRead) {
|
|
|
2827
3365
|
const warnings = [];
|
|
2828
3366
|
if (!rules) return warnings;
|
|
2829
3367
|
for (const [ns, actions] of Object.entries(rules)) {
|
|
2830
|
-
for (const [
|
|
3368
|
+
for (const [action2, expr] of Object.entries(actions)) {
|
|
2831
3369
|
if (typeof expr !== "string" || expr.trim() !== "true") continue;
|
|
2832
|
-
if (
|
|
3370
|
+
if (action2 === "view" && publicRead.includes(ns)) continue;
|
|
2833
3371
|
warnings.push(
|
|
2834
|
-
|
|
3372
|
+
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`
|
|
2835
3373
|
);
|
|
2836
3374
|
}
|
|
2837
3375
|
}
|
|
@@ -2867,17 +3405,17 @@ function wranglerWarnings(rootDir) {
|
|
|
2867
3405
|
for (const { label, block } of blocks) {
|
|
2868
3406
|
const assets = block.assets;
|
|
2869
3407
|
if (assets?.directory) {
|
|
2870
|
-
const dir = (0,
|
|
2871
|
-
if (dir === (0,
|
|
3408
|
+
const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
|
|
3409
|
+
if (dir === (0, import_node_path9.resolve)(rootDir)) {
|
|
2872
3410
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
2873
|
-
} else if ((0, import_node_fs10.existsSync)((0,
|
|
3411
|
+
} else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
|
|
2874
3412
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
2875
3413
|
}
|
|
2876
3414
|
}
|
|
2877
3415
|
const vars = block.vars;
|
|
2878
3416
|
if (vars && typeof vars === "object") {
|
|
2879
|
-
for (const [name,
|
|
2880
|
-
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof
|
|
3417
|
+
for (const [name, value2] of Object.entries(vars)) {
|
|
3418
|
+
if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value2 === "string" && looksSecret(value2)) {
|
|
2881
3419
|
warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
|
|
2882
3420
|
}
|
|
2883
3421
|
}
|
|
@@ -2905,7 +3443,7 @@ function o11yProjectWarnings(rootDir) {
|
|
|
2905
3443
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
2906
3444
|
return warnings;
|
|
2907
3445
|
}
|
|
2908
|
-
const main = typeof config.main === "string" ? (0,
|
|
3446
|
+
const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
|
|
2909
3447
|
if (!main || !(0, import_node_fs10.existsSync)(main)) {
|
|
2910
3448
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
2911
3449
|
} else {
|
|
@@ -2935,7 +3473,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
2935
3473
|
}
|
|
2936
3474
|
function readPackageJson(rootDir) {
|
|
2937
3475
|
try {
|
|
2938
|
-
return JSON.parse((0, import_node_fs10.readFileSync)((0,
|
|
3476
|
+
return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
|
|
2939
3477
|
} catch {
|
|
2940
3478
|
return null;
|
|
2941
3479
|
}
|
|
@@ -2991,8 +3529,8 @@ function integrationWarnings(integrations, schema, rules) {
|
|
|
2991
3529
|
warnings.push(`integration "${integration.id}" has no rules for "${ns}"`);
|
|
2992
3530
|
continue;
|
|
2993
3531
|
}
|
|
2994
|
-
for (const
|
|
2995
|
-
if (rule[
|
|
3532
|
+
for (const action2 of ["view", "create", "update", "delete"]) {
|
|
3533
|
+
if (rule[action2] === void 0) warnings.push(`integration "${integration.id}" rule "${ns}.${action2}" is missing`);
|
|
2996
3534
|
}
|
|
2997
3535
|
}
|
|
2998
3536
|
for (const seed of integration.seeds ?? []) {
|
|
@@ -3045,27 +3583,27 @@ function isUniqueAttr(schema, ns, attr) {
|
|
|
3045
3583
|
const definition = entity.attrs[attr];
|
|
3046
3584
|
return isRecord6(definition) && definition.unique === true;
|
|
3047
3585
|
}
|
|
3048
|
-
function normalizeSchema(
|
|
3049
|
-
if (
|
|
3050
|
-
if (!isRecord6(
|
|
3586
|
+
function normalizeSchema(value2) {
|
|
3587
|
+
if (value2 === void 0 || value2 === null) return { entities: {}, links: {} };
|
|
3588
|
+
if (!isRecord6(value2) || !isRecord6(value2.entities)) {
|
|
3051
3589
|
throw new Error("db schema must be a serialized schema object with an entities map");
|
|
3052
3590
|
}
|
|
3053
|
-
if (
|
|
3591
|
+
if (value2.links !== void 0 && !isRecord6(value2.links)) throw new Error("db schema links must be an object");
|
|
3054
3592
|
return {
|
|
3055
|
-
entities: { ...
|
|
3056
|
-
links: { ...
|
|
3593
|
+
entities: { ...value2.entities },
|
|
3594
|
+
links: { ...value2.links ?? {} }
|
|
3057
3595
|
};
|
|
3058
3596
|
}
|
|
3059
3597
|
function mergeMap(target, fragment, label) {
|
|
3060
|
-
for (const [name,
|
|
3061
|
-
if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name],
|
|
3598
|
+
for (const [name, value2] of Object.entries(fragment)) {
|
|
3599
|
+
if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value2)) {
|
|
3062
3600
|
throw new Error(`${label} "${name}" conflicts with an existing definition`);
|
|
3063
3601
|
}
|
|
3064
|
-
target[name] =
|
|
3602
|
+
target[name] = value2;
|
|
3065
3603
|
}
|
|
3066
3604
|
}
|
|
3067
|
-
function isRecord6(
|
|
3068
|
-
return
|
|
3605
|
+
function isRecord6(value2) {
|
|
3606
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
3069
3607
|
}
|
|
3070
3608
|
|
|
3071
3609
|
// src/doctor.ts
|
|
@@ -3106,9 +3644,9 @@ async function doctor(options) {
|
|
|
3106
3644
|
warnings.push(...integrationWarnings(database.integrations, schema, rules));
|
|
3107
3645
|
if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
|
|
3108
3646
|
if (cfg.auth?.clerk) {
|
|
3109
|
-
for (const [env,
|
|
3110
|
-
if (typeof
|
|
3111
|
-
warnings.push(`auth.clerk.${env} references unset env var ${
|
|
3647
|
+
for (const [env, value2] of Object.entries(cfg.auth.clerk)) {
|
|
3648
|
+
if (typeof value2 === "string" && value2.startsWith("$") && !process.env[value2.slice(1)]) {
|
|
3649
|
+
warnings.push(`auth.clerk.${env} references unset env var ${value2}`);
|
|
3112
3650
|
}
|
|
3113
3651
|
}
|
|
3114
3652
|
}
|
|
@@ -3150,10 +3688,10 @@ function harnessList(parsed) {
|
|
|
3150
3688
|
];
|
|
3151
3689
|
return selected.length ? selected : ["all"];
|
|
3152
3690
|
}
|
|
3153
|
-
function harnessOption(
|
|
3154
|
-
if (
|
|
3155
|
-
if (typeof
|
|
3156
|
-
const raw = Array.isArray(
|
|
3691
|
+
function harnessOption(value2, flag) {
|
|
3692
|
+
if (value2 === void 0) return [];
|
|
3693
|
+
if (typeof value2 === "boolean") throw new Error(`${flag} requires a harness name`);
|
|
3694
|
+
const raw = Array.isArray(value2) ? value2 : [value2];
|
|
3157
3695
|
const parts = raw.flatMap((item) => item.split(","));
|
|
3158
3696
|
if (parts.some((item) => !item.trim())) {
|
|
3159
3697
|
throw new Error(`${flag} requires a harness name`);
|
|
@@ -3163,12 +3701,12 @@ function harnessOption(value, flag) {
|
|
|
3163
3701
|
|
|
3164
3702
|
// src/init.ts
|
|
3165
3703
|
var import_node_fs11 = require("fs");
|
|
3166
|
-
var
|
|
3167
|
-
var
|
|
3704
|
+
var import_node_path10 = require("path");
|
|
3705
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
3168
3706
|
function initProject(options) {
|
|
3169
3707
|
const out = options.stdout ?? console;
|
|
3170
|
-
const rootDir = (0,
|
|
3171
|
-
const configPath = (0,
|
|
3708
|
+
const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
|
|
3709
|
+
const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
3172
3710
|
if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
|
|
3173
3711
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3174
3712
|
}
|
|
@@ -3178,19 +3716,19 @@ function initProject(options) {
|
|
|
3178
3716
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3179
3717
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3180
3718
|
for (const service of services) {
|
|
3181
|
-
const definition = (0,
|
|
3182
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
3719
|
+
const definition = (0, import_apps7.appServiceDefinition)(service);
|
|
3720
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
|
|
3183
3721
|
for (const dependency of definition.requires) {
|
|
3184
3722
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3185
3723
|
}
|
|
3186
3724
|
}
|
|
3187
3725
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3188
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3189
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3190
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
3726
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
|
|
3727
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
3728
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
|
|
3191
3729
|
(0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
3192
|
-
writeIfMissing((0,
|
|
3193
|
-
writeIfMissing((0,
|
|
3730
|
+
writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
3731
|
+
writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3194
3732
|
ensureGitignore(rootDir);
|
|
3195
3733
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3196
3734
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
@@ -3372,8 +3910,8 @@ function assertWranglerConfig(cfg) {
|
|
|
3372
3910
|
}
|
|
3373
3911
|
|
|
3374
3912
|
// src/secrets-set.ts
|
|
3375
|
-
var
|
|
3376
|
-
var
|
|
3913
|
+
var import_ai2 = require("@odla-ai/ai");
|
|
3914
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3377
3915
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3378
3916
|
async function secretsSet(options) {
|
|
3379
3917
|
const name = (options.name ?? "").trim();
|
|
@@ -3381,38 +3919,38 @@ async function secretsSet(options) {
|
|
|
3381
3919
|
if (name.startsWith("$")) {
|
|
3382
3920
|
throw new Error('"$"-prefixed vault names are platform-reserved; for the Clerk secret key use "odla-ai secrets set-clerk-key"');
|
|
3383
3921
|
}
|
|
3384
|
-
const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
|
|
3922
|
+
const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
|
|
3385
3923
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
3386
3924
|
try {
|
|
3387
|
-
await (0,
|
|
3925
|
+
await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
3388
3926
|
} catch (err) {
|
|
3389
|
-
throw new Error(scrubValue(err instanceof Error ? err.message : String(err),
|
|
3927
|
+
throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
|
|
3390
3928
|
}
|
|
3391
3929
|
out.log(`${name} stored in the ${tenantId} vault (write-only; the value was never echoed and cannot be read back here)`);
|
|
3392
3930
|
}
|
|
3393
3931
|
async function secretsSetClerkKey(options) {
|
|
3394
|
-
const { cfg, tenantId, value, doFetch, out } = await resolveVaultWrite(options);
|
|
3395
|
-
if (!
|
|
3396
|
-
if (
|
|
3932
|
+
const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
|
|
3933
|
+
if (!value2.startsWith("sk_")) throw new Error("the Clerk secret key must start with sk_ (sk_test_\u2026 or sk_live_\u2026)");
|
|
3934
|
+
if (value2.startsWith("sk_test_") && PROD_ENV_NAMES2.has(options.env)) {
|
|
3397
3935
|
throw new Error(`refusing to store an sk_test_ Clerk key for "${options.env}" \u2014 use the production instance's sk_live_ key`);
|
|
3398
3936
|
}
|
|
3399
|
-
if (
|
|
3937
|
+
if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
3400
3938
|
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)`);
|
|
3401
3939
|
}
|
|
3402
3940
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
3403
3941
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
|
|
3404
3942
|
method: "POST",
|
|
3405
3943
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
3406
|
-
body: JSON.stringify({ value })
|
|
3944
|
+
body: JSON.stringify({ value: value2 })
|
|
3407
3945
|
});
|
|
3408
3946
|
if (!res.ok) {
|
|
3409
|
-
const text = scrubValue((await res.text().catch(() => "")).slice(0, 300),
|
|
3947
|
+
const text = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
3410
3948
|
throw new Error(`store Clerk secret key failed (${res.status}): ${text || "request failed"}`);
|
|
3411
3949
|
}
|
|
3412
3950
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
3413
3951
|
}
|
|
3414
|
-
function scrubValue(text,
|
|
3415
|
-
return redactSecrets(text).split(
|
|
3952
|
+
function scrubValue(text, value2) {
|
|
3953
|
+
return redactSecrets(text).split(value2).join("[value redacted]");
|
|
3416
3954
|
}
|
|
3417
3955
|
async function resolveVaultWrite(options) {
|
|
3418
3956
|
const out = options.stdout ?? console;
|
|
@@ -3424,14 +3962,14 @@ async function resolveVaultWrite(options) {
|
|
|
3424
3962
|
if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
3425
3963
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3426
3964
|
}
|
|
3427
|
-
const
|
|
3428
|
-
return { cfg, tenantId: (0,
|
|
3965
|
+
const value2 = await secretInputValue(options, "secret");
|
|
3966
|
+
return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3429
3967
|
}
|
|
3430
3968
|
|
|
3431
3969
|
// src/skill.ts
|
|
3432
3970
|
var import_node_fs12 = require("fs");
|
|
3433
3971
|
var import_node_os2 = require("os");
|
|
3434
|
-
var
|
|
3972
|
+
var import_node_path11 = require("path");
|
|
3435
3973
|
var import_node_url2 = require("url");
|
|
3436
3974
|
|
|
3437
3975
|
// src/skill-adapters.ts
|
|
@@ -3510,8 +4048,8 @@ function installSkill(options = {}) {
|
|
|
3510
4048
|
const files = listFiles(sourceDir);
|
|
3511
4049
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
3512
4050
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
3513
|
-
const root = (0,
|
|
3514
|
-
const home = (0,
|
|
4051
|
+
const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
|
|
4052
|
+
const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
3515
4053
|
const plans = /* @__PURE__ */ new Map();
|
|
3516
4054
|
const targets = /* @__PURE__ */ new Map();
|
|
3517
4055
|
const rememberTarget = (harness, target) => {
|
|
@@ -3525,48 +4063,48 @@ function installSkill(options = {}) {
|
|
|
3525
4063
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
3526
4064
|
};
|
|
3527
4065
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
3528
|
-
for (const rel of files) plan((0,
|
|
4066
|
+
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);
|
|
3529
4067
|
};
|
|
3530
4068
|
let targetDir;
|
|
3531
4069
|
if (options.global) {
|
|
3532
|
-
const claudeRoot = (0,
|
|
3533
|
-
const codexRoot = (0,
|
|
4070
|
+
const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
|
|
4071
|
+
const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
|
|
3534
4072
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
3535
4073
|
for (const harness of harnesses) {
|
|
3536
4074
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
3537
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4075
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
|
|
3538
4076
|
rememberTarget(harness, skillRoot);
|
|
3539
4077
|
}
|
|
3540
4078
|
} else {
|
|
3541
|
-
const sharedRoot = (0,
|
|
4079
|
+
const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
|
|
3542
4080
|
planSkillTree(sharedRoot);
|
|
3543
|
-
const claudeRoot = (0,
|
|
4081
|
+
const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
|
|
3544
4082
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
3545
4083
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
3546
4084
|
if (harnesses.includes("claude")) {
|
|
3547
4085
|
for (const skill of skillNames(files)) {
|
|
3548
|
-
const canonical = (0, import_node_fs12.readFileSync)((0,
|
|
3549
|
-
plan((0,
|
|
4086
|
+
const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4087
|
+
plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
3550
4088
|
}
|
|
3551
4089
|
rememberTarget("claude", claudeRoot);
|
|
3552
4090
|
}
|
|
3553
4091
|
if (harnesses.includes("cursor")) {
|
|
3554
|
-
const cursorRule = (0,
|
|
4092
|
+
const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
|
|
3555
4093
|
plan(cursorRule, CURSOR_RULE);
|
|
3556
4094
|
rememberTarget("cursor", cursorRule);
|
|
3557
4095
|
}
|
|
3558
4096
|
if (harnesses.includes("agents")) {
|
|
3559
|
-
const agentsFile = (0,
|
|
4097
|
+
const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
|
|
3560
4098
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3561
4099
|
rememberTarget("agents", agentsFile);
|
|
3562
4100
|
}
|
|
3563
4101
|
if (harnesses.includes("copilot")) {
|
|
3564
|
-
const copilotFile = (0,
|
|
4102
|
+
const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
|
|
3565
4103
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3566
4104
|
rememberTarget("copilot", copilotFile);
|
|
3567
4105
|
}
|
|
3568
4106
|
if (harnesses.includes("gemini")) {
|
|
3569
|
-
const geminiFile = (0,
|
|
4107
|
+
const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
|
|
3570
4108
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3571
4109
|
rememberTarget("gemini", geminiFile);
|
|
3572
4110
|
}
|
|
@@ -3602,7 +4140,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3602
4140
|
}
|
|
3603
4141
|
for (const file of plans.values()) {
|
|
3604
4142
|
if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
|
|
3605
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
4143
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
|
|
3606
4144
|
(0, import_node_fs12.writeFileSync)(file.target, file.content);
|
|
3607
4145
|
}
|
|
3608
4146
|
}
|
|
@@ -3622,7 +4160,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3622
4160
|
};
|
|
3623
4161
|
}
|
|
3624
4162
|
function pathsUnder(root, paths) {
|
|
3625
|
-
return [...paths].map((path) => (0,
|
|
4163
|
+
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();
|
|
3626
4164
|
}
|
|
3627
4165
|
function normalizeHarnesses(values, global) {
|
|
3628
4166
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -3667,13 +4205,13 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
3667
4205
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
3668
4206
|
}
|
|
3669
4207
|
function symlinkedComponent(boundary, target) {
|
|
3670
|
-
const rel = (0,
|
|
3671
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4208
|
+
const rel = (0, import_node_path11.relative)(boundary, target);
|
|
4209
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
|
|
3672
4210
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
3673
4211
|
}
|
|
3674
4212
|
let current = boundary;
|
|
3675
|
-
for (const part of rel.split(
|
|
3676
|
-
current = (0,
|
|
4213
|
+
for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
|
|
4214
|
+
current = (0, import_node_path11.join)(current, part);
|
|
3677
4215
|
try {
|
|
3678
4216
|
if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
|
|
3679
4217
|
} catch (error) {
|
|
@@ -3690,9 +4228,9 @@ function listFiles(dir) {
|
|
|
3690
4228
|
const results = [];
|
|
3691
4229
|
const walk = (current) => {
|
|
3692
4230
|
for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
|
|
3693
|
-
const path = (0,
|
|
4231
|
+
const path = (0, import_node_path11.join)(current, entry.name);
|
|
3694
4232
|
if (entry.isDirectory()) walk(path);
|
|
3695
|
-
else results.push((0,
|
|
4233
|
+
else results.push((0, import_node_path11.relative)(dir, path));
|
|
3696
4234
|
}
|
|
3697
4235
|
};
|
|
3698
4236
|
walk(dir);
|
|
@@ -3768,7 +4306,7 @@ async function smoke(options) {
|
|
|
3768
4306
|
out.log(` schema: ${liveEntities.length} entities`);
|
|
3769
4307
|
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
3770
4308
|
if (aggregateEntity) {
|
|
3771
|
-
const aggregate = await
|
|
4309
|
+
const aggregate = await postJson2(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
|
|
3772
4310
|
ns: aggregateEntity,
|
|
3773
4311
|
aggregate: { count: true }
|
|
3774
4312
|
});
|
|
@@ -3812,16 +4350,16 @@ async function getJson(doFetch, url, bearer) {
|
|
|
3812
4350
|
const res = await doFetch(url, {
|
|
3813
4351
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
3814
4352
|
});
|
|
3815
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4353
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
|
|
3816
4354
|
return res.json();
|
|
3817
4355
|
}
|
|
3818
|
-
async function
|
|
4356
|
+
async function postJson2(doFetch, url, bearer, body) {
|
|
3819
4357
|
const res = await doFetch(url, {
|
|
3820
4358
|
method: "POST",
|
|
3821
4359
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
3822
4360
|
body: JSON.stringify(body)
|
|
3823
4361
|
});
|
|
3824
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4362
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText4(res)}`);
|
|
3825
4363
|
return res.json();
|
|
3826
4364
|
}
|
|
3827
4365
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -3829,7 +4367,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
3829
4367
|
url.searchParams.set("env", env);
|
|
3830
4368
|
return url.toString();
|
|
3831
4369
|
}
|
|
3832
|
-
async function
|
|
4370
|
+
async function safeText4(res) {
|
|
3833
4371
|
try {
|
|
3834
4372
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
3835
4373
|
} catch {
|
|
@@ -3885,6 +4423,26 @@ async function secretsCommand(parsed, deps) {
|
|
|
3885
4423
|
});
|
|
3886
4424
|
}
|
|
3887
4425
|
async function projectCommand(command, parsed, deps) {
|
|
4426
|
+
if (command === "config") {
|
|
4427
|
+
const sub = parsed.positionals[1];
|
|
4428
|
+
if (sub !== "diff" && sub !== "plan") {
|
|
4429
|
+
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4430
|
+
}
|
|
4431
|
+
assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
|
|
4432
|
+
const options = {
|
|
4433
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4434
|
+
token: stringOpt(parsed.options.token),
|
|
4435
|
+
email: stringOpt(parsed.options.email),
|
|
4436
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4437
|
+
json: parsed.options.json === true,
|
|
4438
|
+
fetch: deps.fetch,
|
|
4439
|
+
openApprovalUrl: deps.openUrl,
|
|
4440
|
+
stdout: deps.stdout
|
|
4441
|
+
};
|
|
4442
|
+
if (sub === "diff") await configDiff(options);
|
|
4443
|
+
else await configPlan(options);
|
|
4444
|
+
return true;
|
|
4445
|
+
}
|
|
3888
4446
|
if (command === "init") {
|
|
3889
4447
|
assertArgs(parsed, ["app-id", "name", "config", "env", "services", "ai-provider", "force"], 1);
|
|
3890
4448
|
initProject({
|
|
@@ -3945,7 +4503,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
3945
4503
|
// src/code-connect.ts
|
|
3946
4504
|
var import_node_fs14 = require("fs");
|
|
3947
4505
|
var import_node_os4 = require("os");
|
|
3948
|
-
var
|
|
4506
|
+
var import_node_path13 = require("path");
|
|
3949
4507
|
|
|
3950
4508
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
3951
4509
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -3955,24 +4513,24 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
3955
4513
|
var HarnessProtocolError = class extends Error {
|
|
3956
4514
|
name = "HarnessProtocolError";
|
|
3957
4515
|
};
|
|
3958
|
-
function record2(
|
|
3959
|
-
return
|
|
4516
|
+
function record2(value2) {
|
|
4517
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
3960
4518
|
}
|
|
3961
|
-
function boundedText(
|
|
3962
|
-
if (typeof
|
|
4519
|
+
function boundedText(value2, label, max) {
|
|
4520
|
+
if (typeof value2 !== "string" || !value2 || value2.length > max || CONTROL.test(value2)) {
|
|
3963
4521
|
throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
|
|
3964
4522
|
}
|
|
3965
|
-
return
|
|
4523
|
+
return value2;
|
|
3966
4524
|
}
|
|
3967
4525
|
function parseAgentOutput(line) {
|
|
3968
4526
|
if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
|
|
3969
|
-
let
|
|
4527
|
+
let value2;
|
|
3970
4528
|
try {
|
|
3971
|
-
|
|
4529
|
+
value2 = JSON.parse(line);
|
|
3972
4530
|
} catch {
|
|
3973
4531
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
3974
4532
|
}
|
|
3975
|
-
const message2 = record2(
|
|
4533
|
+
const message2 = record2(value2);
|
|
3976
4534
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
3977
4535
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
3978
4536
|
}
|
|
@@ -4281,7 +4839,7 @@ async function gitOutput(cwd, args, maxBytes) {
|
|
|
4281
4839
|
else stdout.push(chunk);
|
|
4282
4840
|
});
|
|
4283
4841
|
child.stderr.on("data", (chunk) => {
|
|
4284
|
-
if (stderr.reduce((sum,
|
|
4842
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4285
4843
|
});
|
|
4286
4844
|
const code = await new Promise((accept, reject) => {
|
|
4287
4845
|
child.once("error", reject);
|
|
@@ -4302,7 +4860,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
4302
4860
|
else stdout.push(chunk);
|
|
4303
4861
|
});
|
|
4304
4862
|
child.stderr.on("data", (chunk) => {
|
|
4305
|
-
if (stderr.reduce((sum,
|
|
4863
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4306
4864
|
});
|
|
4307
4865
|
child.stdin.end(`${entries.map((entry) => entry.hash).join("\n")}
|
|
4308
4866
|
`);
|
|
@@ -4340,8 +4898,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4340
4898
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4341
4899
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4342
4900
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4343
|
-
const entries = inventory.flatMap((
|
|
4344
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
4901
|
+
const entries = inventory.flatMap((record8) => {
|
|
4902
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
|
|
4345
4903
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4346
4904
|
});
|
|
4347
4905
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -4416,7 +4974,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
4416
4974
|
else stdout.push(chunk);
|
|
4417
4975
|
});
|
|
4418
4976
|
child.stderr.on("data", (chunk) => {
|
|
4419
|
-
if (stderr.reduce((sum,
|
|
4977
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4420
4978
|
});
|
|
4421
4979
|
const code = await new Promise((accept, reject) => {
|
|
4422
4980
|
child.once("error", reject);
|
|
@@ -4476,7 +5034,7 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
4476
5034
|
else stdout.push(chunk);
|
|
4477
5035
|
});
|
|
4478
5036
|
child.stderr.on("data", (chunk) => {
|
|
4479
|
-
if (stderr.reduce((sum,
|
|
5037
|
+
if (stderr.reduce((sum, value2) => sum + value2.byteLength, 0) < 16384) stderr.push(chunk);
|
|
4480
5038
|
});
|
|
4481
5039
|
const code = await new Promise((accept, reject) => {
|
|
4482
5040
|
child.once("error", reject);
|
|
@@ -4563,34 +5121,34 @@ var CamelError = class extends Error {
|
|
|
4563
5121
|
};
|
|
4564
5122
|
|
|
4565
5123
|
// ../camel/dist/chunk-L5DYU2E2.js
|
|
4566
|
-
function
|
|
4567
|
-
return JSON.stringify(normalize(
|
|
5124
|
+
function canonicalJson2(value2) {
|
|
5125
|
+
return JSON.stringify(normalize(value2));
|
|
4568
5126
|
}
|
|
4569
|
-
async function sha256Hex(
|
|
4570
|
-
const bytes = typeof
|
|
5127
|
+
async function sha256Hex(value2) {
|
|
5128
|
+
const bytes = typeof value2 === "string" ? new TextEncoder().encode(value2) : value2;
|
|
4571
5129
|
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
|
|
4572
5130
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4573
5131
|
}
|
|
4574
|
-
function utf8Length(
|
|
4575
|
-
if (typeof
|
|
4576
|
-
if (
|
|
5132
|
+
function utf8Length(value2) {
|
|
5133
|
+
if (typeof value2 === "string") return new TextEncoder().encode(value2).byteLength;
|
|
5134
|
+
if (value2 instanceof Uint8Array) return value2.byteLength;
|
|
4577
5135
|
try {
|
|
4578
|
-
return new TextEncoder().encode(
|
|
5136
|
+
return new TextEncoder().encode(canonicalJson2(value2)).byteLength;
|
|
4579
5137
|
} catch {
|
|
4580
5138
|
throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
|
|
4581
5139
|
}
|
|
4582
5140
|
}
|
|
4583
|
-
function normalize(
|
|
4584
|
-
if (
|
|
4585
|
-
if (typeof
|
|
4586
|
-
if (!Number.isFinite(
|
|
4587
|
-
return
|
|
5141
|
+
function normalize(value2) {
|
|
5142
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
5143
|
+
if (typeof value2 === "number") {
|
|
5144
|
+
if (!Number.isFinite(value2)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
|
|
5145
|
+
return value2;
|
|
4588
5146
|
}
|
|
4589
|
-
if (Array.isArray(
|
|
4590
|
-
if (
|
|
4591
|
-
if (typeof
|
|
4592
|
-
const
|
|
4593
|
-
return Object.fromEntries(Object.keys(
|
|
5147
|
+
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5148
|
+
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5149
|
+
if (typeof value2 === "object") {
|
|
5150
|
+
const record8 = value2;
|
|
5151
|
+
return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
|
|
4594
5152
|
}
|
|
4595
5153
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
4596
5154
|
}
|
|
@@ -4599,10 +5157,10 @@ function canRead(readers, readerId) {
|
|
|
4599
5157
|
}
|
|
4600
5158
|
function dependenciesOf(values, influence = "data") {
|
|
4601
5159
|
const result = [];
|
|
4602
|
-
for (const
|
|
4603
|
-
result.push(...
|
|
4604
|
-
for (const ref of
|
|
4605
|
-
result.push({ ref, influence, promptSafetyAtUse:
|
|
5160
|
+
for (const value2 of values) {
|
|
5161
|
+
result.push(...value2.label.dependencies);
|
|
5162
|
+
for (const ref of value2.label.provenance) {
|
|
5163
|
+
result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
|
|
4606
5164
|
}
|
|
4607
5165
|
}
|
|
4608
5166
|
const unique3 = /* @__PURE__ */ new Map();
|
|
@@ -4613,32 +5171,32 @@ function dependenciesOf(values, influence = "data") {
|
|
|
4613
5171
|
// ../camel/dist/chunk-S7EVNA2U.js
|
|
4614
5172
|
var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
|
|
4615
5173
|
var authenticCamelValues = /* @__PURE__ */ new WeakSet();
|
|
4616
|
-
function isCamelValue(
|
|
4617
|
-
return typeof
|
|
5174
|
+
function isCamelValue(value2) {
|
|
5175
|
+
return typeof value2 === "object" && value2 !== null && authenticCamelValues.has(value2);
|
|
4618
5176
|
}
|
|
4619
|
-
function isSafe(
|
|
4620
|
-
return isCamelValue(
|
|
5177
|
+
function isSafe(value2) {
|
|
5178
|
+
return isCamelValue(value2) && value2.label.promptSafety === "safe";
|
|
4621
5179
|
}
|
|
4622
|
-
function isUnsafe(
|
|
4623
|
-
return isCamelValue(
|
|
5180
|
+
function isUnsafe(value2) {
|
|
5181
|
+
return isCamelValue(value2) && value2.label.promptSafety === "unsafe";
|
|
4624
5182
|
}
|
|
4625
|
-
function createSafeInternal(
|
|
4626
|
-
return createValue(
|
|
5183
|
+
function createSafeInternal(value2, safeBasis, metadata2) {
|
|
5184
|
+
return createValue(value2, {
|
|
4627
5185
|
schemaVersion: 1,
|
|
4628
5186
|
promptSafety: "safe",
|
|
4629
5187
|
safeBasis,
|
|
4630
5188
|
...copyMetadata(metadata2)
|
|
4631
5189
|
});
|
|
4632
5190
|
}
|
|
4633
|
-
function createUnsafeInternal(
|
|
4634
|
-
return createValue(
|
|
5191
|
+
function createUnsafeInternal(value2, metadata2) {
|
|
5192
|
+
return createValue(value2, {
|
|
4635
5193
|
schemaVersion: 1,
|
|
4636
5194
|
promptSafety: "unsafe",
|
|
4637
5195
|
...copyMetadata(metadata2)
|
|
4638
5196
|
});
|
|
4639
5197
|
}
|
|
4640
|
-
function createValue(
|
|
4641
|
-
const result = { value, label: Object.freeze(label) };
|
|
5198
|
+
function createValue(value2, label) {
|
|
5199
|
+
const result = { value: value2, label: Object.freeze(label) };
|
|
4642
5200
|
Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
|
|
4643
5201
|
authenticCamelValues.add(result);
|
|
4644
5202
|
return Object.freeze(result);
|
|
@@ -4701,7 +5259,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
4701
5259
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
4702
5260
|
outcome: fields.outcome
|
|
4703
5261
|
};
|
|
4704
|
-
return `sha256:${await sha256Hex(
|
|
5262
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
4705
5263
|
}
|
|
4706
5264
|
function validate(fields) {
|
|
4707
5265
|
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) {
|
|
@@ -4739,13 +5297,13 @@ async function createCodePortableCheckpoint(input) {
|
|
|
4739
5297
|
const state2 = normalizeState(input.state);
|
|
4740
5298
|
validateBaseAndPatch(input.baseCommitSha, input.patch);
|
|
4741
5299
|
const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
|
|
4742
|
-
const stateDigest = `sha256:${await sha256Hex(
|
|
5300
|
+
const stateDigest = `sha256:${await sha256Hex(canonicalJson2(state2))}`;
|
|
4743
5301
|
const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state: state2, stateDigest };
|
|
4744
|
-
const checkpointDigest = `sha256:${await sha256Hex(
|
|
5302
|
+
const checkpointDigest = `sha256:${await sha256Hex(canonicalJson2(fields))}`;
|
|
4745
5303
|
return freeze({ ...fields, patch: input.patch, checkpointDigest });
|
|
4746
5304
|
}
|
|
4747
|
-
async function verifyCodePortableCheckpoint(
|
|
4748
|
-
const input = object(
|
|
5305
|
+
async function verifyCodePortableCheckpoint(value2) {
|
|
5306
|
+
const input = object(value2, "checkpoint");
|
|
4749
5307
|
exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
|
|
4750
5308
|
if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
|
|
4751
5309
|
throw invalid2("Portable checkpoint fields are malformed.");
|
|
@@ -4760,8 +5318,8 @@ async function verifyCodePortableCheckpoint(value) {
|
|
|
4760
5318
|
}
|
|
4761
5319
|
return created;
|
|
4762
5320
|
}
|
|
4763
|
-
function normalizeState(
|
|
4764
|
-
const state2 = object(
|
|
5321
|
+
function normalizeState(value2) {
|
|
5322
|
+
const state2 = object(value2, "state");
|
|
4765
5323
|
exact2(state2, [
|
|
4766
5324
|
"planCursor",
|
|
4767
5325
|
"conversationRefs",
|
|
@@ -4819,30 +5377,30 @@ function validateBaseAndPatch(baseCommitSha, patch2) {
|
|
|
4819
5377
|
throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
|
|
4820
5378
|
}
|
|
4821
5379
|
}
|
|
4822
|
-
function strings(
|
|
4823
|
-
if (!Array.isArray(
|
|
5380
|
+
function strings(value2, name, pattern, maximum, sort) {
|
|
5381
|
+
if (!Array.isArray(value2) || value2.length > maximum || value2.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value2).size !== value2.length) {
|
|
4824
5382
|
throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
|
|
4825
5383
|
}
|
|
4826
|
-
const result = [...
|
|
5384
|
+
const result = [...value2];
|
|
4827
5385
|
return sort ? result.sort() : result;
|
|
4828
5386
|
}
|
|
4829
|
-
function object(
|
|
4830
|
-
if (!
|
|
4831
|
-
return
|
|
5387
|
+
function object(value2, name) {
|
|
5388
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw invalid2(`Portable ${name} must be an object.`);
|
|
5389
|
+
return value2;
|
|
4832
5390
|
}
|
|
4833
|
-
function exact2(
|
|
4834
|
-
if (Object.keys(
|
|
5391
|
+
function exact2(value2, keys) {
|
|
5392
|
+
if (Object.keys(value2).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value2))) {
|
|
4835
5393
|
throw invalid2("Portable checkpoint contains missing or unsupported fields.");
|
|
4836
5394
|
}
|
|
4837
5395
|
}
|
|
4838
|
-
function freeze(
|
|
5396
|
+
function freeze(value2) {
|
|
4839
5397
|
return Object.freeze({
|
|
4840
|
-
...
|
|
5398
|
+
...value2,
|
|
4841
5399
|
state: Object.freeze({
|
|
4842
|
-
...
|
|
4843
|
-
conversationRefs: Object.freeze([...
|
|
4844
|
-
completedEffects: Object.freeze(
|
|
4845
|
-
unresolvedApprovals: Object.freeze([...
|
|
5400
|
+
...value2.state,
|
|
5401
|
+
conversationRefs: Object.freeze([...value2.state.conversationRefs]),
|
|
5402
|
+
completedEffects: Object.freeze(value2.state.completedEffects.map((item) => Object.freeze({ ...item }))),
|
|
5403
|
+
unresolvedApprovals: Object.freeze([...value2.state.unresolvedApprovals])
|
|
4846
5404
|
})
|
|
4847
5405
|
});
|
|
4848
5406
|
}
|
|
@@ -4859,7 +5417,7 @@ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
|
|
|
4859
5417
|
commitSha: snapshot.commitSha,
|
|
4860
5418
|
files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
|
|
4861
5419
|
};
|
|
4862
|
-
return `sha256:${await sha256Hex(
|
|
5420
|
+
return `sha256:${await sha256Hex(canonicalJson2(normalized))}`;
|
|
4863
5421
|
}
|
|
4864
5422
|
function validateSnapshot(snapshot, limits) {
|
|
4865
5423
|
if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
|
|
@@ -4901,10 +5459,10 @@ var import_path9 = require("path");
|
|
|
4901
5459
|
|
|
4902
5460
|
// ../camel/dist/chunk-LAXU2AVK.js
|
|
4903
5461
|
function conversionPolicyDigest(policy) {
|
|
4904
|
-
return sha256Hex(
|
|
5462
|
+
return sha256Hex(canonicalJson2(policy));
|
|
4905
5463
|
}
|
|
4906
5464
|
function registeredIdRegistryDigest(values) {
|
|
4907
|
-
return sha256Hex(
|
|
5465
|
+
return sha256Hex(canonicalJson2(values));
|
|
4908
5466
|
}
|
|
4909
5467
|
async function createConversionRegistry(config) {
|
|
4910
5468
|
const policies = /* @__PURE__ */ new Map();
|
|
@@ -4933,61 +5491,61 @@ async function createConversionRegistry(config) {
|
|
|
4933
5491
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
4934
5492
|
return policy;
|
|
4935
5493
|
};
|
|
4936
|
-
const emit3 = (source, policy,
|
|
5494
|
+
const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
4937
5495
|
const operations = Object.freeze({
|
|
4938
|
-
boolean: async (
|
|
4939
|
-
const policy = checked(
|
|
4940
|
-
return emit3(
|
|
5496
|
+
boolean: async (value2, id) => {
|
|
5497
|
+
const policy = checked(value2, id, "boolean");
|
|
5498
|
+
return emit3(value2, policy, requireBoolean(value2.value));
|
|
4941
5499
|
},
|
|
4942
|
-
integer: async (
|
|
4943
|
-
const policy = checked(
|
|
4944
|
-
return emit3(
|
|
5500
|
+
integer: async (value2, id) => {
|
|
5501
|
+
const policy = checked(value2, id, "integer");
|
|
5502
|
+
return emit3(value2, policy, boundedInteger(value2.value, policy.output));
|
|
4945
5503
|
},
|
|
4946
|
-
finiteNumber: async (
|
|
4947
|
-
const policy = checked(
|
|
4948
|
-
return emit3(
|
|
5504
|
+
finiteNumber: async (value2, id) => {
|
|
5505
|
+
const policy = checked(value2, id, "finite_number");
|
|
5506
|
+
return emit3(value2, policy, boundedNumber(value2.value, policy.output));
|
|
4949
5507
|
},
|
|
4950
|
-
enum: async (
|
|
4951
|
-
const policy = checked(
|
|
4952
|
-
return emit3(
|
|
5508
|
+
enum: async (value2, id) => {
|
|
5509
|
+
const policy = checked(value2, id, "enum");
|
|
5510
|
+
return emit3(value2, policy, enumMember(value2.value, policy.output));
|
|
4953
5511
|
},
|
|
4954
|
-
date: async (
|
|
4955
|
-
const policy = checked(
|
|
4956
|
-
return emit3(
|
|
5512
|
+
date: async (value2, id) => {
|
|
5513
|
+
const policy = checked(value2, id, "date");
|
|
5514
|
+
return emit3(value2, policy, canonicalDate(value2.value, policy.output));
|
|
4957
5515
|
},
|
|
4958
|
-
registeredId: async (
|
|
4959
|
-
const policy = checked(
|
|
5516
|
+
registeredId: async (value2, id) => {
|
|
5517
|
+
const policy = checked(value2, id, "registered_id");
|
|
4960
5518
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
4961
|
-
const output = typeof
|
|
5519
|
+
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
4962
5520
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
4963
|
-
return emit3(
|
|
5521
|
+
return emit3(value2, policy, output);
|
|
4964
5522
|
},
|
|
4965
|
-
digest: async (
|
|
4966
|
-
const policy = checked(
|
|
4967
|
-
if (!(
|
|
4968
|
-
return emit3(
|
|
5523
|
+
digest: async (value2, id) => {
|
|
5524
|
+
const policy = checked(value2, id, "digest");
|
|
5525
|
+
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
5526
|
+
return emit3(value2, policy, await sha256Hex(value2.value));
|
|
4969
5527
|
},
|
|
4970
|
-
measure: async (
|
|
4971
|
-
const policy = checked(
|
|
4972
|
-
const measured = measure(
|
|
4973
|
-
return emit3(
|
|
5528
|
+
measure: async (value2, metric, id) => {
|
|
5529
|
+
const policy = checked(value2, id, "integer");
|
|
5530
|
+
const measured = measure(value2.value, metric);
|
|
5531
|
+
return emit3(value2, policy, boundedInteger(measured, policy.output));
|
|
4974
5532
|
},
|
|
4975
|
-
test: async (
|
|
4976
|
-
const policy = checked(
|
|
5533
|
+
test: async (value2, predicateId, id) => {
|
|
5534
|
+
const policy = checked(value2, id, "boolean");
|
|
4977
5535
|
const predicate = config.predicates?.[predicateId];
|
|
4978
5536
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
4979
|
-
return emit3(
|
|
5537
|
+
return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
4980
5538
|
}
|
|
4981
5539
|
});
|
|
4982
5540
|
return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
|
|
4983
5541
|
}
|
|
4984
|
-
async function convert(source, policy,
|
|
5542
|
+
async function convert(source, policy, value2, counts) {
|
|
4985
5543
|
const sourceKey = sourceIdentity(source);
|
|
4986
5544
|
const countKey = `${policy.conversionId}\0${sourceKey}`;
|
|
4987
5545
|
const count = counts.get(countKey) ?? 0;
|
|
4988
5546
|
if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
|
|
4989
5547
|
counts.set(countKey, count + 1);
|
|
4990
|
-
return createSafeInternal(
|
|
5548
|
+
return createSafeInternal(value2, "atomic_conversion", {
|
|
4991
5549
|
readers: source.label.readers,
|
|
4992
5550
|
provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
|
|
4993
5551
|
dependencies: dependenciesOf([source])
|
|
@@ -4999,49 +5557,49 @@ function validatePolicyShape(policy) {
|
|
|
4999
5557
|
const output = policy.output;
|
|
5000
5558
|
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.");
|
|
5001
5559
|
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.");
|
|
5002
|
-
if (output.kind === "enum" && (output.values.length === 0 || output.values.some((
|
|
5560
|
+
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.");
|
|
5003
5561
|
if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
|
|
5004
5562
|
if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
|
|
5005
5563
|
if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
|
|
5006
5564
|
}
|
|
5007
|
-
function requireBoolean(
|
|
5008
|
-
if (typeof
|
|
5009
|
-
return
|
|
5565
|
+
function requireBoolean(value2) {
|
|
5566
|
+
if (typeof value2 !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
|
|
5567
|
+
return value2;
|
|
5010
5568
|
}
|
|
5011
|
-
function boundedInteger(
|
|
5012
|
-
if (spec.kind !== "integer" || typeof
|
|
5013
|
-
return
|
|
5569
|
+
function boundedInteger(value2, spec) {
|
|
5570
|
+
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.");
|
|
5571
|
+
return value2;
|
|
5014
5572
|
}
|
|
5015
|
-
function boundedNumber(
|
|
5016
|
-
if (spec.kind !== "finite_number" || typeof
|
|
5017
|
-
const text = String(
|
|
5573
|
+
function boundedNumber(value2, spec) {
|
|
5574
|
+
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.");
|
|
5575
|
+
const text = String(value2);
|
|
5018
5576
|
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.");
|
|
5019
|
-
return
|
|
5577
|
+
return value2;
|
|
5020
5578
|
}
|
|
5021
|
-
function enumMember(
|
|
5022
|
-
if (spec.kind !== "enum" || typeof
|
|
5023
|
-
const member = spec.caseSensitive ? spec.values.find((item) => item ===
|
|
5579
|
+
function enumMember(value2, spec) {
|
|
5580
|
+
if (spec.kind !== "enum" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
|
|
5581
|
+
const member = spec.caseSensitive ? spec.values.find((item) => item === value2) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value2.toLocaleLowerCase("en-US"));
|
|
5024
5582
|
if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
|
|
5025
5583
|
return member;
|
|
5026
5584
|
}
|
|
5027
|
-
function canonicalDate(
|
|
5028
|
-
if (spec.kind !== "date" || typeof
|
|
5585
|
+
function canonicalDate(value2, spec) {
|
|
5586
|
+
if (spec.kind !== "date" || typeof value2 !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
|
|
5029
5587
|
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})$/;
|
|
5030
|
-
const instant = Date.parse(spec.format === "date" ? `${
|
|
5031
|
-
if (!pattern.test(
|
|
5032
|
-
if (spec.earliest &&
|
|
5033
|
-
return
|
|
5034
|
-
}
|
|
5035
|
-
function measure(
|
|
5036
|
-
if (metric === "byte_length" && (typeof
|
|
5037
|
-
if (metric === "codepoint_length" && typeof
|
|
5038
|
-
if (metric === "item_count" && Array.isArray(
|
|
5588
|
+
const instant = Date.parse(spec.format === "date" ? `${value2}T00:00:00Z` : value2);
|
|
5589
|
+
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.");
|
|
5590
|
+
if (spec.earliest && value2 < spec.earliest || spec.latest && value2 > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
|
|
5591
|
+
return value2;
|
|
5592
|
+
}
|
|
5593
|
+
function measure(value2, metric) {
|
|
5594
|
+
if (metric === "byte_length" && (typeof value2 === "string" || value2 instanceof Uint8Array)) return utf8Length(value2);
|
|
5595
|
+
if (metric === "codepoint_length" && typeof value2 === "string") return [...value2].length;
|
|
5596
|
+
if (metric === "item_count" && Array.isArray(value2)) return value2.length;
|
|
5039
5597
|
throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
|
|
5040
5598
|
}
|
|
5041
|
-
function evaluatePredicate(
|
|
5042
|
-
if (predicate.kind === "has_fields") return typeof
|
|
5043
|
-
if (predicate.kind === "registered_id") return typeof
|
|
5044
|
-
const actual =
|
|
5599
|
+
function evaluatePredicate(value2, predicate, registries) {
|
|
5600
|
+
if (predicate.kind === "has_fields") return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && predicate.fields.every((field) => Object.hasOwn(value2, field));
|
|
5601
|
+
if (predicate.kind === "registered_id") return typeof value2 === "string" && registries?.[predicate.registryId]?.values[value2] !== void 0;
|
|
5602
|
+
const actual = value2 === null ? "null" : Array.isArray(value2) ? "array" : typeof value2;
|
|
5045
5603
|
return actual === predicate.valueType;
|
|
5046
5604
|
}
|
|
5047
5605
|
function sourceIdentity(source) {
|
|
@@ -5064,17 +5622,17 @@ function createCamelIngress(constants2 = []) {
|
|
|
5064
5622
|
byId.set(item.id, item);
|
|
5065
5623
|
}
|
|
5066
5624
|
const ingress = {
|
|
5067
|
-
userInstruction: (
|
|
5068
|
-
systemPolicy: (
|
|
5625
|
+
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
5626
|
+
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
5069
5627
|
control: (id) => {
|
|
5070
5628
|
const item = byId.get(id);
|
|
5071
5629
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
5072
5630
|
return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
|
|
5073
5631
|
},
|
|
5074
|
-
external: (
|
|
5075
|
-
quarantinedOutput: (
|
|
5632
|
+
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
5633
|
+
quarantinedOutput: (value2, input) => {
|
|
5076
5634
|
if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
|
|
5077
|
-
return createUnsafeInternal(
|
|
5635
|
+
return createUnsafeInternal(value2, {
|
|
5078
5636
|
readers: input.readers,
|
|
5079
5637
|
dependencies: input.dependencies,
|
|
5080
5638
|
provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
|
|
@@ -5083,15 +5641,15 @@ function createCamelIngress(constants2 = []) {
|
|
|
5083
5641
|
};
|
|
5084
5642
|
return Object.freeze(ingress);
|
|
5085
5643
|
}
|
|
5086
|
-
function assertNoUnsafeConstant(
|
|
5087
|
-
if (typeof
|
|
5088
|
-
seen.add(
|
|
5089
|
-
if (isCamelValue(
|
|
5090
|
-
if (isUnsafe(
|
|
5091
|
-
assertNoUnsafeConstant(
|
|
5644
|
+
function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
5645
|
+
if (typeof value2 !== "object" || value2 === null || seen.has(value2)) return;
|
|
5646
|
+
seen.add(value2);
|
|
5647
|
+
if (isCamelValue(value2)) {
|
|
5648
|
+
if (isUnsafe(value2)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
|
|
5649
|
+
assertNoUnsafeConstant(value2.value, seen);
|
|
5092
5650
|
return;
|
|
5093
5651
|
}
|
|
5094
|
-
for (const child of Object.values(
|
|
5652
|
+
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
5095
5653
|
}
|
|
5096
5654
|
function metadata(kind, id, readers) {
|
|
5097
5655
|
if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
@@ -5105,10 +5663,10 @@ function createEffectPolicy(config = {}) {
|
|
|
5105
5663
|
return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
|
|
5106
5664
|
}
|
|
5107
5665
|
function destinationRegistryDigest(values) {
|
|
5108
|
-
return sha256Hex(
|
|
5666
|
+
return sha256Hex(canonicalJson2([...values].sort()));
|
|
5109
5667
|
}
|
|
5110
5668
|
async function evaluate(input, registries, approvals) {
|
|
5111
|
-
const actionDigest = await sha256Hex(
|
|
5669
|
+
const actionDigest = await sha256Hex(canonicalJson2(input));
|
|
5112
5670
|
const decisionId = `decision_${actionDigest.slice(0, 24)}`;
|
|
5113
5671
|
const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
|
|
5114
5672
|
if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
|
|
@@ -5122,7 +5680,7 @@ async function evaluate(input, registries, approvals) {
|
|
|
5122
5680
|
if (invalid3) return deny(invalid3);
|
|
5123
5681
|
if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
|
|
5124
5682
|
}
|
|
5125
|
-
if (input.controlDependencies.some((
|
|
5683
|
+
if (input.controlDependencies.some((value2) => !isSafe(value2) && !confined.has(value2))) return deny("unsafe_control_dependency");
|
|
5126
5684
|
if (confined.size > 0) {
|
|
5127
5685
|
const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
|
|
5128
5686
|
const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
|
|
@@ -5149,29 +5707,29 @@ async function validateArgument(path, arg, tool, registries) {
|
|
|
5149
5707
|
if (!isSafe(arg.value)) return "unsafe_destination_argument";
|
|
5150
5708
|
if (!isControlOwned(arg.value)) return "destination_not_control_owned";
|
|
5151
5709
|
const registry = registries[arg.registryId];
|
|
5152
|
-
const validValues = registry && registry.values.length > 0 && registry.values.every((
|
|
5710
|
+
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;
|
|
5153
5711
|
if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
|
|
5154
5712
|
}
|
|
5155
5713
|
if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
|
|
5156
5714
|
return void 0;
|
|
5157
5715
|
}
|
|
5158
|
-
function isControlOwned(
|
|
5159
|
-
return
|
|
5716
|
+
function isControlOwned(value2) {
|
|
5717
|
+
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
5160
5718
|
}
|
|
5161
5719
|
function copyRegistries(registries) {
|
|
5162
5720
|
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
5163
5721
|
}
|
|
5164
|
-
function validateUnsafeSelector(path,
|
|
5722
|
+
function validateUnsafeSelector(path, value2, tool) {
|
|
5165
5723
|
const policy = tool.unsafeSelectorPolicy;
|
|
5166
5724
|
if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
|
|
5167
5725
|
if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
|
|
5168
5726
|
if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
|
|
5169
|
-
if (utf8Length(
|
|
5170
|
-
if (typeof
|
|
5727
|
+
if (utf8Length(value2.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
|
|
5728
|
+
if (typeof value2.value === "string" && looksLikeDestination(value2.value)) return "unsafe_selector_is_destination";
|
|
5171
5729
|
return void 0;
|
|
5172
5730
|
}
|
|
5173
|
-
function looksLikeDestination(
|
|
5174
|
-
const text =
|
|
5731
|
+
function looksLikeDestination(value2) {
|
|
5732
|
+
const text = value2.trim();
|
|
5175
5733
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
|
|
5176
5734
|
}
|
|
5177
5735
|
|
|
@@ -5257,9 +5815,9 @@ var CodeRuntimeReconciler = class {
|
|
|
5257
5815
|
}
|
|
5258
5816
|
}
|
|
5259
5817
|
};
|
|
5260
|
-
function retryableControlFailure(
|
|
5261
|
-
if (!
|
|
5262
|
-
const failure =
|
|
5818
|
+
function retryableControlFailure(value2) {
|
|
5819
|
+
if (!value2 || typeof value2 !== "object") return false;
|
|
5820
|
+
const failure = value2;
|
|
5263
5821
|
if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
|
|
5264
5822
|
return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
|
|
5265
5823
|
}
|
|
@@ -5311,16 +5869,16 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5311
5869
|
if (options.signal?.aborted) throw cause;
|
|
5312
5870
|
throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
|
|
5313
5871
|
}
|
|
5314
|
-
const
|
|
5872
|
+
const value2 = await response2.json().catch(() => null);
|
|
5315
5873
|
if (!response2.ok) {
|
|
5316
|
-
const problem = record3(record3(
|
|
5874
|
+
const problem = record3(record3(value2)?.error);
|
|
5317
5875
|
throw new CodeRuntimeControlError(
|
|
5318
5876
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5319
5877
|
response2.status,
|
|
5320
5878
|
typeof problem?.code === "string" ? problem.code : void 0
|
|
5321
5879
|
);
|
|
5322
5880
|
}
|
|
5323
|
-
return
|
|
5881
|
+
return value2;
|
|
5324
5882
|
};
|
|
5325
5883
|
return {
|
|
5326
5884
|
heartbeat: async (version, capabilities) => {
|
|
@@ -5335,15 +5893,15 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5335
5893
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5336
5894
|
),
|
|
5337
5895
|
infer: async (sessionId, inference) => {
|
|
5338
|
-
const
|
|
5896
|
+
const value2 = record3(await call2(
|
|
5339
5897
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5340
5898
|
inference,
|
|
5341
5899
|
modelRequestTimeoutMs
|
|
5342
5900
|
));
|
|
5343
|
-
if (!
|
|
5901
|
+
if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
|
|
5344
5902
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5345
5903
|
}
|
|
5346
|
-
return
|
|
5904
|
+
return value2;
|
|
5347
5905
|
},
|
|
5348
5906
|
review: async (sessionId, review) => parseReview(
|
|
5349
5907
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
|
|
@@ -5368,8 +5926,8 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5368
5926
|
}
|
|
5369
5927
|
};
|
|
5370
5928
|
}
|
|
5371
|
-
function validatedEndpoint(
|
|
5372
|
-
const endpoint =
|
|
5929
|
+
function validatedEndpoint(value2) {
|
|
5930
|
+
const endpoint = value2.replace(/\/+$/, "");
|
|
5373
5931
|
let url;
|
|
5374
5932
|
try {
|
|
5375
5933
|
url = new URL(endpoint);
|
|
@@ -5382,9 +5940,9 @@ function validatedEndpoint(value) {
|
|
|
5382
5940
|
}
|
|
5383
5941
|
return endpoint;
|
|
5384
5942
|
}
|
|
5385
|
-
function validSessionId(
|
|
5386
|
-
if (!/^csess_[0-9a-f]{32}$/.test(
|
|
5387
|
-
return
|
|
5943
|
+
function validSessionId(value2) {
|
|
5944
|
+
if (!/^csess_[0-9a-f]{32}$/.test(value2)) throw new TypeError("invalid Code session id");
|
|
5945
|
+
return value2;
|
|
5388
5946
|
}
|
|
5389
5947
|
function validateHeartbeat(version, capabilities) {
|
|
5390
5948
|
if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
|
|
@@ -5397,8 +5955,8 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5397
5955
|
throw new TypeError("runtime resources must be positive integers");
|
|
5398
5956
|
}
|
|
5399
5957
|
}
|
|
5400
|
-
function parseSnapshot(
|
|
5401
|
-
const root = record3(
|
|
5958
|
+
function parseSnapshot(value2) {
|
|
5959
|
+
const root = record3(value2);
|
|
5402
5960
|
const host = record3(root?.host);
|
|
5403
5961
|
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");
|
|
5404
5962
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
@@ -5423,11 +5981,11 @@ function parseSnapshot(value) {
|
|
|
5423
5981
|
});
|
|
5424
5982
|
return { host, bindings, commands };
|
|
5425
5983
|
}
|
|
5426
|
-
async function parseSource(
|
|
5427
|
-
const snapshot = record3(record3(
|
|
5984
|
+
async function parseSource(value2) {
|
|
5985
|
+
const snapshot = record3(record3(value2)?.snapshot);
|
|
5428
5986
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5429
|
-
const files = snapshot.files.map((
|
|
5430
|
-
const file = record3(
|
|
5987
|
+
const files = snapshot.files.map((value22) => {
|
|
5988
|
+
const file = record3(value22);
|
|
5431
5989
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5432
5990
|
return { path: file.path, content: file.content };
|
|
5433
5991
|
});
|
|
@@ -5454,19 +6012,19 @@ async function parseSource(value) {
|
|
|
5454
6012
|
if (digest !== snapshot.treeDigest) throw invalid("source digest");
|
|
5455
6013
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5456
6014
|
}
|
|
5457
|
-
function parseReview(
|
|
5458
|
-
const review = record3(record3(
|
|
6015
|
+
function parseReview(value2) {
|
|
6016
|
+
const review = record3(record3(value2)?.review);
|
|
5459
6017
|
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");
|
|
5460
6018
|
return review;
|
|
5461
6019
|
}
|
|
5462
|
-
function parseCandidate(
|
|
5463
|
-
const candidate = record3(record3(
|
|
6020
|
+
function parseCandidate(value2) {
|
|
6021
|
+
const candidate = record3(record3(value2)?.candidate);
|
|
5464
6022
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5465
6023
|
throw invalid("candidate");
|
|
5466
6024
|
}
|
|
5467
6025
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5468
6026
|
}
|
|
5469
|
-
var record3 = (
|
|
6027
|
+
var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5470
6028
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5471
6029
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5472
6030
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -5500,8 +6058,8 @@ function validateCodePatch(patch2, maxBytes) {
|
|
|
5500
6058
|
if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
5501
6059
|
return paths;
|
|
5502
6060
|
}
|
|
5503
|
-
function validHeaderPath(
|
|
5504
|
-
return
|
|
6061
|
+
function validHeaderPath(value2, path, prefix) {
|
|
6062
|
+
return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
|
|
5505
6063
|
}
|
|
5506
6064
|
function validateRelativePath(path) {
|
|
5507
6065
|
const parts = path.split("/");
|
|
@@ -5647,8 +6205,8 @@ function assertCodeBuildRecipe(recipe2) {
|
|
|
5647
6205
|
throw new TypeError("build recipe is malformed or exceeds its control bounds");
|
|
5648
6206
|
}
|
|
5649
6207
|
}
|
|
5650
|
-
function parseMemory(
|
|
5651
|
-
const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(
|
|
6208
|
+
function parseMemory(value2) {
|
|
6209
|
+
const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value2.toLowerCase());
|
|
5652
6210
|
if (!match?.[1] || !match[2]) return 0;
|
|
5653
6211
|
const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
|
|
5654
6212
|
return Number(match[1]) * scale;
|
|
@@ -5883,14 +6441,14 @@ function normalizedRecipe(recipe2) {
|
|
|
5883
6441
|
expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
|
|
5884
6442
|
};
|
|
5885
6443
|
}
|
|
5886
|
-
function digestJson(
|
|
5887
|
-
return digestBytes(JSON.stringify(
|
|
6444
|
+
function digestJson(value2) {
|
|
6445
|
+
return digestBytes(JSON.stringify(value2));
|
|
5888
6446
|
}
|
|
5889
|
-
function digestBytes(
|
|
5890
|
-
return `sha256:${(0, import_crypto3.createHash)("sha256").update(
|
|
6447
|
+
function digestBytes(value2) {
|
|
6448
|
+
return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
|
|
5891
6449
|
}
|
|
5892
|
-
function integer(
|
|
5893
|
-
return Number.isSafeInteger(
|
|
6450
|
+
function integer(value2, minimum, maximum) {
|
|
6451
|
+
return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
|
|
5894
6452
|
}
|
|
5895
6453
|
async function prepareRuntimeCheckpoint(input) {
|
|
5896
6454
|
const patch2 = await input.workspace.patch(256 * 1024);
|
|
@@ -5943,7 +6501,7 @@ async function prepareRuntimeCheckpoint(input) {
|
|
|
5943
6501
|
});
|
|
5944
6502
|
return { checkpoint, verification, review, note };
|
|
5945
6503
|
}
|
|
5946
|
-
var message = (
|
|
6504
|
+
var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
|
|
5947
6505
|
var CodeRuntimeCheckpointManager = class {
|
|
5948
6506
|
constructor(options) {
|
|
5949
6507
|
this.options = options;
|
|
@@ -6137,7 +6695,7 @@ async function conversionPolicy(id, output) {
|
|
|
6137
6695
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
6138
6696
|
}
|
|
6139
6697
|
async function registeredPolicy(id, registryId, values) {
|
|
6140
|
-
const mapping = Object.fromEntries(values.map((
|
|
6698
|
+
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
6141
6699
|
return conversionPolicy(id, {
|
|
6142
6700
|
kind: "registered_id",
|
|
6143
6701
|
registryId,
|
|
@@ -6146,7 +6704,7 @@ async function registeredPolicy(id, registryId, values) {
|
|
|
6146
6704
|
}
|
|
6147
6705
|
async function conversionRegistry(policies, values) {
|
|
6148
6706
|
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
|
|
6149
|
-
const mapping = Object.fromEntries(entries.map((
|
|
6707
|
+
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
6150
6708
|
return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
6151
6709
|
})));
|
|
6152
6710
|
return createConversionRegistry({ policies, registeredIds });
|
|
@@ -6170,8 +6728,8 @@ async function environment(input, options, tool) {
|
|
|
6170
6728
|
};
|
|
6171
6729
|
return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
|
|
6172
6730
|
}
|
|
6173
|
-
function unsafe(base,
|
|
6174
|
-
return base.ingress.quarantinedOutput(
|
|
6731
|
+
function unsafe(base, value2, field) {
|
|
6732
|
+
return base.ingress.quarantinedOutput(value2, {
|
|
6175
6733
|
readers: base.reader.label.readers,
|
|
6176
6734
|
runId: `${base.runId}:${field}`
|
|
6177
6735
|
});
|
|
@@ -6251,14 +6809,14 @@ async function read(context, request2, options, policy) {
|
|
|
6251
6809
|
}
|
|
6252
6810
|
async function patch(context, request2, options, policy) {
|
|
6253
6811
|
exactKeys(request2.input, ["patch"]);
|
|
6254
|
-
const
|
|
6255
|
-
const paths = validateCodePatch(
|
|
6812
|
+
const value2 = stringField(request2.input, "patch");
|
|
6813
|
+
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
6256
6814
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
6257
6815
|
throw new TypeError("patch targets a read-only reference source");
|
|
6258
6816
|
}
|
|
6259
|
-
const allowed = await policy.patch(policyContext(context, request2, options, { patch:
|
|
6817
|
+
const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
|
|
6260
6818
|
if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
|
|
6261
|
-
await applyCodePatch(context.workspaceDir,
|
|
6819
|
+
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
6262
6820
|
return response(request2, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
6263
6821
|
}
|
|
6264
6822
|
async function recipe(context, request2, options, recipes, policy) {
|
|
@@ -6349,14 +6907,14 @@ function exactKeys(input, allowed) {
|
|
|
6349
6907
|
if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
|
|
6350
6908
|
}
|
|
6351
6909
|
function stringField(input, name) {
|
|
6352
|
-
const
|
|
6353
|
-
if (typeof
|
|
6354
|
-
return
|
|
6910
|
+
const value2 = input[name];
|
|
6911
|
+
if (typeof value2 !== "string" || !value2) throw new TypeError(`${name} must be a non-empty string`);
|
|
6912
|
+
return value2;
|
|
6355
6913
|
}
|
|
6356
|
-
function optionalInteger(
|
|
6357
|
-
if (
|
|
6358
|
-
if (!Number.isSafeInteger(
|
|
6359
|
-
return
|
|
6914
|
+
function optionalInteger(value2) {
|
|
6915
|
+
if (value2 === void 0) return void 0;
|
|
6916
|
+
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
6917
|
+
return value2;
|
|
6360
6918
|
}
|
|
6361
6919
|
function response(request2, ok, content2, details) {
|
|
6362
6920
|
return { requestId: request2.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
@@ -6402,9 +6960,9 @@ function codeLocalSource(payload) {
|
|
|
6402
6960
|
return source;
|
|
6403
6961
|
}
|
|
6404
6962
|
function codeCheckpointPayload(payload) {
|
|
6405
|
-
const
|
|
6406
|
-
if (!
|
|
6407
|
-
return
|
|
6963
|
+
const value2 = payload.checkpoint;
|
|
6964
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new TypeError("resume checkpoint is missing");
|
|
6965
|
+
return value2;
|
|
6408
6966
|
}
|
|
6409
6967
|
function fakeCodeLease(command, metadata2) {
|
|
6410
6968
|
return {
|
|
@@ -6428,7 +6986,7 @@ function fakeCodeLease(command, metadata2) {
|
|
|
6428
6986
|
}
|
|
6429
6987
|
};
|
|
6430
6988
|
}
|
|
6431
|
-
var record22 = (
|
|
6989
|
+
var record22 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6432
6990
|
var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
6433
6991
|
async function prepareRuntimeLocalSource(input) {
|
|
6434
6992
|
const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
|
|
@@ -6518,24 +7076,24 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
|
6518
7076
|
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
6519
7077
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
6520
7078
|
}
|
|
6521
|
-
var digestRuntimeValue = (
|
|
6522
|
-
var runtimeErrorMessage = (
|
|
6523
|
-
var runtimeRecord = (
|
|
6524
|
-
var safeRuntimeJson = (
|
|
7079
|
+
var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
|
|
7080
|
+
var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
|
|
7081
|
+
var runtimeRecord = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7082
|
+
var safeRuntimeJson = (value2) => {
|
|
6525
7083
|
try {
|
|
6526
|
-
return JSON.stringify(
|
|
7084
|
+
return JSON.stringify(value2).slice(0, 1e4);
|
|
6527
7085
|
} catch {
|
|
6528
7086
|
return "[event]";
|
|
6529
7087
|
}
|
|
6530
7088
|
};
|
|
6531
|
-
function runtimeResultText(
|
|
6532
|
-
const record32 = runtimeRecord(
|
|
7089
|
+
function runtimeResultText(value2) {
|
|
7090
|
+
const record32 = runtimeRecord(value2);
|
|
6533
7091
|
if (record32 && typeof record32.text === "string") return record32.text.slice(0, 2e4);
|
|
6534
7092
|
if (record32 && typeof record32.error === "string") return `Pi failed: ${record32.error.slice(0, 19989)}`;
|
|
6535
7093
|
return null;
|
|
6536
7094
|
}
|
|
6537
|
-
function runtimeResultError(
|
|
6538
|
-
const record32 = runtimeRecord(
|
|
7095
|
+
function runtimeResultError(value2) {
|
|
7096
|
+
const record32 = runtimeRecord(value2);
|
|
6539
7097
|
return record32 && typeof record32.error === "string" && record32.error.trim() ? record32.error.trim().slice(0, 2e3) : null;
|
|
6540
7098
|
}
|
|
6541
7099
|
var CodePiRuntimeEngine = class {
|
|
@@ -6809,14 +7367,14 @@ var CodePiRuntimeEngine = class {
|
|
|
6809
7367
|
this.#active.delete(command.sessionId);
|
|
6810
7368
|
return result;
|
|
6811
7369
|
}
|
|
6812
|
-
async #failure(command, active,
|
|
6813
|
-
active.failure =
|
|
7370
|
+
async #failure(command, active, value2) {
|
|
7371
|
+
active.failure = value2.slice(0, 2e3);
|
|
6814
7372
|
if (active.acknowledged) {
|
|
6815
7373
|
await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
|
|
6816
7374
|
}
|
|
6817
7375
|
}
|
|
6818
|
-
async #diagnostic(command, active,
|
|
6819
|
-
const detail =
|
|
7376
|
+
async #diagnostic(command, active, value2) {
|
|
7377
|
+
const detail = value2.trim().slice(0, 2e3) || "Pi runtime failed";
|
|
6820
7378
|
this.options.onDiagnostic?.(detail);
|
|
6821
7379
|
await this.#event(
|
|
6822
7380
|
command,
|
|
@@ -6829,265 +7387,19 @@ var CodePiRuntimeEngine = class {
|
|
|
6829
7387
|
}
|
|
6830
7388
|
};
|
|
6831
7389
|
|
|
6832
|
-
// src/
|
|
7390
|
+
// src/version.ts
|
|
6833
7391
|
var import_node_fs13 = require("fs");
|
|
6834
7392
|
function cliVersion() {
|
|
6835
7393
|
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
6836
7394
|
return pkg.version ?? "unknown";
|
|
6837
7395
|
}
|
|
6838
|
-
function printHelp(output = console) {
|
|
6839
|
-
output.log(`odla-ai
|
|
6840
7396
|
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
working from memory: runbooks are edited
|
|
6845
|
-
live, so your training data is out of date
|
|
6846
|
-
and this is not.
|
|
6847
|
-
odla-ai runbook impact After a change: which runbooks describe the
|
|
6848
|
-
code you just touched, so you can fix any
|
|
6849
|
-
step it made wrong.
|
|
6850
|
-
|
|
6851
|
-
Usage:
|
|
6852
|
-
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
6853
|
-
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
6854
|
-
odla-ai doctor [--config odla.config.mjs]
|
|
6855
|
-
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
6856
|
-
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
6857
|
-
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
6858
|
-
odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
|
|
6859
|
-
odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
|
|
6860
|
-
odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6861
|
-
odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
|
|
6862
|
-
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
6863
|
-
odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
6864
|
-
odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
6865
|
-
odla-ai app promote [--dry-run] [--json] --yes
|
|
6866
|
-
odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6867
|
-
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6868
|
-
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6869
|
-
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6870
|
-
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6871
|
-
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6872
|
-
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6873
|
-
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6874
|
-
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
6875
|
-
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]
|
|
6876
|
-
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
6877
|
-
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]
|
|
6878
|
-
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6879
|
-
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
6880
|
-
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]
|
|
6881
|
-
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
6882
|
-
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]
|
|
6883
|
-
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
6884
|
-
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
6885
|
-
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
6886
|
-
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6887
|
-
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
6888
|
-
odla-ai pm handoff --app <id> [--json]
|
|
6889
|
-
odla-ai discuss groups [--json]
|
|
6890
|
-
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
6891
|
-
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
6892
|
-
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
6893
|
-
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
6894
|
-
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
6895
|
-
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
6896
|
-
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6897
|
-
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6898
|
-
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6899
|
-
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6900
|
-
odla-ai context list [--json]
|
|
6901
|
-
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6902
|
-
odla-ai context remove <name> --yes [--json]
|
|
6903
|
-
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6904
|
-
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6905
|
-
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6906
|
-
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6907
|
-
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
6908
|
-
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
6909
|
-
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
6910
|
-
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
6911
|
-
odla-ai runbook get <slug> [--app <id>]
|
|
6912
|
-
odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
|
|
6913
|
-
odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
|
|
6914
|
-
odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
|
|
6915
|
-
odla-ai runbook publish <slug> [--app <id>]
|
|
6916
|
-
odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
|
|
6917
|
-
odla-ai runbook archive <slug> [--app <id>]
|
|
6918
|
-
odla-ai runbook history <slug> [--app <id>] [--json]
|
|
6919
|
-
odla-ai runbook revert <slug> --version <n> [--app <id>]
|
|
6920
|
-
odla-ai runbook rm <slug> [--app <id>]
|
|
6921
|
-
odla-ai capabilities [--json]
|
|
6922
|
-
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
6923
|
-
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
6924
|
-
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
6925
|
-
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
6926
|
-
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
6927
|
-
odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
|
|
6928
|
-
--validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
|
|
6929
|
-
odla-ai admin ai credentials [--context <name>] [--json]
|
|
6930
|
-
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
6931
|
-
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
6932
|
-
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
6933
|
-
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
6934
|
-
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
6935
|
-
odla-ai security plan [--env dev] [--json]
|
|
6936
|
-
odla-ai security sources [--env dev] [--json]
|
|
6937
|
-
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
6938
|
-
odla-ai security status <job-id> [--follow] [--json]
|
|
6939
|
-
odla-ai security report <job-id> [--json]
|
|
6940
|
-
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
6941
|
-
odla-ai security run [target] --self --ack-redacted-source
|
|
6942
|
-
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
6943
|
-
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
6944
|
-
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
6945
|
-
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
6946
|
-
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
6947
|
-
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
6948
|
-
odla-ai version
|
|
6949
|
-
|
|
6950
|
-
Commands:
|
|
6951
|
-
agent Inspect durable agent wakeups and explicitly requeue a
|
|
6952
|
-
dead-lettered job; JSON output is stable for remote operators.
|
|
6953
|
-
runbook odla's operational procedures, stored in the database and read at
|
|
6954
|
-
the moment they are followed. "ask" gives a written, cited answer;
|
|
6955
|
-
"search" the passages behind it; "get" the whole document.
|
|
6956
|
-
"impact" diffs your working tree against a base ref and names the
|
|
6957
|
-
runbooks that describe what you changed \u2014 run it after touching a
|
|
6958
|
-
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
6959
|
-
checks the corpus the other way: every odla-ai command the
|
|
6960
|
-
runbooks name is held against this CLI's real command surface,
|
|
6961
|
-
and against the minimum versions each runbook declares. A wrong step
|
|
6962
|
-
is fixed with "edit", which takes effect immediately: a runbook is
|
|
6963
|
-
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
6964
|
-
does and what it guarantees is JSDoc, rendered per package at
|
|
6965
|
-
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
6966
|
-
a question usually needs both.
|
|
6967
|
-
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6968
|
-
platform admin. A handshake-minted device token is never admin,
|
|
6969
|
-
however the human who approved it is configured.
|
|
6970
|
-
context Explain selected config, platform, app, environment, and
|
|
6971
|
-
developer-token provenance without printing credentials or
|
|
6972
|
-
starting a device handshake. Operator work can run outside a
|
|
6973
|
-
project checkout with explicit flags or ODLA_* environment
|
|
6974
|
-
variables.
|
|
6975
|
-
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6976
|
-
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6977
|
-
doctor Validate and summarize the project config without network calls.
|
|
6978
|
-
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
6979
|
-
app Archive (suspend, data retained), restore, export, import, or
|
|
6980
|
-
manage the co-owners of the app. Archiving takes every
|
|
6981
|
-
environment's data plane down until restored; permanent deletion
|
|
6982
|
-
has NO CLI \u2014 it requires a signed-in owner in Studio, and no
|
|
6983
|
-
machine or agent credential can purge. "app export" downloads a
|
|
6984
|
-
portable gzipped-JSONL snapshot of one database (--fresh takes a
|
|
6985
|
-
new snapshot first) \u2014 your data is always yours to take.
|
|
6986
|
-
"app import" upserts rows back in from JSON, JSONL, or a
|
|
6987
|
-
{namespace: rows} map (the shape a query returns); it writes only
|
|
6988
|
-
with --yes, so the bare command is a dry run, and it refuses to
|
|
6989
|
-
guess an id mode that would duplicate rows on a re-run.
|
|
6990
|
-
"app refresh-sandbox" replaces the sandbox with a copy of live
|
|
6991
|
-
(the routine dev loop); "app go-live" copies the sandbox into an
|
|
6992
|
-
EMPTY live database, once, at launch; "app promote" pushes only
|
|
6993
|
-
schema, rules and gates up afterwards, leaving live's rows alone.
|
|
6994
|
-
Each prints its plan and writes nothing without --yes, so the
|
|
6995
|
-
bare command is the dry run. Clerk config, triggers, allowlists,
|
|
6996
|
-
secrets and API keys never travel; identity rows stay behind
|
|
6997
|
-
unless --include-identity.
|
|
6998
|
-
"app rename <name>" changes only the display name humans read;
|
|
6999
|
-
the app id is permanent (tenants, URLs and keys embed it), so a
|
|
7000
|
-
rename never re-provisions anything or invalidates a credential.
|
|
7001
|
-
"app owners add <email>" grants a signed-up odla member the SAME
|
|
7002
|
-
full access as you; they then run their own "provision" to mint
|
|
7003
|
-
their own credentials for the shared db (the live one included)
|
|
7004
|
-
\u2014 no secret is ever copied between people.
|
|
7005
|
-
capabilities Show what the CLI automates vs agent edits and human checkpoints.
|
|
7006
|
-
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
7007
|
-
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
7008
|
-
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
7009
|
-
pm Project management (via @odla-ai/pm) shared across the apps you
|
|
7010
|
-
co-own: conformance goals, kanban tasks, decisions, and bugs. With
|
|
7011
|
-
no --app, "list" spans every co-owned project (the cross-project
|
|
7012
|
-
view); with --app it scopes to one. Same device-grant auth as
|
|
7013
|
-
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
7014
|
-
decision, bug. Status changes and comments post to each item's
|
|
7015
|
-
@odla-ai/chat discussion thread.
|
|
7016
|
-
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
7017
|
-
one group per project, topics with replies, @-mentions of people,
|
|
7018
|
-
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
7019
|
-
question, then "watch" it until someone answers. "watch" exits 75
|
|
7020
|
-
(not 1) when it times out with nothing new, so a script can tell
|
|
7021
|
-
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
7022
|
-
up the exact @[Label](kind/id) markup for a mention.
|
|
7023
|
-
o11y Read one stable status envelope for application RED, current
|
|
7024
|
-
live-sync load/freshness, the protected commit-to-visible
|
|
7025
|
-
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
7026
|
-
runtime metrics, and a machine verdict.
|
|
7027
|
-
--json keeps auth progress on stderr for unattended agents.
|
|
7028
|
-
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
7029
|
-
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
7030
|
-
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
7031
|
-
copilot, gemini, or agents (repeatable or comma-separated).
|
|
7032
|
-
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
7033
|
-
stdin; set stores a tenant-vault secret and set-clerk-key the
|
|
7034
|
-
reserved Clerk secret key, write-only from stdin or an env var.
|
|
7035
|
-
version Print the CLI version.
|
|
7036
|
-
|
|
7037
|
-
Safety:
|
|
7038
|
-
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
7039
|
-
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
7040
|
-
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
7041
|
-
the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
|
|
7042
|
-
provision the live database; use --dry-run first to inspect the resolved plan.
|
|
7043
|
-
Provision caches the approved developer token and service credentials under
|
|
7044
|
-
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
7045
|
-
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
7046
|
-
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
7047
|
-
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
7048
|
-
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
7049
|
-
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
7050
|
-
isolated developer and scoped-token caches. Override their locations with
|
|
7051
|
-
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
7052
|
-
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
7053
|
-
context, which beats project config. There is no ambient current context.
|
|
7054
|
-
"context show" reports only provenance and cache state and never authenticates.
|
|
7055
|
-
Provision opens the approval page in your browser automatically whenever the
|
|
7056
|
-
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
7057
|
-
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
7058
|
-
Browser launch is best-effort: the printed approval URL is authoritative and
|
|
7059
|
-
agents must relay it to the human verbatim. A started handshake is persisted
|
|
7060
|
-
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
7061
|
-
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
7062
|
-
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
7063
|
-
with code 75: relay the URL, wait for approval, and re-run to collect.
|
|
7064
|
-
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
7065
|
-
The email is a non-secret identity hint: never provide a password or session
|
|
7066
|
-
token. The matching account must already exist, be signed in, explicitly
|
|
7067
|
-
review the exact code, and finish any current request before claiming another.
|
|
7068
|
-
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
7069
|
-
odla.config.mjs may select the app explicitly but is not required. Code host
|
|
7070
|
-
approval and credential hashes live in odla-ai/db. The host
|
|
7071
|
-
credential is never written under .odla/; it exists only in the foreground
|
|
7072
|
-
"code connect" process and is rotated by the next approved connection.
|
|
7073
|
-
Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
|
|
7074
|
-
OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
|
|
7075
|
-
GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
|
|
7076
|
-
for a PAT or provider key. GitHub read access is separate from the explicit
|
|
7077
|
-
--ack-redacted-source consent and the exact --plan-digest printed by security
|
|
7078
|
-
plan are required before bounded snippets reach System AI.
|
|
7079
|
-
Run security plan first to inspect the admin-selected providers, models,
|
|
7080
|
-
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
7081
|
-
and digest that binds consent to that exact plan.
|
|
7082
|
-
`);
|
|
7083
|
-
}
|
|
7084
|
-
|
|
7085
|
-
// src/security-hosted-github.ts
|
|
7086
|
-
var import_node_child_process4 = require("child_process");
|
|
7087
|
-
var import_node_util2 = require("util");
|
|
7397
|
+
// src/security-hosted-github.ts
|
|
7398
|
+
var import_node_child_process4 = require("child_process");
|
|
7399
|
+
var import_node_util2 = require("util");
|
|
7088
7400
|
|
|
7089
7401
|
// src/security-hosted-request.ts
|
|
7090
|
-
async function requestHostedSecurityJson(options, path, init,
|
|
7402
|
+
async function requestHostedSecurityJson(options, path, init, action2) {
|
|
7091
7403
|
const platform = platformAudience(options.platform);
|
|
7092
7404
|
const token = hostedSecurityCredential(options.token);
|
|
7093
7405
|
const requestInit = {
|
|
@@ -7107,35 +7419,35 @@ async function requestHostedSecurityJson(options, path, init, action) {
|
|
|
7107
7419
|
if (!response2.ok) {
|
|
7108
7420
|
const code = optionalHostedText(body.error?.code, "error code", 128);
|
|
7109
7421
|
const message2 = optionalHostedText(body.error?.message, "error message", 300);
|
|
7110
|
-
throw new Error(`${
|
|
7422
|
+
throw new Error(`${action2} failed (${response2.status})${code ? ` ${code}` : ""}${message2 ? `: ${message2}` : ""}`);
|
|
7111
7423
|
}
|
|
7112
7424
|
return body;
|
|
7113
7425
|
}
|
|
7114
|
-
function hostedIdentifier(
|
|
7115
|
-
const result = optionalHostedText(
|
|
7426
|
+
function hostedIdentifier(value2, name) {
|
|
7427
|
+
const result = optionalHostedText(value2, name, 180);
|
|
7116
7428
|
if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
|
|
7117
7429
|
throw new Error(`${name} is invalid`);
|
|
7118
7430
|
}
|
|
7119
7431
|
return result;
|
|
7120
7432
|
}
|
|
7121
|
-
function positivePolicyVersion(
|
|
7122
|
-
if (!Number.isSafeInteger(
|
|
7433
|
+
function positivePolicyVersion(value2, name) {
|
|
7434
|
+
if (!Number.isSafeInteger(value2) || value2 < 1) {
|
|
7123
7435
|
throw new Error(`${name} is invalid`);
|
|
7124
7436
|
}
|
|
7125
|
-
return
|
|
7437
|
+
return value2;
|
|
7126
7438
|
}
|
|
7127
|
-
function optionalHostedText(
|
|
7128
|
-
if (
|
|
7129
|
-
if (typeof
|
|
7439
|
+
function optionalHostedText(value2, name, maxLength) {
|
|
7440
|
+
if (value2 === void 0 || value2 === null || value2 === "") return void 0;
|
|
7441
|
+
if (typeof value2 !== "string" || value2.length > maxLength || /[\u0000-\u001f\u007f]/.test(value2)) {
|
|
7130
7442
|
throw new Error(`${name} is invalid`);
|
|
7131
7443
|
}
|
|
7132
|
-
return
|
|
7444
|
+
return value2;
|
|
7133
7445
|
}
|
|
7134
|
-
function githubRepositoryName(
|
|
7135
|
-
if (typeof
|
|
7446
|
+
function githubRepositoryName(value2) {
|
|
7447
|
+
if (typeof value2 !== "string" || value2.length > 201) {
|
|
7136
7448
|
throw new Error("repository must be owner/name");
|
|
7137
7449
|
}
|
|
7138
|
-
const parts =
|
|
7450
|
+
const parts = value2.split("/");
|
|
7139
7451
|
const owner = parts[0] ?? "";
|
|
7140
7452
|
const name = (parts[1] ?? "").replace(/\.git$/i, "");
|
|
7141
7453
|
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 === "..") {
|
|
@@ -7143,10 +7455,10 @@ function githubRepositoryName(value) {
|
|
|
7143
7455
|
}
|
|
7144
7456
|
return `${owner}/${name}`;
|
|
7145
7457
|
}
|
|
7146
|
-
function trustedGitHubInstallUrl(
|
|
7458
|
+
function trustedGitHubInstallUrl(value2) {
|
|
7147
7459
|
let url;
|
|
7148
7460
|
try {
|
|
7149
|
-
url = new URL(
|
|
7461
|
+
url = new URL(value2);
|
|
7150
7462
|
} catch {
|
|
7151
7463
|
throw new Error("odla.ai returned an invalid GitHub installation URL");
|
|
7152
7464
|
}
|
|
@@ -7155,17 +7467,17 @@ function trustedGitHubInstallUrl(value) {
|
|
|
7155
7467
|
}
|
|
7156
7468
|
return url.toString();
|
|
7157
7469
|
}
|
|
7158
|
-
function hostedPollInterval(
|
|
7159
|
-
if (!Number.isSafeInteger(
|
|
7470
|
+
function hostedPollInterval(value2 = 2e3) {
|
|
7471
|
+
if (!Number.isSafeInteger(value2) || value2 < 100 || value2 > 3e4) {
|
|
7160
7472
|
throw new Error("poll interval must be 100-30000ms");
|
|
7161
7473
|
}
|
|
7162
|
-
return
|
|
7474
|
+
return value2;
|
|
7163
7475
|
}
|
|
7164
|
-
function hostedPollTimeout(
|
|
7165
|
-
if (!Number.isSafeInteger(
|
|
7476
|
+
function hostedPollTimeout(value2 = 10 * 6e4) {
|
|
7477
|
+
if (!Number.isSafeInteger(value2) || value2 < 1e3 || value2 > 60 * 6e4) {
|
|
7166
7478
|
throw new Error("poll timeout must be 1000-3600000ms");
|
|
7167
7479
|
}
|
|
7168
|
-
return
|
|
7480
|
+
return value2;
|
|
7169
7481
|
}
|
|
7170
7482
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
7171
7483
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
@@ -7177,16 +7489,16 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
7177
7489
|
}, { once: true });
|
|
7178
7490
|
});
|
|
7179
7491
|
}
|
|
7180
|
-
function isValidHostedSecurityPlan(
|
|
7181
|
-
if (!
|
|
7492
|
+
function isValidHostedSecurityPlan(value2, env) {
|
|
7493
|
+
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;
|
|
7182
7494
|
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;
|
|
7183
|
-
return validRoute(
|
|
7495
|
+
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
7184
7496
|
}
|
|
7185
|
-
function hostedSecurityCredential(
|
|
7186
|
-
if (typeof
|
|
7497
|
+
function hostedSecurityCredential(value2) {
|
|
7498
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
7187
7499
|
throw new Error("Hosted security requires an injected odla developer token");
|
|
7188
7500
|
}
|
|
7189
|
-
return
|
|
7501
|
+
return value2;
|
|
7190
7502
|
}
|
|
7191
7503
|
|
|
7192
7504
|
// src/security-hosted-github.ts
|
|
@@ -7298,7 +7610,7 @@ async function defaultReadOrigin(cwd) {
|
|
|
7298
7610
|
|
|
7299
7611
|
// src/code-local-source.ts
|
|
7300
7612
|
var import_node_child_process5 = require("child_process");
|
|
7301
|
-
var
|
|
7613
|
+
var import_node_crypto2 = require("crypto");
|
|
7302
7614
|
var SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
|
|
7303
7615
|
async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
7304
7616
|
const headCommitSha = await readHead(cwd);
|
|
@@ -7339,25 +7651,25 @@ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
|
|
|
7339
7651
|
}
|
|
7340
7652
|
}
|
|
7341
7653
|
async function readGitHead(cwd) {
|
|
7342
|
-
const
|
|
7654
|
+
const value2 = await new Promise((accept, reject) => {
|
|
7343
7655
|
(0, import_node_child_process5.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
|
|
7344
7656
|
if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
|
|
7345
7657
|
else accept(stdout.trim());
|
|
7346
7658
|
});
|
|
7347
7659
|
});
|
|
7348
|
-
if (!/^[0-9a-f]{40}$/.test(
|
|
7349
|
-
return
|
|
7660
|
+
if (!/^[0-9a-f]{40}$/.test(value2)) throw new Error("code connect could not resolve the checkout HEAD commit");
|
|
7661
|
+
return value2;
|
|
7350
7662
|
}
|
|
7351
|
-
function digestText(
|
|
7352
|
-
return `sha256:${(0,
|
|
7663
|
+
function digestText(value2) {
|
|
7664
|
+
return `sha256:${(0, import_node_crypto2.createHash)("sha256").update(value2).digest("hex")}`;
|
|
7353
7665
|
}
|
|
7354
7666
|
|
|
7355
7667
|
// src/code-images.ts
|
|
7356
7668
|
var import_node_child_process6 = require("child_process");
|
|
7357
|
-
var
|
|
7669
|
+
var import_node_crypto3 = require("crypto");
|
|
7358
7670
|
var import_promises10 = require("fs/promises");
|
|
7359
7671
|
var import_node_os3 = require("os");
|
|
7360
|
-
var
|
|
7672
|
+
var import_node_path12 = require("path");
|
|
7361
7673
|
var import_node_url3 = require("url");
|
|
7362
7674
|
|
|
7363
7675
|
// src/code-runtime-config.ts
|
|
@@ -7440,13 +7752,13 @@ async function embeddedPiImageName() {
|
|
|
7440
7752
|
const bundle = await (0, import_promises10.readFile)(embeddedPiAssetPath()).catch(() => {
|
|
7441
7753
|
throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
|
|
7442
7754
|
});
|
|
7443
|
-
return `odla-ai/pi-agent:embedded-sha256-${(0,
|
|
7755
|
+
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7444
7756
|
}
|
|
7445
7757
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7446
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
7758
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7447
7759
|
try {
|
|
7448
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7449
|
-
await (0, import_promises10.writeFile)((0,
|
|
7760
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
|
|
7761
|
+
await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
|
|
7450
7762
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7451
7763
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7452
7764
|
"WORKDIR /workspace",
|
|
@@ -7462,7 +7774,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7462
7774
|
// src/code-connect.ts
|
|
7463
7775
|
async function codeConnect(options) {
|
|
7464
7776
|
const cwd = options.cwd ?? process.cwd();
|
|
7465
|
-
const configPath = (0,
|
|
7777
|
+
const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
|
|
7466
7778
|
const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7467
7779
|
const requestedAppId = options.appId?.trim();
|
|
7468
7780
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
@@ -7620,8 +7932,8 @@ async function runCodeRuntime(input) {
|
|
|
7620
7932
|
for (const [name, handler] of handlers) process.removeListener(name, handler);
|
|
7621
7933
|
}
|
|
7622
7934
|
}
|
|
7623
|
-
function parseConnection(
|
|
7624
|
-
const root = record4(
|
|
7935
|
+
function parseConnection(value2, appId, appEnv) {
|
|
7936
|
+
const root = record4(value2);
|
|
7625
7937
|
const host = record4(root?.host);
|
|
7626
7938
|
const offer = record4(root?.offer);
|
|
7627
7939
|
const binding = record4(root?.binding);
|
|
@@ -7630,12 +7942,12 @@ function parseConnection(value, appId, appEnv) {
|
|
|
7630
7942
|
}
|
|
7631
7943
|
return root;
|
|
7632
7944
|
}
|
|
7633
|
-
function apiFailure(
|
|
7634
|
-
const message2 = record4(record4(
|
|
7635
|
-
return `${
|
|
7945
|
+
function apiFailure(action2, status, value2) {
|
|
7946
|
+
const message2 = record4(record4(value2)?.error)?.message;
|
|
7947
|
+
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7636
7948
|
}
|
|
7637
|
-
function record4(
|
|
7638
|
-
return
|
|
7949
|
+
function record4(value2) {
|
|
7950
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7639
7951
|
}
|
|
7640
7952
|
|
|
7641
7953
|
// src/code-command.ts
|
|
@@ -7694,8 +8006,8 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
|
7694
8006
|
cacheStatus
|
|
7695
8007
|
};
|
|
7696
8008
|
}
|
|
7697
|
-
function clean3(
|
|
7698
|
-
const normalized =
|
|
8009
|
+
function clean3(value2) {
|
|
8010
|
+
const normalized = value2?.trim();
|
|
7699
8011
|
return normalized || void 0;
|
|
7700
8012
|
}
|
|
7701
8013
|
|
|
@@ -7715,9 +8027,9 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7715
8027
|
],
|
|
7716
8028
|
3
|
|
7717
8029
|
);
|
|
7718
|
-
const
|
|
8030
|
+
const action2 = parsed.positionals[1];
|
|
7719
8031
|
const out = deps.stdout ?? console;
|
|
7720
|
-
if (
|
|
8032
|
+
if (action2 === "list") {
|
|
7721
8033
|
const profiles = listOperatorProfiles();
|
|
7722
8034
|
if (parsed.options.json === true) {
|
|
7723
8035
|
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
@@ -7735,7 +8047,7 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7735
8047
|
}
|
|
7736
8048
|
return;
|
|
7737
8049
|
}
|
|
7738
|
-
if (
|
|
8050
|
+
if (action2 === "save") {
|
|
7739
8051
|
if (parsed.options.token !== void 0) {
|
|
7740
8052
|
throw new Error(
|
|
7741
8053
|
"context save does not accept --token; contexts store scope metadata only"
|
|
@@ -7758,7 +8070,7 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7758
8070
|
}
|
|
7759
8071
|
return;
|
|
7760
8072
|
}
|
|
7761
|
-
if (
|
|
8073
|
+
if (action2 === "remove") {
|
|
7762
8074
|
const name = requireName(parsed);
|
|
7763
8075
|
if (parsed.options.yes !== true) {
|
|
7764
8076
|
throw new Error(`context remove "${name}" requires --yes`);
|
|
@@ -7778,9 +8090,9 @@ async function contextCommand(parsed, deps = {}) {
|
|
|
7778
8090
|
}
|
|
7779
8091
|
return;
|
|
7780
8092
|
}
|
|
7781
|
-
if (
|
|
8093
|
+
if (action2 !== "show") {
|
|
7782
8094
|
throw new Error(
|
|
7783
|
-
`unknown context action "${
|
|
8095
|
+
`unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
|
|
7784
8096
|
);
|
|
7785
8097
|
}
|
|
7786
8098
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -7831,6 +8143,261 @@ function requireName(parsed) {
|
|
|
7831
8143
|
return name;
|
|
7832
8144
|
}
|
|
7833
8145
|
|
|
8146
|
+
// src/help.ts
|
|
8147
|
+
function printHelp(output = console) {
|
|
8148
|
+
output.log(`odla-ai
|
|
8149
|
+
|
|
8150
|
+
Start here:
|
|
8151
|
+
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
8152
|
+
runbooks. Ask BEFORE searching the web or
|
|
8153
|
+
working from memory: runbooks are edited
|
|
8154
|
+
live, so your training data is out of date
|
|
8155
|
+
and this is not.
|
|
8156
|
+
odla-ai runbook impact After a change: which runbooks describe the
|
|
8157
|
+
code you just touched, so you can fix any
|
|
8158
|
+
step it made wrong.
|
|
8159
|
+
|
|
8160
|
+
Usage:
|
|
8161
|
+
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8162
|
+
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8163
|
+
odla-ai doctor [--config odla.config.mjs]
|
|
8164
|
+
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8165
|
+
odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8166
|
+
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8167
|
+
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8168
|
+
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
8169
|
+
odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
|
|
8170
|
+
odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
|
|
8171
|
+
odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8172
|
+
odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
|
|
8173
|
+
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
8174
|
+
odla-ai app refresh-sandbox [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
8175
|
+
odla-ai app go-live [--include-identity] [--include-files] [--dry-run] [--json] --yes
|
|
8176
|
+
odla-ai app promote [--dry-run] [--json] --yes
|
|
8177
|
+
odla-ai app rename <name> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8178
|
+
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8179
|
+
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
8180
|
+
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
8181
|
+
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8182
|
+
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8183
|
+
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8184
|
+
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8185
|
+
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
8186
|
+
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]
|
|
8187
|
+
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
8188
|
+
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]
|
|
8189
|
+
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
8190
|
+
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
8191
|
+
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]
|
|
8192
|
+
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
8193
|
+
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]
|
|
8194
|
+
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
8195
|
+
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
8196
|
+
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
8197
|
+
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
8198
|
+
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
8199
|
+
odla-ai pm handoff --app <id> [--json]
|
|
8200
|
+
odla-ai discuss groups [--json]
|
|
8201
|
+
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
8202
|
+
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
8203
|
+
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
8204
|
+
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
8205
|
+
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
8206
|
+
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
8207
|
+
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
8208
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
8209
|
+
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
8210
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
8211
|
+
odla-ai context list [--json]
|
|
8212
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
8213
|
+
odla-ai context remove <name> --yes [--json]
|
|
8214
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8215
|
+
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8216
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8217
|
+
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8218
|
+
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
8219
|
+
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
8220
|
+
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
8221
|
+
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
8222
|
+
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
8223
|
+
odla-ai runbook get <slug> [--app <id>]
|
|
8224
|
+
odla-ai runbook new <slug> --title <t> --file <path|-> [--summary <s>] [--requires <specs>] [--app <id>]
|
|
8225
|
+
odla-ai runbook edit <slug> [--file <path|->|--body "..."] [--note <why>] [--requires <specs>] [--app <id>]
|
|
8226
|
+
odla-ai runbook import <dir> [--visibility operator|admin] [--dry-run] [--app <id>]
|
|
8227
|
+
odla-ai runbook publish <slug> [--app <id>]
|
|
8228
|
+
odla-ai runbook visibility <slug> <operator|admin> [--app <id>]
|
|
8229
|
+
odla-ai runbook archive <slug> [--app <id>]
|
|
8230
|
+
odla-ai runbook history <slug> [--app <id>] [--json]
|
|
8231
|
+
odla-ai runbook revert <slug> --version <n> [--app <id>]
|
|
8232
|
+
odla-ai runbook rm <slug> [--app <id>]
|
|
8233
|
+
odla-ai capabilities [--json]
|
|
8234
|
+
odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
|
|
8235
|
+
odla-ai admin ai show [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8236
|
+
odla-ai admin ai models [--context <name>] [--provider <id>] [--json]
|
|
8237
|
+
odla-ai admin ai set <purpose> [--context <name>] [--provider <id>] [--model <id>] [--enabled|--no-enabled]
|
|
8238
|
+
[--max-input-bytes <n>] [--max-output-tokens <n>] [--max-calls-per-run <n>] [--json]
|
|
8239
|
+
odla-ai admin ai set security [--context <name>] --discovery-provider <id> --discovery-model <id>
|
|
8240
|
+
--validation-provider <id> --validation-model <id> [--enabled|--no-enabled] [--json]
|
|
8241
|
+
odla-ai admin ai credentials [--context <name>] [--json]
|
|
8242
|
+
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
8243
|
+
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
8244
|
+
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
8245
|
+
odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
|
|
8246
|
+
odla-ai security github disconnect --source <id> [--env dev] [--yes]
|
|
8247
|
+
odla-ai security plan [--env dev] [--json]
|
|
8248
|
+
odla-ai security sources [--env dev] [--json]
|
|
8249
|
+
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
8250
|
+
odla-ai security status <job-id> [--follow] [--json]
|
|
8251
|
+
odla-ai security report <job-id> [--json]
|
|
8252
|
+
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
8253
|
+
odla-ai security run [target] --self --ack-redacted-source
|
|
8254
|
+
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
8255
|
+
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
8256
|
+
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8257
|
+
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
8258
|
+
odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
8259
|
+
odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
|
|
8260
|
+
odla-ai version
|
|
8261
|
+
|
|
8262
|
+
Commands:
|
|
8263
|
+
agent Inspect durable agent wakeups and explicitly requeue a
|
|
8264
|
+
dead-lettered job; JSON output is stable for remote operators.
|
|
8265
|
+
runbook odla's operational procedures, stored in the database and read at
|
|
8266
|
+
the moment they are followed. "ask" gives a written, cited answer;
|
|
8267
|
+
"search" the passages behind it; "get" the whole document.
|
|
8268
|
+
"impact" diffs your working tree against a base ref and names the
|
|
8269
|
+
runbooks that describe what you changed \u2014 run it after touching a
|
|
8270
|
+
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
8271
|
+
checks the corpus the other way: every odla-ai command the
|
|
8272
|
+
runbooks name is held against this CLI's real command surface,
|
|
8273
|
+
and against the minimum versions each runbook declares. A wrong step
|
|
8274
|
+
is fixed with "edit", which takes effect immediately: a runbook is
|
|
8275
|
+
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
8276
|
+
does and what it guarantees is JSDoc, rendered per package at
|
|
8277
|
+
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8278
|
+
a question usually needs both.
|
|
8279
|
+
whoami Report who this terminal is authenticated as, and whether it holds
|
|
8280
|
+
platform admin. A handshake-minted device token is never admin,
|
|
8281
|
+
however the human who approved it is configured.
|
|
8282
|
+
context Explain selected config, platform, app, environment, and
|
|
8283
|
+
developer-token provenance without printing credentials or
|
|
8284
|
+
starting a device handshake. Operator work can run outside a
|
|
8285
|
+
project checkout with explicit flags or ODLA_* environment
|
|
8286
|
+
variables.
|
|
8287
|
+
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8288
|
+
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8289
|
+
doctor Validate and summarize the project config without network calls.
|
|
8290
|
+
config Read-only diff and revision-bound plan for checked-in Registry
|
|
8291
|
+
intent; runtime-owned fields and excluded coverage stay explicit.
|
|
8292
|
+
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8293
|
+
app Archive (suspend, data retained), restore, export, import, or
|
|
8294
|
+
manage the co-owners of the app. Archiving takes every
|
|
8295
|
+
environment's data plane down until restored; permanent deletion
|
|
8296
|
+
has NO CLI \u2014 it requires a signed-in owner in Studio, and no
|
|
8297
|
+
machine or agent credential can purge. "app export" downloads a
|
|
8298
|
+
portable gzipped-JSONL snapshot of one database (--fresh takes a
|
|
8299
|
+
new snapshot first) \u2014 your data is always yours to take.
|
|
8300
|
+
"app import" upserts rows back in from JSON, JSONL, or a
|
|
8301
|
+
{namespace: rows} map (the shape a query returns); it writes only
|
|
8302
|
+
with --yes, so the bare command is a dry run, and it refuses to
|
|
8303
|
+
guess an id mode that would duplicate rows on a re-run.
|
|
8304
|
+
"app refresh-sandbox" replaces the sandbox with a copy of live
|
|
8305
|
+
(the routine dev loop); "app go-live" copies the sandbox into an
|
|
8306
|
+
EMPTY live database, once, at launch; "app promote" pushes only
|
|
8307
|
+
schema, rules and gates up afterwards, leaving live's rows alone.
|
|
8308
|
+
Each prints its plan and writes nothing without --yes, so the
|
|
8309
|
+
bare command is the dry run. Clerk config, triggers, allowlists,
|
|
8310
|
+
secrets and API keys never travel; identity rows stay behind
|
|
8311
|
+
unless --include-identity.
|
|
8312
|
+
"app rename <name>" changes only the display name humans read;
|
|
8313
|
+
the app id is permanent (tenants, URLs and keys embed it), so a
|
|
8314
|
+
rename never re-provisions anything or invalidates a credential.
|
|
8315
|
+
"app owners add <email>" grants a signed-up odla member the SAME
|
|
8316
|
+
full access as you; they then run their own "provision" to mint
|
|
8317
|
+
their own credentials for the shared db (the live one included)
|
|
8318
|
+
\u2014 no secret is ever copied between people.
|
|
8319
|
+
capabilities Show what the CLI automates vs agent edits and human checkpoints.
|
|
8320
|
+
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
8321
|
+
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
8322
|
+
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
8323
|
+
pm Project management (via @odla-ai/pm) shared across the apps you
|
|
8324
|
+
co-own: conformance goals, kanban tasks, decisions, and bugs. With
|
|
8325
|
+
no --app, "list" spans every co-owned project (the cross-project
|
|
8326
|
+
view); with --app it scopes to one. Same device-grant auth as
|
|
8327
|
+
"app". Entities: goal (alias conformance), task (alias kanban),
|
|
8328
|
+
decision, bug. Status changes and comments post to each item's
|
|
8329
|
+
@odla-ai/chat discussion thread.
|
|
8330
|
+
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
8331
|
+
one group per project, topics with replies, @-mentions of people,
|
|
8332
|
+
agents, PM items, and projects. Built for unattended use \u2014 post a
|
|
8333
|
+
question, then "watch" it until someone answers. "watch" exits 75
|
|
8334
|
+
(not 1) when it times out with nothing new, so a script can tell
|
|
8335
|
+
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
8336
|
+
up the exact @[Label](kind/id) markup for a mention.
|
|
8337
|
+
o11y Read one stable status envelope for application RED, current
|
|
8338
|
+
live-sync load/freshness, the protected commit-to-visible
|
|
8339
|
+
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
8340
|
+
runtime metrics, and a machine verdict.
|
|
8341
|
+
--json keeps auth progress on stderr for unattended agents.
|
|
8342
|
+
platform Read canonical fleet health, releases, provider load/freshness,
|
|
8343
|
+
explicit unknowns, and next actions through a read-only grant.
|
|
8344
|
+
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
8345
|
+
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
8346
|
+
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
8347
|
+
copilot, gemini, or agents (repeatable or comma-separated).
|
|
8348
|
+
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
8349
|
+
stdin; set stores a tenant-vault secret and set-clerk-key the
|
|
8350
|
+
reserved Clerk secret key, write-only from stdin or an env var.
|
|
8351
|
+
version Print the CLI version.
|
|
8352
|
+
|
|
8353
|
+
Safety:
|
|
8354
|
+
Every app has two databases on odla.ai: a sandbox (env "dev", tenant
|
|
8355
|
+
<appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
|
|
8356
|
+
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
8357
|
+
the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
|
|
8358
|
+
provision the live database; use --dry-run first to inspect the resolved plan.
|
|
8359
|
+
Provision caches the approved developer token and service credentials under
|
|
8360
|
+
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
8361
|
+
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
8362
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
8363
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
8364
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
8365
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
8366
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
8367
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
8368
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
8369
|
+
context, which beats project config. There is no ambient current context.
|
|
8370
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
8371
|
+
Provision opens the approval page in your browser automatically whenever the
|
|
8372
|
+
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
8373
|
+
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
8374
|
+
Browser launch is best-effort: the printed approval URL is authoritative and
|
|
8375
|
+
agents must relay it to the human verbatim. A started handshake is persisted
|
|
8376
|
+
under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
|
|
8377
|
+
the same code. Outside an interactive terminal the wait is capped (90s by
|
|
8378
|
+
default, --wait <seconds> to change); a still-pending handshake then exits
|
|
8379
|
+
with code 75: relay the URL, wait for approval, and re-run to collect.
|
|
8380
|
+
A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
|
|
8381
|
+
The email is a non-secret identity hint: never provide a password or session
|
|
8382
|
+
token. The matching account must already exist, be signed in, explicitly
|
|
8383
|
+
review the exact code, and finish any current request before claiming another.
|
|
8384
|
+
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
8385
|
+
odla.config.mjs may select the app explicitly but is not required. Code host
|
|
8386
|
+
approval and credential hashes live in odla-ai/db. The host
|
|
8387
|
+
credential is never written under .odla/; it exists only in the foreground
|
|
8388
|
+
"code connect" process and is rotated by the next approved connection.
|
|
8389
|
+
Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
|
|
8390
|
+
OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
|
|
8391
|
+
GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
|
|
8392
|
+
for a PAT or provider key. GitHub read access is separate from the explicit
|
|
8393
|
+
--ack-redacted-source consent and the exact --plan-digest printed by security
|
|
8394
|
+
plan are required before bounded snippets reach System AI.
|
|
8395
|
+
Run security plan first to inspect the admin-selected providers, models,
|
|
8396
|
+
per-route bounds, credential readiness, retention, no-execution boundary,
|
|
8397
|
+
and digest that binds consent to that exact plan.
|
|
8398
|
+
`);
|
|
8399
|
+
}
|
|
8400
|
+
|
|
7834
8401
|
// src/discuss-actions.ts
|
|
7835
8402
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7836
8403
|
async function request(ctx, method, path, body) {
|
|
@@ -7844,8 +8411,8 @@ async function request(ctx, method, path, body) {
|
|
|
7844
8411
|
throw new Error(`discuss ${method} ${path} failed: ${data.error ?? `registry returned ${res.status}`}`);
|
|
7845
8412
|
return data;
|
|
7846
8413
|
}
|
|
7847
|
-
function emit(ctx,
|
|
7848
|
-
if (ctx.json) ctx.out.log(JSON.stringify(
|
|
8414
|
+
function emit(ctx, value2, human) {
|
|
8415
|
+
if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
|
|
7849
8416
|
else human();
|
|
7850
8417
|
}
|
|
7851
8418
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
@@ -7890,8 +8457,8 @@ async function discussList(ctx, parsed) {
|
|
|
7890
8457
|
["limit", "limit"],
|
|
7891
8458
|
["offset", "offset"]
|
|
7892
8459
|
]) {
|
|
7893
|
-
const
|
|
7894
|
-
if (
|
|
8460
|
+
const value2 = stringOpt(parsed.options[flag]);
|
|
8461
|
+
if (value2) query.set(param, value2);
|
|
7895
8462
|
}
|
|
7896
8463
|
const qs = query.toString();
|
|
7897
8464
|
const page = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
|
|
@@ -8088,11 +8655,11 @@ var MAX_CONSECUTIVE_FAILURES = 5;
|
|
|
8088
8655
|
var MAX_BACKOFF_MS = 3e4;
|
|
8089
8656
|
function numberOpt2(parsed, flag, fallback) {
|
|
8090
8657
|
const raw = stringOpt(parsed.options[flag]);
|
|
8091
|
-
const
|
|
8092
|
-
return Number.isFinite(
|
|
8658
|
+
const value2 = raw == null ? NaN : Number(raw);
|
|
8659
|
+
return Number.isFinite(value2) && value2 > 0 ? value2 : fallback;
|
|
8093
8660
|
}
|
|
8094
|
-
function jsonl(ctx, parsed,
|
|
8095
|
-
if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...
|
|
8661
|
+
function jsonl(ctx, parsed, value2) {
|
|
8662
|
+
if (parsed.options.jsonl === true) ctx.out.log(JSON.stringify({ v: 1, ...value2 }));
|
|
8096
8663
|
}
|
|
8097
8664
|
async function discussWatch(ctx, topicId, parsed) {
|
|
8098
8665
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
@@ -8252,8 +8819,8 @@ var ALLOWED = [
|
|
|
8252
8819
|
"platform",
|
|
8253
8820
|
"context"
|
|
8254
8821
|
];
|
|
8255
|
-
function requireId(id,
|
|
8256
|
-
if (!id) throw new Error(`"discuss ${
|
|
8822
|
+
function requireId(id, action2) {
|
|
8823
|
+
if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
8257
8824
|
return id;
|
|
8258
8825
|
}
|
|
8259
8826
|
async function buildContext(parsed, deps) {
|
|
@@ -8287,11 +8854,11 @@ async function buildContext(parsed, deps) {
|
|
|
8287
8854
|
}
|
|
8288
8855
|
async function discussCommand(parsed, deps = {}) {
|
|
8289
8856
|
assertArgs(parsed, ALLOWED, 3);
|
|
8290
|
-
const
|
|
8857
|
+
const action2 = parsed.positionals[1];
|
|
8291
8858
|
const id = parsed.positionals[2];
|
|
8292
|
-
if (!
|
|
8859
|
+
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
8293
8860
|
const ctx = await buildContext(parsed, deps);
|
|
8294
|
-
switch (
|
|
8861
|
+
switch (action2) {
|
|
8295
8862
|
case "groups":
|
|
8296
8863
|
return discussGroups(ctx);
|
|
8297
8864
|
case "list":
|
|
@@ -8313,7 +8880,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
8313
8880
|
return;
|
|
8314
8881
|
}
|
|
8315
8882
|
default:
|
|
8316
|
-
throw new Error(`unknown discuss action "${
|
|
8883
|
+
throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
|
|
8317
8884
|
}
|
|
8318
8885
|
}
|
|
8319
8886
|
|
|
@@ -8354,13 +8921,13 @@ async function pmRequest(ctx, method, path, body) {
|
|
|
8354
8921
|
function collectFields(parsed, allowClear) {
|
|
8355
8922
|
const out = {};
|
|
8356
8923
|
for (const [flag, spec] of Object.entries(FIELD_MAP)) {
|
|
8357
|
-
const
|
|
8358
|
-
if (
|
|
8359
|
-
if (
|
|
8924
|
+
const value2 = parsed.options[flag];
|
|
8925
|
+
if (value2 === void 0 || value2 === true) continue;
|
|
8926
|
+
if (value2 === false) {
|
|
8360
8927
|
if (allowClear) out[spec.key] = null;
|
|
8361
8928
|
continue;
|
|
8362
8929
|
}
|
|
8363
|
-
const text = stringOpt(
|
|
8930
|
+
const text = stringOpt(value2);
|
|
8364
8931
|
out[spec.key] = spec.num ? Number(text) : text;
|
|
8365
8932
|
}
|
|
8366
8933
|
return out;
|
|
@@ -8381,8 +8948,8 @@ function statusCol(entity, r) {
|
|
|
8381
8948
|
function printRecord(ctx, entity, r) {
|
|
8382
8949
|
ctx.out.log(`${r.id} [${statusCol(entity, r)}] ${r.appId} ${r.title ?? ""}`);
|
|
8383
8950
|
}
|
|
8384
|
-
function emit2(ctx,
|
|
8385
|
-
if (ctx.json) ctx.out.log(JSON.stringify(
|
|
8951
|
+
function emit2(ctx, value2, human) {
|
|
8952
|
+
if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
|
|
8386
8953
|
else human();
|
|
8387
8954
|
}
|
|
8388
8955
|
async function pmList(ctx, entity, parsed) {
|
|
@@ -8428,8 +8995,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8428
8995
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8429
8996
|
}
|
|
8430
8997
|
async function pmGet(ctx, entity, id) {
|
|
8431
|
-
const { record:
|
|
8432
|
-
emit2(ctx,
|
|
8998
|
+
const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
8999
|
+
emit2(ctx, record8, () => printRecord(ctx, entity, record8));
|
|
8433
9000
|
}
|
|
8434
9001
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8435
9002
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -8474,9 +9041,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8474
9041
|
]);
|
|
8475
9042
|
const handoff = {
|
|
8476
9043
|
appId,
|
|
8477
|
-
unmetGoals: goals.filter((
|
|
8478
|
-
activeTasks: tasks.filter((
|
|
8479
|
-
openBugs: bugs.filter((
|
|
9044
|
+
unmetGoals: goals.filter((record8) => record8.status !== "met"),
|
|
9045
|
+
activeTasks: tasks.filter((record8) => record8.column !== "done"),
|
|
9046
|
+
openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
|
|
8480
9047
|
};
|
|
8481
9048
|
const result = {
|
|
8482
9049
|
...handoff,
|
|
@@ -8495,10 +9062,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8495
9062
|
]) {
|
|
8496
9063
|
ctx.out.log(`${label}:`);
|
|
8497
9064
|
if (!records.length) ctx.out.log("- (none)");
|
|
8498
|
-
else for (const
|
|
9065
|
+
else for (const record8 of records) printRecord(
|
|
8499
9066
|
ctx,
|
|
8500
9067
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
8501
|
-
|
|
9068
|
+
record8
|
|
8502
9069
|
);
|
|
8503
9070
|
}
|
|
8504
9071
|
});
|
|
@@ -8574,18 +9141,18 @@ var ENTITY_OPTIONS = {
|
|
|
8574
9141
|
done: ["decision"]
|
|
8575
9142
|
}
|
|
8576
9143
|
};
|
|
8577
|
-
function canonicalAction(
|
|
8578
|
-
if (
|
|
8579
|
-
if (
|
|
8580
|
-
if (
|
|
8581
|
-
return
|
|
8582
|
-
}
|
|
8583
|
-
function allowedOptions(entity,
|
|
8584
|
-
const entityOptions =
|
|
8585
|
-
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[
|
|
8586
|
-
}
|
|
8587
|
-
function requireId2(id,
|
|
8588
|
-
if (!id) throw new Error(`"pm ... ${
|
|
9144
|
+
function canonicalAction(action2) {
|
|
9145
|
+
if (action2 === "create") return "add";
|
|
9146
|
+
if (action2 === "update" || action2 === "status" || action2 === "move") return "set";
|
|
9147
|
+
if (action2 === "delete") return "rm";
|
|
9148
|
+
return action2 in ACTION_OPTIONS ? action2 : null;
|
|
9149
|
+
}
|
|
9150
|
+
function allowedOptions(entity, action2) {
|
|
9151
|
+
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
9152
|
+
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
9153
|
+
}
|
|
9154
|
+
function requireId2(id, action2) {
|
|
9155
|
+
if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
8589
9156
|
return id;
|
|
8590
9157
|
}
|
|
8591
9158
|
async function buildContext2(parsed, deps) {
|
|
@@ -8621,29 +9188,136 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8621
9188
|
const entity = ALIASES[word];
|
|
8622
9189
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8623
9190
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
8624
|
-
const
|
|
8625
|
-
if (!
|
|
8626
|
-
assertArgs(parsed, allowedOptions(entity,
|
|
9191
|
+
const action2 = canonicalAction(requestedAction);
|
|
9192
|
+
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
9193
|
+
assertArgs(parsed, allowedOptions(entity, action2), 4);
|
|
8627
9194
|
const ctx = await buildContext2(parsed, deps);
|
|
8628
9195
|
const id = parsed.positionals[3];
|
|
8629
|
-
switch (
|
|
9196
|
+
switch (action2) {
|
|
8630
9197
|
case "list":
|
|
8631
9198
|
return pmList(ctx, entity, parsed);
|
|
8632
9199
|
case "add":
|
|
8633
9200
|
return pmAdd(ctx, entity, parsed);
|
|
8634
9201
|
case "get":
|
|
8635
|
-
return pmGet(ctx, entity, requireId2(id,
|
|
9202
|
+
return pmGet(ctx, entity, requireId2(id, action2));
|
|
8636
9203
|
case "set":
|
|
8637
|
-
return pmSet(ctx, entity, requireId2(id,
|
|
9204
|
+
return pmSet(ctx, entity, requireId2(id, action2), parsed);
|
|
8638
9205
|
case "done":
|
|
8639
|
-
return pmDone(ctx, entity, requireId2(id,
|
|
9206
|
+
return pmDone(ctx, entity, requireId2(id, action2), parsed);
|
|
8640
9207
|
case "comment":
|
|
8641
|
-
return pmComment(ctx, entity, requireId2(id,
|
|
9208
|
+
return pmComment(ctx, entity, requireId2(id, action2), parsed);
|
|
8642
9209
|
case "comments":
|
|
8643
|
-
return pmComments(ctx, entity, requireId2(id,
|
|
9210
|
+
return pmComments(ctx, entity, requireId2(id, action2));
|
|
8644
9211
|
case "rm":
|
|
8645
|
-
return pmRemove(ctx, entity, requireId2(id,
|
|
9212
|
+
return pmRemove(ctx, entity, requireId2(id, action2));
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
|
|
9216
|
+
// src/platform-output.ts
|
|
9217
|
+
function printPlatformStatus(status, out) {
|
|
9218
|
+
out.log(`platform ${status.verdict.status} ${status.observedAt}`);
|
|
9219
|
+
out.log(
|
|
9220
|
+
`fleet ${status.summary.healthy}/${status.summary.services} healthy ${status.summary.required} required ${status.summary.unreachable} unreachable ${status.summary.notConfigured} not configured`
|
|
9221
|
+
);
|
|
9222
|
+
out.log(`catalog ${status.catalog.revision}`);
|
|
9223
|
+
out.log(
|
|
9224
|
+
"service required health probe ms version provider age requests errors cpu p99 us wall p99 us"
|
|
9225
|
+
);
|
|
9226
|
+
for (const service of status.services) {
|
|
9227
|
+
const metrics = service.provider.metrics;
|
|
9228
|
+
out.log([
|
|
9229
|
+
service.id,
|
|
9230
|
+
service.required ? "yes" : "no",
|
|
9231
|
+
service.health.status,
|
|
9232
|
+
value(service.health.latencyMs),
|
|
9233
|
+
service.release.observedVersionId ?? "unknown",
|
|
9234
|
+
service.provider.status,
|
|
9235
|
+
age(service.provider.ageMs),
|
|
9236
|
+
value(metrics?.requests),
|
|
9237
|
+
value(metrics?.errors),
|
|
9238
|
+
value(metrics?.cpuTimeP99),
|
|
9239
|
+
value(metrics?.wallTimeP99)
|
|
9240
|
+
].join(" "));
|
|
9241
|
+
}
|
|
9242
|
+
if (status.verdict.reasons.length) {
|
|
9243
|
+
out.log(`reasons ${status.verdict.reasons.join(", ")}`);
|
|
9244
|
+
}
|
|
9245
|
+
for (const action2 of status.nextActions) {
|
|
9246
|
+
out.log(`next ${action2.service} ${action2.code} ${action2.message}`);
|
|
9247
|
+
}
|
|
9248
|
+
}
|
|
9249
|
+
function value(input) {
|
|
9250
|
+
return input === null || input === void 0 ? "unknown" : String(input);
|
|
9251
|
+
}
|
|
9252
|
+
function age(input) {
|
|
9253
|
+
if (input === null) return "unknown";
|
|
9254
|
+
if (input < 1e3) return `${input}ms`;
|
|
9255
|
+
if (input < 6e4) return `${Math.round(input / 1e3)}s`;
|
|
9256
|
+
return `${Math.round(input / 6e4)}m`;
|
|
9257
|
+
}
|
|
9258
|
+
|
|
9259
|
+
// src/platform-command.ts
|
|
9260
|
+
async function platformCommand(parsed, deps = {}) {
|
|
9261
|
+
assertArgs(
|
|
9262
|
+
parsed,
|
|
9263
|
+
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9264
|
+
2
|
|
9265
|
+
);
|
|
9266
|
+
const action2 = parsed.positionals[1];
|
|
9267
|
+
if (action2 !== "status") {
|
|
9268
|
+
throw new Error(
|
|
9269
|
+
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9270
|
+
);
|
|
9271
|
+
}
|
|
9272
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9273
|
+
allowMissingConfig: true
|
|
9274
|
+
});
|
|
9275
|
+
const platform = context.platform.value;
|
|
9276
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9277
|
+
const out = deps.stdout ?? console;
|
|
9278
|
+
const token = await resolveAdminPlatformToken({
|
|
9279
|
+
platform,
|
|
9280
|
+
scope: "platform:status:read",
|
|
9281
|
+
token: stringOpt(parsed.options.token),
|
|
9282
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9283
|
+
email: stringOpt(parsed.options.email),
|
|
9284
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9285
|
+
fetch: doFetch,
|
|
9286
|
+
stdout: out,
|
|
9287
|
+
openApprovalUrl: deps.openUrl,
|
|
9288
|
+
label: "odla CLI (platform fleet status)"
|
|
9289
|
+
});
|
|
9290
|
+
const response2 = await doFetch(`${platform}/registry/platform/status`, {
|
|
9291
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9292
|
+
});
|
|
9293
|
+
const body = await response2.json().catch(() => null);
|
|
9294
|
+
if (!response2.ok) {
|
|
9295
|
+
throw new Error(
|
|
9296
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage(body)}`
|
|
9297
|
+
);
|
|
9298
|
+
}
|
|
9299
|
+
if (!isPlatformStatus(body)) {
|
|
9300
|
+
throw new Error("platform returned an invalid odla.platform-status/v1 envelope");
|
|
8646
9301
|
}
|
|
9302
|
+
if (parsed.options.json === true) {
|
|
9303
|
+
out.log(JSON.stringify(body, null, 2));
|
|
9304
|
+
} else {
|
|
9305
|
+
printPlatformStatus(body, out);
|
|
9306
|
+
}
|
|
9307
|
+
}
|
|
9308
|
+
function isPlatformStatus(value2) {
|
|
9309
|
+
if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9310
|
+
if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9311
|
+
if (!record5(value2.catalog) || !record5(value2.summary)) return false;
|
|
9312
|
+
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9313
|
+
}
|
|
9314
|
+
function apiMessage(value2) {
|
|
9315
|
+
if (!record5(value2)) return "request failed";
|
|
9316
|
+
const error = record5(value2.error) ? value2.error : value2;
|
|
9317
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9318
|
+
}
|
|
9319
|
+
function record5(value2) {
|
|
9320
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
8647
9321
|
}
|
|
8648
9322
|
|
|
8649
9323
|
// src/o11y-verdict.ts
|
|
@@ -8683,7 +9357,7 @@ function statusVerdict(reads) {
|
|
|
8683
9357
|
severity: "degraded"
|
|
8684
9358
|
});
|
|
8685
9359
|
}
|
|
8686
|
-
const performance =
|
|
9360
|
+
const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
8687
9361
|
if (performance?.status === "unavailable") {
|
|
8688
9362
|
reasons.push({
|
|
8689
9363
|
source: "liveSync",
|
|
@@ -8764,11 +9438,11 @@ function statusVerdict(reads) {
|
|
|
8764
9438
|
reasons
|
|
8765
9439
|
};
|
|
8766
9440
|
}
|
|
8767
|
-
function
|
|
8768
|
-
return Boolean(
|
|
9441
|
+
function record6(value2) {
|
|
9442
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
8769
9443
|
}
|
|
8770
|
-
function numeric2(
|
|
8771
|
-
return typeof
|
|
9444
|
+
function numeric2(value2) {
|
|
9445
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
8772
9446
|
}
|
|
8773
9447
|
function collectorReason(read3, fallback) {
|
|
8774
9448
|
const reasons = read3.body.reasons;
|
|
@@ -8792,7 +9466,7 @@ function printO11yStatus(status, out) {
|
|
|
8792
9466
|
out.log(
|
|
8793
9467
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
8794
9468
|
);
|
|
8795
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
9469
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
|
|
8796
9470
|
const requests = routes.reduce(
|
|
8797
9471
|
(total, row) => total + numeric3(row.requests),
|
|
8798
9472
|
0
|
|
@@ -8804,39 +9478,39 @@ function printO11yStatus(status, out) {
|
|
|
8804
9478
|
out.log(
|
|
8805
9479
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
8806
9480
|
);
|
|
8807
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
9481
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
|
|
8808
9482
|
out.log(
|
|
8809
9483
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
8810
9484
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
8811
9485
|
).join(", ") : "none observed"}`
|
|
8812
9486
|
);
|
|
8813
9487
|
out.log(liveSyncLine(status.liveSync));
|
|
8814
|
-
const canaryDurations =
|
|
9488
|
+
const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
8815
9489
|
out.log(
|
|
8816
9490
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
8817
9491
|
);
|
|
8818
|
-
const collectorIngest =
|
|
8819
|
-
const collectorStorage =
|
|
9492
|
+
const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9493
|
+
const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
8820
9494
|
out.log(
|
|
8821
9495
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8822
9496
|
);
|
|
8823
|
-
const providerMetrics =
|
|
8824
|
-
const providerCapacity =
|
|
8825
|
-
const workerMemory =
|
|
9497
|
+
const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9498
|
+
const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9499
|
+
const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
8826
9500
|
out.log(
|
|
8827
9501
|
`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`
|
|
8828
9502
|
);
|
|
8829
9503
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
8830
9504
|
out.log(line);
|
|
8831
9505
|
}
|
|
8832
|
-
const coverage =
|
|
8833
|
-
const coverageCounts =
|
|
8834
|
-
const coverageBudget =
|
|
9506
|
+
const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9507
|
+
const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9508
|
+
const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
8835
9509
|
out.log(
|
|
8836
9510
|
`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`
|
|
8837
9511
|
);
|
|
8838
9512
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
8839
|
-
const providerFreshness =
|
|
9513
|
+
const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
8840
9514
|
out.log(
|
|
8841
9515
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
8842
9516
|
);
|
|
@@ -8845,17 +9519,17 @@ function printO11yStatus(status, out) {
|
|
|
8845
9519
|
);
|
|
8846
9520
|
}
|
|
8847
9521
|
function providerCapacityLines(read3) {
|
|
8848
|
-
const resources =
|
|
8849
|
-
const durableObjects =
|
|
8850
|
-
const periodic =
|
|
8851
|
-
const storage =
|
|
8852
|
-
const d1 =
|
|
8853
|
-
const d1Activity =
|
|
8854
|
-
const d1Storage =
|
|
8855
|
-
const d1Latency =
|
|
8856
|
-
const r2 =
|
|
8857
|
-
const r2Operations =
|
|
8858
|
-
const r2Storage =
|
|
9522
|
+
const resources = record7(read3.body.resources) ? read3.body.resources : {};
|
|
9523
|
+
const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
|
|
9524
|
+
const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9525
|
+
const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9526
|
+
const d1 = record7(resources.d1) ? resources.d1 : {};
|
|
9527
|
+
const d1Activity = record7(d1.activity) ? d1.activity : {};
|
|
9528
|
+
const d1Storage = record7(d1.storage) ? d1.storage : {};
|
|
9529
|
+
const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
|
|
9530
|
+
const r2 = record7(resources.r2) ? resources.r2 : {};
|
|
9531
|
+
const r2Operations = record7(r2.operations) ? r2.operations : {};
|
|
9532
|
+
const r2Storage = record7(r2.storage) ? r2.storage : {};
|
|
8859
9533
|
const status = String(
|
|
8860
9534
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
8861
9535
|
);
|
|
@@ -8866,42 +9540,42 @@ function providerCapacityLines(read3) {
|
|
|
8866
9540
|
];
|
|
8867
9541
|
}
|
|
8868
9542
|
function liveSyncLine(read3) {
|
|
8869
|
-
const performance =
|
|
8870
|
-
const commitToSend =
|
|
9543
|
+
const performance = record7(read3.body.performance) ? read3.body.performance : {};
|
|
9544
|
+
const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
|
|
8871
9545
|
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`;
|
|
8872
9546
|
}
|
|
8873
|
-
function
|
|
8874
|
-
return Boolean(
|
|
9547
|
+
function record7(value2) {
|
|
9548
|
+
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
8875
9549
|
}
|
|
8876
|
-
function numeric3(
|
|
8877
|
-
return typeof
|
|
9550
|
+
function numeric3(value2) {
|
|
9551
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : 0;
|
|
8878
9552
|
}
|
|
8879
|
-
function optionalNumeric(
|
|
8880
|
-
return typeof
|
|
9553
|
+
function optionalNumeric(value2) {
|
|
9554
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ms` : "\u2014";
|
|
8881
9555
|
}
|
|
8882
|
-
function optionalAge(
|
|
8883
|
-
if (typeof
|
|
8884
|
-
return `${Math.max(0, Math.round(
|
|
9556
|
+
function optionalAge(value2) {
|
|
9557
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return "unknown";
|
|
9558
|
+
return `${Math.max(0, Math.round(value2 / 1e3))}s`;
|
|
8885
9559
|
}
|
|
8886
|
-
function optionalPercent(
|
|
8887
|
-
return typeof
|
|
9560
|
+
function optionalPercent(value2) {
|
|
9561
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.round(value2 * 100)}%` : "\u2014";
|
|
8888
9562
|
}
|
|
8889
|
-
function optionalBytes(
|
|
8890
|
-
if (typeof
|
|
8891
|
-
if (
|
|
8892
|
-
return `${(
|
|
9563
|
+
function optionalBytes(value2) {
|
|
9564
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) return "\u2014";
|
|
9565
|
+
if (value2 >= 1024 * 1024 * 1024) {
|
|
9566
|
+
return `${(value2 / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8893
9567
|
}
|
|
8894
|
-
if (
|
|
8895
|
-
return `${(
|
|
9568
|
+
if (value2 >= 1024 * 1024) {
|
|
9569
|
+
return `${(value2 / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8896
9570
|
}
|
|
8897
|
-
if (
|
|
8898
|
-
return `${Math.round(
|
|
9571
|
+
if (value2 >= 1024) return `${(value2 / 1024).toFixed(1)} KiB`;
|
|
9572
|
+
return `${Math.round(value2)} B`;
|
|
8899
9573
|
}
|
|
8900
|
-
function optionalCount(
|
|
8901
|
-
return typeof
|
|
9574
|
+
function optionalCount(value2, unit) {
|
|
9575
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${value2} ${unit}` : `\u2014 ${unit}`;
|
|
8902
9576
|
}
|
|
8903
|
-
function optionalAgeSeconds(
|
|
8904
|
-
return typeof
|
|
9577
|
+
function optionalAgeSeconds(value2) {
|
|
9578
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${Math.max(0, Math.round(value2))}s` : "unknown";
|
|
8905
9579
|
}
|
|
8906
9580
|
|
|
8907
9581
|
// src/o11y-command.ts
|
|
@@ -8921,10 +9595,10 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8921
9595
|
],
|
|
8922
9596
|
2
|
|
8923
9597
|
);
|
|
8924
|
-
const
|
|
8925
|
-
if (
|
|
9598
|
+
const action2 = parsed.positionals[1];
|
|
9599
|
+
if (action2 !== "status") {
|
|
8926
9600
|
throw new Error(
|
|
8927
|
-
`unknown o11y action "${
|
|
9601
|
+
`unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
|
|
8928
9602
|
);
|
|
8929
9603
|
}
|
|
8930
9604
|
const minutes = statusMinutes(
|
|
@@ -9031,11 +9705,11 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9031
9705
|
}
|
|
9032
9706
|
printO11yStatus(status, out);
|
|
9033
9707
|
}
|
|
9034
|
-
function statusMinutes(
|
|
9035
|
-
if (!Number.isSafeInteger(
|
|
9708
|
+
function statusMinutes(value2) {
|
|
9709
|
+
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 7 * 24 * 60) {
|
|
9036
9710
|
throw new Error("--minutes must be an integer from 1 to 10080");
|
|
9037
9711
|
}
|
|
9038
|
-
return
|
|
9712
|
+
return value2;
|
|
9039
9713
|
}
|
|
9040
9714
|
async function read2(url, headers, doFetch) {
|
|
9041
9715
|
const response2 = await doFetch(url, { headers });
|
|
@@ -9043,8 +9717,8 @@ async function read2(url, headers, doFetch) {
|
|
|
9043
9717
|
let body = {};
|
|
9044
9718
|
if (text) {
|
|
9045
9719
|
try {
|
|
9046
|
-
const
|
|
9047
|
-
body =
|
|
9720
|
+
const value2 = JSON.parse(text);
|
|
9721
|
+
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
9048
9722
|
} catch {
|
|
9049
9723
|
body = { message: text.slice(0, 300) };
|
|
9050
9724
|
}
|
|
@@ -9053,7 +9727,7 @@ async function read2(url, headers, doFetch) {
|
|
|
9053
9727
|
}
|
|
9054
9728
|
|
|
9055
9729
|
// src/provision.ts
|
|
9056
|
-
var
|
|
9730
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
9057
9731
|
var import_ai3 = require("@odla-ai/ai");
|
|
9058
9732
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
9059
9733
|
|
|
@@ -9063,7 +9737,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9063
9737
|
const base = `${endpoint}/app/${encodeURIComponent(tenantId)}`;
|
|
9064
9738
|
for (const integration of integrations) {
|
|
9065
9739
|
for (const seed of integration.seeds ?? []) {
|
|
9066
|
-
const payload = await
|
|
9740
|
+
const payload = await postJson3(doFetch, `${base}/query`, dbKey, {
|
|
9067
9741
|
query: { [seed.ns]: { $: { where: { [seed.key.attr]: seed.key.value }, limit: 1 } } }
|
|
9068
9742
|
});
|
|
9069
9743
|
const rows = isRecord7(payload) && isRecord7(payload.result) ? payload.result[seed.ns] : void 0;
|
|
@@ -9074,7 +9748,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9074
9748
|
out.log(`${env}: integration ${integration.id} seed ${seed.id} already exists`);
|
|
9075
9749
|
continue;
|
|
9076
9750
|
}
|
|
9077
|
-
await
|
|
9751
|
+
await postJson3(doFetch, `${base}/transact`, dbKey, {
|
|
9078
9752
|
mutationId: `integration:${integration.id}:seed:${seed.id}`,
|
|
9079
9753
|
ops: [{
|
|
9080
9754
|
t: "update",
|
|
@@ -9089,7 +9763,7 @@ async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, int
|
|
|
9089
9763
|
}
|
|
9090
9764
|
}
|
|
9091
9765
|
}
|
|
9092
|
-
async function
|
|
9766
|
+
async function postJson3(doFetch, url, bearer, body) {
|
|
9093
9767
|
const res = await doFetch(url, {
|
|
9094
9768
|
method: "POST",
|
|
9095
9769
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
@@ -9098,8 +9772,8 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
9098
9772
|
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await responseText(res)}`);
|
|
9099
9773
|
return res.json().catch(() => ({}));
|
|
9100
9774
|
}
|
|
9101
|
-
function isRecord7(
|
|
9102
|
-
return
|
|
9775
|
+
function isRecord7(value2) {
|
|
9776
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
9103
9777
|
}
|
|
9104
9778
|
async function responseText(res) {
|
|
9105
9779
|
try {
|
|
@@ -9110,9 +9784,9 @@ async function responseText(res) {
|
|
|
9110
9784
|
}
|
|
9111
9785
|
|
|
9112
9786
|
// src/provision-credentials.ts
|
|
9113
|
-
var
|
|
9787
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
9114
9788
|
async function provisionEnvCredentials(opts) {
|
|
9115
|
-
const tenantId = (0,
|
|
9789
|
+
const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9116
9790
|
const prior = opts.credentials?.envs[opts.env];
|
|
9117
9791
|
let credentials = opts.credentials;
|
|
9118
9792
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9168,14 +9842,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
9168
9842
|
appId: tenantId
|
|
9169
9843
|
})
|
|
9170
9844
|
});
|
|
9171
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
9845
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText5(created)}`);
|
|
9172
9846
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
9173
9847
|
method: "POST",
|
|
9174
9848
|
headers,
|
|
9175
9849
|
body: "{}"
|
|
9176
9850
|
});
|
|
9177
9851
|
}
|
|
9178
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
9852
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9179
9853
|
const body = await res.json();
|
|
9180
9854
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
9181
9855
|
return body.key;
|
|
@@ -9191,58 +9865,11 @@ async function issueO11yToken(opts) {
|
|
|
9191
9865
|
`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`
|
|
9192
9866
|
);
|
|
9193
9867
|
}
|
|
9194
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
9868
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9195
9869
|
const body = await res.json();
|
|
9196
9870
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
9197
9871
|
return body.token;
|
|
9198
9872
|
}
|
|
9199
|
-
async function safeText4(res) {
|
|
9200
|
-
try {
|
|
9201
|
-
return redactSecrets((await res.text()).slice(0, 500));
|
|
9202
|
-
} catch {
|
|
9203
|
-
return "";
|
|
9204
|
-
}
|
|
9205
|
-
}
|
|
9206
|
-
|
|
9207
|
-
// src/provision-helpers.ts
|
|
9208
|
-
var import_ai2 = require("@odla-ai/ai");
|
|
9209
|
-
var import_apps6 = require("@odla-ai/apps");
|
|
9210
|
-
function defaultSecretName(provider) {
|
|
9211
|
-
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
9212
|
-
return names[provider] ?? `${provider}_api_key`;
|
|
9213
|
-
}
|
|
9214
|
-
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
9215
|
-
const tenantId = (0, import_apps6.tenantIdFor)(cfg.app.id, env);
|
|
9216
|
-
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
9217
|
-
headers: { authorization: `Bearer ${token}` }
|
|
9218
|
-
});
|
|
9219
|
-
if (res.ok || res.status === 404) return;
|
|
9220
|
-
if (res.status === 403) {
|
|
9221
|
-
throw new Error(
|
|
9222
|
-
`${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`
|
|
9223
|
-
);
|
|
9224
|
-
}
|
|
9225
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
9226
|
-
}
|
|
9227
|
-
async function postJson3(doFetch, url, bearer, body) {
|
|
9228
|
-
const res = await doFetch(url, {
|
|
9229
|
-
method: "POST",
|
|
9230
|
-
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
9231
|
-
body: JSON.stringify(body)
|
|
9232
|
-
});
|
|
9233
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
9234
|
-
}
|
|
9235
|
-
function normalizeClerkConfig(value) {
|
|
9236
|
-
if (!value) return null;
|
|
9237
|
-
if (typeof value === "string") {
|
|
9238
|
-
const publishableKey2 = envValue(value);
|
|
9239
|
-
return publishableKey2 ? { publishableKey: publishableKey2 } : null;
|
|
9240
|
-
}
|
|
9241
|
-
if (typeof value !== "object") return null;
|
|
9242
|
-
const cfg = value;
|
|
9243
|
-
const publishableKey = envValue(cfg.publishableKey);
|
|
9244
|
-
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
9245
|
-
}
|
|
9246
9873
|
async function safeText5(res) {
|
|
9247
9874
|
try {
|
|
9248
9875
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
@@ -9321,7 +9948,7 @@ async function provision(options) {
|
|
|
9321
9948
|
}
|
|
9322
9949
|
const doFetch = options.fetch ?? fetch;
|
|
9323
9950
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9324
|
-
const apps = (0,
|
|
9951
|
+
const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9325
9952
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9326
9953
|
if (existing) {
|
|
9327
9954
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9332,7 +9959,7 @@ async function provision(options) {
|
|
|
9332
9959
|
for (const env of cfg.envs) {
|
|
9333
9960
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9334
9961
|
}
|
|
9335
|
-
const serviceOrder = (0,
|
|
9962
|
+
const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
|
|
9336
9963
|
for (const env of cfg.envs) {
|
|
9337
9964
|
for (const service of serviceOrder) {
|
|
9338
9965
|
if (service === "ai") {
|
|
@@ -9365,7 +9992,7 @@ async function provision(options) {
|
|
|
9365
9992
|
}
|
|
9366
9993
|
}
|
|
9367
9994
|
for (const env of cfg.envs) {
|
|
9368
|
-
const tenantId = (0,
|
|
9995
|
+
const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
|
|
9369
9996
|
credentials = await provisionEnvCredentials({
|
|
9370
9997
|
cfg,
|
|
9371
9998
|
env,
|
|
@@ -9398,11 +10025,11 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
9398
10025
|
}
|
|
9399
10026
|
}
|
|
9400
10027
|
if (schema && dbKey) {
|
|
9401
|
-
await
|
|
10028
|
+
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/schema`, dbKey, { schema });
|
|
9402
10029
|
out.log(`${env}: schema pushed`);
|
|
9403
10030
|
}
|
|
9404
10031
|
if (rules) {
|
|
9405
|
-
await
|
|
10032
|
+
await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(tenantId)}/admin/rules`, token, rules);
|
|
9406
10033
|
out.log(`${env}: rules pushed (${Object.keys(rules).length} namespaces)`);
|
|
9407
10034
|
}
|
|
9408
10035
|
if (dbKey) {
|
|
@@ -9499,6 +10126,7 @@ var COMMAND_SURFACE = {
|
|
|
9499
10126
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
9500
10127
|
capabilities: {},
|
|
9501
10128
|
code: { connect: {} },
|
|
10129
|
+
config: { diff: {}, plan: {} },
|
|
9502
10130
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
9503
10131
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
9504
10132
|
discuss: {
|
|
@@ -9516,6 +10144,7 @@ var COMMAND_SURFACE = {
|
|
|
9516
10144
|
help: {},
|
|
9517
10145
|
init: {},
|
|
9518
10146
|
o11y: { status: {} },
|
|
10147
|
+
platform: { status: {} },
|
|
9519
10148
|
pm: {
|
|
9520
10149
|
...PM_ENTITIES,
|
|
9521
10150
|
handoff: {}
|
|
@@ -9608,7 +10237,7 @@ function recordInvocation(parsed) {
|
|
|
9608
10237
|
try {
|
|
9609
10238
|
const entry = {
|
|
9610
10239
|
path: invocationPath(parsed.positionals),
|
|
9611
|
-
options: Object.entries(parsed.options).map(([name,
|
|
10240
|
+
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
9612
10241
|
};
|
|
9613
10242
|
if (!entry.path.length) return;
|
|
9614
10243
|
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
@@ -9622,10 +10251,10 @@ var import_node_fs16 = require("fs");
|
|
|
9622
10251
|
|
|
9623
10252
|
// src/runbook-requires.ts
|
|
9624
10253
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
9625
|
-
function parseRequires(
|
|
9626
|
-
if (!
|
|
10254
|
+
function parseRequires(value2) {
|
|
10255
|
+
if (!value2) return [];
|
|
9627
10256
|
const out = [];
|
|
9628
|
-
for (const token of
|
|
10257
|
+
for (const token of value2.split(/[\s,]+/).filter(Boolean)) {
|
|
9629
10258
|
const match = SPEC.exec(token);
|
|
9630
10259
|
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
9631
10260
|
}
|
|
@@ -9791,7 +10420,7 @@ async function runbookRemove(ctx, slug) {
|
|
|
9791
10420
|
|
|
9792
10421
|
// src/runbook-import.ts
|
|
9793
10422
|
var import_node_fs17 = require("fs");
|
|
9794
|
-
var
|
|
10423
|
+
var import_node_path14 = require("path");
|
|
9795
10424
|
function parseRunbook(text, slug) {
|
|
9796
10425
|
let rest = text;
|
|
9797
10426
|
const meta = {};
|
|
@@ -9801,9 +10430,9 @@ function parseRunbook(text, slug) {
|
|
|
9801
10430
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
9802
10431
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
9803
10432
|
if (!pair) continue;
|
|
9804
|
-
const
|
|
9805
|
-
if (pair[1] === "summary") meta.summary =
|
|
9806
|
-
if (pair[1] === "tags") meta.tags =
|
|
10433
|
+
const value2 = pair[2].trim().replace(/^["']|["']$/g, "");
|
|
10434
|
+
if (pair[1] === "summary") meta.summary = value2;
|
|
10435
|
+
if (pair[1] === "tags") meta.tags = value2.replace(/^\[|\]$/g, "").trim();
|
|
9807
10436
|
}
|
|
9808
10437
|
}
|
|
9809
10438
|
const lines = rest.split("\n");
|
|
@@ -9820,8 +10449,8 @@ function readRunbookDir(dir) {
|
|
|
9820
10449
|
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9821
10450
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9822
10451
|
return files.map((file) => {
|
|
9823
|
-
const slug = (0,
|
|
9824
|
-
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0,
|
|
10452
|
+
const slug = (0, import_node_path14.basename)(file, ".md");
|
|
10453
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
|
|
9825
10454
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9826
10455
|
});
|
|
9827
10456
|
}
|
|
@@ -9894,7 +10523,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
9894
10523
|
// src/runbook-impact.ts
|
|
9895
10524
|
var import_node_child_process7 = require("child_process");
|
|
9896
10525
|
var import_node_fs18 = require("fs");
|
|
9897
|
-
var
|
|
10526
|
+
var import_node_path15 = require("path");
|
|
9898
10527
|
|
|
9899
10528
|
// src/runbook-impact-scan.ts
|
|
9900
10529
|
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$]*)/;
|
|
@@ -9920,7 +10549,7 @@ var NOISE = /* @__PURE__ */ new Set([
|
|
|
9920
10549
|
"and",
|
|
9921
10550
|
"for"
|
|
9922
10551
|
]);
|
|
9923
|
-
var words = (
|
|
10552
|
+
var words = (value2) => value2.replace(/^@[\w-]+\//, "").split(/[^A-Za-z0-9]+/).filter((w) => w.length > 1 && !NOISE.has(w.toLowerCase()));
|
|
9924
10553
|
function namedExports(clause) {
|
|
9925
10554
|
return clause.split(",").map((part) => part.trim().split(/\s+as\s+/)[0]?.trim() ?? "").filter((name) => /^[A-Za-z_$][\w$]*$/.test(name) && name !== "type");
|
|
9926
10555
|
}
|
|
@@ -10063,7 +10692,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10063
10692
|
}
|
|
10064
10693
|
function manifestLabeller(root) {
|
|
10065
10694
|
return (workspace) => {
|
|
10066
|
-
const manifest = (0,
|
|
10695
|
+
const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
|
|
10067
10696
|
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
10068
10697
|
try {
|
|
10069
10698
|
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
@@ -10133,7 +10762,7 @@ function report3(ctx, impacts) {
|
|
|
10133
10762
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10134
10763
|
const cwd = deps.cwd ?? process.cwd();
|
|
10135
10764
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10136
|
-
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0,
|
|
10765
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
|
|
10137
10766
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10138
10767
|
if (!surfaces.length) {
|
|
10139
10768
|
return ctx.out.log(
|
|
@@ -10268,13 +10897,13 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10268
10897
|
var import_node_child_process8 = require("child_process");
|
|
10269
10898
|
var import_node_fs19 = require("fs");
|
|
10270
10899
|
var import_node_os5 = require("os");
|
|
10271
|
-
var
|
|
10900
|
+
var import_node_path16 = require("path");
|
|
10272
10901
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
10273
10902
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10274
10903
|
function resolveEditor(env = import_node_process12.default.env) {
|
|
10275
10904
|
for (const name of EDITOR_ENV) {
|
|
10276
|
-
const
|
|
10277
|
-
if (
|
|
10905
|
+
const value2 = env[name];
|
|
10906
|
+
if (value2 && value2.trim()) return value2.trim();
|
|
10278
10907
|
}
|
|
10279
10908
|
return null;
|
|
10280
10909
|
}
|
|
@@ -10294,8 +10923,8 @@ function editText(initial, slug, deps = {}) {
|
|
|
10294
10923
|
);
|
|
10295
10924
|
if (!interactive())
|
|
10296
10925
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10297
|
-
const dir = (0, import_node_fs19.mkdtempSync)((0,
|
|
10298
|
-
const file = (0,
|
|
10926
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
10927
|
+
const file = (0, import_node_path16.join)(dir, `${slug}.md`);
|
|
10299
10928
|
try {
|
|
10300
10929
|
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
10301
10930
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
@@ -10403,12 +11032,12 @@ var ALLOWED2 = [
|
|
|
10403
11032
|
"platform",
|
|
10404
11033
|
"context"
|
|
10405
11034
|
];
|
|
10406
|
-
function requireSlug(slug,
|
|
10407
|
-
if (!slug) throw new Error(`"runbook ${
|
|
11035
|
+
function requireSlug(slug, action2) {
|
|
11036
|
+
if (!slug) throw new Error(`"runbook ${action2}" needs a slug, e.g. "odla-ai runbook ${action2} release"`);
|
|
10408
11037
|
return slug;
|
|
10409
11038
|
}
|
|
10410
11039
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
10411
|
-
async function buildContext3(parsed, deps,
|
|
11040
|
+
async function buildContext3(parsed, deps, action2) {
|
|
10412
11041
|
const appIdOption = stringOpt(parsed.options.app);
|
|
10413
11042
|
const context = await resolveOperatorContext(parsed, {
|
|
10414
11043
|
allowMissingConfig: true
|
|
@@ -10418,7 +11047,7 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10418
11047
|
const out = deps.stdout ?? console;
|
|
10419
11048
|
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
10420
11049
|
const dryRun = parsed.options["dry-run"] === true;
|
|
10421
|
-
if (
|
|
11050
|
+
if (action2 === "import" && dryRun) {
|
|
10422
11051
|
return {
|
|
10423
11052
|
platformUrl: cfg.platformUrl,
|
|
10424
11053
|
token: "",
|
|
@@ -10428,12 +11057,12 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10428
11057
|
appId
|
|
10429
11058
|
};
|
|
10430
11059
|
}
|
|
10431
|
-
const needsCapability = WRITES.has(
|
|
11060
|
+
const needsCapability = WRITES.has(action2) && !dryRun && appId === PLATFORM_SCOPE && !stringOpt(parsed.options.token);
|
|
10432
11061
|
const token = needsCapability ? await getScopedPlatformToken({
|
|
10433
11062
|
platform: cfg.platformUrl,
|
|
10434
11063
|
scope: "platform:runbook:write",
|
|
10435
11064
|
email: stringOpt(parsed.options.email),
|
|
10436
|
-
label: `odla CLI (runbook ${
|
|
11065
|
+
label: `odla CLI (runbook ${action2})`,
|
|
10437
11066
|
fetch: doFetch,
|
|
10438
11067
|
stdout: out,
|
|
10439
11068
|
openApprovalUrl: deps.openUrl,
|
|
@@ -10468,11 +11097,11 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10468
11097
|
};
|
|
10469
11098
|
}
|
|
10470
11099
|
async function runbookCommand(parsed, deps = {}) {
|
|
10471
|
-
const
|
|
10472
|
-
assertArgs(parsed, ALLOWED2,
|
|
10473
|
-
const ctx = await buildContext3(parsed, deps,
|
|
11100
|
+
const action2 = parsed.positionals[1] ?? "list";
|
|
11101
|
+
assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
|
|
11102
|
+
const ctx = await buildContext3(parsed, deps, action2);
|
|
10474
11103
|
const slug = parsed.positionals[2];
|
|
10475
|
-
switch (
|
|
11104
|
+
switch (action2) {
|
|
10476
11105
|
case "list":
|
|
10477
11106
|
return runbookList(ctx, parsed.options.all === true, stringOpt(parsed.options.q));
|
|
10478
11107
|
case "ask": {
|
|
@@ -10539,9 +11168,9 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
10539
11168
|
});
|
|
10540
11169
|
}
|
|
10541
11170
|
case "visibility": {
|
|
10542
|
-
const
|
|
10543
|
-
if (!
|
|
10544
|
-
return runbookVisibility(ctx, requireSlug(slug, "visibility"),
|
|
11171
|
+
const value2 = parsed.positionals[3] ?? stringOpt(parsed.options.visibility);
|
|
11172
|
+
if (!value2) throw new Error('"runbook visibility" needs operator|admin, e.g. "runbook visibility release operator"');
|
|
11173
|
+
return runbookVisibility(ctx, requireSlug(slug, "visibility"), value2);
|
|
10545
11174
|
}
|
|
10546
11175
|
case "publish":
|
|
10547
11176
|
return runbookStatus(ctx, requireSlug(slug, "publish"), "published");
|
|
@@ -10557,7 +11186,7 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
10557
11186
|
case "rm":
|
|
10558
11187
|
return runbookRemove(ctx, requireSlug(slug, "rm"));
|
|
10559
11188
|
default:
|
|
10560
|
-
throw new Error(`unknown runbook action "${
|
|
11189
|
+
throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
|
|
10561
11190
|
}
|
|
10562
11191
|
}
|
|
10563
11192
|
|
|
@@ -10597,13 +11226,13 @@ async function interactiveConfirmation(message2, dependencies) {
|
|
|
10597
11226
|
}
|
|
10598
11227
|
}
|
|
10599
11228
|
function requiredSecurityPositional(parsed, index, label) {
|
|
10600
|
-
const
|
|
10601
|
-
if (!
|
|
10602
|
-
return
|
|
11229
|
+
const value2 = parsed.positionals[index];
|
|
11230
|
+
if (!value2) throw new Error(`${label} is required`);
|
|
11231
|
+
return value2;
|
|
10603
11232
|
}
|
|
10604
|
-
function securityProfile(
|
|
10605
|
-
if (
|
|
10606
|
-
if (
|
|
11233
|
+
function securityProfile(value2) {
|
|
11234
|
+
if (value2 === void 0) return void 0;
|
|
11235
|
+
if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
|
|
10607
11236
|
throw new Error("--profile must be odla, cloudflare-app, or generic");
|
|
10608
11237
|
}
|
|
10609
11238
|
|
|
@@ -10704,9 +11333,9 @@ function routeLabel(route2) {
|
|
|
10704
11333
|
return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
|
|
10705
11334
|
}
|
|
10706
11335
|
var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
|
|
10707
|
-
function hostedSeverity(
|
|
10708
|
-
if (HOSTED_SEVERITIES.includes(
|
|
10709
|
-
return
|
|
11336
|
+
function hostedSeverity(value2, flag) {
|
|
11337
|
+
if (HOSTED_SEVERITIES.includes(value2)) {
|
|
11338
|
+
return value2;
|
|
10710
11339
|
}
|
|
10711
11340
|
throw new Error(`${flag} must be informational, low, medium, high, or critical`);
|
|
10712
11341
|
}
|
|
@@ -10715,7 +11344,7 @@ function hostedSeverity(value, flag) {
|
|
|
10715
11344
|
var import_security2 = require("@odla-ai/security");
|
|
10716
11345
|
|
|
10717
11346
|
// src/security.ts
|
|
10718
|
-
var
|
|
11347
|
+
var import_node_path17 = require("path");
|
|
10719
11348
|
var import_security = require("@odla-ai/security");
|
|
10720
11349
|
var import_node3 = require("@odla-ai/security/node");
|
|
10721
11350
|
async function runHostedSecurity(options) {
|
|
@@ -10727,9 +11356,9 @@ async function runHostedSecurity(options) {
|
|
|
10727
11356
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
10728
11357
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
10729
11358
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
10730
|
-
const target = (0,
|
|
10731
|
-
const output = (0,
|
|
10732
|
-
const outputRelative = (0,
|
|
11359
|
+
const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
11360
|
+
const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
|
|
11361
|
+
const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
|
|
10733
11362
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
10734
11363
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
10735
11364
|
const tokenRequest = {
|
|
@@ -10741,7 +11370,7 @@ async function runHostedSecurity(options) {
|
|
|
10741
11370
|
};
|
|
10742
11371
|
const token = await injectedToken(options, tokenRequest);
|
|
10743
11372
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
10744
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
11373
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
10745
11374
|
});
|
|
10746
11375
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
10747
11376
|
platform,
|
|
@@ -10759,7 +11388,7 @@ async function runHostedSecurity(options) {
|
|
|
10759
11388
|
});
|
|
10760
11389
|
const harness = (0, import_security.createSecurityHarness)({
|
|
10761
11390
|
profile,
|
|
10762
|
-
store: new import_node3.FileRunStore((0,
|
|
11391
|
+
store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
|
|
10763
11392
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
10764
11393
|
validationReasoner: hosted.validationReasoner,
|
|
10765
11394
|
policy: {
|
|
@@ -10783,19 +11412,19 @@ async function runHostedSecurity(options) {
|
|
|
10783
11412
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
10784
11413
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
10785
11414
|
if (!env || !declared.includes(env)) {
|
|
10786
|
-
const shown = (0,
|
|
11415
|
+
const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
|
|
10787
11416
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
10788
11417
|
}
|
|
10789
11418
|
return env;
|
|
10790
11419
|
}
|
|
10791
11420
|
async function injectedToken(options, request2) {
|
|
10792
|
-
const
|
|
10793
|
-
if (typeof
|
|
11421
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
|
|
11422
|
+
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
10794
11423
|
throw new Error(
|
|
10795
11424
|
request2.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
10796
11425
|
);
|
|
10797
11426
|
}
|
|
10798
|
-
return
|
|
11427
|
+
return value2;
|
|
10799
11428
|
}
|
|
10800
11429
|
function profileFor(name, maxHuntTasks) {
|
|
10801
11430
|
const profile = name === "odla" ? (0, import_security.odlaProfile)() : name === "cloudflare-app" ? (0, import_security.cloudflareAppProfile)() : name === "generic" ? (0, import_security.genericProfile)() : void 0;
|
|
@@ -10812,7 +11441,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
10812
11441
|
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}`);
|
|
10813
11442
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
10814
11443
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
10815
|
-
out.log(` report: ${(0,
|
|
11444
|
+
out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
|
|
10816
11445
|
}
|
|
10817
11446
|
function formatBudget(usage) {
|
|
10818
11447
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -10841,8 +11470,8 @@ async function getHostedSecurityIntent(options) {
|
|
|
10841
11470
|
}
|
|
10842
11471
|
return response2;
|
|
10843
11472
|
}
|
|
10844
|
-
function isValidIntent(
|
|
10845
|
-
return !!
|
|
11473
|
+
function isValidIntent(value2, expected) {
|
|
11474
|
+
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";
|
|
10846
11475
|
}
|
|
10847
11476
|
|
|
10848
11477
|
// src/security-hosted-jobs.ts
|
|
@@ -10968,17 +11597,17 @@ function hostedJobPath(appIdInput, jobIdInput) {
|
|
|
10968
11597
|
const jobId = hostedIdentifier(jobIdInput, "jobId");
|
|
10969
11598
|
return `/registry/apps/${encodeURIComponent(appId)}/security/jobs/${encodeURIComponent(jobId)}`;
|
|
10970
11599
|
}
|
|
10971
|
-
function securityPlanDigest(
|
|
10972
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(
|
|
11600
|
+
function securityPlanDigest(value2) {
|
|
11601
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
|
|
10973
11602
|
throw new Error("expected plan digest must be a sha256 digest from security plan");
|
|
10974
11603
|
}
|
|
10975
|
-
return
|
|
11604
|
+
return value2;
|
|
10976
11605
|
}
|
|
10977
|
-
function securityExecutionDigest(
|
|
10978
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(
|
|
11606
|
+
function securityExecutionDigest(value2) {
|
|
11607
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(value2)) {
|
|
10979
11608
|
throw new Error("expected execution digest must be a sha256 digest from security intent");
|
|
10980
11609
|
}
|
|
10981
|
-
return
|
|
11610
|
+
return value2;
|
|
10982
11611
|
}
|
|
10983
11612
|
|
|
10984
11613
|
// src/security-run-command.ts
|
|
@@ -11042,7 +11671,7 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
|
|
|
11042
11671
|
...context,
|
|
11043
11672
|
jobId: job.jobId,
|
|
11044
11673
|
wait: dependencies.pollWait,
|
|
11045
|
-
onUpdate: parsed.options.json === true ? void 0 : (
|
|
11674
|
+
onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
|
|
11046
11675
|
}) : job;
|
|
11047
11676
|
if (!follow) {
|
|
11048
11677
|
if (parsed.options.json === true) {
|
|
@@ -11142,9 +11771,9 @@ function enforceLocalGate(report4, parsed) {
|
|
|
11142
11771
|
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
|
|
11143
11772
|
}
|
|
11144
11773
|
}
|
|
11145
|
-
function severityOpt(
|
|
11146
|
-
if (["informational", "low", "medium", "high", "critical"].includes(
|
|
11147
|
-
return
|
|
11774
|
+
function severityOpt(value2, flag) {
|
|
11775
|
+
if (["informational", "low", "medium", "high", "critical"].includes(value2)) {
|
|
11776
|
+
return value2;
|
|
11148
11777
|
}
|
|
11149
11778
|
throw new Error(`${flag} must be informational, low, medium, high, or critical`);
|
|
11150
11779
|
}
|
|
@@ -11189,8 +11818,8 @@ async function securityCommand(parsed, dependencies) {
|
|
|
11189
11818
|
else await runLocalSecurityCommand(parsed, dependencies);
|
|
11190
11819
|
}
|
|
11191
11820
|
async function githubSecurityCommand(parsed, dependencies) {
|
|
11192
|
-
const
|
|
11193
|
-
if (
|
|
11821
|
+
const action2 = parsed.positionals[2];
|
|
11822
|
+
if (action2 === "disconnect") {
|
|
11194
11823
|
assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
|
|
11195
11824
|
const context2 = await hostedSecurityContext(parsed, dependencies);
|
|
11196
11825
|
const sourceId = requiredString(parsed.options.source, "--source");
|
|
@@ -11205,7 +11834,7 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
11205
11834
|
context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
|
|
11206
11835
|
return;
|
|
11207
11836
|
}
|
|
11208
|
-
if (
|
|
11837
|
+
if (action2 !== "connect") {
|
|
11209
11838
|
throw new Error('unknown security github command. Try "odla-ai security github connect".');
|
|
11210
11839
|
}
|
|
11211
11840
|
assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
|
|
@@ -11252,7 +11881,7 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11252
11881
|
...context,
|
|
11253
11882
|
jobId,
|
|
11254
11883
|
wait: dependencies.pollWait,
|
|
11255
|
-
onUpdate: parsed.options.json === true ? void 0 : (
|
|
11884
|
+
onUpdate: parsed.options.json === true ? void 0 : (value2) => printHostedJob(context.stdout, value2, context.platform, context.appId)
|
|
11256
11885
|
}) : await getHostedSecurityJob({ ...context, jobId });
|
|
11257
11886
|
if (parsed.options.json === true) context.stdout.log(JSON.stringify(job, null, 2));
|
|
11258
11887
|
else if (parsed.options.follow !== true) {
|
|
@@ -11336,6 +11965,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
11336
11965
|
await o11yCommand(parsed, runtime);
|
|
11337
11966
|
return;
|
|
11338
11967
|
}
|
|
11968
|
+
if (command === "platform") {
|
|
11969
|
+
await platformCommand(parsed, runtime);
|
|
11970
|
+
return;
|
|
11971
|
+
}
|
|
11339
11972
|
if (command === "provision") {
|
|
11340
11973
|
await provisionCommand(parsed, runtime);
|
|
11341
11974
|
return;
|
|
@@ -11421,8 +12054,11 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
11421
12054
|
calendarServiceConfig,
|
|
11422
12055
|
calendarStatus,
|
|
11423
12056
|
codeConnect,
|
|
12057
|
+
configDiff,
|
|
12058
|
+
configPlan,
|
|
11424
12059
|
connectGitHubSecuritySource,
|
|
11425
12060
|
describeProblem,
|
|
12061
|
+
desiredRegistryState,
|
|
11426
12062
|
disconnectGitHubSecuritySource,
|
|
11427
12063
|
doctor,
|
|
11428
12064
|
exitCodeFor,
|
|
@@ -11442,6 +12078,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
11442
12078
|
prepareCodeImages,
|
|
11443
12079
|
printCapabilities,
|
|
11444
12080
|
provision,
|
|
12081
|
+
reconcileConfig,
|
|
11445
12082
|
redactSecrets,
|
|
11446
12083
|
repositoryFromGitRemote,
|
|
11447
12084
|
runCli,
|