@generativereality/agentherder 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +56 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { consola } from "consola";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
11
11
  //#region package.json
12
12
  var name = "@generativereality/agentherder";
13
- var version = "0.1.3";
13
+ var version = "0.1.4";
14
14
  var description = "Agent session manager for AI coding tools. Terminal tabs as the UI, no tmux.";
15
15
  var package_default = {
16
16
  name,
@@ -618,20 +618,63 @@ const resumeCommand = define({
618
618
  function pathToProjectSlug(dir) {
619
619
  return resolve(dir).replace(/\//g, "-");
620
620
  }
621
- /** Find the most recent Claude Code session ID for a directory */
622
- function findLatestSessionId(dir) {
623
- const slug = pathToProjectSlug(dir);
624
- const projectDir = join(homedir(), ".claude", "projects", slug);
621
+ /** Find the most recent .jsonl session file in a Claude project directory */
622
+ function latestJsonlIn(projectDir) {
625
623
  if (!existsSync(projectDir)) return null;
626
- const jsonlFiles = readdirSync(projectDir).filter((f) => extname(f) === ".jsonl").map((f) => ({
624
+ const files = readdirSync(projectDir).filter((f) => extname(f) === ".jsonl").map((f) => ({
627
625
  name: f,
628
626
  mtime: statSync(join(projectDir, f)).mtimeMs
629
627
  })).sort((a, b) => b.mtime - a.mtime);
630
- if (!jsonlFiles.length) return null;
631
- return basename(jsonlFiles[0].name, ".jsonl");
628
+ return files.length ? basename(files[0].name, ".jsonl") : null;
629
+ }
630
+ /**
631
+ * Find the most recent Claude Code session ID for a directory.
632
+ * Also checks worktree subdirectories (.claude/worktrees/*) since tabs
633
+ * opened with --worktree run from a worktree path, not the repo root.
634
+ */
635
+ function findLatestSessionId(dir) {
636
+ const projectsRoot = join(homedir(), ".claude", "projects");
637
+ const direct = latestJsonlIn(join(projectsRoot, pathToProjectSlug(dir)));
638
+ if (direct) return direct;
639
+ const worktreesDir = join(dir, ".claude", "worktrees");
640
+ if (existsSync(worktreesDir)) {
641
+ const candidates = [];
642
+ for (const entry of readdirSync(worktreesDir)) {
643
+ const projectDir = join(projectsRoot, pathToProjectSlug(join(worktreesDir, entry)));
644
+ const id = latestJsonlIn(projectDir);
645
+ if (id) {
646
+ const mtime = statSync(join(projectDir, id + ".jsonl")).mtimeMs;
647
+ candidates.push({
648
+ id,
649
+ mtime
650
+ });
651
+ }
652
+ }
653
+ if (candidates.length) {
654
+ candidates.sort((a, b) => b.mtime - a.mtime);
655
+ return candidates[0].id;
656
+ }
657
+ }
658
+ return null;
632
659
  }
633
660
  //#endregion
634
661
  //#region src/commands/fork.ts
662
+ /** If dir is inside .claude/worktrees/<name>, return the repo root instead */
663
+ function resolveSessionDir(dir) {
664
+ const worktreeMarker = `${join(".claude", "worktrees")}/`;
665
+ const idx = dir.indexOf(worktreeMarker);
666
+ if (idx !== -1) {
667
+ const repoRoot = dir.slice(0, idx - 1);
668
+ return {
669
+ sessionLookupDir: repoRoot,
670
+ openDir: repoRoot
671
+ };
672
+ }
673
+ return {
674
+ sessionLookupDir: dir,
675
+ openDir: dir
676
+ };
677
+ }
635
678
  const forkCommand = define({
636
679
  name: "fork",
637
680
  description: "Fork a session into a new tab (claude --resume <id> --fork-session)",
@@ -673,16 +716,16 @@ const forkCommand = define({
673
716
  consola.error(`Tab "${tabName}" has no terminal block`);
674
717
  process.exit(1);
675
718
  }
676
- const sourceDir = termBlocks[0].meta?.["cmd:cwd"] ?? process.cwd();
677
- const sessionId = findLatestSessionId(sourceDir);
719
+ const { sessionLookupDir, openDir } = resolveSessionDir(termBlocks[0].meta?.["cmd:cwd"] ?? process.cwd());
720
+ const sessionId = findLatestSessionId(sessionLookupDir);
678
721
  if (!sessionId) {
679
- consola.error(`No Claude session found for ${sourceDir}`);
680
- consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(sourceDir)}/`);
722
+ consola.error(`No Claude session found for ${sessionLookupDir}`);
723
+ consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(sessionLookupDir)}/`);
681
724
  process.exit(1);
682
725
  }
683
726
  const newTabId = await openSession({
684
727
  tabName: newName,
685
- dir: sourceDir,
728
+ dir: openDir,
686
729
  claudeCmd: `claude --resume ${sessionId} --fork-session`
687
730
  });
688
731
  consola.success(`Forked "${tabName}" → "${newName}" [${newTabId.slice(0, 8)}]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@generativereality/agentherder",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Agent session manager for AI coding tools. Terminal tabs as the UI, no tmux.",
5
5
  "type": "module",
6
6
  "bin": {