@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.
@@ -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: ReadFileFn = vi.fn();
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
- vi.mocked(mockReadFileSync).mockReturnValue(
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
- vi.mocked(mockReadFileSync).mockReturnValue(
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
- vi.mocked(mockReadFileSync).mockReturnValue(
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
- vi.mocked(mockReadFileSync).mockReturnValue("");
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
- vi.mocked(mockReadFileSync).mockReturnValue(lines);
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
- vi.mocked(mockReadFileSync).mockReturnValue(lines);
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
- vi.mocked(mockReadFileSync).mockImplementation(() => {
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
- vi.mocked(mockReadFileSync).mockReturnValue(lines);
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
- vi.mocked(mockReadFileSync).mockReturnValue(
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: XFn = vi.fn().mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
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
- vi.mocked(mockX).mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
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 = vi.mocked(mockX).mock.calls[0]![1] as string[];
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
- vi.mocked(mockX).mockResolvedValue({
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
- vi.mocked(mockX).mockRejectedValue(new Error("command not found"));
49
+ mockX.mockRejectedValue(new Error("command not found"));
49
50
 
50
51
  const result = await isGithubCliInstalled(mockX);
51
52
  expect(result).toBe(false);
@@ -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
- }