trismegistus 1.0.3 → 1.1.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 +11 -12
- package/dist/cli.js +153 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,33 +4,30 @@ A local persistent daemon that runs Claude Code sessions from a task queue. Add
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
```bash
|
|
8
|
-
npm install -D trismegistus
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or install globally:
|
|
12
|
-
|
|
13
7
|
```bash
|
|
14
8
|
npm install -g trismegistus
|
|
15
9
|
```
|
|
16
10
|
|
|
11
|
+
This gives you the `tmg` command globally — use it from any project.
|
|
12
|
+
|
|
17
13
|
### Prerequisites
|
|
18
14
|
|
|
19
15
|
- Node.js >= 18
|
|
20
16
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`npm install -g @anthropic-ai/claude-code`)
|
|
17
|
+
- Enable remote control in your Claude Code settings to monitor sessions from claude.ai/code or the Claude mobile app (`remote_control: true` for all sessions)
|
|
21
18
|
|
|
22
19
|
## Quick Start
|
|
23
20
|
|
|
24
21
|
```bash
|
|
25
22
|
# Initialize in your project
|
|
26
|
-
|
|
23
|
+
tmg init
|
|
27
24
|
|
|
28
25
|
# Add tasks from the CLI
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
tmg add "Migrate user model to TypeScript"
|
|
27
|
+
tmg add "Write tests for the payment module"
|
|
31
28
|
|
|
32
29
|
# Start the daemon
|
|
33
|
-
|
|
30
|
+
tmg start
|
|
34
31
|
```
|
|
35
32
|
|
|
36
33
|
The daemon picks up tasks one at a time, runs each in a full Claude Code session with `--dangerously-skip-permissions`, commits the work, and moves on to the next.
|
|
@@ -108,13 +105,15 @@ echo "- [ ] Fix the bug in the login form" >> .trismegistus/tasks.md
|
|
|
108
105
|
|
|
109
106
|
### Monitor from your phone
|
|
110
107
|
|
|
111
|
-
`tmg remote` creates a secure
|
|
108
|
+
`tmg remote` creates a secure tunnel through Microsoft's Azure relay and prints a QR code. Scan it on your phone to get a full editor UI — including terminal access — right in the browser. No port forwarding or same-network requirement.
|
|
109
|
+
|
|
110
|
+
Works with both VS Code and Cursor — it auto-detects which editor you're using and runs the correct tunnel command.
|
|
112
111
|
|
|
113
112
|
```bash
|
|
114
113
|
tmg remote
|
|
115
114
|
```
|
|
116
115
|
|
|
117
|
-
Prerequisites: VS Code `code` CLI installed, GitHub account (one-time device auth on first use).
|
|
116
|
+
Prerequisites: VS Code (`code`) or Cursor (`cursor`) CLI installed, GitHub account (one-time device auth on first use).
|
|
118
117
|
|
|
119
118
|
You can set a custom tunnel name:
|
|
120
119
|
|
package/dist/cli.js
CHANGED
|
@@ -80,9 +80,20 @@ var CLAUDE_COMMANDS = [
|
|
|
80
80
|
name: "tmg.md",
|
|
81
81
|
content: `Start the Trismegistus daemon to continuously run tasks from the queue.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
**Important:** \`tmg start\` is a long-running daemon that needs its own terminal. Do NOT run it inline here.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Run this command in a **separate terminal**:
|
|
86
|
+
|
|
87
|
+
\`\`\`
|
|
88
|
+
tmg start
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
The daemon:
|
|
92
|
+
- Picks up pending tasks from \`.trismegistus/tasks.md\`
|
|
93
|
+
- Opens a VS Code tunnel for remote editor access (QR code)
|
|
94
|
+
- Idles and watches for new tasks when the queue is empty
|
|
95
|
+
|
|
96
|
+
**Note:** To monitor sessions remotely, enable remote control in your Claude Code settings (\`remote_control: true\` for all sessions).
|
|
86
97
|
|
|
87
98
|
$ARGUMENTS
|
|
88
99
|
`
|
|
@@ -377,38 +388,39 @@ function loadConfig(projectDir) {
|
|
|
377
388
|
|
|
378
389
|
// src/runner.ts
|
|
379
390
|
import { spawn } from "child_process";
|
|
380
|
-
function
|
|
391
|
+
function defaultSpawn(command, args, cwd) {
|
|
392
|
+
return spawn(command, args, { cwd, stdio: "ignore" });
|
|
393
|
+
}
|
|
394
|
+
function runClaude(opts) {
|
|
395
|
+
const { prompt, timeoutMs, projectDir, spawnFn = defaultSpawn } = opts;
|
|
381
396
|
return new Promise((resolve) => {
|
|
382
|
-
const ac = new AbortController();
|
|
383
397
|
let timedOut = false;
|
|
398
|
+
let settled = false;
|
|
399
|
+
const child = spawnFn(
|
|
400
|
+
"claude",
|
|
401
|
+
["-p", prompt, "--dangerously-skip-permissions"],
|
|
402
|
+
projectDir
|
|
403
|
+
);
|
|
384
404
|
const timer = setTimeout(() => {
|
|
385
405
|
timedOut = true;
|
|
386
|
-
|
|
406
|
+
child.kill();
|
|
387
407
|
}, timeoutMs);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
stdio: "inherit",
|
|
394
|
-
signal: ac.signal
|
|
408
|
+
function settle(result) {
|
|
409
|
+
if (!settled) {
|
|
410
|
+
settled = true;
|
|
411
|
+
clearTimeout(timer);
|
|
412
|
+
resolve(result);
|
|
395
413
|
}
|
|
396
|
-
|
|
397
|
-
child.on("
|
|
398
|
-
|
|
399
|
-
resolve({
|
|
414
|
+
}
|
|
415
|
+
child.on("exit", (code) => {
|
|
416
|
+
settle({
|
|
400
417
|
success: !timedOut && code === 0,
|
|
401
418
|
exitCode: timedOut ? 124 : code ?? 1,
|
|
402
419
|
timedOut
|
|
403
420
|
});
|
|
404
421
|
});
|
|
405
|
-
child.on("error", (
|
|
406
|
-
|
|
407
|
-
resolve({
|
|
408
|
-
success: false,
|
|
409
|
-
exitCode: 1,
|
|
410
|
-
timedOut: timedOut || err.name === "AbortError"
|
|
411
|
-
});
|
|
422
|
+
child.on("error", () => {
|
|
423
|
+
settle({ success: false, exitCode: 1, timedOut: false });
|
|
412
424
|
});
|
|
413
425
|
});
|
|
414
426
|
}
|
|
@@ -503,12 +515,12 @@ async function runDaemon(opts) {
|
|
|
503
515
|
notes,
|
|
504
516
|
handoff
|
|
505
517
|
);
|
|
506
|
-
const result = await runClaude(
|
|
518
|
+
const result = await runClaude({
|
|
507
519
|
prompt,
|
|
508
|
-
config.timeoutMinutes * 60 * 1e3,
|
|
520
|
+
timeoutMs: config.timeoutMinutes * 60 * 1e3,
|
|
509
521
|
projectDir,
|
|
510
522
|
spawnFn
|
|
511
|
-
);
|
|
523
|
+
});
|
|
512
524
|
if (result.success) {
|
|
513
525
|
setTaskStatus(projectDir, task.text, "x");
|
|
514
526
|
deleteHandoff(projectDir);
|
|
@@ -559,25 +571,29 @@ function cliExists(cli) {
|
|
|
559
571
|
return false;
|
|
560
572
|
}
|
|
561
573
|
}
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
throw new Error(
|
|
568
|
-
`Neither "cursor" nor "code" CLI found. Install VS Code or Cursor, or download the standalone CLI: https://code.visualstudio.com/docs/editor/command-line`
|
|
569
|
-
);
|
|
574
|
+
function isVSCodeAvailable() {
|
|
575
|
+
return cliExists("code");
|
|
576
|
+
}
|
|
577
|
+
function isCursorDetected() {
|
|
578
|
+
return detectEditorCli() === "cursor";
|
|
570
579
|
}
|
|
571
|
-
function startTunnel(name,
|
|
580
|
+
function startTunnel(name, options = {}) {
|
|
581
|
+
if (!isVSCodeAvailable()) {
|
|
582
|
+
return Promise.reject(
|
|
583
|
+
new Error(
|
|
584
|
+
'"code" CLI not found. Install VS Code or download the standalone CLI: https://code.visualstudio.com/docs/editor/command-line'
|
|
585
|
+
)
|
|
586
|
+
);
|
|
587
|
+
}
|
|
572
588
|
return new Promise((resolve, reject) => {
|
|
573
589
|
const child = spawn2(
|
|
574
|
-
|
|
590
|
+
"code",
|
|
575
591
|
["tunnel", "--name", name, "--accept-server-license-terms"],
|
|
576
592
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
577
593
|
);
|
|
578
594
|
let stderr = "";
|
|
579
595
|
let settled = false;
|
|
580
|
-
const TIMEOUT_MS =
|
|
596
|
+
const TIMEOUT_MS = 12e4;
|
|
581
597
|
function settle(fn) {
|
|
582
598
|
if (!settled) {
|
|
583
599
|
settled = true;
|
|
@@ -587,15 +603,26 @@ function startTunnel(name, cli = "code") {
|
|
|
587
603
|
const timer = setTimeout(() => {
|
|
588
604
|
child.kill();
|
|
589
605
|
const msg = stderr ? `Timed out waiting for tunnel URL. stderr:
|
|
590
|
-
${stderr}` :
|
|
606
|
+
${stderr}` : `Timed out waiting for tunnel URL. You may need to authenticate \u2014 run \`code tunnel\` manually first.`;
|
|
591
607
|
settle(() => reject(new Error(msg)));
|
|
592
608
|
}, TIMEOUT_MS);
|
|
593
609
|
child.stderr?.on("data", (chunk) => {
|
|
594
|
-
|
|
610
|
+
const text = chunk.toString();
|
|
611
|
+
stderr += text;
|
|
612
|
+
if (options.onOutput) {
|
|
613
|
+
for (const line of text.split("\n").filter(Boolean)) {
|
|
614
|
+
options.onOutput(line);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
595
617
|
});
|
|
596
618
|
child.stdout?.on("data", (chunk) => {
|
|
597
619
|
const text = chunk.toString();
|
|
598
|
-
|
|
620
|
+
if (options.onOutput) {
|
|
621
|
+
for (const line of text.split("\n").filter(Boolean)) {
|
|
622
|
+
options.onOutput(line);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const match = text.match(/(https:\/\/vscode\.dev\/tunnel\/[^\s]+)/);
|
|
599
626
|
if (match) {
|
|
600
627
|
clearTimeout(timer);
|
|
601
628
|
settle(() => resolve({ url: match[1], process: child }));
|
|
@@ -647,7 +674,7 @@ program.command("status").description("Show task counts").action(() => {
|
|
|
647
674
|
console.log(` Gave up (!!!): ${counts.gaveUp}`);
|
|
648
675
|
console.log("");
|
|
649
676
|
});
|
|
650
|
-
program.command("start").description("Start the daemon \u2014 continuously runs tasks from the queue").action(async () => {
|
|
677
|
+
program.command("start").description("Start the daemon \u2014 continuously runs tasks from the queue").option("--no-tunnel", "Skip VS Code tunnel setup").action(async (opts) => {
|
|
651
678
|
const check = preflight(process.cwd());
|
|
652
679
|
for (const err of check.errors) {
|
|
653
680
|
console.error(` Error: ${err}`);
|
|
@@ -659,7 +686,63 @@ program.command("start").description("Start the daemon \u2014 continuously runs
|
|
|
659
686
|
process.exit(1);
|
|
660
687
|
}
|
|
661
688
|
console.log("");
|
|
662
|
-
|
|
689
|
+
let tunnelProc = null;
|
|
690
|
+
if (opts.tunnel) {
|
|
691
|
+
const editor = detectEditorCli();
|
|
692
|
+
if (editor === "cursor" || !isVSCodeAvailable()) {
|
|
693
|
+
if (isCursorDetected()) {
|
|
694
|
+
console.log(" Note: Remote tunnels only work with VS Code, not Cursor.");
|
|
695
|
+
} else {
|
|
696
|
+
console.log(" Note: VS Code CLI not found \u2014 skipping tunnel.");
|
|
697
|
+
}
|
|
698
|
+
console.log(" Tip: Enable remote control in your Claude Code settings to monitor sessions from claude.ai/code.");
|
|
699
|
+
console.log("");
|
|
700
|
+
} else {
|
|
701
|
+
const tunnelName = hostname();
|
|
702
|
+
console.log(" Starting VS Code tunnel...\n");
|
|
703
|
+
try {
|
|
704
|
+
const { url, process: tp } = await startTunnel(tunnelName, {
|
|
705
|
+
onOutput(line) {
|
|
706
|
+
const deviceCodeMatch = line.match(/use code\s+([A-Z0-9]{4}-[A-Z0-9]{4})/i);
|
|
707
|
+
const deviceUrlMatch = line.match(/(https:\/\/github\.com\/login\/device)/);
|
|
708
|
+
if (deviceCodeMatch || deviceUrlMatch) {
|
|
709
|
+
console.log("");
|
|
710
|
+
if (deviceUrlMatch) {
|
|
711
|
+
console.log(` Open: ${deviceUrlMatch[1]}`);
|
|
712
|
+
}
|
|
713
|
+
if (deviceCodeMatch) {
|
|
714
|
+
console.log(` Code: ${deviceCodeMatch[1]}`);
|
|
715
|
+
}
|
|
716
|
+
console.log("");
|
|
717
|
+
} else if (line.includes("Creating tunnel")) {
|
|
718
|
+
console.log(` ${line.replace(/^\[.*?\]\s*\w+\s*/, "")}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
tunnelProc = tp;
|
|
723
|
+
console.log("");
|
|
724
|
+
console.log(` VS Code tunnel: ${url}`);
|
|
725
|
+
console.log("");
|
|
726
|
+
qrcode.generate(url, { small: true }, (code) => {
|
|
727
|
+
console.log(code);
|
|
728
|
+
});
|
|
729
|
+
console.log(" Scan QR to access editor remotely");
|
|
730
|
+
console.log("");
|
|
731
|
+
} catch (e) {
|
|
732
|
+
console.warn(` Warning: Tunnel failed \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
733
|
+
console.warn(" Continuing without tunnel.\n");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const cleanup = () => {
|
|
738
|
+
tunnelProc?.kill();
|
|
739
|
+
process.exit(0);
|
|
740
|
+
};
|
|
741
|
+
process.on("SIGINT", cleanup);
|
|
742
|
+
process.on("SIGTERM", cleanup);
|
|
743
|
+
await runDaemon({
|
|
744
|
+
projectDir: process.cwd()
|
|
745
|
+
});
|
|
663
746
|
});
|
|
664
747
|
program.command("add").description("Add a task to the queue").argument("<text>", "Task description").action((text) => {
|
|
665
748
|
try {
|
|
@@ -688,18 +771,37 @@ program.command("reset").description("Reset all gave-up [!!!] tasks back to pend
|
|
|
688
771
|
}
|
|
689
772
|
});
|
|
690
773
|
program.command("remote").description("Open a VS Code tunnel for phone access (QR code)").option("--name <name>", "Tunnel name").action(async (opts) => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
console.error(
|
|
774
|
+
if (isCursorDetected()) {
|
|
775
|
+
console.warn(" Warning: Remote tunnels only work with VS Code, not Cursor.");
|
|
776
|
+
console.warn(" Attempting anyway with VS Code CLI...\n");
|
|
777
|
+
}
|
|
778
|
+
if (!isVSCodeAvailable()) {
|
|
779
|
+
console.error(
|
|
780
|
+
' Error: "code" CLI not found. Install VS Code or download the standalone CLI:\n https://code.visualstudio.com/docs/editor/command-line'
|
|
781
|
+
);
|
|
697
782
|
process.exit(1);
|
|
698
783
|
}
|
|
699
|
-
const
|
|
700
|
-
console.log(
|
|
784
|
+
const tunnelName = opts.name ?? hostname();
|
|
785
|
+
console.log("Starting VS Code tunnel...\n");
|
|
701
786
|
try {
|
|
702
|
-
const { url, process: tunnelProc } = await startTunnel(tunnelName,
|
|
787
|
+
const { url, process: tunnelProc } = await startTunnel(tunnelName, {
|
|
788
|
+
onOutput(line) {
|
|
789
|
+
const deviceCodeMatch = line.match(/use code\s+([A-Z0-9]{4}-[A-Z0-9]{4})/i);
|
|
790
|
+
const deviceUrlMatch = line.match(/(https:\/\/github\.com\/login\/device)/);
|
|
791
|
+
if (deviceCodeMatch || deviceUrlMatch) {
|
|
792
|
+
console.log("");
|
|
793
|
+
if (deviceUrlMatch) {
|
|
794
|
+
console.log(` Open: ${deviceUrlMatch[1]}`);
|
|
795
|
+
}
|
|
796
|
+
if (deviceCodeMatch) {
|
|
797
|
+
console.log(` Code: ${deviceCodeMatch[1]}`);
|
|
798
|
+
}
|
|
799
|
+
console.log("");
|
|
800
|
+
} else if (line.includes("Creating tunnel")) {
|
|
801
|
+
console.log(` ${line.replace(/^\[.*?\]\s*\w+\s*/, "")}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
});
|
|
703
805
|
console.log("");
|
|
704
806
|
console.log(` URL: ${url}`);
|
|
705
807
|
console.log("");
|