@kontextmind/kxm 0.7.165 → 0.7.167
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +16 -0
- package/docs/README.md +1 -0
- package/docs/architecture/hosted-direction.md +9 -4
- package/docs/architecture/inventory.md +1 -0
- package/docs/concepts/trust-model.md +1 -1
- package/docs/contributing/decisions.md +153 -0
- package/docs/contributing/learnings.md +54 -53
- package/docs/contributing/operating-rules.md +19 -6
- package/docs/glossary.md +2 -2
- package/docs/operations/troubleshooting.md +2 -2
- package/docs/operations.md +62 -8
- package/docs/reference/cli-reference.md +11 -11
- package/docs/reference/config-reference.md +36 -11
- package/docs/reference/configuration.md +2 -2
- package/docs/reference/tools.md +1 -1
- package/docs/start/quickstart-claude-code.md +2 -2
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +3 -3
- package/plugins/kxm/dist/claude-hook.js +417 -66
- package/plugins/kxm/dist/cli.js +1399 -900
- package/plugins/kxm/dist/extension.js +399 -64
- package/plugins/kxm/dist/mcp-server.js +622 -135
- package/plugins/kxm/dist/runtime-supervisor.js +668 -166
- package/plugins/kxm/dist/runtime.js +713 -211
- package/plugins/kxm/dist/server.js +466 -58
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +15 -7
- package/plugins/kxm/src/cli/hub.ts +80 -21
- package/plugins/kxm/src/cli/types.ts +8 -1
- package/plugins/kxm/src/cli.ts +41 -7
- package/plugins/kxm/src/config.ts +246 -3
- package/plugins/kxm/src/extension.ts +8 -7
- package/plugins/kxm/src/hub-autostart.ts +20 -0
- package/plugins/kxm/src/hub-env.ts +83 -31
- package/plugins/kxm/src/hub-identity.ts +337 -0
- package/plugins/kxm/src/mcp-server.ts +17 -15
- package/plugins/kxm/src/project-name.ts +6 -47
- package/plugins/kxm/src/server.ts +22 -4
- package/scripts/kxm-hub.mjs +5 -0
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -1650,284 +1650,6 @@ var init_client = __esm({
|
|
|
1650
1650
|
}
|
|
1651
1651
|
});
|
|
1652
1652
|
|
|
1653
|
-
// plugins/kxm/src/hub-binding.ts
|
|
1654
|
-
import { spawnSync } from "node:child_process";
|
|
1655
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1656
|
-
import { homedir } from "node:os";
|
|
1657
|
-
import { dirname as dirname2, isAbsolute, join as join3, resolve } from "node:path";
|
|
1658
|
-
function resolveUserStateRoot(env) {
|
|
1659
|
-
const explicit = env.KXM_STATE_HOME?.trim();
|
|
1660
|
-
if (explicit) {
|
|
1661
|
-
if (!isAbsolute(explicit)) throw new HubBindingError("local_state_root_not_absolute");
|
|
1662
|
-
return resolve(explicit);
|
|
1663
|
-
}
|
|
1664
|
-
if (process.platform === "win32") {
|
|
1665
|
-
const localAppData = env.LOCALAPPDATA?.trim();
|
|
1666
|
-
const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join3(homedir(), "AppData", "Local");
|
|
1667
|
-
return resolve(base2, "KXM");
|
|
1668
|
-
}
|
|
1669
|
-
if (process.platform === "darwin") return resolve(homedir(), "Library", "Application Support", "KXM");
|
|
1670
|
-
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
1671
|
-
const base = xdgState && isAbsolute(xdgState) ? xdgState : join3(homedir(), ".local", "state");
|
|
1672
|
-
return resolve(base, "kxm");
|
|
1673
|
-
}
|
|
1674
|
-
function hubBindingFile(env = process.env) {
|
|
1675
|
-
return join3(resolveUserStateRoot(env), "hub-binding.json");
|
|
1676
|
-
}
|
|
1677
|
-
function validateHubUrl(raw) {
|
|
1678
|
-
let parsed;
|
|
1679
|
-
try {
|
|
1680
|
-
parsed = new URL(raw);
|
|
1681
|
-
} catch {
|
|
1682
|
-
throw new HubBindingError("hub_url_invalid");
|
|
1683
|
-
}
|
|
1684
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || raw.includes("?") || raw.includes("#")) {
|
|
1685
|
-
throw new HubBindingError("hub_url_invalid");
|
|
1686
|
-
}
|
|
1687
|
-
return parsed.href.replace(/\/$/, "");
|
|
1688
|
-
}
|
|
1689
|
-
function splitTokenCommand(raw) {
|
|
1690
|
-
if (/[|&;<>$`\n\r]/.test(raw)) {
|
|
1691
|
-
throw new CloudTokenError(
|
|
1692
|
-
"cloud_token_command_invalid",
|
|
1693
|
-
"token command must be a program and arguments, not a shell pipeline"
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
const args = [];
|
|
1697
|
-
let current = "";
|
|
1698
|
-
let quote;
|
|
1699
|
-
for (const ch of raw) {
|
|
1700
|
-
if (quote) {
|
|
1701
|
-
if (ch === quote) quote = void 0;
|
|
1702
|
-
else current += ch;
|
|
1703
|
-
continue;
|
|
1704
|
-
}
|
|
1705
|
-
if (ch === "'" || ch === '"') {
|
|
1706
|
-
quote = ch;
|
|
1707
|
-
continue;
|
|
1708
|
-
}
|
|
1709
|
-
if (ch === " " || ch === " ") {
|
|
1710
|
-
if (current.length > 0) {
|
|
1711
|
-
args.push(current);
|
|
1712
|
-
current = "";
|
|
1713
|
-
}
|
|
1714
|
-
continue;
|
|
1715
|
-
}
|
|
1716
|
-
current += ch;
|
|
1717
|
-
}
|
|
1718
|
-
if (quote) {
|
|
1719
|
-
throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
|
|
1720
|
-
}
|
|
1721
|
-
if (current.length > 0) args.push(current);
|
|
1722
|
-
if (args.length === 0) {
|
|
1723
|
-
throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
|
|
1724
|
-
}
|
|
1725
|
-
return args;
|
|
1726
|
-
}
|
|
1727
|
-
function isCloudTokenEnvName(value) {
|
|
1728
|
-
return TOKEN_ENV_NAME.test(value);
|
|
1729
|
-
}
|
|
1730
|
-
function effectiveHubBindingScope(url, env = process.env) {
|
|
1731
|
-
try {
|
|
1732
|
-
const binding = readHubBinding(env);
|
|
1733
|
-
if (binding?.cloud) {
|
|
1734
|
-
try {
|
|
1735
|
-
if (validateHubUrl(url) === binding.url) return "remote";
|
|
1736
|
-
} catch {
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
} catch (error) {
|
|
1740
|
-
if (!(error instanceof HubBindingError)) throw error;
|
|
1741
|
-
}
|
|
1742
|
-
return hubBindingScope(url);
|
|
1743
|
-
}
|
|
1744
|
-
function resolveCloudHubToken(binding, env = process.env) {
|
|
1745
|
-
if (!binding.cloud) {
|
|
1746
|
-
throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
|
|
1747
|
-
}
|
|
1748
|
-
if (binding.tokenEnv) {
|
|
1749
|
-
const value = env[binding.tokenEnv]?.trim();
|
|
1750
|
-
if (value) return value;
|
|
1751
|
-
}
|
|
1752
|
-
if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
|
|
1753
|
-
const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
|
|
1754
|
-
throw new CloudTokenError(
|
|
1755
|
-
"cloud_token_missing",
|
|
1756
|
-
`cloud hub token is missing: ${source}; the local hub-env token was not used`
|
|
1757
|
-
);
|
|
1758
|
-
}
|
|
1759
|
-
function runTokenCommand(command, env) {
|
|
1760
|
-
const argv = splitTokenCommand(command);
|
|
1761
|
-
const [program2, ...args] = argv;
|
|
1762
|
-
const result = spawnSync(program2, args, {
|
|
1763
|
-
encoding: "utf8",
|
|
1764
|
-
shell: false,
|
|
1765
|
-
timeout: TOKEN_COMMAND_TIMEOUT_MS,
|
|
1766
|
-
maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
|
|
1767
|
-
env,
|
|
1768
|
-
windowsHide: true
|
|
1769
|
-
});
|
|
1770
|
-
if (result.error || result.status !== 0) {
|
|
1771
|
-
throw new CloudTokenError(
|
|
1772
|
-
"cloud_token_command_failed",
|
|
1773
|
-
"token command failed; the local hub-env token was not used"
|
|
1774
|
-
);
|
|
1775
|
-
}
|
|
1776
|
-
const stdout = result.stdout ?? "";
|
|
1777
|
-
if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
|
|
1778
|
-
throw new CloudTokenError(
|
|
1779
|
-
"cloud_token_command_failed",
|
|
1780
|
-
"token command did not print a single token; the local hub-env token was not used"
|
|
1781
|
-
);
|
|
1782
|
-
}
|
|
1783
|
-
return stdout.trim();
|
|
1784
|
-
}
|
|
1785
|
-
function hubBindingScope(url) {
|
|
1786
|
-
let host;
|
|
1787
|
-
try {
|
|
1788
|
-
host = new URL(url).hostname.toLowerCase();
|
|
1789
|
-
} catch {
|
|
1790
|
-
return "remote";
|
|
1791
|
-
}
|
|
1792
|
-
if (host === "localhost" || host === "::1" || host === "[::1]" || host.endsWith(".localhost")) return "loopback";
|
|
1793
|
-
const v4 = /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(host);
|
|
1794
|
-
return v4 && [v4[1], v4[2], v4[3]].every((part) => Number(part) <= 255) ? "loopback" : "remote";
|
|
1795
|
-
}
|
|
1796
|
-
function isIsoTimestamp(value) {
|
|
1797
|
-
if (Number.isNaN(Date.parse(value))) return false;
|
|
1798
|
-
return value === new Date(value).toISOString();
|
|
1799
|
-
}
|
|
1800
|
-
function isAbortError(error) {
|
|
1801
|
-
return Boolean(
|
|
1802
|
-
error && typeof error === "object" && ("name" in error && error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR")
|
|
1803
|
-
);
|
|
1804
|
-
}
|
|
1805
|
-
function readHubBinding(env = process.env) {
|
|
1806
|
-
const file = hubBindingFile(env);
|
|
1807
|
-
if (!existsSync3(file)) return void 0;
|
|
1808
|
-
let parsed;
|
|
1809
|
-
try {
|
|
1810
|
-
parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
1811
|
-
} catch {
|
|
1812
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1813
|
-
}
|
|
1814
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1815
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1816
|
-
}
|
|
1817
|
-
const row = parsed;
|
|
1818
|
-
const keys = Object.keys(row);
|
|
1819
|
-
const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
|
|
1820
|
-
const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
|
|
1821
|
-
if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
|
|
1822
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1823
|
-
}
|
|
1824
|
-
let url;
|
|
1825
|
-
try {
|
|
1826
|
-
url = validateHubUrl(row.url);
|
|
1827
|
-
} catch {
|
|
1828
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1829
|
-
}
|
|
1830
|
-
if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
|
|
1831
|
-
if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1832
|
-
let tokenEnv;
|
|
1833
|
-
let tokenCommand;
|
|
1834
|
-
if (row.tokenEnv !== void 0) {
|
|
1835
|
-
if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
|
|
1836
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1837
|
-
}
|
|
1838
|
-
tokenEnv = row.tokenEnv;
|
|
1839
|
-
}
|
|
1840
|
-
if (row.tokenCommand !== void 0) {
|
|
1841
|
-
if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1842
|
-
try {
|
|
1843
|
-
splitTokenCommand(row.tokenCommand);
|
|
1844
|
-
} catch {
|
|
1845
|
-
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1846
|
-
}
|
|
1847
|
-
tokenCommand = row.tokenCommand;
|
|
1848
|
-
}
|
|
1849
|
-
if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
1850
|
-
return {
|
|
1851
|
-
schema: HUB_BINDING_SCHEMA,
|
|
1852
|
-
url,
|
|
1853
|
-
boundAt: row.boundAt,
|
|
1854
|
-
cloud: true,
|
|
1855
|
-
...tokenEnv ? { tokenEnv } : {},
|
|
1856
|
-
...tokenCommand ? { tokenCommand } : {}
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
function writeHubBinding(record, env = process.env) {
|
|
1860
|
-
const file = hubBindingFile(env);
|
|
1861
|
-
mkdirSync2(dirname2(file), { recursive: true, mode: 448 });
|
|
1862
|
-
const temporary = join3(dirname2(file), `.hub-binding-${process.pid}.tmp`);
|
|
1863
|
-
try {
|
|
1864
|
-
writeFileSync2(temporary, `${JSON.stringify(record, null, 2)}
|
|
1865
|
-
`, { encoding: "utf8", mode: 384 });
|
|
1866
|
-
renameSync(temporary, file);
|
|
1867
|
-
} finally {
|
|
1868
|
-
rmSync(temporary, { force: true });
|
|
1869
|
-
}
|
|
1870
|
-
return file;
|
|
1871
|
-
}
|
|
1872
|
-
function removeHubBinding(env = process.env) {
|
|
1873
|
-
const file = hubBindingFile(env);
|
|
1874
|
-
try {
|
|
1875
|
-
rmSync(file);
|
|
1876
|
-
return true;
|
|
1877
|
-
} catch (error) {
|
|
1878
|
-
if (error.code === "ENOENT") return false;
|
|
1879
|
-
throw error;
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
|
|
1883
|
-
const started = Date.now();
|
|
1884
|
-
const controller = new AbortController();
|
|
1885
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1886
|
-
try {
|
|
1887
|
-
const response = await fetchImpl(`${url}/health`, { signal: controller.signal });
|
|
1888
|
-
if (!response.ok) return { health: "unknown", probeMs: Date.now() - started };
|
|
1889
|
-
let body;
|
|
1890
|
-
try {
|
|
1891
|
-
body = await response.json();
|
|
1892
|
-
} catch {
|
|
1893
|
-
return { health: "unknown", probeMs: Date.now() - started };
|
|
1894
|
-
}
|
|
1895
|
-
if (body && typeof body === "object" && body.ok === true) {
|
|
1896
|
-
return { health: "on", probeMs: Date.now() - started };
|
|
1897
|
-
}
|
|
1898
|
-
return { health: "unknown", probeMs: Date.now() - started };
|
|
1899
|
-
} catch (error) {
|
|
1900
|
-
return { health: isAbortError(error) ? "unknown" : "off", probeMs: Date.now() - started };
|
|
1901
|
-
} finally {
|
|
1902
|
-
clearTimeout(timer);
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, CloudTokenError, TOKEN_ENV_NAME, TOKEN_COMMAND_TIMEOUT_MS, TOKEN_COMMAND_MAX_BUFFER, HubBindingError;
|
|
1906
|
-
var init_hub_binding = __esm({
|
|
1907
|
-
"plugins/kxm/src/hub-binding.ts"() {
|
|
1908
|
-
"use strict";
|
|
1909
|
-
HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
1910
|
-
HUB_HEALTH_PROBE_MS = 300;
|
|
1911
|
-
CloudTokenError = class extends Error {
|
|
1912
|
-
code;
|
|
1913
|
-
constructor(code, message) {
|
|
1914
|
-
super(message);
|
|
1915
|
-
this.name = "CloudTokenError";
|
|
1916
|
-
this.code = code;
|
|
1917
|
-
}
|
|
1918
|
-
};
|
|
1919
|
-
TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
1920
|
-
TOKEN_COMMAND_TIMEOUT_MS = 15e3;
|
|
1921
|
-
TOKEN_COMMAND_MAX_BUFFER = 4096;
|
|
1922
|
-
HubBindingError = class extends Error {
|
|
1923
|
-
constructor(message) {
|
|
1924
|
-
super(message);
|
|
1925
|
-
this.name = "HubBindingError";
|
|
1926
|
-
}
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
});
|
|
1930
|
-
|
|
1931
1653
|
// node_modules/yaml/dist/nodes/identity.js
|
|
1932
1654
|
var require_identity = __commonJS({
|
|
1933
1655
|
"node_modules/yaml/dist/nodes/identity.js"(exports) {
|
|
@@ -9258,30 +8980,730 @@ var require_dist = __commonJS({
|
|
|
9258
8980
|
}
|
|
9259
8981
|
});
|
|
9260
8982
|
|
|
9261
|
-
// plugins/kxm/src/
|
|
9262
|
-
import { existsSync as
|
|
9263
|
-
import {
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
8983
|
+
// plugins/kxm/src/config.ts
|
|
8984
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8985
|
+
import { tmpdir, homedir } from "node:os";
|
|
8986
|
+
import { dirname as dirname2, join as join3, resolve } from "node:path";
|
|
8987
|
+
function isHubKeyEnvName(value) {
|
|
8988
|
+
return HUB_ENV_NAME.test(value);
|
|
8989
|
+
}
|
|
8990
|
+
function isHubOpReference(value) {
|
|
8991
|
+
return HUB_OP_REF.test(value);
|
|
8992
|
+
}
|
|
8993
|
+
function userConfigDirectory(overrideDir) {
|
|
8994
|
+
if (overrideDir) return resolve(overrideDir);
|
|
8995
|
+
return resolve(process.env.KXM_USER_CONFIG_DIR?.trim() || join3(homedir(), ".config", "kxm"));
|
|
8996
|
+
}
|
|
8997
|
+
function repoConfigDirectory(repoRoot) {
|
|
8998
|
+
return resolve(repoRoot, ".kxm");
|
|
8999
|
+
}
|
|
9000
|
+
function deepMerge(target, source) {
|
|
9001
|
+
const result = { ...target };
|
|
9002
|
+
for (const [key, val] of Object.entries(source)) {
|
|
9003
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
9004
|
+
const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
|
|
9005
|
+
result[key] = deepMerge(existing, val);
|
|
9006
|
+
} else if (val !== void 0) {
|
|
9007
|
+
result[key] = val;
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
return result;
|
|
9011
|
+
}
|
|
9012
|
+
function literalSecretMessage(path4) {
|
|
9013
|
+
return `refusing literal hub token at ${path4}: store an op:// reference (key.op) or an environment variable name (key.env), never the token`;
|
|
9014
|
+
}
|
|
9015
|
+
function rejectLiteralHubSecrets(value, path4) {
|
|
9016
|
+
if (Array.isArray(value)) {
|
|
9017
|
+
value.forEach((item, index) => rejectLiteralHubSecrets(item, `${path4}[${index}]`));
|
|
9018
|
+
return;
|
|
9019
|
+
}
|
|
9020
|
+
if (!value || typeof value !== "object") return;
|
|
9021
|
+
for (const [key, child] of Object.entries(value)) {
|
|
9022
|
+
const childPath = `${path4}.${key}`;
|
|
9023
|
+
if (typeof child === "string" && (key === "key" || LITERAL_SECRET_KEY.test(key))) {
|
|
9024
|
+
throw new KxmHubConfigError(literalSecretMessage(childPath));
|
|
9025
|
+
}
|
|
9026
|
+
if (child && typeof child === "object") rejectLiteralHubSecrets(child, childPath);
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
function parseKeyRef(raw, path4) {
|
|
9030
|
+
if (raw === void 0) return void 0;
|
|
9031
|
+
if (typeof raw === "string") throw new KxmHubConfigError(literalSecretMessage(path4));
|
|
9032
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
9033
|
+
throw new KxmHubConfigError(`${path4} must be a key reference with op and/or env, never a token`);
|
|
9034
|
+
}
|
|
9035
|
+
const row = raw;
|
|
9036
|
+
for (const key of Object.keys(row)) {
|
|
9037
|
+
if (key !== "op" && key !== "env") {
|
|
9038
|
+
throw new KxmHubConfigError(literalSecretMessage(`${path4}.${key}`));
|
|
9039
|
+
}
|
|
9040
|
+
}
|
|
9041
|
+
const ref = {};
|
|
9042
|
+
if (row.op !== void 0) {
|
|
9043
|
+
if (typeof row.op !== "string" || !isHubOpReference(row.op)) {
|
|
9044
|
+
throw new KxmHubConfigError(`${path4}.op must be an op://vault/item/field reference, not a token`);
|
|
9045
|
+
}
|
|
9046
|
+
ref.op = row.op;
|
|
9047
|
+
}
|
|
9048
|
+
if (row.env !== void 0) {
|
|
9049
|
+
if (typeof row.env !== "string" || !isHubKeyEnvName(row.env)) {
|
|
9050
|
+
throw new KxmHubConfigError(`${path4}.env must be an environment variable name, not a token`);
|
|
9051
|
+
}
|
|
9052
|
+
ref.env = row.env;
|
|
9053
|
+
}
|
|
9054
|
+
if (!ref.op && !ref.env) throw new KxmHubConfigError(`${path4} needs key.op, key.env, or both`);
|
|
9055
|
+
return ref;
|
|
9056
|
+
}
|
|
9057
|
+
function parseEndpoint(raw, path4) {
|
|
9058
|
+
if (raw === void 0) return void 0;
|
|
9059
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
9060
|
+
throw new KxmHubConfigError(`${path4} must be an object with url, project, and key`);
|
|
9061
|
+
}
|
|
9062
|
+
const row = raw;
|
|
9063
|
+
const endpoint = {};
|
|
9064
|
+
if (row.url !== void 0) {
|
|
9065
|
+
if (typeof row.url !== "string" || !isHubUrl(row.url)) {
|
|
9066
|
+
throw new KxmHubConfigError(`${path4}.url must be an http or https URL without credentials, query, or fragment`);
|
|
9067
|
+
}
|
|
9068
|
+
endpoint.url = row.url.replace(/\/$/, "");
|
|
9069
|
+
}
|
|
9070
|
+
if (row.project !== void 0) {
|
|
9071
|
+
if (typeof row.project !== "string" || !isHubProjectId(row.project)) {
|
|
9072
|
+
throw new KxmHubConfigError(`${path4}.project must be a single-line project id, not a token`);
|
|
9073
|
+
}
|
|
9074
|
+
endpoint.project = row.project.trim();
|
|
9075
|
+
}
|
|
9076
|
+
const key = parseKeyRef(row.key, `${path4}.key`);
|
|
9077
|
+
if (key) endpoint.key = key;
|
|
9078
|
+
for (const name of Object.keys(row)) {
|
|
9079
|
+
if (name !== "url" && name !== "project" && name !== "key") {
|
|
9080
|
+
if (typeof row[name] === "string") throw new KxmHubConfigError(literalSecretMessage(`${path4}.${name}`));
|
|
9081
|
+
throw new KxmHubConfigError(`${path4}.${name} is not a hub endpoint field`);
|
|
9082
|
+
}
|
|
9083
|
+
}
|
|
9084
|
+
return endpoint;
|
|
9085
|
+
}
|
|
9086
|
+
function isHubUrl(raw) {
|
|
9271
9087
|
try {
|
|
9272
|
-
const
|
|
9273
|
-
|
|
9088
|
+
const parsed = new URL(raw);
|
|
9089
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && !raw.includes("?") && !raw.includes("#");
|
|
9274
9090
|
} catch {
|
|
9091
|
+
return false;
|
|
9092
|
+
}
|
|
9093
|
+
}
|
|
9094
|
+
function isHubProjectId(raw) {
|
|
9095
|
+
const value = raw.trim();
|
|
9096
|
+
return value.length > 0 && value.length <= 200 && !/[\s\r\n]/.test(value);
|
|
9097
|
+
}
|
|
9098
|
+
function parseProjectMap(raw) {
|
|
9099
|
+
if (raw === void 0) return void 0;
|
|
9100
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
9101
|
+
throw new KxmHubConfigError("hub.projects must be a map of project id to key reference, never a token");
|
|
9102
|
+
}
|
|
9103
|
+
const projects = {};
|
|
9104
|
+
for (const [project, value] of Object.entries(raw)) {
|
|
9105
|
+
if (!isHubProjectId(project)) {
|
|
9106
|
+
throw new KxmHubConfigError(`hub.projects key ${JSON.stringify(project)} must be a project id, not a token`);
|
|
9107
|
+
}
|
|
9108
|
+
const ref = parseKeyRef(value, `hub.projects.${project}`);
|
|
9109
|
+
if (!ref) throw new KxmHubConfigError(`hub.projects.${project} needs an op:// reference or an environment variable name`);
|
|
9110
|
+
projects[project.trim()] = ref;
|
|
9275
9111
|
}
|
|
9276
|
-
return
|
|
9112
|
+
return projects;
|
|
9277
9113
|
}
|
|
9278
|
-
function
|
|
9279
|
-
|
|
9114
|
+
function normalizeHubConfig(raw) {
|
|
9115
|
+
const autoStart = raw?.autoStart;
|
|
9116
|
+
const hub = {
|
|
9117
|
+
autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart
|
|
9118
|
+
};
|
|
9119
|
+
if (raw === void 0) return hub;
|
|
9120
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
9121
|
+
throw new KxmHubConfigError("hub must be an object");
|
|
9122
|
+
}
|
|
9123
|
+
rejectLiteralHubSecrets(raw, "hub");
|
|
9124
|
+
const row = raw;
|
|
9125
|
+
if (row.mode !== void 0) {
|
|
9126
|
+
if (row.mode !== "local" && row.mode !== "cloud") {
|
|
9127
|
+
throw new KxmHubConfigError("hub.mode must be local or cloud");
|
|
9128
|
+
}
|
|
9129
|
+
hub.mode = row.mode;
|
|
9130
|
+
}
|
|
9131
|
+
const local = parseEndpoint(row.local, "hub.local");
|
|
9132
|
+
const cloud = parseEndpoint(row.cloud, "hub.cloud");
|
|
9133
|
+
const projects = parseProjectMap(row.projects);
|
|
9134
|
+
if (local) hub.local = local;
|
|
9135
|
+
if (cloud) hub.cloud = cloud;
|
|
9136
|
+
if (projects && Object.keys(projects).length > 0) hub.projects = projects;
|
|
9137
|
+
return hub;
|
|
9138
|
+
}
|
|
9139
|
+
function findConfigRoot(start) {
|
|
9140
|
+
let dir = resolve(start);
|
|
9280
9141
|
for (; ; ) {
|
|
9281
|
-
|
|
9282
|
-
|
|
9142
|
+
if (existsSync3(join3(dir, ".kxm", "project.yaml")) || existsSync3(join3(dir, ".kxm", "config.yaml"))) return dir;
|
|
9143
|
+
const parent = dirname2(dir);
|
|
9144
|
+
if (parent === dir) return resolve(start);
|
|
9145
|
+
dir = parent;
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
function userConfigDirForEnv(env) {
|
|
9149
|
+
const explicit = env.KXM_USER_CONFIG_DIR?.trim();
|
|
9150
|
+
if (explicit) return explicit;
|
|
9151
|
+
if (env === process.env) return void 0;
|
|
9152
|
+
return join3(tmpdir(), "kxm-no-user-config");
|
|
9153
|
+
}
|
|
9154
|
+
function loadHubSettings(start, env = process.env) {
|
|
9155
|
+
const userConfigDir = userConfigDirForEnv(env);
|
|
9156
|
+
return loadKxmConfig(findConfigRoot(start), userConfigDir === void 0 ? {} : { userConfigDir }).hub;
|
|
9157
|
+
}
|
|
9158
|
+
function recordOf(raw) {
|
|
9159
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
9160
|
+
}
|
|
9161
|
+
function finiteNumber(value) {
|
|
9162
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
9163
|
+
}
|
|
9164
|
+
function normalizeImprovementConfig(raw) {
|
|
9165
|
+
const value = recordOf(raw);
|
|
9166
|
+
const threshold = recordOf(value.autoThreshold);
|
|
9167
|
+
const policy = value.promotionPolicy;
|
|
9168
|
+
const halfLife = finiteNumber(value.telemetryHalfLifeDays);
|
|
9169
|
+
const minRuns = finiteNumber(threshold.minRuns);
|
|
9170
|
+
const minPassRate = finiteNumber(threshold.minPassRate);
|
|
9171
|
+
const minCostSavings = finiteNumber(threshold.minCostSavings);
|
|
9172
|
+
return {
|
|
9173
|
+
promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
|
|
9174
|
+
telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
|
|
9175
|
+
autoThreshold: {
|
|
9176
|
+
minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
|
|
9177
|
+
minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
|
|
9178
|
+
minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
|
|
9179
|
+
}
|
|
9180
|
+
};
|
|
9181
|
+
}
|
|
9182
|
+
function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
9183
|
+
const userDir = userConfigDirectory(options.userConfigDir);
|
|
9184
|
+
const userConfigFile = join3(userDir, "config.yaml");
|
|
9185
|
+
const repoDir = repoConfigDirectory(repoRoot);
|
|
9186
|
+
const repoConfigFile = join3(repoDir, "config.yaml");
|
|
9187
|
+
let userRaw = {};
|
|
9188
|
+
let userLoadedPath;
|
|
9189
|
+
if (existsSync3(userConfigFile)) {
|
|
9190
|
+
try {
|
|
9191
|
+
const text = readFileSync2(userConfigFile, "utf8");
|
|
9192
|
+
userRaw = (0, import_yaml.parse)(text) ?? {};
|
|
9193
|
+
userLoadedPath = userConfigFile;
|
|
9194
|
+
} catch (error) {
|
|
9195
|
+
throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
let repoRaw = {};
|
|
9199
|
+
let repoLoadedPath;
|
|
9200
|
+
if (existsSync3(repoConfigFile)) {
|
|
9201
|
+
try {
|
|
9202
|
+
const text = readFileSync2(repoConfigFile, "utf8");
|
|
9203
|
+
repoRaw = (0, import_yaml.parse)(text) ?? {};
|
|
9204
|
+
repoLoadedPath = repoConfigFile;
|
|
9205
|
+
} catch (error) {
|
|
9206
|
+
throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
|
|
9210
|
+
const mergedUser = deepMerge(baseCopy, userRaw);
|
|
9211
|
+
const mergedAll = deepMerge(mergedUser, repoRaw);
|
|
9212
|
+
return {
|
|
9213
|
+
schema: KXM_CONFIG_SCHEMA,
|
|
9214
|
+
user: mergedAll.user ?? {},
|
|
9215
|
+
defaults: mergedAll.defaults ?? {},
|
|
9216
|
+
dash: mergedAll.dash ?? {},
|
|
9217
|
+
sync: mergedAll.sync ?? {},
|
|
9218
|
+
hub: normalizeHubConfig(mergedAll.hub),
|
|
9219
|
+
improvement: normalizeImprovementConfig(mergedAll.improvement),
|
|
9220
|
+
routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
|
|
9221
|
+
telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
|
|
9222
|
+
loadedFrom: {
|
|
9223
|
+
userConfigPath: userLoadedPath,
|
|
9224
|
+
repoConfigPath: repoLoadedPath
|
|
9225
|
+
}
|
|
9226
|
+
};
|
|
9227
|
+
}
|
|
9228
|
+
function getKxmConfigValue(config, keyPath) {
|
|
9229
|
+
const parts = keyPath.split(".");
|
|
9230
|
+
let current = config;
|
|
9231
|
+
for (const part of parts) {
|
|
9232
|
+
if (!current || typeof current !== "object") return void 0;
|
|
9233
|
+
current = current[part];
|
|
9234
|
+
}
|
|
9235
|
+
return current;
|
|
9236
|
+
}
|
|
9237
|
+
function kxmConfigFileForScope(repoRoot, scope, userConfigDir) {
|
|
9238
|
+
return scope === "user" ? join3(userConfigDirectory(userConfigDir), "config.yaml") : join3(repoConfigDirectory(repoRoot), "config.yaml");
|
|
9239
|
+
}
|
|
9240
|
+
function configKeyParts(keyPath) {
|
|
9241
|
+
const parts = keyPath.split(".");
|
|
9242
|
+
if (parts.some((part) => !part || part === "__proto__" || part === "prototype" || part === "constructor")) {
|
|
9243
|
+
throw new Error("invalid config key path");
|
|
9244
|
+
}
|
|
9245
|
+
return parts;
|
|
9246
|
+
}
|
|
9247
|
+
function readConfigFile(targetFile2) {
|
|
9248
|
+
let existing = {};
|
|
9249
|
+
if (existsSync3(targetFile2)) {
|
|
9250
|
+
try {
|
|
9251
|
+
existing = (0, import_yaml.parse)(readFileSync2(targetFile2, "utf8")) ?? {};
|
|
9252
|
+
} catch {
|
|
9253
|
+
existing = {};
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9256
|
+
return existing;
|
|
9257
|
+
}
|
|
9258
|
+
function writeConfigFile(targetFile2, value) {
|
|
9259
|
+
mkdirSync2(dirname2(targetFile2), { recursive: true });
|
|
9260
|
+
writeFileSync2(targetFile2, (0, import_yaml.stringify)(value).trim() + "\n", "utf8");
|
|
9261
|
+
}
|
|
9262
|
+
function setKxmConfigValue(repoRoot, keyPath, value, options = {}) {
|
|
9263
|
+
const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
|
|
9264
|
+
const existing = readConfigFile(targetFile2);
|
|
9265
|
+
const parts = configKeyParts(keyPath);
|
|
9266
|
+
let cursor2 = existing;
|
|
9267
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
9268
|
+
const p = parts[i];
|
|
9269
|
+
if (!cursor2[p] || typeof cursor2[p] !== "object") {
|
|
9270
|
+
cursor2[p] = {};
|
|
9271
|
+
}
|
|
9272
|
+
cursor2 = cursor2[p];
|
|
9273
|
+
}
|
|
9274
|
+
cursor2[parts[parts.length - 1]] = value;
|
|
9275
|
+
if (parts[0] === "hub") normalizeHubConfig(existing.hub);
|
|
9276
|
+
if (!options.dryRun) writeConfigFile(targetFile2, existing);
|
|
9277
|
+
return { file: targetFile2 };
|
|
9278
|
+
}
|
|
9279
|
+
function writeHubEndpoint(start, endpoint) {
|
|
9280
|
+
if (!isHubUrl(endpoint.url)) {
|
|
9281
|
+
throw new KxmHubConfigError("hub url must be an http or https URL without credentials, query, or fragment");
|
|
9282
|
+
}
|
|
9283
|
+
if (!isHubProjectId(endpoint.project)) {
|
|
9284
|
+
throw new KxmHubConfigError("hub project id must be a single-line project id, not a token");
|
|
9285
|
+
}
|
|
9286
|
+
if (endpoint.key) parseKeyRef(endpoint.key, `hub.${endpoint.mode}.key`);
|
|
9287
|
+
const root = findConfigRoot(start);
|
|
9288
|
+
const file = join3(root, ".kxm", "config.yaml");
|
|
9289
|
+
const existing = readConfigFile(file);
|
|
9290
|
+
const hub = recordOf(existing.hub);
|
|
9291
|
+
const current = recordOf(hub[endpoint.mode]);
|
|
9292
|
+
current.url = endpoint.url.replace(/\/$/, "");
|
|
9293
|
+
current.project = endpoint.project.trim();
|
|
9294
|
+
if (endpoint.key && (endpoint.key.op || endpoint.key.env)) {
|
|
9295
|
+
const key = recordOf(current.key);
|
|
9296
|
+
if (endpoint.key.op) key.op = endpoint.key.op;
|
|
9297
|
+
if (endpoint.key.env) key.env = endpoint.key.env;
|
|
9298
|
+
current.key = key;
|
|
9299
|
+
}
|
|
9300
|
+
hub[endpoint.mode] = current;
|
|
9301
|
+
hub.mode = endpoint.mode;
|
|
9302
|
+
existing.hub = hub;
|
|
9303
|
+
normalizeHubConfig(hub);
|
|
9304
|
+
writeConfigFile(file, existing);
|
|
9305
|
+
return { file };
|
|
9306
|
+
}
|
|
9307
|
+
function deleteKxmConfigValue(repoRoot, keyPath, options = {}) {
|
|
9308
|
+
const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
|
|
9309
|
+
if (!existsSync3(targetFile2)) return false;
|
|
9310
|
+
const existing = readConfigFile(targetFile2);
|
|
9311
|
+
const parts = configKeyParts(keyPath);
|
|
9312
|
+
const parents = [existing];
|
|
9313
|
+
let cursor2 = existing;
|
|
9314
|
+
for (const part of parts.slice(0, -1)) {
|
|
9315
|
+
const next = cursor2[part];
|
|
9316
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) return false;
|
|
9317
|
+
cursor2 = next;
|
|
9318
|
+
parents.push(cursor2);
|
|
9319
|
+
}
|
|
9320
|
+
const leaf = parts[parts.length - 1];
|
|
9321
|
+
if (!Object.prototype.hasOwnProperty.call(cursor2, leaf)) return false;
|
|
9322
|
+
delete cursor2[leaf];
|
|
9323
|
+
for (let i = parents.length - 1; i > 0; i -= 1) {
|
|
9324
|
+
if (Object.keys(parents[i]).length > 0) break;
|
|
9325
|
+
delete parents[i - 1][parts[i - 1]];
|
|
9326
|
+
}
|
|
9327
|
+
writeConfigFile(targetFile2, existing);
|
|
9328
|
+
return true;
|
|
9329
|
+
}
|
|
9330
|
+
function formatKxmConfig(config) {
|
|
9331
|
+
const display = {
|
|
9332
|
+
schema: config.schema,
|
|
9333
|
+
user: config.user,
|
|
9334
|
+
defaults: config.defaults,
|
|
9335
|
+
dash: config.dash,
|
|
9336
|
+
sync: config.sync,
|
|
9337
|
+
loadedFrom: config.loadedFrom
|
|
9338
|
+
};
|
|
9339
|
+
return (0, import_yaml.stringify)(display).trim();
|
|
9340
|
+
}
|
|
9341
|
+
var import_yaml, KXM_CONFIG_SCHEMA, KxmHubConfigError, HUB_ENV_NAME, HUB_OP_REF, LITERAL_SECRET_KEY, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
|
|
9342
|
+
var init_config = __esm({
|
|
9343
|
+
"plugins/kxm/src/config.ts"() {
|
|
9344
|
+
"use strict";
|
|
9345
|
+
import_yaml = __toESM(require_dist(), 1);
|
|
9346
|
+
KXM_CONFIG_SCHEMA = "kxm.config.v1";
|
|
9347
|
+
KxmHubConfigError = class extends Error {
|
|
9348
|
+
code = "hub_config_invalid";
|
|
9349
|
+
constructor(message) {
|
|
9350
|
+
super(message);
|
|
9351
|
+
this.name = "KxmHubConfigError";
|
|
9352
|
+
}
|
|
9353
|
+
};
|
|
9354
|
+
HUB_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
9355
|
+
HUB_OP_REF = /^op:\/\/\S+\/\S+\/\S+$/;
|
|
9356
|
+
LITERAL_SECRET_KEY = /^(token|secret|password|authToken|auth_token|apiKey|api_key|api_token|bearer|credential|access_token)$/i;
|
|
9357
|
+
IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
|
|
9358
|
+
DEFAULT_KXM_CONFIG = {
|
|
9359
|
+
schema: KXM_CONFIG_SCHEMA,
|
|
9360
|
+
user: {
|
|
9361
|
+
theme: "dark",
|
|
9362
|
+
preferredCritics: ["reviewer-arch", "reviewer-cli"],
|
|
9363
|
+
tokenBudget: 16e3
|
|
9364
|
+
},
|
|
9365
|
+
defaults: {
|
|
9366
|
+
workflow: "software-engineering/feature-implementation",
|
|
9367
|
+
harness: "pi"
|
|
9368
|
+
},
|
|
9369
|
+
dash: {
|
|
9370
|
+
defaultScreen: "agents",
|
|
9371
|
+
refreshIntervalMs: 1e3,
|
|
9372
|
+
autoOpen: false
|
|
9373
|
+
},
|
|
9374
|
+
sync: {
|
|
9375
|
+
defaultTracker: "none"
|
|
9376
|
+
},
|
|
9377
|
+
hub: {
|
|
9378
|
+
autoStart: "background"
|
|
9379
|
+
},
|
|
9380
|
+
improvement: {
|
|
9381
|
+
promotionPolicy: "manual_pr",
|
|
9382
|
+
telemetryHalfLifeDays: 14,
|
|
9383
|
+
autoThreshold: {
|
|
9384
|
+
minRuns: 10,
|
|
9385
|
+
minPassRate: 0.95,
|
|
9386
|
+
minCostSavings: 0.5
|
|
9387
|
+
}
|
|
9388
|
+
},
|
|
9389
|
+
routing: {
|
|
9390
|
+
shadowExecution: {
|
|
9391
|
+
enabled: false,
|
|
9392
|
+
sampleRate: 0.05,
|
|
9393
|
+
candidateModels: []
|
|
9394
|
+
},
|
|
9395
|
+
circuitBreaker: {
|
|
9396
|
+
mode: "soft_demotion",
|
|
9397
|
+
failureThreshold: 3,
|
|
9398
|
+
windowSeconds: 3600,
|
|
9399
|
+
cooldownSeconds: 1800,
|
|
9400
|
+
penaltyMultiplier: 5
|
|
9401
|
+
}
|
|
9402
|
+
},
|
|
9403
|
+
telemetry: {
|
|
9404
|
+
federated: true,
|
|
9405
|
+
anonymize: true
|
|
9406
|
+
}
|
|
9407
|
+
};
|
|
9408
|
+
}
|
|
9409
|
+
});
|
|
9410
|
+
|
|
9411
|
+
// plugins/kxm/src/hub-binding.ts
|
|
9412
|
+
import { spawnSync } from "node:child_process";
|
|
9413
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9414
|
+
import { homedir as homedir2 } from "node:os";
|
|
9415
|
+
import { dirname as dirname3, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
|
|
9416
|
+
function resolveUserStateRoot(env) {
|
|
9417
|
+
const explicit = env.KXM_STATE_HOME?.trim();
|
|
9418
|
+
if (explicit) {
|
|
9419
|
+
if (!isAbsolute(explicit)) throw new HubBindingError("local_state_root_not_absolute");
|
|
9420
|
+
return resolve2(explicit);
|
|
9421
|
+
}
|
|
9422
|
+
if (process.platform === "win32") {
|
|
9423
|
+
const localAppData = env.LOCALAPPDATA?.trim();
|
|
9424
|
+
const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join4(homedir2(), "AppData", "Local");
|
|
9425
|
+
return resolve2(base2, "KXM");
|
|
9426
|
+
}
|
|
9427
|
+
if (process.platform === "darwin") return resolve2(homedir2(), "Library", "Application Support", "KXM");
|
|
9428
|
+
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
9429
|
+
const base = xdgState && isAbsolute(xdgState) ? xdgState : join4(homedir2(), ".local", "state");
|
|
9430
|
+
return resolve2(base, "kxm");
|
|
9431
|
+
}
|
|
9432
|
+
function hubBindingFile(env = process.env) {
|
|
9433
|
+
return join4(resolveUserStateRoot(env), "hub-binding.json");
|
|
9434
|
+
}
|
|
9435
|
+
function validateHubUrl(raw) {
|
|
9436
|
+
let parsed;
|
|
9437
|
+
try {
|
|
9438
|
+
parsed = new URL(raw);
|
|
9439
|
+
} catch {
|
|
9440
|
+
throw new HubBindingError("hub_url_invalid");
|
|
9441
|
+
}
|
|
9442
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || raw.includes("?") || raw.includes("#")) {
|
|
9443
|
+
throw new HubBindingError("hub_url_invalid");
|
|
9444
|
+
}
|
|
9445
|
+
return parsed.href.replace(/\/$/, "");
|
|
9446
|
+
}
|
|
9447
|
+
function splitTokenCommand(raw) {
|
|
9448
|
+
if (/[|&;<>$`\n\r]/.test(raw)) {
|
|
9449
|
+
throw new CloudTokenError(
|
|
9450
|
+
"cloud_token_command_invalid",
|
|
9451
|
+
"token command must be a program and arguments, not a shell pipeline"
|
|
9452
|
+
);
|
|
9453
|
+
}
|
|
9454
|
+
const args = [];
|
|
9455
|
+
let current = "";
|
|
9456
|
+
let quote;
|
|
9457
|
+
for (const ch of raw) {
|
|
9458
|
+
if (quote) {
|
|
9459
|
+
if (ch === quote) quote = void 0;
|
|
9460
|
+
else current += ch;
|
|
9461
|
+
continue;
|
|
9462
|
+
}
|
|
9463
|
+
if (ch === "'" || ch === '"') {
|
|
9464
|
+
quote = ch;
|
|
9465
|
+
continue;
|
|
9466
|
+
}
|
|
9467
|
+
if (ch === " " || ch === " ") {
|
|
9468
|
+
if (current.length > 0) {
|
|
9469
|
+
args.push(current);
|
|
9470
|
+
current = "";
|
|
9471
|
+
}
|
|
9472
|
+
continue;
|
|
9473
|
+
}
|
|
9474
|
+
current += ch;
|
|
9475
|
+
}
|
|
9476
|
+
if (quote) {
|
|
9477
|
+
throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
|
|
9478
|
+
}
|
|
9479
|
+
if (current.length > 0) args.push(current);
|
|
9480
|
+
if (args.length === 0) {
|
|
9481
|
+
throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
|
|
9482
|
+
}
|
|
9483
|
+
return args;
|
|
9484
|
+
}
|
|
9485
|
+
function isCloudTokenEnvName(value) {
|
|
9486
|
+
return TOKEN_ENV_NAME.test(value);
|
|
9487
|
+
}
|
|
9488
|
+
function effectiveHubBindingScope(url, env = process.env) {
|
|
9489
|
+
try {
|
|
9490
|
+
const binding = readHubBinding(env);
|
|
9491
|
+
if (binding?.cloud) {
|
|
9283
9492
|
try {
|
|
9284
|
-
|
|
9493
|
+
if (validateHubUrl(url) === binding.url) return "remote";
|
|
9494
|
+
} catch {
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
} catch (error) {
|
|
9498
|
+
if (!(error instanceof HubBindingError)) throw error;
|
|
9499
|
+
}
|
|
9500
|
+
return hubBindingScope(url);
|
|
9501
|
+
}
|
|
9502
|
+
function resolveCloudHubToken(binding, env = process.env) {
|
|
9503
|
+
if (!binding.cloud) {
|
|
9504
|
+
throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
|
|
9505
|
+
}
|
|
9506
|
+
if (binding.tokenEnv) {
|
|
9507
|
+
const value = env[binding.tokenEnv]?.trim();
|
|
9508
|
+
if (value) return value;
|
|
9509
|
+
}
|
|
9510
|
+
if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
|
|
9511
|
+
const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
|
|
9512
|
+
throw new CloudTokenError(
|
|
9513
|
+
"cloud_token_missing",
|
|
9514
|
+
`cloud hub token is missing: ${source}; the local hub-env token was not used`
|
|
9515
|
+
);
|
|
9516
|
+
}
|
|
9517
|
+
function runTokenCommand(command, env) {
|
|
9518
|
+
const argv = splitTokenCommand(command);
|
|
9519
|
+
const [program2, ...args] = argv;
|
|
9520
|
+
const result = spawnSync(program2, args, {
|
|
9521
|
+
encoding: "utf8",
|
|
9522
|
+
shell: false,
|
|
9523
|
+
timeout: TOKEN_COMMAND_TIMEOUT_MS,
|
|
9524
|
+
maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
|
|
9525
|
+
env,
|
|
9526
|
+
windowsHide: true
|
|
9527
|
+
});
|
|
9528
|
+
if (result.error || result.status !== 0) {
|
|
9529
|
+
throw new CloudTokenError(
|
|
9530
|
+
"cloud_token_command_failed",
|
|
9531
|
+
"token command failed; the local hub-env token was not used"
|
|
9532
|
+
);
|
|
9533
|
+
}
|
|
9534
|
+
const stdout = result.stdout ?? "";
|
|
9535
|
+
if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
|
|
9536
|
+
throw new CloudTokenError(
|
|
9537
|
+
"cloud_token_command_failed",
|
|
9538
|
+
"token command did not print a single token; the local hub-env token was not used"
|
|
9539
|
+
);
|
|
9540
|
+
}
|
|
9541
|
+
return stdout.trim();
|
|
9542
|
+
}
|
|
9543
|
+
function hubBindingScope(url) {
|
|
9544
|
+
let host;
|
|
9545
|
+
try {
|
|
9546
|
+
host = new URL(url).hostname.toLowerCase();
|
|
9547
|
+
} catch {
|
|
9548
|
+
return "remote";
|
|
9549
|
+
}
|
|
9550
|
+
if (host === "localhost" || host === "::1" || host === "[::1]" || host.endsWith(".localhost")) return "loopback";
|
|
9551
|
+
const v4 = /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(host);
|
|
9552
|
+
return v4 && [v4[1], v4[2], v4[3]].every((part) => Number(part) <= 255) ? "loopback" : "remote";
|
|
9553
|
+
}
|
|
9554
|
+
function isIsoTimestamp(value) {
|
|
9555
|
+
if (Number.isNaN(Date.parse(value))) return false;
|
|
9556
|
+
return value === new Date(value).toISOString();
|
|
9557
|
+
}
|
|
9558
|
+
function isAbortError(error) {
|
|
9559
|
+
return Boolean(
|
|
9560
|
+
error && typeof error === "object" && ("name" in error && error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR")
|
|
9561
|
+
);
|
|
9562
|
+
}
|
|
9563
|
+
function readHubBinding(env = process.env) {
|
|
9564
|
+
const file = hubBindingFile(env);
|
|
9565
|
+
if (!existsSync4(file)) return void 0;
|
|
9566
|
+
let parsed;
|
|
9567
|
+
try {
|
|
9568
|
+
parsed = JSON.parse(readFileSync3(file, "utf8"));
|
|
9569
|
+
} catch {
|
|
9570
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9571
|
+
}
|
|
9572
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9573
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9574
|
+
}
|
|
9575
|
+
const row = parsed;
|
|
9576
|
+
const keys = Object.keys(row);
|
|
9577
|
+
const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
|
|
9578
|
+
const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
|
|
9579
|
+
if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
|
|
9580
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9581
|
+
}
|
|
9582
|
+
let url;
|
|
9583
|
+
try {
|
|
9584
|
+
url = validateHubUrl(row.url);
|
|
9585
|
+
} catch {
|
|
9586
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9587
|
+
}
|
|
9588
|
+
if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
|
|
9589
|
+
if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9590
|
+
let tokenEnv;
|
|
9591
|
+
let tokenCommand;
|
|
9592
|
+
if (row.tokenEnv !== void 0) {
|
|
9593
|
+
if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
|
|
9594
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9595
|
+
}
|
|
9596
|
+
tokenEnv = row.tokenEnv;
|
|
9597
|
+
}
|
|
9598
|
+
if (row.tokenCommand !== void 0) {
|
|
9599
|
+
if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9600
|
+
try {
|
|
9601
|
+
splitTokenCommand(row.tokenCommand);
|
|
9602
|
+
} catch {
|
|
9603
|
+
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9604
|
+
}
|
|
9605
|
+
tokenCommand = row.tokenCommand;
|
|
9606
|
+
}
|
|
9607
|
+
if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
9608
|
+
return {
|
|
9609
|
+
schema: HUB_BINDING_SCHEMA,
|
|
9610
|
+
url,
|
|
9611
|
+
boundAt: row.boundAt,
|
|
9612
|
+
cloud: true,
|
|
9613
|
+
...tokenEnv ? { tokenEnv } : {},
|
|
9614
|
+
...tokenCommand ? { tokenCommand } : {}
|
|
9615
|
+
};
|
|
9616
|
+
}
|
|
9617
|
+
function writeHubBinding(record, env = process.env) {
|
|
9618
|
+
const file = hubBindingFile(env);
|
|
9619
|
+
mkdirSync3(dirname3(file), { recursive: true, mode: 448 });
|
|
9620
|
+
const temporary = join4(dirname3(file), `.hub-binding-${process.pid}.tmp`);
|
|
9621
|
+
try {
|
|
9622
|
+
writeFileSync3(temporary, `${JSON.stringify(record, null, 2)}
|
|
9623
|
+
`, { encoding: "utf8", mode: 384 });
|
|
9624
|
+
renameSync(temporary, file);
|
|
9625
|
+
} finally {
|
|
9626
|
+
rmSync(temporary, { force: true });
|
|
9627
|
+
}
|
|
9628
|
+
return file;
|
|
9629
|
+
}
|
|
9630
|
+
function removeHubBinding(env = process.env) {
|
|
9631
|
+
const file = hubBindingFile(env);
|
|
9632
|
+
try {
|
|
9633
|
+
rmSync(file);
|
|
9634
|
+
return true;
|
|
9635
|
+
} catch (error) {
|
|
9636
|
+
if (error.code === "ENOENT") return false;
|
|
9637
|
+
throw error;
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
|
|
9641
|
+
const started = Date.now();
|
|
9642
|
+
const controller = new AbortController();
|
|
9643
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
9644
|
+
try {
|
|
9645
|
+
const response = await fetchImpl(`${url}/health`, { signal: controller.signal });
|
|
9646
|
+
if (!response.ok) return { health: "unknown", probeMs: Date.now() - started };
|
|
9647
|
+
let body;
|
|
9648
|
+
try {
|
|
9649
|
+
body = await response.json();
|
|
9650
|
+
} catch {
|
|
9651
|
+
return { health: "unknown", probeMs: Date.now() - started };
|
|
9652
|
+
}
|
|
9653
|
+
if (body && typeof body === "object" && body.ok === true) {
|
|
9654
|
+
return { health: "on", probeMs: Date.now() - started };
|
|
9655
|
+
}
|
|
9656
|
+
return { health: "unknown", probeMs: Date.now() - started };
|
|
9657
|
+
} catch (error) {
|
|
9658
|
+
return { health: isAbortError(error) ? "unknown" : "off", probeMs: Date.now() - started };
|
|
9659
|
+
} finally {
|
|
9660
|
+
clearTimeout(timer);
|
|
9661
|
+
}
|
|
9662
|
+
}
|
|
9663
|
+
var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, CloudTokenError, TOKEN_ENV_NAME, TOKEN_COMMAND_TIMEOUT_MS, TOKEN_COMMAND_MAX_BUFFER, HubBindingError;
|
|
9664
|
+
var init_hub_binding = __esm({
|
|
9665
|
+
"plugins/kxm/src/hub-binding.ts"() {
|
|
9666
|
+
"use strict";
|
|
9667
|
+
HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
9668
|
+
HUB_HEALTH_PROBE_MS = 300;
|
|
9669
|
+
CloudTokenError = class extends Error {
|
|
9670
|
+
code;
|
|
9671
|
+
constructor(code, message) {
|
|
9672
|
+
super(message);
|
|
9673
|
+
this.name = "CloudTokenError";
|
|
9674
|
+
this.code = code;
|
|
9675
|
+
}
|
|
9676
|
+
};
|
|
9677
|
+
TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
9678
|
+
TOKEN_COMMAND_TIMEOUT_MS = 15e3;
|
|
9679
|
+
TOKEN_COMMAND_MAX_BUFFER = 4096;
|
|
9680
|
+
HubBindingError = class extends Error {
|
|
9681
|
+
constructor(message) {
|
|
9682
|
+
super(message);
|
|
9683
|
+
this.name = "HubBindingError";
|
|
9684
|
+
}
|
|
9685
|
+
};
|
|
9686
|
+
}
|
|
9687
|
+
});
|
|
9688
|
+
|
|
9689
|
+
// plugins/kxm/src/hub-identity.ts
|
|
9690
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9691
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
9692
|
+
import { basename, dirname as dirname4, join as join5, resolve as resolve3 } from "node:path";
|
|
9693
|
+
function normalizeUrl(raw) {
|
|
9694
|
+
try {
|
|
9695
|
+
return validateHubUrl(raw);
|
|
9696
|
+
} catch {
|
|
9697
|
+
return void 0;
|
|
9698
|
+
}
|
|
9699
|
+
}
|
|
9700
|
+
function projectYamlId(start) {
|
|
9701
|
+
let dir = resolve3(start);
|
|
9702
|
+
for (; ; ) {
|
|
9703
|
+
const file = join5(dir, ".kxm", "project.yaml");
|
|
9704
|
+
if (existsSync5(file)) {
|
|
9705
|
+
try {
|
|
9706
|
+
const doc = (0, import_yaml2.parse)(readFileSync4(file, "utf8"));
|
|
9285
9707
|
if (doc && typeof doc === "object" && !Array.isArray(doc) && typeof doc.id === "string" && doc.id.trim()) {
|
|
9286
9708
|
return doc.id.trim();
|
|
9287
9709
|
}
|
|
@@ -9289,48 +9711,217 @@ function readProjectYamlId(start) {
|
|
|
9289
9711
|
}
|
|
9290
9712
|
return void 0;
|
|
9291
9713
|
}
|
|
9292
|
-
const parent =
|
|
9714
|
+
const parent = dirname4(dir);
|
|
9293
9715
|
if (parent === dir) return void 0;
|
|
9294
9716
|
dir = parent;
|
|
9295
9717
|
}
|
|
9296
9718
|
}
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9719
|
+
function packageName(cwd) {
|
|
9720
|
+
try {
|
|
9721
|
+
const pkg = JSON.parse(readFileSync4(join5(cwd, "package.json"), "utf8"));
|
|
9722
|
+
if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
|
|
9723
|
+
} catch {
|
|
9724
|
+
}
|
|
9725
|
+
return void 0;
|
|
9726
|
+
}
|
|
9727
|
+
function activeBinding(env) {
|
|
9728
|
+
try {
|
|
9729
|
+
return readHubBinding(env);
|
|
9730
|
+
} catch (error) {
|
|
9731
|
+
if (error instanceof HubBindingError) return void 0;
|
|
9732
|
+
throw error;
|
|
9733
|
+
}
|
|
9734
|
+
}
|
|
9735
|
+
function endpointFor(mode, settings) {
|
|
9736
|
+
return mode === "cloud" ? settings.cloud : settings.local;
|
|
9737
|
+
}
|
|
9738
|
+
function resolveProjectIdentity(cwd, env = process.env, explicit) {
|
|
9739
|
+
const fromExplicit = explicit?.trim();
|
|
9740
|
+
if (fromExplicit) return { project: fromExplicit, source: "flag", sourceLabel: "--project" };
|
|
9741
|
+
const fromEnv = env.KXM_PROJECT?.trim();
|
|
9742
|
+
if (fromEnv) return { project: fromEnv, source: "env", sourceLabel: "KXM_PROJECT" };
|
|
9743
|
+
const settings = loadHubSettings(cwd, env);
|
|
9744
|
+
const mode = settings.mode ?? (activeBinding(env)?.cloud ? "cloud" : "local");
|
|
9745
|
+
const fromConfig = endpointFor(mode, settings)?.project?.trim();
|
|
9746
|
+
if (fromConfig) {
|
|
9747
|
+
return { project: fromConfig, source: "config", sourceLabel: `.kxm/config.yaml hub.${mode}.project` };
|
|
9748
|
+
}
|
|
9749
|
+
const fromProject = projectYamlId(cwd);
|
|
9750
|
+
if (fromProject) return { project: fromProject, source: "project.yaml", sourceLabel: ".kxm/project.yaml id" };
|
|
9751
|
+
const fromPackage = packageName(cwd);
|
|
9752
|
+
if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
|
|
9753
|
+
return { project: basename(cwd), source: "directory", sourceLabel: "directory name" };
|
|
9754
|
+
}
|
|
9755
|
+
function defaultProjectName(cwd, env = process.env, explicit) {
|
|
9756
|
+
return resolveProjectIdentity(cwd, env, explicit).project;
|
|
9757
|
+
}
|
|
9758
|
+
function opReferenceFromTokenCommand(command) {
|
|
9759
|
+
try {
|
|
9760
|
+
const args = splitTokenCommand(command);
|
|
9761
|
+
if (args[0] === "op" && args[1] === "read" && args[2] && isHubOpReference(args[2])) return args[2];
|
|
9762
|
+
} catch {
|
|
9763
|
+
return void 0;
|
|
9764
|
+
}
|
|
9765
|
+
return void 0;
|
|
9766
|
+
}
|
|
9767
|
+
function defaultOpRead(reference, env) {
|
|
9768
|
+
if (!isHubOpReference(reference)) {
|
|
9769
|
+
throw new CloudTokenError("cloud_token_command_invalid", `refusing to resolve ${reference}; an op:// reference is required`);
|
|
9770
|
+
}
|
|
9771
|
+
const result = spawnSync2("op", ["read", reference], {
|
|
9772
|
+
encoding: "utf8",
|
|
9773
|
+
shell: false,
|
|
9774
|
+
timeout: OP_TIMEOUT_MS,
|
|
9775
|
+
maxBuffer: OP_MAX_BUFFER,
|
|
9776
|
+
env,
|
|
9777
|
+
windowsHide: true
|
|
9778
|
+
});
|
|
9779
|
+
if (result.error || result.status !== 0) {
|
|
9780
|
+
throw new CloudTokenError(
|
|
9781
|
+
"cloud_token_command_failed",
|
|
9782
|
+
`op read failed for ${reference}; the token was not saved`
|
|
9783
|
+
);
|
|
9784
|
+
}
|
|
9785
|
+
const line = (result.stdout ?? "").trim();
|
|
9786
|
+
if (!line || line.split(/\r?\n/).length !== 1) {
|
|
9787
|
+
throw new CloudTokenError(
|
|
9788
|
+
"cloud_token_command_failed",
|
|
9789
|
+
`op read did not print a single token for ${reference}; the token was not saved`
|
|
9790
|
+
);
|
|
9791
|
+
}
|
|
9792
|
+
return line;
|
|
9793
|
+
}
|
|
9794
|
+
function resolveKeyReference(ref, env, opRead = defaultOpRead) {
|
|
9795
|
+
if (!ref) return void 0;
|
|
9796
|
+
if (ref.env) {
|
|
9797
|
+
const value = env[ref.env]?.trim();
|
|
9798
|
+
if (value) return { value, source: `env:${ref.env}` };
|
|
9799
|
+
}
|
|
9800
|
+
if (ref.op) {
|
|
9801
|
+
const value = opRead(ref.op, env).trim();
|
|
9802
|
+
if (!value || value.split(/\r?\n/).length !== 1) {
|
|
9803
|
+
throw new CloudTokenError("cloud_token_command_failed", `op read did not print a single token for ${ref.op}`);
|
|
9804
|
+
}
|
|
9805
|
+
return { value, source: `op:${ref.op}` };
|
|
9806
|
+
}
|
|
9807
|
+
return void 0;
|
|
9808
|
+
}
|
|
9809
|
+
function keySourceFor(input) {
|
|
9810
|
+
const ref = input.endpoint?.key;
|
|
9811
|
+
if (input.mode === "local" && input.env.KXM_AUTH_TOKEN?.trim()) return "env:KXM_AUTH_TOKEN";
|
|
9812
|
+
if (ref?.env && input.env[ref.env]?.trim()) return `env:${ref.env}`;
|
|
9813
|
+
if (ref?.op) return `op:${ref.op}`;
|
|
9814
|
+
if (ref?.env) return `env:${ref.env}`;
|
|
9815
|
+
if (input.mode === "cloud") {
|
|
9816
|
+
if (input.binding?.tokenEnv) return `env:${input.binding.tokenEnv}`;
|
|
9817
|
+
if (input.binding?.tokenCommand) {
|
|
9818
|
+
const op = opReferenceFromTokenCommand(input.binding.tokenCommand);
|
|
9819
|
+
return op ? `op:${op}` : "command";
|
|
9820
|
+
}
|
|
9821
|
+
return "missing";
|
|
9822
|
+
}
|
|
9823
|
+
if (input.hasSavedProjectToken?.(input.project)) return "hub-env";
|
|
9824
|
+
return "missing";
|
|
9825
|
+
}
|
|
9826
|
+
function describeHubConnection(cwd, env = process.env, hooks = {}) {
|
|
9827
|
+
const settings = loadHubSettings(cwd, env);
|
|
9828
|
+
const binding = activeBinding(env);
|
|
9829
|
+
const mode = settings.mode ?? (binding?.cloud ? "cloud" : "local");
|
|
9830
|
+
const modeSource = settings.mode ? "config" : binding?.cloud ? "binding" : "default";
|
|
9831
|
+
const endpoint = endpointFor(mode, settings);
|
|
9832
|
+
const fromEnv = env.KXM_SERVER_URL?.trim();
|
|
9833
|
+
const fromConfig = endpoint?.url?.trim();
|
|
9834
|
+
const fromBinding = binding?.url;
|
|
9835
|
+
let url = "http://127.0.0.1:7331";
|
|
9836
|
+
let urlSource = "default";
|
|
9837
|
+
if (fromEnv) {
|
|
9838
|
+
url = fromEnv.replace(/\/$/, "");
|
|
9839
|
+
urlSource = "env";
|
|
9840
|
+
} else if (fromConfig) {
|
|
9841
|
+
url = fromConfig.replace(/\/$/, "");
|
|
9842
|
+
urlSource = "config";
|
|
9843
|
+
} else if (fromBinding) {
|
|
9844
|
+
url = fromBinding;
|
|
9845
|
+
urlSource = "binding";
|
|
9846
|
+
}
|
|
9847
|
+
const identity = resolveProjectIdentity(cwd, env, hooks.explicitProject);
|
|
9848
|
+
const configuredCloudUrl = settings.cloud?.url ?? (binding?.cloud ? binding.url : void 0);
|
|
9849
|
+
let scope;
|
|
9850
|
+
if (mode === "cloud") {
|
|
9851
|
+
const override = fromEnv ? normalizeUrl(fromEnv) : void 0;
|
|
9852
|
+
const named = configuredCloudUrl ? normalizeUrl(configuredCloudUrl) : void 0;
|
|
9853
|
+
scope = override && named && override !== named ? hubBindingScope(url) : "remote";
|
|
9854
|
+
} else {
|
|
9855
|
+
scope = hubBindingScope(url);
|
|
9856
|
+
}
|
|
9857
|
+
return {
|
|
9858
|
+
...identity,
|
|
9859
|
+
mode,
|
|
9860
|
+
modeSource,
|
|
9861
|
+
url,
|
|
9862
|
+
urlSource,
|
|
9863
|
+
scope,
|
|
9864
|
+
...endpoint ? { endpoint } : {},
|
|
9865
|
+
...binding ? { binding } : {},
|
|
9866
|
+
keySource: keySourceFor({
|
|
9867
|
+
mode,
|
|
9868
|
+
env,
|
|
9869
|
+
...endpoint ? { endpoint } : {},
|
|
9870
|
+
...binding ? { binding } : {},
|
|
9871
|
+
project: identity.project,
|
|
9872
|
+
...hooks.hasSavedProjectToken ? { hasSavedProjectToken: hooks.hasSavedProjectToken } : {}
|
|
9873
|
+
})
|
|
9874
|
+
};
|
|
9875
|
+
}
|
|
9876
|
+
function projectTokenMissingMessage(identity) {
|
|
9877
|
+
return [
|
|
9878
|
+
`kxm has no project token for project ${identity.project} (from ${identity.sourceLabel}).`,
|
|
9879
|
+
`Ask the user to set this project's token. Set KXM_AUTH_TOKEN, or set hub.local.project or hub.cloud.project in .kxm/config.yaml to the project key the hub already knows, or add a key reference under hub.projects.${identity.project} (key.op is an op:// reference, key.env is an environment variable name, never the token) and restart the hub.`,
|
|
9880
|
+
"KXM_PROJECT or --project overrides the id for one command.",
|
|
9881
|
+
"An agent never uses the hub admin token."
|
|
9882
|
+
].join(" ");
|
|
9883
|
+
}
|
|
9884
|
+
var import_yaml2, OP_TIMEOUT_MS, OP_MAX_BUFFER;
|
|
9885
|
+
var init_hub_identity = __esm({
|
|
9886
|
+
"plugins/kxm/src/hub-identity.ts"() {
|
|
9300
9887
|
"use strict";
|
|
9301
|
-
|
|
9888
|
+
import_yaml2 = __toESM(require_dist(), 1);
|
|
9889
|
+
init_config();
|
|
9890
|
+
init_hub_binding();
|
|
9891
|
+
OP_TIMEOUT_MS = 15e3;
|
|
9892
|
+
OP_MAX_BUFFER = 4096;
|
|
9302
9893
|
}
|
|
9303
9894
|
});
|
|
9304
9895
|
|
|
9305
9896
|
// plugins/kxm/src/hub-env.ts
|
|
9306
|
-
import { existsSync as
|
|
9307
|
-
import { homedir as
|
|
9308
|
-
import { dirname as
|
|
9897
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9898
|
+
import { homedir as homedir3 } from "node:os";
|
|
9899
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "node:path";
|
|
9309
9900
|
function resolveUserStateRoot2(env) {
|
|
9310
9901
|
const explicit = env.KXM_STATE_HOME?.trim();
|
|
9311
9902
|
if (explicit) {
|
|
9312
9903
|
if (!isAbsolute2(explicit)) throw new HubEnvError("local_state_root_not_absolute");
|
|
9313
|
-
return
|
|
9904
|
+
return resolve4(explicit);
|
|
9314
9905
|
}
|
|
9315
9906
|
if (process.platform === "win32") {
|
|
9316
9907
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
9317
|
-
const base2 = localAppData && isAbsolute2(localAppData) ? localAppData :
|
|
9318
|
-
return
|
|
9908
|
+
const base2 = localAppData && isAbsolute2(localAppData) ? localAppData : join6(homedir3(), "AppData", "Local");
|
|
9909
|
+
return resolve4(base2, "KXM");
|
|
9319
9910
|
}
|
|
9320
|
-
if (process.platform === "darwin") return
|
|
9911
|
+
if (process.platform === "darwin") return resolve4(homedir3(), "Library", "Application Support", "KXM");
|
|
9321
9912
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
9322
|
-
const base = xdgState && isAbsolute2(xdgState) ? xdgState :
|
|
9323
|
-
return
|
|
9913
|
+
const base = xdgState && isAbsolute2(xdgState) ? xdgState : join6(homedir3(), ".local", "state");
|
|
9914
|
+
return resolve4(base, "kxm");
|
|
9324
9915
|
}
|
|
9325
9916
|
function hubEnvFile(env = process.env) {
|
|
9326
|
-
return
|
|
9917
|
+
return join6(resolveUserStateRoot2(env), "hub-env.json");
|
|
9327
9918
|
}
|
|
9328
9919
|
function readHubEnvRecord(env = process.env) {
|
|
9329
9920
|
const file = hubEnvFile(env);
|
|
9330
|
-
if (!
|
|
9921
|
+
if (!existsSync6(file)) return void 0;
|
|
9331
9922
|
let parsed;
|
|
9332
9923
|
try {
|
|
9333
|
-
parsed = JSON.parse(
|
|
9924
|
+
parsed = JSON.parse(readFileSync5(file, "utf8"));
|
|
9334
9925
|
} catch (error) {
|
|
9335
9926
|
throw new HubEnvError(`hub env file is malformed at ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9336
9927
|
}
|
|
@@ -9375,29 +9966,39 @@ function activeCloudBinding(env) {
|
|
|
9375
9966
|
throw error;
|
|
9376
9967
|
}
|
|
9377
9968
|
}
|
|
9378
|
-
function resolveControlPlaneAuthToken(env = process.env) {
|
|
9379
|
-
const cloud =
|
|
9380
|
-
if (cloud) return
|
|
9969
|
+
function resolveControlPlaneAuthToken(env = process.env, cwd = process.cwd(), opRead) {
|
|
9970
|
+
const cloud = cloudModeToken(cwd, env, void 0, opRead);
|
|
9971
|
+
if (cloud) return cloud;
|
|
9381
9972
|
return env.KXM_AUTH_TOKEN?.trim() || void 0;
|
|
9382
9973
|
}
|
|
9383
|
-
function
|
|
9384
|
-
const
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
if (
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9974
|
+
function cloudModeToken(cwd, env, explicitProject, opRead) {
|
|
9975
|
+
const connection = describeHubConnection(cwd, env, explicitProject === void 0 ? {} : { explicitProject });
|
|
9976
|
+
if (connection.mode !== "cloud") return void 0;
|
|
9977
|
+
const fromConfig = resolveKeyReference(connection.endpoint?.key, env, opRead);
|
|
9978
|
+
if (fromConfig) return fromConfig.value;
|
|
9979
|
+
if (connection.binding?.cloud) return resolveCloudHubToken(connection.binding, env);
|
|
9980
|
+
throw new CloudTokenError(
|
|
9981
|
+
"cloud_token_missing",
|
|
9982
|
+
"cloud hub token is missing; the local hub-env token was not used"
|
|
9983
|
+
);
|
|
9984
|
+
}
|
|
9985
|
+
function agentSessionEnv(cwd, env = process.env, explicitProject, opRead) {
|
|
9986
|
+
const connection = describeHubConnection(cwd, env, explicitProject === void 0 ? {} : { explicitProject });
|
|
9987
|
+
const session = { KXM_PROJECT: connection.project };
|
|
9988
|
+
if (connection.url) session.KXM_SERVER_URL = connection.url.replace(/\/$/, "");
|
|
9989
|
+
if (connection.mode === "cloud") {
|
|
9990
|
+
const token = cloudModeToken(cwd, env, explicitProject, opRead);
|
|
9991
|
+
if (token) session.KXM_AUTH_TOKEN = token;
|
|
9393
9992
|
}
|
|
9394
|
-
const session = { KXM_PROJECT: project };
|
|
9395
|
-
if (serverUrl) session.KXM_SERVER_URL = serverUrl.replace(/\/$/, "");
|
|
9396
|
-
if (cloud) session.KXM_AUTH_TOKEN = resolveCloudHubToken(cloud, env);
|
|
9397
9993
|
return session;
|
|
9398
9994
|
}
|
|
9399
|
-
function hasClientHubCredential(env = process.env, project) {
|
|
9995
|
+
function hasClientHubCredential(env = process.env, project, cwd = process.cwd()) {
|
|
9400
9996
|
if (env.KXM_AUTH_TOKEN?.trim()) return true;
|
|
9997
|
+
const settings = loadHubSettings(cwd, env);
|
|
9998
|
+
const mode = settings.mode ?? (activeCloudBinding(env) ? "cloud" : "local");
|
|
9999
|
+
const key = (mode === "cloud" ? settings.cloud : settings.local)?.key;
|
|
10000
|
+
if (key?.op) return true;
|
|
10001
|
+
if (key?.env && env[key.env]?.trim()) return true;
|
|
9401
10002
|
let record;
|
|
9402
10003
|
try {
|
|
9403
10004
|
record = readHubEnvRecord(env);
|
|
@@ -9411,26 +10012,30 @@ function hasClientHubCredential(env = process.env, project) {
|
|
|
9411
10012
|
if (project !== void 0) return typeof tokens[project] === "string" && tokens[project].trim().length > 0;
|
|
9412
10013
|
return Object.values(tokens).some((token) => typeof token === "string" && token.trim().length > 0);
|
|
9413
10014
|
}
|
|
9414
|
-
function resolveClientHubAuthToken(env, project) {
|
|
9415
|
-
const cloud =
|
|
9416
|
-
if (cloud) return
|
|
10015
|
+
function resolveClientHubAuthToken(env, project, cwd = process.cwd(), opRead) {
|
|
10016
|
+
const cloud = cloudModeToken(cwd, env, void 0, opRead);
|
|
10017
|
+
if (cloud) return cloud;
|
|
9417
10018
|
const envToken = env.KXM_AUTH_TOKEN?.trim();
|
|
9418
10019
|
if (envToken) return envToken;
|
|
10020
|
+
const fromConfig = resolveKeyReference(describeHubConnection(cwd, env).endpoint?.key, env, opRead);
|
|
10021
|
+
if (fromConfig) return fromConfig.value;
|
|
9419
10022
|
const record = readHubEnvRecord(env);
|
|
9420
10023
|
return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || void 0;
|
|
9421
10024
|
}
|
|
9422
|
-
function resolveAgentHubAuthToken(env, project) {
|
|
9423
|
-
const cloud =
|
|
9424
|
-
if (cloud) return
|
|
10025
|
+
function resolveAgentHubAuthToken(env, project, cwd = process.cwd(), opRead) {
|
|
10026
|
+
const cloud = cloudModeToken(cwd, env, void 0, opRead);
|
|
10027
|
+
if (cloud) return cloud;
|
|
9425
10028
|
const envToken = env.KXM_AUTH_TOKEN?.trim();
|
|
9426
10029
|
if (envToken) return envToken;
|
|
10030
|
+
const fromConfig = resolveKeyReference(describeHubConnection(cwd, env).endpoint?.key, env, opRead);
|
|
10031
|
+
if (fromConfig) return fromConfig.value;
|
|
9427
10032
|
const tokens = readHubEnvRecord(env)?.projectTokens;
|
|
9428
10033
|
if (!tokens || !Object.hasOwn(tokens, project)) return void 0;
|
|
9429
10034
|
return tokens[project]?.trim() || void 0;
|
|
9430
10035
|
}
|
|
9431
|
-
function resolveClientAdminAuthToken(env = process.env) {
|
|
9432
|
-
const cloud =
|
|
9433
|
-
if (cloud) return
|
|
10036
|
+
function resolveClientAdminAuthToken(env = process.env, cwd = process.cwd(), opRead) {
|
|
10037
|
+
const cloud = cloudModeToken(cwd, env, void 0, opRead);
|
|
10038
|
+
if (cloud) return cloud;
|
|
9434
10039
|
const envToken = env.KXM_AUTH_TOKEN?.trim();
|
|
9435
10040
|
if (envToken) return envToken;
|
|
9436
10041
|
return readHubEnvRecord(env)?.authToken?.trim() || void 0;
|
|
@@ -9439,8 +10044,9 @@ var HUB_ENV_SCHEMA, HubEnvError, AgentProjectTokenMissingError;
|
|
|
9439
10044
|
var init_hub_env = __esm({
|
|
9440
10045
|
"plugins/kxm/src/hub-env.ts"() {
|
|
9441
10046
|
"use strict";
|
|
10047
|
+
init_config();
|
|
10048
|
+
init_hub_identity();
|
|
9442
10049
|
init_hub_binding();
|
|
9443
|
-
init_project_name();
|
|
9444
10050
|
HUB_ENV_SCHEMA = "kxm.hub-env.v1";
|
|
9445
10051
|
HubEnvError = class extends Error {
|
|
9446
10052
|
constructor(message) {
|
|
@@ -9451,12 +10057,16 @@ var init_hub_env = __esm({
|
|
|
9451
10057
|
AgentProjectTokenMissingError = class extends Error {
|
|
9452
10058
|
code = "project_token_missing";
|
|
9453
10059
|
project;
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
10060
|
+
projectSource;
|
|
10061
|
+
hint;
|
|
10062
|
+
constructor(projectOrIdentity) {
|
|
10063
|
+
const identity = typeof projectOrIdentity === "string" ? { project: projectOrIdentity, source: "env", sourceLabel: "the project id sent to the hub" } : projectOrIdentity;
|
|
10064
|
+
const message = projectTokenMissingMessage(identity);
|
|
10065
|
+
super(message);
|
|
9458
10066
|
this.name = "AgentProjectTokenMissingError";
|
|
9459
|
-
this.project = project;
|
|
10067
|
+
this.project = identity.project;
|
|
10068
|
+
this.projectSource = typeof projectOrIdentity === "string" ? "unknown" : identity.source;
|
|
10069
|
+
this.hint = message;
|
|
9460
10070
|
}
|
|
9461
10071
|
};
|
|
9462
10072
|
}
|
|
@@ -16924,7 +17534,7 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
16924
17534
|
fail("parse", "document_too_large", label, `document exceeds ${limits.maxDocumentBytes} bytes`);
|
|
16925
17535
|
}
|
|
16926
17536
|
const text = decodeUtf8(input, label);
|
|
16927
|
-
const document = (0,
|
|
17537
|
+
const document = (0, import_yaml3.parseDocument)(text, {
|
|
16928
17538
|
customTags: [],
|
|
16929
17539
|
strict: true,
|
|
16930
17540
|
uniqueKeys: true
|
|
@@ -16937,27 +17547,27 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
16937
17547
|
}
|
|
16938
17548
|
let nodes = 0;
|
|
16939
17549
|
let keys = 0;
|
|
16940
|
-
(0,
|
|
17550
|
+
(0, import_yaml3.visit)(document, (_key, node, path4) => {
|
|
16941
17551
|
nodes += 1;
|
|
16942
17552
|
if (nodes > limits.maxTotalNodes) fail("parse", "node_limit", label, `document exceeds ${limits.maxTotalNodes} nodes`);
|
|
16943
17553
|
if (path4.length > limits.maxDepth) fail("parse", "depth_limit", label, `document exceeds nesting depth ${limits.maxDepth}`);
|
|
16944
|
-
if ((0,
|
|
17554
|
+
if ((0, import_yaml3.isAlias)(node)) fail("parse", "alias_forbidden", label, "aliases are forbidden");
|
|
16945
17555
|
if (node && typeof node === "object" && "anchor" in node && typeof node.anchor === "string") {
|
|
16946
17556
|
fail("parse", "anchor_forbidden", label, "anchors are forbidden");
|
|
16947
17557
|
}
|
|
16948
|
-
if ((0,
|
|
17558
|
+
if ((0, import_yaml3.isCollection)(node) && node.items.length > limits.maxCollectionItems) {
|
|
16949
17559
|
fail("parse", "collection_limit", label, `collection exceeds ${limits.maxCollectionItems} items`);
|
|
16950
17560
|
}
|
|
16951
|
-
if ((0,
|
|
17561
|
+
if ((0, import_yaml3.isMap)(node)) {
|
|
16952
17562
|
keys += node.items.length;
|
|
16953
17563
|
if (keys > limits.maxKeys) fail("parse", "key_limit", label, `document exceeds ${limits.maxKeys} mapping keys`);
|
|
16954
17564
|
for (const pair of node.items) {
|
|
16955
|
-
if (!(0,
|
|
17565
|
+
if (!(0, import_yaml3.isScalar)(pair.key) || typeof pair.key.value !== "string") {
|
|
16956
17566
|
fail("parse", "non_string_key", label, "mapping keys must be strings");
|
|
16957
17567
|
}
|
|
16958
17568
|
}
|
|
16959
17569
|
}
|
|
16960
|
-
if ((0,
|
|
17570
|
+
if ((0, import_yaml3.isScalar)(node) && typeof node.value === "string" && Buffer.byteLength(node.value, "utf8") > limits.maxScalarBytes) {
|
|
16961
17571
|
fail("parse", "scalar_limit", label, `scalar exceeds ${limits.maxScalarBytes} bytes`);
|
|
16962
17572
|
}
|
|
16963
17573
|
if (node && typeof node === "object" && "tag" in node && typeof node.tag === "string" && !ALLOWED_YAML_TAGS.has(node.tag)) {
|
|
@@ -16969,11 +17579,11 @@ function parseRestrictedYaml(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
16969
17579
|
if (!isJsonObject(value)) fail("parse", "root_not_object", label, "resource root must be a mapping");
|
|
16970
17580
|
return value;
|
|
16971
17581
|
}
|
|
16972
|
-
var
|
|
17582
|
+
var import_yaml3, KXM_YAML_LIMITS, ALLOWED_YAML_TAGS, RestrictedYamlError;
|
|
16973
17583
|
var init_restricted_yaml = __esm({
|
|
16974
17584
|
"plugins/kxm/src/restricted-yaml.mjs"() {
|
|
16975
17585
|
"use strict";
|
|
16976
|
-
|
|
17586
|
+
import_yaml3 = __toESM(require_dist(), 1);
|
|
16977
17587
|
KXM_YAML_LIMITS = Object.freeze({
|
|
16978
17588
|
maxDocumentBytes: 256 * 1024,
|
|
16979
17589
|
maxDepth: 32,
|
|
@@ -17598,8 +18208,8 @@ var init_policy_draft = __esm({
|
|
|
17598
18208
|
});
|
|
17599
18209
|
|
|
17600
18210
|
// plugins/kxm/src/workforce-names.mjs
|
|
17601
|
-
import { existsSync as
|
|
17602
|
-
import { join as
|
|
18211
|
+
import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7 } from "node:fs";
|
|
18212
|
+
import { join as join8 } from "node:path";
|
|
17603
18213
|
function noteDeprecatedId(kind, from, to) {
|
|
17604
18214
|
const key = `${kind}\0${from}\0${to}`;
|
|
17605
18215
|
if (warned.has(key)) return;
|
|
@@ -17650,15 +18260,15 @@ function lookupById(records, requested, kind) {
|
|
|
17650
18260
|
}
|
|
17651
18261
|
function findYamlBasename(directory, requested, kind) {
|
|
17652
18262
|
if (typeof requested !== "string" || !requested) return void 0;
|
|
17653
|
-
if (
|
|
17654
|
-
if (!
|
|
18263
|
+
if (existsSync8(join8(directory, `${requested}.yaml`))) return requested;
|
|
18264
|
+
if (!existsSync8(directory)) return void 0;
|
|
17655
18265
|
const found = [];
|
|
17656
18266
|
for (const name of readdirSync(directory)) {
|
|
17657
18267
|
if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
|
|
17658
18268
|
const id = name.slice(0, -5);
|
|
17659
18269
|
let doc;
|
|
17660
18270
|
try {
|
|
17661
|
-
doc =
|
|
18271
|
+
doc = import_yaml4.default.parse(readFileSync7(join8(directory, name), "utf8"));
|
|
17662
18272
|
} catch {
|
|
17663
18273
|
continue;
|
|
17664
18274
|
}
|
|
@@ -17669,7 +18279,7 @@ function findYamlBasename(directory, requested, kind) {
|
|
|
17669
18279
|
return hit?.record.id;
|
|
17670
18280
|
}
|
|
17671
18281
|
function readYamlDir(directory, skipInventory) {
|
|
17672
|
-
if (!
|
|
18282
|
+
if (!existsSync8(directory)) return [];
|
|
17673
18283
|
const docs = [];
|
|
17674
18284
|
for (const name of readdirSync(directory)) {
|
|
17675
18285
|
if (!name.endsWith(".yaml")) continue;
|
|
@@ -17677,7 +18287,7 @@ function readYamlDir(directory, skipInventory) {
|
|
|
17677
18287
|
if (skipInventory && id === "inventory") continue;
|
|
17678
18288
|
let doc;
|
|
17679
18289
|
try {
|
|
17680
|
-
doc =
|
|
18290
|
+
doc = import_yaml4.default.parse(readFileSync7(join8(directory, name), "utf8"));
|
|
17681
18291
|
} catch (error) {
|
|
17682
18292
|
docs.push({ id, file: `${directory}/${name}`, error: error instanceof Error ? error.message : "invalid YAML" });
|
|
17683
18293
|
continue;
|
|
@@ -17702,18 +18312,18 @@ function selectorsFor(doc) {
|
|
|
17702
18312
|
function lintWorkforce(root) {
|
|
17703
18313
|
const errors = [];
|
|
17704
18314
|
const warnings = [];
|
|
17705
|
-
const models = readYamlDir(
|
|
17706
|
-
for (const entry of readYamlDir(
|
|
18315
|
+
const models = readYamlDir(join8(root, ".kxm", "models"), true).filter((entry) => !entry.error);
|
|
18316
|
+
for (const entry of readYamlDir(join8(root, ".kxm", "models"), true)) {
|
|
17707
18317
|
if (entry.error) push(errors, "error", "invalid_yaml", `.kxm/models/${entry.id}.yaml`, entry.error);
|
|
17708
18318
|
}
|
|
17709
|
-
const roles = readYamlDir(
|
|
17710
|
-
const agents = readYamlDir(
|
|
17711
|
-
const workflows = readYamlDir(
|
|
18319
|
+
const roles = readYamlDir(join8(root, ".kxm", "roles"), false).filter((entry) => !entry.error);
|
|
18320
|
+
const agents = readYamlDir(join8(root, ".kxm", "agents"), false).filter((entry) => !entry.error);
|
|
18321
|
+
const workflows = readYamlDir(join8(root, ".kxm", "workflows"), false).filter((entry) => !entry.error);
|
|
17712
18322
|
let admitted = [];
|
|
17713
|
-
const routesFile =
|
|
17714
|
-
if (
|
|
18323
|
+
const routesFile = join8(root, ".kxm", "routes.yaml");
|
|
18324
|
+
if (existsSync8(routesFile)) {
|
|
17715
18325
|
try {
|
|
17716
|
-
const policy =
|
|
18326
|
+
const policy = import_yaml4.default.parse(readFileSync7(routesFile, "utf8"));
|
|
17717
18327
|
admitted = Array.isArray(policy?.admitted) ? policy.admitted.filter((item) => typeof item === "string") : [];
|
|
17718
18328
|
} catch (error) {
|
|
17719
18329
|
push(errors, "error", "invalid_yaml", ".kxm/routes.yaml", error instanceof Error ? error.message : "invalid YAML");
|
|
@@ -17816,11 +18426,11 @@ function lintWorkforce(root) {
|
|
|
17816
18426
|
}
|
|
17817
18427
|
return { errors, warnings };
|
|
17818
18428
|
}
|
|
17819
|
-
var
|
|
18429
|
+
var import_yaml4, ROSTER_EFFORTS, ROLE_IDS, ROUTE_RENAMES, AGENT_RENAMES, WORKFLOW_RENAMES, STEP_RENAMES, RENAMES, RESERVED_WORKFLOW_IDS, warned;
|
|
17820
18430
|
var init_workforce_names = __esm({
|
|
17821
18431
|
"plugins/kxm/src/workforce-names.mjs"() {
|
|
17822
18432
|
"use strict";
|
|
17823
|
-
|
|
18433
|
+
import_yaml4 = __toESM(require_dist(), 1);
|
|
17824
18434
|
ROSTER_EFFORTS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
17825
18435
|
ROLE_IDS = Object.freeze([
|
|
17826
18436
|
"writer",
|
|
@@ -18095,7 +18705,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
18095
18705
|
const values = coreTemplate(projectId, projectName, variant);
|
|
18096
18706
|
const coreFiles = /* @__PURE__ */ new Map();
|
|
18097
18707
|
const records = [...values.entries()].map(([path4, value]) => {
|
|
18098
|
-
const bytes = Buffer.from((0,
|
|
18708
|
+
const bytes = Buffer.from((0, import_yaml5.stringify)(value, { lineWidth: 0 }), "utf8");
|
|
18099
18709
|
coreFiles.set(path4, bytes);
|
|
18100
18710
|
return {
|
|
18101
18711
|
path: path4,
|
|
@@ -18115,7 +18725,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
18115
18725
|
const fileEntries = [...coreFiles.entries()];
|
|
18116
18726
|
fileEntries.push([
|
|
18117
18727
|
KXM_TEMPLATE_PROVENANCE_PATH,
|
|
18118
|
-
Buffer.from((0,
|
|
18728
|
+
Buffer.from((0, import_yaml5.stringify)(provenance, { lineWidth: 0 }), "utf8")
|
|
18119
18729
|
]);
|
|
18120
18730
|
fileEntries.sort(([left], [right]) => compareCodeUnits3(left, right));
|
|
18121
18731
|
const files = new Map(fileEntries);
|
|
@@ -18137,11 +18747,11 @@ function resolveKxmTemplateBaseline(value) {
|
|
|
18137
18747
|
}
|
|
18138
18748
|
return void 0;
|
|
18139
18749
|
}
|
|
18140
|
-
var
|
|
18750
|
+
var import_yaml5, KXM_TEMPLATE_ID, KXM_TEMPLATE_PROVENANCE_PATH, CURRENT_KXM_TEMPLATE_VARIANT, SUPPORTED_KXM_TEMPLATE_VARIANTS;
|
|
18141
18751
|
var init_template = __esm({
|
|
18142
18752
|
"plugins/kxm/src/template.ts"() {
|
|
18143
18753
|
"use strict";
|
|
18144
|
-
|
|
18754
|
+
import_yaml5 = __toESM(require_dist(), 1);
|
|
18145
18755
|
KXM_TEMPLATE_ID = "builtin-minimal";
|
|
18146
18756
|
KXM_TEMPLATE_PROVENANCE_PATH = ".kxm/template-provenance.yaml";
|
|
18147
18757
|
CURRENT_KXM_TEMPLATE_VARIANT = "v4-registry";
|
|
@@ -18309,8 +18919,8 @@ var init_oneshot_process = __esm({
|
|
|
18309
18919
|
});
|
|
18310
18920
|
|
|
18311
18921
|
// plugins/kxm/src/harness.ts
|
|
18312
|
-
import { spawnSync as
|
|
18313
|
-
import { existsSync as
|
|
18922
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18923
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
18314
18924
|
import { win32 as win32Path } from "node:path";
|
|
18315
18925
|
function reportedModelId(value) {
|
|
18316
18926
|
return typeof value === "string" && /^[a-z0-9][a-z0-9._:/-]{0,199}$/i.test(value) ? value : void 0;
|
|
@@ -18586,7 +19196,7 @@ function findWinNpmInnerExe(cliId, options = {}) {
|
|
|
18586
19196
|
if ((options.platform ?? process.platform) !== "win32") return void 0;
|
|
18587
19197
|
const segments = WIN_NPM_INNER_EXE[cliId];
|
|
18588
19198
|
if (!segments) return void 0;
|
|
18589
|
-
const exists = options.existsSync ??
|
|
19199
|
+
const exists = options.existsSync ?? existsSync9;
|
|
18590
19200
|
const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
|
|
18591
19201
|
for (const dir of pathEnv.split(";").filter(Boolean)) {
|
|
18592
19202
|
if (!exists(win32Path.join(dir, `${cliId}.cmd`))) continue;
|
|
@@ -18598,7 +19208,7 @@ function findWinNpmInnerExe(cliId, options = {}) {
|
|
|
18598
19208
|
function defaultRunner(env) {
|
|
18599
19209
|
return (command, args, timeoutMs) => {
|
|
18600
19210
|
try {
|
|
18601
|
-
const result =
|
|
19211
|
+
const result = spawnSync3(command, [...args], {
|
|
18602
19212
|
encoding: "utf8",
|
|
18603
19213
|
timeout: timeoutMs,
|
|
18604
19214
|
windowsHide: true,
|
|
@@ -19332,13 +19942,13 @@ var init_harness = __esm({
|
|
|
19332
19942
|
});
|
|
19333
19943
|
|
|
19334
19944
|
// plugins/kxm/src/project-config.ts
|
|
19335
|
-
import { spawnSync as
|
|
19945
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
19336
19946
|
import { createHash as createHash7 } from "node:crypto";
|
|
19337
|
-
import { existsSync as
|
|
19338
|
-
import { basename as basename2, dirname as
|
|
19947
|
+
import { existsSync as existsSync10, lstatSync, readFileSync as readFileSync8, readdirSync as readdirSync2, realpathSync } from "node:fs";
|
|
19948
|
+
import { basename as basename2, dirname as dirname7, extname, isAbsolute as isAbsolute3, join as join9, relative, resolve as resolve6, sep } from "node:path";
|
|
19339
19949
|
import { pathToFileURL } from "node:url";
|
|
19340
19950
|
function defaultKxmSchemaDir() {
|
|
19341
|
-
return
|
|
19951
|
+
return join9(findKxmRepoRoot(import.meta.url), "schemas");
|
|
19342
19952
|
}
|
|
19343
19953
|
function compareCodeUnits4(left, right) {
|
|
19344
19954
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -19364,7 +19974,7 @@ function parseRestrictedYaml2(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
19364
19974
|
}
|
|
19365
19975
|
}
|
|
19366
19976
|
function readJsonObject(file) {
|
|
19367
|
-
const parsed = JSON.parse(
|
|
19977
|
+
const parsed = JSON.parse(readFileSync8(file, "utf8"));
|
|
19368
19978
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${file} is not a JSON object`);
|
|
19369
19979
|
return parsed;
|
|
19370
19980
|
}
|
|
@@ -19437,7 +20047,7 @@ function schemaIssue(file, error) {
|
|
|
19437
20047
|
return issue3("schema", `schema_${error.keyword}`, file, `${location} ${error.message ?? "is invalid"}${suffix}`);
|
|
19438
20048
|
}
|
|
19439
20049
|
function canonicalHostPath(path4) {
|
|
19440
|
-
const resolved =
|
|
20050
|
+
const resolved = resolve6(path4);
|
|
19441
20051
|
try {
|
|
19442
20052
|
return realpathSync.native(resolved);
|
|
19443
20053
|
} catch {
|
|
@@ -19456,15 +20066,15 @@ function containedHostPath(root, candidate) {
|
|
|
19456
20066
|
function portableBindingIssue(root, pathHint, repositoryId) {
|
|
19457
20067
|
let current = root;
|
|
19458
20068
|
for (const segment of pathHint === "." ? [] : pathHint.split("/")) {
|
|
19459
|
-
current =
|
|
19460
|
-
if (!
|
|
20069
|
+
current = join9(current, segment);
|
|
20070
|
+
if (!existsSync10(current)) break;
|
|
19461
20071
|
const stat = lstatSync(current);
|
|
19462
20072
|
if (stat.isSymbolicLink()) {
|
|
19463
20073
|
return issue3("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
|
|
19464
20074
|
}
|
|
19465
20075
|
}
|
|
19466
|
-
const binding =
|
|
19467
|
-
if (
|
|
20076
|
+
const binding = resolve6(root, ...pathHint.split("/"));
|
|
20077
|
+
if (existsSync10(binding)) {
|
|
19468
20078
|
const realRoot = canonicalHostPath(root);
|
|
19469
20079
|
const realBinding = canonicalHostPath(binding);
|
|
19470
20080
|
if (!containedHostPath(realRoot, realBinding)) {
|
|
@@ -19501,10 +20111,10 @@ function readResource(registry, root, file, logicalPath, kind, id, containmentRo
|
|
|
19501
20111
|
fail2("discovery", "resource_missing", label, "resource does not exist");
|
|
19502
20112
|
}
|
|
19503
20113
|
if (stat.isSymbolicLink()) fail2("path", "resource_symlink", label, "configuration resources must not be symbolic links");
|
|
19504
|
-
const boundary =
|
|
19505
|
-
let parent =
|
|
20114
|
+
const boundary = resolve6(containmentRoot);
|
|
20115
|
+
let parent = dirname7(file);
|
|
19506
20116
|
while (parent !== boundary) {
|
|
19507
|
-
if (parent ===
|
|
20117
|
+
if (parent === dirname7(parent)) fail2("path", "resource_outside_project", label, "resource escapes the project root");
|
|
19508
20118
|
let parentStat;
|
|
19509
20119
|
try {
|
|
19510
20120
|
parentStat = lstatSync(parent);
|
|
@@ -19512,10 +20122,10 @@ function readResource(registry, root, file, logicalPath, kind, id, containmentRo
|
|
|
19512
20122
|
fail2("discovery", "resource_parent_missing", label, "resource parent does not exist");
|
|
19513
20123
|
}
|
|
19514
20124
|
if (parentStat.isSymbolicLink()) fail2("path", "resource_parent_symlink", label, "configuration resource parents must not be symbolic links");
|
|
19515
|
-
parent =
|
|
20125
|
+
parent = dirname7(parent);
|
|
19516
20126
|
}
|
|
19517
20127
|
if (!stat.isFile()) fail2("path", "resource_not_file", label, "configuration resource must be a regular file");
|
|
19518
|
-
return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry,
|
|
20128
|
+
return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry, readFileSync8(file), label, kind) };
|
|
19519
20129
|
}
|
|
19520
20130
|
function resourceValue(registry, input, label, kind) {
|
|
19521
20131
|
const value = parseRestrictedYaml2(input, label);
|
|
@@ -19524,24 +20134,24 @@ function resourceValue(registry, input, label, kind) {
|
|
|
19524
20134
|
return value;
|
|
19525
20135
|
}
|
|
19526
20136
|
function readTemplateProvenance(registry, root) {
|
|
19527
|
-
const file =
|
|
19528
|
-
if (!
|
|
20137
|
+
const file = join9(root, ".kxm", "template-provenance.yaml");
|
|
20138
|
+
if (!existsSync10(file)) return void 0;
|
|
19529
20139
|
const label = ".kxm/template-provenance.yaml";
|
|
19530
20140
|
const stat = lstatSync(file);
|
|
19531
20141
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
19532
20142
|
fail2("path", "resource_not_file", label, "template provenance must be a regular file, not a link or directory");
|
|
19533
20143
|
}
|
|
19534
|
-
const configRoot =
|
|
19535
|
-
let parent =
|
|
20144
|
+
const configRoot = join9(root, ".kxm");
|
|
20145
|
+
let parent = dirname7(file);
|
|
19536
20146
|
while (parent !== root) {
|
|
19537
20147
|
const parentStat = lstatSync(parent);
|
|
19538
20148
|
if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) {
|
|
19539
20149
|
fail2("path", "resource_parent_symlink", label, "template provenance parents must be regular directories");
|
|
19540
20150
|
}
|
|
19541
20151
|
if (parent === configRoot) break;
|
|
19542
|
-
parent =
|
|
20152
|
+
parent = dirname7(parent);
|
|
19543
20153
|
}
|
|
19544
|
-
const value = parseRestrictedYaml2(
|
|
20154
|
+
const value = parseRestrictedYaml2(readFileSync8(file), label);
|
|
19545
20155
|
const issues = registry.validateTemplateProvenance(value, label);
|
|
19546
20156
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
19547
20157
|
if (!resolveKxmTemplateBaseline(value)) {
|
|
@@ -19572,7 +20182,7 @@ function readTemplateProvenance(registry, root) {
|
|
|
19572
20182
|
}
|
|
19573
20183
|
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
|
|
19574
20184
|
const resources = /* @__PURE__ */ new Map();
|
|
19575
|
-
if (!
|
|
20185
|
+
if (!existsSync10(directory)) return resources;
|
|
19576
20186
|
const stat = lstatSync(directory);
|
|
19577
20187
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
19578
20188
|
fail2("path", "resource_directory_invalid", displayPath(root, directory), "resource directory must be a regular directory");
|
|
@@ -19581,28 +20191,28 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
|
|
|
19581
20191
|
const entries = readdirSync2(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits4(left.name, right.name));
|
|
19582
20192
|
for (const entry of entries) {
|
|
19583
20193
|
if (entry.isDirectory()) {
|
|
19584
|
-
issues.push(issue3("path", "nested_resource_directory", displayPath(root,
|
|
20194
|
+
issues.push(issue3("path", "nested_resource_directory", displayPath(root, join9(directory, entry.name)), "nested resource directories are not allowed"));
|
|
19585
20195
|
continue;
|
|
19586
20196
|
}
|
|
19587
20197
|
if (!entry.name.toLowerCase().endsWith(".yaml") && !entry.name.toLowerCase().endsWith(".yml")) continue;
|
|
19588
20198
|
if (!entry.isFile() || extname(entry.name) !== ".yaml") {
|
|
19589
|
-
issues.push(issue3("path", "resource_filename_invalid", displayPath(root,
|
|
20199
|
+
issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join9(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
|
|
19590
20200
|
continue;
|
|
19591
20201
|
}
|
|
19592
20202
|
const id = basename2(entry.name, ".yaml");
|
|
19593
20203
|
if (kind === "model" && id === "inventory") continue;
|
|
19594
20204
|
if (id === replacedId) continue;
|
|
19595
20205
|
if (!resourceIdentifier(id)) {
|
|
19596
|
-
issues.push(issue3("path", "resource_id_invalid", displayPath(root,
|
|
20206
|
+
issues.push(issue3("path", "resource_id_invalid", displayPath(root, join9(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
|
|
19597
20207
|
continue;
|
|
19598
20208
|
}
|
|
19599
20209
|
const collision = [...resources.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
19600
20210
|
if (collision) {
|
|
19601
|
-
issues.push(issue3("path", "resource_id_collision", displayPath(root,
|
|
20211
|
+
issues.push(issue3("path", "resource_id_collision", displayPath(root, join9(directory, entry.name)), `${id} case-folds to existing ${collision}`));
|
|
19602
20212
|
continue;
|
|
19603
20213
|
}
|
|
19604
20214
|
try {
|
|
19605
|
-
const resource = readResource(registry, root,
|
|
20215
|
+
const resource = readResource(registry, root, join9(directory, entry.name), `${logicalDirectory}/${id}.yaml`, kind, id);
|
|
19606
20216
|
resources.set(id, resource);
|
|
19607
20217
|
} catch (error) {
|
|
19608
20218
|
if (error instanceof KxmConfigError) issues.push(...error.issues);
|
|
@@ -20143,10 +20753,10 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
20143
20753
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
20144
20754
|
if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
20145
20755
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
20146
|
-
if (projectRoot &&
|
|
20756
|
+
if (projectRoot && existsSync10(join9(projectRoot, ".kxm", "roster.yaml"))) {
|
|
20147
20757
|
issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
|
|
20148
20758
|
}
|
|
20149
|
-
if (projectRoot &&
|
|
20759
|
+
if (projectRoot && existsSync10(join9(projectRoot, ".kxm", "roles"))) {
|
|
20150
20760
|
issues.push(...developerRolePolicyIssues(projectRoot));
|
|
20151
20761
|
}
|
|
20152
20762
|
if (projectRoot) {
|
|
@@ -20161,12 +20771,12 @@ function rosterModelIssues(projectRoot, roles) {
|
|
|
20161
20771
|
for (const entry of valuesOf(role.value, "roster")) {
|
|
20162
20772
|
const route = stringValue(objectValue(entry)?.route);
|
|
20163
20773
|
if (!route) continue;
|
|
20164
|
-
const resolved = findYamlBasename(
|
|
20774
|
+
const resolved = findYamlBasename(join9(projectRoot, ".kxm", "models"), route, "route") ?? route;
|
|
20165
20775
|
const logical = `.kxm/models/${resolved}.yaml`;
|
|
20166
|
-
const file =
|
|
20776
|
+
const file = join9(projectRoot, ".kxm", "models", `${resolved}.yaml`);
|
|
20167
20777
|
let harness;
|
|
20168
20778
|
try {
|
|
20169
|
-
if (
|
|
20779
|
+
if (existsSync10(file)) harness = stringValue(parseRestrictedYaml2(readFileSync8(file, "utf8"), logical).harness);
|
|
20170
20780
|
} catch {
|
|
20171
20781
|
harness = void 0;
|
|
20172
20782
|
}
|
|
@@ -20184,7 +20794,7 @@ function originHashIssues(projectRoot, models) {
|
|
|
20184
20794
|
const source = origin ? stringValue(origin.source) : void 0;
|
|
20185
20795
|
const digest = origin ? stringValue(origin.sha256) : void 0;
|
|
20186
20796
|
if (!source || !digest) continue;
|
|
20187
|
-
const file =
|
|
20797
|
+
const file = resolve6(projectRoot, source);
|
|
20188
20798
|
const escaped = relative(projectRoot, file);
|
|
20189
20799
|
if (isAbsolute3(source) || escaped.startsWith("..") || isAbsolute3(escaped)) {
|
|
20190
20800
|
issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
|
|
@@ -20192,7 +20802,7 @@ function originHashIssues(projectRoot, models) {
|
|
|
20192
20802
|
}
|
|
20193
20803
|
let actual;
|
|
20194
20804
|
try {
|
|
20195
|
-
actual = createHash7("sha256").update(
|
|
20805
|
+
actual = createHash7("sha256").update(readFileSync8(file, "utf8"), "utf8").digest("hex");
|
|
20196
20806
|
} catch (error) {
|
|
20197
20807
|
const code = error.code;
|
|
20198
20808
|
if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
@@ -20208,10 +20818,10 @@ function originHashIssues(projectRoot, models) {
|
|
|
20208
20818
|
return issues;
|
|
20209
20819
|
}
|
|
20210
20820
|
function developerCeilings() {
|
|
20211
|
-
const script =
|
|
20821
|
+
const script = join9(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
|
|
20212
20822
|
let loaded;
|
|
20213
20823
|
try {
|
|
20214
|
-
loaded =
|
|
20824
|
+
loaded = spawnSync4(process.execPath, ["--input-type=module", "-e", `const m = await import(${JSON.stringify(pathToFileURL(script).href)}); process.stdout.write(JSON.stringify({ROUTES:m.ROUTES,NATIVE_PI_BRAKE_PROVIDERS:m.NATIVE_PI_BRAKE_PROVIDERS,PI_ALLOWED_PROVIDERS:m.PI_ALLOWED_PROVIDERS,PI_NATIVE_VENDOR_PROVIDERS:m.PI_NATIVE_VENDOR_PROVIDERS}))`], {
|
|
20215
20825
|
encoding: "utf8",
|
|
20216
20826
|
timeout: 15e3
|
|
20217
20827
|
});
|
|
@@ -20234,22 +20844,22 @@ function developerCeilings() {
|
|
|
20234
20844
|
}
|
|
20235
20845
|
}
|
|
20236
20846
|
function developerRolePolicyIssues(projectRoot) {
|
|
20237
|
-
if (
|
|
20847
|
+
if (resolve6(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
|
|
20238
20848
|
const lintIssues = lintWorkforce(projectRoot).errors.map((entry) => issue3("semantic", entry.code, entry.file, entry.message));
|
|
20239
|
-
const rolesDir =
|
|
20240
|
-
const modelsDir =
|
|
20849
|
+
const rolesDir = join9(projectRoot, ".kxm", "roles");
|
|
20850
|
+
const modelsDir = join9(projectRoot, ".kxm", "models");
|
|
20241
20851
|
const roles = {};
|
|
20242
20852
|
const models = {};
|
|
20243
20853
|
const evidence = {};
|
|
20244
20854
|
const parseIssues = [];
|
|
20245
20855
|
const readMap = (dir, into, skipInventory) => {
|
|
20246
|
-
if (!
|
|
20856
|
+
if (!existsSync10(dir)) return;
|
|
20247
20857
|
for (const name of readdirSync2(dir)) {
|
|
20248
20858
|
if (!name.endsWith(".yaml")) continue;
|
|
20249
20859
|
const id = basename2(name, ".yaml");
|
|
20250
20860
|
if (skipInventory && id === "inventory") continue;
|
|
20251
20861
|
try {
|
|
20252
|
-
const value = parseRestrictedYaml2(
|
|
20862
|
+
const value = parseRestrictedYaml2(readFileSync8(join9(dir, name), "utf8"), `${dir}/${name}`);
|
|
20253
20863
|
into[id] = value;
|
|
20254
20864
|
} catch (error) {
|
|
20255
20865
|
if (error instanceof KxmConfigError) {
|
|
@@ -20266,8 +20876,8 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
20266
20876
|
const origin = objectValue(model.origin);
|
|
20267
20877
|
const source = origin ? stringValue(origin.source) : void 0;
|
|
20268
20878
|
if (!source || evidence[source] !== void 0) continue;
|
|
20269
|
-
const evidenceFile = isAbsolute3(source) ? source :
|
|
20270
|
-
if (
|
|
20879
|
+
const evidenceFile = isAbsolute3(source) ? source : join9(projectRoot, source);
|
|
20880
|
+
if (existsSync10(evidenceFile)) evidence[source] = readFileSync8(evidenceFile, "utf8");
|
|
20271
20881
|
}
|
|
20272
20882
|
const ceilings = developerCeilings();
|
|
20273
20883
|
if (!ceilings.ok) {
|
|
@@ -20305,21 +20915,21 @@ function gitEnvironment() {
|
|
|
20305
20915
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
20306
20916
|
}
|
|
20307
20917
|
function discoverGitRoot(start = process.cwd()) {
|
|
20308
|
-
let current =
|
|
20309
|
-
if (
|
|
20310
|
-
const result =
|
|
20918
|
+
let current = resolve6(start);
|
|
20919
|
+
if (existsSync10(current) && !lstatSync(current).isDirectory()) current = dirname7(current);
|
|
20920
|
+
const result = spawnSync4("git", ["-C", current, "rev-parse", "--show-toplevel"], {
|
|
20311
20921
|
encoding: "utf8",
|
|
20312
20922
|
env: gitEnvironment(),
|
|
20313
20923
|
timeout: 5e3,
|
|
20314
20924
|
windowsHide: true
|
|
20315
20925
|
});
|
|
20316
20926
|
if (result.status !== 0 || result.error || !result.stdout.trim()) return void 0;
|
|
20317
|
-
const root =
|
|
20318
|
-
return
|
|
20927
|
+
const root = resolve6(result.stdout.trim());
|
|
20928
|
+
return existsSync10(root) && lstatSync(root).isDirectory() ? root : void 0;
|
|
20319
20929
|
}
|
|
20320
20930
|
function discoverKxmProjectRoot(start = process.cwd()) {
|
|
20321
20931
|
const gitRoot = discoverGitRoot(start);
|
|
20322
|
-
return gitRoot &&
|
|
20932
|
+
return gitRoot && existsSync10(join9(gitRoot, ".kxm", "project.yaml")) ? gitRoot : void 0;
|
|
20323
20933
|
}
|
|
20324
20934
|
function assertNoRegisteredGates(options) {
|
|
20325
20935
|
if ("registeredGates" in options) {
|
|
@@ -20348,7 +20958,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20348
20958
|
const workflowCandidate = candidate?.kind === "workflow" ? candidate : void 0;
|
|
20349
20959
|
const writerRole = candidate?.kind === "role" && candidate.id.toLocaleLowerCase("en-US") === "writer" ? candidate.document : void 0;
|
|
20350
20960
|
assertNoRegisteredGates(options);
|
|
20351
|
-
const root =
|
|
20961
|
+
const root = resolve6(projectRoot);
|
|
20352
20962
|
const legacyPresent = legacyConfigFilesAt(root);
|
|
20353
20963
|
if (legacyPresent.length > 0) {
|
|
20354
20964
|
throw new KxmConfigError(legacyPresent.map((file) => issue3(
|
|
@@ -20359,7 +20969,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20359
20969
|
)));
|
|
20360
20970
|
}
|
|
20361
20971
|
const registry = new KxmSchemaRegistry(options.schemasDir);
|
|
20362
|
-
const project = readResource(registry, root,
|
|
20972
|
+
const project = readResource(registry, root, join9(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
|
|
20363
20973
|
const earlyIssues = [];
|
|
20364
20974
|
validatePortablePaths(project, earlyIssues);
|
|
20365
20975
|
const templateProvenance = readTemplateProvenance(registry, root);
|
|
@@ -20378,30 +20988,30 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20378
20988
|
}
|
|
20379
20989
|
}
|
|
20380
20990
|
if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
|
|
20381
|
-
const agents = listNamedResources(registry, root,
|
|
20991
|
+
const agents = listNamedResources(registry, root, join9(root, ".kxm", "agents"), ".kxm/agents", "agent");
|
|
20382
20992
|
const modelIssues = [];
|
|
20383
|
-
const models = listNamedResources(registry, root,
|
|
20384
|
-
const roles = listNamedResources(registry, root,
|
|
20993
|
+
const models = listNamedResources(registry, root, join9(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
|
|
20994
|
+
const roles = listNamedResources(registry, root, join9(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
|
|
20385
20995
|
if (writerRole !== void 0) {
|
|
20386
20996
|
const logicalPath = ".kxm/roles/writer.yaml";
|
|
20387
|
-
roles.set("writer", { kind: "role", id: "writer", file:
|
|
20997
|
+
roles.set("writer", { kind: "role", id: "writer", file: join9(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
|
|
20388
20998
|
}
|
|
20389
|
-
const workflows = listNamedResources(registry, root,
|
|
20999
|
+
const workflows = listNamedResources(registry, root, join9(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
|
|
20390
21000
|
if (workflowCandidate) {
|
|
20391
21001
|
const { id, document } = workflowCandidate;
|
|
20392
21002
|
const logicalPath = `.kxm/workflows/${id}.yaml`;
|
|
20393
21003
|
if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
|
|
20394
21004
|
const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
20395
21005
|
if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
|
|
20396
|
-
workflows.set(id, { kind: "workflow", id, file:
|
|
21006
|
+
workflows.set(id, { kind: "workflow", id, file: join9(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
|
|
20397
21007
|
}
|
|
20398
|
-
const gatePath =
|
|
20399
|
-
const gateRegistry =
|
|
21008
|
+
const gatePath = join9(root, ".kxm", "gates.yaml");
|
|
21009
|
+
const gateRegistry = existsSync10(gatePath) ? readResource(registry, root, gatePath, ".kxm/gates.yaml", "gate-registry") : void 0;
|
|
20400
21010
|
const environments = [];
|
|
20401
21011
|
const repositories = /* @__PURE__ */ new Map();
|
|
20402
21012
|
const loadIssues = [];
|
|
20403
|
-
const projectEnvironment =
|
|
20404
|
-
if (
|
|
21013
|
+
const projectEnvironment = join9(root, ".kxm", "project", "env.yaml");
|
|
21014
|
+
if (existsSync10(projectEnvironment)) {
|
|
20405
21015
|
try {
|
|
20406
21016
|
environments.push(readResource(registry, root, projectEnvironment, ".kxm/project/env.yaml", "environment"));
|
|
20407
21017
|
} catch (error) {
|
|
@@ -20438,12 +21048,12 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20438
21048
|
continue;
|
|
20439
21049
|
}
|
|
20440
21050
|
}
|
|
20441
|
-
const binding = hasLocalBinding && localBinding ?
|
|
21051
|
+
const binding = hasLocalBinding && localBinding ? resolve6(localBinding) : role === "control" ? root : pathHint && portablePath(pathHint) ? resolve6(root, ...pathHint.split("/")) : void 0;
|
|
20442
21052
|
if (!binding) {
|
|
20443
21053
|
if (required2) loadIssues.push(issue3("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
|
|
20444
21054
|
continue;
|
|
20445
21055
|
}
|
|
20446
|
-
if (!
|
|
21056
|
+
if (!existsSync10(binding)) {
|
|
20447
21057
|
if (required2 || hasLocalBinding) loadIssues.push(issue3("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
|
|
20448
21058
|
continue;
|
|
20449
21059
|
}
|
|
@@ -20475,9 +21085,9 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20475
21085
|
continue;
|
|
20476
21086
|
}
|
|
20477
21087
|
seenBindings.set(foldedBinding, repositoryId);
|
|
20478
|
-
const repositoryFile =
|
|
21088
|
+
const repositoryFile = join9(binding, ".kxm", "repo", "repo.yaml");
|
|
20479
21089
|
let definitionValidated = false;
|
|
20480
|
-
if (
|
|
21090
|
+
if (existsSync10(repositoryFile)) {
|
|
20481
21091
|
try {
|
|
20482
21092
|
const resource = readResource(registry, root, repositoryFile, `.kxm/repositories/${repositoryId}/repo.yaml`, "repository", repositoryId, binding);
|
|
20483
21093
|
if (resource.value.repositoryId !== repositoryId) {
|
|
@@ -20493,8 +21103,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
20493
21103
|
} else if (required2 || hasLocalBinding) {
|
|
20494
21104
|
loadIssues.push(issue3("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
|
|
20495
21105
|
}
|
|
20496
|
-
const environmentFile =
|
|
20497
|
-
if (
|
|
21106
|
+
const environmentFile = join9(binding, ".kxm", "repo", "env.yaml");
|
|
21107
|
+
if (existsSync10(environmentFile)) {
|
|
20498
21108
|
if (!definitionValidated) {
|
|
20499
21109
|
loadIssues.push(issue3("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
|
|
20500
21110
|
} else {
|
|
@@ -20533,19 +21143,19 @@ function legacyInputsAt(root) {
|
|
|
20533
21143
|
".kxm/config/workflows",
|
|
20534
21144
|
".kxm/state/kxm.db"
|
|
20535
21145
|
];
|
|
20536
|
-
return candidates.filter((candidate) =>
|
|
21146
|
+
return candidates.filter((candidate) => existsSync10(join9(root, ...candidate.split("/"))));
|
|
20537
21147
|
}
|
|
20538
21148
|
function legacyConfigFilesAt(root) {
|
|
20539
21149
|
const files = [];
|
|
20540
21150
|
for (const component of [".kxm", ".kxm/config"]) {
|
|
20541
|
-
const absolute =
|
|
20542
|
-
if (
|
|
21151
|
+
const absolute = join9(root, ...component.split("/"));
|
|
21152
|
+
if (existsSync10(absolute) && lstatSync(absolute).isSymbolicLink()) return [component];
|
|
20543
21153
|
}
|
|
20544
21154
|
for (const candidate of LEGACY_CONFIG_FILES) {
|
|
20545
|
-
if (
|
|
21155
|
+
if (existsSync10(join9(root, ...candidate.split("/")))) files.push(candidate);
|
|
20546
21156
|
}
|
|
20547
|
-
const workflowsDir =
|
|
20548
|
-
if (
|
|
21157
|
+
const workflowsDir = join9(root, ".kxm", "config", "workflows");
|
|
21158
|
+
if (existsSync10(workflowsDir)) {
|
|
20549
21159
|
const stat = lstatSync(workflowsDir);
|
|
20550
21160
|
if (stat.isSymbolicLink()) {
|
|
20551
21161
|
files.push(".kxm/config/workflows");
|
|
@@ -20560,14 +21170,14 @@ function legacyConfigFilesAt(root) {
|
|
|
20560
21170
|
return files;
|
|
20561
21171
|
}
|
|
20562
21172
|
function discoverLegacyRoot(start) {
|
|
20563
|
-
let initial =
|
|
20564
|
-
if (
|
|
21173
|
+
let initial = resolve6(start);
|
|
21174
|
+
if (existsSync10(initial) && !lstatSync(initial).isDirectory()) initial = dirname7(initial);
|
|
20565
21175
|
const candidate = discoverGitRoot(initial) ?? initial;
|
|
20566
21176
|
const inputs = legacyInputsAt(candidate);
|
|
20567
21177
|
return inputs.length > 0 ? { root: candidate, inputs } : void 0;
|
|
20568
21178
|
}
|
|
20569
21179
|
function planKxmInitialization(start = process.cwd(), options = {}) {
|
|
20570
|
-
const inspectedFrom =
|
|
21180
|
+
const inspectedFrom = resolve6(start);
|
|
20571
21181
|
const projectRoot = discoverKxmProjectRoot(inspectedFrom);
|
|
20572
21182
|
if (projectRoot) {
|
|
20573
21183
|
try {
|
|
@@ -20604,7 +21214,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
|
|
|
20604
21214
|
}
|
|
20605
21215
|
const gitRoot = discoverGitRoot(inspectedFrom);
|
|
20606
21216
|
const candidateRoot = gitRoot ?? inspectedFrom;
|
|
20607
|
-
if (
|
|
21217
|
+
if (existsSync10(join9(candidateRoot, ".kxm"))) {
|
|
20608
21218
|
return {
|
|
20609
21219
|
mode: "repair",
|
|
20610
21220
|
inspectedFrom,
|
|
@@ -20673,12 +21283,12 @@ var init_project_config = __esm({
|
|
|
20673
21283
|
intakeMessageValidator;
|
|
20674
21284
|
syncEventValidator;
|
|
20675
21285
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
20676
|
-
this.schemasDir =
|
|
21286
|
+
this.schemasDir = resolve6(schemasDir);
|
|
20677
21287
|
this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
20678
|
-
const common = readJsonObject(
|
|
21288
|
+
const common = readJsonObject(join9(this.schemasDir, "common.schema.json"));
|
|
20679
21289
|
this.ajv.addSchema(common);
|
|
20680
21290
|
for (const definition of Object.values(RESOURCE_SCHEMA)) {
|
|
20681
|
-
this.ajv.addSchema(readJsonObject(
|
|
21291
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, definition.file)));
|
|
20682
21292
|
}
|
|
20683
21293
|
const localBindingsFile = "local-repository-bindings.schema.json";
|
|
20684
21294
|
const templateProvenanceFile = "template-provenance.schema.json";
|
|
@@ -20689,15 +21299,15 @@ var init_project_config = __esm({
|
|
|
20689
21299
|
const coordinatorFile = "coordinator.schema.json";
|
|
20690
21300
|
const intakeMessageFile = "intake-message.schema.json";
|
|
20691
21301
|
const syncEventFile = "sync-event.schema.json";
|
|
20692
|
-
this.ajv.addSchema(readJsonObject(
|
|
20693
|
-
this.ajv.addSchema(readJsonObject(
|
|
20694
|
-
this.ajv.addSchema(readJsonObject(
|
|
20695
|
-
this.ajv.addSchema(readJsonObject(
|
|
20696
|
-
this.ajv.addSchema(readJsonObject(
|
|
20697
|
-
this.ajv.addSchema(readJsonObject(
|
|
20698
|
-
this.ajv.addSchema(readJsonObject(
|
|
20699
|
-
this.ajv.addSchema(readJsonObject(
|
|
20700
|
-
this.ajv.addSchema(readJsonObject(
|
|
21302
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, localBindingsFile)));
|
|
21303
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, templateProvenanceFile)));
|
|
21304
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, initOperationFile)));
|
|
21305
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, permissionDiffFile)));
|
|
21306
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, runEventFile)));
|
|
21307
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, driveReceiptFile)));
|
|
21308
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, coordinatorFile)));
|
|
21309
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, intakeMessageFile)));
|
|
21310
|
+
this.ajv.addSchema(readJsonObject(join9(this.schemasDir, syncEventFile)));
|
|
20701
21311
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
|
|
20702
21312
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/${definition.file}`);
|
|
20703
21313
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -20773,9 +21383,9 @@ var init_project_config = __esm({
|
|
|
20773
21383
|
});
|
|
20774
21384
|
|
|
20775
21385
|
// plugins/kxm/src/sqlite.ts
|
|
20776
|
-
import { existsSync as
|
|
21386
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
20777
21387
|
import { createRequire } from "node:module";
|
|
20778
|
-
import { resolve as
|
|
21388
|
+
import { resolve as resolve7 } from "node:path";
|
|
20779
21389
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
20780
21390
|
function loadNative() {
|
|
20781
21391
|
try {
|
|
@@ -20792,8 +21402,8 @@ function loadNative() {
|
|
|
20792
21402
|
throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
|
|
20793
21403
|
}
|
|
20794
21404
|
function openReadOnlyDatabase(path4) {
|
|
20795
|
-
if (
|
|
20796
|
-
const uri = pathToFileURL2(
|
|
21405
|
+
if (existsSync11(`${path4}-wal`)) return new DatabaseSync(path4, { readOnly: true });
|
|
21406
|
+
const uri = pathToFileURL2(resolve7(path4));
|
|
20797
21407
|
uri.searchParams.set("immutable", "1");
|
|
20798
21408
|
return new DatabaseSync(uri.href, { readOnly: true });
|
|
20799
21409
|
}
|
|
@@ -20827,24 +21437,24 @@ var init_sqlite = __esm({
|
|
|
20827
21437
|
});
|
|
20828
21438
|
|
|
20829
21439
|
// plugins/kxm/src/bindings.ts
|
|
20830
|
-
import { spawnSync as
|
|
21440
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
20831
21441
|
import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
|
|
20832
21442
|
import {
|
|
20833
21443
|
chmodSync as chmodSync2,
|
|
20834
21444
|
closeSync,
|
|
20835
|
-
existsSync as
|
|
21445
|
+
existsSync as existsSync12,
|
|
20836
21446
|
fsyncSync,
|
|
20837
21447
|
lstatSync as lstatSync2,
|
|
20838
|
-
mkdirSync as
|
|
21448
|
+
mkdirSync as mkdirSync6,
|
|
20839
21449
|
openSync,
|
|
20840
|
-
readFileSync as
|
|
21450
|
+
readFileSync as readFileSync9,
|
|
20841
21451
|
realpathSync as realpathSync2,
|
|
20842
21452
|
renameSync as renameSync3,
|
|
20843
21453
|
rmSync as rmSync3,
|
|
20844
|
-
writeFileSync as
|
|
21454
|
+
writeFileSync as writeFileSync6
|
|
20845
21455
|
} from "node:fs";
|
|
20846
|
-
import { homedir as
|
|
20847
|
-
import { dirname as
|
|
21456
|
+
import { homedir as homedir5 } from "node:os";
|
|
21457
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join10, parse as parse2, relative as relative2, resolve as resolve8, sep as sep2 } from "node:path";
|
|
20848
21458
|
function bindingIssue(phase, code, message) {
|
|
20849
21459
|
return { phase, code, file: BINDING_LABEL, message };
|
|
20850
21460
|
}
|
|
@@ -20852,7 +21462,7 @@ function bindingError(phase, code, message) {
|
|
|
20852
21462
|
throw new KxmConfigError([bindingIssue(phase, code, message)]);
|
|
20853
21463
|
}
|
|
20854
21464
|
function canonicalHostPath2(path4) {
|
|
20855
|
-
const absolute =
|
|
21465
|
+
const absolute = resolve8(path4);
|
|
20856
21466
|
try {
|
|
20857
21467
|
return realpathSync2.native(absolute);
|
|
20858
21468
|
} catch {
|
|
@@ -20867,21 +21477,21 @@ function sameHostPath2(left, right, platform = process.platform) {
|
|
|
20867
21477
|
function kxmUserStateRoot(options = {}) {
|
|
20868
21478
|
const env = options.env ?? process.env;
|
|
20869
21479
|
const platform = options.platform ?? process.platform;
|
|
20870
|
-
const home = options.homeDir ??
|
|
21480
|
+
const home = options.homeDir ?? homedir5();
|
|
20871
21481
|
const explicit = options.stateRoot ?? env.KXM_STATE_HOME?.trim();
|
|
20872
21482
|
if (explicit) {
|
|
20873
21483
|
if (!isAbsolute4(explicit)) bindingError("path", "local_state_root_not_absolute", "KXM_STATE_HOME must be an absolute host-local path");
|
|
20874
|
-
return
|
|
21484
|
+
return resolve8(explicit);
|
|
20875
21485
|
}
|
|
20876
21486
|
if (platform === "win32") {
|
|
20877
21487
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
20878
|
-
const base2 = localAppData && isAbsolute4(localAppData) ? localAppData :
|
|
20879
|
-
return
|
|
21488
|
+
const base2 = localAppData && isAbsolute4(localAppData) ? localAppData : join10(home, "AppData", "Local");
|
|
21489
|
+
return resolve8(base2, "KXM");
|
|
20880
21490
|
}
|
|
20881
|
-
if (platform === "darwin") return
|
|
21491
|
+
if (platform === "darwin") return resolve8(home, "Library", "Application Support", "KXM");
|
|
20882
21492
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
20883
|
-
const base = xdgState && isAbsolute4(xdgState) ? xdgState :
|
|
20884
|
-
return
|
|
21493
|
+
const base = xdgState && isAbsolute4(xdgState) ? xdgState : join10(home, ".local", "state");
|
|
21494
|
+
return resolve8(base, "kxm");
|
|
20885
21495
|
}
|
|
20886
21496
|
function projectBindingKey(projectRoot, platform = process.platform) {
|
|
20887
21497
|
const canonical2 = canonicalHostPath2(projectRoot);
|
|
@@ -20890,10 +21500,10 @@ function projectBindingKey(projectRoot, platform = process.platform) {
|
|
|
20890
21500
|
}
|
|
20891
21501
|
function kxmLocalBindingFile(projectRoot, options = {}) {
|
|
20892
21502
|
const stateRoot = kxmUserStateRoot(options);
|
|
20893
|
-
return
|
|
21503
|
+
return join10(stateRoot, "projects", projectBindingKey(projectRoot, options.platform), "repository-bindings.json");
|
|
20894
21504
|
}
|
|
20895
21505
|
function checkedDirectory(path4, description) {
|
|
20896
|
-
if (!
|
|
21506
|
+
if (!existsSync12(path4)) return false;
|
|
20897
21507
|
const stat = lstatSync2(path4);
|
|
20898
21508
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
20899
21509
|
bindingError("path", "local_binding_directory_invalid", `${description} must be a regular directory, not a link or file`);
|
|
@@ -20901,8 +21511,8 @@ function checkedDirectory(path4, description) {
|
|
|
20901
21511
|
return true;
|
|
20902
21512
|
}
|
|
20903
21513
|
function existingBindingDirectory(file, stateRoot) {
|
|
20904
|
-
const projects =
|
|
20905
|
-
const project =
|
|
21514
|
+
const projects = join10(stateRoot, "projects");
|
|
21515
|
+
const project = dirname8(file);
|
|
20906
21516
|
if (!checkedDirectory(stateRoot, "local state root")) return false;
|
|
20907
21517
|
if (!checkedDirectory(projects, "local projects directory")) return false;
|
|
20908
21518
|
return checkedDirectory(project, "local project binding directory");
|
|
@@ -20925,10 +21535,10 @@ function asRecord2(value, file, projectRoot, schemasDir) {
|
|
|
20925
21535
|
};
|
|
20926
21536
|
}
|
|
20927
21537
|
function readKxmLocalBindings(projectRoot, options = {}) {
|
|
20928
|
-
const root =
|
|
21538
|
+
const root = resolve8(projectRoot);
|
|
20929
21539
|
const stateRoot = kxmUserStateRoot(options);
|
|
20930
21540
|
const file = kxmLocalBindingFile(root, options);
|
|
20931
|
-
if (!existingBindingDirectory(file, stateRoot) || !
|
|
21541
|
+
if (!existingBindingDirectory(file, stateRoot) || !existsSync12(file)) return void 0;
|
|
20932
21542
|
const stat = lstatSync2(file);
|
|
20933
21543
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
20934
21544
|
bindingError("path", "local_binding_file_invalid", "binding record must be a regular file, not a link or directory");
|
|
@@ -20936,7 +21546,7 @@ function readKxmLocalBindings(projectRoot, options = {}) {
|
|
|
20936
21546
|
if (stat.size > MAX_BINDING_RECORD_BYTES) {
|
|
20937
21547
|
bindingError("parse", "local_binding_file_too_large", `binding record exceeds ${MAX_BINDING_RECORD_BYTES} bytes`);
|
|
20938
21548
|
}
|
|
20939
|
-
const parsed = parseRestrictedYaml2(
|
|
21549
|
+
const parsed = parseRestrictedYaml2(readFileSync9(file), BINDING_LABEL);
|
|
20940
21550
|
return asRecord2(parsed, file, root, options.schemasDir);
|
|
20941
21551
|
}
|
|
20942
21552
|
function normalizedRecord(projectRoot, projectId, repositories, schemasDir) {
|
|
@@ -20976,48 +21586,48 @@ function syncDirectory(path4) {
|
|
|
20976
21586
|
}
|
|
20977
21587
|
}
|
|
20978
21588
|
function createDirectoryIfMissing(path4) {
|
|
20979
|
-
if (
|
|
21589
|
+
if (existsSync12(path4)) return;
|
|
20980
21590
|
try {
|
|
20981
|
-
|
|
20982
|
-
syncDirectory(
|
|
21591
|
+
mkdirSync6(path4, { mode: 448 });
|
|
21592
|
+
syncDirectory(dirname8(path4));
|
|
20983
21593
|
} catch (error) {
|
|
20984
21594
|
if (error.code !== "EEXIST") throw error;
|
|
20985
21595
|
}
|
|
20986
21596
|
}
|
|
20987
21597
|
function ensureDurableDirectory(path4, description) {
|
|
20988
21598
|
const missing = [];
|
|
20989
|
-
let current =
|
|
20990
|
-
while (!
|
|
21599
|
+
let current = resolve8(path4);
|
|
21600
|
+
while (!existsSync12(current)) {
|
|
20991
21601
|
missing.push(current);
|
|
20992
|
-
const parent =
|
|
21602
|
+
const parent = dirname8(current);
|
|
20993
21603
|
if (parent === current) bindingError("path", "local_binding_directory_invalid", `${description} has no existing filesystem ancestor`);
|
|
20994
21604
|
current = parent;
|
|
20995
21605
|
}
|
|
20996
21606
|
checkedDirectory(current, `${description} ancestor`);
|
|
20997
21607
|
for (const directory of missing.reverse()) {
|
|
20998
|
-
|
|
21608
|
+
mkdirSync6(directory, { mode: 448 });
|
|
20999
21609
|
checkedDirectory(directory, description);
|
|
21000
|
-
syncDirectory(
|
|
21610
|
+
syncDirectory(dirname8(directory));
|
|
21001
21611
|
}
|
|
21002
21612
|
}
|
|
21003
21613
|
function ensureBindingDirectory(file, stateRoot) {
|
|
21004
21614
|
ensureDurableDirectory(stateRoot, "local state root");
|
|
21005
21615
|
checkedDirectory(stateRoot, "local state root");
|
|
21006
21616
|
syncDirectory(stateRoot);
|
|
21007
|
-
const projects =
|
|
21617
|
+
const projects = join10(stateRoot, "projects");
|
|
21008
21618
|
createDirectoryIfMissing(projects);
|
|
21009
21619
|
checkedDirectory(projects, "local projects directory");
|
|
21010
|
-
const project =
|
|
21620
|
+
const project = dirname8(file);
|
|
21011
21621
|
createDirectoryIfMissing(project);
|
|
21012
21622
|
checkedDirectory(project, "local project binding directory");
|
|
21013
21623
|
}
|
|
21014
21624
|
function assertNoLinkedDirectoryComponents(path4, description) {
|
|
21015
|
-
const absolute =
|
|
21016
|
-
const filesystemRoot =
|
|
21625
|
+
const absolute = resolve8(path4);
|
|
21626
|
+
const filesystemRoot = parse2(absolute).root;
|
|
21017
21627
|
let current = filesystemRoot;
|
|
21018
21628
|
const remainder = relative2(filesystemRoot, absolute);
|
|
21019
21629
|
for (const segment of remainder.split(sep2).filter(Boolean)) {
|
|
21020
|
-
current =
|
|
21630
|
+
current = join10(current, segment);
|
|
21021
21631
|
const stat = lstatSync2(current);
|
|
21022
21632
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
21023
21633
|
bindingError("path", "project_operation_lock_parent_invalid", `${description} contains a linked or non-directory component`);
|
|
@@ -21029,7 +21639,7 @@ function projectOperationLockFile(projectRoot) {
|
|
|
21029
21639
|
for (const key of Object.keys(env)) {
|
|
21030
21640
|
if (key.toLocaleUpperCase("en-US").startsWith("GIT_")) delete env[key];
|
|
21031
21641
|
}
|
|
21032
|
-
const result =
|
|
21642
|
+
const result = spawnSync5("git", ["-C", projectRoot, "rev-parse", "--absolute-git-dir"], {
|
|
21033
21643
|
encoding: "utf8",
|
|
21034
21644
|
env,
|
|
21035
21645
|
timeout: 5e3,
|
|
@@ -21038,28 +21648,28 @@ function projectOperationLockFile(projectRoot) {
|
|
|
21038
21648
|
if (result.status !== 0 || result.error || !result.stdout.trim()) {
|
|
21039
21649
|
bindingError("discovery", "project_operation_lock_unavailable", "cannot resolve the authoritative Git metadata path for project locking");
|
|
21040
21650
|
}
|
|
21041
|
-
const gitDirectory =
|
|
21651
|
+
const gitDirectory = resolve8(result.stdout.trim());
|
|
21042
21652
|
assertNoLinkedDirectoryComponents(gitDirectory, "authoritative Git metadata path");
|
|
21043
21653
|
const gitStat = lstatSync2(gitDirectory);
|
|
21044
21654
|
if (gitStat.isSymbolicLink() || !gitStat.isDirectory()) {
|
|
21045
21655
|
bindingError("path", "project_operation_lock_parent_invalid", "authoritative Git metadata must resolve to a regular directory");
|
|
21046
21656
|
}
|
|
21047
|
-
const lockDirectory =
|
|
21048
|
-
if (
|
|
21657
|
+
const lockDirectory = join10(gitDirectory, "kxm");
|
|
21658
|
+
if (existsSync12(lockDirectory)) {
|
|
21049
21659
|
const stat = lstatSync2(lockDirectory);
|
|
21050
21660
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
21051
21661
|
bindingError("path", "project_operation_lock_parent_invalid", "Git-local KXM lock directory must be regular, not a link or file");
|
|
21052
21662
|
}
|
|
21053
21663
|
} else {
|
|
21054
|
-
|
|
21664
|
+
mkdirSync6(lockDirectory, { mode: 448 });
|
|
21055
21665
|
syncDirectory(gitDirectory);
|
|
21056
21666
|
}
|
|
21057
|
-
return
|
|
21667
|
+
return join10(lockDirectory, "project-operation-lock.sqlite");
|
|
21058
21668
|
}
|
|
21059
21669
|
function withKxmLocalBindingLock(projectRoot, options, callback) {
|
|
21060
|
-
const root =
|
|
21670
|
+
const root = resolve8(projectRoot);
|
|
21061
21671
|
const file = projectOperationLockFile(root);
|
|
21062
|
-
if (
|
|
21672
|
+
if (existsSync12(file)) {
|
|
21063
21673
|
const stat = lstatSync2(file);
|
|
21064
21674
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
21065
21675
|
bindingError("path", "local_binding_lock_invalid", "project-operation lock must be a regular file, not a link or directory");
|
|
@@ -21092,7 +21702,7 @@ function withKxmLocalBindingLock(projectRoot, options, callback) {
|
|
|
21092
21702
|
}
|
|
21093
21703
|
}
|
|
21094
21704
|
function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}) {
|
|
21095
|
-
const root =
|
|
21705
|
+
const root = resolve8(projectRoot);
|
|
21096
21706
|
const file = kxmLocalBindingFile(root, options);
|
|
21097
21707
|
const record = normalizedRecord(root, projectId, repositories, options.schemasDir);
|
|
21098
21708
|
const existing = readKxmLocalBindings(root, options);
|
|
@@ -21102,7 +21712,7 @@ function planKxmLocalBindings(projectRoot, projectId, repositories, options = {}
|
|
|
21102
21712
|
return { file, written: !existing || !recordsEqual(existing, record), record };
|
|
21103
21713
|
}
|
|
21104
21714
|
function writeKxmLocalBindings(projectRoot, projectId, repositories, options = {}, lock) {
|
|
21105
|
-
const root =
|
|
21715
|
+
const root = resolve8(projectRoot);
|
|
21106
21716
|
if (!lock) {
|
|
21107
21717
|
return withKxmLocalBindingLock(root, options, (acquired) => writeKxmLocalBindings(root, projectId, repositories, options, acquired));
|
|
21108
21718
|
}
|
|
@@ -21113,17 +21723,17 @@ function writeKxmLocalBindings(projectRoot, projectId, repositories, options = {
|
|
|
21113
21723
|
const { file, record } = planned;
|
|
21114
21724
|
if (!planned.written) return planned;
|
|
21115
21725
|
ensureBindingDirectory(file, kxmUserStateRoot(options));
|
|
21116
|
-
const temporary =
|
|
21726
|
+
const temporary = join10(dirname8(file), `.repository-bindings-${process.pid}-${randomUUID3()}.tmp`);
|
|
21117
21727
|
let descriptor;
|
|
21118
21728
|
try {
|
|
21119
21729
|
descriptor = openSync(temporary, "wx", 384);
|
|
21120
|
-
|
|
21730
|
+
writeFileSync6(descriptor, `${JSON.stringify(record, null, 2)}
|
|
21121
21731
|
`, "utf8");
|
|
21122
21732
|
fsyncSync(descriptor);
|
|
21123
21733
|
closeSync(descriptor);
|
|
21124
21734
|
descriptor = void 0;
|
|
21125
21735
|
renameSync3(temporary, file);
|
|
21126
|
-
syncDirectory(
|
|
21736
|
+
syncDirectory(dirname8(file));
|
|
21127
21737
|
} finally {
|
|
21128
21738
|
if (descriptor !== void 0) closeSync(descriptor);
|
|
21129
21739
|
rmSync3(temporary, { force: true });
|
|
@@ -21149,29 +21759,29 @@ var init_bindings = __esm({
|
|
|
21149
21759
|
// plugins/kxm/src/runtime-paths.ts
|
|
21150
21760
|
import { createHash as createHash9 } from "node:crypto";
|
|
21151
21761
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
21152
|
-
import { join as
|
|
21762
|
+
import { join as join11, resolve as resolve9 } from "node:path";
|
|
21153
21763
|
function kxmRuntimePaths(options = {}) {
|
|
21154
|
-
const stateRoot = options.stateRoot ?
|
|
21155
|
-
const runtimeDir =
|
|
21764
|
+
const stateRoot = options.stateRoot ? resolve9(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
|
|
21765
|
+
const runtimeDir = join11(stateRoot, "runtime");
|
|
21156
21766
|
return {
|
|
21157
21767
|
stateRoot,
|
|
21158
21768
|
runtimeDir,
|
|
21159
|
-
registryDb:
|
|
21160
|
-
projectsDir:
|
|
21769
|
+
registryDb: join11(runtimeDir, "registry.db"),
|
|
21770
|
+
projectsDir: join11(runtimeDir, "projects")
|
|
21161
21771
|
};
|
|
21162
21772
|
}
|
|
21163
21773
|
function projectRuntimeKey(projectRoot) {
|
|
21164
21774
|
let canonical2;
|
|
21165
21775
|
try {
|
|
21166
|
-
canonical2 = realpathSync3.native(
|
|
21776
|
+
canonical2 = realpathSync3.native(resolve9(projectRoot));
|
|
21167
21777
|
} catch {
|
|
21168
|
-
canonical2 =
|
|
21778
|
+
canonical2 = resolve9(projectRoot);
|
|
21169
21779
|
}
|
|
21170
21780
|
const folded = process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
|
|
21171
21781
|
return createHash9("sha256").update(folded, "utf8").digest("hex").slice(0, 24);
|
|
21172
21782
|
}
|
|
21173
21783
|
function kxmProjectRunEventsPath(projectRoot, env) {
|
|
21174
|
-
return
|
|
21784
|
+
return join11(kxmRuntimePaths({ env }).projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
|
|
21175
21785
|
}
|
|
21176
21786
|
var init_runtime_paths = __esm({
|
|
21177
21787
|
"plugins/kxm/src/runtime-paths.ts"() {
|
|
@@ -21498,22 +22108,22 @@ import { createHash as createHash11, randomBytes } from "node:crypto";
|
|
|
21498
22108
|
import {
|
|
21499
22109
|
chmodSync as chmodSync3,
|
|
21500
22110
|
copyFileSync,
|
|
21501
|
-
existsSync as
|
|
22111
|
+
existsSync as existsSync13,
|
|
21502
22112
|
lstatSync as lstatSync3,
|
|
21503
|
-
mkdirSync as
|
|
22113
|
+
mkdirSync as mkdirSync7,
|
|
21504
22114
|
readdirSync as readdirSync3,
|
|
21505
|
-
readFileSync as
|
|
22115
|
+
readFileSync as readFileSync10,
|
|
21506
22116
|
unlinkSync as unlinkSync2,
|
|
21507
|
-
writeFileSync as
|
|
22117
|
+
writeFileSync as writeFileSync7
|
|
21508
22118
|
} from "node:fs";
|
|
21509
|
-
import { basename as basename3, dirname as
|
|
22119
|
+
import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative3, resolve as resolve10, sep as sep3 } from "node:path";
|
|
21510
22120
|
function databaseError(code, file, message) {
|
|
21511
22121
|
const issue4 = { phase: "semantic", code, file, message };
|
|
21512
22122
|
return new KxmConfigError([issue4]);
|
|
21513
22123
|
}
|
|
21514
22124
|
function checkedParent(path4, description) {
|
|
21515
|
-
const parent =
|
|
21516
|
-
if (!
|
|
22125
|
+
const parent = dirname9(path4);
|
|
22126
|
+
if (!existsSync13(parent)) mkdirSync7(parent, { recursive: true, mode: 448 });
|
|
21517
22127
|
const stat = lstatSync3(parent, { throwIfNoEntry: false });
|
|
21518
22128
|
if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
21519
22129
|
throw databaseError("runtime_path_invalid", description, `${description} parent must be a regular directory, not a link`);
|
|
@@ -21727,18 +22337,18 @@ function checkIntegrity(database) {
|
|
|
21727
22337
|
return rows.length === 1 && rows[0]?.integrity_check === "ok";
|
|
21728
22338
|
}
|
|
21729
22339
|
function fileSha256(filePath) {
|
|
21730
|
-
const bytes =
|
|
22340
|
+
const bytes = readFileSync10(filePath);
|
|
21731
22341
|
return `sha256:${createHash11("sha256").update(bytes).digest("hex")}`;
|
|
21732
22342
|
}
|
|
21733
22343
|
function backupDatabaseFile(sourcePath2, targetPath, storeId) {
|
|
21734
|
-
const resolvedSource =
|
|
21735
|
-
const resolvedTarget =
|
|
22344
|
+
const resolvedSource = resolve10(sourcePath2);
|
|
22345
|
+
const resolvedTarget = resolve10(targetPath);
|
|
21736
22346
|
const sourceStat = lstatSync3(resolvedSource, { throwIfNoEntry: false });
|
|
21737
22347
|
if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
|
|
21738
22348
|
throw databaseError("runtime_path_invalid", resolvedSource, `source database ${resolvedSource} must be a regular file, not a link or directory`);
|
|
21739
22349
|
}
|
|
21740
22350
|
checkedParent(resolvedTarget, "backup target");
|
|
21741
|
-
if (
|
|
22351
|
+
if (existsSync13(resolvedTarget)) {
|
|
21742
22352
|
unlinkSync2(resolvedTarget);
|
|
21743
22353
|
}
|
|
21744
22354
|
const sourceDb = new DatabaseSync(resolvedSource);
|
|
@@ -21782,8 +22392,8 @@ function backupDatabaseFile(sourcePath2, targetPath, storeId) {
|
|
|
21782
22392
|
};
|
|
21783
22393
|
}
|
|
21784
22394
|
function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVersion, maxSupportedVersion) {
|
|
21785
|
-
const resolvedBackup =
|
|
21786
|
-
const resolvedTarget =
|
|
22395
|
+
const resolvedBackup = resolve10(backupPath);
|
|
22396
|
+
const resolvedTarget = resolve10(targetPath);
|
|
21787
22397
|
const backupStat = lstatSync3(resolvedBackup, { throwIfNoEntry: false });
|
|
21788
22398
|
if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
|
|
21789
22399
|
throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
|
|
@@ -21816,7 +22426,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
|
|
|
21816
22426
|
}
|
|
21817
22427
|
checkedParent(resolvedTarget, "restore target");
|
|
21818
22428
|
for (const file of [resolvedTarget, `${resolvedTarget}-wal`, `${resolvedTarget}-shm`]) {
|
|
21819
|
-
if (
|
|
22429
|
+
if (existsSync13(file)) {
|
|
21820
22430
|
try {
|
|
21821
22431
|
unlinkSync2(file);
|
|
21822
22432
|
} catch {
|
|
@@ -21855,14 +22465,14 @@ function pushStore(stores, storeId, sourcePath2) {
|
|
|
21855
22465
|
stores.push({ storeId, sourcePath: sourcePath2, maxSupportedVersion: kxmBackupCeiling(storeId) });
|
|
21856
22466
|
}
|
|
21857
22467
|
function pushEventStore(stores, files, paths, key) {
|
|
21858
|
-
const dbPath =
|
|
21859
|
-
if (!
|
|
22468
|
+
const dbPath = join12(paths.projectsDir, key, "run-events.db");
|
|
22469
|
+
if (!existsSync13(dbPath)) return;
|
|
21860
22470
|
const safe = key.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21861
22471
|
let storeId = `events:${safe}`;
|
|
21862
22472
|
if (stores.some((store) => store.storeId === storeId)) storeId = `events:runtime:${safe}`;
|
|
21863
22473
|
pushStore(stores, storeId, dbPath);
|
|
21864
22474
|
const sidecar = `${dbPath}.run-prompts.json`;
|
|
21865
|
-
if (
|
|
22475
|
+
if (existsSync13(sidecar)) {
|
|
21866
22476
|
const id = `${storeId}:run-prompts`;
|
|
21867
22477
|
if (!files.some((file) => file.id === id || file.sourcePath === sidecar)) {
|
|
21868
22478
|
files.push({ id, sourcePath: sidecar });
|
|
@@ -21880,11 +22490,11 @@ function discoverUserRuntime(runtimeProjectKey, options, stores, files) {
|
|
|
21880
22490
|
pushEventStore(stores, files, paths, runtimeProjectKey);
|
|
21881
22491
|
return paths.stateRoot;
|
|
21882
22492
|
}
|
|
21883
|
-
if (
|
|
22493
|
+
if (existsSync13(paths.registryDb)) {
|
|
21884
22494
|
const storeId = stores.some((store) => store.storeId === "registry") ? "runtime-registry" : "registry";
|
|
21885
22495
|
pushStore(stores, storeId, paths.registryDb);
|
|
21886
22496
|
}
|
|
21887
|
-
if (!
|
|
22497
|
+
if (!existsSync13(paths.projectsDir)) return paths.stateRoot;
|
|
21888
22498
|
let entries;
|
|
21889
22499
|
try {
|
|
21890
22500
|
entries = readdirSync3(paths.projectsDir, { withFileTypes: true });
|
|
@@ -21897,22 +22507,22 @@ function discoverUserRuntime(runtimeProjectKey, options, stores, files) {
|
|
|
21897
22507
|
return paths.stateRoot;
|
|
21898
22508
|
}
|
|
21899
22509
|
function discoverBackupSources(projectRoot, options = {}) {
|
|
21900
|
-
const root =
|
|
22510
|
+
const root = resolve10(projectRoot);
|
|
21901
22511
|
const stores = [];
|
|
21902
22512
|
const files = [];
|
|
21903
|
-
const hubPath = options.hubDataPath ?
|
|
21904
|
-
if (
|
|
21905
|
-
const registryPath =
|
|
21906
|
-
if (
|
|
21907
|
-
const bindingsPath =
|
|
21908
|
-
if (
|
|
21909
|
-
const eventsDir =
|
|
21910
|
-
if (
|
|
22513
|
+
const hubPath = options.hubDataPath ? resolve10(options.hubDataPath) : join12(root, ".kxm", "state", "kxm.db");
|
|
22514
|
+
if (existsSync13(hubPath)) pushStore(stores, "hub-store", hubPath);
|
|
22515
|
+
const registryPath = join12(root, ".kxm", "runtime", "registry.db");
|
|
22516
|
+
if (existsSync13(registryPath)) pushStore(stores, "registry", registryPath);
|
|
22517
|
+
const bindingsPath = join12(root, ".kxm", "runtime", "bindings.db");
|
|
22518
|
+
if (existsSync13(bindingsPath)) pushStore(stores, "binding-store", bindingsPath);
|
|
22519
|
+
const eventsDir = join12(root, ".kxm", "runtime", "events");
|
|
22520
|
+
if (existsSync13(eventsDir)) {
|
|
21911
22521
|
const entries = readdirSync3(eventsDir, { withFileTypes: true });
|
|
21912
22522
|
for (const entry of entries) {
|
|
21913
22523
|
if (entry.isFile() && entry.name.endsWith(".db")) {
|
|
21914
22524
|
const key = entry.name.replace(/\.db$/, "").replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21915
|
-
pushStore(stores, `events:${key}`,
|
|
22525
|
+
pushStore(stores, `events:${key}`, join12(eventsDir, entry.name));
|
|
21916
22526
|
}
|
|
21917
22527
|
}
|
|
21918
22528
|
}
|
|
@@ -21921,13 +22531,13 @@ function discoverBackupSources(projectRoot, options = {}) {
|
|
|
21921
22531
|
return { stores, files, runtimeProjectKey, ...stateRoot !== void 0 ? { stateRoot } : {} };
|
|
21922
22532
|
}
|
|
21923
22533
|
function backupPlainFile(sourcePath2, targetPath, id) {
|
|
21924
|
-
const resolvedSource =
|
|
22534
|
+
const resolvedSource = resolve10(sourcePath2);
|
|
21925
22535
|
const sourceStat = lstatSync3(resolvedSource, { throwIfNoEntry: false });
|
|
21926
22536
|
if (!sourceStat || !sourceStat.isFile() || sourceStat.isSymbolicLink()) {
|
|
21927
22537
|
throw databaseError("runtime_path_invalid", resolvedSource, `backup file ${resolvedSource} must be a regular file, not a link or directory`);
|
|
21928
22538
|
}
|
|
21929
22539
|
checkedParent(targetPath, "backup target");
|
|
21930
|
-
if (
|
|
22540
|
+
if (existsSync13(targetPath)) unlinkSync2(targetPath);
|
|
21931
22541
|
copyFileSync(resolvedSource, targetPath);
|
|
21932
22542
|
try {
|
|
21933
22543
|
chmodSync3(targetPath, 384);
|
|
@@ -21951,7 +22561,7 @@ function backupFilename(sourcePath2, id, used) {
|
|
|
21951
22561
|
}
|
|
21952
22562
|
function restorePlainFile(backupFilePath, targetPath) {
|
|
21953
22563
|
checkedParent(targetPath, "restore target");
|
|
21954
|
-
if (
|
|
22564
|
+
if (existsSync13(targetPath)) unlinkSync2(targetPath);
|
|
21955
22565
|
copyFileSync(backupFilePath, targetPath);
|
|
21956
22566
|
try {
|
|
21957
22567
|
chmodSync3(targetPath, 384);
|
|
@@ -21966,14 +22576,14 @@ function backupDiscoverOptions(options) {
|
|
|
21966
22576
|
};
|
|
21967
22577
|
}
|
|
21968
22578
|
function planBackup(options = {}) {
|
|
21969
|
-
const projectRoot = options.projectRoot ?
|
|
22579
|
+
const projectRoot = options.projectRoot ? resolve10(options.projectRoot) : process.cwd();
|
|
21970
22580
|
const discovered = discoverBackupSources(projectRoot, backupDiscoverOptions(options));
|
|
21971
22581
|
if (discovered.stores.length === 0) {
|
|
21972
22582
|
throw databaseError("backup_no_stores", projectRoot, "no existing SQLite stores found to backup");
|
|
21973
22583
|
}
|
|
21974
22584
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21975
22585
|
const timestamp = createdAt.replace(/[:.]/g, "-");
|
|
21976
|
-
const outDir = options.outDir ?
|
|
22586
|
+
const outDir = options.outDir ? resolve10(options.outDir) : join12(projectRoot, ".kxm", "backups", `backup-${timestamp}`);
|
|
21977
22587
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
21978
22588
|
const stores = discovered.stores.map((store) => ({
|
|
21979
22589
|
storeId: store.storeId,
|
|
@@ -21999,22 +22609,22 @@ function planBackup(options = {}) {
|
|
|
21999
22609
|
function createBackup(options = {}) {
|
|
22000
22610
|
const { projectRoot, outDir, createdAt, scope, stateRoot, runtimeProjectKey, stores, files } = planBackup(options);
|
|
22001
22611
|
const backupId = `bk_${randomBytes(8).toString("hex")}`;
|
|
22002
|
-
if (!
|
|
22003
|
-
|
|
22612
|
+
if (!existsSync13(outDir)) {
|
|
22613
|
+
mkdirSync7(outDir, { recursive: true, mode: 448 });
|
|
22004
22614
|
}
|
|
22005
22615
|
const backedUpStores = [];
|
|
22006
22616
|
const backedUpFiles = [];
|
|
22007
22617
|
const omitted = [];
|
|
22008
22618
|
for (const store of stores) {
|
|
22009
22619
|
try {
|
|
22010
|
-
backedUpStores.push(backupDatabaseFile(store.sourcePath,
|
|
22620
|
+
backedUpStores.push(backupDatabaseFile(store.sourcePath, join12(outDir, store.backupFile), store.storeId));
|
|
22011
22621
|
} catch {
|
|
22012
22622
|
omitted.push(store.storeId);
|
|
22013
22623
|
}
|
|
22014
22624
|
}
|
|
22015
22625
|
for (const file of files) {
|
|
22016
22626
|
try {
|
|
22017
|
-
backedUpFiles.push(backupPlainFile(file.sourcePath,
|
|
22627
|
+
backedUpFiles.push(backupPlainFile(file.sourcePath, join12(outDir, file.backupFile), file.id));
|
|
22018
22628
|
} catch {
|
|
22019
22629
|
omitted.push(file.id);
|
|
22020
22630
|
}
|
|
@@ -22050,8 +22660,8 @@ function createBackup(options = {}) {
|
|
|
22050
22660
|
const manifestSha256 = `sha256:${createHash11("sha256").update(manifestJson, "utf8").digest("hex")}`;
|
|
22051
22661
|
manifest.manifestSha256 = manifestSha256;
|
|
22052
22662
|
const finalJson = JSON.stringify(manifest, null, 2) + "\n";
|
|
22053
|
-
const manifestPath =
|
|
22054
|
-
|
|
22663
|
+
const manifestPath = join12(outDir, "manifest.json");
|
|
22664
|
+
writeFileSync7(manifestPath, finalJson, "utf8");
|
|
22055
22665
|
return { manifest, outDir };
|
|
22056
22666
|
}
|
|
22057
22667
|
function pathUnder(root, path4) {
|
|
@@ -22064,33 +22674,33 @@ function restoreTarget(manifest, sourcePath2, id, options, currentStateRoot) {
|
|
|
22064
22674
|
const stateRel = pathUnder(manifest.stateRoot, sourcePath2);
|
|
22065
22675
|
const projectRel = pathUnder(manifest.projectRoot, sourcePath2);
|
|
22066
22676
|
if (stateRel !== void 0 && (projectRel === void 0 || stateRel.length <= projectRel.length)) {
|
|
22067
|
-
const ownEvents = manifest.runtimeProjectKey !== void 0 ?
|
|
22677
|
+
const ownEvents = manifest.runtimeProjectKey !== void 0 ? join12("runtime", "projects", manifest.runtimeProjectKey, "run-events.db") : void 0;
|
|
22068
22678
|
const own2 = ownEvents !== void 0 && (stateRel === ownEvents || stateRel === `${ownEvents}.run-prompts.json`);
|
|
22069
22679
|
if (own2 && !options.allProjects && options.projectRoot !== void 0) {
|
|
22070
|
-
const eventsPath = kxmProjectRunEventsPath(
|
|
22680
|
+
const eventsPath = kxmProjectRunEventsPath(resolve10(options.projectRoot), options.env ?? process.env);
|
|
22071
22681
|
return { targetPath: stateRel === ownEvents ? eventsPath : `${eventsPath}.run-prompts.json`, machineWide: false };
|
|
22072
22682
|
}
|
|
22073
|
-
return { targetPath:
|
|
22683
|
+
return { targetPath: join12(currentStateRoot(), stateRel), machineWide: !own2 };
|
|
22074
22684
|
}
|
|
22075
22685
|
if (projectRel !== void 0) {
|
|
22076
|
-
return { targetPath: options.projectRoot ?
|
|
22686
|
+
return { targetPath: options.projectRoot ? join12(resolve10(options.projectRoot), projectRel) : sourcePath2, machineWide: false };
|
|
22077
22687
|
}
|
|
22078
22688
|
return { targetPath: sourcePath2, machineWide: id !== "hub-store" };
|
|
22079
22689
|
}
|
|
22080
22690
|
function planRestore(manifestPathOrDir, options = {}) {
|
|
22081
|
-
let manifestPath =
|
|
22691
|
+
let manifestPath = resolve10(manifestPathOrDir);
|
|
22082
22692
|
const stat = lstatSync3(manifestPath, { throwIfNoEntry: false });
|
|
22083
22693
|
if (!stat) {
|
|
22084
22694
|
throw databaseError("runtime_path_invalid", manifestPath, `manifest path ${manifestPath} does not exist`);
|
|
22085
22695
|
}
|
|
22086
22696
|
if (stat.isDirectory()) {
|
|
22087
|
-
manifestPath =
|
|
22697
|
+
manifestPath = join12(manifestPath, "manifest.json");
|
|
22088
22698
|
}
|
|
22089
|
-
if (!
|
|
22699
|
+
if (!existsSync13(manifestPath)) {
|
|
22090
22700
|
throw databaseError("runtime_path_invalid", manifestPath, `backup manifest ${manifestPath} not found`);
|
|
22091
22701
|
}
|
|
22092
|
-
const manifestDir =
|
|
22093
|
-
const rawText =
|
|
22702
|
+
const manifestDir = dirname9(manifestPath);
|
|
22703
|
+
const rawText = readFileSync10(manifestPath, "utf8");
|
|
22094
22704
|
let manifest;
|
|
22095
22705
|
try {
|
|
22096
22706
|
manifest = JSON.parse(rawText);
|
|
@@ -22108,8 +22718,8 @@ function planRestore(manifestPathOrDir, options = {}) {
|
|
|
22108
22718
|
const machineWide = [];
|
|
22109
22719
|
const stores = [];
|
|
22110
22720
|
for (const store of manifest.stores) {
|
|
22111
|
-
const backupFilePath =
|
|
22112
|
-
if (!
|
|
22721
|
+
const backupFilePath = join12(manifestDir, store.backupFile);
|
|
22722
|
+
if (!existsSync13(backupFilePath)) {
|
|
22113
22723
|
throw databaseError("restore_file_missing", backupFilePath, `backup file ${store.backupFile} missing from ${manifestDir}`);
|
|
22114
22724
|
}
|
|
22115
22725
|
const actualSha256 = fileSha256(backupFilePath);
|
|
@@ -22134,8 +22744,8 @@ function planRestore(manifestPathOrDir, options = {}) {
|
|
|
22134
22744
|
}
|
|
22135
22745
|
const files = [];
|
|
22136
22746
|
for (const file of manifest.files ?? []) {
|
|
22137
|
-
const backupFilePath =
|
|
22138
|
-
if (!
|
|
22747
|
+
const backupFilePath = join12(manifestDir, file.backupFile);
|
|
22748
|
+
if (!existsSync13(backupFilePath)) {
|
|
22139
22749
|
throw databaseError("restore_file_missing", backupFilePath, `backup file ${file.backupFile} missing from ${manifestDir}`);
|
|
22140
22750
|
}
|
|
22141
22751
|
const actualSha256 = fileSha256(backupFilePath);
|
|
@@ -22206,10 +22816,10 @@ var init_database = __esm({
|
|
|
22206
22816
|
});
|
|
22207
22817
|
|
|
22208
22818
|
// plugins/kxm/src/runtime-store.ts
|
|
22209
|
-
import { spawnSync as
|
|
22819
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
22210
22820
|
import { createHash as createHash12, randomUUID as randomUUID4 } from "node:crypto";
|
|
22211
|
-
import { existsSync as
|
|
22212
|
-
import { dirname as
|
|
22821
|
+
import { existsSync as existsSync14, lstatSync as lstatSync4, mkdirSync as mkdirSync8, readFileSync as readFileSync11, realpathSync as realpathSync4, statSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
22822
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, resolve as resolve11 } from "node:path";
|
|
22213
22823
|
function runtimeIssue(phase, code, file, message) {
|
|
22214
22824
|
return { phase, code, file, message };
|
|
22215
22825
|
}
|
|
@@ -22218,7 +22828,7 @@ function runtimeError(code, file, message) {
|
|
|
22218
22828
|
}
|
|
22219
22829
|
function projectRuntimeOwnsRun(projectRoot, runId, env) {
|
|
22220
22830
|
const path4 = kxmProjectRunEventsPath(projectRoot, env);
|
|
22221
|
-
if (!
|
|
22831
|
+
if (!existsSync14(path4)) return false;
|
|
22222
22832
|
const database = openReadOnlyDatabase(path4);
|
|
22223
22833
|
try {
|
|
22224
22834
|
database.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -22246,7 +22856,7 @@ function gitEnv() {
|
|
|
22246
22856
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
22247
22857
|
}
|
|
22248
22858
|
function gitCommonDirectory(projectRoot) {
|
|
22249
|
-
const result =
|
|
22859
|
+
const result = spawnSync6("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
|
|
22250
22860
|
encoding: "utf8",
|
|
22251
22861
|
env: gitEnv(),
|
|
22252
22862
|
timeout: 3e4,
|
|
@@ -22255,23 +22865,23 @@ function gitCommonDirectory(projectRoot) {
|
|
|
22255
22865
|
if (result.status !== 0) return void 0;
|
|
22256
22866
|
const text = (result.stdout ?? "").trim();
|
|
22257
22867
|
if (!text) return void 0;
|
|
22258
|
-
const absolute = isAbsolute6(text) ? text :
|
|
22868
|
+
const absolute = isAbsolute6(text) ? text : resolve11(projectRoot, text);
|
|
22259
22869
|
let canonical2;
|
|
22260
22870
|
try {
|
|
22261
22871
|
canonical2 = realpathSync4.native(absolute);
|
|
22262
22872
|
} catch {
|
|
22263
|
-
canonical2 =
|
|
22873
|
+
canonical2 = resolve11(absolute);
|
|
22264
22874
|
}
|
|
22265
22875
|
return process.platform === "win32" ? canonical2.toLocaleLowerCase("en-US") : canonical2;
|
|
22266
22876
|
}
|
|
22267
22877
|
function isPrimaryWorktree(projectRoot, commonDir) {
|
|
22268
22878
|
if (commonDir === void 0) return false;
|
|
22269
|
-
const dotGit =
|
|
22879
|
+
const dotGit = resolve11(projectRoot, ".git");
|
|
22270
22880
|
let canonical2;
|
|
22271
22881
|
try {
|
|
22272
22882
|
canonical2 = realpathSync4.native(dotGit);
|
|
22273
22883
|
} catch {
|
|
22274
|
-
canonical2 =
|
|
22884
|
+
canonical2 = resolve11(dotGit);
|
|
22275
22885
|
}
|
|
22276
22886
|
if (process.platform === "win32") canonical2 = canonical2.toLocaleLowerCase("en-US");
|
|
22277
22887
|
return canonical2 === commonDir;
|
|
@@ -22700,7 +23310,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
|
22700
23310
|
path;
|
|
22701
23311
|
database;
|
|
22702
23312
|
constructor(path4) {
|
|
22703
|
-
this.path =
|
|
23313
|
+
this.path = resolve11(path4);
|
|
22704
23314
|
migrateRegistryLanes(this.path);
|
|
22705
23315
|
this.database = openDatabase(this.path, "runtime registry", {
|
|
22706
23316
|
schema: REGISTRY_SCHEMA,
|
|
@@ -22800,7 +23410,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
|
22800
23410
|
* `project_home_conflict`.
|
|
22801
23411
|
*/
|
|
22802
23412
|
registerProject(registration) {
|
|
22803
|
-
const projectRoot =
|
|
23413
|
+
const projectRoot = resolve11(registration.projectRoot);
|
|
22804
23414
|
const projectKey = projectRuntimeKey(projectRoot);
|
|
22805
23415
|
const candidateCommon = gitCommonDirectory(projectRoot);
|
|
22806
23416
|
const commonByRoot = /* @__PURE__ */ new Map();
|
|
@@ -22972,7 +23582,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
|
22972
23582
|
}
|
|
22973
23583
|
/** How many other rows share this home root's project. Zero when this root is a lane or missing. */
|
|
22974
23584
|
homeLaneCount(projectRoot) {
|
|
22975
|
-
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(
|
|
23585
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve11(projectRoot)));
|
|
22976
23586
|
if (!row || row.lane_of !== null) return 0;
|
|
22977
23587
|
const count = this.database.prepare(
|
|
22978
23588
|
"SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
|
|
@@ -22981,7 +23591,7 @@ CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
|
22981
23591
|
}
|
|
22982
23592
|
/** Caller holds the write transaction. Does not commit. */
|
|
22983
23593
|
deleteRegisteredRoot(projectRoot) {
|
|
22984
|
-
const projectKey = projectRuntimeKey(
|
|
23594
|
+
const projectKey = projectRuntimeKey(resolve11(projectRoot));
|
|
22985
23595
|
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
|
|
22986
23596
|
if (!row) return false;
|
|
22987
23597
|
this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
|
|
@@ -23356,7 +23966,7 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
|
|
|
23356
23966
|
syncRedactor = new KxmSyncRedactor();
|
|
23357
23967
|
database;
|
|
23358
23968
|
constructor(path4) {
|
|
23359
|
-
this.path =
|
|
23969
|
+
this.path = resolve11(path4);
|
|
23360
23970
|
this.database = openDatabase(this.path, "run event store", {
|
|
23361
23971
|
schema: EVENT_STORE_SCHEMA,
|
|
23362
23972
|
version: KXM_EVENT_STORE_SCHEMA_VERSION,
|
|
@@ -23381,9 +23991,9 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
|
|
|
23381
23991
|
}
|
|
23382
23992
|
readRunPrompts() {
|
|
23383
23993
|
const sidecar = this.runPromptSidecarPath();
|
|
23384
|
-
if (!
|
|
23994
|
+
if (!existsSync14(sidecar)) return {};
|
|
23385
23995
|
try {
|
|
23386
|
-
const parsed = JSON.parse(
|
|
23996
|
+
const parsed = JSON.parse(readFileSync11(sidecar, "utf8"));
|
|
23387
23997
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
23388
23998
|
return Object.fromEntries(
|
|
23389
23999
|
Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
|
|
@@ -23393,7 +24003,7 @@ CREATE INDEX outbox_refused ON outbox(seq) WHERE refused_code IS NOT NULL;
|
|
|
23393
24003
|
}
|
|
23394
24004
|
}
|
|
23395
24005
|
writeRunPrompts(all) {
|
|
23396
|
-
|
|
24006
|
+
writeFileSync8(this.runPromptSidecarPath(), `${JSON.stringify(all)}
|
|
23397
24007
|
`, { encoding: "utf8", mode: 384 });
|
|
23398
24008
|
}
|
|
23399
24009
|
/** Run one immutable transaction, rolling back on any failure. */
|
|
@@ -24848,8 +25458,8 @@ var init_price_calc = __esm({
|
|
|
24848
25458
|
|
|
24849
25459
|
// plugins/kxm/src/prices.ts
|
|
24850
25460
|
import { createHash as createHash14 } from "node:crypto";
|
|
24851
|
-
import { existsSync as
|
|
24852
|
-
import { join as
|
|
25461
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
25462
|
+
import { join as join14 } from "node:path";
|
|
24853
25463
|
function hashPriceCatalog(catalog) {
|
|
24854
25464
|
const canonical2 = {
|
|
24855
25465
|
schema: catalog.schema,
|
|
@@ -24872,7 +25482,7 @@ function hashPriceCatalog(catalog) {
|
|
|
24872
25482
|
return createHash14("sha256").update(JSON.stringify(canonical2), "utf8").digest("hex");
|
|
24873
25483
|
}
|
|
24874
25484
|
function parsePriceCatalog(text) {
|
|
24875
|
-
const parsed = (0,
|
|
25485
|
+
const parsed = (0, import_yaml6.parse)(text);
|
|
24876
25486
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
24877
25487
|
throw new Error("price catalog must be an object");
|
|
24878
25488
|
}
|
|
@@ -24960,11 +25570,11 @@ function parsePriceCatalog(text) {
|
|
|
24960
25570
|
return catalog;
|
|
24961
25571
|
}
|
|
24962
25572
|
function loadPriceCatalog(rootOrPath) {
|
|
24963
|
-
const candidatePath =
|
|
24964
|
-
if (!candidatePath || !
|
|
25573
|
+
const candidatePath = existsSync15(join14(rootOrPath, ".kxm", "prices.yaml")) ? join14(rootOrPath, ".kxm", "prices.yaml") : existsSync15(join14(rootOrPath, "prices.yaml")) ? join14(rootOrPath, "prices.yaml") : existsSync15(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
|
|
25574
|
+
if (!candidatePath || !existsSync15(candidatePath)) {
|
|
24965
25575
|
return void 0;
|
|
24966
25576
|
}
|
|
24967
|
-
const content =
|
|
25577
|
+
const content = readFileSync12(candidatePath, "utf8");
|
|
24968
25578
|
return parsePriceCatalog(content);
|
|
24969
25579
|
}
|
|
24970
25580
|
function loadPriceCatalogForEstimate(options) {
|
|
@@ -24980,7 +25590,7 @@ function loadPriceCatalogForEstimate(options) {
|
|
|
24980
25590
|
return { catalog, unavailable: false, stale: false };
|
|
24981
25591
|
}
|
|
24982
25592
|
function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date()) {
|
|
24983
|
-
const path4 =
|
|
25593
|
+
const path4 = join14(projectRoot, ".kxm", "prices.yaml");
|
|
24984
25594
|
const loaded = loadPriceCatalog(projectRoot);
|
|
24985
25595
|
if (!loaded) throw new Error("price catalog missing");
|
|
24986
25596
|
const date = now.toISOString().slice(0, 10);
|
|
@@ -24991,7 +25601,7 @@ function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date())
|
|
|
24991
25601
|
models: loaded.models
|
|
24992
25602
|
};
|
|
24993
25603
|
const stamped = { ...body, sha256: hashPriceCatalog(body) };
|
|
24994
|
-
|
|
25604
|
+
writeFileSync9(path4, (0, import_yaml6.stringify)({
|
|
24995
25605
|
schema: stamped.schema,
|
|
24996
25606
|
date: stamped.date,
|
|
24997
25607
|
sha256: stamped.sha256,
|
|
@@ -25004,234 +25614,16 @@ function acknowledgePriceCatalog(projectRoot, now = /* @__PURE__ */ new Date())
|
|
|
25004
25614
|
}
|
|
25005
25615
|
return again;
|
|
25006
25616
|
}
|
|
25007
|
-
var
|
|
25617
|
+
var import_yaml6;
|
|
25008
25618
|
var init_prices = __esm({
|
|
25009
25619
|
"plugins/kxm/src/prices.ts"() {
|
|
25010
25620
|
"use strict";
|
|
25011
|
-
|
|
25621
|
+
import_yaml6 = __toESM(require_dist(), 1);
|
|
25012
25622
|
init_price_calc();
|
|
25013
25623
|
init_price_calc();
|
|
25014
25624
|
}
|
|
25015
25625
|
});
|
|
25016
25626
|
|
|
25017
|
-
// plugins/kxm/src/config.ts
|
|
25018
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
|
|
25019
|
-
import { homedir as homedir5 } from "node:os";
|
|
25020
|
-
import { dirname as dirname10, join as join14, resolve as resolve11 } from "node:path";
|
|
25021
|
-
function userConfigDirectory(overrideDir) {
|
|
25022
|
-
if (overrideDir) return resolve11(overrideDir);
|
|
25023
|
-
return resolve11(process.env.KXM_USER_CONFIG_DIR?.trim() || join14(homedir5(), ".config", "kxm"));
|
|
25024
|
-
}
|
|
25025
|
-
function repoConfigDirectory(repoRoot) {
|
|
25026
|
-
return resolve11(repoRoot, ".kxm");
|
|
25027
|
-
}
|
|
25028
|
-
function deepMerge(target, source) {
|
|
25029
|
-
const result = { ...target };
|
|
25030
|
-
for (const [key, val] of Object.entries(source)) {
|
|
25031
|
-
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
25032
|
-
const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
|
|
25033
|
-
result[key] = deepMerge(existing, val);
|
|
25034
|
-
} else if (val !== void 0) {
|
|
25035
|
-
result[key] = val;
|
|
25036
|
-
}
|
|
25037
|
-
}
|
|
25038
|
-
return result;
|
|
25039
|
-
}
|
|
25040
|
-
function normalizeHubConfig(raw) {
|
|
25041
|
-
const autoStart = raw?.autoStart;
|
|
25042
|
-
return { autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart };
|
|
25043
|
-
}
|
|
25044
|
-
function recordOf(raw) {
|
|
25045
|
-
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
25046
|
-
}
|
|
25047
|
-
function finiteNumber(value) {
|
|
25048
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
25049
|
-
}
|
|
25050
|
-
function normalizeImprovementConfig(raw) {
|
|
25051
|
-
const value = recordOf(raw);
|
|
25052
|
-
const threshold = recordOf(value.autoThreshold);
|
|
25053
|
-
const policy = value.promotionPolicy;
|
|
25054
|
-
const halfLife = finiteNumber(value.telemetryHalfLifeDays);
|
|
25055
|
-
const minRuns = finiteNumber(threshold.minRuns);
|
|
25056
|
-
const minPassRate = finiteNumber(threshold.minPassRate);
|
|
25057
|
-
const minCostSavings = finiteNumber(threshold.minCostSavings);
|
|
25058
|
-
return {
|
|
25059
|
-
promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
|
|
25060
|
-
telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
|
|
25061
|
-
autoThreshold: {
|
|
25062
|
-
minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
|
|
25063
|
-
minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
|
|
25064
|
-
minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
|
|
25065
|
-
}
|
|
25066
|
-
};
|
|
25067
|
-
}
|
|
25068
|
-
function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
25069
|
-
const userDir = userConfigDirectory(options.userConfigDir);
|
|
25070
|
-
const userConfigFile = join14(userDir, "config.yaml");
|
|
25071
|
-
const repoDir = repoConfigDirectory(repoRoot);
|
|
25072
|
-
const repoConfigFile = join14(repoDir, "config.yaml");
|
|
25073
|
-
let userRaw = {};
|
|
25074
|
-
let userLoadedPath;
|
|
25075
|
-
if (existsSync15(userConfigFile)) {
|
|
25076
|
-
try {
|
|
25077
|
-
const text = readFileSync12(userConfigFile, "utf8");
|
|
25078
|
-
userRaw = (0, import_yaml6.parse)(text) ?? {};
|
|
25079
|
-
userLoadedPath = userConfigFile;
|
|
25080
|
-
} catch (error) {
|
|
25081
|
-
throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
|
|
25082
|
-
}
|
|
25083
|
-
}
|
|
25084
|
-
let repoRaw = {};
|
|
25085
|
-
let repoLoadedPath;
|
|
25086
|
-
if (existsSync15(repoConfigFile)) {
|
|
25087
|
-
try {
|
|
25088
|
-
const text = readFileSync12(repoConfigFile, "utf8");
|
|
25089
|
-
repoRaw = (0, import_yaml6.parse)(text) ?? {};
|
|
25090
|
-
repoLoadedPath = repoConfigFile;
|
|
25091
|
-
} catch (error) {
|
|
25092
|
-
throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
|
|
25093
|
-
}
|
|
25094
|
-
}
|
|
25095
|
-
const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
|
|
25096
|
-
const mergedUser = deepMerge(baseCopy, userRaw);
|
|
25097
|
-
const mergedAll = deepMerge(mergedUser, repoRaw);
|
|
25098
|
-
return {
|
|
25099
|
-
schema: KXM_CONFIG_SCHEMA,
|
|
25100
|
-
user: mergedAll.user ?? {},
|
|
25101
|
-
defaults: mergedAll.defaults ?? {},
|
|
25102
|
-
dash: mergedAll.dash ?? {},
|
|
25103
|
-
sync: mergedAll.sync ?? {},
|
|
25104
|
-
hub: normalizeHubConfig(mergedAll.hub),
|
|
25105
|
-
improvement: normalizeImprovementConfig(mergedAll.improvement),
|
|
25106
|
-
routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
|
|
25107
|
-
telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
|
|
25108
|
-
loadedFrom: {
|
|
25109
|
-
userConfigPath: userLoadedPath,
|
|
25110
|
-
repoConfigPath: repoLoadedPath
|
|
25111
|
-
}
|
|
25112
|
-
};
|
|
25113
|
-
}
|
|
25114
|
-
function getKxmConfigValue(config, keyPath) {
|
|
25115
|
-
const parts = keyPath.split(".");
|
|
25116
|
-
let current = config;
|
|
25117
|
-
for (const part of parts) {
|
|
25118
|
-
if (!current || typeof current !== "object") return void 0;
|
|
25119
|
-
current = current[part];
|
|
25120
|
-
}
|
|
25121
|
-
return current;
|
|
25122
|
-
}
|
|
25123
|
-
function kxmConfigFileForScope(repoRoot, scope, userConfigDir) {
|
|
25124
|
-
return scope === "user" ? join14(userConfigDirectory(userConfigDir), "config.yaml") : join14(repoConfigDirectory(repoRoot), "config.yaml");
|
|
25125
|
-
}
|
|
25126
|
-
function configKeyParts(keyPath) {
|
|
25127
|
-
const parts = keyPath.split(".");
|
|
25128
|
-
if (parts.some((part) => !part || part === "__proto__" || part === "prototype" || part === "constructor")) {
|
|
25129
|
-
throw new Error("invalid config key path");
|
|
25130
|
-
}
|
|
25131
|
-
return parts;
|
|
25132
|
-
}
|
|
25133
|
-
function readConfigFile(targetFile2) {
|
|
25134
|
-
let existing = {};
|
|
25135
|
-
if (existsSync15(targetFile2)) {
|
|
25136
|
-
try {
|
|
25137
|
-
existing = (0, import_yaml6.parse)(readFileSync12(targetFile2, "utf8")) ?? {};
|
|
25138
|
-
} catch {
|
|
25139
|
-
existing = {};
|
|
25140
|
-
}
|
|
25141
|
-
}
|
|
25142
|
-
return existing;
|
|
25143
|
-
}
|
|
25144
|
-
function writeConfigFile(targetFile2, value) {
|
|
25145
|
-
mkdirSync8(dirname10(targetFile2), { recursive: true });
|
|
25146
|
-
writeFileSync9(targetFile2, (0, import_yaml6.stringify)(value).trim() + "\n", "utf8");
|
|
25147
|
-
}
|
|
25148
|
-
function setKxmConfigValue(repoRoot, keyPath, value, options = {}) {
|
|
25149
|
-
const targetFile2 = kxmConfigFileForScope(repoRoot, options.scope ?? "project", options.userConfigDir);
|
|
25150
|
-
const existing = readConfigFile(targetFile2);
|
|
25151
|
-
const parts = configKeyParts(keyPath);
|
|
25152
|
-
let cursor2 = existing;
|
|
25153
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
25154
|
-
const p = parts[i];
|
|
25155
|
-
if (!cursor2[p] || typeof cursor2[p] !== "object") {
|
|
25156
|
-
cursor2[p] = {};
|
|
25157
|
-
}
|
|
25158
|
-
cursor2 = cursor2[p];
|
|
25159
|
-
}
|
|
25160
|
-
cursor2[parts[parts.length - 1]] = value;
|
|
25161
|
-
if (!options.dryRun) writeConfigFile(targetFile2, existing);
|
|
25162
|
-
return { file: targetFile2 };
|
|
25163
|
-
}
|
|
25164
|
-
function formatKxmConfig(config) {
|
|
25165
|
-
const display = {
|
|
25166
|
-
schema: config.schema,
|
|
25167
|
-
user: config.user,
|
|
25168
|
-
defaults: config.defaults,
|
|
25169
|
-
dash: config.dash,
|
|
25170
|
-
sync: config.sync,
|
|
25171
|
-
loadedFrom: config.loadedFrom
|
|
25172
|
-
};
|
|
25173
|
-
return (0, import_yaml6.stringify)(display).trim();
|
|
25174
|
-
}
|
|
25175
|
-
var import_yaml6, KXM_CONFIG_SCHEMA, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
|
|
25176
|
-
var init_config = __esm({
|
|
25177
|
-
"plugins/kxm/src/config.ts"() {
|
|
25178
|
-
"use strict";
|
|
25179
|
-
import_yaml6 = __toESM(require_dist(), 1);
|
|
25180
|
-
KXM_CONFIG_SCHEMA = "kxm.config.v1";
|
|
25181
|
-
IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
|
|
25182
|
-
DEFAULT_KXM_CONFIG = {
|
|
25183
|
-
schema: KXM_CONFIG_SCHEMA,
|
|
25184
|
-
user: {
|
|
25185
|
-
theme: "dark",
|
|
25186
|
-
preferredCritics: ["reviewer-arch", "reviewer-cli"],
|
|
25187
|
-
tokenBudget: 16e3
|
|
25188
|
-
},
|
|
25189
|
-
defaults: {
|
|
25190
|
-
workflow: "software-engineering/feature-implementation",
|
|
25191
|
-
harness: "pi"
|
|
25192
|
-
},
|
|
25193
|
-
dash: {
|
|
25194
|
-
defaultScreen: "agents",
|
|
25195
|
-
refreshIntervalMs: 1e3,
|
|
25196
|
-
autoOpen: false
|
|
25197
|
-
},
|
|
25198
|
-
sync: {
|
|
25199
|
-
defaultTracker: "none"
|
|
25200
|
-
},
|
|
25201
|
-
hub: {
|
|
25202
|
-
autoStart: "background"
|
|
25203
|
-
},
|
|
25204
|
-
improvement: {
|
|
25205
|
-
promotionPolicy: "manual_pr",
|
|
25206
|
-
telemetryHalfLifeDays: 14,
|
|
25207
|
-
autoThreshold: {
|
|
25208
|
-
minRuns: 10,
|
|
25209
|
-
minPassRate: 0.95,
|
|
25210
|
-
minCostSavings: 0.5
|
|
25211
|
-
}
|
|
25212
|
-
},
|
|
25213
|
-
routing: {
|
|
25214
|
-
shadowExecution: {
|
|
25215
|
-
enabled: false,
|
|
25216
|
-
sampleRate: 0.05,
|
|
25217
|
-
candidateModels: []
|
|
25218
|
-
},
|
|
25219
|
-
circuitBreaker: {
|
|
25220
|
-
mode: "soft_demotion",
|
|
25221
|
-
failureThreshold: 3,
|
|
25222
|
-
windowSeconds: 3600,
|
|
25223
|
-
cooldownSeconds: 1800,
|
|
25224
|
-
penaltyMultiplier: 5
|
|
25225
|
-
}
|
|
25226
|
-
},
|
|
25227
|
-
telemetry: {
|
|
25228
|
-
federated: true,
|
|
25229
|
-
anonymize: true
|
|
25230
|
-
}
|
|
25231
|
-
};
|
|
25232
|
-
}
|
|
25233
|
-
});
|
|
25234
|
-
|
|
25235
25627
|
// plugins/kxm/src/role.ts
|
|
25236
25628
|
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync4, readFileSync as readFileSync13, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "node:fs";
|
|
25237
25629
|
import { join as join15 } from "node:path";
|
|
@@ -27902,6 +28294,10 @@ function printWorker(runtime, worker, payload, text, outcome) {
|
|
|
27902
28294
|
print(runtime.io, runtime.json, envelope2, text);
|
|
27903
28295
|
}
|
|
27904
28296
|
function hostMode(runtime) {
|
|
28297
|
+
try {
|
|
28298
|
+
if (describeHubConnection(runtime.cwd, runtime.env).scope === "remote") return "hub";
|
|
28299
|
+
} catch {
|
|
28300
|
+
}
|
|
27905
28301
|
try {
|
|
27906
28302
|
if (effectiveHubBindingScope(runtime.serverUrl, runtime.env) === "remote") return "hub";
|
|
27907
28303
|
} catch {
|
|
@@ -27982,13 +28378,14 @@ function runtimeFrom(ctx, command) {
|
|
|
27982
28378
|
throw error;
|
|
27983
28379
|
}
|
|
27984
28380
|
}
|
|
28381
|
+
const connection = describeHubConnection(ctx.cwd, ctx.env);
|
|
27985
28382
|
return {
|
|
27986
28383
|
...ctx,
|
|
27987
28384
|
json: Boolean(opts.json),
|
|
27988
28385
|
dryRun: Boolean(opts.dryRun),
|
|
27989
28386
|
...opts.workspace === void 0 ? {} : { workspaceFlag: opts.workspace },
|
|
27990
28387
|
dirs: workspaceDirs(ctx.cwd, opts.workspace, ctx.env),
|
|
27991
|
-
serverUrl: envServerUrl || boundHubUrl || "http://127.0.0.1:7331",
|
|
28388
|
+
serverUrl: connection.url || envServerUrl || boundHubUrl || "http://127.0.0.1:7331",
|
|
27992
28389
|
...boundHubUrl ? { boundHubUrl } : {},
|
|
27993
28390
|
fetchImpl: ctx.io.fetchImpl ?? fetch
|
|
27994
28391
|
};
|
|
@@ -28014,6 +28411,7 @@ var init_types = __esm({
|
|
|
28014
28411
|
init_envelope();
|
|
28015
28412
|
init_workflow();
|
|
28016
28413
|
init_hub_binding();
|
|
28414
|
+
init_hub_identity();
|
|
28017
28415
|
CLI_RESULT_SCHEMA = "kxm.cli-result.v1";
|
|
28018
28416
|
DRY_RUN_UNSUPPORTED = "dry_run_unsupported";
|
|
28019
28417
|
}
|
|
@@ -28051,6 +28449,7 @@ var init_hub_autostart = __esm({
|
|
|
28051
28449
|
"plugins/kxm/src/hub-autostart.ts"() {
|
|
28052
28450
|
"use strict";
|
|
28053
28451
|
init_hub_binding();
|
|
28452
|
+
init_hub_identity();
|
|
28054
28453
|
init_hub_env();
|
|
28055
28454
|
init_repo_root();
|
|
28056
28455
|
init_redact();
|
|
@@ -28222,10 +28621,10 @@ var init_vision_gate = __esm({
|
|
|
28222
28621
|
});
|
|
28223
28622
|
|
|
28224
28623
|
// plugins/kxm/src/permission.ts
|
|
28225
|
-
import { spawnSync as
|
|
28624
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28226
28625
|
import { createHash as createHash17 } from "node:crypto";
|
|
28227
28626
|
import { existsSync as existsSync27, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync25, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
|
|
28228
|
-
import { tmpdir } from "node:os";
|
|
28627
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
28229
28628
|
import { dirname as dirname15, join as join29, resolve as resolve20, sep as sep4 } from "node:path";
|
|
28230
28629
|
function canonical(value) {
|
|
28231
28630
|
return kxmCanonicalJson(value === void 0 ? null : value);
|
|
@@ -28696,7 +29095,7 @@ function gitEnvironment2() {
|
|
|
28696
29095
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
28697
29096
|
}
|
|
28698
29097
|
function git(root, args) {
|
|
28699
|
-
const result =
|
|
29098
|
+
const result = spawnSync7("git", ["-C", root, ...args], {
|
|
28700
29099
|
encoding: "utf8",
|
|
28701
29100
|
env: gitEnvironment2(),
|
|
28702
29101
|
timeout: 15e3,
|
|
@@ -28714,7 +29113,7 @@ function git(root, args) {
|
|
|
28714
29113
|
return result.stdout;
|
|
28715
29114
|
}
|
|
28716
29115
|
function gitBuffer(root, args) {
|
|
28717
|
-
const result =
|
|
29116
|
+
const result = spawnSync7("git", ["-C", root, ...args], {
|
|
28718
29117
|
env: gitEnvironment2(),
|
|
28719
29118
|
timeout: 15e3,
|
|
28720
29119
|
windowsHide: true,
|
|
@@ -28745,7 +29144,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
28745
29144
|
const tree = resolveTreeRevision(root, revision);
|
|
28746
29145
|
const listing = gitBuffer(root, ["ls-tree", "-r", "-z", "--name-only", tree]).toString("utf8");
|
|
28747
29146
|
const paths = listing.split("\0").filter((line) => line.length > 0 && (line.startsWith(".kxm/") || line.includes("/.kxm/")));
|
|
28748
|
-
const shadow = mkdtempSync(join29(
|
|
29147
|
+
const shadow = mkdtempSync(join29(tmpdir2(), "kxm-permission-base-"));
|
|
28749
29148
|
const shadowResolved = resolve20(shadow);
|
|
28750
29149
|
const shadowMembersDir = `.kxm-shadow-members-${createHash17("sha256").update(shadowResolved, "utf8").digest("hex").slice(0, 8)}`;
|
|
28751
29150
|
try {
|
|
@@ -28912,7 +29311,7 @@ function loadBaseProjectDeclarations(projectFile) {
|
|
|
28912
29311
|
return members;
|
|
28913
29312
|
}
|
|
28914
29313
|
function initShadowGitRoot(directory) {
|
|
28915
|
-
const result =
|
|
29314
|
+
const result = spawnSync7("git", ["-c", "init.defaultBranch=main", "init", "--quiet", directory], {
|
|
28916
29315
|
encoding: "utf8",
|
|
28917
29316
|
env: gitEnvironment2(),
|
|
28918
29317
|
timeout: 1e4,
|
|
@@ -30377,6 +30776,14 @@ var init_tenant_status = __esm({
|
|
|
30377
30776
|
}
|
|
30378
30777
|
});
|
|
30379
30778
|
|
|
30779
|
+
// plugins/kxm/src/project-name.ts
|
|
30780
|
+
var init_project_name = __esm({
|
|
30781
|
+
"plugins/kxm/src/project-name.ts"() {
|
|
30782
|
+
"use strict";
|
|
30783
|
+
init_hub_identity();
|
|
30784
|
+
}
|
|
30785
|
+
});
|
|
30786
|
+
|
|
30380
30787
|
// plugins/kxm/src/cli/project.ts
|
|
30381
30788
|
var project_exports = {};
|
|
30382
30789
|
__export(project_exports, {
|
|
@@ -34957,16 +35364,17 @@ init_repo_root();
|
|
|
34957
35364
|
init_client();
|
|
34958
35365
|
init_hub_env();
|
|
34959
35366
|
init_hub_binding();
|
|
34960
|
-
|
|
35367
|
+
init_config();
|
|
35368
|
+
init_hub_identity();
|
|
34961
35369
|
|
|
34962
35370
|
// plugins/kxm/src/commands.ts
|
|
34963
35371
|
init_client();
|
|
34964
35372
|
init_protocol();
|
|
34965
35373
|
init_workflow();
|
|
34966
35374
|
import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
34967
|
-
import { chmodSync, existsSync as
|
|
34968
|
-
import { homedir as
|
|
34969
|
-
import { dirname as
|
|
35375
|
+
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
35376
|
+
import { homedir as homedir4 } from "node:os";
|
|
35377
|
+
import { dirname as dirname6, join as join7, resolve as resolve5 } from "node:path";
|
|
34970
35378
|
function forwardedHops(handling) {
|
|
34971
35379
|
if (!handling?.length) return void 0;
|
|
34972
35380
|
return {
|
|
@@ -35742,18 +36150,18 @@ function parseSessionToken(token) {
|
|
|
35742
36150
|
return void 0;
|
|
35743
36151
|
}
|
|
35744
36152
|
function resolveUserConfigDirectory(overrideDir) {
|
|
35745
|
-
if (overrideDir) return
|
|
35746
|
-
return
|
|
36153
|
+
if (overrideDir) return resolve5(overrideDir);
|
|
36154
|
+
return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir4(), ".config", "kxm"));
|
|
35747
36155
|
}
|
|
35748
36156
|
function sessionTokenPath(userConfigDir) {
|
|
35749
|
-
return
|
|
36157
|
+
return join7(resolveUserConfigDirectory(userConfigDir), "session.token");
|
|
35750
36158
|
}
|
|
35751
36159
|
function persistSessionTokenToDisk(token, options) {
|
|
35752
36160
|
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
35753
|
-
const dir =
|
|
35754
|
-
|
|
36161
|
+
const dir = dirname6(filePath);
|
|
36162
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
35755
36163
|
const mode = options?.mode ?? 384;
|
|
35756
|
-
|
|
36164
|
+
writeFileSync5(filePath, `${token.trim()}
|
|
35757
36165
|
`, { encoding: "utf8", mode });
|
|
35758
36166
|
try {
|
|
35759
36167
|
chmodSync(filePath, mode);
|
|
@@ -35763,9 +36171,9 @@ function persistSessionTokenToDisk(token, options) {
|
|
|
35763
36171
|
}
|
|
35764
36172
|
function readSessionTokenFromDisk(options) {
|
|
35765
36173
|
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
35766
|
-
if (!
|
|
36174
|
+
if (!existsSync7(filePath)) return void 0;
|
|
35767
36175
|
try {
|
|
35768
|
-
const token =
|
|
36176
|
+
const token = readFileSync6(filePath, "utf8").trim();
|
|
35769
36177
|
if (!token) return void 0;
|
|
35770
36178
|
const payload = parseSessionToken(token);
|
|
35771
36179
|
if (!payload) return void 0;
|
|
@@ -35776,7 +36184,7 @@ function readSessionTokenFromDisk(options) {
|
|
|
35776
36184
|
}
|
|
35777
36185
|
function clearSessionTokenFromDisk(options) {
|
|
35778
36186
|
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
35779
|
-
if (!
|
|
36187
|
+
if (!existsSync7(filePath)) return false;
|
|
35780
36188
|
try {
|
|
35781
36189
|
unlinkSync(filePath);
|
|
35782
36190
|
return true;
|
|
@@ -35875,10 +36283,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
|
|
|
35875
36283
|
return { allowed: true };
|
|
35876
36284
|
}
|
|
35877
36285
|
const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
|
|
35878
|
-
if (
|
|
36286
|
+
if (existsSync7(tokenFile)) {
|
|
35879
36287
|
let tokenRaw;
|
|
35880
36288
|
try {
|
|
35881
|
-
tokenRaw =
|
|
36289
|
+
tokenRaw = readFileSync6(tokenFile, "utf8").trim();
|
|
35882
36290
|
} catch {
|
|
35883
36291
|
return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
|
|
35884
36292
|
}
|
|
@@ -39668,7 +40076,7 @@ init_project();
|
|
|
39668
40076
|
init_repo_root();
|
|
39669
40077
|
init_harness();
|
|
39670
40078
|
init_types();
|
|
39671
|
-
import { spawnSync as
|
|
40079
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
39672
40080
|
var PLUGIN_SUPPORTED_HARNESSES = Object.freeze(["pi", "omp", "claude"]);
|
|
39673
40081
|
async function cmdPluginInstall(runtime, options) {
|
|
39674
40082
|
const repoRoot = findKxmRepoRoot(import.meta.url);
|
|
@@ -39707,7 +40115,7 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
39707
40115
|
detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`
|
|
39708
40116
|
});
|
|
39709
40117
|
} else {
|
|
39710
|
-
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) :
|
|
40118
|
+
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync8(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
39711
40119
|
if (run.error || run.status !== 0 && run.status !== null) {
|
|
39712
40120
|
results.push({
|
|
39713
40121
|
harness: "pi",
|
|
@@ -39738,7 +40146,7 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
39738
40146
|
detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`
|
|
39739
40147
|
});
|
|
39740
40148
|
} else {
|
|
39741
|
-
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) :
|
|
40149
|
+
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync8(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
39742
40150
|
if (run.error || run.status !== 0 && run.status !== null) {
|
|
39743
40151
|
results.push({
|
|
39744
40152
|
harness: "omp",
|
|
@@ -39773,9 +40181,9 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
39773
40181
|
if (runtime.io.spawnSync) {
|
|
39774
40182
|
runtime.io.spawnSync(command, marketplaceArgs);
|
|
39775
40183
|
} else {
|
|
39776
|
-
|
|
40184
|
+
spawnSync8(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
39777
40185
|
}
|
|
39778
|
-
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) :
|
|
40186
|
+
const run = runtime.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync8(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
39779
40187
|
if (run.error || run.status !== 0 && run.status !== null) {
|
|
39780
40188
|
results.push({
|
|
39781
40189
|
harness: "claude",
|
|
@@ -39899,7 +40307,7 @@ init_project_config();
|
|
|
39899
40307
|
init_runtime_store();
|
|
39900
40308
|
init_runtime_supervisor();
|
|
39901
40309
|
init_types();
|
|
39902
|
-
import { spawnSync as
|
|
40310
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
39903
40311
|
import { chmodSync as chmodSync6, existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "node:fs";
|
|
39904
40312
|
import { basename as basename8, dirname as dirname18, join as join37, resolve as resolve25 } from "node:path";
|
|
39905
40313
|
var LANE_SCHEMA = "kxm.lanes.v1";
|
|
@@ -39914,7 +40322,7 @@ function gitEnv2() {
|
|
|
39914
40322
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
39915
40323
|
}
|
|
39916
40324
|
function git2(cwd, args) {
|
|
39917
|
-
const result =
|
|
40325
|
+
const result = spawnSync9("git", ["-C", cwd, ...args], {
|
|
39918
40326
|
encoding: "utf8",
|
|
39919
40327
|
env: gitEnv2(),
|
|
39920
40328
|
timeout: 3e4,
|
|
@@ -40489,7 +40897,7 @@ function cmdLand(runtime, options = {}) {
|
|
|
40489
40897
|
// plugins/kxm/src/cli/assign.ts
|
|
40490
40898
|
init_project_config();
|
|
40491
40899
|
init_types();
|
|
40492
|
-
import { spawnSync as
|
|
40900
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
40493
40901
|
import { existsSync as existsSync34 } from "node:fs";
|
|
40494
40902
|
import { join as join39 } from "node:path";
|
|
40495
40903
|
var kxmAssignCliSeams = {};
|
|
@@ -40575,7 +40983,7 @@ async function cmdAssign(runtime, subcommand, args) {
|
|
|
40575
40983
|
return 0;
|
|
40576
40984
|
}
|
|
40577
40985
|
const spawn6 = kxmAssignCliSeams.spawn ?? ((commandName, commandArgs, options) => {
|
|
40578
|
-
const result2 =
|
|
40986
|
+
const result2 = spawnSync10(commandName, commandArgs, {
|
|
40579
40987
|
cwd: options.cwd,
|
|
40580
40988
|
stdio: options.stdio,
|
|
40581
40989
|
env: options.env,
|
|
@@ -40595,6 +41003,8 @@ async function cmdAssign(runtime, subcommand, args) {
|
|
|
40595
41003
|
init_redact();
|
|
40596
41004
|
init_project_name();
|
|
40597
41005
|
init_hub_env();
|
|
41006
|
+
init_config();
|
|
41007
|
+
init_hub_identity();
|
|
40598
41008
|
init_envelope();
|
|
40599
41009
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
40600
41010
|
import { existsSync as existsSync40, mkdirSync as mkdirSync28, readFileSync as readFileSync36, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
|
|
@@ -49038,7 +49448,7 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
|
|
|
49038
49448
|
// plugins/kxm/src/tui.ts
|
|
49039
49449
|
import { mkdirSync as mkdirSync25 } from "node:fs";
|
|
49040
49450
|
import { join as join44, resolve as resolve27, dirname as dirname20 } from "node:path";
|
|
49041
|
-
import { spawnSync as
|
|
49451
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
49042
49452
|
|
|
49043
49453
|
// packages/core/tui/src/types/surface.ts
|
|
49044
49454
|
var KXM_TUI_LIMITS = Object.freeze({
|
|
@@ -49595,16 +50005,16 @@ function applyMeshTuiKey(view, key, itemCount = 0) {
|
|
|
49595
50005
|
function copyToClipboard(text) {
|
|
49596
50006
|
try {
|
|
49597
50007
|
if (process.platform === "darwin") {
|
|
49598
|
-
const proc =
|
|
50008
|
+
const proc = spawnSync11("pbcopy", { input: text, encoding: "utf8", windowsHide: true });
|
|
49599
50009
|
return proc.status === 0;
|
|
49600
50010
|
}
|
|
49601
50011
|
if (process.platform === "win32") {
|
|
49602
|
-
const proc =
|
|
50012
|
+
const proc = spawnSync11("clip", { input: text, encoding: "utf8", windowsHide: true });
|
|
49603
50013
|
return proc.status === 0;
|
|
49604
50014
|
}
|
|
49605
|
-
const wl =
|
|
50015
|
+
const wl = spawnSync11("wl-copy", [text], { encoding: "utf8", windowsHide: true });
|
|
49606
50016
|
if (wl.status === 0) return true;
|
|
49607
|
-
const xclip =
|
|
50017
|
+
const xclip = spawnSync11("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true });
|
|
49608
50018
|
return xclip.status === 0;
|
|
49609
50019
|
} catch {
|
|
49610
50020
|
return false;
|
|
@@ -49612,7 +50022,7 @@ function copyToClipboard(text) {
|
|
|
49612
50022
|
}
|
|
49613
50023
|
function spawnDegradeWorktree(repoRoot, runId, options) {
|
|
49614
50024
|
const runner = options?.execFn ?? ((cmd, args) => {
|
|
49615
|
-
const res =
|
|
50025
|
+
const res = spawnSync11(cmd, args, {
|
|
49616
50026
|
cwd: repoRoot,
|
|
49617
50027
|
encoding: "utf8",
|
|
49618
50028
|
windowsHide: true,
|
|
@@ -50396,7 +50806,7 @@ async function runMeshTui(input) {
|
|
|
50396
50806
|
}
|
|
50397
50807
|
|
|
50398
50808
|
// plugins/kxm/src/session-work.ts
|
|
50399
|
-
import { spawnSync as
|
|
50809
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
50400
50810
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
50401
50811
|
import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync32, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
50402
50812
|
import { join as join45 } from "node:path";
|
|
@@ -50457,10 +50867,10 @@ function formatShipLine(ship) {
|
|
|
50457
50867
|
}
|
|
50458
50868
|
function readGitShip(cwd) {
|
|
50459
50869
|
try {
|
|
50460
|
-
const dirty =
|
|
50870
|
+
const dirty = spawnSync12("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
|
|
50461
50871
|
if (dirty.status !== 0) return void 0;
|
|
50462
50872
|
const isDirty = dirty.stdout.trim().length > 0;
|
|
50463
|
-
const upstream =
|
|
50873
|
+
const upstream = spawnSync12("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
|
|
50464
50874
|
if (upstream.status === 0) {
|
|
50465
50875
|
return {
|
|
50466
50876
|
dirty: isDirty,
|
|
@@ -50468,9 +50878,9 @@ function readGitShip(cwd) {
|
|
|
50468
50878
|
};
|
|
50469
50879
|
}
|
|
50470
50880
|
for (const baseRef of ["origin/HEAD", "main", "origin/main", "master", "origin/master"]) {
|
|
50471
|
-
const mb =
|
|
50881
|
+
const mb = spawnSync12("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
|
|
50472
50882
|
if (mb.status === 0 && mb.stdout.trim()) {
|
|
50473
|
-
const count =
|
|
50883
|
+
const count = spawnSync12("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
|
|
50474
50884
|
if (count.status === 0) {
|
|
50475
50885
|
return {
|
|
50476
50886
|
dirty: isDirty,
|
|
@@ -51079,11 +51489,21 @@ async function refreshKxmUpdateNotice(runtime, config, current) {
|
|
|
51079
51489
|
return notice;
|
|
51080
51490
|
}
|
|
51081
51491
|
async function cmdStatus(runtime) {
|
|
51492
|
+
const connection = describeHubConnection(runtime.dirs.workdir, runtime.env, {
|
|
51493
|
+
hasSavedProjectToken: (project) => {
|
|
51494
|
+
try {
|
|
51495
|
+
const token = readHubEnvRecord(runtime.env)?.projectTokens?.[project];
|
|
51496
|
+
return typeof token === "string" && token.trim().length > 0;
|
|
51497
|
+
} catch {
|
|
51498
|
+
return false;
|
|
51499
|
+
}
|
|
51500
|
+
}
|
|
51501
|
+
});
|
|
51082
51502
|
let headers;
|
|
51083
51503
|
try {
|
|
51084
|
-
|
|
51085
|
-
|
|
51086
|
-
headers = { authorization: `Bearer ${
|
|
51504
|
+
if (connection.mode === "cloud") {
|
|
51505
|
+
const token = resolveClientHubAuthToken(runtime.env, connection.project, runtime.dirs.workdir);
|
|
51506
|
+
if (token) headers = { authorization: `Bearer ${token}` };
|
|
51087
51507
|
}
|
|
51088
51508
|
} catch (error) {
|
|
51089
51509
|
if (error instanceof CloudTokenError) {
|
|
@@ -51094,12 +51514,22 @@ async function cmdStatus(runtime) {
|
|
|
51094
51514
|
}
|
|
51095
51515
|
const health = await hubGet(`${runtime.serverUrl}/health`, runtime.fetchImpl, headers);
|
|
51096
51516
|
const ready = await hubGet(`${runtime.serverUrl}/ready`, runtime.fetchImpl, headers);
|
|
51097
|
-
const effectiveScope =
|
|
51098
|
-
const overridden =
|
|
51517
|
+
const effectiveScope = connection.scope;
|
|
51518
|
+
const overridden = connection.urlSource === "env";
|
|
51519
|
+
const identity = {
|
|
51520
|
+
mode: connection.mode,
|
|
51521
|
+
modeSource: connection.modeSource,
|
|
51522
|
+
url: connection.url,
|
|
51523
|
+
urlSource: connection.urlSource,
|
|
51524
|
+
project: connection.project,
|
|
51525
|
+
projectSource: connection.source,
|
|
51526
|
+
keySource: connection.keySource
|
|
51527
|
+
};
|
|
51099
51528
|
const payload = {
|
|
51100
51529
|
ok: health.ok && ready.ok,
|
|
51101
51530
|
command: "hub view",
|
|
51102
51531
|
target: { url: runtime.serverUrl, scope: effectiveScope, ...overridden ? { source: "env" } : {} },
|
|
51532
|
+
identity,
|
|
51103
51533
|
health: health.body,
|
|
51104
51534
|
ready: ready.body
|
|
51105
51535
|
};
|
|
@@ -51107,7 +51537,7 @@ async function cmdStatus(runtime) {
|
|
|
51107
51537
|
runtime.io,
|
|
51108
51538
|
runtime.json,
|
|
51109
51539
|
payload,
|
|
51110
|
-
`hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""}`
|
|
51540
|
+
`hub health=${health.ok} ready=${ready.ok} \xB7 ${effectiveScope} hub${overridden ? " (KXM_SERVER_URL)" : ""} \xB7 mode=${identity.mode} project=${identity.project} (${connection.sourceLabel}) key=${identity.keySource}`
|
|
51111
51541
|
);
|
|
51112
51542
|
return payload.ok ? 0 : 1;
|
|
51113
51543
|
}
|
|
@@ -51134,10 +51564,10 @@ async function cmdDash(runtime, options = {}) {
|
|
|
51134
51564
|
runtime.io.stderr("kxm dash does not support --json; use kxm hub view\n");
|
|
51135
51565
|
return 2;
|
|
51136
51566
|
}
|
|
51137
|
-
const project =
|
|
51567
|
+
const project = resolveProjectIdentity(runtime.dirs.workdir, runtime.env).project || "project";
|
|
51138
51568
|
let authToken;
|
|
51139
51569
|
try {
|
|
51140
|
-
authToken = resolveClientHubAuthToken(runtime.env, project);
|
|
51570
|
+
authToken = resolveClientHubAuthToken(runtime.env, project, runtime.dirs.workdir);
|
|
51141
51571
|
} catch (error) {
|
|
51142
51572
|
if (error instanceof CloudTokenError) {
|
|
51143
51573
|
print(runtime.io, runtime.json, { ok: false, command: "dash", error: error.code, detail: error.message }, error.message);
|
|
@@ -51222,25 +51652,35 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
|
|
|
51222
51652
|
const cloud = options.cloud === true;
|
|
51223
51653
|
const tokenEnv = options.tokenEnv?.trim() || void 0;
|
|
51224
51654
|
const tokenCommand = options.tokenCommand?.trim() || void 0;
|
|
51225
|
-
|
|
51655
|
+
const keyOp = options.keyOp?.trim() || opReferenceFromTokenCommand(tokenCommand ?? "") || void 0;
|
|
51656
|
+
if (options.keyOp?.trim() && !isHubOpReference(options.keyOp.trim())) {
|
|
51657
|
+
print(
|
|
51658
|
+
runtime.io,
|
|
51659
|
+
runtime.json,
|
|
51660
|
+
{ ok: false, command: "hub bind", error: "hub_config_invalid", url },
|
|
51661
|
+
"--key-op must be an op://vault/item/field reference, not a token; no binding was written"
|
|
51662
|
+
);
|
|
51663
|
+
return 2;
|
|
51664
|
+
}
|
|
51665
|
+
if (tokenCommand && !cloud) {
|
|
51226
51666
|
print(
|
|
51227
51667
|
runtime.io,
|
|
51228
51668
|
runtime.json,
|
|
51229
51669
|
{ ok: false, command: "hub bind", error: "cloud_flag_required", url },
|
|
51230
|
-
"--token-
|
|
51670
|
+
"--token-command requires --cloud; no binding was written"
|
|
51231
51671
|
);
|
|
51232
51672
|
return 2;
|
|
51233
51673
|
}
|
|
51234
|
-
if (cloud && !tokenEnv && !tokenCommand) {
|
|
51674
|
+
if (cloud && !tokenEnv && !tokenCommand && !keyOp) {
|
|
51235
51675
|
print(
|
|
51236
51676
|
runtime.io,
|
|
51237
51677
|
runtime.json,
|
|
51238
51678
|
{ ok: false, command: "hub bind", error: "cloud_token_source_required", url },
|
|
51239
|
-
"--cloud needs --token-env, --token-command, or
|
|
51679
|
+
"--cloud needs --token-env, --token-command, --key-op, or a combination; the token is not stored"
|
|
51240
51680
|
);
|
|
51241
51681
|
return 2;
|
|
51242
51682
|
}
|
|
51243
|
-
if (
|
|
51683
|
+
if (tokenEnv && !isCloudTokenEnvName(tokenEnv)) {
|
|
51244
51684
|
print(
|
|
51245
51685
|
runtime.io,
|
|
51246
51686
|
runtime.json,
|
|
@@ -51265,10 +51705,10 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
|
|
|
51265
51705
|
}
|
|
51266
51706
|
const scope = cloud ? "remote" : hubBindingScope(url);
|
|
51267
51707
|
if (!cloud && scope === "remote") {
|
|
51268
|
-
const bindProject =
|
|
51708
|
+
const bindProject = resolveProjectIdentity(runtime.dirs.workdir, runtime.env).project || "project";
|
|
51269
51709
|
let credentialReady = false;
|
|
51270
51710
|
try {
|
|
51271
|
-
credentialReady = hasClientHubCredential(runtime.env, bindProject);
|
|
51711
|
+
credentialReady = hasClientHubCredential(runtime.env, bindProject, runtime.dirs.workdir);
|
|
51272
51712
|
} catch (error) {
|
|
51273
51713
|
print(
|
|
51274
51714
|
runtime.io,
|
|
@@ -51309,29 +51749,55 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
|
|
|
51309
51749
|
}
|
|
51310
51750
|
}
|
|
51311
51751
|
const file = hubBindingFile(runtime.env);
|
|
51312
|
-
const
|
|
51752
|
+
const bindingCommand = tokenCommand ?? (cloud && keyOp ? `op read ${keyOp}` : void 0);
|
|
51753
|
+
const cloudFields = cloud ? { cloud: true, ...tokenEnv ? { tokenEnv } : {}, ...bindingCommand ? { tokenCommand: bindingCommand } : {} } : {};
|
|
51313
51754
|
const record = {
|
|
51314
51755
|
schema: HUB_BINDING_SCHEMA,
|
|
51315
51756
|
url,
|
|
51316
51757
|
boundAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51317
51758
|
...cloudFields
|
|
51318
51759
|
};
|
|
51760
|
+
const identity = resolveProjectIdentity(runtime.dirs.workdir, runtime.env);
|
|
51761
|
+
const configFile = join49(findConfigRoot(runtime.dirs.workdir), ".kxm", "config.yaml");
|
|
51762
|
+
const key = {
|
|
51763
|
+
...keyOp ? { op: keyOp } : {},
|
|
51764
|
+
...tokenEnv ? { env: tokenEnv } : {}
|
|
51765
|
+
};
|
|
51319
51766
|
const cloudNote = cloud ? " \xB7 token is not stored" : "";
|
|
51320
51767
|
if (runtime.dryRun) {
|
|
51321
51768
|
print(
|
|
51322
51769
|
runtime.io,
|
|
51323
51770
|
runtime.json,
|
|
51324
|
-
{ ok: true, command: "hub bind", dryRun: true, url, scope, file, ...cloudFields },
|
|
51771
|
+
{ ok: true, command: "hub bind", dryRun: true, url, scope, file, configFile, project: identity.project, ...cloudFields },
|
|
51325
51772
|
`would bind hub ${url} (${scope})${cloudNote}`
|
|
51326
51773
|
);
|
|
51327
51774
|
return 0;
|
|
51328
51775
|
}
|
|
51776
|
+
try {
|
|
51777
|
+
writeHubEndpoint(runtime.dirs.workdir, {
|
|
51778
|
+
mode: cloud ? "cloud" : "local",
|
|
51779
|
+
url,
|
|
51780
|
+
project: identity.project,
|
|
51781
|
+
...key.op || key.env ? { key } : {}
|
|
51782
|
+
});
|
|
51783
|
+
} catch (error) {
|
|
51784
|
+
if (error instanceof KxmHubConfigError) {
|
|
51785
|
+
print(
|
|
51786
|
+
runtime.io,
|
|
51787
|
+
runtime.json,
|
|
51788
|
+
{ ok: false, command: "hub bind", error: error.code, detail: error.message },
|
|
51789
|
+
`${error.message}; no binding was written`
|
|
51790
|
+
);
|
|
51791
|
+
return 2;
|
|
51792
|
+
}
|
|
51793
|
+
throw error;
|
|
51794
|
+
}
|
|
51329
51795
|
writeHubBinding({ ...record, boundAt: (/* @__PURE__ */ new Date()).toISOString() }, runtime.env);
|
|
51330
51796
|
const { health, probeMs } = await probeHubHealth(url, runtime.fetchImpl);
|
|
51331
51797
|
print(
|
|
51332
51798
|
runtime.io,
|
|
51333
51799
|
runtime.json,
|
|
51334
|
-
{ ok: true, command: "hub bind", url, scope, file, health, probeMs, ...cloudFields },
|
|
51800
|
+
{ ok: true, command: "hub bind", url, scope, file, configFile, project: identity.project, health, probeMs, ...cloudFields },
|
|
51335
51801
|
`bound hub ${url} \xB7 ${scope} \xB7 ${formatHubBindHealth(health)}${cloud ? cloudNote : scope === "remote" ? " \xB7 token leaves this machine" : ""}`
|
|
51336
51802
|
);
|
|
51337
51803
|
return 0;
|
|
@@ -51360,6 +51826,7 @@ async function cmdHubUnbind(runtime) {
|
|
|
51360
51826
|
return 1;
|
|
51361
51827
|
}
|
|
51362
51828
|
removeHubBinding(runtime.env);
|
|
51829
|
+
deleteKxmConfigValue(findConfigRoot(runtime.dirs.workdir), "hub.mode");
|
|
51363
51830
|
if (malformed) {
|
|
51364
51831
|
print(runtime.io, runtime.json, { ok: true, command: "hub unbind", file }, "unbound hub (record was malformed)");
|
|
51365
51832
|
return 0;
|
|
@@ -51764,10 +52231,10 @@ init_redact();
|
|
|
51764
52231
|
init_telemetry();
|
|
51765
52232
|
init_routing();
|
|
51766
52233
|
init_prices();
|
|
51767
|
-
import { spawnSync as
|
|
52234
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
51768
52235
|
import { createHash as createHash19 } from "node:crypto";
|
|
51769
52236
|
import { existsSync as existsSync47, mkdtempSync as mkdtempSync3, readFileSync as readFileSync41, rmSync as rmSync13 } from "node:fs";
|
|
51770
|
-
import { tmpdir as
|
|
52237
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
51771
52238
|
import { basename as basename11, extname as extname3, join as join55, resolve as resolve33 } from "node:path";
|
|
51772
52239
|
import { createInterface as createInterface3 } from "node:readline";
|
|
51773
52240
|
|
|
@@ -52643,7 +53110,7 @@ ${divider}
|
|
|
52643
53110
|
|
|
52644
53111
|
// plugins/kxm/src/ssh-remote.ts
|
|
52645
53112
|
init_safety_integrity();
|
|
52646
|
-
import { spawnSync as
|
|
53113
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
52647
53114
|
import { existsSync as existsSync44, mkdirSync as mkdirSync30, readFileSync as readFileSync39, readdirSync as readdirSync14, rmSync as rmSync12, statSync as statSync7 } from "node:fs";
|
|
52648
53115
|
import { homedir as homedir8 } from "node:os";
|
|
52649
53116
|
import { join as join52, resolve as resolve31 } from "node:path";
|
|
@@ -52719,7 +53186,7 @@ function parseSshConfig(configPath) {
|
|
|
52719
53186
|
return [];
|
|
52720
53187
|
}
|
|
52721
53188
|
}
|
|
52722
|
-
function resolveSshHostG(host, execFn =
|
|
53189
|
+
function resolveSshHostG(host, execFn = spawnSync13) {
|
|
52723
53190
|
try {
|
|
52724
53191
|
const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
|
|
52725
53192
|
if (result.status !== 0 || !result.stdout) {
|
|
@@ -52785,7 +53252,7 @@ function buildSshArgs(options) {
|
|
|
52785
53252
|
args.push(options.host);
|
|
52786
53253
|
return args;
|
|
52787
53254
|
}
|
|
52788
|
-
function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn =
|
|
53255
|
+
function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync13) {
|
|
52789
53256
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
52790
53257
|
const controlPath = join52(resolvedDir, "%C");
|
|
52791
53258
|
try {
|
|
@@ -52797,7 +53264,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
|
|
|
52797
53264
|
return false;
|
|
52798
53265
|
}
|
|
52799
53266
|
}
|
|
52800
|
-
function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn =
|
|
53267
|
+
function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync13) {
|
|
52801
53268
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
52802
53269
|
const controlPath = join52(resolvedDir, "%C");
|
|
52803
53270
|
try {
|
|
@@ -52811,7 +53278,7 @@ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
|
|
|
52811
53278
|
}
|
|
52812
53279
|
function executeSshRun(params) {
|
|
52813
53280
|
const startTime = Date.now();
|
|
52814
|
-
const execSyncFn = params.execFn ??
|
|
53281
|
+
const execSyncFn = params.execFn ?? spawnSync13;
|
|
52815
53282
|
if (params.action === "info") {
|
|
52816
53283
|
if (params.host) {
|
|
52817
53284
|
const hostInfo = resolveSshHostG(params.host, execSyncFn);
|
|
@@ -53956,7 +54423,7 @@ init_types();
|
|
|
53956
54423
|
init_repo_root();
|
|
53957
54424
|
function cliSpawn(runtime, command, args, extra) {
|
|
53958
54425
|
if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
|
|
53959
|
-
const result =
|
|
54426
|
+
const result = spawnSync14(command, [...args], {
|
|
53960
54427
|
encoding: "utf8",
|
|
53961
54428
|
windowsHide: true,
|
|
53962
54429
|
shell: process.platform === "win32",
|
|
@@ -53994,7 +54461,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
53994
54461
|
detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
|
|
53995
54462
|
};
|
|
53996
54463
|
}
|
|
53997
|
-
const releaseDir = mkdtempSync3(join55(
|
|
54464
|
+
const releaseDir = mkdtempSync3(join55(tmpdir3(), "kxm-pkg-update-"));
|
|
53998
54465
|
try {
|
|
53999
54466
|
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset);
|
|
54000
54467
|
if (runtime.dryRun) {
|
|
@@ -54942,11 +55409,12 @@ function addGlobalOptions(command) {
|
|
|
54942
55409
|
}
|
|
54943
55410
|
async function ensureCliClient(runtime) {
|
|
54944
55411
|
const serverUrl = runtime.serverUrl;
|
|
54945
|
-
const
|
|
55412
|
+
const identity = resolveProjectIdentity(runtime.cwd, runtime.env);
|
|
55413
|
+
const project = identity.project;
|
|
54946
55414
|
const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
|
|
54947
55415
|
const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
|
|
54948
|
-
const authToken = resolveAgentHubAuthToken(runtime.env, project);
|
|
54949
|
-
if (!authToken) throw new AgentProjectTokenMissingError(
|
|
55416
|
+
const authToken = resolveAgentHubAuthToken(runtime.env, project, runtime.cwd);
|
|
55417
|
+
if (!authToken) throw new AgentProjectTokenMissingError(identity);
|
|
54950
55418
|
const client = new HubClient({
|
|
54951
55419
|
serverUrl,
|
|
54952
55420
|
name,
|
|
@@ -55055,11 +55523,38 @@ async function dispatchAgentCliCommand(runtime, toolName, rawArgs) {
|
|
|
55055
55523
|
print(
|
|
55056
55524
|
runtime.io,
|
|
55057
55525
|
runtime.json,
|
|
55058
|
-
{
|
|
55526
|
+
{
|
|
55527
|
+
ok: false,
|
|
55528
|
+
error: error.code,
|
|
55529
|
+
project: error.project,
|
|
55530
|
+
projectSource: error.projectSource,
|
|
55531
|
+
nextAction: "configure_hub_project",
|
|
55532
|
+
hint: error.hint,
|
|
55533
|
+
detail: message
|
|
55534
|
+
},
|
|
55059
55535
|
message
|
|
55060
55536
|
);
|
|
55061
55537
|
return 2;
|
|
55062
55538
|
}
|
|
55539
|
+
if (error instanceof HubHttpError && error.code === "project_token_missing") {
|
|
55540
|
+
const identity = resolveProjectIdentity(runtime.cwd, runtime.env);
|
|
55541
|
+
const missing = new AgentProjectTokenMissingError(identity);
|
|
55542
|
+
print(
|
|
55543
|
+
runtime.io,
|
|
55544
|
+
runtime.json,
|
|
55545
|
+
{
|
|
55546
|
+
ok: false,
|
|
55547
|
+
error: missing.code,
|
|
55548
|
+
project: identity.project,
|
|
55549
|
+
projectSource: identity.source,
|
|
55550
|
+
nextAction: "configure_hub_project",
|
|
55551
|
+
hint: missing.hint,
|
|
55552
|
+
detail: missing.message
|
|
55553
|
+
},
|
|
55554
|
+
missing.message
|
|
55555
|
+
);
|
|
55556
|
+
return 2;
|
|
55557
|
+
}
|
|
55063
55558
|
if (error instanceof CloudTokenError) {
|
|
55064
55559
|
print(
|
|
55065
55560
|
runtime.io,
|
|
@@ -55547,7 +56042,7 @@ function createProgram(ctx, result, argv) {
|
|
|
55547
56042
|
addGlobalOptions(hub.command("stop").description("Request managed hub and worker shutdown")).option("--wait-ms <ms>", "How long to wait for PID files to clear").action(async function hubStopAction(options) {
|
|
55548
56043
|
result.code = await cmdStop(runtimeFrom(ctx, this), options.waitMs);
|
|
55549
56044
|
});
|
|
55550
|
-
addGlobalOptions(hub.command("bind").description("Bind this machine to a running hub").argument("<url>", "Hub base URL (http or https)")).option("--cloud", "Mark the hub remote even when the URL is a loopback forward").option("--token-env <name>", "Environment variable that holds the hub token; the token is not stored").option("--token-command <command>", "Program that prints the hub token; the token is not stored").action(async function hubBindAction(url, options) {
|
|
56045
|
+
addGlobalOptions(hub.command("bind").description("Bind this machine to a running hub").argument("<url>", "Hub base URL (http or https)")).option("--cloud", "Mark the hub remote even when the URL is a loopback forward").option("--token-env <name>", "Environment variable that holds the hub token; the token is not stored").option("--token-command <command>", "Program that prints the hub token; the token is not stored").option("--key-op <ref>", "op:// reference for the hub token; resolved with op read, never stored").action(async function hubBindAction(url, options) {
|
|
55551
56046
|
result.code = await cmdHubBind(runtimeFrom(ctx, this), url, options);
|
|
55552
56047
|
});
|
|
55553
56048
|
addGlobalOptions(hub.command("unbind").description("Remove this machine's hub binding")).action(bind(cmdHubUnbind));
|
|
@@ -55715,6 +56210,10 @@ async function runCli(argv, env = process.env, io = { stdout: (text) => process.
|
|
|
55715
56210
|
} catch (error) {
|
|
55716
56211
|
if (error instanceof CommanderError) return mapCommanderError(error, argv, program2, io);
|
|
55717
56212
|
if (error instanceof DryRunRefused) return error.code;
|
|
56213
|
+
if (error instanceof KxmHubConfigError) {
|
|
56214
|
+
print(io, hasJsonFlag(argv), { ok: false, error: error.code, detail: error.message }, error.message);
|
|
56215
|
+
return 2;
|
|
56216
|
+
}
|
|
55718
56217
|
throw error;
|
|
55719
56218
|
}
|
|
55720
56219
|
}
|