@sma1lboy/kobe 0.7.15 → 0.7.16

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 (2) hide show
  1. package/dist/cli/index.js +849 -280
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
90
90
  package_default = {
91
91
  $schema: "https://json.schemastore.org/package.json",
92
92
  name: "@sma1lboy/kobe",
93
- version: "0.7.15",
93
+ version: "0.7.16",
94
94
  description: "TUI orchestrator for Claude Code (codename)",
95
95
  type: "module",
96
96
  packageManager: "bun@1.3.13",
@@ -167,6 +167,10 @@ function kobeStateDir() {
167
167
  function kvStatePath() {
168
168
  return join(homeDir(), ".config", "kobe", "state.json");
169
169
  }
170
+ function remoteControlSocketPath(host, user, port) {
171
+ const hash = createHash("sha1").update(`${user}@${host}:${port ?? 22}`).digest("hex").slice(0, 16);
172
+ return join(kobeStateDir(), "ssh", `${hash}.sock`);
173
+ }
170
174
  function worktreeInitMarkerPath(worktreePath) {
171
175
  const hash = createHash("sha1").update(worktreePath).digest("hex").slice(0, 16);
172
176
  return join(kobeStateDir(), "worktree-init", hash);
@@ -332,6 +336,202 @@ var init_version = __esm(() => {
332
336
  UPDATE_COMMAND = `curl -fsSL ${UPDATE_SCRIPT_URL} | sh`;
333
337
  });
334
338
 
339
+ // src/exec/exec-host.ts
340
+ import { spawnSync } from "child_process";
341
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "fs";
342
+ function shQuote(s) {
343
+ return `'${s.replace(/'/g, "'\\''")}'`;
344
+ }
345
+ function shJoin(argv) {
346
+ return argv.map(shQuote).join(" ");
347
+ }
348
+ function remoteShellCommand(argv, cwd) {
349
+ const cmd = shJoin(argv);
350
+ return cwd ? `cd ${shQuote(cwd)} && ${cmd}` : cmd;
351
+ }
352
+ function sshConnectArgs(spec, opts = {}) {
353
+ const argv = ["ssh"];
354
+ if (opts.tty)
355
+ argv.push("-tt");
356
+ if (opts.batch)
357
+ argv.push("-o", "BatchMode=yes");
358
+ argv.push("-o", "ControlMaster=auto", "-o", `ControlPath=${spec.controlPath}`, "-o", "ControlPersist=300");
359
+ argv.push("-o", "StrictHostKeyChecking=accept-new");
360
+ if (spec.port)
361
+ argv.push("-p", String(spec.port));
362
+ if (spec.auth.kind === "key" && spec.auth.keyPath)
363
+ argv.push("-i", spec.auth.keyPath);
364
+ argv.push(`${spec.user}@${spec.host}`);
365
+ return argv;
366
+ }
367
+
368
+ class LocalExecHost {
369
+ isRemote = false;
370
+ run(argv, opts = {}) {
371
+ const [cmd, ...rest] = argv;
372
+ const proc = spawnSync(cmd ?? "", rest, {
373
+ cwd: opts.cwd,
374
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
375
+ encoding: "utf8",
376
+ shell: false
377
+ });
378
+ return toResult(proc);
379
+ }
380
+ async runAsync(argv, opts = {}) {
381
+ return this.run(argv, opts);
382
+ }
383
+ exists(path) {
384
+ return existsSync(path);
385
+ }
386
+ mkdirp(path) {
387
+ mkdirSync(path, { recursive: true });
388
+ }
389
+ readFile(path) {
390
+ try {
391
+ return readFileSync(path, "utf8");
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ readdir(path) {
397
+ try {
398
+ return readdirSync(path);
399
+ } catch {
400
+ return [];
401
+ }
402
+ }
403
+ wrapCommand(command) {
404
+ return command;
405
+ }
406
+ ensureReady() {}
407
+ }
408
+
409
+ class RemoteExecHost {
410
+ spec;
411
+ spawn;
412
+ isRemote = true;
413
+ masterUp = false;
414
+ constructor(spec, spawn = defaultSpawner) {
415
+ this.spec = spec;
416
+ this.spawn = spawn;
417
+ }
418
+ ensureReady() {
419
+ if (this.masterUp)
420
+ return;
421
+ const check = this.spawn([...sshConnectArgs(this.spec, { batch: true }), "-O", "check"]);
422
+ if (check.exitCode === 0) {
423
+ this.masterUp = true;
424
+ return;
425
+ }
426
+ const base = [...sshConnectArgs(this.spec, { batch: this.spec.auth.kind !== "password" }), "-fN"];
427
+ if (this.spec.auth.kind === "password") {
428
+ const pw = this.spec.auth.getPassword();
429
+ if (pw != null) {
430
+ this.spawn(["sshpass", "-e", ...base], { SSHPASS: pw });
431
+ this.masterUp = true;
432
+ return;
433
+ }
434
+ }
435
+ this.spawn(base);
436
+ this.masterUp = true;
437
+ }
438
+ run(argv, opts = {}) {
439
+ this.ensureReady();
440
+ return this.spawn([...sshConnectArgs(this.spec, { batch: true }), remoteShellCommand(argv, opts.cwd)]);
441
+ }
442
+ async runAsync(argv, opts = {}) {
443
+ return this.run(argv, opts);
444
+ }
445
+ exists(path) {
446
+ return this.run(["test", "-e", path]).exitCode === 0;
447
+ }
448
+ mkdirp(path) {
449
+ this.run(["mkdir", "-p", path]);
450
+ }
451
+ readFile(path) {
452
+ const r = this.run(["cat", path]);
453
+ return r.exitCode === 0 ? r.stdout : null;
454
+ }
455
+ readdir(path) {
456
+ const r = this.run(["ls", "-1A", path]);
457
+ if (r.exitCode !== 0)
458
+ return [];
459
+ return r.stdout.split(`
460
+ `).filter((s) => s.length > 0);
461
+ }
462
+ wrapCommand(command, opts = {}) {
463
+ const remote = opts.cwd ? `cd ${shQuote(opts.cwd)} && ${command}` : command;
464
+ return `${sshConnectArgs(this.spec, { tty: opts.tty }).join(" ")} ${shQuote(remote)}`;
465
+ }
466
+ }
467
+ function toResult(proc) {
468
+ return { stdout: proc.stdout ?? "", stderr: proc.stderr ?? "", exitCode: proc.status ?? -1 };
469
+ }
470
+ var defaultSpawner = (argv, env) => {
471
+ const [cmd, ...rest] = argv;
472
+ return toResult(spawnSync(cmd ?? "", rest, {
473
+ env: env ? { ...process.env, ...env } : process.env,
474
+ encoding: "utf8",
475
+ shell: false
476
+ }));
477
+ };
478
+ var init_exec_host = () => {};
479
+
480
+ // src/exec/keychain.ts
481
+ import { spawnSync as spawnSync2 } from "child_process";
482
+ import { platform } from "os";
483
+ function remoteKeychainRef(host, user, port) {
484
+ return { service: KOBE_KEYCHAIN_SERVICE, account: port ? `${user}@${host}:${port}` : `${user}@${host}` };
485
+ }
486
+ function isKeychainSupported(deps = defaultDeps) {
487
+ return deps.platform() === "darwin";
488
+ }
489
+ function setKeychainPassword(ref, password, deps = defaultDeps) {
490
+ if (deps.platform() !== "darwin")
491
+ return false;
492
+ const { exitCode } = deps.run([
493
+ "security",
494
+ "add-generic-password",
495
+ "-U",
496
+ "-s",
497
+ ref.service,
498
+ "-a",
499
+ ref.account,
500
+ "-w",
501
+ password
502
+ ]);
503
+ return exitCode === 0;
504
+ }
505
+ function getKeychainPassword(ref, deps = defaultDeps) {
506
+ if (deps.platform() !== "darwin")
507
+ return null;
508
+ const { stdout, exitCode } = deps.run([
509
+ "security",
510
+ "find-generic-password",
511
+ "-s",
512
+ ref.service,
513
+ "-a",
514
+ ref.account,
515
+ "-w"
516
+ ]);
517
+ if (exitCode !== 0)
518
+ return null;
519
+ return stdout.replace(/\n$/, "");
520
+ }
521
+ var defaultDeps, KOBE_KEYCHAIN_SERVICE = "kobe-remote-ssh";
522
+ var init_keychain = __esm(() => {
523
+ defaultDeps = {
524
+ run(argv) {
525
+ const [cmd, ...rest] = argv;
526
+ const proc = spawnSync2(cmd ?? "", rest, { encoding: "utf8", shell: false });
527
+ return { stdout: proc.stdout ?? "", exitCode: proc.status ?? -1 };
528
+ },
529
+ platform() {
530
+ return platform();
531
+ }
532
+ };
533
+ });
534
+
335
535
  // src/state/repos.ts
336
536
  var exports_repos = {};
337
537
  __export(exports_repos, {
@@ -340,18 +540,26 @@ __export(exports_repos, {
340
540
  setPersistedString: () => setPersistedString,
341
541
  resolveRepoRoot: () => resolveRepoRoot,
342
542
  removeSavedRepo: () => removeSavedRepo,
543
+ remoteRepoKey: () => remoteRepoKey,
343
544
  normalizeSavedRepos: () => normalizeSavedRepos,
545
+ isRemoteRepoKey: () => isRemoteRepoKey,
546
+ isRemoteProjectsEnabled: () => isRemoteProjectsEnabled,
344
547
  getSavedRepos: () => getSavedRepos,
345
548
  getRepoInitOverride: () => getRepoInitOverride,
549
+ getRemoteRepos: () => getRemoteRepos,
550
+ getRemoteRepoConfig: () => getRemoteRepoConfig,
346
551
  getPersistedString: () => getPersistedString,
347
552
  getCustomEngineIds: () => getCustomEngineIds,
348
- addSavedRepo: () => addSavedRepo
553
+ addSavedRepo: () => addSavedRepo,
554
+ addRemoteRepo: () => addRemoteRepo
349
555
  });
350
- import { spawnSync } from "child_process";
351
- import { mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "fs";
556
+ import { spawnSync as spawnSync3 } from "child_process";
557
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, renameSync, writeFileSync } from "fs";
352
558
  import { dirname } from "path";
353
559
  function resolveRepoRoot(absPath) {
354
- const r = spawnSync("git", ["rev-parse", "--show-toplevel"], {
560
+ if (isRemoteRepoKey(absPath))
561
+ return absPath;
562
+ const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
355
563
  cwd: absPath,
356
564
  encoding: "utf8",
357
565
  shell: false
@@ -372,7 +580,7 @@ function statePath() {
372
580
  }
373
581
  function load() {
374
582
  try {
375
- const text = readFileSync(statePath(), "utf8");
583
+ const text = readFileSync2(statePath(), "utf8");
376
584
  const parsed = JSON.parse(text);
377
585
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
378
586
  return parsed;
@@ -382,7 +590,7 @@ function load() {
382
590
  }
383
591
  function save(state) {
384
592
  const path = statePath();
385
- mkdirSync(dirname(path), { recursive: true });
593
+ mkdirSync2(dirname(path), { recursive: true });
386
594
  const tmp = `${path}.tmp`;
387
595
  writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
388
596
  renameSync(tmp, path);
@@ -491,10 +699,197 @@ function removeSavedRepo(absPath) {
491
699
  save(state);
492
700
  return { removed: true, path: absPath, total: cur.length - 1 };
493
701
  }
702
+ function isRemoteRepoKey(key) {
703
+ return key.startsWith("ssh://");
704
+ }
705
+ function isRemoteProjectsEnabled() {
706
+ return load()["experimental.remoteProjects"] === true;
707
+ }
708
+ function remoteRepoKey(host, user, port) {
709
+ return port ? `ssh://${user}@${host}:${port}` : `ssh://${user}@${host}`;
710
+ }
711
+ function readRemoteRepos(state) {
712
+ const raw = state.remoteRepos;
713
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
714
+ return {};
715
+ return raw;
716
+ }
717
+ function getRemoteRepoConfig(key) {
718
+ return readRemoteRepos(load())[key] ?? null;
719
+ }
720
+ function getRemoteRepos() {
721
+ return readRemoteRepos(load());
722
+ }
723
+ function addRemoteRepo(config) {
724
+ const key = remoteRepoKey(config.host, config.user, config.port);
725
+ const state = load();
726
+ const repos = { ...readRemoteRepos(state) };
727
+ repos[key] = config;
728
+ state.remoteRepos = repos;
729
+ const saved = getSavedRepos();
730
+ const added = !saved.includes(key);
731
+ if (added)
732
+ state.savedRepos = [...saved, key];
733
+ save(state);
734
+ return { key, added };
735
+ }
494
736
  var init_repos = __esm(() => {
495
737
  init_env();
496
738
  });
497
739
 
740
+ // src/cli/add-remote.ts
741
+ var exports_add_remote = {};
742
+ __export(exports_add_remote, {
743
+ runAddRemote: () => runAddRemote,
744
+ parseRemoteFlags: () => parseRemoteFlags
745
+ });
746
+ import { createInterface } from "readline";
747
+ function parseRemoteFlags(args) {
748
+ const f = {};
749
+ for (let i = 0;i < args.length; i++) {
750
+ const a = args[i];
751
+ switch (a) {
752
+ case "--host":
753
+ f.host = args[++i];
754
+ break;
755
+ case "--user":
756
+ f.user = args[++i];
757
+ break;
758
+ case "--path":
759
+ f.path = args[++i];
760
+ break;
761
+ case "--port": {
762
+ const n = Number(args[++i]);
763
+ if (!Number.isInteger(n) || n <= 0)
764
+ fail(`invalid --port "${args[i]}"`);
765
+ f.port = n;
766
+ break;
767
+ }
768
+ case "--key": {
769
+ const next = args[i + 1];
770
+ if (next && !next.startsWith("-")) {
771
+ f.key = { present: true, path: next };
772
+ i++;
773
+ } else {
774
+ f.key = { present: true };
775
+ }
776
+ break;
777
+ }
778
+ case "--password":
779
+ f.password = true;
780
+ break;
781
+ case "--help":
782
+ case "-h":
783
+ process.stdout.write(USAGE);
784
+ process.exit(0);
785
+ break;
786
+ default:
787
+ fail(`unknown flag "${a}"`);
788
+ }
789
+ }
790
+ return f;
791
+ }
792
+ function fail(msg) {
793
+ process.stderr.write(`kobe add --remote: ${msg}
794
+
795
+ ${USAGE}`);
796
+ process.exit(2);
797
+ }
798
+ async function promptHidden(prompt) {
799
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
800
+ const out = process.stdout;
801
+ let muted = false;
802
+ const original = out._writeToOutput?.bind(out);
803
+ out._writeToOutput = (s) => {
804
+ if (!muted || !original)
805
+ process.stdout.write(s);
806
+ };
807
+ process.stdout.write(prompt);
808
+ muted = true;
809
+ return new Promise((res) => {
810
+ rl.question("", (answer) => {
811
+ muted = false;
812
+ if (original)
813
+ out._writeToOutput = original;
814
+ process.stdout.write(`
815
+ `);
816
+ rl.close();
817
+ res(answer);
818
+ });
819
+ });
820
+ }
821
+ async function runAddRemote(args) {
822
+ if (!isRemoteProjectsEnabled()) {
823
+ fail("remote projects are experimental and disabled \u2014 enable Settings \u2192 Dev \u2192 Experimental \u2192 Remote projects first");
824
+ }
825
+ const f = parseRemoteFlags(args);
826
+ if (!f.host)
827
+ fail("--host is required");
828
+ if (!f.user)
829
+ fail("--user is required");
830
+ if (!f.path)
831
+ fail("--path (remote base path) is required");
832
+ if (f.key && f.password)
833
+ fail("choose ONE of --key or --password, not both");
834
+ if (!f.key && !f.password)
835
+ fail("an auth method is required: --key [path] or --password");
836
+ let auth;
837
+ if (f.password) {
838
+ if (!isKeychainSupported())
839
+ fail("--password needs the macOS keychain (unsupported on this platform)");
840
+ const ref = remoteKeychainRef(f.host, f.user, f.port);
841
+ const pw = await promptHidden(`Password for ${f.user}@${f.host}: `);
842
+ if (pw.length === 0)
843
+ fail("empty password");
844
+ if (!setKeychainPassword(ref, pw))
845
+ fail("failed to store the password in the keychain");
846
+ auth = { kind: "password", keychainRef: ref };
847
+ } else {
848
+ auth = { kind: "key", keyPath: f.key?.path };
849
+ }
850
+ const { key, added } = addRemoteRepo({ host: f.host, user: f.user, port: f.port, basePath: f.path, auth });
851
+ console.log(added ? `added remote project ${key} (base ${f.path})` : `updated remote project ${key} (base ${f.path})`);
852
+ await probe(f, auth);
853
+ }
854
+ async function probe(f, auth) {
855
+ if (auth.kind === "password" && !isKeychainSupported())
856
+ return;
857
+ const runtimeAuth = auth.kind === "key" ? { kind: "key", keyPath: auth.keyPath } : { kind: "password", getPassword: () => getKeychainPassword(auth.keychainRef) };
858
+ const spec = {
859
+ host: f.host,
860
+ user: f.user,
861
+ port: f.port,
862
+ auth: runtimeAuth,
863
+ controlPath: remoteControlSocketPath(f.host, f.user, f.port)
864
+ };
865
+ process.stdout.write("checking connection\u2026 ");
866
+ try {
867
+ const host = new RemoteExecHost(spec);
868
+ const r = host.run(["test", "-d", f.path]);
869
+ if (r.exitCode === 0)
870
+ console.log("ok");
871
+ else
872
+ console.log(`reachable, but base path "${f.path}" is not a directory (you can create it later)`);
873
+ } catch (err) {
874
+ console.log(`could not connect (${err instanceof Error ? err.message : String(err)})`);
875
+ console.log("the project is saved; fix the host/credentials and it will connect on first use.");
876
+ }
877
+ }
878
+ var USAGE;
879
+ var init_add_remote = __esm(() => {
880
+ init_env();
881
+ init_exec_host();
882
+ init_keychain();
883
+ init_repos();
884
+ USAGE = `Usage: kobe add --remote --host <host> --user <user> --path <basePath>
885
+ ` + ` [--port N] [--key [path] | --password]
886
+
887
+ ` + `Register an SSH-backed project. Worktrees + the engine run on <host> under
888
+ ` + `<basePath>. Choose ONE auth: --key [path] (ssh-agent when path omitted) or
889
+ ` + `--password (prompted, stored in the OS keychain \u2014 never in state.json).
890
+ `;
891
+ });
892
+
498
893
  // src/types/task.ts
499
894
  var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
500
895
 
@@ -866,28 +1261,40 @@ var init_store = __esm(() => {
866
1261
  init_ulid();
867
1262
  });
868
1263
 
869
- // src/orchestrator/worktree/git.ts
870
- import { spawnSync as spawnSync2 } from "child_process";
871
- function git(args, opts) {
872
- if (!opts.cwd) {
873
- throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
874
- }
875
- const proc = spawnSync2("git", [...args], {
876
- cwd: opts.cwd,
877
- env: opts.env ? { ...process.env, ...opts.env } : process.env,
878
- encoding: "utf8",
879
- shell: false
880
- });
881
- const result = {
882
- stdout: proc.stdout ?? "",
883
- stderr: proc.stderr ?? "",
884
- exitCode: proc.status ?? -1
1264
+ // src/exec/resolve.ts
1265
+ function remoteSpecFromConfig(config) {
1266
+ const cfgAuth = config.auth;
1267
+ const auth = cfgAuth.kind === "key" ? { kind: "key", keyPath: cfgAuth.keyPath } : { kind: "password", getPassword: () => getKeychainPassword(cfgAuth.keychainRef) };
1268
+ return {
1269
+ host: config.host,
1270
+ user: config.user,
1271
+ port: config.port,
1272
+ auth,
1273
+ controlPath: remoteControlSocketPath(config.host, config.user, config.port)
885
1274
  };
886
- if (result.exitCode !== 0 && !opts.allowFail) {
887
- throw new GitCommandError(args, opts.cwd, result);
1275
+ }
1276
+ function execHostForRepo(repoKey) {
1277
+ const config = getRemoteRepoConfig(repoKey);
1278
+ if (!config)
1279
+ return new LocalExecHost;
1280
+ return new RemoteExecHost(remoteSpecFromConfig(config));
1281
+ }
1282
+ function execHostForWorktreePath(worktreePath) {
1283
+ for (const config of Object.values(getRemoteRepos())) {
1284
+ if (worktreePath === config.basePath || worktreePath.startsWith(`${config.basePath}/`)) {
1285
+ return new RemoteExecHost(remoteSpecFromConfig(config));
1286
+ }
888
1287
  }
889
- return result;
1288
+ return new LocalExecHost;
890
1289
  }
1290
+ var init_resolve = __esm(() => {
1291
+ init_env();
1292
+ init_repos();
1293
+ init_exec_host();
1294
+ init_keychain();
1295
+ });
1296
+
1297
+ // src/orchestrator/worktree/git.ts
891
1298
  var GitCommandError;
892
1299
  var init_git = __esm(() => {
893
1300
  GitCommandError = class GitCommandError extends Error {
@@ -934,6 +1341,12 @@ function worktreePathFor(repo, slug) {
934
1341
  return path.join(worktreeRootFor(repo), slug);
935
1342
  }
936
1343
  function listWorktreeDirNames(repo) {
1344
+ if (isRemoteRepoKey(repo)) {
1345
+ const basePath = getRemoteRepoConfig(repo)?.basePath;
1346
+ if (!basePath)
1347
+ return [];
1348
+ return execHostForRepo(repo).readdir(remoteWorktreeRootFor(basePath));
1349
+ }
937
1350
  const names = new Set;
938
1351
  for (const root of managedWorktreeRootsFor(repo)) {
939
1352
  try {
@@ -961,6 +1374,18 @@ function managedWorktreeRootForPath(repo, candidate) {
961
1374
  function isKobeManagedPath(repo, candidate) {
962
1375
  return managedWorktreeRootForPath(repo, candidate) !== null;
963
1376
  }
1377
+ function remoteWorktreeRootFor(basePath) {
1378
+ return `${stripTrailingSlash(basePath)}/.kobe/worktrees`;
1379
+ }
1380
+ function remoteWorktreePathFor(basePath, slug) {
1381
+ if (!slug || /[/\\\0]/.test(slug)) {
1382
+ throw new Error(`remoteWorktreePathFor: invalid slug: ${JSON.stringify(slug)}`);
1383
+ }
1384
+ return `${remoteWorktreeRootFor(basePath)}/${slug}`;
1385
+ }
1386
+ function stripTrailingSlash(p) {
1387
+ return p.length > 1 && p.endsWith("/") ? p.replace(/\/+$/, "") : p;
1388
+ }
964
1389
  function canonicalize(p) {
965
1390
  try {
966
1391
  return fs.realpathSync(p);
@@ -977,6 +1402,8 @@ function repoWorktreeDirName(repo) {
977
1402
  var KOBE_WORKTREE_ROOT_DIR = "worktrees", REPO_LOCAL_KOBE_WORKTREE_ROOT_SUBPATH = ".kobe/worktrees", LEGACY_KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees", REPO_LOCAL_KOBE_MANAGED_WORKTREE_ROOT_SUBPATHS;
978
1403
  var init_paths = __esm(() => {
979
1404
  init_env();
1405
+ init_resolve();
1406
+ init_repos();
980
1407
  REPO_LOCAL_KOBE_MANAGED_WORKTREE_ROOT_SUBPATHS = [
981
1408
  REPO_LOCAL_KOBE_WORKTREE_ROOT_SUBPATH,
982
1409
  LEGACY_KOBE_WORKTREE_ROOT_SUBPATH
@@ -992,13 +1419,33 @@ import fs2 from "fs";
992
1419
  import path2 from "path";
993
1420
 
994
1421
  class GitWorktreeManager {
1422
+ execDeps;
1423
+ constructor(execDeps = defaultExecDeps) {
1424
+ this.execDeps = execDeps;
1425
+ }
1426
+ ctxFor(repoKey) {
1427
+ const basePath = this.execDeps.remoteBasePath(repoKey);
1428
+ return basePath ? { exec: this.execDeps.execForRepo(repoKey), dir: basePath, remote: true } : { exec: this.execDeps.execForRepo(repoKey), dir: repoKey, remote: false };
1429
+ }
1430
+ runGit(exec, args, opts) {
1431
+ if (!opts.cwd) {
1432
+ throw new Error("runGit(): cwd is required; refusing to inherit from process.cwd()");
1433
+ }
1434
+ const r = exec.run(["git", ...args], { cwd: opts.cwd, env: opts.env });
1435
+ const result = { stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode };
1436
+ if (result.exitCode !== 0 && !opts.allowFail) {
1437
+ throw new GitCommandError(args, opts.cwd, result);
1438
+ }
1439
+ return result;
1440
+ }
995
1441
  async create(repo, branch, worktreePath, baseRef) {
996
- requireAbsolute("repo", repo);
1442
+ const ctx = this.ctxFor(repo);
1443
+ requireAbsolute("repo", ctx.dir);
997
1444
  requireAbsolute("path", worktreePath);
998
1445
  if (!branch)
999
1446
  throw new Error("create(): branch must be a non-empty string");
1000
- if (fs2.existsSync(worktreePath)) {
1001
- const existing = await this.tryDescribe(repo, worktreePath);
1447
+ if (ctx.exec.exists(worktreePath)) {
1448
+ const existing = await this.tryDescribe(ctx, worktreePath);
1002
1449
  if (existing) {
1003
1450
  if (existing.branch !== branch) {
1004
1451
  throw new Error(`worktree at ${worktreePath} is on branch '${existing.branch}', refusing to hijack to '${branch}'`);
@@ -1007,11 +1454,11 @@ class GitWorktreeManager {
1007
1454
  }
1008
1455
  throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
1009
1456
  }
1010
- fs2.mkdirSync(path2.dirname(worktreePath), { recursive: true });
1011
- const branchExists = this.branchExists(repo, branch);
1457
+ ctx.exec.mkdirp(path2.dirname(worktreePath));
1458
+ const branchExists = this.branchExists(ctx, branch);
1012
1459
  const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
1013
- git(args, { cwd: repo });
1014
- const info = await this.tryDescribe(repo, worktreePath);
1460
+ this.runGit(ctx.exec, args, { cwd: ctx.dir });
1461
+ const info = await this.tryDescribe(ctx, worktreePath);
1015
1462
  if (!info) {
1016
1463
  throw new Error(`create(): git reported success but ${worktreePath} is not a worktree`);
1017
1464
  }
@@ -1021,19 +1468,21 @@ class GitWorktreeManager {
1021
1468
  return info;
1022
1469
  }
1023
1470
  async createForTask(args) {
1024
- const target = worktreePathFor(args.repo, args.slug);
1471
+ const basePath = this.execDeps.remoteBasePath(args.repo);
1472
+ const target = basePath ? remoteWorktreePathFor(basePath, args.slug) : worktreePathFor(args.repo, args.slug);
1025
1473
  return this.create(args.repo, args.branch, target, args.baseRef);
1026
1474
  }
1027
1475
  async remove(worktreePath, opts) {
1028
1476
  requireAbsolute("path", worktreePath);
1477
+ const exec = this.execDeps.execForPath(worktreePath);
1029
1478
  const force = opts?.force === true;
1030
- if (!fs2.existsSync(worktreePath)) {
1031
- const repo2 = this.findRepoFor(worktreePath);
1479
+ if (!exec.exists(worktreePath)) {
1480
+ const repo2 = this.findRepoFor(exec, worktreePath);
1032
1481
  if (repo2)
1033
- git(["worktree", "prune"], { cwd: repo2, allowFail: true });
1482
+ this.runGit(exec, ["worktree", "prune"], { cwd: repo2, allowFail: true });
1034
1483
  return;
1035
1484
  }
1036
- const repo = this.findRepoFor(worktreePath);
1485
+ const repo = this.findRepoFor(exec, worktreePath);
1037
1486
  if (!repo) {
1038
1487
  throw new Error(`remove(): ${worktreePath} is not a git worktree`);
1039
1488
  }
@@ -1044,18 +1493,19 @@ class GitWorktreeManager {
1044
1493
  }
1045
1494
  }
1046
1495
  const args = force ? ["worktree", "remove", "--force", worktreePath] : ["worktree", "remove", worktreePath];
1047
- git(args, { cwd: repo });
1048
- git(["worktree", "prune"], { cwd: repo, allowFail: true });
1496
+ this.runGit(exec, args, { cwd: repo });
1497
+ this.runGit(exec, ["worktree", "prune"], { cwd: repo, allowFail: true });
1049
1498
  }
1050
1499
  async list(repo) {
1051
- requireAbsolute("repo", repo);
1052
- const out = git(["worktree", "list", "--porcelain"], { cwd: repo });
1500
+ const ctx = this.ctxFor(repo);
1501
+ requireAbsolute("repo", ctx.dir);
1502
+ const out = this.runGit(ctx.exec, ["worktree", "list", "--porcelain"], { cwd: ctx.dir });
1053
1503
  const all = parsePorcelain(out.stdout);
1054
1504
  const infos = [];
1055
1505
  for (const entry of all) {
1056
1506
  if (!entry.path)
1057
1507
  continue;
1058
- const callerRoot = managedWorktreeRootForPath(repo, entry.path);
1508
+ const callerRoot = ctx.remote ? remoteManagedRootForPath(ctx.dir, entry.path) : managedWorktreeRootForPath(repo, entry.path);
1059
1509
  if (!callerRoot)
1060
1510
  continue;
1061
1511
  if (!entry.branch || entry.detached)
@@ -1075,10 +1525,11 @@ class GitWorktreeManager {
1075
1525
  return infos;
1076
1526
  }
1077
1527
  async listAll(repo) {
1078
- requireAbsolute("repo", repo);
1079
- const out = git(["worktree", "list", "--porcelain"], { cwd: repo });
1528
+ const ctx = this.ctxFor(repo);
1529
+ requireAbsolute("repo", ctx.dir);
1530
+ const out = this.runGit(ctx.exec, ["worktree", "list", "--porcelain"], { cwd: ctx.dir });
1080
1531
  const all = parsePorcelain(out.stdout);
1081
- const canonRepo = canonicalize2(repo);
1532
+ const canonRepo = ctx.remote ? ctx.dir : canonicalize2(ctx.dir);
1082
1533
  const infos = [];
1083
1534
  for (const entry of all) {
1084
1535
  if (!entry.path)
@@ -1087,7 +1538,7 @@ class GitWorktreeManager {
1087
1538
  continue;
1088
1539
  if (!entry.branch || entry.detached)
1089
1540
  continue;
1090
- if (canonicalize2(entry.path) === canonRepo)
1541
+ if ((ctx.remote ? entry.path : canonicalize2(entry.path)) === canonRepo)
1091
1542
  continue;
1092
1543
  const dirty = await this.isDirty(entry.path);
1093
1544
  infos.push({
@@ -1095,34 +1546,37 @@ class GitWorktreeManager {
1095
1546
  branch: entry.branch,
1096
1547
  head: entry.head ?? "",
1097
1548
  dirty,
1098
- kobeManaged: isKobeManagedPath(repo, entry.path),
1099
- lastActivityMs: this.lastActivityMs(entry.path)
1549
+ kobeManaged: ctx.remote ? remoteManagedRootForPath(ctx.dir, entry.path) !== null : isKobeManagedPath(repo, entry.path),
1550
+ lastActivityMs: this.lastActivityMs(ctx.exec, entry.path)
1100
1551
  });
1101
1552
  }
1102
1553
  infos.sort((a, b) => b.lastActivityMs - a.lastActivityMs);
1103
1554
  return infos;
1104
1555
  }
1105
- lastActivityMs(worktreePath) {
1556
+ lastActivityMs(exec, worktreePath) {
1106
1557
  try {
1107
- const out = git(["log", "-1", "--format=%ct"], { cwd: worktreePath });
1558
+ const out = this.runGit(exec, ["log", "-1", "--format=%ct"], { cwd: worktreePath });
1108
1559
  const secs = Number.parseInt(out.stdout.trim(), 10);
1109
1560
  if (Number.isFinite(secs) && secs > 0)
1110
1561
  return secs * 1000;
1111
1562
  } catch {}
1112
- try {
1113
- return fs2.statSync(worktreePath).mtimeMs;
1114
- } catch {
1115
- return 0;
1563
+ if (!exec.isRemote) {
1564
+ try {
1565
+ return fs2.statSync(worktreePath).mtimeMs;
1566
+ } catch {}
1116
1567
  }
1568
+ return 0;
1117
1569
  }
1118
1570
  async isDirty(worktreePath) {
1119
1571
  requireAbsolute("path", worktreePath);
1120
- const out = git(["status", "--porcelain"], { cwd: worktreePath });
1572
+ const exec = this.execDeps.execForPath(worktreePath);
1573
+ const out = this.runGit(exec, ["status", "--porcelain"], { cwd: worktreePath });
1121
1574
  return out.stdout.length > 0;
1122
1575
  }
1123
1576
  async currentBranch(worktreePath) {
1124
1577
  requireAbsolute("path", worktreePath);
1125
- const out = git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreePath });
1578
+ const exec = this.execDeps.execForPath(worktreePath);
1579
+ const out = this.runGit(exec, ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreePath });
1126
1580
  const name = out.stdout.trim();
1127
1581
  if (!name || name === "HEAD") {
1128
1582
  throw new Error(`currentBranch(): ${worktreePath} is in detached-HEAD state`);
@@ -1133,16 +1587,18 @@ class GitWorktreeManager {
1133
1587
  requireAbsolute("path", worktreePath);
1134
1588
  if (from === to)
1135
1589
  return;
1136
- const repo = this.findRepoFor(worktreePath);
1590
+ const exec = this.execDeps.execForPath(worktreePath);
1591
+ const repo = this.findRepoFor(exec, worktreePath);
1137
1592
  if (!repo)
1138
1593
  throw new Error(`renameBranch(): ${worktreePath} is not a git worktree`);
1139
- git(["branch", "-m", from, to], { cwd: repo });
1594
+ this.runGit(exec, ["branch", "-m", from, to], { cwd: repo });
1140
1595
  }
1141
- async tryDescribe(repo, worktreePath) {
1142
- const out = git(["worktree", "list", "--porcelain"], { cwd: repo });
1596
+ async tryDescribe(ctx, worktreePath) {
1597
+ const out = this.runGit(ctx.exec, ["worktree", "list", "--porcelain"], { cwd: ctx.dir });
1143
1598
  const entries = parsePorcelain(out.stdout);
1144
- const target = canonicalize2(worktreePath);
1145
- const match = entries.find((e) => e.path && canonicalize2(e.path) === target);
1599
+ const norm = (p) => ctx.remote ? p : canonicalize2(p);
1600
+ const target = norm(worktreePath);
1601
+ const match = entries.find((e) => e.path && norm(e.path) === target);
1146
1602
  if (!match || !match.path || !match.branch || match.detached)
1147
1603
  return null;
1148
1604
  return {
@@ -1152,14 +1608,14 @@ class GitWorktreeManager {
1152
1608
  dirty: await this.isDirty(match.path)
1153
1609
  };
1154
1610
  }
1155
- branchExists(repo, branch) {
1611
+ branchExists(ctx, branch) {
1156
1612
  const ref = `refs/heads/${branch}`;
1157
- const out = git(["show-ref", "--verify", "--quiet", ref], { cwd: repo, allowFail: true });
1613
+ const out = this.runGit(ctx.exec, ["show-ref", "--verify", "--quiet", ref], { cwd: ctx.dir, allowFail: true });
1158
1614
  return out.exitCode === 0;
1159
1615
  }
1160
- findRepoFor(worktreePath) {
1616
+ findRepoFor(exec, worktreePath) {
1161
1617
  try {
1162
- const out = git(["rev-parse", "--git-common-dir"], { cwd: worktreePath, allowFail: true });
1618
+ const out = this.runGit(exec, ["rev-parse", "--git-common-dir"], { cwd: worktreePath, allowFail: true });
1163
1619
  if (out.exitCode !== 0)
1164
1620
  return null;
1165
1621
  const gitDir = out.stdout.trim();
@@ -1206,6 +1662,10 @@ function parsePorcelain(out) {
1206
1662
  records.push(current);
1207
1663
  return records;
1208
1664
  }
1665
+ function remoteManagedRootForPath(basePath, candidate) {
1666
+ const root = `${basePath.replace(/\/+$/, "")}/.kobe/worktrees`;
1667
+ return candidate === root || candidate.startsWith(`${root}/`) ? root : null;
1668
+ }
1209
1669
  function requireAbsolute(name, value) {
1210
1670
  if (!value || !path2.isAbsolute(value)) {
1211
1671
  throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
@@ -1218,9 +1678,19 @@ function canonicalize2(p) {
1218
1678
  return path2.resolve(p);
1219
1679
  }
1220
1680
  }
1681
+ var defaultExecDeps;
1221
1682
  var init_manager = __esm(() => {
1683
+ init_resolve();
1684
+ init_repos();
1222
1685
  init_git();
1223
1686
  init_paths();
1687
+ defaultExecDeps = {
1688
+ execForRepo: execHostForRepo,
1689
+ execForPath: execHostForWorktreePath,
1690
+ remoteBasePath(repoKey) {
1691
+ return getRemoteRepoConfig(repoKey)?.basePath ?? null;
1692
+ }
1693
+ };
1224
1694
  });
1225
1695
 
1226
1696
  // ../../node_modules/.bun/solid-js@1.9.12/node_modules/solid-js/dist/dev.js
@@ -2992,6 +3462,9 @@ __export(exports_core, {
2992
3462
  });
2993
3463
  import { realpathSync as realpathSync2 } from "fs";
2994
3464
  import { basename as basename2, resolve } from "path";
3465
+ function repoWorkingDir(repo) {
3466
+ return getRemoteRepoConfig(repo)?.basePath ?? repo;
3467
+ }
2995
3468
 
2996
3469
  class Orchestrator {
2997
3470
  store;
@@ -3081,7 +3554,7 @@ class Orchestrator {
3081
3554
  repo,
3082
3555
  title: titleFromRepo(repo),
3083
3556
  branch: "",
3084
- worktreePath: repo,
3557
+ worktreePath: repoWorkingDir(repo),
3085
3558
  status: "backlog",
3086
3559
  kind: "main",
3087
3560
  vendor: DEFAULT_TASK_VENDOR
@@ -3098,7 +3571,7 @@ class Orchestrator {
3098
3571
  async ensureWorktree(id) {
3099
3572
  const task = this.requireTask(id);
3100
3573
  if (task.kind === "main")
3101
- return task.repo;
3574
+ return repoWorkingDir(task.repo);
3102
3575
  if (task.worktreePath)
3103
3576
  return task.worktreePath;
3104
3577
  const inflight = this.worktreeLocks.get(task.id);
@@ -3315,6 +3788,7 @@ function canonPath(p) {
3315
3788
  var PLACEHOLDER_TASK_TITLE = "(new task)";
3316
3789
  var init_core = __esm(() => {
3317
3790
  init_dev();
3791
+ init_repos();
3318
3792
  init_errors();
3319
3793
  init_slug_allocator();
3320
3794
  });
@@ -3839,7 +4313,7 @@ async function latestTranscriptMtimeForWorktree(worktree) {
3839
4313
  const files = await listSessionFilesForWorktree(worktree);
3840
4314
  return files[0]?.mtimeMs ?? 0;
3841
4315
  }
3842
- async function readHistory(sessionId, deps = defaultDeps) {
4316
+ async function readHistory(sessionId, deps = defaultDeps2) {
3843
4317
  const root = deps.projectsDir();
3844
4318
  const projectDirs = await deps.readdir(root);
3845
4319
  for (const dir of projectDirs) {
@@ -3916,9 +4390,9 @@ function extractUsage(v) {
3916
4390
  function isObject(v) {
3917
4391
  return typeof v === "object" && v !== null && !Array.isArray(v);
3918
4392
  }
3919
- var defaultDeps;
4393
+ var defaultDeps2;
3920
4394
  var init_history = __esm(() => {
3921
- defaultDeps = {
4395
+ defaultDeps2 = {
3922
4396
  projectsDir() {
3923
4397
  return path3.join(homedir4(), ".claude", "projects");
3924
4398
  },
@@ -4024,7 +4498,7 @@ function validPositive(v) {
4024
4498
  import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
4025
4499
  import { homedir as homedir5 } from "os";
4026
4500
  import path4 from "path";
4027
- async function listRolloutFiles(deps = defaultDeps2) {
4501
+ async function listRolloutFiles(deps = defaultDeps3) {
4028
4502
  const root = deps.sessionsDir();
4029
4503
  const years = (await deps.readdir(root)).sort().reverse();
4030
4504
  const out = [];
@@ -4045,7 +4519,7 @@ async function listRolloutFiles(deps = defaultDeps2) {
4045
4519
  }
4046
4520
  return out;
4047
4521
  }
4048
- async function findRolloutFile(sessionId, deps = defaultDeps2) {
4522
+ async function findRolloutFile(sessionId, deps = defaultDeps3) {
4049
4523
  const all = await listRolloutFiles(deps);
4050
4524
  for (const p of all) {
4051
4525
  if (path4.basename(p).endsWith(`-${sessionId}.jsonl`))
@@ -4068,7 +4542,7 @@ function rolloutCwd(raw) {
4068
4542
  }
4069
4543
  return "";
4070
4544
  }
4071
- async function listSessionIdsForWorktree(worktree, deps = defaultDeps2) {
4545
+ async function listSessionIdsForWorktree(worktree, deps = defaultDeps3) {
4072
4546
  if (!worktree)
4073
4547
  return [];
4074
4548
  const files = await listRolloutFiles(deps);
@@ -4092,7 +4566,7 @@ async function listSessionIdsForWorktree(worktree, deps = defaultDeps2) {
4092
4566
  }
4093
4567
  return matches.reverse();
4094
4568
  }
4095
- async function latestTranscriptMtimeForWorktree2(worktree, deps = defaultDeps2) {
4569
+ async function latestTranscriptMtimeForWorktree2(worktree, deps = defaultDeps3) {
4096
4570
  if (!worktree)
4097
4571
  return 0;
4098
4572
  const files = await listRolloutFiles(deps);
@@ -4117,10 +4591,10 @@ async function latestTranscriptMtimeForWorktree2(worktree, deps = defaultDeps2)
4117
4591
  }
4118
4592
  return 0;
4119
4593
  }
4120
- async function readHistory2(sessionId, deps = defaultDeps2) {
4594
+ async function readHistory2(sessionId, deps = defaultDeps3) {
4121
4595
  return (await readHistoryWithMetrics(sessionId, deps)).messages;
4122
4596
  }
4123
- async function readHistoryWithMetrics(sessionId, deps = defaultDeps2) {
4597
+ async function readHistoryWithMetrics(sessionId, deps = defaultDeps3) {
4124
4598
  const file = await findRolloutFile(sessionId, deps);
4125
4599
  if (!file)
4126
4600
  return { messages: [] };
@@ -4348,10 +4822,10 @@ function parseTimestampMs(value) {
4348
4822
  function isObject3(v) {
4349
4823
  return typeof v === "object" && v !== null && !Array.isArray(v);
4350
4824
  }
4351
- var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
4825
+ var defaultDeps3, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
4352
4826
  var init_history2 = __esm(() => {
4353
4827
  init_synthetic();
4354
- defaultDeps2 = {
4828
+ defaultDeps3 = {
4355
4829
  sessionsDir() {
4356
4830
  return path4.join(homedir5(), ".codex", "sessions");
4357
4831
  },
@@ -4408,12 +4882,12 @@ function numberOr2(value, fallback) {
4408
4882
  import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4409
4883
  import { homedir as homedir6 } from "os";
4410
4884
  import path5 from "path";
4411
- async function listSessionDirs(deps = defaultDeps3) {
4885
+ async function listSessionDirs(deps = defaultDeps4) {
4412
4886
  const root = path5.join(deps.copilotDir(), "session-state");
4413
4887
  const names = await deps.readdir(root);
4414
4888
  return names.map((name) => path5.join(root, name));
4415
4889
  }
4416
- async function listSessionIdsForWorktree2(worktree, deps = defaultDeps3) {
4890
+ async function listSessionIdsForWorktree2(worktree, deps = defaultDeps4) {
4417
4891
  const matches = [];
4418
4892
  for (const dir of await listSessionDirs(deps)) {
4419
4893
  const workspace = await readWorkspace(dir, deps);
@@ -4423,7 +4897,7 @@ async function listSessionIdsForWorktree2(worktree, deps = defaultDeps3) {
4423
4897
  }
4424
4898
  return matches.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt)).map((m) => m.id);
4425
4899
  }
4426
- async function latestTranscriptMtimeForWorktree3(worktree, deps = defaultDeps3) {
4900
+ async function latestTranscriptMtimeForWorktree3(worktree, deps = defaultDeps4) {
4427
4901
  if (!worktree)
4428
4902
  return 0;
4429
4903
  let newest = 0;
@@ -4439,7 +4913,7 @@ async function latestTranscriptMtimeForWorktree3(worktree, deps = defaultDeps3)
4439
4913
  }
4440
4914
  return newest;
4441
4915
  }
4442
- async function readHistoryWithMetrics2(sessionId, deps = defaultDeps3) {
4916
+ async function readHistoryWithMetrics2(sessionId, deps = defaultDeps4) {
4443
4917
  const dir = await findSessionDir(sessionId, deps);
4444
4918
  if (!dir)
4445
4919
  return { messages: [] };
@@ -4447,10 +4921,10 @@ async function readHistoryWithMetrics2(sessionId, deps = defaultDeps3) {
4447
4921
  const parsed = parseEvents(raw, sessionId);
4448
4922
  return { messages: parsed.messages, ...parsed.usageMetrics ? { usageMetrics: parsed.usageMetrics } : {} };
4449
4923
  }
4450
- async function readHistory3(sessionId, deps = defaultDeps3) {
4924
+ async function readHistory3(sessionId, deps = defaultDeps4) {
4451
4925
  return (await readHistoryWithMetrics2(sessionId, deps)).messages;
4452
4926
  }
4453
- async function findSessionDir(sessionId, deps = defaultDeps3) {
4927
+ async function findSessionDir(sessionId, deps = defaultDeps4) {
4454
4928
  for (const dir of await listSessionDirs(deps)) {
4455
4929
  if (path5.basename(dir) === sessionId)
4456
4930
  return dir;
@@ -4460,7 +4934,7 @@ async function findSessionDir(sessionId, deps = defaultDeps3) {
4460
4934
  }
4461
4935
  return;
4462
4936
  }
4463
- async function readWorkspace(dir, deps = defaultDeps3) {
4937
+ async function readWorkspace(dir, deps = defaultDeps4) {
4464
4938
  const raw = await deps.readFile(path5.join(dir, "workspace.yaml")).catch(() => "");
4465
4939
  return parseWorkspaceYaml(raw);
4466
4940
  }
@@ -4568,9 +5042,9 @@ function parseEvents(raw, fallbackSessionId) {
4568
5042
  function isObject5(v) {
4569
5043
  return typeof v === "object" && v !== null && !Array.isArray(v);
4570
5044
  }
4571
- var defaultDeps3, PREVIEW_CHAR_CAP = 200;
5045
+ var defaultDeps4, PREVIEW_CHAR_CAP = 200;
4572
5046
  var init_history3 = __esm(() => {
4573
- defaultDeps3 = {
5047
+ defaultDeps4 = {
4574
5048
  copilotDir() {
4575
5049
  const override = process.env.COPILOT_HOME?.trim();
4576
5050
  if (override)
@@ -4674,12 +5148,12 @@ function keepAlive(cmd) {
4674
5148
  const banner = "\\n \u26A0 Engine exited (code %s). Check Settings \u2192 Engines, fix the launch command, then press R to relaunch.\\n\\n";
4675
5149
  return `${cmd}; __rc=$?; [ "$__rc" -ne 0 ] && printf '${banner}' "$__rc"; exec "\${SHELL:-/bin/sh}"`;
4676
5150
  }
4677
- function shQuote(s) {
5151
+ function shQuote2(s) {
4678
5152
  return `'${s.replace(/'/g, "'\\''")}'`;
4679
5153
  }
4680
5154
  function homeWelcomeCommand() {
4681
5155
  const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
4682
- return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
5156
+ return `clear; printf ${shQuote2(msg)}; exec "\${SHELL:-/bin/sh}"`;
4683
5157
  }
4684
5158
  function engineLaunchLine(engineCmd, init) {
4685
5159
  const tail = keepAlive(engineCmd);
@@ -4689,8 +5163,8 @@ function engineLaunchLine(engineCmd, init) {
4689
5163
  const group = ["{", script, "}"].join(`
4690
5164
  `);
4691
5165
  if (init?.markerPath) {
4692
- const marker = shQuote(init.markerPath);
4693
- const markerDir = shQuote(markerDirOf(init.markerPath));
5166
+ const marker = shQuote2(init.markerPath);
5167
+ const markerDir = shQuote2(markerDirOf(init.markerPath));
4694
5168
  return [
4695
5169
  `if [ ! -f ${marker} ]; then`,
4696
5170
  group,
@@ -5376,17 +5850,17 @@ var exports_repo_init = {};
5376
5850
  __export(exports_repo_init, {
5377
5851
  resolveRepoInit: () => resolveRepoInit
5378
5852
  });
5379
- import { existsSync, readFileSync as readFileSync2 } from "fs";
5853
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
5380
5854
  import { join as join4 } from "path";
5381
5855
  function repoFileScript(worktreePath) {
5382
- return existsSync(join4(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
5856
+ return existsSync2(join4(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
5383
5857
  }
5384
5858
  function repoFilePrompt(worktreePath) {
5385
5859
  const p = join4(worktreePath, INIT_PROMPT_REL);
5386
- if (!existsSync(p))
5860
+ if (!existsSync2(p))
5387
5861
  return;
5388
5862
  try {
5389
- const text = readFileSync2(p, "utf8");
5863
+ const text = readFileSync3(p, "utf8");
5390
5864
  return text.trim().length > 0 ? text : undefined;
5391
5865
  } catch {
5392
5866
  return;
@@ -5473,6 +5947,18 @@ __export(exports_tmux, {
5473
5947
  CHAT_TAB_CLOSE_BINDING: () => CHAT_TAB_CLOSE_BINDING,
5474
5948
  CHAT_TAB_CHOOSE_ENGINE_BINDINGS: () => CHAT_TAB_CHOOSE_ENGINE_BINDINGS
5475
5949
  });
5950
+ function localSpawnCwd(cwd) {
5951
+ return execHostForWorktreePath(cwd).isRemote ? homeDir() : cwd;
5952
+ }
5953
+ function wrapEngineLaunch(engineCmd, remoteKey, remoteCwd) {
5954
+ if (!remoteKey || !isRemoteRepoKey(remoteKey))
5955
+ return engineCmd;
5956
+ const host = execHostForRepo(remoteKey);
5957
+ if (!host.isRemote)
5958
+ return engineCmd;
5959
+ host.ensureReady();
5960
+ return host.wrapCommand(engineCmd, { tty: true, cwd: remoteCwd });
5961
+ }
5476
5962
  function tmuxInitialSizeArgs(stdout = process.stdout, env = process.env) {
5477
5963
  const columns = positiveInt(stdout.columns) ?? positiveInt(env.COLUMNS);
5478
5964
  const rows = positiveInt(stdout.rows) ?? positiveInt(env.LINES);
@@ -5508,7 +5994,7 @@ async function ensureSessionImpl(opts) {
5508
5994
  return true;
5509
5995
  }
5510
5996
  if (worktreeOk && !vendorOk && opts.command.length > 0) {
5511
- if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command)) {
5997
+ if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command, opts.remoteKey)) {
5512
5998
  if (opts.vendor)
5513
5999
  await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
5514
6000
  await healTaskPaneWidths(opts.name);
@@ -5525,20 +6011,22 @@ async function ensureSessionImpl(opts) {
5525
6011
  }
5526
6012
  const inv = kobeCliInvocation();
5527
6013
  const launch = withClaudeSessionId(opts.command, opts.vendor);
6014
+ const engineCmd = wrapEngineLaunch(shellQuoteArgv(launch.argv), opts.remoteKey, opts.cwd);
6015
+ const remote = Boolean(opts.remoteKey && isRemoteRepoKey(opts.remoteKey));
5528
6016
  const r0 = await runTmuxCapturing([
5529
6017
  "new-session",
5530
6018
  "-d",
5531
6019
  "-s",
5532
6020
  opts.name,
5533
6021
  "-c",
5534
- opts.cwd,
6022
+ localSpawnCwd(opts.cwd),
5535
6023
  ...tmuxInitialSizeArgs(),
5536
6024
  "-P",
5537
6025
  "-F",
5538
6026
  "#{pane_id}",
5539
- engineLaunchLine(shellQuoteArgv(launch.argv), {
5540
- initScript: opts.initScript,
5541
- markerPath: opts.initScript ? worktreeInitMarkerPath(opts.cwd) : undefined
6027
+ engineLaunchLine(engineCmd, {
6028
+ initScript: remote ? undefined : opts.initScript,
6029
+ markerPath: !remote && opts.initScript ? worktreeInitMarkerPath(opts.cwd) : undefined
5542
6030
  })
5543
6031
  ]);
5544
6032
  const pane0 = r0.stdout.trim();
@@ -5551,7 +6039,8 @@ async function ensureSessionImpl(opts) {
5551
6039
  await runTmuxSequence([
5552
6040
  ...opts.taskId ? [["set-option", "-t", opts.name, "@kobe_task", opts.taskId]] : [],
5553
6041
  ["set-option", "-t", opts.name, "@kobe_worktree", opts.cwd],
5554
- ...opts.vendor ? [["set-option", "-t", opts.name, "@kobe_vendor", opts.vendor]] : []
6042
+ ...opts.vendor ? [["set-option", "-t", opts.name, "@kobe_vendor", opts.vendor]] : [],
6043
+ ...remote ? [["set-option", "-t", opts.name, REMOTE_KEY_OPTION, opts.remoteKey]] : []
5555
6044
  ]);
5556
6045
  await buildPanesAround(pane0, {
5557
6046
  cwd: opts.cwd,
@@ -5595,7 +6084,7 @@ async function ensureSessionImpl(opts) {
5595
6084
  }
5596
6085
  return true;
5597
6086
  }
5598
- async function relaunchEngineInAllWindows(session, cwd, command) {
6087
+ async function relaunchEngineInAllWindows(session, cwd, command, remoteKey) {
5599
6088
  const { code, stdout } = await runTmuxCapturing([
5600
6089
  "list-panes",
5601
6090
  "-s",
@@ -5610,9 +6099,9 @@ async function relaunchEngineInAllWindows(session, cwd, command) {
5610
6099
  `).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "claude").map(([id]) => id?.trim()).filter((id) => !!id);
5611
6100
  if (enginePanes.length === 0)
5612
6101
  return false;
5613
- const cmd = keepAlive(shellQuoteArgv(command));
6102
+ const cmd = keepAlive(wrapEngineLaunch(shellQuoteArgv(command), remoteKey, cwd));
5614
6103
  for (const pane of enginePanes) {
5615
- await runTmux(["respawn-pane", "-k", "-c", cwd, "-t", pane, cmd]);
6104
+ await runTmux(["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]);
5616
6105
  }
5617
6106
  return true;
5618
6107
  }
@@ -5676,7 +6165,7 @@ async function healKobePaneVersions(session, cwd, taskId, vendor) {
5676
6165
  "-t",
5677
6166
  tasksPane.paneId,
5678
6167
  "-c",
5679
- cwd,
6168
+ localSpawnCwd(cwd),
5680
6169
  keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
5681
6170
  ], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
5682
6171
  }
@@ -5687,7 +6176,7 @@ async function healKobePaneVersions(session, cwd, taskId, vendor) {
5687
6176
  "-t",
5688
6177
  opsPane.paneId,
5689
6178
  "-c",
5690
- cwd,
6179
+ localSpawnCwd(cwd),
5691
6180
  keepAlive(envPrefix + opsPaneCommand({
5692
6181
  cwd,
5693
6182
  taskId,
@@ -5736,7 +6225,7 @@ async function refreshKobeWorkspacePanes(session) {
5736
6225
  "-t",
5737
6226
  tasksPane.paneId,
5738
6227
  "-c",
5739
- cwd,
6228
+ localSpawnCwd(cwd),
5740
6229
  keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
5741
6230
  ], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
5742
6231
  }
@@ -5747,7 +6236,7 @@ async function refreshKobeWorkspacePanes(session) {
5747
6236
  "-t",
5748
6237
  opsPane.paneId,
5749
6238
  "-c",
5750
- cwd,
6239
+ localSpawnCwd(cwd),
5751
6240
  keepAlive(envPrefix + opsPaneCommand({
5752
6241
  cwd,
5753
6242
  taskId,
@@ -5791,7 +6280,7 @@ async function buildPanesAround(claudePane, args) {
5791
6280
  "-l",
5792
6281
  `${TASKS_PANE_WIDTH}`,
5793
6282
  "-c",
5794
- args.cwd,
6283
+ localSpawnCwd(args.cwd),
5795
6284
  "-P",
5796
6285
  "-F",
5797
6286
  "tasks=#{pane_id}",
@@ -5805,13 +6294,13 @@ async function buildPanesAround(claudePane, args) {
5805
6294
  "-l",
5806
6295
  `${100 - CLAUDE_PANE_PERCENT}%`,
5807
6296
  "-c",
5808
- args.cwd,
6297
+ localSpawnCwd(args.cwd),
5809
6298
  "-P",
5810
6299
  "-F",
5811
6300
  "ops=#{pane_id}",
5812
6301
  opsCmd
5813
6302
  ],
5814
- ["split-window", "-v", "-l", `${100 - OPS_PANE_PERCENT}%`, "-c", args.cwd],
6303
+ ["split-window", "-v", "-l", `${100 - OPS_PANE_PERCENT}%`, "-c", localSpawnCwd(args.cwd)],
5815
6304
  ["select-pane", "-t", claudePane]
5816
6305
  ]);
5817
6306
  const ids = Object.fromEntries(stdout.split(`
@@ -5826,9 +6315,15 @@ async function buildPanesAround(claudePane, args) {
5826
6315
  async function newChatTab(session, vendorOverride) {
5827
6316
  if (!await sessionExists(session))
5828
6317
  return;
5829
- const sessionOptions = await getSessionOptions(session, ["@kobe_worktree", "@kobe_task", "@kobe_vendor"]);
6318
+ const sessionOptions = await getSessionOptions(session, [
6319
+ "@kobe_worktree",
6320
+ "@kobe_task",
6321
+ "@kobe_vendor",
6322
+ REMOTE_KEY_OPTION
6323
+ ]);
5830
6324
  const cwd = sessionOptions["@kobe_worktree"] || process.cwd();
5831
6325
  const taskId = sessionOptions["@kobe_task"] || undefined;
6326
+ const remoteKey = sessionOptions[REMOTE_KEY_OPTION] || undefined;
5832
6327
  const vendor = vendorOverride ?? sessionOptions["@kobe_vendor"];
5833
6328
  if (vendorOverride)
5834
6329
  await rememberSessionVendor(session, taskId, vendorOverride);
@@ -5840,11 +6335,11 @@ async function newChatTab(session, vendorOverride) {
5840
6335
  "-t",
5841
6336
  `=${session}`,
5842
6337
  "-c",
5843
- cwd,
6338
+ localSpawnCwd(cwd),
5844
6339
  "-P",
5845
6340
  "-F",
5846
6341
  "#{pane_id}",
5847
- keepAlive(shellQuoteArgv(launch.argv))
6342
+ keepAlive(wrapEngineLaunch(shellQuoteArgv(launch.argv), remoteKey, cwd))
5848
6343
  ]);
5849
6344
  const claudePane = r.stdout.trim();
5850
6345
  if (!claudePane)
@@ -5861,7 +6356,7 @@ async function openSettingsTab(session) {
5861
6356
  const inv = kobeCliInvocation();
5862
6357
  const envPrefix = inheritedEnvPrefix();
5863
6358
  const command = `${envPrefix}${inv.map(shellQuote).join(" ")} settings`;
5864
- await newWindow(session, { cwd, command, name: "settings" });
6359
+ await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "settings" });
5865
6360
  }
5866
6361
  async function openNewTaskTab(session, defaultRepo) {
5867
6362
  if (!await sessionExists(session))
@@ -5872,7 +6367,7 @@ async function openNewTaskTab(session, defaultRepo) {
5872
6367
  const envPrefix = inheritedEnvPrefix();
5873
6368
  const repoArg = defaultRepo ? ` --repo ${shellQuote(defaultRepo)}` : "";
5874
6369
  const command = `${envPrefix}${inv.map(shellQuote).join(" ")} new-task${repoArg}`;
5875
- await newWindow(session, { cwd, command, name: "new task" });
6370
+ await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "new task" });
5876
6371
  }
5877
6372
  async function openUpdateTab(session) {
5878
6373
  if (!await sessionExists(session))
@@ -5882,7 +6377,7 @@ async function openUpdateTab(session) {
5882
6377
  const inv = kobeCliInvocation();
5883
6378
  const envPrefix = inheritedEnvPrefix();
5884
6379
  const command = `${envPrefix}${updatePageCommand({ cliInvocation: inv })}`;
5885
- await newWindow(session, { cwd, command, name: "update" });
6380
+ await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "update" });
5886
6381
  }
5887
6382
  async function rememberSessionVendor(session, taskId, vendor) {
5888
6383
  await setSessionOption(session, "@kobe_vendor", vendor);
@@ -5917,13 +6412,15 @@ async function quickCreate(session) {
5917
6412
  const inv = kobeCliInvocation();
5918
6413
  const envPrefix = inheritedEnvPrefix();
5919
6414
  const command = `${envPrefix}${inv.map(shellQuote).join(" ")} quick-task --session ${shellQuote(session)}`;
5920
- await newWindow(session, { cwd, command, name: "quick task" });
6415
+ await newWindow(session, { cwd: localSpawnCwd(cwd), command, name: "quick task" });
5921
6416
  }
5922
- var CHAT_TAB_SWITCH_BINDINGS, CHAT_TAB_CLOSE_BINDING, CHAT_TAB_RENAME_BINDING, CHAT_TAB_ENGINE_PROMPT, CHAT_TAB_CHOOSE_ENGINE_BINDINGS, KOBE_STATUS_RIGHT = "#[fg=brightblack]^h tasks ^q detach ^t tab ", CHAT_TAB_STATE_OPTION = "@kobe_tab_state", PANE_VERSION_OPTION = "@kobe_pane_version", CHAT_TAB_STATUS_FORMAT = "#{?#{==:#{@kobe_tab_state},running},\u25CF,#{?#{==:#{@kobe_tab_state},done},\u2713,#{?#{==:#{@kobe_tab_state},error},!,#{?#{==:#{@kobe_tab_state},unknown},?,\u25CB}}}} #I:#W", CHAT_TAB_STATUS_CURRENT_FORMAT, ensureSessionLocks;
6417
+ var CHAT_TAB_SWITCH_BINDINGS, CHAT_TAB_CLOSE_BINDING, CHAT_TAB_RENAME_BINDING, CHAT_TAB_ENGINE_PROMPT, REMOTE_KEY_OPTION = "@kobe_remote", CHAT_TAB_CHOOSE_ENGINE_BINDINGS, KOBE_STATUS_RIGHT = "#[fg=brightblack]^h tasks ^q detach ^t tab ", CHAT_TAB_STATE_OPTION = "@kobe_tab_state", PANE_VERSION_OPTION = "@kobe_pane_version", CHAT_TAB_STATUS_FORMAT = "#{?#{==:#{@kobe_tab_state},running},\u25CF,#{?#{==:#{@kobe_tab_state},done},\u2713,#{?#{==:#{@kobe_tab_state},error},!,#{?#{==:#{@kobe_tab_state},unknown},?,\u25CB}}}} #I:#W", CHAT_TAB_STATUS_CURRENT_FORMAT, ensureSessionLocks;
5923
6418
  var init_tmux = __esm(() => {
5924
6419
  init_invocation();
5925
6420
  init_interactive_command();
5926
6421
  init_env();
6422
+ init_resolve();
6423
+ init_repos();
5927
6424
  init_client2();
5928
6425
  init_prompt_delivery();
5929
6426
  init_vendor();
@@ -6191,7 +6688,7 @@ var init_notes = __esm(() => {
6191
6688
  });
6192
6689
 
6193
6690
  // ../kobe-daemon/src/daemon/web.ts
6194
- import { existsSync as existsSync2 } from "fs";
6691
+ import { existsSync as existsSync3 } from "fs";
6195
6692
  import { join as join6, normalize as normalize2 } from "path";
6196
6693
  function sseResponse(bus, snapshot) {
6197
6694
  let unsubscribe = null;
@@ -6321,7 +6818,7 @@ async function staticResponse(pathname, staticDir) {
6321
6818
  const resolved = normalize2(join6(staticDir, rel));
6322
6819
  if (!resolved.startsWith(staticDir))
6323
6820
  return new Response("forbidden", { status: 403 });
6324
- const file = Bun.file(existsSync2(resolved) ? resolved : join6(staticDir, "index.html"));
6821
+ const file = Bun.file(existsSync3(resolved) ? resolved : join6(staticDir, "index.html"));
6325
6822
  if (!await file.exists()) {
6326
6823
  return new Response("kobe web assets not built \u2014 run `bun --filter kobe-web build`", { status: 503 });
6327
6824
  }
@@ -7018,14 +7515,14 @@ __export(exports_daemon_process, {
7018
7515
  connectIfRunning: () => connectIfRunning
7019
7516
  });
7020
7517
  import { spawn } from "child_process";
7021
- import { closeSync, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync } from "fs";
7518
+ import { closeSync, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync } from "fs";
7022
7519
  import { dirname as dirname5, resolve as resolve2 } from "path";
7023
7520
  import { fileURLToPath as fileURLToPath2 } from "url";
7024
7521
  function spawnDetachedDaemon(command, args, env, logPath) {
7025
7522
  let stdio = "ignore";
7026
7523
  let logFd;
7027
7524
  try {
7028
- mkdirSync2(dirname5(logPath), { recursive: true });
7525
+ mkdirSync3(dirname5(logPath), { recursive: true });
7029
7526
  logFd = openSync(logPath, "a");
7030
7527
  stdio = ["ignore", logFd, logFd];
7031
7528
  } catch {
@@ -7069,14 +7566,14 @@ async function connectIfRunning() {
7069
7566
  return client;
7070
7567
  }
7071
7568
  async function testDaemonResponds(socketPath, timeoutMs = DAEMON_HELLO_TIMEOUT_MS) {
7072
- const probe = new KobeDaemonClient(socketPath);
7569
+ const probe2 = new KobeDaemonClient(socketPath);
7073
7570
  try {
7074
- await probe.connect();
7571
+ await probe2.connect();
7075
7572
  } catch {
7076
- probe.close();
7573
+ probe2.close();
7077
7574
  return false;
7078
7575
  }
7079
- const replied = probe.request("hello", { protocolVersion: DAEMON_PROTOCOL_VERSION }).then(() => true).catch(() => true);
7576
+ const replied = probe2.request("hello", { protocolVersion: DAEMON_PROTOCOL_VERSION }).then(() => true).catch(() => true);
7080
7577
  let timer;
7081
7578
  const timedOut = new Promise((resolve3) => {
7082
7579
  timer = setTimeout(() => resolve3(false), timeoutMs);
@@ -7084,7 +7581,7 @@ async function testDaemonResponds(socketPath, timeoutMs = DAEMON_HELLO_TIMEOUT_M
7084
7581
  const alive = await Promise.race([replied, timedOut]);
7085
7582
  if (timer)
7086
7583
  clearTimeout(timer);
7087
- probe.close();
7584
+ probe2.close();
7088
7585
  return alive;
7089
7586
  }
7090
7587
  function resolveKobeSpawn(subcommand) {
@@ -7098,7 +7595,7 @@ function resolveKobeSpawn(subcommand) {
7098
7595
  resolve2(dir, "../../../kobe/src/cli/index.ts"),
7099
7596
  resolve2(dir, "../cli/index.js")
7100
7597
  ];
7101
- const entry = candidates.find((candidate) => existsSync3(candidate));
7598
+ const entry = candidates.find((candidate) => existsSync4(candidate));
7102
7599
  if (entry)
7103
7600
  return [process.execPath, entry, ...subcommand];
7104
7601
  throw new Error(`kobe: could not locate kobe entry near ${dir}; checked ${candidates.join(", ")}`);
@@ -7117,7 +7614,7 @@ var exports_repo_cmd = {};
7117
7614
  __export(exports_repo_cmd, {
7118
7615
  runRepoSubcommand: () => runRepoSubcommand
7119
7616
  });
7120
- import { readFileSync as readFileSync3 } from "fs";
7617
+ import { readFileSync as readFileSync4 } from "fs";
7121
7618
  import { resolve as resolve3 } from "path";
7122
7619
  function usageError(message) {
7123
7620
  process.stderr.write(`kobe repo: ${message}
@@ -7128,7 +7625,7 @@ ${REPO_USAGE}
7128
7625
  }
7129
7626
  function readArgFile(path7) {
7130
7627
  try {
7131
- return readFileSync3(resolve3(process.cwd(), path7), "utf8");
7628
+ return readFileSync4(resolve3(process.cwd(), path7), "utf8");
7132
7629
  } catch (err) {
7133
7630
  usageError(`cannot read ${path7}: ${err instanceof Error ? err.message : String(err)}`);
7134
7631
  }
@@ -7190,14 +7687,14 @@ async function runRepoSubcommand(args) {
7190
7687
  return;
7191
7688
  }
7192
7689
  const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
7193
- const { existsSync: existsSync4 } = await import("fs");
7690
+ const { existsSync: existsSync5 } = await import("fs");
7194
7691
  const { join: join7 } = await import("path");
7195
7692
  if (verb === "show") {
7196
7693
  const [pathArg] = rest.filter((a) => !a.startsWith("-"));
7197
7694
  const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
7198
7695
  const override = getRepoInitOverride2(repo);
7199
- const hasFileScript = existsSync4(join7(repo, ".kobe", "init.sh"));
7200
- const hasFilePrompt = existsSync4(join7(repo, ".kobe", "init-prompt.md"));
7696
+ const hasFileScript = existsSync5(join7(repo, ".kobe", "init.sh"));
7697
+ const hasFilePrompt = existsSync5(join7(repo, ".kobe", "init-prompt.md"));
7201
7698
  console.log(`repo: ${repo}`);
7202
7699
  console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
7203
7700
  console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
@@ -7264,7 +7761,7 @@ var init_repo_cmd = __esm(() => {
7264
7761
  });
7265
7762
 
7266
7763
  // src/lib/feedback.ts
7267
- import { spawnSync as spawnSync3 } from "child_process";
7764
+ import { spawnSync as spawnSync4 } from "child_process";
7268
7765
  function parseRepoSlug(slug) {
7269
7766
  const [owner, name] = slug.split("/");
7270
7767
  if (!owner || !name)
@@ -7319,7 +7816,7 @@ function submitFeedback(input, deps = {}) {
7319
7816
  throw new Error("package repository is not a GitHub repository");
7320
7817
  const { owner, name } = parseRepoSlug(slug);
7321
7818
  const categorySlug = input.categorySlug?.trim() || DEFAULT_FEEDBACK_CATEGORY_SLUG;
7322
- const io = { spawn: deps.spawn ?? spawnSync3 };
7819
+ const io = { spawn: deps.spawn ?? spawnSync4 };
7323
7820
  const categoryData = runGhGraphql(DISCUSSION_CATEGORY_QUERY, { owner, name }, io);
7324
7821
  const repository = categoryData.repository;
7325
7822
  const repositoryId = repository?.id;
@@ -7378,12 +7875,12 @@ __export(exports_worktree_changes, {
7378
7875
  readWorktreeChanges: () => readWorktreeChanges,
7379
7876
  parsePorcelain: () => parsePorcelain2
7380
7877
  });
7381
- import { spawnSync as spawnSync4 } from "child_process";
7878
+ import { spawnSync as spawnSync5 } from "child_process";
7382
7879
  function readWorktreeChanges(worktreePath) {
7383
7880
  if (!worktreePath)
7384
7881
  return ZERO;
7385
7882
  try {
7386
- const out = spawnSync4("git", ["status", "--porcelain=v1"], {
7883
+ const out = spawnSync5("git", ["status", "--porcelain=v1"], {
7387
7884
  cwd: worktreePath,
7388
7885
  encoding: "utf8",
7389
7886
  stdio: ["ignore", "pipe", "pipe"],
@@ -7713,7 +8210,7 @@ function emit(value, pretty) {
7713
8210
  process.stdout.write(`${text}
7714
8211
  `);
7715
8212
  }
7716
- function fail(message, code, exitCode = 1) {
8213
+ function fail2(message, code, exitCode = 1) {
7717
8214
  process.stderr.write(`${JSON.stringify({ error: { message, code } })}
7718
8215
  `);
7719
8216
  process.exit(exitCode);
@@ -7731,6 +8228,7 @@ async function deliverPrompt(client, target, prompt) {
7731
8228
  if (!existed) {
7732
8229
  const { ensureSession: ensureSession2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
7733
8230
  const { resolveRepoInit: resolveRepoInit2 } = await Promise.resolve().then(() => (init_repo_init(), exports_repo_init));
8231
+ const { isRemoteRepoKey: isRemoteRepoKey2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
7734
8232
  const init = resolveRepoInit2(target.repo ?? "", worktree);
7735
8233
  const ok = await ensureSession2({
7736
8234
  name: session,
@@ -7738,6 +8236,7 @@ async function deliverPrompt(client, target, prompt) {
7738
8236
  command: interactiveEngineCommand(target.vendor),
7739
8237
  taskId: target.id,
7740
8238
  vendor: target.vendor,
8239
+ remoteKey: target.repo && isRemoteRepoKey2(target.repo) ? target.repo : undefined,
7741
8240
  initScript: init.initScript
7742
8241
  });
7743
8242
  if (!ok)
@@ -7949,14 +8448,14 @@ async function runApiSubcommand(argv) {
7949
8448
  const [verbName, ...rest] = argv;
7950
8449
  if (!verbName || verbName === "--help" || verbName === "-h" || verbName === "help") {
7951
8450
  if (!verbName)
7952
- fail(apiUsage(), "MISSING_VERB", 2);
8451
+ fail2(apiUsage(), "MISSING_VERB", 2);
7953
8452
  process.stdout.write(`${apiUsage()}
7954
8453
  `);
7955
8454
  return;
7956
8455
  }
7957
8456
  const verb = findVerb(verbName);
7958
8457
  if (!verb)
7959
- fail(`unknown verb: ${verbName}
8458
+ fail2(`unknown verb: ${verbName}
7960
8459
  ${apiUsage()}`, "BAD_VERB", 2);
7961
8460
  const booleanFlags = new Set(verb.flags.filter((f) => f.type === "bool").map((f) => f.name));
7962
8461
  let parsed;
@@ -7964,8 +8463,8 @@ ${apiUsage()}`, "BAD_VERB", 2);
7964
8463
  parsed = parseFlags(rest, booleanFlags);
7965
8464
  } catch (err) {
7966
8465
  if (err instanceof ApiError)
7967
- fail(err.message, err.code, 2);
7968
- fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
8466
+ fail2(err.message, err.code, 2);
8467
+ fail2(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
7969
8468
  }
7970
8469
  if (parsed.help) {
7971
8470
  process.stdout.write(`${verbHelp(verb)}
@@ -7976,15 +8475,15 @@ ${apiUsage()}`, "BAD_VERB", 2);
7976
8475
  validateAgainstSpec(verb, parsed.flags);
7977
8476
  } catch (err) {
7978
8477
  if (err instanceof ApiError)
7979
- fail(err.message, err.code, 2);
7980
- fail(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
8478
+ fail2(err.message, err.code, 2);
8479
+ fail2(err instanceof Error ? err.message : String(err), "BAD_FLAG", 2);
7981
8480
  }
7982
8481
  let client = null;
7983
8482
  if (!verb.offline) {
7984
8483
  try {
7985
8484
  client = await connectOrStartDaemon();
7986
8485
  } catch (err) {
7987
- fail(`could not reach or start the kobe daemon: ${err instanceof Error ? err.message : String(err)}`, "BAD_DAEMON", 2);
8486
+ fail2(`could not reach or start the kobe daemon: ${err instanceof Error ? err.message : String(err)}`, "BAD_DAEMON", 2);
7988
8487
  }
7989
8488
  }
7990
8489
  try {
@@ -7992,8 +8491,8 @@ ${apiUsage()}`, "BAD_VERB", 2);
7992
8491
  emit(result, parsed.pretty);
7993
8492
  } catch (err) {
7994
8493
  if (err instanceof ApiError)
7995
- fail(err.message, err.code, 1);
7996
- fail(err instanceof Error ? err.message : String(err), "RPC_ERROR", 1);
8494
+ fail2(err.message, err.code, 1);
8495
+ fail2(err instanceof Error ? err.message : String(err), "RPC_ERROR", 1);
7997
8496
  } finally {
7998
8497
  client?.close();
7999
8498
  }
@@ -8277,7 +8776,7 @@ __export(exports_update, {
8277
8776
  runUpdateSubcommand: () => runUpdateSubcommand,
8278
8777
  parseUpdateArgs: () => parseUpdateArgs
8279
8778
  });
8280
- import { spawnSync as spawnSync5 } from "child_process";
8779
+ import { spawnSync as spawnSync6 } from "child_process";
8281
8780
  function updatePlan() {
8282
8781
  return {
8283
8782
  command: "sh",
@@ -8327,7 +8826,7 @@ function printUsage(out) {
8327
8826
  }
8328
8827
  async function runUpdateSubcommand(args, deps) {
8329
8828
  const io = {
8330
- spawn: deps?.spawn ?? spawnSync5,
8829
+ spawn: deps?.spawn ?? spawnSync6,
8331
8830
  stdout: deps?.stdout ?? process.stdout,
8332
8831
  stderr: deps?.stderr ?? process.stderr,
8333
8832
  exit: deps?.exit ?? ((code) => process.exit(code))
@@ -8407,7 +8906,7 @@ function validateTheme(value) {
8407
8906
  var init_schema = () => {};
8408
8907
 
8409
8908
  // src/tui/context/theme/loader.ts
8410
- import { readFileSync as readFileSync4, readdirSync } from "fs";
8909
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
8411
8910
  import { join as join7 } from "path";
8412
8911
  function userThemesDir() {
8413
8912
  return join7(kobeStateDir(), "themes");
@@ -8416,7 +8915,7 @@ function loadUserThemes() {
8416
8915
  const dir = userThemesDir();
8417
8916
  let entries;
8418
8917
  try {
8419
- entries = readdirSync(dir);
8918
+ entries = readdirSync2(dir);
8420
8919
  } catch {
8421
8920
  return [];
8422
8921
  }
@@ -8427,7 +8926,7 @@ function loadUserThemes() {
8427
8926
  const path7 = join7(dir, file);
8428
8927
  let parsed;
8429
8928
  try {
8430
- const text = readFileSync4(path7, "utf8");
8929
+ const text = readFileSync5(path7, "utf8");
8431
8930
  parsed = JSON.parse(text);
8432
8931
  } catch (err) {
8433
8932
  const msg = err instanceof Error ? err.message : String(err);
@@ -8454,9 +8953,9 @@ var exports_theme = {};
8454
8953
  __export(exports_theme, {
8455
8954
  runThemeSubcommand: () => runThemeSubcommand
8456
8955
  });
8457
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
8956
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
8458
8957
  import { basename as basename3, join as join8, resolve as resolve5 } from "path";
8459
- function fail2(message) {
8958
+ function fail3(message) {
8460
8959
  process.stderr.write(`kobe theme: ${message}
8461
8960
  `);
8462
8961
  process.exit(1);
@@ -8477,7 +8976,7 @@ function listThemes() {
8477
8976
  const dir = userThemesDir();
8478
8977
  let userFiles = [];
8479
8978
  try {
8480
- userFiles = readdirSync2(dir).filter((f) => f.endsWith(".json")).sort();
8979
+ userFiles = readdirSync3(dir).filter((f) => f.endsWith(".json")).sort();
8481
8980
  } catch {}
8482
8981
  lines.push("");
8483
8982
  lines.push(`user (${dir}):`);
@@ -8501,10 +9000,10 @@ async function readSource(source) {
8501
9000
  try {
8502
9001
  res = await fetch(source);
8503
9002
  } catch (err) {
8504
- fail2(`failed to fetch ${source}: ${err instanceof Error ? err.message : String(err)}`);
9003
+ fail3(`failed to fetch ${source}: ${err instanceof Error ? err.message : String(err)}`);
8505
9004
  }
8506
9005
  if (!res.ok) {
8507
- fail2(`failed to fetch ${source}: HTTP ${res.status} ${res.statusText}`);
9006
+ fail3(`failed to fetch ${source}: HTTP ${res.status} ${res.statusText}`);
8508
9007
  }
8509
9008
  const text2 = await res.text();
8510
9009
  const cleanPath = source.split(/[?#]/)[0] ?? source;
@@ -8515,9 +9014,9 @@ async function readSource(source) {
8515
9014
  const abs = resolve5(process.cwd(), source);
8516
9015
  let text;
8517
9016
  try {
8518
- text = readFileSync5(abs, "utf8");
9017
+ text = readFileSync6(abs, "utf8");
8519
9018
  } catch (err) {
8520
- fail2(`failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}`);
9019
+ fail3(`failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}`);
8521
9020
  }
8522
9021
  const file = basename3(abs);
8523
9022
  const defaultName = file.endsWith(".json") ? file.slice(0, -".json".length) : file;
@@ -8564,21 +9063,21 @@ async function addTheme(args) {
8564
9063
  try {
8565
9064
  parsed = JSON.parse(text);
8566
9065
  } catch (err) {
8567
- fail2(`source is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
9066
+ fail3(`source is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
8568
9067
  }
8569
9068
  const result = validateTheme(parsed);
8570
9069
  if (!result.ok) {
8571
- fail2(`source is not a valid kobe theme: ${result.reason}`);
9070
+ fail3(`source is not a valid kobe theme: ${result.reason}`);
8572
9071
  }
8573
9072
  const name = opts.name ?? defaultName;
8574
9073
  if (!name || !/^[a-zA-Z0-9._-]+$/.test(name)) {
8575
- fail2(`invalid theme name "${name}" (use letters, digits, '.', '_', '-')`);
9074
+ fail3(`invalid theme name "${name}" (use letters, digits, '.', '_', '-')`);
8576
9075
  }
8577
9076
  const dir = userThemesDir();
8578
- mkdirSync3(dir, { recursive: true });
9077
+ mkdirSync4(dir, { recursive: true });
8579
9078
  const dest = join8(dir, `${name}.json`);
8580
- if (existsSync4(dest) && !opts.force) {
8581
- fail2(`${dest} already exists (pass --force to overwrite)`);
9079
+ if (existsSync5(dest) && !opts.force) {
9080
+ fail3(`${dest} already exists (pass --force to overwrite)`);
8582
9081
  }
8583
9082
  writeFileSync2(dest, `${JSON.stringify(result.theme, null, 2)}
8584
9083
  `, "utf8");
@@ -8592,11 +9091,11 @@ function removeTheme(args) {
8592
9091
  if (args.length > 1)
8593
9092
  failUsage(`unexpected extra arguments after "${name}"`);
8594
9093
  if (BUNDLED_NAMES.includes(name)) {
8595
- fail2(`"${name}" is a built-in theme and cannot be removed`);
9094
+ fail3(`"${name}" is a built-in theme and cannot be removed`);
8596
9095
  }
8597
9096
  const dest = join8(userThemesDir(), `${name}.json`);
8598
- if (!existsSync4(dest)) {
8599
- fail2(`no user theme named "${name}" (looked for ${dest})`);
9097
+ if (!existsSync5(dest)) {
9098
+ fail3(`no user theme named "${name}" (looked for ${dest})`);
8600
9099
  }
8601
9100
  unlinkSync(dest);
8602
9101
  process.stdout.write(`removed theme "${name}" (${dest})
@@ -8664,7 +9163,7 @@ __export(exports_feedback_cmd, {
8664
9163
  runFeedbackSubcommand: () => runFeedbackSubcommand,
8665
9164
  parseFeedbackArgs: () => parseFeedbackArgs
8666
9165
  });
8667
- import { readFileSync as readFileSync6 } from "fs";
9166
+ import { readFileSync as readFileSync7 } from "fs";
8668
9167
  function usageError2(message) {
8669
9168
  process.stderr.write(`kobe feedback: ${message}
8670
9169
 
@@ -8674,8 +9173,8 @@ ${FEEDBACK_USAGE}
8674
9173
  }
8675
9174
  function readBodyFile(path7) {
8676
9175
  if (path7 === "-")
8677
- return readFileSync6(0, "utf8");
8678
- return readFileSync6(path7, "utf8");
9176
+ return readFileSync7(0, "utf8");
9177
+ return readFileSync7(path7, "utf8");
8679
9178
  }
8680
9179
  function parseFeedbackArgs(args) {
8681
9180
  const parsed = { help: false };
@@ -8874,7 +9373,7 @@ var init_daemon_cmd = __esm(() => {
8874
9373
  });
8875
9374
 
8876
9375
  // src/lib/skill-install.ts
8877
- import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
9376
+ import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
8878
9377
  import { homedir as homedir10 } from "os";
8879
9378
  import { join as join9 } from "path";
8880
9379
  function npxSkillsArgv(opts = {}) {
@@ -8893,13 +9392,13 @@ function parseSkillVersion(content) {
8893
9392
  return m ? Number.parseInt(m[1], 10) : null;
8894
9393
  }
8895
9394
  function kobeSkillState(opts) {
8896
- const path7 = kobeSkillPaths(opts).find((p) => existsSync5(p));
9395
+ const path7 = kobeSkillPaths(opts).find((p) => existsSync6(p));
8897
9396
  if (!path7) {
8898
9397
  return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
8899
9398
  }
8900
9399
  let installedVersion = null;
8901
9400
  try {
8902
- installedVersion = parseSkillVersion(readFileSync7(path7, "utf8"));
9401
+ installedVersion = parseSkillVersion(readFileSync8(path7, "utf8"));
8903
9402
  } catch {
8904
9403
  installedVersion = null;
8905
9404
  }
@@ -8945,10 +9444,10 @@ __export(exports_maintenance, {
8945
9444
  runReloadSubcommand: () => runReloadSubcommand,
8946
9445
  runDoctorSubcommand: () => runDoctorSubcommand
8947
9446
  });
8948
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync } from "fs";
9447
+ import { existsSync as existsSync7, readFileSync as readFileSync9, statSync } from "fs";
8949
9448
  import { unlink as unlink6 } from "fs/promises";
8950
9449
  import { join as join10 } from "path";
8951
- import { createInterface } from "readline";
9450
+ import { createInterface as createInterface2 } from "readline";
8952
9451
  function isProcessAlive2(pid) {
8953
9452
  try {
8954
9453
  process.kill(pid, 0);
@@ -8994,7 +9493,7 @@ function describeFile(path7) {
8994
9493
  }
8995
9494
  function taskCount(tasksPath) {
8996
9495
  try {
8997
- const parsed = JSON.parse(readFileSync8(tasksPath, "utf8"));
9496
+ const parsed = JSON.parse(readFileSync9(tasksPath, "utf8"));
8998
9497
  return Array.isArray(parsed.tasks) ? parsed.tasks.length : null;
8999
9498
  } catch {
9000
9499
  return null;
@@ -9002,7 +9501,7 @@ function taskCount(tasksPath) {
9002
9501
  }
9003
9502
  function tailFile(path7, n) {
9004
9503
  try {
9005
- const lines = readFileSync8(path7, "utf8").split(`
9504
+ const lines = readFileSync9(path7, "utf8").split(`
9006
9505
  `).filter((l) => l.trim().length > 0);
9007
9506
  return lines.slice(-n).join(`
9008
9507
  `);
@@ -9072,7 +9571,7 @@ async function runDoctorSubcommand(argv = []) {
9072
9571
  } else {
9073
9572
  out.push("daemon: \u2717 not running (no pidfile)");
9074
9573
  }
9075
- if (existsSync6(socketPath))
9574
+ if (existsSync7(socketPath))
9076
9575
  out.push(` orphan socket file present: ${socketPath}`);
9077
9576
  const tail = tailFile(logPath, 8);
9078
9577
  if (tail) {
@@ -9109,7 +9608,7 @@ async function runDoctorSubcommand(argv = []) {
9109
9608
  `));
9110
9609
  }
9111
9610
  async function confirmTty(prompt) {
9112
- const rl = createInterface({ input: process.stdin, output: process.stdout });
9611
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9113
9612
  try {
9114
9613
  const answer = await new Promise((resolve6) => rl.question(prompt, resolve6));
9115
9614
  return /^y(es)?$/i.test(answer.trim());
@@ -9272,7 +9771,7 @@ var exports_web_cmd = {};
9272
9771
  __export(exports_web_cmd, {
9273
9772
  runWebSubcommand: () => runWebSubcommand
9274
9773
  });
9275
- import { existsSync as existsSync7 } from "fs";
9774
+ import { existsSync as existsSync8 } from "fs";
9276
9775
  import { resolve as resolve6 } from "path";
9277
9776
  import { fileURLToPath as fileURLToPath3 } from "url";
9278
9777
  function resolveStaticDir() {
@@ -9282,7 +9781,7 @@ function resolveStaticDir() {
9282
9781
  resolve6(here, "../../web-ui")
9283
9782
  ];
9284
9783
  for (const dir of candidates) {
9285
- if (existsSync7(`${dir}/index.html`))
9784
+ if (existsSync8(`${dir}/index.html`))
9286
9785
  return dir;
9287
9786
  }
9288
9787
  return;
@@ -9294,7 +9793,7 @@ function resolvePtyServer() {
9294
9793
  resolve6(here, "../../web-ui/pty-server.mjs")
9295
9794
  ];
9296
9795
  for (const file of candidates) {
9297
- if (existsSync7(file))
9796
+ if (existsSync8(file))
9298
9797
  return file;
9299
9798
  }
9300
9799
  return;
@@ -9358,7 +9857,7 @@ async function startPtyServer(opts) {
9358
9857
  }
9359
9858
  async function runWebSubcommand(args) {
9360
9859
  if (args.includes("--help") || args.includes("-h")) {
9361
- process.stdout.write(USAGE);
9860
+ process.stdout.write(USAGE2);
9362
9861
  return;
9363
9862
  }
9364
9863
  let port = 5173;
@@ -9426,7 +9925,7 @@ async function runWebSubcommand(args) {
9426
9925
  process.exit(1);
9427
9926
  }
9428
9927
  }
9429
- var USAGE = `Usage: kobe web [options]
9928
+ var USAGE2 = `Usage: kobe web [options]
9430
9929
 
9431
9930
  Launch the kobe web UI on http://localhost:<port>.
9432
9931
 
@@ -11439,11 +11938,11 @@ var init_remote_orchestrator = __esm(() => {
11439
11938
  });
11440
11939
 
11441
11940
  // src/engine/claude-code-local/binary.ts
11442
- import { spawnSync as spawnSync6 } from "child_process";
11443
- import { existsSync as existsSync8, statSync as statSync2 } from "fs";
11941
+ import { spawnSync as spawnSync7 } from "child_process";
11942
+ import { existsSync as existsSync9, statSync as statSync2 } from "fs";
11444
11943
  import { homedir as homedir12 } from "os";
11445
11944
  import path7 from "path";
11446
- async function findClaudeBinary(deps = defaultDeps4) {
11945
+ async function findClaudeBinary(deps = defaultDeps5) {
11447
11946
  const checked = [];
11448
11947
  const tryPath = (p) => {
11449
11948
  if (!p)
@@ -11492,7 +11991,7 @@ async function findClaudeBinary(deps = defaultDeps4) {
11492
11991
  }
11493
11992
  throw new ClaudeBinaryNotFoundError(checked);
11494
11993
  }
11495
- var ClaudeBinaryNotFoundError, defaultDeps4;
11994
+ var ClaudeBinaryNotFoundError, defaultDeps5;
11496
11995
  var init_binary = __esm(() => {
11497
11996
  ClaudeBinaryNotFoundError = class ClaudeBinaryNotFoundError extends Error {
11498
11997
  checkedPaths;
@@ -11502,7 +12001,7 @@ var init_binary = __esm(() => {
11502
12001
  this.checkedPaths = checkedPaths;
11503
12002
  }
11504
12003
  };
11505
- defaultDeps4 = {
12004
+ defaultDeps5 = {
11506
12005
  fileExists(p) {
11507
12006
  try {
11508
12007
  return statSync2(p).isFile();
@@ -11518,7 +12017,7 @@ var init_binary = __esm(() => {
11518
12017
  },
11519
12018
  which(name) {
11520
12019
  const cmd = process.platform === "win32" ? "where" : "which";
11521
- const out = spawnSync6(cmd, [name], { encoding: "utf8" });
12020
+ const out = spawnSync7(cmd, [name], { encoding: "utf8" });
11522
12021
  if (out.status !== 0)
11523
12022
  return;
11524
12023
  const first = out.stdout.split(`
@@ -11527,7 +12026,7 @@ var init_binary = __esm(() => {
11527
12026
  return;
11528
12027
  if (first.startsWith("claude:") && first.includes("aliased to")) {
11529
12028
  const aliasTarget = first.split("aliased to")[1]?.trim();
11530
- return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
12029
+ return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
11531
12030
  }
11532
12031
  return first;
11533
12032
  },
@@ -11543,11 +12042,11 @@ var init_binary = __esm(() => {
11543
12042
  });
11544
12043
 
11545
12044
  // src/engine/codex-local/binary.ts
11546
- import { spawnSync as spawnSync7 } from "child_process";
11547
- import { existsSync as existsSync9, statSync as statSync3 } from "fs";
12045
+ import { spawnSync as spawnSync8 } from "child_process";
12046
+ import { existsSync as existsSync10, statSync as statSync3 } from "fs";
11548
12047
  import { homedir as homedir13 } from "os";
11549
12048
  import path8 from "path";
11550
- async function findCodexBinary(deps = defaultDeps5) {
12049
+ async function findCodexBinary(deps = defaultDeps6) {
11551
12050
  const checked = [];
11552
12051
  const tryPath = (p) => {
11553
12052
  if (!p)
@@ -11580,7 +12079,7 @@ async function findCodexBinary(deps = defaultDeps5) {
11580
12079
  }
11581
12080
  throw new CodexBinaryNotFoundError(checked);
11582
12081
  }
11583
- var CodexBinaryNotFoundError, defaultDeps5;
12082
+ var CodexBinaryNotFoundError, defaultDeps6;
11584
12083
  var init_binary2 = __esm(() => {
11585
12084
  CodexBinaryNotFoundError = class CodexBinaryNotFoundError extends Error {
11586
12085
  checkedPaths;
@@ -11590,7 +12089,7 @@ var init_binary2 = __esm(() => {
11590
12089
  this.checkedPaths = checkedPaths;
11591
12090
  }
11592
12091
  };
11593
- defaultDeps5 = {
12092
+ defaultDeps6 = {
11594
12093
  fileExists(p) {
11595
12094
  try {
11596
12095
  return statSync3(p).isFile();
@@ -11606,7 +12105,7 @@ var init_binary2 = __esm(() => {
11606
12105
  },
11607
12106
  which(name) {
11608
12107
  const cmd = process.platform === "win32" ? "where" : "which";
11609
- const out = spawnSync7(cmd, [name], { encoding: "utf8" });
12108
+ const out = spawnSync8(cmd, [name], { encoding: "utf8" });
11610
12109
  if (out.status !== 0)
11611
12110
  return;
11612
12111
  const first = out.stdout.split(`
@@ -11615,7 +12114,7 @@ var init_binary2 = __esm(() => {
11615
12114
  return;
11616
12115
  if (first.startsWith("codex:") && first.includes("aliased to")) {
11617
12116
  const aliasTarget = first.split("aliased to")[1]?.trim();
11618
- return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
12117
+ return aliasTarget && existsSync10(aliasTarget) ? aliasTarget : undefined;
11619
12118
  }
11620
12119
  return first;
11621
12120
  },
@@ -11631,11 +12130,11 @@ var init_binary2 = __esm(() => {
11631
12130
  });
11632
12131
 
11633
12132
  // src/engine/copilot-local/binary.ts
11634
- import { spawnSync as spawnSync8 } from "child_process";
11635
- import { existsSync as existsSync10, statSync as statSync4 } from "fs";
12133
+ import { spawnSync as spawnSync9 } from "child_process";
12134
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
11636
12135
  import { homedir as homedir14 } from "os";
11637
12136
  import path9 from "path";
11638
- async function findCopilotBinary(deps = defaultDeps6) {
12137
+ async function findCopilotBinary(deps = defaultDeps7) {
11639
12138
  const checked = [];
11640
12139
  const tryPath = (p) => {
11641
12140
  if (!p)
@@ -11692,7 +12191,7 @@ async function findCopilotBinary(deps = defaultDeps6) {
11692
12191
  }
11693
12192
  throw new CopilotBinaryNotFoundError(checked);
11694
12193
  }
11695
- var CopilotBinaryNotFoundError, defaultDeps6;
12194
+ var CopilotBinaryNotFoundError, defaultDeps7;
11696
12195
  var init_binary3 = __esm(() => {
11697
12196
  CopilotBinaryNotFoundError = class CopilotBinaryNotFoundError extends Error {
11698
12197
  checkedPaths;
@@ -11702,7 +12201,7 @@ var init_binary3 = __esm(() => {
11702
12201
  this.checkedPaths = checkedPaths;
11703
12202
  }
11704
12203
  };
11705
- defaultDeps6 = {
12204
+ defaultDeps7 = {
11706
12205
  fileExists(p) {
11707
12206
  try {
11708
12207
  return statSync4(p).isFile();
@@ -11718,7 +12217,7 @@ var init_binary3 = __esm(() => {
11718
12217
  },
11719
12218
  which(name) {
11720
12219
  const cmd = process.platform === "win32" ? "where" : "which";
11721
- const out = spawnSync8(cmd, [name], { encoding: "utf8" });
12220
+ const out = spawnSync9(cmd, [name], { encoding: "utf8" });
11722
12221
  if (out.status !== 0)
11723
12222
  return;
11724
12223
  const first = out.stdout.split(`
@@ -11727,7 +12226,7 @@ var init_binary3 = __esm(() => {
11727
12226
  return;
11728
12227
  if (first.startsWith("copilot:") && first.includes("aliased to")) {
11729
12228
  const aliasTarget = first.split("aliased to")[1]?.trim();
11730
- return aliasTarget && existsSync10(aliasTarget) ? aliasTarget : undefined;
12229
+ return aliasTarget && existsSync11(aliasTarget) ? aliasTarget : undefined;
11731
12230
  }
11732
12231
  return first;
11733
12232
  },
@@ -11738,7 +12237,7 @@ var init_binary3 = __esm(() => {
11738
12237
  });
11739
12238
 
11740
12239
  // src/engine/account-detect.ts
11741
- import { readFileSync as readFileSync9, statSync as statSync5 } from "fs";
12240
+ import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
11742
12241
  import { homedir as homedir15 } from "os";
11743
12242
  import path10 from "path";
11744
12243
  function claudeGlobalConfigPath(env, home) {
@@ -11774,9 +12273,9 @@ function decodeJwtPayload(jwt) {
11774
12273
  return null;
11775
12274
  }
11776
12275
  }
11777
- async function probeBinary(probe) {
12276
+ async function probeBinary(probe2) {
11778
12277
  try {
11779
- const p = await probe();
12278
+ const p = await probe2();
11780
12279
  return { found: true, path: p };
11781
12280
  } catch (err) {
11782
12281
  if (err instanceof ClaudeBinaryNotFoundError || err instanceof CodexBinaryNotFoundError || err instanceof CopilotBinaryNotFoundError) {
@@ -11785,20 +12284,20 @@ async function probeBinary(probe) {
11785
12284
  return { found: false, error: err instanceof Error ? err.message : String(err) };
11786
12285
  }
11787
12286
  }
11788
- async function detectAvailableVendors(deps = defaultDeps7) {
12287
+ async function detectAvailableVendors(deps = defaultDeps8) {
11789
12288
  const probes = [
11790
12289
  ["claude", () => deps.findClaudeBinary()],
11791
12290
  ["codex", () => deps.findCodexBinary()],
11792
12291
  ["copilot", () => deps.findCopilotBinary()]
11793
12292
  ];
11794
- const detected = await Promise.all(probes.map(async ([vendor, probe]) => (await probeBinary(probe)).found ? vendor : null));
12293
+ const detected = await Promise.all(probes.map(async ([vendor, probe2]) => (await probeBinary(probe2)).found ? vendor : null));
11795
12294
  return detected.filter((v) => v !== null);
11796
12295
  }
11797
- async function availableEngineIds(deps = defaultDeps7) {
12296
+ async function availableEngineIds(deps = defaultDeps8) {
11798
12297
  const builtins = await detectAvailableVendors(deps);
11799
12298
  return [...builtins, ...getCustomEngineIds()];
11800
12299
  }
11801
- async function detectClaudeAccount(deps = defaultDeps7) {
12300
+ async function detectClaudeAccount(deps = defaultDeps8) {
11802
12301
  const binary = await probeBinary(() => deps.findClaudeBinary());
11803
12302
  const configPath = claudeGlobalConfigPath(deps.env, deps.home());
11804
12303
  let raw;
@@ -11841,7 +12340,7 @@ async function detectClaudeAccount(deps = defaultDeps7) {
11841
12340
  }
11842
12341
  };
11843
12342
  }
11844
- async function detectCodexAccount(deps = defaultDeps7) {
12343
+ async function detectCodexAccount(deps = defaultDeps8) {
11845
12344
  const binary = await probeBinary(() => deps.findCodexBinary());
11846
12345
  const authPath = codexAuthPath(deps.env, deps.home());
11847
12346
  let raw;
@@ -11892,7 +12391,7 @@ async function detectCodexAccount(deps = defaultDeps7) {
11892
12391
  }
11893
12392
  return { binary, account: { kind: "none" } };
11894
12393
  }
11895
- async function detectCopilotAccount(deps = defaultDeps7) {
12394
+ async function detectCopilotAccount(deps = defaultDeps8) {
11896
12395
  const binary = await probeBinary(() => deps.findCopilotBinary());
11897
12396
  for (const source of ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]) {
11898
12397
  if (deps.env(source)?.trim())
@@ -11950,13 +12449,13 @@ function hasStringDeep(value, interestingKeys, depth = 0) {
11950
12449
  }
11951
12450
  return false;
11952
12451
  }
11953
- var defaultDeps7;
12452
+ var defaultDeps8;
11954
12453
  var init_account_detect = __esm(() => {
11955
12454
  init_repos();
11956
12455
  init_binary();
11957
12456
  init_binary2();
11958
12457
  init_binary3();
11959
- defaultDeps7 = {
12458
+ defaultDeps8 = {
11960
12459
  readFile(p) {
11961
12460
  try {
11962
12461
  statSync5(p);
@@ -11965,7 +12464,7 @@ var init_account_detect = __esm(() => {
11965
12464
  return null;
11966
12465
  throw err;
11967
12466
  }
11968
- return readFileSync9(p, "utf8");
12467
+ return readFileSync10(p, "utf8");
11969
12468
  },
11970
12469
  env(name) {
11971
12470
  return process.env[name];
@@ -12096,8 +12595,8 @@ function validateRepoPath(repo) {
12096
12595
  if (!stat4.isDirectory())
12097
12596
  return `not a directory: ${trimmed}`;
12098
12597
  try {
12099
- const { spawnSync: spawnSync9 } = __require("child_process");
12100
- const out = spawnSync9("git", ["rev-parse", "--git-dir"], {
12598
+ const { spawnSync: spawnSync10 } = __require("child_process");
12599
+ const out = spawnSync10("git", ["rev-parse", "--git-dir"], {
12101
12600
  cwd: trimmed,
12102
12601
  encoding: "utf-8",
12103
12602
  timeout: 2000,
@@ -12114,8 +12613,8 @@ function getCurrentBranch(repo) {
12114
12613
  if (!repo)
12115
12614
  return null;
12116
12615
  try {
12117
- const { spawnSync: spawnSync9 } = __require("child_process");
12118
- const out = spawnSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
12616
+ const { spawnSync: spawnSync10 } = __require("child_process");
12617
+ const out = spawnSync10("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
12119
12618
  cwd: repo,
12120
12619
  encoding: "utf-8",
12121
12620
  timeout: 2000,
@@ -12135,8 +12634,8 @@ function listLocalBranches(repo) {
12135
12634
  if (!repo)
12136
12635
  return [];
12137
12636
  try {
12138
- const { spawnSync: spawnSync9 } = __require("child_process");
12139
- const out = spawnSync9("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
12637
+ const { spawnSync: spawnSync10 } = __require("child_process");
12638
+ const out = spawnSync10("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
12140
12639
  cwd: repo,
12141
12640
  encoding: "utf-8",
12142
12641
  timeout: 2000
@@ -15838,12 +16337,12 @@ var init_focus = __esm(() => {
15838
16337
  });
15839
16338
 
15840
16339
  // src/tui/context/kv.tsx
15841
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
16340
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
15842
16341
  import { dirname as dirname7 } from "path";
15843
16342
  function loadInitial() {
15844
16343
  const statePath2 = kvStatePath();
15845
16344
  try {
15846
- const text = readFileSync10(statePath2, "utf8");
16345
+ const text = readFileSync11(statePath2, "utf8");
15847
16346
  const parsed = JSON.parse(text);
15848
16347
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
15849
16348
  return parsed;
@@ -15867,7 +16366,7 @@ var init_kv = __esm(() => {
15867
16366
  function writeNow(label) {
15868
16367
  const statePath2 = kvStatePath();
15869
16368
  try {
15870
- mkdirSync4(dirname7(statePath2), {
16369
+ mkdirSync5(dirname7(statePath2), {
15871
16370
  recursive: true
15872
16371
  });
15873
16372
  const tmp = `${statePath2}.tmp`;
@@ -15922,7 +16421,7 @@ var init_kv = __esm(() => {
15922
16421
  }
15923
16422
  const statePath2 = kvStatePath();
15924
16423
  try {
15925
- mkdirSync4(dirname7(statePath2), {
16424
+ mkdirSync5(dirname7(statePath2), {
15926
16425
  recursive: true
15927
16426
  });
15928
16427
  const tmp = `${statePath2}.tmp`;
@@ -15939,10 +16438,10 @@ var init_kv = __esm(() => {
15939
16438
  });
15940
16439
 
15941
16440
  // src/tui/lib/persisted-ui-prefs.ts
15942
- import { readFileSync as readFileSync11 } from "fs";
16441
+ import { readFileSync as readFileSync12 } from "fs";
15943
16442
  function readPersistedUiPrefs(fallbackTheme) {
15944
16443
  try {
15945
- const parsed = JSON.parse(readFileSync11(kvStatePath(), "utf8"));
16444
+ const parsed = JSON.parse(readFileSync12(kvStatePath(), "utf8"));
15946
16445
  const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
15947
16446
  const transparent = parsed.transparentBackground === true;
15948
16447
  const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
@@ -16222,6 +16721,9 @@ async function ensureTaskSession2(orch, task, repo, vendor) {
16222
16721
  const {
16223
16722
  resolveRepoInit: resolveRepoInit2
16224
16723
  } = await Promise.resolve().then(() => (init_repo_init(), exports_repo_init));
16724
+ const {
16725
+ isRemoteRepoKey: isRemoteRepoKey2
16726
+ } = await Promise.resolve().then(() => (init_repos(), exports_repos));
16225
16727
  const init2 = resolveRepoInit2(repo, worktree);
16226
16728
  const ok = await ensureSession2({
16227
16729
  name: session,
@@ -16229,6 +16731,7 @@ async function ensureTaskSession2(orch, task, repo, vendor) {
16229
16731
  command: interactiveEngineCommand(vendor),
16230
16732
  taskId: task.id,
16231
16733
  vendor,
16734
+ remoteKey: isRemoteRepoKey2(repo) ? repo : undefined,
16232
16735
  initScript: init2.initScript
16233
16736
  });
16234
16737
  if (!ok)
@@ -17646,6 +18149,9 @@ function engineRowCount(customCount) {
17646
18149
  return ALL_VENDORS.length + customCount + 1;
17647
18150
  }
17648
18151
  function devRowCount(hasDaemon) {
18152
+ return (hasDaemon ? 2 : 1) + 1;
18153
+ }
18154
+ function experimentalRemoteRowIndex(hasDaemon) {
17649
18155
  return hasDaemon ? 2 : 1;
17650
18156
  }
17651
18157
  function feedbackRowCount() {
@@ -18529,12 +19035,15 @@ function DevSettingsSection(props) {
18529
19035
  } = useTheme();
18530
19036
  const resetIsCursor = () => props.level() === "body" && props.bodyRow() === 0;
18531
19037
  const restartIsCursor = () => props.level() === "body" && props.bodyRow() === 1;
19038
+ const experimentalRow = () => experimentalRemoteRowIndex(props.hasDaemon);
19039
+ const remoteIsCursor = () => props.level() === "body" && props.bodyRow() === experimentalRow();
18532
19040
  return (() => {
18533
- var _el$128 = createElement("box"), _el$129 = createElement("text"), _el$131 = createElement("text"), _el$133 = createElement("box"), _el$134 = createElement("text"), _el$144 = createElement("text");
19041
+ var _el$128 = createElement("box"), _el$129 = createElement("text"), _el$131 = createElement("text"), _el$133 = createElement("box"), _el$134 = createElement("text"), _el$144 = createElement("text"), _el$146 = createElement("box"), _el$147 = createElement("text"), _el$149 = createElement("text"), _el$151 = createElement("box"), _el$152 = createElement("text");
18534
19042
  insertNode(_el$128, _el$129);
18535
19043
  insertNode(_el$128, _el$131);
18536
19044
  insertNode(_el$128, _el$133);
18537
19045
  insertNode(_el$128, _el$144);
19046
+ insertNode(_el$128, _el$146);
18538
19047
  setProp(_el$128, "flexDirection", "column");
18539
19048
  setProp(_el$128, "gap", 1);
18540
19049
  insertNode(_el$129, createTextNode(`Reset UI state`));
@@ -18597,8 +19106,27 @@ function DevSettingsSection(props) {
18597
19106
  }), _el$144);
18598
19107
  insertNode(_el$144, createTextNode(`Daemon wedged or unresponsive? From a shell, run \`kobe doctor\` to diagnose, or \`kobe reset\` to stop the daemon + kill sessions (keeps your tasks). Use \`kobe reset --hard\` only to also wipe the task index + UI state.`));
18599
19108
  setProp(_el$144, "wrapMode", "word");
19109
+ insertNode(_el$146, _el$147);
19110
+ insertNode(_el$146, _el$149);
19111
+ insertNode(_el$146, _el$151);
19112
+ setProp(_el$146, "flexDirection", "column");
19113
+ setProp(_el$146, "gap", 0);
19114
+ setProp(_el$146, "paddingTop", 1);
19115
+ insertNode(_el$147, createTextNode(`Experimental`));
19116
+ insertNode(_el$149, createTextNode(`Remote projects (SSH): register a project whose git worktrees + engine run on another host over SSH, driven from this local kobe. Unfinished \u2014 file/diff panes still degrade for remote. Enables \`kobe add --remote\`.`));
19117
+ setProp(_el$149, "wrapMode", "word");
19118
+ insertNode(_el$151, _el$152);
19119
+ setProp(_el$151, "flexDirection", "row");
19120
+ setProp(_el$151, "paddingLeft", 1);
19121
+ setProp(_el$151, "paddingRight", 1);
19122
+ setProp(_el$151, "onMouseUp", () => {
19123
+ props.setLevel("body");
19124
+ props.setBodyRow(experimentalRow());
19125
+ props.toggleRemoteProjects();
19126
+ });
19127
+ insert(_el$152, () => props.remoteProjectsEnabled() ? "[x] Remote projects (on)" : "[ ] Remote projects (off)");
18600
19128
  effect((_p$) => {
18601
- var _v$86 = theme.text, _v$87 = TextAttributes8.BOLD, _v$88 = theme.textMuted, _v$89 = resetIsCursor() ? theme.primary : theme.backgroundElement, _v$90 = resetIsCursor() ? theme.selectedListItemText : theme.warning, _v$91 = TextAttributes8.BOLD, _v$92 = theme.textMuted;
19129
+ var _v$86 = theme.text, _v$87 = TextAttributes8.BOLD, _v$88 = theme.textMuted, _v$89 = resetIsCursor() ? theme.primary : theme.backgroundElement, _v$90 = resetIsCursor() ? theme.selectedListItemText : theme.warning, _v$91 = TextAttributes8.BOLD, _v$92 = theme.textMuted, _v$93 = theme.text, _v$94 = TextAttributes8.BOLD, _v$95 = theme.textMuted, _v$96 = remoteIsCursor() ? theme.primary : theme.backgroundElement, _v$97 = remoteIsCursor() ? theme.selectedListItemText : theme.text, _v$98 = props.remoteProjectsEnabled() ? TextAttributes8.BOLD : undefined;
18602
19130
  _v$86 !== _p$.e && (_p$.e = setProp(_el$129, "fg", _v$86, _p$.e));
18603
19131
  _v$87 !== _p$.t && (_p$.t = setProp(_el$129, "attributes", _v$87, _p$.t));
18604
19132
  _v$88 !== _p$.a && (_p$.a = setProp(_el$131, "fg", _v$88, _p$.a));
@@ -18606,6 +19134,12 @@ function DevSettingsSection(props) {
18606
19134
  _v$90 !== _p$.i && (_p$.i = setProp(_el$134, "fg", _v$90, _p$.i));
18607
19135
  _v$91 !== _p$.n && (_p$.n = setProp(_el$134, "attributes", _v$91, _p$.n));
18608
19136
  _v$92 !== _p$.s && (_p$.s = setProp(_el$144, "fg", _v$92, _p$.s));
19137
+ _v$93 !== _p$.h && (_p$.h = setProp(_el$147, "fg", _v$93, _p$.h));
19138
+ _v$94 !== _p$.r && (_p$.r = setProp(_el$147, "attributes", _v$94, _p$.r));
19139
+ _v$95 !== _p$.d && (_p$.d = setProp(_el$149, "fg", _v$95, _p$.d));
19140
+ _v$96 !== _p$.l && (_p$.l = setProp(_el$151, "backgroundColor", _v$96, _p$.l));
19141
+ _v$97 !== _p$.u && (_p$.u = setProp(_el$152, "fg", _v$97, _p$.u));
19142
+ _v$98 !== _p$.c && (_p$.c = setProp(_el$152, "attributes", _v$98, _p$.c));
18609
19143
  return _p$;
18610
19144
  }, {
18611
19145
  e: undefined,
@@ -18614,7 +19148,13 @@ function DevSettingsSection(props) {
18614
19148
  o: undefined,
18615
19149
  i: undefined,
18616
19150
  n: undefined,
18617
- s: undefined
19151
+ s: undefined,
19152
+ h: undefined,
19153
+ r: undefined,
19154
+ d: undefined,
19155
+ l: undefined,
19156
+ u: undefined,
19157
+ c: undefined
18618
19158
  });
18619
19159
  return _el$128;
18620
19160
  })();
@@ -18739,6 +19279,12 @@ function SettingsDialog(props) {
18739
19279
  function toggleSound() {
18740
19280
  props.kv.set("notifications.sound.enabled", !soundEnabled());
18741
19281
  }
19282
+ function remoteProjectsEnabled() {
19283
+ return props.kv.get("experimental.remoteProjects", false) === true;
19284
+ }
19285
+ function toggleRemoteProjects() {
19286
+ props.kv.set("experimental.remoteProjects", !remoteProjectsEnabled());
19287
+ }
18742
19288
  function customEngines() {
18743
19289
  const raw = props.kv.get("customEngineIds", []);
18744
19290
  return Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
@@ -19006,6 +19552,8 @@ function SettingsDialog(props) {
19006
19552
  confirmResetState(dialog, props.kv, renderer);
19007
19553
  else if (hasDaemon && bodyRow() === 1)
19008
19554
  confirmRestartDaemon(dialog, props.orchestrator, renderer);
19555
+ else if (bodyRow() === experimentalRemoteRowIndex(hasDaemon))
19556
+ toggleRemoteProjects();
19009
19557
  }
19010
19558
  }
19011
19559
  useBindings(() => ({
@@ -19196,7 +19744,9 @@ function SettingsDialog(props) {
19196
19744
  setBodyRow,
19197
19745
  hasDaemon,
19198
19746
  confirmReset: () => void confirmResetState(dialog, props.kv, renderer),
19199
- confirmRestartDaemon: () => void confirmRestartDaemon(dialog, props.orchestrator, renderer)
19747
+ confirmRestartDaemon: () => void confirmRestartDaemon(dialog, props.orchestrator, renderer),
19748
+ remoteProjectsEnabled,
19749
+ toggleRemoteProjects
19200
19750
  });
19201
19751
  }
19202
19752
  }), null);
@@ -19266,7 +19816,7 @@ var pulse_default = "../pulse-n3cq1btw.wav";
19266
19816
  var init_pulse = () => {};
19267
19817
 
19268
19818
  // src/tui/lib/sound.ts
19269
- import { existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
19819
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
19270
19820
  import { tmpdir as tmpdir2 } from "os";
19271
19821
  import { basename as basename4, isAbsolute, join as join14, resolve as resolve8 } from "path";
19272
19822
  function args(player, file, volume) {
@@ -19291,12 +19841,12 @@ function pickPlayer() {
19291
19841
  return cachedPlayer;
19292
19842
  const path12 = process.env.PATH ?? "";
19293
19843
  const segments = path12.split(":").filter(Boolean);
19294
- cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync12(join14(dir, p)))) ?? null;
19844
+ cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync13(join14(dir, p)))) ?? null;
19295
19845
  return cachedPlayer;
19296
19846
  }
19297
19847
  async function ensureAsset() {
19298
19848
  cachedPath ??= (async () => {
19299
- mkdirSync5(DIR, { recursive: true });
19849
+ mkdirSync6(DIR, { recursive: true });
19300
19850
  const dest = join14(DIR, basename4(pulseAsset));
19301
19851
  const out = Bun.file(dest);
19302
19852
  if (await out.exists())
@@ -19632,7 +20182,7 @@ var init_task_actions = __esm(() => {
19632
20182
 
19633
20183
  // src/tui/lib/worktree-opener.ts
19634
20184
  import { spawn as spawn3 } from "child_process";
19635
- import { existsSync as existsSync13 } from "fs";
20185
+ import { existsSync as existsSync14 } from "fs";
19636
20186
  import { basename as basename5, delimiter, isAbsolute as isAbsolute2, join as join15 } from "path";
19637
20187
  function executableOnPath(command, env, exists) {
19638
20188
  if (isAbsolute2(command))
@@ -19660,8 +20210,8 @@ function labelForOverride(command) {
19660
20210
  }
19661
20211
  function detectWorktreeOpener(deps = {}) {
19662
20212
  const env = deps.env ?? process.env;
19663
- const platform = deps.platform ?? process.platform;
19664
- const exists = deps.exists ?? existsSync13;
20213
+ const platform2 = deps.platform ?? process.platform;
20214
+ const exists = deps.exists ?? existsSync14;
19665
20215
  const override = env.KOBE_OPEN_EDITOR?.trim();
19666
20216
  if (override) {
19667
20217
  return { id: "env", label: labelForOverride(override), command: override, args: [] };
@@ -19671,7 +20221,7 @@ function detectWorktreeOpener(deps = {}) {
19671
20221
  return { id: c.id, label: c.label, command: c.command, args: [] };
19672
20222
  }
19673
20223
  }
19674
- if (platform === "darwin" && executableOnPath("open", env, exists)) {
20224
+ if (platform2 === "darwin" && executableOnPath("open", env, exists)) {
19675
20225
  for (const app of MAC_APP_CANDIDATES) {
19676
20226
  if (app.paths.some((path12) => exists(path12))) {
19677
20227
  return { id: app.id, label: app.label, command: "open", args: ["-a", app.appName] };
@@ -19679,7 +20229,7 @@ function detectWorktreeOpener(deps = {}) {
19679
20229
  }
19680
20230
  return { id: "mac-open", label: "Finder", command: "open", args: [] };
19681
20231
  }
19682
- if (platform === "linux" && executableOnPath("xdg-open", env, exists)) {
20232
+ if (platform2 === "linux" && executableOnPath("xdg-open", env, exists)) {
19683
20233
  return { id: "xdg-open", label: "Open", command: "xdg-open", args: [] };
19684
20234
  }
19685
20235
  return null;
@@ -19739,12 +20289,12 @@ var init_worktree_opener = __esm(() => {
19739
20289
  });
19740
20290
 
19741
20291
  // src/tui/panes/sidebar/git-head.ts
19742
- import { spawnSync as spawnSync9 } from "child_process";
20292
+ import { spawnSync as spawnSync10 } from "child_process";
19743
20293
  function readCurrentBranch(repo) {
19744
20294
  if (!repo)
19745
20295
  return "";
19746
20296
  try {
19747
- const out = spawnSync9("git", ["symbolic-ref", "--short", "HEAD"], {
20297
+ const out = spawnSync10("git", ["symbolic-ref", "--short", "HEAD"], {
19748
20298
  cwd: repo,
19749
20299
  encoding: "utf8",
19750
20300
  stdio: ["ignore", "pipe", "pipe"],
@@ -19755,7 +20305,7 @@ function readCurrentBranch(repo) {
19755
20305
  if (name && name !== "HEAD")
19756
20306
  return name;
19757
20307
  }
19758
- const head = spawnSync9("git", ["rev-parse", "--verify", "HEAD"], {
20308
+ const head = spawnSync10("git", ["rev-parse", "--verify", "HEAD"], {
19759
20309
  cwd: repo,
19760
20310
  encoding: "utf8",
19761
20311
  stdio: ["ignore", "pipe", "pipe"],
@@ -20954,8 +21504,13 @@ var exports_host3 = {};
20954
21504
  __export(exports_host3, {
20955
21505
  startTasksPane: () => startTasksPane
20956
21506
  });
20957
- import { existsSync as existsSync14 } from "fs";
21507
+ import { existsSync as existsSync15 } from "fs";
20958
21508
  import { TextAttributes as TextAttributes13 } from "@opentui/core";
21509
+ function worktreeCwdUsable(cwd) {
21510
+ if (!cwd)
21511
+ return false;
21512
+ return execHostForWorktreePath(cwd).isRemote || existsSync15(cwd);
21513
+ }
20959
21514
  function TasksShell(props) {
20960
21515
  const themeCtx = useTheme();
20961
21516
  const {
@@ -21228,7 +21783,7 @@ function TasksShell(props) {
21228
21783
  async function openSelectedWorktree(id) {
21229
21784
  const task = props.tasks().find((t) => t.id === id);
21230
21785
  let worktree = task?.worktreePath;
21231
- if (!worktree || !existsSync14(worktree)) {
21786
+ if (!worktree || !existsSync15(worktree)) {
21232
21787
  if (!props.orch) {
21233
21788
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
21234
21789
  notifyError("No daemon running \u2014 can't create the worktree");
@@ -21243,7 +21798,7 @@ function TasksShell(props) {
21243
21798
  }
21244
21799
  await props.reload();
21245
21800
  }
21246
- if (!worktree || !existsSync14(worktree))
21801
+ if (!worktree || !existsSync15(worktree))
21247
21802
  return;
21248
21803
  const opener = detectWorktreeOpener();
21249
21804
  if (!opener) {
@@ -21321,13 +21876,14 @@ function TasksShell(props) {
21321
21876
  const exists = await sessionExists(name);
21322
21877
  if (exists) {
21323
21878
  const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
21324
- if (cwd2 && existsSync14(cwd2)) {
21879
+ if (worktreeCwdUsable(cwd2)) {
21325
21880
  await ensureSession({
21326
21881
  name,
21327
21882
  cwd: cwd2,
21328
21883
  command: interactiveEngineCommand(task?.vendor),
21329
21884
  taskId: id,
21330
- vendor: task?.vendor
21885
+ vendor: task?.vendor,
21886
+ remoteKey: task?.repo && isRemoteRepoKey(task.repo) ? task.repo : undefined
21331
21887
  });
21332
21888
  }
21333
21889
  await runTmux(["switch-client", "-t", `=${name}`]);
@@ -21335,7 +21891,7 @@ function TasksShell(props) {
21335
21891
  return;
21336
21892
  }
21337
21893
  let cwd = task?.worktreePath;
21338
- if (!cwd || !existsSync14(cwd)) {
21894
+ if (!worktreeCwdUsable(cwd)) {
21339
21895
  if (!props.orch) {
21340
21896
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
21341
21897
  notifyError("No daemon running \u2014 can't open this task");
@@ -21350,7 +21906,7 @@ function TasksShell(props) {
21350
21906
  }
21351
21907
  await props.reload();
21352
21908
  }
21353
- if (!cwd || !existsSync14(cwd))
21909
+ if (!worktreeCwdUsable(cwd))
21354
21910
  return;
21355
21911
  const init2 = task?.repo ? resolveRepoInit(task.repo, cwd) : {};
21356
21912
  const ready = await ensureSession({
@@ -21359,6 +21915,7 @@ function TasksShell(props) {
21359
21915
  command: interactiveEngineCommand(task?.vendor),
21360
21916
  taskId: id,
21361
21917
  vendor: task?.vendor,
21918
+ remoteKey: task?.repo && isRemoteRepoKey(task.repo) ? task.repo : undefined,
21362
21919
  initScript: init2.initScript,
21363
21920
  initPrompt: init2.initPrompt
21364
21921
  });
@@ -21720,6 +22277,7 @@ var init_host3 = __esm(() => {
21720
22277
  init_account_detect();
21721
22278
  init_interactive_command();
21722
22279
  init_env();
22280
+ init_resolve();
21723
22281
  init_errors();
21724
22282
  init_store();
21725
22283
  init_repo_init();
@@ -21906,13 +22464,13 @@ var exports_host5 = {};
21906
22464
  __export(exports_host5, {
21907
22465
  startUpdateHost: () => startUpdateHost
21908
22466
  });
21909
- import { spawn as spawn4, spawnSync as spawnSync10 } from "child_process";
22467
+ import { spawn as spawn4, spawnSync as spawnSync11 } from "child_process";
21910
22468
  import { TextAttributes as TextAttributes14 } from "@opentui/core";
21911
22469
  function openExternalUrl(url) {
21912
22470
  if (!url)
21913
22471
  return false;
21914
- const platform = process.platform;
21915
- const [command, args2] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
22472
+ const platform2 = process.platform;
22473
+ const [command, args2] = platform2 === "darwin" ? ["open", [url]] : platform2 === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
21916
22474
  try {
21917
22475
  const child = spawn4(command, args2, {
21918
22476
  stdio: "ignore",
@@ -22012,7 +22570,7 @@ kobe ${CURRENT_VERSION} -> latest
22012
22570
  process.stdout.write(`running: ${UPDATE_COMMAND}
22013
22571
 
22014
22572
  `);
22015
- const result = spawnSync10("sh", ["-c", UPDATE_COMMAND], {
22573
+ const result = spawnSync11("sh", ["-c", UPDATE_COMMAND], {
22016
22574
  stdio: "inherit"
22017
22575
  });
22018
22576
  const code = result.status ?? (result.error ? 1 : 0);
@@ -22799,14 +23357,14 @@ var init_keys2 = __esm(() => {
22799
23357
 
22800
23358
  // src/tui/panes/filetree/open-external.ts
22801
23359
  import { spawn as spawn5 } from "child_process";
22802
- import { existsSync as existsSync15 } from "fs";
22803
- import { platform } from "os";
23360
+ import { existsSync as existsSync16 } from "fs";
23361
+ import { platform as platform2 } from "os";
22804
23362
  function openExternally(absPath) {
22805
23363
  if (!absPath)
22806
23364
  return;
22807
- const plat = platform();
23365
+ const plat = platform2();
22808
23366
  if (plat === "linux") {
22809
- if (existsSync15("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
23367
+ if (existsSync16("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
22810
23368
  spawnDetached("wslview", [absPath], () => {
22811
23369
  const child = spawn5("wslpath", ["-w", absPath], {
22812
23370
  stdio: ["ignore", "pipe", "ignore"]
@@ -23614,12 +24172,12 @@ var init_filetree = __esm(() => {
23614
24172
  });
23615
24173
 
23616
24174
  // src/tui/ops/pr-prompt.ts
23617
- import { spawnSync as spawnSync11 } from "child_process";
24175
+ import { spawnSync as spawnSync12 } from "child_process";
23618
24176
  import { promises as fs4 } from "fs";
23619
24177
  import path12 from "path";
23620
- function git2(cwd, args2) {
24178
+ function git(cwd, args2) {
23621
24179
  try {
23622
- const out = spawnSync11("git", args2.slice(), {
24180
+ const out = spawnSync12("git", args2.slice(), {
23623
24181
  cwd,
23624
24182
  encoding: "utf8",
23625
24183
  timeout: GIT_TIMEOUT_MS2,
@@ -23635,20 +24193,20 @@ function git2(cwd, args2) {
23635
24193
  }
23636
24194
  }
23637
24195
  function currentBranch(cwd) {
23638
- return git2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
24196
+ return git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
23639
24197
  }
23640
24198
  function targetBranch(cwd) {
23641
- const out = git2(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
24199
+ const out = git(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
23642
24200
  if (!out)
23643
24201
  return "main";
23644
24202
  return out.startsWith("origin/") ? out.slice("origin/".length) : out;
23645
24203
  }
23646
24204
  function hasUpstream(cwd) {
23647
- const out = git2(cwd, ["rev-parse", "--abbrev-ref", "@{u}"]);
24205
+ const out = git(cwd, ["rev-parse", "--abbrev-ref", "@{u}"]);
23648
24206
  return out !== null && out.length > 0;
23649
24207
  }
23650
24208
  function dirtyCount(cwd) {
23651
- const out = git2(cwd, ["status", "--porcelain"]);
24209
+ const out = git(cwd, ["status", "--porcelain"]);
23652
24210
  if (!out)
23653
24211
  return 0;
23654
24212
  return out.split(`
@@ -24273,6 +24831,7 @@ async function startDirectTmux() {
24273
24831
  command: interactiveEngineCommand(task.vendor),
24274
24832
  taskId: task.id,
24275
24833
  vendor: task.vendor,
24834
+ remoteKey: isRemoteRepoKey(task.repo) ? task.repo : undefined,
24276
24835
  initScript: init2.initScript,
24277
24836
  initPrompt: init2.initPrompt
24278
24837
  });
@@ -24687,7 +25246,7 @@ function repoBasename2(repo) {
24687
25246
  }
24688
25247
 
24689
25248
  // src/tui/component/top-bar.tsx
24690
- import { spawnSync as spawnSync12 } from "child_process";
25249
+ import { spawnSync as spawnSync13 } from "child_process";
24691
25250
  import { TextAttributes as TextAttributes18 } from "@opentui/core";
24692
25251
  function TopBar(props) {
24693
25252
  const {
@@ -24716,7 +25275,7 @@ function TopBar(props) {
24716
25275
  `);
24717
25276
  process.stderr.write(`running: ${UPDATE_COMMAND}
24718
25277
  `);
24719
- const result = spawnSync12("sh", ["-c", UPDATE_COMMAND], {
25278
+ const result = spawnSync13("sh", ["-c", UPDATE_COMMAND], {
24720
25279
  stdio: "inherit"
24721
25280
  });
24722
25281
  if (result.error) {
@@ -25435,6 +25994,7 @@ async function launchTaskTmux(opts) {
25435
25994
  command: opts.command,
25436
25995
  taskId: opts.taskId,
25437
25996
  vendor: opts.vendor,
25997
+ remoteKey: opts.repo && isRemoteRepoKey(opts.repo) ? opts.repo : undefined,
25438
25998
  initScript: init2.initScript,
25439
25999
  initPrompt: init2.initPrompt
25440
26000
  });
@@ -25535,6 +26095,7 @@ var init_fullscreen = __esm(() => {
25535
26095
  init_solid();
25536
26096
  init_dev();
25537
26097
  init_repo_init();
26098
+ init_repos();
25538
26099
  init_theme2();
25539
26100
  init_keymap();
25540
26101
  init_dialog();
@@ -26214,19 +26775,27 @@ function topLevelUsage() {
26214
26775
  }
26215
26776
 
26216
26777
  // src/cli/index.ts
26217
- async function runAddSubcommand(arg) {
26218
- if (arg === "--help" || arg === "-h" || arg === "help") {
26219
- process.stdout.write(`Usage: kobe add [path]
26778
+ var ADD_USAGE = `Usage: kobe add [path]
26779
+ ` + ` kobe add --remote --host <host> --user <user> --path <basePath> [--port N] [--key <path> | --password]
26220
26780
 
26221
- Save a repo path (default: the current directory) for the new-task picker.
26222
- `);
26781
+ ` + `Save a repo for the new-task picker. With --remote, register an SSH-backed
26782
+ ` + `project whose worktrees + engine run on <host> under <basePath>.
26783
+ `;
26784
+ async function runAddSubcommand(rest) {
26785
+ const arg = rest[0];
26786
+ if (arg === "--help" || arg === "-h" || arg === "help") {
26787
+ process.stdout.write(ADD_USAGE);
26788
+ return;
26789
+ }
26790
+ if (arg === "--remote") {
26791
+ const { runAddRemote: runAddRemote2 } = await Promise.resolve().then(() => (init_add_remote(), exports_add_remote));
26792
+ await runAddRemote2(rest.slice(1));
26223
26793
  return;
26224
26794
  }
26225
26795
  if (arg?.startsWith("-")) {
26226
26796
  process.stderr.write(`kobe add: unknown flag "${arg}"
26227
26797
 
26228
- Usage: kobe add [path]
26229
- `);
26798
+ ${ADD_USAGE}`);
26230
26799
  process.exit(2);
26231
26800
  }
26232
26801
  const target = resolve10(process.cwd(), arg && arg.length > 0 ? arg : ".");
@@ -26414,7 +26983,7 @@ async function main() {
26414
26983
  return;
26415
26984
  }
26416
26985
  if (subcommand === "add") {
26417
- await runAddSubcommand(rest[0]);
26986
+ await runAddSubcommand(rest);
26418
26987
  return;
26419
26988
  }
26420
26989
  if (subcommand === "adopt") {