khala-cli 0.4.9 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD_PARTY_NOTICES.txt +28 -0
- package/dist/{chunk-DJM23SIU.mjs → chunk-3KHLSF4A.mjs} +7 -8
- package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-4CENUF7M.mjs} +24 -9
- package/dist/{chunk-QKWEZXR3.mjs → chunk-4PUNVPC3.mjs} +12 -4
- package/dist/{chunk-WDOHRV3D.mjs → chunk-7FFHZ7EI.mjs} +14 -14
- package/dist/chunk-AQX2OYEF.mjs +19 -0
- package/dist/{chunk-ITFD3JZS.mjs → chunk-BVG7PJNZ.mjs} +424 -78
- package/dist/{chunk-NGSYOVQ4.mjs → chunk-CXVANPY5.mjs} +2 -2
- package/dist/chunk-CYINL2V3.mjs +20 -0
- package/dist/{chunk-KMLXOPBQ.mjs → chunk-E5QRD6W3.mjs} +4 -2
- package/dist/{chunk-FO5GXCST.mjs → chunk-FGHPOKXG.mjs} +5 -5
- package/dist/{chunk-FZEH2IXZ.mjs → chunk-GVZD4TLW.mjs} +180 -26
- package/dist/{chunk-V4F3SYKW.mjs → chunk-H2L5GQ5W.mjs} +25 -17
- package/dist/chunk-HZH74L5T.mjs +901 -0
- package/dist/{chunk-NPSQNCOY.mjs → chunk-JNSB3E6C.mjs} +13 -4
- package/dist/{chunk-7R3EIDGR.mjs → chunk-LDW4HOQW.mjs} +1 -1
- package/dist/chunk-LNKI3CZO.mjs +15 -0
- package/dist/{chunk-TEFOQXBK.mjs → chunk-MAIMFERG.mjs} +3 -3
- package/dist/chunk-MYIZQCRW.mjs +27 -0
- package/dist/{chunk-TANCKTDK.mjs → chunk-N6UYX3SY.mjs} +72 -5
- package/dist/{chunk-6BO3JSUP.mjs → chunk-NE4S7N3R.mjs} +2 -2
- package/dist/{chunk-BLCRVRIK.mjs → chunk-NJ5ICCVZ.mjs} +456 -87
- package/dist/{chunk-3R6HFSLL.mjs → chunk-NN5RHU6C.mjs} +92 -38
- package/dist/{chunk-NIX5F4LT.mjs → chunk-NOKTVPSY.mjs} +330 -66
- package/dist/{chunk-3KDOTR54.mjs → chunk-NUQSDSVT.mjs} +1 -1
- package/dist/{chunk-BEO7WIIM.mjs → chunk-OSVXLZKJ.mjs} +28 -8
- package/dist/{chunk-SSO5RFOX.mjs → chunk-R5NDTJRA.mjs} +1 -1
- package/dist/{chunk-K7V7X6WU.mjs → chunk-TVQ4MSEI.mjs} +21 -4
- package/dist/{chunk-BV45NK7K.mjs → chunk-VWWI4S5A.mjs} +100 -6
- package/dist/{chunk-UK5GJO4Y.mjs → chunk-WA5LB5GN.mjs} +6 -5
- package/dist/chunk-X3BAMHAR.mjs +14 -0
- package/dist/{chunk-PAJ4JJPV.mjs → chunk-YDEZFVF7.mjs} +43 -6
- package/dist/{chunk-GSZADJMW.mjs → chunk-YFX5NDSL.mjs} +338 -81
- package/dist/{chunk-ANSR4IAW.mjs → chunk-YIKJI26V.mjs} +2 -2
- package/dist/{chunk-T7IL7V7Q.mjs → chunk-YNUCRFM2.mjs} +13 -10
- package/dist/iterm2_send.py +56 -0
- package/dist/khala.mjs +11 -11
- package/dist/web/assets/local-DPcMk9Xz.css +1 -0
- package/dist/web/assets/local-SDOFp8OA.js +9 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-47XNLAJG.mjs +0 -38
- package/dist/chunk-LBT47HLZ.mjs +0 -19
- package/dist/chunk-P3YIN7G7.mjs +0 -26
- package/dist/chunk-XBQDIKB5.mjs +0 -18
- package/dist/chunk-XUCWSYVS.mjs +0 -148
- package/dist/web/assets/local-CFzRHZpB.js +0 -9
- package/dist/web/assets/local-ciypUFoK.css +0 -1
|
@@ -1,47 +1,53 @@
|
|
|
1
1
|
import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
bundle
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NUQSDSVT.mjs";
|
|
5
5
|
import {
|
|
6
6
|
consentLine,
|
|
7
7
|
setWake
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OSVXLZKJ.mjs";
|
|
9
9
|
import {
|
|
10
|
+
antigravityRegisterHint,
|
|
10
11
|
wakeDrivers
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HZH74L5T.mjs";
|
|
12
13
|
import {
|
|
13
14
|
adapterFor
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-BVG7PJNZ.mjs";
|
|
16
|
+
import "./chunk-4PUNVPC3.mjs";
|
|
16
17
|
import {
|
|
17
18
|
ManagedFiles,
|
|
18
19
|
cursorPaths,
|
|
19
20
|
formatJson,
|
|
20
21
|
installCursor,
|
|
22
|
+
installedLauncher,
|
|
21
23
|
jsonFormat,
|
|
24
|
+
launcherShellQuote,
|
|
22
25
|
museMonitorInstruction,
|
|
23
26
|
museWatchCommand,
|
|
24
27
|
nodeScriptCommand,
|
|
28
|
+
qwenControllerFile,
|
|
29
|
+
qwenHome,
|
|
25
30
|
readManaged,
|
|
26
31
|
textFormat
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-NOKTVPSY.mjs";
|
|
33
|
+
import "./chunk-CXVANPY5.mjs";
|
|
34
|
+
import "./chunk-N6UYX3SY.mjs";
|
|
35
|
+
import "./chunk-VWWI4S5A.mjs";
|
|
30
36
|
import {
|
|
31
37
|
ensureStateDir,
|
|
32
38
|
isHarnessId,
|
|
33
39
|
stateRoot
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
40
|
+
} from "./chunk-JNSB3E6C.mjs";
|
|
41
|
+
import "./chunk-TVQ4MSEI.mjs";
|
|
36
42
|
import {
|
|
37
43
|
init_define_KHALA_BUNDLE
|
|
38
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-LDW4HOQW.mjs";
|
|
39
45
|
|
|
40
46
|
// src/install/main.ts
|
|
41
47
|
init_define_KHALA_BUNDLE();
|
|
42
48
|
import { spawnSync } from "node:child_process";
|
|
43
49
|
import os from "node:os";
|
|
44
|
-
import
|
|
50
|
+
import path2 from "node:path";
|
|
45
51
|
|
|
46
52
|
// codex/hooks-config.mjs
|
|
47
53
|
init_define_KHALA_BUNDLE();
|
|
@@ -137,9 +143,180 @@ async function runMcpInstall(flags, deps) {
|
|
|
137
143
|
return 0;
|
|
138
144
|
}
|
|
139
145
|
|
|
140
|
-
// src/install/
|
|
146
|
+
// src/install/qwen.ts
|
|
141
147
|
init_define_KHALA_BUNDLE();
|
|
142
148
|
import * as fs from "node:fs/promises";
|
|
149
|
+
import path from "node:path";
|
|
150
|
+
import { randomUUID } from "node:crypto";
|
|
151
|
+
var QWEN_WATCH_PERMISSION = "Bash(khala watch --harness qwen --session *)";
|
|
152
|
+
var suffix = " hook deliver --harness qwen";
|
|
153
|
+
var QWEN_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PostToolUse", "Stop"];
|
|
154
|
+
var object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
155
|
+
var ours = (server) => object(server) && Array.isArray(server.args) && server.args.includes("mcp") && server.args.indexOf("--harness") !== -1 && server.args[server.args.indexOf("--harness") + 1] === "qwen";
|
|
156
|
+
function mergeQwenSettings(config, entry, command, backgroundWake = false, watchCommand = "khala watch --harness qwen", watchPermission = `Bash(${watchCommand} --session *)`, previousPermission) {
|
|
157
|
+
if (!object(config) || config.mcpServers !== void 0 && !object(config.mcpServers) || config.hooks !== void 0 && !object(config.hooks)) return { error: "invalid_config" };
|
|
158
|
+
const servers = { ...config.mcpServers ?? {} };
|
|
159
|
+
if (Object.hasOwn(servers, "khala") && !ours(servers.khala)) return { error: "qwen_mcp_exists" };
|
|
160
|
+
const hooks = {};
|
|
161
|
+
for (const [event, groups] of Object.entries(config.hooks ?? {})) {
|
|
162
|
+
if (!Array.isArray(groups)) return { error: "invalid_config" };
|
|
163
|
+
const kept = [];
|
|
164
|
+
for (const group of groups) {
|
|
165
|
+
if (!object(group) || !Array.isArray(group.hooks) || group.hooks.some((h) => !object(h))) return { error: "invalid_config" };
|
|
166
|
+
const handlers = group.hooks.filter((h) => !(typeof h.command === "string" && h.command.endsWith(suffix)));
|
|
167
|
+
if (handlers.length) kept.push({ ...group, hooks: handlers });
|
|
168
|
+
}
|
|
169
|
+
if (kept.length) hooks[event] = kept;
|
|
170
|
+
}
|
|
171
|
+
delete servers.khala;
|
|
172
|
+
if (entry) servers.khala = entry;
|
|
173
|
+
if (command) for (const event of QWEN_HOOK_EVENTS) {
|
|
174
|
+
hooks[event] = [...hooks[event] ?? [], {
|
|
175
|
+
...event === "PostToolUse" ? { matcher: ".*" } : {},
|
|
176
|
+
hooks: [{ type: "command", command, timeout: 10 }]
|
|
177
|
+
}];
|
|
178
|
+
}
|
|
179
|
+
const next = { ...config };
|
|
180
|
+
delete next.mcpServers;
|
|
181
|
+
delete next.hooks;
|
|
182
|
+
if (Object.keys(servers).length) next.mcpServers = servers;
|
|
183
|
+
if (Object.keys(hooks).length) next.hooks = hooks;
|
|
184
|
+
if (backgroundWake) {
|
|
185
|
+
if (config.permissions !== void 0 && !object(config.permissions)) return { error: "invalid_config" };
|
|
186
|
+
const permissions = { ...config.permissions ?? {} };
|
|
187
|
+
if (permissions.allow !== void 0 && (!Array.isArray(permissions.allow) || permissions.allow.some((rule) => typeof rule !== "string"))) return { error: "invalid_config" };
|
|
188
|
+
const allow = (permissions.allow ?? []).filter((rule) => rule !== QWEN_WATCH_PERMISSION);
|
|
189
|
+
const permission = watchPermission;
|
|
190
|
+
const scoped = allow.filter((rule) => rule !== permission && rule !== previousPermission);
|
|
191
|
+
if (command) scoped.push(permission);
|
|
192
|
+
permissions.allow = scoped;
|
|
193
|
+
Object.assign(next, { permissions });
|
|
194
|
+
}
|
|
195
|
+
return { config: next };
|
|
196
|
+
}
|
|
197
|
+
async function installQwen(input) {
|
|
198
|
+
const file = path.join(qwenHome(input.env), "settings.json");
|
|
199
|
+
const current = await readManaged(file);
|
|
200
|
+
const before = current.text;
|
|
201
|
+
const managed = new ManagedFiles(stateRoot(input.env));
|
|
202
|
+
let config;
|
|
203
|
+
try {
|
|
204
|
+
config = before?.trim() ? JSON.parse(before.replace(/^\uFEFF/u, "")) : {};
|
|
205
|
+
} catch {
|
|
206
|
+
input.stderr("khala: invalid Qwen settings.json");
|
|
207
|
+
return 1;
|
|
208
|
+
}
|
|
209
|
+
const watchFile = path.join(path.dirname(qwenControllerFile(input.env)), "watch-command.json");
|
|
210
|
+
let previousPermission;
|
|
211
|
+
try {
|
|
212
|
+
const previous = JSON.parse(await fs.readFile(watchFile, "utf8"));
|
|
213
|
+
if (typeof previous.command === "string") previousPermission = typeof previous.permission === "string" ? previous.permission : `Bash(${previous.command} --session *)`;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error.code !== "ENOENT") {
|
|
216
|
+
input.stderr("khala: invalid saved Qwen watcher command");
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const merged = mergeQwenSettings(config, input.uninstall ? null : input.entry, input.uninstall ? null : input.command, input.backgroundWake, input.watchCommand, input.watchPermission, previousPermission);
|
|
221
|
+
if ("error" in merged) {
|
|
222
|
+
input.stderr(`khala: ${merged.error}`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
const credentialFile = qwenControllerFile(input.env);
|
|
226
|
+
const registryFile = path.join(qwenHome(input.env), "peer-controllers.json");
|
|
227
|
+
let minted;
|
|
228
|
+
try {
|
|
229
|
+
if (!input.uninstall && (input.mint || input.backgroundWake)) {
|
|
230
|
+
await ensureStateDir(path.dirname(credentialFile));
|
|
231
|
+
}
|
|
232
|
+
if (!input.uninstall && input.install && !input.install()) {
|
|
233
|
+
input.stderr("khala: npm install failed");
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
if (!input.uninstall && input.mint) {
|
|
237
|
+
await managed.record(file);
|
|
238
|
+
await managed.record(registryFile);
|
|
239
|
+
let existing;
|
|
240
|
+
try {
|
|
241
|
+
const stat2 = await fs.lstat(credentialFile);
|
|
242
|
+
if (!stat2.isFile() || stat2.isSymbolicLink() || (stat2.mode & 63) !== 0 || typeof process.getuid === "function" && stat2.uid !== process.getuid()) throw new Error("unsafe credential");
|
|
243
|
+
existing = JSON.parse(await fs.readFile(credentialFile, "utf8"));
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error.code !== "ENOENT") throw error;
|
|
246
|
+
}
|
|
247
|
+
const ids = input.list?.();
|
|
248
|
+
if (input.list && !ids) throw new Error("controller listing failed");
|
|
249
|
+
const reusable = typeof existing?.id === "string" && /^qpc_[0-9a-f]{64}$/u.test(existing?.token ?? "") && (!input.list || ids.includes(existing.id));
|
|
250
|
+
const credential = reusable ? existing : minted = input.mint();
|
|
251
|
+
if (!credential || typeof credential.id !== "string" || !/^qpc_[0-9a-f]{64}$/u.test(credential.token)) throw new Error("mint failed");
|
|
252
|
+
const temporary = credentialFile + ".tmp-" + randomUUID();
|
|
253
|
+
try {
|
|
254
|
+
await fs.writeFile(temporary, JSON.stringify(credential) + "\n", { mode: 384, flag: "wx" });
|
|
255
|
+
await fs.rename(temporary, credentialFile);
|
|
256
|
+
} finally {
|
|
257
|
+
await fs.rm(temporary, { force: true });
|
|
258
|
+
}
|
|
259
|
+
await fs.chmod(credentialFile, 384);
|
|
260
|
+
}
|
|
261
|
+
if (input.uninstall) {
|
|
262
|
+
let credential;
|
|
263
|
+
try {
|
|
264
|
+
credential = JSON.parse(await fs.readFile(credentialFile, "utf8"));
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error.code !== "ENOENT") throw error;
|
|
267
|
+
}
|
|
268
|
+
const activeIds = input.list?.();
|
|
269
|
+
if (typeof credential?.id === "string" && (!activeIds || activeIds.includes(credential.id)) && input.remove && !input.remove(credential.id)) {
|
|
270
|
+
input.stderr("khala: could not revoke Qwen controller; credential retained for retry");
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
if (await managed.original(registryFile) !== void 0) {
|
|
274
|
+
await managed.restore(await readManaged(registryFile), {
|
|
275
|
+
...jsonFormat,
|
|
276
|
+
prune: (value) => value,
|
|
277
|
+
serialize: (_value, like) => like ?? "",
|
|
278
|
+
empty: (value) => object(value) && value.schemaVersion === 1 && Array.isArray(value.controllers) && value.controllers.length === 0 && Object.keys(value).every((key) => key === "schemaVersion" || key === "controllers")
|
|
279
|
+
}, (value) => value);
|
|
280
|
+
}
|
|
281
|
+
await managed.restore(current, jsonFormat, (value, original) => {
|
|
282
|
+
const removed = mergeQwenSettings(value, null, null, input.backgroundWake, input.watchCommand, input.watchPermission, previousPermission);
|
|
283
|
+
if ("error" in removed) throw new Error(removed.error);
|
|
284
|
+
for (const key of ["mcpServers", "hooks"]) {
|
|
285
|
+
if (object(original) && object(original[key]) && !Object.keys(original[key]).length && !Object.hasOwn(removed.config, key)) removed.config[key] = original[key];
|
|
286
|
+
}
|
|
287
|
+
return removed.config;
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
await managed.write([{ current, text: formatJson(merged.config, before), mode: 384 }]);
|
|
291
|
+
}
|
|
292
|
+
if (input.backgroundWake) {
|
|
293
|
+
if (input.uninstall) await fs.rm(watchFile, { force: true });
|
|
294
|
+
else if (input.watchCommand) {
|
|
295
|
+
await fs.mkdir(path.dirname(watchFile), { recursive: true, mode: 448 });
|
|
296
|
+
await fs.writeFile(watchFile, JSON.stringify({ command: input.watchCommand, permission: input.watchPermission ?? `Bash(${input.watchCommand} --session *)` }) + "\n", { mode: 384 });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (input.uninstall) {
|
|
300
|
+
await fs.rm(credentialFile, { force: true });
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
if (minted) {
|
|
304
|
+
if (input.remove?.(minted.id)) await fs.rm(credentialFile, { force: true });
|
|
305
|
+
else {
|
|
306
|
+
await fs.writeFile(credentialFile, JSON.stringify(minted) + "\n", { mode: 384 });
|
|
307
|
+
input.stderr("khala: controller rollback failed; retry install qwen --uninstall to revoke the retained credential");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
input.stderr("khala: Qwen installation failed; check private state directories and controller availability");
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
input.stdout(input.uninstall ? "removed Khala Qwen MCP server, hooks, and local controller credential" : "configured Qwen; restart or resume Qwen to load Khala");
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/install/muse.ts
|
|
318
|
+
init_define_KHALA_BUNDLE();
|
|
319
|
+
import * as fs2 from "node:fs/promises";
|
|
143
320
|
import nodePath from "node:path";
|
|
144
321
|
|
|
145
322
|
// src/install/muse-skill.ts
|
|
@@ -166,7 +343,7 @@ The owner controls listening mode per channel: steer delivers after tools, sync
|
|
|
166
343
|
// src/install/muse.ts
|
|
167
344
|
var SUFFIX = " hook deliver --harness muse";
|
|
168
345
|
var MUSE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PostToolUse", "Stop"];
|
|
169
|
-
var
|
|
346
|
+
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
170
347
|
function musePaths(input, packageName = "khala-cli") {
|
|
171
348
|
const { prefix, script } = cursorPaths(input, packageName);
|
|
172
349
|
const configHome = input.env.XDG_CONFIG_HOME && input.path.isAbsolute(input.env.XDG_CONFIG_HOME) ? input.env.XDG_CONFIG_HOME : input.path.join(input.home, ".config");
|
|
@@ -192,41 +369,41 @@ function museMcpEntry(platform, bin, env = {}) {
|
|
|
192
369
|
env
|
|
193
370
|
};
|
|
194
371
|
}
|
|
195
|
-
var
|
|
372
|
+
var ours2 = (entry) => object2(entry) && Array.isArray(entry.args) && entry.args.includes("mcp") && entry.args[entry.args.indexOf("--harness") + 1] === "muse";
|
|
196
373
|
function mergeMuseSettings(config, entry, command, original) {
|
|
197
|
-
if (!
|
|
374
|
+
if (!object2(config) || config.schema_version !== void 0 && config.schema_version !== 1) return { error: "invalid_config" };
|
|
198
375
|
const keys = ["mcp_servers", "mcpServers"].filter((key) => Object.hasOwn(config, key));
|
|
199
376
|
for (const key of keys) {
|
|
200
377
|
const servers = config[key];
|
|
201
|
-
if (!
|
|
202
|
-
if (entry && Object.hasOwn(servers, "khala") && !
|
|
378
|
+
if (!object2(servers)) return { error: "invalid_config" };
|
|
379
|
+
if (entry && Object.hasOwn(servers, "khala") && !ours2(servers.khala)) return { error: "muse_mcp_exists" };
|
|
203
380
|
}
|
|
204
381
|
const hooks = Object.hasOwn(config, "hooks") ? config.hooks : {};
|
|
205
|
-
if (!
|
|
382
|
+
if (!object2(hooks)) return { error: "invalid_config" };
|
|
206
383
|
const nextHooks = {};
|
|
207
384
|
for (const [event, groups] of Object.entries(hooks)) {
|
|
208
385
|
if (!Array.isArray(groups)) return { error: "invalid_config" };
|
|
209
386
|
const kept = [];
|
|
210
387
|
for (const group of groups) {
|
|
211
|
-
if (!
|
|
388
|
+
if (!object2(group) || !Array.isArray(group.hooks) || group.hooks.some((item) => !object2(item))) return { error: "invalid_config" };
|
|
212
389
|
const handlers = group.hooks.filter((item) => !(typeof item.command === "string" && (item.command.endsWith(SUFFIX) || item.command.includes(SUFFIX + " --state-home "))));
|
|
213
390
|
if (handlers.length || handlers.length === group.hooks.length) kept.push({ ...group, hooks: handlers });
|
|
214
391
|
}
|
|
215
|
-
const wasEmpty =
|
|
392
|
+
const wasEmpty = object2(original?.hooks) && Array.isArray(original.hooks[event]) && original.hooks[event].length === 0;
|
|
216
393
|
if (kept.length || groups.length === 0 && command !== null || wasEmpty) nextHooks[event] = kept;
|
|
217
394
|
}
|
|
218
395
|
if (command) for (const event of MUSE_HOOK_EVENTS) {
|
|
219
396
|
nextHooks[event] = [...nextHooks[event] ?? [], { hooks: [{ type: "command", command, timeout: 10 }] }];
|
|
220
397
|
}
|
|
221
398
|
const next = { ...config };
|
|
222
|
-
if (Object.keys(nextHooks).length ||
|
|
399
|
+
if (Object.keys(nextHooks).length || object2(original?.hooks)) next.hooks = nextHooks;
|
|
223
400
|
else delete next.hooks;
|
|
224
401
|
const target = keys.includes("mcp_servers") ? "mcp_servers" : keys[0] ?? "mcp_servers";
|
|
225
402
|
for (const key of /* @__PURE__ */ new Set([...keys, target])) {
|
|
226
403
|
const servers = { ...config[key] };
|
|
227
|
-
if (
|
|
404
|
+
if (ours2(servers.khala)) delete servers.khala;
|
|
228
405
|
if (key === target && entry) servers.khala = entry;
|
|
229
|
-
if (Object.keys(servers).length || entry !== null ||
|
|
406
|
+
if (Object.keys(servers).length || entry !== null || object2(original?.[key])) next[key] = servers;
|
|
230
407
|
else delete next[key];
|
|
231
408
|
}
|
|
232
409
|
return { config: next };
|
|
@@ -236,7 +413,7 @@ async function installMuse(input) {
|
|
|
236
413
|
let before = null;
|
|
237
414
|
let config = { schema_version: 1 };
|
|
238
415
|
try {
|
|
239
|
-
before = await
|
|
416
|
+
before = await fs2.readFile(paths.settingsFile, "utf8");
|
|
240
417
|
} catch (error) {
|
|
241
418
|
if (error.code !== "ENOENT") throw error;
|
|
242
419
|
}
|
|
@@ -252,13 +429,13 @@ async function installMuse(input) {
|
|
|
252
429
|
let skillsParentCreated = false;
|
|
253
430
|
if (uninstall) {
|
|
254
431
|
try {
|
|
255
|
-
skillsParentCreated = JSON.parse(await
|
|
432
|
+
skillsParentCreated = JSON.parse(await fs2.readFile(metadataFile, "utf8")).skillsParentCreated === true;
|
|
256
433
|
} catch (error) {
|
|
257
434
|
if (!(error instanceof SyntaxError) && error.code !== "ENOENT") throw error;
|
|
258
435
|
}
|
|
259
436
|
}
|
|
260
437
|
const managed = new ManagedFiles(input.stateDir);
|
|
261
|
-
const legacy = await
|
|
438
|
+
const legacy = await fs2.readFile(backupFile).catch((error) => {
|
|
262
439
|
if (error.code === "ENOENT") return null;
|
|
263
440
|
throw error;
|
|
264
441
|
});
|
|
@@ -267,7 +444,7 @@ async function installMuse(input) {
|
|
|
267
444
|
if (originalBytes) {
|
|
268
445
|
try {
|
|
269
446
|
const parsed = jsonFormat.parse(originalBytes.toString("utf8"));
|
|
270
|
-
if (
|
|
447
|
+
if (object2(parsed)) original = parsed;
|
|
271
448
|
} catch {
|
|
272
449
|
}
|
|
273
450
|
}
|
|
@@ -285,7 +462,7 @@ async function installMuse(input) {
|
|
|
285
462
|
const skillFile = nodePath.join(nodePath.dirname(paths.settingsFile), "skills", "khala", "SKILL.md");
|
|
286
463
|
let skill = null;
|
|
287
464
|
try {
|
|
288
|
-
skill = await
|
|
465
|
+
skill = await fs2.readFile(skillFile, "utf8");
|
|
289
466
|
} catch (error) {
|
|
290
467
|
if (error.code !== "ENOENT") throw error;
|
|
291
468
|
}
|
|
@@ -301,7 +478,7 @@ async function installMuse(input) {
|
|
|
301
478
|
if (skill?.includes(MUSE_SKILL_MARKER)) {
|
|
302
479
|
if (skillsParentCreated) {
|
|
303
480
|
for (const dir of [nodePath.dirname(skillFile), nodePath.dirname(nodePath.dirname(skillFile))]) {
|
|
304
|
-
await
|
|
481
|
+
await fs2.rmdir(dir).catch((error) => {
|
|
305
482
|
if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code ?? "")) throw error;
|
|
306
483
|
});
|
|
307
484
|
}
|
|
@@ -312,11 +489,11 @@ async function installMuse(input) {
|
|
|
312
489
|
prune: (value) => value,
|
|
313
490
|
serialize: (value) => before !== null && jsonFormat.equal(value, config) ? before : formatJson(value, originalBytes?.toString("utf8") ?? before)
|
|
314
491
|
}, () => merged.config);
|
|
315
|
-
await
|
|
492
|
+
await fs2.rm(metadataFile, { force: true });
|
|
316
493
|
} else {
|
|
317
494
|
await managed.write([
|
|
318
495
|
{ current: { file: paths.settingsFile, text: before }, text: formatJson(merged.config, before), mode: 384 },
|
|
319
|
-
{ current: { file: skillFile, text: skill }, text: renderMuseSkill(museWatchCommand(void 0, paths.bin)), mode: 384 }
|
|
496
|
+
{ current: { file: skillFile, text: skill }, text: renderMuseSkill(museWatchCommand(void 0, paths.bin, input.platform)), mode: 384 }
|
|
320
497
|
]);
|
|
321
498
|
}
|
|
322
499
|
stdout(uninstall ? `removed Khala from ${paths.settingsFile}; delete ${paths.prefix} to remove the CLI` : `configured ${paths.settingsFile}; restart or resume Muse, then ask it to join your Khala channel`);
|
|
@@ -327,27 +504,27 @@ async function installMuse(input) {
|
|
|
327
504
|
init_define_KHALA_BUNDLE();
|
|
328
505
|
var COPILOT_HOOK_EVENTS = ["sessionStart", "userPromptSubmitted", "postToolUse", "agentStop"];
|
|
329
506
|
var HOOK_SUFFIX = " hook deliver --harness copilot";
|
|
330
|
-
function copilotPaths({ platform, path:
|
|
331
|
-
const copilotDir = env.COPILOT_HOME &&
|
|
507
|
+
function copilotPaths({ platform, path: path3, home, env }, packageName = "khala-cli") {
|
|
508
|
+
const copilotDir = env.COPILOT_HOME && path3.isAbsolute(env.COPILOT_HOME) ? env.COPILOT_HOME : path3.join(home, ".copilot");
|
|
332
509
|
const windows = platform === "win32";
|
|
333
|
-
const dataHome = windows ? env.LOCALAPPDATA &&
|
|
334
|
-
const prefix =
|
|
335
|
-
const modules = windows ?
|
|
510
|
+
const dataHome = windows ? env.LOCALAPPDATA && path3.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : path3.join(home, "AppData", "Local") : env.XDG_DATA_HOME && path3.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path3.join(home, ".local", "share");
|
|
511
|
+
const prefix = path3.join(dataHome, "khala", "npm");
|
|
512
|
+
const modules = windows ? path3.join(prefix, "node_modules") : path3.join(prefix, "lib", "node_modules");
|
|
336
513
|
return {
|
|
337
514
|
copilotDir,
|
|
338
515
|
prefix,
|
|
339
|
-
mcpFile:
|
|
340
|
-
hooksFile:
|
|
341
|
-
script:
|
|
516
|
+
mcpFile: path3.join(copilotDir, "mcp-config.json"),
|
|
517
|
+
hooksFile: path3.join(copilotDir, "hooks", "khala.json"),
|
|
518
|
+
script: path3.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
|
|
342
519
|
};
|
|
343
520
|
}
|
|
344
521
|
var bashQuote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
345
522
|
var powershellQuote = (value) => "'" + value.replaceAll("'", "''") + "'";
|
|
346
523
|
function copilotHookCommands(node, script, event) {
|
|
347
|
-
const
|
|
524
|
+
const suffix2 = `${HOOK_SUFFIX} --event ${event}`;
|
|
348
525
|
return {
|
|
349
|
-
bash: `${bashQuote(node.replaceAll("\\", "/"))} ${bashQuote(script.replaceAll("\\", "/"))}${
|
|
350
|
-
powershell: `& ${powershellQuote(node)} ${powershellQuote(script)}${
|
|
526
|
+
bash: `${bashQuote(node.replaceAll("\\", "/"))} ${bashQuote(script.replaceAll("\\", "/"))}${suffix2}`,
|
|
527
|
+
powershell: `& ${powershellQuote(node)} ${powershellQuote(script)}${suffix2}`
|
|
351
528
|
};
|
|
352
529
|
}
|
|
353
530
|
function copilotMcpEntry(node, script) {
|
|
@@ -430,30 +607,98 @@ async function installCopilot(input) {
|
|
|
430
607
|
return 0;
|
|
431
608
|
}
|
|
432
609
|
|
|
610
|
+
// src/install/antigravity.ts
|
|
611
|
+
init_define_KHALA_BUNDLE();
|
|
612
|
+
var HOOK_EVENTS = ["PreInvocation", "Stop"];
|
|
613
|
+
var HOOK_SUFFIX2 = " hook deliver --harness antigravity --event ";
|
|
614
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
615
|
+
var antigravityFormat = {
|
|
616
|
+
...jsonFormat,
|
|
617
|
+
parse(text) {
|
|
618
|
+
const plain = text.replace(/^\uFEFF/u, "").replace(/"(?:\\.|[^"\\])*"|\/\/[^\r\n]*|\/\*[\s\S]*?\*\//gu, (match) => match.startsWith('"') ? match : " ").replace(/"(?:\\.|[^"\\])*"|,(?=\s*[}\]])/gu, (match) => match === "," ? "" : match);
|
|
619
|
+
return plain.trim() ? JSON.parse(plain) : {};
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
function antigravityPaths(input, packageName = "khala-cli") {
|
|
623
|
+
const { prefix, script } = cursorPaths(input, packageName);
|
|
624
|
+
const configDir = input.path.join(input.home, ".gemini", "config");
|
|
625
|
+
const { bin } = installedLauncher(input.platform, input.path, input.home, input.env);
|
|
626
|
+
return { prefix, script, bin, mcpFile: input.path.join(configDir, "mcp_config.json"), hooksFile: input.path.join(configDir, "hooks.json") };
|
|
627
|
+
}
|
|
628
|
+
function antigravityHooks(platform, bin) {
|
|
629
|
+
const command = launcherShellQuote(platform, bin) + HOOK_SUFFIX2;
|
|
630
|
+
return Object.fromEntries(HOOK_EVENTS.map((event) => [event, [{ type: "command", command: command + event, timeout: 10 }]]));
|
|
631
|
+
}
|
|
632
|
+
function ownedEntry(value) {
|
|
633
|
+
return isObject2(value) && Array.isArray(value.args) && value.args.includes("mcp") && value.args.includes("--harness") && value.args[value.args.indexOf("--harness") + 1] === "antigravity";
|
|
634
|
+
}
|
|
635
|
+
function ownedHook(value) {
|
|
636
|
+
return isObject2(value) && HOOK_EVENTS.every((event) => Array.isArray(value[event]) && value[event].every((handler) => isObject2(handler) && typeof handler.command === "string" && handler.command.endsWith(HOOK_SUFFIX2 + event))) && Object.keys(value).every((key) => HOOK_EVENTS.some((event) => event === key));
|
|
637
|
+
}
|
|
638
|
+
function mergeAntigravity(config, kind, value) {
|
|
639
|
+
if (!isObject2(config)) throw new Error("invalid_config");
|
|
640
|
+
const container = kind === "mcp" ? config.mcpServers ?? {} : config;
|
|
641
|
+
if (!isObject2(container)) throw new Error("invalid_config");
|
|
642
|
+
if (Object.hasOwn(container, "khala") && !(kind === "mcp" ? ownedEntry(container.khala) : ownedHook(container.khala)))
|
|
643
|
+
throw new Error("antigravity_foreign_khala");
|
|
644
|
+
const next = { ...container };
|
|
645
|
+
delete next.khala;
|
|
646
|
+
if (value) next.khala = value;
|
|
647
|
+
return kind === "mcp" ? { ...config, ...Object.hasOwn(config, "mcpServers") || value ? { mcpServers: next } : {} } : next;
|
|
648
|
+
}
|
|
649
|
+
async function installAntigravity(input) {
|
|
650
|
+
const { paths, uninstall, stdout, stderr } = input;
|
|
651
|
+
const entries = [];
|
|
652
|
+
for (const [file, kind, value] of [
|
|
653
|
+
[paths.mcpFile, "mcp", { command: paths.bin, args: ["mcp", "--harness", "antigravity"] }],
|
|
654
|
+
[paths.hooksFile, "hooks", antigravityHooks(input.platform, paths.bin)]
|
|
655
|
+
]) {
|
|
656
|
+
const current = await readManaged(file);
|
|
657
|
+
try {
|
|
658
|
+
const config = mergeAntigravity(antigravityFormat.parse(current.text ?? ""), kind, uninstall ? null : value);
|
|
659
|
+
entries.push({ current, kind, text: formatJson(config, current.text), mode: 384 });
|
|
660
|
+
} catch {
|
|
661
|
+
stderr(`khala: invalid config or a foreign khala entry in ${file}; resolve it before retrying`);
|
|
662
|
+
return 1;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (!uninstall && input.install && !input.install()) return 1;
|
|
666
|
+
const managed = new ManagedFiles(input.stateDir);
|
|
667
|
+
if (uninstall) for (const entry of entries)
|
|
668
|
+
await managed.restore(entry.current, antigravityFormat, (value) => mergeAntigravity(value, entry.kind, null));
|
|
669
|
+
else await managed.write(entries);
|
|
670
|
+
stdout(uninstall ? `removed the Khala MCP server and hooks; delete ${paths.prefix} to remove the CLI` : `configured ${paths.mcpFile} and ${paths.hooksFile}; restart Antigravity CLI to load Khala`);
|
|
671
|
+
if (!uninstall) {
|
|
672
|
+
stdout("These config files are shared with Antigravity desktop and IDE; their Khala hooks and MCP entry are installed too.");
|
|
673
|
+
stdout("Native idle wake and Sync continuation start billed model turns. " + antigravityRegisterHint(void 0, paths.bin));
|
|
674
|
+
}
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
433
678
|
// src/install/gemini.ts
|
|
434
679
|
init_define_KHALA_BUNDLE();
|
|
435
680
|
var GEMINI_HOOK_EVENTS = ["SessionStart", "BeforeAgent", "AfterTool", "AfterAgent"];
|
|
436
|
-
var
|
|
681
|
+
var HOOK_SUFFIX3 = " hook deliver --harness gemini";
|
|
437
682
|
function geminiPaths(input, packageName = "khala-cli") {
|
|
438
683
|
const { prefix, script } = cursorPaths(input, packageName);
|
|
439
684
|
const geminiDir = input.path.join(input.home, ".gemini");
|
|
440
685
|
return { geminiDir, settingsFile: input.path.join(geminiDir, "settings.json"), prefix, script };
|
|
441
686
|
}
|
|
442
687
|
function geminiHookCommand(platform, node, script) {
|
|
443
|
-
return nodeScriptCommand(platform, node, script) +
|
|
688
|
+
return nodeScriptCommand(platform, node, script) + HOOK_SUFFIX3;
|
|
444
689
|
}
|
|
445
690
|
function geminiMcpEntry(node, script, trustTools = false) {
|
|
446
691
|
return { command: node, args: [script, "mcp", "--harness", "gemini"], ...trustTools ? { trust: true } : {} };
|
|
447
692
|
}
|
|
448
|
-
var
|
|
693
|
+
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
449
694
|
function mergeGeminiSettings(config, entry, command) {
|
|
450
|
-
if (!
|
|
695
|
+
if (!isObject3(config)) return { error: "invalid_config" };
|
|
451
696
|
const servers = config.mcpServers ?? {};
|
|
452
697
|
const hooks = config.hooks ?? {};
|
|
453
|
-
if (!
|
|
698
|
+
if (!isObject3(servers) || !isObject3(hooks)) return { error: "invalid_config" };
|
|
454
699
|
if (Object.hasOwn(servers, "khala")) {
|
|
455
700
|
const current = servers.khala;
|
|
456
|
-
if (!
|
|
701
|
+
if (!isObject3(current) || !Array.isArray(current.args) || !current.args.includes("mcp") || current.args.indexOf("--harness") === -1 || current.args[current.args.indexOf("--harness") + 1] !== "gemini") return { error: "gemini_mcp_exists" };
|
|
457
702
|
}
|
|
458
703
|
const nextServers = { ...servers };
|
|
459
704
|
delete nextServers.khala;
|
|
@@ -467,8 +712,8 @@ function mergeGeminiSettings(config, entry, command) {
|
|
|
467
712
|
if (!Array.isArray(groups)) return { error: "invalid_config" };
|
|
468
713
|
const kept = [];
|
|
469
714
|
for (const group of groups) {
|
|
470
|
-
if (!
|
|
471
|
-
const handlers = group.hooks.filter((handler) => !(typeof handler.command === "string" && handler.command.endsWith(
|
|
715
|
+
if (!isObject3(group) || !Array.isArray(group.hooks) || group.hooks.some((handler) => !isObject3(handler))) return { error: "invalid_config" };
|
|
716
|
+
const handlers = group.hooks.filter((handler) => !(typeof handler.command === "string" && handler.command.endsWith(HOOK_SUFFIX3)));
|
|
472
717
|
if (handlers.length || group.hooks.length === 0) kept.push({ ...group, hooks: handlers });
|
|
473
718
|
}
|
|
474
719
|
if (kept.length || groups.length === 0) nextHooks[event] = kept;
|
|
@@ -505,7 +750,7 @@ async function installGemini(input) {
|
|
|
505
750
|
await managed.restore(current, jsonFormat, (value) => {
|
|
506
751
|
const stripped = mergeGeminiSettings(value, null, null).config;
|
|
507
752
|
for (const key of ["mcpServers", "hooks"]) {
|
|
508
|
-
if (
|
|
753
|
+
if (isObject3(value) && !Object.hasOwn(value, key)) delete stripped[key];
|
|
509
754
|
}
|
|
510
755
|
return stripped;
|
|
511
756
|
});
|
|
@@ -524,17 +769,17 @@ async function installGemini(input) {
|
|
|
524
769
|
|
|
525
770
|
// src/install/opencode.ts
|
|
526
771
|
init_define_KHALA_BUNDLE();
|
|
527
|
-
import * as
|
|
772
|
+
import * as fs3 from "node:fs/promises";
|
|
528
773
|
function opencodePaths(input) {
|
|
529
|
-
const { path:
|
|
530
|
-
const configHome = env.XDG_CONFIG_HOME &&
|
|
531
|
-
const configDir =
|
|
774
|
+
const { path: path3, home, env, platform } = input;
|
|
775
|
+
const configHome = env.XDG_CONFIG_HOME && path3.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path3.join(home, ".config");
|
|
776
|
+
const configDir = path3.join(configHome, "opencode");
|
|
532
777
|
const { prefix } = cursorPaths(input);
|
|
533
778
|
return {
|
|
534
779
|
configDir,
|
|
535
|
-
configFile:
|
|
780
|
+
configFile: path3.join(configDir, "opencode.json"),
|
|
536
781
|
prefix,
|
|
537
|
-
bin: platform === "win32" ?
|
|
782
|
+
bin: platform === "win32" ? path3.join(prefix, "khala.cmd") : path3.join(prefix, "bin", "khala")
|
|
538
783
|
};
|
|
539
784
|
}
|
|
540
785
|
function opencodeMcpEntry(platform, bin) {
|
|
@@ -545,21 +790,21 @@ function opencodeMcpEntry(platform, bin) {
|
|
|
545
790
|
enabled: true
|
|
546
791
|
};
|
|
547
792
|
}
|
|
548
|
-
var
|
|
793
|
+
var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
549
794
|
var isPlugin = (spec) => spec === "khala-opencode" || spec.startsWith("khala-opencode@") || /^file:.*[/\\]khala-opencode-[^/\\]+\.tgz$/u.test(spec);
|
|
550
795
|
var isMcp = (entry) => {
|
|
551
|
-
if (!
|
|
796
|
+
if (!isObject4(entry) || !Array.isArray(entry.command)) return false;
|
|
552
797
|
const at = entry.command.indexOf("--harness");
|
|
553
798
|
return entry.command.includes("mcp") && at >= 0 && entry.command[at + 1] === "opencode";
|
|
554
799
|
};
|
|
555
800
|
function mergeOpenCodeConfig(config, plugin, previousOverride) {
|
|
556
|
-
if (!
|
|
801
|
+
if (!isObject4(config) || config.plugin !== void 0 && (!Array.isArray(config.plugin) || config.plugin.some((p) => typeof p !== "string")) || config.mcp !== void 0 && !isObject4(config.mcp)) return { error: "invalid_config" };
|
|
557
802
|
const plugins = (config.plugin ?? []).filter((spec) => !isPlugin(spec) && spec !== previousOverride && spec !== plugin);
|
|
558
803
|
if (plugin) plugins.push(plugin);
|
|
559
804
|
const next = { ...config };
|
|
560
805
|
if (plugins.length) next.plugin = plugins;
|
|
561
806
|
else delete next.plugin;
|
|
562
|
-
if (
|
|
807
|
+
if (isObject4(config.mcp) && isMcp(config.mcp.khala)) {
|
|
563
808
|
const mcp = { ...config.mcp };
|
|
564
809
|
delete mcp.khala;
|
|
565
810
|
if (Object.keys(mcp).length) next.mcp = mcp;
|
|
@@ -572,12 +817,12 @@ async function installOpenCode(input) {
|
|
|
572
817
|
let configFile = paths.configFile.replace(/\.json$/u, ".jsonc");
|
|
573
818
|
let text = null;
|
|
574
819
|
try {
|
|
575
|
-
text = await
|
|
820
|
+
text = await fs3.readFile(configFile, "utf8");
|
|
576
821
|
} catch (error) {
|
|
577
822
|
if (error.code !== "ENOENT") throw error;
|
|
578
823
|
configFile = paths.configFile;
|
|
579
824
|
try {
|
|
580
|
-
text = await
|
|
825
|
+
text = await fs3.readFile(configFile, "utf8");
|
|
581
826
|
} catch (error2) {
|
|
582
827
|
if (error2.code !== "ENOENT") throw error2;
|
|
583
828
|
}
|
|
@@ -585,7 +830,7 @@ async function installOpenCode(input) {
|
|
|
585
830
|
if (configFile !== paths.configFile) {
|
|
586
831
|
let jsonExists = false;
|
|
587
832
|
try {
|
|
588
|
-
await
|
|
833
|
+
await fs3.stat(paths.configFile);
|
|
589
834
|
jsonExists = true;
|
|
590
835
|
} catch (error) {
|
|
591
836
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -605,7 +850,7 @@ async function installOpenCode(input) {
|
|
|
605
850
|
const marker = paths.configFile + ".khala-plugin";
|
|
606
851
|
let previousOverride;
|
|
607
852
|
try {
|
|
608
|
-
previousOverride = (await
|
|
853
|
+
previousOverride = (await fs3.readFile(marker, "utf8")).trim();
|
|
609
854
|
} catch (error) {
|
|
610
855
|
if (error.code !== "ENOENT") throw error;
|
|
611
856
|
}
|
|
@@ -615,7 +860,7 @@ async function installOpenCode(input) {
|
|
|
615
860
|
return 1;
|
|
616
861
|
}
|
|
617
862
|
if (!uninstall && input.plugin === null) {
|
|
618
|
-
if (
|
|
863
|
+
if (isObject4(config) && isObject4(config.mcp) && Object.hasOwn(config.mcp, "khala") && !isMcp(config.mcp.khala)) {
|
|
619
864
|
stderr(`khala: refusing to overwrite existing mcp.khala in ${configFile}; remove or rename it before retrying`);
|
|
620
865
|
return 1;
|
|
621
866
|
}
|
|
@@ -625,13 +870,13 @@ async function installOpenCode(input) {
|
|
|
625
870
|
const managed = new ManagedFiles(input.stateDir);
|
|
626
871
|
const current = { file: configFile, text };
|
|
627
872
|
if (uninstall) {
|
|
628
|
-
await
|
|
873
|
+
await fs3.rm(marker, { force: true });
|
|
629
874
|
await managed.restore(current, jsonFormat, (value) => mergeOpenCodeConfig(value, null, previousOverride).config);
|
|
630
875
|
if (text === null) return 0;
|
|
631
876
|
} else {
|
|
632
877
|
await managed.write([{ current, text: formatJson(merged.config, text) }]);
|
|
633
|
-
if (input.plugin === null) await
|
|
634
|
-
else await
|
|
878
|
+
if (input.plugin === null) await fs3.rm(marker, { force: true });
|
|
879
|
+
else await fs3.writeFile(marker, input.plugin + "\n");
|
|
635
880
|
}
|
|
636
881
|
stdout(uninstall ? `removed the Khala plugin and MCP entry from ${configFile}; delete ${paths.prefix} to remove the CLI` : input.plugin ? `configured ${configFile}; restart OpenCode to load the Khala plugin` : "OpenCode installed in MCP-only mode (Async, no wake); re-run khala install opencode after updating");
|
|
637
882
|
return 0;
|
|
@@ -647,7 +892,7 @@ async function opencodePluginPublished(version, fetchRegistry = fetch) {
|
|
|
647
892
|
|
|
648
893
|
// src/install/main.ts
|
|
649
894
|
var MCP_MARKER = "# Khala MCP server, managed by `khala install codex`";
|
|
650
|
-
var USAGE2 = "usage: khala install codex [--codex-home <dir>] [--wake|--no-wake] [--uninstall] | khala install cursor [--wake|--no-wake] [--uninstall] | khala install gemini [--trust-tools] [--wake|--no-wake] [--uninstall] | khala install opencode [--uninstall] | khala install copilot [--wake|--no-wake] [--uninstall] | khala install muse [--wake|--no-wake] [--uninstall] | khala install mcp --print [--harness <id>]";
|
|
895
|
+
var USAGE2 = "usage: khala install codex [--codex-home <dir>] [--wake|--no-wake] [--uninstall] | khala install cursor [--wake|--no-wake] [--uninstall] | khala install gemini [--trust-tools] [--wake|--no-wake] [--uninstall] | khala install opencode [--uninstall] | khala install copilot [--wake|--no-wake] [--uninstall] | khala install antigravity [--wake|--no-wake] [--uninstall] | khala install muse [--wake|--no-wake] [--uninstall] | khala install qwen [--wake|--no-wake] [--uninstall] | khala install mcp --print [--harness <id>]";
|
|
651
896
|
async function runCursorInstall(flags, deps) {
|
|
652
897
|
const env = deps.env ?? process.env;
|
|
653
898
|
const stdout = deps.stdout ?? ((line) => {
|
|
@@ -670,7 +915,7 @@ async function runCursorInstall(flags, deps) {
|
|
|
670
915
|
return 1;
|
|
671
916
|
}
|
|
672
917
|
const platform = deps.platform ?? process.platform;
|
|
673
|
-
const pathApi = platform === "win32" ?
|
|
918
|
+
const pathApi = platform === "win32" ? path2.win32 : path2.posix;
|
|
674
919
|
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
675
920
|
const paths = cursorPaths({ platform, path: pathApi, home, env }, pkg?.name);
|
|
676
921
|
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
@@ -710,7 +955,7 @@ async function runGeminiInstall(flags, deps) {
|
|
|
710
955
|
}
|
|
711
956
|
const platform = deps.platform ?? process.platform;
|
|
712
957
|
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
713
|
-
const paths = geminiPaths({ platform, path: platform === "win32" ?
|
|
958
|
+
const paths = geminiPaths({ platform, path: platform === "win32" ? path2.win32 : path2.posix, home, env }, pkg?.name);
|
|
714
959
|
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
715
960
|
return installGemini({
|
|
716
961
|
paths,
|
|
@@ -729,6 +974,44 @@ async function runGeminiInstall(flags, deps) {
|
|
|
729
974
|
}
|
|
730
975
|
});
|
|
731
976
|
}
|
|
977
|
+
async function runAntigravityInstall(flags, deps) {
|
|
978
|
+
const env = deps.env ?? process.env;
|
|
979
|
+
const stdout = deps.stdout ?? ((line) => {
|
|
980
|
+
process.stdout.write(line + "\n");
|
|
981
|
+
});
|
|
982
|
+
const stderr = deps.stderr ?? ((line) => {
|
|
983
|
+
process.stderr.write(line + "\n");
|
|
984
|
+
});
|
|
985
|
+
if (flags.some((flag) => flag !== "--uninstall")) {
|
|
986
|
+
stderr(USAGE2);
|
|
987
|
+
return 1;
|
|
988
|
+
}
|
|
989
|
+
const uninstall = flags.includes("--uninstall");
|
|
990
|
+
const pkg = "package" in deps ? deps.package : bundle;
|
|
991
|
+
if (!pkg && !uninstall) {
|
|
992
|
+
stderr("khala: install runs from the published package (npx -y khala-cli install antigravity)");
|
|
993
|
+
return 1;
|
|
994
|
+
}
|
|
995
|
+
const platform = deps.platform ?? process.platform;
|
|
996
|
+
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
997
|
+
const paths = antigravityPaths({ platform, path: platform === "win32" ? path2.win32 : path2.posix, home, env }, pkg?.name);
|
|
998
|
+
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
999
|
+
return installAntigravity({
|
|
1000
|
+
paths,
|
|
1001
|
+
platform,
|
|
1002
|
+
node: deps.node ?? process.execPath,
|
|
1003
|
+
uninstall,
|
|
1004
|
+
stdout,
|
|
1005
|
+
stderr,
|
|
1006
|
+
stateDir: installStateDir(env, home),
|
|
1007
|
+
install: () => {
|
|
1008
|
+
stdout(`installing ${spec} into ${paths.prefix}`);
|
|
1009
|
+
if ((deps.npmInstall ?? defaultNpmInstall)(paths.prefix, spec)) return true;
|
|
1010
|
+
stderr("khala: npm install failed for " + spec);
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
732
1015
|
async function runCopilotInstall(flags, deps) {
|
|
733
1016
|
const env = deps.env ?? process.env;
|
|
734
1017
|
const stdout = deps.stdout ?? ((line) => {
|
|
@@ -751,7 +1034,7 @@ async function runCopilotInstall(flags, deps) {
|
|
|
751
1034
|
return 1;
|
|
752
1035
|
}
|
|
753
1036
|
const platform = deps.platform ?? process.platform;
|
|
754
|
-
const pathApi = platform === "win32" ?
|
|
1037
|
+
const pathApi = platform === "win32" ? path2.win32 : path2.posix;
|
|
755
1038
|
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
756
1039
|
const paths = copilotPaths({ platform, path: pathApi, home, env }, pkg?.name);
|
|
757
1040
|
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
@@ -790,7 +1073,7 @@ async function runOpenCodeInstall(flags, deps) {
|
|
|
790
1073
|
}
|
|
791
1074
|
const platform = deps.platform ?? process.platform;
|
|
792
1075
|
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
793
|
-
const paths = opencodePaths({ platform, path: platform === "win32" ?
|
|
1076
|
+
const paths = opencodePaths({ platform, path: platform === "win32" ? path2.win32 : path2.posix, home, env });
|
|
794
1077
|
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
795
1078
|
const override = env.KHALA_OPENCODE_PLUGIN_SPEC;
|
|
796
1079
|
const plugin = uninstall ? null : override || (await opencodePluginPublished(pkg.version, deps.fetchRegistry) ? `khala-opencode@${pkg.version}` : null);
|
|
@@ -810,6 +1093,90 @@ async function runOpenCodeInstall(flags, deps) {
|
|
|
810
1093
|
}
|
|
811
1094
|
});
|
|
812
1095
|
}
|
|
1096
|
+
async function runQwenInstall(flags, deps) {
|
|
1097
|
+
const env = { ...deps.env ?? process.env, ...deps.home ? { HOME: deps.home } : {} };
|
|
1098
|
+
const stderr = deps.stderr ?? ((line) => {
|
|
1099
|
+
process.stderr.write(line + "\n");
|
|
1100
|
+
});
|
|
1101
|
+
const stdout = deps.stdout ?? ((line) => {
|
|
1102
|
+
process.stdout.write(line + "\n");
|
|
1103
|
+
});
|
|
1104
|
+
if (flags.some((flag) => flag !== "--uninstall")) {
|
|
1105
|
+
stderr(USAGE2);
|
|
1106
|
+
return 1;
|
|
1107
|
+
}
|
|
1108
|
+
const uninstall = flags.includes("--uninstall");
|
|
1109
|
+
const pkg = "package" in deps ? deps.package : bundle;
|
|
1110
|
+
if (!pkg && !uninstall) {
|
|
1111
|
+
stderr("khala: install runs from the published package (npx -y khala-cli install qwen)");
|
|
1112
|
+
return 1;
|
|
1113
|
+
}
|
|
1114
|
+
const platform = deps.platform ?? process.platform;
|
|
1115
|
+
const paths = cursorPaths({
|
|
1116
|
+
platform,
|
|
1117
|
+
path: platform === "win32" ? path2.win32 : path2.posix,
|
|
1118
|
+
home: deps.home ?? env.HOME ?? os.homedir(),
|
|
1119
|
+
env
|
|
1120
|
+
}, pkg?.name);
|
|
1121
|
+
const node = deps.node ?? process.execPath;
|
|
1122
|
+
const command = platform === "win32" ? `node "${paths.script.replaceAll("\\", "/")}" hook deliver --harness qwen` : `${shellQuote(node)} ${shellQuote(paths.script)} hook deliver --harness qwen`;
|
|
1123
|
+
const launcher = opencodePaths({
|
|
1124
|
+
platform,
|
|
1125
|
+
path: platform === "win32" ? path2.win32 : path2.posix,
|
|
1126
|
+
home: deps.home ?? env.HOME ?? os.homedir(),
|
|
1127
|
+
env
|
|
1128
|
+
}).bin.replaceAll("\\", "/");
|
|
1129
|
+
const watchCommand = `${/\s/u.test(launcher) ? `"${launcher}"` : launcher} watch --harness qwen`;
|
|
1130
|
+
return installQwen({
|
|
1131
|
+
env,
|
|
1132
|
+
uninstall,
|
|
1133
|
+
command,
|
|
1134
|
+
watchCommand,
|
|
1135
|
+
watchPermission: `Bash(${launcher} watch --harness qwen --session *)`,
|
|
1136
|
+
backgroundWake: platform === "win32",
|
|
1137
|
+
entry: { command: node, args: [paths.script, "mcp", "--harness", "qwen"] },
|
|
1138
|
+
stdout,
|
|
1139
|
+
stderr,
|
|
1140
|
+
install: () => (deps.npmInstall ?? defaultNpmInstall)(paths.prefix, env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}`),
|
|
1141
|
+
...platform === "win32" ? {} : {
|
|
1142
|
+
list: deps.qwenList ?? (() => {
|
|
1143
|
+
const result = spawnSync("qwen", ["sessions", "controllers", "list", "--json"], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
|
|
1144
|
+
try {
|
|
1145
|
+
return result.status === 0 ? parseQwenControllerIds(result.stdout) : void 0;
|
|
1146
|
+
} catch {
|
|
1147
|
+
return void 0;
|
|
1148
|
+
}
|
|
1149
|
+
}),
|
|
1150
|
+
remove: deps.qwenRemove ?? ((id) => {
|
|
1151
|
+
const result = spawnSync("qwen", ["sessions", "controllers", "remove", id], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
|
|
1152
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
1153
|
+
stderr("khala: Qwen executable unavailable; controller could not be revoked");
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
return result.status === 0;
|
|
1157
|
+
}),
|
|
1158
|
+
mint: deps.qwenMint ?? (() => {
|
|
1159
|
+
const result = spawnSync(
|
|
1160
|
+
"qwen",
|
|
1161
|
+
["sessions", "controllers", "add", "--label", "khala", "--json"],
|
|
1162
|
+
{ env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 }
|
|
1163
|
+
);
|
|
1164
|
+
try {
|
|
1165
|
+
return result.status === 0 ? JSON.parse(result.stdout) : void 0;
|
|
1166
|
+
} catch {
|
|
1167
|
+
return void 0;
|
|
1168
|
+
}
|
|
1169
|
+
})
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
function parseQwenControllerIds(stdout) {
|
|
1174
|
+
return stdout.split(/\r?\n/u).filter((line) => line.trim()).map((line) => {
|
|
1175
|
+
const value = JSON.parse(line);
|
|
1176
|
+
if (!value || typeof value !== "object" || !("id" in value) || typeof value.id !== "string") throw new Error("invalid controller list");
|
|
1177
|
+
return value.id;
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
813
1180
|
async function runMuseInstall(flags, deps) {
|
|
814
1181
|
const env = deps.env ?? process.env;
|
|
815
1182
|
const stdout = deps.stdout ?? console.log;
|
|
@@ -826,7 +1193,7 @@ async function runMuseInstall(flags, deps) {
|
|
|
826
1193
|
}
|
|
827
1194
|
const platform = deps.platform ?? process.platform;
|
|
828
1195
|
const home = deps.home ?? (platform === "win32" ? env.USERPROFILE || os.homedir() : env.HOME || os.homedir());
|
|
829
|
-
const paths = musePaths({ platform, path: platform === "win32" ?
|
|
1196
|
+
const paths = musePaths({ platform, path: platform === "win32" ? path2.win32 : path2.posix, home, env }, pkg?.name);
|
|
830
1197
|
const spec = pkg ? env.KHALA_INSTALL_SPEC || `${pkg.name}@${pkg.version}` : "";
|
|
831
1198
|
return installMuse({
|
|
832
1199
|
paths,
|
|
@@ -874,9 +1241,9 @@ function updateCodexToml(text, block) {
|
|
|
874
1241
|
return { text: out ? out + "\n" : "" };
|
|
875
1242
|
}
|
|
876
1243
|
function codexMcpBlock(bin, env, codexHome) {
|
|
877
|
-
const home = env.HOME &&
|
|
878
|
-
const state = env.XDG_STATE_HOME &&
|
|
879
|
-
const customHome = codexHome && codexHome !==
|
|
1244
|
+
const home = env.HOME && path2.isAbsolute(env.HOME) ? env.HOME : os.homedir();
|
|
1245
|
+
const state = env.XDG_STATE_HOME && path2.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : path2.join(home, ".local/state");
|
|
1246
|
+
const customHome = codexHome && codexHome !== path2.join(home, ".codex") ? `, CODEX_HOME = ${JSON.stringify(codexHome)}` : "";
|
|
880
1247
|
return [
|
|
881
1248
|
MCP_MARKER,
|
|
882
1249
|
"[mcp_servers.khala]",
|
|
@@ -893,11 +1260,11 @@ async function runCodexInstall(flags, deps) {
|
|
|
893
1260
|
const stderr = deps.stderr ?? ((line) => {
|
|
894
1261
|
process.stderr.write(line + "\n");
|
|
895
1262
|
});
|
|
896
|
-
let codexHome = env.CODEX_HOME &&
|
|
1263
|
+
let codexHome = env.CODEX_HOME && path2.isAbsolute(env.CODEX_HOME) ? env.CODEX_HOME : path2.join(env.HOME ?? os.homedir(), ".codex");
|
|
897
1264
|
let uninstall = false;
|
|
898
1265
|
for (let i = 0; i < flags.length; i++) {
|
|
899
1266
|
if (flags[i] === "--uninstall") uninstall = true;
|
|
900
|
-
else if (flags[i] === "--codex-home" && flags[i + 1]) codexHome =
|
|
1267
|
+
else if (flags[i] === "--codex-home" && flags[i + 1]) codexHome = path2.resolve(flags[++i]);
|
|
901
1268
|
else {
|
|
902
1269
|
stderr(USAGE2);
|
|
903
1270
|
return 1;
|
|
@@ -908,12 +1275,11 @@ async function runCodexInstall(flags, deps) {
|
|
|
908
1275
|
stderr("khala: install runs from the published package (npx -y <package> install codex); from a checkout follow packages/agent/docs/install-codex.md");
|
|
909
1276
|
return 1;
|
|
910
1277
|
}
|
|
911
|
-
const
|
|
912
|
-
const prefix =
|
|
913
|
-
const bin = path.join(prefix, "bin", "khala");
|
|
1278
|
+
const platform = deps.platform ?? process.platform;
|
|
1279
|
+
const { prefix, bin } = installedLauncher(platform, platform === "win32" ? path2.win32 : path2.posix, deps.home ?? env.HOME ?? os.homedir(), env);
|
|
914
1280
|
const command = `${shellQuote(bin)} hook deliver --harness codex`;
|
|
915
|
-
const hooksFile =
|
|
916
|
-
const tomlFile =
|
|
1281
|
+
const hooksFile = path2.join(codexHome, "hooks.json");
|
|
1282
|
+
const tomlFile = path2.join(codexHome, "config.toml");
|
|
917
1283
|
const hooks = await readManaged(hooksFile);
|
|
918
1284
|
const toml = await readManaged(tomlFile);
|
|
919
1285
|
const fragment = codexHooksFragment(command);
|
|
@@ -968,7 +1334,7 @@ async function runInstall(argv, deps = {}) {
|
|
|
968
1334
|
const wake = !flags.includes("--no-wake");
|
|
969
1335
|
const result = await run(flags.filter((flag) => flag !== "--wake" && flag !== "--no-wake"), deps);
|
|
970
1336
|
if (result === 0 && !flags.includes("--uninstall")) {
|
|
971
|
-
const drivers = wakeDrivers(target).filter((driver) => driver.optIn || target === "muse").map((driver) => driver.id);
|
|
1337
|
+
const drivers = wakeDrivers(target).filter((driver) => driver.optIn || target === "muse" || target === "qwen").map((driver) => driver.id);
|
|
972
1338
|
if (drivers.length) {
|
|
973
1339
|
await setWake(target, drivers, wake, deps.env ?? process.env);
|
|
974
1340
|
(deps.stdout ?? console.log)(consentLine(target, drivers, wake));
|
|
@@ -988,6 +1354,8 @@ export {
|
|
|
988
1354
|
MCP_MARKER,
|
|
989
1355
|
codexMcpBlock,
|
|
990
1356
|
main as default,
|
|
1357
|
+
parseQwenControllerIds,
|
|
1358
|
+
runAntigravityInstall,
|
|
991
1359
|
runCodexInstall,
|
|
992
1360
|
runCopilotInstall,
|
|
993
1361
|
runCursorInstall,
|
|
@@ -995,6 +1363,7 @@ export {
|
|
|
995
1363
|
runInstall,
|
|
996
1364
|
runMuseInstall,
|
|
997
1365
|
runOpenCodeInstall,
|
|
1366
|
+
runQwenInstall,
|
|
998
1367
|
shellQuote,
|
|
999
1368
|
updateCodexToml
|
|
1000
1369
|
};
|