@odla-ai/cli 0.25.22 → 0.25.23
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 +30 -10
- package/dist/bin.cjs +625 -225
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-2AFAXLKE.js → chunk-JJ42NV7X.js} +589 -180
- package/dist/chunk-JJ42NV7X.js.map +1 -0
- package/dist/index.cjs +633 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -12
- package/dist/index.d.ts +39 -12
- package/dist/index.js +9 -1
- package/package.json +2 -2
- package/dist/chunk-2AFAXLKE.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
CODE_BUILD_RECIPES: () => CODE_BUILD_RECIPES,
|
|
37
37
|
CODE_PI_IMAGE: () => CODE_PI_IMAGE,
|
|
38
38
|
COMMAND_SURFACE: () => COMMAND_SURFACE,
|
|
39
|
+
ConfigOperationCommandError: () => ConfigOperationCommandError,
|
|
39
40
|
GOOGLE_CALENDAR_EVENTS_SCOPE: () => GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
40
41
|
SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
|
|
41
42
|
acceptedAfter: () => acceptedAfter,
|
|
@@ -47,7 +48,10 @@ __export(index_exports, {
|
|
|
47
48
|
calendarServiceConfig: () => calendarServiceConfig,
|
|
48
49
|
calendarStatus: () => calendarStatus,
|
|
49
50
|
codeConnect: () => codeConnect,
|
|
51
|
+
configApply: () => configApply,
|
|
50
52
|
configDiff: () => configDiff,
|
|
53
|
+
configOperationGet: () => configOperationGet,
|
|
54
|
+
configOperationWait: () => configOperationWait,
|
|
51
55
|
configPlan: () => configPlan,
|
|
52
56
|
connectGitHubSecuritySource: () => connectGitHubSecuritySource,
|
|
53
57
|
describeProblem: () => describeProblem,
|
|
@@ -557,6 +561,7 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
557
561
|
var SCOPE_PURPOSE = {
|
|
558
562
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
559
563
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
564
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
560
565
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
561
566
|
"platform:ai:policy:write": "change System AI model routing",
|
|
562
567
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -1112,6 +1117,7 @@ function unique(values) {
|
|
|
1112
1117
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1113
1118
|
var DEFAULT_ENVS = ["dev"];
|
|
1114
1119
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1120
|
+
var configImportSerial = 0;
|
|
1115
1121
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1116
1122
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1117
1123
|
const resolved = (0, import_node_path4.resolve)(configPath);
|
|
@@ -1319,7 +1325,8 @@ function validId2(value2) {
|
|
|
1319
1325
|
}
|
|
1320
1326
|
async function loadConfigModule(path) {
|
|
1321
1327
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1322
|
-
const
|
|
1328
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1329
|
+
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
|
|
1323
1330
|
const value2 = mod.default ?? mod.config;
|
|
1324
1331
|
if (typeof value2 === "function") return await value2();
|
|
1325
1332
|
return value2;
|
|
@@ -2703,7 +2710,8 @@ var CAPABILITIES = {
|
|
|
2703
2710
|
"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",
|
|
2704
2711
|
"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
2712
|
"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
|
|
2713
|
+
"compare one project's checked-in Registry intent with live owner-visible state, freeze a secret-free Registry-revision-bound plan through app:config:read, and conditionally apply checkpoint-free actions through exact app:config:write operation routes",
|
|
2714
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
2707
2715
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2708
2716
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2709
2717
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2750,10 +2758,31 @@ function printGroup(out, heading, items) {
|
|
|
2750
2758
|
out.log("");
|
|
2751
2759
|
}
|
|
2752
2760
|
|
|
2753
|
-
// src/config-
|
|
2761
|
+
// src/config-operation-command.ts
|
|
2754
2762
|
var import_apps6 = require("@odla-ai/apps");
|
|
2755
2763
|
var import_node_path7 = require("path");
|
|
2756
2764
|
|
|
2765
|
+
// src/version.ts
|
|
2766
|
+
var import_node_fs9 = require("fs");
|
|
2767
|
+
function cliVersion() {
|
|
2768
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
2769
|
+
return pkg.version ?? "unknown";
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
// src/config-operation-error.ts
|
|
2773
|
+
var ConfigOperationCommandError = class extends Error {
|
|
2774
|
+
constructor(message2, code) {
|
|
2775
|
+
super(message2);
|
|
2776
|
+
this.code = code;
|
|
2777
|
+
this.name = "ConfigOperationCommandError";
|
|
2778
|
+
}
|
|
2779
|
+
code;
|
|
2780
|
+
};
|
|
2781
|
+
|
|
2782
|
+
// src/config-operation-validate.ts
|
|
2783
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2784
|
+
var import_node_fs10 = require("fs");
|
|
2785
|
+
|
|
2757
2786
|
// src/config-reconcile-digest.ts
|
|
2758
2787
|
var import_node_crypto = require("crypto");
|
|
2759
2788
|
function canonicalJson(value2) {
|
|
@@ -2763,27 +2792,160 @@ function configDigest(value2) {
|
|
|
2763
2792
|
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2764
2793
|
}
|
|
2765
2794
|
function canonicalValue(value2) {
|
|
2795
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
2796
|
+
if (typeof value2 === "number") {
|
|
2797
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
2798
|
+
return value2;
|
|
2799
|
+
}
|
|
2766
2800
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2767
2801
|
if (value2 && typeof value2 === "object") {
|
|
2802
|
+
const record10 = value2;
|
|
2768
2803
|
return Object.fromEntries(
|
|
2769
|
-
Object.
|
|
2804
|
+
Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
|
|
2770
2805
|
);
|
|
2771
2806
|
}
|
|
2772
|
-
|
|
2807
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
// src/config-operation-validate.ts
|
|
2811
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
2812
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
2813
|
+
var OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2814
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
2815
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
2816
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
2817
|
+
function readPlan(path) {
|
|
2818
|
+
let value2;
|
|
2819
|
+
try {
|
|
2820
|
+
const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
|
|
2821
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
2822
|
+
value2 = JSON.parse(raw);
|
|
2823
|
+
} catch (error) {
|
|
2824
|
+
throw new ConfigOperationCommandError(
|
|
2825
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2826
|
+
"invalid_plan"
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
2830
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2831
|
+
invalidPlan("plan scope is invalid");
|
|
2832
|
+
}
|
|
2833
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
2834
|
+
invalidPlan("plan content revisions are invalid");
|
|
2835
|
+
}
|
|
2836
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
2837
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
2838
|
+
}
|
|
2839
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
2840
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
2841
|
+
}
|
|
2842
|
+
assertActions(value2.actions);
|
|
2843
|
+
const plan = value2;
|
|
2844
|
+
const digest = configDigest({
|
|
2845
|
+
schemaVersion: plan.schemaVersion,
|
|
2846
|
+
desiredRevision: plan.desiredRevision,
|
|
2847
|
+
observedRevision: plan.observedRevision,
|
|
2848
|
+
registryRevision: plan.registryRevision,
|
|
2849
|
+
actions: plan.actions
|
|
2850
|
+
});
|
|
2851
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
2852
|
+
return plan;
|
|
2853
|
+
}
|
|
2854
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
2855
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
2856
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
2860
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
2861
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
2862
|
+
}
|
|
2863
|
+
if (receipt.receiptDigest) {
|
|
2864
|
+
const { receiptDigest, ...fields } = receipt;
|
|
2865
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
2866
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
2867
|
+
}
|
|
2868
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
2869
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
function assertOperationId(value2) {
|
|
2873
|
+
if (!OPERATION_ID.test(value2)) {
|
|
2874
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
function assertActions(actions) {
|
|
2878
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2879
|
+
for (const action2 of actions) {
|
|
2880
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
2881
|
+
const id = String(action2.id ?? "");
|
|
2882
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
2883
|
+
ids.add(id);
|
|
2884
|
+
if (typeof action2.path !== "string" || typeof action2.reason !== "string" || !action2.reason || action2.reason.length > 500 || typeof action2.requiresApproval !== "boolean" || !["low", "medium", "high"].includes(String(action2.risk)) || !["provision", "command", "studio"].includes(String(action2.applySupport))) {
|
|
2885
|
+
invalidPlan("plan action metadata is invalid");
|
|
2886
|
+
}
|
|
2887
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
2888
|
+
assertConditionalAction(action2);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
function assertConditionalAction(action2) {
|
|
2893
|
+
if (action2.kind === "rename_app") {
|
|
2894
|
+
if (action2.path !== "app.name" || action2.applySupport !== "command" || typeof action2.before !== "string" || typeof action2.after !== "string" || action2.after !== action2.after.trim() || !action2.after || action2.after.length > 80) invalidPlan("rename action is invalid");
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
2898
|
+
if (action2.kind === "set_link") {
|
|
2899
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
|
|
2903
|
+
invalidPlan("service action names an unknown service");
|
|
2904
|
+
}
|
|
2905
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
2906
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
2907
|
+
invalidPlan("service action path is invalid");
|
|
2908
|
+
}
|
|
2909
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
2910
|
+
invalidPlan("service action payload is invalid");
|
|
2911
|
+
}
|
|
2912
|
+
if (action2.kind === "enable_service") {
|
|
2913
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
2914
|
+
invalidPlan("service enable action is invalid");
|
|
2915
|
+
}
|
|
2916
|
+
} else if (!record2(action2.before)) {
|
|
2917
|
+
invalidPlan("service configure action is invalid");
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
function linkState(value2) {
|
|
2921
|
+
if (value2 === null) return true;
|
|
2922
|
+
if (typeof value2 !== "string") return false;
|
|
2923
|
+
try {
|
|
2924
|
+
const url = new URL(value2.trim());
|
|
2925
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2926
|
+
} catch {
|
|
2927
|
+
return false;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
function invalidPlan(message2) {
|
|
2931
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
2932
|
+
}
|
|
2933
|
+
function record2(value2) {
|
|
2934
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2773
2935
|
}
|
|
2774
2936
|
|
|
2775
2937
|
// src/config-reconcile-desired.ts
|
|
2776
|
-
var
|
|
2938
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2777
2939
|
|
|
2778
2940
|
// src/provision-helpers.ts
|
|
2779
2941
|
var import_ai = require("@odla-ai/ai");
|
|
2780
|
-
var
|
|
2942
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2781
2943
|
function defaultSecretName(provider) {
|
|
2782
2944
|
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2783
2945
|
return names[provider] ?? `${provider}_api_key`;
|
|
2784
2946
|
}
|
|
2785
2947
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2786
|
-
const tenantId = (0,
|
|
2948
|
+
const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
|
|
2787
2949
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2788
2950
|
headers: { authorization: `Bearer ${token}` }
|
|
2789
2951
|
});
|
|
@@ -2825,7 +2987,7 @@ async function safeText3(res) {
|
|
|
2825
2987
|
// src/config-reconcile-desired.ts
|
|
2826
2988
|
function desiredRegistryState(cfg) {
|
|
2827
2989
|
const environments = {};
|
|
2828
|
-
const services = (0,
|
|
2990
|
+
const services = (0, import_apps5.orderAppServices)(cfg.services);
|
|
2829
2991
|
for (const env of cfg.envs) {
|
|
2830
2992
|
const desiredServices = {};
|
|
2831
2993
|
for (const service of services) {
|
|
@@ -2865,8 +3027,208 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
2865
3027
|
return {};
|
|
2866
3028
|
}
|
|
2867
3029
|
|
|
3030
|
+
// src/config-reconcile-support.ts
|
|
3031
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
3032
|
+
"rename_app",
|
|
3033
|
+
"enable_service",
|
|
3034
|
+
"configure_service",
|
|
3035
|
+
"set_link"
|
|
3036
|
+
]);
|
|
3037
|
+
function configApplySupport(reconciliation) {
|
|
3038
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
3039
|
+
return {
|
|
3040
|
+
supported: false,
|
|
3041
|
+
checkpointRequired: false,
|
|
3042
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
if (!reconciliation.actions.length) {
|
|
3046
|
+
return {
|
|
3047
|
+
supported: false,
|
|
3048
|
+
checkpointRequired: false,
|
|
3049
|
+
reason: "there are no managed changes to apply"
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
const blocked = reconciliation.actions.filter(
|
|
3053
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
3054
|
+
);
|
|
3055
|
+
if (blocked.length) {
|
|
3056
|
+
return {
|
|
3057
|
+
supported: false,
|
|
3058
|
+
checkpointRequired: blocked.some(
|
|
3059
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
3060
|
+
),
|
|
3061
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
return {
|
|
3065
|
+
supported: true,
|
|
3066
|
+
checkpointRequired: false,
|
|
3067
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// src/config-operation-command.ts
|
|
3072
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
3073
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
3074
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
3075
|
+
async function configApply(options) {
|
|
3076
|
+
const plan = readPlan(options.planPath);
|
|
3077
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3078
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
3079
|
+
const support = configApplySupport(plan);
|
|
3080
|
+
if (!support.supported) {
|
|
3081
|
+
throw new ConfigOperationCommandError(
|
|
3082
|
+
support.reason,
|
|
3083
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
3087
|
+
throw new ConfigOperationCommandError(
|
|
3088
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
3089
|
+
"checkpoint_required"
|
|
3090
|
+
);
|
|
3091
|
+
}
|
|
3092
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
3093
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
3094
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3095
|
+
}
|
|
3096
|
+
const client = await operationClient(cfg, options, "apply");
|
|
3097
|
+
const request2 = {
|
|
3098
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
3099
|
+
expectedRevision: plan.registryRevision,
|
|
3100
|
+
desiredRevision: plan.desiredRevision,
|
|
3101
|
+
observedRevision: plan.observedRevision,
|
|
3102
|
+
planDigest: plan.planDigest,
|
|
3103
|
+
idempotencyKey,
|
|
3104
|
+
source: { kind: "cli", version: cliVersion() },
|
|
3105
|
+
actions: plan.actions
|
|
3106
|
+
};
|
|
3107
|
+
let receipt;
|
|
3108
|
+
try {
|
|
3109
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
3110
|
+
} catch (error) {
|
|
3111
|
+
const retained = retainedReceipt(error);
|
|
3112
|
+
if (retained) {
|
|
3113
|
+
verifyReceipt(retained, cfg.app.id);
|
|
3114
|
+
printReceipt(retained, options);
|
|
3115
|
+
throw failureForReceipt(retained);
|
|
3116
|
+
}
|
|
3117
|
+
throw normalizeRequestError(error);
|
|
3118
|
+
}
|
|
3119
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
3120
|
+
printReceipt(receipt, options);
|
|
3121
|
+
assertApplyCompleted(receipt);
|
|
3122
|
+
return receipt;
|
|
3123
|
+
}
|
|
3124
|
+
async function configOperationGet(options) {
|
|
3125
|
+
assertOperationId(options.operationId);
|
|
3126
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3127
|
+
const client = await operationClient(cfg, options, "read");
|
|
3128
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3129
|
+
throw normalizeRequestError(error);
|
|
3130
|
+
});
|
|
3131
|
+
if (!receipt) {
|
|
3132
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3133
|
+
}
|
|
3134
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3135
|
+
printReceipt(receipt, options);
|
|
3136
|
+
return receipt;
|
|
3137
|
+
}
|
|
3138
|
+
async function configOperationWait(options) {
|
|
3139
|
+
assertOperationId(options.operationId);
|
|
3140
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3141
|
+
const client = await operationClient(cfg, options, "wait");
|
|
3142
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
3143
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3144
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3145
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
3146
|
+
let receipt = null;
|
|
3147
|
+
for (; ; ) {
|
|
3148
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3149
|
+
throw normalizeRequestError(error);
|
|
3150
|
+
});
|
|
3151
|
+
if (!receipt) {
|
|
3152
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3153
|
+
}
|
|
3154
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3155
|
+
if (receipt.state !== "running") break;
|
|
3156
|
+
if (now() >= deadline) {
|
|
3157
|
+
printReceipt(receipt, options);
|
|
3158
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
3159
|
+
}
|
|
3160
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
3161
|
+
}
|
|
3162
|
+
printReceipt(receipt, options);
|
|
3163
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
3164
|
+
return receipt;
|
|
3165
|
+
}
|
|
3166
|
+
async function operationClient(cfg, options, purpose) {
|
|
3167
|
+
const doFetch = options.fetch ?? fetch;
|
|
3168
|
+
const out = options.stdout ?? console;
|
|
3169
|
+
const token = await resolveAdminPlatformToken({
|
|
3170
|
+
platform: cfg.platformUrl,
|
|
3171
|
+
scope: "app:config:write",
|
|
3172
|
+
token: options.token,
|
|
3173
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3174
|
+
rootDir: cfg.rootDir,
|
|
3175
|
+
email: options.email,
|
|
3176
|
+
open: options.open,
|
|
3177
|
+
fetch: doFetch,
|
|
3178
|
+
stdout: out,
|
|
3179
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3180
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
3181
|
+
});
|
|
3182
|
+
return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3183
|
+
}
|
|
3184
|
+
function printReceipt(receipt, options) {
|
|
3185
|
+
const out = options.stdout ?? console;
|
|
3186
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
3187
|
+
else {
|
|
3188
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
3189
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
3190
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
3191
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
3192
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
function assertApplyCompleted(receipt) {
|
|
3196
|
+
if (receipt.state === "succeeded") return;
|
|
3197
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
3198
|
+
}
|
|
3199
|
+
function failureForReceipt(receipt) {
|
|
3200
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
3201
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
3202
|
+
"checkpoint_required"
|
|
3203
|
+
);
|
|
3204
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
3205
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3206
|
+
}
|
|
3207
|
+
function retainedReceipt(error) {
|
|
3208
|
+
if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
|
|
3209
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
3210
|
+
}
|
|
3211
|
+
function normalizeRequestError(error) {
|
|
3212
|
+
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
3213
|
+
if (error.status === 401 || error.status === 403) {
|
|
3214
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
3215
|
+
}
|
|
3216
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
3217
|
+
if (error.status === 429 || error.status >= 500) {
|
|
3218
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
3219
|
+
}
|
|
3220
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3221
|
+
}
|
|
3222
|
+
function record3(value2) {
|
|
3223
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
// src/config-reconcile-command.ts
|
|
3227
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3228
|
+
var import_node_path8 = require("path");
|
|
3229
|
+
|
|
2868
3230
|
// src/config-reconcile.ts
|
|
2869
|
-
var
|
|
3231
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
2870
3232
|
|
|
2871
3233
|
// src/config-reconcile-values.ts
|
|
2872
3234
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
@@ -2967,6 +3329,7 @@ function reconcileConfig(input) {
|
|
|
2967
3329
|
sources: { desired: desiredSource, observed: observedSource },
|
|
2968
3330
|
desiredRevision,
|
|
2969
3331
|
observedRevision,
|
|
3332
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
2970
3333
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2971
3334
|
summary: {
|
|
2972
3335
|
differences: different,
|
|
@@ -3021,7 +3384,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
|
|
|
3021
3384
|
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
3022
3385
|
const wanted = desired.environments[env]?.services ?? {};
|
|
3023
3386
|
const live = observed?.environments[env] ?? {};
|
|
3024
|
-
const knownOrder = (0,
|
|
3387
|
+
const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
|
|
3025
3388
|
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
3026
3389
|
for (const service of services) {
|
|
3027
3390
|
const next = wanted[service];
|
|
@@ -3132,20 +3495,19 @@ async function configDiff(options) {
|
|
|
3132
3495
|
}
|
|
3133
3496
|
async function configPlan(options) {
|
|
3134
3497
|
const reconciliation = await inspectConfig(options);
|
|
3498
|
+
const apply = configApplySupport(reconciliation);
|
|
3135
3499
|
const planDigest = configDigest({
|
|
3136
|
-
schemaVersion: "odla.config-plan/
|
|
3500
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3137
3501
|
desiredRevision: reconciliation.desiredRevision,
|
|
3138
3502
|
observedRevision: reconciliation.observedRevision,
|
|
3503
|
+
registryRevision: reconciliation.registryRevision,
|
|
3139
3504
|
actions: reconciliation.actions
|
|
3140
3505
|
});
|
|
3141
3506
|
const document2 = {
|
|
3142
|
-
schemaVersion: "odla.config-plan/
|
|
3507
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3143
3508
|
...reconciliation,
|
|
3144
3509
|
planDigest,
|
|
3145
|
-
apply
|
|
3146
|
-
supported: false,
|
|
3147
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3148
|
-
},
|
|
3510
|
+
apply,
|
|
3149
3511
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3150
3512
|
};
|
|
3151
3513
|
printPlan2(document2, options);
|
|
@@ -3159,7 +3521,7 @@ async function inspectConfig(options) {
|
|
|
3159
3521
|
platform: cfg.platformUrl,
|
|
3160
3522
|
scope: "app:config:read",
|
|
3161
3523
|
token: options.token,
|
|
3162
|
-
tokenFile: (0,
|
|
3524
|
+
tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3163
3525
|
rootDir: cfg.rootDir,
|
|
3164
3526
|
email: options.email,
|
|
3165
3527
|
open: options.open,
|
|
@@ -3168,7 +3530,7 @@ async function inspectConfig(options) {
|
|
|
3168
3530
|
openApprovalUrl: options.openApprovalUrl,
|
|
3169
3531
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
3170
3532
|
});
|
|
3171
|
-
const client = (0,
|
|
3533
|
+
const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3172
3534
|
const observed = await client.resolveApp(cfg.app.id);
|
|
3173
3535
|
return reconcileConfig({
|
|
3174
3536
|
desired: desiredRegistryState(cfg),
|
|
@@ -3204,7 +3566,7 @@ function printPlan2(document2, options) {
|
|
|
3204
3566
|
}
|
|
3205
3567
|
if (!document2.actions.length) out.log(" no managed changes");
|
|
3206
3568
|
out.log(`plan digest: ${document2.planDigest}`);
|
|
3207
|
-
out.log(`apply:
|
|
3569
|
+
out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
|
|
3208
3570
|
printNext(out, document2.nextActions);
|
|
3209
3571
|
}
|
|
3210
3572
|
function printHeader(out, kind, document2) {
|
|
@@ -3212,6 +3574,7 @@ function printHeader(out, kind, document2) {
|
|
|
3212
3574
|
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3213
3575
|
out.log(`desired: ${document2.desiredRevision}`);
|
|
3214
3576
|
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3577
|
+
out.log(`registry: ${document2.registryRevision ?? "absent"}`);
|
|
3215
3578
|
out.log(
|
|
3216
3579
|
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3217
3580
|
);
|
|
@@ -3244,6 +3607,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
3244
3607
|
}
|
|
3245
3608
|
function planNextActions(reconciliation, configPath) {
|
|
3246
3609
|
const next = [];
|
|
3610
|
+
if (configApplySupport(reconciliation).supported) {
|
|
3611
|
+
next.push({
|
|
3612
|
+
code: "apply_frozen_plan",
|
|
3613
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
3614
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3247
3617
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3248
3618
|
next.push({
|
|
3249
3619
|
code: "review_provision",
|
|
@@ -3279,13 +3649,13 @@ function quoteArg2(value2) {
|
|
|
3279
3649
|
|
|
3280
3650
|
// src/doctor-checks.ts
|
|
3281
3651
|
var import_node_child_process3 = require("child_process");
|
|
3282
|
-
var
|
|
3283
|
-
var
|
|
3652
|
+
var import_node_fs12 = require("fs");
|
|
3653
|
+
var import_node_path10 = require("path");
|
|
3284
3654
|
|
|
3285
3655
|
// src/wrangler.ts
|
|
3286
3656
|
var import_node_child_process2 = require("child_process");
|
|
3287
|
-
var
|
|
3288
|
-
var
|
|
3657
|
+
var import_node_fs11 = require("fs");
|
|
3658
|
+
var import_node_path9 = require("path");
|
|
3289
3659
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3290
3660
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3291
3661
|
let stdout = "";
|
|
@@ -3299,15 +3669,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3299
3669
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3300
3670
|
function findWranglerConfig(rootDir) {
|
|
3301
3671
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3302
|
-
const path = (0,
|
|
3303
|
-
if ((0,
|
|
3672
|
+
const path = (0, import_node_path9.join)(rootDir, name);
|
|
3673
|
+
if ((0, import_node_fs11.existsSync)(path)) return path;
|
|
3304
3674
|
}
|
|
3305
3675
|
return null;
|
|
3306
3676
|
}
|
|
3307
3677
|
function readWranglerConfig(path) {
|
|
3308
3678
|
if (path.endsWith(".toml")) return null;
|
|
3309
3679
|
try {
|
|
3310
|
-
return JSON.parse(stripJsonComments((0,
|
|
3680
|
+
return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
|
|
3311
3681
|
} catch {
|
|
3312
3682
|
return null;
|
|
3313
3683
|
}
|
|
@@ -3405,10 +3775,10 @@ function wranglerWarnings(rootDir) {
|
|
|
3405
3775
|
for (const { label, block } of blocks) {
|
|
3406
3776
|
const assets = block.assets;
|
|
3407
3777
|
if (assets?.directory) {
|
|
3408
|
-
const dir = (0,
|
|
3409
|
-
if (dir === (0,
|
|
3778
|
+
const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
|
|
3779
|
+
if (dir === (0, import_node_path10.resolve)(rootDir)) {
|
|
3410
3780
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
3411
|
-
} else if ((0,
|
|
3781
|
+
} else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
|
|
3412
3782
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
3413
3783
|
}
|
|
3414
3784
|
}
|
|
@@ -3443,13 +3813,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
3443
3813
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
3444
3814
|
return warnings;
|
|
3445
3815
|
}
|
|
3446
|
-
const main = typeof config.main === "string" ? (0,
|
|
3447
|
-
if (!main || !(0,
|
|
3816
|
+
const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
|
|
3817
|
+
if (!main || !(0, import_node_fs12.existsSync)(main)) {
|
|
3448
3818
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
3449
3819
|
} else {
|
|
3450
3820
|
let source = "";
|
|
3451
3821
|
try {
|
|
3452
|
-
source = (0,
|
|
3822
|
+
source = (0, import_node_fs12.readFileSync)(main, "utf8");
|
|
3453
3823
|
} catch {
|
|
3454
3824
|
}
|
|
3455
3825
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -3473,7 +3843,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
3473
3843
|
}
|
|
3474
3844
|
function readPackageJson(rootDir) {
|
|
3475
3845
|
try {
|
|
3476
|
-
return JSON.parse((0,
|
|
3846
|
+
return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
|
|
3477
3847
|
} catch {
|
|
3478
3848
|
return null;
|
|
3479
3849
|
}
|
|
@@ -3700,14 +4070,14 @@ function harnessOption(value2, flag) {
|
|
|
3700
4070
|
}
|
|
3701
4071
|
|
|
3702
4072
|
// src/init.ts
|
|
3703
|
-
var
|
|
3704
|
-
var
|
|
3705
|
-
var
|
|
4073
|
+
var import_node_fs13 = require("fs");
|
|
4074
|
+
var import_node_path11 = require("path");
|
|
4075
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
3706
4076
|
function initProject(options) {
|
|
3707
4077
|
const out = options.stdout ?? console;
|
|
3708
|
-
const rootDir = (0,
|
|
3709
|
-
const configPath = (0,
|
|
3710
|
-
if ((0,
|
|
4078
|
+
const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
|
|
4079
|
+
const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4080
|
+
if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
|
|
3711
4081
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3712
4082
|
}
|
|
3713
4083
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -3716,27 +4086,27 @@ function initProject(options) {
|
|
|
3716
4086
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3717
4087
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3718
4088
|
for (const service of services) {
|
|
3719
|
-
const definition = (0,
|
|
3720
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
4089
|
+
const definition = (0, import_apps9.appServiceDefinition)(service);
|
|
4090
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
|
|
3721
4091
|
for (const dependency of definition.requires) {
|
|
3722
4092
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3723
4093
|
}
|
|
3724
4094
|
}
|
|
3725
4095
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3726
|
-
(0,
|
|
3727
|
-
(0,
|
|
3728
|
-
(0,
|
|
3729
|
-
(0,
|
|
3730
|
-
writeIfMissing((0,
|
|
3731
|
-
writeIfMissing((0,
|
|
4096
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
|
|
4097
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4098
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4099
|
+
(0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4100
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4101
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3732
4102
|
ensureGitignore(rootDir);
|
|
3733
4103
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3734
4104
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
3735
4105
|
out.log("updated .gitignore for local odla credentials");
|
|
3736
4106
|
}
|
|
3737
4107
|
function writeIfMissing(path, text) {
|
|
3738
|
-
if ((0,
|
|
3739
|
-
(0,
|
|
4108
|
+
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4109
|
+
(0, import_node_fs13.writeFileSync)(path, text);
|
|
3740
4110
|
}
|
|
3741
4111
|
function configTemplate(input) {
|
|
3742
4112
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -3911,7 +4281,7 @@ function assertWranglerConfig(cfg) {
|
|
|
3911
4281
|
|
|
3912
4282
|
// src/secrets-set.ts
|
|
3913
4283
|
var import_ai2 = require("@odla-ai/ai");
|
|
3914
|
-
var
|
|
4284
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
3915
4285
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3916
4286
|
async function secretsSet(options) {
|
|
3917
4287
|
const name = (options.name ?? "").trim();
|
|
@@ -3963,13 +4333,13 @@ async function resolveVaultWrite(options) {
|
|
|
3963
4333
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3964
4334
|
}
|
|
3965
4335
|
const value2 = await secretInputValue(options, "secret");
|
|
3966
|
-
return { cfg, tenantId: (0,
|
|
4336
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3967
4337
|
}
|
|
3968
4338
|
|
|
3969
4339
|
// src/skill.ts
|
|
3970
|
-
var
|
|
4340
|
+
var import_node_fs14 = require("fs");
|
|
3971
4341
|
var import_node_os2 = require("os");
|
|
3972
|
-
var
|
|
4342
|
+
var import_node_path12 = require("path");
|
|
3973
4343
|
var import_node_url2 = require("url");
|
|
3974
4344
|
|
|
3975
4345
|
// src/skill-adapters.ts
|
|
@@ -4048,8 +4418,8 @@ function installSkill(options = {}) {
|
|
|
4048
4418
|
const files = listFiles(sourceDir);
|
|
4049
4419
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
4050
4420
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
4051
|
-
const root = (0,
|
|
4052
|
-
const home = (0,
|
|
4421
|
+
const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
|
|
4422
|
+
const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
4053
4423
|
const plans = /* @__PURE__ */ new Map();
|
|
4054
4424
|
const targets = /* @__PURE__ */ new Map();
|
|
4055
4425
|
const rememberTarget = (harness, target) => {
|
|
@@ -4063,48 +4433,48 @@ function installSkill(options = {}) {
|
|
|
4063
4433
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4064
4434
|
};
|
|
4065
4435
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4066
|
-
for (const rel of files) plan((0,
|
|
4436
|
+
for (const rel of files) plan((0, import_node_path12.join)(targetDir2, rel), (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
4067
4437
|
};
|
|
4068
4438
|
let targetDir;
|
|
4069
4439
|
if (options.global) {
|
|
4070
|
-
const claudeRoot = (0,
|
|
4071
|
-
const codexRoot = (0,
|
|
4440
|
+
const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
|
|
4441
|
+
const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
|
|
4072
4442
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4073
4443
|
for (const harness of harnesses) {
|
|
4074
4444
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
4075
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4445
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
|
|
4076
4446
|
rememberTarget(harness, skillRoot);
|
|
4077
4447
|
}
|
|
4078
4448
|
} else {
|
|
4079
|
-
const sharedRoot = (0,
|
|
4449
|
+
const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
|
|
4080
4450
|
planSkillTree(sharedRoot);
|
|
4081
|
-
const claudeRoot = (0,
|
|
4451
|
+
const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
|
|
4082
4452
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4083
4453
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4084
4454
|
if (harnesses.includes("claude")) {
|
|
4085
4455
|
for (const skill of skillNames(files)) {
|
|
4086
|
-
const canonical = (0,
|
|
4087
|
-
plan((0,
|
|
4456
|
+
const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4457
|
+
plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
4088
4458
|
}
|
|
4089
4459
|
rememberTarget("claude", claudeRoot);
|
|
4090
4460
|
}
|
|
4091
4461
|
if (harnesses.includes("cursor")) {
|
|
4092
|
-
const cursorRule = (0,
|
|
4462
|
+
const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
|
|
4093
4463
|
plan(cursorRule, CURSOR_RULE);
|
|
4094
4464
|
rememberTarget("cursor", cursorRule);
|
|
4095
4465
|
}
|
|
4096
4466
|
if (harnesses.includes("agents")) {
|
|
4097
|
-
const agentsFile = (0,
|
|
4467
|
+
const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
|
|
4098
4468
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4099
4469
|
rememberTarget("agents", agentsFile);
|
|
4100
4470
|
}
|
|
4101
4471
|
if (harnesses.includes("copilot")) {
|
|
4102
|
-
const copilotFile = (0,
|
|
4472
|
+
const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
|
|
4103
4473
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4104
4474
|
rememberTarget("copilot", copilotFile);
|
|
4105
4475
|
}
|
|
4106
4476
|
if (harnesses.includes("gemini")) {
|
|
4107
|
-
const geminiFile = (0,
|
|
4477
|
+
const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
|
|
4108
4478
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4109
4479
|
rememberTarget("gemini", geminiFile);
|
|
4110
4480
|
}
|
|
@@ -4118,11 +4488,11 @@ function installSkill(options = {}) {
|
|
|
4118
4488
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4119
4489
|
continue;
|
|
4120
4490
|
}
|
|
4121
|
-
if (!(0,
|
|
4491
|
+
if (!(0, import_node_fs14.existsSync)(file.target)) {
|
|
4122
4492
|
writtenPaths.add(file.target);
|
|
4123
4493
|
continue;
|
|
4124
4494
|
}
|
|
4125
|
-
const current = (0,
|
|
4495
|
+
const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
|
|
4126
4496
|
if (current === file.content) {
|
|
4127
4497
|
unchangedPaths.add(file.target);
|
|
4128
4498
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4139,9 +4509,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4139
4509
|
);
|
|
4140
4510
|
}
|
|
4141
4511
|
for (const file of plans.values()) {
|
|
4142
|
-
if (!(0,
|
|
4143
|
-
(0,
|
|
4144
|
-
(0,
|
|
4512
|
+
if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
|
|
4513
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
|
|
4514
|
+
(0, import_node_fs14.writeFileSync)(file.target, file.content);
|
|
4145
4515
|
}
|
|
4146
4516
|
}
|
|
4147
4517
|
const skills = skillNames(files);
|
|
@@ -4160,7 +4530,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4160
4530
|
};
|
|
4161
4531
|
}
|
|
4162
4532
|
function pathsUnder(root, paths) {
|
|
4163
|
-
return [...paths].map((path) => (0,
|
|
4533
|
+
return [...paths].map((path) => (0, import_node_path12.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path12.sep}`) && !(0, import_node_path12.isAbsolute)(path)).sort();
|
|
4164
4534
|
}
|
|
4165
4535
|
function normalizeHarnesses(values, global) {
|
|
4166
4536
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -4182,9 +4552,9 @@ function normalizeHarnesses(values, global) {
|
|
|
4182
4552
|
function managedFileContent(path, block, force, boundary) {
|
|
4183
4553
|
const symlink = symlinkedComponent(boundary, path);
|
|
4184
4554
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
4185
|
-
if (!(0,
|
|
4555
|
+
if (!(0, import_node_fs14.existsSync)(path)) return `${block}
|
|
4186
4556
|
`;
|
|
4187
|
-
const current = (0,
|
|
4557
|
+
const current = (0, import_node_fs14.readFileSync)(path, "utf8");
|
|
4188
4558
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
4189
4559
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
4190
4560
|
const startAt = current.indexOf(start);
|
|
@@ -4205,15 +4575,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
4205
4575
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
4206
4576
|
}
|
|
4207
4577
|
function symlinkedComponent(boundary, target) {
|
|
4208
|
-
const rel = (0,
|
|
4209
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4578
|
+
const rel = (0, import_node_path12.relative)(boundary, target);
|
|
4579
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
|
|
4210
4580
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
4211
4581
|
}
|
|
4212
4582
|
let current = boundary;
|
|
4213
|
-
for (const part of rel.split(
|
|
4214
|
-
current = (0,
|
|
4583
|
+
for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
|
|
4584
|
+
current = (0, import_node_path12.join)(current, part);
|
|
4215
4585
|
try {
|
|
4216
|
-
if ((0,
|
|
4586
|
+
if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
|
|
4217
4587
|
} catch (error) {
|
|
4218
4588
|
if (error.code !== "ENOENT") throw error;
|
|
4219
4589
|
}
|
|
@@ -4224,13 +4594,13 @@ function skillNames(files) {
|
|
|
4224
4594
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
4225
4595
|
}
|
|
4226
4596
|
function listFiles(dir) {
|
|
4227
|
-
if (!(0,
|
|
4597
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return [];
|
|
4228
4598
|
const results = [];
|
|
4229
4599
|
const walk = (current) => {
|
|
4230
|
-
for (const entry of (0,
|
|
4231
|
-
const path = (0,
|
|
4600
|
+
for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
|
|
4601
|
+
const path = (0, import_node_path12.join)(current, entry.name);
|
|
4232
4602
|
if (entry.isDirectory()) walk(path);
|
|
4233
|
-
else results.push((0,
|
|
4603
|
+
else results.push((0, import_node_path12.relative)(dir, path));
|
|
4234
4604
|
}
|
|
4235
4605
|
};
|
|
4236
4606
|
walk(dir);
|
|
@@ -4425,10 +4795,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
4425
4795
|
async function projectCommand(command, parsed, deps) {
|
|
4426
4796
|
if (command === "config") {
|
|
4427
4797
|
const sub = parsed.positionals[1];
|
|
4428
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
4798
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
4429
4799
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4430
4800
|
}
|
|
4431
|
-
assertArgs(
|
|
4801
|
+
assertArgs(
|
|
4802
|
+
parsed,
|
|
4803
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
4804
|
+
2
|
|
4805
|
+
);
|
|
4432
4806
|
const options = {
|
|
4433
4807
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4434
4808
|
token: stringOpt(parsed.options.token),
|
|
@@ -4440,7 +4814,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4440
4814
|
stdout: deps.stdout
|
|
4441
4815
|
};
|
|
4442
4816
|
if (sub === "diff") await configDiff(options);
|
|
4443
|
-
else await configPlan(options);
|
|
4817
|
+
else if (sub === "plan") await configPlan(options);
|
|
4818
|
+
else await configApply({
|
|
4819
|
+
...options,
|
|
4820
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
4821
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
4822
|
+
});
|
|
4823
|
+
return true;
|
|
4824
|
+
}
|
|
4825
|
+
if (command === "operations") {
|
|
4826
|
+
const sub = parsed.positionals[1];
|
|
4827
|
+
if (sub !== "get" && sub !== "wait") {
|
|
4828
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
4829
|
+
}
|
|
4830
|
+
assertArgs(
|
|
4831
|
+
parsed,
|
|
4832
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
4833
|
+
3
|
|
4834
|
+
);
|
|
4835
|
+
const operationId = parsed.positionals[2];
|
|
4836
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
4837
|
+
const options = {
|
|
4838
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4839
|
+
operationId,
|
|
4840
|
+
token: stringOpt(parsed.options.token),
|
|
4841
|
+
email: stringOpt(parsed.options.email),
|
|
4842
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4843
|
+
json: parsed.options.json === true,
|
|
4844
|
+
fetch: deps.fetch,
|
|
4845
|
+
openApprovalUrl: deps.openUrl,
|
|
4846
|
+
stdout: deps.stdout,
|
|
4847
|
+
pollWait: deps.pollWait,
|
|
4848
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
4849
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
4850
|
+
};
|
|
4851
|
+
if (sub === "get") await configOperationGet(options);
|
|
4852
|
+
else await configOperationWait(options);
|
|
4444
4853
|
return true;
|
|
4445
4854
|
}
|
|
4446
4855
|
if (command === "init") {
|
|
@@ -4501,9 +4910,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4501
4910
|
}
|
|
4502
4911
|
|
|
4503
4912
|
// src/code-connect.ts
|
|
4504
|
-
var
|
|
4913
|
+
var import_node_fs15 = require("fs");
|
|
4505
4914
|
var import_node_os4 = require("os");
|
|
4506
|
-
var
|
|
4915
|
+
var import_node_path14 = require("path");
|
|
4507
4916
|
|
|
4508
4917
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
4509
4918
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -4513,7 +4922,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
4513
4922
|
var HarnessProtocolError = class extends Error {
|
|
4514
4923
|
name = "HarnessProtocolError";
|
|
4515
4924
|
};
|
|
4516
|
-
function
|
|
4925
|
+
function record4(value2) {
|
|
4517
4926
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
4518
4927
|
}
|
|
4519
4928
|
function boundedText(value2, label, max) {
|
|
@@ -4530,7 +4939,7 @@ function parseAgentOutput(line) {
|
|
|
4530
4939
|
} catch {
|
|
4531
4940
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
4532
4941
|
}
|
|
4533
|
-
const message2 =
|
|
4942
|
+
const message2 = record4(value2);
|
|
4534
4943
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
4535
4944
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
4536
4945
|
}
|
|
@@ -4543,7 +4952,7 @@ function parseAgentOutput(line) {
|
|
|
4543
4952
|
};
|
|
4544
4953
|
}
|
|
4545
4954
|
if (message2.type === "inference.request") {
|
|
4546
|
-
const call2 =
|
|
4955
|
+
const call2 = record4(message2.call);
|
|
4547
4956
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
4548
4957
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
4549
4958
|
}
|
|
@@ -4555,7 +4964,7 @@ function parseAgentOutput(line) {
|
|
|
4555
4964
|
};
|
|
4556
4965
|
}
|
|
4557
4966
|
if (message2.type === "tool.request") {
|
|
4558
|
-
const input =
|
|
4967
|
+
const input = record4(message2.input);
|
|
4559
4968
|
const tool = String(message2.tool);
|
|
4560
4969
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
4561
4970
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -4898,8 +5307,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4898
5307
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4899
5308
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4900
5309
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4901
|
-
const entries = inventory.flatMap((
|
|
4902
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5310
|
+
const entries = inventory.flatMap((record10) => {
|
|
5311
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
|
|
4903
5312
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4904
5313
|
});
|
|
4905
5314
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5147,8 +5556,8 @@ function normalize(value2) {
|
|
|
5147
5556
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5148
5557
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5149
5558
|
if (typeof value2 === "object") {
|
|
5150
|
-
const
|
|
5151
|
-
return Object.fromEntries(Object.keys(
|
|
5559
|
+
const record10 = value2;
|
|
5560
|
+
return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
|
|
5152
5561
|
}
|
|
5153
5562
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5154
5563
|
}
|
|
@@ -5227,7 +5636,7 @@ function normalizeReaders(readers) {
|
|
|
5227
5636
|
}
|
|
5228
5637
|
|
|
5229
5638
|
// ../camel/dist/code.js
|
|
5230
|
-
var
|
|
5639
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
5231
5640
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5232
5641
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5233
5642
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -5262,18 +5671,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5262
5671
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
5263
5672
|
}
|
|
5264
5673
|
function validate(fields) {
|
|
5265
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
5674
|
+
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST2.test(fields.trustedBaseDigest) || !DIGEST2.test(fields.patchDigest) || !DIGEST2.test(fields.candidateDigest) || !DIGEST2.test(fields.sourceDigest) || !DIGEST2.test(fields.policyDigest) || !DIGEST2.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) {
|
|
5266
5675
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
5267
5676
|
}
|
|
5268
5677
|
const ids = /* @__PURE__ */ new Set();
|
|
5269
5678
|
for (const recipe2 of fields.recipes) {
|
|
5270
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
5679
|
+
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST2.test(recipe2.recipeDigest) || !["passed", "failed", "timed_out", "output_limited"].includes(recipe2.status) || !Number.isSafeInteger(recipe2.exitCode) || recipe2.exitCode < 0 || recipe2.exitCode > 255 || !Number.isSafeInteger(recipe2.durationMs) || recipe2.durationMs < 0 || recipe2.durationMs > 30 * 6e4) {
|
|
5271
5680
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
5272
5681
|
}
|
|
5273
5682
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
5274
5683
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
5275
5684
|
for (const artifact of recipe2.artifacts) {
|
|
5276
|
-
if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !
|
|
5685
|
+
if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !DIGEST2.test(artifact.digest) || artifact.status === "verified" !== (artifact.bytes !== null && artifact.digest !== null) || ["missing", "invalid"].includes(artifact.status) && (artifact.bytes !== null || artifact.digest !== null) || artifact.status === "too_large" && (artifact.bytes === null || artifact.digest !== null)) {
|
|
5277
5686
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
5278
5687
|
}
|
|
5279
5688
|
artifactIds.add(artifact.artifactId);
|
|
@@ -5289,7 +5698,7 @@ function validate(fields) {
|
|
|
5289
5698
|
}
|
|
5290
5699
|
}
|
|
5291
5700
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5292
|
-
var
|
|
5701
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
5293
5702
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
5294
5703
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
5295
5704
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -5334,14 +5743,14 @@ function normalizeState(value2) {
|
|
|
5334
5743
|
]);
|
|
5335
5744
|
const planCursor = state2.planCursor;
|
|
5336
5745
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
5337
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
5338
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
5746
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
5747
|
+
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !DIGEST22.test(state2.planningInputDigest) || typeof state2.buildPolicyDigest !== "string" || !DIGEST22.test(state2.buildPolicyDigest) || state2.dependencyLayerDigest !== null && (typeof state2.dependencyLayerDigest !== "string" || !DIGEST22.test(state2.dependencyLayerDigest)) || state2.verificationDigest !== null && (typeof state2.verificationDigest !== "string" || !DIGEST22.test(state2.verificationDigest)) || state2.reviewDigest !== null && (typeof state2.reviewDigest !== "string" || !DIGEST22.test(state2.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state2.trustStatus)) || !Array.isArray(state2.completedEffects) || state2.completedEffects.length > 256) {
|
|
5339
5748
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
5340
5749
|
}
|
|
5341
5750
|
const effects = state2.completedEffects.map((item) => {
|
|
5342
5751
|
const effect = object(item, "completed effect");
|
|
5343
5752
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
5344
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
5753
|
+
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST22.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST22.test(effect.receiptDigest)) {
|
|
5345
5754
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
5346
5755
|
}
|
|
5347
5756
|
return {
|
|
@@ -5871,7 +6280,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5871
6280
|
}
|
|
5872
6281
|
const value2 = await response2.json().catch(() => null);
|
|
5873
6282
|
if (!response2.ok) {
|
|
5874
|
-
const problem =
|
|
6283
|
+
const problem = record5(record5(value2)?.error);
|
|
5875
6284
|
throw new CodeRuntimeControlError(
|
|
5876
6285
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5877
6286
|
response2.status,
|
|
@@ -5893,12 +6302,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5893
6302
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5894
6303
|
),
|
|
5895
6304
|
infer: async (sessionId, inference) => {
|
|
5896
|
-
const value2 =
|
|
6305
|
+
const value2 = record5(await call2(
|
|
5897
6306
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5898
6307
|
inference,
|
|
5899
6308
|
modelRequestTimeoutMs
|
|
5900
6309
|
));
|
|
5901
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6310
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5902
6311
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5903
6312
|
}
|
|
5904
6313
|
return value2;
|
|
@@ -5956,12 +6365,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5956
6365
|
}
|
|
5957
6366
|
}
|
|
5958
6367
|
function parseSnapshot(value2) {
|
|
5959
|
-
const root =
|
|
5960
|
-
const host =
|
|
6368
|
+
const root = record5(value2);
|
|
6369
|
+
const host = record5(root?.host);
|
|
5961
6370
|
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");
|
|
5962
6371
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5963
6372
|
const bindings = root.bindings.map((item) => {
|
|
5964
|
-
const binding =
|
|
6373
|
+
const binding = record5(item);
|
|
5965
6374
|
if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
|
|
5966
6375
|
throw invalid("binding");
|
|
5967
6376
|
}
|
|
@@ -5971,10 +6380,10 @@ function parseSnapshot(value2) {
|
|
|
5971
6380
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5972
6381
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5973
6382
|
const commands = root.commands.map((item) => {
|
|
5974
|
-
const command =
|
|
6383
|
+
const command = record5(item);
|
|
5975
6384
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5976
6385
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5977
|
-
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !
|
|
6386
|
+
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record5(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
|
|
5978
6387
|
commandIds.add(command.commandId);
|
|
5979
6388
|
commandSequences.add(sequenceKey);
|
|
5980
6389
|
return command;
|
|
@@ -5982,10 +6391,10 @@ function parseSnapshot(value2) {
|
|
|
5982
6391
|
return { host, bindings, commands };
|
|
5983
6392
|
}
|
|
5984
6393
|
async function parseSource(value2) {
|
|
5985
|
-
const snapshot =
|
|
6394
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5986
6395
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5987
6396
|
const files = snapshot.files.map((value22) => {
|
|
5988
|
-
const file =
|
|
6397
|
+
const file = record5(value22);
|
|
5989
6398
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5990
6399
|
return { path: file.path, content: file.content };
|
|
5991
6400
|
});
|
|
@@ -5994,11 +6403,11 @@ async function parseSource(value2) {
|
|
|
5994
6403
|
const aliases = /* @__PURE__ */ new Set();
|
|
5995
6404
|
const references = [];
|
|
5996
6405
|
for (const item of referencesValue) {
|
|
5997
|
-
const reference =
|
|
6406
|
+
const reference = record5(item);
|
|
5998
6407
|
if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
|
|
5999
6408
|
aliases.add(reference.alias);
|
|
6000
6409
|
const referenceFiles = reference.files.map((entry) => {
|
|
6001
|
-
const file =
|
|
6410
|
+
const file = record5(entry);
|
|
6002
6411
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
6003
6412
|
return { path: file.path, content: file.content };
|
|
6004
6413
|
});
|
|
@@ -6013,18 +6422,18 @@ async function parseSource(value2) {
|
|
|
6013
6422
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
6014
6423
|
}
|
|
6015
6424
|
function parseReview(value2) {
|
|
6016
|
-
const review =
|
|
6425
|
+
const review = record5(record5(value2)?.review);
|
|
6017
6426
|
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");
|
|
6018
6427
|
return review;
|
|
6019
6428
|
}
|
|
6020
6429
|
function parseCandidate(value2) {
|
|
6021
|
-
const candidate =
|
|
6430
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
6022
6431
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
6023
6432
|
throw invalid("candidate");
|
|
6024
6433
|
}
|
|
6025
6434
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
6026
6435
|
}
|
|
6027
|
-
var
|
|
6436
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6028
6437
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
6029
6438
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
6030
6439
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -7387,13 +7796,6 @@ var CodePiRuntimeEngine = class {
|
|
|
7387
7796
|
}
|
|
7388
7797
|
};
|
|
7389
7798
|
|
|
7390
|
-
// src/version.ts
|
|
7391
|
-
var import_node_fs13 = require("fs");
|
|
7392
|
-
function cliVersion() {
|
|
7393
|
-
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
7394
|
-
return pkg.version ?? "unknown";
|
|
7395
|
-
}
|
|
7396
|
-
|
|
7397
7799
|
// src/security-hosted-github.ts
|
|
7398
7800
|
var import_node_child_process4 = require("child_process");
|
|
7399
7801
|
var import_node_util2 = require("util");
|
|
@@ -7669,7 +8071,7 @@ var import_node_child_process6 = require("child_process");
|
|
|
7669
8071
|
var import_node_crypto3 = require("crypto");
|
|
7670
8072
|
var import_promises10 = require("fs/promises");
|
|
7671
8073
|
var import_node_os3 = require("os");
|
|
7672
|
-
var
|
|
8074
|
+
var import_node_path13 = require("path");
|
|
7673
8075
|
var import_node_url3 = require("url");
|
|
7674
8076
|
|
|
7675
8077
|
// src/code-runtime-config.ts
|
|
@@ -7755,10 +8157,10 @@ async function embeddedPiImageName() {
|
|
|
7755
8157
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7756
8158
|
}
|
|
7757
8159
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7758
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
8160
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7759
8161
|
try {
|
|
7760
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7761
|
-
await (0, import_promises10.writeFile)((0,
|
|
8162
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
|
|
8163
|
+
await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
|
|
7762
8164
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7763
8165
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7764
8166
|
"WORKDIR /workspace",
|
|
@@ -7774,8 +8176,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7774
8176
|
// src/code-connect.ts
|
|
7775
8177
|
async function codeConnect(options) {
|
|
7776
8178
|
const cwd = options.cwd ?? process.cwd();
|
|
7777
|
-
const configPath = (0,
|
|
7778
|
-
const cfg = (0,
|
|
8179
|
+
const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
|
|
8180
|
+
const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7779
8181
|
const requestedAppId = options.appId?.trim();
|
|
7780
8182
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
7781
8183
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -7933,20 +8335,20 @@ async function runCodeRuntime(input) {
|
|
|
7933
8335
|
}
|
|
7934
8336
|
}
|
|
7935
8337
|
function parseConnection(value2, appId, appEnv) {
|
|
7936
|
-
const root =
|
|
7937
|
-
const host =
|
|
7938
|
-
const offer =
|
|
7939
|
-
const binding =
|
|
8338
|
+
const root = record6(value2);
|
|
8339
|
+
const host = record6(root?.host);
|
|
8340
|
+
const offer = record6(root?.offer);
|
|
8341
|
+
const binding = record6(root?.binding);
|
|
7940
8342
|
if (!root || typeof root.token !== "string" || !/^odla_code_host_[0-9a-f]{64}$/.test(root.token) || typeof root.resumed !== "boolean" || !host || !/^chost_[0-9a-f]{32}$/.test(String(host.hostId)) || typeof host.name !== "string" || !offer || !Number.isSafeInteger(offer.slots) || !binding || typeof binding.appId !== "string" || !binding.appId || appId && binding.appId !== appId || binding.env !== appEnv || !Number.isSafeInteger(binding.generation)) {
|
|
7941
8343
|
throw new Error("connect Code host returned an invalid response");
|
|
7942
8344
|
}
|
|
7943
8345
|
return root;
|
|
7944
8346
|
}
|
|
7945
8347
|
function apiFailure(action2, status, value2) {
|
|
7946
|
-
const message2 =
|
|
8348
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
7947
8349
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7948
8350
|
}
|
|
7949
|
-
function
|
|
8351
|
+
function record6(value2) {
|
|
7950
8352
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7951
8353
|
}
|
|
7952
8354
|
|
|
@@ -8161,8 +8563,9 @@ Usage:
|
|
|
8161
8563
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8162
8564
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8163
8565
|
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 [--
|
|
8566
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8567
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8568
|
+
odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8166
8569
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8167
8570
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8168
8571
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8287,8 +8690,8 @@ Commands:
|
|
|
8287
8690
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8288
8691
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8289
8692
|
doctor Validate and summarize the project config without network calls.
|
|
8290
|
-
config
|
|
8291
|
-
|
|
8693
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
8694
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8292
8695
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8293
8696
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8294
8697
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -8995,8 +9398,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8995
9398
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8996
9399
|
}
|
|
8997
9400
|
async function pmGet(ctx, entity, id) {
|
|
8998
|
-
const { record:
|
|
8999
|
-
emit2(ctx,
|
|
9401
|
+
const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9402
|
+
emit2(ctx, record10, () => printRecord(ctx, entity, record10));
|
|
9000
9403
|
}
|
|
9001
9404
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9002
9405
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9041,9 +9444,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9041
9444
|
]);
|
|
9042
9445
|
const handoff = {
|
|
9043
9446
|
appId,
|
|
9044
|
-
unmetGoals: goals.filter((
|
|
9045
|
-
activeTasks: tasks.filter((
|
|
9046
|
-
openBugs: bugs.filter((
|
|
9447
|
+
unmetGoals: goals.filter((record10) => record10.status !== "met"),
|
|
9448
|
+
activeTasks: tasks.filter((record10) => record10.column !== "done"),
|
|
9449
|
+
openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
|
|
9047
9450
|
};
|
|
9048
9451
|
const result = {
|
|
9049
9452
|
...handoff,
|
|
@@ -9062,10 +9465,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9062
9465
|
]) {
|
|
9063
9466
|
ctx.out.log(`${label}:`);
|
|
9064
9467
|
if (!records.length) ctx.out.log("- (none)");
|
|
9065
|
-
else for (const
|
|
9468
|
+
else for (const record10 of records) printRecord(
|
|
9066
9469
|
ctx,
|
|
9067
9470
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9068
|
-
|
|
9471
|
+
record10
|
|
9069
9472
|
);
|
|
9070
9473
|
}
|
|
9071
9474
|
});
|
|
@@ -9306,17 +9709,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9306
9709
|
}
|
|
9307
9710
|
}
|
|
9308
9711
|
function isPlatformStatus(value2) {
|
|
9309
|
-
if (!
|
|
9310
|
-
if (!
|
|
9311
|
-
if (!
|
|
9712
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9713
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9714
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
9312
9715
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9313
9716
|
}
|
|
9314
9717
|
function apiMessage(value2) {
|
|
9315
|
-
if (!
|
|
9316
|
-
const error =
|
|
9718
|
+
if (!record7(value2)) return "request failed";
|
|
9719
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9317
9720
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9318
9721
|
}
|
|
9319
|
-
function
|
|
9722
|
+
function record7(value2) {
|
|
9320
9723
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9321
9724
|
}
|
|
9322
9725
|
|
|
@@ -9357,7 +9760,7 @@ function statusVerdict(reads) {
|
|
|
9357
9760
|
severity: "degraded"
|
|
9358
9761
|
});
|
|
9359
9762
|
}
|
|
9360
|
-
const performance =
|
|
9763
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9361
9764
|
if (performance?.status === "unavailable") {
|
|
9362
9765
|
reasons.push({
|
|
9363
9766
|
source: "liveSync",
|
|
@@ -9438,7 +9841,7 @@ function statusVerdict(reads) {
|
|
|
9438
9841
|
reasons
|
|
9439
9842
|
};
|
|
9440
9843
|
}
|
|
9441
|
-
function
|
|
9844
|
+
function record8(value2) {
|
|
9442
9845
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9443
9846
|
}
|
|
9444
9847
|
function numeric2(value2) {
|
|
@@ -9466,7 +9869,7 @@ function printO11yStatus(status, out) {
|
|
|
9466
9869
|
out.log(
|
|
9467
9870
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9468
9871
|
);
|
|
9469
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
9872
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
9470
9873
|
const requests = routes.reduce(
|
|
9471
9874
|
(total, row) => total + numeric3(row.requests),
|
|
9472
9875
|
0
|
|
@@ -9478,39 +9881,39 @@ function printO11yStatus(status, out) {
|
|
|
9478
9881
|
out.log(
|
|
9479
9882
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9480
9883
|
);
|
|
9481
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
9884
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
9482
9885
|
out.log(
|
|
9483
9886
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9484
9887
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9485
9888
|
).join(", ") : "none observed"}`
|
|
9486
9889
|
);
|
|
9487
9890
|
out.log(liveSyncLine(status.liveSync));
|
|
9488
|
-
const canaryDurations =
|
|
9891
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9489
9892
|
out.log(
|
|
9490
9893
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9491
9894
|
);
|
|
9492
|
-
const collectorIngest =
|
|
9493
|
-
const collectorStorage =
|
|
9895
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9896
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9494
9897
|
out.log(
|
|
9495
9898
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9496
9899
|
);
|
|
9497
|
-
const providerMetrics =
|
|
9498
|
-
const providerCapacity =
|
|
9499
|
-
const workerMemory =
|
|
9900
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9901
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9902
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9500
9903
|
out.log(
|
|
9501
9904
|
`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`
|
|
9502
9905
|
);
|
|
9503
9906
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9504
9907
|
out.log(line);
|
|
9505
9908
|
}
|
|
9506
|
-
const coverage =
|
|
9507
|
-
const coverageCounts =
|
|
9508
|
-
const coverageBudget =
|
|
9909
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9910
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9911
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9509
9912
|
out.log(
|
|
9510
9913
|
`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`
|
|
9511
9914
|
);
|
|
9512
9915
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9513
|
-
const providerFreshness =
|
|
9916
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9514
9917
|
out.log(
|
|
9515
9918
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9516
9919
|
);
|
|
@@ -9519,17 +9922,17 @@ function printO11yStatus(status, out) {
|
|
|
9519
9922
|
);
|
|
9520
9923
|
}
|
|
9521
9924
|
function providerCapacityLines(read3) {
|
|
9522
|
-
const resources =
|
|
9523
|
-
const durableObjects =
|
|
9524
|
-
const periodic =
|
|
9525
|
-
const storage =
|
|
9526
|
-
const d1 =
|
|
9527
|
-
const d1Activity =
|
|
9528
|
-
const d1Storage =
|
|
9529
|
-
const d1Latency =
|
|
9530
|
-
const r2 =
|
|
9531
|
-
const r2Operations =
|
|
9532
|
-
const r2Storage =
|
|
9925
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
9926
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
9927
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9928
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9929
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
9930
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
9931
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
9932
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
9933
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
9934
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
9935
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
9533
9936
|
const status = String(
|
|
9534
9937
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9535
9938
|
);
|
|
@@ -9540,11 +9943,11 @@ function providerCapacityLines(read3) {
|
|
|
9540
9943
|
];
|
|
9541
9944
|
}
|
|
9542
9945
|
function liveSyncLine(read3) {
|
|
9543
|
-
const performance =
|
|
9544
|
-
const commitToSend =
|
|
9946
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
9947
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
9545
9948
|
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`;
|
|
9546
9949
|
}
|
|
9547
|
-
function
|
|
9950
|
+
function record9(value2) {
|
|
9548
9951
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9549
9952
|
}
|
|
9550
9953
|
function numeric3(value2) {
|
|
@@ -9727,7 +10130,7 @@ async function read2(url, headers, doFetch) {
|
|
|
9727
10130
|
}
|
|
9728
10131
|
|
|
9729
10132
|
// src/provision.ts
|
|
9730
|
-
var
|
|
10133
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
9731
10134
|
var import_ai3 = require("@odla-ai/ai");
|
|
9732
10135
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
9733
10136
|
|
|
@@ -9784,9 +10187,9 @@ async function responseText(res) {
|
|
|
9784
10187
|
}
|
|
9785
10188
|
|
|
9786
10189
|
// src/provision-credentials.ts
|
|
9787
|
-
var
|
|
10190
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
9788
10191
|
async function provisionEnvCredentials(opts) {
|
|
9789
|
-
const tenantId = (0,
|
|
10192
|
+
const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9790
10193
|
const prior = opts.credentials?.envs[opts.env];
|
|
9791
10194
|
let credentials = opts.credentials;
|
|
9792
10195
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9948,7 +10351,7 @@ async function provision(options) {
|
|
|
9948
10351
|
}
|
|
9949
10352
|
const doFetch = options.fetch ?? fetch;
|
|
9950
10353
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9951
|
-
const apps = (0,
|
|
10354
|
+
const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9952
10355
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9953
10356
|
if (existing) {
|
|
9954
10357
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9959,7 +10362,7 @@ async function provision(options) {
|
|
|
9959
10362
|
for (const env of cfg.envs) {
|
|
9960
10363
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9961
10364
|
}
|
|
9962
|
-
const serviceOrder = (0,
|
|
10365
|
+
const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
|
|
9963
10366
|
for (const env of cfg.envs) {
|
|
9964
10367
|
for (const service of serviceOrder) {
|
|
9965
10368
|
if (service === "ai") {
|
|
@@ -9992,7 +10395,7 @@ async function provision(options) {
|
|
|
9992
10395
|
}
|
|
9993
10396
|
}
|
|
9994
10397
|
for (const env of cfg.envs) {
|
|
9995
|
-
const tenantId = (0,
|
|
10398
|
+
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
9996
10399
|
credentials = await provisionEnvCredentials({
|
|
9997
10400
|
cfg,
|
|
9998
10401
|
env,
|
|
@@ -10077,7 +10480,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10077
10480
|
}
|
|
10078
10481
|
|
|
10079
10482
|
// src/record.ts
|
|
10080
|
-
var
|
|
10483
|
+
var import_node_fs16 = require("fs");
|
|
10081
10484
|
var import_node_process11 = __toESM(require("process"), 1);
|
|
10082
10485
|
|
|
10083
10486
|
// src/surface.ts
|
|
@@ -10126,7 +10529,7 @@ var COMMAND_SURFACE = {
|
|
|
10126
10529
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10127
10530
|
capabilities: {},
|
|
10128
10531
|
code: { connect: {} },
|
|
10129
|
-
config: { diff: {}, plan: {} },
|
|
10532
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
10130
10533
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
10131
10534
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
10132
10535
|
discuss: {
|
|
@@ -10144,6 +10547,7 @@ var COMMAND_SURFACE = {
|
|
|
10144
10547
|
help: {},
|
|
10145
10548
|
init: {},
|
|
10146
10549
|
o11y: { status: {} },
|
|
10550
|
+
operations: { get: {}, wait: {} },
|
|
10147
10551
|
platform: { status: {} },
|
|
10148
10552
|
pm: {
|
|
10149
10553
|
...PM_ENTITIES,
|
|
@@ -10240,14 +10644,14 @@ function recordInvocation(parsed) {
|
|
|
10240
10644
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
10241
10645
|
};
|
|
10242
10646
|
if (!entry.path.length) return;
|
|
10243
|
-
(0,
|
|
10647
|
+
(0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
10244
10648
|
`);
|
|
10245
10649
|
} catch {
|
|
10246
10650
|
}
|
|
10247
10651
|
}
|
|
10248
10652
|
|
|
10249
10653
|
// src/runbook-actions.ts
|
|
10250
|
-
var
|
|
10654
|
+
var import_node_fs17 = require("fs");
|
|
10251
10655
|
|
|
10252
10656
|
// src/runbook-requires.ts
|
|
10253
10657
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10332,7 +10736,7 @@ async function bySlug(ctx, slug) {
|
|
|
10332
10736
|
function readBody(file, inline) {
|
|
10333
10737
|
if (inline !== void 0) return inline;
|
|
10334
10738
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10335
|
-
return (0,
|
|
10739
|
+
return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
10336
10740
|
}
|
|
10337
10741
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10338
10742
|
async function runbookList(ctx, all, query) {
|
|
@@ -10419,8 +10823,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
10419
10823
|
}
|
|
10420
10824
|
|
|
10421
10825
|
// src/runbook-import.ts
|
|
10422
|
-
var
|
|
10423
|
-
var
|
|
10826
|
+
var import_node_fs18 = require("fs");
|
|
10827
|
+
var import_node_path15 = require("path");
|
|
10424
10828
|
function parseRunbook(text, slug) {
|
|
10425
10829
|
let rest = text;
|
|
10426
10830
|
const meta = {};
|
|
@@ -10445,12 +10849,12 @@ function parseRunbook(text, slug) {
|
|
|
10445
10849
|
};
|
|
10446
10850
|
}
|
|
10447
10851
|
function readRunbookDir(dir) {
|
|
10448
|
-
if (!(0,
|
|
10449
|
-
const files = (0,
|
|
10852
|
+
if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
10853
|
+
const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10450
10854
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10451
10855
|
return files.map((file) => {
|
|
10452
|
-
const slug = (0,
|
|
10453
|
-
const parsed = parseRunbook((0,
|
|
10856
|
+
const slug = (0, import_node_path15.basename)(file, ".md");
|
|
10857
|
+
const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
|
|
10454
10858
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10455
10859
|
});
|
|
10456
10860
|
}
|
|
@@ -10522,8 +10926,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10522
10926
|
|
|
10523
10927
|
// src/runbook-impact.ts
|
|
10524
10928
|
var import_node_child_process7 = require("child_process");
|
|
10525
|
-
var
|
|
10526
|
-
var
|
|
10929
|
+
var import_node_fs19 = require("fs");
|
|
10930
|
+
var import_node_path16 = require("path");
|
|
10527
10931
|
|
|
10528
10932
|
// src/runbook-impact-scan.ts
|
|
10529
10933
|
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$]*)/;
|
|
@@ -10692,10 +11096,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10692
11096
|
}
|
|
10693
11097
|
function manifestLabeller(root) {
|
|
10694
11098
|
return (workspace) => {
|
|
10695
|
-
const manifest = (0,
|
|
10696
|
-
if (!(0,
|
|
11099
|
+
const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
|
|
11100
|
+
if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
|
|
10697
11101
|
try {
|
|
10698
|
-
const name = JSON.parse((0,
|
|
11102
|
+
const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
|
|
10699
11103
|
return typeof name === "string" ? name : void 0;
|
|
10700
11104
|
} catch {
|
|
10701
11105
|
return void 0;
|
|
@@ -10762,7 +11166,7 @@ function report3(ctx, impacts) {
|
|
|
10762
11166
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10763
11167
|
const cwd = deps.cwd ?? process.cwd();
|
|
10764
11168
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10765
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
11169
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
|
|
10766
11170
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10767
11171
|
if (!surfaces.length) {
|
|
10768
11172
|
return ctx.out.log(
|
|
@@ -10895,9 +11299,9 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10895
11299
|
|
|
10896
11300
|
// src/runbook-editor.ts
|
|
10897
11301
|
var import_node_child_process8 = require("child_process");
|
|
10898
|
-
var
|
|
11302
|
+
var import_node_fs20 = require("fs");
|
|
10899
11303
|
var import_node_os5 = require("os");
|
|
10900
|
-
var
|
|
11304
|
+
var import_node_path17 = require("path");
|
|
10901
11305
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
10902
11306
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10903
11307
|
function resolveEditor(env = import_node_process12.default.env) {
|
|
@@ -10923,16 +11327,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
10923
11327
|
);
|
|
10924
11328
|
if (!interactive())
|
|
10925
11329
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10926
|
-
const dir = (0,
|
|
10927
|
-
const file = (0,
|
|
11330
|
+
const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
11331
|
+
const file = (0, import_node_path17.join)(dir, `${slug}.md`);
|
|
10928
11332
|
try {
|
|
10929
|
-
(0,
|
|
11333
|
+
(0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
|
|
10930
11334
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
10931
11335
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
10932
|
-
const edited = (0,
|
|
11336
|
+
const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
|
|
10933
11337
|
return edited === initial ? null : edited;
|
|
10934
11338
|
} finally {
|
|
10935
|
-
(0,
|
|
11339
|
+
(0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
|
|
10936
11340
|
}
|
|
10937
11341
|
}
|
|
10938
11342
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -11344,7 +11748,7 @@ function hostedSeverity(value2, flag) {
|
|
|
11344
11748
|
var import_security2 = require("@odla-ai/security");
|
|
11345
11749
|
|
|
11346
11750
|
// src/security.ts
|
|
11347
|
-
var
|
|
11751
|
+
var import_node_path18 = require("path");
|
|
11348
11752
|
var import_security = require("@odla-ai/security");
|
|
11349
11753
|
var import_node3 = require("@odla-ai/security/node");
|
|
11350
11754
|
async function runHostedSecurity(options) {
|
|
@@ -11356,9 +11760,9 @@ async function runHostedSecurity(options) {
|
|
|
11356
11760
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
11357
11761
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
11358
11762
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
11359
|
-
const target = (0,
|
|
11360
|
-
const output = (0,
|
|
11361
|
-
const outputRelative = (0,
|
|
11763
|
+
const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
11764
|
+
const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
|
|
11765
|
+
const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
|
|
11362
11766
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
11363
11767
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
11364
11768
|
const tokenRequest = {
|
|
@@ -11370,7 +11774,7 @@ async function runHostedSecurity(options) {
|
|
|
11370
11774
|
};
|
|
11371
11775
|
const token = await injectedToken(options, tokenRequest);
|
|
11372
11776
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
11373
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
11777
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
11374
11778
|
});
|
|
11375
11779
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
11376
11780
|
platform,
|
|
@@ -11388,7 +11792,7 @@ async function runHostedSecurity(options) {
|
|
|
11388
11792
|
});
|
|
11389
11793
|
const harness = (0, import_security.createSecurityHarness)({
|
|
11390
11794
|
profile,
|
|
11391
|
-
store: new import_node3.FileRunStore((0,
|
|
11795
|
+
store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
|
|
11392
11796
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
11393
11797
|
validationReasoner: hosted.validationReasoner,
|
|
11394
11798
|
policy: {
|
|
@@ -11412,7 +11816,7 @@ async function runHostedSecurity(options) {
|
|
|
11412
11816
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
11413
11817
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
11414
11818
|
if (!env || !declared.includes(env)) {
|
|
11415
|
-
const shown = (0,
|
|
11819
|
+
const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
|
|
11416
11820
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
11417
11821
|
}
|
|
11418
11822
|
return env;
|
|
@@ -11441,7 +11845,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
11441
11845
|
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}`);
|
|
11442
11846
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
11443
11847
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
11444
|
-
out.log(` report: ${(0,
|
|
11848
|
+
out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
|
|
11445
11849
|
}
|
|
11446
11850
|
function formatBudget(usage) {
|
|
11447
11851
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -11892,11 +12296,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11892
12296
|
// src/cli.ts
|
|
11893
12297
|
function exitCodeFor(err) {
|
|
11894
12298
|
const code = err?.code;
|
|
11895
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12299
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11896
12300
|
if (code === "checkpoint_required") return 3;
|
|
11897
12301
|
if (code === "remote_unavailable") return 6;
|
|
11898
12302
|
if (code === "auth_failed") return 5;
|
|
11899
|
-
if (code === "invalid_cursor") return 2;
|
|
12303
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11900
12304
|
return 1;
|
|
11901
12305
|
}
|
|
11902
12306
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
@@ -12043,6 +12447,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12043
12447
|
CODE_BUILD_RECIPES,
|
|
12044
12448
|
CODE_PI_IMAGE,
|
|
12045
12449
|
COMMAND_SURFACE,
|
|
12450
|
+
ConfigOperationCommandError,
|
|
12046
12451
|
GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
12047
12452
|
SYSTEM_AI_PURPOSES,
|
|
12048
12453
|
acceptedAfter,
|
|
@@ -12054,7 +12459,10 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12054
12459
|
calendarServiceConfig,
|
|
12055
12460
|
calendarStatus,
|
|
12056
12461
|
codeConnect,
|
|
12462
|
+
configApply,
|
|
12057
12463
|
configDiff,
|
|
12464
|
+
configOperationGet,
|
|
12465
|
+
configOperationWait,
|
|
12058
12466
|
configPlan,
|
|
12059
12467
|
connectGitHubSecuritySource,
|
|
12060
12468
|
describeProblem,
|