@odla-ai/cli 0.27.10 → 0.27.12
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/dist/bin.cjs +244 -617
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-OGZELVS5.js → chunk-ECRBOR66.js} +128 -435
- package/dist/chunk-ECRBOR66.js.map +1 -0
- package/dist/index.cjs +244 -550
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OGZELVS5.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1736,7 +1736,7 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1736
1736
|
if (action2 !== "jobs" && action2 !== "retry") {
|
|
1737
1737
|
throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
1738
1738
|
}
|
|
1739
|
-
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"
|
|
1739
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
|
|
1740
1740
|
if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
1741
1741
|
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
1742
1742
|
}
|
|
@@ -1744,17 +1744,17 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1744
1744
|
const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
|
|
1745
1745
|
const doFetch = deps.fetch ?? fetch;
|
|
1746
1746
|
const out = deps.stdout ?? console;
|
|
1747
|
-
const credential2 =
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1747
|
+
const credential2 = stringOpt(parsed.options.token) ?? readCredentials(cfg.local.credentialsFile)?.envs[env]?.dbKey;
|
|
1748
|
+
if (!credential2) {
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`no ${env} app credential found; run \`odla-ai provision --write-dev-vars --yes\` or pass --token <ODLA_API_KEY>`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
if (credential2.startsWith("odla_dev_")) {
|
|
1754
|
+
throw new Error(
|
|
1755
|
+
"agent job administration requires an app credential (ODLA_API_KEY / odla_sk_\u2026), not a developer device token"
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
1758
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1759
1759
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1760
1760
|
if (action2 === "retry") {
|
|
@@ -1810,51 +1810,23 @@ function errorMessage(body) {
|
|
|
1810
1810
|
return "request failed";
|
|
1811
1811
|
}
|
|
1812
1812
|
|
|
1813
|
+
// src/human-session.ts
|
|
1814
|
+
async function requireStudioHuman(configPath, action2, destination = "app", env) {
|
|
1815
|
+
const cfg = await loadProjectConfig(configPath);
|
|
1816
|
+
const appEnv = env && cfg.envs.includes(env) ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
1817
|
+
const path = destination === "app" ? `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/settings/app` : `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/${destination}`;
|
|
1818
|
+
throw new Error(
|
|
1819
|
+
`human_session_required: ${action2} requires the signed-in owner in Studio. A CLI device token is an agent credential, so another approval or retry cannot work. ${new URL(path, cfg.platformUrl).href}`
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1813
1823
|
// src/app-export.ts
|
|
1814
|
-
var import_node_fs7 = require("fs");
|
|
1815
|
-
var import_node_stream = require("stream");
|
|
1816
|
-
var import_promises = require("stream/promises");
|
|
1817
1824
|
async function appExport(options) {
|
|
1818
|
-
|
|
1819
|
-
const out = options.stdout ?? console;
|
|
1820
|
-
const doFetch = options.fetch ?? fetch;
|
|
1821
|
-
const { tenant } = resolveTenant(cfg, options.env);
|
|
1822
|
-
const token = await getDeveloperToken(
|
|
1823
|
-
cfg,
|
|
1824
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
1825
|
-
doFetch,
|
|
1826
|
-
out
|
|
1827
|
-
);
|
|
1828
|
-
const auth = { authorization: `Bearer ${token}` };
|
|
1829
|
-
const base = `${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenant)}`;
|
|
1830
|
-
if (options.fresh) {
|
|
1831
|
-
const res = await doFetch(`${base}/export`, { method: "POST", headers: auth });
|
|
1832
|
-
const body = await res.json().catch(() => ({}));
|
|
1833
|
-
if (!res.ok) throw new Error(`export failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? res.status}`);
|
|
1834
|
-
}
|
|
1835
|
-
const list = await doFetch(`${base}/backups`, { headers: auth });
|
|
1836
|
-
if (!list.ok) throw new Error(`couldn't list backups (${list.status})`);
|
|
1837
|
-
const { backups } = await list.json();
|
|
1838
|
-
const newest = backups[0];
|
|
1839
|
-
if (!newest) {
|
|
1840
|
-
throw new Error(
|
|
1841
|
-
`${tenant} has no backups yet \u2014 run \`odla-ai app export --fresh\` to take one now (nightly snapshots appear after the first day with writes)`
|
|
1842
|
-
);
|
|
1843
|
-
}
|
|
1844
|
-
const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
|
|
1845
|
-
if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
|
|
1846
|
-
const file = options.out ?? `${tenant}-${new Date(newest.created_at).toISOString().slice(0, 10)}-tx${newest.max_tx}.jsonl.gz`;
|
|
1847
|
-
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(download.body), (0, import_node_fs7.createWriteStream)(file));
|
|
1848
|
-
if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
|
|
1849
|
-
else {
|
|
1850
|
-
out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
|
|
1851
|
-
out.log(`sha256 ${download.headers.get("x-odla-sha256") ?? newest.sha256}`);
|
|
1852
|
-
}
|
|
1853
|
-
return { file, backup: newest };
|
|
1825
|
+
return requireStudioHuman(options.configPath, "database export", "database", options.env);
|
|
1854
1826
|
}
|
|
1855
1827
|
|
|
1856
1828
|
// src/app-import.ts
|
|
1857
|
-
var
|
|
1829
|
+
var import_node_fs7 = require("fs");
|
|
1858
1830
|
var import_import = require("@odla-ai/db/import");
|
|
1859
1831
|
function chooseIdMode(options, rows) {
|
|
1860
1832
|
const chosen = [options.idField && "field", options.key && "key", options.generateIds && "generate"].filter(Boolean);
|
|
@@ -1871,9 +1843,8 @@ async function appImport(options) {
|
|
|
1871
1843
|
const cfg = await loadProjectConfig(options.configPath);
|
|
1872
1844
|
const out = options.stdout ?? console;
|
|
1873
1845
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1874
|
-
const doFetch = options.fetch ?? fetch;
|
|
1875
1846
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1876
|
-
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0,
|
|
1847
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs7.readFileSync)(0, "utf8")))() : (0, import_node_fs7.readFileSync)(options.file, "utf8");
|
|
1877
1848
|
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1878
1849
|
if (format === "namespace-map" && options.ns) {
|
|
1879
1850
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
@@ -1899,100 +1870,18 @@ ${detail}${more}`);
|
|
|
1899
1870
|
if (options.json) out.log(JSON.stringify(result, null, 2));
|
|
1900
1871
|
return result;
|
|
1901
1872
|
}
|
|
1902
|
-
|
|
1903
|
-
cfg,
|
|
1904
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
1905
|
-
doFetch,
|
|
1906
|
-
out
|
|
1907
|
-
);
|
|
1908
|
-
const runId = crypto.randomUUID();
|
|
1909
|
-
const url = `${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenant)}/transact`;
|
|
1910
|
-
for (const chunk of chunks) {
|
|
1911
|
-
const res = await doFetch(url, {
|
|
1912
|
-
method: "POST",
|
|
1913
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
1914
|
-
body: JSON.stringify({ mutationId: (0, import_import.importMutationId)(runId, chunk.index), ops: chunk.ops })
|
|
1915
|
-
});
|
|
1916
|
-
const body = await res.json().catch(() => ({}));
|
|
1917
|
-
if (!res.ok) {
|
|
1918
|
-
const code = body.error?.code ? ` (${body.error.code})` : "";
|
|
1919
|
-
throw new Error(
|
|
1920
|
-
`chunk ${chunk.index + 1} of ${chunks.length} failed${code}: ${body.error?.message ?? res.status}. ${result.committed} row(s) already committed; fix the input and re-run to import the rest.`
|
|
1921
|
-
);
|
|
1922
|
-
}
|
|
1923
|
-
result.committed += chunk.ops.length;
|
|
1924
|
-
if (typeof body.txId === "number") result.txIds.push(body.txId);
|
|
1925
|
-
if (body.duplicate) result.duplicate++;
|
|
1926
|
-
}
|
|
1927
|
-
if (options.json) out.log(JSON.stringify(result, null, 2));
|
|
1928
|
-
else {
|
|
1929
|
-
const dup = result.duplicate > 0 ? ` (${result.duplicate} chunk(s) were already applied)` : "";
|
|
1930
|
-
out.log(`${tenant}: upserted ${result.committed} row(s) in ${chunks.length} transaction(s)${dup}`);
|
|
1931
|
-
}
|
|
1932
|
-
return result;
|
|
1873
|
+
return requireStudioHuman(options.configPath, "database import", "database", options.env);
|
|
1933
1874
|
}
|
|
1934
1875
|
|
|
1935
1876
|
// src/app-owners.ts
|
|
1936
|
-
var sink = (options) => options.stdout ?? console;
|
|
1937
|
-
async function ownersRequest(method, suffix, options, body) {
|
|
1938
|
-
const cfg = await loadProjectConfig(options.configPath);
|
|
1939
|
-
const doFetch = options.fetch ?? fetch;
|
|
1940
|
-
const token = await getDeveloperToken(
|
|
1941
|
-
cfg,
|
|
1942
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
1943
|
-
doFetch,
|
|
1944
|
-
sink(options)
|
|
1945
|
-
);
|
|
1946
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/owners${suffix}`, {
|
|
1947
|
-
method,
|
|
1948
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
1949
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
1950
|
-
});
|
|
1951
|
-
const data = await res.json().catch(() => ({}));
|
|
1952
|
-
if (!res.ok) {
|
|
1953
|
-
throw new Error(
|
|
1954
|
-
`owners ${method} failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
return data.owners ?? [];
|
|
1958
|
-
}
|
|
1959
|
-
function report(options, owners, headline) {
|
|
1960
|
-
const out = sink(options);
|
|
1961
|
-
if (options.json === true) {
|
|
1962
|
-
out.log(JSON.stringify(owners, null, 2));
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
if (headline) out.log(headline);
|
|
1966
|
-
out.log(`owners (${owners.length}):`);
|
|
1967
|
-
for (const o of owners) {
|
|
1968
|
-
const name = o.email?.trim() || "Unnamed member";
|
|
1969
|
-
out.log(
|
|
1970
|
-
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1971
|
-
);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
1877
|
async function ownersList(options) {
|
|
1975
|
-
|
|
1878
|
+
await requireStudioHuman(options.configPath, "listing app owners", "app");
|
|
1976
1879
|
}
|
|
1977
1880
|
async function ownersAdd(email, options) {
|
|
1978
|
-
|
|
1979
|
-
report(
|
|
1980
|
-
options,
|
|
1981
|
-
owners,
|
|
1982
|
-
`added ${email} as a co-owner \u2014 they share full access. They can now run \`odla-ai provision\` to mint their own credentials (no secret sharing).`
|
|
1983
|
-
);
|
|
1881
|
+
await requireStudioHuman(options.configPath, `adding ${email} as an app owner`, "app");
|
|
1984
1882
|
}
|
|
1985
1883
|
async function ownersRemove(target, options) {
|
|
1986
|
-
|
|
1987
|
-
if (target.includes("@")) {
|
|
1988
|
-
const owners2 = await ownersRequest("GET", "", options);
|
|
1989
|
-
const match = owners2.find((o) => o.email?.toLowerCase() === target.toLowerCase());
|
|
1990
|
-
if (!match) throw new Error(`no co-owner with email ${target}`);
|
|
1991
|
-
if (match.primary) throw new Error("can't remove the primary owner");
|
|
1992
|
-
ownerId = match.ownerId;
|
|
1993
|
-
}
|
|
1994
|
-
const owners = await ownersRequest("DELETE", `/${encodeURIComponent(ownerId)}`, options);
|
|
1995
|
-
report(options, owners, `removed ${target}`);
|
|
1884
|
+
await requireStudioHuman(options.configPath, `removing ${target} as an app owner`, "app");
|
|
1996
1885
|
}
|
|
1997
1886
|
async function appOwnersCommand(parsed, dependencies = {}) {
|
|
1998
1887
|
const sub = parsed.positionals[2] ?? "list";
|
|
@@ -2024,35 +1913,9 @@ async function appOwnersCommand(parsed, dependencies = {}) {
|
|
|
2024
1913
|
|
|
2025
1914
|
// src/app-rename.ts
|
|
2026
1915
|
async function appRename(name, options) {
|
|
2027
|
-
const out = options.stdout ?? console;
|
|
2028
1916
|
const trimmed = name.trim();
|
|
2029
1917
|
if (!trimmed) throw new Error('"app rename" needs a name \u2014 try `odla-ai app rename "Acme Storefront"`.');
|
|
2030
|
-
|
|
2031
|
-
const doFetch = options.fetch ?? fetch;
|
|
2032
|
-
const token = await getDeveloperToken(
|
|
2033
|
-
cfg,
|
|
2034
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
2035
|
-
doFetch,
|
|
2036
|
-
out
|
|
2037
|
-
);
|
|
2038
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/name`, {
|
|
2039
|
-
method: "PUT",
|
|
2040
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
2041
|
-
body: JSON.stringify({ name: trimmed })
|
|
2042
|
-
});
|
|
2043
|
-
const data = await res.json().catch(() => ({}));
|
|
2044
|
-
if (!res.ok || !data.app) {
|
|
2045
|
-
throw new Error(
|
|
2046
|
-
`rename failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
|
|
2047
|
-
);
|
|
2048
|
-
}
|
|
2049
|
-
if (options.json === true) {
|
|
2050
|
-
out.log(JSON.stringify(data.app, null, 2));
|
|
2051
|
-
return;
|
|
2052
|
-
}
|
|
2053
|
-
out.log(`renamed ${data.app.appId} \u2192 "${data.app.name}"`);
|
|
2054
|
-
out.log("The app id is unchanged, so credentials, tenants, and URLs keep working.");
|
|
2055
|
-
out.log(`Update the "name" in ${cfg.configPath} to match.`);
|
|
1918
|
+
await requireStudioHuman(options.configPath, `renaming the app to "${trimmed}"`, "app");
|
|
2056
1919
|
}
|
|
2057
1920
|
async function appRenameCommand(parsed, dependencies = {}) {
|
|
2058
1921
|
assertArgs(parsed, ["config", "token", "email", "json"], parsed.positionals.length);
|
|
@@ -2068,172 +1931,23 @@ async function appRenameCommand(parsed, dependencies = {}) {
|
|
|
2068
1931
|
}
|
|
2069
1932
|
|
|
2070
1933
|
// src/app-transfer.ts
|
|
2071
|
-
function endpointsFor(verb, tenants) {
|
|
2072
|
-
const up = { source: tenants.sandbox, target: tenants.live, from: "dev" };
|
|
2073
|
-
const down = { source: tenants.live, target: tenants.sandbox, from: "prod" };
|
|
2074
|
-
if (verb === "refresh-sandbox") return { ...down, mode: "refresh" };
|
|
2075
|
-
return { ...up, mode: "cutover" };
|
|
2076
|
-
}
|
|
2077
|
-
async function api(cfg, token, path, init, doFetch) {
|
|
2078
|
-
const res = await doFetch(`${cfg.dbEndpoint}${path}`, {
|
|
2079
|
-
...init,
|
|
2080
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...init?.headers }
|
|
2081
|
-
});
|
|
2082
|
-
return { status: res.status, body: await res.json().catch(() => ({})) };
|
|
2083
|
-
}
|
|
2084
|
-
function describe(side) {
|
|
2085
|
-
if (!side.exists) return "not provisioned";
|
|
2086
|
-
if (side.maxTx === 0) return "empty \u2014 never written";
|
|
2087
|
-
const ns = side.namespaces.length === 1 ? "1 namespace" : `${side.namespaces.length} namespaces`;
|
|
2088
|
-
const identity = side.identityRows > 0 ? `, ${side.identityRows} identity row(s)` : "";
|
|
2089
|
-
return `${ns} \xB7 ${side.triples} value(s) \xB7 tx ${side.maxTx}${identity}`;
|
|
2090
|
-
}
|
|
2091
|
-
function printPlan(out, verb, pre, opts) {
|
|
2092
|
-
const arrow = verb === "refresh-sandbox" ? "live \u2192 sandbox" : "sandbox \u2192 live";
|
|
2093
|
-
out.log(`${verb} (${arrow})`);
|
|
2094
|
-
out.log(` from ${pre.source.tenant} ${describe(pre.source)}`);
|
|
2095
|
-
out.log(` to ${pre.target.tenant} ${describe(pre.target)}`);
|
|
2096
|
-
if (verb === "promote") {
|
|
2097
|
-
out.log(" moves schema, rules and gates only \u2014 no rows are copied or removed");
|
|
2098
|
-
} else {
|
|
2099
|
-
out.log(` ${pre.target.tenant} is REPLACED by ${pre.source.tenant}`);
|
|
2100
|
-
out.log(` stays put: ${pre.staysPut.join(", ")}`);
|
|
2101
|
-
const identity = opts.includeIdentity ? "INCLUDED (--include-identity)" : `left behind: ${pre.excluded.join(", ")}`;
|
|
2102
|
-
out.log(` identity: ${identity}`);
|
|
2103
|
-
out.log(` files: ${opts.includeFiles ? "copied (--include-files)" : "not copied"}`);
|
|
2104
|
-
}
|
|
2105
|
-
for (const blocker of pre.blockers) out.log(` \u2716 ${blocker.code}: ${blocker.message}`);
|
|
2106
|
-
}
|
|
2107
1934
|
async function appTransfer(options) {
|
|
2108
1935
|
const cfg = await loadProjectConfig(options.configPath);
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
const doFetch = options.fetch ?? fetch;
|
|
2112
|
-
const tenants = bothTenants(cfg);
|
|
2113
|
-
const route2 = endpointsFor(options.verb, tenants);
|
|
2114
|
-
const token = await getDeveloperToken(
|
|
2115
|
-
cfg,
|
|
2116
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
2117
|
-
doFetch,
|
|
2118
|
-
out
|
|
2119
|
-
);
|
|
2120
|
-
const pre = await api(
|
|
2121
|
-
cfg,
|
|
2122
|
-
token,
|
|
2123
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-preflight?from=${route2.from}`,
|
|
2124
|
-
void 0,
|
|
2125
|
-
doFetch
|
|
2126
|
-
);
|
|
2127
|
-
if (pre.status !== 200) {
|
|
2128
|
-
throw new Error(`pre-flight failed${pre.body.error?.code ? ` (${pre.body.error.code})` : ""}: ${pre.body.error?.message ?? pre.status}`);
|
|
2129
|
-
}
|
|
2130
|
-
const plan = pre.body;
|
|
2131
|
-
printPlan({ log: say }, options.verb, plan, options);
|
|
2132
|
-
if (options.verb === "go-live" && !plan.targetEmpty) {
|
|
2133
|
-
throw new Error(
|
|
2134
|
-
`${route2.target} already has data \u2014 go-live has already happened. Use \`odla-ai app promote --yes\` to push schema and rules, or \`odla-ai app refresh-sandbox --yes\` to pull live back down.`
|
|
2135
|
-
);
|
|
2136
|
-
}
|
|
2137
|
-
if (plan.blockers.length > 0) throw new Error(`cannot ${options.verb}: ${plan.blockers.map((b) => b.message).join("; ")}`);
|
|
2138
|
-
if (options.dryRun || options.yes !== true) {
|
|
2139
|
-
say(`nothing written (${options.dryRun ? "--dry-run" : "no --yes"})`);
|
|
2140
|
-
if (options.json) out.log(JSON.stringify(plan, null, 2));
|
|
2141
|
-
return { ok: true, plan };
|
|
2142
|
-
}
|
|
2143
|
-
if (options.verb === "promote") {
|
|
2144
|
-
const res2 = await api(
|
|
2145
|
-
cfg,
|
|
2146
|
-
token,
|
|
2147
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/promote-definitions`,
|
|
2148
|
-
{ method: "POST", body: JSON.stringify({ from: "dev" }) },
|
|
2149
|
-
doFetch
|
|
2150
|
-
);
|
|
2151
|
-
if (res2.status !== 200) throw new Error(`promote failed${res2.body.error?.code ? ` (${res2.body.error.code})` : ""}: ${res2.body.error?.message ?? res2.status}`);
|
|
2152
|
-
if (options.json) out.log(JSON.stringify(res2.body, null, 2));
|
|
2153
|
-
else say(`${route2.target}: promoted ${(res2.body.applied ?? []).join(", ")}`);
|
|
2154
|
-
return { ok: true, plan, result: res2.body };
|
|
2155
|
-
}
|
|
2156
|
-
const res = await api(
|
|
2157
|
-
cfg,
|
|
2158
|
-
token,
|
|
2159
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-db`,
|
|
2160
|
-
{
|
|
2161
|
-
method: "POST",
|
|
2162
|
-
body: JSON.stringify({
|
|
2163
|
-
from: route2.from,
|
|
2164
|
-
mode: route2.mode,
|
|
2165
|
-
...options.includeIdentity ? { includeUsers: true } : {},
|
|
2166
|
-
...options.includeFiles ? { includeFiles: true } : {}
|
|
2167
|
-
})
|
|
2168
|
-
},
|
|
2169
|
-
doFetch
|
|
2170
|
-
);
|
|
2171
|
-
if (res.status !== 200) throw new Error(`${options.verb} failed${res.body.error?.code ? ` (${res.body.error.code})` : ""}: ${res.body.error?.message ?? res.status}`);
|
|
2172
|
-
say(`${route2.target}: replaced from ${route2.source} (tx ${res.body.destination?.maxTx}, epoch ${res.body.destination?.epoch}) \u2014 connected clients resync automatically`);
|
|
2173
|
-
if (options.includeFiles) await copyFiles(cfg, token, route2, doFetch, out);
|
|
2174
|
-
if (options.json) out.log(JSON.stringify(res.body, null, 2));
|
|
2175
|
-
return { ok: true, plan, result: res.body };
|
|
2176
|
-
}
|
|
2177
|
-
async function copyFiles(cfg, token, route2, doFetch, out) {
|
|
2178
|
-
for (let attempt = 1; attempt <= 20; attempt++) {
|
|
2179
|
-
const res = await api(
|
|
2180
|
-
cfg,
|
|
2181
|
-
token,
|
|
2182
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-files`,
|
|
2183
|
-
{ method: "POST", body: JSON.stringify({ from: route2.from }) },
|
|
2184
|
-
doFetch
|
|
2185
|
-
);
|
|
2186
|
-
if (res.status === 200) {
|
|
2187
|
-
out.log(`${route2.target}: files copied`);
|
|
2188
|
-
return;
|
|
2189
|
-
}
|
|
2190
|
-
if (!res.body.error?.retry) {
|
|
2191
|
-
throw new Error(`file copy failed${res.body.error?.code ? ` (${res.body.error.code})` : ""}: ${res.body.error?.message ?? res.status}`);
|
|
2192
|
-
}
|
|
2193
|
-
out.log(` files: bounded at attempt ${attempt}, resuming\u2026`);
|
|
2194
|
-
}
|
|
2195
|
-
throw new Error("file copy did not finish within 20 rounds \u2014 re-run to continue where it left off");
|
|
1936
|
+
bothTenants(cfg);
|
|
1937
|
+
return requireStudioHuman(options.configPath, `app ${options.verb}`, "database");
|
|
2196
1938
|
}
|
|
2197
1939
|
|
|
2198
1940
|
// src/app-lifecycle.ts
|
|
2199
|
-
async function lifecycleCall(action2, options) {
|
|
2200
|
-
const cfg = await loadProjectConfig(options.configPath);
|
|
2201
|
-
const out = options.stdout ?? console;
|
|
2202
|
-
const doFetch = options.fetch ?? fetch;
|
|
2203
|
-
const token = await getDeveloperToken(
|
|
2204
|
-
cfg,
|
|
2205
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
2206
|
-
doFetch,
|
|
2207
|
-
out
|
|
2208
|
-
);
|
|
2209
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
|
|
2210
|
-
method: "POST",
|
|
2211
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
|
|
2212
|
-
});
|
|
2213
|
-
const body = await res.json().catch(() => ({}));
|
|
2214
|
-
if (!res.ok || !body.ok) {
|
|
2215
|
-
throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
|
|
2216
|
-
}
|
|
2217
|
-
return body;
|
|
2218
|
-
}
|
|
2219
1941
|
async function appArchive(options) {
|
|
2220
1942
|
if (options.yes !== true) {
|
|
2221
1943
|
throw new Error(
|
|
2222
1944
|
"app archive suspends EVERY environment: API keys stop working and all services refuse requests until restored. All data is retained. Pass --yes to proceed."
|
|
2223
1945
|
);
|
|
2224
1946
|
}
|
|
2225
|
-
|
|
2226
|
-
const body = await lifecycleCall("archive", options);
|
|
2227
|
-
if (options.json) out.log(JSON.stringify(body, null, 2));
|
|
2228
|
-
else if (body.operation?.state === "noop") out.log(`${body.app?.appId}: already archived`);
|
|
2229
|
-
else out.log(`${body.app?.appId}: archived \u2014 data retained; run \`odla-ai app restore\` (or use Studio) to bring it back`);
|
|
1947
|
+
await requireStudioHuman(options.configPath, "app archive", "app");
|
|
2230
1948
|
}
|
|
2231
1949
|
async function appRestore(options) {
|
|
2232
|
-
|
|
2233
|
-
const body = await lifecycleCall("restore", options);
|
|
2234
|
-
if (options.json) out.log(JSON.stringify(body, null, 2));
|
|
2235
|
-
else if (body.operation?.state === "noop") out.log(`${body.app?.appId}: already active`);
|
|
2236
|
-
else out.log(`${body.app?.appId}: restored \u2014 every service's data plane is live again`);
|
|
1950
|
+
await requireStudioHuman(options.configPath, "app restore", "app");
|
|
2237
1951
|
}
|
|
2238
1952
|
async function appCommand(parsed, dependencies = {}) {
|
|
2239
1953
|
const sub = parsed.positionals[1];
|
|
@@ -2319,7 +2033,7 @@ async function appCommand(parsed, dependencies = {}) {
|
|
|
2319
2033
|
}
|
|
2320
2034
|
|
|
2321
2035
|
// src/brand-command.ts
|
|
2322
|
-
var
|
|
2036
|
+
var import_promises = require("fs/promises");
|
|
2323
2037
|
var import_node_path7 = require("path");
|
|
2324
2038
|
|
|
2325
2039
|
// src/brand-design-unpack.ts
|
|
@@ -2422,7 +2136,7 @@ function describeUnpack(result, outDir) {
|
|
|
2422
2136
|
// src/brand-command.ts
|
|
2423
2137
|
var USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
|
|
2424
2138
|
async function readBundle(source, deps) {
|
|
2425
|
-
if (source !== "-") return (0,
|
|
2139
|
+
if (source !== "-") return (0, import_promises.readFile)((0, import_node_path7.resolve)(source), "utf8");
|
|
2426
2140
|
const readStdin = deps.readStdin;
|
|
2427
2141
|
if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
|
|
2428
2142
|
return readStdin();
|
|
@@ -2430,8 +2144,8 @@ async function readBundle(source, deps) {
|
|
|
2430
2144
|
async function writeAll(result, outDir) {
|
|
2431
2145
|
for (const file of result.files) {
|
|
2432
2146
|
const target = (0, import_node_path7.resolve)(outDir, file.path);
|
|
2433
|
-
await (0,
|
|
2434
|
-
await (0,
|
|
2147
|
+
await (0, import_promises.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
|
|
2148
|
+
await (0, import_promises.writeFile)(target, file.bytes);
|
|
2435
2149
|
}
|
|
2436
2150
|
}
|
|
2437
2151
|
async function designUnpack(parsed, deps) {
|
|
@@ -2591,12 +2305,6 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
2591
2305
|
ctx.env
|
|
2592
2306
|
);
|
|
2593
2307
|
}
|
|
2594
|
-
async function requestCalendarDisconnect(ctx) {
|
|
2595
|
-
return parseCalendarStatus(
|
|
2596
|
-
await calendarJson(ctx, "/disconnect", { method: "POST", body: JSON.stringify({ purge: false }) }),
|
|
2597
|
-
ctx.env
|
|
2598
|
-
);
|
|
2599
|
-
}
|
|
2600
2308
|
function parseCalendarStatus(raw, env) {
|
|
2601
2309
|
const outer = wrapped(raw, "calendar");
|
|
2602
2310
|
const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
|
|
@@ -2765,7 +2473,7 @@ async function calendarCalendars(options) {
|
|
|
2765
2473
|
return calendars;
|
|
2766
2474
|
}
|
|
2767
2475
|
async function calendarConnect(options) {
|
|
2768
|
-
const { cfg, ctx, out } = await lifecycleContext(options);
|
|
2476
|
+
const { cfg, ctx, out } = await lifecycleContext(options, ["app.manage"]);
|
|
2769
2477
|
productionConsent(ctx.env, options.yes, "connect calendar");
|
|
2770
2478
|
const page2 = calendarBookingPageUrl(cfg, ctx.env);
|
|
2771
2479
|
const applied = page2 === void 0 ? await readCalendarStatus(ctx) : await applyCalendarSettings(ctx, page2);
|
|
@@ -2780,10 +2488,7 @@ async function applyCalendarBookingPage(ctx, bookingPageUrl, out) {
|
|
|
2780
2488
|
}
|
|
2781
2489
|
async function calendarDisconnect(options) {
|
|
2782
2490
|
if (!options.yes) throw new Error("calendar disconnect requires --yes");
|
|
2783
|
-
|
|
2784
|
-
const status = await requestCalendarDisconnect(ctx);
|
|
2785
|
-
out.log(`${ctx.env}: calendar disconnected; no calendar data was stored`);
|
|
2786
|
-
return status;
|
|
2491
|
+
return requireStudioHuman(options.configPath, "calendar disconnect", "calendar", options.env);
|
|
2787
2492
|
}
|
|
2788
2493
|
async function ensureCalendarConnected(ctx, options) {
|
|
2789
2494
|
const out = options.stdout ?? console;
|
|
@@ -2791,7 +2496,7 @@ async function ensureCalendarConnected(ctx, options) {
|
|
|
2791
2496
|
const connectOptions = connectionOptions(options, out);
|
|
2792
2497
|
return await continueConnectedCalendar(ctx, current, connectOptions) ?? connectWithContext(ctx, connectOptions);
|
|
2793
2498
|
}
|
|
2794
|
-
async function lifecycleContext(options) {
|
|
2499
|
+
async function lifecycleContext(options, optionalProjectCapabilities = []) {
|
|
2795
2500
|
const cfg = await loadProjectConfig(options.configPath);
|
|
2796
2501
|
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
2797
2502
|
const env = options.env ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
|
|
@@ -2809,7 +2514,8 @@ async function lifecycleContext(options) {
|
|
|
2809
2514
|
openApprovalUrl: options.openConsentUrl
|
|
2810
2515
|
},
|
|
2811
2516
|
doFetch,
|
|
2812
|
-
out
|
|
2517
|
+
out,
|
|
2518
|
+
{ optionalProjectCapabilities }
|
|
2813
2519
|
);
|
|
2814
2520
|
return { cfg, ctx: { platform: cfg.platformUrl, appId: cfg.app.id, env, token, fetch: doFetch }, out };
|
|
2815
2521
|
}
|
|
@@ -2996,9 +2702,9 @@ var import_apps6 = require("@odla-ai/apps");
|
|
|
2996
2702
|
var import_node_path8 = require("path");
|
|
2997
2703
|
|
|
2998
2704
|
// src/version.ts
|
|
2999
|
-
var
|
|
2705
|
+
var import_node_fs8 = require("fs");
|
|
3000
2706
|
function cliVersion() {
|
|
3001
|
-
const pkg = JSON.parse((0,
|
|
2707
|
+
const pkg = JSON.parse((0, import_node_fs8.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
3002
2708
|
return pkg.version ?? "unknown";
|
|
3003
2709
|
}
|
|
3004
2710
|
|
|
@@ -3014,7 +2720,7 @@ var ConfigOperationCommandError = class extends Error {
|
|
|
3014
2720
|
|
|
3015
2721
|
// src/config-operation-validate.ts
|
|
3016
2722
|
var import_apps3 = require("@odla-ai/apps");
|
|
3017
|
-
var
|
|
2723
|
+
var import_node_fs9 = require("fs");
|
|
3018
2724
|
|
|
3019
2725
|
// src/config-reconcile-digest.ts
|
|
3020
2726
|
var import_node_crypto2 = require("crypto");
|
|
@@ -3050,7 +2756,7 @@ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
|
3050
2756
|
function readPlan(path) {
|
|
3051
2757
|
let value2;
|
|
3052
2758
|
try {
|
|
3053
|
-
const raw = (0,
|
|
2759
|
+
const raw = (0, import_node_fs9.readFileSync)(path, "utf8");
|
|
3054
2760
|
if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
|
|
3055
2761
|
value2 = JSON.parse(raw);
|
|
3056
2762
|
} catch (error) {
|
|
@@ -3185,11 +2891,17 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3185
2891
|
if (res.ok || res.status === 404) return;
|
|
3186
2892
|
if (res.status === 403) {
|
|
3187
2893
|
const detail = await safeText4(res);
|
|
3188
|
-
|
|
2894
|
+
const code = errorCode(detail);
|
|
2895
|
+
if (code === "human_session_required") {
|
|
3189
2896
|
throw new Error(
|
|
3190
2897
|
`${env}: odla-db rejected the provision credential before checking ownership for "${cfg.app.id}" (tenant ${tenantId}): human_session_required. Retrying or changing app owners will not help; the deployed odla-db must accept owner-approved app.manage credentials on provisioning routes`
|
|
3191
2898
|
);
|
|
3192
2899
|
}
|
|
2900
|
+
if (code === "provision_approval_required") {
|
|
2901
|
+
throw new Error(
|
|
2902
|
+
`${env}: the agent credential does not carry the owner-reviewed app.manage grant required to provision "${cfg.app.id}" (tenant ${tenantId}). The human owner id on the token is accountability, not agent authority. Discard the cached or supplied token and run this command with the current CLI to approve one fresh exact-project provisioning handshake`
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
3193
2905
|
throw new Error(
|
|
3194
2906
|
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; re-run provision with a fresh owner-approved provision handshake. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
|
|
3195
2907
|
);
|
|
@@ -3757,7 +3469,7 @@ async function configPlan(options) {
|
|
|
3757
3469
|
apply,
|
|
3758
3470
|
nextActions: planNextActions(reconciliation, options.configPath)
|
|
3759
3471
|
};
|
|
3760
|
-
|
|
3472
|
+
printPlan(document2, options);
|
|
3761
3473
|
return document2;
|
|
3762
3474
|
}
|
|
3763
3475
|
async function inspectConfig(options) {
|
|
@@ -3797,7 +3509,7 @@ function printDiff(document2, options) {
|
|
|
3797
3509
|
printDifferences(out, document2);
|
|
3798
3510
|
printNext(out, document2.nextActions);
|
|
3799
3511
|
}
|
|
3800
|
-
function
|
|
3512
|
+
function printPlan(document2, options) {
|
|
3801
3513
|
const out = options.stdout ?? console;
|
|
3802
3514
|
if (options.json) {
|
|
3803
3515
|
out.log(JSON.stringify(document2, null, 2));
|
|
@@ -3900,12 +3612,12 @@ function quoteArg2(value2) {
|
|
|
3900
3612
|
|
|
3901
3613
|
// src/doctor-checks.ts
|
|
3902
3614
|
var import_node_child_process3 = require("child_process");
|
|
3903
|
-
var
|
|
3615
|
+
var import_node_fs11 = require("fs");
|
|
3904
3616
|
var import_node_path11 = require("path");
|
|
3905
3617
|
|
|
3906
3618
|
// src/wrangler.ts
|
|
3907
3619
|
var import_node_child_process2 = require("child_process");
|
|
3908
|
-
var
|
|
3620
|
+
var import_node_fs10 = require("fs");
|
|
3909
3621
|
var import_node_path10 = require("path");
|
|
3910
3622
|
var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
|
|
3911
3623
|
const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -3921,14 +3633,14 @@ var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"]
|
|
|
3921
3633
|
function findWranglerConfig(rootDir) {
|
|
3922
3634
|
for (const name of WRANGLER_CONFIG_FILES) {
|
|
3923
3635
|
const path = (0, import_node_path10.join)(rootDir, name);
|
|
3924
|
-
if ((0,
|
|
3636
|
+
if ((0, import_node_fs10.existsSync)(path)) return path;
|
|
3925
3637
|
}
|
|
3926
3638
|
return null;
|
|
3927
3639
|
}
|
|
3928
3640
|
function readWranglerConfig(path) {
|
|
3929
3641
|
if (path.endsWith(".toml")) return null;
|
|
3930
3642
|
try {
|
|
3931
|
-
return JSON.parse(stripJsonComments((0,
|
|
3643
|
+
return JSON.parse(stripJsonComments((0, import_node_fs10.readFileSync)(path, "utf8")));
|
|
3932
3644
|
} catch {
|
|
3933
3645
|
return null;
|
|
3934
3646
|
}
|
|
@@ -4036,7 +3748,7 @@ function wranglerWarnings(rootDir) {
|
|
|
4036
3748
|
const dir = (0, import_node_path11.resolve)(rootDir, assets.directory);
|
|
4037
3749
|
if (dir === (0, import_node_path11.resolve)(rootDir)) {
|
|
4038
3750
|
warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
|
|
4039
|
-
} else if ((0,
|
|
3751
|
+
} else if ((0, import_node_fs11.existsSync)((0, import_node_path11.join)(dir, "node_modules"))) {
|
|
4040
3752
|
warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
|
|
4041
3753
|
}
|
|
4042
3754
|
}
|
|
@@ -4072,12 +3784,12 @@ function o11yProjectWarnings(rootDir) {
|
|
|
4072
3784
|
return warnings;
|
|
4073
3785
|
}
|
|
4074
3786
|
const main = typeof config.main === "string" ? (0, import_node_path11.resolve)(rootDir, config.main) : null;
|
|
4075
|
-
if (!main || !(0,
|
|
3787
|
+
if (!main || !(0, import_node_fs11.existsSync)(main)) {
|
|
4076
3788
|
warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
|
|
4077
3789
|
} else {
|
|
4078
3790
|
let source = "";
|
|
4079
3791
|
try {
|
|
4080
|
-
source = (0,
|
|
3792
|
+
source = (0, import_node_fs11.readFileSync)(main, "utf8");
|
|
4081
3793
|
} catch {
|
|
4082
3794
|
}
|
|
4083
3795
|
if (!/\bwithObservability\b/.test(source)) {
|
|
@@ -4101,7 +3813,7 @@ function calendarProjectWarnings(rootDir) {
|
|
|
4101
3813
|
}
|
|
4102
3814
|
function readPackageJson(rootDir) {
|
|
4103
3815
|
try {
|
|
4104
|
-
return JSON.parse((0,
|
|
3816
|
+
return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path11.join)(rootDir, "package.json"), "utf8"));
|
|
4105
3817
|
} catch {
|
|
4106
3818
|
return null;
|
|
4107
3819
|
}
|
|
@@ -4330,14 +4042,14 @@ function harnessOption(value2, flag) {
|
|
|
4330
4042
|
}
|
|
4331
4043
|
|
|
4332
4044
|
// src/init.ts
|
|
4333
|
-
var
|
|
4045
|
+
var import_node_fs12 = require("fs");
|
|
4334
4046
|
var import_node_path12 = require("path");
|
|
4335
4047
|
var import_apps9 = require("@odla-ai/apps");
|
|
4336
4048
|
function initProject(options) {
|
|
4337
4049
|
const out = options.stdout ?? console;
|
|
4338
4050
|
const rootDir = (0, import_node_path12.resolve)(options.rootDir ?? process.cwd());
|
|
4339
4051
|
const configPath = (0, import_node_path12.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
|
|
4340
|
-
if ((0,
|
|
4052
|
+
if ((0, import_node_fs12.existsSync)(configPath) && !options.force) {
|
|
4341
4053
|
throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
|
|
4342
4054
|
}
|
|
4343
4055
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
|
|
@@ -4353,10 +4065,10 @@ function initProject(options) {
|
|
|
4353
4065
|
}
|
|
4354
4066
|
}
|
|
4355
4067
|
const aiProvider = options.aiProvider;
|
|
4356
|
-
(0,
|
|
4357
|
-
(0,
|
|
4358
|
-
(0,
|
|
4359
|
-
(0,
|
|
4068
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path12.dirname)(configPath), { recursive: true });
|
|
4069
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path12.resolve)(rootDir, "src/odla"), { recursive: true });
|
|
4070
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path12.resolve)(rootDir, ".odla"), { recursive: true });
|
|
4071
|
+
(0, import_node_fs12.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
|
|
4360
4072
|
writeIfMissing((0, import_node_path12.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
|
|
4361
4073
|
writeIfMissing((0, import_node_path12.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
|
|
4362
4074
|
ensureGitignore(rootDir);
|
|
@@ -4365,8 +4077,8 @@ function initProject(options) {
|
|
|
4365
4077
|
out.log("updated .gitignore for local odla credentials");
|
|
4366
4078
|
}
|
|
4367
4079
|
function writeIfMissing(path, text2) {
|
|
4368
|
-
if ((0,
|
|
4369
|
-
(0,
|
|
4080
|
+
if ((0, import_node_fs12.existsSync)(path)) return;
|
|
4081
|
+
(0, import_node_fs12.writeFileSync)(path, text2);
|
|
4370
4082
|
}
|
|
4371
4083
|
function configTemplate(input) {
|
|
4372
4084
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4554,7 +4266,9 @@ async function secretsSet(options) {
|
|
|
4554
4266
|
throw new Error('"$"-prefixed vault names are platform-reserved; for the Clerk secret key use "odla-ai secrets set-clerk-key"');
|
|
4555
4267
|
}
|
|
4556
4268
|
const { cfg, tenantId, value: value2, doFetch, out } = await resolveVaultWrite(options);
|
|
4557
|
-
const token = await getDeveloperToken(cfg, options, doFetch, out
|
|
4269
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
4270
|
+
optionalProjectCapabilities: ["app.manage"]
|
|
4271
|
+
});
|
|
4558
4272
|
try {
|
|
4559
4273
|
await (0, import_ai2.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
4560
4274
|
} catch (err) {
|
|
@@ -4571,7 +4285,9 @@ async function secretsSetClerkKey(options) {
|
|
|
4571
4285
|
if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
4572
4286
|
throw new Error(`refusing to store an sk_live_ Clerk key for "${options.env}" without --yes (live users would sync into a non-prod tenant)`);
|
|
4573
4287
|
}
|
|
4574
|
-
const token = await getDeveloperToken(cfg, options, doFetch, out
|
|
4288
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
4289
|
+
optionalProjectCapabilities: ["app.manage"]
|
|
4290
|
+
});
|
|
4575
4291
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
|
|
4576
4292
|
method: "POST",
|
|
4577
4293
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
@@ -4601,7 +4317,7 @@ async function resolveVaultWrite(options) {
|
|
|
4601
4317
|
}
|
|
4602
4318
|
|
|
4603
4319
|
// src/skill.ts
|
|
4604
|
-
var
|
|
4320
|
+
var import_node_fs13 = require("fs");
|
|
4605
4321
|
var import_node_os2 = require("os");
|
|
4606
4322
|
var import_node_path13 = require("path");
|
|
4607
4323
|
var import_node_url2 = require("url");
|
|
@@ -4698,7 +4414,7 @@ function installSkill(options = {}) {
|
|
|
4698
4414
|
plans.set(target, { target, content: content2, boundary, managedMerge });
|
|
4699
4415
|
};
|
|
4700
4416
|
const planSkillTree = (targetDir2, boundary = root) => {
|
|
4701
|
-
for (const rel of files) plan((0, import_node_path13.join)(targetDir2, rel), (0,
|
|
4417
|
+
for (const rel of files) plan((0, import_node_path13.join)(targetDir2, rel), (0, import_node_fs13.readFileSync)((0, import_node_path13.join)(sourceDir, rel), "utf8"), false, boundary);
|
|
4702
4418
|
};
|
|
4703
4419
|
let targetDir;
|
|
4704
4420
|
if (options.global) {
|
|
@@ -4718,7 +4434,7 @@ function installSkill(options = {}) {
|
|
|
4718
4434
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4719
4435
|
if (harnesses.includes("claude")) {
|
|
4720
4436
|
for (const skill of skillNames(files)) {
|
|
4721
|
-
const canonical = (0,
|
|
4437
|
+
const canonical = (0, import_node_fs13.readFileSync)((0, import_node_path13.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
4722
4438
|
plan((0, import_node_path13.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
|
|
4723
4439
|
}
|
|
4724
4440
|
rememberTarget("claude", claudeRoot);
|
|
@@ -4753,11 +4469,11 @@ function installSkill(options = {}) {
|
|
|
4753
4469
|
conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
|
|
4754
4470
|
continue;
|
|
4755
4471
|
}
|
|
4756
|
-
if (!(0,
|
|
4472
|
+
if (!(0, import_node_fs13.existsSync)(file.target)) {
|
|
4757
4473
|
writtenPaths.add(file.target);
|
|
4758
4474
|
continue;
|
|
4759
4475
|
}
|
|
4760
|
-
const current = (0,
|
|
4476
|
+
const current = (0, import_node_fs13.readFileSync)(file.target, "utf8");
|
|
4761
4477
|
if (current === file.content) {
|
|
4762
4478
|
unchangedPaths.add(file.target);
|
|
4763
4479
|
} else if (file.managedMerge || options.force) {
|
|
@@ -4774,9 +4490,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
4774
4490
|
);
|
|
4775
4491
|
}
|
|
4776
4492
|
for (const file of plans.values()) {
|
|
4777
|
-
if (!(0,
|
|
4778
|
-
(0,
|
|
4779
|
-
(0,
|
|
4493
|
+
if (!(0, import_node_fs13.existsSync)(file.target) || (0, import_node_fs13.readFileSync)(file.target, "utf8") !== file.content) {
|
|
4494
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(file.target), { recursive: true });
|
|
4495
|
+
(0, import_node_fs13.writeFileSync)(file.target, file.content);
|
|
4780
4496
|
}
|
|
4781
4497
|
}
|
|
4782
4498
|
const skills = skillNames(files);
|
|
@@ -4817,9 +4533,9 @@ function normalizeHarnesses(values, global) {
|
|
|
4817
4533
|
function managedFileContent(path, block, force, boundary) {
|
|
4818
4534
|
const symlink = symlinkedComponent(boundary, path);
|
|
4819
4535
|
if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
|
|
4820
|
-
if (!(0,
|
|
4536
|
+
if (!(0, import_node_fs13.existsSync)(path)) return `${block}
|
|
4821
4537
|
`;
|
|
4822
|
-
const current = (0,
|
|
4538
|
+
const current = (0, import_node_fs13.readFileSync)(path, "utf8");
|
|
4823
4539
|
const start = "<!-- odla-ai agent setup:start -->";
|
|
4824
4540
|
const end = "<!-- odla-ai agent setup:end -->";
|
|
4825
4541
|
const startAt = current.indexOf(start);
|
|
@@ -4848,7 +4564,7 @@ function symlinkedComponent(boundary, target) {
|
|
|
4848
4564
|
for (const part of rel.split(import_node_path13.sep).filter(Boolean)) {
|
|
4849
4565
|
current = (0, import_node_path13.join)(current, part);
|
|
4850
4566
|
try {
|
|
4851
|
-
if ((0,
|
|
4567
|
+
if ((0, import_node_fs13.lstatSync)(current).isSymbolicLink()) return current;
|
|
4852
4568
|
} catch (error) {
|
|
4853
4569
|
if (error.code !== "ENOENT") throw error;
|
|
4854
4570
|
}
|
|
@@ -4859,10 +4575,10 @@ function skillNames(files) {
|
|
|
4859
4575
|
return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
|
|
4860
4576
|
}
|
|
4861
4577
|
function listFiles(dir) {
|
|
4862
|
-
if (!(0,
|
|
4578
|
+
if (!(0, import_node_fs13.existsSync)(dir)) return [];
|
|
4863
4579
|
const results = [];
|
|
4864
4580
|
const walk = (current) => {
|
|
4865
|
-
for (const entry of (0,
|
|
4581
|
+
for (const entry of (0, import_node_fs13.readdirSync)(current, { withFileTypes: true })) {
|
|
4866
4582
|
const path = (0, import_node_path13.join)(current, entry.name);
|
|
4867
4583
|
if (entry.isDirectory()) walk(path);
|
|
4868
4584
|
else results.push((0, import_node_path13.relative)(dir, path));
|
|
@@ -5179,7 +4895,7 @@ async function projectCommand(command, parsed, deps) {
|
|
|
5179
4895
|
}
|
|
5180
4896
|
|
|
5181
4897
|
// src/code-connect.ts
|
|
5182
|
-
var
|
|
4898
|
+
var import_node_fs14 = require("fs");
|
|
5183
4899
|
var import_node_os4 = require("os");
|
|
5184
4900
|
var import_node_path15 = require("path");
|
|
5185
4901
|
|
|
@@ -5267,15 +4983,15 @@ function encodeAgentInput(message2) {
|
|
|
5267
4983
|
// ../harness/dist/chunk-PHXQH4YM.js
|
|
5268
4984
|
var import_child_process = require("child_process");
|
|
5269
4985
|
var import_fs = require("fs");
|
|
5270
|
-
var
|
|
4986
|
+
var import_promises2 = require("fs/promises");
|
|
5271
4987
|
var import_path = require("path");
|
|
5272
4988
|
var import_process = require("process");
|
|
5273
|
-
var
|
|
4989
|
+
var import_promises3 = require("fs/promises");
|
|
5274
4990
|
var import_os = require("os");
|
|
5275
4991
|
var import_path2 = require("path");
|
|
5276
4992
|
var import_child_process2 = require("child_process");
|
|
5277
4993
|
var import_path3 = require("path");
|
|
5278
|
-
var
|
|
4994
|
+
var import_promises4 = require("fs/promises");
|
|
5279
4995
|
var import_os2 = require("os");
|
|
5280
4996
|
var import_path4 = require("path");
|
|
5281
4997
|
var import_child_process3 = require("child_process");
|
|
@@ -5286,7 +5002,7 @@ function assertPinnedImage(image) {
|
|
|
5286
5002
|
async function commandAvailable(engine) {
|
|
5287
5003
|
for (const directory of (process.env.PATH ?? "").split(import_path.delimiter).filter(Boolean)) {
|
|
5288
5004
|
try {
|
|
5289
|
-
await (0,
|
|
5005
|
+
await (0, import_promises2.access)((0, import_path.join)(directory, engine), import_fs.constants.X_OK);
|
|
5290
5006
|
return true;
|
|
5291
5007
|
} catch {
|
|
5292
5008
|
}
|
|
@@ -5572,7 +5288,7 @@ async function gitBlobs(cwd, entries, maxBytes) {
|
|
|
5572
5288
|
}
|
|
5573
5289
|
async function materializeGitTree(source, commitSha, options = {}) {
|
|
5574
5290
|
if (!/^[0-9a-f]{40}$/.test(commitSha)) throw new TypeError("Git tree requires an exact commit SHA");
|
|
5575
|
-
const sourceDir = await (0,
|
|
5291
|
+
const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
|
|
5576
5292
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5577
5293
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5578
5294
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
@@ -5581,9 +5297,9 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5581
5297
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5582
5298
|
});
|
|
5583
5299
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
5584
|
-
const root = await (0,
|
|
5300
|
+
const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-git-tree-"));
|
|
5585
5301
|
const targetRoot = (0, import_path2.join)(root, "source");
|
|
5586
|
-
await (0,
|
|
5302
|
+
await (0, import_promises3.mkdir)(targetRoot);
|
|
5587
5303
|
let byteCount = 0;
|
|
5588
5304
|
try {
|
|
5589
5305
|
const blobs = await gitBlobs(sourceDir, entries, maxBytes);
|
|
@@ -5593,18 +5309,18 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5593
5309
|
if (byteCount > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
5594
5310
|
const target = (0, import_path2.resolve)(targetRoot, entry.path);
|
|
5595
5311
|
if (!target.startsWith(`${(0, import_path2.resolve)(targetRoot)}${import_path2.sep}`)) throw new TypeError("Git tree path escapes workspace");
|
|
5596
|
-
await (0,
|
|
5597
|
-
await (0,
|
|
5312
|
+
await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
|
|
5313
|
+
await (0, import_promises3.writeFile)(target, content2, { flag: "wx", mode: entry.mode === "100755" ? 493 : 420 });
|
|
5598
5314
|
}
|
|
5599
5315
|
return {
|
|
5600
5316
|
root,
|
|
5601
5317
|
sourceDir: targetRoot,
|
|
5602
5318
|
fileCount: entries.length,
|
|
5603
5319
|
byteCount,
|
|
5604
|
-
cleanup: () => (0,
|
|
5320
|
+
cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
|
|
5605
5321
|
};
|
|
5606
5322
|
} catch (error) {
|
|
5607
|
-
await (0,
|
|
5323
|
+
await (0, import_promises3.rm)(root, { recursive: true, force: true });
|
|
5608
5324
|
throw error;
|
|
5609
5325
|
}
|
|
5610
5326
|
}
|
|
@@ -5612,7 +5328,7 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5612
5328
|
const files = [];
|
|
5613
5329
|
let bytes = 0;
|
|
5614
5330
|
const walk = async (dir) => {
|
|
5615
|
-
for (const entry of await (0,
|
|
5331
|
+
for (const entry of await (0, import_promises4.readdir)(dir, { withFileTypes: true })) {
|
|
5616
5332
|
if (entry.isDirectory() && SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
5617
5333
|
if (!entry.isDirectory() && SECRET_WORKSPACE_FILE.test(entry.name)) continue;
|
|
5618
5334
|
const path = (0, import_path4.join)(dir, entry.name);
|
|
@@ -5622,7 +5338,7 @@ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5622
5338
|
continue;
|
|
5623
5339
|
}
|
|
5624
5340
|
if (!entry.isFile()) continue;
|
|
5625
|
-
const metadata2 = await (0,
|
|
5341
|
+
const metadata2 = await (0, import_promises4.stat)(path);
|
|
5626
5342
|
bytes += metadata2.size;
|
|
5627
5343
|
if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
5628
5344
|
if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
|
|
@@ -5671,7 +5387,7 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5671
5387
|
if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
5672
5388
|
let metadata2;
|
|
5673
5389
|
try {
|
|
5674
|
-
metadata2 = await (0,
|
|
5390
|
+
metadata2 = await (0, import_promises4.lstat)(source);
|
|
5675
5391
|
} catch (error) {
|
|
5676
5392
|
if (error.code === "ENOENT") continue;
|
|
5677
5393
|
throw error;
|
|
@@ -5686,9 +5402,9 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
5686
5402
|
async function copyTree(files, destination) {
|
|
5687
5403
|
for (const file of files) {
|
|
5688
5404
|
const target = (0, import_path4.join)(destination, file.relativePath);
|
|
5689
|
-
await (0,
|
|
5690
|
-
await (0,
|
|
5691
|
-
await (0,
|
|
5405
|
+
await (0, import_promises4.mkdir)((0, import_path4.resolve)(target, ".."), { recursive: true });
|
|
5406
|
+
await (0, import_promises4.copyFile)(file.source, target);
|
|
5407
|
+
await (0, import_promises4.chmod)(target, file.mode);
|
|
5692
5408
|
}
|
|
5693
5409
|
}
|
|
5694
5410
|
async function captureGitDiff(root, maxBytes) {
|
|
@@ -5725,13 +5441,13 @@ async function captureGitDiff(root, maxBytes) {
|
|
|
5725
5441
|
return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("a/workspace/", "a/").replaceAll("b/baseline/", "b/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
|
|
5726
5442
|
}
|
|
5727
5443
|
async function stageWorkspace(source, options = {}) {
|
|
5728
|
-
const sourceDir = await (0,
|
|
5729
|
-
const sourceStat = await (0,
|
|
5444
|
+
const sourceDir = await (0, import_promises4.realpath)((0, import_path4.resolve)(source));
|
|
5445
|
+
const sourceStat = await (0, import_promises4.stat)(sourceDir);
|
|
5730
5446
|
if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
|
|
5731
|
-
const root = await (0,
|
|
5447
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
5732
5448
|
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
5733
5449
|
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
5734
|
-
await Promise.all([(0,
|
|
5450
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
5735
5451
|
try {
|
|
5736
5452
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5737
5453
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
@@ -5744,26 +5460,26 @@ async function stageWorkspace(source, options = {}) {
|
|
|
5744
5460
|
fileCount: files.length,
|
|
5745
5461
|
byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
|
|
5746
5462
|
patch: (maxBytes2) => captureGitDiff(root, maxBytes2),
|
|
5747
|
-
cleanup: () => (0,
|
|
5463
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
5748
5464
|
};
|
|
5749
5465
|
} catch (error) {
|
|
5750
|
-
await (0,
|
|
5466
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
5751
5467
|
throw error;
|
|
5752
5468
|
}
|
|
5753
5469
|
}
|
|
5754
5470
|
async function stageWorkspacePair(baselineSource, workspaceSource, options = {}) {
|
|
5755
|
-
const baselineDirSource = await (0,
|
|
5756
|
-
const workspaceDirSource = await (0,
|
|
5471
|
+
const baselineDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(baselineSource));
|
|
5472
|
+
const workspaceDirSource = await (0, import_promises4.realpath)((0, import_path4.resolve)(workspaceSource));
|
|
5757
5473
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5758
5474
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5759
5475
|
const [baselineFiles, workspaceFiles] = await Promise.all([
|
|
5760
5476
|
sourceFiles(baselineDirSource, maxFiles, maxBytes),
|
|
5761
5477
|
sourceFiles(workspaceDirSource, maxFiles, maxBytes)
|
|
5762
5478
|
]);
|
|
5763
|
-
const root = await (0,
|
|
5479
|
+
const root = await (0, import_promises4.mkdtemp)((0, import_path4.join)(options.tempRoot ?? (0, import_os2.tmpdir)(), "odla-harness-"));
|
|
5764
5480
|
const baselineDir = (0, import_path4.join)(root, "baseline");
|
|
5765
5481
|
const workspaceDir = (0, import_path4.join)(root, "workspace");
|
|
5766
|
-
await Promise.all([(0,
|
|
5482
|
+
await Promise.all([(0, import_promises4.mkdir)(baselineDir), (0, import_promises4.mkdir)(workspaceDir)]);
|
|
5767
5483
|
try {
|
|
5768
5484
|
await Promise.all([copyTree(baselineFiles, baselineDir), copyTree(workspaceFiles, workspaceDir)]);
|
|
5769
5485
|
return {
|
|
@@ -5773,17 +5489,17 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
5773
5489
|
fileCount: workspaceFiles.length,
|
|
5774
5490
|
byteCount: workspaceFiles.reduce((sum, file) => sum + file.bytes, 0),
|
|
5775
5491
|
patch: (maxPatchBytes) => captureGitDiff(root, maxPatchBytes),
|
|
5776
|
-
cleanup: () => (0,
|
|
5492
|
+
cleanup: () => (0, import_promises4.rm)(root, { recursive: true, force: true })
|
|
5777
5493
|
};
|
|
5778
5494
|
} catch (error) {
|
|
5779
|
-
await (0,
|
|
5495
|
+
await (0, import_promises4.rm)(root, { recursive: true, force: true });
|
|
5780
5496
|
throw error;
|
|
5781
5497
|
}
|
|
5782
5498
|
}
|
|
5783
5499
|
|
|
5784
5500
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
5785
5501
|
var import_crypto = require("crypto");
|
|
5786
|
-
var
|
|
5502
|
+
var import_promises5 = require("fs/promises");
|
|
5787
5503
|
var import_path5 = require("path");
|
|
5788
5504
|
|
|
5789
5505
|
// ../camel/dist/chunk-7FHPOQVP.js
|
|
@@ -6120,19 +5836,19 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6120
5836
|
|
|
6121
5837
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
6122
5838
|
var import_child_process4 = require("child_process");
|
|
6123
|
-
var
|
|
5839
|
+
var import_promises6 = require("fs/promises");
|
|
6124
5840
|
var import_path6 = require("path");
|
|
6125
5841
|
var import_child_process5 = require("child_process");
|
|
6126
5842
|
var import_process2 = require("process");
|
|
6127
5843
|
var import_crypto2 = require("crypto");
|
|
6128
5844
|
var import_crypto3 = require("crypto");
|
|
6129
5845
|
var import_fs2 = require("fs");
|
|
6130
|
-
var
|
|
5846
|
+
var import_promises7 = require("fs/promises");
|
|
6131
5847
|
var import_path7 = require("path");
|
|
6132
|
-
var
|
|
5848
|
+
var import_promises8 = require("fs/promises");
|
|
6133
5849
|
var import_os3 = require("os");
|
|
6134
5850
|
var import_path8 = require("path");
|
|
6135
|
-
var
|
|
5851
|
+
var import_promises9 = require("fs/promises");
|
|
6136
5852
|
var import_path9 = require("path");
|
|
6137
5853
|
|
|
6138
5854
|
// ../camel/dist/chunk-4EIRFS3A.js
|
|
@@ -6424,7 +6140,7 @@ var import_crypto4 = require("crypto");
|
|
|
6424
6140
|
async function digestStagedWorkspace(root, limits) {
|
|
6425
6141
|
const files = [];
|
|
6426
6142
|
const walk = async (directory) => {
|
|
6427
|
-
const entries = await (0,
|
|
6143
|
+
const entries = await (0, import_promises5.readdir)(directory, { withFileTypes: true });
|
|
6428
6144
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
6429
6145
|
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
6430
6146
|
const target = (0, import_path5.resolve)(directory, entry.name);
|
|
@@ -6439,7 +6155,7 @@ async function digestStagedWorkspace(root, limits) {
|
|
|
6439
6155
|
const hash = (0, import_crypto.createHash)("sha256");
|
|
6440
6156
|
let bytes = 0;
|
|
6441
6157
|
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
6442
|
-
const content2 = await (0,
|
|
6158
|
+
const content2 = await (0, import_promises5.readFile)(file.target);
|
|
6443
6159
|
bytes += Buffer.byteLength(file.path) + content2.byteLength;
|
|
6444
6160
|
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
6445
6161
|
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content2.byteLength}:`);
|
|
@@ -6765,7 +6481,7 @@ async function applyCodePatch(workspaceDir, patch2, paths) {
|
|
|
6765
6481
|
await gitApply(workspaceDir, patch2, false);
|
|
6766
6482
|
for (const path of paths) {
|
|
6767
6483
|
try {
|
|
6768
|
-
const info = await (0,
|
|
6484
|
+
const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
|
|
6769
6485
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
6770
6486
|
throw new TypeError("patch created a non-regular workspace entry");
|
|
6771
6487
|
}
|
|
@@ -7056,7 +6772,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
7056
6772
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
7057
6773
|
try {
|
|
7058
6774
|
const path = (0, import_path7.join)(workspaceDir, artifact.path);
|
|
7059
|
-
const info = await (0,
|
|
6775
|
+
const info = await (0, import_promises7.lstat)(path);
|
|
7060
6776
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
7061
6777
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
7062
6778
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -7237,9 +6953,9 @@ var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_mod
|
|
|
7237
6953
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
7238
6954
|
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
|
|
7239
6955
|
if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
|
|
7240
|
-
const root = await (0,
|
|
6956
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
|
|
7241
6957
|
const sourceDir = (0, import_path8.join)(root, "source");
|
|
7242
|
-
await (0,
|
|
6958
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
7243
6959
|
const seen = /* @__PURE__ */ new Set();
|
|
7244
6960
|
let bytes = 0;
|
|
7245
6961
|
try {
|
|
@@ -7251,8 +6967,8 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
|
|
|
7251
6967
|
if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
|
|
7252
6968
|
const target = (0, import_path8.resolve)(sourceDir, file.path);
|
|
7253
6969
|
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
7254
|
-
await (0,
|
|
7255
|
-
await (0,
|
|
6970
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
6971
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
7256
6972
|
}
|
|
7257
6973
|
for (const reference of snapshot.references ?? []) {
|
|
7258
6974
|
validateAlias(reference.alias);
|
|
@@ -7266,13 +6982,13 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
|
|
|
7266
6982
|
if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
|
|
7267
6983
|
const target = (0, import_path8.resolve)(sourceDir, path);
|
|
7268
6984
|
if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
7269
|
-
await (0,
|
|
7270
|
-
await (0,
|
|
6985
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
6986
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
7271
6987
|
}
|
|
7272
6988
|
}
|
|
7273
|
-
return { sourceDir, cleanup: () => (0,
|
|
6989
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
7274
6990
|
} catch (cause) {
|
|
7275
|
-
await (0,
|
|
6991
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
7276
6992
|
throw cause;
|
|
7277
6993
|
}
|
|
7278
6994
|
}
|
|
@@ -7293,8 +7009,8 @@ async function attachCodeRuntimeReferences(workspace, references) {
|
|
|
7293
7009
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
7294
7010
|
const target = (0, import_path8.resolve)(root, path);
|
|
7295
7011
|
if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
7296
|
-
await (0,
|
|
7297
|
-
await (0,
|
|
7012
|
+
await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
|
|
7013
|
+
await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
7298
7014
|
}
|
|
7299
7015
|
}
|
|
7300
7016
|
}
|
|
@@ -7480,11 +7196,11 @@ async function read(context, request2, options, policy) {
|
|
|
7480
7196
|
const allowed = await policy.read(policyContext(context, request2, options, { paths, path, startLine, endLine }));
|
|
7481
7197
|
if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
|
|
7482
7198
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
7483
|
-
const info = await (0,
|
|
7199
|
+
const info = await (0, import_promises9.stat)(target);
|
|
7484
7200
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
7485
7201
|
throw new TypeError("file is not a bounded regular source file");
|
|
7486
7202
|
}
|
|
7487
|
-
const source = await (0,
|
|
7203
|
+
const source = await (0, import_promises9.readFile)(target);
|
|
7488
7204
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
7489
7205
|
const lines = source.toString("utf8").split("\n");
|
|
7490
7206
|
const content2 = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -7561,7 +7277,7 @@ function policyContext(context, request2, options, extra) {
|
|
|
7561
7277
|
async function registeredFiles(root, limit) {
|
|
7562
7278
|
const paths = [];
|
|
7563
7279
|
const walk = async (directory) => {
|
|
7564
|
-
for (const entry of await (0,
|
|
7280
|
+
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
7565
7281
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
7566
7282
|
const target = (0, import_path9.resolve)(directory, entry.name);
|
|
7567
7283
|
if (entry.isDirectory()) await walk(target);
|
|
@@ -8346,7 +8062,7 @@ function digestText(value2) {
|
|
|
8346
8062
|
// src/code-images.ts
|
|
8347
8063
|
var import_node_child_process6 = require("child_process");
|
|
8348
8064
|
var import_node_crypto4 = require("crypto");
|
|
8349
|
-
var
|
|
8065
|
+
var import_promises10 = require("fs/promises");
|
|
8350
8066
|
var import_node_os3 = require("os");
|
|
8351
8067
|
var import_node_path14 = require("path");
|
|
8352
8068
|
var import_node_url3 = require("url");
|
|
@@ -8428,16 +8144,16 @@ function embeddedPiAssetPath() {
|
|
|
8428
8144
|
return (0, import_node_url3.fileURLToPath)(new URL("./runtime/pi-agent.js", importMetaUrl));
|
|
8429
8145
|
}
|
|
8430
8146
|
async function embeddedPiImageName() {
|
|
8431
|
-
const bundle = await (0,
|
|
8147
|
+
const bundle = await (0, import_promises10.readFile)(embeddedPiAssetPath()).catch(() => {
|
|
8432
8148
|
throw new Error("CLI-embedded Pi runtime is missing; reinstall this exact @odla-ai/cli version");
|
|
8433
8149
|
});
|
|
8434
8150
|
return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto4.createHash)("sha256").update(bundle).digest("hex")}`;
|
|
8435
8151
|
}
|
|
8436
8152
|
async function buildEmbeddedPiImage(engine, image, run) {
|
|
8437
|
-
const context = await (0,
|
|
8153
|
+
const context = await (0, import_promises10.mkdtemp)((0, import_node_path14.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
|
|
8438
8154
|
try {
|
|
8439
|
-
await (0,
|
|
8440
|
-
await (0,
|
|
8155
|
+
await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path14.join)(context, "pi-agent.js"));
|
|
8156
|
+
await (0, import_promises10.writeFile)((0, import_node_path14.join)(context, "Dockerfile"), [
|
|
8441
8157
|
`FROM ${CODE_NODE_IMAGE}`,
|
|
8442
8158
|
"COPY pi-agent.js /opt/odla/pi-agent.js",
|
|
8443
8159
|
"WORKDIR /workspace",
|
|
@@ -8446,7 +8162,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
8446
8162
|
].join("\n"), { mode: 384 });
|
|
8447
8163
|
await run(engine, ["build", "--tag", image, context], "inherit");
|
|
8448
8164
|
} finally {
|
|
8449
|
-
await (0,
|
|
8165
|
+
await (0, import_promises10.rm)(context, { recursive: true, force: true });
|
|
8450
8166
|
}
|
|
8451
8167
|
}
|
|
8452
8168
|
|
|
@@ -8454,7 +8170,7 @@ async function buildEmbeddedPiImage(engine, image, run) {
|
|
|
8454
8170
|
async function codeConnect(options) {
|
|
8455
8171
|
const cwd = options.cwd ?? process.cwd();
|
|
8456
8172
|
const configPath = (0, import_node_path15.resolve)(cwd, options.configPath);
|
|
8457
|
-
const cfg = (0,
|
|
8173
|
+
const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
|
|
8458
8174
|
const requestedAppId = options.appId?.trim();
|
|
8459
8175
|
if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
|
|
8460
8176
|
throw new Error("--app-id must be a valid odla app id");
|
|
@@ -8845,18 +8561,17 @@ Usage:
|
|
|
8845
8561
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8846
8562
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8847
8563
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
8848
|
-
odla-ai calendar disconnect [--env dev] [
|
|
8849
|
-
odla-ai app archive [--config odla.config.mjs] [
|
|
8850
|
-
odla-ai app restore [--config odla.config.mjs]
|
|
8851
|
-
odla-ai app export [--env dev]
|
|
8564
|
+
odla-ai calendar disconnect [--env dev] --yes [continue in Studio; human session required]
|
|
8565
|
+
odla-ai app archive [--config odla.config.mjs] --yes [continue in Studio; human session required]
|
|
8566
|
+
odla-ai app restore [--config odla.config.mjs] [continue in Studio; human session required]
|
|
8567
|
+
odla-ai app export [--env dev] [continue in Studio; human session required]
|
|
8852
8568
|
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
8853
|
-
|
|
8854
|
-
odla-ai app
|
|
8855
|
-
odla-ai app
|
|
8856
|
-
odla-ai app
|
|
8857
|
-
odla-ai app
|
|
8858
|
-
odla-ai app owners add
|
|
8859
|
-
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
8569
|
+
[dry-run is local; writes continue in Studio]
|
|
8570
|
+
odla-ai app refresh-sandbox [continue in Studio; human session required]
|
|
8571
|
+
odla-ai app go-live [continue in Studio; human session required]
|
|
8572
|
+
odla-ai app promote [continue in Studio; human session required]
|
|
8573
|
+
odla-ai app rename <name> [continue in Studio; human session required]
|
|
8574
|
+
odla-ai app owners <list|add|remove> [...] [continue in Studio; human session required]
|
|
8860
8575
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
8861
8576
|
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8862
8577
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
@@ -8891,8 +8606,8 @@ Usage:
|
|
|
8891
8606
|
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
8892
8607
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
8893
8608
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
8894
|
-
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--
|
|
8895
|
-
odla-ai agent retry <job-id> [--env dev] [--
|
|
8609
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--token <ODLA_API_KEY>] [--json]
|
|
8610
|
+
odla-ai agent retry <job-id> [--env dev] [--token <ODLA_API_KEY>] [--json]
|
|
8896
8611
|
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
8897
8612
|
odla-ai context list [--json]
|
|
8898
8613
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
@@ -8928,8 +8643,8 @@ Usage:
|
|
|
8928
8643
|
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
8929
8644
|
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
8930
8645
|
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
8931
|
-
odla-ai security github connect [--repo owner/name] [--env dev]
|
|
8932
|
-
odla-ai security github disconnect --source <id> [--env dev]
|
|
8646
|
+
odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
|
|
8647
|
+
odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
|
|
8933
8648
|
odla-ai security plan [--env dev] [--json]
|
|
8934
8649
|
odla-ai security sources [--env dev] [--json]
|
|
8935
8650
|
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
@@ -9440,7 +9155,7 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9440
9155
|
throw new WatchRemoteError(cursor, error);
|
|
9441
9156
|
}
|
|
9442
9157
|
if (deadline !== void 0 && now() >= deadline) {
|
|
9443
|
-
const result =
|
|
9158
|
+
const result = report(ctx, parsed, { found: false, cursor: cursor ?? "" });
|
|
9444
9159
|
throw new WatchTimeoutError(result.cursor);
|
|
9445
9160
|
}
|
|
9446
9161
|
const base = Math.min(intervalMs, 1e3);
|
|
@@ -9481,7 +9196,7 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9481
9196
|
});
|
|
9482
9197
|
const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
|
|
9483
9198
|
const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
|
|
9484
|
-
return
|
|
9199
|
+
return report(ctx, parsed, {
|
|
9485
9200
|
found: true,
|
|
9486
9201
|
cursor,
|
|
9487
9202
|
events: matching,
|
|
@@ -9508,13 +9223,13 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9508
9223
|
}
|
|
9509
9224
|
if (page2.hasMore) continue;
|
|
9510
9225
|
if (deadline !== void 0 && now() >= deadline) {
|
|
9511
|
-
return
|
|
9226
|
+
return report(ctx, parsed, { found: false, cursor });
|
|
9512
9227
|
}
|
|
9513
9228
|
const wait2 = deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()));
|
|
9514
9229
|
await sleep(wait2);
|
|
9515
9230
|
}
|
|
9516
9231
|
}
|
|
9517
|
-
function
|
|
9232
|
+
function report(ctx, parsed, result) {
|
|
9518
9233
|
if (ctx.json) {
|
|
9519
9234
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9520
9235
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
@@ -10015,7 +9730,7 @@ function eventLabel(event) {
|
|
|
10015
9730
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
10016
9731
|
return body || event.payload.entityId;
|
|
10017
9732
|
}
|
|
10018
|
-
function
|
|
9733
|
+
function report2(ctx, parsed, result) {
|
|
10019
9734
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
10020
9735
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
10021
9736
|
for (const event of result.events ?? []) {
|
|
@@ -10075,7 +9790,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10075
9790
|
});
|
|
10076
9791
|
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) throw error;
|
|
10077
9792
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10078
|
-
return
|
|
9793
|
+
return report2(ctx, parsed, { found: false, cursor: cursor ?? "" });
|
|
10079
9794
|
}
|
|
10080
9795
|
const backoff = Math.min(
|
|
10081
9796
|
MAX_BACKOFF_MS2,
|
|
@@ -10116,7 +9831,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10116
9831
|
cursor,
|
|
10117
9832
|
serverTime: current.serverTime
|
|
10118
9833
|
});
|
|
10119
|
-
return
|
|
9834
|
+
return report2(ctx, parsed, { found: true, cursor, events: matching });
|
|
10120
9835
|
}
|
|
10121
9836
|
if (current.events.length > 0) {
|
|
10122
9837
|
jsonl2(ctx, parsed, {
|
|
@@ -10135,7 +9850,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10135
9850
|
}
|
|
10136
9851
|
if (current.hasMore) continue;
|
|
10137
9852
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10138
|
-
return
|
|
9853
|
+
return report2(ctx, parsed, { found: false, cursor });
|
|
10139
9854
|
}
|
|
10140
9855
|
await sleep(
|
|
10141
9856
|
deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()))
|
|
@@ -11168,7 +10883,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
11168
10883
|
}
|
|
11169
10884
|
|
|
11170
10885
|
// src/record.ts
|
|
11171
|
-
var
|
|
10886
|
+
var import_node_fs15 = require("fs");
|
|
11172
10887
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
11173
10888
|
|
|
11174
10889
|
// src/surface.ts
|
|
@@ -11348,14 +11063,14 @@ function recordInvocation(parsed) {
|
|
|
11348
11063
|
options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
|
|
11349
11064
|
};
|
|
11350
11065
|
if (!entry.path.length) return;
|
|
11351
|
-
(0,
|
|
11066
|
+
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
11352
11067
|
`);
|
|
11353
11068
|
} catch {
|
|
11354
11069
|
}
|
|
11355
11070
|
}
|
|
11356
11071
|
|
|
11357
11072
|
// src/runbook-actions.ts
|
|
11358
|
-
var
|
|
11073
|
+
var import_node_fs16 = require("fs");
|
|
11359
11074
|
|
|
11360
11075
|
// src/runbook-requires.ts
|
|
11361
11076
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -11440,7 +11155,7 @@ async function bySlug(ctx, slug) {
|
|
|
11440
11155
|
function readBody(file, inline) {
|
|
11441
11156
|
if (inline !== void 0) return inline;
|
|
11442
11157
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
11443
|
-
return (0,
|
|
11158
|
+
return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
11444
11159
|
}
|
|
11445
11160
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
11446
11161
|
async function runbookList(ctx, all, query) {
|
|
@@ -11532,7 +11247,7 @@ async function runbookRemove(ctx, slug) {
|
|
|
11532
11247
|
}
|
|
11533
11248
|
|
|
11534
11249
|
// src/runbook-import.ts
|
|
11535
|
-
var
|
|
11250
|
+
var import_node_fs17 = require("fs");
|
|
11536
11251
|
var import_node_path16 = require("path");
|
|
11537
11252
|
function parseRunbook(text2, slug) {
|
|
11538
11253
|
let rest = text2;
|
|
@@ -11558,12 +11273,12 @@ function parseRunbook(text2, slug) {
|
|
|
11558
11273
|
};
|
|
11559
11274
|
}
|
|
11560
11275
|
function readRunbookDir(dir) {
|
|
11561
|
-
if (!(0,
|
|
11562
|
-
const files = (0,
|
|
11276
|
+
if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
11277
|
+
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
11563
11278
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
11564
11279
|
return files.map((file) => {
|
|
11565
11280
|
const slug = (0, import_node_path16.basename)(file, ".md");
|
|
11566
|
-
const parsed = parseRunbook((0,
|
|
11281
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path16.join)(dir, file), "utf8"), slug);
|
|
11567
11282
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
11568
11283
|
});
|
|
11569
11284
|
}
|
|
@@ -11636,7 +11351,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
11636
11351
|
|
|
11637
11352
|
// src/runbook-impact.ts
|
|
11638
11353
|
var import_node_child_process7 = require("child_process");
|
|
11639
|
-
var
|
|
11354
|
+
var import_node_fs18 = require("fs");
|
|
11640
11355
|
var import_node_path17 = require("path");
|
|
11641
11356
|
|
|
11642
11357
|
// src/runbook-impact-scan.ts
|
|
@@ -11807,9 +11522,9 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
11807
11522
|
function manifestLabeller(root) {
|
|
11808
11523
|
return (workspace) => {
|
|
11809
11524
|
const manifest = (0, import_node_path17.join)(root, workspace, "package.json");
|
|
11810
|
-
if (!(0,
|
|
11525
|
+
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
11811
11526
|
try {
|
|
11812
|
-
const name = JSON.parse((0,
|
|
11527
|
+
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
11813
11528
|
return typeof name === "string" ? name : void 0;
|
|
11814
11529
|
} catch {
|
|
11815
11530
|
return void 0;
|
|
@@ -11847,7 +11562,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
|
|
|
11847
11562
|
return out;
|
|
11848
11563
|
}
|
|
11849
11564
|
var editHint = (slug, appId) => `odla-ai runbook edit ${slug}${appId === PLATFORM_SCOPE ? "" : ` --app ${appId}`} --note "<what changed>"`;
|
|
11850
|
-
function
|
|
11565
|
+
function report3(ctx, impacts) {
|
|
11851
11566
|
const covered = impacts.filter((i) => i.runbooks.length);
|
|
11852
11567
|
ctx.out.log(
|
|
11853
11568
|
`${impacts.length} changed surface${impacts.length === 1 ? "" : "s"}; ${covered.length} covered by a runbook. Reread each one and fix any step this change made wrong.`
|
|
@@ -11876,7 +11591,7 @@ function report4(ctx, impacts) {
|
|
|
11876
11591
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
11877
11592
|
const cwd = deps.cwd ?? process.cwd();
|
|
11878
11593
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
11879
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
11594
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path17.join)(cwd, path), "utf8"));
|
|
11880
11595
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
11881
11596
|
if (!surfaces.length) {
|
|
11882
11597
|
return ctx.out.log(
|
|
@@ -11885,7 +11600,7 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
11885
11600
|
}
|
|
11886
11601
|
const impacts = await assessImpact(ctx, surfaces, options.all, options.limit ?? 4);
|
|
11887
11602
|
if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
|
|
11888
|
-
|
|
11603
|
+
report3(ctx, impacts);
|
|
11889
11604
|
}
|
|
11890
11605
|
|
|
11891
11606
|
// src/runbook-lint.ts
|
|
@@ -12009,7 +11724,7 @@ async function runbookComment(ctx, slug, body) {
|
|
|
12009
11724
|
|
|
12010
11725
|
// src/runbook-editor.ts
|
|
12011
11726
|
var import_node_child_process8 = require("child_process");
|
|
12012
|
-
var
|
|
11727
|
+
var import_node_fs19 = require("fs");
|
|
12013
11728
|
var import_node_os5 = require("os");
|
|
12014
11729
|
var import_node_path18 = require("path");
|
|
12015
11730
|
var import_node_process13 = __toESM(require("process"), 1);
|
|
@@ -12037,16 +11752,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
12037
11752
|
);
|
|
12038
11753
|
if (!interactive())
|
|
12039
11754
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
12040
|
-
const dir = (0,
|
|
11755
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path18.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
12041
11756
|
const file = (0, import_node_path18.join)(dir, `${slug}.md`);
|
|
12042
11757
|
try {
|
|
12043
|
-
(0,
|
|
11758
|
+
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
12044
11759
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
12045
11760
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
12046
|
-
const edited = (0,
|
|
11761
|
+
const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
|
|
12047
11762
|
return edited === initial ? null : edited;
|
|
12048
11763
|
} finally {
|
|
12049
|
-
(0,
|
|
11764
|
+
(0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
|
|
12050
11765
|
}
|
|
12051
11766
|
}
|
|
12052
11767
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -12385,7 +12100,6 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
12385
12100
|
}
|
|
12386
12101
|
|
|
12387
12102
|
// src/security-command-context.ts
|
|
12388
|
-
var import_promises12 = require("readline/promises");
|
|
12389
12103
|
async function hostedSecurityContext(parsed, dependencies) {
|
|
12390
12104
|
const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
12391
12105
|
const cfg = await loadProjectConfig(configPath);
|
|
@@ -12404,21 +12118,11 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
12404
12118
|
cfg,
|
|
12405
12119
|
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
12406
12120
|
doFetch,
|
|
12407
|
-
stdout
|
|
12121
|
+
stdout,
|
|
12122
|
+
{ optionalProjectCapabilities: ["app.manage"] }
|
|
12408
12123
|
);
|
|
12409
12124
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
12410
12125
|
}
|
|
12411
|
-
async function interactiveConfirmation(message2, dependencies) {
|
|
12412
|
-
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
12413
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
12414
|
-
const prompt = (0, import_promises12.createInterface)({ input: process.stdin, output: process.stdout });
|
|
12415
|
-
try {
|
|
12416
|
-
const answer = await prompt.question(`${message2} [y/N] `);
|
|
12417
|
-
return /^y(?:es)?$/i.test(answer.trim());
|
|
12418
|
-
} finally {
|
|
12419
|
-
prompt.close();
|
|
12420
|
-
}
|
|
12421
|
-
}
|
|
12422
12126
|
function requiredSecurityPositional(parsed, index, label) {
|
|
12423
12127
|
const value2 = parsed.positionals[index];
|
|
12424
12128
|
if (!value2) throw new Error(`${label} is required`);
|
|
@@ -12484,31 +12188,31 @@ function printHostedJob(out, job, platform, appId) {
|
|
|
12484
12188
|
url.searchParams.set("job", job.jobId);
|
|
12485
12189
|
out.log(` Studio: ${url.toString()}`);
|
|
12486
12190
|
}
|
|
12487
|
-
function printHostedReport(out,
|
|
12488
|
-
out.log(`security report ${
|
|
12489
|
-
out.log(` coverage: ${
|
|
12490
|
-
out.log(` findings: confirmed=${
|
|
12491
|
-
out.log(` discovery: ${
|
|
12492
|
-
out.log(` validation: ${
|
|
12493
|
-
for (const finding of
|
|
12191
|
+
function printHostedReport(out, report4) {
|
|
12192
|
+
out.log(`security report ${report4.jobId}: ${report4.repository}@${report4.revision}`);
|
|
12193
|
+
out.log(` coverage: ${report4.coverageStatus} cells=${report4.metrics.coverageCells} shallow=${report4.metrics.shallowCells} blocked=${report4.metrics.blockedCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
12194
|
+
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates} rejected=${report4.metrics.rejected}`);
|
|
12195
|
+
out.log(` discovery: ${report4.provenance.discovery?.provider ?? "unknown"}/${report4.provenance.discovery?.model ?? "unknown"}`);
|
|
12196
|
+
out.log(` validation: ${report4.provenance.validation?.provider ?? "unknown"}/${report4.provenance.validation?.model ?? "unknown"} independent=${String(report4.provenance.independentValidation)}`);
|
|
12197
|
+
for (const finding of report4.findings) {
|
|
12494
12198
|
const location = finding.locations[0];
|
|
12495
12199
|
out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
|
|
12496
12200
|
}
|
|
12497
|
-
for (const limitation of
|
|
12201
|
+
for (const limitation of report4.limitations) out.log(` limitation: ${limitation}`);
|
|
12498
12202
|
}
|
|
12499
|
-
function enforceHostedReportGate(
|
|
12203
|
+
function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
12500
12204
|
const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
|
|
12501
12205
|
const candidateValue = parsed.options["fail-on-candidates"];
|
|
12502
12206
|
const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
|
|
12503
12207
|
const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
|
|
12504
|
-
const confirmed =
|
|
12505
|
-
const leads = failOnCandidates ?
|
|
12506
|
-
const incomplete =
|
|
12208
|
+
const confirmed = report4.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
|
|
12209
|
+
const leads = failOnCandidates ? report4.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
|
|
12210
|
+
const incomplete = report4.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
|
|
12507
12211
|
if (confirmed.length || leads.length || incomplete) {
|
|
12508
|
-
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${
|
|
12212
|
+
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report4.coverageStatus}` : ""}`);
|
|
12509
12213
|
}
|
|
12510
12214
|
if (emitSuccess) {
|
|
12511
|
-
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${
|
|
12215
|
+
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
12512
12216
|
}
|
|
12513
12217
|
}
|
|
12514
12218
|
function printHostedSecurityPlanRoute(out, label, route2) {
|
|
@@ -12591,17 +12295,17 @@ async function runHostedSecurity(options) {
|
|
|
12591
12295
|
allowNetwork: false
|
|
12592
12296
|
}
|
|
12593
12297
|
});
|
|
12594
|
-
const
|
|
12595
|
-
await (0, import_node3.writeSecurityArtifacts)(output,
|
|
12596
|
-
const reportDigest = await (0, import_security.securityFingerprint)(
|
|
12298
|
+
const report4 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
|
|
12299
|
+
await (0, import_node3.writeSecurityArtifacts)(output, report4);
|
|
12300
|
+
const reportDigest = await (0, import_security.securityFingerprint)(report4);
|
|
12597
12301
|
await hosted.complete({
|
|
12598
12302
|
reportDigest,
|
|
12599
|
-
coverageStatus:
|
|
12600
|
-
confirmed:
|
|
12601
|
-
candidates:
|
|
12303
|
+
coverageStatus: report4.coverageStatus,
|
|
12304
|
+
confirmed: report4.metrics.confirmed,
|
|
12305
|
+
candidates: report4.metrics.candidates
|
|
12602
12306
|
}, { signal: options.signal });
|
|
12603
|
-
printSummary(options.stdout ?? console, appId, env, hosted.run,
|
|
12604
|
-
return Object.freeze({ report:
|
|
12307
|
+
printSummary(options.stdout ?? console, appId, env, hosted.run, report4, output);
|
|
12308
|
+
return Object.freeze({ report: report4, run: hosted.run, output });
|
|
12605
12309
|
}
|
|
12606
12310
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
12607
12311
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
@@ -12627,14 +12331,14 @@ function profileFor(name, maxHuntTasks) {
|
|
|
12627
12331
|
if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
|
|
12628
12332
|
return { ...profile, maxHuntTasks };
|
|
12629
12333
|
}
|
|
12630
|
-
function printSummary(out, appId, env, run,
|
|
12631
|
-
const complete =
|
|
12334
|
+
function printSummary(out, appId, env, run, report4, output) {
|
|
12335
|
+
const complete = report4.coverage.filter((cell) => cell.state === "complete").length;
|
|
12632
12336
|
out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
|
|
12633
12337
|
out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
|
|
12634
12338
|
out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
|
|
12635
|
-
out.log(` coverage: ${
|
|
12636
|
-
if (
|
|
12637
|
-
out.log(` findings: confirmed=${
|
|
12339
|
+
out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
12340
|
+
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
12341
|
+
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
12638
12342
|
out.log(` report: ${(0, import_node_path19.resolve)(output, "REPORT.md")}`);
|
|
12639
12343
|
}
|
|
12640
12344
|
function formatBudget(usage) {
|
|
@@ -12881,13 +12585,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
|
|
|
12881
12585
|
}
|
|
12882
12586
|
throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
|
|
12883
12587
|
}
|
|
12884
|
-
const
|
|
12588
|
+
const report4 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
|
|
12885
12589
|
if (parsed.options.json === true) {
|
|
12886
|
-
context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report:
|
|
12590
|
+
context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report4 }, null, 2));
|
|
12887
12591
|
} else {
|
|
12888
|
-
printHostedReport(context.stdout,
|
|
12592
|
+
printHostedReport(context.stdout, report4);
|
|
12889
12593
|
}
|
|
12890
|
-
enforceHostedReportGate(
|
|
12594
|
+
enforceHostedReportGate(report4, parsed, context.stdout, parsed.options.json !== true);
|
|
12891
12595
|
}
|
|
12892
12596
|
async function runLocalSecurityCommand(parsed, dependencies) {
|
|
12893
12597
|
if (parsed.options.source === true) {
|
|
@@ -12948,19 +12652,20 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
12948
12652
|
cfg,
|
|
12949
12653
|
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
12950
12654
|
doFetch,
|
|
12951
|
-
out
|
|
12655
|
+
out,
|
|
12656
|
+
{ optionalProjectCapabilities: ["app.manage"] }
|
|
12952
12657
|
);
|
|
12953
12658
|
}
|
|
12954
12659
|
});
|
|
12955
12660
|
enforceLocalGate(result.report, parsed);
|
|
12956
12661
|
}
|
|
12957
|
-
function enforceLocalGate(
|
|
12662
|
+
function enforceLocalGate(report4, parsed) {
|
|
12958
12663
|
const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
|
|
12959
12664
|
const candidateValue = parsed.options["fail-on-candidates"];
|
|
12960
12665
|
const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
|
|
12961
|
-
const confirmed = (0, import_security2.findingsAtOrAbove)(
|
|
12962
|
-
const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(
|
|
12963
|
-
const incomplete =
|
|
12666
|
+
const confirmed = (0, import_security2.findingsAtOrAbove)(report4, failOn);
|
|
12667
|
+
const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(report4, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
|
|
12668
|
+
const incomplete = report4.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
|
|
12964
12669
|
if (confirmed.length || leads.length || incomplete) {
|
|
12965
12670
|
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
|
|
12966
12671
|
}
|
|
@@ -12999,9 +12704,9 @@ async function securityCommand(parsed, dependencies) {
|
|
|
12999
12704
|
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
|
|
13000
12705
|
const jobId = requiredSecurityPositional(parsed, 2, "job id");
|
|
13001
12706
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
13002
|
-
const
|
|
13003
|
-
if (parsed.options.json === true) context.stdout.log(JSON.stringify(
|
|
13004
|
-
else printHostedReport(context.stdout,
|
|
12707
|
+
const report4 = await getHostedSecurityReport({ ...context, jobId });
|
|
12708
|
+
if (parsed.options.json === true) context.stdout.log(JSON.stringify(report4, null, 2));
|
|
12709
|
+
else printHostedReport(context.stdout, report4);
|
|
13005
12710
|
return;
|
|
13006
12711
|
}
|
|
13007
12712
|
if (sub !== "run") {
|
|
@@ -13015,35 +12720,24 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
13015
12720
|
const action2 = parsed.positionals[2];
|
|
13016
12721
|
if (action2 === "disconnect") {
|
|
13017
12722
|
assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
|
|
13018
|
-
const context2 = await hostedSecurityContext(parsed, dependencies);
|
|
13019
12723
|
const sourceId = requiredString(parsed.options.source, "--source");
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
12724
|
+
return requireStudioHuman(
|
|
12725
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
12726
|
+
`disconnecting GitHub security source ${sourceId}`,
|
|
12727
|
+
"security",
|
|
12728
|
+
stringOpt(parsed.options.env)
|
|
13023
12729
|
);
|
|
13024
|
-
if (!confirmed) {
|
|
13025
|
-
throw new Error("GitHub source disconnect cancelled; pass --yes in a non-interactive shell");
|
|
13026
|
-
}
|
|
13027
|
-
await disconnectGitHubSecuritySource({ ...context2, sourceId });
|
|
13028
|
-
context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
|
|
13029
|
-
return;
|
|
13030
12730
|
}
|
|
13031
12731
|
if (action2 !== "connect") {
|
|
13032
12732
|
throw new Error('unknown security github command. Try "odla-ai security github connect".');
|
|
13033
12733
|
}
|
|
13034
12734
|
assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
openInstallUrl: dependencies.openUrl ?? openUrl,
|
|
13042
|
-
wait: dependencies.pollWait,
|
|
13043
|
-
stdout: context.stdout
|
|
13044
|
-
});
|
|
13045
|
-
context.stdout.log(`github: connected ${connection.repository ?? repository ?? "app repository"} (${connection.sourceId ?? "source pending"})`);
|
|
13046
|
-
context.stdout.log("github: odla.ai stores the installation; no PAT or GitHub token is written locally");
|
|
12735
|
+
await requireStudioHuman(
|
|
12736
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
12737
|
+
"connecting the GitHub security repository",
|
|
12738
|
+
"security",
|
|
12739
|
+
stringOpt(parsed.options.env)
|
|
12740
|
+
);
|
|
13047
12741
|
}
|
|
13048
12742
|
async function listSecuritySources(parsed, dependencies) {
|
|
13049
12743
|
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 2);
|