@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
package/src/commands/gh/pr.ts
CHANGED
|
@@ -1,58 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
2
|
import { x } from "tinyexec";
|
|
3
3
|
import consola from "consola";
|
|
4
4
|
import { colors } from "consola/utils";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { debugArg } from "../shared.js";
|
|
7
7
|
import { isGithubCliInstalled } from "../../utils/git/gh-cli-wrapper.js";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
9
|
+
function generatePrContent(branch: string, commits: string[]): { title: string; body: string } {
|
|
10
|
+
// Extract issue number from branch name if present (e.g., feature/123-some-feature)
|
|
11
|
+
const issueMatch = branch.match(/(\d+)/);
|
|
12
|
+
const issueNumber = issueMatch ? issueMatch[1] : null;
|
|
13
|
+
|
|
14
|
+
// Generate title from first commit or branch name
|
|
15
|
+
let title: string;
|
|
16
|
+
if (commits.length === 1) {
|
|
17
|
+
title = commits[0];
|
|
18
|
+
} else {
|
|
19
|
+
// Use branch name, cleaned up
|
|
20
|
+
title = branch
|
|
21
|
+
.replace(/^(feature|fix|bugfix|hotfix|chore|refactor)\//, "")
|
|
22
|
+
.replace(/^\d+-/, "")
|
|
23
|
+
.replace(/-/g, " ")
|
|
24
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Generate body
|
|
28
|
+
const lines: string[] = ["## Summary", ""];
|
|
29
|
+
|
|
30
|
+
if (commits.length === 1) {
|
|
31
|
+
lines.push(`- ${commits[0]}`);
|
|
32
|
+
} else {
|
|
33
|
+
for (const commit of commits.slice(0, 10)) {
|
|
34
|
+
lines.push(`- ${commit}`);
|
|
35
|
+
}
|
|
36
|
+
if (commits.length > 10) {
|
|
37
|
+
lines.push(`- ... and ${commits.length - 10} more commits`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
lines.push("");
|
|
42
|
+
|
|
43
|
+
if (issueNumber) {
|
|
44
|
+
lines.push(`Closes #${issueNumber}`);
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
lines.push("## Test plan");
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push("- [ ] Tests pass");
|
|
51
|
+
lines.push("- [ ] Manual testing");
|
|
52
|
+
|
|
53
|
+
return { title, body: lines.join("\n") };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default defineCommand({
|
|
57
|
+
meta: { name: "pr", description: "Create a pull request from the current branch" },
|
|
58
|
+
args: {
|
|
59
|
+
debug: debugArg,
|
|
60
|
+
draft: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
alias: "D",
|
|
33
63
|
description: "Create as draft PR",
|
|
34
64
|
default: false,
|
|
35
|
-
}
|
|
36
|
-
base:
|
|
37
|
-
|
|
65
|
+
},
|
|
66
|
+
base: {
|
|
67
|
+
type: "string",
|
|
68
|
+
alias: "b",
|
|
38
69
|
description: "Base branch for the PR",
|
|
39
70
|
default: "main",
|
|
40
|
-
}
|
|
41
|
-
yes:
|
|
42
|
-
|
|
71
|
+
},
|
|
72
|
+
yes: {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
alias: "y",
|
|
43
75
|
description: "Skip confirmation prompt",
|
|
44
76
|
default: false,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async run(): Promise<void> {
|
|
49
|
-
const { flags } = await this.parse(Pr);
|
|
50
|
-
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
async run({ args }) {
|
|
51
80
|
// Check prerequisites
|
|
52
81
|
const cliInstalled = await isGithubCliInstalled();
|
|
53
82
|
if (!cliInstalled) {
|
|
54
83
|
consola.error("GitHub CLI not installed");
|
|
55
|
-
|
|
84
|
+
process.exit(1);
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
// Get current branch
|
|
@@ -61,31 +90,31 @@ export default class Pr extends BaseCommand {
|
|
|
61
90
|
|
|
62
91
|
if (!currentBranch) {
|
|
63
92
|
consola.error("Not on a branch (detached HEAD?)");
|
|
64
|
-
|
|
93
|
+
process.exit(1);
|
|
65
94
|
}
|
|
66
95
|
|
|
67
|
-
if (currentBranch ===
|
|
68
|
-
consola.error(`Already on base branch ${
|
|
69
|
-
|
|
96
|
+
if (currentBranch === args.base) {
|
|
97
|
+
consola.error(`Already on base branch ${args.base}`);
|
|
98
|
+
process.exit(1);
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
consola.info(`Current branch: ${colors.cyan(currentBranch)}`);
|
|
73
|
-
consola.info(`Base branch: ${colors.cyan(
|
|
102
|
+
consola.info(`Base branch: ${colors.cyan(args.base)}`);
|
|
74
103
|
|
|
75
104
|
// Get commits between base and current branch
|
|
76
|
-
const logResult = await x("git", ["log", `${
|
|
105
|
+
const logResult = await x("git", ["log", `${args.base}..HEAD`, "--pretty=format:%s"]);
|
|
77
106
|
|
|
78
107
|
const commits = logResult.stdout.trim().split("\n").filter(Boolean);
|
|
79
108
|
|
|
80
109
|
if (commits.length === 0) {
|
|
81
|
-
consola.error(`No commits between ${
|
|
82
|
-
|
|
110
|
+
consola.error(`No commits between ${args.base} and ${currentBranch}`);
|
|
111
|
+
process.exit(1);
|
|
83
112
|
}
|
|
84
113
|
|
|
85
114
|
consola.info(`Found ${colors.green(commits.length.toString())} commits`);
|
|
86
115
|
|
|
87
116
|
// Generate PR title and body
|
|
88
|
-
const { title, body } =
|
|
117
|
+
const { title, body } = generatePrContent(currentBranch, commits);
|
|
89
118
|
|
|
90
119
|
consola.box({
|
|
91
120
|
title: "PR Preview",
|
|
@@ -93,7 +122,7 @@ export default class Pr extends BaseCommand {
|
|
|
93
122
|
});
|
|
94
123
|
|
|
95
124
|
// Confirm unless --yes
|
|
96
|
-
if (!
|
|
125
|
+
if (!args.yes) {
|
|
97
126
|
const confirmed = await consola.prompt("Create this PR?", {
|
|
98
127
|
type: "confirm",
|
|
99
128
|
initial: true,
|
|
@@ -101,7 +130,7 @@ export default class Pr extends BaseCommand {
|
|
|
101
130
|
|
|
102
131
|
if (!confirmed) {
|
|
103
132
|
consola.info(colors.dim("Canceled"));
|
|
104
|
-
|
|
133
|
+
process.exit(0);
|
|
105
134
|
}
|
|
106
135
|
}
|
|
107
136
|
|
|
@@ -115,9 +144,9 @@ export default class Pr extends BaseCommand {
|
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
// Create PR
|
|
118
|
-
const prArgs = ["pr", "create", "--title", title, "--body", body, "--base",
|
|
147
|
+
const prArgs = ["pr", "create", "--title", title, "--body", body, "--base", args.base];
|
|
119
148
|
|
|
120
|
-
if (
|
|
149
|
+
if (args.draft) {
|
|
121
150
|
prArgs.push("--draft");
|
|
122
151
|
}
|
|
123
152
|
|
|
@@ -125,52 +154,5 @@ export default class Pr extends BaseCommand {
|
|
|
125
154
|
const prUrl = prResult.stdout.trim();
|
|
126
155
|
|
|
127
156
|
consola.success(`PR created: ${colors.cyan(prUrl)}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private generatePrContent(branch: string, commits: string[]): { title: string; body: string } {
|
|
131
|
-
// Extract issue number from branch name if present (e.g., feature/123-some-feature)
|
|
132
|
-
const issueMatch = branch.match(/(\d+)/);
|
|
133
|
-
const issueNumber = issueMatch ? issueMatch[1] : null;
|
|
134
|
-
|
|
135
|
-
// Generate title from first commit or branch name
|
|
136
|
-
let title: string;
|
|
137
|
-
if (commits.length === 1) {
|
|
138
|
-
title = commits[0];
|
|
139
|
-
} else {
|
|
140
|
-
// Use branch name, cleaned up
|
|
141
|
-
title = branch
|
|
142
|
-
.replace(/^(feature|fix|bugfix|hotfix|chore|refactor)\//, "")
|
|
143
|
-
.replace(/^\d+-/, "")
|
|
144
|
-
.replace(/-/g, " ")
|
|
145
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Generate body
|
|
149
|
-
const lines: string[] = ["## Summary", ""];
|
|
150
|
-
|
|
151
|
-
if (commits.length === 1) {
|
|
152
|
-
lines.push(`- ${commits[0]}`);
|
|
153
|
-
} else {
|
|
154
|
-
for (const commit of commits.slice(0, 10)) {
|
|
155
|
-
lines.push(`- ${commit}`);
|
|
156
|
-
}
|
|
157
|
-
if (commits.length > 10) {
|
|
158
|
-
lines.push(`- ... and ${commits.length - 10} more commits`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
lines.push("");
|
|
163
|
-
|
|
164
|
-
if (issueNumber) {
|
|
165
|
-
lines.push(`Closes #${issueNumber}`);
|
|
166
|
-
lines.push("");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
lines.push("## Test plan");
|
|
170
|
-
lines.push("");
|
|
171
|
-
lines.push("- [ ] Tests pass");
|
|
172
|
-
lines.push("- [ ] Manual testing");
|
|
173
|
-
|
|
174
|
-
return { title, body: lines.join("\n") };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
2
|
import { DateTime } from "luxon";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { x } from "tinyexec";
|
|
7
|
-
import
|
|
7
|
+
import consola from "consola";
|
|
8
|
+
|
|
9
|
+
import { debugArg } from "../shared.js";
|
|
8
10
|
import {
|
|
9
11
|
buildAllSessionsTreemap,
|
|
10
12
|
buildBarChartData,
|
|
@@ -40,86 +42,73 @@ export type {
|
|
|
40
42
|
TreemapNode,
|
|
41
43
|
} from "../../lib/graph/index.js";
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
description: "Generate treemap for all recent sessions",
|
|
52
|
-
command: "<%= config.bin %> <%= command.id %>",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
description: "Generate treemap for a specific session",
|
|
56
|
-
command: "<%= config.bin %> <%= command.id %> --session abc123",
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
description: "Generate and auto-open in browser",
|
|
60
|
-
command: "<%= config.bin %> <%= command.id %> --open",
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
static override flags = {
|
|
65
|
-
...BaseCommand.baseFlags,
|
|
66
|
-
session: Flags.string({
|
|
67
|
-
char: "s",
|
|
45
|
+
export default defineCommand({
|
|
46
|
+
meta: { name: "graph", description: "Generate interactive HTML treemap from session token data" },
|
|
47
|
+
args: {
|
|
48
|
+
debug: debugArg,
|
|
49
|
+
session: {
|
|
50
|
+
type: "string" as const,
|
|
51
|
+
alias: "s",
|
|
68
52
|
description: "Session ID to analyze (shows all sessions if not provided)",
|
|
69
|
-
}
|
|
70
|
-
open:
|
|
71
|
-
|
|
53
|
+
},
|
|
54
|
+
open: {
|
|
55
|
+
type: "boolean" as const,
|
|
56
|
+
alias: "o",
|
|
72
57
|
description: "Open treemap in browser after generating",
|
|
73
58
|
default: true,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
},
|
|
60
|
+
serve: {
|
|
61
|
+
type: "boolean" as const,
|
|
77
62
|
description: "Start local HTTP server to serve treemap (default: true)",
|
|
78
63
|
default: true,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
description: "Port for local server",
|
|
84
|
-
default: 8765,
|
|
85
|
-
}
|
|
86
|
-
days:
|
|
87
|
-
|
|
88
|
-
default: 7,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
async run()
|
|
93
|
-
const
|
|
64
|
+
},
|
|
65
|
+
port: {
|
|
66
|
+
type: "string" as const,
|
|
67
|
+
alias: "p",
|
|
68
|
+
description: "Port for local server (default: 8765)",
|
|
69
|
+
default: "8765",
|
|
70
|
+
},
|
|
71
|
+
days: {
|
|
72
|
+
type: "string" as const,
|
|
73
|
+
description: "Filter to sessions from last N days (0=no limit, default: 7)",
|
|
74
|
+
default: "7",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
async run({ args }) {
|
|
78
|
+
const port = Number(args.port);
|
|
79
|
+
const days = Number(args.days);
|
|
94
80
|
|
|
95
81
|
const projectsDir = path.join(os.homedir(), ".claude", "projects");
|
|
96
82
|
if (!fs.existsSync(projectsDir)) {
|
|
97
|
-
|
|
83
|
+
consola.error("No Claude projects directory found at ~/.claude/projects/");
|
|
84
|
+
process.exit(1);
|
|
98
85
|
}
|
|
99
86
|
|
|
100
|
-
const sessionId =
|
|
87
|
+
const sessionId = args.session;
|
|
101
88
|
let treemapData;
|
|
102
89
|
let barChartData = { days: [] as any[] };
|
|
103
90
|
|
|
104
91
|
if (!sessionId) {
|
|
105
92
|
// All sessions mode
|
|
106
|
-
const sessions = findRecentSessions(projectsDir, 500,
|
|
93
|
+
const sessions = findRecentSessions(projectsDir, 500, days);
|
|
107
94
|
if (sessions.length === 0) {
|
|
108
|
-
|
|
95
|
+
consola.error("No sessions found");
|
|
96
|
+
process.exit(1);
|
|
109
97
|
}
|
|
110
98
|
|
|
111
|
-
const daysMsg =
|
|
112
|
-
|
|
99
|
+
const daysMsg = days > 0 ? ` (last ${days} days)` : "";
|
|
100
|
+
consola.info(`š Generating treemap for ${sessions.length} sessions${daysMsg}...`);
|
|
113
101
|
treemapData = buildAllSessionsTreemap(sessions);
|
|
114
102
|
barChartData = buildBarChartData(sessions);
|
|
115
103
|
} else {
|
|
116
104
|
// Single session mode
|
|
117
105
|
const sessionPath = findSessionPath(projectsDir, sessionId);
|
|
118
106
|
if (!sessionPath) {
|
|
119
|
-
|
|
107
|
+
consola.error(`Session ${sessionId} not found`);
|
|
108
|
+
process.exit(1);
|
|
120
109
|
}
|
|
121
110
|
|
|
122
|
-
|
|
111
|
+
consola.info(`š Generating treemap for session ${sessionId}...`);
|
|
123
112
|
const entries = parseJsonl(sessionPath);
|
|
124
113
|
treemapData = buildSessionTreemap(sessionId, entries);
|
|
125
114
|
// Bar chart not meaningful for single session, leave empty
|
|
@@ -135,35 +124,35 @@ export default class Graph extends BaseCommand {
|
|
|
135
124
|
}
|
|
136
125
|
|
|
137
126
|
const timestamp = DateTime.now().toFormat("yyyy-MM-dd'T'HH-mmZZZ");
|
|
138
|
-
const daysLabel =
|
|
127
|
+
const daysLabel = days > 0 ? `${days}d` : "all";
|
|
139
128
|
const filename = sessionId
|
|
140
129
|
? `treemap-${sessionId.slice(0, 8)}-${timestamp}.html`
|
|
141
130
|
: `treemap-${daysLabel}-${timestamp}.html`;
|
|
142
131
|
const outputPath = path.join(reportsDir, filename);
|
|
143
132
|
|
|
144
133
|
fs.writeFileSync(outputPath, html);
|
|
145
|
-
|
|
134
|
+
consola.info(`ā Saved to ${outputPath}`);
|
|
146
135
|
|
|
147
|
-
if (
|
|
148
|
-
const { server, port: actualPort } = await startServer(html, filename,
|
|
136
|
+
if (args.serve) {
|
|
137
|
+
const { server, port: actualPort } = await startServer(html, filename, port);
|
|
149
138
|
const url = `http://localhost:${actualPort}/`;
|
|
150
|
-
if (actualPort !==
|
|
151
|
-
|
|
139
|
+
if (actualPort !== port) {
|
|
140
|
+
consola.info(`\nā ļø Port ${port} in use, using ${actualPort}`);
|
|
152
141
|
}
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
consola.info(`š Server running at ${url}`);
|
|
143
|
+
consola.info(" Press Ctrl+C to stop\n");
|
|
155
144
|
|
|
156
|
-
if (
|
|
145
|
+
if (args.open) {
|
|
157
146
|
openInBrowser(url);
|
|
158
147
|
}
|
|
159
148
|
|
|
160
149
|
// Keep server running until Ctrl+C
|
|
161
150
|
await waitForShutdown(server);
|
|
162
|
-
|
|
163
|
-
} else if (
|
|
164
|
-
|
|
151
|
+
consola.info("\nš Stopping server...");
|
|
152
|
+
} else if (args.open) {
|
|
153
|
+
consola.info("\nš Opening treemap...");
|
|
165
154
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
166
155
|
await x(openCmd, [outputPath]);
|
|
167
156
|
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
157
|
+
},
|
|
158
|
+
});
|