@towles/tool 0.0.62 → 0.0.63

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 (83) hide show
  1. package/package.json +50 -57
  2. package/src/commands/agentboard.ts +176 -0
  3. package/src/commands/{auto-claude.ts → auto-claude/index.ts} +18 -28
  4. package/src/commands/auto-claude/list.ts +114 -0
  5. package/src/commands/auto-claude/retry.test.ts +138 -0
  6. package/src/commands/auto-claude/retry.ts +139 -0
  7. package/src/commands/auto-claude/status.test.ts +147 -0
  8. package/src/commands/auto-claude/status.ts +123 -0
  9. package/src/commands/base.ts +7 -2
  10. package/src/commands/config.ts +5 -7
  11. package/src/commands/doctor.ts +111 -12
  12. package/src/commands/gh/branch.ts +4 -4
  13. package/src/commands/gh/pr.ts +1 -0
  14. package/src/commands/graph/index.ts +169 -0
  15. package/src/commands/graph.test.ts +1 -1
  16. package/src/commands/install.ts +40 -68
  17. package/src/commands/journal/daily-notes.ts +3 -3
  18. package/src/commands/journal/meeting.ts +3 -3
  19. package/src/commands/journal/note.ts +3 -3
  20. package/src/lib/auto-claude/claude-cli.ts +183 -0
  21. package/src/lib/auto-claude/config.test.ts +6 -8
  22. package/src/lib/auto-claude/config.ts +3 -4
  23. package/src/lib/auto-claude/index.ts +2 -3
  24. package/src/lib/auto-claude/labels.test.ts +85 -0
  25. package/src/lib/auto-claude/labels.ts +42 -0
  26. package/src/lib/auto-claude/pipeline-execution.test.ts +129 -33
  27. package/src/lib/auto-claude/pipeline.test.ts +2 -2
  28. package/src/lib/auto-claude/pipeline.ts +120 -36
  29. package/src/lib/auto-claude/prompt-templates/01_plan.prompt.md +68 -0
  30. package/src/lib/auto-claude/prompt-templates/{05_implement.prompt.md → 02_implement.prompt.md} +3 -2
  31. package/src/lib/auto-claude/prompt-templates/03_simplify.prompt.md +52 -0
  32. package/src/lib/auto-claude/prompt-templates/{06_review.prompt.md → 04_review.prompt.md} +29 -6
  33. package/src/lib/auto-claude/prompt-templates/index.test.ts +9 -42
  34. package/src/lib/auto-claude/prompt-templates/index.ts +13 -28
  35. package/src/lib/auto-claude/run-claude.test.ts +48 -68
  36. package/src/lib/auto-claude/shell.ts +6 -0
  37. package/src/lib/auto-claude/steps/create-pr.ts +89 -25
  38. package/src/lib/auto-claude/steps/fetch-issues.ts +4 -1
  39. package/src/lib/auto-claude/steps/implement.ts +9 -16
  40. package/src/lib/auto-claude/steps/simple-steps.ts +34 -0
  41. package/src/lib/auto-claude/steps/steps.test.ts +68 -63
  42. package/src/lib/auto-claude/templates.test.ts +91 -0
  43. package/src/lib/auto-claude/templates.ts +34 -0
  44. package/src/lib/auto-claude/test-helpers.ts +2 -1
  45. package/src/lib/auto-claude/utils-execution.test.ts +9 -57
  46. package/src/lib/auto-claude/utils.test.ts +5 -9
  47. package/src/lib/auto-claude/utils.ts +27 -253
  48. package/src/lib/graph/analyzer.test.ts +451 -0
  49. package/src/lib/graph/analyzer.ts +165 -0
  50. package/src/lib/graph/index.ts +24 -0
  51. package/src/lib/graph/labels.ts +87 -0
  52. package/src/lib/graph/parser.test.ts +150 -0
  53. package/src/lib/graph/parser.ts +65 -0
  54. package/src/lib/graph/render.ts +25 -0
  55. package/src/lib/graph/server.ts +70 -0
  56. package/src/lib/graph/sessions.ts +104 -0
  57. package/src/lib/graph/tools.ts +90 -0
  58. package/src/lib/graph/treemap.ts +211 -0
  59. package/src/lib/graph/types.ts +80 -0
  60. package/src/lib/install/claude-settings.ts +64 -0
  61. package/src/lib/journal/editor.ts +33 -0
  62. package/src/lib/journal/fs.ts +13 -0
  63. package/src/lib/journal/index.ts +11 -0
  64. package/src/lib/journal/paths.ts +106 -0
  65. package/src/lib/journal/{utils.ts → templates.ts} +3 -151
  66. package/src/utils/fs.ts +19 -0
  67. package/src/utils/git/exec.ts +18 -0
  68. package/src/utils/git/gh-cli-wrapper.test.ts +47 -8
  69. package/src/utils/git/gh-cli-wrapper.ts +31 -19
  70. package/src/utils/render.ts +3 -1
  71. package/src/commands/graph.ts +0 -970
  72. package/src/lib/auto-claude/prompt-templates/01_research.prompt.md +0 -21
  73. package/src/lib/auto-claude/prompt-templates/02_plan.prompt.md +0 -27
  74. package/src/lib/auto-claude/prompt-templates/03_plan-annotations.prompt.md +0 -15
  75. package/src/lib/auto-claude/prompt-templates/04_plan-implementation.prompt.md +0 -35
  76. package/src/lib/auto-claude/prompt-templates/07_refresh.prompt.md +0 -30
  77. package/src/lib/auto-claude/steps/plan-annotations.ts +0 -54
  78. package/src/lib/auto-claude/steps/plan-implementation.ts +0 -14
  79. package/src/lib/auto-claude/steps/plan.ts +0 -14
  80. package/src/lib/auto-claude/steps/refresh.ts +0 -114
  81. package/src/lib/auto-claude/steps/remove-label.ts +0 -22
  82. package/src/lib/auto-claude/steps/research.ts +0 -21
  83. package/src/lib/auto-claude/steps/review.ts +0 -14
