@odla-ai/cli 0.25.22 → 0.26.1
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 +61 -11
- package/dist/bin.cjs +1061 -341
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-2AFAXLKE.js → chunk-ZAZBUURZ.js} +1023 -294
- package/dist/chunk-ZAZBUURZ.js.map +1 -0
- package/dist/index.cjs +1069 -341
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -23
- package/dist/index.d.ts +52 -23
- package/dist/index.js +9 -1
- package/package.json +2 -2
- package/skills/odla/references/sdks.md +5 -3
- package/skills/odla-migrate/references/phase-2b-calendar.md +13 -8
- package/dist/chunk-2AFAXLKE.js.map +0 -1
|
@@ -115,10 +115,10 @@ function isManagedDevVar(line) {
|
|
|
115
115
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
116
116
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
117
117
|
}
|
|
118
|
-
function writePrivateText(path,
|
|
118
|
+
function writePrivateText(path, text2) {
|
|
119
119
|
mkdirSync(dirname(path), { recursive: true });
|
|
120
120
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
121
|
-
writeFileSync(temporary,
|
|
121
|
+
writeFileSync(temporary, text2, { mode: 384 });
|
|
122
122
|
chmodSync(temporary, 384);
|
|
123
123
|
renameSync(temporary, path);
|
|
124
124
|
}
|
|
@@ -439,7 +439,9 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
439
439
|
}
|
|
440
440
|
var SCOPE_PURPOSE = {
|
|
441
441
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
442
|
+
"platform:chat:credential:write": "rotate the built-in Discussion responder credential",
|
|
442
443
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
444
|
+
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
443
445
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
444
446
|
"platform:ai:policy:write": "change System AI model routing",
|
|
445
447
|
"platform:ai:policy:read": "read System AI model routing",
|
|
@@ -537,6 +539,14 @@ async function readSecretStream(kind, stream = process6.stdin) {
|
|
|
537
539
|
return value2;
|
|
538
540
|
}
|
|
539
541
|
|
|
542
|
+
// src/principal-presentation.ts
|
|
543
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
544
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
545
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
546
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
547
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
540
550
|
// src/admin-ai-audit.ts
|
|
541
551
|
function adminAiAuditQuery(filters) {
|
|
542
552
|
if (filters.limit === void 0) return "";
|
|
@@ -566,17 +576,17 @@ async function readAdminAiAudit(request2) {
|
|
|
566
576
|
String(event.changeKind ?? ""),
|
|
567
577
|
String(event.purpose ?? event.provider ?? ""),
|
|
568
578
|
route2,
|
|
569
|
-
|
|
579
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
570
580
|
].join(" "));
|
|
571
581
|
}
|
|
572
582
|
}
|
|
573
583
|
async function responseBody(response2) {
|
|
574
|
-
const
|
|
575
|
-
if (!
|
|
584
|
+
const text2 = await response2.text();
|
|
585
|
+
if (!text2) return {};
|
|
576
586
|
try {
|
|
577
|
-
return JSON.parse(
|
|
587
|
+
return JSON.parse(text2);
|
|
578
588
|
} catch {
|
|
579
|
-
return { message:
|
|
589
|
+
return { message: text2.slice(0, 300) };
|
|
580
590
|
}
|
|
581
591
|
}
|
|
582
592
|
function apiError(status, body) {
|
|
@@ -643,7 +653,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
643
653
|
timestamp2(event.created_at),
|
|
644
654
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
645
655
|
String(event.run_id ?? ""),
|
|
646
|
-
|
|
656
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
647
657
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
648
658
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
649
659
|
String(input + output),
|
|
@@ -664,12 +674,12 @@ function timestamp2(value2) {
|
|
|
664
674
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
665
675
|
}
|
|
666
676
|
async function responseBody2(res) {
|
|
667
|
-
const
|
|
668
|
-
if (!
|
|
677
|
+
const text2 = await res.text();
|
|
678
|
+
if (!text2) return {};
|
|
669
679
|
try {
|
|
670
|
-
return JSON.parse(
|
|
680
|
+
return JSON.parse(text2);
|
|
671
681
|
} catch {
|
|
672
|
-
return { message:
|
|
682
|
+
return { message: text2.slice(0, 300) };
|
|
673
683
|
}
|
|
674
684
|
}
|
|
675
685
|
function apiError2(action2, status, body) {
|
|
@@ -845,12 +855,12 @@ function catalogModels(body) {
|
|
|
845
855
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
846
856
|
}
|
|
847
857
|
async function responseBody3(res) {
|
|
848
|
-
const
|
|
849
|
-
if (!
|
|
858
|
+
const text2 = await res.text();
|
|
859
|
+
if (!text2) return {};
|
|
850
860
|
try {
|
|
851
|
-
return JSON.parse(
|
|
861
|
+
return JSON.parse(text2);
|
|
852
862
|
} catch {
|
|
853
|
-
return { message:
|
|
863
|
+
return { message: text2.slice(0, 300) };
|
|
854
864
|
}
|
|
855
865
|
}
|
|
856
866
|
function apiError3(action2, status, body) {
|
|
@@ -941,6 +951,7 @@ function unique(values) {
|
|
|
941
951
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
942
952
|
var DEFAULT_ENVS = ["dev"];
|
|
943
953
|
var DEFAULT_SERVICES = ["db", "ai"];
|
|
954
|
+
var configImportSerial = 0;
|
|
944
955
|
var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
|
|
945
956
|
async function loadProjectConfig(configPath = "odla.config.mjs") {
|
|
946
957
|
const resolved = resolve2(configPath);
|
|
@@ -1148,7 +1159,8 @@ function validId2(value2) {
|
|
|
1148
1159
|
}
|
|
1149
1160
|
async function loadConfigModule(path) {
|
|
1150
1161
|
if (path.endsWith(".json")) return JSON.parse(readFileSync2(path, "utf8"));
|
|
1151
|
-
const
|
|
1162
|
+
const nonce = `${Date.now()}-${configImportSerial++}`;
|
|
1163
|
+
const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
|
|
1152
1164
|
const value2 = mod.default ?? mod.config;
|
|
1153
1165
|
if (typeof value2 === "function") return await value2();
|
|
1154
1166
|
return value2;
|
|
@@ -1638,7 +1650,8 @@ var CAPABILITIES = {
|
|
|
1638
1650
|
"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",
|
|
1639
1651
|
"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",
|
|
1640
1652
|
"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",
|
|
1641
|
-
"compare one project's checked-in Registry intent with live owner-visible state
|
|
1653
|
+
"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",
|
|
1654
|
+
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
1642
1655
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
1643
1656
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
1644
1657
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -1685,6 +1698,16 @@ function printGroup(out, heading, items) {
|
|
|
1685
1698
|
out.log("");
|
|
1686
1699
|
}
|
|
1687
1700
|
|
|
1701
|
+
// src/config-operation-error.ts
|
|
1702
|
+
var ConfigOperationCommandError = class extends Error {
|
|
1703
|
+
constructor(message2, code) {
|
|
1704
|
+
super(message2);
|
|
1705
|
+
this.code = code;
|
|
1706
|
+
this.name = "ConfigOperationCommandError";
|
|
1707
|
+
}
|
|
1708
|
+
code;
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1688
1711
|
// src/config-reconcile-desired.ts
|
|
1689
1712
|
import { orderAppServices } from "@odla-ai/apps";
|
|
1690
1713
|
|
|
@@ -1778,8 +1801,25 @@ function managedServiceConfig(cfg, env, service) {
|
|
|
1778
1801
|
return {};
|
|
1779
1802
|
}
|
|
1780
1803
|
|
|
1781
|
-
// src/config-
|
|
1782
|
-
import {
|
|
1804
|
+
// src/config-operation-command.ts
|
|
1805
|
+
import {
|
|
1806
|
+
AppsError,
|
|
1807
|
+
createAppsClient
|
|
1808
|
+
} from "@odla-ai/apps";
|
|
1809
|
+
import { join as join3 } from "path";
|
|
1810
|
+
|
|
1811
|
+
// src/version.ts
|
|
1812
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1813
|
+
function cliVersion() {
|
|
1814
|
+
const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
|
|
1815
|
+
return pkg.version ?? "unknown";
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/config-operation-validate.ts
|
|
1819
|
+
import {
|
|
1820
|
+
appServiceDefinition as appServiceDefinition2
|
|
1821
|
+
} from "@odla-ai/apps";
|
|
1822
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1783
1823
|
|
|
1784
1824
|
// src/config-reconcile-digest.ts
|
|
1785
1825
|
import { createHash } from "crypto";
|
|
@@ -1790,15 +1830,347 @@ function configDigest(value2) {
|
|
|
1790
1830
|
return `sha256:${createHash("sha256").update(canonicalJson(value2)).digest("hex")}`;
|
|
1791
1831
|
}
|
|
1792
1832
|
function canonicalValue(value2) {
|
|
1833
|
+
if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
1834
|
+
if (typeof value2 === "number") {
|
|
1835
|
+
if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
1836
|
+
return value2;
|
|
1837
|
+
}
|
|
1793
1838
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
1794
1839
|
if (value2 && typeof value2 === "object") {
|
|
1840
|
+
const record11 = value2;
|
|
1795
1841
|
return Object.fromEntries(
|
|
1796
|
-
Object.
|
|
1842
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
1797
1843
|
);
|
|
1798
1844
|
}
|
|
1799
|
-
|
|
1845
|
+
throw new TypeError("canonical JSON rejects unsupported values");
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/config-operation-validate.ts
|
|
1849
|
+
var DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
1850
|
+
var REVISION = /^registry:[1-9][0-9]*$/;
|
|
1851
|
+
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;
|
|
1852
|
+
var ACTION_ID = /^action-[0-9a-f]{16}$/;
|
|
1853
|
+
var ENV = /^[a-z0-9]{2,12}$/;
|
|
1854
|
+
var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
1855
|
+
function readPlan(path) {
|
|
1856
|
+
let value2;
|
|
1857
|
+
try {
|
|
1858
|
+
const raw = readFileSync4(path, "utf8");
|
|
1859
|
+
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
1860
|
+
value2 = JSON.parse(raw);
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
throw new ConfigOperationCommandError(
|
|
1863
|
+
`cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1864
|
+
"invalid_plan"
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
1868
|
+
if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
1869
|
+
invalidPlan("plan scope is invalid");
|
|
1870
|
+
}
|
|
1871
|
+
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
1872
|
+
invalidPlan("plan content revisions are invalid");
|
|
1873
|
+
}
|
|
1874
|
+
if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
|
|
1875
|
+
invalidPlan("plan Registry revision or digest is invalid");
|
|
1876
|
+
}
|
|
1877
|
+
if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
|
|
1878
|
+
invalidPlan("plan actions must contain 1-32 entries");
|
|
1879
|
+
}
|
|
1880
|
+
assertActions(value2.actions);
|
|
1881
|
+
const plan = value2;
|
|
1882
|
+
const digest = configDigest({
|
|
1883
|
+
schemaVersion: plan.schemaVersion,
|
|
1884
|
+
desiredRevision: plan.desiredRevision,
|
|
1885
|
+
observedRevision: plan.observedRevision,
|
|
1886
|
+
registryRevision: plan.registryRevision,
|
|
1887
|
+
actions: plan.actions
|
|
1888
|
+
});
|
|
1889
|
+
if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
|
|
1890
|
+
return plan;
|
|
1891
|
+
}
|
|
1892
|
+
function assertPlanContext(plan, appId, platformUrl) {
|
|
1893
|
+
if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
|
|
1894
|
+
throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
function verifyReceipt(receipt, appId, operationId) {
|
|
1898
|
+
if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
|
|
1899
|
+
throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
|
|
1900
|
+
}
|
|
1901
|
+
if (receipt.receiptDigest) {
|
|
1902
|
+
const { receiptDigest, ...fields } = receipt;
|
|
1903
|
+
if (configDigest(fields) !== receiptDigest) {
|
|
1904
|
+
throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
|
|
1905
|
+
}
|
|
1906
|
+
} else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
|
|
1907
|
+
throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function assertOperationId(value2) {
|
|
1911
|
+
if (!OPERATION_ID.test(value2)) {
|
|
1912
|
+
throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
function assertActions(actions) {
|
|
1916
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1917
|
+
for (const action2 of actions) {
|
|
1918
|
+
if (!record2(action2)) invalidPlan("every plan action must be an object");
|
|
1919
|
+
const id = String(action2.id ?? "");
|
|
1920
|
+
if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
|
|
1921
|
+
ids.add(id);
|
|
1922
|
+
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))) {
|
|
1923
|
+
invalidPlan("plan action metadata is invalid");
|
|
1924
|
+
}
|
|
1925
|
+
if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
|
|
1926
|
+
assertConditionalAction(action2);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
function assertConditionalAction(action2) {
|
|
1931
|
+
if (action2.kind === "rename_app") {
|
|
1932
|
+
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");
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
|
|
1936
|
+
if (action2.kind === "set_link") {
|
|
1937
|
+
if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (!action2.service || !SERVICE.test(action2.service) || !appServiceDefinition2(action2.service)) {
|
|
1941
|
+
invalidPlan("service action names an unknown service");
|
|
1942
|
+
}
|
|
1943
|
+
const base = `environments.${action2.env}.services.${action2.service}`;
|
|
1944
|
+
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
1945
|
+
invalidPlan("service action path is invalid");
|
|
1946
|
+
}
|
|
1947
|
+
if (action2.applySupport !== "provision" || !record2(action2.after)) {
|
|
1948
|
+
invalidPlan("service action payload is invalid");
|
|
1949
|
+
}
|
|
1950
|
+
if (action2.kind === "enable_service") {
|
|
1951
|
+
if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
|
|
1952
|
+
invalidPlan("service enable action is invalid");
|
|
1953
|
+
}
|
|
1954
|
+
} else if (!record2(action2.before)) {
|
|
1955
|
+
invalidPlan("service configure action is invalid");
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
function linkState(value2) {
|
|
1959
|
+
if (value2 === null) return true;
|
|
1960
|
+
if (typeof value2 !== "string") return false;
|
|
1961
|
+
try {
|
|
1962
|
+
const url = new URL(value2.trim());
|
|
1963
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1964
|
+
} catch {
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function invalidPlan(message2) {
|
|
1969
|
+
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
1970
|
+
}
|
|
1971
|
+
function record2(value2) {
|
|
1972
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/config-reconcile-support.ts
|
|
1976
|
+
var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
|
|
1977
|
+
"rename_app",
|
|
1978
|
+
"enable_service",
|
|
1979
|
+
"configure_service",
|
|
1980
|
+
"set_link"
|
|
1981
|
+
]);
|
|
1982
|
+
function configApplySupport(reconciliation) {
|
|
1983
|
+
if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
|
|
1984
|
+
return {
|
|
1985
|
+
supported: false,
|
|
1986
|
+
checkpointRequired: false,
|
|
1987
|
+
reason: "the app must already exist in a revision-aware Registry"
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
if (!reconciliation.actions.length) {
|
|
1991
|
+
return {
|
|
1992
|
+
supported: false,
|
|
1993
|
+
checkpointRequired: false,
|
|
1994
|
+
reason: "there are no managed changes to apply"
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
const blocked = reconciliation.actions.filter(
|
|
1998
|
+
(action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
1999
|
+
);
|
|
2000
|
+
if (blocked.length) {
|
|
2001
|
+
return {
|
|
2002
|
+
supported: false,
|
|
2003
|
+
checkpointRequired: blocked.some(
|
|
2004
|
+
(action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
|
|
2005
|
+
),
|
|
2006
|
+
reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
return {
|
|
2010
|
+
supported: true,
|
|
2011
|
+
checkpointRequired: false,
|
|
2012
|
+
reason: "all actions are low-risk, checkpoint-free Registry changes"
|
|
2013
|
+
};
|
|
1800
2014
|
}
|
|
1801
2015
|
|
|
2016
|
+
// src/config-operation-command.ts
|
|
2017
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
|
|
2018
|
+
var DEFAULT_WAIT_SECONDS = 60;
|
|
2019
|
+
var DEFAULT_INTERVAL_SECONDS = 2;
|
|
2020
|
+
async function configApply(options) {
|
|
2021
|
+
const plan = readPlan(options.planPath);
|
|
2022
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2023
|
+
assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
|
|
2024
|
+
const support = configApplySupport(plan);
|
|
2025
|
+
if (!support.supported) {
|
|
2026
|
+
throw new ConfigOperationCommandError(
|
|
2027
|
+
support.reason,
|
|
2028
|
+
support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
|
|
2032
|
+
throw new ConfigOperationCommandError(
|
|
2033
|
+
"project config changed after this plan was frozen; generate and review a fresh plan",
|
|
2034
|
+
"checkpoint_required"
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
|
|
2038
|
+
if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
|
|
2039
|
+
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
2040
|
+
}
|
|
2041
|
+
const client = await operationClient(cfg, options, "apply");
|
|
2042
|
+
const request2 = {
|
|
2043
|
+
schemaVersion: "odla.config-operation-request/v1",
|
|
2044
|
+
expectedRevision: plan.registryRevision,
|
|
2045
|
+
desiredRevision: plan.desiredRevision,
|
|
2046
|
+
observedRevision: plan.observedRevision,
|
|
2047
|
+
planDigest: plan.planDigest,
|
|
2048
|
+
idempotencyKey,
|
|
2049
|
+
source: { kind: "cli", version: cliVersion() },
|
|
2050
|
+
actions: plan.actions
|
|
2051
|
+
};
|
|
2052
|
+
let receipt;
|
|
2053
|
+
try {
|
|
2054
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request2);
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
const retained = retainedReceipt(error);
|
|
2057
|
+
if (retained) {
|
|
2058
|
+
verifyReceipt(retained, cfg.app.id);
|
|
2059
|
+
printReceipt(retained, options);
|
|
2060
|
+
throw failureForReceipt(retained);
|
|
2061
|
+
}
|
|
2062
|
+
throw normalizeRequestError(error);
|
|
2063
|
+
}
|
|
2064
|
+
verifyReceipt(receipt, cfg.app.id);
|
|
2065
|
+
printReceipt(receipt, options);
|
|
2066
|
+
assertApplyCompleted(receipt);
|
|
2067
|
+
return receipt;
|
|
2068
|
+
}
|
|
2069
|
+
async function configOperationGet(options) {
|
|
2070
|
+
assertOperationId(options.operationId);
|
|
2071
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2072
|
+
const client = await operationClient(cfg, options, "read");
|
|
2073
|
+
const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
2074
|
+
throw normalizeRequestError(error);
|
|
2075
|
+
});
|
|
2076
|
+
if (!receipt) {
|
|
2077
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
2078
|
+
}
|
|
2079
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
2080
|
+
printReceipt(receipt, options);
|
|
2081
|
+
return receipt;
|
|
2082
|
+
}
|
|
2083
|
+
async function configOperationWait(options) {
|
|
2084
|
+
assertOperationId(options.operationId);
|
|
2085
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
2086
|
+
const client = await operationClient(cfg, options, "wait");
|
|
2087
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
2088
|
+
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
2089
|
+
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
2090
|
+
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
2091
|
+
let receipt = null;
|
|
2092
|
+
for (; ; ) {
|
|
2093
|
+
receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
|
|
2094
|
+
throw normalizeRequestError(error);
|
|
2095
|
+
});
|
|
2096
|
+
if (!receipt) {
|
|
2097
|
+
throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
|
|
2098
|
+
}
|
|
2099
|
+
verifyReceipt(receipt, cfg.app.id, options.operationId);
|
|
2100
|
+
if (receipt.state !== "running") break;
|
|
2101
|
+
if (now() >= deadline) {
|
|
2102
|
+
printReceipt(receipt, options);
|
|
2103
|
+
throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
|
|
2104
|
+
}
|
|
2105
|
+
await wait2(Math.min(interval, Math.max(0, deadline - now())));
|
|
2106
|
+
}
|
|
2107
|
+
printReceipt(receipt, options);
|
|
2108
|
+
if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
|
|
2109
|
+
return receipt;
|
|
2110
|
+
}
|
|
2111
|
+
async function operationClient(cfg, options, purpose) {
|
|
2112
|
+
const doFetch = options.fetch ?? fetch;
|
|
2113
|
+
const out = options.stdout ?? console;
|
|
2114
|
+
const token = await resolveAdminPlatformToken({
|
|
2115
|
+
platform: cfg.platformUrl,
|
|
2116
|
+
scope: "app:config:write",
|
|
2117
|
+
token: options.token,
|
|
2118
|
+
tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
2119
|
+
rootDir: cfg.rootDir,
|
|
2120
|
+
email: options.email,
|
|
2121
|
+
open: options.open,
|
|
2122
|
+
fetch: doFetch,
|
|
2123
|
+
stdout: out,
|
|
2124
|
+
openApprovalUrl: options.openApprovalUrl,
|
|
2125
|
+
label: `odla CLI (${cfg.app.id} config operation ${purpose})`
|
|
2126
|
+
});
|
|
2127
|
+
return createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
2128
|
+
}
|
|
2129
|
+
function printReceipt(receipt, options) {
|
|
2130
|
+
const out = options.stdout ?? console;
|
|
2131
|
+
if (options.json) out.log(JSON.stringify(receipt, null, 2));
|
|
2132
|
+
else {
|
|
2133
|
+
out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
|
|
2134
|
+
out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
|
|
2135
|
+
for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
|
|
2136
|
+
out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
|
|
2137
|
+
out.log(`studio: ${receipt.studioUrl}`);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
function assertApplyCompleted(receipt) {
|
|
2141
|
+
if (receipt.state === "succeeded") return;
|
|
2142
|
+
throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
|
|
2143
|
+
}
|
|
2144
|
+
function failureForReceipt(receipt) {
|
|
2145
|
+
if (receipt.state === "conflict") return new ConfigOperationCommandError(
|
|
2146
|
+
receipt.error?.message ?? "config operation conflicted with current Registry state",
|
|
2147
|
+
"checkpoint_required"
|
|
2148
|
+
);
|
|
2149
|
+
if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
|
|
2150
|
+
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
2151
|
+
}
|
|
2152
|
+
function retainedReceipt(error) {
|
|
2153
|
+
if (!(error instanceof AppsError) || !record3(error.details)) return null;
|
|
2154
|
+
return record3(error.details.operation) ? error.details.operation : null;
|
|
2155
|
+
}
|
|
2156
|
+
function normalizeRequestError(error) {
|
|
2157
|
+
if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
2158
|
+
if (error.status === 401 || error.status === 403) {
|
|
2159
|
+
return new ConfigOperationCommandError(error.message, "auth_failed");
|
|
2160
|
+
}
|
|
2161
|
+
if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
|
|
2162
|
+
if (error.status === 429 || error.status >= 500) {
|
|
2163
|
+
return new ConfigOperationCommandError(error.message, "remote_unavailable");
|
|
2164
|
+
}
|
|
2165
|
+
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
2166
|
+
}
|
|
2167
|
+
function record3(value2) {
|
|
2168
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// src/config-reconcile.ts
|
|
2172
|
+
import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
|
|
2173
|
+
|
|
1802
2174
|
// src/config-reconcile-values.ts
|
|
1803
2175
|
function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
|
|
1804
2176
|
return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
|
|
@@ -1898,6 +2270,7 @@ function reconcileConfig(input) {
|
|
|
1898
2270
|
sources: { desired: desiredSource, observed: observedSource },
|
|
1899
2271
|
desiredRevision,
|
|
1900
2272
|
observedRevision,
|
|
2273
|
+
registryRevision: input.observed?.configRevision ?? null,
|
|
1901
2274
|
status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
|
|
1902
2275
|
summary: {
|
|
1903
2276
|
differences: different,
|
|
@@ -2050,8 +2423,8 @@ function compareOptionalEnvironmentState(env, desired, observed, desiredSource,
|
|
|
2050
2423
|
}
|
|
2051
2424
|
|
|
2052
2425
|
// src/config-reconcile-command.ts
|
|
2053
|
-
import { createAppsClient } from "@odla-ai/apps";
|
|
2054
|
-
import { join as
|
|
2426
|
+
import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
|
|
2427
|
+
import { join as join4 } from "path";
|
|
2055
2428
|
async function configDiff(options) {
|
|
2056
2429
|
const reconciliation = await inspectConfig(options);
|
|
2057
2430
|
const { actions: _actions, ...read3 } = reconciliation;
|
|
@@ -2065,20 +2438,19 @@ async function configDiff(options) {
|
|
|
2065
2438
|
}
|
|
2066
2439
|
async function configPlan(options) {
|
|
2067
2440
|
const reconciliation = await inspectConfig(options);
|
|
2441
|
+
const apply = configApplySupport(reconciliation);
|
|
2068
2442
|
const planDigest = configDigest({
|
|
2069
|
-
schemaVersion: "odla.config-plan/
|
|
2443
|
+
schemaVersion: "odla.config-plan/v2",
|
|
2070
2444
|
desiredRevision: reconciliation.desiredRevision,
|
|
2071
2445
|
observedRevision: reconciliation.observedRevision,
|
|
2446
|
+
registryRevision: reconciliation.registryRevision,
|
|
2072
2447
|
actions: reconciliation.actions
|
|
2073
2448
|
});
|
|
2074
2449
|
const document = {
|
|
2075
|
-
schemaVersion: "odla.config-plan/
|
|
2450
|
+
schemaVersion: "odla.config-plan/v2",
|
|
2076
2451
|
...reconciliation,
|
|
2077
2452
|
planDigest,
|
|
2078
|
-
apply
|
|
2079
|
-
supported: false,
|
|
2080
|
-
reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
|
|
2081
|
-
},
|
|
2453
|
+
apply,
|
|
2082
2454
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
2083
2455
|
};
|
|
2084
2456
|
printPlan(document, options);
|
|
@@ -2092,7 +2464,7 @@ async function inspectConfig(options) {
|
|
|
2092
2464
|
platform: cfg.platformUrl,
|
|
2093
2465
|
scope: "app:config:read",
|
|
2094
2466
|
token: options.token,
|
|
2095
|
-
tokenFile:
|
|
2467
|
+
tokenFile: join4(cfg.rootDir, ".odla", "admin-token.local.json"),
|
|
2096
2468
|
rootDir: cfg.rootDir,
|
|
2097
2469
|
email: options.email,
|
|
2098
2470
|
open: options.open,
|
|
@@ -2101,7 +2473,7 @@ async function inspectConfig(options) {
|
|
|
2101
2473
|
openApprovalUrl: options.openApprovalUrl,
|
|
2102
2474
|
label: `odla CLI (${cfg.app.id} config read)`
|
|
2103
2475
|
});
|
|
2104
|
-
const client =
|
|
2476
|
+
const client = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
2105
2477
|
const observed = await client.resolveApp(cfg.app.id);
|
|
2106
2478
|
return reconcileConfig({
|
|
2107
2479
|
desired: desiredRegistryState(cfg),
|
|
@@ -2137,7 +2509,7 @@ function printPlan(document, options) {
|
|
|
2137
2509
|
}
|
|
2138
2510
|
if (!document.actions.length) out.log(" no managed changes");
|
|
2139
2511
|
out.log(`plan digest: ${document.planDigest}`);
|
|
2140
|
-
out.log(`apply:
|
|
2512
|
+
out.log(`apply: ${document.apply.supported ? "supported" : "blocked"} \u2014 ${document.apply.reason}`);
|
|
2141
2513
|
printNext(out, document.nextActions);
|
|
2142
2514
|
}
|
|
2143
2515
|
function printHeader(out, kind, document) {
|
|
@@ -2145,6 +2517,7 @@ function printHeader(out, kind, document) {
|
|
|
2145
2517
|
out.log(`platform: ${document.scope.platformUrl}`);
|
|
2146
2518
|
out.log(`desired: ${document.desiredRevision}`);
|
|
2147
2519
|
out.log(`observed: ${document.observedRevision ?? "absent"}`);
|
|
2520
|
+
out.log(`registry: ${document.registryRevision ?? "absent"}`);
|
|
2148
2521
|
out.log(
|
|
2149
2522
|
`summary: ${document.summary.differences} different, ${document.summary.unmanaged} unmanaged, ${document.summary.actions} planned actions`
|
|
2150
2523
|
);
|
|
@@ -2177,6 +2550,13 @@ function diffNextActions(reconciliation, configPath) {
|
|
|
2177
2550
|
}
|
|
2178
2551
|
function planNextActions(reconciliation, configPath) {
|
|
2179
2552
|
const next = [];
|
|
2553
|
+
if (configApplySupport(reconciliation).supported) {
|
|
2554
|
+
next.push({
|
|
2555
|
+
code: "apply_frozen_plan",
|
|
2556
|
+
command: "odla-ai config apply --plan <saved-plan.json> --json",
|
|
2557
|
+
description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2180
2560
|
if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
|
|
2181
2561
|
next.push({
|
|
2182
2562
|
code: "review_provision",
|
|
@@ -2190,35 +2570,39 @@ function planNextActions(reconciliation, configPath) {
|
|
|
2190
2570
|
}
|
|
2191
2571
|
}
|
|
2192
2572
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
2193
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
2194
2573
|
next.push({
|
|
2195
2574
|
code: "review_destructive",
|
|
2196
|
-
command:
|
|
2575
|
+
command: studioSettingsUrl(reconciliation),
|
|
2197
2576
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
2198
2577
|
});
|
|
2199
2578
|
}
|
|
2200
2579
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
2201
2580
|
next.push({
|
|
2202
2581
|
code: "declare_or_accept_runtime_state",
|
|
2203
|
-
command:
|
|
2582
|
+
command: studioSettingsUrl(reconciliation),
|
|
2204
2583
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
2205
2584
|
});
|
|
2206
2585
|
}
|
|
2207
2586
|
return next;
|
|
2208
2587
|
}
|
|
2588
|
+
function studioSettingsUrl(reconciliation) {
|
|
2589
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
2590
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
2591
|
+
return `${origin}${studioAppSettingsPath(reconciliation.scope.appId, environment2, "environment")}`;
|
|
2592
|
+
}
|
|
2209
2593
|
function quoteArg2(value2) {
|
|
2210
2594
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
2211
2595
|
}
|
|
2212
2596
|
|
|
2213
2597
|
// src/doctor-checks.ts
|
|
2214
2598
|
import { execFileSync } from "child_process";
|
|
2215
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2216
|
-
import { join as
|
|
2599
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
2600
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
2217
2601
|
|
|
2218
2602
|
// src/wrangler.ts
|
|
2219
2603
|
import { spawn as spawn2 } from "child_process";
|
|
2220
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2221
|
-
import { join as
|
|
2604
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
2605
|
+
import { join as join5 } from "path";
|
|
2222
2606
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
2223
2607
|
const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2224
2608
|
let stdout = "";
|
|
@@ -2232,7 +2616,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
|
|
|
2232
2616
|
var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
|
|
2233
2617
|
function findWranglerConfig(rootDir) {
|
|
2234
2618
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
2235
|
-
const path =
|
|
2619
|
+
const path = join5(rootDir, name);
|
|
2236
2620
|
if (existsSync4(path)) return path;
|
|
2237
2621
|
}
|
|
2238
2622
|
return null;
|
|
@@ -2240,20 +2624,20 @@ function findWranglerConfig(rootDir) {
|
|
|
2240
2624
|
function readWranglerConfig(path) {
|
|
2241
2625
|
if (path.endsWith(".toml")) return null;
|
|
2242
2626
|
try {
|
|
2243
|
-
return JSON.parse(stripJsonComments(
|
|
2627
|
+
return JSON.parse(stripJsonComments(readFileSync5(path, "utf8")));
|
|
2244
2628
|
} catch {
|
|
2245
2629
|
return null;
|
|
2246
2630
|
}
|
|
2247
2631
|
}
|
|
2248
|
-
function stripJsonComments(
|
|
2632
|
+
function stripJsonComments(text2) {
|
|
2249
2633
|
let result = "";
|
|
2250
2634
|
let inString = false;
|
|
2251
|
-
for (let i = 0; i <
|
|
2252
|
-
const ch =
|
|
2635
|
+
for (let i = 0; i < text2.length; i++) {
|
|
2636
|
+
const ch = text2[i];
|
|
2253
2637
|
if (inString) {
|
|
2254
2638
|
result += ch;
|
|
2255
2639
|
if (ch === "\\") {
|
|
2256
|
-
result +=
|
|
2640
|
+
result += text2[i + 1] ?? "";
|
|
2257
2641
|
i++;
|
|
2258
2642
|
} else if (ch === '"') {
|
|
2259
2643
|
inString = false;
|
|
@@ -2265,14 +2649,14 @@ function stripJsonComments(text) {
|
|
|
2265
2649
|
result += ch;
|
|
2266
2650
|
continue;
|
|
2267
2651
|
}
|
|
2268
|
-
if (ch === "/" &&
|
|
2269
|
-
while (i <
|
|
2652
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
2653
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
2270
2654
|
result += "\n";
|
|
2271
2655
|
continue;
|
|
2272
2656
|
}
|
|
2273
|
-
if (ch === "/" &&
|
|
2657
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
2274
2658
|
i += 2;
|
|
2275
|
-
while (i <
|
|
2659
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
2276
2660
|
i++;
|
|
2277
2661
|
continue;
|
|
2278
2662
|
}
|
|
@@ -2289,7 +2673,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
2289
2673
|
}
|
|
2290
2674
|
}
|
|
2291
2675
|
function wranglerPutSecret(run, opts) {
|
|
2292
|
-
const args = [
|
|
2676
|
+
const args = [
|
|
2677
|
+
"wrangler",
|
|
2678
|
+
"secret",
|
|
2679
|
+
"put",
|
|
2680
|
+
opts.name,
|
|
2681
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
2682
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
2683
|
+
];
|
|
2293
2684
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
2294
2685
|
}
|
|
2295
2686
|
|
|
@@ -2341,7 +2732,7 @@ function wranglerWarnings(rootDir) {
|
|
|
2341
2732
|
const dir = resolve3(rootDir, assets.directory);
|
|
2342
2733
|
if (dir === resolve3(rootDir)) {
|
|
2343
2734
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
2344
|
-
} else if (existsSync5(
|
|
2735
|
+
} else if (existsSync5(join6(dir, "node_modules"))) {
|
|
2345
2736
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
2346
2737
|
}
|
|
2347
2738
|
}
|
|
@@ -2382,7 +2773,7 @@ function o11yProjectWarnings(rootDir) {
|
|
|
2382
2773
|
} else {
|
|
2383
2774
|
let source = "";
|
|
2384
2775
|
try {
|
|
2385
|
-
source =
|
|
2776
|
+
source = readFileSync6(main, "utf8");
|
|
2386
2777
|
} catch {
|
|
2387
2778
|
}
|
|
2388
2779
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -2406,7 +2797,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
2406
2797
|
}
|
|
2407
2798
|
function readPackageJson(rootDir) {
|
|
2408
2799
|
try {
|
|
2409
|
-
return JSON.parse(
|
|
2800
|
+
return JSON.parse(readFileSync6(join6(rootDir, "package.json"), "utf8"));
|
|
2410
2801
|
} catch {
|
|
2411
2802
|
return null;
|
|
2412
2803
|
}
|
|
@@ -2616,7 +3007,7 @@ async function doctor(options) {
|
|
|
2616
3007
|
// src/init.ts
|
|
2617
3008
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2618
3009
|
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
2619
|
-
import { appServiceDefinition as
|
|
3010
|
+
import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
|
|
2620
3011
|
function initProject(options) {
|
|
2621
3012
|
const out = options.stdout ?? console;
|
|
2622
3013
|
const rootDir = resolve4(options.rootDir ?? process.cwd());
|
|
@@ -2630,7 +3021,7 @@ function initProject(options) {
|
|
|
2630
3021
|
const envs = options.envs?.length ? options.envs : ["dev"];
|
|
2631
3022
|
const services = options.services?.length ? options.services : ["db", "ai"];
|
|
2632
3023
|
for (const service of services) {
|
|
2633
|
-
const definition =
|
|
3024
|
+
const definition = appServiceDefinition3(service);
|
|
2634
3025
|
if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${appServiceIds3().join(", ")})`);
|
|
2635
3026
|
for (const dependency of definition.requires) {
|
|
2636
3027
|
if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
|
|
@@ -2648,9 +3039,9 @@ function initProject(options) {
|
|
|
2648
3039
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
2649
3040
|
out.log("updated .gitignore for local odla credentials");
|
|
2650
3041
|
}
|
|
2651
|
-
function writeIfMissing(path,
|
|
3042
|
+
function writeIfMissing(path, text2) {
|
|
2652
3043
|
if (existsSync6(path)) return;
|
|
2653
|
-
writeFileSync2(path,
|
|
3044
|
+
writeFileSync2(path, text2);
|
|
2654
3045
|
}
|
|
2655
3046
|
function configTemplate(input) {
|
|
2656
3047
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -2858,13 +3249,13 @@ async function secretsSetClerkKey(options) {
|
|
|
2858
3249
|
body: JSON.stringify({ value: value2 })
|
|
2859
3250
|
});
|
|
2860
3251
|
if (!res.ok) {
|
|
2861
|
-
const
|
|
2862
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
3252
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
3253
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
2863
3254
|
}
|
|
2864
3255
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
2865
3256
|
}
|
|
2866
|
-
function scrubValue(
|
|
2867
|
-
return redactSecrets(
|
|
3257
|
+
function scrubValue(text2, value2) {
|
|
3258
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
2868
3259
|
}
|
|
2869
3260
|
async function resolveVaultWrite(options) {
|
|
2870
3261
|
const out = options.stdout ?? console;
|
|
@@ -2881,9 +3272,9 @@ async function resolveVaultWrite(options) {
|
|
|
2881
3272
|
}
|
|
2882
3273
|
|
|
2883
3274
|
// src/skill.ts
|
|
2884
|
-
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as
|
|
3275
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2885
3276
|
import { homedir } from "os";
|
|
2886
|
-
import { dirname as dirname5, isAbsolute as isAbsolute3, join as
|
|
3277
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5, sep } from "path";
|
|
2887
3278
|
import { fileURLToPath } from "url";
|
|
2888
3279
|
|
|
2889
3280
|
// src/skill-adapters.ts
|
|
@@ -2977,12 +3368,12 @@ function installSkill(options = {}) {
|
|
|
2977
3368
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
2978
3369
|
};
|
|
2979
3370
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
2980
|
-
for (const rel of files) plan(
|
|
3371
|
+
for (const rel of files) plan(join7(targetDir2, rel), readFileSync7(join7(sourceDir, rel), "utf8"), false, boundary);
|
|
2981
3372
|
};
|
|
2982
3373
|
let targetDir;
|
|
2983
3374
|
if (options.global) {
|
|
2984
|
-
const claudeRoot =
|
|
2985
|
-
const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ??
|
|
3375
|
+
const claudeRoot = join7(home, ".claude", "skills");
|
|
3376
|
+
const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
|
|
2986
3377
|
targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
|
|
2987
3378
|
for (const harness of harnesses) {
|
|
2988
3379
|
const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
|
|
@@ -2990,35 +3381,35 @@ function installSkill(options = {}) {
|
|
|
2990
3381
|
rememberTarget(harness, skillRoot);
|
|
2991
3382
|
}
|
|
2992
3383
|
} else {
|
|
2993
|
-
const sharedRoot =
|
|
3384
|
+
const sharedRoot = join7(root, ".agents", "skills");
|
|
2994
3385
|
planSkillTree(sharedRoot);
|
|
2995
|
-
const claudeRoot =
|
|
3386
|
+
const claudeRoot = join7(root, ".claude", "skills");
|
|
2996
3387
|
targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
|
|
2997
3388
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
2998
3389
|
if (harnesses.includes("claude")) {
|
|
2999
3390
|
for (const skill of skillNames(files)) {
|
|
3000
|
-
const canonical =
|
|
3001
|
-
plan(
|
|
3391
|
+
const canonical = readFileSync7(join7(sourceDir, skill, "SKILL.md"), "utf8");
|
|
3392
|
+
plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
3002
3393
|
}
|
|
3003
3394
|
rememberTarget("claude", claudeRoot);
|
|
3004
3395
|
}
|
|
3005
3396
|
if (harnesses.includes("cursor")) {
|
|
3006
|
-
const cursorRule =
|
|
3397
|
+
const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
|
|
3007
3398
|
plan(cursorRule, CURSOR_RULE);
|
|
3008
3399
|
rememberTarget("cursor", cursorRule);
|
|
3009
3400
|
}
|
|
3010
3401
|
if (harnesses.includes("agents")) {
|
|
3011
|
-
const agentsFile =
|
|
3402
|
+
const agentsFile = join7(root, "AGENTS.md");
|
|
3012
3403
|
plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3013
3404
|
rememberTarget("agents", agentsFile);
|
|
3014
3405
|
}
|
|
3015
3406
|
if (harnesses.includes("copilot")) {
|
|
3016
|
-
const copilotFile =
|
|
3407
|
+
const copilotFile = join7(root, ".github", "copilot-instructions.md");
|
|
3017
3408
|
plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3018
3409
|
rememberTarget("copilot", copilotFile);
|
|
3019
3410
|
}
|
|
3020
3411
|
if (harnesses.includes("gemini")) {
|
|
3021
|
-
const geminiFile =
|
|
3412
|
+
const geminiFile = join7(root, "GEMINI.md");
|
|
3022
3413
|
plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
|
|
3023
3414
|
rememberTarget("gemini", geminiFile);
|
|
3024
3415
|
}
|
|
@@ -3036,7 +3427,7 @@ function installSkill(options = {}) {
|
|
|
3036
3427
|
writtenPaths.add(file.target);
|
|
3037
3428
|
continue;
|
|
3038
3429
|
}
|
|
3039
|
-
const current =
|
|
3430
|
+
const current = readFileSync7(file.target, "utf8");
|
|
3040
3431
|
if (current === file.content) {
|
|
3041
3432
|
unchangedPaths.add(file.target);
|
|
3042
3433
|
} else if (file.managedMerge || options.force) {
|
|
@@ -3053,7 +3444,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
3053
3444
|
);
|
|
3054
3445
|
}
|
|
3055
3446
|
for (const file of plans.values()) {
|
|
3056
|
-
if (!existsSync7(file.target) ||
|
|
3447
|
+
if (!existsSync7(file.target) || readFileSync7(file.target, "utf8") !== file.content) {
|
|
3057
3448
|
mkdirSync3(dirname5(file.target), { recursive: true });
|
|
3058
3449
|
writeFileSync3(file.target, file.content);
|
|
3059
3450
|
}
|
|
@@ -3098,7 +3489,7 @@ function managedFileContent(path, block, force, boundary) {
|
|
|
3098
3489
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
3099
3490
|
if (!existsSync7(path)) return `${block}
|
|
3100
3491
|
`;
|
|
3101
|
-
const current =
|
|
3492
|
+
const current = readFileSync7(path, "utf8");
|
|
3102
3493
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
3103
3494
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
3104
3495
|
const startAt = current.indexOf(start);
|
|
@@ -3125,7 +3516,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
3125
3516
|
}
|
|
3126
3517
|
let current = boundary;
|
|
3127
3518
|
for (const part of rel.split(sep).filter(Boolean)) {
|
|
3128
|
-
current =
|
|
3519
|
+
current = join7(current, part);
|
|
3129
3520
|
try {
|
|
3130
3521
|
if (lstatSync(current).isSymbolicLink()) return current;
|
|
3131
3522
|
} catch (error) {
|
|
@@ -3142,7 +3533,7 @@ function listFiles(dir) {
|
|
|
3142
3533
|
const results = [];
|
|
3143
3534
|
const walk = (current) => {
|
|
3144
3535
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
3145
|
-
const path =
|
|
3536
|
+
const path = join7(current, entry.name);
|
|
3146
3537
|
if (entry.isDirectory()) walk(path);
|
|
3147
3538
|
else results.push(relative2(dir, path));
|
|
3148
3539
|
}
|
|
@@ -3533,7 +3924,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
3533
3924
|
import { createHash as createHash2 } from "crypto";
|
|
3534
3925
|
import { copyFile, mkdtemp, readFile, rm, writeFile } from "fs/promises";
|
|
3535
3926
|
import { tmpdir } from "os";
|
|
3536
|
-
import { join as
|
|
3927
|
+
import { join as join8 } from "path";
|
|
3537
3928
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3538
3929
|
var runCodeImageCommand = (command, args, stdio) => new Promise((accept, reject) => {
|
|
3539
3930
|
const child = spawn3(command, [...args], { shell: false, stdio });
|
|
@@ -3592,10 +3983,10 @@ async function embeddedPiImageName() {
|
|
|
3592
3983
|
return `odla-ai/pi-agent:embedded-sha256-${createHash2("sha256").update(bundle).digest("hex")}`;
|
|
3593
3984
|
}
|
|
3594
3985
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
3595
|
-
const context = await mkdtemp(
|
|
3986
|
+
const context = await mkdtemp(join8(tmpdir(), "odla-code-pi-"));
|
|
3596
3987
|
try {
|
|
3597
|
-
await copyFile(embeddedPiAssetPath(),
|
|
3598
|
-
await writeFile(
|
|
3988
|
+
await copyFile(embeddedPiAssetPath(), join8(context, "pi-agent.js"));
|
|
3989
|
+
await writeFile(join8(context, "Dockerfile"), [
|
|
3599
3990
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
3600
3991
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
3601
3992
|
"WORKDIR /workspace",
|
|
@@ -3621,7 +4012,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
|
3621
4012
|
var HarnessProtocolError = class extends Error {
|
|
3622
4013
|
name = "HarnessProtocolError";
|
|
3623
4014
|
};
|
|
3624
|
-
function
|
|
4015
|
+
function record4(value2) {
|
|
3625
4016
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
3626
4017
|
}
|
|
3627
4018
|
function boundedText(value2, label, max) {
|
|
@@ -3638,7 +4029,7 @@ function parseAgentOutput(line) {
|
|
|
3638
4029
|
} catch {
|
|
3639
4030
|
throw new HarnessProtocolError("agent emitted invalid JSON");
|
|
3640
4031
|
}
|
|
3641
|
-
const message2 =
|
|
4032
|
+
const message2 = record4(value2);
|
|
3642
4033
|
if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
|
|
3643
4034
|
throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
|
|
3644
4035
|
}
|
|
@@ -3651,7 +4042,7 @@ function parseAgentOutput(line) {
|
|
|
3651
4042
|
};
|
|
3652
4043
|
}
|
|
3653
4044
|
if (message2.type === "inference.request") {
|
|
3654
|
-
const call2 =
|
|
4045
|
+
const call2 = record4(message2.call);
|
|
3655
4046
|
if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
|
|
3656
4047
|
throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
|
|
3657
4048
|
}
|
|
@@ -3663,7 +4054,7 @@ function parseAgentOutput(line) {
|
|
|
3663
4054
|
};
|
|
3664
4055
|
}
|
|
3665
4056
|
if (message2.type === "tool.request") {
|
|
3666
|
-
const input =
|
|
4057
|
+
const input = record4(message2.input);
|
|
3667
4058
|
const tool = String(message2.tool);
|
|
3668
4059
|
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
3669
4060
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
@@ -3698,7 +4089,7 @@ function encodeAgentInput(message2) {
|
|
|
3698
4089
|
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
3699
4090
|
import { constants } from "fs";
|
|
3700
4091
|
import { access } from "fs/promises";
|
|
3701
|
-
import { delimiter, join as
|
|
4092
|
+
import { delimiter, join as join9 } from "path";
|
|
3702
4093
|
import { getgid, getuid } from "process";
|
|
3703
4094
|
import { mkdir, mkdtemp as mkdtemp2, realpath, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3704
4095
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -3716,7 +4107,7 @@ function assertPinnedImage(image) {
|
|
|
3716
4107
|
async function commandAvailable(engine) {
|
|
3717
4108
|
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
3718
4109
|
try {
|
|
3719
|
-
await access(
|
|
4110
|
+
await access(join9(directory, engine), constants.X_OK);
|
|
3720
4111
|
return true;
|
|
3721
4112
|
} catch {
|
|
3722
4113
|
}
|
|
@@ -3847,8 +4238,8 @@ async function runContainerAttempt(options) {
|
|
|
3847
4238
|
let stopped = false;
|
|
3848
4239
|
let exited = false;
|
|
3849
4240
|
child.stderr.setEncoding("utf8");
|
|
3850
|
-
child.stderr.on("data", (
|
|
3851
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
4241
|
+
child.stderr.on("data", (text2) => {
|
|
4242
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
3852
4243
|
});
|
|
3853
4244
|
const stop = (reason) => {
|
|
3854
4245
|
if (stopped || exited) return;
|
|
@@ -4006,8 +4397,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
4006
4397
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
4007
4398
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
4008
4399
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
4009
|
-
const entries = inventory.flatMap((
|
|
4010
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
4400
|
+
const entries = inventory.flatMap((record11) => {
|
|
4401
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
4011
4402
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
4012
4403
|
});
|
|
4013
4404
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -4255,8 +4646,8 @@ function normalize(value2) {
|
|
|
4255
4646
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
4256
4647
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
4257
4648
|
if (typeof value2 === "object") {
|
|
4258
|
-
const
|
|
4259
|
-
return Object.fromEntries(Object.keys(
|
|
4649
|
+
const record11 = value2;
|
|
4650
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
4260
4651
|
}
|
|
4261
4652
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
4262
4653
|
}
|
|
@@ -4335,7 +4726,7 @@ function normalizeReaders(readers) {
|
|
|
4335
4726
|
}
|
|
4336
4727
|
|
|
4337
4728
|
// ../camel/dist/code.js
|
|
4338
|
-
var
|
|
4729
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
4339
4730
|
var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
4340
4731
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
4341
4732
|
async function digestCodeVerificationReceipt(fields) {
|
|
@@ -4370,18 +4761,18 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
4370
4761
|
return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
|
|
4371
4762
|
}
|
|
4372
4763
|
function validate(fields) {
|
|
4373
|
-
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !
|
|
4764
|
+
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) {
|
|
4374
4765
|
throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
|
|
4375
4766
|
}
|
|
4376
4767
|
const ids = /* @__PURE__ */ new Set();
|
|
4377
4768
|
for (const recipe2 of fields.recipes) {
|
|
4378
|
-
if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !
|
|
4769
|
+
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) {
|
|
4379
4770
|
throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
|
|
4380
4771
|
}
|
|
4381
4772
|
const artifactIds = /* @__PURE__ */ new Set();
|
|
4382
4773
|
if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
|
|
4383
4774
|
for (const artifact of recipe2.artifacts) {
|
|
4384
|
-
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 && !
|
|
4775
|
+
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)) {
|
|
4385
4776
|
throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
|
|
4386
4777
|
}
|
|
4387
4778
|
artifactIds.add(artifact.artifactId);
|
|
@@ -4397,7 +4788,7 @@ function validate(fields) {
|
|
|
4397
4788
|
}
|
|
4398
4789
|
}
|
|
4399
4790
|
var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
4400
|
-
var
|
|
4791
|
+
var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
|
|
4401
4792
|
var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
|
|
4402
4793
|
var MAX_PATCH_BYTES = 256 * 1024;
|
|
4403
4794
|
var MAX_STATE_BYTES = 64 * 1024;
|
|
@@ -4442,14 +4833,14 @@ function normalizeState(value2) {
|
|
|
4442
4833
|
]);
|
|
4443
4834
|
const planCursor = state2.planCursor;
|
|
4444
4835
|
const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
|
|
4445
|
-
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals",
|
|
4446
|
-
if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !
|
|
4836
|
+
const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
|
|
4837
|
+
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) {
|
|
4447
4838
|
throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
|
|
4448
4839
|
}
|
|
4449
4840
|
const effects = state2.completedEffects.map((item) => {
|
|
4450
4841
|
const effect = object(item, "completed effect");
|
|
4451
4842
|
exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
|
|
4452
|
-
if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !
|
|
4843
|
+
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)) {
|
|
4453
4844
|
throw invalid2("Portable checkpoint effect receipt is malformed.");
|
|
4454
4845
|
}
|
|
4455
4846
|
return {
|
|
@@ -4558,7 +4949,7 @@ import { randomUUID } from "crypto";
|
|
|
4558
4949
|
import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
|
|
4559
4950
|
import { createReadStream } from "fs";
|
|
4560
4951
|
import { lstat as lstat22 } from "fs/promises";
|
|
4561
|
-
import { join as
|
|
4952
|
+
import { join as join10 } from "path";
|
|
4562
4953
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4563
4954
|
import { tmpdir as tmpdir3 } from "os";
|
|
4564
4955
|
import { dirname as dirname6, join as join23, resolve as resolve32, sep as sep23 } from "path";
|
|
@@ -4680,8 +5071,8 @@ function boundedInteger(value2, spec) {
|
|
|
4680
5071
|
}
|
|
4681
5072
|
function boundedNumber(value2, spec) {
|
|
4682
5073
|
if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
|
|
4683
|
-
const
|
|
4684
|
-
if (/e/i.test(
|
|
5074
|
+
const text2 = String(value2);
|
|
5075
|
+
if (/e/i.test(text2) || (text2.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
4685
5076
|
return value2;
|
|
4686
5077
|
}
|
|
4687
5078
|
function enumMember(value2, spec) {
|
|
@@ -4837,8 +5228,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
4837
5228
|
return void 0;
|
|
4838
5229
|
}
|
|
4839
5230
|
function looksLikeDestination(value2) {
|
|
4840
|
-
const
|
|
4841
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
5231
|
+
const text2 = value2.trim();
|
|
5232
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
4842
5233
|
}
|
|
4843
5234
|
|
|
4844
5235
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -4979,7 +5370,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
4979
5370
|
}
|
|
4980
5371
|
const value2 = await response2.json().catch(() => null);
|
|
4981
5372
|
if (!response2.ok) {
|
|
4982
|
-
const problem =
|
|
5373
|
+
const problem = record5(record5(value2)?.error);
|
|
4983
5374
|
throw new CodeRuntimeControlError(
|
|
4984
5375
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
4985
5376
|
response2.status,
|
|
@@ -5001,12 +5392,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
5001
5392
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
5002
5393
|
),
|
|
5003
5394
|
infer: async (sessionId, inference) => {
|
|
5004
|
-
const value2 =
|
|
5395
|
+
const value2 = record5(await call2(
|
|
5005
5396
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
5006
5397
|
inference,
|
|
5007
5398
|
modelRequestTimeoutMs
|
|
5008
5399
|
));
|
|
5009
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
5400
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
5010
5401
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
5011
5402
|
}
|
|
5012
5403
|
return value2;
|
|
@@ -5064,12 +5455,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
5064
5455
|
}
|
|
5065
5456
|
}
|
|
5066
5457
|
function parseSnapshot(value2) {
|
|
5067
|
-
const root =
|
|
5068
|
-
const host =
|
|
5458
|
+
const root = record5(value2);
|
|
5459
|
+
const host = record5(root?.host);
|
|
5069
5460
|
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");
|
|
5070
5461
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
5071
5462
|
const bindings = root.bindings.map((item) => {
|
|
5072
|
-
const binding =
|
|
5463
|
+
const binding = record5(item);
|
|
5073
5464
|
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)) {
|
|
5074
5465
|
throw invalid("binding");
|
|
5075
5466
|
}
|
|
@@ -5079,10 +5470,10 @@ function parseSnapshot(value2) {
|
|
|
5079
5470
|
const commandIds = /* @__PURE__ */ new Set();
|
|
5080
5471
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
5081
5472
|
const commands = root.commands.map((item) => {
|
|
5082
|
-
const command =
|
|
5473
|
+
const command = record5(item);
|
|
5083
5474
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
5084
5475
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
5085
|
-
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)) || !
|
|
5476
|
+
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");
|
|
5086
5477
|
commandIds.add(command.commandId);
|
|
5087
5478
|
commandSequences.add(sequenceKey);
|
|
5088
5479
|
return command;
|
|
@@ -5090,10 +5481,10 @@ function parseSnapshot(value2) {
|
|
|
5090
5481
|
return { host, bindings, commands };
|
|
5091
5482
|
}
|
|
5092
5483
|
async function parseSource(value2) {
|
|
5093
|
-
const snapshot =
|
|
5484
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
5094
5485
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
5095
5486
|
const files = snapshot.files.map((value22) => {
|
|
5096
|
-
const file =
|
|
5487
|
+
const file = record5(value22);
|
|
5097
5488
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
5098
5489
|
return { path: file.path, content: file.content };
|
|
5099
5490
|
});
|
|
@@ -5102,11 +5493,11 @@ async function parseSource(value2) {
|
|
|
5102
5493
|
const aliases = /* @__PURE__ */ new Set();
|
|
5103
5494
|
const references = [];
|
|
5104
5495
|
for (const item of referencesValue) {
|
|
5105
|
-
const reference =
|
|
5496
|
+
const reference = record5(item);
|
|
5106
5497
|
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");
|
|
5107
5498
|
aliases.add(reference.alias);
|
|
5108
5499
|
const referenceFiles = reference.files.map((entry) => {
|
|
5109
|
-
const file =
|
|
5500
|
+
const file = record5(entry);
|
|
5110
5501
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
5111
5502
|
return { path: file.path, content: file.content };
|
|
5112
5503
|
});
|
|
@@ -5121,18 +5512,18 @@ async function parseSource(value2) {
|
|
|
5121
5512
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
5122
5513
|
}
|
|
5123
5514
|
function parseReview(value2) {
|
|
5124
|
-
const review =
|
|
5515
|
+
const review = record5(record5(value2)?.review);
|
|
5125
5516
|
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");
|
|
5126
5517
|
return review;
|
|
5127
5518
|
}
|
|
5128
5519
|
function parseCandidate(value2) {
|
|
5129
|
-
const candidate =
|
|
5520
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
5130
5521
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
5131
5522
|
throw invalid("candidate");
|
|
5132
5523
|
}
|
|
5133
5524
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
5134
5525
|
}
|
|
5135
|
-
var
|
|
5526
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
5136
5527
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
5137
5528
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
5138
5529
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -5207,8 +5598,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
5207
5598
|
});
|
|
5208
5599
|
let stderr = "";
|
|
5209
5600
|
child.stderr.setEncoding("utf8");
|
|
5210
|
-
child.stderr.on("data", (
|
|
5211
|
-
if (stderr.length < 4e3) stderr +=
|
|
5601
|
+
child.stderr.on("data", (text2) => {
|
|
5602
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
5212
5603
|
});
|
|
5213
5604
|
child.once("error", reject);
|
|
5214
5605
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -5477,7 +5868,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
5477
5868
|
const receipts = [];
|
|
5478
5869
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
5479
5870
|
try {
|
|
5480
|
-
const path =
|
|
5871
|
+
const path = join10(workspaceDir, artifact.path);
|
|
5481
5872
|
const info = await lstat22(path);
|
|
5482
5873
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
5483
5874
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
@@ -6379,10 +6770,10 @@ var CodePiRuntimeEngine = class {
|
|
|
6379
6770
|
task: lease.task,
|
|
6380
6771
|
limits: this.options.limits,
|
|
6381
6772
|
signal: active.abort.signal,
|
|
6382
|
-
onStderr: (
|
|
6773
|
+
onStderr: (text2) => this.#event(command, {
|
|
6383
6774
|
type: "message",
|
|
6384
6775
|
actor: "system",
|
|
6385
|
-
body:
|
|
6776
|
+
body: text2.slice(0, 4e3)
|
|
6386
6777
|
}, active.conversationRefs),
|
|
6387
6778
|
onMessage: async (output) => {
|
|
6388
6779
|
if (output.type === "inference.request") {
|
|
@@ -6495,13 +6886,6 @@ var CodePiRuntimeEngine = class {
|
|
|
6495
6886
|
}
|
|
6496
6887
|
};
|
|
6497
6888
|
|
|
6498
|
-
// src/version.ts
|
|
6499
|
-
import { readFileSync as readFileSync6 } from "fs";
|
|
6500
|
-
function cliVersion() {
|
|
6501
|
-
const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
6502
|
-
return pkg.version ?? "unknown";
|
|
6503
|
-
}
|
|
6504
|
-
|
|
6505
6889
|
// src/code-local-source.ts
|
|
6506
6890
|
import { execFile as execFile3 } from "child_process";
|
|
6507
6891
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -6720,25 +7104,25 @@ async function runCodeRuntime(input) {
|
|
|
6720
7104
|
}
|
|
6721
7105
|
}
|
|
6722
7106
|
function parseConnection(value2, appId, appEnv) {
|
|
6723
|
-
const root =
|
|
6724
|
-
const host =
|
|
6725
|
-
const offer =
|
|
6726
|
-
const binding =
|
|
7107
|
+
const root = record6(value2);
|
|
7108
|
+
const host = record6(root?.host);
|
|
7109
|
+
const offer = record6(root?.offer);
|
|
7110
|
+
const binding = record6(root?.binding);
|
|
6727
7111
|
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)) {
|
|
6728
7112
|
throw new Error("connect Code host returned an invalid response");
|
|
6729
7113
|
}
|
|
6730
7114
|
return root;
|
|
6731
7115
|
}
|
|
6732
7116
|
function apiFailure(action2, status, value2) {
|
|
6733
|
-
const message2 =
|
|
7117
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
6734
7118
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
6735
7119
|
}
|
|
6736
|
-
function
|
|
7120
|
+
function record6(value2) {
|
|
6737
7121
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6738
7122
|
}
|
|
6739
7123
|
|
|
6740
7124
|
// src/provision.ts
|
|
6741
|
-
import { createAppsClient as
|
|
7125
|
+
import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
|
|
6742
7126
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
6743
7127
|
import process8 from "process";
|
|
6744
7128
|
|
|
@@ -6959,7 +7343,7 @@ async function provision(options) {
|
|
|
6959
7343
|
}
|
|
6960
7344
|
const doFetch = options.fetch ?? fetch;
|
|
6961
7345
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
6962
|
-
const apps =
|
|
7346
|
+
const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
6963
7347
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
6964
7348
|
if (existing) {
|
|
6965
7349
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -7133,7 +7517,7 @@ var COMMAND_SURFACE = {
|
|
|
7133
7517
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
7134
7518
|
capabilities: {},
|
|
7135
7519
|
code: { connect: {} },
|
|
7136
|
-
config: { diff: {}, plan: {} },
|
|
7520
|
+
config: { diff: {}, plan: {}, apply: {} },
|
|
7137
7521
|
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
7138
7522
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
7139
7523
|
discuss: {
|
|
@@ -7151,7 +7535,11 @@ var COMMAND_SURFACE = {
|
|
|
7151
7535
|
help: {},
|
|
7152
7536
|
init: {},
|
|
7153
7537
|
o11y: { status: {} },
|
|
7154
|
-
|
|
7538
|
+
operations: { get: {}, wait: {} },
|
|
7539
|
+
platform: {
|
|
7540
|
+
status: {},
|
|
7541
|
+
"chat-credentials": { rotate: {} }
|
|
7542
|
+
},
|
|
7155
7543
|
pm: {
|
|
7156
7544
|
...PM_ENTITIES,
|
|
7157
7545
|
handoff: {}
|
|
@@ -7588,17 +7976,17 @@ function addOption(options, name, value2) {
|
|
|
7588
7976
|
|
|
7589
7977
|
// src/operator-context.ts
|
|
7590
7978
|
import { existsSync as existsSync10 } from "fs";
|
|
7591
|
-
import { join as
|
|
7979
|
+
import { join as join12, resolve as resolve11 } from "path";
|
|
7592
7980
|
import process10 from "process";
|
|
7593
7981
|
|
|
7594
7982
|
// src/operator-profiles.ts
|
|
7595
|
-
import { existsSync as existsSync9, readFileSync as
|
|
7983
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
7596
7984
|
import { homedir as homedir2 } from "os";
|
|
7597
|
-
import { dirname as dirname7, join as
|
|
7985
|
+
import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
|
|
7598
7986
|
import process9 from "process";
|
|
7599
7987
|
function operatorProfileFile() {
|
|
7600
7988
|
return resolve10(
|
|
7601
|
-
clean(process9.env.ODLA_CONTEXT_FILE) ??
|
|
7989
|
+
clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
|
|
7602
7990
|
);
|
|
7603
7991
|
}
|
|
7604
7992
|
function resolveOperatorProfile(parsed) {
|
|
@@ -7643,10 +8031,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
|
7643
8031
|
return true;
|
|
7644
8032
|
}
|
|
7645
8033
|
function operatorCredentialFiles(selection) {
|
|
7646
|
-
const base = selection.name ?
|
|
8034
|
+
const base = selection.name ? join11(dirname7(selection.file), "profiles", selection.name) : join11(homedir2(), ".odla");
|
|
7647
8035
|
return {
|
|
7648
|
-
developer:
|
|
7649
|
-
scoped:
|
|
8036
|
+
developer: join11(base, "dev-token.json"),
|
|
8037
|
+
scoped: join11(base, "admin-token.local.json")
|
|
7650
8038
|
};
|
|
7651
8039
|
}
|
|
7652
8040
|
function assertOperatorName(value2, label) {
|
|
@@ -7660,7 +8048,7 @@ function readOperatorProfiles(file) {
|
|
|
7660
8048
|
if (!existsSync9(file)) return emptyProfiles();
|
|
7661
8049
|
let raw;
|
|
7662
8050
|
try {
|
|
7663
|
-
raw = JSON.parse(
|
|
8051
|
+
raw = JSON.parse(readFileSync8(file, "utf8"));
|
|
7664
8052
|
} catch {
|
|
7665
8053
|
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7666
8054
|
}
|
|
@@ -7757,7 +8145,7 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
7757
8145
|
const rootDir = loaded?.rootDir ?? process10.cwd();
|
|
7758
8146
|
const profileCredentials = operatorCredentialFiles(profile);
|
|
7759
8147
|
const tokenFile = clean2(process10.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process10.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
7760
|
-
const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ?
|
|
8148
|
+
const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join12(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
7761
8149
|
const cfg = loaded ? {
|
|
7762
8150
|
...loaded,
|
|
7763
8151
|
platformUrl: platformValue,
|
|
@@ -7779,8 +8167,8 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
7779
8167
|
services: [],
|
|
7780
8168
|
local: {
|
|
7781
8169
|
tokenFile,
|
|
7782
|
-
credentialsFile:
|
|
7783
|
-
devVarsFile:
|
|
8170
|
+
credentialsFile: join12(rootDir, ".odla", "credentials.local.json"),
|
|
8171
|
+
devVarsFile: join12(rootDir, ".dev.vars"),
|
|
7784
8172
|
gitignore: true
|
|
7785
8173
|
}
|
|
7786
8174
|
};
|
|
@@ -8024,7 +8412,7 @@ async function appExport(options) {
|
|
|
8024
8412
|
}
|
|
8025
8413
|
|
|
8026
8414
|
// src/app-import.ts
|
|
8027
|
-
import { readFileSync as
|
|
8415
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
8028
8416
|
import {
|
|
8029
8417
|
buildImportOps,
|
|
8030
8418
|
importMutationId,
|
|
@@ -8048,8 +8436,8 @@ async function appImport(options) {
|
|
|
8048
8436
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
8049
8437
|
const doFetch = options.fetch ?? fetch;
|
|
8050
8438
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
8051
|
-
const
|
|
8052
|
-
const { format, sources } = parseImport(
|
|
8439
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => readFileSync9(0, "utf8")))() : readFileSync9(options.file, "utf8");
|
|
8440
|
+
const { format, sources } = parseImport(text2, options.ns);
|
|
8053
8441
|
if (format === "namespace-map" && options.ns) {
|
|
8054
8442
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
8055
8443
|
}
|
|
@@ -8140,7 +8528,10 @@ function report(options, owners, headline) {
|
|
|
8140
8528
|
if (headline) out.log(headline);
|
|
8141
8529
|
out.log(`owners (${owners.length}):`);
|
|
8142
8530
|
for (const o of owners) {
|
|
8143
|
-
|
|
8531
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
8532
|
+
out.log(
|
|
8533
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
8534
|
+
);
|
|
8144
8535
|
}
|
|
8145
8536
|
}
|
|
8146
8537
|
async function ownersList(options) {
|
|
@@ -8559,10 +8950,14 @@ async function secretsCommand(parsed, deps) {
|
|
|
8559
8950
|
async function projectCommand(command, parsed, deps) {
|
|
8560
8951
|
if (command === "config") {
|
|
8561
8952
|
const sub = parsed.positionals[1];
|
|
8562
|
-
if (sub !== "diff" && sub !== "plan") {
|
|
8953
|
+
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
8563
8954
|
throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
|
|
8564
8955
|
}
|
|
8565
|
-
assertArgs(
|
|
8956
|
+
assertArgs(
|
|
8957
|
+
parsed,
|
|
8958
|
+
sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
|
|
8959
|
+
2
|
|
8960
|
+
);
|
|
8566
8961
|
const options = {
|
|
8567
8962
|
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
8568
8963
|
token: stringOpt(parsed.options.token),
|
|
@@ -8574,7 +8969,42 @@ async function projectCommand(command, parsed, deps) {
|
|
|
8574
8969
|
stdout: deps.stdout
|
|
8575
8970
|
};
|
|
8576
8971
|
if (sub === "diff") await configDiff(options);
|
|
8577
|
-
else await configPlan(options);
|
|
8972
|
+
else if (sub === "plan") await configPlan(options);
|
|
8973
|
+
else await configApply({
|
|
8974
|
+
...options,
|
|
8975
|
+
planPath: requiredString(parsed.options.plan, "--plan"),
|
|
8976
|
+
idempotencyKey: stringOpt(parsed.options["idempotency-key"])
|
|
8977
|
+
});
|
|
8978
|
+
return true;
|
|
8979
|
+
}
|
|
8980
|
+
if (command === "operations") {
|
|
8981
|
+
const sub = parsed.positionals[1];
|
|
8982
|
+
if (sub !== "get" && sub !== "wait") {
|
|
8983
|
+
throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
|
|
8984
|
+
}
|
|
8985
|
+
assertArgs(
|
|
8986
|
+
parsed,
|
|
8987
|
+
sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
|
|
8988
|
+
3
|
|
8989
|
+
);
|
|
8990
|
+
const operationId = parsed.positionals[2];
|
|
8991
|
+
if (!operationId) throw new Error(`operation id is required`);
|
|
8992
|
+
const options = {
|
|
8993
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
8994
|
+
operationId,
|
|
8995
|
+
token: stringOpt(parsed.options.token),
|
|
8996
|
+
email: stringOpt(parsed.options.email),
|
|
8997
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
8998
|
+
json: parsed.options.json === true,
|
|
8999
|
+
fetch: deps.fetch,
|
|
9000
|
+
openApprovalUrl: deps.openUrl,
|
|
9001
|
+
stdout: deps.stdout,
|
|
9002
|
+
pollWait: deps.pollWait,
|
|
9003
|
+
intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
|
|
9004
|
+
timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
|
|
9005
|
+
};
|
|
9006
|
+
if (sub === "get") await configOperationGet(options);
|
|
9007
|
+
else await configOperationWait(options);
|
|
8578
9008
|
return true;
|
|
8579
9009
|
}
|
|
8580
9010
|
if (command === "init") {
|
|
@@ -8845,8 +9275,10 @@ Usage:
|
|
|
8845
9275
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8846
9276
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
|
|
8847
9277
|
odla-ai doctor [--config odla.config.mjs]
|
|
8848
|
-
odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8849
|
-
odla-ai config plan [--
|
|
9278
|
+
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
9279
|
+
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
9280
|
+
odla-ai operations get <operation-id> [--json]
|
|
9281
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8850
9282
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8851
9283
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8852
9284
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8897,6 +9329,7 @@ Usage:
|
|
|
8897
9329
|
odla-ai context remove <name> --yes [--json]
|
|
8898
9330
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8899
9331
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
9332
|
+
odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
|
|
8900
9333
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8901
9334
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8902
9335
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -8960,9 +9393,9 @@ Commands:
|
|
|
8960
9393
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8961
9394
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8962
9395
|
a question usually needs both.
|
|
8963
|
-
whoami Report
|
|
8964
|
-
|
|
8965
|
-
|
|
9396
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
9397
|
+
credential kind, accountable owner, and platform-admin status.
|
|
9398
|
+
A manager relationship never supplies authority by itself.
|
|
8966
9399
|
context Explain selected config, platform, app, environment, and
|
|
8967
9400
|
developer-token provenance without printing credentials or
|
|
8968
9401
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8971,8 +9404,8 @@ Commands:
|
|
|
8971
9404
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
8972
9405
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
8973
9406
|
doctor Validate and summarize the project config without network calls.
|
|
8974
|
-
config
|
|
8975
|
-
|
|
9407
|
+
config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
|
|
9408
|
+
operations Inspect or wait on one exact, durable config-operation receipt.
|
|
8976
9409
|
calendar Inspect, connect, or disconnect the live Google booking connection.
|
|
8977
9410
|
app Archive (suspend, data retained), restore, export, import, or
|
|
8978
9411
|
manage the co-owners of the app. Archiving takes every
|
|
@@ -9082,6 +9515,58 @@ Safety:
|
|
|
9082
9515
|
`);
|
|
9083
9516
|
}
|
|
9084
9517
|
|
|
9518
|
+
// src/discuss-principals.ts
|
|
9519
|
+
function mergeDiscussPrincipals(target, source) {
|
|
9520
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
9521
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
9522
|
+
}
|
|
9523
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
9524
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
9525
|
+
const profile = projection.principals?.[principalId];
|
|
9526
|
+
if (profile) {
|
|
9527
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
9528
|
+
if (profile.kind === "agent") {
|
|
9529
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
9530
|
+
}
|
|
9531
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
9532
|
+
}
|
|
9533
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
9534
|
+
if (legacyName) {
|
|
9535
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
9536
|
+
}
|
|
9537
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
9538
|
+
}
|
|
9539
|
+
|
|
9540
|
+
// src/discuss-read-render.ts
|
|
9541
|
+
function discussBodyWithRefs(post) {
|
|
9542
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
9543
|
+
let out = "";
|
|
9544
|
+
let cursor = 0;
|
|
9545
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
9546
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
9547
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
9548
|
+
cursor = ref.end;
|
|
9549
|
+
}
|
|
9550
|
+
return out + post.body.slice(cursor);
|
|
9551
|
+
}
|
|
9552
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
9553
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
9554
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
9555
|
+
for (const post of posts) {
|
|
9556
|
+
const who = discussPrincipalLabel(
|
|
9557
|
+
post.authorId,
|
|
9558
|
+
post.authorKind,
|
|
9559
|
+
projection
|
|
9560
|
+
);
|
|
9561
|
+
ctx.out.log(`
|
|
9562
|
+
\u2014 ${who}`);
|
|
9563
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
9564
|
+
for (const file of post.attachments ?? []) {
|
|
9565
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9085
9570
|
// src/discuss-actions.ts
|
|
9086
9571
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
9087
9572
|
async function request(ctx, method, path, body) {
|
|
@@ -9100,17 +9585,6 @@ function emit(ctx, value2, human) {
|
|
|
9100
9585
|
else human();
|
|
9101
9586
|
}
|
|
9102
9587
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
9103
|
-
function bodyWithRefs(post) {
|
|
9104
|
-
if (!post.refs || post.refs.length === 0) return post.body;
|
|
9105
|
-
let out = "";
|
|
9106
|
-
let cursor = 0;
|
|
9107
|
-
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
9108
|
-
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
9109
|
-
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
9110
|
-
cursor = ref.end;
|
|
9111
|
-
}
|
|
9112
|
-
return out + post.body.slice(cursor);
|
|
9113
|
-
}
|
|
9114
9588
|
function content(parsed) {
|
|
9115
9589
|
const markup = stringOpt(parsed.options.markup);
|
|
9116
9590
|
if (markup) return { markup };
|
|
@@ -9165,11 +9639,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
9165
9639
|
offset: requestedOffset ?? "0"
|
|
9166
9640
|
});
|
|
9167
9641
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
9168
|
-
emit(
|
|
9642
|
+
emit(
|
|
9643
|
+
ctx,
|
|
9644
|
+
page,
|
|
9645
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
9646
|
+
);
|
|
9169
9647
|
return;
|
|
9170
9648
|
}
|
|
9171
9649
|
for (let scan = 0; scan < 3; scan++) {
|
|
9172
9650
|
const posts = /* @__PURE__ */ new Map();
|
|
9651
|
+
const projection = {
|
|
9652
|
+
authors: {},
|
|
9653
|
+
principals: {}
|
|
9654
|
+
};
|
|
9173
9655
|
let topic = null;
|
|
9174
9656
|
let offset = 0;
|
|
9175
9657
|
for (; ; ) {
|
|
@@ -9180,6 +9662,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
9180
9662
|
);
|
|
9181
9663
|
topic = page.topic;
|
|
9182
9664
|
for (const post of page.posts) posts.set(post.id, post);
|
|
9665
|
+
mergeDiscussPrincipals(projection, page);
|
|
9183
9666
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
9184
9667
|
if (!page.page?.hasMore) break;
|
|
9185
9668
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -9192,24 +9675,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
9192
9675
|
);
|
|
9193
9676
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
9194
9677
|
if (ordered.length === expected) {
|
|
9195
|
-
const result = { topic, posts: ordered };
|
|
9196
|
-
emit(
|
|
9678
|
+
const result = { topic, posts: ordered, ...projection };
|
|
9679
|
+
emit(
|
|
9680
|
+
ctx,
|
|
9681
|
+
result,
|
|
9682
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
9683
|
+
);
|
|
9197
9684
|
return;
|
|
9198
9685
|
}
|
|
9199
9686
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
9200
9687
|
}
|
|
9201
9688
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
9202
9689
|
}
|
|
9203
|
-
function renderRead(ctx, topic, posts) {
|
|
9204
|
-
ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
|
|
9205
|
-
for (const post of posts) {
|
|
9206
|
-
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
9207
|
-
ctx.out.log(`
|
|
9208
|
-
\u2014 ${who}`);
|
|
9209
|
-
ctx.out.log(bodyWithRefs(post));
|
|
9210
|
-
for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
9211
|
-
}
|
|
9212
|
-
}
|
|
9213
9690
|
async function discussPost(ctx, parsed) {
|
|
9214
9691
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
9215
9692
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -9434,6 +9911,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9434
9911
|
found: true,
|
|
9435
9912
|
cursor,
|
|
9436
9913
|
events: matching,
|
|
9914
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9915
|
+
...page.principals ? { principals: page.principals } : {},
|
|
9437
9916
|
...posts && posts.length > 0 ? { posts } : {},
|
|
9438
9917
|
...topics && topics.length > 0 ? { topics } : {}
|
|
9439
9918
|
});
|
|
@@ -9466,7 +9945,11 @@ function report2(ctx, parsed, result) {
|
|
|
9466
9945
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9467
9946
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
9468
9947
|
for (const post of result.posts ?? []) {
|
|
9469
|
-
const who =
|
|
9948
|
+
const who = discussPrincipalLabel(
|
|
9949
|
+
post.authorId,
|
|
9950
|
+
post.authorKind,
|
|
9951
|
+
result
|
|
9952
|
+
);
|
|
9470
9953
|
ctx.out.log(`\u2014 ${who}
|
|
9471
9954
|
${post.body}`);
|
|
9472
9955
|
}
|
|
@@ -9611,8 +10094,8 @@ function collectFields(parsed, allowClear) {
|
|
|
9611
10094
|
if (allowClear) out[spec.key] = null;
|
|
9612
10095
|
continue;
|
|
9613
10096
|
}
|
|
9614
|
-
const
|
|
9615
|
-
out[spec.key] = spec.num ? Number(
|
|
10097
|
+
const text2 = stringOpt(value2);
|
|
10098
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
9616
10099
|
}
|
|
9617
10100
|
return out;
|
|
9618
10101
|
}
|
|
@@ -9679,8 +10162,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9679
10162
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
9680
10163
|
}
|
|
9681
10164
|
async function pmGet(ctx, entity, id) {
|
|
9682
|
-
const { record:
|
|
9683
|
-
emit2(ctx,
|
|
10165
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
10166
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
9684
10167
|
}
|
|
9685
10168
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9686
10169
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9725,9 +10208,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9725
10208
|
]);
|
|
9726
10209
|
const handoff = {
|
|
9727
10210
|
appId,
|
|
9728
|
-
unmetGoals: goals.filter((
|
|
9729
|
-
activeTasks: tasks.filter((
|
|
9730
|
-
openBugs: bugs.filter((
|
|
10211
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
10212
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
10213
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
9731
10214
|
};
|
|
9732
10215
|
const result = {
|
|
9733
10216
|
...handoff,
|
|
@@ -9746,10 +10229,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9746
10229
|
]) {
|
|
9747
10230
|
ctx.out.log(`${label}:`);
|
|
9748
10231
|
if (!records.length) ctx.out.log("- (none)");
|
|
9749
|
-
else for (const
|
|
10232
|
+
else for (const record11 of records) printRecord(
|
|
9750
10233
|
ctx,
|
|
9751
10234
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9752
|
-
|
|
10235
|
+
record11
|
|
9753
10236
|
);
|
|
9754
10237
|
}
|
|
9755
10238
|
});
|
|
@@ -9940,19 +10423,180 @@ function age(input) {
|
|
|
9940
10423
|
return `${Math.round(input / 6e4)}m`;
|
|
9941
10424
|
}
|
|
9942
10425
|
|
|
10426
|
+
// src/platform-chat-credential-command.ts
|
|
10427
|
+
import process12 from "process";
|
|
10428
|
+
async function rotatePlatformChatCredential(parsed, deps) {
|
|
10429
|
+
assertArgs(
|
|
10430
|
+
parsed,
|
|
10431
|
+
[
|
|
10432
|
+
"config",
|
|
10433
|
+
"context",
|
|
10434
|
+
"platform",
|
|
10435
|
+
"token",
|
|
10436
|
+
"email",
|
|
10437
|
+
"open",
|
|
10438
|
+
"json",
|
|
10439
|
+
"yes",
|
|
10440
|
+
"wrangler-config",
|
|
10441
|
+
"expected-version"
|
|
10442
|
+
],
|
|
10443
|
+
3
|
|
10444
|
+
);
|
|
10445
|
+
if (parsed.options.yes !== true) {
|
|
10446
|
+
throw new Error(
|
|
10447
|
+
"platform chat-credentials rotate changes the production chat secret; pass --yes"
|
|
10448
|
+
);
|
|
10449
|
+
}
|
|
10450
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10451
|
+
allowMissingConfig: true
|
|
10452
|
+
});
|
|
10453
|
+
const platform = context.platform.value;
|
|
10454
|
+
const doFetch = deps.fetch ?? fetch;
|
|
10455
|
+
const out = deps.stdout ?? console;
|
|
10456
|
+
const run = deps.runner ?? defaultRunner;
|
|
10457
|
+
const cwd = process12.cwd();
|
|
10458
|
+
const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
|
|
10459
|
+
if (!await wranglerLoggedIn(run, cwd)) {
|
|
10460
|
+
throw new Error(
|
|
10461
|
+
'Wrangler is not authenticated; run "npx wrangler login" and retry'
|
|
10462
|
+
);
|
|
10463
|
+
}
|
|
10464
|
+
const priorHealth = await doFetch(`${platform}/health/services/chat`);
|
|
10465
|
+
const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
|
|
10466
|
+
await priorHealth.body?.cancel().catch(() => {
|
|
10467
|
+
});
|
|
10468
|
+
if (!priorVersion) {
|
|
10469
|
+
throw new Error(
|
|
10470
|
+
"chat health did not identify its current Worker version; refusing to rotate"
|
|
10471
|
+
);
|
|
10472
|
+
}
|
|
10473
|
+
const expectedVersion = stringOpt(parsed.options["expected-version"]);
|
|
10474
|
+
if (expectedVersion && priorVersion !== expectedVersion) {
|
|
10475
|
+
throw new Error(
|
|
10476
|
+
`chat health answered from version ${priorVersion}; expected ${expectedVersion}`
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
const token = await resolveAdminPlatformToken({
|
|
10480
|
+
platform,
|
|
10481
|
+
scope: "platform:chat:credential:write",
|
|
10482
|
+
token: stringOpt(parsed.options.token),
|
|
10483
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
10484
|
+
email: stringOpt(parsed.options.email),
|
|
10485
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
10486
|
+
fetch: doFetch,
|
|
10487
|
+
stdout: out,
|
|
10488
|
+
openApprovalUrl: deps.openUrl,
|
|
10489
|
+
label: "odla CLI (rotate built-in Discussion responder)"
|
|
10490
|
+
});
|
|
10491
|
+
const mintedResponse = await doFetch(
|
|
10492
|
+
`${platform}/registry/platform/chat-credentials/rotate`,
|
|
10493
|
+
{
|
|
10494
|
+
method: "POST",
|
|
10495
|
+
headers: { authorization: `Bearer ${token}` }
|
|
10496
|
+
}
|
|
10497
|
+
);
|
|
10498
|
+
const minted = await mintedResponse.json().catch(() => null);
|
|
10499
|
+
if (!mintedResponse.ok) {
|
|
10500
|
+
throw new Error(
|
|
10501
|
+
`mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
|
|
10502
|
+
);
|
|
10503
|
+
}
|
|
10504
|
+
if (!isPlatformChatCredential(minted)) {
|
|
10505
|
+
throw new Error(
|
|
10506
|
+
"platform returned an invalid odla.platform-chat-credential/v1 envelope"
|
|
10507
|
+
);
|
|
10508
|
+
}
|
|
10509
|
+
const put = await wranglerPutSecret(run, {
|
|
10510
|
+
name: "ODLA_BOT_TOKENS",
|
|
10511
|
+
value: JSON.stringify({
|
|
10512
|
+
[minted.appId]: { [minted.principalId]: minted.key }
|
|
10513
|
+
}),
|
|
10514
|
+
configPath: wranglerConfig,
|
|
10515
|
+
cwd
|
|
10516
|
+
});
|
|
10517
|
+
if (put.code !== 0) {
|
|
10518
|
+
throw new Error(
|
|
10519
|
+
"Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
|
|
10520
|
+
);
|
|
10521
|
+
}
|
|
10522
|
+
const version = await waitForRotatedChatHealth({
|
|
10523
|
+
fetch: doFetch,
|
|
10524
|
+
platform,
|
|
10525
|
+
priorVersion,
|
|
10526
|
+
wait: deps.pollWait
|
|
10527
|
+
});
|
|
10528
|
+
const result = {
|
|
10529
|
+
schemaVersion: "odla.platform-chat-credential-rotation/v1",
|
|
10530
|
+
appId: minted.appId,
|
|
10531
|
+
appIncarnation: minted.appIncarnation,
|
|
10532
|
+
principalId: minted.principalId,
|
|
10533
|
+
health: { ok: true, service: "odla-chat-agent", version }
|
|
10534
|
+
};
|
|
10535
|
+
if (parsed.options.json === true) {
|
|
10536
|
+
out.log(JSON.stringify(result, null, 2));
|
|
10537
|
+
} else {
|
|
10538
|
+
out.log(
|
|
10539
|
+
`rotated ${result.appId} ${result.principalId} ${result.health.version}`
|
|
10540
|
+
);
|
|
10541
|
+
}
|
|
10542
|
+
}
|
|
10543
|
+
async function waitForRotatedChatHealth(opts) {
|
|
10544
|
+
const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
|
|
10545
|
+
let lastStatus = 0;
|
|
10546
|
+
let lastVersion = null;
|
|
10547
|
+
let lastError = "private_service_unready";
|
|
10548
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
10549
|
+
const response2 = await opts.fetch(
|
|
10550
|
+
`${opts.platform}/health/services/chat`
|
|
10551
|
+
);
|
|
10552
|
+
const body = await response2.json().catch(() => null);
|
|
10553
|
+
const version = response2.headers.get("x-odla-worker-version-id");
|
|
10554
|
+
if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
|
|
10555
|
+
return version;
|
|
10556
|
+
}
|
|
10557
|
+
lastStatus = response2.status;
|
|
10558
|
+
lastVersion = version;
|
|
10559
|
+
lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
|
|
10560
|
+
if (attempt < 59) await wait2(5e3);
|
|
10561
|
+
}
|
|
10562
|
+
throw new Error(
|
|
10563
|
+
`chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
|
|
10564
|
+
);
|
|
10565
|
+
}
|
|
10566
|
+
function isPlatformChatCredential(value2) {
|
|
10567
|
+
return record7(value2) && value2.schemaVersion === "odla.platform-chat-credential/v1" && value2.appId === "odla-pm" && typeof value2.appIncarnation === "string" && value2.appIncarnation.length > 0 && value2.principalId === "agent_odla" && typeof value2.key === "string" && value2.key.startsWith("odla_sk_");
|
|
10568
|
+
}
|
|
10569
|
+
function apiMessage(value2) {
|
|
10570
|
+
if (!record7(value2)) return "request failed";
|
|
10571
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
10572
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
10573
|
+
}
|
|
10574
|
+
function record7(value2) {
|
|
10575
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
10576
|
+
}
|
|
10577
|
+
|
|
9943
10578
|
// src/platform-command.ts
|
|
9944
10579
|
async function platformCommand(parsed, deps = {}) {
|
|
10580
|
+
const action2 = parsed.positionals[1];
|
|
10581
|
+
if (action2 === "status") {
|
|
10582
|
+
return platformStatus(parsed, deps);
|
|
10583
|
+
}
|
|
10584
|
+
if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
|
|
10585
|
+
return rotatePlatformChatCredential(parsed, deps);
|
|
10586
|
+
}
|
|
10587
|
+
throw new Error(
|
|
10588
|
+
`unknown platform action "${[
|
|
10589
|
+
action2,
|
|
10590
|
+
parsed.positionals[2]
|
|
10591
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
10592
|
+
);
|
|
10593
|
+
}
|
|
10594
|
+
async function platformStatus(parsed, deps) {
|
|
9945
10595
|
assertArgs(
|
|
9946
10596
|
parsed,
|
|
9947
10597
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9948
10598
|
2
|
|
9949
10599
|
);
|
|
9950
|
-
const action2 = parsed.positionals[1];
|
|
9951
|
-
if (action2 !== "status") {
|
|
9952
|
-
throw new Error(
|
|
9953
|
-
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9954
|
-
);
|
|
9955
|
-
}
|
|
9956
10600
|
const context = await resolveOperatorContext(parsed, {
|
|
9957
10601
|
allowMissingConfig: true
|
|
9958
10602
|
});
|
|
@@ -9977,7 +10621,7 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9977
10621
|
const body = await response2.json().catch(() => null);
|
|
9978
10622
|
if (!response2.ok) {
|
|
9979
10623
|
throw new Error(
|
|
9980
|
-
`read platform status failed (HTTP ${response2.status}): ${
|
|
10624
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
|
|
9981
10625
|
);
|
|
9982
10626
|
}
|
|
9983
10627
|
if (!isPlatformStatus(body)) {
|
|
@@ -9990,17 +10634,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9990
10634
|
}
|
|
9991
10635
|
}
|
|
9992
10636
|
function isPlatformStatus(value2) {
|
|
9993
|
-
if (!
|
|
9994
|
-
if (!
|
|
9995
|
-
if (!
|
|
10637
|
+
if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
10638
|
+
if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
10639
|
+
if (!record8(value2.catalog) || !record8(value2.summary)) return false;
|
|
9996
10640
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9997
10641
|
}
|
|
9998
|
-
function
|
|
9999
|
-
if (!
|
|
10000
|
-
const error =
|
|
10642
|
+
function apiMessage2(value2) {
|
|
10643
|
+
if (!record8(value2)) return "request failed";
|
|
10644
|
+
const error = record8(value2.error) ? value2.error : value2;
|
|
10001
10645
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
10002
10646
|
}
|
|
10003
|
-
function
|
|
10647
|
+
function record8(value2) {
|
|
10004
10648
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
10005
10649
|
}
|
|
10006
10650
|
|
|
@@ -10041,7 +10685,7 @@ function statusVerdict(reads) {
|
|
|
10041
10685
|
severity: "degraded"
|
|
10042
10686
|
});
|
|
10043
10687
|
}
|
|
10044
|
-
const performance =
|
|
10688
|
+
const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
10045
10689
|
if (performance?.status === "unavailable") {
|
|
10046
10690
|
reasons.push({
|
|
10047
10691
|
source: "liveSync",
|
|
@@ -10122,7 +10766,7 @@ function statusVerdict(reads) {
|
|
|
10122
10766
|
reasons
|
|
10123
10767
|
};
|
|
10124
10768
|
}
|
|
10125
|
-
function
|
|
10769
|
+
function record9(value2) {
|
|
10126
10770
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
10127
10771
|
}
|
|
10128
10772
|
function numeric2(value2) {
|
|
@@ -10150,7 +10794,7 @@ function printO11yStatus(status, out) {
|
|
|
10150
10794
|
out.log(
|
|
10151
10795
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
10152
10796
|
);
|
|
10153
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10797
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
|
|
10154
10798
|
const requests = routes.reduce(
|
|
10155
10799
|
(total, row) => total + numeric3(row.requests),
|
|
10156
10800
|
0
|
|
@@ -10162,39 +10806,39 @@ function printO11yStatus(status, out) {
|
|
|
10162
10806
|
out.log(
|
|
10163
10807
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
10164
10808
|
);
|
|
10165
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10809
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
|
|
10166
10810
|
out.log(
|
|
10167
10811
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
10168
10812
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
10169
10813
|
).join(", ") : "none observed"}`
|
|
10170
10814
|
);
|
|
10171
10815
|
out.log(liveSyncLine(status.liveSync));
|
|
10172
|
-
const canaryDurations =
|
|
10816
|
+
const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
10173
10817
|
out.log(
|
|
10174
10818
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
10175
10819
|
);
|
|
10176
|
-
const collectorIngest =
|
|
10177
|
-
const collectorStorage =
|
|
10820
|
+
const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10821
|
+
const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
10178
10822
|
out.log(
|
|
10179
10823
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
10180
10824
|
);
|
|
10181
|
-
const providerMetrics =
|
|
10182
|
-
const providerCapacity =
|
|
10183
|
-
const workerMemory =
|
|
10825
|
+
const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10826
|
+
const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10827
|
+
const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
10184
10828
|
out.log(
|
|
10185
10829
|
`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`
|
|
10186
10830
|
);
|
|
10187
10831
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
10188
10832
|
out.log(line);
|
|
10189
10833
|
}
|
|
10190
|
-
const coverage =
|
|
10191
|
-
const coverageCounts =
|
|
10192
|
-
const coverageBudget =
|
|
10834
|
+
const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10835
|
+
const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10836
|
+
const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
10193
10837
|
out.log(
|
|
10194
10838
|
`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`
|
|
10195
10839
|
);
|
|
10196
10840
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
10197
|
-
const providerFreshness =
|
|
10841
|
+
const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
10198
10842
|
out.log(
|
|
10199
10843
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
10200
10844
|
);
|
|
@@ -10203,17 +10847,17 @@ function printO11yStatus(status, out) {
|
|
|
10203
10847
|
);
|
|
10204
10848
|
}
|
|
10205
10849
|
function providerCapacityLines(read3) {
|
|
10206
|
-
const resources =
|
|
10207
|
-
const durableObjects =
|
|
10208
|
-
const periodic =
|
|
10209
|
-
const storage =
|
|
10210
|
-
const d1 =
|
|
10211
|
-
const d1Activity =
|
|
10212
|
-
const d1Storage =
|
|
10213
|
-
const d1Latency =
|
|
10214
|
-
const r2 =
|
|
10215
|
-
const r2Operations =
|
|
10216
|
-
const r2Storage =
|
|
10850
|
+
const resources = record10(read3.body.resources) ? read3.body.resources : {};
|
|
10851
|
+
const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
|
|
10852
|
+
const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10853
|
+
const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10854
|
+
const d1 = record10(resources.d1) ? resources.d1 : {};
|
|
10855
|
+
const d1Activity = record10(d1.activity) ? d1.activity : {};
|
|
10856
|
+
const d1Storage = record10(d1.storage) ? d1.storage : {};
|
|
10857
|
+
const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
|
|
10858
|
+
const r2 = record10(resources.r2) ? resources.r2 : {};
|
|
10859
|
+
const r2Operations = record10(r2.operations) ? r2.operations : {};
|
|
10860
|
+
const r2Storage = record10(r2.storage) ? r2.storage : {};
|
|
10217
10861
|
const status = String(
|
|
10218
10862
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
10219
10863
|
);
|
|
@@ -10224,11 +10868,11 @@ function providerCapacityLines(read3) {
|
|
|
10224
10868
|
];
|
|
10225
10869
|
}
|
|
10226
10870
|
function liveSyncLine(read3) {
|
|
10227
|
-
const performance =
|
|
10228
|
-
const commitToSend =
|
|
10871
|
+
const performance = record10(read3.body.performance) ? read3.body.performance : {};
|
|
10872
|
+
const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
|
|
10229
10873
|
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`;
|
|
10230
10874
|
}
|
|
10231
|
-
function
|
|
10875
|
+
function record10(value2) {
|
|
10232
10876
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
10233
10877
|
}
|
|
10234
10878
|
function numeric3(value2) {
|
|
@@ -10397,14 +11041,14 @@ function statusMinutes(value2) {
|
|
|
10397
11041
|
}
|
|
10398
11042
|
async function read2(url, headers, doFetch) {
|
|
10399
11043
|
const response2 = await doFetch(url, { headers });
|
|
10400
|
-
const
|
|
11044
|
+
const text2 = await response2.text();
|
|
10401
11045
|
let body = {};
|
|
10402
|
-
if (
|
|
11046
|
+
if (text2) {
|
|
10403
11047
|
try {
|
|
10404
|
-
const value2 = JSON.parse(
|
|
11048
|
+
const value2 = JSON.parse(text2);
|
|
10405
11049
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
10406
11050
|
} catch {
|
|
10407
|
-
body = { message:
|
|
11051
|
+
body = { message: text2.slice(0, 300) };
|
|
10408
11052
|
}
|
|
10409
11053
|
}
|
|
10410
11054
|
return { httpStatus: response2.status, body };
|
|
@@ -10412,9 +11056,9 @@ async function read2(url, headers, doFetch) {
|
|
|
10412
11056
|
|
|
10413
11057
|
// src/record.ts
|
|
10414
11058
|
import { appendFileSync } from "fs";
|
|
10415
|
-
import
|
|
11059
|
+
import process13 from "process";
|
|
10416
11060
|
function recordInvocation(parsed) {
|
|
10417
|
-
const file =
|
|
11061
|
+
const file = process13.env.ODLA_CLI_RECORD;
|
|
10418
11062
|
if (!file) return;
|
|
10419
11063
|
try {
|
|
10420
11064
|
const entry = {
|
|
@@ -10429,7 +11073,7 @@ function recordInvocation(parsed) {
|
|
|
10429
11073
|
}
|
|
10430
11074
|
|
|
10431
11075
|
// src/runbook-actions.ts
|
|
10432
|
-
import { readFileSync as
|
|
11076
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
10433
11077
|
|
|
10434
11078
|
// src/runbook-requires.ts
|
|
10435
11079
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -10514,7 +11158,7 @@ async function bySlug(ctx, slug) {
|
|
|
10514
11158
|
function readBody(file, inline) {
|
|
10515
11159
|
if (inline !== void 0) return inline;
|
|
10516
11160
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
10517
|
-
return
|
|
11161
|
+
return readFileSync10(file === "-" ? 0 : file, "utf8");
|
|
10518
11162
|
}
|
|
10519
11163
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
10520
11164
|
async function runbookList(ctx, all, query) {
|
|
@@ -10544,12 +11188,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10544
11188
|
});
|
|
10545
11189
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10546
11190
|
}
|
|
10547
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
11191
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10548
11192
|
const runbook = await bySlug(ctx, slug);
|
|
10549
11193
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10550
11194
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10551
11195
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10552
|
-
patch: {
|
|
11196
|
+
patch: {
|
|
11197
|
+
body,
|
|
11198
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
11199
|
+
...note ? { note } : {},
|
|
11200
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
11201
|
+
}
|
|
10553
11202
|
});
|
|
10554
11203
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10555
11204
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10601,14 +11250,14 @@ async function runbookRemove(ctx, slug) {
|
|
|
10601
11250
|
}
|
|
10602
11251
|
|
|
10603
11252
|
// src/runbook-import.ts
|
|
10604
|
-
import { readFileSync as
|
|
10605
|
-
import { basename as basename2, join as
|
|
10606
|
-
function parseRunbook(
|
|
10607
|
-
let rest =
|
|
11253
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
11254
|
+
import { basename as basename2, join as join13 } from "path";
|
|
11255
|
+
function parseRunbook(text2, slug) {
|
|
11256
|
+
let rest = text2;
|
|
10608
11257
|
const meta = {};
|
|
10609
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
11258
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10610
11259
|
if (fm) {
|
|
10611
|
-
rest =
|
|
11260
|
+
rest = text2.slice(fm[0].length);
|
|
10612
11261
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10613
11262
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10614
11263
|
if (!pair) continue;
|
|
@@ -10632,7 +11281,7 @@ function readRunbookDir(dir) {
|
|
|
10632
11281
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
10633
11282
|
return files.map((file) => {
|
|
10634
11283
|
const slug = basename2(file, ".md");
|
|
10635
|
-
const parsed = parseRunbook(
|
|
11284
|
+
const parsed = parseRunbook(readFileSync11(join13(dir, file), "utf8"), slug);
|
|
10636
11285
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
10637
11286
|
});
|
|
10638
11287
|
}
|
|
@@ -10689,6 +11338,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10689
11338
|
patch: {
|
|
10690
11339
|
title: r.title,
|
|
10691
11340
|
body: r.body,
|
|
11341
|
+
expectedVersion: found.version,
|
|
10692
11342
|
...r.summary ? { summary: r.summary } : {},
|
|
10693
11343
|
...r.tags ? { tags: r.tags } : {},
|
|
10694
11344
|
note: `imported from ${r.file}`
|
|
@@ -10704,8 +11354,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
10704
11354
|
|
|
10705
11355
|
// src/runbook-impact.ts
|
|
10706
11356
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10707
|
-
import { existsSync as existsSync11, readFileSync as
|
|
10708
|
-
import { join as
|
|
11357
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
11358
|
+
import { join as join14 } from "path";
|
|
10709
11359
|
|
|
10710
11360
|
// src/runbook-impact-scan.ts
|
|
10711
11361
|
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$]*)/;
|
|
@@ -10874,10 +11524,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
10874
11524
|
}
|
|
10875
11525
|
function manifestLabeller(root) {
|
|
10876
11526
|
return (workspace) => {
|
|
10877
|
-
const manifest =
|
|
11527
|
+
const manifest = join14(root, workspace, "package.json");
|
|
10878
11528
|
if (!existsSync11(manifest)) return void 0;
|
|
10879
11529
|
try {
|
|
10880
|
-
const name = JSON.parse(
|
|
11530
|
+
const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
|
|
10881
11531
|
return typeof name === "string" ? name : void 0;
|
|
10882
11532
|
} catch {
|
|
10883
11533
|
return void 0;
|
|
@@ -10944,7 +11594,7 @@ function report3(ctx, impacts) {
|
|
|
10944
11594
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
10945
11595
|
const cwd = deps.cwd ?? process.cwd();
|
|
10946
11596
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
10947
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
11597
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join14(cwd, path), "utf8"));
|
|
10948
11598
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
10949
11599
|
if (!surfaces.length) {
|
|
10950
11600
|
return ctx.out.log(
|
|
@@ -11077,12 +11727,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
11077
11727
|
|
|
11078
11728
|
// src/runbook-editor.ts
|
|
11079
11729
|
import { spawnSync } from "child_process";
|
|
11080
|
-
import { mkdtempSync, readFileSync as
|
|
11730
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
11081
11731
|
import { tmpdir as tmpdir4 } from "os";
|
|
11082
|
-
import { join as
|
|
11083
|
-
import
|
|
11732
|
+
import { join as join15 } from "path";
|
|
11733
|
+
import process14 from "process";
|
|
11084
11734
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11085
|
-
function resolveEditor(env =
|
|
11735
|
+
function resolveEditor(env = process14.env) {
|
|
11086
11736
|
for (const name of EDITOR_ENV) {
|
|
11087
11737
|
const value2 = env[name];
|
|
11088
11738
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11096,8 +11746,8 @@ function defaultRun(command, path) {
|
|
|
11096
11746
|
return result.status ?? 0;
|
|
11097
11747
|
}
|
|
11098
11748
|
function editText(initial, slug, deps = {}) {
|
|
11099
|
-
const env = deps.env ??
|
|
11100
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11749
|
+
const env = deps.env ?? process14.env;
|
|
11750
|
+
const interactive = deps.interactive ?? (() => Boolean(process14.stdin.isTTY));
|
|
11101
11751
|
const editor = resolveEditor(env);
|
|
11102
11752
|
if (!editor)
|
|
11103
11753
|
throw new Error(
|
|
@@ -11105,13 +11755,13 @@ function editText(initial, slug, deps = {}) {
|
|
|
11105
11755
|
);
|
|
11106
11756
|
if (!interactive())
|
|
11107
11757
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
11108
|
-
const dir = mkdtempSync(
|
|
11109
|
-
const file =
|
|
11758
|
+
const dir = mkdtempSync(join15(tmpdir4(), "odla-runbook-"));
|
|
11759
|
+
const file = join15(dir, `${slug}.md`);
|
|
11110
11760
|
try {
|
|
11111
11761
|
writeFileSync4(file, initial, { mode: 384 });
|
|
11112
11762
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
11113
11763
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
11114
|
-
const edited =
|
|
11764
|
+
const edited = readFileSync13(file, "utf8");
|
|
11115
11765
|
return edited === initial ? null : edited;
|
|
11116
11766
|
} finally {
|
|
11117
11767
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -11129,27 +11779,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11129
11779
|
const found = page.records[0];
|
|
11130
11780
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
11131
11781
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
11132
|
-
|
|
11782
|
+
const body = await editText(found.body, slug, deps);
|
|
11783
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
11133
11784
|
}
|
|
11134
11785
|
|
|
11135
11786
|
// src/whoami-command.ts
|
|
11787
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11788
|
+
function principalKind(value2, machine) {
|
|
11789
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11790
|
+
}
|
|
11791
|
+
function credentialKind(value2, machine, scopes) {
|
|
11792
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11793
|
+
if (machine) return "machine";
|
|
11794
|
+
if (scopes.length) return "device";
|
|
11795
|
+
return "unknown";
|
|
11796
|
+
}
|
|
11797
|
+
function managerOf(value2) {
|
|
11798
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11799
|
+
const row = value2;
|
|
11800
|
+
const principalId = text(row.principalId);
|
|
11801
|
+
if (!principalId) return null;
|
|
11802
|
+
return {
|
|
11803
|
+
principalId,
|
|
11804
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11805
|
+
handle: text(row.handle) ?? ""
|
|
11806
|
+
};
|
|
11807
|
+
}
|
|
11808
|
+
function unnamedPrincipal(kind) {
|
|
11809
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11810
|
+
if (kind === "service") return "Unnamed service";
|
|
11811
|
+
return "Unnamed member";
|
|
11812
|
+
}
|
|
11136
11813
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11137
11814
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11138
11815
|
headers: { authorization: `Bearer ${token}` }
|
|
11139
11816
|
});
|
|
11140
11817
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11141
11818
|
const body = await res.json();
|
|
11819
|
+
const developerId = text(body.developerId) ?? "";
|
|
11820
|
+
const machine = body.machine === true;
|
|
11821
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11822
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11823
|
+
const email = text(body.email);
|
|
11824
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11825
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11826
|
+
const handle = text(body.handle) ?? "";
|
|
11827
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11142
11828
|
return {
|
|
11143
|
-
developerId
|
|
11144
|
-
|
|
11829
|
+
developerId,
|
|
11830
|
+
principalId,
|
|
11831
|
+
principalKind: kind,
|
|
11832
|
+
displayName,
|
|
11833
|
+
handle,
|
|
11834
|
+
manager: managerOf(body.manager),
|
|
11835
|
+
credential: {
|
|
11836
|
+
id: text(credential2.id),
|
|
11837
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11838
|
+
},
|
|
11839
|
+
email,
|
|
11145
11840
|
admin: body.admin === true,
|
|
11146
|
-
machine
|
|
11147
|
-
scopes
|
|
11841
|
+
machine,
|
|
11842
|
+
scopes
|
|
11148
11843
|
};
|
|
11149
11844
|
}
|
|
11150
|
-
function
|
|
11151
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
11152
|
-
|
|
11845
|
+
function credentialLabel(identity) {
|
|
11846
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11847
|
+
if (identity.credential.kind === "device")
|
|
11848
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11849
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11850
|
+
return "unknown (legacy server)";
|
|
11851
|
+
}
|
|
11852
|
+
function namedPrincipal(identity) {
|
|
11853
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11854
|
+
}
|
|
11855
|
+
function namedManager(manager) {
|
|
11856
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11857
|
+
}
|
|
11858
|
+
function accountableOwner(identity) {
|
|
11859
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11860
|
+
return namedManager(identity.manager);
|
|
11861
|
+
}
|
|
11862
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11863
|
+
return namedPrincipal(identity);
|
|
11864
|
+
}
|
|
11865
|
+
return identity.email ?? "Unnamed member";
|
|
11153
11866
|
}
|
|
11154
11867
|
async function whoamiCommand(parsed, deps = {}) {
|
|
11155
11868
|
assertArgs(
|
|
@@ -11180,9 +11893,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
11180
11893
|
return;
|
|
11181
11894
|
}
|
|
11182
11895
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11183
|
-
out.log(`
|
|
11896
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11897
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11898
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11899
|
+
if (identity.principalKind === "agent")
|
|
11900
|
+
out.log(
|
|
11901
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11902
|
+
);
|
|
11903
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11904
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11184
11905
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11185
|
-
out.log(`credential: ${
|
|
11906
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11907
|
+
if (identity.credential.id)
|
|
11908
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11186
11909
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11187
11910
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11188
11911
|
if (!identity.admin) {
|
|
@@ -11331,14 +12054,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11331
12054
|
const name = requireSlug(slug, "edit");
|
|
11332
12055
|
const file = stringOpt(parsed.options.file);
|
|
11333
12056
|
const inline = stringOpt(parsed.options.body);
|
|
11334
|
-
const
|
|
11335
|
-
if (
|
|
12057
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
12058
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
12059
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11336
12060
|
return runbookEdit(
|
|
11337
12061
|
ctx,
|
|
11338
12062
|
name,
|
|
11339
12063
|
body,
|
|
11340
12064
|
stringOpt(parsed.options.note),
|
|
11341
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
12065
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
12066
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11342
12067
|
);
|
|
11343
12068
|
}
|
|
11344
12069
|
case "import": {
|
|
@@ -11805,11 +12530,11 @@ async function securityStatus(parsed, dependencies) {
|
|
|
11805
12530
|
// src/cli.ts
|
|
11806
12531
|
function exitCodeFor(err) {
|
|
11807
12532
|
const code = err?.code;
|
|
11808
|
-
if (code === "handshake_pending" || code === "watch_timeout") return 75;
|
|
12533
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
11809
12534
|
if (code === "checkpoint_required") return 3;
|
|
11810
12535
|
if (code === "remote_unavailable") return 6;
|
|
11811
12536
|
if (code === "auth_failed") return 5;
|
|
11812
|
-
if (code === "invalid_cursor") return 2;
|
|
12537
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
11813
12538
|
return 1;
|
|
11814
12539
|
}
|
|
11815
12540
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
@@ -11964,7 +12689,11 @@ export {
|
|
|
11964
12689
|
calendarDisconnect,
|
|
11965
12690
|
CAPABILITIES,
|
|
11966
12691
|
printCapabilities,
|
|
12692
|
+
ConfigOperationCommandError,
|
|
11967
12693
|
desiredRegistryState,
|
|
12694
|
+
configApply,
|
|
12695
|
+
configOperationGet,
|
|
12696
|
+
configOperationWait,
|
|
11968
12697
|
reconcileConfig,
|
|
11969
12698
|
configDiff,
|
|
11970
12699
|
configPlan,
|
|
@@ -12005,4 +12734,4 @@ export {
|
|
|
12005
12734
|
exitCodeFor,
|
|
12006
12735
|
runCli
|
|
12007
12736
|
};
|
|
12008
|
-
//# sourceMappingURL=chunk-
|
|
12737
|
+
//# sourceMappingURL=chunk-ZAZBUURZ.js.map
|