claude-resume-hub 1.0.0 → 1.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 CHANGED
@@ -7,13 +7,14 @@
7
7
 
8
8
  <p align="center">
9
9
  <a href="https://www.npmjs.com/package/claude-resume-hub"><img src="https://img.shields.io/npm/v/claude-resume-hub?color=c96442" alt="npm"></a>
10
+ <a href="https://github.com/IbrahimKalemci/claude-resume-hub/actions/workflows/ci.yml"><img src="https://github.com/IbrahimKalemci/claude-resume-hub/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
10
11
  <img src="https://img.shields.io/badge/node-%3E%3D16-3fb950" alt="node >= 16">
11
12
  <img src="https://img.shields.io/badge/Windows%20%7C%20macOS%20%7C%20Linux-8b93a7" alt="platforms">
12
13
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT"></a>
13
14
  </p>
14
15
 
15
16
  <p align="center">
16
- <img src="https://raw.githubusercontent.com/IbrahimKalemci/claude-resume-hub/main/docs/dashboard.svg" alt="live dashboard with a big countdown, status and streaming log" width="620">
17
+ <img src="https://raw.githubusercontent.com/IbrahimKalemci/claude-resume-hub/main/docs/demo.svg" alt="countdown reaching zero, then sending continue to Claude to resume the task" width="700">
17
18
  </p>
18
19
 
19
20
  <p align="center"><b>🇬🇧 <a href="#-english">English</a> &nbsp;·&nbsp; 🇹🇷 <a href="#-türkçe">Türkçe</a></b></p>
@@ -52,10 +53,13 @@ That's it. No install, no config. It works on **Windows, macOS and Linux**.
52
53
  npx claude-resume-hub # no dashboard, just auto-resume
53
54
  npx claude-resume-hub --web # with dashboard + alerts
54
55
  npx claude-resume-hub -t "run all tests and fix failures" # start a task
56
+ npx claude-resume-hub --list # list this project's sessions (+ their ids)
57
+ npx claude-resume-hub -s <id> # resume a specific session
55
58
  npx claude-resume-hub --help # all options
56
59
  ```
57
60
 
58
61
  Prefer a permanent command? `npm i -g claude-resume-hub` gives you `crh --web`.
62
+ **Always up to date:** `npx claude-resume-hub@latest` pulls the newest version automatically.
59
63
 
60
64
  ---
61
65
 
@@ -91,10 +95,25 @@ Hepsi bu. Kurulum yok, ayar yok. **Windows, macOS ve Linux**'ta çalışır.
91
95
  npx claude-resume-hub # panelsiz, sadece otomatik devam
92
96
  npx claude-resume-hub --web # panel + bildirim
93
97
  npx claude-resume-hub -t "tüm testleri çalıştır ve hataları düzelt" # görev başlat
98
+ npx claude-resume-hub --list # bu projenin session'larını listele (+ id'leri)
99
+ npx claude-resume-hub -s <id> # belirli bir session'ı devam ettir
94
100
  npx claude-resume-hub --help # tüm seçenekler
95
101
  ```
96
102
 
97
103
  Kalıcı komut ister misin? `npm i -g claude-resume-hub` sana `crh --web` verir.
104
+ **Hep güncel:** `npx claude-resume-hub@latest` otomatik en yeni sürümü çeker.
105
+
106
+ ---
107
+
108
+ ## 🛠️ From source · Kaynaktan
109
+
110
+ Prefer to run it straight from the repo (no npm)? / npm'siz, doğrudan repodan mı çalıştırmak istiyorsun?
111
+
112
+ ```bash
113
+ git clone https://github.com/IbrahimKalemci/claude-resume-hub.git
114
+ cd claude-resume-hub
115
+ node bin/cli.js --web
116
+ ```
98
117
 
99
118
  ---
100
119
 