@@ -0,0 +1,19 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+
4
+ export function ensureDir(dir: string): void {
5
+ mkdirSync(dir, { recursive: true });
6
+ }
7
+
8
+ export function fileExists(path: string): boolean {
9
+ return existsSync(path);
10
+ }
11
+
12
+ export function readFile(path: string): string {
13
+ return readFileSync(path, "utf-8");
14
+ }
15
+
16
+ export function writeFile(path: string, content: string): void {
17
+ ensureDir(dirname(path));
18
+ writeFileSync(path, content, "utf-8");
19
+ }
@@ -0,0 +1,18 @@
1
+ import { x } from "tinyexec";
2
+
3
+ async function exec(cmd: string, args: string[]): Promise<string> {
4
+ const result = await x(cmd, args, { nodeOptions: { cwd: process.cwd() }, throwOnError: true });
5
+ return result.stdout.trim();
6
+ }
7
+
8
+ export async function execSafe(
9
+ cmd: string,
10
+ args: string[],
11
+ ): Promise<{ stdout: string; ok: boolean }> {
12
+ const result = await x(cmd, args, { nodeOptions: { cwd: process.cwd() }, throwOnError: false });
13
+ return { stdout: (result.stdout ?? "").trim(), ok: result.exitCode === 0 };
14
+ }
15
+
16
+ export async function git(args: string[]): Promise<string> {
17
+ return exec("git", args);
18
+ }
@@ -1,14 +1,53 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { x } from "tinyexec";
2
3
  import { getIssues, isGithubCliInstalled } from "./gh-cli-wrapper";
3
4
 
