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.
Files changed (48) hide show
  1. package/dist/THIRD_PARTY_NOTICES.txt +28 -0
  2. package/dist/{chunk-DJM23SIU.mjs → chunk-3KHLSF4A.mjs} +7 -8
  3. package/dist/{chunk-PDZ5Z5ZE.mjs → chunk-4CENUF7M.mjs} +24 -9
  4. package/dist/{chunk-QKWEZXR3.mjs → chunk-4PUNVPC3.mjs} +12 -4
  5. package/dist/{chunk-WDOHRV3D.mjs → chunk-7FFHZ7EI.mjs} +14 -14
  6. package/dist/chunk-AQX2OYEF.mjs +19 -0
  7. package/dist/{chunk-ITFD3JZS.mjs → chunk-BVG7PJNZ.mjs} +424 -78
  8. package/dist/{chunk-NGSYOVQ4.mjs → chunk-CXVANPY5.mjs} +2 -2
  9. package/dist/chunk-CYINL2V3.mjs +20 -0
  10. package/dist/{chunk-KMLXOPBQ.mjs → chunk-E5QRD6W3.mjs} +4 -2
  11. package/dist/{chunk-FO5GXCST.mjs → chunk-FGHPOKXG.mjs} +5 -5
  12. package/dist/{chunk-FZEH2IXZ.mjs → chunk-GVZD4TLW.mjs} +180 -26
  13. package/dist/{chunk-V4F3SYKW.mjs → chunk-H2L5GQ5W.mjs} +25 -17
  14. package/dist/chunk-HZH74L5T.mjs +901 -0
  15. package/dist/{chunk-NPSQNCOY.mjs → chunk-JNSB3E6C.mjs} +13 -4
  16. package/dist/{chunk-7R3EIDGR.mjs → chunk-LDW4HOQW.mjs} +1 -1
  17. package/dist/chunk-LNKI3CZO.mjs +15 -0
  18. package/dist/{chunk-TEFOQXBK.mjs → chunk-MAIMFERG.mjs} +3 -3
  19. package/dist/chunk-MYIZQCRW.mjs +27 -0
  20. package/dist/{chunk-TANCKTDK.mjs → chunk-N6UYX3SY.mjs} +72 -5
  21. package/dist/{chunk-6BO3JSUP.mjs → chunk-NE4S7N3R.mjs} +2 -2
  22. package/dist/{chunk-BLCRVRIK.mjs → chunk-NJ5ICCVZ.mjs} +456 -87
  23. package/dist/{chunk-3R6HFSLL.mjs → chunk-NN5RHU6C.mjs} +92 -38
  24. package/dist/{chunk-NIX5F4LT.mjs → chunk-NOKTVPSY.mjs} +330 -66
  25. package/dist/{chunk-3KDOTR54.mjs → chunk-NUQSDSVT.mjs} +1 -1
  26. package/dist/{chunk-BEO7WIIM.mjs → chunk-OSVXLZKJ.mjs} +28 -8
  27. package/dist/{chunk-SSO5RFOX.mjs → chunk-R5NDTJRA.mjs} +1 -1
  28. package/dist/{chunk-K7V7X6WU.mjs → chunk-TVQ4MSEI.mjs} +21 -4
  29. package/dist/{chunk-BV45NK7K.mjs → chunk-VWWI4S5A.mjs} +100 -6
  30. package/dist/{chunk-UK5GJO4Y.mjs → chunk-WA5LB5GN.mjs} +6 -5
  31. package/dist/chunk-X3BAMHAR.mjs +14 -0
  32. package/dist/{chunk-PAJ4JJPV.mjs → chunk-YDEZFVF7.mjs} +43 -6
  33. package/dist/{chunk-GSZADJMW.mjs → chunk-YFX5NDSL.mjs} +338 -81
  34. package/dist/{chunk-ANSR4IAW.mjs → chunk-YIKJI26V.mjs} +2 -2
  35. package/dist/{chunk-T7IL7V7Q.mjs → chunk-YNUCRFM2.mjs} +13 -10
  36. package/dist/iterm2_send.py +56 -0
  37. package/dist/khala.mjs +11 -11
  38. package/dist/web/assets/local-DPcMk9Xz.css +1 -0
  39. package/dist/web/assets/local-SDOFp8OA.js +9 -0
  40. package/dist/web/index.html +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-47XNLAJG.mjs +0 -38
  43. package/dist/chunk-LBT47HLZ.mjs +0 -19
  44. package/dist/chunk-P3YIN7G7.mjs +0 -26
  45. package/dist/chunk-XBQDIKB5.mjs +0 -18
  46. package/dist/chunk-XUCWSYVS.mjs +0 -148
  47. package/dist/web/assets/local-CFzRHZpB.js +0 -9
  48. 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-3KDOTR54.mjs";
