@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/bin.cjs
CHANGED
|
@@ -493,6 +493,7 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
493
493
|
var SCOPE_PURPOSE = {
|
|
494
494
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
495
495
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
496
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
496
497
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
497
498
|
"platform:ai:policy:write": "change System AI model routing",
|
|
498
499
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -1048,6 +1049,7 @@ function unique(values) {
|
|
|
1048
1049
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1049
1050
|
var DEFAULT_ENVS = ["dev"];
|
|
1050
1051
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
1052
|
+
var configImportSerial = 0;
|
|
1051
1053
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
1052
1054
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
1053
1055
|
const resolved = (0, import_node_path4.resolve)(configPath);
|
|
@@ -1255,7 +1257,8 @@ function validId2(value2) {
|
|
|
1255
1257
|
}
|
|
1256
1258
|
async function loadConfigModule(path) {
|
|
1257
1259
|
if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
|
|
1258
|
-
const
|
|
1260
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1261
|
+
const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
|
|
1259
1262
|
const value2 = mod.default ?? mod.config;
|
|
1260
1263
|
if (typeof value2 === "function") return await value2();
|
|
1261
1264
|
return value2;
|
|
@@ -2639,7 +2642,8 @@ var CAPABILITIES = {
|
|
|
2639
2642
|
"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",
|
|
2640
2643
|
"read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
|
|
2641
2644
|
"read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
|
|
2642
|
-
"compare one project's checked-in Registry intent with live owner-visible state
|
|
2645
|
+
"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",
|
|
2646
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
2643
2647
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2644
2648
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2645
2649
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2686,10 +2690,31 @@ function printGroup(out, heading, items) {
|
|
|
2686
2690
|
out.log("");
|
|
2687
2691
|
}
|
|
2688
2692
|
|
|
2689
|
-
// src/config-
|
|
2693
|
+
// src/config-operation-command.ts
|
|
2690
2694
|
var import_apps6 = require("@odla-ai/apps");
|
|
2691
2695
|
var import_node_path7 = require("path");
|
|
2692
2696
|
|
|
2697
|
+
// src/version.ts
|
|
2698
|
+
var import_node_fs9 = require("fs");
|
|
2699
|
+
function cliVersion() {
|
|
2700
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
2701
|
+
return pkg.version ?? "unknown";
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
// src/config-operation-error.ts
|
|
2705
|
+
var ConfigOperationCommandError = class extends Error {
|
|
2706
|
+
constructor(message2, code) {
|
|
2707
|
+
super(message2);
|
|
2708
|
+
this.code = code;
|
|
2709
|
+
this.name = "ConfigOperationCommandError";
|
|
2710
|
+
}
|
|
2711
|
+
code;
|
|
2712
|
+
};
|
|
2713
|
+
|
|
2714
|
+
// src/config-operation-validate.ts
|
|
2715
|
+
var import_apps3 = require("@odla-ai/apps");
|
|
2716
|
+
var import_node_fs10 = require("fs");
|
|
2717
|
+
|
|
2693
2718
|
// src/config-reconcile-digest.ts
|
|
2694
2719
|
var import_node_crypto = require("crypto");
|
|
2695
2720
|
function canonicalJson(value2) {
|
|
@@ -2699,27 +2724,160 @@ function configDigest(value2) {
|
|
|
2699
2724
|
return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
2700
2725
|
}
|
|
2701
2726
|
function canonicalValue(value2) {
|
|
2727
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
2728
|
+
if (typeof value2 === "number") {
|
|
2729
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
2730
|
+
return value2;
|
|
2731
|
+
}
|
|
2702
2732
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2703
2733
|
if (value2 && typeof value2 === "object") {
|
|
2734
|
+
const record10 = value2;
|
|
2704
2735
|
return Object.fromEntries(
|
|
2705
|
-
Object.
|
|
2736
|
+
Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
|
|
2706
2737
|
);
|
|
2707
2738
|
}
|
|
2708
|
-
|
|
2739
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// src/config-operation-validate.ts
|
|
2743
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
2744
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
2745
|
+
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;
|
|
2746
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
2747
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
2748
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
2749
|
+
function readPlan(path) {
|
|
2750
|
+
let value2;
|
|
2751
|
+
try {
|
|
2752
|
+
const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
|
|
2753
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
2754
|
+
value2 = JSON.parse(raw);
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
throw new ConfigOperationCommandError(
|
|
2757
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2758
|
+
"invalid_plan"
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
2762
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2763
|
+
invalidPlan("plan scope is invalid");
|
|
2764
|
+
}
|
|
2765
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
2766
|
+
invalidPlan("plan content revisions are invalid");
|
|
2767
|
+
}
|
|
2768
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
2769
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
2770
|
+
}
|
|
2771
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
2772
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
2773
|
+
}
|
|
2774
|
+
assertActions(value2.actions);
|
|
2775
|
+
const plan = value2;
|
|
2776
|
+
const digest = configDigest({
|
|
2777
|
+
schemaVersion: plan.schemaVersion,
|
|
2778
|
+
desiredRevision: plan.desiredRevision,
|
|
2779
|
+
observedRevision: plan.observedRevision,
|
|
2780
|
+
registryRevision: plan.registryRevision,
|
|
2781
|
+
actions: plan.actions
|
|
2782
|
+
});
|
|
2783
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
2784
|
+
return plan;
|
|
2785
|
+
}
|
|
2786
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
2787
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
2788
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
2792
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
2793
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
2794
|
+
}
|
|
2795
|
+
if (receipt.receiptDigest) {
|
|
2796
|
+
const { receiptDigest, ...fields } = receipt;
|
|
2797
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
2798
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
2799
|
+
}
|
|
2800
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
2801
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
function assertOperationId(value2) {
|
|
2805
|
+
if (!OPERATION_ID.test(value2)) {
|
|
2806
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function assertActions(actions) {
|
|
2810
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2811
|
+
for (const action2 of actions) {
|
|
2812
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
2813
|
+
const id = String(action2.id ?? "");
|
|
2814
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
2815
|
+
ids.add(id);
|
|
2816
|
+
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))) {
|
|
2817
|
+
invalidPlan("plan action metadata is invalid");
|
|
2818
|
+
}
|
|
2819
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
2820
|
+
assertConditionalAction(action2);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function assertConditionalAction(action2) {
|
|
2825
|
+
if (action2.kind === "rename_app") {
|
|
2826
|
+
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");
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
2830
|
+
if (action2.kind === "set_link") {
|
|
2831
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
|
|
2835
|
+
invalidPlan("service action names an unknown service");
|
|
2836
|
+
}
|
|
2837
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
2838
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
2839
|
+
invalidPlan("service action path is invalid");
|
|
2840
|
+
}
|
|
2841
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
2842
|
+
invalidPlan("service action payload is invalid");
|
|
2843
|
+
}
|
|
2844
|
+
if (action2.kind === "enable_service") {
|
|
2845
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
2846
|
+
invalidPlan("service enable action is invalid");
|
|
2847
|
+
}
|
|
2848
|
+
} else if (!record2(action2.before)) {
|
|
2849
|
+
invalidPlan("service configure action is invalid");
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function linkState(value2) {
|
|
2853
|
+
if (value2 === null) return true;
|
|
2854
|
+
if (typeof value2 !== "string") return false;
|
|
2855
|
+
try {
|
|
2856
|
+
const url = new URL(value2.trim());
|
|
2857
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
2858
|
+
} catch {
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function invalidPlan(message2) {
|
|
2863
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
2864
|
+
}
|
|
2865
|
+
function record2(value2) {
|
|
2866
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2709
2867
|
}
|
|
2710
2868
|
|
|
2711
2869
|
// src/config-reconcile-desired.ts
|
|
2712
|
-
var
|
|
2870
|
+
var import_apps5 = require("@odla-ai/apps");
|
|
2713
2871
|
|
|
2714
2872
|
// src/provision-helpers.ts
|
|
2715
2873
|
var import_ai = require("@odla-ai/ai");
|
|
2716
|
-
var
|
|
2874
|
+
var import_apps4 = require("@odla-ai/apps");
|
|
2717
2875
|
function defaultSecretName(provider) {
|
|
2718
2876
|
const names = import_ai.DEFAULT_SECRET_NAMES;
|
|
2719
2877
|
return names[provider] ?? `${provider}_api_key`;
|
|
2720
2878
|
}
|
|
2721
2879
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
2722
|
-
const tenantId = (0,
|
|
2880
|
+
const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
|
|
2723
2881
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
|
|
2724
2882
|
headers: { authorization: `Bearer ${token}` }
|
|
2725
2883
|
});
|
|
@@ -2761,7 +2919,7 @@ async function safeText3(res) {
|
|
|
2761
2919
|
// src/config-reconcile-desired.ts
|
|
2762
2920
|
function desiredRegistryState(cfg) {
|
|
2763
2921
|
const environments = {};
|
|
2764
|
-
const services = (0,
|
|
2922
|
+
const services = (0, import_apps5.orderAppServices)(cfg.services);
|
|
2765
2923
|
for (const env of cfg.envs) {
|
|
2766
2924
|
const desiredServices = {};
|
|
2767
2925
|
for (const service of services) {
|
|
@@ -2801,8 +2959,208 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
2801
2959
|
return {};
|
|
2802
2960
|
}
|
|
2803
2961
|
|
|
2962
|
+
// src/config-reconcile-support.ts
|
|
2963
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
2964
|
+
"rename_app",
|
|
2965
|
+
"enable_service",
|
|
2966
|
+
"configure_service",
|
|
2967
|
+
"set_link"
|
|
2968
|
+
]);
|
|
2969
|
+
function configApplySupport(reconciliation) {
|
|
2970
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
2971
|
+
return {
|
|
2972
|
+
supported: false,
|
|
2973
|
+
checkpointRequired: false,
|
|
2974
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
if (!reconciliation.actions.length) {
|
|
2978
|
+
return {
|
|
2979
|
+
supported: false,
|
|
2980
|
+
checkpointRequired: false,
|
|
2981
|
+
reason: "there are no managed changes to apply"
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
const blocked = reconciliation.actions.filter(
|
|
2985
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
2986
|
+
);
|
|
2987
|
+
if (blocked.length) {
|
|
2988
|
+
return {
|
|
2989
|
+
supported: false,
|
|
2990
|
+
checkpointRequired: blocked.some(
|
|
2991
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
2992
|
+
),
|
|
2993
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
return {
|
|
2997
|
+
supported: true,
|
|
2998
|
+
checkpointRequired: false,
|
|
2999
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// src/config-operation-command.ts
|
|
3004
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
3005
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
3006
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
3007
|
+
async function configApply(options) {
|
|
3008
|
+
const plan = readPlan(options.planPath);
|
|
3009
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3010
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
3011
|
+
const support = configApplySupport(plan);
|
|
3012
|
+
if (!support.supported) {
|
|
3013
|
+
throw new ConfigOperationCommandError(
|
|
3014
|
+
support.reason,
|
|
3015
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
3019
|
+
throw new ConfigOperationCommandError(
|
|
3020
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
3021
|
+
"checkpoint_required"
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
3025
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
3026
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3027
|
+
}
|
|
3028
|
+
const client = await operationClient(cfg, options, "apply");
|
|
3029
|
+
const request2 = {
|
|
3030
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
3031
|
+
expectedRevision: plan.registryRevision,
|
|
3032
|
+
desiredRevision: plan.desiredRevision,
|
|
3033
|
+
observedRevision: plan.observedRevision,
|
|
3034
|
+
planDigest: plan.planDigest,
|
|
3035
|
+
idempotencyKey,
|
|
3036
|
+
source: { kind: "cli", version: cliVersion() },
|
|
3037
|
+
actions: plan.actions
|
|
3038
|
+
};
|
|
3039
|
+
let receipt;
|
|
3040
|
+
try {
|
|
3041
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
3042
|
+
} catch (error) {
|
|
3043
|
+
const retained = retainedReceipt(error);
|
|
3044
|
+
if (retained) {
|
|
3045
|
+
verifyReceipt(retained, cfg.app.id);
|
|
3046
|
+
printReceipt(retained, options);
|
|
3047
|
+
throw failureForReceipt(retained);
|
|
3048
|
+
}
|
|
3049
|
+
throw normalizeRequestError(error);
|
|
3050
|
+
}
|
|
3051
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
3052
|
+
printReceipt(receipt, options);
|
|
3053
|
+
assertApplyCompleted(receipt);
|
|
3054
|
+
return receipt;
|
|
3055
|
+
}
|
|
3056
|
+
async function configOperationGet(options) {
|
|
3057
|
+
assertOperationId(options.operationId);
|
|
3058
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3059
|
+
const client = await operationClient(cfg, options, "read");
|
|
3060
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3061
|
+
throw normalizeRequestError(error);
|
|
3062
|
+
});
|
|
3063
|
+
if (!receipt) {
|
|
3064
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3065
|
+
}
|
|
3066
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3067
|
+
printReceipt(receipt, options);
|
|
3068
|
+
return receipt;
|
|
3069
|
+
}
|
|
3070
|
+
async function configOperationWait(options) {
|
|
3071
|
+
assertOperationId(options.operationId);
|
|
3072
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
3073
|
+
const client = await operationClient(cfg, options, "wait");
|
|
3074
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
3075
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3076
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3077
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
3078
|
+
let receipt = null;
|
|
3079
|
+
for (; ; ) {
|
|
3080
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
3081
|
+
throw normalizeRequestError(error);
|
|
3082
|
+
});
|
|
3083
|
+
if (!receipt) {
|
|
3084
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
3085
|
+
}
|
|
3086
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
3087
|
+
if (receipt.state !== "running") break;
|
|
3088
|
+
if (now() >= deadline) {
|
|
3089
|
+
printReceipt(receipt, options);
|
|
3090
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
3091
|
+
}
|
|
3092
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
3093
|
+
}
|
|
3094
|
+
printReceipt(receipt, options);
|
|
3095
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
3096
|
+
return receipt;
|
|
3097
|
+
}
|
|
3098
|
+
async function operationClient(cfg, options, purpose) {
|
|
3099
|
+
const doFetch = options.fetch ?? fetch;
|
|
3100
|
+
const out = options.stdout ?? console;
|
|
3101
|
+
const token = await resolveAdminPlatformToken({
|
|
3102
|
+
platform: cfg.platformUrl,
|
|
3103
|
+
scope: "app:config:write",
|
|
3104
|
+
token: options.token,
|
|
3105
|
+
tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3106
|
+
rootDir: cfg.rootDir,
|
|
3107
|
+
email: options.email,
|
|
3108
|
+
open: options.open,
|
|
3109
|
+
fetch: doFetch,
|
|
3110
|
+
stdout: out,
|
|
3111
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
3112
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
3113
|
+
});
|
|
3114
|
+
return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3115
|
+
}
|
|
3116
|
+
function printReceipt(receipt, options) {
|
|
3117
|
+
const out = options.stdout ?? console;
|
|
3118
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
3119
|
+
else {
|
|
3120
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
3121
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
3122
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
3123
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
3124
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
function assertApplyCompleted(receipt) {
|
|
3128
|
+
if (receipt.state === "succeeded") return;
|
|
3129
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
3130
|
+
}
|
|
3131
|
+
function failureForReceipt(receipt) {
|
|
3132
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
3133
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
3134
|
+
"checkpoint_required"
|
|
3135
|
+
);
|
|
3136
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
3137
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3138
|
+
}
|
|
3139
|
+
function retainedReceipt(error) {
|
|
3140
|
+
if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
|
|
3141
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
3142
|
+
}
|
|
3143
|
+
function normalizeRequestError(error) {
|
|
3144
|
+
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
3145
|
+
if (error.status === 401 || error.status === 403) {
|
|
3146
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
3147
|
+
}
|
|
3148
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
3149
|
+
if (error.status === 429 || error.status >= 500) {
|
|
3150
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
3151
|
+
}
|
|
3152
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3153
|
+
}
|
|
3154
|
+
function record3(value2) {
|
|
3155
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// src/config-reconcile-command.ts
|
|
3159
|
+
var import_apps8 = require("@odla-ai/apps");
|
|
3160
|
+
var import_node_path8 = require("path");
|
|
3161
|
+
|
|
2804
3162
|
// src/config-reconcile.ts
|
|
2805
|
-
var
|
|
3163
|
+
var import_apps7 = require("@odla-ai/apps");
|
|
2806
3164
|
|
|
2807
3165
|
// src/config-reconcile-values.ts
|
|
2808
3166
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
@@ -2903,6 +3261,7 @@ function reconcileConfig(input) {
|
|
|
2903
3261
|
sources: { desired: desiredSource, observed: observedSource },
|
|
2904
3262
|
desiredRevision,
|
|
2905
3263
|
observedRevision,
|
|
3264
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
2906
3265
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
2907
3266
|
summary: {
|
|
2908
3267
|
differences: different,
|
|
@@ -2957,7 +3316,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
|
|
|
2957
3316
|
function compareServices(env, desired, observed, desiredSource, observedSource, add) {
|
|
2958
3317
|
const wanted = desired.environments[env]?.services ?? {};
|
|
2959
3318
|
const live = observed?.environments[env] ?? {};
|
|
2960
|
-
const knownOrder = (0,
|
|
3319
|
+
const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
|
|
2961
3320
|
const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
|
|
2962
3321
|
for (const service of services) {
|
|
2963
3322
|
const next = wanted[service];
|
|
@@ -3068,20 +3427,19 @@ async function configDiff(options) {
|
|
|
3068
3427
|
}
|
|
3069
3428
|
async function configPlan(options) {
|
|
3070
3429
|
const reconciliation = await inspectConfig(options);
|
|
3430
|
+
const apply = configApplySupport(reconciliation);
|
|
3071
3431
|
const planDigest = configDigest({
|
|
3072
|
-
schemaVersion: "odla.config-plan/
|
|
3432
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3073
3433
|
desiredRevision: reconciliation.desiredRevision,
|
|
3074
3434
|
observedRevision: reconciliation.observedRevision,
|
|
3435
|
+
registryRevision: reconciliation.registryRevision,
|
|
3075
3436
|
actions: reconciliation.actions
|
|
3076
3437
|
});
|
|
3077
3438
|
const document2 = {
|
|
3078
|
-
schemaVersion: "odla.config-plan/
|
|
3439
|
+
schemaVersion: "odla.config-plan/v2",
|
|
3079
3440
|
...reconciliation,
|
|
3080
3441
|
planDigest,
|
|
3081
|
-
apply
|
|
3082
|
-
supported: false,
|
|
3083
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
3084
|
-
},
|
|
3442
|
+
apply,
|
|
3085
3443
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3086
3444
|
};
|
|
3087
3445
|
printPlan2(document2, options);
|
|
@@ -3095,7 +3453,7 @@ async function inspectConfig(options) {
|
|
|
3095
3453
|
platform: cfg.platformUrl,
|
|
3096
3454
|
scope: "app:config:read",
|
|
3097
3455
|
token: options.token,
|
|
3098
|
-
tokenFile: (0,
|
|
3456
|
+
tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
3099
3457
|
rootDir: cfg.rootDir,
|
|
3100
3458
|
email: options.email,
|
|
3101
3459
|
open: options.open,
|
|
@@ -3104,7 +3462,7 @@ async function inspectConfig(options) {
|
|
|
3104
3462
|
openApprovalUrl: options.openApprovalUrl,
|
|
3105
3463
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
3106
3464
|
});
|
|
3107
|
-
const client = (0,
|
|
3465
|
+
const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
3108
3466
|
const observed = await client.resolveApp(cfg.app.id);
|
|
3109
3467
|
return reconcileConfig({
|
|
3110
3468
|
desired: desiredRegistryState(cfg),
|
|
@@ -3140,7 +3498,7 @@ function printPlan2(document2, options) {
|
|
|
3140
3498
|
}
|
|
3141
3499
|
if (!document2.actions.length) out.log(" no managed changes");
|
|
3142
3500
|
out.log(`plan digest: ${document2.planDigest}`);
|
|
3143
|
-
out.log(`apply:
|
|
3501
|
+
out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
|
|
3144
3502
|
printNext(out, document2.nextActions);
|
|
3145
3503
|
}
|
|
3146
3504
|
function printHeader(out, kind, document2) {
|
|
@@ -3148,6 +3506,7 @@ function printHeader(out, kind, document2) {
|
|
|
3148
3506
|
out.log(`platform: ${document2.scope.platformUrl}`);
|
|
3149
3507
|
out.log(`desired: ${document2.desiredRevision}`);
|
|
3150
3508
|
out.log(`observed: ${document2.observedRevision ?? "absent"}`);
|
|
3509
|
+
out.log(`registry: ${document2.registryRevision ?? "absent"}`);
|
|
3151
3510
|
out.log(
|
|
3152
3511
|
`summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
|
|
3153
3512
|
);
|
|
@@ -3180,6 +3539,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
3180
3539
|
}
|
|
3181
3540
|
function planNextActions(reconciliation, configPath) {
|
|
3182
3541
|
const next = [];
|
|
3542
|
+
if (configApplySupport(reconciliation).supported) {
|
|
3543
|
+
next.push({
|
|
3544
|
+
code: "apply_frozen_plan",
|
|
3545
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
3546
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3183
3549
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
3184
3550
|
next.push({
|
|
3185
3551
|
code: "review_provision",
|
|
@@ -3215,13 +3581,13 @@ function quoteArg2(value2) {
|
|
|
3215
3581
|
|
|
3216
3582
|
// src/doctor-checks.ts
|
|
3217
3583
|
var import_node_child_process3 = require("child_process");
|
|
3218
|
-
var
|
|
3219
|
-
var
|
|
3584
|
+
var import_node_fs12 = require("fs");
|
|
3585
|
+
var import_node_path10 = require("path");
|
|
3220
3586
|
|
|
3221
3587
|
// src/wrangler.ts
|
|
3222
3588
|
var import_node_child_process2 = require("child_process");
|
|
3223
|
-
var
|
|
3224
|
-
var
|
|
3589
|
+
var import_node_fs11 = require("fs");
|
|
3590
|
+
var import_node_path9 = require("path");
|
|
3225
3591
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3226
3592
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
3227
3593
|
let stdout = "";
|
|
@@ -3235,15 +3601,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
3235
3601
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
3236
3602
|
function findWranglerConfig(rootDir) {
|
|
3237
3603
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3238
|
-
const path = (0,
|
|
3239
|
-
if ((0,
|
|
3604
|
+
const path = (0, import_node_path9.join)(rootDir, name);
|
|
3605
|
+
if ((0, import_node_fs11.existsSync)(path)) return path;
|
|
3240
3606
|
}
|
|
3241
3607
|
return null;
|
|
3242
3608
|
}
|
|
3243
3609
|
function readWranglerConfig(path) {
|
|
3244
3610
|
if (path.endsWith(".toml")) return null;
|
|
3245
3611
|
try {
|
|
3246
|
-
return JSON.parse(stripJsonComments((0,
|
|
3612
|
+
return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
|
|
3247
3613
|
} catch {
|
|
3248
3614
|
return null;
|
|
3249
3615
|
}
|
|
@@ -3341,10 +3707,10 @@ function wranglerWarnings(rootDir) {
|
|
|
3341
3707
|
for (const { label, block } of blocks) {
|
|
3342
3708
|
const assets = block.assets;
|
|
3343
3709
|
if (assets?.directory) {
|
|
3344
|
-
const dir = (0,
|
|
3345
|
-
if (dir === (0,
|
|
3710
|
+
const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
|
|
3711
|
+
if (dir === (0, import_node_path10.resolve)(rootDir)) {
|
|
3346
3712
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
3347
|
-
} else if ((0,
|
|
3713
|
+
} else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
|
|
3348
3714
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
3349
3715
|
}
|
|
3350
3716
|
}
|
|
@@ -3379,13 +3745,13 @@ function o11yProjectWarnings(rootDir) {
|
|
|
3379
3745
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
|
|
3380
3746
|
return warnings;
|
|
3381
3747
|
}
|
|
3382
|
-
const main = typeof config.main === "string" ? (0,
|
|
3383
|
-
if (!main || !(0,
|
|
3748
|
+
const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
|
|
3749
|
+
if (!main || !(0, import_node_fs12.existsSync)(main)) {
|
|
3384
3750
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
3385
3751
|
} else {
|
|
3386
3752
|
let source = "";
|
|
3387
3753
|
try {
|
|
3388
|
-
source = (0,
|
|
3754
|
+
source = (0, import_node_fs12.readFileSync)(main, "utf8");
|
|
3389
3755
|
} catch {
|
|
3390
3756
|
}
|
|
3391
3757
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -3409,7 +3775,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
3409
3775
|
}
|
|
3410
3776
|
function readPackageJson(rootDir) {
|
|
3411
3777
|
try {
|
|
3412
|
-
return JSON.parse((0,
|
|
3778
|
+
return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
|
|
3413
3779
|
} catch {
|
|
3414
3780
|
return null;
|
|
3415
3781
|
}
|
|
@@ -3636,14 +4002,14 @@ function harnessOption(value2, flag) {
|
|
|
3636
4002
|
}
|
|
3637
4003
|
|
|
3638
4004
|
// src/init.ts
|
|
3639
|
-
var
|
|
3640
|
-
var
|
|
3641
|
-
var
|
|
4005
|
+
var import_node_fs13 = require("fs");
|
|
4006
|
+
var import_node_path11 = require("path");
|
|
4007
|
+
var import_apps9 = require("@odla-ai/apps");
|
|
3642
4008
|
function initProject(options) {
|
|
3643
4009
|
const out = options.stdout ?? console;
|
|
3644
|
-
const rootDir = (0,
|
|
3645
|
-
const configPath = (0,
|
|
3646
|
-
if ((0,
|
|
4010
|
+
const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
|
|
4011
|
+
const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4012
|
+
if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
|
|
3647
4013
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
3648
4014
|
}
|
|
3649
4015
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -3652,27 +4018,27 @@ function initProject(options) {
|
|
|
3652
4018
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
3653
4019
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
3654
4020
|
for (const service of services) {
|
|
3655
|
-
const definition = (0,
|
|
3656
|
-
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0,
|
|
4021
|
+
const definition = (0, import_apps9.appServiceDefinition)(service);
|
|
4022
|
+
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
|
|
3657
4023
|
for (const dependency of definition.requires) {
|
|
3658
4024
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
3659
4025
|
}
|
|
3660
4026
|
}
|
|
3661
4027
|
const aiProvider = options.aiProvider ?? "anthropic";
|
|
3662
|
-
(0,
|
|
3663
|
-
(0,
|
|
3664
|
-
(0,
|
|
3665
|
-
(0,
|
|
3666
|
-
writeIfMissing((0,
|
|
3667
|
-
writeIfMissing((0,
|
|
4028
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
|
|
4029
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4030
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4031
|
+
(0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4032
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4033
|
+
writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
3668
4034
|
ensureGitignore(rootDir);
|
|
3669
4035
|
out.log(`created ${relativeDisplay(configPath, rootDir)}`);
|
|
3670
4036
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
3671
4037
|
out.log("updated .gitignore for local odla credentials");
|
|
3672
4038
|
}
|
|
3673
4039
|
function writeIfMissing(path, text) {
|
|
3674
|
-
if ((0,
|
|
3675
|
-
(0,
|
|
4040
|
+
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4041
|
+
(0, import_node_fs13.writeFileSync)(path, text);
|
|
3676
4042
|
}
|
|
3677
4043
|
function configTemplate(input) {
|
|
3678
4044
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -3847,7 +4213,7 @@ function assertWranglerConfig(cfg) {
|
|
|
3847
4213
|
|
|
3848
4214
|
// src/secrets-set.ts
|
|
3849
4215
|
var import_ai2 = require("@odla-ai/ai");
|
|
3850
|
-
var
|
|
4216
|
+
var import_apps10 = require("@odla-ai/apps");
|
|
3851
4217
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
3852
4218
|
async function secretsSet(options) {
|
|
3853
4219
|
const name = (options.name ?? "").trim();
|
|
@@ -3899,13 +4265,13 @@ async function resolveVaultWrite(options) {
|
|
|
3899
4265
|
throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
|
|
3900
4266
|
}
|
|
3901
4267
|
const value2 = await secretInputValue(options, "secret");
|
|
3902
|
-
return { cfg, tenantId: (0,
|
|
4268
|
+
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
|
|
3903
4269
|
}
|
|
3904
4270
|
|
|
3905
4271
|
// src/skill.ts
|
|
3906
|
-
var
|
|
4272
|
+
var import_node_fs14 = require("fs");
|
|
3907
4273
|
var import_node_os2 = require("os");
|
|
3908
|
-
var
|
|
4274
|
+
var import_node_path12 = require("path");
|
|
3909
4275
|
var import_node_url2 = require("url");
|
|
3910
4276
|
|
|
3911
4277
|
// src/skill-adapters.ts
|
|
@@ -3984,8 +4350,8 @@ function installSkill(options = {}) {
|
|
|
3984
4350
|
const files = listFiles(sourceDir);
|
|
3985
4351
|
if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
|
|
3986
4352
|
const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
|
|
3987
|
-
const root = (0,
|
|
3988
|
-
const home = (0,
|
|
4353
|
+
const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
|
|
4354
|
+
const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
|
|
3989
4355
|
const plans = /* @__PURE__ */ new Map();
|
|
3990
4356
|
const targets = /* @__PURE__ */ new Map();
|
|
3991
4357
|
const rememberTarget = (harness, target) => {
|
|
@@ -3999,48 +4365,48 @@ function installSkill(options = {}) {
|
|
|
3999
4365
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4000
4366
|
};
|
|
4001
4367
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4002
|
-
for (const rel of files) plan((0,
|
|
4368
|
+
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);
|
|
4003
4369
|
};
|
|
4004
4370
|
let targetDir;
|
|
4005
4371
|
if (options.global) {
|
|
4006
|
-
const claudeRoot = (0,
|
|
4007
|
-
const codexRoot = (0,
|
|
4372
|
+
const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
|
|
4373
|
+
const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
|
|
4008
4374
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
4009
4375
|
for (const harness of harnesses) {
|
|
4010
4376
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
4011
|
-
planSkillTree(skillRoot, harness === "claude" ? home : (0,
|
|
4377
|
+
planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
|
|
4012
4378
|
rememberTarget(harness, skillRoot);
|
|
4013
4379
|
}
|
|
4014
4380
|
} else {
|
|
4015
|
-
const sharedRoot = (0,
|
|
4381
|
+
const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
|
|
4016
4382
|
planSkillTree(sharedRoot);
|
|
4017
|
-
const claudeRoot = (0,
|
|
4383
|
+
const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
|
|
4018
4384
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
4019
4385
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4020
4386
|
if (harnesses.includes("claude")) {
|
|
4021
4387
|
for (const skill of skillNames(files)) {
|
|
4022
|
-
const canonical = (0,
|
|
4023
|
-
plan((0,
|
|
4388
|
+
const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4389
|
+
plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
4024
4390
|
}
|
|
4025
4391
|
rememberTarget("claude", claudeRoot);
|
|
4026
4392
|
}
|
|
4027
4393
|
if (harnesses.includes("cursor")) {
|
|
4028
|
-
const cursorRule = (0,
|
|
4394
|
+
const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
|
|
4029
4395
|
plan(cursorRule, CURSOR_RULE);
|
|
4030
4396
|
rememberTarget("cursor", cursorRule);
|
|
4031
4397
|
}
|
|
4032
4398
|
if (harnesses.includes("agents")) {
|
|
4033
|
-
const agentsFile = (0,
|
|
4399
|
+
const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
|
|
4034
4400
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4035
4401
|
rememberTarget("agents", agentsFile);
|
|
4036
4402
|
}
|
|
4037
4403
|
if (harnesses.includes("copilot")) {
|
|
4038
|
-
const copilotFile = (0,
|
|
4404
|
+
const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
|
|
4039
4405
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4040
4406
|
rememberTarget("copilot", copilotFile);
|
|
4041
4407
|
}
|
|
4042
4408
|
if (harnesses.includes("gemini")) {
|
|
4043
|
-
const geminiFile = (0,
|
|
4409
|
+
const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
|
|
4044
4410
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
4045
4411
|
rememberTarget("gemini", geminiFile);
|
|
4046
4412
|
}
|
|
@@ -4054,11 +4420,11 @@ function installSkill(options = {}) {
|
|
|
4054
4420
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4055
4421
|
continue;
|
|
4056
4422
|
}
|
|
4057
|
-
if (!(0,
|
|
4423
|
+
if (!(0, import_node_fs14.existsSync)(file.target)) {
|
|
4058
4424
|
writtenPaths.add(file.target);
|
|
4059
4425
|
continue;
|
|
4060
4426
|
}
|
|
4061
|
-
const current = (0,
|
|
4427
|
+
const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
|
|
4062
4428
|
if (current === file.content) {
|
|
4063
4429
|
unchangedPaths.add(file.target);
|
|
4064
4430
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4075,9 +4441,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4075
4441
|
);
|
|
4076
4442
|
}
|
|
4077
4443
|
for (const file of plans.values()) {
|
|
4078
|
-
if (!(0,
|
|
4079
|
-
(0,
|
|
4080
|
-
(0,
|
|
4444
|
+
if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
|
|
4445
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
|
|
4446
|
+
(0, import_node_fs14.writeFileSync)(file.target, file.content);
|
|
4081
4447
|
}
|
|
4082
4448
|
}
|
|
4083
4449
|
const skills = skillNames(files);
|
|
@@ -4096,7 +4462,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4096
4462
|
};
|
|
4097
4463
|
}
|
|
4098
4464
|
function pathsUnder(root, paths) {
|
|
4099
|
-
return [...paths].map((path) => (0,
|
|
4465
|
+
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();
|
|
4100
4466
|
}
|
|
4101
4467
|
function normalizeHarnesses(values, global) {
|
|
4102
4468
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -4118,9 +4484,9 @@ function normalizeHarnesses(values, global) {
|
|
|
4118
4484
|
function managedFileContent(path, block, force, boundary) {
|
|
4119
4485
|
const symlink = symlinkedComponent(boundary, path);
|
|
4120
4486
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
4121
|
-
if (!(0,
|
|
4487
|
+
if (!(0, import_node_fs14.existsSync)(path)) return `${block}
|
|
4122
4488
|
`;
|
|
4123
|
-
const current = (0,
|
|
4489
|
+
const current = (0, import_node_fs14.readFileSync)(path, "utf8");
|
|
4124
4490
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
4125
4491
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
4126
4492
|
const startAt = current.indexOf(start);
|
|
@@ -4141,15 +4507,15 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
4141
4507
|
return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
|
|
4142
4508
|
}
|
|
4143
4509
|
function symlinkedComponent(boundary, target) {
|
|
4144
|
-
const rel = (0,
|
|
4145
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
4510
|
+
const rel = (0, import_node_path12.relative)(boundary, target);
|
|
4511
|
+
if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
|
|
4146
4512
|
throw new Error(`agent setup target escapes its install root: ${target}`);
|
|
4147
4513
|
}
|
|
4148
4514
|
let current = boundary;
|
|
4149
|
-
for (const part of rel.split(
|
|
4150
|
-
current = (0,
|
|
4515
|
+
for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
|
|
4516
|
+
current = (0, import_node_path12.join)(current, part);
|
|
4151
4517
|
try {
|
|
4152
|
-
if ((0,
|
|
4518
|
+
if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
|
|
4153
4519
|
} catch (error) {
|
|
4154
4520
|
if (error.code !== "ENOENT") throw error;
|
|
4155
4521
|
}
|
|
@@ -4160,13 +4526,13 @@ function skillNames(files) {
|
|
|
4160
4526
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
4161
4527
|
}
|
|
4162
4528
|
function listFiles(dir) {
|
|
4163
|
-
if (!(0,
|
|
4529
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return [];
|
|
4164
4530
|
const results = [];
|
|
4165
4531
|
const walk = (current) => {
|
|
4166
|
-
for (const entry of (0,
|
|
4167
|
-
const path = (0,
|
|
4532
|
+
for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
|
|
4533
|
+
const path = (0, import_node_path12.join)(current, entry.name);
|
|
4168
4534
|
if (entry.isDirectory()) walk(path);
|
|
4169
|
-
else results.push((0,
|
|
4535
|
+
else results.push((0, import_node_path12.relative)(dir, path));
|
|
4170
4536
|
}
|
|
4171
4537
|
};
|
|
4172
4538
|
walk(dir);
|
|
@@ -4361,10 +4727,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
4361
4727
|
async function projectCommand(command, parsed, deps) {
|
|
4362
4728
|
if (command === "config") {
|
|
4363
4729
|
const sub = parsed.positionals[1];
|
|
4364
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
4730
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
4365
4731
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
4366
4732
|
}
|
|
4367
|
-
assertArgs(
|
|
4733
|
+
assertArgs(
|
|
4734
|
+
parsed,
|
|
4735
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
4736
|
+
2
|
|
4737
|
+
);
|
|
4368
4738
|
const options = {
|
|
4369
4739
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4370
4740
|
token: stringOpt(parsed.options.token),
|
|
@@ -4376,7 +4746,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4376
4746
|
stdout: deps.stdout
|
|
4377
4747
|
};
|
|
4378
4748
|
if (sub === "diff") await configDiff(options);
|
|
4379
|
-
else await configPlan(options);
|
|
4749
|
+
else if (sub === "plan") await configPlan(options);
|
|
4750
|
+
else await configApply({
|
|
4751
|
+
...options,
|
|
4752
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
4753
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
4754
|
+
});
|
|
4755
|
+
return true;
|
|
4756
|
+
}
|
|
4757
|
+
if (command === "operations") {
|
|
4758
|
+
const sub = parsed.positionals[1];
|
|
4759
|
+
if (sub !== "get" && sub !== "wait") {
|
|
4760
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
4761
|
+
}
|
|
4762
|
+
assertArgs(
|
|
4763
|
+
parsed,
|
|
4764
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
4765
|
+
3
|
|
4766
|
+
);
|
|
4767
|
+
const operationId = parsed.positionals[2];
|
|
4768
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
4769
|
+
const options = {
|
|
4770
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4771
|
+
operationId,
|
|
4772
|
+
token: stringOpt(parsed.options.token),
|
|
4773
|
+
email: stringOpt(parsed.options.email),
|
|
4774
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
4775
|
+
json: parsed.options.json === true,
|
|
4776
|
+
fetch: deps.fetch,
|
|
4777
|
+
openApprovalUrl: deps.openUrl,
|
|
4778
|
+
stdout: deps.stdout,
|
|
4779
|
+
pollWait: deps.pollWait,
|
|
4780
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
4781
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
4782
|
+
};
|
|
4783
|
+
if (sub === "get") await configOperationGet(options);
|
|
4784
|
+
else await configOperationWait(options);
|
|
4380
4785
|
return true;
|
|
4381
4786
|
}
|
|
4382
4787
|
if (command === "init") {
|
|
@@ -4437,9 +4842,9 @@ async function projectCommand(command, parsed, deps) {
|
|
|
4437
4842
|
}
|
|
4438
4843
|
|
|
4439
4844
|
// src/code-connect.ts
|
|
4440
|
-
var
|
|
4845
|
+
var import_node_fs15 = require("fs");
|
|
4441
4846
|
var import_node_os4 = require("os");
|
|
4442
|
-
var
|
|
4847
|
+
var import_node_path14 = require("path");
|
|
4443
4848
|
|
|
4444
4849
|
// ../harness/dist/chunk-QTUEF2HZ.js
|
|
4445
4850
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
@@ -4449,7 +4854,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
4449
4854
|
var HarnessProtocolError = class extends Error {
|
|
4450
4855
|
name = "HarnessProtocolError";
|
|
4451
4856
|
};
|
|
4452
|
-
function
|
|
4857
|
+
function record4(value2) {
|
|
4453
4858
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
4454
4859
|
}
|
|
4455
4860
|
function boundedText(value2, label, max) {
|
|
@@ -4466,7 +4871,7 @@ function parseAgentOutput(line) {
|
|
|
4466
4871
|
} catch {
|
|
4467
4872
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
4468
4873
|
}
|
|
4469
|
-
const message2 =
|
|
4874
|
+
const message2 = record4(value2);
|
|
4470
4875
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
4471
4876
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
4472
4877
|
}
|
|
@@ -4479,7 +4884,7 @@ function parseAgentOutput(line) {
|
|
|
4479
4884
|
};
|
|
4480
4885
|
}
|
|
4481
4886
|
if (message2.type === "inference.request") {
|
|
4482
|
-
const call2 =
|
|
4887
|
+
const call2 = record4(message2.call);
|
|
4483
4888
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
4484
4889
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
4485
4890
|
}
|
|
@@ -4491,7 +4896,7 @@ function parseAgentOutput(line) {
|
|
|
4491
4896
|
};
|
|
4492
4897
|
}
|
|
4493
4898
|
if (message2.type === "tool.request") {
|
|
4494
|
-
const input =
|
|
4899
|
+
const input = record4(message2.input);
|
|
4495
4900
|
const tool = String(message2.tool);
|
|
4496
4901
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
4497
4902
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -4834,8 +5239,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4834
5239
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4835
5240
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4836
5241
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4837
|
-
const entries = inventory.flatMap((
|
|
4838
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5242
|
+
const entries = inventory.flatMap((record10) => {
|
|
5243
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
|
|
4839
5244
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4840
5245
|
});
|
|
4841
5246
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5083,8 +5488,8 @@ function normalize(value2) {
|
|
|
5083
5488
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5084
5489
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5085
5490
|
if (typeof value2 === "object") {
|
|
5086
|
-
const
|
|
5087
|
-
return Object.fromEntries(Object.keys(
|
|
5491
|
+
const record10 = value2;
|
|
5492
|
+
return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
|
|
5088
5493
|
}
|
|
5089
5494
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5090
5495
|
}
|
|
@@ -5163,7 +5568,7 @@ function normalizeReaders(readers) {
|
|
|
5163
5568
|
}
|
|
5164
5569
|
|
|
5165
5570
|
// ../camel/dist/code.js
|
|
5166
|
-
var
|
|
5571
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
5167
5572
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5168
5573
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5169
5574
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -5198,18 +5603,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5198
5603
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
5199
5604
|
}
|
|
5200
5605
|
function validate(fields) {
|
|
5201
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
5606
|
+
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) {
|
|
5202
5607
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
5203
5608
|
}
|
|
5204
5609
|
const ids = /* @__PURE__ */ new Set();
|
|
5205
5610
|
for (const recipe2 of fields.recipes) {
|
|
5206
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
5611
|
+
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) {
|
|
5207
5612
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
5208
5613
|
}
|
|
5209
5614
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
5210
5615
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
5211
5616
|
for (const artifact of recipe2.artifacts) {
|
|
5212
|
-
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 && !
|
|
5617
|
+
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)) {
|
|
5213
5618
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
5214
5619
|
}
|
|
5215
5620
|
artifactIds.add(artifact.artifactId);
|
|
@@ -5225,7 +5630,7 @@ function validate(fields) {
|
|
|
5225
5630
|
}
|
|
5226
5631
|
}
|
|
5227
5632
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
5228
|
-
var
|
|
5633
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
5229
5634
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
5230
5635
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
5231
5636
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -5270,14 +5675,14 @@ function normalizeState(value2) {
|
|
|
5270
5675
|
]);
|
|
5271
5676
|
const planCursor = state2.planCursor;
|
|
5272
5677
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
5273
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
5274
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
5678
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
5679
|
+
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) {
|
|
5275
5680
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
5276
5681
|
}
|
|
5277
5682
|
const effects = state2.completedEffects.map((item) => {
|
|
5278
5683
|
const effect = object(item, "completed effect");
|
|
5279
5684
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
5280
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
5685
|
+
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)) {
|
|
5281
5686
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
5282
5687
|
}
|
|
5283
5688
|
return {
|
|
@@ -5807,7 +6212,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5807
6212
|
}
|
|
5808
6213
|
const value2 = await response2.json().catch(() => null);
|
|
5809
6214
|
if (!response2.ok) {
|
|
5810
|
-
const problem =
|
|
6215
|
+
const problem = record5(record5(value2)?.error);
|
|
5811
6216
|
throw new CodeRuntimeControlError(
|
|
5812
6217
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
5813
6218
|
response2.status,
|
|
@@ -5829,12 +6234,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5829
6234
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5830
6235
|
),
|
|
5831
6236
|
infer: async (sessionId, inference) => {
|
|
5832
|
-
const value2 =
|
|
6237
|
+
const value2 = record5(await call2(
|
|
5833
6238
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5834
6239
|
inference,
|
|
5835
6240
|
modelRequestTimeoutMs
|
|
5836
6241
|
));
|
|
5837
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6242
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5838
6243
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5839
6244
|
}
|
|
5840
6245
|
return value2;
|
|
@@ -5892,12 +6297,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5892
6297
|
}
|
|
5893
6298
|
}
|
|
5894
6299
|
function parseSnapshot(value2) {
|
|
5895
|
-
const root =
|
|
5896
|
-
const host =
|
|
6300
|
+
const root = record5(value2);
|
|
6301
|
+
const host = record5(root?.host);
|
|
5897
6302
|
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");
|
|
5898
6303
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5899
6304
|
const bindings = root.bindings.map((item) => {
|
|
5900
|
-
const binding =
|
|
6305
|
+
const binding = record5(item);
|
|
5901
6306
|
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)) {
|
|
5902
6307
|
throw invalid("binding");
|
|
5903
6308
|
}
|
|
@@ -5907,10 +6312,10 @@ function parseSnapshot(value2) {
|
|
|
5907
6312
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5908
6313
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5909
6314
|
const commands = root.commands.map((item) => {
|
|
5910
|
-
const command =
|
|
6315
|
+
const command = record5(item);
|
|
5911
6316
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5912
6317
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5913
|
-
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)) || !
|
|
6318
|
+
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");
|
|
5914
6319
|
commandIds.add(command.commandId);
|
|
5915
6320
|
commandSequences.add(sequenceKey);
|
|
5916
6321
|
return command;
|
|
@@ -5918,10 +6323,10 @@ function parseSnapshot(value2) {
|
|
|
5918
6323
|
return { host, bindings, commands };
|
|
5919
6324
|
}
|
|
5920
6325
|
async function parseSource(value2) {
|
|
5921
|
-
const snapshot =
|
|
6326
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5922
6327
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5923
6328
|
const files = snapshot.files.map((value22) => {
|
|
5924
|
-
const file =
|
|
6329
|
+
const file = record5(value22);
|
|
5925
6330
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5926
6331
|
return { path: file.path, content: file.content };
|
|
5927
6332
|
});
|
|
@@ -5930,11 +6335,11 @@ async function parseSource(value2) {
|
|
|
5930
6335
|
const aliases = /* @__PURE__ */ new Set();
|
|
5931
6336
|
const references = [];
|
|
5932
6337
|
for (const item of referencesValue) {
|
|
5933
|
-
const reference =
|
|
6338
|
+
const reference = record5(item);
|
|
5934
6339
|
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");
|
|
5935
6340
|
aliases.add(reference.alias);
|
|
5936
6341
|
const referenceFiles = reference.files.map((entry) => {
|
|
5937
|
-
const file =
|
|
6342
|
+
const file = record5(entry);
|
|
5938
6343
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
5939
6344
|
return { path: file.path, content: file.content };
|
|
5940
6345
|
});
|
|
@@ -5949,18 +6354,18 @@ async function parseSource(value2) {
|
|
|
5949
6354
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5950
6355
|
}
|
|
5951
6356
|
function parseReview(value2) {
|
|
5952
|
-
const review =
|
|
6357
|
+
const review = record5(record5(value2)?.review);
|
|
5953
6358
|
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");
|
|
5954
6359
|
return review;
|
|
5955
6360
|
}
|
|
5956
6361
|
function parseCandidate(value2) {
|
|
5957
|
-
const candidate =
|
|
6362
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
5958
6363
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5959
6364
|
throw invalid("candidate");
|
|
5960
6365
|
}
|
|
5961
6366
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5962
6367
|
}
|
|
5963
|
-
var
|
|
6368
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5964
6369
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5965
6370
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5966
6371
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -7323,13 +7728,6 @@ var CodePiRuntimeEngine = class {
|
|
|
7323
7728
|
}
|
|
7324
7729
|
};
|
|
7325
7730
|
|
|
7326
|
-
// src/version.ts
|
|
7327
|
-
var import_node_fs13 = require("fs");
|
|
7328
|
-
function cliVersion() {
|
|
7329
|
-
const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
7330
|
-
return pkg.version ?? "unknown";
|
|
7331
|
-
}
|
|
7332
|
-
|
|
7333
7731
|
// src/security-hosted-github.ts
|
|
7334
7732
|
var import_node_child_process4 = require("child_process");
|
|
7335
7733
|
var import_node_util2 = require("util");
|
|
@@ -7605,7 +8003,7 @@ var import_node_child_process6 = require("child_process");
|
|
|
7605
8003
|
var import_node_crypto3 = require("crypto");
|
|
7606
8004
|
var import_promises10 = require("fs/promises");
|
|
7607
8005
|
var import_node_os3 = require("os");
|
|
7608
|
-
var
|
|
8006
|
+
var import_node_path13 = require("path");
|
|
7609
8007
|
var import_node_url3 = require("url");
|
|
7610
8008
|
|
|
7611
8009
|
// src/code-runtime-config.ts
|
|
@@ -7691,10 +8089,10 @@ async function embeddedPiImageName() {
|
|
|
7691
8089
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
7692
8090
|
}
|
|
7693
8091
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
7694
|
-
const context = await (0, import_promises10.mkdtemp)((0,
|
|
8092
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
7695
8093
|
try {
|
|
7696
|
-
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0,
|
|
7697
|
-
await (0, import_promises10.writeFile)((0,
|
|
8094
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
|
|
8095
|
+
await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
|
|
7698
8096
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
7699
8097
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
7700
8098
|
"WORKDIR /workspace",
|
|
@@ -7710,8 +8108,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
7710
8108
|
// src/code-connect.ts
|
|
7711
8109
|
async function codeConnect(options) {
|
|
7712
8110
|
const cwd = options.cwd ?? process.cwd();
|
|
7713
|
-
const configPath = (0,
|
|
7714
|
-
const cfg = (0,
|
|
8111
|
+
const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
|
|
8112
|
+
const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
7715
8113
|
const requestedAppId = options.appId?.trim();
|
|
7716
8114
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
7717
8115
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -7869,20 +8267,20 @@ async function runCodeRuntime(input) {
|
|
|
7869
8267
|
}
|
|
7870
8268
|
}
|
|
7871
8269
|
function parseConnection(value2, appId, appEnv) {
|
|
7872
|
-
const root =
|
|
7873
|
-
const host =
|
|
7874
|
-
const offer =
|
|
7875
|
-
const binding =
|
|
8270
|
+
const root = record6(value2);
|
|
8271
|
+
const host = record6(root?.host);
|
|
8272
|
+
const offer = record6(root?.offer);
|
|
8273
|
+
const binding = record6(root?.binding);
|
|
7876
8274
|
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)) {
|
|
7877
8275
|
throw new Error("connect Code host returned an invalid response");
|
|
7878
8276
|
}
|
|
7879
8277
|
return root;
|
|
7880
8278
|
}
|
|
7881
8279
|
function apiFailure(action2, status, value2) {
|
|
7882
|
-
const message2 =
|
|
8280
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
7883
8281
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
7884
8282
|
}
|
|
7885
|
-
function
|
|
8283
|
+
function record6(value2) {
|
|
7886
8284
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7887
8285
|
}
|
|
7888
8286
|
|
|
@@ -8097,8 +8495,9 @@ Usage:
|
|
|
8097
8495
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8098
8496
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8099
8497
|
odla-ai doctor [--config odla.config.mjs]
|
|
8100
|
-
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8101
|
-
odla-ai config plan [--
|
|
8498
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8499
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8500
|
+
odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8102
8501
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8103
8502
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8104
8503
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8223,8 +8622,8 @@ Commands:
|
|
|
8223
8622
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8224
8623
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8225
8624
|
doctor Validate and summarize the project config without network calls.
|
|
8226
|
-
config
|
|
8227
|
-
|
|
8625
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
8626
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8228
8627
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8229
8628
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8230
8629
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -8931,8 +9330,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8931
9330
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8932
9331
|
}
|
|
8933
9332
|
async function pmGet(ctx, entity, id) {
|
|
8934
|
-
const { record:
|
|
8935
|
-
emit2(ctx,
|
|
9333
|
+
const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9334
|
+
emit2(ctx, record10, () => printRecord(ctx, entity, record10));
|
|
8936
9335
|
}
|
|
8937
9336
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8938
9337
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -8977,9 +9376,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8977
9376
|
]);
|
|
8978
9377
|
const handoff = {
|
|
8979
9378
|
appId,
|
|
8980
|
-
unmetGoals: goals.filter((
|
|
8981
|
-
activeTasks: tasks.filter((
|
|
8982
|
-
openBugs: bugs.filter((
|
|
9379
|
+
unmetGoals: goals.filter((record10) => record10.status !== "met"),
|
|
9380
|
+
activeTasks: tasks.filter((record10) => record10.column !== "done"),
|
|
9381
|
+
openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
|
|
8983
9382
|
};
|
|
8984
9383
|
const result = {
|
|
8985
9384
|
...handoff,
|
|
@@ -8998,10 +9397,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8998
9397
|
]) {
|
|
8999
9398
|
ctx.out.log(`${label}:`);
|
|
9000
9399
|
if (!records.length) ctx.out.log("- (none)");
|
|
9001
|
-
else for (const
|
|
9400
|
+
else for (const record10 of records) printRecord(
|
|
9002
9401
|
ctx,
|
|
9003
9402
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9004
|
-
|
|
9403
|
+
record10
|
|
9005
9404
|
);
|
|
9006
9405
|
}
|
|
9007
9406
|
});
|
|
@@ -9242,17 +9641,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9242
9641
|
}
|
|
9243
9642
|
}
|
|
9244
9643
|
function isPlatformStatus(value2) {
|
|
9245
|
-
if (!
|
|
9246
|
-
if (!
|
|
9247
|
-
if (!
|
|
9644
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9645
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9646
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
9248
9647
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9249
9648
|
}
|
|
9250
9649
|
function apiMessage(value2) {
|
|
9251
|
-
if (!
|
|
9252
|
-
const error =
|
|
9650
|
+
if (!record7(value2)) return "request failed";
|
|
9651
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9253
9652
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9254
9653
|
}
|
|
9255
|
-
function
|
|
9654
|
+
function record7(value2) {
|
|
9256
9655
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9257
9656
|
}
|
|
9258
9657
|
|
|
@@ -9293,7 +9692,7 @@ function statusVerdict(reads) {
|
|
|
9293
9692
|
severity: "degraded"
|
|
9294
9693
|
});
|
|
9295
9694
|
}
|
|
9296
|
-
const performance =
|
|
9695
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9297
9696
|
if (performance?.status === "unavailable") {
|
|
9298
9697
|
reasons.push({
|
|
9299
9698
|
source: "liveSync",
|
|
@@ -9374,7 +9773,7 @@ function statusVerdict(reads) {
|
|
|
9374
9773
|
reasons
|
|
9375
9774
|
};
|
|
9376
9775
|
}
|
|
9377
|
-
function
|
|
9776
|
+
function record8(value2) {
|
|
9378
9777
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9379
9778
|
}
|
|
9380
9779
|
function numeric2(value2) {
|
|
@@ -9402,7 +9801,7 @@ function printO11yStatus(status, out) {
|
|
|
9402
9801
|
out.log(
|
|
9403
9802
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9404
9803
|
);
|
|
9405
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
9804
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
9406
9805
|
const requests = routes.reduce(
|
|
9407
9806
|
(total, row) => total + numeric3(row.requests),
|
|
9408
9807
|
0
|
|
@@ -9414,39 +9813,39 @@ function printO11yStatus(status, out) {
|
|
|
9414
9813
|
out.log(
|
|
9415
9814
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9416
9815
|
);
|
|
9417
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
9816
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
9418
9817
|
out.log(
|
|
9419
9818
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9420
9819
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9421
9820
|
).join(", ") : "none observed"}`
|
|
9422
9821
|
);
|
|
9423
9822
|
out.log(liveSyncLine(status.liveSync));
|
|
9424
|
-
const canaryDurations =
|
|
9823
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9425
9824
|
out.log(
|
|
9426
9825
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9427
9826
|
);
|
|
9428
|
-
const collectorIngest =
|
|
9429
|
-
const collectorStorage =
|
|
9827
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9828
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9430
9829
|
out.log(
|
|
9431
9830
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9432
9831
|
);
|
|
9433
|
-
const providerMetrics =
|
|
9434
|
-
const providerCapacity =
|
|
9435
|
-
const workerMemory =
|
|
9832
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9833
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9834
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9436
9835
|
out.log(
|
|
9437
9836
|
`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`
|
|
9438
9837
|
);
|
|
9439
9838
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9440
9839
|
out.log(line);
|
|
9441
9840
|
}
|
|
9442
|
-
const coverage =
|
|
9443
|
-
const coverageCounts =
|
|
9444
|
-
const coverageBudget =
|
|
9841
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9842
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9843
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9445
9844
|
out.log(
|
|
9446
9845
|
`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`
|
|
9447
9846
|
);
|
|
9448
9847
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9449
|
-
const providerFreshness =
|
|
9848
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9450
9849
|
out.log(
|
|
9451
9850
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9452
9851
|
);
|
|
@@ -9455,17 +9854,17 @@ function printO11yStatus(status, out) {
|
|
|
9455
9854
|
);
|
|
9456
9855
|
}
|
|
9457
9856
|
function providerCapacityLines(read3) {
|
|
9458
|
-
const resources =
|
|
9459
|
-
const durableObjects =
|
|
9460
|
-
const periodic =
|
|
9461
|
-
const storage =
|
|
9462
|
-
const d1 =
|
|
9463
|
-
const d1Activity =
|
|
9464
|
-
const d1Storage =
|
|
9465
|
-
const d1Latency =
|
|
9466
|
-
const r2 =
|
|
9467
|
-
const r2Operations =
|
|
9468
|
-
const r2Storage =
|
|
9857
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
9858
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
9859
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9860
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9861
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
9862
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
9863
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
9864
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
9865
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
9866
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
9867
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
9469
9868
|
const status = String(
|
|
9470
9869
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9471
9870
|
);
|
|
@@ -9476,11 +9875,11 @@ function providerCapacityLines(read3) {
|
|
|
9476
9875
|
];
|
|
9477
9876
|
}
|
|
9478
9877
|
function liveSyncLine(read3) {
|
|
9479
|
-
const performance =
|
|
9480
|
-
const commitToSend =
|
|
9878
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
9879
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
9481
9880
|
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`;
|
|
9482
9881
|
}
|
|
9483
|
-
function
|
|
9882
|
+
function record9(value2) {
|
|
9484
9883
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9485
9884
|
}
|
|
9486
9885
|
function numeric3(value2) {
|
|
@@ -9663,7 +10062,7 @@ async function read2(url, headers, doFetch) {
|
|
|
9663
10062
|
}
|
|
9664
10063
|
|
|
9665
10064
|
// src/provision.ts
|
|
9666
|
-
var
|
|
10065
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
9667
10066
|
var import_ai3 = require("@odla-ai/ai");
|
|
9668
10067
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
9669
10068
|
|
|
@@ -9720,9 +10119,9 @@ async function responseText(res) {
|
|
|
9720
10119
|
}
|
|
9721
10120
|
|
|
9722
10121
|
// src/provision-credentials.ts
|
|
9723
|
-
var
|
|
10122
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
9724
10123
|
async function provisionEnvCredentials(opts) {
|
|
9725
|
-
const tenantId = (0,
|
|
10124
|
+
const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
9726
10125
|
const prior = opts.credentials?.envs[opts.env];
|
|
9727
10126
|
let credentials = opts.credentials;
|
|
9728
10127
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -9884,7 +10283,7 @@ async function provision(options) {
|
|
|
9884
10283
|
}
|
|
9885
10284
|
const doFetch = options.fetch ?? fetch;
|
|
9886
10285
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
9887
|
-
const apps = (0,
|
|
10286
|
+
const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
9888
10287
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
9889
10288
|
if (existing) {
|
|
9890
10289
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -9895,7 +10294,7 @@ async function provision(options) {
|
|
|
9895
10294
|
for (const env of cfg.envs) {
|
|
9896
10295
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
9897
10296
|
}
|
|
9898
|
-
const serviceOrder = (0,
|
|
10297
|
+
const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
|
|
9899
10298
|
for (const env of cfg.envs) {
|
|
9900
10299
|
for (const service of serviceOrder) {
|
|
9901
10300
|
if (service === "ai") {
|
|
@@ -9928,7 +10327,7 @@ async function provision(options) {
|
|
|
9928
10327
|
}
|
|
9929
10328
|
}
|
|
9930
10329
|
for (const env of cfg.envs) {
|
|
9931
|
-
const tenantId = (0,
|
|
10330
|
+
const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
|
|
9932
10331
|
credentials = await provisionEnvCredentials({
|
|
9933
10332
|
cfg,
|
|
9934
10333
|
env,
|
|
@@ -10013,7 +10412,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10013
10412
|
}
|
|
10014
10413
|
|
|
10015
10414
|
// src/record.ts
|
|
10016
|
-
var
|
|
10415
|
+
var import_node_fs16 = require("fs");
|
|
10017
10416
|
var import_node_process11 = __toESM(require("process"), 1);
|
|
10018
10417
|
|
|
10019
10418
|
// src/surface.ts
|
|
@@ -10062,7 +10461,7 @@ var COMMAND_SURFACE = {
|
|
|
10062
10461
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
10063
10462
|
capabilities: {},
|
|
10064
10463
|
code: { connect: {} },
|
|
10065
|
-
config: { diff: {}, plan: {} },
|
|
10464
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
10066
10465
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
10067
10466
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
10068
10467
|
discuss: {
|
|
@@ -10080,6 +10479,7 @@ var COMMAND_SURFACE = {
|
|
|
10080
10479
|
help: {},
|
|
10081
10480
|
init: {},
|
|
10082
10481
|
o11y: { status: {} },
|
|
10482
|
+
operations: { get: {}, wait: {} },
|
|
10083
10483
|
platform: { status: {} },
|
|
10084
10484
|
pm: {
|
|
10085
10485
|
...PM_ENTITIES,
|
|
@@ -10167,14 +10567,14 @@ function recordInvocation(parsed) {
|
|
|
10167
10567
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
10168
10568
|
};
|
|
10169
10569
|
if (!entry.path.length) return;
|
|
10170
|
-
(0,
|
|
10570
|
+
(0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
10171
10571
|
`);
|
|
10172
10572
|
} catch {
|
|
10173
10573
|
}
|
|
10174
10574
|
}
|
|
10175
10575
|
|
|
10176
10576
|
// src/runbook-actions.ts
|
|
10177
|
-
var
|
|
10577
|
+
var import_node_fs17 = require("fs");
|
|
10178
10578
|
|
|
10179
10579
|
// src/runbook-requires.ts
|
|
10180
10580
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10259,7 +10659,7 @@ async function bySlug(ctx, slug) {
|
|
|
10259
10659
|
function readBody(file, inline) {
|
|
10260
10660
|
if (inline !== void 0) return inline;
|
|
10261
10661
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10262
|
-
return (0,
|
|
10662
|
+
return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
10263
10663
|
}
|
|
10264
10664
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10265
10665
|
async function runbookList(ctx, all, query) {
|
|
@@ -10346,8 +10746,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
10346
10746
|
}
|
|
10347
10747
|
|
|
10348
10748
|
// src/runbook-import.ts
|
|
10349
|
-
var
|
|
10350
|
-
var
|
|
10749
|
+
var import_node_fs18 = require("fs");
|
|
10750
|
+
var import_node_path15 = require("path");
|
|
10351
10751
|
function parseRunbook(text, slug) {
|
|
10352
10752
|
let rest = text;
|
|
10353
10753
|
const meta = {};
|
|
@@ -10372,12 +10772,12 @@ function parseRunbook(text, slug) {
|
|
|
10372
10772
|
};
|
|
10373
10773
|
}
|
|
10374
10774
|
function readRunbookDir(dir) {
|
|
10375
|
-
if (!(0,
|
|
10376
|
-
const files = (0,
|
|
10775
|
+
if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
10776
|
+
const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
10377
10777
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10378
10778
|
return files.map((file) => {
|
|
10379
|
-
const slug = (0,
|
|
10380
|
-
const parsed = parseRunbook((0,
|
|
10779
|
+
const slug = (0, import_node_path15.basename)(file, ".md");
|
|
10780
|
+
const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
|
|
10381
10781
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10382
10782
|
});
|
|
10383
10783
|
}
|
|
@@ -10449,8 +10849,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10449
10849
|
|
|
10450
10850
|
// src/runbook-impact.ts
|
|
10451
10851
|
var import_node_child_process7 = require("child_process");
|
|
10452
|
-
var
|
|
10453
|
-
var
|
|
10852
|
+
var import_node_fs19 = require("fs");
|
|
10853
|
+
var import_node_path16 = require("path");
|
|
10454
10854
|
|
|
10455
10855
|
// src/runbook-impact-scan.ts
|
|
10456
10856
|
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$]*)/;
|
|
@@ -10619,10 +11019,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10619
11019
|
}
|
|
10620
11020
|
function manifestLabeller(root) {
|
|
10621
11021
|
return (workspace) => {
|
|
10622
|
-
const manifest = (0,
|
|
10623
|
-
if (!(0,
|
|
11022
|
+
const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
|
|
11023
|
+
if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
|
|
10624
11024
|
try {
|
|
10625
|
-
const name = JSON.parse((0,
|
|
11025
|
+
const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
|
|
10626
11026
|
return typeof name === "string" ? name : void 0;
|
|
10627
11027
|
} catch {
|
|
10628
11028
|
return void 0;
|
|
@@ -10689,7 +11089,7 @@ function report3(ctx, impacts) {
|
|
|
10689
11089
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10690
11090
|
const cwd = deps.cwd ?? process.cwd();
|
|
10691
11091
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10692
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
11092
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
|
|
10693
11093
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10694
11094
|
if (!surfaces.length) {
|
|
10695
11095
|
return ctx.out.log(
|
|
@@ -10822,9 +11222,9 @@ async function runbookComment(ctx, slug, body) {
|
|
|
10822
11222
|
|
|
10823
11223
|
// src/runbook-editor.ts
|
|
10824
11224
|
var import_node_child_process8 = require("child_process");
|
|
10825
|
-
var
|
|
11225
|
+
var import_node_fs20 = require("fs");
|
|
10826
11226
|
var import_node_os5 = require("os");
|
|
10827
|
-
var
|
|
11227
|
+
var import_node_path17 = require("path");
|
|
10828
11228
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
10829
11229
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
10830
11230
|
function resolveEditor(env = import_node_process12.default.env) {
|
|
@@ -10850,16 +11250,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
10850
11250
|
);
|
|
10851
11251
|
if (!interactive())
|
|
10852
11252
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
10853
|
-
const dir = (0,
|
|
10854
|
-
const file = (0,
|
|
11253
|
+
const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
11254
|
+
const file = (0, import_node_path17.join)(dir, `${slug}.md`);
|
|
10855
11255
|
try {
|
|
10856
|
-
(0,
|
|
11256
|
+
(0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
|
|
10857
11257
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
10858
11258
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
10859
|
-
const edited = (0,
|
|
11259
|
+
const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
|
|
10860
11260
|
return edited === initial ? null : edited;
|
|
10861
11261
|
} finally {
|
|
10862
|
-
(0,
|
|
11262
|
+
(0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
|
|
10863
11263
|
}
|
|
10864
11264
|
}
|
|
10865
11265
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -11271,7 +11671,7 @@ function hostedSeverity(value2, flag) {
|
|
|
11271
11671
|
var import_security2 = require("@odla-ai/security");
|
|
11272
11672
|
|
|
11273
11673
|
// src/security.ts
|
|
11274
|
-
var
|
|
11674
|
+
var import_node_path18 = require("path");
|
|
11275
11675
|
var import_security = require("@odla-ai/security");
|
|
11276
11676
|
var import_node3 = require("@odla-ai/security/node");
|
|
11277
11677
|
async function runHostedSecurity(options) {
|
|
@@ -11283,9 +11683,9 @@ async function runHostedSecurity(options) {
|
|
|
11283
11683
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
11284
11684
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
11285
11685
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
11286
|
-
const target = (0,
|
|
11287
|
-
const output = (0,
|
|
11288
|
-
const outputRelative = (0,
|
|
11686
|
+
const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
11687
|
+
const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
|
|
11688
|
+
const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
|
|
11289
11689
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
11290
11690
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
11291
11691
|
const tokenRequest = {
|
|
@@ -11297,7 +11697,7 @@ async function runHostedSecurity(options) {
|
|
|
11297
11697
|
};
|
|
11298
11698
|
const token = await injectedToken(options, tokenRequest);
|
|
11299
11699
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
11300
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
11700
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
11301
11701
|
});
|
|
11302
11702
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
11303
11703
|
platform,
|
|
@@ -11315,7 +11715,7 @@ async function runHostedSecurity(options) {
|
|
|
11315
11715
|
});
|
|
11316
11716
|
const harness = (0, import_security.createSecurityHarness)({
|
|
11317
11717
|
profile,
|
|
11318
|
-
store: new import_node3.FileRunStore((0,
|
|
11718
|
+
store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
|
|
11319
11719
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
11320
11720
|
validationReasoner: hosted.validationReasoner,
|
|
11321
11721
|
policy: {
|
|
@@ -11339,7 +11739,7 @@ async function runHostedSecurity(options) {
|
|
|
11339
11739
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
11340
11740
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
11341
11741
|
if (!env || !declared.includes(env)) {
|
|
11342
|
-
const shown = (0,
|
|
11742
|
+
const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
|
|
11343
11743
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
11344
11744
|
}
|
|
11345
11745
|
return env;
|
|
@@ -11368,7 +11768,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
11368
11768
|
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}`);
|
|
11369
11769
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
11370
11770
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
11371
|
-
out.log(` report: ${(0,
|
|
11771
|
+
out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
|
|
11372
11772
|
}
|
|
11373
11773
|
function formatBudget(usage) {
|
|
11374
11774
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -11808,11 +12208,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11808
12208
|
// src/cli.ts
|
|
11809
12209
|
function exitCodeFor(err) {
|
|
11810
12210
|
const code = err?.code;
|
|
11811
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12211
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11812
12212
|
if (code === "checkpoint_required") return 3;
|
|
11813
12213
|
if (code === "remote_unavailable") return 6;
|
|
11814
12214
|
if (code === "auth_failed") return 5;
|
|
11815
|
-
if (code === "invalid_cursor") return 2;
|
|
12215
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11816
12216
|
return 1;
|
|
11817
12217
|
}
|
|
11818
12218
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|