taktiko 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -8
- package/dist/cli.js +424 -33
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,23 +24,37 @@ Requires Node.js ≥ 20. Provides a single `taktiko` command.
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
taktiko pair <pairingToken> --server https://your-taktiko-server
|
|
27
|
-
taktiko
|
|
27
|
+
taktiko start # runs in the background — no terminal to keep open
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
`pair` redeems the token and saves a daemon credential to `~/.taktiko/daemon.json`. `
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
`pair` redeems the token and saves a daemon credential to `~/.taktiko/daemon.json`. `start` launches
|
|
31
|
+
the daemon as a **detached background process** (logging to `~/.taktiko/daemon.log`) and returns
|
|
32
|
+
immediately, so you can close the terminal. It reconnects automatically with exponential backoff if
|
|
33
|
+
the server restarts or the network blips.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
To also have it **start automatically on every login/reboot**, install it as a system service
|
|
36
|
+
(launchd on macOS, systemd `--user` on Linux):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
taktiko install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The web app's computer status flips to **online** as soon as the daemon connects; on connect it reports
|
|
43
|
+
which agent CLIs are on your `PATH` (claude / codex / kimi) and candidate working directories, so the
|
|
44
|
+
agent editor only offers what this machine actually has.
|
|
37
45
|
|
|
38
46
|
## Commands
|
|
39
47
|
|
|
40
48
|
| Command | What it does |
|
|
41
49
|
|---|---|
|
|
42
50
|
| `taktiko pair <token> [--server <url>] [--name <name>]` | Redeem a pairing token; store this machine as a daemon. |
|
|
43
|
-
| `taktiko
|
|
51
|
+
| `taktiko start [--server <url>] [--foreground]` | Start the daemon in the background (detached). `--foreground` runs it inline. |
|
|
52
|
+
| `taktiko stop` | Stop the background daemon. |
|
|
53
|
+
| `taktiko restart` | Restart it (cycles the installed service in place, else the background process). |
|
|
54
|
+
| `taktiko status [--json]` | Show the local process (pid/uptime), server reachability, and service state. |
|
|
55
|
+
| `taktiko logs [-n <lines>] [-f]` | Print / follow `~/.taktiko/daemon.log`. |
|
|
56
|
+
| `taktiko install` / `taktiko uninstall` | Add / remove the login service (auto-start on boot). |
|
|
57
|
+
| `taktiko daemon [--server <url>]` | Run the daemon in the **foreground** (what `start` and the service run under the hood). |
|
|
44
58
|
| `taktiko --version` / `taktiko --help` | Version / help. |
|
|
45
59
|
|
|
46
60
|
Config is read from `~/.taktiko/daemon.json`, or from `TAKTIKO_SERVER` + `TAKTIKO_DAEMON_TOKEN`
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../backend/src/daemon/cli.ts
|
|
4
|
-
import { execFileSync, spawn as
|
|
4
|
+
import { execFileSync as execFileSync2, spawn as spawn3 } from "child_process";
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
|
-
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
6
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
7
7
|
import { hostname } from "os";
|
|
8
|
-
import { homedir } from "os";
|
|
9
|
-
import { dirname, join, relative } from "path";
|
|
8
|
+
import { homedir as homedir2 } from "os";
|
|
9
|
+
import { dirname as dirname2, join as join2, relative } from "path";
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import WebSocket from "ws";
|
|
12
12
|
|
|
@@ -279,12 +279,263 @@ function selectAdapter(kind, command) {
|
|
|
279
279
|
return genericAdapter;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// ../backend/src/daemon/service.ts
|
|
283
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
284
|
+
import { existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
285
|
+
import { homedir, platform } from "os";
|
|
286
|
+
import { dirname, join } from "path";
|
|
287
|
+
var CONFIG_DIR = join(homedir(), ".taktiko");
|
|
288
|
+
var PID_FILE = join(CONFIG_DIR, "daemon.pid");
|
|
289
|
+
var LOG_FILE = join(CONFIG_DIR, "daemon.log");
|
|
290
|
+
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
291
|
+
var SERVICE_LABEL_DARWIN = "ai.taktiko.daemon";
|
|
292
|
+
var SERVICE_NAME_LINUX = "taktiko-daemon";
|
|
293
|
+
function readPid() {
|
|
294
|
+
try {
|
|
295
|
+
const pid = Number.parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
296
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function isRunning() {
|
|
302
|
+
const pid = readPid();
|
|
303
|
+
if (pid === null) return false;
|
|
304
|
+
try {
|
|
305
|
+
process.kill(pid, 0);
|
|
306
|
+
return true;
|
|
307
|
+
} catch {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function stopRunning() {
|
|
312
|
+
const pid = readPid();
|
|
313
|
+
if (pid === null) return null;
|
|
314
|
+
try {
|
|
315
|
+
process.kill(pid, "SIGTERM");
|
|
316
|
+
} catch {
|
|
317
|
+
try {
|
|
318
|
+
unlinkSync(PID_FILE);
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return pid;
|
|
324
|
+
}
|
|
325
|
+
function processInfo() {
|
|
326
|
+
const pid = readPid();
|
|
327
|
+
if (pid === null) return null;
|
|
328
|
+
try {
|
|
329
|
+
process.kill(pid, 0);
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const st = statSync(PID_FILE);
|
|
335
|
+
return { pid, startedAt: st.mtime, uptimeMs: Date.now() - st.mtimeMs };
|
|
336
|
+
} catch {
|
|
337
|
+
return { pid, startedAt: null, uptimeMs: null };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function rotateLogIfLarge() {
|
|
341
|
+
try {
|
|
342
|
+
if (!existsSync(LOG_FILE)) return;
|
|
343
|
+
if (statSync(LOG_FILE).size <= MAX_LOG_BYTES) return;
|
|
344
|
+
const rotated = `${LOG_FILE}.1`;
|
|
345
|
+
try {
|
|
346
|
+
if (existsSync(rotated)) unlinkSync(rotated);
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
renameSync(LOG_FILE, rotated);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function selfBin() {
|
|
354
|
+
const bin = process.argv[1];
|
|
355
|
+
if (!bin || !existsSync(bin)) throw new Error("cannot resolve the taktiko binary from process.argv[1]");
|
|
356
|
+
if (bin.endsWith(".ts")) {
|
|
357
|
+
throw new Error("`start` is for the installed CLI \u2014 in dev run `taktiko daemon` (foreground) instead");
|
|
358
|
+
}
|
|
359
|
+
return bin;
|
|
360
|
+
}
|
|
361
|
+
function spawnDetached(extraArgs = []) {
|
|
362
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
363
|
+
const bin = selfBin();
|
|
364
|
+
rotateLogIfLarge();
|
|
365
|
+
const out = openSync(LOG_FILE, "a");
|
|
366
|
+
const child = spawn2(process.execPath, [bin, "daemon", ...extraArgs], {
|
|
367
|
+
detached: true,
|
|
368
|
+
stdio: ["ignore", out, out],
|
|
369
|
+
env: process.env
|
|
370
|
+
});
|
|
371
|
+
child.unref();
|
|
372
|
+
if (typeof child.pid !== "number") throw new Error("failed to spawn the daemon (no pid)");
|
|
373
|
+
return child.pid;
|
|
374
|
+
}
|
|
375
|
+
function currentPlatform() {
|
|
376
|
+
const p = platform();
|
|
377
|
+
return p === "darwin" ? "darwin" : p === "linux" ? "linux" : "unsupported";
|
|
378
|
+
}
|
|
379
|
+
function darwinPlistPath() {
|
|
380
|
+
return join(homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL_DARWIN}.plist`);
|
|
381
|
+
}
|
|
382
|
+
function linuxUnitPath() {
|
|
383
|
+
return join(homedir(), ".config", "systemd", "user", `${SERVICE_NAME_LINUX}.service`);
|
|
384
|
+
}
|
|
385
|
+
function buildServicePath() {
|
|
386
|
+
const defaults = ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
|
|
387
|
+
const seen = /* @__PURE__ */ new Set();
|
|
388
|
+
const out = [];
|
|
389
|
+
for (const entry of [...(process.env.PATH ?? "").split(":"), ...defaults]) {
|
|
390
|
+
const t = entry.trim();
|
|
391
|
+
if (!t || seen.has(t) || !existsSync(t)) continue;
|
|
392
|
+
seen.add(t);
|
|
393
|
+
out.push(t);
|
|
394
|
+
}
|
|
395
|
+
return out.join(":");
|
|
396
|
+
}
|
|
397
|
+
function darwinPlist(nodeBin, bin) {
|
|
398
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
399
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
400
|
+
<plist version="1.0">
|
|
401
|
+
<dict>
|
|
402
|
+
<key>Label</key>
|
|
403
|
+
<string>${SERVICE_LABEL_DARWIN}</string>
|
|
404
|
+
<key>ProgramArguments</key>
|
|
405
|
+
<array>
|
|
406
|
+
<string>${nodeBin}</string>
|
|
407
|
+
<string>${bin}</string>
|
|
408
|
+
<string>daemon</string>
|
|
409
|
+
</array>
|
|
410
|
+
<key>RunAtLoad</key>
|
|
411
|
+
<true/>
|
|
412
|
+
<key>KeepAlive</key>
|
|
413
|
+
<true/>
|
|
414
|
+
<key>ProcessType</key>
|
|
415
|
+
<string>Interactive</string>
|
|
416
|
+
<key>StandardOutPath</key>
|
|
417
|
+
<string>${LOG_FILE}</string>
|
|
418
|
+
<key>StandardErrorPath</key>
|
|
419
|
+
<string>${LOG_FILE}</string>
|
|
420
|
+
<key>EnvironmentVariables</key>
|
|
421
|
+
<dict>
|
|
422
|
+
<key>PATH</key>
|
|
423
|
+
<string>${buildServicePath()}</string>
|
|
424
|
+
<key>HOME</key>
|
|
425
|
+
<string>${process.env.HOME ?? homedir()}</string>
|
|
426
|
+
</dict>
|
|
427
|
+
</dict>
|
|
428
|
+
</plist>
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
function linuxUnit(nodeBin, bin) {
|
|
432
|
+
return `[Unit]
|
|
433
|
+
Description=taktiko daemon
|
|
434
|
+
After=network-online.target
|
|
435
|
+
Wants=network-online.target
|
|
436
|
+
|
|
437
|
+
[Service]
|
|
438
|
+
Type=simple
|
|
439
|
+
ExecStart=${nodeBin} ${bin} daemon
|
|
440
|
+
Restart=always
|
|
441
|
+
RestartSec=2
|
|
442
|
+
StandardOutput=append:${LOG_FILE}
|
|
443
|
+
StandardError=append:${LOG_FILE}
|
|
444
|
+
Environment=PATH=${buildServicePath()}
|
|
445
|
+
|
|
446
|
+
[Install]
|
|
447
|
+
WantedBy=default.target
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
function run(cmd, args) {
|
|
451
|
+
try {
|
|
452
|
+
execFileSync(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
453
|
+
} catch (e) {
|
|
454
|
+
const err = e;
|
|
455
|
+
const detail = err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message || String(e);
|
|
456
|
+
throw new Error(`${cmd} ${args.join(" ")} \u2192 ${detail}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function runQuiet(cmd, args) {
|
|
460
|
+
try {
|
|
461
|
+
execFileSync(cmd, args, { stdio: "ignore" });
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function getServiceStatus() {
|
|
466
|
+
const plat = currentPlatform();
|
|
467
|
+
if (plat === "darwin") return { installed: existsSync(darwinPlistPath()), path: darwinPlistPath(), platformLabel: "launchd" };
|
|
468
|
+
if (plat === "linux") return { installed: existsSync(linuxUnitPath()), path: linuxUnitPath(), platformLabel: "systemd --user" };
|
|
469
|
+
return { installed: false, path: null, platformLabel: "unsupported" };
|
|
470
|
+
}
|
|
471
|
+
function installService() {
|
|
472
|
+
const plat = currentPlatform();
|
|
473
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
474
|
+
const nodeBin = process.execPath;
|
|
475
|
+
const bin = selfBin();
|
|
476
|
+
if (plat === "darwin") {
|
|
477
|
+
const plistPath = darwinPlistPath();
|
|
478
|
+
mkdirSync(dirname(plistPath), { recursive: true });
|
|
479
|
+
writeFileSync(plistPath, darwinPlist(nodeBin, bin));
|
|
480
|
+
runQuiet("launchctl", ["unload", plistPath]);
|
|
481
|
+
run("launchctl", ["load", plistPath]);
|
|
482
|
+
return { path: plistPath, label: "launchd" };
|
|
483
|
+
}
|
|
484
|
+
if (plat === "linux") {
|
|
485
|
+
const unitPath = linuxUnitPath();
|
|
486
|
+
mkdirSync(dirname(unitPath), { recursive: true });
|
|
487
|
+
writeFileSync(unitPath, linuxUnit(nodeBin, bin));
|
|
488
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
489
|
+
run("systemctl", ["--user", "enable", "--now", SERVICE_NAME_LINUX]);
|
|
490
|
+
return { path: unitPath, label: "systemd --user" };
|
|
491
|
+
}
|
|
492
|
+
throw new Error("service install is only supported on macOS and Linux \u2014 on Windows, register `taktiko daemon` via Task Scheduler");
|
|
493
|
+
}
|
|
494
|
+
function uninstallService() {
|
|
495
|
+
const plat = currentPlatform();
|
|
496
|
+
if (plat === "darwin") {
|
|
497
|
+
const plistPath = darwinPlistPath();
|
|
498
|
+
if (!existsSync(plistPath)) return false;
|
|
499
|
+
runQuiet("launchctl", ["unload", plistPath]);
|
|
500
|
+
unlinkSync(plistPath);
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
if (plat === "linux") {
|
|
504
|
+
const unitPath = linuxUnitPath();
|
|
505
|
+
if (!existsSync(unitPath)) return false;
|
|
506
|
+
runQuiet("systemctl", ["--user", "disable", "--now", SERVICE_NAME_LINUX]);
|
|
507
|
+
unlinkSync(unitPath);
|
|
508
|
+
runQuiet("systemctl", ["--user", "daemon-reload"]);
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
throw new Error("service uninstall is only supported on macOS and Linux");
|
|
512
|
+
}
|
|
513
|
+
function restartService() {
|
|
514
|
+
const plat = currentPlatform();
|
|
515
|
+
if (plat === "darwin") {
|
|
516
|
+
if (!existsSync(darwinPlistPath())) return false;
|
|
517
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
518
|
+
if (uid !== null) run("launchctl", ["kickstart", "-k", `gui/${uid}/${SERVICE_LABEL_DARWIN}`]);
|
|
519
|
+
else {
|
|
520
|
+
runQuiet("launchctl", ["unload", darwinPlistPath()]);
|
|
521
|
+
run("launchctl", ["load", darwinPlistPath()]);
|
|
522
|
+
}
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
if (plat === "linux") {
|
|
526
|
+
if (!existsSync(linuxUnitPath())) return false;
|
|
527
|
+
run("systemctl", ["--user", "restart", SERVICE_NAME_LINUX]);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
282
533
|
// ../backend/src/daemon/cli.ts
|
|
283
534
|
function probeInstalledClis() {
|
|
284
535
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
285
536
|
return CLI_COMMANDS.filter((cmd) => {
|
|
286
537
|
try {
|
|
287
|
-
|
|
538
|
+
execFileSync2(finder, [cmd], { stdio: "ignore" });
|
|
288
539
|
return true;
|
|
289
540
|
} catch {
|
|
290
541
|
return false;
|
|
@@ -292,10 +543,10 @@ function probeInstalledClis() {
|
|
|
292
543
|
});
|
|
293
544
|
}
|
|
294
545
|
function gatherWorkDirs() {
|
|
295
|
-
const home =
|
|
546
|
+
const home = homedir2();
|
|
296
547
|
const isDir = (p) => {
|
|
297
548
|
try {
|
|
298
|
-
return
|
|
549
|
+
return statSync2(p).isDirectory();
|
|
299
550
|
} catch {
|
|
300
551
|
return false;
|
|
301
552
|
}
|
|
@@ -309,7 +560,7 @@ function gatherWorkDirs() {
|
|
|
309
560
|
}
|
|
310
561
|
};
|
|
311
562
|
for (const name of ["Projects", "projects", "code", "Code", "dev", "Developer", "work", "repos", "src"]) {
|
|
312
|
-
add(
|
|
563
|
+
add(join2(home, name));
|
|
313
564
|
}
|
|
314
565
|
const NOISE = /* @__PURE__ */ new Set([
|
|
315
566
|
"Library",
|
|
@@ -328,21 +579,21 @@ function gatherWorkDirs() {
|
|
|
328
579
|
]);
|
|
329
580
|
try {
|
|
330
581
|
for (const ent of readdirSync(home, { withFileTypes: true })) {
|
|
331
|
-
if (ent.isDirectory() && !ent.name.startsWith(".") && !NOISE.has(ent.name)) add(
|
|
582
|
+
if (ent.isDirectory() && !ent.name.startsWith(".") && !NOISE.has(ent.name)) add(join2(home, ent.name));
|
|
332
583
|
}
|
|
333
584
|
} catch {
|
|
334
585
|
}
|
|
335
586
|
return out.slice(0, 50);
|
|
336
587
|
}
|
|
337
|
-
var CONFIG_PATH =
|
|
338
|
-
var PID_PATH =
|
|
588
|
+
var CONFIG_PATH = join2(CONFIG_DIR, "daemon.json");
|
|
589
|
+
var PID_PATH = PID_FILE;
|
|
339
590
|
var JOB_HARD_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_TIMEOUT_MS) || 10 * 6e4;
|
|
340
591
|
var JOB_IDLE_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_IDLE_MS) || 3 * 6e4;
|
|
341
592
|
var KILL_GRACE_MS = 3e3;
|
|
342
593
|
var MAX_CONCURRENT = Number(process.env.TAKTIKO_MAX_CONCURRENT) || 20;
|
|
343
594
|
function loadConfig() {
|
|
344
595
|
try {
|
|
345
|
-
return JSON.parse(
|
|
596
|
+
return JSON.parse(readFileSync2(CONFIG_PATH, "utf8"));
|
|
346
597
|
} catch {
|
|
347
598
|
const server = process.env.TAKTIKO_SERVER;
|
|
348
599
|
const daemonToken = process.env.TAKTIKO_DAEMON_TOKEN;
|
|
@@ -350,12 +601,12 @@ function loadConfig() {
|
|
|
350
601
|
}
|
|
351
602
|
}
|
|
352
603
|
function saveConfig(cfg) {
|
|
353
|
-
|
|
354
|
-
|
|
604
|
+
mkdirSync2(dirname2(CONFIG_PATH), { recursive: true });
|
|
605
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
355
606
|
}
|
|
356
607
|
function acquireSingleInstanceLock() {
|
|
357
608
|
try {
|
|
358
|
-
const existing = Number(
|
|
609
|
+
const existing = Number(readFileSync2(PID_PATH, "utf8").trim());
|
|
359
610
|
if (existing && existing !== process.pid) {
|
|
360
611
|
try {
|
|
361
612
|
process.kill(existing, 0);
|
|
@@ -366,12 +617,12 @@ function acquireSingleInstanceLock() {
|
|
|
366
617
|
}
|
|
367
618
|
} catch {
|
|
368
619
|
}
|
|
369
|
-
|
|
370
|
-
|
|
620
|
+
mkdirSync2(dirname2(PID_PATH), { recursive: true });
|
|
621
|
+
writeFileSync2(PID_PATH, String(process.pid));
|
|
371
622
|
}
|
|
372
623
|
function releaseLock() {
|
|
373
624
|
try {
|
|
374
|
-
if (Number(
|
|
625
|
+
if (Number(readFileSync2(PID_PATH, "utf8").trim()) === process.pid) unlinkSync2(PID_PATH);
|
|
375
626
|
} catch {
|
|
376
627
|
}
|
|
377
628
|
}
|
|
@@ -382,21 +633,21 @@ async function memoryPull(job) {
|
|
|
382
633
|
const apiUrl = env.TAKTIKO_API_URL;
|
|
383
634
|
const token = env.TAKTIKO_AGENT_TOKEN;
|
|
384
635
|
if (!storeId || !apiUrl || !token) return null;
|
|
385
|
-
const agentDir =
|
|
386
|
-
const dir =
|
|
636
|
+
const agentDir = join2(homedir2(), ".taktiko", "agents", job.agent.id);
|
|
637
|
+
const dir = join2(agentDir, "memory");
|
|
387
638
|
const snapshot = /* @__PURE__ */ new Map();
|
|
388
639
|
try {
|
|
389
640
|
rmSync(dir, { recursive: true, force: true });
|
|
390
|
-
|
|
641
|
+
mkdirSync2(dir, { recursive: true });
|
|
391
642
|
const res = await fetch(`${apiUrl}/api/agent/memory?storeId=${encodeURIComponent(storeId)}`, {
|
|
392
643
|
headers: { authorization: `Bearer ${token}` }
|
|
393
644
|
});
|
|
394
645
|
if (res.ok) {
|
|
395
646
|
const data = await res.json();
|
|
396
647
|
for (const doc of data.docs ?? []) {
|
|
397
|
-
const p =
|
|
398
|
-
|
|
399
|
-
|
|
648
|
+
const p = join2(dir, doc.path);
|
|
649
|
+
mkdirSync2(dirname2(p), { recursive: true });
|
|
650
|
+
writeFileSync2(p, doc.content);
|
|
400
651
|
snapshot.set(doc.path, sha256(doc.content));
|
|
401
652
|
}
|
|
402
653
|
}
|
|
@@ -420,11 +671,11 @@ async function memoryPush(job, mem) {
|
|
|
420
671
|
};
|
|
421
672
|
const walk = (d) => {
|
|
422
673
|
for (const e of listDir(d)) {
|
|
423
|
-
const full =
|
|
674
|
+
const full = join2(d, e.name);
|
|
424
675
|
if (e.isDirectory()) walk(full);
|
|
425
676
|
else if (e.isFile()) {
|
|
426
677
|
try {
|
|
427
|
-
current.set(relative(mem.dir, full),
|
|
678
|
+
current.set(relative(mem.dir, full), readFileSync2(full, "utf8"));
|
|
428
679
|
} catch {
|
|
429
680
|
}
|
|
430
681
|
}
|
|
@@ -555,7 +806,7 @@ async function runJob(ws, frame, onDone) {
|
|
|
555
806
|
captured = id;
|
|
556
807
|
},
|
|
557
808
|
spawn(command, args, opts) {
|
|
558
|
-
const child =
|
|
809
|
+
const child = spawn3(command, args, { ...opts, stdio: ["pipe", "pipe", "pipe"] });
|
|
559
810
|
children.set(job.id, child);
|
|
560
811
|
child.on("close", () => {
|
|
561
812
|
if (children.get(job.id) === child) children.delete(job.id);
|
|
@@ -606,7 +857,7 @@ function runDaemon(cfg) {
|
|
|
606
857
|
type: "daemon_ready",
|
|
607
858
|
installedClis: probeInstalledClis(),
|
|
608
859
|
workDirs: gatherWorkDirs(),
|
|
609
|
-
homeDir:
|
|
860
|
+
homeDir: homedir2()
|
|
610
861
|
})
|
|
611
862
|
);
|
|
612
863
|
ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send(JSON.stringify({ type: "ping" })), 25e3);
|
|
@@ -641,7 +892,7 @@ function runDaemon(cfg) {
|
|
|
641
892
|
connect();
|
|
642
893
|
}
|
|
643
894
|
var program = new Command();
|
|
644
|
-
program.name("taktiko").description("Taktiko daemon \u2014 runs your local agent CLIs").version("0.
|
|
895
|
+
program.name("taktiko").description("Taktiko daemon \u2014 runs your local agent CLIs").version("0.2.0");
|
|
645
896
|
program.command("pair <pairingToken>").description("redeem a pairing token from the server and store this machine as a daemon").option("--server <url>", "server base url", process.env.TAKTIKO_SERVER ?? "http://localhost:7100").option("--name <name>", "daemon name", hostname()).action(async (pairingToken, opts) => {
|
|
646
897
|
const res = await fetch(`${opts.server}/api/daemons/pair`, {
|
|
647
898
|
method: "POST",
|
|
@@ -656,13 +907,153 @@ program.command("pair <pairingToken>").description("redeem a pairing token from
|
|
|
656
907
|
saveConfig({ server: opts.server, daemonToken: data.daemonToken });
|
|
657
908
|
console.log(`paired. daemonId=${data.daemonId}. config saved to ${CONFIG_PATH}`);
|
|
658
909
|
});
|
|
659
|
-
program.command("daemon"
|
|
910
|
+
program.command("daemon").description("run the daemon in the foreground (connect to the server and run dispatched turns)").option("--server <url>", "override server base url").action((opts) => {
|
|
911
|
+
const cfg = requireConfig();
|
|
912
|
+
runDaemon({ ...cfg, server: opts.server ?? cfg.server });
|
|
913
|
+
});
|
|
914
|
+
program.command("start").description("start the daemon in the background \u2014 no terminal needed (logs to ~/.taktiko/daemon.log)").option("--foreground", "run in the foreground instead (for debugging)").option("--server <url>", "override server base url").action((opts) => {
|
|
915
|
+
const cfg = requireConfig();
|
|
916
|
+
if (opts.foreground) {
|
|
917
|
+
runDaemon({ ...cfg, server: opts.server ?? cfg.server });
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (isRunning()) {
|
|
921
|
+
console.log(`taktiko daemon is already running (pid ${readPid()}). \`taktiko status\` for details`);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
const pid = spawnDetached(opts.server ? ["--server", opts.server] : []);
|
|
926
|
+
console.log(`taktiko daemon started in the background (pid ${pid})`);
|
|
927
|
+
console.log(` status: taktiko status`);
|
|
928
|
+
console.log(` logs: taktiko logs -f`);
|
|
929
|
+
console.log(` stop: taktiko stop`);
|
|
930
|
+
console.log(` login: taktiko install \u2192 also auto-start on every login/reboot`);
|
|
931
|
+
} catch (e) {
|
|
932
|
+
console.error(`failed to start: ${e instanceof Error ? e.message : String(e)}`);
|
|
933
|
+
process.exit(1);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
program.command("stop").description("stop the background daemon").action(() => {
|
|
937
|
+
const pid = stopRunning();
|
|
938
|
+
console.log(pid ? `stopped taktiko daemon (pid ${pid})` : "no running taktiko daemon");
|
|
939
|
+
});
|
|
940
|
+
program.command("restart").description("restart the daemon (cycles the installed service in place, else the background process)").action(async () => {
|
|
941
|
+
if (restartService()) {
|
|
942
|
+
console.log("restarted the installed taktiko service");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
requireConfig();
|
|
946
|
+
stopRunning();
|
|
947
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
948
|
+
try {
|
|
949
|
+
const pid = spawnDetached();
|
|
950
|
+
console.log(`restarted taktiko daemon (pid ${pid})`);
|
|
951
|
+
} catch (e) {
|
|
952
|
+
console.error(`failed to restart: ${e instanceof Error ? e.message : String(e)}`);
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
program.command("status").description("show the local daemon process + server reachability").option("--json", "machine-readable output").action(async (opts) => {
|
|
957
|
+
const info = processInfo();
|
|
958
|
+
const svc = getServiceStatus();
|
|
660
959
|
const cfg = loadConfig();
|
|
661
|
-
|
|
662
|
-
|
|
960
|
+
let serverReachable = null;
|
|
961
|
+
if (cfg?.server) {
|
|
962
|
+
try {
|
|
963
|
+
const res = await fetch(`${cfg.server}/api/health`, { signal: AbortSignal.timeout(2500) });
|
|
964
|
+
serverReachable = res.ok;
|
|
965
|
+
} catch {
|
|
966
|
+
serverReachable = false;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (opts.json) {
|
|
970
|
+
console.log(
|
|
971
|
+
JSON.stringify(
|
|
972
|
+
{
|
|
973
|
+
running: !!info,
|
|
974
|
+
pid: info?.pid ?? null,
|
|
975
|
+
uptimeMs: info?.uptimeMs ?? null,
|
|
976
|
+
server: cfg?.server ?? null,
|
|
977
|
+
serverReachable,
|
|
978
|
+
service: svc,
|
|
979
|
+
logFile: LOG_FILE
|
|
980
|
+
},
|
|
981
|
+
null,
|
|
982
|
+
2
|
|
983
|
+
)
|
|
984
|
+
);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const up = info?.uptimeMs != null ? `, up ${formatDuration(info.uptimeMs)}` : "";
|
|
988
|
+
const reach = serverReachable === null ? "" : serverReachable ? " \u2713 reachable" : " \u2717 unreachable";
|
|
989
|
+
console.log(`daemon: ${info ? `running (pid ${info.pid}${up})` : "not running"}`);
|
|
990
|
+
console.log(`server: ${cfg?.server ?? "(unpaired \u2014 run `taktiko pair <token>`)"}${reach}`);
|
|
991
|
+
console.log(`service: ${svc.installed ? `installed (${svc.platformLabel})` : "not installed \u2014 `taktiko install` for login auto-start"}`);
|
|
992
|
+
console.log(`logs: ${LOG_FILE}`);
|
|
993
|
+
});
|
|
994
|
+
program.command("logs").description("show the daemon log (~/.taktiko/daemon.log)").option("-n, --lines <n>", "how many trailing lines to show", "50").option("-f, --follow", "follow the log (like tail -f)").action((opts) => {
|
|
995
|
+
const n = Math.max(0, Number.parseInt(opts.lines, 10) || 50);
|
|
996
|
+
if (!existsSync2(LOG_FILE)) {
|
|
997
|
+
console.error(`no log yet at ${LOG_FILE} \u2014 start the daemon first (\`taktiko start\`)`);
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
if (opts.follow) {
|
|
1001
|
+
const child = spawn3("tail", ["-n", String(n), "-f", LOG_FILE], { stdio: "inherit" });
|
|
1002
|
+
child.on("error", () => {
|
|
1003
|
+
console.error("`tail` is unavailable here; printing the last lines instead");
|
|
1004
|
+
printTail(n);
|
|
1005
|
+
});
|
|
1006
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
printTail(n);
|
|
1010
|
+
});
|
|
1011
|
+
program.command("install").description("install a login service so the daemon auto-starts on boot (launchd on macOS / systemd on Linux)").action(() => {
|
|
1012
|
+
requireConfig();
|
|
1013
|
+
try {
|
|
1014
|
+
const { path, label } = installService();
|
|
1015
|
+
console.log(`installed the ${label} service \u2014 the daemon now starts automatically on login`);
|
|
1016
|
+
console.log(` unit: ${path}`);
|
|
1017
|
+
console.log(` logs: ${LOG_FILE}`);
|
|
1018
|
+
} catch (e) {
|
|
1019
|
+
console.error(`install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
663
1020
|
process.exit(1);
|
|
664
1021
|
}
|
|
665
|
-
runDaemon({ ...cfg, server: opts.server ?? cfg.server });
|
|
666
1022
|
});
|
|
1023
|
+
program.command("uninstall").description("remove the login service installed by `taktiko install`").action(() => {
|
|
1024
|
+
try {
|
|
1025
|
+
console.log(uninstallService() ? "removed the taktiko login service" : "no taktiko login service installed");
|
|
1026
|
+
} catch (e) {
|
|
1027
|
+
console.error(`uninstall failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
function requireConfig() {
|
|
1032
|
+
const cfg = loadConfig();
|
|
1033
|
+
if (!cfg) {
|
|
1034
|
+
console.error("no daemon config \u2014 run `taktiko pair <token>` first (or set TAKTIKO_SERVER + TAKTIKO_DAEMON_TOKEN)");
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
return cfg;
|
|
1038
|
+
}
|
|
1039
|
+
function printTail(n) {
|
|
1040
|
+
try {
|
|
1041
|
+
const lines = readFileSync2(LOG_FILE, "utf8").split("\n");
|
|
1042
|
+
if (lines.at(-1) === "") lines.pop();
|
|
1043
|
+
process.stdout.write(`${lines.slice(-n).join("\n")}
|
|
1044
|
+
`);
|
|
1045
|
+
} catch (e) {
|
|
1046
|
+
console.error(`could not read ${LOG_FILE}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function formatDuration(ms) {
|
|
1050
|
+
const s = Math.floor(ms / 1e3);
|
|
1051
|
+
if (s < 60) return `${s}s`;
|
|
1052
|
+
const m = Math.floor(s / 60);
|
|
1053
|
+
if (m < 60) return `${m}m`;
|
|
1054
|
+
const h = Math.floor(m / 60);
|
|
1055
|
+
if (h < 24) return `${h}h ${m % 60}m`;
|
|
1056
|
+
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
1057
|
+
}
|
|
667
1058
|
program.parseAsync(process.argv);
|
|
668
1059
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../backend/src/daemon/cli.ts","../../backend/src/backends.ts","../../backend/src/daemon/adapters/types.ts","../../backend/src/daemon/adapters/claude.ts","../../backend/src/daemon/adapters/codex.ts","../../backend/src/daemon/adapters/generic.ts","../../backend/src/daemon/adapters/kimi.ts","../../backend/src/daemon/adapters/index.ts"],"sourcesContent":["#!/usr/bin/env node\n// `taktiko` — the daemon that runs on a user's machine. It pairs with the server, connects over\n// WebSocket, and for each dispatched job runs the agent's local CLI (e.g. `claude`), streaming\n// stdout back as the reply.\n//\n// Provider-specific adapters (src/daemon/adapters/) build the CLI invocation, parse the stream, and\n// capture the native session id for resume. The runner here adds the machine-side hardening:\n// single-instance locking, a per-job timeout/idle watchdog, SIGTERM→SIGKILL escalation, a\n// concurrency cap, and exponential-backoff reconnect.\nimport { type ChildProcess, execFileSync, spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { homedir } from 'node:os'\nimport { dirname, join, relative } from 'node:path'\n\nimport { Command } from 'commander'\nimport WebSocket from 'ws'\n\nimport { CLI_COMMANDS } from '../backends.js'\nimport { type RunContext, type ToolCall, selectAdapter } from './adapters/index.js'\n\n// ── machine capability probing ───────────────────────────────────────────────────────────────────\n// Reported to the server on connect so the agent editor offers only the CLIs/dirs this computer has.\n\n/** Which known agent CLIs are on PATH (resolved via which/where, so PATH shims count). */\nfunction probeInstalledClis(): string[] {\n const finder = process.platform === 'win32' ? 'where' : 'which'\n return CLI_COMMANDS.filter((cmd) => {\n try {\n execFileSync(finder, [cmd], { stdio: 'ignore' })\n return true\n } catch {\n return false\n }\n })\n}\n\n/** Candidate project directories to offer as the agent's working dir: common dev roots plus the\n * non-hidden immediate subdirectories of home. Home leads the list; capped so the picker stays sane. */\nfunction gatherWorkDirs(): string[] {\n const home = homedir()\n const isDir = (p: string): boolean => {\n try {\n return statSync(p).isDirectory()\n } catch {\n return false\n }\n }\n const out: string[] = [home]\n const seen = new Set(out)\n const add = (p: string) => {\n if (!seen.has(p) && isDir(p)) {\n seen.add(p)\n out.push(p)\n }\n }\n for (const name of ['Projects', 'projects', 'code', 'Code', 'dev', 'Developer', 'work', 'repos', 'src']) {\n add(join(home, name))\n }\n // Standard OS home folders that are never project dirs — keep the picker focused.\n const NOISE = new Set([\n 'Library', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Movies', 'Music',\n 'Pictures', 'Public', 'Templates', 'Videos', 'AppData', 'OneDrive',\n ])\n try {\n for (const ent of readdirSync(home, { withFileTypes: true })) {\n if (ent.isDirectory() && !ent.name.startsWith('.') && !NOISE.has(ent.name)) add(join(home, ent.name))\n }\n } catch {\n /* unreadable home — home alone is fine */\n }\n return out.slice(0, 50)\n}\n\ninterface DaemonConfig {\n server: string\n daemonToken: string\n}\n\nconst CONFIG_PATH = join(homedir(), '.taktiko', 'daemon.json')\nconst PID_PATH = join(homedir(), '.taktiko', 'daemon.pid')\n\n// A hung CLI must never wedge the server's per-conversation serialization key, so every job is\n// bounded by a hard wall-clock cap and an idle (no-output) cap; both are env-overridable.\nconst JOB_HARD_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_TIMEOUT_MS) || 10 * 60_000\nconst JOB_IDLE_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_IDLE_MS) || 3 * 60_000\nconst KILL_GRACE_MS = 3_000\n// Cap concurrent local CLIs so one user's many conversations can't fork-bomb their machine.\nconst MAX_CONCURRENT = Number(process.env.TAKTIKO_MAX_CONCURRENT) || 20\n\nfunction loadConfig(): DaemonConfig | null {\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) as DaemonConfig\n } catch {\n const server = process.env.TAKTIKO_SERVER\n const daemonToken = process.env.TAKTIKO_DAEMON_TOKEN\n return server && daemonToken ? { server, daemonToken } : null\n }\n}\n\nfunction saveConfig(cfg: DaemonConfig): void {\n mkdirSync(dirname(CONFIG_PATH), { recursive: true })\n writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2))\n}\n\n// ---- single-instance lock ----\n// Two daemons sharing one token would both pull jobs → double execution (e.g. double trades). The\n// pidfile blocks a second local daemon; the server independently supersedes a stale connection.\nfunction acquireSingleInstanceLock(): void {\n try {\n const existing = Number(readFileSync(PID_PATH, 'utf8').trim())\n if (existing && existing !== process.pid) {\n try {\n process.kill(existing, 0) // throws if the pid is gone\n console.error(`[daemon] already running (pid ${existing}); refusing to start a second daemon on this machine`)\n process.exit(1)\n } catch {\n /* stale pidfile — overwrite it */\n }\n }\n } catch {\n /* no pidfile yet — first run */\n }\n mkdirSync(dirname(PID_PATH), { recursive: true })\n writeFileSync(PID_PATH, String(process.pid))\n}\n\nfunction releaseLock(): void {\n try {\n if (Number(readFileSync(PID_PATH, 'utf8').trim()) === process.pid) unlinkSync(PID_PATH)\n } catch {\n /* ignore */\n }\n}\n\ntype ResultOut =\n | { type: 'daemon_job_result'; jobId: string; status: 'succeeded'; result: string; sessionId?: string }\n | { type: 'daemon_job_result'; jobId: string; status: 'failed'; error: string; sessionId?: string }\n\ninterface JobFrame {\n type: 'daemon_job'\n job: {\n id: string\n query: string\n conversationId: string | null\n threadId: string | null\n resumeSessionId: string | null\n agent: {\n id: string\n name: string\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n }\n}\n\n// ── memory sync ──────────────────────────────────────────────────────────────────────────────────\n// Server-side memory is a versioned doc store; with a local daemon we mirror the turn's store into a\n// dedicated agent dir the CLI reads/writes with normal file tools, then push the diff back. The agent's\n// own skills live alongside (the agent dir, $TAKTIKO_AGENT_DIR); workspace skills live in the env's\n// working dir — both reachable by the CLI, both meant to land in the system prompt's context.\nconst sha256 = (s: string) => createHash('sha256').update(s).digest('hex')\n\ninterface MemoryCtx {\n dir: string // <agentDir>/memory — the mirrored store\n agentDir: string // ~/.taktiko/agents/<id> — memory + the agent's own skills\n snapshot: Map<string, string> // store-relative path → sha256(content) as pulled\n}\n\n/** Pull the turn's memory store into the agent dir before the CLI runs. Resilient: never throws. */\nasync function memoryPull(job: JobFrame['job']): Promise<MemoryCtx | null> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return null\n const agentDir = join(homedir(), '.taktiko', 'agents', job.agent.id)\n const dir = join(agentDir, 'memory')\n const snapshot = new Map<string, string>()\n try {\n rmSync(dir, { recursive: true, force: true }) // mirror exactly — reflect server-side deletes\n mkdirSync(dir, { recursive: true })\n const res = await fetch(`${apiUrl}/api/agent/memory?storeId=${encodeURIComponent(storeId)}`, {\n headers: { authorization: `Bearer ${token}` },\n })\n if (res.ok) {\n const data = (await res.json()) as { docs?: { path: string; content: string }[] }\n for (const doc of data.docs ?? []) {\n const p = join(dir, doc.path)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, doc.content)\n snapshot.set(doc.path, sha256(doc.content))\n }\n }\n } catch {\n /* server unreachable — run with whatever is (or isn't) on disk */\n }\n return { dir, agentDir, snapshot }\n}\n\n/** Push back any memory the CLI changed in the agent dir. Resilient: never throws. */\nasync function memoryPush(job: JobFrame['job'], mem: MemoryCtx): Promise<void> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return\n const current = new Map<string, string>()\n const listDir = (d: string) => {\n try {\n return readdirSync(d, { withFileTypes: true })\n } catch {\n return []\n }\n }\n const walk = (d: string): void => {\n for (const e of listDir(d)) {\n const full = join(d, e.name)\n if (e.isDirectory()) walk(full)\n else if (e.isFile()) {\n try {\n current.set(relative(mem.dir, full), readFileSync(full, 'utf8'))\n } catch {\n /* skip unreadable */\n }\n }\n }\n }\n walk(mem.dir)\n const writes: { path: string; content: string }[] = []\n for (const [path, content] of current) if (mem.snapshot.get(path) !== sha256(content)) writes.push({ path, content })\n const deletes: string[] = []\n for (const path of mem.snapshot.keys()) if (!current.has(path)) deletes.push(path)\n if (!writes.length && !deletes.length) return\n try {\n await fetch(`${apiUrl}/api/agent/memory`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },\n body: JSON.stringify({ storeId, writes, deletes }),\n })\n } catch {\n /* best-effort */\n }\n}\n\nconst children = new Map<string, ChildProcess>()\n\n/** SIGTERM a job's child, escalating to SIGKILL if it ignores the term within the grace window. */\nfunction killJob(jobId: string): void {\n const child = children.get(jobId)\n if (!child) return\n child.kill('SIGTERM')\n setTimeout(() => {\n if (children.get(jobId) === child) child.kill('SIGKILL')\n }, KILL_GRACE_MS)\n}\n\nfunction killAllJobs(): void {\n for (const jobId of children.keys()) killJob(jobId)\n}\n\n// ---- concurrency cap ----\nconst jobQueue: Array<{ ws: WebSocket; frame: JobFrame }> = []\nlet activeCount = 0\n\nfunction submitJob(ws: WebSocket, frame: JobFrame): void {\n jobQueue.push({ ws, frame })\n pump()\n}\n\nfunction pump(): void {\n while (activeCount < MAX_CONCURRENT && jobQueue.length > 0) {\n const next = jobQueue.shift()!\n activeCount += 1\n void runJob(next.ws, next.frame, () => {\n activeCount -= 1\n pump()\n })\n }\n}\n\n/** Cancel a job whether it's still queued (drop it, report failed) or already running (kill it). */\nfunction cancelJob(jobId: string): void {\n const qi = jobQueue.findIndex((q) => q.frame.job.id === jobId)\n if (qi >= 0) {\n const [removed] = jobQueue.splice(qi, 1)\n if (removed.ws.readyState === WebSocket.OPEN) {\n removed.ws.send(JSON.stringify({ type: 'daemon_job_result', jobId, status: 'failed', error: 'cancelled' }))\n }\n return\n }\n killJob(jobId)\n}\n\n// Run a turn through the provider adapter selected by the agent's `kind`. The adapter builds the\n// CLI invocation, parses the provider's stream into cumulative content + tool calls, and captures\n// the native session id. Exactly one result frame is emitted, even on timeout, so the server's\n// serialization key is always freed.\nasync function runJob(ws: WebSocket, frame: JobFrame, onDone: () => void): Promise<void> {\n const { job } = frame\n // Mirror the turn's memory store into the agent dir before the CLI runs (pushed back after).\n const mem = await memoryPull(job)\n // Expose this turn's conversation/thread (so the agent can schedule itself \"back here\" via\n // POST /api/agent/schedules) plus the agent/memory dirs the CLI reads/writes.\n job.agent.env = {\n ...(job.agent.env ?? {}),\n ...(job.conversationId ? { TAKTIKO_CONVERSATION_ID: job.conversationId } : {}),\n ...(job.threadId ? { TAKTIKO_THREAD_ID: job.threadId } : {}),\n ...(mem ? { TAKTIKO_MEMORY_DIR: mem.dir, TAKTIKO_AGENT_DIR: mem.agentDir } : {}),\n }\n ws.send(JSON.stringify({ type: 'daemon_job_started', jobId: job.id }))\n\n const adapter = selectAdapter(job.agent.kind, job.agent.command)\n let lastContent = ''\n let captured: string | null = null\n const toolCalls: ToolCall[] = []\n\n let resultSent = false\n let timedOut: string | null = null\n let idleTimer: ReturnType<typeof setTimeout> | undefined\n let hardTimer: ReturnType<typeof setTimeout> | undefined\n let fallbackTimer: ReturnType<typeof setTimeout> | undefined\n\n const clearTimers = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n if (hardTimer) clearTimeout(hardTimer)\n if (fallbackTimer) clearTimeout(fallbackTimer)\n }\n const finish = (out: ResultOut): void => {\n if (resultSent) return\n resultSent = true\n clearTimers()\n children.delete(job.id)\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(out))\n onDone()\n }\n const sendUpdate = (): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n ws.send(\n JSON.stringify({ type: 'daemon_job_update', jobId: job.id, update: { content: lastContent, toolCalls } }),\n )\n }\n const resetIdle = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = setTimeout(() => {\n timedOut = `no output for ${Math.round(JOB_IDLE_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n }, JOB_IDLE_TIMEOUT_MS)\n }\n hardTimer = setTimeout(() => {\n timedOut = `exceeded ${Math.round(JOB_HARD_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n // If killing the child doesn't make the adapter settle, free the key anyway.\n fallbackTimer = setTimeout(\n () => finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}` }),\n KILL_GRACE_MS + 2_000,\n )\n }, JOB_HARD_TIMEOUT_MS)\n resetIdle()\n\n const ctx: RunContext = {\n job,\n emitContent(content) {\n resetIdle()\n if (content === lastContent) return\n lastContent = content\n sendUpdate()\n },\n emitToolCall(call) {\n resetIdle()\n toolCalls.push(call)\n sendUpdate()\n },\n setSessionId(id) {\n captured = id\n },\n spawn(command, args, opts) {\n const child = spawn(command, args, { ...opts, stdio: ['pipe', 'pipe', 'pipe'] })\n children.set(job.id, child)\n child.on('close', () => {\n if (children.get(job.id) === child) children.delete(job.id)\n })\n return child\n },\n }\n\n const pushMem = async (): Promise<void> => {\n if (mem) await memoryPush(job, mem)\n }\n adapter(ctx)\n .then(async (result) => {\n await pushMem() // persist whatever the CLI wrote to its memory dir before settling the turn\n const sessionId = result.sessionId ?? captured ?? undefined\n if (timedOut && result.status !== 'succeeded') {\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}`, sessionId })\n return\n }\n finish(\n result.status === 'succeeded'\n ? { type: 'daemon_job_result', jobId: job.id, status: 'succeeded', result: result.content, sessionId }\n : { type: 'daemon_job_result', jobId: job.id, status: 'failed', error: result.error ?? 'failed', sessionId },\n )\n })\n .catch(async (e) => {\n await pushMem()\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: String(e) })\n })\n}\n\nfunction runDaemon(cfg: DaemonConfig): void {\n acquireSingleInstanceLock()\n process.on('exit', releaseLock)\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n killAllJobs()\n releaseLock()\n process.exit(0)\n })\n }\n\n const wsUrl = `${cfg.server.replace(/^http/, 'ws')}/ws/daemon?token=${encodeURIComponent(cfg.daemonToken)}`\n let ping: ReturnType<typeof setInterval> | null = null\n let retryMs = 1000\n const MAX_RETRY_MS = 30_000\n\n function connect(): void {\n console.log(`[daemon] connecting to ${wsUrl}`)\n const ws = new WebSocket(wsUrl)\n\n ws.on('open', () => {\n console.log('[daemon] connected')\n retryMs = 1000 // reset backoff on a successful connection\n ws.send(\n JSON.stringify({\n type: 'daemon_ready',\n installedClis: probeInstalledClis(),\n workDirs: gatherWorkDirs(),\n homeDir: homedir(),\n }),\n )\n ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send(JSON.stringify({ type: 'ping' })), 25_000)\n })\n ws.on('message', (raw: Buffer) => {\n let frame: { type: string } & Record<string, unknown>\n try {\n frame = JSON.parse(raw.toString())\n } catch {\n return\n }\n if (frame.type === 'daemon_job') {\n submitJob(ws, frame as unknown as JobFrame)\n } else if (frame.type === 'daemon_job_cancel') {\n cancelJob(frame.jobId as string)\n }\n })\n ws.on('close', (code: number) => {\n if (ping) clearInterval(ping)\n if (code === 4000) {\n console.error('[daemon] another daemon on this machine took over (superseded); exiting')\n killAllJobs()\n releaseLock()\n process.exit(0)\n }\n console.log(`[daemon] disconnected; retrying in ${Math.round(retryMs / 1000)}s`)\n setTimeout(connect, retryMs)\n retryMs = Math.min(retryMs * 2, MAX_RETRY_MS)\n })\n ws.on('error', (e: Error) => console.error('[daemon] ws error:', e.message))\n }\n\n connect()\n}\n\nconst program = new Command()\nprogram\n .name('taktiko')\n .description('Taktiko daemon — runs your local agent CLIs')\n // Stamped at publish time by the @taktiko/cli build (tsup define); '0.0.0-dev' when run from source.\n .version(process.env.TAKTIKO_CLI_VERSION ?? '0.0.0-dev')\n\nprogram\n .command('pair <pairingToken>')\n .description('redeem a pairing token from the server and store this machine as a daemon')\n .option('--server <url>', 'server base url', process.env.TAKTIKO_SERVER ?? 'http://localhost:7100')\n .option('--name <name>', 'daemon name', hostname())\n .action(async (pairingToken: string, opts: { server: string; name: string }) => {\n const res = await fetch(`${opts.server}/api/daemons/pair`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ pairingToken, name: opts.name, machine: hostname() }),\n })\n const data = (await res.json()) as { daemonToken?: string; daemonId?: string; error?: string }\n if (!res.ok || !data.daemonToken) {\n console.error('pairing failed:', data)\n process.exit(1)\n }\n saveConfig({ server: opts.server, daemonToken: data.daemonToken })\n console.log(`paired. daemonId=${data.daemonId}. config saved to ${CONFIG_PATH}`)\n })\n\nprogram\n .command('daemon', { isDefault: true })\n .description('connect to the server and run dispatched turns')\n .option('--server <url>', 'override server base url')\n .action((opts: { server?: string }) => {\n const cfg = loadConfig()\n if (!cfg) {\n console.error(`no daemon config — run \\`taktiko pair <token>\\` first (or set TAKTIKO_SERVER + TAKTIKO_DAEMON_TOKEN)`)\n process.exit(1)\n }\n runDaemon({ ...cfg, server: opts.server ?? cfg.server })\n })\n\nprogram.parseAsync(process.argv)\n","// Backend presets — the fixed set of provider+model combinations an agent can run as (adapted from\n// the reference IM). Each agent owns its backend; picking one rewrites the agent's runtime columns\n// (kind/command/args/model). The user picks a model directly — no free-form command, no `generic`.\n//\n// taktiko's adapters inject `--model` from the agent's `model` column (see daemon/adapters/*), so a\n// preset only needs kind + command + model; `args` stays empty. A null model = the CLI's own default\n// (the adapter then injects no `--model`, so fast-moving CLIs like codex/kimi don't get pinned stale).\n\nexport interface BackendPreset {\n id: string\n displayName: string\n kind: string\n command: string\n args: string[]\n model: string | null\n}\n\nexport const BACKEND_PRESETS: BackendPreset[] = [\n { id: 'claude-opus', displayName: 'Claude Code · Opus', kind: 'claude', command: 'claude', args: [], model: 'opus' },\n { id: 'claude-sonnet', displayName: 'Claude Code · Sonnet', kind: 'claude', command: 'claude', args: [], model: 'sonnet' },\n { id: 'codex', displayName: 'Codex', kind: 'codex', command: 'codex', args: [], model: null },\n { id: 'kimi', displayName: 'Kimi', kind: 'kimi', command: 'kimi', args: [], model: null },\n]\n\nexport const DEFAULT_BACKEND_ID = 'claude-sonnet'\n\nexport function getBackendPreset(id: string | null | undefined): BackendPreset | null {\n if (!id) return null\n return BACKEND_PRESETS.find((b) => b.id === id) ?? null\n}\n\n// ── CLI catalog ────────────────────────────────────────────────────────────────────────────────\n// The agent editor picks a CLI first — constrained to the ones actually installed on the chosen\n// computer (the daemon probes its PATH and reports them) — then a model from that CLI's list. This is\n// the two-level decomposition of the flat presets above. `command` doubles as the binary probed on\n// PATH. An empty `models` array = the CLI's own default (no `--model`), for single-line / fast-moving\n// CLIs like codex & kimi (avoids pinning a version that goes stale).\nexport interface CliModelOption {\n id: string // stored on agent.model\n displayName: string\n}\nexport interface CliOption {\n kind: string // adapter selector + stored on agent.kind\n command: string // executable the daemon spawns + the binary probed on PATH\n displayName: string\n models: CliModelOption[]\n}\n\nexport const CLI_CATALOG: CliOption[] = [\n {\n kind: 'claude',\n command: 'claude',\n displayName: 'Claude Code',\n models: [\n { id: 'opus', displayName: 'Opus' },\n { id: 'sonnet', displayName: 'Sonnet' },\n { id: 'haiku', displayName: 'Haiku' },\n ],\n },\n { kind: 'codex', command: 'codex', displayName: 'Codex', models: [] },\n { kind: 'kimi', command: 'kimi', displayName: 'Kimi', models: [] },\n]\n\n/** The CLI binaries the daemon probes for on PATH. */\nexport const CLI_COMMANDS: string[] = CLI_CATALOG.map((c) => c.command)\n","// Provider adapter contract for the daemon. Each adapter knows how to drive one family of agent\n// CLIs (Claude Code, codex, kimi, …) in non-interactive mode: build the right argv, parse the\n// provider's streaming output into cumulative text + tool calls, and capture the provider-native\n// session id so the next turn can resume. Modeled on botapp/multica/kitty's per-provider runners.\nimport { type ChildProcess, spawn, type SpawnOptionsWithoutStdio } from 'node:child_process'\nimport { createInterface } from 'node:readline'\n\nexport interface AdapterJob {\n id: string\n query: string\n resumeSessionId: string | null\n agent: {\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n}\n\n/** A tool invocation surfaced to the UI as the turn runs (rendered as a chip on the message). */\nexport interface ToolCall {\n id?: string\n name: string\n input?: unknown\n}\n\nexport interface RunContext {\n job: AdapterJob\n /** Stream a cumulative content update to the server (it overwrites the assistant message). */\n emitContent(content: string): void\n /** Append a tool call to the cumulative list streamed alongside content (for live tool chips). */\n emitToolCall(call: ToolCall): void\n /** Record the provider-native session id (returned in the result for next-turn resume). */\n setSessionId(id: string): void\n /** Spawn a child registered for cancellation (SIGTERM on daemon_job_cancel). */\n spawn(command: string, args: string[], opts: SpawnOptionsWithoutStdio): ChildProcess\n}\n\nexport interface AdapterResult {\n status: 'succeeded' | 'failed'\n content: string\n error?: string\n sessionId: string | null\n}\n\nexport type Adapter = (ctx: RunContext) => Promise<AdapterResult>\n\nexport const hasFlag = (args: string[], ...flags: string[]): boolean => flags.some((f) => args.includes(f))\n\n/** Spawn a child and stream stdout/stderr line-by-line; resolves once both the process has exited\n * and stdout has been fully drained (so the last JSON line is never dropped). */\nexport async function spawnLines(\n ctx: RunContext,\n command: string,\n args: string[],\n opts: {\n cwd?: string | null\n env?: Record<string, string> | null\n stdin?: string // written then closed; omit to just close stdin\n onLine: (line: string) => void\n onStderr?: (line: string) => void\n },\n): Promise<{ code: number | null; stderr: string }> {\n const child = ctx.spawn(command, args, {\n cwd: opts.cwd ?? undefined,\n env: { ...process.env, ...(opts.env ?? {}) },\n })\n let stderr = ''\n const out = createInterface({ input: child.stdout! })\n out.on('line', (line) => {\n try {\n opts.onLine(line)\n } catch {\n /* a malformed line must not kill the run */\n }\n })\n if (child.stderr) {\n const err = createInterface({ input: child.stderr })\n err.on('line', (line) => {\n stderr += `${line}\\n`\n opts.onStderr?.(line)\n })\n }\n if (opts.stdin !== undefined) {\n child.stdin?.write(opts.stdin)\n }\n child.stdin?.end()\n\n const exited = new Promise<number | null>((resolve) => child.on('close', resolve))\n const drained = new Promise<void>((resolve) => out.on('close', () => resolve()))\n const code = await exited\n await drained\n return { code, stderr }\n}\n\n/** Parse a line as JSON only if it looks like a JSON object (skips human-readable noise). */\nexport function parseJsonLine(line: string): Record<string, unknown> | null {\n const t = line.trim()\n if (!t || t[0] !== '{') return null\n try {\n return JSON.parse(t) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nexport { spawn } // re-export for the runner\n","// Claude Code adapter: `claude -p --output-format stream-json --verbose [...] [--resume <sid>]`,\n// prompt over stdin (avoids argv length limits). Parses the stream-json events, accumulates\n// assistant text, and captures session_id (present on every event) for next-turn --resume.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const claudeAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, '-p', '--print')) args.push('-p')\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (!args.includes('--verbose')) args.push('--verbose')\n if (a.model && !args.includes('--model')) args.push('--model', a.model)\n if (!hasFlag(args, '--permission-mode', '--dangerously-skip-permissions')) {\n args.push('--permission-mode', 'bypassPermissions')\n }\n if (a.systemPrompt && !args.includes('--append-system-prompt')) {\n args.push('--append-system-prompt', a.systemPrompt)\n }\n const resume = job.resumeSessionId ?? a.env?.CLAUDE_SESSION_ID ?? null\n if (resume && !args.includes('--resume')) args.push('--resume', resume)\n\n let sessionId: string | null = resume\n let finalText = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const sid = (ev.session_id ?? ev.sessionId) as string | undefined\n if (typeof sid === 'string' && sid) {\n sessionId = sid\n ctx.setSessionId(sid)\n }\n const msg = ev.message as { content?: unknown } | undefined\n if (ev.type === 'assistant' && Array.isArray(msg?.content)) {\n for (const block of msg.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n } else if (block?.type === 'tool_use' && typeof block.name === 'string') {\n ctx.emitToolCall({ id: typeof block.id === 'string' ? block.id : undefined, name: block.name, input: block.input })\n }\n }\n } else if (ev.type === 'result' && typeof ev.result === 'string') {\n finalText = ev.result\n ctx.emitContent(finalText)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n stdin: job.query,\n onLine,\n })\n\n const content = finalText || messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `claude exited with code ${code}`, sessionId }\n}\n","// Codex adapter: `codex exec [resume <thread>] --json --skip-git-repo-check\n// --dangerously-bypass-approvals-and-sandbox [--model X] <query>`. Parses the NDJSON event\n// stream; the thread id (from thread.started) is the session handle reused via `exec resume`.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\n// Friendlier chip labels for codex's structured item types (anything else uses the raw type).\nconst CODEX_TOOL_NAMES: Record<string, string> = {\n command_execution: 'shell',\n file_change: 'edit',\n mcp_tool_call: 'mcp',\n web_search: 'web_search',\n}\n\nexport const codexAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, 'exec', 'e')) args.unshift('exec')\n const resume = job.resumeSessionId ?? null\n if (resume && !args.includes('resume')) {\n const i = args.indexOf('exec')\n args.splice(i + 1, 0, 'resume', resume) // codex exec resume <thread_id>\n }\n if (!args.includes('--json')) args.push('--json')\n if (!args.includes('--skip-git-repo-check')) args.push('--skip-git-repo-check')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n if (!hasFlag(args, '--sandbox', '-s', '--ask-for-approval', '-a', '--full-auto', '--dangerously-bypass-approvals-and-sandbox')) {\n args.push('--dangerously-bypass-approvals-and-sandbox')\n }\n args.push(job.query)\n\n let sessionId: string | null = resume\n const messages: string[] = []\n const errors: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const tid = (ev.thread_id ?? ev.session_id) as string | undefined\n if (typeof tid === 'string' && tid) {\n sessionId = tid\n ctx.setSessionId(tid)\n }\n const item = ev.item as Record<string, unknown> | undefined\n if (ev.type === 'item.completed' && item?.type === 'agent_message' && typeof item.text === 'string') {\n messages.push(item.text)\n ctx.emitContent(messages.join('\\n'))\n } else if (ev.type === 'item.completed' && item && item.type !== 'reasoning' && item.type !== 'agent_message') {\n // command_execution / file_change / mcp_tool_call / web_search → a tool chip.\n const name = CODEX_TOOL_NAMES[item.type as string] ?? (typeof item.type === 'string' ? item.type : 'tool')\n ctx.emitToolCall({ id: typeof item.id === 'string' ? item.id : undefined, name, input: item })\n } else if (ev.type === 'error' && typeof ev.message === 'string') {\n errors.push(ev.message)\n } else if (ev.type === 'turn.failed') {\n const err = ev.error as { message?: string } | undefined\n if (err?.message) errors.push(err.message)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, { cwd: a.cwd, env: a.env, onLine })\n\n const content = messages.join('\\n')\n if (code === 0 && !errors.length) return { status: 'succeeded', content, sessionId }\n const error = errors.join('; ') || stderr.trim() || `codex exited with code ${code}`\n return { status: 'failed', content, error, sessionId }\n}\n","// Generic stdio adapter — the fallback for any command without a provider-specific adapter.\n// Runs `<command> <args...> [--resume <id>] <query>` and streams raw stdout as the reply. No\n// structured parsing, so no native session capture (the server still threads --resume through).\nimport { type Adapter, spawnLines } from './types.js'\n\nexport const genericAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n if (job.resumeSessionId) args.push('--resume', job.resumeSessionId)\n args.push(job.query)\n\n let out = ''\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine: (line) => {\n out += `${line}\\n`\n ctx.emitContent(out.trimEnd())\n },\n })\n\n const content = out.trim()\n if (code === 0) return { status: 'succeeded', content, sessionId: job.resumeSessionId ?? null }\n return { status: 'failed', content, error: stderr.trim() || `process exited with code ${code}`, sessionId: null }\n}\n","// Kimi adapter: `kimi -p \"<prompt>\" --output-format stream-json [-r <sid>] [--model X]`.\n// Parses the stream-json events; the session id arrives as a meta `session.resume_hint` event\n// (with a stderr \"kimi -r <id>\" fallback) and is reused via `-r` on the next turn.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const kimiAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n const resume = job.resumeSessionId ?? a.env?.KIMI_SESSION_ID ?? null\n if (resume && !hasFlag(args, '-r', '--resume', '--session', '-S', '--continue', '-C')) {\n args.push('-r', resume)\n }\n if (!hasFlag(args, '-p', '--prompt')) args.push('-p', job.query)\n\n let sessionId: string | null = resume\n let stderrAll = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n if (ev.role === 'assistant') {\n if (typeof ev.content === 'string' && ev.content) {\n messages.push(ev.content)\n ctx.emitContent(messages.join(''))\n } else if (Array.isArray(ev.content)) {\n for (const block of ev.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n }\n }\n }\n } else if (ev.role === 'meta' && ev.type === 'session.resume_hint' && typeof ev.session_id === 'string') {\n sessionId = ev.session_id\n ctx.setSessionId(ev.session_id)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine,\n onStderr: (l) => {\n stderrAll += `${l}\\n`\n },\n })\n\n if (!sessionId || sessionId === resume) {\n const m = (stderr || stderrAll).match(/kimi\\s+-r\\s+([A-Za-z0-9_-]+)/)\n if (m) {\n sessionId = m[1]\n ctx.setSessionId(m[1])\n }\n }\n\n const content = messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `kimi exited with code ${code}`, sessionId }\n}\n","// Adapter registry — pick a provider adapter by the agent's `kind` (the migration's \"adapter\n// selector\"), falling back to the command name, then to the generic stdio runner.\nimport { claudeAdapter } from './claude.js'\nimport { codexAdapter } from './codex.js'\nimport { genericAdapter } from './generic.js'\nimport { kimiAdapter } from './kimi.js'\nimport type { Adapter } from './types.js'\n\nexport type { Adapter, AdapterJob, AdapterResult, RunContext, ToolCall } from './types.js'\n\nconst BY_KIND: Record<string, Adapter> = {\n claude: claudeAdapter,\n 'claude-code': claudeAdapter,\n claude_code: claudeAdapter,\n codex: codexAdapter,\n kimi: kimiAdapter,\n generic: genericAdapter,\n shell: genericAdapter,\n}\n\n/** Resolve an adapter for an agent. `kind` wins; otherwise infer from the command basename. */\nexport function selectAdapter(kind: string | null | undefined, command: string): Adapter {\n if (kind && BY_KIND[kind]) return BY_KIND[kind]\n const base = (command.split('/').pop() ?? command).toLowerCase()\n if (base === 'claude') return claudeAdapter\n if (base === 'codex') return codexAdapter\n if (base === 'kimi') return kimiAdapter\n return genericAdapter\n}\n"],"mappings":"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","names":["spawn","spawn"]}
|
|
1
|
+
{"version":3,"sources":["../../backend/src/daemon/cli.ts","../../backend/src/backends.ts","../../backend/src/daemon/adapters/types.ts","../../backend/src/daemon/adapters/claude.ts","../../backend/src/daemon/adapters/codex.ts","../../backend/src/daemon/adapters/generic.ts","../../backend/src/daemon/adapters/kimi.ts","../../backend/src/daemon/adapters/index.ts","../../backend/src/daemon/service.ts"],"sourcesContent":["#!/usr/bin/env node\n// `taktiko` — the daemon that runs on a user's machine. It pairs with the server, connects over\n// WebSocket, and for each dispatched job runs the agent's local CLI (e.g. `claude`), streaming\n// stdout back as the reply.\n//\n// Provider-specific adapters (src/daemon/adapters/) build the CLI invocation, parse the stream, and\n// capture the native session id for resume. The runner here adds the machine-side hardening:\n// single-instance locking, a per-job timeout/idle watchdog, SIGTERM→SIGKILL escalation, a\n// concurrency cap, and exponential-backoff reconnect.\nimport { type ChildProcess, execFileSync, spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { homedir } from 'node:os'\nimport { dirname, join, relative } from 'node:path'\n\nimport { Command } from 'commander'\nimport WebSocket from 'ws'\n\nimport { CLI_COMMANDS } from '../backends.js'\nimport { type RunContext, type ToolCall, selectAdapter } from './adapters/index.js'\nimport * as service from './service.js'\n\n// ── machine capability probing ───────────────────────────────────────────────────────────────────\n// Reported to the server on connect so the agent editor offers only the CLIs/dirs this computer has.\n\n/** Which known agent CLIs are on PATH (resolved via which/where, so PATH shims count). */\nfunction probeInstalledClis(): string[] {\n const finder = process.platform === 'win32' ? 'where' : 'which'\n return CLI_COMMANDS.filter((cmd) => {\n try {\n execFileSync(finder, [cmd], { stdio: 'ignore' })\n return true\n } catch {\n return false\n }\n })\n}\n\n/** Candidate project directories to offer as the agent's working dir: common dev roots plus the\n * non-hidden immediate subdirectories of home. Home leads the list; capped so the picker stays sane. */\nfunction gatherWorkDirs(): string[] {\n const home = homedir()\n const isDir = (p: string): boolean => {\n try {\n return statSync(p).isDirectory()\n } catch {\n return false\n }\n }\n const out: string[] = [home]\n const seen = new Set(out)\n const add = (p: string) => {\n if (!seen.has(p) && isDir(p)) {\n seen.add(p)\n out.push(p)\n }\n }\n for (const name of ['Projects', 'projects', 'code', 'Code', 'dev', 'Developer', 'work', 'repos', 'src']) {\n add(join(home, name))\n }\n // Standard OS home folders that are never project dirs — keep the picker focused.\n const NOISE = new Set([\n 'Library', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Movies', 'Music',\n 'Pictures', 'Public', 'Templates', 'Videos', 'AppData', 'OneDrive',\n ])\n try {\n for (const ent of readdirSync(home, { withFileTypes: true })) {\n if (ent.isDirectory() && !ent.name.startsWith('.') && !NOISE.has(ent.name)) add(join(home, ent.name))\n }\n } catch {\n /* unreadable home — home alone is fine */\n }\n return out.slice(0, 50)\n}\n\ninterface DaemonConfig {\n server: string\n daemonToken: string\n}\n\n// Shared with service.ts so the background supervisor and the run loop agree on the pidfile/dir.\nconst CONFIG_PATH = join(service.CONFIG_DIR, 'daemon.json')\nconst PID_PATH = service.PID_FILE\n\n// A hung CLI must never wedge the server's per-conversation serialization key, so every job is\n// bounded by a hard wall-clock cap and an idle (no-output) cap; both are env-overridable.\nconst JOB_HARD_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_TIMEOUT_MS) || 10 * 60_000\nconst JOB_IDLE_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_IDLE_MS) || 3 * 60_000\nconst KILL_GRACE_MS = 3_000\n// Cap concurrent local CLIs so one user's many conversations can't fork-bomb their machine.\nconst MAX_CONCURRENT = Number(process.env.TAKTIKO_MAX_CONCURRENT) || 20\n\nfunction loadConfig(): DaemonConfig | null {\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) as DaemonConfig\n } catch {\n const server = process.env.TAKTIKO_SERVER\n const daemonToken = process.env.TAKTIKO_DAEMON_TOKEN\n return server && daemonToken ? { server, daemonToken } : null\n }\n}\n\nfunction saveConfig(cfg: DaemonConfig): void {\n mkdirSync(dirname(CONFIG_PATH), { recursive: true })\n writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2))\n}\n\n// ---- single-instance lock ----\n// Two daemons sharing one token would both pull jobs → double execution (e.g. double trades). The\n// pidfile blocks a second local daemon; the server independently supersedes a stale connection.\nfunction acquireSingleInstanceLock(): void {\n try {\n const existing = Number(readFileSync(PID_PATH, 'utf8').trim())\n if (existing && existing !== process.pid) {\n try {\n process.kill(existing, 0) // throws if the pid is gone\n console.error(`[daemon] already running (pid ${existing}); refusing to start a second daemon on this machine`)\n process.exit(1)\n } catch {\n /* stale pidfile — overwrite it */\n }\n }\n } catch {\n /* no pidfile yet — first run */\n }\n mkdirSync(dirname(PID_PATH), { recursive: true })\n writeFileSync(PID_PATH, String(process.pid))\n}\n\nfunction releaseLock(): void {\n try {\n if (Number(readFileSync(PID_PATH, 'utf8').trim()) === process.pid) unlinkSync(PID_PATH)\n } catch {\n /* ignore */\n }\n}\n\ntype ResultOut =\n | { type: 'daemon_job_result'; jobId: string; status: 'succeeded'; result: string; sessionId?: string }\n | { type: 'daemon_job_result'; jobId: string; status: 'failed'; error: string; sessionId?: string }\n\ninterface JobFrame {\n type: 'daemon_job'\n job: {\n id: string\n query: string\n conversationId: string | null\n threadId: string | null\n resumeSessionId: string | null\n agent: {\n id: string\n name: string\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n }\n}\n\n// ── memory sync ──────────────────────────────────────────────────────────────────────────────────\n// Server-side memory is a versioned doc store; with a local daemon we mirror the turn's store into a\n// dedicated agent dir the CLI reads/writes with normal file tools, then push the diff back. The agent's\n// own skills live alongside (the agent dir, $TAKTIKO_AGENT_DIR); workspace skills live in the env's\n// working dir — both reachable by the CLI, both meant to land in the system prompt's context.\nconst sha256 = (s: string) => createHash('sha256').update(s).digest('hex')\n\ninterface MemoryCtx {\n dir: string // <agentDir>/memory — the mirrored store\n agentDir: string // ~/.taktiko/agents/<id> — memory + the agent's own skills\n snapshot: Map<string, string> // store-relative path → sha256(content) as pulled\n}\n\n/** Pull the turn's memory store into the agent dir before the CLI runs. Resilient: never throws. */\nasync function memoryPull(job: JobFrame['job']): Promise<MemoryCtx | null> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return null\n const agentDir = join(homedir(), '.taktiko', 'agents', job.agent.id)\n const dir = join(agentDir, 'memory')\n const snapshot = new Map<string, string>()\n try {\n rmSync(dir, { recursive: true, force: true }) // mirror exactly — reflect server-side deletes\n mkdirSync(dir, { recursive: true })\n const res = await fetch(`${apiUrl}/api/agent/memory?storeId=${encodeURIComponent(storeId)}`, {\n headers: { authorization: `Bearer ${token}` },\n })\n if (res.ok) {\n const data = (await res.json()) as { docs?: { path: string; content: string }[] }\n for (const doc of data.docs ?? []) {\n const p = join(dir, doc.path)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, doc.content)\n snapshot.set(doc.path, sha256(doc.content))\n }\n }\n } catch {\n /* server unreachable — run with whatever is (or isn't) on disk */\n }\n return { dir, agentDir, snapshot }\n}\n\n/** Push back any memory the CLI changed in the agent dir. Resilient: never throws. */\nasync function memoryPush(job: JobFrame['job'], mem: MemoryCtx): Promise<void> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return\n const current = new Map<string, string>()\n const listDir = (d: string) => {\n try {\n return readdirSync(d, { withFileTypes: true })\n } catch {\n return []\n }\n }\n const walk = (d: string): void => {\n for (const e of listDir(d)) {\n const full = join(d, e.name)\n if (e.isDirectory()) walk(full)\n else if (e.isFile()) {\n try {\n current.set(relative(mem.dir, full), readFileSync(full, 'utf8'))\n } catch {\n /* skip unreadable */\n }\n }\n }\n }\n walk(mem.dir)\n const writes: { path: string; content: string }[] = []\n for (const [path, content] of current) if (mem.snapshot.get(path) !== sha256(content)) writes.push({ path, content })\n const deletes: string[] = []\n for (const path of mem.snapshot.keys()) if (!current.has(path)) deletes.push(path)\n if (!writes.length && !deletes.length) return\n try {\n await fetch(`${apiUrl}/api/agent/memory`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },\n body: JSON.stringify({ storeId, writes, deletes }),\n })\n } catch {\n /* best-effort */\n }\n}\n\nconst children = new Map<string, ChildProcess>()\n\n/** SIGTERM a job's child, escalating to SIGKILL if it ignores the term within the grace window. */\nfunction killJob(jobId: string): void {\n const child = children.get(jobId)\n if (!child) return\n child.kill('SIGTERM')\n setTimeout(() => {\n if (children.get(jobId) === child) child.kill('SIGKILL')\n }, KILL_GRACE_MS)\n}\n\nfunction killAllJobs(): void {\n for (const jobId of children.keys()) killJob(jobId)\n}\n\n// ---- concurrency cap ----\nconst jobQueue: Array<{ ws: WebSocket; frame: JobFrame }> = []\nlet activeCount = 0\n\nfunction submitJob(ws: WebSocket, frame: JobFrame): void {\n jobQueue.push({ ws, frame })\n pump()\n}\n\nfunction pump(): void {\n while (activeCount < MAX_CONCURRENT && jobQueue.length > 0) {\n const next = jobQueue.shift()!\n activeCount += 1\n void runJob(next.ws, next.frame, () => {\n activeCount -= 1\n pump()\n })\n }\n}\n\n/** Cancel a job whether it's still queued (drop it, report failed) or already running (kill it). */\nfunction cancelJob(jobId: string): void {\n const qi = jobQueue.findIndex((q) => q.frame.job.id === jobId)\n if (qi >= 0) {\n const [removed] = jobQueue.splice(qi, 1)\n if (removed.ws.readyState === WebSocket.OPEN) {\n removed.ws.send(JSON.stringify({ type: 'daemon_job_result', jobId, status: 'failed', error: 'cancelled' }))\n }\n return\n }\n killJob(jobId)\n}\n\n// Run a turn through the provider adapter selected by the agent's `kind`. The adapter builds the\n// CLI invocation, parses the provider's stream into cumulative content + tool calls, and captures\n// the native session id. Exactly one result frame is emitted, even on timeout, so the server's\n// serialization key is always freed.\nasync function runJob(ws: WebSocket, frame: JobFrame, onDone: () => void): Promise<void> {\n const { job } = frame\n // Mirror the turn's memory store into the agent dir before the CLI runs (pushed back after).\n const mem = await memoryPull(job)\n // Expose this turn's conversation/thread (so the agent can schedule itself \"back here\" via\n // POST /api/agent/schedules) plus the agent/memory dirs the CLI reads/writes.\n job.agent.env = {\n ...(job.agent.env ?? {}),\n ...(job.conversationId ? { TAKTIKO_CONVERSATION_ID: job.conversationId } : {}),\n ...(job.threadId ? { TAKTIKO_THREAD_ID: job.threadId } : {}),\n ...(mem ? { TAKTIKO_MEMORY_DIR: mem.dir, TAKTIKO_AGENT_DIR: mem.agentDir } : {}),\n }\n ws.send(JSON.stringify({ type: 'daemon_job_started', jobId: job.id }))\n\n const adapter = selectAdapter(job.agent.kind, job.agent.command)\n let lastContent = ''\n let captured: string | null = null\n const toolCalls: ToolCall[] = []\n\n let resultSent = false\n let timedOut: string | null = null\n let idleTimer: ReturnType<typeof setTimeout> | undefined\n let hardTimer: ReturnType<typeof setTimeout> | undefined\n let fallbackTimer: ReturnType<typeof setTimeout> | undefined\n\n const clearTimers = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n if (hardTimer) clearTimeout(hardTimer)\n if (fallbackTimer) clearTimeout(fallbackTimer)\n }\n const finish = (out: ResultOut): void => {\n if (resultSent) return\n resultSent = true\n clearTimers()\n children.delete(job.id)\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(out))\n onDone()\n }\n const sendUpdate = (): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n ws.send(\n JSON.stringify({ type: 'daemon_job_update', jobId: job.id, update: { content: lastContent, toolCalls } }),\n )\n }\n const resetIdle = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = setTimeout(() => {\n timedOut = `no output for ${Math.round(JOB_IDLE_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n }, JOB_IDLE_TIMEOUT_MS)\n }\n hardTimer = setTimeout(() => {\n timedOut = `exceeded ${Math.round(JOB_HARD_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n // If killing the child doesn't make the adapter settle, free the key anyway.\n fallbackTimer = setTimeout(\n () => finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}` }),\n KILL_GRACE_MS + 2_000,\n )\n }, JOB_HARD_TIMEOUT_MS)\n resetIdle()\n\n const ctx: RunContext = {\n job,\n emitContent(content) {\n resetIdle()\n if (content === lastContent) return\n lastContent = content\n sendUpdate()\n },\n emitToolCall(call) {\n resetIdle()\n toolCalls.push(call)\n sendUpdate()\n },\n setSessionId(id) {\n captured = id\n },\n spawn(command, args, opts) {\n const child = spawn(command, args, { ...opts, stdio: ['pipe', 'pipe', 'pipe'] })\n children.set(job.id, child)\n child.on('close', () => {\n if (children.get(job.id) === child) children.delete(job.id)\n })\n return child\n },\n }\n\n const pushMem = async (): Promise<void> => {\n if (mem) await memoryPush(job, mem)\n }\n adapter(ctx)\n .then(async (result) => {\n await pushMem() // persist whatever the CLI wrote to its memory dir before settling the turn\n const sessionId = result.sessionId ?? captured ?? undefined\n if (timedOut && result.status !== 'succeeded') {\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}`, sessionId })\n return\n }\n finish(\n result.status === 'succeeded'\n ? { type: 'daemon_job_result', jobId: job.id, status: 'succeeded', result: result.content, sessionId }\n : { type: 'daemon_job_result', jobId: job.id, status: 'failed', error: result.error ?? 'failed', sessionId },\n )\n })\n .catch(async (e) => {\n await pushMem()\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: String(e) })\n })\n}\n\nfunction runDaemon(cfg: DaemonConfig): void {\n acquireSingleInstanceLock()\n process.on('exit', releaseLock)\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n killAllJobs()\n releaseLock()\n process.exit(0)\n })\n }\n\n const wsUrl = `${cfg.server.replace(/^http/, 'ws')}/ws/daemon?token=${encodeURIComponent(cfg.daemonToken)}`\n let ping: ReturnType<typeof setInterval> | null = null\n let retryMs = 1000\n const MAX_RETRY_MS = 30_000\n\n function connect(): void {\n console.log(`[daemon] connecting to ${wsUrl}`)\n const ws = new WebSocket(wsUrl)\n\n ws.on('open', () => {\n console.log('[daemon] connected')\n retryMs = 1000 // reset backoff on a successful connection\n ws.send(\n JSON.stringify({\n type: 'daemon_ready',\n installedClis: probeInstalledClis(),\n workDirs: gatherWorkDirs(),\n homeDir: homedir(),\n }),\n )\n ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send(JSON.stringify({ type: 'ping' })), 25_000)\n })\n ws.on('message', (raw: Buffer) => {\n let frame: { type: string } & Record<string, unknown>\n try {\n frame = JSON.parse(raw.toString())\n } catch {\n return\n }\n if (frame.type === 'daemon_job') {\n submitJob(ws, frame as unknown as JobFrame)\n } else if (frame.type === 'daemon_job_cancel') {\n cancelJob(frame.jobId as string)\n }\n })\n ws.on('close', (code: number) => {\n if (ping) clearInterval(ping)\n if (code === 4000) {\n console.error('[daemon] another daemon on this machine took over (superseded); exiting')\n killAllJobs()\n releaseLock()\n process.exit(0)\n }\n console.log(`[daemon] disconnected; retrying in ${Math.round(retryMs / 1000)}s`)\n setTimeout(connect, retryMs)\n retryMs = Math.min(retryMs * 2, MAX_RETRY_MS)\n })\n ws.on('error', (e: Error) => console.error('[daemon] ws error:', e.message))\n }\n\n connect()\n}\n\nconst program = new Command()\nprogram\n .name('taktiko')\n .description('Taktiko daemon — runs your local agent CLIs')\n // Stamped at publish time by the @taktiko/cli build (tsup define); '0.0.0-dev' when run from source.\n .version(process.env.TAKTIKO_CLI_VERSION ?? '0.0.0-dev')\n\nprogram\n .command('pair <pairingToken>')\n .description('redeem a pairing token from the server and store this machine as a daemon')\n .option('--server <url>', 'server base url', process.env.TAKTIKO_SERVER ?? 'http://localhost:7100')\n .option('--name <name>', 'daemon name', hostname())\n .action(async (pairingToken: string, opts: { server: string; name: string }) => {\n const res = await fetch(`${opts.server}/api/daemons/pair`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ pairingToken, name: opts.name, machine: hostname() }),\n })\n const data = (await res.json()) as { daemonToken?: string; daemonId?: string; error?: string }\n if (!res.ok || !data.daemonToken) {\n console.error('pairing failed:', data)\n process.exit(1)\n }\n saveConfig({ server: opts.server, daemonToken: data.daemonToken })\n console.log(`paired. daemonId=${data.daemonId}. config saved to ${CONFIG_PATH}`)\n })\n\n/** The long-running foreground loop. `taktiko start` spawns this detached; the OS service runs it too. */\nprogram\n .command('daemon')\n .description('run the daemon in the foreground (connect to the server and run dispatched turns)')\n .option('--server <url>', 'override server base url')\n .action((opts: { server?: string }) => {\n const cfg = requireConfig()\n runDaemon({ ...cfg, server: opts.server ?? cfg.server })\n })\n\nprogram\n .command('start')\n .description('start the daemon in the background — no terminal needed (logs to ~/.taktiko/daemon.log)')\n .option('--foreground', 'run in the foreground instead (for debugging)')\n .option('--server <url>', 'override server base url')\n .action((opts: { foreground?: boolean; server?: string }) => {\n const cfg = requireConfig()\n if (opts.foreground) {\n runDaemon({ ...cfg, server: opts.server ?? cfg.server })\n return\n }\n if (service.isRunning()) {\n console.log(`taktiko daemon is already running (pid ${service.readPid()}). \\`taktiko status\\` for details`)\n return\n }\n try {\n const pid = service.spawnDetached(opts.server ? ['--server', opts.server] : [])\n console.log(`taktiko daemon started in the background (pid ${pid})`)\n console.log(` status: taktiko status`)\n console.log(` logs: taktiko logs -f`)\n console.log(` stop: taktiko stop`)\n console.log(` login: taktiko install → also auto-start on every login/reboot`)\n } catch (e) {\n console.error(`failed to start: ${e instanceof Error ? e.message : String(e)}`)\n process.exit(1)\n }\n })\n\nprogram\n .command('stop')\n .description('stop the background daemon')\n .action(() => {\n const pid = service.stopRunning()\n console.log(pid ? `stopped taktiko daemon (pid ${pid})` : 'no running taktiko daemon')\n })\n\nprogram\n .command('restart')\n .description('restart the daemon (cycles the installed service in place, else the background process)')\n .action(async () => {\n if (service.restartService()) {\n console.log('restarted the installed taktiko service')\n return\n }\n requireConfig()\n service.stopRunning()\n await new Promise((r) => setTimeout(r, 500)) // let the old process release its single-instance lock\n try {\n const pid = service.spawnDetached()\n console.log(`restarted taktiko daemon (pid ${pid})`)\n } catch (e) {\n console.error(`failed to restart: ${e instanceof Error ? e.message : String(e)}`)\n process.exit(1)\n }\n })\n\nprogram\n .command('status')\n .description('show the local daemon process + server reachability')\n .option('--json', 'machine-readable output')\n .action(async (opts: { json?: boolean }) => {\n const info = service.processInfo()\n const svc = service.getServiceStatus()\n const cfg = loadConfig()\n let serverReachable: boolean | null = null\n if (cfg?.server) {\n try {\n const res = await fetch(`${cfg.server}/api/health`, { signal: AbortSignal.timeout(2500) })\n serverReachable = res.ok\n } catch {\n serverReachable = false\n }\n }\n if (opts.json) {\n console.log(\n JSON.stringify(\n {\n running: !!info,\n pid: info?.pid ?? null,\n uptimeMs: info?.uptimeMs ?? null,\n server: cfg?.server ?? null,\n serverReachable,\n service: svc,\n logFile: service.LOG_FILE,\n },\n null,\n 2,\n ),\n )\n return\n }\n const up = info?.uptimeMs != null ? `, up ${formatDuration(info.uptimeMs)}` : ''\n const reach = serverReachable === null ? '' : serverReachable ? ' ✓ reachable' : ' ✗ unreachable'\n console.log(`daemon: ${info ? `running (pid ${info.pid}${up})` : 'not running'}`)\n console.log(`server: ${cfg?.server ?? '(unpaired — run `taktiko pair <token>`)'}${reach}`)\n console.log(`service: ${svc.installed ? `installed (${svc.platformLabel})` : 'not installed — `taktiko install` for login auto-start'}`)\n console.log(`logs: ${service.LOG_FILE}`)\n })\n\nprogram\n .command('logs')\n .description('show the daemon log (~/.taktiko/daemon.log)')\n .option('-n, --lines <n>', 'how many trailing lines to show', '50')\n .option('-f, --follow', 'follow the log (like tail -f)')\n .action((opts: { lines: string; follow?: boolean }) => {\n const n = Math.max(0, Number.parseInt(opts.lines, 10) || 50)\n if (!existsSync(service.LOG_FILE)) {\n console.error(`no log yet at ${service.LOG_FILE} — start the daemon first (\\`taktiko start\\`)`)\n process.exit(1)\n }\n if (opts.follow) {\n const child = spawn('tail', ['-n', String(n), '-f', service.LOG_FILE], { stdio: 'inherit' })\n child.on('error', () => {\n console.error('`tail` is unavailable here; printing the last lines instead')\n printTail(n)\n })\n child.on('exit', (code) => process.exit(code ?? 0))\n return\n }\n printTail(n)\n })\n\nprogram\n .command('install')\n .description('install a login service so the daemon auto-starts on boot (launchd on macOS / systemd on Linux)')\n .action(() => {\n requireConfig()\n try {\n const { path, label } = service.installService()\n console.log(`installed the ${label} service — the daemon now starts automatically on login`)\n console.log(` unit: ${path}`)\n console.log(` logs: ${service.LOG_FILE}`)\n } catch (e) {\n console.error(`install failed: ${e instanceof Error ? e.message : String(e)}`)\n process.exit(1)\n }\n })\n\nprogram\n .command('uninstall')\n .description('remove the login service installed by `taktiko install`')\n .action(() => {\n try {\n console.log(service.uninstallService() ? 'removed the taktiko login service' : 'no taktiko login service installed')\n } catch (e) {\n console.error(`uninstall failed: ${e instanceof Error ? e.message : String(e)}`)\n process.exit(1)\n }\n })\n\n/** Load config or exit with the pairing hint — shared by daemon/start/restart/install. */\nfunction requireConfig(): DaemonConfig {\n const cfg = loadConfig()\n if (!cfg) {\n console.error('no daemon config — run `taktiko pair <token>` first (or set TAKTIKO_SERVER + TAKTIKO_DAEMON_TOKEN)')\n process.exit(1)\n }\n return cfg\n}\n\n/** Print the last `n` lines of the daemon log (log is size-capped, so reading it whole is fine). */\nfunction printTail(n: number): void {\n try {\n const lines = readFileSync(service.LOG_FILE, 'utf8').split('\\n')\n if (lines.at(-1) === '') lines.pop()\n process.stdout.write(`${lines.slice(-n).join('\\n')}\\n`)\n } catch (e) {\n console.error(`could not read ${service.LOG_FILE}: ${e instanceof Error ? e.message : String(e)}`)\n }\n}\n\nfunction formatDuration(ms: number): string {\n const s = Math.floor(ms / 1000)\n if (s < 60) return `${s}s`\n const m = Math.floor(s / 60)\n if (m < 60) return `${m}m`\n const h = Math.floor(m / 60)\n if (h < 24) return `${h}h ${m % 60}m`\n return `${Math.floor(h / 24)}d ${h % 24}h`\n}\n\nprogram.parseAsync(process.argv)\n","// Backend presets — the fixed set of provider+model combinations an agent can run as (adapted from\n// the reference IM). Each agent owns its backend; picking one rewrites the agent's runtime columns\n// (kind/command/args/model). The user picks a model directly — no free-form command, no `generic`.\n//\n// taktiko's adapters inject `--model` from the agent's `model` column (see daemon/adapters/*), so a\n// preset only needs kind + command + model; `args` stays empty. A null model = the CLI's own default\n// (the adapter then injects no `--model`, so fast-moving CLIs like codex/kimi don't get pinned stale).\n\nexport interface BackendPreset {\n id: string\n displayName: string\n kind: string\n command: string\n args: string[]\n model: string | null\n}\n\nexport const BACKEND_PRESETS: BackendPreset[] = [\n { id: 'claude-opus', displayName: 'Claude Code · Opus', kind: 'claude', command: 'claude', args: [], model: 'opus' },\n { id: 'claude-sonnet', displayName: 'Claude Code · Sonnet', kind: 'claude', command: 'claude', args: [], model: 'sonnet' },\n { id: 'codex', displayName: 'Codex', kind: 'codex', command: 'codex', args: [], model: null },\n { id: 'kimi', displayName: 'Kimi', kind: 'kimi', command: 'kimi', args: [], model: null },\n]\n\nexport const DEFAULT_BACKEND_ID = 'claude-sonnet'\n\nexport function getBackendPreset(id: string | null | undefined): BackendPreset | null {\n if (!id) return null\n return BACKEND_PRESETS.find((b) => b.id === id) ?? null\n}\n\n// ── CLI catalog ────────────────────────────────────────────────────────────────────────────────\n// The agent editor picks a CLI first — constrained to the ones actually installed on the chosen\n// computer (the daemon probes its PATH and reports them) — then a model from that CLI's list. This is\n// the two-level decomposition of the flat presets above. `command` doubles as the binary probed on\n// PATH. An empty `models` array = the CLI's own default (no `--model`), for single-line / fast-moving\n// CLIs like codex & kimi (avoids pinning a version that goes stale).\nexport interface CliModelOption {\n id: string // stored on agent.model\n displayName: string\n}\nexport interface CliOption {\n kind: string // adapter selector + stored on agent.kind\n command: string // executable the daemon spawns + the binary probed on PATH\n displayName: string\n models: CliModelOption[]\n}\n\nexport const CLI_CATALOG: CliOption[] = [\n {\n kind: 'claude',\n command: 'claude',\n displayName: 'Claude Code',\n models: [\n { id: 'opus', displayName: 'Opus' },\n { id: 'sonnet', displayName: 'Sonnet' },\n { id: 'haiku', displayName: 'Haiku' },\n ],\n },\n { kind: 'codex', command: 'codex', displayName: 'Codex', models: [] },\n { kind: 'kimi', command: 'kimi', displayName: 'Kimi', models: [] },\n]\n\n/** The CLI binaries the daemon probes for on PATH. */\nexport const CLI_COMMANDS: string[] = CLI_CATALOG.map((c) => c.command)\n","// Provider adapter contract for the daemon. Each adapter knows how to drive one family of agent\n// CLIs (Claude Code, codex, kimi, …) in non-interactive mode: build the right argv, parse the\n// provider's streaming output into cumulative text + tool calls, and capture the provider-native\n// session id so the next turn can resume. Modeled on botapp/multica/kitty's per-provider runners.\nimport { type ChildProcess, spawn, type SpawnOptionsWithoutStdio } from 'node:child_process'\nimport { createInterface } from 'node:readline'\n\nexport interface AdapterJob {\n id: string\n query: string\n resumeSessionId: string | null\n agent: {\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n}\n\n/** A tool invocation surfaced to the UI as the turn runs (rendered as a chip on the message). */\nexport interface ToolCall {\n id?: string\n name: string\n input?: unknown\n}\n\nexport interface RunContext {\n job: AdapterJob\n /** Stream a cumulative content update to the server (it overwrites the assistant message). */\n emitContent(content: string): void\n /** Append a tool call to the cumulative list streamed alongside content (for live tool chips). */\n emitToolCall(call: ToolCall): void\n /** Record the provider-native session id (returned in the result for next-turn resume). */\n setSessionId(id: string): void\n /** Spawn a child registered for cancellation (SIGTERM on daemon_job_cancel). */\n spawn(command: string, args: string[], opts: SpawnOptionsWithoutStdio): ChildProcess\n}\n\nexport interface AdapterResult {\n status: 'succeeded' | 'failed'\n content: string\n error?: string\n sessionId: string | null\n}\n\nexport type Adapter = (ctx: RunContext) => Promise<AdapterResult>\n\nexport const hasFlag = (args: string[], ...flags: string[]): boolean => flags.some((f) => args.includes(f))\n\n/** Spawn a child and stream stdout/stderr line-by-line; resolves once both the process has exited\n * and stdout has been fully drained (so the last JSON line is never dropped). */\nexport async function spawnLines(\n ctx: RunContext,\n command: string,\n args: string[],\n opts: {\n cwd?: string | null\n env?: Record<string, string> | null\n stdin?: string // written then closed; omit to just close stdin\n onLine: (line: string) => void\n onStderr?: (line: string) => void\n },\n): Promise<{ code: number | null; stderr: string }> {\n const child = ctx.spawn(command, args, {\n cwd: opts.cwd ?? undefined,\n env: { ...process.env, ...(opts.env ?? {}) },\n })\n let stderr = ''\n const out = createInterface({ input: child.stdout! })\n out.on('line', (line) => {\n try {\n opts.onLine(line)\n } catch {\n /* a malformed line must not kill the run */\n }\n })\n if (child.stderr) {\n const err = createInterface({ input: child.stderr })\n err.on('line', (line) => {\n stderr += `${line}\\n`\n opts.onStderr?.(line)\n })\n }\n if (opts.stdin !== undefined) {\n child.stdin?.write(opts.stdin)\n }\n child.stdin?.end()\n\n const exited = new Promise<number | null>((resolve) => child.on('close', resolve))\n const drained = new Promise<void>((resolve) => out.on('close', () => resolve()))\n const code = await exited\n await drained\n return { code, stderr }\n}\n\n/** Parse a line as JSON only if it looks like a JSON object (skips human-readable noise). */\nexport function parseJsonLine(line: string): Record<string, unknown> | null {\n const t = line.trim()\n if (!t || t[0] !== '{') return null\n try {\n return JSON.parse(t) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nexport { spawn } // re-export for the runner\n","// Claude Code adapter: `claude -p --output-format stream-json --verbose [...] [--resume <sid>]`,\n// prompt over stdin (avoids argv length limits). Parses the stream-json events, accumulates\n// assistant text, and captures session_id (present on every event) for next-turn --resume.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const claudeAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, '-p', '--print')) args.push('-p')\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (!args.includes('--verbose')) args.push('--verbose')\n if (a.model && !args.includes('--model')) args.push('--model', a.model)\n if (!hasFlag(args, '--permission-mode', '--dangerously-skip-permissions')) {\n args.push('--permission-mode', 'bypassPermissions')\n }\n if (a.systemPrompt && !args.includes('--append-system-prompt')) {\n args.push('--append-system-prompt', a.systemPrompt)\n }\n const resume = job.resumeSessionId ?? a.env?.CLAUDE_SESSION_ID ?? null\n if (resume && !args.includes('--resume')) args.push('--resume', resume)\n\n let sessionId: string | null = resume\n let finalText = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const sid = (ev.session_id ?? ev.sessionId) as string | undefined\n if (typeof sid === 'string' && sid) {\n sessionId = sid\n ctx.setSessionId(sid)\n }\n const msg = ev.message as { content?: unknown } | undefined\n if (ev.type === 'assistant' && Array.isArray(msg?.content)) {\n for (const block of msg.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n } else if (block?.type === 'tool_use' && typeof block.name === 'string') {\n ctx.emitToolCall({ id: typeof block.id === 'string' ? block.id : undefined, name: block.name, input: block.input })\n }\n }\n } else if (ev.type === 'result' && typeof ev.result === 'string') {\n finalText = ev.result\n ctx.emitContent(finalText)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n stdin: job.query,\n onLine,\n })\n\n const content = finalText || messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `claude exited with code ${code}`, sessionId }\n}\n","// Codex adapter: `codex exec [resume <thread>] --json --skip-git-repo-check\n// --dangerously-bypass-approvals-and-sandbox [--model X] <query>`. Parses the NDJSON event\n// stream; the thread id (from thread.started) is the session handle reused via `exec resume`.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\n// Friendlier chip labels for codex's structured item types (anything else uses the raw type).\nconst CODEX_TOOL_NAMES: Record<string, string> = {\n command_execution: 'shell',\n file_change: 'edit',\n mcp_tool_call: 'mcp',\n web_search: 'web_search',\n}\n\nexport const codexAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, 'exec', 'e')) args.unshift('exec')\n const resume = job.resumeSessionId ?? null\n if (resume && !args.includes('resume')) {\n const i = args.indexOf('exec')\n args.splice(i + 1, 0, 'resume', resume) // codex exec resume <thread_id>\n }\n if (!args.includes('--json')) args.push('--json')\n if (!args.includes('--skip-git-repo-check')) args.push('--skip-git-repo-check')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n if (!hasFlag(args, '--sandbox', '-s', '--ask-for-approval', '-a', '--full-auto', '--dangerously-bypass-approvals-and-sandbox')) {\n args.push('--dangerously-bypass-approvals-and-sandbox')\n }\n args.push(job.query)\n\n let sessionId: string | null = resume\n const messages: string[] = []\n const errors: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const tid = (ev.thread_id ?? ev.session_id) as string | undefined\n if (typeof tid === 'string' && tid) {\n sessionId = tid\n ctx.setSessionId(tid)\n }\n const item = ev.item as Record<string, unknown> | undefined\n if (ev.type === 'item.completed' && item?.type === 'agent_message' && typeof item.text === 'string') {\n messages.push(item.text)\n ctx.emitContent(messages.join('\\n'))\n } else if (ev.type === 'item.completed' && item && item.type !== 'reasoning' && item.type !== 'agent_message') {\n // command_execution / file_change / mcp_tool_call / web_search → a tool chip.\n const name = CODEX_TOOL_NAMES[item.type as string] ?? (typeof item.type === 'string' ? item.type : 'tool')\n ctx.emitToolCall({ id: typeof item.id === 'string' ? item.id : undefined, name, input: item })\n } else if (ev.type === 'error' && typeof ev.message === 'string') {\n errors.push(ev.message)\n } else if (ev.type === 'turn.failed') {\n const err = ev.error as { message?: string } | undefined\n if (err?.message) errors.push(err.message)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, { cwd: a.cwd, env: a.env, onLine })\n\n const content = messages.join('\\n')\n if (code === 0 && !errors.length) return { status: 'succeeded', content, sessionId }\n const error = errors.join('; ') || stderr.trim() || `codex exited with code ${code}`\n return { status: 'failed', content, error, sessionId }\n}\n","// Generic stdio adapter — the fallback for any command without a provider-specific adapter.\n// Runs `<command> <args...> [--resume <id>] <query>` and streams raw stdout as the reply. No\n// structured parsing, so no native session capture (the server still threads --resume through).\nimport { type Adapter, spawnLines } from './types.js'\n\nexport const genericAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n if (job.resumeSessionId) args.push('--resume', job.resumeSessionId)\n args.push(job.query)\n\n let out = ''\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine: (line) => {\n out += `${line}\\n`\n ctx.emitContent(out.trimEnd())\n },\n })\n\n const content = out.trim()\n if (code === 0) return { status: 'succeeded', content, sessionId: job.resumeSessionId ?? null }\n return { status: 'failed', content, error: stderr.trim() || `process exited with code ${code}`, sessionId: null }\n}\n","// Kimi adapter: `kimi -p \"<prompt>\" --output-format stream-json [-r <sid>] [--model X]`.\n// Parses the stream-json events; the session id arrives as a meta `session.resume_hint` event\n// (with a stderr \"kimi -r <id>\" fallback) and is reused via `-r` on the next turn.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const kimiAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n const resume = job.resumeSessionId ?? a.env?.KIMI_SESSION_ID ?? null\n if (resume && !hasFlag(args, '-r', '--resume', '--session', '-S', '--continue', '-C')) {\n args.push('-r', resume)\n }\n if (!hasFlag(args, '-p', '--prompt')) args.push('-p', job.query)\n\n let sessionId: string | null = resume\n let stderrAll = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n if (ev.role === 'assistant') {\n if (typeof ev.content === 'string' && ev.content) {\n messages.push(ev.content)\n ctx.emitContent(messages.join(''))\n } else if (Array.isArray(ev.content)) {\n for (const block of ev.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n }\n }\n }\n } else if (ev.role === 'meta' && ev.type === 'session.resume_hint' && typeof ev.session_id === 'string') {\n sessionId = ev.session_id\n ctx.setSessionId(ev.session_id)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine,\n onStderr: (l) => {\n stderrAll += `${l}\\n`\n },\n })\n\n if (!sessionId || sessionId === resume) {\n const m = (stderr || stderrAll).match(/kimi\\s+-r\\s+([A-Za-z0-9_-]+)/)\n if (m) {\n sessionId = m[1]\n ctx.setSessionId(m[1])\n }\n }\n\n const content = messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `kimi exited with code ${code}`, sessionId }\n}\n","// Adapter registry — pick a provider adapter by the agent's `kind` (the migration's \"adapter\n// selector\"), falling back to the command name, then to the generic stdio runner.\nimport { claudeAdapter } from './claude.js'\nimport { codexAdapter } from './codex.js'\nimport { genericAdapter } from './generic.js'\nimport { kimiAdapter } from './kimi.js'\nimport type { Adapter } from './types.js'\n\nexport type { Adapter, AdapterJob, AdapterResult, RunContext, ToolCall } from './types.js'\n\nconst BY_KIND: Record<string, Adapter> = {\n claude: claudeAdapter,\n 'claude-code': claudeAdapter,\n claude_code: claudeAdapter,\n codex: codexAdapter,\n kimi: kimiAdapter,\n generic: genericAdapter,\n shell: genericAdapter,\n}\n\n/** Resolve an adapter for an agent. `kind` wins; otherwise infer from the command basename. */\nexport function selectAdapter(kind: string | null | undefined, command: string): Adapter {\n if (kind && BY_KIND[kind]) return BY_KIND[kind]\n const base = (command.split('/').pop() ?? command).toLowerCase()\n if (base === 'claude') return claudeAdapter\n if (base === 'codex') return codexAdapter\n if (base === 'kimi') return kimiAdapter\n return genericAdapter\n}\n","// Background supervision + OS-service install for the taktiko daemon, so running it doesn't have to\n// hold a terminal open. Modeled on botapp's daemon-supervisor / daemon-service and multica's\n// `daemon start/stop/status/logs`:\n// start → spawn `taktiko daemon` (the foreground loop) detached; its stdout/stderr go to\n// ~/.taktiko/daemon.log, and the loop's own single-instance lock writes the pid.\n// stop → SIGTERM the recorded pid.\n// status / logs → inspect the local process + the log.\n// install/uninstall→ launchd (macOS) / systemd --user (Linux) unit that runs `taktiko daemon` at\n// login and respawns it on crash — survives logout/reboot, no terminal at all.\nimport { execFileSync, spawn } from 'node:child_process'\nimport { existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir, platform } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport const CONFIG_DIR = join(homedir(), '.taktiko')\nexport const PID_FILE = join(CONFIG_DIR, 'daemon.pid')\nexport const LOG_FILE = join(CONFIG_DIR, 'daemon.log')\n\n// The log grows a line per reconnect attempt, so a paired-but-offline machine would fill the disk over\n// weeks. Rotate to a single `.1` backup past this size (best-effort; a failed rotation never blocks).\nconst MAX_LOG_BYTES = 5 * 1024 * 1024\n\nconst SERVICE_LABEL_DARWIN = 'ai.taktiko.daemon'\nconst SERVICE_NAME_LINUX = 'taktiko-daemon'\n\n// ── local background process ───────────────────────────────────────────────────────────────────────\n\nexport function readPid(): number | null {\n try {\n const pid = Number.parseInt(readFileSync(PID_FILE, 'utf8').trim(), 10)\n return Number.isInteger(pid) && pid > 0 ? pid : null\n } catch {\n return null\n }\n}\n\n/** Is the recorded daemon pid a live process on this machine? */\nexport function isRunning(): boolean {\n const pid = readPid()\n if (pid === null) return false\n try {\n process.kill(pid, 0) // signal 0 just probes; throws if the pid is gone\n return true\n } catch {\n return false\n }\n}\n\n/** SIGTERM the recorded daemon (its handler kills jobs + clears the pidfile). Returns the pid hit. */\nexport function stopRunning(): number | null {\n const pid = readPid()\n if (pid === null) return null\n try {\n process.kill(pid, 'SIGTERM')\n } catch {\n // already gone — clear the stale pidfile ourselves\n try {\n unlinkSync(PID_FILE)\n } catch {\n /* ignore */\n }\n return null\n }\n return pid\n}\n\nexport interface ProcessInfo {\n pid: number\n startedAt: Date | null\n uptimeMs: number | null\n}\n\n/** The supervised daemon's pid + a start-time proxy (the pidfile mtime), or null if not running. */\nexport function processInfo(): ProcessInfo | null {\n const pid = readPid()\n if (pid === null) return null\n try {\n process.kill(pid, 0)\n } catch {\n return null\n }\n try {\n const st = statSync(PID_FILE)\n return { pid, startedAt: st.mtime, uptimeMs: Date.now() - st.mtimeMs }\n } catch {\n return { pid, startedAt: null, uptimeMs: null }\n }\n}\n\nfunction rotateLogIfLarge(): void {\n try {\n if (!existsSync(LOG_FILE)) return\n if (statSync(LOG_FILE).size <= MAX_LOG_BYTES) return\n const rotated = `${LOG_FILE}.1`\n try {\n if (existsSync(rotated)) unlinkSync(rotated)\n } catch {\n /* rename overwrites on POSIX */\n }\n renameSync(LOG_FILE, rotated)\n } catch {\n /* best-effort */\n }\n}\n\n/** Resolve the path to this CLI's entry so we can re-spawn `<entry> daemon`. */\nfunction selfBin(): string {\n const bin = process.argv[1]\n if (!bin || !existsSync(bin)) throw new Error('cannot resolve the taktiko binary from process.argv[1]')\n if (bin.endsWith('.ts')) {\n // Running from TS source (npm link / dev). Detaching `node foo.ts` won't work without a loader;\n // foreground is the right dev path.\n throw new Error('`start` is for the installed CLI — in dev run `taktiko daemon` (foreground) instead')\n }\n return bin\n}\n\n/** Spawn `taktiko daemon` detached, logging to ~/.taktiko/daemon.log. Returns the child pid. The child\n * writes the canonical pidfile itself via its single-instance lock, so its pid == this returned pid. */\nexport function spawnDetached(extraArgs: string[] = []): number {\n mkdirSync(CONFIG_DIR, { recursive: true })\n const bin = selfBin()\n rotateLogIfLarge()\n const out = openSync(LOG_FILE, 'a')\n const child = spawn(process.execPath, [bin, 'daemon', ...extraArgs], {\n detached: true,\n stdio: ['ignore', out, out],\n env: process.env,\n })\n child.unref()\n if (typeof child.pid !== 'number') throw new Error('failed to spawn the daemon (no pid)')\n return child.pid\n}\n\n// ── OS service (login auto-start) ────────────────────────────────────────────────────────────────────\n\ntype Plat = 'darwin' | 'linux' | 'unsupported'\nfunction currentPlatform(): Plat {\n const p = platform()\n return p === 'darwin' ? 'darwin' : p === 'linux' ? 'linux' : 'unsupported'\n}\n\nfunction darwinPlistPath(): string {\n return join(homedir(), 'Library', 'LaunchAgents', `${SERVICE_LABEL_DARWIN}.plist`)\n}\nfunction linuxUnitPath(): string {\n return join(homedir(), '.config', 'systemd', 'user', `${SERVICE_NAME_LINUX}.service`)\n}\n\n/** launchd/systemd start from a minimal environment and don't inherit the shell PATH, so agent CLIs in\n * ~/.local/bin, homebrew, nvm shims, etc. would be invisible. Snapshot the current PATH at install\n * time, keep only entries that exist on disk (shell-only shims don't survive a fresh login), and add a\n * conservative system default. Re-run `install` after putting an agent CLI somewhere new. */\nfunction buildServicePath(): string {\n const defaults = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']\n const seen = new Set<string>()\n const out: string[] = []\n for (const entry of [...(process.env.PATH ?? '').split(':'), ...defaults]) {\n const t = entry.trim()\n if (!t || seen.has(t) || !existsSync(t)) continue\n seen.add(t)\n out.push(t)\n }\n return out.join(':')\n}\n\nfunction darwinPlist(nodeBin: string, bin: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${SERVICE_LABEL_DARWIN}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${nodeBin}</string>\n <string>${bin}</string>\n <string>daemon</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>ProcessType</key>\n <string>Interactive</string>\n <key>StandardOutPath</key>\n <string>${LOG_FILE}</string>\n <key>StandardErrorPath</key>\n <string>${LOG_FILE}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>PATH</key>\n <string>${buildServicePath()}</string>\n <key>HOME</key>\n <string>${process.env.HOME ?? homedir()}</string>\n </dict>\n</dict>\n</plist>\n`\n}\n\nfunction linuxUnit(nodeBin: string, bin: string): string {\n return `[Unit]\nDescription=taktiko daemon\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${nodeBin} ${bin} daemon\nRestart=always\nRestartSec=2\nStandardOutput=append:${LOG_FILE}\nStandardError=append:${LOG_FILE}\nEnvironment=PATH=${buildServicePath()}\n\n[Install]\nWantedBy=default.target\n`\n}\n\nfunction run(cmd: string, args: string[]): void {\n try {\n execFileSync(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] })\n } catch (e) {\n const err = e as { stderr?: Buffer; stdout?: Buffer; message?: string }\n const detail = err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message || String(e)\n throw new Error(`${cmd} ${args.join(' ')} → ${detail}`)\n }\n}\nfunction runQuiet(cmd: string, args: string[]): void {\n try {\n execFileSync(cmd, args, { stdio: 'ignore' })\n } catch {\n /* best-effort */\n }\n}\n\nexport interface ServiceStatus {\n installed: boolean\n path: string | null\n platformLabel: string\n}\n\nexport function getServiceStatus(): ServiceStatus {\n const plat = currentPlatform()\n if (plat === 'darwin') return { installed: existsSync(darwinPlistPath()), path: darwinPlistPath(), platformLabel: 'launchd' }\n if (plat === 'linux') return { installed: existsSync(linuxUnitPath()), path: linuxUnitPath(), platformLabel: 'systemd --user' }\n return { installed: false, path: null, platformLabel: 'unsupported' }\n}\n\n/** Install a per-user service that runs `taktiko daemon` at login and respawns it on crash. */\nexport function installService(): { path: string; label: string } {\n const plat = currentPlatform()\n mkdirSync(CONFIG_DIR, { recursive: true })\n const nodeBin = process.execPath\n const bin = selfBin()\n if (plat === 'darwin') {\n const plistPath = darwinPlistPath()\n mkdirSync(dirname(plistPath), { recursive: true })\n writeFileSync(plistPath, darwinPlist(nodeBin, bin))\n runQuiet('launchctl', ['unload', plistPath]) // so a re-install picks up new ProgramArguments\n run('launchctl', ['load', plistPath])\n return { path: plistPath, label: 'launchd' }\n }\n if (plat === 'linux') {\n const unitPath = linuxUnitPath()\n mkdirSync(dirname(unitPath), { recursive: true })\n writeFileSync(unitPath, linuxUnit(nodeBin, bin))\n run('systemctl', ['--user', 'daemon-reload'])\n run('systemctl', ['--user', 'enable', '--now', SERVICE_NAME_LINUX])\n return { path: unitPath, label: 'systemd --user' }\n }\n throw new Error('service install is only supported on macOS and Linux — on Windows, register `taktiko daemon` via Task Scheduler')\n}\n\nexport function uninstallService(): boolean {\n const plat = currentPlatform()\n if (plat === 'darwin') {\n const plistPath = darwinPlistPath()\n if (!existsSync(plistPath)) return false\n runQuiet('launchctl', ['unload', plistPath])\n unlinkSync(plistPath)\n return true\n }\n if (plat === 'linux') {\n const unitPath = linuxUnitPath()\n if (!existsSync(unitPath)) return false\n runQuiet('systemctl', ['--user', 'disable', '--now', SERVICE_NAME_LINUX])\n unlinkSync(unitPath)\n runQuiet('systemctl', ['--user', 'daemon-reload'])\n return true\n }\n throw new Error('service uninstall is only supported on macOS and Linux')\n}\n\n/** Cycle the installed service in place (picks up a CLI upgrade). Returns false if none is installed. */\nexport function restartService(): boolean {\n const plat = currentPlatform()\n if (plat === 'darwin') {\n if (!existsSync(darwinPlistPath())) return false\n const uid = typeof process.getuid === 'function' ? process.getuid() : null\n if (uid !== null) run('launchctl', ['kickstart', '-k', `gui/${uid}/${SERVICE_LABEL_DARWIN}`])\n else {\n runQuiet('launchctl', ['unload', darwinPlistPath()])\n run('launchctl', ['load', darwinPlistPath()])\n }\n return true\n }\n if (plat === 'linux') {\n if (!existsSync(linuxUnitPath())) return false\n run('systemctl', ['--user', 'restart', SERVICE_NAME_LINUX])\n return true\n }\n return false\n}\n"],"mappings":"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","names":["execFileSync","spawn","existsSync","mkdirSync","readFileSync","statSync","unlinkSync","writeFileSync","homedir","dirname","join","spawn","execFileSync","homedir","statSync","join","readFileSync","mkdirSync","dirname","writeFileSync","unlinkSync","spawn","existsSync"]}
|
package/package.json
CHANGED