4
+ } from "./chunk-NUQSDSVT.mjs";
5
5
  import {
6
6
  consentLine,
7
7
  setWake
8
- } from "./chunk-BEO7WIIM.mjs";
8
+ } from "./chunk-OSVXLZKJ.mjs";
9
9
  import {
10
+ antigravityRegisterHint,
10
11
  wakeDrivers
11
- } from "./chunk-XUCWSYVS.mjs";
12
+ } from "./chunk-HZH74L5T.mjs";
12
13
  import {
13
14
  adapterFor
14
- } from "./chunk-ITFD3JZS.mjs";
15
- import "./chunk-QKWEZXR3.mjs";
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-NIX5F4LT.mjs";
28
- import "./chunk-NGSYOVQ4.mjs";
29
- import "./chunk-TANCKTDK.mjs";
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-NPSQNCOY.mjs";
35
- import "./chunk-K7V7X6WU.mjs";
40
+ } from "./chunk-JNSB3E6C.mjs";
41
+ import "./chunk-TVQ4MSEI.mjs";
36
42
  import {
37
43
  init_define_KHALA_BUNDLE
38
- } from "./chunk-7R3EIDGR.mjs";
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 path from "node:path";
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/muse.ts
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 object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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 ours = (entry) => object(entry) && Array.isArray(entry.args) && entry.args.includes("mcp") && entry.args[entry.args.indexOf("--harness") + 1] === "muse";
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 (!object(config) || config.schema_version !== void 0 && config.schema_version !== 1) return { error: "invalid_config" };
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 (!object(servers)) return { error: "invalid_config" };
202
- if (entry && Object.hasOwn(servers, "khala") && !ours(servers.khala)) return { error: "muse_mcp_exists" };
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 (!object(hooks)) return { error: "invalid_config" };
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 (!object(group) || !Array.isArray(group.hooks) || group.hooks.some((item) => !object(item))) return { error: "invalid_config" };
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 = object(original?.hooks) && Array.isArray(original.hooks[event]) && original.hooks[event].length === 0;
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 || object(original?.hooks)) next.hooks = nextHooks;
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 (ours(servers.khala)) delete servers.khala;
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 || object(original?.[key])) next[key] = servers;
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 fs.readFile(paths.settingsFile, "utf8");
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 fs.readFile(metadataFile, "utf8")).skillsParentCreated === true;
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 fs.readFile(backupFile).catch((error) => {
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 (object(parsed)) original = parsed;
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 fs.readFile(skillFile, "utf8");
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 fs.rmdir(dir).catch((error) => {
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 fs.rm(metadataFile, { force: true });
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: path2, home, env }, packageName = "khala-cli") {
331
- const copilotDir = env.COPILOT_HOME && path2.isAbsolute(env.COPILOT_HOME) ? env.COPILOT_HOME : path2.join(home, ".copilot");
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 && 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");
334
- const prefix = path2.join(dataHome, "khala", "npm");
335
- const modules = windows ? path2.join(prefix, "node_modules") : path2.join(prefix, "lib", "node_modules");
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: path2.join(copilotDir, "mcp-config.json"),
340
- hooksFile: path2.join(copilotDir, "hooks", "khala.json"),
341
- script: path2.join(modules, ...packageName.split("/"), "dist", "khala.mjs")
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 suffix = `${HOOK_SUFFIX} --event ${event}`;
524
+ const suffix2 = `${HOOK_SUFFIX} --event ${event}`;
348
525
  return {
349
- bash: `${bashQuote(node.replaceAll("\\", "/"))} ${bashQuote(script.replaceAll("\\", "/"))}${suffix}`,
350
- powershell: `& ${powershellQuote(node)} ${powershellQuote(script)}${suffix}`
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 HOOK_SUFFIX2 = " hook deliver --harness gemini";
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) + HOOK_SUFFIX2;
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 isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
693
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
449
694
  function mergeGeminiSettings(config, entry, command) {
450
- if (!isObject2(config)) return { error: "invalid_config" };
695
+ if (!isObject3(config)) return { error: "invalid_config" };
451
696
  const servers = config.mcpServers ?? {};
452
697
  const hooks = config.hooks ?? {};
453
- if (!isObject2(servers) || !isObject2(hooks)) return { error: "invalid_config" };
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 (!isObject2(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" };
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 (!isObject2(group) || !Array.isArray(group.hooks) || group.hooks.some((handler) => !isObject2(handler))) return { error: "invalid_config" };
471
- const handlers = group.hooks.filter((handler) => !(typeof handler.command === "string" && handler.command.endsWith(HOOK_SUFFIX2)));
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 (isObject2(value) && !Object.hasOwn(value, key)) delete stripped[key];
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 fs2 from "node:fs/promises";
772
+ import * as fs3 from "node:fs/promises";
528
773
  function opencodePaths(input) {
529
- const { path: path2, home, env, platform } = input;
530
- const configHome = env.XDG_CONFIG_HOME && path2.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path2.join(home, ".config");
531
- const configDir = path2.join(configHome, "opencode");
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: path2.join(configDir, "opencode.json"),
780
+ configFile: path3.join(configDir, "opencode.json"),
536
781
  prefix,
537
- bin: platform === "win32" ? path2.join(prefix, "khala.cmd") : path2.join(prefix, "bin", "khala")
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 isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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 (!isObject3(entry) || !Array.isArray(entry.command)) return false;
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 (!isObject3(config) || config.plugin !== void 0 && (!Array.isArray(config.plugin) || config.plugin.some((p) => typeof p !== "string")) || config.mcp !== void 0 && !isObject3(config.mcp)) return { error: "invalid_config" };
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 (isObject3(config.mcp) && isMcp(config.mcp.khala)) {
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 fs2.readFile(configFile, "utf8");
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 fs2.readFile(configFile, "utf8");
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 fs2.stat(paths.configFile);
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 fs2.readFile(marker, "utf8")).trim();
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 (isObject3(config) && isObject3(config.mcp) && Object.hasOwn(config.mcp, "khala") && !isMcp(config.mcp.khala)) {
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 fs2.rm(marker, { force: true });
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 fs2.rm(marker, { force: true });
634
- else await fs2.writeFile(marker, input.plugin + "\n");
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" ? path.win32 : path.posix;
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" ? path.win32 : path.posix, home, env }, pkg?.name);
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" ? path.win32 : path.posix;
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" ? path.win32 : path.posix, home, env });
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" ? path.win32 : path.posix, home, env }, pkg?.name);
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 && path.isAbsolute(env.HOME) ? env.HOME : os.homedir();
878
- const state = env.XDG_STATE_HOME && path.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : path.join(home, ".local/state");
879
- const customHome = codexHome && codexHome !== path.join(home, ".codex") ? `, CODEX_HOME = ${JSON.stringify(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 && path.isAbsolute(env.CODEX_HOME) ? env.CODEX_HOME : path.join(env.HOME ?? os.homedir(), ".codex");
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 = path.resolve(flags[++i]);
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 dataHome = env.XDG_DATA_HOME && path.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path.join(env.HOME ?? os.homedir(), ".local/share");
912
- const prefix = path.join(dataHome, "khala", "npm");
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 = path.join(codexHome, "hooks.json");
916
- const tomlFile = path.join(codexHome, "config.toml");
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
  };