@generativereality/agentherder 0.1.2 → 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.
- package/dist/index.js +90 -29
- 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.
|
|
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,
|
|
@@ -534,6 +534,7 @@ async function openSession(opts) {
|
|
|
534
534
|
const extraFlags = config.claude.flags.join(" ");
|
|
535
535
|
const cmd = `cd ${JSON.stringify(dir)} && ${claudeCmd} --name ${JSON.stringify(tabName)}${extraFlags ? " " + extraFlags : ""}\n`;
|
|
536
536
|
await adapter.sendInput(blockId, cmd);
|
|
537
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
537
538
|
adapter.closeSocket();
|
|
538
539
|
return tabId;
|
|
539
540
|
}
|
|
@@ -617,20 +618,63 @@ const resumeCommand = define({
|
|
|
617
618
|
function pathToProjectSlug(dir) {
|
|
618
619
|
return resolve(dir).replace(/\//g, "-");
|
|
619
620
|
}
|
|
620
|
-
/** Find the most recent
|
|
621
|
-
function
|
|
622
|
-
const slug = pathToProjectSlug(dir);
|
|
623
|
-
const projectDir = join(homedir(), ".claude", "projects", slug);
|
|
621
|
+
/** Find the most recent .jsonl session file in a Claude project directory */
|
|
622
|
+
function latestJsonlIn(projectDir) {
|
|
624
623
|
if (!existsSync(projectDir)) return null;
|
|
625
|
-
const
|
|
624
|
+
const files = readdirSync(projectDir).filter((f) => extname(f) === ".jsonl").map((f) => ({
|
|
626
625
|
name: f,
|
|
627
626
|
mtime: statSync(join(projectDir, f)).mtimeMs
|
|
628
627
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
629
|
-
|
|
630
|
-
|
|
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;
|
|
631
659
|
}
|
|
632
660
|
//#endregion
|
|
633
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
|
+
}
|
|
634
678
|
const forkCommand = define({
|
|
635
679
|
name: "fork",
|
|
636
680
|
description: "Fork a session into a new tab (claude --resume <id> --fork-session)",
|
|
@@ -672,16 +716,16 @@ const forkCommand = define({
|
|
|
672
716
|
consola.error(`Tab "${tabName}" has no terminal block`);
|
|
673
717
|
process.exit(1);
|
|
674
718
|
}
|
|
675
|
-
const
|
|
676
|
-
const sessionId = findLatestSessionId(
|
|
719
|
+
const { sessionLookupDir, openDir } = resolveSessionDir(termBlocks[0].meta?.["cmd:cwd"] ?? process.cwd());
|
|
720
|
+
const sessionId = findLatestSessionId(sessionLookupDir);
|
|
677
721
|
if (!sessionId) {
|
|
678
|
-
consola.error(`No Claude session found for ${
|
|
679
|
-
consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(
|
|
722
|
+
consola.error(`No Claude session found for ${sessionLookupDir}`);
|
|
723
|
+
consola.info(`Looked in ~/.claude/projects/${pathToProjectSlug(sessionLookupDir)}/`);
|
|
680
724
|
process.exit(1);
|
|
681
725
|
}
|
|
682
726
|
const newTabId = await openSession({
|
|
683
727
|
tabName: newName,
|
|
684
|
-
dir:
|
|
728
|
+
dir: openDir,
|
|
685
729
|
claudeCmd: `claude --resume ${sessionId} --fork-session`
|
|
686
730
|
});
|
|
687
731
|
consola.success(`Forked "${tabName}" → "${newName}" [${newTabId.slice(0, 8)}]`);
|
|
@@ -707,7 +751,7 @@ const closeCommand = define({
|
|
|
707
751
|
const { tabsById, tabNames } = await adapter.getAllData();
|
|
708
752
|
const matches = adapter.resolveTab(query, tabsById, tabNames);
|
|
709
753
|
if (!matches.length) {
|
|
710
|
-
consola.error(`No tab matching '${query}'`);
|
|
754
|
+
consola.error(`No tab matching '${query}' (tabs in workspaces with no open window are not visible — open that workspace first)`);
|
|
711
755
|
process.exit(1);
|
|
712
756
|
}
|
|
713
757
|
if (matches.length > 1) {
|
|
@@ -766,11 +810,11 @@ const renameCommand = define({
|
|
|
766
810
|
//#region src/commands/scrollback.ts
|
|
767
811
|
const scrollbackCommand = define({
|
|
768
812
|
name: "scrollback",
|
|
769
|
-
description: "Show terminal output for a block (default: last 50 lines)",
|
|
813
|
+
description: "Show terminal output for a tab or block (default: last 50 lines)",
|
|
770
814
|
args: {
|
|
771
|
-
|
|
815
|
+
target: {
|
|
772
816
|
type: "positional",
|
|
773
|
-
description: "
|
|
817
|
+
description: "Tab name, tab ID prefix, or block ID prefix"
|
|
774
818
|
},
|
|
775
819
|
lines: {
|
|
776
820
|
type: "number",
|
|
@@ -782,22 +826,39 @@ const scrollbackCommand = define({
|
|
|
782
826
|
const query = ctx.positionals[1];
|
|
783
827
|
const lines = ctx.values.lines ?? 50;
|
|
784
828
|
if (!query) {
|
|
785
|
-
consola.error("
|
|
829
|
+
consola.error("Tab name or block ID is required");
|
|
786
830
|
process.exit(1);
|
|
787
831
|
}
|
|
788
832
|
const adapter = requireWaveAdapter();
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
833
|
+
const { tabsById, tabNames } = await adapter.getAllData();
|
|
834
|
+
const tabMatches = adapter.resolveTab(query, tabsById, tabNames);
|
|
835
|
+
let blockId;
|
|
836
|
+
if (tabMatches.length === 1) {
|
|
837
|
+
const blocks = (tabsById.get(tabMatches[0]) ?? []).filter((b) => b.view === "term");
|
|
838
|
+
if (!blocks.length) {
|
|
839
|
+
consola.error(`Tab "${tabNames.get(tabMatches[0])}" has no terminal block`);
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
blockId = blocks[0].blockid;
|
|
843
|
+
} else if (tabMatches.length > 1) {
|
|
844
|
+
consola.error(`Multiple tabs match '${query}':`);
|
|
845
|
+
for (const tid of tabMatches) consola.log(` "${tabNames.get(tid)}" [${tid.slice(0, 8)}]`);
|
|
798
846
|
process.exit(1);
|
|
847
|
+
} else {
|
|
848
|
+
const allBlocks = adapter.blocksList();
|
|
849
|
+
const blockMatches = adapter.resolveBlock(query, allBlocks);
|
|
850
|
+
if (!blockMatches.length) {
|
|
851
|
+
consola.error(`No tab or block matching '${query}' (tabs in workspaces with no open window are not visible — open that workspace first)`);
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
if (blockMatches.length > 1) {
|
|
855
|
+
consola.error(`Multiple blocks match '${query}':`);
|
|
856
|
+
for (const b of blockMatches) consola.log(` ${b.blockid}`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
blockId = blockMatches[0].blockid;
|
|
799
860
|
}
|
|
800
|
-
process.stdout.write(adapter.scrollback(
|
|
861
|
+
process.stdout.write(adapter.scrollback(blockId, lines));
|
|
801
862
|
}
|
|
802
863
|
});
|
|
803
864
|
//#endregion
|
|
@@ -861,7 +922,7 @@ const sendCommand = define({
|
|
|
861
922
|
const allBlocks = adapter.blocksList();
|
|
862
923
|
const blockMatches = adapter.resolveBlock(query, allBlocks);
|
|
863
924
|
if (!blockMatches.length) {
|
|
864
|
-
consola.error(`No tab or block matching '${query}'`);
|
|
925
|
+
consola.error(`No tab or block matching '${query}' (tabs in workspaces with no open window are not visible — open that workspace first)`);
|
|
865
926
|
process.exit(1);
|
|
866
927
|
}
|
|
867
928
|
if (blockMatches.length > 1) {
|