@dashup/connector 0.1.18 → 0.1.19

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 (3) hide show
  1. package/README.md +6 -6
  2. package/dist/index.js +629 -192
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ var require_XMLHttpRequest = __commonJS({
42
42
  "use strict";
43
43
  var fs = __require("fs");
44
44
  var Url = __require("url");
45
- var spawn7 = __require("child_process").spawn;
45
+ var spawn8 = __require("child_process").spawn;
46
46
  module.exports = XMLHttpRequest3;
47
47
  XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
48
48
  function XMLHttpRequest3(opts) {
@@ -338,7 +338,7 @@ var require_XMLHttpRequest = __commonJS({
338
338
  var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
339
339
  fs.writeFileSync(syncFile, "", "utf8");
340
340
  var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
341
- var syncProc = spawn7(process.argv[0], ["-e", execString]);
341
+ var syncProc = spawn8(process.argv[0], ["-e", execString]);
342
342
  var statusText;
343
343
  while (fs.existsSync(syncFile)) {
344
344
  }
@@ -2746,7 +2746,7 @@ var HARNESSES = {
2746
2746
  opencode: "OpenCode",
2747
2747
  other: "Agent"
2748
2748
  };
2749
- var isStartHarness = (harness) => harness !== "other" && harness !== "kimi";
2749
+ var isStartHarness = (harness) => harness !== "other";
2750
2750
 
2751
2751
  // ../../node_modules/.pnpm/@noble+ciphers@2.4.0/node_modules/@noble/ciphers/utils.js
2752
2752
  function isBytes(a) {
@@ -3648,12 +3648,12 @@ function loadConfig() {
3648
3648
  localSecret: ""
3649
3649
  };
3650
3650
  }
3651
- function saveConfig(config2) {
3651
+ function saveConfig(config3) {
3652
3652
  const path = configPath();
3653
3653
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
3654
3654
  const file = openSync(path + ".tmp", "w", 384);
3655
3655
  try {
3656
- writeFileSync(file, JSON.stringify(config2, null, 2));
3656
+ writeFileSync(file, JSON.stringify(config3, null, 2));
3657
3657
  fsyncSync(file);
3658
3658
  } finally {
3659
3659
  closeSync(file);
@@ -3668,13 +3668,13 @@ function saveConfig(config2) {
3668
3668
  }
3669
3669
  }
3670
3670
  }
3671
- async function api(config2, path, body2, method = body2 ? "POST" : "GET") {
3671
+ async function api(config3, path, body2, method = body2 ? "POST" : "GET") {
3672
3672
  const bytes = body2 instanceof Uint8Array;
3673
- const response = await fetch(`${config2.api}/v1${path}`, {
3673
+ const response = await fetch(`${config3.api}/v1${path}`, {
3674
3674
  method,
3675
3675
  headers: {
3676
3676
  "Content-Type": bytes ? "application/octet-stream" : "application/json",
3677
- Authorization: `Bearer ${config2.secret}`
3677
+ Authorization: `Bearer ${config3.secret}`
3678
3678
  },
3679
3679
  body: bytes ? new Uint8Array(body2) : body2 ? JSON.stringify(body2) : void 0
3680
3680
  });
@@ -3683,8 +3683,8 @@ async function api(config2, path, body2, method = body2 ? "POST" : "GET") {
3683
3683
  throw new Error(value2.error ?? `Dashup request failed (${response.status})`);
3684
3684
  return value2.result;
3685
3685
  }
3686
- function activePair(config2, pair) {
3687
- return Boolean(!pair.revokedAt && config2.pairs[pair.id] && pair.device === config2.device?.id);
3686
+ function activePair(config3, pair) {
3687
+ return Boolean(!pair.revokedAt && config3.pairs[pair.id] && pair.device === config3.device?.id);
3688
3688
  }
3689
3689
 
3690
3690
  // src/acp.ts
@@ -3868,15 +3868,15 @@ function attachAcp(socket, onEvent) {
3868
3868
  }
3869
3869
  });
3870
3870
  }
3871
- async function proxyAcp(config2, executable, args2) {
3872
- if (!config2.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
3871
+ async function proxyAcp(config3, executable, args2) {
3872
+ if (!config3.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
3873
3873
  const socket = await new Promise((resolve3, reject) => {
3874
3874
  const connect3 = request({
3875
3875
  hostname: "127.0.0.1",
3876
3876
  port: connectorPort(),
3877
3877
  method: "CONNECT",
3878
3878
  path: "/acp",
3879
- headers: { Authorization: `Bearer ${config2.localSecret}` }
3879
+ headers: { Authorization: `Bearer ${config3.localSecret}` }
3880
3880
  });
3881
3881
  connect3.on("connect", (response, stream) => response.statusCode === 200 ? resolve3(stream) : reject(new Error("Dashup ACP connection was rejected")));
3882
3882
  connect3.on("error", reject);
@@ -4048,7 +4048,7 @@ import * as ui from "@clack/prompts";
4048
4048
  import { z } from "zod";
4049
4049
 
4050
4050
  // package.json
4051
- var version = "0.1.18";
4051
+ var version = "0.1.19";
4052
4052
 
4053
4053
  // src/pair.ts
4054
4054
  var Invitation = z.object({
@@ -4058,11 +4058,11 @@ var Invitation = z.object({
4058
4058
  }).strict();
4059
4059
  var interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
4060
4060
  var intro2 = () => ui.intro(`Dashup ${version}`);
4061
- function invitation(config2, secret = config2.secret) {
4061
+ function invitation(config3, secret = config3.secret) {
4062
4062
  const code = randomBytes2(8).toString("hex");
4063
4063
  const key = newSecret(randomBytes2);
4064
4064
  const computer = createHash2("sha256").update(secret).digest("hex");
4065
- return { code, key, text: formatPairingCode({ api: config2.api, computer, code, key }) };
4065
+ return { code, key, text: formatPairingCode({ api: config3.api, computer, code, key }) };
4066
4066
  }
4067
4067
  var SCAN = "Scan this in the Dashup app under Pair a computer. It expires in 5 minutes.";
4068
4068
  var BROWSER = {
@@ -4099,7 +4099,7 @@ async function show(text3) {
4099
4099
  `);
4100
4100
  return () => server.close();
4101
4101
  }
4102
- async function scanned(config2, { code, text: text3 }, done, secret = config2.secret) {
4102
+ async function scanned(config3, { code, text: text3 }, done, secret = config3.secret) {
4103
4103
  if (interactive) {
4104
4104
  const qr = await QRCode.toString(text3, {
4105
4105
  type: "terminal",
@@ -4118,7 +4118,7 @@ async function scanned(config2, { code, text: text3 }, done, secret = config2.se
4118
4118
  try {
4119
4119
  for (const deadline = Date.now() + PAIRING_TTL_MS; Date.now() < deadline; ) {
4120
4120
  await new Promise((resolve3) => setTimeout(resolve3, 1e3));
4121
- const response = await fetch(`${config2.api}/v1/dashup/pairings?code=${code}`, {
4121
+ const response = await fetch(`${config3.api}/v1/dashup/pairings?code=${code}`, {
4122
4122
  headers: { Authorization: `Bearer ${secret}` }
4123
4123
  });
4124
4124
  if (response.status === 401 || response.status === 404) continue;
@@ -4140,16 +4140,16 @@ async function scanned(config2, { code, text: text3 }, done, secret = config2.se
4140
4140
  }
4141
4141
  }
4142
4142
  var reported = (error) => error?.reported === true;
4143
- async function link(config2, account) {
4143
+ async function link(config3, account) {
4144
4144
  const secret = newSecret(randomBytes2);
4145
- const invite = invitation(config2, secret);
4145
+ const invite = invitation(config3, secret);
4146
4146
  const { pairing, email } = await scanned(
4147
- config2,
4147
+ config3,
4148
4148
  invite,
4149
4149
  (account2) => `Scanned by ${account2}`,
4150
4150
  secret
4151
4151
  );
4152
- const linked = { ...config2, secret };
4152
+ const linked = { ...config3, secret };
4153
4153
  const platform = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux";
4154
4154
  const { device } = await api(linked, "/dashup/devices", {
4155
4155
  id: pairing.device,
@@ -4166,7 +4166,7 @@ async function link(config2, account) {
4166
4166
  );
4167
4167
  return false;
4168
4168
  }
4169
- Object.assign(config2, {
4169
+ Object.assign(config3, {
4170
4170
  secret,
4171
4171
  device,
4172
4172
  pairs: { [pairing.id]: { key: invite.key, sequence: 0, received: 0 } },
@@ -4174,7 +4174,7 @@ async function link(config2, account) {
4174
4174
  queued: {},
4175
4175
  unconfirmed: {}
4176
4176
  });
4177
- saveConfig(config2);
4177
+ saveConfig(config3);
4178
4178
  return true;
4179
4179
  }
4180
4180
 
@@ -4188,6 +4188,7 @@ import { watch, existsSync as existsSync3, readdirSync as readdirSync2, readFile
4188
4188
 
4189
4189
  // src/harness/history.ts
4190
4190
  import { homedir as homedir2 } from "node:os";
4191
+ import { createHash as createHash3 } from "node:crypto";
4191
4192
  import { DatabaseSync } from "node:sqlite";
4192
4193
  import { join as join2, dirname as dirname2, resolve as resolve2, isAbsolute } from "node:path";
4193
4194
  import { closeSync as closeSync2, existsSync as existsSync2, openSync as openSync2, readSync, realpathSync, readdirSync, statSync } from "node:fs";
@@ -4435,7 +4436,7 @@ function attachments(id, content) {
4435
4436
  const source = object(part?.source);
4436
4437
  if (source?.type === "base64" && typeof source.data === "string")
4437
4438
  return [attached(key, String(source.media_type), source.data, name)];
4438
- const url2 = String(part?.image_url ?? part?.url ?? "");
4439
+ const url2 = String(object(part?.image_url)?.url ?? part?.image_url ?? part?.url ?? "");
4439
4440
  const data = DATA_URL.exec(url2);
4440
4441
  return data ? [attached(key, data[1], url2.slice(data[0].length), name)] : [];
4441
4442
  });
@@ -4782,6 +4783,89 @@ function codex(root, scan) {
4782
4783
  }
4783
4784
  }
4784
4785
  }
4786
+ var kimiHome = () => process.env.DASHUP_KIMI_HOME ?? join2(homedir2(), ".kimi");
4787
+ var KIMI_FAILED = /^<system>ERROR/;
4788
+ function kimiMessages(directory2) {
4789
+ const path = join2(directory2, "context.jsonl");
4790
+ const rows = journal(path, true);
4791
+ const results = /* @__PURE__ */ new Map();
4792
+ for (const row of rows) if (row.role === "tool") results.set(row.tool_call_id, row);
4793
+ const messages5 = rows.flatMap((row, index) => {
4794
+ const id = String(index);
4795
+ if (row.role === "user") return injected(row.content) ? [] : prose(id, "user", row.content);
4796
+ if (row.role !== "assistant") return [];
4797
+ return [
4798
+ ...prose(id, "assistant", row.content),
4799
+ ...list(row.tool_calls).map((call, position) => {
4800
+ const result = results.get(call?.id);
4801
+ const output = result && text(result.content);
4802
+ return step(
4803
+ String(call?.id ?? `${id}:${position}`),
4804
+ call?.function?.name,
4805
+ parse(String(call?.function?.arguments ?? "")) ?? call?.function?.arguments,
4806
+ result && {
4807
+ text: output,
4808
+ status: KIMI_FAILED.test(output) ? "error" : "ok",
4809
+ attachments: attachments(String(call?.id), result.content)
4810
+ }
4811
+ );
4812
+ })
4813
+ ];
4814
+ });
4815
+ const shown = windowed(path, messages5);
4816
+ return existsSync2(join2(directory2, "context_1.jsonl")) && shown[0] !== EARLIER ? [EARLIER, ...shown] : shown;
4817
+ }
4818
+ function kimiSettings(directory2) {
4819
+ const state = existsSync2(join2(directory2, "state.json")) ? jsonFile(join2(directory2, "state.json")) : void 0;
4820
+ return typeof state?.plan_mode === "boolean" ? { plan: state.plan_mode } : {};
4821
+ }
4822
+ function kimi(root, scan) {
4823
+ const listed = existsSync2(join2(root, "kimi.json")) ? jsonFile(join2(root, "kimi.json")) : void 0;
4824
+ if (listed) scan.paths.add(root);
4825
+ for (const entry of list(listed?.work_dirs)) {
4826
+ const cwd = canonical(entry?.path);
4827
+ if (!cwd) continue;
4828
+ const folder = join2(root, "sessions", createHash3("md5").update(entry.path).digest("hex"));
4829
+ if (!existsSync2(folder)) continue;
4830
+ scan.paths.add(folder);
4831
+ for (const id of readdirSync(folder)) {
4832
+ const directory2 = join2(folder, id);
4833
+ const context = join2(directory2, "context.jsonl");
4834
+ const wire = join2(directory2, "wire.jsonl");
4835
+ if (!existsSync2(context)) continue;
4836
+ scan.paths.add(directory2);
4837
+ try {
4838
+ const state = existsSync2(join2(directory2, "state.json")) ? jsonFile(join2(directory2, "state.json")) : void 0;
4839
+ if (state?.archived === true) continue;
4840
+ const asked = described(
4841
+ context,
4842
+ (row) => row.role === "user",
4843
+ (rows) => text(rows.find((row) => row.role === "user" && !injected(row.content))?.content)
4844
+ );
4845
+ const updated = existsSync2(wire) ? described(
4846
+ wire,
4847
+ (row) => typeof row.timestamp === "number",
4848
+ (rows) => [...rows].reverse().find((row) => typeof row.timestamp === "number")?.timestamp
4849
+ ) : void 0;
4850
+ scan.sessions.push({
4851
+ id,
4852
+ cwd,
4853
+ harness: "kimi",
4854
+ title: title(state?.custom_title || asked, "Kimi thread"),
4855
+ updatedAt: date(
4856
+ typeof updated === "number" ? updated * 1e3 : void 0,
4857
+ statSync(context).mtimeMs
4858
+ ),
4859
+ files: [context, ...existsSync2(wire) ? [wire] : []],
4860
+ read: () => kimiMessages(directory2),
4861
+ settings: () => kimiSettings(directory2)
4862
+ });
4863
+ } catch {
4864
+ scan.warnings.push(`Cannot read Kimi session: ${directory2}`);
4865
+ }
4866
+ }
4867
+ }
4868
+ }
4785
4869
  function openCodeChange(state) {
4786
4870
  const metadata2 = object(state.metadata);
4787
4871
  const input = object(state.input);
@@ -4847,8 +4931,8 @@ function databaseMessages(path, session) {
4847
4931
  }
4848
4932
  }
4849
4933
  function codexSubagent(row) {
4850
- const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4851
- const task = typeof spawn7?.agent_path === "string" ? spawn7.agent_path.split("/").pop() : "";
4934
+ const spawn8 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4935
+ const task = typeof spawn8?.agent_path === "string" ? spawn8.agent_path.split("/").pop() : "";
4852
4936
  return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
4853
4937
  }
4854
4938
  var codexState;
@@ -4975,6 +5059,7 @@ function scanHistories() {
4975
5059
  const scan = { sessions: [], paths: /* @__PURE__ */ new Set(), warnings: [] };
4976
5060
  claude(claudeHome(), scan);
4977
5061
  codex(process.env.CODEX_HOME ?? join2(home, ".codex"), scan);
5062
+ kimi(kimiHome(), scan);
4978
5063
  const opencode = join2(process.env.XDG_DATA_HOME ?? join2(home, ".local/share"), "opencode");
4979
5064
  openCodeDatabase(process.env.OPENCODE_DB ?? join2(opencode, "opencode.db"), scan);
4980
5065
  openCodeLegacy(join2(opencode, "storage"), scan);
@@ -5019,6 +5104,7 @@ function vscodeHost(authority) {
5019
5104
  return host;
5020
5105
  }
5021
5106
  }
5107
+ var zedData = () => process.env.DASHUP_ZED_DATA ?? dataPaths().zed;
5022
5108
  function dataPaths() {
5023
5109
  const home = homedir3();
5024
5110
  if (process.platform === "darwin")
@@ -5112,7 +5198,7 @@ var Discovery = class {
5112
5198
  this.paths.clear();
5113
5199
  this.warnings.length = 0;
5114
5200
  const defaults = dataPaths();
5115
- this.zed(process.env.DASHUP_ZED_DATA ?? defaults.zed);
5201
+ this.zed(zedData());
5116
5202
  this.vscode(process.env.DASHUP_VSCODE_DATA ?? defaults.code);
5117
5203
  const history = scanHistories();
5118
5204
  for (const path of history.paths) this.paths.add(path);
@@ -5391,7 +5477,7 @@ var Discovery = class {
5391
5477
 
5392
5478
  // src/connector.ts
5393
5479
  import { z as z2 } from "zod";
5394
- import { join as join9 } from "node:path";
5480
+ import { join as join10 } from "node:path";
5395
5481
  import { mkdir as mkdir2 } from "node:fs/promises";
5396
5482
  import { randomBytes as randomBytes4 } from "node:crypto";
5397
5483
 
@@ -15427,16 +15513,16 @@ var SocketTransport = class extends import_index2.default {
15427
15513
  helloReady = Promise.resolve();
15428
15514
  /** The token the server validated at the last resolved hello. */
15429
15515
  confirmedHelloToken = null;
15430
- constructor(config2) {
15516
+ constructor(config3) {
15431
15517
  super();
15432
- this.url = config2.url;
15433
- this.version = config2.version ?? "v1";
15434
- this.getToken = config2.getToken;
15435
- this.handshakeTimeout = config2.handshakeTimeout ?? DEFAULT_TIMEOUT;
15436
- const socketPath = config2.path ?? "/ws";
15437
- const transports2 = config2.transports ?? ["websocket"];
15438
- const extraHeaders = config2.extraHeaders;
15439
- const autoConnect = config2.autoConnect ?? true;
15518
+ this.url = config3.url;
15519
+ this.version = config3.version ?? "v1";
15520
+ this.getToken = config3.getToken;
15521
+ this.handshakeTimeout = config3.handshakeTimeout ?? DEFAULT_TIMEOUT;
15522
+ const socketPath = config3.path ?? "/ws";
15523
+ const transports2 = config3.transports ?? ["websocket"];
15524
+ const extraHeaders = config3.extraHeaders;
15525
+ const autoConnect = config3.autoConnect ?? true;
15440
15526
  this.socket = lookup(this.url, {
15441
15527
  path: socketPath,
15442
15528
  transports: transports2,
@@ -15870,14 +15956,14 @@ var SocketTransport = class extends import_index2.default {
15870
15956
  this.connection.disconnected();
15871
15957
  }
15872
15958
  };
15873
- function createClient(config2) {
15959
+ function createClient(config3) {
15874
15960
  const transport = new SocketTransport({
15875
- url: config2.url,
15876
- version: config2.version ?? "v1",
15877
- getToken: config2.getToken,
15878
- transports: config2.transports,
15879
- extraHeaders: config2.extraHeaders,
15880
- autoConnect: config2.autoConnect
15961
+ url: config3.url,
15962
+ version: config3.version ?? "v1",
15963
+ getToken: config3.getToken,
15964
+ transports: config3.transports,
15965
+ extraHeaders: config3.extraHeaders,
15966
+ autoConnect: config3.autoConnect
15881
15967
  });
15882
15968
  const adapter = new FrontendAdapter(transport);
15883
15969
  const client = {
@@ -16392,20 +16478,20 @@ function changes(copy2, messages5) {
16392
16478
  return true;
16393
16479
  });
16394
16480
  }
16395
- async function upload(config2, batch) {
16481
+ async function upload(config3, batch) {
16396
16482
  if (batch.records.length > HISTORY_BATCH_ITEMS || Buffer.byteLength(JSON.stringify(batch)) > HISTORY_BATCH_BYTES)
16397
16483
  throw new Error("The encrypted history upload exceeds the batch limit");
16398
16484
  try {
16399
- return await api(config2, HISTORY_PATH, batch);
16485
+ return await api(config3, HISTORY_PATH, batch);
16400
16486
  } catch {
16401
- return api(config2, HISTORY_PATH, batch);
16487
+ return api(config3, HISTORY_PATH, batch);
16402
16488
  }
16403
16489
  }
16404
- async function publishHistory(config2, pair, project, thread, messages5) {
16405
- if (!activePair(config2, pair)) throw new Error("Pairing is no longer available");
16406
- const local = config2.pairs[pair.id];
16490
+ async function publishHistory(config3, pair, project, thread, messages5) {
16491
+ if (!activePair(config3, pair)) throw new Error("Pairing is no longer available");
16492
+ const local = config3.pairs[pair.id];
16407
16493
  const generation = ++local.sequence;
16408
- saveConfig(config2);
16494
+ saveConfig(config3);
16409
16495
  const all = historyMessages(local.key, project, thread.id, messages5);
16410
16496
  let start2 = Math.max(0, all.length - HISTORY_KEPT);
16411
16497
  while (start2 < all.length && (all[start2].part ?? 0) > 0) start2++;
@@ -16439,15 +16525,15 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16439
16525
  );
16440
16526
  const size = Buffer.byteLength(JSON.stringify(record));
16441
16527
  if (batch.records.length && (batch.records.length === HISTORY_BATCH_ITEMS || bytes + size + 1 > HISTORY_BATCH_BYTES)) {
16442
- await upload(config2, batch);
16528
+ await upload(config3, batch);
16443
16529
  batch.records = [];
16444
16530
  bytes = overhead;
16445
16531
  }
16446
16532
  bytes += size + (batch.records.length ? 1 : 0);
16447
16533
  batch.records.push(record);
16448
16534
  }
16449
- if (batch.records.length) await upload(config2, batch);
16450
- const published = await upload(config2, { ...batch, records: [], complete: true });
16535
+ if (batch.records.length) await upload(config3, batch);
16536
+ const published = await upload(config3, { ...batch, records: [], complete: true });
16451
16537
  if (published.generation !== generation || published.total !== summary.total)
16452
16538
  throw new Error("The encrypted history snapshot was not published");
16453
16539
  return summary;
@@ -16484,9 +16570,9 @@ var named = (key, project, thread, attachment) => ({
16484
16570
  ...attachment.name ? { name: attachment.name } : {}
16485
16571
  });
16486
16572
  var readable = (attachment) => "read" in attachment;
16487
- async function shareAttachment(config2, pair, project, thread, messages5, id) {
16488
- if (!activePair(config2, pair)) throw new Error("Pairing is no longer available");
16489
- const key = config2.pairs[pair.id].key;
16573
+ async function shareAttachment(config3, pair, project, thread, messages5, id) {
16574
+ if (!activePair(config3, pair)) throw new Error("Pairing is no longer available");
16575
+ const key = config3.pairs[pair.id].key;
16490
16576
  const found = messages5.flatMap((message) => {
16491
16577
  const { full, attachments: attachments2 = [] } = kept(message);
16492
16578
  return full ? [full, ...attachments2] : attachments2;
@@ -16495,12 +16581,12 @@ async function shareAttachment(config2, pair, project, thread, messages5, id) {
16495
16581
  if (found.bytes > MAX_ATTACHMENT_BYTES)
16496
16582
  throw new Error("This attachment is too large to open on your phone");
16497
16583
  const sealed = sealAttachment(key, pair.id, id, found.read(), randomBytes3);
16498
- await api(config2, `${ATTACHMENTS_PATH}/${pair.id}/${id}`, sealed);
16584
+ await api(config3, `${ATTACHMENTS_PATH}/${pair.id}/${id}`, sealed);
16499
16585
  }
16500
16586
 
16501
16587
  // src/harness/live.ts
16502
- import { promisify as promisify5 } from "node:util";
16503
- import { execFile as execFile5 } from "node:child_process";
16588
+ import { promisify as promisify6 } from "node:util";
16589
+ import { execFile as execFile6 } from "node:child_process";
16504
16590
 
16505
16591
  // src/harness/headless.ts
16506
16592
  import { randomUUID as randomUUID3 } from "node:crypto";
@@ -17595,16 +17681,362 @@ var Codex = class {
17595
17681
  }
17596
17682
  };
17597
17683
 
17684
+ // src/harness/kimi.ts
17685
+ import { join as join6 } from "node:path";
17686
+ import { promisify as promisify3 } from "node:util";
17687
+ import { randomUUID as randomUUID5 } from "node:crypto";
17688
+ import { createInterface as createInterface4 } from "node:readline";
17689
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
17690
+ import { execFile as execFile3, spawn as spawn4 } from "node:child_process";
17691
+ var run = promisify3(execFile3);
17692
+ var MAX_REQUESTS3 = 1e3;
17693
+ var NOTIFY_MS2 = 100;
17694
+ var CHOICES = [
17695
+ { id: "approve", label: "Allow once" },
17696
+ { id: "approve_for_session", label: "Allow for this session" },
17697
+ { id: "reject", label: "Reject", text: true }
17698
+ ];
17699
+ async function kimiCommand() {
17700
+ const shell = process.platform === "win32";
17701
+ if (await run("kimi", ["--version"], { timeout: 1e4, shell }).then(
17702
+ () => true,
17703
+ () => false
17704
+ ))
17705
+ return "kimi";
17706
+ const registry2 = join6(zedData(), "external_agents/registry/kimi");
17707
+ return (existsSync4(registry2) ? readdirSync3(registry2) : []).map((version2) => join6(registry2, version2, shell ? "kimi.exe" : "kimi")).filter((path) => existsSync4(path)).sort((left, right) => statSync3(right).mtimeMs - statSync3(left).mtimeMs)[0];
17708
+ }
17709
+ function config() {
17710
+ let text3 = "";
17711
+ try {
17712
+ text3 = readFileSync5(join6(kimiHome(), "config.toml"), "utf8");
17713
+ } catch {
17714
+ }
17715
+ const found = { models: [] };
17716
+ let section = "";
17717
+ let model;
17718
+ for (const line of text3.split("\n")) {
17719
+ const header = /^\s*\[(.+)\]\s*$/.exec(line)?.[1];
17720
+ if (header !== void 0) {
17721
+ section = header;
17722
+ const named2 = /^models\."(.+)"$/.exec(header)?.[1];
17723
+ model = named2 ? { value: named2, name: named2, thinking: false } : void 0;
17724
+ if (model) found.models.push(model);
17725
+ continue;
17726
+ }
17727
+ const [, key, value2 = ""] = /^\s*(\w+)\s*=\s*(.+?)\s*$/.exec(line) ?? [];
17728
+ if (model && key === "display_name") model.name = JSON.parse(value2);
17729
+ else if (model && key === "capabilities") model.thinking = value2.includes('"thinking"');
17730
+ else if (!section && key === "default_model") found.model = JSON.parse(value2);
17731
+ else if (!section && key === "default_thinking") found.thinking = value2 === "true";
17732
+ }
17733
+ return found;
17734
+ }
17735
+ function kimiOptions(recorded) {
17736
+ const { models: models2 } = config();
17737
+ const model = typeof recorded.model === "string" ? recorded.model : void 0;
17738
+ const current = models2.find((item) => item.value === model);
17739
+ const choices = models2.map(({ value: value2, name }) => ({ value: value2, name }));
17740
+ if (model && !current) choices.push({ value: model, name: model });
17741
+ return [
17742
+ ...model ? [{ id: "model", name: "Model", value: model, choices }] : [],
17743
+ ...model && current?.thinking !== false && typeof recorded.thinking === "boolean" ? [{ id: "thinking", name: "Thinking", value: recorded.thinking }] : [],
17744
+ ...typeof recorded.plan === "boolean" ? [{ id: "plan", name: "Plan", value: recorded.plan }] : []
17745
+ ];
17746
+ }
17747
+ var Session2 = class {
17748
+ constructor(id, cwd, title2, changed) {
17749
+ this.id = id;
17750
+ this.cwd = cwd;
17751
+ this.title = title2;
17752
+ this.changed = changed;
17753
+ }
17754
+ id;
17755
+ cwd;
17756
+ title;
17757
+ changed;
17758
+ messages = [];
17759
+ status = "idle";
17760
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17761
+ turn;
17762
+ sequence = 0;
17763
+ // The reply Kimi is writing, as far as it has streamed.
17764
+ typing = "";
17765
+ begun;
17766
+ calls = /* @__PURE__ */ new Map();
17767
+ approvals = /* @__PURE__ */ new Map();
17768
+ requests = /* @__PURE__ */ new Map();
17769
+ chosen = {};
17770
+ write(value2) {
17771
+ this.turn?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", ...value2 })}
17772
+ `);
17773
+ }
17774
+ call(method, params) {
17775
+ const id = `dashup-${++this.sequence}`;
17776
+ return new Promise((resolve3, reject) => {
17777
+ this.calls.set(id, { resolve: resolve3, reject });
17778
+ this.write({ id, method, ...params ? { params } : {} });
17779
+ });
17780
+ }
17781
+ once(id, action) {
17782
+ const known = this.requests.get(id);
17783
+ if (known) return known;
17784
+ if (this.requests.size >= MAX_REQUESTS3)
17785
+ throw new Error("Kimi command history is full; restart Dashup before sending");
17786
+ const result = action();
17787
+ this.requests.set(id, result);
17788
+ return result;
17789
+ }
17790
+ // What the session starts with: the person's choice, else Kimi's defaults.
17791
+ launch() {
17792
+ const defaults = config();
17793
+ const model = this.chosen.model ?? defaults.model;
17794
+ const thinking = this.chosen.thinking ?? defaults.thinking;
17795
+ return {
17796
+ ...model === void 0 ? {} : { model },
17797
+ ...thinking === void 0 ? {} : { thinking }
17798
+ };
17799
+ }
17800
+ // Resolves once Kimi begins the turn, or says why it could not. A message while a turn runs is
17801
+ // steered into it.
17802
+ async say(text3) {
17803
+ this.messages.push({ id: randomUUID5(), role: "user", text: text3 });
17804
+ this.typing = "";
17805
+ this.status = "working";
17806
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17807
+ if (this.turn) {
17808
+ await this.call("steer", { user_input: text3 });
17809
+ this.changed();
17810
+ return;
17811
+ }
17812
+ const command2 = await kimiCommand();
17813
+ if (!command2) throw new Error("Kimi is not installed on this computer");
17814
+ const { model, thinking } = this.launch();
17815
+ const turn = this.turn = spawn4(
17816
+ command2,
17817
+ [
17818
+ "--wire",
17819
+ "--work-dir",
17820
+ this.cwd,
17821
+ "--session",
17822
+ this.id,
17823
+ ...typeof model === "string" ? ["--model", model] : [],
17824
+ ...typeof thinking === "boolean" ? [thinking ? "--thinking" : "--no-thinking"] : []
17825
+ ],
17826
+ { cwd: this.cwd, stdio: "pipe", shell: process.platform === "win32" }
17827
+ );
17828
+ let errors = "";
17829
+ turn.stdin.on("error", () => {
17830
+ });
17831
+ turn.stderr.on("data", (chunk) => {
17832
+ errors = (errors + String(chunk)).slice(-2e3);
17833
+ });
17834
+ createInterface4({ input: turn.stdout }).on("line", (line) => this.receive(line));
17835
+ const begun = new Promise((resolve3, reject) => {
17836
+ this.begun = { resolve: resolve3, reject };
17837
+ const end = (reason) => {
17838
+ reject(new Error(reason));
17839
+ if (this.turn !== turn) return;
17840
+ this.turn = void 0;
17841
+ for (const pending of this.calls.values()) pending.reject(new Error(reason));
17842
+ this.calls.clear();
17843
+ this.approvals.clear();
17844
+ this.status = "idle";
17845
+ this.changed();
17846
+ };
17847
+ turn.once("error", () => end("Kimi is not installed on this computer"));
17848
+ turn.once("exit", () => end(errors.trim() || "Kimi stopped before starting"));
17849
+ });
17850
+ await this.call("initialize", {
17851
+ protocol_version: "1.10",
17852
+ client: { name: "dashup", version },
17853
+ capabilities: { supports_question: true, supports_plan_mode: true }
17854
+ });
17855
+ if (typeof this.chosen.plan === "boolean")
17856
+ await this.call("set_plan_mode", { enabled: this.chosen.plan });
17857
+ void this.call("prompt", { user_input: text3 }).catch((error) => {
17858
+ this.begun?.reject(error);
17859
+ this.messages.push({ id: randomUUID5(), role: "system", text: error.message });
17860
+ }).finally(() => turn.stdin.end());
17861
+ this.changed();
17862
+ return begun;
17863
+ }
17864
+ // Handles one line of Kimi's output: the answer to a call, an event, or a request to answer.
17865
+ receive(line) {
17866
+ let message;
17867
+ try {
17868
+ message = JSON.parse(line);
17869
+ } catch {
17870
+ return;
17871
+ }
17872
+ if (message.id !== void 0 && !message.method) {
17873
+ const pending = this.calls.get(String(message.id));
17874
+ this.calls.delete(String(message.id));
17875
+ if (message.error) pending?.reject(new Error(String(message.error.message ?? "Kimi failed")));
17876
+ else pending?.resolve(message.result ?? {});
17877
+ return;
17878
+ }
17879
+ const type = message.params?.type;
17880
+ const payload = message.params?.payload ?? {};
17881
+ if (message.method === "event") {
17882
+ if (type === "TurnBegin") this.begun?.resolve();
17883
+ else if (type === "ContentPart" && payload.type === "text") {
17884
+ this.typing += String(payload.text ?? "");
17885
+ } else if (type === "TurnEnd") {
17886
+ if (this.typing)
17887
+ this.messages.push({ id: randomUUID5(), role: "assistant", text: this.typing });
17888
+ this.typing = "";
17889
+ this.status = this.approvals.size ? "attention" : "idle";
17890
+ } else return;
17891
+ } else if (message.method === "request" && type === "ApprovalRequest") {
17892
+ const id = randomUUID5();
17893
+ let detail = [payload.description, payload.sender].filter(Boolean).join("\n\n");
17894
+ if (Buffer.byteLength(detail) > MAX_PREVIEW)
17895
+ detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
17896
+ this.approvals.set(id, {
17897
+ rpc: message.id,
17898
+ request: payload.id,
17899
+ approval: {
17900
+ id,
17901
+ title: `Allow ${String(payload.action ?? "this")}?`,
17902
+ detail,
17903
+ choices: CHOICES.map((choice) => ({ ...choice }))
17904
+ }
17905
+ });
17906
+ this.status = "attention";
17907
+ } else if (message.method === "request" && type === "QuestionRequest") {
17908
+ const id = randomUUID5();
17909
+ const questions = Array.isArray(payload.questions) ? payload.questions : [];
17910
+ this.approvals.set(id, {
17911
+ rpc: message.id,
17912
+ request: payload.id,
17913
+ questions,
17914
+ approval: questionApproval(id, questions, (item) => item.multi_select === true)
17915
+ });
17916
+ this.status = "attention";
17917
+ } else return;
17918
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17919
+ this.changed();
17920
+ }
17921
+ live() {
17922
+ return {
17923
+ id: this.id,
17924
+ cwd: this.cwd,
17925
+ title: this.title,
17926
+ harness: "kimi",
17927
+ updatedAt: this.updatedAt,
17928
+ status: this.status,
17929
+ messages: [...this.messages],
17930
+ typing: this.typing,
17931
+ approvals: [...this.approvals.values()].map((pending) => pending.approval),
17932
+ send: (requestId, text3) => this.once(requestId, () => this.say(text3)),
17933
+ now: true,
17934
+ stop: this.turn ? (requestId) => this.once(requestId, async () => {
17935
+ await this.call("cancel");
17936
+ }) : void 0,
17937
+ approve: this.approvals.size ? (requestId, approvalId, reply) => this.once(requestId, async () => {
17938
+ const pending = this.approvals.get(approvalId);
17939
+ const choice = "choice" in reply ? reply.choice : void 0;
17940
+ if (!pending || !this.turn) throw new Error("Kimi is no longer asking");
17941
+ if (pending.questions)
17942
+ this.write({
17943
+ id: pending.rpc,
17944
+ result: {
17945
+ request_id: pending.request,
17946
+ answers: Object.fromEntries(
17947
+ "answers" in reply ? pending.questions.map((item, index) => [
17948
+ item.question,
17949
+ (reply.answers[index] ?? []).join(", ")
17950
+ ]) : []
17951
+ )
17952
+ }
17953
+ });
17954
+ else if (CHOICES.some((value2) => value2.id === choice))
17955
+ this.write({
17956
+ id: pending.rpc,
17957
+ result: {
17958
+ request_id: pending.request,
17959
+ response: choice,
17960
+ ..."text" in reply && reply.text ? { feedback: reply.text } : {}
17961
+ }
17962
+ });
17963
+ else throw new Error("Kimi is no longer asking");
17964
+ this.approvals.delete(approvalId);
17965
+ if (!this.approvals.size) this.status = "working";
17966
+ this.changed();
17967
+ }) : void 0,
17968
+ options: (recorded) => kimiOptions({ ...recorded, ...this.launch() }),
17969
+ // Plan mode applies to the turn under way; the model and thinking apply as the next starts.
17970
+ configure: (requestId, option2, value2) => this.once(requestId, async () => {
17971
+ this.chosen[option2] = value2;
17972
+ if (option2 === "plan" && this.turn) await this.call("set_plan_mode", { enabled: value2 });
17973
+ this.changed();
17974
+ })
17975
+ };
17976
+ }
17977
+ end() {
17978
+ this.turn?.kill();
17979
+ }
17980
+ };
17981
+ var Kimi = class {
17982
+ constructor(changed) {
17983
+ this.changed = changed;
17984
+ }
17985
+ changed;
17986
+ sessions = /* @__PURE__ */ new Map();
17987
+ notify;
17988
+ soon = () => {
17989
+ if (this.notify) return;
17990
+ this.notify = setTimeout(() => {
17991
+ this.notify = void 0;
17992
+ this.changed();
17993
+ }, NOTIFY_MS2);
17994
+ this.notify.unref();
17995
+ };
17996
+ // Starts a session in `cwd` with its first message, and resolves with the session's id.
17997
+ async start(cwd, text3) {
17998
+ const title2 = text3.trim().split("\n")[0].slice(0, 80);
17999
+ const session = new Session2(randomUUID5(), cwd, title2, this.soon);
18000
+ this.sessions.set(session.id, session);
18001
+ try {
18002
+ await session.say(text3);
18003
+ } catch (error) {
18004
+ this.sessions.delete(session.id);
18005
+ throw error;
18006
+ }
18007
+ return session.id;
18008
+ }
18009
+ // Continues a saved session with a message; Kimi resumes it from its own session folder.
18010
+ async resume(id, cwd, text3) {
18011
+ const known = this.sessions.get(id);
18012
+ const session = known ?? new Session2(id, cwd, void 0, this.soon);
18013
+ this.sessions.set(id, session);
18014
+ try {
18015
+ await session.say(text3);
18016
+ } catch (error) {
18017
+ if (!known) this.sessions.delete(id);
18018
+ throw error;
18019
+ }
18020
+ }
18021
+ async refresh() {
18022
+ return [...this.sessions.values()].map((session) => session.live());
18023
+ }
18024
+ stop() {
18025
+ clearTimeout(this.notify);
18026
+ for (const session of this.sessions.values()) session.end();
18027
+ }
18028
+ };
18029
+
17598
18030
  // src/harness/vscode.ts
17599
18031
  import Socket3 from "ws";
17600
18032
  import { homedir as homedir6 } from "node:os";
17601
18033
  import { connect as connect2 } from "node:net";
17602
- import { promisify as promisify3 } from "node:util";
18034
+ import { promisify as promisify4 } from "node:util";
17603
18035
  import { fileURLToPath } from "node:url";
17604
- import { execFile as execFile3 } from "node:child_process";
17605
- import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
18036
+ import { execFile as execFile4 } from "node:child_process";
18037
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
17606
18038
  import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
17607
- import { join as join6, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
18039
+ import { join as join7, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
17608
18040
  import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
17609
18041
  import { AhpClient } from "@microsoft/agent-host-protocol/client";
17610
18042
  import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
@@ -17614,7 +18046,7 @@ var MAX_METADATA = 64 * 1024;
17614
18046
  var MAX_HOSTS = 16;
17615
18047
  var MAX_SESSIONS3 = 100;
17616
18048
  var ROOT_CHANNEL = "ahp-root://";
17617
- var run = promisify3(execFile3);
18049
+ var run2 = promisify4(execFile4);
17618
18050
  var CONFIRMATION_CHOICES = [
17619
18051
  { id: "approve", label: "Allow once", kind: "approve" },
17620
18052
  { id: "deny", label: "Reject", kind: "deny" }
@@ -17624,8 +18056,8 @@ function roots() {
17624
18056
  if (override !== void 0)
17625
18057
  return override ? [basename3(override) === "User" ? dirname5(override) : override] : [];
17626
18058
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
17627
- const base = process.platform === "darwin" ? join6(homedir6(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join6(homedir6(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join6(homedir6(), ".config");
17628
- return ["Code", "Code - Insiders"].map((name) => join6(base, name));
18059
+ const base = process.platform === "darwin" ? join7(homedir6(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join7(homedir6(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join7(homedir6(), ".config");
18060
+ return ["Code", "Code - Insiders"].map((name) => join7(base, name));
17629
18061
  }
17630
18062
  async function privateFile(path, limit) {
17631
18063
  const file = await stat2(path).catch(() => void 0);
@@ -17634,7 +18066,7 @@ async function privateFile(path, limit) {
17634
18066
  const encodedPath = Buffer.from(path).toString("base64");
17635
18067
  const script = `$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}')); $a=Get-Acl -LiteralPath $p -ErrorAction Stop; $sid=[Security.Principal.SecurityIdentifier]; $me=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value; $ok=$a.GetOwner($sid).Value -eq $me; foreach($r in $a.Access){if($r.AccessControlType -eq 'Allow' -and $r.IdentityReference.Translate($sid).Value -notin @($me,'S-1-5-18','S-1-5-32-544')){$ok=$false}}; $ok`;
17636
18068
  try {
17637
- const { stdout } = await run(
18069
+ const { stdout } = await run2(
17638
18070
  "powershell.exe",
17639
18071
  [
17640
18072
  "-NoProfile",
@@ -17654,11 +18086,11 @@ async function privateFile(path, limit) {
17654
18086
  async function entries() {
17655
18087
  const result = /* @__PURE__ */ new Map();
17656
18088
  for (const root of roots()) {
17657
- const registry2 = join6(root, "agent-host/local-endpoint");
17658
- const files2 = (await readdir3(join6(registry2, "entries")).catch(() => [])).filter((name) => name.endsWith(".json")).slice(0, MAX_HOSTS);
18089
+ const registry2 = join7(root, "agent-host/local-endpoint");
18090
+ const files2 = (await readdir3(join7(registry2, "entries")).catch(() => [])).filter((name) => name.endsWith(".json")).slice(0, MAX_HOSTS);
17659
18091
  const sources = [
17660
- ...files2.map((name) => join6(registry2, "entries", name)),
17661
- join6(registry2, "metadata.json")
18092
+ ...files2.map((name) => join7(registry2, "entries", name)),
18093
+ join7(registry2, "metadata.json")
17662
18094
  ];
17663
18095
  for (const path of sources) {
17664
18096
  try {
@@ -17668,7 +18100,7 @@ async function entries() {
17668
18100
  for (const row of rows.slice(0, MAX_HOSTS)) {
17669
18101
  if (row?.schemaVersion !== 2 || !["editor", "standalone"].includes(row.type) || !Number.isSafeInteger(row.pid) || row.pid <= 0 || typeof row.instanceId !== "string" || !row.instanceId || typeof row.connectionToken !== "string" || !row.connectionToken || row.connectionToken.length > 1024 || row.endpoint?.type !== "socket" || typeof row.endpoint.path !== "string" || !isAbsolute3(row.endpoint.path))
17670
18102
  continue;
17671
- const identity2 = createHash3("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
18103
+ const identity2 = createHash4("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
17672
18104
  if (basename3(path) !== "metadata.json" && basename3(path) !== `${identity2}.json`) continue;
17673
18105
  try {
17674
18106
  process.kill(row.pid, 0);
@@ -17795,7 +18227,7 @@ var Host = class {
17795
18227
  client;
17796
18228
  connecting;
17797
18229
  stopped = false;
17798
- clientId = `dashup-${randomUUID5()}`;
18230
+ clientId = `dashup-${randomUUID6()}`;
17799
18231
  resources = /* @__PURE__ */ new Map();
17800
18232
  subscriptions = /* @__PURE__ */ new Set();
17801
18233
  receipts = /* @__PURE__ */ new Map();
@@ -17984,7 +18416,7 @@ var Host = class {
17984
18416
  choices: preview3.choices.map(({ id: id2, label }) => ({ id: id2, label }))
17985
18417
  });
17986
18418
  }
17987
- const id = `vscode-ahp:${createHash3("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
18419
+ const id = `vscode-ahp:${createHash4("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
17988
18420
  result.push({
17989
18421
  id,
17990
18422
  cwd,
@@ -18095,12 +18527,12 @@ var VSCode = class {
18095
18527
  };
18096
18528
 
18097
18529
  // src/harness/opencode.ts
18098
- import { join as join7 } from "node:path";
18099
- import { promisify as promisify4 } from "node:util";
18100
- import { createHash as createHash4 } from "node:crypto";
18101
- import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
18530
+ import { join as join8 } from "node:path";
18531
+ import { promisify as promisify5 } from "node:util";
18532
+ import { createHash as createHash5 } from "node:crypto";
18533
+ import { execFile as execFile5, spawn as spawn5 } from "node:child_process";
18102
18534
  import { stat as stat3, readlink as readlink2, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
18103
- var run2 = promisify4(execFile4);
18535
+ var run3 = promisify5(execFile5);
18104
18536
  var REQUEST_MS3 = 4e3;
18105
18537
  var MAX_BYTES3 = 8 * 1024 * 1024;
18106
18538
  var MAX_SESSIONS4 = 100;
@@ -18145,16 +18577,16 @@ async function linuxEndpoints() {
18145
18577
  const directory2 = `/proc/${pid}`;
18146
18578
  const owner = await stat3(directory2);
18147
18579
  if (process.getuid && owner.uid !== process.getuid()) continue;
18148
- const command2 = (await readFile4(join7(directory2, "cmdline"), "utf8")).slice(0, 4096);
18580
+ const command2 = (await readFile4(join8(directory2, "cmdline"), "utf8")).slice(0, 4096);
18149
18581
  if (!/(?:^|[\/\0 ])opencode(?:[\0 .]|$)/.test(command2)) continue;
18150
18582
  const environment = Object.fromEntries(
18151
- (await readFile4(join7(directory2, "environ"), "utf8")).split("\0").map((entry) => {
18583
+ (await readFile4(join8(directory2, "environ"), "utf8")).split("\0").map((entry) => {
18152
18584
  const split = entry.indexOf("=");
18153
18585
  return [entry.slice(0, split), entry.slice(split + 1)];
18154
18586
  })
18155
18587
  );
18156
- for (const fd of (await readdir4(join7(directory2, "fd"))).slice(0, 1024)) {
18157
- const target = await readlink2(join7(directory2, "fd", fd)).catch(() => "");
18588
+ for (const fd of (await readdir4(join8(directory2, "fd"))).slice(0, 1024)) {
18589
+ const target = await readlink2(join8(directory2, "fd", fd)).catch(() => "");
18158
18590
  const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1];
18159
18591
  const url2 = inode ? listeners.get(inode) : void 0;
18160
18592
  if (url2) result.push({ url: url2, authorization: authorization(environment) });
@@ -18180,7 +18612,7 @@ async function endpoints() {
18180
18612
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
18181
18613
  if (process.platform === "linux") return linuxEndpoints();
18182
18614
  if (process.platform === "darwin") {
18183
- const { stdout } = await run2(
18615
+ const { stdout } = await run3(
18184
18616
  "lsof",
18185
18617
  ["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
18186
18618
  { timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
@@ -18197,7 +18629,7 @@ async function endpoints() {
18197
18629
  }
18198
18630
  if (process.platform === "win32") {
18199
18631
  const script = "$ids = @(Get-Process opencode -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id); Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Where-Object { $ids -contains $_.OwningProcess } | Select-Object -ExpandProperty LocalPort";
18200
- const { stdout } = await run2(
18632
+ const { stdout } = await run3(
18201
18633
  "powershell.exe",
18202
18634
  ["-NoProfile", "-NonInteractive", "-Command", script],
18203
18635
  { timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
@@ -18478,7 +18910,7 @@ var Server = class {
18478
18910
  if (this.stopped) throw new Error("OpenCode is disconnected");
18479
18911
  if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES3 / 2)
18480
18912
  throw new Error("Message is empty or too large");
18481
- const messageID = `msg_${createHash4("sha256").update(requestId).digest("hex").slice(0, 32)}`;
18913
+ const messageID = `msg_${createHash5("sha256").update(requestId).digest("hex").slice(0, 32)}`;
18482
18914
  const existing = await this.request(
18483
18915
  `/session/${encodeURIComponent(id)}/message?limit=200`,
18484
18916
  cwd
@@ -18622,7 +19054,7 @@ var Server = class {
18622
19054
  }
18623
19055
  };
18624
19056
  function serve(cwd) {
18625
- const child = spawn4("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
19057
+ const child = spawn5("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
18626
19058
  cwd,
18627
19059
  stdio: ["ignore", "pipe", "ignore"],
18628
19060
  shell: process.platform === "win32"
@@ -18710,6 +19142,7 @@ var LiveHarnesses = class {
18710
19142
  listeners = /* @__PURE__ */ new Set();
18711
19143
  codex = new Codex(() => this.changed());
18712
19144
  headless = new Headless(() => this.changed());
19145
+ kimi = new Kimi(() => this.changed());
18713
19146
  claude = new Claude(() => this.changed());
18714
19147
  vscode = new VSCode(() => this.changed());
18715
19148
  opencode = new OpenCode(() => this.changed());
@@ -18725,6 +19158,7 @@ var LiveHarnesses = class {
18725
19158
  this.refreshing = (async () => {
18726
19159
  const results = await Promise.allSettled([
18727
19160
  this.headless.refresh(),
19161
+ this.kimi.refresh(),
18728
19162
  this.opencode.refresh(),
18729
19163
  this.codex.refresh(),
18730
19164
  this.claude.refresh(),
@@ -18747,14 +19181,16 @@ var LiveHarnesses = class {
18747
19181
  recorded(harness, settings) {
18748
19182
  const changed = () => this.changed();
18749
19183
  if (harness === "claude") return claudeOptions(claudeModels(changed), settings);
19184
+ if (harness === "kimi") return kimiOptions(settings);
18750
19185
  return harness === "codex" ? codexOptions(settings) : [];
18751
19186
  }
18752
19187
  // The agents whose own command runs on this computer. Claude Code is asked its models again.
18753
19188
  async agents() {
18754
19189
  const harnesses = ["claude", "codex", "opencode"];
19190
+ const kimi2 = kimiCommand();
18755
19191
  forgetModels();
18756
19192
  const runs = await Promise.all(
18757
- harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
19193
+ harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
18758
19194
  timeout: 1e4,
18759
19195
  shell: process.platform === "win32"
18760
19196
  }).then(
@@ -18762,22 +19198,22 @@ var LiveHarnesses = class {
18762
19198
  () => false
18763
19199
  ))
18764
19200
  );
18765
- return harnesses.filter((_, index) => runs[index]);
19201
+ return [
19202
+ ...harnesses.filter((_, index) => runs[index]),
19203
+ ...await kimi2 ? ["kimi"] : []
19204
+ ];
19205
+ }
19206
+ // The harness that starts and resumes each agent's sessions.
19207
+ host(harness) {
19208
+ return { claude: this.headless, codex: this.codex, kimi: this.kimi, opencode: this.opencode }[harness];
18766
19209
  }
18767
19210
  // Starts a session in `cwd` with its first message, and resolves with the session's native id.
18768
19211
  start(harness, cwd, text3) {
18769
- return { claude: this.headless, codex: this.codex, opencode: this.opencode }[harness].start(
18770
- cwd,
18771
- text3
18772
- );
19212
+ return this.host(harness).start(cwd, text3);
18773
19213
  }
18774
19214
  // Continues a saved session with a message, and resolves once its agent takes it.
18775
19215
  resume(harness, id, cwd, text3) {
18776
- return { claude: this.headless, codex: this.codex, opencode: this.opencode }[harness].resume(
18777
- id,
18778
- cwd,
18779
- text3
18780
- );
19216
+ return this.host(harness).resume(id, cwd, text3);
18781
19217
  }
18782
19218
  watch(onChange) {
18783
19219
  this.listeners.add(onChange);
@@ -18802,6 +19238,7 @@ var LiveHarnesses = class {
18802
19238
  clearInterval(this.timer);
18803
19239
  this.listeners.clear();
18804
19240
  this.headless.stop();
19241
+ this.kimi.stop();
18805
19242
  this.opencode.stop();
18806
19243
  this.codex.stop();
18807
19244
  this.claude.stop();
@@ -18810,11 +19247,11 @@ var LiveHarnesses = class {
18810
19247
  };
18811
19248
 
18812
19249
  // src/harness/queue.ts
18813
- import { randomUUID as randomUUID6 } from "node:crypto";
18814
- import { spawn as spawn5 } from "node:child_process";
19250
+ import { randomUUID as randomUUID7 } from "node:crypto";
19251
+ import { spawn as spawn6 } from "node:child_process";
18815
19252
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
18816
- import { join as join8, dirname as dirname6, basename as basename4 } from "node:path";
18817
- import { statSync as statSync3, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
19253
+ import { join as join9, dirname as dirname6, basename as basename4 } from "node:path";
19254
+ import { statSync as statSync4, existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
18818
19255
  var CODEX_VERSION = "0.160.1";
18819
19256
  var REQUEST_MS4 = 1e4;
18820
19257
  var MAX_BYTES4 = 1024 * 1024;
@@ -18823,7 +19260,7 @@ var MAX_FILES = 4096;
18823
19260
  var THREAD_ID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
18824
19261
  function identity(path) {
18825
19262
  const canonical2 = realpathSync2.native(path);
18826
- const file = statSync3(canonical2);
19263
+ const file = statSync4(canonical2);
18827
19264
  if (!file.isFile() || process.getuid && file.uid !== process.getuid())
18828
19265
  throw new Error("Native queue files are not owned by this user");
18829
19266
  return { path: canonical2, device: file.dev, inode: file.ino };
@@ -18834,25 +19271,25 @@ function sameFile(left, right) {
18834
19271
  function packageVersion(executable) {
18835
19272
  let directory2 = dirname6(executable);
18836
19273
  for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
18837
- const path = join8(directory2, "package.json");
18838
- if (!existsSync4(path)) continue;
18839
- const value2 = JSON.parse(readFileSync5(path, "utf8"));
19274
+ const path = join9(directory2, "package.json");
19275
+ if (!existsSync5(path)) continue;
19276
+ const value2 = JSON.parse(readFileSync6(path, "utf8"));
18840
19277
  if (value2.name?.startsWith("@openai/codex-")) return value2.version;
18841
19278
  }
18842
19279
  return void 0;
18843
19280
  }
18844
19281
  function processOwners(pid) {
18845
19282
  const root = `/proc/${pid}`;
18846
- if (process.getuid && statSync3(root).uid !== process.getuid()) return [];
18847
- const executable = realpathSync2.native(join8(root, "exe"));
19283
+ if (process.getuid && statSync4(root).uid !== process.getuid()) return [];
19284
+ const executable = realpathSync2.native(join9(root, "exe"));
18848
19285
  if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
18849
- const status2 = readFileSync5(join8(root, "stat"), "utf8");
19286
+ const status2 = readFileSync6(join9(root, "stat"), "utf8");
18850
19287
  const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
18851
19288
  if (!start2 || !/^\d+$/.test(start2)) return [];
18852
- const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
19289
+ const fds = readdirSync4(join9(root, "fd")).slice(0, MAX_FILES);
18853
19290
  const files2 = fds.flatMap((fd) => {
18854
19291
  try {
18855
- return [{ fd, path: readlinkSync(join8(root, "fd", fd)) }];
19292
+ return [{ fd, path: readlinkSync(join9(root, "fd", fd)) }];
18856
19293
  } catch {
18857
19294
  return [];
18858
19295
  }
@@ -18862,22 +19299,22 @@ function processOwners(pid) {
18862
19299
  const thread = basename4(path, ".lock");
18863
19300
  if (!THREAD_ID.test(thread)) return [];
18864
19301
  try {
18865
- const lock = readFileSync5(join8(root, "fdinfo", fd), "utf8");
19302
+ const lock = readFileSync6(join9(root, "fdinfo", fd), "utf8");
18866
19303
  if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
18867
19304
  const home = realpathSync2.native(dirname6(dirname6(path)));
18868
- const directory2 = statSync3(home);
19305
+ const directory2 = statSync4(home);
18869
19306
  if (process.getuid && directory2.uid !== process.getuid()) return [];
18870
- const queue = identity(join8(home, "queue_1.sqlite"));
19307
+ const queue = identity(join9(home, "queue_1.sqlite"));
18871
19308
  if (!files2.some((file) => {
18872
19309
  try {
18873
- return sameFile(identity(join8(root, "fd", file.fd)), queue);
19310
+ return sameFile(identity(join9(root, "fd", file.fd)), queue);
18874
19311
  } catch {
18875
19312
  return false;
18876
19313
  }
18877
19314
  }))
18878
19315
  return [];
18879
19316
  const rollout = files2.find(
18880
- (file) => file.path.startsWith(join8(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
19317
+ (file) => file.path.startsWith(join9(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
18881
19318
  );
18882
19319
  if (!rollout) return [];
18883
19320
  return [
@@ -18889,7 +19326,7 @@ function processOwners(pid) {
18889
19326
  thread,
18890
19327
  queue,
18891
19328
  version: CODEX_VERSION,
18892
- cwd: realpathSync2.native(join8(root, "cwd")),
19329
+ cwd: realpathSync2.native(join9(root, "cwd")),
18893
19330
  rollout: identity(rollout.path)
18894
19331
  }
18895
19332
  ];
@@ -18901,7 +19338,7 @@ function processOwners(pid) {
18901
19338
  function queueOwners() {
18902
19339
  if (process.platform !== "linux" || process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
18903
19340
  try {
18904
- return readdirSync3("/proc").filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES3).flatMap((value2) => {
19341
+ return readdirSync4("/proc").filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES3).flatMap((value2) => {
18905
19342
  try {
18906
19343
  return processOwners(Number(value2));
18907
19344
  } catch {
@@ -18933,10 +19370,10 @@ function validateOwner(owner) {
18933
19370
  throw new Error("The original native conversation owner is no longer available");
18934
19371
  }
18935
19372
  function validateEvidence(owner) {
18936
- if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync3(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
19373
+ if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync4(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
18937
19374
  throw new Error("The original native history storage changed");
18938
- const path = join8(owner.home, "thread_history_1.sqlite");
18939
- if (existsSync4(path) && identity(path).path !== path)
19375
+ const path = join9(owner.home, "thread_history_1.sqlite");
19376
+ if (existsSync5(path) && identity(path).path !== path)
18940
19377
  throw new Error("Native history is outside the original home");
18941
19378
  }
18942
19379
  function nativeReceipt(owner, clientId) {
@@ -18952,8 +19389,8 @@ function nativeReceipt(owner, clientId) {
18952
19389
  } finally {
18953
19390
  queue.close();
18954
19391
  }
18955
- const path = join8(owner.home, "thread_history_1.sqlite");
18956
- if (!existsSync4(path)) return { queueId };
19392
+ const path = join9(owner.home, "thread_history_1.sqlite");
19393
+ if (!existsSync5(path)) return { queueId };
18957
19394
  const history = new DatabaseSync3(path, { readOnly: true });
18958
19395
  try {
18959
19396
  const rows = history.prepare(
@@ -19008,7 +19445,7 @@ var Writer = class {
19008
19445
  tail = "";
19009
19446
  pending = /* @__PURE__ */ new Map();
19010
19447
  constructor(owner) {
19011
- this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
19448
+ this.process = spawn6(owner.executable, ["app-server", "--listen", "stdio://"], {
19012
19449
  cwd: owner.cwd,
19013
19450
  env: { ...process.env, CODEX_HOME: owner.home },
19014
19451
  stdio: ["pipe", "pipe", "ignore"]
@@ -19095,7 +19532,7 @@ var NativeQueue = class {
19095
19532
  if (existing) return existing;
19096
19533
  if (this.stopped) throw new Error("The connector is stopping");
19097
19534
  validateOwner(scope2.owner);
19098
- const clientUserMessageId = randomUUID6();
19535
+ const clientUserMessageId = randomUUID7();
19099
19536
  const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
19100
19537
  let writer;
19101
19538
  try {
@@ -19203,32 +19640,32 @@ var Command = z2.object({
19203
19640
  z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
19204
19641
  z2.object({
19205
19642
  kind: z2.literal("session.start"),
19206
- harness: z2.enum(["claude", "codex", "opencode"]),
19643
+ harness: z2.enum(["claude", "codex", "kimi", "opencode"]),
19207
19644
  text: z2.string().min(1).max(1e5),
19208
19645
  folder: z2.object({ parent: z2.string().min(1), name: z2.string().min(1).max(255) }).strict().optional()
19209
19646
  }).strict()
19210
19647
  ])
19211
19648
  }).strict();
19212
19649
  var Connector = class {
19213
- constructor(config2) {
19214
- this.config = config2;
19215
- config2.unconfirmed ??= {};
19216
- config2.queued ??= {};
19650
+ constructor(config3) {
19651
+ this.config = config3;
19652
+ config3.unconfirmed ??= {};
19653
+ config3.queued ??= {};
19217
19654
  this.nativeQueue = new NativeQueue(
19218
- config2.queued,
19219
- () => saveConfig(config2),
19655
+ config3.queued,
19656
+ () => saveConfig(config3),
19220
19657
  (request2) => this.isActive(request2.pairing)
19221
19658
  );
19222
- if (!config2.device || !config2.secret) throw new Error("Pair this computer with dashup pair first");
19659
+ if (!config3.device || !config3.secret) throw new Error("Pair this computer with dashup pair first");
19223
19660
  this.client = createClient({
19224
- url: config2.api,
19225
- getToken: async () => config2.secret,
19661
+ url: config3.api,
19662
+ getToken: async () => config3.secret,
19226
19663
  autoConnect: false
19227
19664
  });
19228
19665
  provideClient(() => this.client);
19229
- this.discovery = new Discovery(config2.device.id, config2.localSecret);
19230
- this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config2.device.id }).whereNull("revokedAt").clearLimit());
19231
- this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config2.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
19666
+ this.discovery = new Discovery(config3.device.id, config3.localSecret);
19667
+ this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config3.device.id }).whereNull("revokedAt").clearLimit());
19668
+ this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config3.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
19232
19669
  }
19233
19670
  config;
19234
19671
  client;
@@ -19426,7 +19863,7 @@ var Connector = class {
19426
19863
  saveConfig(this.config);
19427
19864
  }
19428
19865
  flush() {
19429
- const run4 = this.drain.catch(() => {
19866
+ const run5 = this.drain.catch(() => {
19430
19867
  }).then(async () => {
19431
19868
  this.discardLegacySnapshots();
19432
19869
  while (this.config.outbox.length && this.client.isConnected) {
@@ -19436,8 +19873,8 @@ var Connector = class {
19436
19873
  saveConfig(this.config);
19437
19874
  }
19438
19875
  });
19439
- this.drain = run4;
19440
- return run4.catch((error) => {
19876
+ this.drain = run5;
19877
+ return run5.catch((error) => {
19441
19878
  this.report(error);
19442
19879
  clearTimeout(this.retry);
19443
19880
  this.retry = setTimeout(() => {
@@ -19511,13 +19948,13 @@ var Connector = class {
19511
19948
  // transcript costs one rescan rather than one each.
19512
19949
  queueChange() {
19513
19950
  if (this.pendingChange) return this.pendingChange;
19514
- const run4 = this.work.then(() => {
19951
+ const run5 = this.work.then(() => {
19515
19952
  this.pendingChange = void 0;
19516
19953
  return this.changed();
19517
19954
  });
19518
- this.pendingChange = run4;
19519
- this.work = run4.catch((error) => this.report(error));
19520
- return run4;
19955
+ this.pendingChange = run5;
19956
+ this.work = run5.catch((error) => this.report(error));
19957
+ return run5;
19521
19958
  }
19522
19959
  async changed() {
19523
19960
  if (this.stopped) return;
@@ -20001,7 +20438,7 @@ var Connector = class {
20001
20438
  throw new Error("That folder is no longer offered for new projects");
20002
20439
  if (!name || name === "." || name === ".." || /[\\/:*?"<>|\x00-\x1f]/.test(name))
20003
20440
  throw new Error("Name the folder without slashes or special characters");
20004
- cwd = join9(folder.parent, name);
20441
+ cwd = join10(folder.parent, name);
20005
20442
  await mkdir2(cwd).catch((error) => {
20006
20443
  throw new Error(error.code === "EEXIST" ? `${name} already exists` : `Couldn't create ${name}`);
20007
20444
  });
@@ -20027,32 +20464,32 @@ var Connector = class {
20027
20464
  };
20028
20465
 
20029
20466
  // src/install.ts
20030
- import { existsSync as existsSync5 } from "node:fs";
20467
+ import { existsSync as existsSync6 } from "node:fs";
20031
20468
  import { homedir as homedir7, userInfo } from "node:os";
20032
- import { promisify as promisify6 } from "node:util";
20469
+ import { promisify as promisify7 } from "node:util";
20033
20470
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20034
- import { execFile as execFile6, spawn as spawn6 } from "node:child_process";
20471
+ import { execFile as execFile7, spawn as spawn7 } from "node:child_process";
20035
20472
  import { cp, mkdir as mkdir3, rm as rm2, writeFile } from "node:fs/promises";
20036
- import { dirname as dirname7, join as join10, relative, sep } from "node:path";
20037
- var run3 = promisify6(execFile6);
20473
+ import { dirname as dirname7, join as join11, relative, sep } from "node:path";
20474
+ var run4 = promisify7(execFile7);
20038
20475
  var xml = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
20039
20476
  var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
20040
- var systemctl = (...args2) => run3("systemctl", ["--user", ...args2], {
20477
+ var systemctl = (...args2) => run4("systemctl", ["--user", ...args2], {
20041
20478
  env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
20042
20479
  });
20043
- var serviceFile = () => process.platform === "darwin" ? join10(homedir7(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
20044
- process.env.APPDATA ?? join10(homedir7(), "AppData/Roaming"),
20480
+ var serviceFile = () => process.platform === "darwin" ? join11(homedir7(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join11(
20481
+ process.env.APPDATA ?? join11(homedir7(), "AppData/Roaming"),
20045
20482
  "Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
20046
- ) : join10(homedir7(), ".config/systemd/user/dashup.service");
20047
- var startupInstalled = () => existsSync5(serviceFile());
20048
- function service(platform, node, entry, config2) {
20483
+ ) : join11(homedir7(), ".config/systemd/user/dashup.service");
20484
+ var startupInstalled = () => existsSync6(serviceFile());
20485
+ function service(platform, node, entry, config3) {
20049
20486
  const command2 = [node, ...NODE_FLAGS, entry];
20050
20487
  if (platform === "darwin")
20051
- return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.dashup.connector</string><key>EnvironmentVariables</key><dict><key>DASHUP_CONFIG</key><string>${xml(config2)}</string></dict><key>ProgramArguments</key><array>${[...command2, "watch"].map((value2) => `<string>${xml(value2)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict></dict></plist>`;
20488
+ return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.dashup.connector</string><key>EnvironmentVariables</key><dict><key>DASHUP_CONFIG</key><string>${xml(config3)}</string></dict><key>ProgramArguments</key><array>${[...command2, "watch"].map((value2) => `<string>${xml(value2)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict></dict></plist>`;
20052
20489
  if (platform === "win32") {
20053
20490
  const quote2 = (value2) => '""' + value2.replaceAll('"', '""') + '""';
20054
20491
  return `Set shell = CreateObject("WScript.Shell")\r
20055
- shell.Environment("Process")("DASHUP_CONFIG") = "${config2.replaceAll('"', '""')}"\r
20492
+ shell.Environment("Process")("DASHUP_CONFIG") = "${config3.replaceAll('"', '""')}"\r
20056
20493
  shell.Run "${command2.map(quote2).join(" ")} watch", 0, False\r
20057
20494
  `;
20058
20495
  }
@@ -20062,7 +20499,7 @@ Description=Dashup desktop connector
20062
20499
  After=network-online.target
20063
20500
 
20064
20501
  [Service]
20065
- Environment=${quote(`DASHUP_CONFIG=${config2}`)}
20502
+ Environment=${quote(`DASHUP_CONFIG=${config3}`)}
20066
20503
  ExecStart=${command2.map(quote).join(" ")} watch
20067
20504
  Restart=on-failure
20068
20505
  RestartSec=5
@@ -20079,19 +20516,19 @@ async function durableEntry() {
20079
20516
  );
20080
20517
  if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
20081
20518
  const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
20082
- const app = join10(dirname7(configPath()), "app");
20519
+ const app = join11(dirname7(configPath()), "app");
20083
20520
  await rm2(app, { recursive: true, force: true });
20084
- await cp(join10(root, "node_modules"), join10(app, "node_modules"), {
20521
+ await cp(join11(root, "node_modules"), join11(app, "node_modules"), {
20085
20522
  recursive: true,
20086
20523
  verbatimSymlinks: true
20087
20524
  });
20088
- return join10(app, relative(root, entry));
20525
+ return join11(app, relative(root, entry));
20089
20526
  }
20090
- var claude2 = (...args2) => run3("claude", ["plugin", ...args2], { timeout: 6e4 });
20527
+ var claude2 = (...args2) => run4("claude", ["plugin", ...args2], { timeout: 6e4 });
20091
20528
  async function installClaude(entry) {
20092
20529
  if (process.platform === "win32") return false;
20093
20530
  try {
20094
- await claude2("marketplace", "add", join10(dirname7(entry), "..", "claude"));
20531
+ await claude2("marketplace", "add", join11(dirname7(entry), "..", "claude"));
20095
20532
  await claude2("install", "dashup@dashup", "--scope", "user");
20096
20533
  return true;
20097
20534
  } catch {
@@ -20108,13 +20545,13 @@ async function installStartup() {
20108
20545
  }
20109
20546
  async function start(path) {
20110
20547
  if (process.platform === "darwin") {
20111
- await run3("launchctl", ["unload", path]).catch(() => {
20548
+ await run4("launchctl", ["unload", path]).catch(() => {
20112
20549
  });
20113
- await run3("launchctl", ["load", path]);
20550
+ await run4("launchctl", ["load", path]);
20114
20551
  } else if (process.platform === "win32") {
20115
- spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
20552
+ spawn7("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
20116
20553
  } else {
20117
- const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run3("loginctl", ["enable-linger"]).then(
20554
+ const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run4("loginctl", ["enable-linger"]).then(
20118
20555
  () => void 0,
20119
20556
  () => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
20120
20557
  );
@@ -20130,17 +20567,17 @@ async function uninstallStartup() {
20130
20567
  });
20131
20568
  await claude2("marketplace", "remove", "dashup").catch(() => {
20132
20569
  });
20133
- if (process.platform === "darwin") await run3("launchctl", ["unload", path]).catch(() => {
20570
+ if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
20134
20571
  });
20135
20572
  else if (process.platform !== "win32")
20136
20573
  await systemctl("disable", "dashup.service").catch(() => {
20137
20574
  });
20138
20575
  await rm2(path, { force: true });
20139
- await rm2(join10(dirname7(configPath()), "app"), { recursive: true, force: true });
20576
+ await rm2(join11(dirname7(configPath()), "app"), { recursive: true, force: true });
20140
20577
  }
20141
20578
 
20142
20579
  // src/index.ts
20143
- var config = loadConfig();
20580
+ var config2 = loadConfig();
20144
20581
  var args = process.argv.slice(2);
20145
20582
  var command = args[0] ?? "watch";
20146
20583
  var option = (name) => {
@@ -20149,7 +20586,7 @@ var option = (name) => {
20149
20586
  };
20150
20587
  var running = (path, init) => fetch(`http://127.0.0.1:${connectorPort()}${path}`, {
20151
20588
  ...init,
20152
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.localSecret}` }
20589
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.localSecret}` }
20153
20590
  });
20154
20591
  var refused = (error) => {
20155
20592
  if (error.cause?.code === "ECONNREFUSED") return null;
@@ -20192,7 +20629,7 @@ async function background() {
20192
20629
  return false;
20193
20630
  }
20194
20631
  var authorized = (request2) => {
20195
- const expected = Buffer.from(`Bearer ${config.localSecret}`);
20632
+ const expected = Buffer.from(`Bearer ${config2.localSecret}`);
20196
20633
  const actual = Buffer.from(request2.headers.authorization ?? "");
20197
20634
  return expected.length === actual.length && timingSafeEqual(expected, actual);
20198
20635
  };
@@ -20210,15 +20647,15 @@ async function main() {
20210
20647
  throw new Error("Use an HTTPS API origin");
20211
20648
  if (url2.username || url2.password || url2.search || url2.hash || url2.pathname !== "/")
20212
20649
  throw new Error("Use the API origin without a path");
20213
- if (config.secret && config.api !== url2.origin)
20650
+ if (config2.secret && config2.api !== url2.origin)
20214
20651
  throw new Error("Use a separate DASHUP_CONFIG file to connect a different server");
20215
- config.api = url2.origin;
20652
+ config2.api = url2.origin;
20216
20653
  }
20217
20654
  const account = option("--account");
20218
20655
  if (account !== void 0 && !/^[^\s@]+@[^\s@]+$/.test(account))
20219
20656
  throw new Error("Use --account with the email you sign in to the Dashup app with");
20220
20657
  if (command === "install") {
20221
- if (!config.secret) throw new Error("Pair this computer first: run dashup pair.");
20658
+ if (!config2.secret) throw new Error("Pair this computer first: run dashup pair.");
20222
20659
  if (!await background()) process.exitCode = 1;
20223
20660
  return;
20224
20661
  }
@@ -20240,10 +20677,10 @@ async function main() {
20240
20677
  const [executable, ...agentArgs] = native;
20241
20678
  if (!executable)
20242
20679
  throw new Error("Usage: dashup acp -- <original-agent-command> [arguments...]");
20243
- return proxyAcp(config, executable, agentArgs);
20680
+ return proxyAcp(config2, executable, agentArgs);
20244
20681
  }
20245
20682
  if (command === "doctor" || command === "projects") {
20246
- const discovery = new Discovery(config.device?.id ?? "local", config.localSecret);
20683
+ const discovery = new Discovery(config2.device?.id ?? "local", config2.localSecret);
20247
20684
  discovery.scan();
20248
20685
  const live = new LiveHarnesses();
20249
20686
  try {
@@ -20256,7 +20693,7 @@ async function main() {
20256
20693
  JSON.stringify(
20257
20694
  command === "doctor" ? {
20258
20695
  node: process.version,
20259
- paired: Boolean(config.secret),
20696
+ paired: Boolean(config2.secret),
20260
20697
  platform: process.platform,
20261
20698
  projectCount: projects.length,
20262
20699
  harnesses: Object.fromEntries(
@@ -20276,7 +20713,7 @@ async function main() {
20276
20713
  return;
20277
20714
  }
20278
20715
  if (command === "alert") {
20279
- if (!config.localSecret) throw new Error("Start dashup watch on this computer first");
20716
+ if (!config2.localSecret) throw new Error("Start dashup watch on this computer first");
20280
20717
  const input = Attention.parse({
20281
20718
  projectId: option("--project"),
20282
20719
  threadId: option("--thread") ?? "",
@@ -20295,7 +20732,7 @@ async function main() {
20295
20732
  );
20296
20733
  return;
20297
20734
  }
20298
- const pairPhone = (invite) => scanned(config, invite, (email) => `Paired a phone signed in as ${email}`);
20735
+ const pairPhone = (invite) => scanned(config2, invite, (email) => `Paired a phone signed in as ${email}`);
20299
20736
  const keepRunning = async (stopHere) => {
20300
20737
  const answer = startupInstalled() || interactive && await ui2.confirm({ message: BACKGROUND }) === true;
20301
20738
  if (answer) {
@@ -20334,7 +20771,7 @@ async function main() {
20334
20771
  if (interactive) ui2.outro(DONE2);
20335
20772
  process.exit();
20336
20773
  }
20337
- if (!config.secret && !interactive && !account)
20774
+ if (!config2.secret && !interactive && !account)
20338
20775
  throw new Error(
20339
20776
  "To pair this computer, run dashup pair in a terminal, or add --account with your Dashup email."
20340
20777
  );
@@ -20398,10 +20835,10 @@ async function main() {
20398
20835
  server.once("error", reject);
20399
20836
  server.listen(connectorPort(), "127.0.0.1", resolve3);
20400
20837
  });
20401
- const selectedApi = config.api;
20402
- Object.assign(config, loadConfig(), { api: selectedApi });
20403
- config.localSecret ||= newSecret(randomBytes5);
20404
- saveConfig(config);
20838
+ const selectedApi = config2.api;
20839
+ Object.assign(config2, loadConfig(), { api: selectedApi });
20840
+ config2.localSecret ||= newSecret(randomBytes5);
20841
+ saveConfig(config2);
20405
20842
  for (const signal of ["SIGINT", "SIGTERM"])
20406
20843
  process.once(signal, () => {
20407
20844
  connector?.stop();
@@ -20409,13 +20846,13 @@ async function main() {
20409
20846
  process.exit(0);
20410
20847
  });
20411
20848
  try {
20412
- const linking = !config.secret;
20849
+ const linking = !config2.secret;
20413
20850
  if (linking) {
20414
20851
  intro2();
20415
- if (!await link(config, account)) process.exit(1);
20852
+ if (!await link(config2, account)) process.exit(1);
20416
20853
  await keepRunning(() => server.close());
20417
20854
  }
20418
- connector = new Connector(config);
20855
+ connector = new Connector(config2);
20419
20856
  await connector.start();
20420
20857
  if (command === "pair" && !linking) {
20421
20858
  if (interactive) intro2();