@hexclave/shared-backend 1.0.17

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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Stack is licensed per-package, and 100% open-source.
2
+
3
+ Generally speaking, client code and examples are licensed under an open-source MIT license, while server components are licensed under an open-source AGPLv3 license. Please refer to each package's `LICENSE` files for more information.
4
+
5
+ We also have enterprise licenses available, if you prefer; please contact [enterprise@hexclave.com](mailto:enterprise@hexclave.com).
@@ -0,0 +1,34 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+
29
+ Object.defineProperty(exports, '__toESM', {
30
+ enumerable: true,
31
+ get: function () {
32
+ return __toESM;
33
+ }
34
+ });
@@ -0,0 +1,42 @@
1
+ //#region src/config-agent.d.ts
2
+ type ClaudeAgentToolName = "Read" | "Write" | "Edit" | "MultiEdit" | "NotebookEdit" | "Bash" | "Glob" | "Grep";
3
+ type ClaudeAgentHookResult = {
4
+ continue: true;
5
+ } | {
6
+ hookSpecificOutput: {
7
+ hookEventName: "PreToolUse";
8
+ permissionDecision: "deny";
9
+ permissionDecisionReason: string;
10
+ };
11
+ };
12
+ type ClaudeAgentPreToolUseInput = {
13
+ hook_event_name: "PreToolUse";
14
+ tool_name: string;
15
+ tool_input: unknown;
16
+ };
17
+ type RunClaudeAgentOptions = {
18
+ prompt: string;
19
+ cwd: string;
20
+ allowedTools: ClaudeAgentToolName[];
21
+ timeoutMs?: number;
22
+ strictIsolation?: boolean;
23
+ stderr?: (data: string) => void;
24
+ onMessage?: (message: unknown) => void;
25
+ onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult;
26
+ };
27
+ type RunClaudeAgentResult = {
28
+ resultText: string;
29
+ };
30
+ declare class ClaudeAgentTimeoutError extends Error {
31
+ constructor(timeoutMs?: number);
32
+ }
33
+ declare class ClaudeAgentFailureError extends Error {
34
+ constructor(subtype: string);
35
+ }
36
+ declare function stripClaudeCodeEnv(): Record<string, string | undefined>;
37
+ declare function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult>;
38
+ declare function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null;
39
+ declare function isPathInsideDir(dir: string, target: string): boolean;
40
+ //#endregion
41
+ export { ClaudeAgentFailureError, ClaudeAgentPreToolUseInput, ClaudeAgentTimeoutError, ClaudeAgentToolName, RunClaudeAgentOptions, RunClaudeAgentResult, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
42
+ //# sourceMappingURL=config-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-agent.d.ts","names":[],"sources":["../src/config-agent.ts"],"mappings":";KAMY,mBAAA;AAAA,KAEP,qBAAA;EAA0B,QAAA;AAAA;EAC7B,kBAAA;IACE,aAAA;IACA,kBAAA;IACA,wBAAA;EAAA;AAAA;AAAA,KAIQ,0BAAA;EACV,eAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,qBAAA;EACV,MAAA;EACA,GAAA;EACA,YAAA,EAAc,mBAAA;EACd,SAAA;EACA,eAAA;EACA,MAAA,IAAU,IAAA;EACV,SAAA,IAAa,OAAA;EACb,YAAA,IAAgB,KAAA,EAAO,0BAAA,KAA+B,OAAA,CAAQ,qBAAA,IAAyB,qBAAA;AAAA;AAAA,KAG7E,oBAAA;EACV,UAAA;AAAA;AAAA,cAGW,uBAAA,SAAgC,KAAA;cAC/B,SAAA;AAAA;AAAA,cAMD,uBAAA,SAAgC,KAAA;cAC/B,OAAA;AAAA;AAAA,iBAUE,kBAAA,CAAA,GAAsB,MAAA;AAAA,iBAOhB,sBAAA,CAAuB,OAAA,EAAS,qBAAA,GAAwB,OAAA,CAAQ,oBAAA;AAAA,iBA0EtE,sBAAA,CAAuB,QAAA,UAAkB,SAAA,WAAoB,GAAA;AAAA,iBAO7D,eAAA,CAAgB,GAAA,UAAa,MAAA"}
@@ -0,0 +1,96 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('./chunk-BE-pF4vm.js');
3
+ let _anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
4
+ let path = require("path");
5
+ path = require_chunk.__toESM(path);
6
+
7
+ //#region src/config-agent.ts
8
+ const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/v1/integrations/ai-proxy";
9
+ var ClaudeAgentTimeoutError = class extends Error {
10
+ constructor(timeoutMs) {
11
+ super(`Claude agent timed out${timeoutMs == null ? "" : ` after ${timeoutMs}ms`}.`);
12
+ this.name = "ClaudeAgentTimeoutError";
13
+ }
14
+ };
15
+ var ClaudeAgentFailureError = class extends Error {
16
+ constructor(subtype) {
17
+ super(`Claude agent failed (${subtype}).`);
18
+ this.name = "ClaudeAgentFailureError";
19
+ }
20
+ };
21
+ function isAbortError(error) {
22
+ return error instanceof Error && error.name === "AbortError";
23
+ }
24
+ function stripClaudeCodeEnv() {
25
+ const env = { ...process.env };
26
+ delete env.CLAUDECODE;
27
+ return env;
28
+ }
29
+ async function runHeadlessClaudeAgent(options) {
30
+ const abortController = new AbortController();
31
+ const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);
32
+ let sawResult = false;
33
+ let resultText = "";
34
+ const onPreToolUse = options.onPreToolUse;
35
+ try {
36
+ for await (const message of (0, _anthropic_ai_claude_agent_sdk.query)({
37
+ prompt: options.prompt,
38
+ options: {
39
+ model: "nvidia/nemotron-3-super-120b-a12b:nitro",
40
+ ...options.strictIsolation === true ? {
41
+ settingSources: [],
42
+ strictMcpConfig: true
43
+ } : {},
44
+ ...onPreToolUse == null ? {} : { hooks: { PreToolUse: [{ hooks: [async (input) => {
45
+ if (input.hook_event_name !== "PreToolUse") return { continue: true };
46
+ return await onPreToolUse(input);
47
+ }] }] } },
48
+ allowedTools: options.allowedTools,
49
+ permissionMode: "dontAsk",
50
+ cwd: options.cwd,
51
+ abortController,
52
+ env: {
53
+ ...stripClaudeCodeEnv(),
54
+ ...options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" } : {},
55
+ ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,
56
+ ANTHROPIC_API_KEY: "stack-auth-proxy"
57
+ },
58
+ stderr: options.stderr
59
+ }
60
+ })) {
61
+ options.onMessage?.(message);
62
+ if (message.type === "result") if ("result" in message) {
63
+ sawResult = true;
64
+ resultText = message.result;
65
+ } else throw new ClaudeAgentFailureError(message.subtype);
66
+ }
67
+ } catch (error) {
68
+ if (abortController.signal.aborted && isAbortError(error)) throw new ClaudeAgentTimeoutError(options.timeoutMs ?? void 0);
69
+ throw error;
70
+ } finally {
71
+ if (timeout != null) clearTimeout(timeout);
72
+ }
73
+ if (!sawResult) throw new Error("Claude agent ended without reporting a result.");
74
+ return { resultText };
75
+ }
76
+ const FILE_MUTATING_TOOLS = new Set(["Write", "Edit"]);
77
+ function hasStringFilePath(input) {
78
+ return typeof input === "object" && input !== null && "file_path" in input && typeof input.file_path === "string";
79
+ }
80
+ function getToolWriteTargetPath(toolName, toolInput, cwd) {
81
+ if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) return null;
82
+ return path.default.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.default.resolve(cwd, toolInput.file_path);
83
+ }
84
+ function isPathInsideDir(dir, target) {
85
+ const relative = path.default.relative(path.default.resolve(dir), path.default.resolve(target));
86
+ return relative === "" || !relative.startsWith("..") && !path.default.isAbsolute(relative);
87
+ }
88
+
89
+ //#endregion
90
+ exports.ClaudeAgentFailureError = ClaudeAgentFailureError;
91
+ exports.ClaudeAgentTimeoutError = ClaudeAgentTimeoutError;
92
+ exports.getToolWriteTargetPath = getToolWriteTargetPath;
93
+ exports.isPathInsideDir = isPathInsideDir;
94
+ exports.runHeadlessClaudeAgent = runHeadlessClaudeAgent;
95
+ exports.stripClaudeCodeEnv = stripClaudeCodeEnv;
96
+ //# sourceMappingURL=config-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-agent.js","names":[],"sources":["../src/config-agent.ts"],"sourcesContent":["import { query } from \"@anthropic-ai/claude-agent-sdk\";\nimport path from \"path\";\n\nconst DEFAULT_PROXY_URL = \"https://api.hexclave.com/api/v1/integrations/ai-proxy\";\nconst ANTHROPIC_PROXY_BASE_URL: string = process.env.STACK_CLAUDE_PROXY_URL ?? DEFAULT_PROXY_URL;\n\nexport type ClaudeAgentToolName = \"Read\" | \"Write\" | \"Edit\" | \"MultiEdit\" | \"NotebookEdit\" | \"Bash\" | \"Glob\" | \"Grep\";\n\ntype ClaudeAgentHookResult = { continue: true } | {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: string,\n },\n};\n\nexport type ClaudeAgentPreToolUseInput = {\n hook_event_name: \"PreToolUse\",\n tool_name: string,\n tool_input: unknown,\n};\n\nexport type RunClaudeAgentOptions = {\n prompt: string,\n cwd: string,\n allowedTools: ClaudeAgentToolName[],\n timeoutMs?: number,\n strictIsolation?: boolean,\n stderr?: (data: string) => void,\n onMessage?: (message: unknown) => void,\n onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult,\n};\n\nexport type RunClaudeAgentResult = {\n resultText: string,\n};\n\nexport class ClaudeAgentTimeoutError extends Error {\n constructor(timeoutMs?: number) {\n super(`Claude agent timed out${timeoutMs == null ? \"\" : ` after ${timeoutMs}ms`}.`);\n this.name = \"ClaudeAgentTimeoutError\";\n }\n}\n\nexport class ClaudeAgentFailureError extends Error {\n constructor(subtype: string) {\n super(`Claude agent failed (${subtype}).`);\n this.name = \"ClaudeAgentFailureError\";\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nexport function stripClaudeCodeEnv(): Record<string, string | undefined> {\n const env = { ...process.env };\n // CLAUDECODE must be unset for nested agents; ANTHROPIC_API_KEY must be non-empty (proxy ignores it).\n delete env.CLAUDECODE;\n return env;\n}\n\nexport async function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult> {\n const abortController = new AbortController();\n const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);\n let sawResult = false;\n let resultText = \"\";\n const onPreToolUse = options.onPreToolUse;\n try {\n for await (const message of query({\n prompt: options.prompt,\n options: {\n model: \"nvidia/nemotron-3-super-120b-a12b:nitro\",\n ...(options.strictIsolation === true ? {\n settingSources: [],\n strictMcpConfig: true,\n } : {}),\n ...(onPreToolUse == null ? {} : {\n hooks: {\n PreToolUse: [{\n hooks: [async (input) => {\n if (input.hook_event_name !== \"PreToolUse\") {\n return { continue: true };\n }\n return await onPreToolUse(input);\n }],\n }],\n },\n }),\n allowedTools: options.allowedTools,\n permissionMode: \"dontAsk\",\n cwd: options.cwd,\n abortController,\n env: {\n ...stripClaudeCodeEnv(),\n ...(options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: \"1\" } : {}),\n ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,\n ANTHROPIC_API_KEY: \"stack-auth-proxy\",\n },\n stderr: options.stderr,\n },\n })) {\n options.onMessage?.(message);\n if (message.type === \"result\") {\n if (\"result\" in message) {\n sawResult = true;\n resultText = message.result;\n } else {\n throw new ClaudeAgentFailureError(message.subtype);\n }\n }\n }\n } catch (error) {\n if (abortController.signal.aborted && isAbortError(error)) {\n throw new ClaudeAgentTimeoutError(options.timeoutMs ?? undefined);\n }\n throw error;\n } finally {\n if (timeout != null) {\n clearTimeout(timeout);\n }\n }\n if (!sawResult) {\n throw new Error(\"Claude agent ended without reporting a result.\");\n }\n return { resultText };\n}\n\n// Only the tools actually in the agent's allowedTools list; MultiEdit and\n// NotebookEdit are intentionally excluded from the agent's tool set.\nconst FILE_MUTATING_TOOLS = new Set([\"Write\", \"Edit\"]);\n\nfunction hasStringFilePath(input: unknown): input is { file_path: string } {\n return typeof input === \"object\" && input !== null && \"file_path\" in input && typeof input.file_path === \"string\";\n}\n\nexport function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null {\n if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) {\n return null;\n }\n return path.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.resolve(cwd, toolInput.file_path);\n}\n\nexport function isPathInsideDir(dir: string, target: string): boolean {\n const relative = path.relative(path.resolve(dir), path.resolve(target));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":";;;;;;;AAIA,MAAM,2BAAmC,QAAQ,IAAI,0BAD3B;AAkC1B,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,WAAoB;AAC9B,QAAM,yBAAyB,aAAa,OAAO,KAAK,UAAU,UAAU,IAAI,GAAG;AACnF,OAAK,OAAO;;;AAIhB,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,SAAiB;AAC3B,QAAM,wBAAwB,QAAQ,IAAI;AAC1C,OAAK,OAAO;;;AAIhB,SAAS,aAAa,OAAyB;AAC7C,QAAO,iBAAiB,SAAS,MAAM,SAAS;;AAGlD,SAAgB,qBAAyD;CACvE,MAAM,MAAM,EAAE,GAAG,QAAQ,KAAK;AAE9B,QAAO,IAAI;AACX,QAAO;;AAGT,eAAsB,uBAAuB,SAA+D;CAC1G,MAAM,kBAAkB,IAAI,iBAAiB;CAC7C,MAAM,UAAU,QAAQ,aAAa,OAAO,OAAO,iBAAiB,gBAAgB,OAAO,EAAE,QAAQ,UAAU;CAC/G,IAAI,YAAY;CAChB,IAAI,aAAa;CACjB,MAAM,eAAe,QAAQ;AAC7B,KAAI;AACF,aAAW,MAAM,qDAAiB;GAChC,QAAQ,QAAQ;GAChB,SAAS;IACP,OAAO;IACP,GAAI,QAAQ,oBAAoB,OAAO;KACrC,gBAAgB,EAAE;KAClB,iBAAiB;KAClB,GAAG,EAAE;IACN,GAAI,gBAAgB,OAAO,EAAE,GAAG,EAC9B,OAAO,EACL,YAAY,CAAC,EACX,OAAO,CAAC,OAAO,UAAU;AACvB,SAAI,MAAM,oBAAoB,aAC5B,QAAO,EAAE,UAAU,MAAM;AAE3B,YAAO,MAAM,aAAa,MAAM;MAChC,EACH,CAAC,EACH,EACF;IACD,cAAc,QAAQ;IACtB,gBAAgB;IAChB,KAAK,QAAQ;IACb;IACA,KAAK;KACH,GAAG,oBAAoB;KACvB,GAAI,QAAQ,oBAAoB,OAAO,EAAE,iCAAiC,KAAK,GAAG,EAAE;KACpF,oBAAoB;KACpB,mBAAmB;KACpB;IACD,QAAQ,QAAQ;IACjB;GACF,CAAC,EAAE;AACF,WAAQ,YAAY,QAAQ;AAC5B,OAAI,QAAQ,SAAS,SACnB,KAAI,YAAY,SAAS;AACvB,gBAAY;AACZ,iBAAa,QAAQ;SAErB,OAAM,IAAI,wBAAwB,QAAQ,QAAQ;;UAIjD,OAAO;AACd,MAAI,gBAAgB,OAAO,WAAW,aAAa,MAAM,CACvD,OAAM,IAAI,wBAAwB,QAAQ,aAAa,OAAU;AAEnE,QAAM;WACE;AACR,MAAI,WAAW,KACb,cAAa,QAAQ;;AAGzB,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,QAAO,EAAE,YAAY;;AAKvB,MAAM,sBAAsB,IAAI,IAAI,CAAC,SAAS,OAAO,CAAC;AAEtD,SAAS,kBAAkB,OAAgD;AACzE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,eAAe,SAAS,OAAO,MAAM,cAAc;;AAG3G,SAAgB,uBAAuB,UAAkB,WAAoB,KAA4B;AACvG,KAAI,CAAC,oBAAoB,IAAI,SAAS,IAAI,CAAC,kBAAkB,UAAU,CACrE,QAAO;AAET,QAAO,aAAK,WAAW,UAAU,UAAU,GAAG,UAAU,YAAY,aAAK,QAAQ,KAAK,UAAU,UAAU;;AAG5G,SAAgB,gBAAgB,KAAa,QAAyB;CACpE,MAAM,WAAW,aAAK,SAAS,aAAK,QAAQ,IAAI,EAAE,aAAK,QAAQ,OAAO,CAAC;AACvE,QAAO,aAAa,MAAO,CAAC,SAAS,WAAW,KAAK,IAAI,CAAC,aAAK,WAAW,SAAS"}
@@ -0,0 +1,42 @@
1
+ //#region src/config-agent.d.ts
2
+ type ClaudeAgentToolName = "Read" | "Write" | "Edit" | "MultiEdit" | "NotebookEdit" | "Bash" | "Glob" | "Grep";
3
+ type ClaudeAgentHookResult = {
4
+ continue: true;
5
+ } | {
6
+ hookSpecificOutput: {
7
+ hookEventName: "PreToolUse";
8
+ permissionDecision: "deny";
9
+ permissionDecisionReason: string;
10
+ };
11
+ };
12
+ type ClaudeAgentPreToolUseInput = {
13
+ hook_event_name: "PreToolUse";
14
+ tool_name: string;
15
+ tool_input: unknown;
16
+ };
17
+ type RunClaudeAgentOptions = {
18
+ prompt: string;
19
+ cwd: string;
20
+ allowedTools: ClaudeAgentToolName[];
21
+ timeoutMs?: number;
22
+ strictIsolation?: boolean;
23
+ stderr?: (data: string) => void;
24
+ onMessage?: (message: unknown) => void;
25
+ onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult;
26
+ };
27
+ type RunClaudeAgentResult = {
28
+ resultText: string;
29
+ };
30
+ declare class ClaudeAgentTimeoutError extends Error {
31
+ constructor(timeoutMs?: number);
32
+ }
33
+ declare class ClaudeAgentFailureError extends Error {
34
+ constructor(subtype: string);
35
+ }
36
+ declare function stripClaudeCodeEnv(): Record<string, string | undefined>;
37
+ declare function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult>;
38
+ declare function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null;
39
+ declare function isPathInsideDir(dir: string, target: string): boolean;
40
+ //#endregion
41
+ export { ClaudeAgentFailureError, ClaudeAgentPreToolUseInput, ClaudeAgentTimeoutError, ClaudeAgentToolName, RunClaudeAgentOptions, RunClaudeAgentResult, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
42
+ //# sourceMappingURL=config-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-agent.d.ts","names":[],"sources":["../../src/config-agent.ts"],"mappings":";KAMY,mBAAA;AAAA,KAEP,qBAAA;EAA0B,QAAA;AAAA;EAC7B,kBAAA;IACE,aAAA;IACA,kBAAA;IACA,wBAAA;EAAA;AAAA;AAAA,KAIQ,0BAAA;EACV,eAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,qBAAA;EACV,MAAA;EACA,GAAA;EACA,YAAA,EAAc,mBAAA;EACd,SAAA;EACA,eAAA;EACA,MAAA,IAAU,IAAA;EACV,SAAA,IAAa,OAAA;EACb,YAAA,IAAgB,KAAA,EAAO,0BAAA,KAA+B,OAAA,CAAQ,qBAAA,IAAyB,qBAAA;AAAA;AAAA,KAG7E,oBAAA;EACV,UAAA;AAAA;AAAA,cAGW,uBAAA,SAAgC,KAAA;cAC/B,SAAA;AAAA;AAAA,cAMD,uBAAA,SAAgC,KAAA;cAC/B,OAAA;AAAA;AAAA,iBAUE,kBAAA,CAAA,GAAsB,MAAA;AAAA,iBAOhB,sBAAA,CAAuB,OAAA,EAAS,qBAAA,GAAwB,OAAA,CAAQ,oBAAA;AAAA,iBA0EtE,sBAAA,CAAuB,QAAA,UAAkB,SAAA,WAAoB,GAAA;AAAA,iBAO7D,eAAA,CAAgB,GAAA,UAAa,MAAA"}
@@ -0,0 +1,88 @@
1
+ import { query } from "@anthropic-ai/claude-agent-sdk";
2
+ import path from "path";
3
+
4
+ //#region src/config-agent.ts
5
+ const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/v1/integrations/ai-proxy";
6
+ var ClaudeAgentTimeoutError = class extends Error {
7
+ constructor(timeoutMs) {
8
+ super(`Claude agent timed out${timeoutMs == null ? "" : ` after ${timeoutMs}ms`}.`);
9
+ this.name = "ClaudeAgentTimeoutError";
10
+ }
11
+ };
12
+ var ClaudeAgentFailureError = class extends Error {
13
+ constructor(subtype) {
14
+ super(`Claude agent failed (${subtype}).`);
15
+ this.name = "ClaudeAgentFailureError";
16
+ }
17
+ };
18
+ function isAbortError(error) {
19
+ return error instanceof Error && error.name === "AbortError";
20
+ }
21
+ function stripClaudeCodeEnv() {
22
+ const env = { ...process.env };
23
+ delete env.CLAUDECODE;
24
+ return env;
25
+ }
26
+ async function runHeadlessClaudeAgent(options) {
27
+ const abortController = new AbortController();
28
+ const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);
29
+ let sawResult = false;
30
+ let resultText = "";
31
+ const onPreToolUse = options.onPreToolUse;
32
+ try {
33
+ for await (const message of query({
34
+ prompt: options.prompt,
35
+ options: {
36
+ model: "nvidia/nemotron-3-super-120b-a12b:nitro",
37
+ ...options.strictIsolation === true ? {
38
+ settingSources: [],
39
+ strictMcpConfig: true
40
+ } : {},
41
+ ...onPreToolUse == null ? {} : { hooks: { PreToolUse: [{ hooks: [async (input) => {
42
+ if (input.hook_event_name !== "PreToolUse") return { continue: true };
43
+ return await onPreToolUse(input);
44
+ }] }] } },
45
+ allowedTools: options.allowedTools,
46
+ permissionMode: "dontAsk",
47
+ cwd: options.cwd,
48
+ abortController,
49
+ env: {
50
+ ...stripClaudeCodeEnv(),
51
+ ...options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" } : {},
52
+ ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,
53
+ ANTHROPIC_API_KEY: "stack-auth-proxy"
54
+ },
55
+ stderr: options.stderr
56
+ }
57
+ })) {
58
+ options.onMessage?.(message);
59
+ if (message.type === "result") if ("result" in message) {
60
+ sawResult = true;
61
+ resultText = message.result;
62
+ } else throw new ClaudeAgentFailureError(message.subtype);
63
+ }
64
+ } catch (error) {
65
+ if (abortController.signal.aborted && isAbortError(error)) throw new ClaudeAgentTimeoutError(options.timeoutMs ?? void 0);
66
+ throw error;
67
+ } finally {
68
+ if (timeout != null) clearTimeout(timeout);
69
+ }
70
+ if (!sawResult) throw new Error("Claude agent ended without reporting a result.");
71
+ return { resultText };
72
+ }
73
+ const FILE_MUTATING_TOOLS = new Set(["Write", "Edit"]);
74
+ function hasStringFilePath(input) {
75
+ return typeof input === "object" && input !== null && "file_path" in input && typeof input.file_path === "string";
76
+ }
77
+ function getToolWriteTargetPath(toolName, toolInput, cwd) {
78
+ if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) return null;
79
+ return path.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.resolve(cwd, toolInput.file_path);
80
+ }
81
+ function isPathInsideDir(dir, target) {
82
+ const relative = path.relative(path.resolve(dir), path.resolve(target));
83
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
84
+ }
85
+
86
+ //#endregion
87
+ export { ClaudeAgentFailureError, ClaudeAgentTimeoutError, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
88
+ //# sourceMappingURL=config-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-agent.js","names":[],"sources":["../../src/config-agent.ts"],"sourcesContent":["import { query } from \"@anthropic-ai/claude-agent-sdk\";\nimport path from \"path\";\n\nconst DEFAULT_PROXY_URL = \"https://api.hexclave.com/api/v1/integrations/ai-proxy\";\nconst ANTHROPIC_PROXY_BASE_URL: string = process.env.STACK_CLAUDE_PROXY_URL ?? DEFAULT_PROXY_URL;\n\nexport type ClaudeAgentToolName = \"Read\" | \"Write\" | \"Edit\" | \"MultiEdit\" | \"NotebookEdit\" | \"Bash\" | \"Glob\" | \"Grep\";\n\ntype ClaudeAgentHookResult = { continue: true } | {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: string,\n },\n};\n\nexport type ClaudeAgentPreToolUseInput = {\n hook_event_name: \"PreToolUse\",\n tool_name: string,\n tool_input: unknown,\n};\n\nexport type RunClaudeAgentOptions = {\n prompt: string,\n cwd: string,\n allowedTools: ClaudeAgentToolName[],\n timeoutMs?: number,\n strictIsolation?: boolean,\n stderr?: (data: string) => void,\n onMessage?: (message: unknown) => void,\n onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult,\n};\n\nexport type RunClaudeAgentResult = {\n resultText: string,\n};\n\nexport class ClaudeAgentTimeoutError extends Error {\n constructor(timeoutMs?: number) {\n super(`Claude agent timed out${timeoutMs == null ? \"\" : ` after ${timeoutMs}ms`}.`);\n this.name = \"ClaudeAgentTimeoutError\";\n }\n}\n\nexport class ClaudeAgentFailureError extends Error {\n constructor(subtype: string) {\n super(`Claude agent failed (${subtype}).`);\n this.name = \"ClaudeAgentFailureError\";\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nexport function stripClaudeCodeEnv(): Record<string, string | undefined> {\n const env = { ...process.env };\n // CLAUDECODE must be unset for nested agents; ANTHROPIC_API_KEY must be non-empty (proxy ignores it).\n delete env.CLAUDECODE;\n return env;\n}\n\nexport async function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult> {\n const abortController = new AbortController();\n const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);\n let sawResult = false;\n let resultText = \"\";\n const onPreToolUse = options.onPreToolUse;\n try {\n for await (const message of query({\n prompt: options.prompt,\n options: {\n model: \"nvidia/nemotron-3-super-120b-a12b:nitro\",\n ...(options.strictIsolation === true ? {\n settingSources: [],\n strictMcpConfig: true,\n } : {}),\n ...(onPreToolUse == null ? {} : {\n hooks: {\n PreToolUse: [{\n hooks: [async (input) => {\n if (input.hook_event_name !== \"PreToolUse\") {\n return { continue: true };\n }\n return await onPreToolUse(input);\n }],\n }],\n },\n }),\n allowedTools: options.allowedTools,\n permissionMode: \"dontAsk\",\n cwd: options.cwd,\n abortController,\n env: {\n ...stripClaudeCodeEnv(),\n ...(options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: \"1\" } : {}),\n ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,\n ANTHROPIC_API_KEY: \"stack-auth-proxy\",\n },\n stderr: options.stderr,\n },\n })) {\n options.onMessage?.(message);\n if (message.type === \"result\") {\n if (\"result\" in message) {\n sawResult = true;\n resultText = message.result;\n } else {\n throw new ClaudeAgentFailureError(message.subtype);\n }\n }\n }\n } catch (error) {\n if (abortController.signal.aborted && isAbortError(error)) {\n throw new ClaudeAgentTimeoutError(options.timeoutMs ?? undefined);\n }\n throw error;\n } finally {\n if (timeout != null) {\n clearTimeout(timeout);\n }\n }\n if (!sawResult) {\n throw new Error(\"Claude agent ended without reporting a result.\");\n }\n return { resultText };\n}\n\n// Only the tools actually in the agent's allowedTools list; MultiEdit and\n// NotebookEdit are intentionally excluded from the agent's tool set.\nconst FILE_MUTATING_TOOLS = new Set([\"Write\", \"Edit\"]);\n\nfunction hasStringFilePath(input: unknown): input is { file_path: string } {\n return typeof input === \"object\" && input !== null && \"file_path\" in input && typeof input.file_path === \"string\";\n}\n\nexport function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null {\n if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) {\n return null;\n }\n return path.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.resolve(cwd, toolInput.file_path);\n}\n\nexport function isPathInsideDir(dir: string, target: string): boolean {\n const relative = path.relative(path.resolve(dir), path.resolve(target));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":";;;;AAIA,MAAM,2BAAmC,QAAQ,IAAI,0BAD3B;AAkC1B,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,WAAoB;AAC9B,QAAM,yBAAyB,aAAa,OAAO,KAAK,UAAU,UAAU,IAAI,GAAG;AACnF,OAAK,OAAO;;;AAIhB,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,SAAiB;AAC3B,QAAM,wBAAwB,QAAQ,IAAI;AAC1C,OAAK,OAAO;;;AAIhB,SAAS,aAAa,OAAyB;AAC7C,QAAO,iBAAiB,SAAS,MAAM,SAAS;;AAGlD,SAAgB,qBAAyD;CACvE,MAAM,MAAM,EAAE,GAAG,QAAQ,KAAK;AAE9B,QAAO,IAAI;AACX,QAAO;;AAGT,eAAsB,uBAAuB,SAA+D;CAC1G,MAAM,kBAAkB,IAAI,iBAAiB;CAC7C,MAAM,UAAU,QAAQ,aAAa,OAAO,OAAO,iBAAiB,gBAAgB,OAAO,EAAE,QAAQ,UAAU;CAC/G,IAAI,YAAY;CAChB,IAAI,aAAa;CACjB,MAAM,eAAe,QAAQ;AAC7B,KAAI;AACF,aAAW,MAAM,WAAW,MAAM;GAChC,QAAQ,QAAQ;GAChB,SAAS;IACP,OAAO;IACP,GAAI,QAAQ,oBAAoB,OAAO;KACrC,gBAAgB,EAAE;KAClB,iBAAiB;KAClB,GAAG,EAAE;IACN,GAAI,gBAAgB,OAAO,EAAE,GAAG,EAC9B,OAAO,EACL,YAAY,CAAC,EACX,OAAO,CAAC,OAAO,UAAU;AACvB,SAAI,MAAM,oBAAoB,aAC5B,QAAO,EAAE,UAAU,MAAM;AAE3B,YAAO,MAAM,aAAa,MAAM;MAChC,EACH,CAAC,EACH,EACF;IACD,cAAc,QAAQ;IACtB,gBAAgB;IAChB,KAAK,QAAQ;IACb;IACA,KAAK;KACH,GAAG,oBAAoB;KACvB,GAAI,QAAQ,oBAAoB,OAAO,EAAE,iCAAiC,KAAK,GAAG,EAAE;KACpF,oBAAoB;KACpB,mBAAmB;KACpB;IACD,QAAQ,QAAQ;IACjB;GACF,CAAC,EAAE;AACF,WAAQ,YAAY,QAAQ;AAC5B,OAAI,QAAQ,SAAS,SACnB,KAAI,YAAY,SAAS;AACvB,gBAAY;AACZ,iBAAa,QAAQ;SAErB,OAAM,IAAI,wBAAwB,QAAQ,QAAQ;;UAIjD,OAAO;AACd,MAAI,gBAAgB,OAAO,WAAW,aAAa,MAAM,CACvD,OAAM,IAAI,wBAAwB,QAAQ,aAAa,OAAU;AAEnE,QAAM;WACE;AACR,MAAI,WAAW,KACb,cAAa,QAAQ;;AAGzB,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,QAAO,EAAE,YAAY;;AAKvB,MAAM,sBAAsB,IAAI,IAAI,CAAC,SAAS,OAAO,CAAC;AAEtD,SAAS,kBAAkB,OAAgD;AACzE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,eAAe,SAAS,OAAO,MAAM,cAAc;;AAG3G,SAAgB,uBAAuB,UAAkB,WAAoB,KAA4B;AACvG,KAAI,CAAC,oBAAoB,IAAI,SAAS,IAAI,CAAC,kBAAkB,UAAU,CACrE,QAAO;AAET,QAAO,KAAK,WAAW,UAAU,UAAU,GAAG,UAAU,YAAY,KAAK,QAAQ,KAAK,UAAU,UAAU;;AAG5G,SAAgB,gBAAgB,KAAa,QAAyB;CACpE,MAAM,WAAW,KAAK,SAAS,KAAK,QAAQ,IAAI,EAAE,KAAK,QAAQ,OAAO,CAAC;AACvE,QAAO,aAAa,MAAO,CAAC,SAAS,WAAW,KAAK,IAAI,CAAC,KAAK,WAAW,SAAS"}
@@ -0,0 +1,16 @@
1
+ import { Config } from "@hexclave/shared/dist/config/format";
2
+
3
+ //#region src/index.d.ts
4
+ declare function sha256String(value: string): string;
5
+ declare function resolveConfigFilePath(inputPath: string): string;
6
+ declare function ensureConfigFileExists(configFilePath: string): void;
7
+ declare function readConfigObject(configFilePath: string): Promise<Config>;
8
+ declare function readConfigFile(configFilePath: string): Promise<{
9
+ config: Config;
10
+ showOnboarding: boolean;
11
+ }>;
12
+ declare function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void>;
13
+ declare function replaceConfigObject(configFilePath: string, config: Config): Promise<void>;
14
+ //#endregion
15
+ export { ensureConfigFileExists, readConfigFile, readConfigObject, replaceConfigObject, resolveConfigFilePath, sha256String, updateConfigObject };
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;iBA2BgB,YAAA,CAAa,KAAA;AAAA,iBAIb,qBAAA,CAAsB,SAAA;AAAA,iBAkBtB,sBAAA,CAAuB,cAAA;AAAA,iBAMjB,gBAAA,CAAiB,cAAA,WAAyB,OAAA,CAAQ,MAAA;AAAA,iBAIlD,cAAA,CAAe,cAAA,WAAyB,OAAA;EAAU,MAAA,EAAQ,MAAA;EAAQ,cAAA;AAAA;AAAA,iBAkElE,kBAAA,CAAmB,cAAA,UAAwB,YAAA,EAAc,MAAA,GAAS,OAAA;AAAA,iBA2ClE,mBAAA,CAAoB,cAAA,UAAwB,MAAA,EAAQ,MAAA,GAAS,OAAA"}
@@ -0,0 +1,327 @@
1
+ import path from "path";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
3
+ import { showOnboardingHexclaveConfigValue } from "@hexclave/shared/dist/config-authoring";
4
+ import { detectImportPackageFromDir, parseHexclaveConfigFileContent, renderConfigFileContent } from "@hexclave/shared/dist/config-rendering";
5
+ import { isValidConfig, normalize, override } from "@hexclave/shared/dist/config/format";
6
+ import { captureError } from "@hexclave/shared/dist/utils/errors";
7
+ import { createHash } from "crypto";
8
+ import { createJiti } from "jiti";
9
+ import { ClaudeAgentFailureError, ClaudeAgentTimeoutError, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent } from "./config-agent.js";
10
+
11
+ //#region src/index.ts
12
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
13
+ const LOG_PREFIX = "[Stack config updater]";
14
+ const DEFAULT_AGENT_TIMEOUT_MS = 12e4;
15
+ function isConfigModule(value) {
16
+ return value !== null && typeof value === "object";
17
+ }
18
+ function sha256String(value) {
19
+ return createHash("sha256").update(value).digest("hex");
20
+ }
21
+ function resolveConfigFilePath(inputPath) {
22
+ const resolved = path.resolve(inputPath);
23
+ if (/\.(ts|js|mjs|cjs)$/i.test(resolved)) return resolved;
24
+ const hexclaveCandidate = path.join(resolved, "hexclave.config.ts");
25
+ const legacyCandidate = path.join(resolved, "stack.config.ts");
26
+ if (existsSync(hexclaveCandidate)) return hexclaveCandidate;
27
+ if (existsSync(legacyCandidate)) return legacyCandidate;
28
+ return hexclaveCandidate;
29
+ }
30
+ function ensureConfigFileExists(configFilePath) {
31
+ if (existsSync(configFilePath)) return;
32
+ mkdirSync(path.dirname(configFilePath), { recursive: true });
33
+ renderConfigObjectToFile(configFilePath, {});
34
+ }
35
+ async function readConfigObject(configFilePath) {
36
+ return (await readConfigFile(configFilePath)).config;
37
+ }
38
+ async function readConfigFile(configFilePath) {
39
+ ensureConfigFileExists(configFilePath);
40
+ if (readFileSync(configFilePath, "utf-8").trim() === "") return {
41
+ config: {},
42
+ showOnboarding: false
43
+ };
44
+ let configModule;
45
+ try {
46
+ configModule = await jiti.import(configFilePath);
47
+ } catch (error) {
48
+ captureError("shared-backend/readConfigFile", error);
49
+ throw new Error(`Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as "@hexclave/next", import it from that package's lightweight "/config" entrypoint instead, which doesn't load the framework runtime:\n\n import { defineHexclaveConfig } from "@hexclave/next/config";\n`);
50
+ }
51
+ if (!isConfigModule(configModule)) throw new Error(`Invalid config in ${configFilePath}. The file must export a plain \`config\` object or "show-onboarding".`);
52
+ const config = configModule.config;
53
+ if (config === showOnboardingHexclaveConfigValue) return {
54
+ config: {},
55
+ showOnboarding: true
56
+ };
57
+ if (!isValidConfig(config)) throw new Error(`Invalid config in ${configFilePath}.`);
58
+ return {
59
+ config,
60
+ showOnboarding: false
61
+ };
62
+ }
63
+ function renderConfigObjectToString(configFilePath, config) {
64
+ return renderConfigFileContent(config, detectImportPackageFromDir(path.dirname(configFilePath)));
65
+ }
66
+ function writeFileAtomic(configFilePath, content) {
67
+ const dir = path.dirname(configFilePath);
68
+ mkdirSync(dir, { recursive: true });
69
+ const tempPath = path.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);
70
+ writeFileSync(tempPath, content, "utf-8");
71
+ try {
72
+ renameSync(tempPath, configFilePath);
73
+ } catch (error) {
74
+ try {
75
+ rmSync(tempPath);
76
+ } catch {}
77
+ throw error;
78
+ }
79
+ }
80
+ function renderConfigObjectToFile(configFilePath, config) {
81
+ writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));
82
+ }
83
+ async function updateConfigObject(configFilePath, configUpdate) {
84
+ ensureConfigFileExists(configFilePath);
85
+ if (flattenConfigUpdate(configUpdate).length === 0) return;
86
+ const content = readFileSync(configFilePath, "utf-8");
87
+ const staticConfig = tryParseStaticConfigFileContent(content, configFilePath);
88
+ if (staticConfig != null && isValidConfig(staticConfig)) {
89
+ const merged = override(staticConfig, configUpdate);
90
+ if (!isValidConfig(merged)) throw new Error(`${LOG_PREFIX} Merged config is invalid after applying update to ${configFilePath}`);
91
+ renderConfigObjectToFile(configFilePath, merged);
92
+ return;
93
+ }
94
+ const baselineConfig = await tryReadConfigForValidation(configFilePath);
95
+ const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);
96
+ try {
97
+ await runConfigUpdateAgent({
98
+ prompt: buildConfigUpdatePrompt(path.basename(configFilePath), configUpdate, baselineConfig),
99
+ cwd: path.dirname(configFilePath),
100
+ onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen)
101
+ });
102
+ await validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots);
103
+ } catch (error) {
104
+ try {
105
+ restoreConfigFiles(snapshots);
106
+ } catch (restoreError) {
107
+ console.error(`${LOG_PREFIX} Failed to fully roll back config files after a failed update of ${configFilePath}; some files may be left in a partially-restored state`, {
108
+ configFilePath,
109
+ restoreError: restoreError instanceof Error ? restoreError.message : String(restoreError)
110
+ });
111
+ }
112
+ throw error;
113
+ }
114
+ }
115
+ async function replaceConfigObject(configFilePath, config) {
116
+ renderConfigObjectToFile(configFilePath, config);
117
+ }
118
+ async function runConfigUpdateAgent(options) {
119
+ const timeoutMs = parseAgentTimeoutMs();
120
+ const deniedOutOfBoundsWrites = /* @__PURE__ */ new Set();
121
+ try {
122
+ await runHeadlessClaudeAgent({
123
+ prompt: options.prompt,
124
+ cwd: options.cwd,
125
+ allowedTools: [
126
+ "Read",
127
+ "Write",
128
+ "Edit",
129
+ "Glob",
130
+ "Grep"
131
+ ],
132
+ strictIsolation: true,
133
+ timeoutMs,
134
+ stderr: (data) => {
135
+ console.warn(`${LOG_PREFIX} [agent] ${data}`);
136
+ },
137
+ onPreToolUse: (input) => {
138
+ const target = getToolWriteTargetPath(input.tool_name, input.tool_input, options.cwd);
139
+ if (target == null) return { continue: true };
140
+ if (!isPathInsideDir(options.cwd, target)) {
141
+ deniedOutOfBoundsWrites.add(target);
142
+ return { hookSpecificOutput: {
143
+ hookEventName: "PreToolUse",
144
+ permissionDecision: "deny",
145
+ permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`
146
+ } };
147
+ }
148
+ options.onFileWillChange?.(target);
149
+ return { continue: true };
150
+ }
151
+ });
152
+ } catch (error) {
153
+ if (error instanceof ClaudeAgentTimeoutError) throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);
154
+ if (error instanceof ClaudeAgentFailureError) throw new Error(`${error.message} It was unable to apply the config changes to the file.`);
155
+ throw error;
156
+ }
157
+ if (deniedOutOfBoundsWrites.size > 0) throw new Error(`Config update agent tried to modify ${deniedOutOfBoundsWrites.size} file(s) outside the config directory, which is not allowed: ${[...deniedOutOfBoundsWrites].join(", ")}. The config was not updated.`);
158
+ }
159
+ function parseAgentTimeoutMs() {
160
+ const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;
161
+ if (raw == null || raw.trim() === "") return DEFAULT_AGENT_TIMEOUT_MS;
162
+ const parsed = Number(raw);
163
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);
164
+ return parsed;
165
+ }
166
+ function captureSnapshotIfAbsent(snapshots, filePath, seen) {
167
+ const resolved = path.resolve(filePath);
168
+ if (seen.has(resolved)) return;
169
+ seen.add(resolved);
170
+ snapshots.push({
171
+ path: resolved,
172
+ content: existsSync(resolved) ? readFileSync(resolved, "utf-8") : null
173
+ });
174
+ }
175
+ function snapshotConfigFiles(configFilePath, configContent) {
176
+ const dir = path.dirname(configFilePath);
177
+ const resolvedConfig = path.resolve(configFilePath);
178
+ const snapshots = [{
179
+ path: resolvedConfig,
180
+ content: configContent
181
+ }];
182
+ const seen = new Set([resolvedConfig]);
183
+ for (const specifier of getRelativeImportSpecifiers(configContent)) {
184
+ const resolved = path.resolve(dir, specifier);
185
+ if (!isPathInsideDir(dir, resolved)) continue;
186
+ captureSnapshotIfAbsent(snapshots, resolved, seen);
187
+ }
188
+ return {
189
+ snapshots,
190
+ seen
191
+ };
192
+ }
193
+ function restoreConfigFiles(snapshots) {
194
+ const failures = [];
195
+ for (const { path: filePath, content } of snapshots) try {
196
+ if (content === null) {
197
+ if (existsSync(filePath)) rmSync(filePath);
198
+ } else writeFileSync(filePath, content, "utf-8");
199
+ } catch (error) {
200
+ failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);
201
+ }
202
+ if (failures.length > 0) throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join("; ")}`);
203
+ }
204
+ async function tryReadConfigForValidation(configFilePath) {
205
+ try {
206
+ return (await readConfigFile(configFilePath)).config;
207
+ } catch (error) {
208
+ console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {
209
+ configFilePath,
210
+ error: error instanceof Error ? error.message : String(error)
211
+ });
212
+ return null;
213
+ }
214
+ }
215
+ async function validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots) {
216
+ if (baselineConfig != null) {
217
+ const target = canonicalizeConfig(override(baselineConfig, configUpdate));
218
+ if (!configsEqual(canonicalizeConfig((await readConfigFile(configFilePath)).config), target)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);
219
+ return;
220
+ }
221
+ if (flattenConfigUpdate(configUpdate).length > 0 && !snapshotsChangedOnDisk(snapshots)) console.warn(`${LOG_PREFIX} Agent did not modify any file for ${configFilePath}; assuming values are already up to date.`);
222
+ if (!configFileExportsConfig(readFileSync(configFilePath, "utf-8"), configFilePath)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \`config\`.`);
223
+ }
224
+ function tryParseStaticConfigFileContent(content, configFilePath) {
225
+ try {
226
+ const parsed = parseHexclaveConfigFileContent(content, configFilePath);
227
+ return isValidConfig(parsed) ? parsed : null;
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ function configFileExportsConfig(content, configFilePath) {
233
+ try {
234
+ parseHexclaveConfigFileContent(content, configFilePath);
235
+ return true;
236
+ } catch {
237
+ return /\bexport\s+const\s+config\b/.test(content);
238
+ }
239
+ }
240
+ function getRelativeImportSpecifiers(content) {
241
+ const specifiers = [];
242
+ const importPattern = /\bimport\b(?:[^'"]*?\bfrom\s*)?["'](\.{1,2}\/[^"']+)["']/g;
243
+ let match;
244
+ while ((match = importPattern.exec(content)) !== null) specifiers.push(match[1]);
245
+ return specifiers;
246
+ }
247
+ function snapshotsChangedOnDisk(snapshots) {
248
+ return snapshots.some(({ path: filePath, content }) => {
249
+ return (existsSync(filePath) ? readFileSync(filePath, "utf-8") : null) !== content;
250
+ });
251
+ }
252
+ function flattenConfigUpdate(update) {
253
+ const changes = [];
254
+ const walk = (prefix, obj) => {
255
+ for (const [key, value] of Object.entries(obj)) {
256
+ const fullPath = prefix === "" ? key : `${prefix}.${key}`;
257
+ if (value === void 0) continue;
258
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0) walk(fullPath, value);
259
+ else changes.push({
260
+ path: fullPath,
261
+ value
262
+ });
263
+ }
264
+ };
265
+ walk("", update);
266
+ return changes;
267
+ }
268
+ function buildConfigUpdatePrompt(configFileName, configUpdate, baselineConfig) {
269
+ const changeLines = flattenConfigUpdate(configUpdate).map(({ path: configPath, value }) => {
270
+ return `- ${JSON.stringify(configPath)}: set to ${JSON.stringify(value)}`;
271
+ }).join("\n");
272
+ const expectedConfig = baselineConfig == null ? null : canonicalizeConfig(override(baselineConfig, configUpdate));
273
+ const expectedConfigSection = expectedConfig == null ? "" : `
274
+ After the edit, evaluating the exported \`config\` must produce this exact JSON value:
275
+
276
+ ${JSON.stringify(expectedConfig, null, 2)}
277
+ `;
278
+ return `You are editing a Hexclave / Stack Auth configuration file in place. Apply a set of configuration changes WITHOUT changing how the file is written.
279
+
280
+ Config file: ${JSON.stringify(configFileName)} (in the current working directory).
281
+
282
+ The file exports a \`config\` object (it may be wrapped in a helper such as \`defineStackConfig(...)\`). Some config values may be sourced from other files via imports, for example:
283
+
284
+ import welcomeEmail from "./welcome-email.tsx" with { type: "text" };
285
+ export const config = { emails: { templates: { welcome: welcomeEmail } } };
286
+
287
+ Apply EXACTLY these changes. Paths use dot notation, so \`a.b.c\` refers to \`config.a.b.c\`:
288
+
289
+ ${changeLines}
290
+ ${expectedConfigSection}
291
+
292
+ Rules:
293
+ - Change ONLY the config paths listed above. Leave every other part of the file byte-for-byte unchanged: imports, comments, formatting, helper wrappers, and any config fields not listed.
294
+ - If a listed path's value is currently provided by an imported external file (like the \`import ... with { type: "text" }\` example above), DO NOT inline the new value into the config file. Instead, overwrite that external file with the new value and keep the import statement intact.
295
+ - If a listed path's value is a plain inline literal, edit it inline.
296
+ - Keep the file valid: it must still export a \`config\` that, once evaluated, reflects the new values exactly.
297
+ - Do not run any shell commands and do not create files other than what is required to apply these changes.`;
298
+ }
299
+ function canonicalizeConfig(config) {
300
+ const droppedKeys = [];
301
+ const normalized = normalize(config, {
302
+ onDotIntoNonObject: "ignore",
303
+ onDotIntoNull: "empty-object",
304
+ droppedKeys
305
+ });
306
+ if (droppedKeys.length > 0) throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")}`);
307
+ return normalized;
308
+ }
309
+ function configsEqual(a, b) {
310
+ if (a === b) return true;
311
+ if (a === null || b === null) return a === b;
312
+ if (Array.isArray(a) || Array.isArray(b)) {
313
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
314
+ return a.every((value, index) => configsEqual(value, b[index]));
315
+ }
316
+ if (typeof a === "object" && typeof b === "object") {
317
+ const aEntries = Object.entries(a);
318
+ const bMap = new Map(Object.entries(b));
319
+ if (aEntries.length !== bMap.size) return false;
320
+ return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));
321
+ }
322
+ return false;
323
+ }
324
+
325
+ //#endregion
326
+ export { ensureConfigFileExists, readConfigFile, readConfigObject, replaceConfigObject, resolveConfigFilePath, sha256String, updateConfigObject };
327
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { showOnboardingHexclaveConfigValue } from \"@hexclave/shared/dist/config-authoring\";\nimport { detectImportPackageFromDir, parseHexclaveConfigFileContent, renderConfigFileContent } from \"@hexclave/shared/dist/config-rendering\";\nimport type { Config, ConfigValue, NormalizedConfig } from \"@hexclave/shared/dist/config/format\";\nimport { isValidConfig, normalize, override } from \"@hexclave/shared/dist/config/format\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { createHash } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { createJiti } from \"jiti\";\nimport path from \"path\";\nimport { ClaudeAgentFailureError, ClaudeAgentTimeoutError, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent } from \"./config-agent\";\n\nconst jiti = createJiti(import.meta.url, { moduleCache: false });\n\nconst LOG_PREFIX = \"[Stack config updater]\";\nconst DEFAULT_AGENT_TIMEOUT_MS = 120_000;\n\ntype ConfigModule = {\n config?: unknown,\n};\n\ntype ConfigFileSnapshot = { path: string, content: string | null };\ntype ConfigChange = { path: string, value: ConfigValue };\n\nfunction isConfigModule(value: unknown): value is ConfigModule {\n return value !== null && typeof value === \"object\";\n}\n\nexport function sha256String(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function resolveConfigFilePath(inputPath: string): string {\n const resolved = path.resolve(inputPath);\n const looksLikeConfigFile = /\\.(ts|js|mjs|cjs)$/i.test(resolved);\n if (looksLikeConfigFile) {\n return resolved;\n }\n // Prefer hexclave.config.ts, fall back to stack.config.ts, default to the new name.\n const hexclaveCandidate = path.join(resolved, \"hexclave.config.ts\");\n const legacyCandidate = path.join(resolved, \"stack.config.ts\");\n if (existsSync(hexclaveCandidate)) {\n return hexclaveCandidate;\n }\n if (existsSync(legacyCandidate)) {\n return legacyCandidate;\n }\n return hexclaveCandidate;\n}\n\nexport function ensureConfigFileExists(configFilePath: string): void {\n if (existsSync(configFilePath)) return;\n mkdirSync(path.dirname(configFilePath), { recursive: true });\n renderConfigObjectToFile(configFilePath, {});\n}\n\nexport async function readConfigObject(configFilePath: string): Promise<Config> {\n return (await readConfigFile(configFilePath)).config;\n}\n\nexport async function readConfigFile(configFilePath: string): Promise<{ config: Config, showOnboarding: boolean }> {\n ensureConfigFileExists(configFilePath);\n const content = readFileSync(configFilePath, \"utf-8\");\n if (content.trim() === \"\") {\n return {\n config: {},\n showOnboarding: false,\n };\n }\n\n let configModule: unknown;\n try {\n configModule = await jiti.import<unknown>(configFilePath);\n } catch (error) {\n // Capture the raw jiti/framework error for diagnostics, but don't attach it as `cause` on the thrown error:\n // dashboard error formatting renders causes recursively, which would leak framework internals into the\n // user-facing message we're deliberately replacing.\n captureError(\"shared-backend/readConfigFile\", error);\n throw new Error(\n `Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as \"@hexclave/next\", import it from that package's lightweight \"/config\" entrypoint instead, which doesn't load the framework runtime:\\n\\n import { defineHexclaveConfig } from \"@hexclave/next/config\";\\n`,\n );\n }\n if (!isConfigModule(configModule)) {\n throw new Error(`Invalid config in ${configFilePath}. The file must export a plain \\`config\\` object or \"show-onboarding\".`);\n }\n\n const config = configModule.config;\n if (config === showOnboardingHexclaveConfigValue) {\n return {\n config: {},\n showOnboarding: true,\n };\n }\n if (!isValidConfig(config)) {\n throw new Error(`Invalid config in ${configFilePath}.`);\n }\n return {\n config,\n showOnboarding: false,\n };\n}\n\nfunction renderConfigObjectToString(configFilePath: string, config: Config): string {\n const importPackage = detectImportPackageFromDir(path.dirname(configFilePath));\n return renderConfigFileContent(config, importPackage);\n}\n\nfunction writeFileAtomic(configFilePath: string, content: string): void {\n const dir = path.dirname(configFilePath);\n mkdirSync(dir, { recursive: true });\n const tempPath = path.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);\n writeFileSync(tempPath, content, \"utf-8\");\n try {\n renameSync(tempPath, configFilePath);\n } catch (error) {\n try {\n rmSync(tempPath);\n } catch { /* best-effort cleanup */ }\n throw error;\n }\n}\n\nfunction renderConfigObjectToFile(configFilePath: string, config: Config): void {\n writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));\n}\n\nexport async function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void> {\n ensureConfigFileExists(configFilePath);\n\n if (flattenConfigUpdate(configUpdate).length === 0) return;\n\n const content = readFileSync(configFilePath, \"utf-8\");\n\n // Fast path: if the config is a plain static literal (no imports, no helpers),\n // apply the update deterministically without invoking the AI agent.\n const staticConfig = tryParseStaticConfigFileContent(content, configFilePath);\n if (staticConfig != null && isValidConfig(staticConfig)) {\n const merged = override(staticConfig, configUpdate);\n if (!isValidConfig(merged)) {\n throw new Error(`${LOG_PREFIX} Merged config is invalid after applying update to ${configFilePath}`);\n }\n renderConfigObjectToFile(configFilePath, merged);\n return;\n }\n\n // Agent path: config has custom structure (imports, helpers, external files)\n // that must be preserved — delegate to the AI agent.\n const baselineConfig = await tryReadConfigForValidation(configFilePath);\n const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);\n try {\n await runConfigUpdateAgent({\n prompt: buildConfigUpdatePrompt(path.basename(configFilePath), configUpdate, baselineConfig),\n cwd: path.dirname(configFilePath),\n onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen),\n });\n await validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots);\n } catch (error) {\n try {\n restoreConfigFiles(snapshots);\n } catch (restoreError) {\n console.error(`${LOG_PREFIX} Failed to fully roll back config files after a failed update of ${configFilePath}; some files may be left in a partially-restored state`, {\n configFilePath,\n restoreError: restoreError instanceof Error ? restoreError.message : String(restoreError),\n });\n }\n throw error;\n }\n}\n\nexport async function replaceConfigObject(configFilePath: string, config: Config): Promise<void> {\n renderConfigObjectToFile(configFilePath, config);\n}\n\nasync function runConfigUpdateAgent(options: {\n prompt: string,\n cwd: string,\n onFileWillChange?: (filePath: string) => void,\n}): Promise<void> {\n const timeoutMs = parseAgentTimeoutMs();\n const deniedOutOfBoundsWrites = new Set<string>();\n try {\n await runHeadlessClaudeAgent({\n prompt: options.prompt,\n cwd: options.cwd,\n allowedTools: [\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\"],\n strictIsolation: true,\n timeoutMs,\n stderr: (data) => { console.warn(`${LOG_PREFIX} [agent] ${data}`); },\n onPreToolUse: (input) => {\n const target = getToolWriteTargetPath(input.tool_name, input.tool_input, options.cwd);\n if (target == null) return { continue: true };\n if (!isPathInsideDir(options.cwd, target)) {\n deniedOutOfBoundsWrites.add(target);\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`,\n },\n };\n }\n options.onFileWillChange?.(target);\n return { continue: true };\n },\n });\n } catch (error) {\n if (error instanceof ClaudeAgentTimeoutError) {\n throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);\n }\n if (error instanceof ClaudeAgentFailureError) {\n throw new Error(`${error.message} It was unable to apply the config changes to the file.`);\n }\n throw error;\n }\n if (deniedOutOfBoundsWrites.size > 0) {\n throw new Error(`Config update agent tried to modify ${deniedOutOfBoundsWrites.size} file(s) outside the config directory, which is not allowed: ${[...deniedOutOfBoundsWrites].join(\", \")}. The config was not updated.`);\n }\n}\n\nfunction parseAgentTimeoutMs(): number {\n const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;\n if (raw == null || raw.trim() === \"\") return DEFAULT_AGENT_TIMEOUT_MS;\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);\n }\n return parsed;\n}\n\nfunction captureSnapshotIfAbsent(snapshots: ConfigFileSnapshot[], filePath: string, seen: Set<string>): void {\n const resolved = path.resolve(filePath);\n if (seen.has(resolved)) return;\n seen.add(resolved);\n snapshots.push({ path: resolved, content: existsSync(resolved) ? readFileSync(resolved, \"utf-8\") : null });\n}\n\nfunction snapshotConfigFiles(configFilePath: string, configContent: string): { snapshots: ConfigFileSnapshot[]; seen: Set<string> } {\n const dir = path.dirname(configFilePath);\n const resolvedConfig = path.resolve(configFilePath);\n const snapshots: ConfigFileSnapshot[] = [{ path: resolvedConfig, content: configContent }];\n const seen = new Set<string>([resolvedConfig]);\n for (const specifier of getRelativeImportSpecifiers(configContent)) {\n const resolved = path.resolve(dir, specifier);\n if (!isPathInsideDir(dir, resolved)) continue;\n captureSnapshotIfAbsent(snapshots, resolved, seen);\n }\n return { snapshots, seen };\n}\n\nfunction restoreConfigFiles(snapshots: ConfigFileSnapshot[]): void {\n const failures: string[] = [];\n for (const { path: filePath, content } of snapshots) {\n try {\n if (content === null) {\n if (existsSync(filePath)) rmSync(filePath);\n } else {\n writeFileSync(filePath, content, \"utf-8\");\n }\n } catch (error) {\n failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n if (failures.length > 0) {\n throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join(\"; \")}`);\n }\n}\n\nasync function tryReadConfigForValidation(configFilePath: string): Promise<Config | null> {\n try {\n return (await readConfigFile(configFilePath)).config;\n } catch (error) {\n console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {\n configFilePath,\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\nasync function validateAgentUpdate(configFilePath: string, baselineConfig: Config | null, configUpdate: Config, snapshots: ConfigFileSnapshot[]): Promise<void> {\n if (baselineConfig != null) {\n const target = canonicalizeConfig(override(baselineConfig, configUpdate));\n const result = canonicalizeConfig((await readConfigFile(configFilePath)).config);\n if (!configsEqual(result, target)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);\n }\n return;\n }\n\n // Structural-only fallback: when jiti can't evaluate the config (e.g. missing\n // runtime dependencies in import-with attributes), we can only verify that\n // (a) something changed on disk and (b) the file still exports `config`.\n // This cannot catch silently mis-applied values — an accepted tradeoff vs.\n // blocking updates entirely for configs we can't evaluate.\n // When nothing changed on disk the update is either already applied or the\n // agent couldn't figure out what to do. Treat it as a no-op rather than a\n // hard failure: the structural check below still verifies the file is valid.\n if (flattenConfigUpdate(configUpdate).length > 0 && !snapshotsChangedOnDisk(snapshots)) {\n console.warn(`${LOG_PREFIX} Agent did not modify any file for ${configFilePath}; assuming values are already up to date.`);\n }\n\n const content = readFileSync(configFilePath, \"utf-8\");\n if (!configFileExportsConfig(content, configFilePath)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \\`config\\`.`);\n }\n}\n\nfunction tryParseStaticConfigFileContent(content: string, configFilePath: string): Config | null {\n try {\n const parsed = parseHexclaveConfigFileContent(content, configFilePath);\n return isValidConfig(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction configFileExportsConfig(content: string, configFilePath: string): boolean {\n try {\n parseHexclaveConfigFileContent(content, configFilePath);\n return true;\n } catch {\n // Dynamic configs can be valid even when the static parser cannot evaluate\n // them. For the structural fallback we only need to know that a runtime\n // config binding still exists after the agent edited the file.\n return /\\bexport\\s+const\\s+config\\b/.test(content);\n }\n}\n\nfunction getRelativeImportSpecifiers(content: string): string[] {\n const specifiers: string[] = [];\n const importPattern = /\\bimport\\b(?:[^'\"]*?\\bfrom\\s*)?[\"'](\\.{1,2}\\/[^\"']+)[\"']/g;\n let match: RegExpExecArray | null;\n while ((match = importPattern.exec(content)) !== null) {\n specifiers.push(match[1]);\n }\n return specifiers;\n}\n\nfunction snapshotsChangedOnDisk(snapshots: ConfigFileSnapshot[]): boolean {\n return snapshots.some(({ path: filePath, content }) => {\n const current = existsSync(filePath) ? readFileSync(filePath, \"utf-8\") : null;\n return current !== content;\n });\n}\n\nfunction flattenConfigUpdate(update: Config): ConfigChange[] {\n const changes: ConfigChange[] = [];\n const walk = (prefix: string, obj: Config): void => {\n for (const [key, value] of Object.entries(obj)) {\n const fullPath = prefix === \"\" ? key : `${prefix}.${key}`;\n if (value === undefined) continue;\n if (value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.keys(value).length > 0) {\n walk(fullPath, value);\n } else {\n changes.push({ path: fullPath, value });\n }\n }\n };\n walk(\"\", update);\n return changes;\n}\n\nfunction buildConfigUpdatePrompt(configFileName: string, configUpdate: Config, baselineConfig: Config | null): string {\n const changes = flattenConfigUpdate(configUpdate);\n const changeLines = changes.map(({ path: configPath, value }) => {\n return `- ${JSON.stringify(configPath)}: set to ${JSON.stringify(value)}`;\n }).join(\"\\n\");\n const expectedConfig = baselineConfig == null ? null : canonicalizeConfig(override(baselineConfig, configUpdate));\n const expectedConfigSection = expectedConfig == null ? \"\" : `\nAfter the edit, evaluating the exported \\`config\\` must produce this exact JSON value:\n\n${JSON.stringify(expectedConfig, null, 2)}\n`;\n\n return `You are editing a Hexclave / Stack Auth configuration file in place. Apply a set of configuration changes WITHOUT changing how the file is written.\n\nConfig file: ${JSON.stringify(configFileName)} (in the current working directory).\n\nThe file exports a \\`config\\` object (it may be wrapped in a helper such as \\`defineStackConfig(...)\\`). Some config values may be sourced from other files via imports, for example:\n\n import welcomeEmail from \"./welcome-email.tsx\" with { type: \"text\" };\n export const config = { emails: { templates: { welcome: welcomeEmail } } };\n\nApply EXACTLY these changes. Paths use dot notation, so \\`a.b.c\\` refers to \\`config.a.b.c\\`:\n\n${changeLines}\n${expectedConfigSection}\n\nRules:\n- Change ONLY the config paths listed above. Leave every other part of the file byte-for-byte unchanged: imports, comments, formatting, helper wrappers, and any config fields not listed.\n- If a listed path's value is currently provided by an imported external file (like the \\`import ... with { type: \"text\" }\\` example above), DO NOT inline the new value into the config file. Instead, overwrite that external file with the new value and keep the import statement intact.\n- If a listed path's value is a plain inline literal, edit it inline.\n- Keep the file valid: it must still export a \\`config\\` that, once evaluated, reflects the new values exactly.\n- Do not run any shell commands and do not create files other than what is required to apply these changes.`;\n}\n\nfunction canonicalizeConfig(config: Config): NormalizedConfig {\n const droppedKeys: string[] = [];\n const normalized = normalize(config, {\n onDotIntoNonObject: \"ignore\",\n onDotIntoNull: \"empty-object\",\n droppedKeys,\n });\n if (droppedKeys.length > 0) {\n throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(\", \")}`);\n }\n return normalized;\n}\n\nfunction configsEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((value, index) => configsEqual(value, b[index]));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aEntries = Object.entries(a);\n const bMap = new Map(Object.entries(b));\n if (aEntries.length !== bMap.size) return false;\n return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));\n }\n return false;\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Config } from "@hexclave/shared/dist/config/format";
2
+
3
+ //#region src/index.d.ts
4
+ declare function sha256String(value: string): string;
5
+ declare function resolveConfigFilePath(inputPath: string): string;
6
+ declare function ensureConfigFileExists(configFilePath: string): void;
7
+ declare function readConfigObject(configFilePath: string): Promise<Config>;
8
+ declare function readConfigFile(configFilePath: string): Promise<{
9
+ config: Config;
10
+ showOnboarding: boolean;
11
+ }>;
12
+ declare function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void>;
13
+ declare function replaceConfigObject(configFilePath: string, config: Config): Promise<void>;
14
+ //#endregion
15
+ export { ensureConfigFileExists, readConfigFile, readConfigObject, replaceConfigObject, resolveConfigFilePath, sha256String, updateConfigObject };
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;iBA2BgB,YAAA,CAAa,KAAA;AAAA,iBAIb,qBAAA,CAAsB,SAAA;AAAA,iBAkBtB,sBAAA,CAAuB,cAAA;AAAA,iBAMjB,gBAAA,CAAiB,cAAA,WAAyB,OAAA,CAAQ,MAAA;AAAA,iBAIlD,cAAA,CAAe,cAAA,WAAyB,OAAA;EAAU,MAAA,EAAQ,MAAA;EAAQ,cAAA;AAAA;AAAA,iBAkElE,kBAAA,CAAmB,cAAA,UAAwB,YAAA,EAAc,MAAA,GAAS,OAAA;AAAA,iBA2ClE,mBAAA,CAAoB,cAAA,UAAwB,MAAA,EAAQ,MAAA,GAAS,OAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,336 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('./chunk-BE-pF4vm.js');
3
+ let path = require("path");
4
+ path = require_chunk.__toESM(path);
5
+ let fs = require("fs");
6
+ let _hexclave_shared_dist_config_authoring = require("@hexclave/shared/dist/config-authoring");
7
+ let _hexclave_shared_dist_config_rendering = require("@hexclave/shared/dist/config-rendering");
8
+ let _hexclave_shared_dist_config_format = require("@hexclave/shared/dist/config/format");
9
+ let _hexclave_shared_dist_utils_errors = require("@hexclave/shared/dist/utils/errors");
10
+ let crypto = require("crypto");
11
+ let jiti = require("jiti");
12
+ let __config_agent_js = require("./config-agent.js");
13
+
14
+ //#region src/index.ts
15
+ const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href, { moduleCache: false });
16
+ const LOG_PREFIX = "[Stack config updater]";
17
+ const DEFAULT_AGENT_TIMEOUT_MS = 12e4;
18
+ function isConfigModule(value) {
19
+ return value !== null && typeof value === "object";
20
+ }
21
+ function sha256String(value) {
22
+ return (0, crypto.createHash)("sha256").update(value).digest("hex");
23
+ }
24
+ function resolveConfigFilePath(inputPath) {
25
+ const resolved = path.default.resolve(inputPath);
26
+ if (/\.(ts|js|mjs|cjs)$/i.test(resolved)) return resolved;
27
+ const hexclaveCandidate = path.default.join(resolved, "hexclave.config.ts");
28
+ const legacyCandidate = path.default.join(resolved, "stack.config.ts");
29
+ if ((0, fs.existsSync)(hexclaveCandidate)) return hexclaveCandidate;
30
+ if ((0, fs.existsSync)(legacyCandidate)) return legacyCandidate;
31
+ return hexclaveCandidate;
32
+ }
33
+ function ensureConfigFileExists(configFilePath) {
34
+ if ((0, fs.existsSync)(configFilePath)) return;
35
+ (0, fs.mkdirSync)(path.default.dirname(configFilePath), { recursive: true });
36
+ renderConfigObjectToFile(configFilePath, {});
37
+ }
38
+ async function readConfigObject(configFilePath) {
39
+ return (await readConfigFile(configFilePath)).config;
40
+ }
41
+ async function readConfigFile(configFilePath) {
42
+ ensureConfigFileExists(configFilePath);
43
+ if ((0, fs.readFileSync)(configFilePath, "utf-8").trim() === "") return {
44
+ config: {},
45
+ showOnboarding: false
46
+ };
47
+ let configModule;
48
+ try {
49
+ configModule = await jiti$1.import(configFilePath);
50
+ } catch (error) {
51
+ (0, _hexclave_shared_dist_utils_errors.captureError)("shared-backend/readConfigFile", error);
52
+ throw new Error(`Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as "@hexclave/next", import it from that package's lightweight "/config" entrypoint instead, which doesn't load the framework runtime:\n\n import { defineHexclaveConfig } from "@hexclave/next/config";\n`);
53
+ }
54
+ if (!isConfigModule(configModule)) throw new Error(`Invalid config in ${configFilePath}. The file must export a plain \`config\` object or "show-onboarding".`);
55
+ const config = configModule.config;
56
+ if (config === _hexclave_shared_dist_config_authoring.showOnboardingHexclaveConfigValue) return {
57
+ config: {},
58
+ showOnboarding: true
59
+ };
60
+ if (!(0, _hexclave_shared_dist_config_format.isValidConfig)(config)) throw new Error(`Invalid config in ${configFilePath}.`);
61
+ return {
62
+ config,
63
+ showOnboarding: false
64
+ };
65
+ }
66
+ function renderConfigObjectToString(configFilePath, config) {
67
+ return (0, _hexclave_shared_dist_config_rendering.renderConfigFileContent)(config, (0, _hexclave_shared_dist_config_rendering.detectImportPackageFromDir)(path.default.dirname(configFilePath)));
68
+ }
69
+ function writeFileAtomic(configFilePath, content) {
70
+ const dir = path.default.dirname(configFilePath);
71
+ (0, fs.mkdirSync)(dir, { recursive: true });
72
+ const tempPath = path.default.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);
73
+ (0, fs.writeFileSync)(tempPath, content, "utf-8");
74
+ try {
75
+ (0, fs.renameSync)(tempPath, configFilePath);
76
+ } catch (error) {
77
+ try {
78
+ (0, fs.rmSync)(tempPath);
79
+ } catch {}
80
+ throw error;
81
+ }
82
+ }
83
+ function renderConfigObjectToFile(configFilePath, config) {
84
+ writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));
85
+ }
86
+ async function updateConfigObject(configFilePath, configUpdate) {
87
+ ensureConfigFileExists(configFilePath);
88
+ if (flattenConfigUpdate(configUpdate).length === 0) return;
89
+ const content = (0, fs.readFileSync)(configFilePath, "utf-8");
90
+ const staticConfig = tryParseStaticConfigFileContent(content, configFilePath);
91
+ if (staticConfig != null && (0, _hexclave_shared_dist_config_format.isValidConfig)(staticConfig)) {
92
+ const merged = (0, _hexclave_shared_dist_config_format.override)(staticConfig, configUpdate);
93
+ if (!(0, _hexclave_shared_dist_config_format.isValidConfig)(merged)) throw new Error(`${LOG_PREFIX} Merged config is invalid after applying update to ${configFilePath}`);
94
+ renderConfigObjectToFile(configFilePath, merged);
95
+ return;
96
+ }
97
+ const baselineConfig = await tryReadConfigForValidation(configFilePath);
98
+ const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);
99
+ try {
100
+ await runConfigUpdateAgent({
101
+ prompt: buildConfigUpdatePrompt(path.default.basename(configFilePath), configUpdate, baselineConfig),
102
+ cwd: path.default.dirname(configFilePath),
103
+ onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen)
104
+ });
105
+ await validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots);
106
+ } catch (error) {
107
+ try {
108
+ restoreConfigFiles(snapshots);
109
+ } catch (restoreError) {
110
+ console.error(`${LOG_PREFIX} Failed to fully roll back config files after a failed update of ${configFilePath}; some files may be left in a partially-restored state`, {
111
+ configFilePath,
112
+ restoreError: restoreError instanceof Error ? restoreError.message : String(restoreError)
113
+ });
114
+ }
115
+ throw error;
116
+ }
117
+ }
118
+ async function replaceConfigObject(configFilePath, config) {
119
+ renderConfigObjectToFile(configFilePath, config);
120
+ }
121
+ async function runConfigUpdateAgent(options) {
122
+ const timeoutMs = parseAgentTimeoutMs();
123
+ const deniedOutOfBoundsWrites = /* @__PURE__ */ new Set();
124
+ try {
125
+ await (0, __config_agent_js.runHeadlessClaudeAgent)({
126
+ prompt: options.prompt,
127
+ cwd: options.cwd,
128
+ allowedTools: [
129
+ "Read",
130
+ "Write",
131
+ "Edit",
132
+ "Glob",
133
+ "Grep"
134
+ ],
135
+ strictIsolation: true,
136
+ timeoutMs,
137
+ stderr: (data) => {
138
+ console.warn(`${LOG_PREFIX} [agent] ${data}`);
139
+ },
140
+ onPreToolUse: (input) => {
141
+ const target = (0, __config_agent_js.getToolWriteTargetPath)(input.tool_name, input.tool_input, options.cwd);
142
+ if (target == null) return { continue: true };
143
+ if (!(0, __config_agent_js.isPathInsideDir)(options.cwd, target)) {
144
+ deniedOutOfBoundsWrites.add(target);
145
+ return { hookSpecificOutput: {
146
+ hookEventName: "PreToolUse",
147
+ permissionDecision: "deny",
148
+ permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`
149
+ } };
150
+ }
151
+ options.onFileWillChange?.(target);
152
+ return { continue: true };
153
+ }
154
+ });
155
+ } catch (error) {
156
+ if (error instanceof __config_agent_js.ClaudeAgentTimeoutError) throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);
157
+ if (error instanceof __config_agent_js.ClaudeAgentFailureError) throw new Error(`${error.message} It was unable to apply the config changes to the file.`);
158
+ throw error;
159
+ }
160
+ if (deniedOutOfBoundsWrites.size > 0) throw new Error(`Config update agent tried to modify ${deniedOutOfBoundsWrites.size} file(s) outside the config directory, which is not allowed: ${[...deniedOutOfBoundsWrites].join(", ")}. The config was not updated.`);
161
+ }
162
+ function parseAgentTimeoutMs() {
163
+ const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;
164
+ if (raw == null || raw.trim() === "") return DEFAULT_AGENT_TIMEOUT_MS;
165
+ const parsed = Number(raw);
166
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);
167
+ return parsed;
168
+ }
169
+ function captureSnapshotIfAbsent(snapshots, filePath, seen) {
170
+ const resolved = path.default.resolve(filePath);
171
+ if (seen.has(resolved)) return;
172
+ seen.add(resolved);
173
+ snapshots.push({
174
+ path: resolved,
175
+ content: (0, fs.existsSync)(resolved) ? (0, fs.readFileSync)(resolved, "utf-8") : null
176
+ });
177
+ }
178
+ function snapshotConfigFiles(configFilePath, configContent) {
179
+ const dir = path.default.dirname(configFilePath);
180
+ const resolvedConfig = path.default.resolve(configFilePath);
181
+ const snapshots = [{
182
+ path: resolvedConfig,
183
+ content: configContent
184
+ }];
185
+ const seen = new Set([resolvedConfig]);
186
+ for (const specifier of getRelativeImportSpecifiers(configContent)) {
187
+ const resolved = path.default.resolve(dir, specifier);
188
+ if (!(0, __config_agent_js.isPathInsideDir)(dir, resolved)) continue;
189
+ captureSnapshotIfAbsent(snapshots, resolved, seen);
190
+ }
191
+ return {
192
+ snapshots,
193
+ seen
194
+ };
195
+ }
196
+ function restoreConfigFiles(snapshots) {
197
+ const failures = [];
198
+ for (const { path: filePath, content } of snapshots) try {
199
+ if (content === null) {
200
+ if ((0, fs.existsSync)(filePath)) (0, fs.rmSync)(filePath);
201
+ } else (0, fs.writeFileSync)(filePath, content, "utf-8");
202
+ } catch (error) {
203
+ failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);
204
+ }
205
+ if (failures.length > 0) throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join("; ")}`);
206
+ }
207
+ async function tryReadConfigForValidation(configFilePath) {
208
+ try {
209
+ return (await readConfigFile(configFilePath)).config;
210
+ } catch (error) {
211
+ console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {
212
+ configFilePath,
213
+ error: error instanceof Error ? error.message : String(error)
214
+ });
215
+ return null;
216
+ }
217
+ }
218
+ async function validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots) {
219
+ if (baselineConfig != null) {
220
+ const target = canonicalizeConfig((0, _hexclave_shared_dist_config_format.override)(baselineConfig, configUpdate));
221
+ if (!configsEqual(canonicalizeConfig((await readConfigFile(configFilePath)).config), target)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);
222
+ return;
223
+ }
224
+ if (flattenConfigUpdate(configUpdate).length > 0 && !snapshotsChangedOnDisk(snapshots)) console.warn(`${LOG_PREFIX} Agent did not modify any file for ${configFilePath}; assuming values are already up to date.`);
225
+ if (!configFileExportsConfig((0, fs.readFileSync)(configFilePath, "utf-8"), configFilePath)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \`config\`.`);
226
+ }
227
+ function tryParseStaticConfigFileContent(content, configFilePath) {
228
+ try {
229
+ const parsed = (0, _hexclave_shared_dist_config_rendering.parseHexclaveConfigFileContent)(content, configFilePath);
230
+ return (0, _hexclave_shared_dist_config_format.isValidConfig)(parsed) ? parsed : null;
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ function configFileExportsConfig(content, configFilePath) {
236
+ try {
237
+ (0, _hexclave_shared_dist_config_rendering.parseHexclaveConfigFileContent)(content, configFilePath);
238
+ return true;
239
+ } catch {
240
+ return /\bexport\s+const\s+config\b/.test(content);
241
+ }
242
+ }
243
+ function getRelativeImportSpecifiers(content) {
244
+ const specifiers = [];
245
+ const importPattern = /\bimport\b(?:[^'"]*?\bfrom\s*)?["'](\.{1,2}\/[^"']+)["']/g;
246
+ let match;
247
+ while ((match = importPattern.exec(content)) !== null) specifiers.push(match[1]);
248
+ return specifiers;
249
+ }
250
+ function snapshotsChangedOnDisk(snapshots) {
251
+ return snapshots.some(({ path: filePath, content }) => {
252
+ return ((0, fs.existsSync)(filePath) ? (0, fs.readFileSync)(filePath, "utf-8") : null) !== content;
253
+ });
254
+ }
255
+ function flattenConfigUpdate(update) {
256
+ const changes = [];
257
+ const walk = (prefix, obj) => {
258
+ for (const [key, value] of Object.entries(obj)) {
259
+ const fullPath = prefix === "" ? key : `${prefix}.${key}`;
260
+ if (value === void 0) continue;
261
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0) walk(fullPath, value);
262
+ else changes.push({
263
+ path: fullPath,
264
+ value
265
+ });
266
+ }
267
+ };
268
+ walk("", update);
269
+ return changes;
270
+ }
271
+ function buildConfigUpdatePrompt(configFileName, configUpdate, baselineConfig) {
272
+ const changeLines = flattenConfigUpdate(configUpdate).map(({ path: configPath, value }) => {
273
+ return `- ${JSON.stringify(configPath)}: set to ${JSON.stringify(value)}`;
274
+ }).join("\n");
275
+ const expectedConfig = baselineConfig == null ? null : canonicalizeConfig((0, _hexclave_shared_dist_config_format.override)(baselineConfig, configUpdate));
276
+ const expectedConfigSection = expectedConfig == null ? "" : `
277
+ After the edit, evaluating the exported \`config\` must produce this exact JSON value:
278
+
279
+ ${JSON.stringify(expectedConfig, null, 2)}
280
+ `;
281
+ return `You are editing a Hexclave / Stack Auth configuration file in place. Apply a set of configuration changes WITHOUT changing how the file is written.
282
+
283
+ Config file: ${JSON.stringify(configFileName)} (in the current working directory).
284
+
285
+ The file exports a \`config\` object (it may be wrapped in a helper such as \`defineStackConfig(...)\`). Some config values may be sourced from other files via imports, for example:
286
+
287
+ import welcomeEmail from "./welcome-email.tsx" with { type: "text" };
288
+ export const config = { emails: { templates: { welcome: welcomeEmail } } };
289
+
290
+ Apply EXACTLY these changes. Paths use dot notation, so \`a.b.c\` refers to \`config.a.b.c\`:
291
+
292
+ ${changeLines}
293
+ ${expectedConfigSection}
294
+
295
+ Rules:
296
+ - Change ONLY the config paths listed above. Leave every other part of the file byte-for-byte unchanged: imports, comments, formatting, helper wrappers, and any config fields not listed.
297
+ - If a listed path's value is currently provided by an imported external file (like the \`import ... with { type: "text" }\` example above), DO NOT inline the new value into the config file. Instead, overwrite that external file with the new value and keep the import statement intact.
298
+ - If a listed path's value is a plain inline literal, edit it inline.
299
+ - Keep the file valid: it must still export a \`config\` that, once evaluated, reflects the new values exactly.
300
+ - Do not run any shell commands and do not create files other than what is required to apply these changes.`;
301
+ }
302
+ function canonicalizeConfig(config) {
303
+ const droppedKeys = [];
304
+ const normalized = (0, _hexclave_shared_dist_config_format.normalize)(config, {
305
+ onDotIntoNonObject: "ignore",
306
+ onDotIntoNull: "empty-object",
307
+ droppedKeys
308
+ });
309
+ if (droppedKeys.length > 0) throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")}`);
310
+ return normalized;
311
+ }
312
+ function configsEqual(a, b) {
313
+ if (a === b) return true;
314
+ if (a === null || b === null) return a === b;
315
+ if (Array.isArray(a) || Array.isArray(b)) {
316
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
317
+ return a.every((value, index) => configsEqual(value, b[index]));
318
+ }
319
+ if (typeof a === "object" && typeof b === "object") {
320
+ const aEntries = Object.entries(a);
321
+ const bMap = new Map(Object.entries(b));
322
+ if (aEntries.length !== bMap.size) return false;
323
+ return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));
324
+ }
325
+ return false;
326
+ }
327
+
328
+ //#endregion
329
+ exports.ensureConfigFileExists = ensureConfigFileExists;
330
+ exports.readConfigFile = readConfigFile;
331
+ exports.readConfigObject = readConfigObject;
332
+ exports.replaceConfigObject = replaceConfigObject;
333
+ exports.resolveConfigFilePath = resolveConfigFilePath;
334
+ exports.sha256String = sha256String;
335
+ exports.updateConfigObject = updateConfigObject;
336
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["jiti","showOnboardingHexclaveConfigValue","ClaudeAgentTimeoutError","ClaudeAgentFailureError"],"sources":["../src/index.ts"],"sourcesContent":["import { showOnboardingHexclaveConfigValue } from \"@hexclave/shared/dist/config-authoring\";\nimport { detectImportPackageFromDir, parseHexclaveConfigFileContent, renderConfigFileContent } from \"@hexclave/shared/dist/config-rendering\";\nimport type { Config, ConfigValue, NormalizedConfig } from \"@hexclave/shared/dist/config/format\";\nimport { isValidConfig, normalize, override } from \"@hexclave/shared/dist/config/format\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { createHash } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { createJiti } from \"jiti\";\nimport path from \"path\";\nimport { ClaudeAgentFailureError, ClaudeAgentTimeoutError, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent } from \"./config-agent\";\n\nconst jiti = createJiti(import.meta.url, { moduleCache: false });\n\nconst LOG_PREFIX = \"[Stack config updater]\";\nconst DEFAULT_AGENT_TIMEOUT_MS = 120_000;\n\ntype ConfigModule = {\n config?: unknown,\n};\n\ntype ConfigFileSnapshot = { path: string, content: string | null };\ntype ConfigChange = { path: string, value: ConfigValue };\n\nfunction isConfigModule(value: unknown): value is ConfigModule {\n return value !== null && typeof value === \"object\";\n}\n\nexport function sha256String(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function resolveConfigFilePath(inputPath: string): string {\n const resolved = path.resolve(inputPath);\n const looksLikeConfigFile = /\\.(ts|js|mjs|cjs)$/i.test(resolved);\n if (looksLikeConfigFile) {\n return resolved;\n }\n // Prefer hexclave.config.ts, fall back to stack.config.ts, default to the new name.\n const hexclaveCandidate = path.join(resolved, \"hexclave.config.ts\");\n const legacyCandidate = path.join(resolved, \"stack.config.ts\");\n if (existsSync(hexclaveCandidate)) {\n return hexclaveCandidate;\n }\n if (existsSync(legacyCandidate)) {\n return legacyCandidate;\n }\n return hexclaveCandidate;\n}\n\nexport function ensureConfigFileExists(configFilePath: string): void {\n if (existsSync(configFilePath)) return;\n mkdirSync(path.dirname(configFilePath), { recursive: true });\n renderConfigObjectToFile(configFilePath, {});\n}\n\nexport async function readConfigObject(configFilePath: string): Promise<Config> {\n return (await readConfigFile(configFilePath)).config;\n}\n\nexport async function readConfigFile(configFilePath: string): Promise<{ config: Config, showOnboarding: boolean }> {\n ensureConfigFileExists(configFilePath);\n const content = readFileSync(configFilePath, \"utf-8\");\n if (content.trim() === \"\") {\n return {\n config: {},\n showOnboarding: false,\n };\n }\n\n let configModule: unknown;\n try {\n configModule = await jiti.import<unknown>(configFilePath);\n } catch (error) {\n // Capture the raw jiti/framework error for diagnostics, but don't attach it as `cause` on the thrown error:\n // dashboard error formatting renders causes recursively, which would leak framework internals into the\n // user-facing message we're deliberately replacing.\n captureError(\"shared-backend/readConfigFile\", error);\n throw new Error(\n `Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as \"@hexclave/next\", import it from that package's lightweight \"/config\" entrypoint instead, which doesn't load the framework runtime:\\n\\n import { defineHexclaveConfig } from \"@hexclave/next/config\";\\n`,\n );\n }\n if (!isConfigModule(configModule)) {\n throw new Error(`Invalid config in ${configFilePath}. The file must export a plain \\`config\\` object or \"show-onboarding\".`);\n }\n\n const config = configModule.config;\n if (config === showOnboardingHexclaveConfigValue) {\n return {\n config: {},\n showOnboarding: true,\n };\n }\n if (!isValidConfig(config)) {\n throw new Error(`Invalid config in ${configFilePath}.`);\n }\n return {\n config,\n showOnboarding: false,\n };\n}\n\nfunction renderConfigObjectToString(configFilePath: string, config: Config): string {\n const importPackage = detectImportPackageFromDir(path.dirname(configFilePath));\n return renderConfigFileContent(config, importPackage);\n}\n\nfunction writeFileAtomic(configFilePath: string, content: string): void {\n const dir = path.dirname(configFilePath);\n mkdirSync(dir, { recursive: true });\n const tempPath = path.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);\n writeFileSync(tempPath, content, \"utf-8\");\n try {\n renameSync(tempPath, configFilePath);\n } catch (error) {\n try {\n rmSync(tempPath);\n } catch { /* best-effort cleanup */ }\n throw error;\n }\n}\n\nfunction renderConfigObjectToFile(configFilePath: string, config: Config): void {\n writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));\n}\n\nexport async function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void> {\n ensureConfigFileExists(configFilePath);\n\n if (flattenConfigUpdate(configUpdate).length === 0) return;\n\n const content = readFileSync(configFilePath, \"utf-8\");\n\n // Fast path: if the config is a plain static literal (no imports, no helpers),\n // apply the update deterministically without invoking the AI agent.\n const staticConfig = tryParseStaticConfigFileContent(content, configFilePath);\n if (staticConfig != null && isValidConfig(staticConfig)) {\n const merged = override(staticConfig, configUpdate);\n if (!isValidConfig(merged)) {\n throw new Error(`${LOG_PREFIX} Merged config is invalid after applying update to ${configFilePath}`);\n }\n renderConfigObjectToFile(configFilePath, merged);\n return;\n }\n\n // Agent path: config has custom structure (imports, helpers, external files)\n // that must be preserved — delegate to the AI agent.\n const baselineConfig = await tryReadConfigForValidation(configFilePath);\n const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);\n try {\n await runConfigUpdateAgent({\n prompt: buildConfigUpdatePrompt(path.basename(configFilePath), configUpdate, baselineConfig),\n cwd: path.dirname(configFilePath),\n onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen),\n });\n await validateAgentUpdate(configFilePath, baselineConfig, configUpdate, snapshots);\n } catch (error) {\n try {\n restoreConfigFiles(snapshots);\n } catch (restoreError) {\n console.error(`${LOG_PREFIX} Failed to fully roll back config files after a failed update of ${configFilePath}; some files may be left in a partially-restored state`, {\n configFilePath,\n restoreError: restoreError instanceof Error ? restoreError.message : String(restoreError),\n });\n }\n throw error;\n }\n}\n\nexport async function replaceConfigObject(configFilePath: string, config: Config): Promise<void> {\n renderConfigObjectToFile(configFilePath, config);\n}\n\nasync function runConfigUpdateAgent(options: {\n prompt: string,\n cwd: string,\n onFileWillChange?: (filePath: string) => void,\n}): Promise<void> {\n const timeoutMs = parseAgentTimeoutMs();\n const deniedOutOfBoundsWrites = new Set<string>();\n try {\n await runHeadlessClaudeAgent({\n prompt: options.prompt,\n cwd: options.cwd,\n allowedTools: [\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\"],\n strictIsolation: true,\n timeoutMs,\n stderr: (data) => { console.warn(`${LOG_PREFIX} [agent] ${data}`); },\n onPreToolUse: (input) => {\n const target = getToolWriteTargetPath(input.tool_name, input.tool_input, options.cwd);\n if (target == null) return { continue: true };\n if (!isPathInsideDir(options.cwd, target)) {\n deniedOutOfBoundsWrites.add(target);\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`,\n },\n };\n }\n options.onFileWillChange?.(target);\n return { continue: true };\n },\n });\n } catch (error) {\n if (error instanceof ClaudeAgentTimeoutError) {\n throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);\n }\n if (error instanceof ClaudeAgentFailureError) {\n throw new Error(`${error.message} It was unable to apply the config changes to the file.`);\n }\n throw error;\n }\n if (deniedOutOfBoundsWrites.size > 0) {\n throw new Error(`Config update agent tried to modify ${deniedOutOfBoundsWrites.size} file(s) outside the config directory, which is not allowed: ${[...deniedOutOfBoundsWrites].join(\", \")}. The config was not updated.`);\n }\n}\n\nfunction parseAgentTimeoutMs(): number {\n const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;\n if (raw == null || raw.trim() === \"\") return DEFAULT_AGENT_TIMEOUT_MS;\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);\n }\n return parsed;\n}\n\nfunction captureSnapshotIfAbsent(snapshots: ConfigFileSnapshot[], filePath: string, seen: Set<string>): void {\n const resolved = path.resolve(filePath);\n if (seen.has(resolved)) return;\n seen.add(resolved);\n snapshots.push({ path: resolved, content: existsSync(resolved) ? readFileSync(resolved, \"utf-8\") : null });\n}\n\nfunction snapshotConfigFiles(configFilePath: string, configContent: string): { snapshots: ConfigFileSnapshot[]; seen: Set<string> } {\n const dir = path.dirname(configFilePath);\n const resolvedConfig = path.resolve(configFilePath);\n const snapshots: ConfigFileSnapshot[] = [{ path: resolvedConfig, content: configContent }];\n const seen = new Set<string>([resolvedConfig]);\n for (const specifier of getRelativeImportSpecifiers(configContent)) {\n const resolved = path.resolve(dir, specifier);\n if (!isPathInsideDir(dir, resolved)) continue;\n captureSnapshotIfAbsent(snapshots, resolved, seen);\n }\n return { snapshots, seen };\n}\n\nfunction restoreConfigFiles(snapshots: ConfigFileSnapshot[]): void {\n const failures: string[] = [];\n for (const { path: filePath, content } of snapshots) {\n try {\n if (content === null) {\n if (existsSync(filePath)) rmSync(filePath);\n } else {\n writeFileSync(filePath, content, \"utf-8\");\n }\n } catch (error) {\n failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n if (failures.length > 0) {\n throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join(\"; \")}`);\n }\n}\n\nasync function tryReadConfigForValidation(configFilePath: string): Promise<Config | null> {\n try {\n return (await readConfigFile(configFilePath)).config;\n } catch (error) {\n console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {\n configFilePath,\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\nasync function validateAgentUpdate(configFilePath: string, baselineConfig: Config | null, configUpdate: Config, snapshots: ConfigFileSnapshot[]): Promise<void> {\n if (baselineConfig != null) {\n const target = canonicalizeConfig(override(baselineConfig, configUpdate));\n const result = canonicalizeConfig((await readConfigFile(configFilePath)).config);\n if (!configsEqual(result, target)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);\n }\n return;\n }\n\n // Structural-only fallback: when jiti can't evaluate the config (e.g. missing\n // runtime dependencies in import-with attributes), we can only verify that\n // (a) something changed on disk and (b) the file still exports `config`.\n // This cannot catch silently mis-applied values — an accepted tradeoff vs.\n // blocking updates entirely for configs we can't evaluate.\n // When nothing changed on disk the update is either already applied or the\n // agent couldn't figure out what to do. Treat it as a no-op rather than a\n // hard failure: the structural check below still verifies the file is valid.\n if (flattenConfigUpdate(configUpdate).length > 0 && !snapshotsChangedOnDisk(snapshots)) {\n console.warn(`${LOG_PREFIX} Agent did not modify any file for ${configFilePath}; assuming values are already up to date.`);\n }\n\n const content = readFileSync(configFilePath, \"utf-8\");\n if (!configFileExportsConfig(content, configFilePath)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \\`config\\`.`);\n }\n}\n\nfunction tryParseStaticConfigFileContent(content: string, configFilePath: string): Config | null {\n try {\n const parsed = parseHexclaveConfigFileContent(content, configFilePath);\n return isValidConfig(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction configFileExportsConfig(content: string, configFilePath: string): boolean {\n try {\n parseHexclaveConfigFileContent(content, configFilePath);\n return true;\n } catch {\n // Dynamic configs can be valid even when the static parser cannot evaluate\n // them. For the structural fallback we only need to know that a runtime\n // config binding still exists after the agent edited the file.\n return /\\bexport\\s+const\\s+config\\b/.test(content);\n }\n}\n\nfunction getRelativeImportSpecifiers(content: string): string[] {\n const specifiers: string[] = [];\n const importPattern = /\\bimport\\b(?:[^'\"]*?\\bfrom\\s*)?[\"'](\\.{1,2}\\/[^\"']+)[\"']/g;\n let match: RegExpExecArray | null;\n while ((match = importPattern.exec(content)) !== null) {\n specifiers.push(match[1]);\n }\n return specifiers;\n}\n\nfunction snapshotsChangedOnDisk(snapshots: ConfigFileSnapshot[]): boolean {\n return snapshots.some(({ path: filePath, content }) => {\n const current = existsSync(filePath) ? readFileSync(filePath, \"utf-8\") : null;\n return current !== content;\n });\n}\n\nfunction flattenConfigUpdate(update: Config): ConfigChange[] {\n const changes: ConfigChange[] = [];\n const walk = (prefix: string, obj: Config): void => {\n for (const [key, value] of Object.entries(obj)) {\n const fullPath = prefix === \"\" ? key : `${prefix}.${key}`;\n if (value === undefined) continue;\n if (value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.keys(value).length > 0) {\n walk(fullPath, value);\n } else {\n changes.push({ path: fullPath, value });\n }\n }\n };\n walk(\"\", update);\n return changes;\n}\n\nfunction buildConfigUpdatePrompt(configFileName: string, configUpdate: Config, baselineConfig: Config | null): string {\n const changes = flattenConfigUpdate(configUpdate);\n const changeLines = changes.map(({ path: configPath, value }) => {\n return `- ${JSON.stringify(configPath)}: set to ${JSON.stringify(value)}`;\n }).join(\"\\n\");\n const expectedConfig = baselineConfig == null ? null : canonicalizeConfig(override(baselineConfig, configUpdate));\n const expectedConfigSection = expectedConfig == null ? \"\" : `\nAfter the edit, evaluating the exported \\`config\\` must produce this exact JSON value:\n\n${JSON.stringify(expectedConfig, null, 2)}\n`;\n\n return `You are editing a Hexclave / Stack Auth configuration file in place. Apply a set of configuration changes WITHOUT changing how the file is written.\n\nConfig file: ${JSON.stringify(configFileName)} (in the current working directory).\n\nThe file exports a \\`config\\` object (it may be wrapped in a helper such as \\`defineStackConfig(...)\\`). Some config values may be sourced from other files via imports, for example:\n\n import welcomeEmail from \"./welcome-email.tsx\" with { type: \"text\" };\n export const config = { emails: { templates: { welcome: welcomeEmail } } };\n\nApply EXACTLY these changes. Paths use dot notation, so \\`a.b.c\\` refers to \\`config.a.b.c\\`:\n\n${changeLines}\n${expectedConfigSection}\n\nRules:\n- Change ONLY the config paths listed above. Leave every other part of the file byte-for-byte unchanged: imports, comments, formatting, helper wrappers, and any config fields not listed.\n- If a listed path's value is currently provided by an imported external file (like the \\`import ... with { type: \"text\" }\\` example above), DO NOT inline the new value into the config file. Instead, overwrite that external file with the new value and keep the import statement intact.\n- If a listed path's value is a plain inline literal, edit it inline.\n- Keep the file valid: it must still export a \\`config\\` that, once evaluated, reflects the new values exactly.\n- Do not run any shell commands and do not create files other than what is required to apply these changes.`;\n}\n\nfunction canonicalizeConfig(config: Config): NormalizedConfig {\n const droppedKeys: string[] = [];\n const normalized = normalize(config, {\n onDotIntoNonObject: \"ignore\",\n onDotIntoNull: \"empty-object\",\n droppedKeys,\n });\n if (droppedKeys.length > 0) {\n throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(\", \")}`);\n }\n return normalized;\n}\n\nfunction configsEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((value, index) => configsEqual(value, b[index]));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aEntries = Object.entries(a);\n const bMap = new Map(Object.entries(b));\n if (aEntries.length !== bMap.size) return false;\n return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));\n }\n return false;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@hexclave/shared-backend",
3
+ "version": "1.0.17",
4
+ "repository": "https://github.com/hexclave/hexclave",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": {
11
+ "default": "./dist/index.js"
12
+ },
13
+ "default": "./dist/esm/index.js"
14
+ },
15
+ "./config-agent": {
16
+ "types": "./dist/config-agent.d.ts",
17
+ "require": {
18
+ "default": "./dist/config-agent.js"
19
+ },
20
+ "default": "./dist/esm/config-agent.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "!dist/**/*.test.*",
26
+ "CHANGELOG.md",
27
+ "LICENSE"
28
+ ],
29
+ "dependencies": {
30
+ "@anthropic-ai/claude-agent-sdk": "^0.2.73",
31
+ "jiti": "^2.4.2",
32
+ "@hexclave/shared": "1.0.17"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "20.17.6",
36
+ "typescript": "5.9.3"
37
+ },
38
+ "scripts": {
39
+ "build": "rimraf dist && tsdown",
40
+ "clean": "rimraf dist && rimraf node_modules",
41
+ "dev": "tsdown --watch",
42
+ "lint": "eslint --ext .tsx,.ts .",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run"
45
+ }
46
+ }