khala-cli 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/THIRD_PARTY_NOTICES.txt +809 -0
- package/dist/chunk-2AMWQ7RB.mjs +1127 -0
- package/dist/chunk-3E5DH3Y6.mjs +9864 -0
- package/dist/{chunk-IQJQ4RF5.mjs → chunk-4BP7V3OP.mjs} +12 -1
- package/dist/{chunk-FV32AQTC.mjs → chunk-4BZQ4GVW.mjs} +10 -6
- package/dist/chunk-5JSGA6OB.mjs +1483 -0
- package/dist/chunk-6AQVW5W7.mjs +60 -0
- package/dist/{chunk-W6R4UJUQ.mjs → chunk-6J2PUM7L.mjs} +16 -9
- package/dist/chunk-6SUV3EW6.mjs +27 -0
- package/dist/{chunk-RNDUFTBF.mjs → chunk-ARIDZOTI.mjs} +499 -115
- package/dist/chunk-C2WEDMAF.mjs +15 -0
- package/dist/{chunk-Z4JF2M5C.mjs → chunk-CHC4VZF5.mjs} +9 -9
- package/dist/chunk-CKLCR5G4.mjs +1369 -0
- package/dist/chunk-EVT2DWNZ.mjs +58 -0
- package/dist/chunk-FJVVSKZ3.mjs +22 -0
- package/dist/{chunk-APMWDF6B.mjs → chunk-G3NMB4PY.mjs} +4 -2
- package/dist/chunk-HHEZVPRR.mjs +7 -0
- package/dist/{chunk-54XFVSEC.mjs → chunk-JKOJQTFT.mjs} +103 -5
- package/dist/{chunk-ZJ4W7P33.mjs → chunk-NDMFD5IM.mjs} +15 -10
- package/dist/{chunk-VKQ3KUYO.mjs → chunk-OEV5DFLW.mjs} +77 -7
- package/dist/chunk-OIZ4R5H7.mjs +173 -0
- package/dist/{chunk-CJTCU755.mjs → chunk-PJKI7SRI.mjs} +256 -26
- package/dist/chunk-PN25BUQ4.mjs +599 -0
- package/dist/chunk-QDPJ4VT2.mjs +432 -0
- package/dist/{chunk-2DHIAFEV.mjs → chunk-SRZTHCXP.mjs} +24 -10
- package/dist/chunk-TSK3HJEG.mjs +20 -0
- package/dist/chunk-UX34IVN5.mjs +33 -0
- package/dist/{chunk-BKLW2SPG.mjs → chunk-VAC3JTAV.mjs} +34 -17
- package/dist/{chunk-VAI7IUI4.mjs → chunk-VDZK4LW3.mjs} +30 -12
- package/dist/chunk-W6HM6F2U.mjs +901 -0
- package/dist/{chunk-H3K4GA2P.mjs → chunk-WPNMWNCL.mjs} +33 -21
- package/dist/chunk-WUDXHF2Q.mjs +14 -0
- package/dist/chunk-WZJ6DQJV.mjs +19 -0
- package/dist/{chunk-5LIE5GUU.mjs → chunk-XR625T3I.mjs} +5 -1
- package/dist/chunk-ZSKLNZWZ.mjs +224 -0
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +20 -13
- package/dist/web/assets/local-CSPuGnH2.css +1 -0
- package/dist/web/assets/local-Dtyxaacx.js +9 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -3
- package/dist/chunk-2VCAW4GH.mjs +0 -34
- package/dist/chunk-5BQTYU66.mjs +0 -886
- package/dist/chunk-5GLTKWJ5.mjs +0 -1
- package/dist/chunk-5PUQT3YS.mjs +0 -569
- package/dist/chunk-7HBRFY3Z.mjs +0 -8
- package/dist/chunk-CBPLCCRZ.mjs +0 -191
- package/dist/chunk-FH34B3I3.mjs +0 -235
- package/dist/chunk-NKX3SLS7.mjs +0 -227
- package/dist/chunk-VUSV2ADL.mjs +0 -267
- package/dist/web/assets/local-BQl2KrE1.js +0 -9
- package/dist/web/assets/local-OwDq47O4.css +0 -1
package/dist/chunk-5GLTKWJ5.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
package/dist/chunk-5PUQT3YS.mjs
DELETED
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
bundle
|
|
4
|
-
} from "./chunk-APMWDF6B.mjs";
|
|
5
|
-
import {
|
|
6
|
-
CURSOR_WORKSPACE_ENV,
|
|
7
|
-
adapterFor
|
|
8
|
-
} from "./chunk-5BQTYU66.mjs";
|
|
9
|
-
import "./chunk-VKQ3KUYO.mjs";
|
|
10
|
-
import "./chunk-CBPLCCRZ.mjs";
|
|
11
|
-
import "./chunk-IQJQ4RF5.mjs";
|
|
12
|
-
import {
|
|
13
|
-
isHarnessId
|
|
14
|
-
} from "./chunk-NKX3SLS7.mjs";
|
|
15
|
-
import "./chunk-7HBRFY3Z.mjs";
|
|
16
|
-
|
|
17
|
-
// src/install/main.ts
|
|
18
|
-
import { spawnSync } from "node:child_process";
|
|
19
|
-
import * as fs3 from "node:fs/promises";
|
|
20
|
-
import os from "node:os";
|
|
21
|
-
import path from "node:path";
|
|
22
|
-
|
|
23
|
-
// hooks/hooks.codex.json
|
|
24
|
-
var hooks_codex_default = {
|
|
25
|
-
hooks: {
|
|
26
|
-
PostToolUse: [
|
|
27
|
-
{ hooks: [{ type: "command", command: "khala hook deliver --harness codex", timeout: 10 }] }
|
|
28
|
-
],
|
|
29
|
-
UserPromptSubmit: [{ hooks: [{ type: "command", command: "khala hook deliver --harness codex", timeout: 10 }] }],
|
|
30
|
-
Stop: [{ hooks: [{ type: "command", command: "khala hook deliver --harness codex", timeout: 10 }] }]
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// codex/hooks-config.mjs
|
|
35
|
-
function codexHooksFragment(command = "khala hook deliver --harness codex") {
|
|
36
|
-
const hooks = {};
|
|
37
|
-
for (const [event, groups] of Object.entries(hooks_codex_default.hooks)) {
|
|
38
|
-
hooks[event] = groups.map((group) => ({ ...group, hooks: group.hooks.map((hook) => ({ ...hook, command })) }));
|
|
39
|
-
}
|
|
40
|
-
return { hooks };
|
|
41
|
-
}
|
|
42
|
-
function mergeCodexHooks(config, action, hooksFragment = codexHooksFragment()) {
|
|
43
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error("invalid_hooks_config");
|
|
44
|
-
config.hooks ??= {};
|
|
45
|
-
if (typeof config.hooks !== "object" || Array.isArray(config.hooks)) throw new Error("invalid_hooks_config");
|
|
46
|
-
for (const groups of Object.values(config.hooks)) {
|
|
47
|
-
if (!Array.isArray(groups) || groups.some((group) => !group || !Array.isArray(group.hooks))) {
|
|
48
|
-
throw new Error("invalid_hooks_config");
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const commands = new Set(Object.values(hooksFragment.hooks).flatMap((groups) => groups.flatMap((group) => group.hooks.map((hook) => hook.command))));
|
|
52
|
-
const warnings = [];
|
|
53
|
-
if (action === "install") {
|
|
54
|
-
for (const [event, groups] of Object.entries(hooksFragment.hooks)) {
|
|
55
|
-
const existing = config.hooks[event] ??= [];
|
|
56
|
-
for (const group of groups) {
|
|
57
|
-
if (!existing.some((item) => item.hooks.some((hook) => hook.command === group.hooks[0].command))) existing.push(group);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
for (const groups of Object.values(config.hooks)) {
|
|
61
|
-
for (const group of groups) {
|
|
62
|
-
for (const hook of group.hooks) {
|
|
63
|
-
if (typeof hook.command === "string" && hook.command.endsWith(" codex-hook")) {
|
|
64
|
-
warnings.push("warning: remove old Khala handler: " + hook.command);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
for (const [event, groups] of Object.entries(config.hooks)) {
|
|
71
|
-
const remaining = groups.flatMap((group) => {
|
|
72
|
-
const hooks = group.hooks.filter((hook) => !commands.has(hook.command));
|
|
73
|
-
if (hooks.length === group.hooks.length) return [group];
|
|
74
|
-
return hooks.length ? [{ ...group, hooks }] : [];
|
|
75
|
-
});
|
|
76
|
-
if (remaining.length === groups.length && remaining.every((group, index) => group === groups[index])) continue;
|
|
77
|
-
if (remaining.length) config.hooks[event] = remaining;
|
|
78
|
-
else delete config.hooks[event];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { config, warnings };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/install/mcp.ts
|
|
85
|
-
var USAGE = "usage: khala install mcp --print [--harness <id>]";
|
|
86
|
-
async function runMcpInstall(flags, deps) {
|
|
87
|
-
const stdout = deps.stdout ?? ((line) => {
|
|
88
|
-
process.stdout.write(line + "\n");
|
|
89
|
-
});
|
|
90
|
-
const stderr = deps.stderr ?? ((line) => {
|
|
91
|
-
process.stderr.write(line + "\n");
|
|
92
|
-
});
|
|
93
|
-
let harness = "generic";
|
|
94
|
-
let print = false;
|
|
95
|
-
for (let i = 0; i < flags.length; i++) {
|
|
96
|
-
if (flags[i] === "--print" && !print) print = true;
|
|
97
|
-
else if (flags[i] === "--harness" && isHarnessId(flags[i + 1])) harness = flags[++i];
|
|
98
|
-
else {
|
|
99
|
-
stderr(USAGE);
|
|
100
|
-
return 1;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!print) {
|
|
104
|
-
stderr(USAGE);
|
|
105
|
-
return 1;
|
|
106
|
-
}
|
|
107
|
-
const args = ["-y", "khala-cli", "mcp", "--harness", harness];
|
|
108
|
-
stdout(JSON.stringify({ mcpServers: { khala: { command: "npx", args } } }, null, 2));
|
|
109
|
-
stdout(`npx ${args.join(" ")}`);
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/install/cursor.ts
|
|
114
|
-
import * as fs from "node:fs/promises";
|
|
115
|
-
var CURSOR_HOOK_EVENTS = ["beforeSubmitPrompt", "postToolUse", "stop"];
|
|
116
|
-
var HOOK_SUFFIX = " hook deliver --harness cursor";
|
|
117
|
-
function cursorPaths({ platform, path: path2, home, env }, packageName = "khala-cli") {
|
|
118
|
-
const cursorDir = path2.join(home, ".cursor");
|
|
119
|
-
const windows = platform === "win32";
|
|
120
|
-
const dataHome = windows ? env.LOCALAPPDATA && path2.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : path2.join(home, "AppData", "Local") : env.XDG_DATA_HOME && path2.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path2.join(home, ".local", "share");
|
|
121
|
-
const prefix = path2.join(dataHome, "khala", "npm");
|
|
122
|
-
const modules = windows ? path2.join(prefix, "node_modules") : path2.join(prefix, "lib", "node_modules");
|
|
123
|
-
return {
|
|
124
|
-
cursorDir,
|
|
125
|
-
prefix,
|
|
126
|
-
mcpFile: path2.join(cursorDir, "mcp.json"),
|
|
127
|
-
hooksFile: path2.join(cursorDir, "hooks.json"),
|
|
128
|
-
script: path2.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
var posixQuote = (value) => /^[A-Za-z0-9_./:-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
132
|
-
function cursorHookCommand(platform, node, script) {
|
|
133
|
-
if (platform === "win32") return `node "${script.replaceAll("\\", "/")}"${HOOK_SUFFIX}`;
|
|
134
|
-
return `${posixQuote(node)} ${posixQuote(script)}${HOOK_SUFFIX}`;
|
|
135
|
-
}
|
|
136
|
-
function cursorMcpEntry(node, script) {
|
|
137
|
-
return {
|
|
138
|
-
type: "stdio",
|
|
139
|
-
command: node,
|
|
140
|
-
args: [script, "mcp", "--harness", "cursor"],
|
|
141
|
-
env: { [CURSOR_WORKSPACE_ENV]: "${workspaceFolder}" }
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
145
|
-
function isKhalaCursorServer(entry) {
|
|
146
|
-
if (!isObject(entry) || !Array.isArray(entry.args)) return false;
|
|
147
|
-
const args = entry.args;
|
|
148
|
-
const at = args.indexOf("--harness");
|
|
149
|
-
return args.includes("mcp") && at !== -1 && args[at + 1] === "cursor";
|
|
150
|
-
}
|
|
151
|
-
function mergeCursorMcp(config, entry) {
|
|
152
|
-
if (!isObject(config)) return { error: "invalid_config" };
|
|
153
|
-
const servers = config.mcpServers ?? {};
|
|
154
|
-
if (!isObject(servers)) return { error: "invalid_config" };
|
|
155
|
-
if (Object.hasOwn(servers, "khala") && !isKhalaCursorServer(servers.khala)) return { error: "cursor_mcp_exists" };
|
|
156
|
-
const next = { ...servers };
|
|
157
|
-
delete next.khala;
|
|
158
|
-
if (entry) next.khala = entry;
|
|
159
|
-
return { config: { ...config, mcpServers: next } };
|
|
160
|
-
}
|
|
161
|
-
function mergeCursorHooks(config, command) {
|
|
162
|
-
if (!isObject(config)) return { error: "invalid_config" };
|
|
163
|
-
const hooks = config.hooks ?? {};
|
|
164
|
-
if (!isObject(hooks) || Object.values(hooks).some((list) => !Array.isArray(list) || list.some((item) => !isObject(item)))) return { error: "invalid_config" };
|
|
165
|
-
const ours = (item) => typeof item.command === "string" && item.command.endsWith(HOOK_SUFFIX);
|
|
166
|
-
const next = {};
|
|
167
|
-
for (const [event, list] of Object.entries(hooks)) {
|
|
168
|
-
const kept = list.filter((item) => !ours(item));
|
|
169
|
-
if (kept.length) next[event] = kept;
|
|
170
|
-
}
|
|
171
|
-
if (command) {
|
|
172
|
-
for (const event of CURSOR_HOOK_EVENTS) next[event] = [...next[event] ?? [], { command, timeout: 10 }];
|
|
173
|
-
}
|
|
174
|
-
return { config: { version: 1, ...config, hooks: next } };
|
|
175
|
-
}
|
|
176
|
-
async function readJsonFile(file) {
|
|
177
|
-
let text;
|
|
178
|
-
try {
|
|
179
|
-
text = await fs.readFile(file, "utf8");
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (error.code === "ENOENT") return { text: null, value: {} };
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
184
|
-
if (!text.trim()) return { text, value: {} };
|
|
185
|
-
try {
|
|
186
|
-
return { text, value: JSON.parse(text.replace(/^/u, "")) };
|
|
187
|
-
} catch {
|
|
188
|
-
return { text, value: void 0 };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async function installCursor(input) {
|
|
192
|
-
const { paths, uninstall, stdout, stderr } = input;
|
|
193
|
-
const mcp = await readJsonFile(paths.mcpFile);
|
|
194
|
-
const hooks = await readJsonFile(paths.hooksFile);
|
|
195
|
-
const command = cursorHookCommand(input.platform, input.node, paths.script);
|
|
196
|
-
const nextMcp = mergeCursorMcp(mcp.value, uninstall ? null : cursorMcpEntry(input.node, paths.script));
|
|
197
|
-
if ("error" in nextMcp) {
|
|
198
|
-
stderr(nextMcp.error === "cursor_mcp_exists" ? `khala: ${paths.mcpFile} already has a "khala" server that is not this CLI; remove it and run this again` : `khala: invalid JSON in ${paths.mcpFile}`);
|
|
199
|
-
return 1;
|
|
200
|
-
}
|
|
201
|
-
const nextHooks = mergeCursorHooks(hooks.value, uninstall ? null : command);
|
|
202
|
-
if ("error" in nextHooks) {
|
|
203
|
-
stderr(`khala: invalid JSON in ${paths.hooksFile}`);
|
|
204
|
-
return 1;
|
|
205
|
-
}
|
|
206
|
-
if (!uninstall && input.install && !input.install()) return 1;
|
|
207
|
-
await fs.mkdir(paths.cursorDir, { recursive: true });
|
|
208
|
-
for (const [file, before, after] of [[paths.mcpFile, mcp.text, nextMcp.config], [paths.hooksFile, hooks.text, nextHooks.config]]) {
|
|
209
|
-
if (uninstall && before === null) continue;
|
|
210
|
-
if (!uninstall && before !== null) {
|
|
211
|
-
try {
|
|
212
|
-
await fs.writeFile(file + ".khala-bak", before, { flag: "wx" });
|
|
213
|
-
} catch (error) {
|
|
214
|
-
if (error.code !== "EEXIST") throw error;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const text = JSON.stringify(after, null, 2) + "\n";
|
|
218
|
-
if (text !== before) await fs.writeFile(file, text);
|
|
219
|
-
}
|
|
220
|
-
if (uninstall) {
|
|
221
|
-
stdout(`removed the Khala MCP server and hooks from ${paths.cursorDir}; delete ${paths.prefix} to remove the CLI`);
|
|
222
|
-
} else {
|
|
223
|
-
stdout(`configured ${paths.mcpFile} and ${paths.hooksFile}`);
|
|
224
|
-
stdout('restart Cursor (or toggle "khala" off and on in Settings > MCP), then check that khala shows its five tools');
|
|
225
|
-
}
|
|
226
|
-
return 0;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// src/install/opencode.ts
|
|
230
|
-
import * as fs2 from "node:fs/promises";
|
|
231
|
-
function opencodePaths(input) {
|
|
232
|
-
const { path: path2, home, env, platform } = input;
|
|
233
|
-
const configHome = env.XDG_CONFIG_HOME && path2.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path2.join(home, ".config");
|
|
234
|
-
const configDir = path2.join(configHome, "opencode");
|
|
235
|
-
const { prefix } = cursorPaths(input);
|
|
236
|
-
return {
|
|
237
|
-
configDir,
|
|
238
|
-
configFile: path2.join(configDir, "opencode.json"),
|
|
239
|
-
prefix,
|
|
240
|
-
bin: platform === "win32" ? path2.join(prefix, "khala.cmd") : path2.join(prefix, "bin", "khala")
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function opencodeMcpEntry(platform, bin) {
|
|
244
|
-
return {
|
|
245
|
-
type: "local",
|
|
246
|
-
command: [...platform === "win32" ? ["cmd", "/c"] : [], bin, "mcp", "--harness", "opencode"],
|
|
247
|
-
environment: {},
|
|
248
|
-
enabled: true
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
252
|
-
var isPlugin = (spec) => spec === "khala-opencode" || spec.startsWith("khala-opencode@") || /^file:.*[/\\]khala-opencode-[^/\\]+\.tgz$/u.test(spec);
|
|
253
|
-
var isMcp = (entry) => {
|
|
254
|
-
if (!isObject2(entry) || !Array.isArray(entry.command)) return false;
|
|
255
|
-
const at = entry.command.indexOf("--harness");
|
|
256
|
-
return entry.command.includes("mcp") && at >= 0 && entry.command[at + 1] === "opencode";
|
|
257
|
-
};
|
|
258
|
-
function mergeOpenCodeConfig(config, plugin, previousOverride) {
|
|
259
|
-
if (!isObject2(config) || config.plugin !== void 0 && (!Array.isArray(config.plugin) || config.plugin.some((p) => typeof p !== "string")) || config.mcp !== void 0 && !isObject2(config.mcp)) return { error: "invalid_config" };
|
|
260
|
-
const plugins = (config.plugin ?? []).filter((spec) => !isPlugin(spec) && spec !== previousOverride && spec !== plugin);
|
|
261
|
-
if (plugin) plugins.push(plugin);
|
|
262
|
-
const next = { ...config };
|
|
263
|
-
if (plugins.length) next.plugin = plugins;
|
|
264
|
-
else delete next.plugin;
|
|
265
|
-
if (isObject2(config.mcp) && isMcp(config.mcp.khala)) {
|
|
266
|
-
const mcp = { ...config.mcp };
|
|
267
|
-
delete mcp.khala;
|
|
268
|
-
if (Object.keys(mcp).length) next.mcp = mcp;
|
|
269
|
-
else delete next.mcp;
|
|
270
|
-
}
|
|
271
|
-
return { config: next };
|
|
272
|
-
}
|
|
273
|
-
async function installOpenCode(input) {
|
|
274
|
-
const { paths, uninstall, stdout, stderr } = input;
|
|
275
|
-
try {
|
|
276
|
-
await fs2.stat(paths.configFile.replace(/\.json$/u, ".jsonc"));
|
|
277
|
-
stderr(`khala: opencode.jsonc exists in ${paths.configDir}; ${uninstall ? "remove" : "merge"} the Khala config manually or convert it to opencode.json before retrying`);
|
|
278
|
-
return 1;
|
|
279
|
-
} catch (error) {
|
|
280
|
-
if (error.code !== "ENOENT") throw error;
|
|
281
|
-
}
|
|
282
|
-
let text = null;
|
|
283
|
-
try {
|
|
284
|
-
text = await fs2.readFile(paths.configFile, "utf8");
|
|
285
|
-
} catch (error) {
|
|
286
|
-
if (error.code !== "ENOENT") throw error;
|
|
287
|
-
}
|
|
288
|
-
let config;
|
|
289
|
-
try {
|
|
290
|
-
config = text?.trim() ? JSON.parse(text.replace(/^\uFEFF/u, "")) : {};
|
|
291
|
-
} catch {
|
|
292
|
-
stderr(`khala: invalid JSON in ${paths.configFile}`);
|
|
293
|
-
return 1;
|
|
294
|
-
}
|
|
295
|
-
const marker = paths.configFile + ".khala-plugin";
|
|
296
|
-
let previousOverride;
|
|
297
|
-
try {
|
|
298
|
-
previousOverride = (await fs2.readFile(marker, "utf8")).trim();
|
|
299
|
-
} catch (error) {
|
|
300
|
-
if (error.code !== "ENOENT") throw error;
|
|
301
|
-
}
|
|
302
|
-
const merged = mergeOpenCodeConfig(config, uninstall ? null : input.plugin, previousOverride ?? input.plugin ?? void 0);
|
|
303
|
-
if ("error" in merged) {
|
|
304
|
-
stderr(`khala: invalid config in ${paths.configFile}`);
|
|
305
|
-
return 1;
|
|
306
|
-
}
|
|
307
|
-
if (!uninstall && input.plugin === null) {
|
|
308
|
-
if (isObject2(config) && isObject2(config.mcp) && Object.hasOwn(config.mcp, "khala") && !isMcp(config.mcp.khala)) {
|
|
309
|
-
stderr(`khala: refusing to overwrite existing mcp.khala in ${paths.configFile}; remove or rename it before retrying`);
|
|
310
|
-
return 1;
|
|
311
|
-
}
|
|
312
|
-
merged.config.mcp = { ...merged.config.mcp, khala: opencodeMcpEntry(input.platform, paths.bin) };
|
|
313
|
-
}
|
|
314
|
-
if (!uninstall && input.install && !input.install()) return 1;
|
|
315
|
-
if (uninstall && text === null) return 0;
|
|
316
|
-
await fs2.mkdir(paths.configDir, { recursive: true });
|
|
317
|
-
if (!uninstall && text !== null) {
|
|
318
|
-
try {
|
|
319
|
-
await fs2.writeFile(paths.configFile + ".khala-bak", text, { flag: "wx" });
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (error.code !== "EEXIST") throw error;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const next = JSON.stringify(merged.config, null, 2) + "\n";
|
|
325
|
-
if (next !== text) await fs2.writeFile(paths.configFile, next);
|
|
326
|
-
if (uninstall || input.plugin === null) await fs2.rm(marker, { force: true });
|
|
327
|
-
else await fs2.writeFile(marker, input.plugin + "\n");
|
|
328
|
-
stdout(uninstall ? `removed the Khala plugin and MCP entry from ${paths.configFile}; delete ${paths.prefix} to remove the CLI` : input.plugin ? `configured ${paths.configFile}; restart OpenCode to load the Khala plugin` : "OpenCode installed in MCP-only mode (Async, no wake); re-run khala install opencode after updating");
|
|
329
|
-
return 0;
|
|
330
|
-
}
|
|
331
|
-
async function opencodePluginPublished(version, fetchRegistry = fetch) {
|
|
332
|
-
try {
|
|
333
|
-
const response = await fetchRegistry(`https://registry.npmjs.org/khala-opencode/${encodeURIComponent(version)}`, { signal: AbortSignal.timeout(3e3) });
|
|
334
|
-
return response.ok;
|
|
335
|
-
} catch {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// src/install/main.ts
|
|
341
|
-
var MCP_MARKER = "# Khala MCP server, managed by `khala install codex`";
|
|
342
|
-
var USAGE2 = "usage: khala install codex [--codex-home <dir>] [--uninstall] | khala install cursor [--uninstall] | khala install opencode [--uninstall] | khala install mcp --print [--harness <id>]";
|
|
343
|
-
async function runCursorInstall(flags, deps) {
|
|
344
|
-
const env = deps.env ?? process.env;
|
|
345
|
-
const stdout = deps.stdout ?? ((line) => {
|
|
346
|
-
process.stdout.write(line + "\n");
|
|
347
|
-
});
|
|
348
|
-
const stderr = deps.stderr ?? ((line) => {
|
|
349
|
-
process.stderr.write(line + "\n");
|
|
350
|
-
});
|
|
351
|
-
let uninstall = false;
|
|
352
|
-
for (const flag of flags) {
|
|
353
|
-
if (flag === "--uninstall") uninstall = true;
|
|
354
|
-
else {
|
|
355
|
-
stderr(USAGE2);
|
|
356
|
-
return 1;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
const pkg = "package" in deps ? deps.package : bundle;
|
|
360
|
-
if (!pkg && !uninstall) {
|
|
361
|
-
stderr("khala: install runs from the published package (npx -y khala-cli install cursor)");
|
|
362
|
-
return 1;
|
|
363
|
-
}
|
|
364
|
-
const platform = deps.platform ?? process.platform;
|
|
365
|
-
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
366
|
-
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
367
|
-
const paths = cursorPaths({ platform, path: pathApi, home, env }, pkg?.name);
|
|
368
|
-
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
369
|
-
return installCursor({
|
|
370
|
-
paths,
|
|
371
|
-
platform,
|
|
372
|
-
node: deps.node ?? process.execPath,
|
|
373
|
-
uninstall,
|
|
374
|
-
stdout,
|
|
375
|
-
stderr,
|
|
376
|
-
install: () => {
|
|
377
|
-
stdout(`installing ${spec} into ${paths.prefix}`);
|
|
378
|
-
if ((deps.npmInstall ?? defaultNpmInstall)(paths.prefix, spec)) return true;
|
|
379
|
-
stderr("khala: npm install failed for " + spec);
|
|
380
|
-
return false;
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
async function runOpenCodeInstall(flags, deps) {
|
|
385
|
-
const env = deps.env ?? process.env;
|
|
386
|
-
const stdout = deps.stdout ?? ((line) => {
|
|
387
|
-
process.stdout.write(line + "\n");
|
|
388
|
-
});
|
|
389
|
-
const stderr = deps.stderr ?? ((line) => {
|
|
390
|
-
process.stderr.write(line + "\n");
|
|
391
|
-
});
|
|
392
|
-
if (flags.some((flag) => flag !== "--uninstall")) {
|
|
393
|
-
stderr(USAGE2);
|
|
394
|
-
return 1;
|
|
395
|
-
}
|
|
396
|
-
const uninstall = flags.includes("--uninstall");
|
|
397
|
-
const pkg = "package" in deps ? deps.package : bundle;
|
|
398
|
-
if (!pkg && !uninstall) {
|
|
399
|
-
stderr("khala: install runs from the published package (npx -y khala-cli install opencode)");
|
|
400
|
-
return 1;
|
|
401
|
-
}
|
|
402
|
-
const platform = deps.platform ?? process.platform;
|
|
403
|
-
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
404
|
-
const paths = opencodePaths({ platform, path: platform === "win32" ? path.win32 : path.posix, home, env });
|
|
405
|
-
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
406
|
-
const override = env.KHALA_OPENCODE_PLUGIN_SPEC;
|
|
407
|
-
const plugin = uninstall ? null : override || (await opencodePluginPublished(pkg.version, deps.fetchRegistry) ? `khala-opencode@${pkg.version}` : null);
|
|
408
|
-
return installOpenCode({
|
|
409
|
-
paths,
|
|
410
|
-
platform,
|
|
411
|
-
uninstall,
|
|
412
|
-
stdout,
|
|
413
|
-
stderr,
|
|
414
|
-
plugin,
|
|
415
|
-
install: () => {
|
|
416
|
-
stdout(`installing ${spec} into ${paths.prefix}`);
|
|
417
|
-
if ((deps.npmInstall ?? defaultNpmInstall)(paths.prefix, spec)) return true;
|
|
418
|
-
stderr("khala: npm install failed for " + spec);
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
function shellQuote(value) {
|
|
424
|
-
return /^[A-Za-z0-9_./-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
425
|
-
}
|
|
426
|
-
function defaultNpmInstall(prefix, spec) {
|
|
427
|
-
const result = spawnSync("npm", ["install", "--global", "--prefix", prefix, "--no-audit", "--no-fund", "--loglevel=error", spec], {
|
|
428
|
-
stdio: ["ignore", "ignore", "inherit"],
|
|
429
|
-
shell: process.platform === "win32"
|
|
430
|
-
});
|
|
431
|
-
return result.status === 0;
|
|
432
|
-
}
|
|
433
|
-
function updateCodexToml(text, block) {
|
|
434
|
-
const lines = text.split("\n");
|
|
435
|
-
const start = lines.indexOf(MCP_MARKER);
|
|
436
|
-
if (start >= 0) {
|
|
437
|
-
let end = start + 2;
|
|
438
|
-
while (end < lines.length && !/^\s*\[/u.test(lines[end] ?? "")) end++;
|
|
439
|
-
while (end > start + 2 && (lines[end - 1] ?? "").trim() === "") end--;
|
|
440
|
-
const from = start > 0 && (lines[start - 1] ?? "").trim() === "" ? start - 1 : start;
|
|
441
|
-
lines.splice(from, end - from);
|
|
442
|
-
} else if (lines.some((line) => /^\s*\[\s*mcp_servers\s*\.\s*"?khala"?\s*\]/u.test(line))) {
|
|
443
|
-
return { error: "codex_mcp_exists" };
|
|
444
|
-
}
|
|
445
|
-
let out = lines.join("\n").replace(/\n*$/u, "");
|
|
446
|
-
if (block) out = (out ? out + "\n\n" : "") + block;
|
|
447
|
-
return { text: out ? out + "\n" : "" };
|
|
448
|
-
}
|
|
449
|
-
function codexMcpBlock(bin, env) {
|
|
450
|
-
const home = env.HOME && path.isAbsolute(env.HOME) ? env.HOME : os.homedir();
|
|
451
|
-
const state = env.XDG_STATE_HOME && path.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : path.join(home, ".local/state");
|
|
452
|
-
return [
|
|
453
|
-
MCP_MARKER,
|
|
454
|
-
"[mcp_servers.khala]",
|
|
455
|
-
`command = ${JSON.stringify(bin)}`,
|
|
456
|
-
'args = ["mcp", "--harness", "codex"]',
|
|
457
|
-
`env = { HOME = ${JSON.stringify(home)}, XDG_STATE_HOME = ${JSON.stringify(state)} }`
|
|
458
|
-
].join("\n");
|
|
459
|
-
}
|
|
460
|
-
async function readOr(file, fallback) {
|
|
461
|
-
try {
|
|
462
|
-
return { text: await fs3.readFile(file, "utf8"), mode: (await fs3.stat(file)).mode & 511 };
|
|
463
|
-
} catch (error) {
|
|
464
|
-
if (error.code === "ENOENT") return { text: fallback, mode: 384 };
|
|
465
|
-
throw error;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
async function runCodexInstall(flags, deps) {
|
|
469
|
-
const env = deps.env ?? process.env;
|
|
470
|
-
const stdout = deps.stdout ?? ((line) => {
|
|
471
|
-
process.stdout.write(line + "\n");
|
|
472
|
-
});
|
|
473
|
-
const stderr = deps.stderr ?? ((line) => {
|
|
474
|
-
process.stderr.write(line + "\n");
|
|
475
|
-
});
|
|
476
|
-
let codexHome = env.CODEX_HOME && path.isAbsolute(env.CODEX_HOME) ? env.CODEX_HOME : path.join(env.HOME ?? os.homedir(), ".codex");
|
|
477
|
-
let uninstall = false;
|
|
478
|
-
for (let i = 0; i < flags.length; i++) {
|
|
479
|
-
if (flags[i] === "--uninstall") uninstall = true;
|
|
480
|
-
else if (flags[i] === "--codex-home" && flags[i + 1]) codexHome = path.resolve(flags[++i]);
|
|
481
|
-
else {
|
|
482
|
-
stderr(USAGE2);
|
|
483
|
-
return 1;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const pkg = "package" in deps ? deps.package : bundle;
|
|
487
|
-
if (!pkg && !uninstall) {
|
|
488
|
-
stderr("khala: install runs from the published package (npx -y <package> install codex); from a checkout follow packages/agent/docs/install-codex.md");
|
|
489
|
-
return 1;
|
|
490
|
-
}
|
|
491
|
-
const dataHome = env.XDG_DATA_HOME && path.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path.join(env.HOME ?? os.homedir(), ".local/share");
|
|
492
|
-
const prefix = path.join(dataHome, "khala", "npm");
|
|
493
|
-
const bin = path.join(prefix, "bin", "khala");
|
|
494
|
-
const command = `${shellQuote(bin)} hook deliver --harness codex`;
|
|
495
|
-
const hooksFile = path.join(codexHome, "hooks.json");
|
|
496
|
-
const tomlFile = path.join(codexHome, "config.toml");
|
|
497
|
-
const hooks = await readOr(hooksFile, '{"hooks":{}}\n');
|
|
498
|
-
const toml = await readOr(tomlFile, "");
|
|
499
|
-
let parsed;
|
|
500
|
-
try {
|
|
501
|
-
parsed = JSON.parse(hooks.text);
|
|
502
|
-
} catch {
|
|
503
|
-
stderr("khala: invalid hooks.json in " + codexHome);
|
|
504
|
-
return 1;
|
|
505
|
-
}
|
|
506
|
-
let merged;
|
|
507
|
-
try {
|
|
508
|
-
merged = mergeCodexHooks(parsed, uninstall ? "uninstall" : "install", codexHooksFragment(command));
|
|
509
|
-
} catch {
|
|
510
|
-
stderr("khala: invalid hooks.json in " + codexHome);
|
|
511
|
-
return 1;
|
|
512
|
-
}
|
|
513
|
-
const nextToml = updateCodexToml(toml.text, uninstall ? null : codexMcpBlock(bin, env));
|
|
514
|
-
if ("error" in nextToml) {
|
|
515
|
-
stderr(`khala: ${tomlFile} already has an unmanaged [mcp_servers.khala] table; remove it and run this again`);
|
|
516
|
-
return 1;
|
|
517
|
-
}
|
|
518
|
-
if (!uninstall && pkg) {
|
|
519
|
-
const spec = env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}`;
|
|
520
|
-
stdout(`installing ${spec} into ${prefix}`);
|
|
521
|
-
if (!(deps.npmInstall ?? defaultNpmInstall)(prefix, spec)) {
|
|
522
|
-
stderr("khala: npm install failed for " + spec);
|
|
523
|
-
return 1;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
await fs3.mkdir(codexHome, { recursive: true });
|
|
527
|
-
if (!uninstall) {
|
|
528
|
-
try {
|
|
529
|
-
await fs3.writeFile(hooksFile + ".khala-bak", hooks.text, { flag: "wx", mode: hooks.mode });
|
|
530
|
-
} catch (error) {
|
|
531
|
-
if (error.code !== "EEXIST") throw error;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
for (const warning of merged.warnings) stderr(warning);
|
|
535
|
-
await fs3.writeFile(hooksFile, JSON.stringify(merged.config, null, 2) + "\n", { mode: hooks.mode });
|
|
536
|
-
await fs3.writeFile(tomlFile, nextToml.text, { mode: toml.mode });
|
|
537
|
-
if (uninstall) {
|
|
538
|
-
stdout(`removed the Khala MCP server and hooks from ${codexHome}; delete ${prefix} to remove the CLI`);
|
|
539
|
-
} else {
|
|
540
|
-
stdout(`configured ${tomlFile} and ${hooksFile}`);
|
|
541
|
-
stdout('restart or resume Codex, then trust the three Khala hooks in "Hooks need review"');
|
|
542
|
-
}
|
|
543
|
-
return 0;
|
|
544
|
-
}
|
|
545
|
-
async function runInstall(argv, deps = {}) {
|
|
546
|
-
const [target = "", ...flags] = argv;
|
|
547
|
-
if (target === "mcp") return runMcpInstall(flags, deps);
|
|
548
|
-
const adapter = adapterFor(target);
|
|
549
|
-
const run = adapter?.install;
|
|
550
|
-
if (run) return run(flags, deps);
|
|
551
|
-
(deps.stderr ?? ((line) => {
|
|
552
|
-
process.stderr.write(line + "\n");
|
|
553
|
-
}))(USAGE2);
|
|
554
|
-
return 1;
|
|
555
|
-
}
|
|
556
|
-
async function main(argv) {
|
|
557
|
-
return runInstall(argv);
|
|
558
|
-
}
|
|
559
|
-
export {
|
|
560
|
-
MCP_MARKER,
|
|
561
|
-
codexMcpBlock,
|
|
562
|
-
main as default,
|
|
563
|
-
runCodexInstall,
|
|
564
|
-
runCursorInstall,
|
|
565
|
-
runInstall,
|
|
566
|
-
runOpenCodeInstall,
|
|
567
|
-
shellQuote,
|
|
568
|
-
updateCodexToml
|
|
569
|
-
};
|
package/dist/chunk-7HBRFY3Z.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
|
-
|
|
3
|
-
// <define:__KHALA_BUNDLE__>
|
|
4
|
-
var define_KHALA_BUNDLE_default = { name: "khala-cli", version: "0.4.8" };
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
define_KHALA_BUNDLE_default
|
|
8
|
-
};
|