package/bin/cli.js CHANGED
@@ -16,11 +16,13 @@ function parseArgs(argv) {
16
16
  const opts = {
17
17
  prompt: "continue",
18
18
  task: null,
19
+ session: null,
19
20
  dir: process.cwd(),
20
21
  buffer: 30,
21
22
  poll: 5,
22
23
  maxCycles: 100,
23
24
  verbose: false,
25
+ list: false,
24
26
  web: false,
25
27
  port: 4177,
26
28
  open: true,
@@ -36,10 +38,12 @@ function parseArgs(argv) {
36
38
  switch (a) {
37
39
  case "-p": case "--prompt": opts.prompt = next(); break;
38
40
  case "-t": case "--task": opts.task = next(); break;
41
+ case "-s": case "--session": opts.session = next(); break;
39
42
  case "-d": case "--dir": opts.dir = next(); break;
40
43
  case "-b": case "--buffer": opts.buffer = parseInt(next(), 10); break;
41
44
  case "--poll": opts.poll = parseInt(next(), 10); break;
42
45
  case "-m": case "--max-cycles": opts.maxCycles = parseInt(next(), 10); break;
46
+ case "-l": case "--list": opts.list = true; break;
43
47
  case "-w": case "--web": opts.web = true; break;
44
48
  case "--port": opts.port = parseInt(next(), 10); break;
45
49
  case "--no-open": opts.open = false; break;
@@ -67,10 +71,13 @@ OPTIONS
67
71
  -p, --prompt <text> Message sent to continue after a reset (default: "continue")
68
72
  -t, --task <text> Initial task; starts a fresh session on the first run,
69
73
  then continues it. Omit to attach to the most recent session.
74
+ -s, --session <id> Resume a specific session id (instead of the most recent).
75
+ Find ids by running: claude --resume
70
76
  -d, --dir <path> Working directory / project (default: current dir)
71
77
  -b, --buffer <seconds> Safety margin added after the reset time (default: 30)
72
78
  --poll <minutes> Retry interval if a reset time can't be determined (default: 5)
73
79
  -m, --max-cycles <n> Max limit->wait->continue cycles (default: 100)
80
+ -l, --list List Claude Code sessions in this project and exit
74
81
  -w, --web Open a live dashboard (countdown + desktop alerts)
75
82
  --port <n> Dashboard port (default: 4177)
76
83
  --no-open Don't auto-open the browser for the dashboard
@@ -81,6 +88,8 @@ OPTIONS
81
88
  EXAMPLES
82
89
  claude-resume-hub # keep the latest session going across resets
83
90
  claude-resume-hub --web # ...with a live dashboard + desktop alerts
91
+ claude-resume-hub --list # see this project's sessions and their ids
92
+ claude-resume-hub -s <id> # resume a specific session id
84
93
  claude-resume-hub -t "run all the tests and fix failures"
85
94
  claude-resume-hub -- --model opus # forward flags to claude
86
95
  `);
@@ -89,8 +98,36 @@ EXAMPLES
89
98
  const C = { cyan: "\x1b[36m", dim: "\x1b[2m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", reset: "\x1b[0m" };
90
99
  const stamp = () => new Date().toLocaleString();
91
100
 
101
+ function printSessions(dir) {
102
+ const { listSessions, findProjectFolder } = require("../lib/sessions");
103
+ if (!findProjectFolder(dir)) {
104
+ console.log(`No Claude Code sessions found for:\n ${dir}`);
105
+ console.log(`${C.dim}(Looked in ~/.claude/projects. Run this in your project folder, or pass -d <path>.)${C.reset}`);
106
+ return;
107
+ }
108
+ const sessions = listSessions(dir);
109
+ if (!sessions.length) { console.log("No sessions in this project yet."); return; }
110
+
111
+ console.log(`\nClaude Code sessions for ${C.cyan}${dir}${C.reset} ${C.dim}(newest first)${C.reset}\n`);
112
+ sessions.forEach((s, i) => {
113
+ const dot = i === 0 ? `${C.green}●${C.reset}` : " ";
114
+ console.log(`${dot} ${s.id}`);
115
+ console.log(` ${C.dim}${s.mtime.toLocaleString()} · ${s.turns} turns · ${s.sizeKB} KB${C.reset}`);
116
+ if (s.preview) console.log(` ${C.dim}"${s.preview}${s.preview.length >= 80 ? "…" : ""}"${C.reset}`);
117
+ console.log("");
118
+ });
119
+ console.log(`${C.green}●${C.reset} ${C.dim}= what plain "continue" (-c) resumes (the most recent).${C.reset}`);
120
+ console.log(`${C.dim}Resume a specific one: ${C.reset}crh --session <id>`);
121
+ }
122
+
92
123
  async function main() {
93
124
  const opts = parseArgs(process.argv.slice(2));
125
+
126
+ if (opts.list) {
127
+ printSessions(opts.dir);
128
+ process.exit(0);
129
+ }
130
+
94
131
  const engine = new AutoResumeEngine(opts);
95
132
 
96
133
  // Colorize a few known log lines for the terminal.
package/lib/engine.js CHANGED
@@ -4,6 +4,23 @@ const { spawn } = require("child_process");
4
4
  const { EventEmitter } = require("events");
5
5
  const { detectLimit, fmtDuration } = require("./detect");
6
6
 
7
+ /**
8
+ * Build the argument list passed to `claude` for a given cycle.
9
+ * - First cycle with an initial task -> start a fresh session with that task
10
+ * - A specific session id (--session) -> resume exactly that session
11
+ * - Otherwise -> continue the most recent session (-c)
12
+ */
13
+ function buildClaudeArgs(opts, cycle) {
14
+ const extra = opts.passthrough || [];
15
+ if (cycle === 1 && opts.task) {
16
+ return ["-p", opts.task, ...extra];
17
+ }
18
+ if (opts.session) {
19
+ return ["--resume", opts.session, "-p", opts.prompt, ...extra];
20
+ }
21
+ return ["-c", "-p", opts.prompt, ...extra];
22
+ }
23
+
7
24
  /**
8
25
  * AutoResumeEngine runs Claude Code in a loop, waiting out usage limits.
9
26
  *
@@ -88,12 +105,10 @@ class AutoResumeEngine extends EventEmitter {
88
105
  const o = this.opts;
89
106
  this.log(`Starting. dir="${o.dir}" prompt="${o.prompt}"`);
90
107
  if (o.task) this.log(`Initial task: "${o.task}"`);
108
+ if (o.session) this.log(`Resuming session: ${o.session}`);
91
109
 
92
110
  for (let cycle = 1; cycle <= o.maxCycles && !this._stopped; cycle++) {
93
- const claudeArgs =
94
- cycle === 1 && o.task
95
- ? ["-p", o.task, ...o.passthrough]
96
- : ["-c", "-p", o.prompt, ...o.passthrough];
111
+ const claudeArgs = buildClaudeArgs(o, cycle);
97
112
 
98
113
  this._setState({ phase: "running", cycle, resetAt: null, wakeAt: null, message: "Running Claude…" });
99
114
  this.log(`Cycle ${cycle}/${o.maxCycles} — running claude…`);
@@ -143,4 +158,4 @@ class AutoResumeEngine extends EventEmitter {
143
158
  }
144
159
  }
145
160
 
146
- module.exports = { AutoResumeEngine };
161
+ module.exports = { AutoResumeEngine, buildClaudeArgs };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+
7
+ /** Root where Claude Code stores per-project session transcripts. */
8
+ function projectsRoot() {
9
+ return path.join(os.homedir(), ".claude", "projects");
10
+ }
11
+
12
+ /**
13
+ * Encode an absolute directory the way Claude Code names its project folders:
14
+ * the path with `:`, `/` and `\` all replaced by `-`.
15
+ * e.g. C:\Users\me\proj -> C--Users-me-proj
16
+ */
17
+ function encodeDir(dir) {
18
+ return path.resolve(dir).replace(/[/\\:]/g, "-");
19
+ }
20
+
21
+ /** Locate the project folder for `dir` (exact, then case-insensitive). */
22
+ function findProjectFolder(dir) {
23
+ const root = projectsRoot();
24
+ if (!fs.existsSync(root)) return null;
25
+ const want = encodeDir(dir);
26
+ let entries = [];
27
+ try { entries = fs.readdirSync(root); } catch { return null; }
28
+ if (entries.includes(want)) return path.join(root, want);
29
+ const ci = entries.find((e) => e.toLowerCase() === want.toLowerCase());
30
+ return ci ? path.join(root, ci) : null;
31
+ }
32
+
33
+ /** First non-empty user message in a transcript — used as a preview. */
34
+ function firstUserMessage(lines) {
35
+ for (const l of lines) {
36
+ try {
37
+ const o = JSON.parse(l);
38
+ if (o.type === "user" && o.message) {
39
+ let c = o.message.content;
40
+ if (Array.isArray(c)) c = c.map((x) => (x && x.text) || "").join(" ");
41
+ if (typeof c === "string" && c.trim()) return c.trim().replace(/\s+/g, " ");
42
+ }
43
+ } catch { /* ignore malformed lines */ }
44
+ }
45
+ return "";
46
+ }
47
+
48
+ /**
49
+ * List Claude Code sessions for a project directory, newest first.
50
+ * Returns [{ id, mtime, sizeKB, turns, preview }].
51
+ */
52
+ function listSessions(dir) {
53
+ const folder = findProjectFolder(dir);
54
+ if (!folder) return [];
55
+ let files = [];
56
+ try { files = fs.readdirSync(folder).filter((f) => f.endsWith(".jsonl")); } catch { return []; }
57
+
58
+ const out = files.map((f) => {
59
+ const full = path.join(folder, f);
60
+ let st, lines = [];
61
+ try {
62
+ st = fs.statSync(full);
63
+ lines = fs.readFileSync(full, "utf8").split("\n").filter(Boolean);
64
+ } catch {
65
+ st = { mtime: new Date(0), size: 0 };
66
+ }
67
+ const turns = lines.reduce((n, l) => n + (/"type":"(user|assistant)"/.test(l) ? 1 : 0), 0);
68
+ return {
69
+ id: f.replace(/\.jsonl$/, ""),
70
+ mtime: st.mtime,
71
+ sizeKB: Math.round((st.size || 0) / 1024),
72
+ turns,
73
+ preview: firstUserMessage(lines).slice(0, 80),
74
+ };
75
+ });
76
+
77
+ out.sort((a, b) => b.mtime - a.mtime); // newest first
78
+ return out;
79
+ }
80
+
81
+ module.exports = { projectsRoot, encodeDir, findProjectFolder, listSessions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-resume-hub",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Run Claude Code and auto-continue the moment a usage/session limit resets — with a live dashboard and desktop alerts. Cross-platform, zero-dependency.",
5
5
  "bin": {
6
6
  "claude-resume-hub": "bin/cli.js",