@towles/tool 0.0.96 → 0.0.104
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 +2 -2
- package/bin/run.ts +4 -3
- package/package.json +10 -37
- package/src/cli.ts +19 -0
- package/src/commands/agentboard.ts +386 -214
- package/src/commands/auto-claude/index.ts +74 -91
- package/src/commands/auto-claude/list.ts +33 -43
- package/src/commands/auto-claude/retry.test.ts +10 -6
- package/src/commands/auto-claude/retry.ts +26 -39
- package/src/commands/auto-claude/status.ts +10 -17
- package/src/commands/config.test.ts +4 -10
- package/src/commands/config.ts +14 -28
- package/src/commands/doctor.ts +156 -178
- package/src/commands/gh/branch-clean.ts +28 -43
- package/src/commands/gh/branch.ts +22 -37
- package/src/commands/gh/index.ts +10 -0
- package/src/commands/gh/pr.ts +82 -100
- package/src/commands/graph/index.ts +59 -70
- package/src/commands/install.ts +91 -115
- package/src/commands/journal/daily-notes.ts +16 -24
- package/src/commands/journal/index.ts +10 -0
- package/src/commands/journal/meeting.ts +16 -34
- package/src/commands/journal/note.ts +16 -34
- package/src/commands/shared.ts +21 -0
- package/src/lib/auto-claude/templates.test.ts +16 -11
- package/src/lib/graph/parser.test.ts +11 -10
- package/src/utils/git/gh-cli-wrapper.test.ts +6 -5
- package/src/commands/base.ts +0 -32
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
2
3
|
|
|
3
4
|
import type { ReadFileFn } from "./parser";
|
|
4
5
|
import { calculateCutoffMs, filterByDays, parseJsonl, quickTokenCount } from "./parser";
|
|
5
6
|
|
|
6
|
-
const mockReadFileSync
|
|
7
|
+
const mockReadFileSync = vi.fn() as Mock & ReadFileFn;
|
|
7
8
|
|
|
8
9
|
// ── Pure functions (no mocking needed) ──
|
|
9
10
|
|
|
@@ -67,7 +68,7 @@ describe("filterByDays", () => {
|
|
|
67
68
|
|
|
68
69
|
describe("parseJsonl", () => {
|
|
69
70
|
it("parses valid JSONL lines", () => {
|
|
70
|
-
|
|
71
|
+
mockReadFileSync.mockReturnValue(
|
|
71
72
|
'{"type":"user","sessionId":"s1","timestamp":"2025-01-01T00:00:00Z"}\n{"type":"assistant","sessionId":"s1","timestamp":"2025-01-01T00:01:00Z"}\n',
|
|
72
73
|
);
|
|
73
74
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -77,7 +78,7 @@ describe("parseJsonl", () => {
|
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
it("skips empty lines", () => {
|
|
80
|
-
|
|
81
|
+
mockReadFileSync.mockReturnValue(
|
|
81
82
|
'{"type":"user","sessionId":"s1","timestamp":"t"}\n\n\n{"type":"assistant","sessionId":"s1","timestamp":"t"}\n',
|
|
82
83
|
);
|
|
83
84
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -85,7 +86,7 @@ describe("parseJsonl", () => {
|
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
it("skips invalid JSON lines", () => {
|
|
88
|
-
|
|
89
|
+
mockReadFileSync.mockReturnValue(
|
|
89
90
|
'{"type":"user","sessionId":"s1","timestamp":"t"}\nnot-json\n{"type":"assistant","sessionId":"s1","timestamp":"t"}\n',
|
|
90
91
|
);
|
|
91
92
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -93,7 +94,7 @@ describe("parseJsonl", () => {
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
it("returns empty array for empty file", () => {
|
|
96
|
-
|
|
97
|
+
mockReadFileSync.mockReturnValue("");
|
|
97
98
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
98
99
|
expect(entries).toHaveLength(0);
|
|
99
100
|
});
|
|
@@ -109,7 +110,7 @@ describe("quickTokenCount", () => {
|
|
|
109
110
|
message: { usage: { input_tokens: 200, output_tokens: 75 } },
|
|
110
111
|
}),
|
|
111
112
|
].join("\n");
|
|
112
|
-
|
|
113
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
113
114
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(425);
|
|
114
115
|
});
|
|
115
116
|
|
|
@@ -118,12 +119,12 @@ describe("quickTokenCount", () => {
|
|
|
118
119
|
JSON.stringify({ message: { content: "text" } }),
|
|
119
120
|
JSON.stringify({ message: { usage: { input_tokens: 100, output_tokens: 50 } } }),
|
|
120
121
|
].join("\n");
|
|
121
|
-
|
|
122
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
122
123
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(150);
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
it("returns 0 for unreadable files", () => {
|
|
126
|
-
|
|
127
|
+
mockReadFileSync.mockImplementation(() => {
|
|
127
128
|
throw new Error("ENOENT");
|
|
128
129
|
});
|
|
129
130
|
expect(quickTokenCount("/missing/file.jsonl", mockReadFileSync)).toBe(0);
|
|
@@ -133,12 +134,12 @@ describe("quickTokenCount", () => {
|
|
|
133
134
|
const lines = JSON.stringify({
|
|
134
135
|
message: { usage: { input_tokens: 100 } },
|
|
135
136
|
});
|
|
136
|
-
|
|
137
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
137
138
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(100);
|
|
138
139
|
});
|
|
139
140
|
|
|
140
141
|
it("skips invalid JSON lines gracefully", () => {
|
|
141
|
-
|
|
142
|
+
mockReadFileSync.mockReturnValue(
|
|
142
143
|
'{"message":{"usage":{"input_tokens":50,"output_tokens":50}}}\nbadline\n',
|
|
143
144
|
);
|
|
144
145
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(100);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
2
3
|
|
|
3
4
|
import type { XFn } from "./gh-cli-wrapper";
|
|
4
5
|
import { getIssues, isGithubCliInstalled } from "./gh-cli-wrapper";
|
|
5
6
|
|
|
6
|
-
const mockX
|
|
7
|
+
const mockX = vi.fn().mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 }) as Mock & XFn;
|
|
7
8
|
|
|
8
9
|
describe("gh-cli-wrapper", () => {
|
|
9
10
|
beforeEach(() => {
|
|
10
11
|
vi.clearAllMocks();
|
|
11
|
-
|
|
12
|
+
mockX.mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
describe("getIssues", () => {
|
|
@@ -21,7 +22,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
21
22
|
it("does not pass --label flag when label not provided", async () => {
|
|
22
23
|
await getIssues({ cwd: ".", exec: mockX });
|
|
23
24
|
|
|
24
|
-
const args =
|
|
25
|
+
const args = mockX.mock.calls[0]![1] as string[];
|
|
25
26
|
expect(args).not.toContain("--label");
|
|
26
27
|
});
|
|
27
28
|
|
|
@@ -34,7 +35,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
34
35
|
|
|
35
36
|
describe("isGithubCliInstalled", () => {
|
|
36
37
|
it("returns true when gh CLI outputs expected string", async () => {
|
|
37
|
-
|
|
38
|
+
mockX.mockResolvedValue({
|
|
38
39
|
stdout: "gh version 2.0.0 (https://github.com/cli/cli)",
|
|
39
40
|
stderr: "",
|
|
40
41
|
exitCode: 0,
|
|
@@ -45,7 +46,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
it("returns false when gh CLI is not available", async () => {
|
|
48
|
-
|
|
49
|
+
mockX.mockRejectedValue(new Error("command not found"));
|
|
49
50
|
|
|
50
51
|
const result = await isGithubCliInstalled(mockX);
|
|
51
52
|
expect(result).toBe(false);
|
package/src/commands/base.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import type { SettingsFile } from "../config/settings.js";
|
|
3
|
-
import { loadSettings } from "../config/settings.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base command that all towles-tool commands extend.
|
|
7
|
-
* Provides shared functionality like settings loading and debug flag.
|
|
8
|
-
*/
|
|
9
|
-
export abstract class BaseCommand extends Command {
|
|
10
|
-
static baseFlags = {
|
|
11
|
-
debug: Flags.boolean({
|
|
12
|
-
char: "d",
|
|
13
|
-
description: "Enable debug output",
|
|
14
|
-
default: false,
|
|
15
|
-
}),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
protected settingsFile!: SettingsFile;
|
|
19
|
-
|
|
20
|
-
/** Shortcut to avoid `this.settingsFile.settings.X` stutter */
|
|
21
|
-
protected get userSettings() {
|
|
22
|
-
return this.settingsFile.settings;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Called before run(). Loads user settings.
|
|
27
|
-
*/
|
|
28
|
-
async init(): Promise<void> {
|
|
29
|
-
await super.init();
|
|
30
|
-
this.settingsFile = await loadSettings();
|
|
31
|
-
}
|
|
32
|
-
}
|