4
- describe.skipIf(!!process.env.CI)("gh-cli-wrapper", () => {
5
- it("should return true if gh is installed", async () => {
6
- const result = await isGithubCliInstalled();
7
- expect(result).toBe(true);
5
+ vi.mock("tinyexec", () => ({
6
+ x: vi.fn().mockResolvedValue({ stdout: "[]" }),
7
+ }));
8
+
9
+ const mockX = vi.mocked(x);
10
+
11
+ describe("gh-cli-wrapper", () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ mockX.mockResolvedValue({ stdout: "[]" } as never);
15
+ });
16
+
17
+ describe("getIssues", () => {
18
+ it("passes --label flag when label provided", async () => {
19
+ await getIssues({ cwd: ".", label: "auto-claude" });
20
+
21
+ expect(mockX).toHaveBeenCalledWith("gh", expect.arrayContaining(["--label", "auto-claude"]));
22
+ });
23
+
24
+ it("does not pass --label flag when label not provided", async () => {
25
+ await getIssues({ cwd: "." });
26
+
27
+ const args = mockX.mock.calls[0]![1] as string[];
28
+ expect(args).not.toContain("--label");
29
+ });
30
+
31
+ it("passes --assignee @me flag when assignedToMe is true", async () => {
32
+ await getIssues({ cwd: ".", assignedToMe: true });
33
+
34
+ expect(mockX).toHaveBeenCalledWith("gh", expect.arrayContaining(["--assignee", "@me"]));
35
+ });
8
36
  });
9
37
 
10
- it("get issues", async () => {
11
- const issues = await getIssues({ assignedToMe: false, cwd: "." });
12
- expect(issues.length).toBeGreaterThan(0);
38
+ describe("isGithubCliInstalled", () => {
39
+ it("returns true when gh CLI outputs expected string", async () => {
40
+ mockX.mockResolvedValue({ stdout: "gh version 2.0.0 (https://github.com/cli/cli)" } as never);
41
+
42
+ const result = await isGithubCliInstalled();
43
+ expect(result).toBe(true);
44
+ });
45
+
46
+ it("returns false when gh CLI is not available", async () => {
47
+ mockX.mockRejectedValue(new Error("command not found"));
48
+
49
+ const result = await isGithubCliInstalled();
50
+ expect(result).toBe(false);
51
+ });
13
52
  });
14
53
  });
@@ -1,14 +1,27 @@
1
1
  import stripAnsi from "strip-ansi";
2
2
  import { x } from "tinyexec";
3
3
 
4
- export const isGithubCliInstalled = async (): Promise<boolean> => {
4
+ import { execSafe } from "./exec.js";
5
+
6
+ export async function isGithubCliInstalled(): Promise<boolean> {
5
7
  try {
6
- const proc = await x(`gh`, ["--version"]);
7
- return proc.stdout.indexOf("https://github.com/cli/cli") > 0;
8
- } catch (e) {
8
+ const proc = await x("gh", ["--version"]);
9
+ return proc.stdout.includes("https://github.com/cli/cli");
10
+ } catch {
11
+ // gh CLI not installed or not accessible
9
12
  return false;
10
13
  }
11
- };
14
+ }
15
+
16
+ export async function gh<T = unknown>(args: string[]): Promise<T> {
17
+ const result = await x("gh", args, { nodeOptions: { cwd: process.cwd() }, throwOnError: true });
18
+ return JSON.parse(result.stdout.trim()) as T;
19
+ }
20
+
21
+ export async function ghRaw(args: string[]): Promise<string> {
22
+ const result = await execSafe("gh", args);
23
+ return result.stdout;
24
+ }
12
25
 
13
26
  export interface Issue {
14
27
  labels: {
@@ -20,35 +33,34 @@ export interface Issue {
20
33
  state: string;
21
34
  }
22
35
 
23
- export const getIssues = async ({
36
+ export async function getIssues({
24
37
  assignedToMe,
25
38
  cwd,
39
+ label,
26
40
  }: {
27
- assignedToMe: boolean;
41
+ assignedToMe?: boolean;
28
42
  cwd: string;
29
- }): Promise<Issue[]> => {
30
- let issues: Issue[] = [];
31
-
32
- const flags = ["issue", "list", "--json", "labels,number,title,state"];
43
+ label?: string;
44
+ }): Promise<Issue[]> {
45
+ const args = ["issue", "list", "--json", "labels,number,title,state"];
33
46
 
34
47
  if (assignedToMe) {
35
- flags.push("--assignee");
36
- flags.push("@me");
48
+ args.push("--assignee", "@me");
37
49
  }
38
50
 
39
- //console.log('Current working directory:', cwd.stdout.trim())
51
+ if (label) {
52
+ args.push("--label", label);
53
+ }
40
54
 
41
- const result = await x(`gh`, flags);
55
+ const result = await x("gh", args);
42
56
  // Setting NO_COLOR=1 didn't remove colors so had to use stripAnsi
43
57
  const stripped = stripAnsi(result.stdout);
44
58
 
45
59
  try {
46
- issues = JSON.parse(stripped);
60
+ return JSON.parse(stripped) as Issue[];
47
61
  } catch {
48
62
  throw new Error(
49
63
  `Failed to parse GitHub CLI output as JSON. Raw output: ${stripped.slice(0, 200)}${stripped.length > 200 ? "..." : ""}`,
50
64
  );
51
65
  }
52
-
53
- return issues;
54
- };
66
+ }
@@ -1,6 +1,8 @@
1
1
  import { colors } from "consola/utils";
2
2
 
3
- export const getTerminalColumns = () => process.stdout?.columns || 80;
3
+ export function getTerminalColumns(): number {
4
+ return process.stdout?.columns || 80;
5
+ }
4
6
 
5
7
  export const limitText = (text: string, maxWidth: number): string => {
6
8
  if (text.length <= maxWidth) return text;