@towles/tool 0.0.7 → 0.0.11-beta.2
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 +8 -4
- package/dist/index.mjs +374 -43
- package/package.json +27 -16
package/README.md
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
One off quality of life scripts that I use on a daily basis.
|
|
10
10
|
|
|
11
11
|
## Tools to add
|
|
12
|
-
- [
|
|
13
|
-
- [ ] git
|
|
12
|
+
- [x] Today - creates and opens a markdown file, named the current week of the year, for you to keep your daily notes and use a scratch pad for notes.
|
|
13
|
+
- [ ] use claude code to generate git commits with multiple options for the commit message.
|
|
14
14
|
|
|
15
15
|
## Install from repository
|
|
16
16
|
|
|
@@ -22,7 +22,6 @@ tt
|
|
|
22
22
|
|
|
23
23
|
# or
|
|
24
24
|
towles-tool
|
|
25
|
-
|
|
26
25
|
```
|
|
27
26
|
|
|
28
27
|
## Unisntall
|
|
@@ -41,9 +40,14 @@ pnpm tt
|
|
|
41
40
|
if that works, then you need to add the pnpm global bin directory to your PATH.
|
|
42
41
|
|
|
43
42
|
## packages to consider
|
|
44
|
-
|
|
43
|
+
- [@anthropic-ai/claude-code](https://github.com/anthropic-ai/claude-code) - A library for interacting with the Claude code
|
|
44
|
+
- [zod](https://github.com/colinhacks/zod) - TypeScript-first schema validation
|
|
45
45
|
- [Consola](https://github.com/unjs/consola) console wrapper and colors
|
|
46
46
|
- [c12](https://github.com/unjs/c12) configuration loader and utilities
|
|
47
|
+
- [rolldown-vite](https://voidzero.dev/posts/announcing-rolldown-vite) - A Vite plugin for rolling down your code
|
|
48
|
+
- ~~[zx](https://github.com/google/zx) google created library to write shell scripts in a more powerful and expressive way via the Anthropic API.~~
|
|
49
|
+
- [prompts](https://github.com/terkelg/prompts) - A library for creating beautiful command-line prompts, with fuzzy search and other features.
|
|
50
|
+
- [commander](https://github.com/tj/commander.js) - A library for building command-line interfaces, with support for subcommands, argument parsing, and more.
|
|
47
51
|
|
|
48
52
|
## Document verbose and debug options
|
|
49
53
|
|
package/dist/index.mjs
CHANGED
|
@@ -3,16 +3,363 @@ import process from 'node:process';
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import consola from 'consola';
|
|
5
5
|
import { colors } from 'consola/utils';
|
|
6
|
-
import {
|
|
6
|
+
import { spinner } from '@clack/prompts';
|
|
7
|
+
import { Fzf } from 'fzf';
|
|
8
|
+
import prompts from 'prompts';
|
|
9
|
+
import z$1, { z } from 'zod/v4';
|
|
10
|
+
import { err, ok } from 'neverthrow';
|
|
11
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
12
|
+
import { execSync, exec } from 'node:child_process';
|
|
13
|
+
import util, { promisify } from 'node:util';
|
|
7
14
|
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
15
|
import * as path from 'node:path';
|
|
9
16
|
import path__default from 'node:path';
|
|
10
|
-
import util, { promisify } from 'node:util';
|
|
11
17
|
import { homedir } from 'node:os';
|
|
12
18
|
import { setupDotenv, loadConfig } from 'c12';
|
|
13
19
|
import { updateConfig } from 'c12/update';
|
|
14
20
|
|
|
15
|
-
const version = "0.0.
|
|
21
|
+
const version = "0.0.11-beta.2";
|
|
22
|
+
|
|
23
|
+
class AnyError extends Error {
|
|
24
|
+
name = "AnyError";
|
|
25
|
+
cause;
|
|
26
|
+
constructor(message, cause) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.cause = isError(cause) ? cause : void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
class ClaudeError extends AnyError {
|
|
32
|
+
name = "ClaudeError";
|
|
33
|
+
}
|
|
34
|
+
function isError(error) {
|
|
35
|
+
return error instanceof Error || error instanceof AnyError;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ValidationError extends AnyError {
|
|
39
|
+
// eslint-disable-next-line node/handle-callback-err
|
|
40
|
+
constructor(error, message, cause) {
|
|
41
|
+
super(message, cause);
|
|
42
|
+
this.error = error;
|
|
43
|
+
this.message = message;
|
|
44
|
+
}
|
|
45
|
+
name = "ValidationError";
|
|
46
|
+
}
|
|
47
|
+
function validate(schema, data) {
|
|
48
|
+
const result = schema.safeParse(data);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
consola.error("Validation failed:", result.error);
|
|
51
|
+
return err(new ValidationError(result.error, "Validation error occurred"));
|
|
52
|
+
}
|
|
53
|
+
return ok(result.data);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const jsonPrimitiveSchema = z.union([
|
|
57
|
+
z.string(),
|
|
58
|
+
z.number(),
|
|
59
|
+
z.boolean(),
|
|
60
|
+
z.null()
|
|
61
|
+
]);
|
|
62
|
+
const jsonValueSchema = z.lazy(
|
|
63
|
+
() => z.union([
|
|
64
|
+
jsonPrimitiveSchema,
|
|
65
|
+
z.array(jsonValueSchema),
|
|
66
|
+
z.record(z.string(), jsonValueSchema)
|
|
67
|
+
])
|
|
68
|
+
);
|
|
69
|
+
z.record(
|
|
70
|
+
z.string(),
|
|
71
|
+
jsonValueSchema
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
class ClaudeService {
|
|
75
|
+
pathToClaudeCodeExecutable;
|
|
76
|
+
constructor(pathToClaudeCodeExecutable) {
|
|
77
|
+
this.pathToClaudeCodeExecutable = pathToClaudeCodeExecutable;
|
|
78
|
+
}
|
|
79
|
+
async sendMessageStream(input, onChunk) {
|
|
80
|
+
try {
|
|
81
|
+
const options = {
|
|
82
|
+
pathToClaudeCodeExecutable: this.pathToClaudeCodeExecutable,
|
|
83
|
+
permissionMode: "bypassPermissions"
|
|
84
|
+
};
|
|
85
|
+
if (input.sessionId) {
|
|
86
|
+
options.resume = input.sessionId;
|
|
87
|
+
}
|
|
88
|
+
if (input.cwd) {
|
|
89
|
+
options.cwd = input.cwd;
|
|
90
|
+
}
|
|
91
|
+
if (input.allowedTools) {
|
|
92
|
+
options.allowedTools = input.allowedTools;
|
|
93
|
+
}
|
|
94
|
+
if (input.bypassPermissions) {
|
|
95
|
+
options.permissionMode = "bypassPermissions";
|
|
96
|
+
}
|
|
97
|
+
const messages = [];
|
|
98
|
+
for await (const message of query({
|
|
99
|
+
prompt: input.message,
|
|
100
|
+
options
|
|
101
|
+
})) {
|
|
102
|
+
const customMessage = message;
|
|
103
|
+
messages.push(customMessage);
|
|
104
|
+
onChunk(customMessage);
|
|
105
|
+
}
|
|
106
|
+
if (messages.length === 0) {
|
|
107
|
+
throw new Error("No response received from Claude Code SDK");
|
|
108
|
+
}
|
|
109
|
+
return ok(messages);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return err(
|
|
112
|
+
new ClaudeError("Failed to stream message from Claude", error)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
parseAssistantContent(rawContent) {
|
|
117
|
+
const parsed = validate(jsonValueSchema, rawContent);
|
|
118
|
+
if (parsed.isErr()) {
|
|
119
|
+
return err(
|
|
120
|
+
new ClaudeError("Invalid assistant content format", parsed.error)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (!Array.isArray(parsed.value)) {
|
|
124
|
+
return err(
|
|
125
|
+
new ClaudeError("Assistant content must be an array of content blocks")
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
for (const item of parsed.value) {
|
|
129
|
+
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
130
|
+
return err(
|
|
131
|
+
new ClaudeError("Assistant content contains invalid content block")
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return ok(parsed.value);
|
|
136
|
+
}
|
|
137
|
+
parseUserContent(rawContent) {
|
|
138
|
+
const parsed = validate(jsonValueSchema, rawContent);
|
|
139
|
+
if (parsed.isErr()) {
|
|
140
|
+
return err(new ClaudeError("Invalid user content format", parsed.error));
|
|
141
|
+
}
|
|
142
|
+
if (typeof parsed.value === "string") {
|
|
143
|
+
return ok(parsed.value);
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(parsed.value)) {
|
|
146
|
+
for (const item of parsed.value) {
|
|
147
|
+
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
148
|
+
return err(
|
|
149
|
+
new ClaudeError(
|
|
150
|
+
"User content array contains invalid content block"
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return ok(parsed.value);
|
|
156
|
+
}
|
|
157
|
+
return err(
|
|
158
|
+
new ClaudeError(
|
|
159
|
+
"User content must be a string or array of content blocks"
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function execCommand(cmd, cwd) {
|
|
166
|
+
return execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function getGitDiff(cwd) {
|
|
170
|
+
const r = execCommand(
|
|
171
|
+
// `git --no-pager log "${from ? `${from}...` : ''}${to}" `,
|
|
172
|
+
`git --no-pager diff --staged`,
|
|
173
|
+
// --name-status
|
|
174
|
+
// --pretty="----%n%s|%h|%an|%ae%n%b"
|
|
175
|
+
cwd
|
|
176
|
+
);
|
|
177
|
+
return r;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function printJson(obj) {
|
|
181
|
+
consola.log(util.inspect(obj, {
|
|
182
|
+
depth: 2,
|
|
183
|
+
colors: true,
|
|
184
|
+
showHidden: false,
|
|
185
|
+
compact: false
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
function printDebug(message, ...args) {
|
|
189
|
+
if (process.env.DEBUG) {
|
|
190
|
+
consola.debug(`DEBUG: ${message}`, ...args);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const commitTypes = ["feat", "fix", "docs", "test", "wip"];
|
|
195
|
+
const maxSubjectLength = 72;
|
|
196
|
+
const llmMaxSubjectLength = maxSubjectLength - 12;
|
|
197
|
+
const gitCommitSuggestionSchema = z$1.object({
|
|
198
|
+
subject: z$1.string().min(1).max(maxSubjectLength),
|
|
199
|
+
// larger than we told the LLM to generate
|
|
200
|
+
body: z$1.string().optional()
|
|
201
|
+
});
|
|
202
|
+
function finalPrompt(diff, generate_number) {
|
|
203
|
+
const generate_number_plural = "s" ;
|
|
204
|
+
const first_part = [
|
|
205
|
+
`You are a helpful assistant specializing in writing clear and informative Git commit messages using the conventional style.`,
|
|
206
|
+
`Based on the given code changes or context, generate exactly ${generate_number} conventional Git commit message${generate_number_plural} based on the following guidelines.`,
|
|
207
|
+
`1. Format: follow the conventional commits format following of one of the following. :`,
|
|
208
|
+
"<type>: <commit message>",
|
|
209
|
+
"<type>(<optional scope>): <commit message>",
|
|
210
|
+
"",
|
|
211
|
+
`2. Types: use one of the following types:`,
|
|
212
|
+
` ${commitTypes.join("-, ")}`,
|
|
213
|
+
"3. Guidelines for writing commit messages:",
|
|
214
|
+
" - Be specific about what changes were made",
|
|
215
|
+
' - Use imperative mood ("add feature" not "added feature")',
|
|
216
|
+
` - Keep subject line under ${llmMaxSubjectLength} characters`,
|
|
217
|
+
" - make each entry more generic and not too specific to the code changes",
|
|
218
|
+
" - Do not end the subject line with a period",
|
|
219
|
+
" - Use the body to explain what and why vs. how",
|
|
220
|
+
"4. Focus on:",
|
|
221
|
+
" - What problem this commit solves",
|
|
222
|
+
" - Why this change was necessary",
|
|
223
|
+
" - Any important technical details",
|
|
224
|
+
"5. Exclude anything unnecessary such as translation or implementation details."
|
|
225
|
+
];
|
|
226
|
+
const diff_part = [
|
|
227
|
+
`Here is the context for the commit message${generate_number_plural}:`,
|
|
228
|
+
"",
|
|
229
|
+
"```diff",
|
|
230
|
+
`${diff}`,
|
|
231
|
+
"```",
|
|
232
|
+
"",
|
|
233
|
+
`If no code changes are provided, use the context to generate a commit message based on the task or issue description.`
|
|
234
|
+
];
|
|
235
|
+
const last_part = [
|
|
236
|
+
"",
|
|
237
|
+
`Lastly, Provide your response as a JSON array containing exactly ${generate_number} object${generate_number_plural}, each with the following keys:`,
|
|
238
|
+
`- "subject": The main commit message using the conventional commit style. It should be a concise summary of the changes.`,
|
|
239
|
+
`- "body": An optional detailed explanation of the changes. If not needed, use an empty string.`,
|
|
240
|
+
`The array must always contain ${generate_number} element${generate_number_plural}, no more and no less.`,
|
|
241
|
+
`The LLM result will be valid json only if it is well-formed and follows the schema below.`,
|
|
242
|
+
"",
|
|
243
|
+
`<result>`,
|
|
244
|
+
`[`,
|
|
245
|
+
` {`,
|
|
246
|
+
` "subject": "chore: update <file> to handle auth",`,
|
|
247
|
+
` "body": "- Update login function to handle edge cases\\n- Add additional error logging for debugging",`,
|
|
248
|
+
` }`,
|
|
249
|
+
` {`,
|
|
250
|
+
` "subject": "fix(auth): fix bug in user authentication process",`,
|
|
251
|
+
` "body": "- Update login function to handle edge cases\\n- Add additional error logging for debugging",`,
|
|
252
|
+
` }`,
|
|
253
|
+
`]`,
|
|
254
|
+
`</result>`,
|
|
255
|
+
"",
|
|
256
|
+
"",
|
|
257
|
+
`Ensure you generate exactly ${generate_number} commit message${generate_number_plural}, even if it requires creating slightly varied versions for similar changes.`,
|
|
258
|
+
`The response should be valid JSON that can be parsed without errors.`
|
|
259
|
+
];
|
|
260
|
+
return [
|
|
261
|
+
...first_part,
|
|
262
|
+
...diff_part,
|
|
263
|
+
...last_part
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
async function gitCommitCommand(config) {
|
|
267
|
+
const s = spinner();
|
|
268
|
+
s.start("Generating commit message based on diff...");
|
|
269
|
+
s.message("getting git diff...");
|
|
270
|
+
const diff = await getGitDiff(config.cwd);
|
|
271
|
+
if (diff.trim().length === 0) {
|
|
272
|
+
consola.error(`No staged changes found to commit. use '${colors.blue("git add <file>...")}' to stage changes before committing.`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
s.message("generating prompt...");
|
|
276
|
+
const prompt = finalPrompt(
|
|
277
|
+
diff,
|
|
278
|
+
/* config.generate_number || */
|
|
279
|
+
6
|
|
280
|
+
);
|
|
281
|
+
const service = new ClaudeService();
|
|
282
|
+
const input = {
|
|
283
|
+
message: prompt
|
|
284
|
+
};
|
|
285
|
+
s.message("sending prompt to claude and waiting answer...");
|
|
286
|
+
const result = await service.sendMessageStream(input, () => {
|
|
287
|
+
});
|
|
288
|
+
if (result.isErr()) {
|
|
289
|
+
consola.error(`Error sending message: ${result.error.message}`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
s.stop(`Received commit messages from Claude!${colors.green("\u2713")}`);
|
|
293
|
+
const filteredResults = result.value.filter((msg) => msg.type === "result");
|
|
294
|
+
if (filteredResults.length === 0) {
|
|
295
|
+
consola.error("No valid commit messages received from Claude");
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
if (filteredResults.length > 1) {
|
|
299
|
+
consola.warn(`Received ${filteredResults.length} commit messages, using the first one`);
|
|
300
|
+
}
|
|
301
|
+
const startIndex = filteredResults[0].result.indexOf("[");
|
|
302
|
+
const endIndex = filteredResults[0].result.lastIndexOf("]");
|
|
303
|
+
if (startIndex === -1 || endIndex === -1 || startIndex >= endIndex) {
|
|
304
|
+
consola.error("Could not find valid JSON array in Claude response");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const cleanedResultRaw = filteredResults[0].result.substring(startIndex, endIndex + 1);
|
|
308
|
+
const cleanedResult = JSON.parse(cleanedResultRaw);
|
|
309
|
+
const suggestions = validate(z$1.array(gitCommitSuggestionSchema), cleanedResult);
|
|
310
|
+
consola.success("Claude suggestions validated for format!");
|
|
311
|
+
if (!suggestions.isOk()) {
|
|
312
|
+
consola.error("Invalid suggestions format:", suggestions.error);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
const choosenSuggestion = await promptUserForCommitMessage(suggestions.value);
|
|
316
|
+
const commandWithArgs = `git commit -m "${choosenSuggestion.subject}"`;
|
|
317
|
+
consola.info(`Running command: ${colors.bgCyan(commandWithArgs)}`);
|
|
318
|
+
try {
|
|
319
|
+
execCommand(commandWithArgs, config.cwd);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
consola.error(`Failed to commit changes:`);
|
|
322
|
+
printJson(error);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function promptUserForCommitMessage(suggestions) {
|
|
327
|
+
let profileChoices = suggestions.map((x) => ({
|
|
328
|
+
title: x.subject,
|
|
329
|
+
value: x.subject
|
|
330
|
+
// description: limitText(`${x.getFriendlyAccountName()}`, 15),
|
|
331
|
+
}));
|
|
332
|
+
profileChoices = profileChoices.sort((a, b) => a.title.localeCompare(b.title));
|
|
333
|
+
const fzf = new Fzf(profileChoices, {
|
|
334
|
+
selector: (item) => `${item.title}`,
|
|
335
|
+
casing: "case-insensitive"
|
|
336
|
+
});
|
|
337
|
+
try {
|
|
338
|
+
const { fn } = await prompts({
|
|
339
|
+
name: "fn",
|
|
340
|
+
message: "Choose your git commit:",
|
|
341
|
+
type: "autocomplete",
|
|
342
|
+
choices: profileChoices,
|
|
343
|
+
async suggest(input, choices) {
|
|
344
|
+
const results = fzf.find(input);
|
|
345
|
+
return results.map((r) => choices.find((x) => x.value === r.item.title));
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if (!fn) {
|
|
349
|
+
consola.log(colors.dim("Cancelled!"));
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
const entry = suggestions.find((x) => x.subject === fn);
|
|
353
|
+
if (!entry) {
|
|
354
|
+
consola.error("No matching commit message found");
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
return entry;
|
|
358
|
+
} catch (ex) {
|
|
359
|
+
consola.error("Failed to run : exit", ex);
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
16
363
|
|
|
17
364
|
function getMondayOfWeek(date) {
|
|
18
365
|
const newDate = new Date(date);
|
|
@@ -66,18 +413,18 @@ function createJournalContent({ mondayDate }) {
|
|
|
66
413
|
content.push(``);
|
|
67
414
|
return content.join("\n");
|
|
68
415
|
}
|
|
69
|
-
async function openInEditor(
|
|
416
|
+
async function openInEditor({ editor, filePath }) {
|
|
70
417
|
try {
|
|
71
|
-
await execAsync(`"${
|
|
418
|
+
await execAsync(`"${editor}" "${filePath}"`);
|
|
72
419
|
} catch (ex) {
|
|
73
|
-
consola.warn(`Could not open in editor : '${
|
|
420
|
+
consola.warn(`Could not open in editor : '${editor}'. Modify your editor in the config: examples include 'code', 'code-insiders', etc...`, ex);
|
|
74
421
|
}
|
|
75
422
|
}
|
|
76
|
-
async function todayCommand(
|
|
423
|
+
async function todayCommand(userConfig) {
|
|
77
424
|
try {
|
|
78
425
|
const fileInfo = generateJournalFileInfo();
|
|
79
|
-
const filePath = path__default.join(
|
|
80
|
-
ensureDirectoryExists(path__default.join(
|
|
426
|
+
const filePath = path__default.join(userConfig.journalDir, ...fileInfo.pathPrefix, fileInfo.fileName);
|
|
427
|
+
ensureDirectoryExists(path__default.join(userConfig.journalDir, ...fileInfo.pathPrefix));
|
|
81
428
|
if (!existsSync(filePath)) {
|
|
82
429
|
const content = createJournalContent({ mondayDate: fileInfo.mondayDate });
|
|
83
430
|
writeFileSync(filePath, content, "utf8");
|
|
@@ -85,9 +432,9 @@ async function todayCommand(config) {
|
|
|
85
432
|
} else {
|
|
86
433
|
consola.info(`Opening existing journal file: ${colors.cyan(filePath)}`);
|
|
87
434
|
}
|
|
88
|
-
await openInEditor(
|
|
435
|
+
await openInEditor({ editor: userConfig.editor, filePath });
|
|
89
436
|
} catch (error) {
|
|
90
|
-
consola.
|
|
437
|
+
consola.warn("Error creating journal file:", error);
|
|
91
438
|
process.exit(1);
|
|
92
439
|
}
|
|
93
440
|
}
|
|
@@ -102,25 +449,10 @@ const constants = {
|
|
|
102
449
|
toolName: "towles-tool"
|
|
103
450
|
};
|
|
104
451
|
|
|
105
|
-
function
|
|
106
|
-
consola.log(util.inspect(obj, {
|
|
107
|
-
depth: 2,
|
|
108
|
-
colors: true,
|
|
109
|
-
showHidden: false,
|
|
110
|
-
compact: false
|
|
111
|
-
}));
|
|
112
|
-
}
|
|
113
|
-
function printDebug(message, ...args) {
|
|
114
|
-
if (process.env.DEBUG) {
|
|
115
|
-
consola.log(`DEBUG: ${message}`, ...args);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function getDefaultConfig() {
|
|
452
|
+
function getDefaultUserConfig() {
|
|
120
453
|
return {
|
|
121
454
|
journalDir: path.join(homedir(), "journal"),
|
|
122
|
-
editor: "code"
|
|
123
|
-
debug: false
|
|
455
|
+
editor: "code"
|
|
124
456
|
};
|
|
125
457
|
}
|
|
126
458
|
async function loadTowlesToolConfig({
|
|
@@ -128,7 +460,7 @@ async function loadTowlesToolConfig({
|
|
|
128
460
|
overrides
|
|
129
461
|
}) {
|
|
130
462
|
await setupDotenv({ cwd });
|
|
131
|
-
const defaults =
|
|
463
|
+
const defaults = getDefaultUserConfig();
|
|
132
464
|
const defaultConfigFolder = path.join(homedir(), ".config", constants.toolName);
|
|
133
465
|
const updateResult = await updateConfig({
|
|
134
466
|
cwd: defaultConfigFolder,
|
|
@@ -151,11 +483,10 @@ async function loadTowlesToolConfig({
|
|
|
151
483
|
// cwd: null means it will use the current working directory
|
|
152
484
|
export default ${JSON.stringify(defaults, null, 2)};
|
|
153
485
|
`;
|
|
486
|
+
},
|
|
487
|
+
async onUpdate(config) {
|
|
488
|
+
return config;
|
|
154
489
|
}
|
|
155
|
-
// async onUpdate(config) {
|
|
156
|
-
// consola.info(`Configuration updated in ${colors.cyan(path.relative('.', configFile))}`)
|
|
157
|
-
// return config
|
|
158
|
-
// },
|
|
159
490
|
}).catch((error) => {
|
|
160
491
|
consola.error(`Failed to update config: ${error.message}`);
|
|
161
492
|
return null;
|
|
@@ -164,7 +495,7 @@ export default ${JSON.stringify(defaults, null, 2)};
|
|
|
164
495
|
consola.error(`Failed to load or update config. Please check your configuration.`);
|
|
165
496
|
process.exit(1);
|
|
166
497
|
}
|
|
167
|
-
const { config, configFile } = await loadConfig({
|
|
498
|
+
const { config: userConfig, configFile } = await loadConfig({
|
|
168
499
|
cwd: cwd || defaultConfigFolder,
|
|
169
500
|
configFile: updateResult.configFile,
|
|
170
501
|
name: constants.toolName,
|
|
@@ -176,28 +507,28 @@ export default ${JSON.stringify(defaults, null, 2)};
|
|
|
176
507
|
}
|
|
177
508
|
});
|
|
178
509
|
printDebug(`Using config from: ${colors.cyan(configFile)}`);
|
|
179
|
-
return await resolveTowlesToolConfig(config, configFile, cwd);
|
|
180
|
-
}
|
|
181
|
-
async function resolveTowlesToolConfig(config, configFile, cwd) {
|
|
182
510
|
return {
|
|
183
511
|
configFile,
|
|
184
|
-
|
|
185
|
-
|
|
512
|
+
cwd,
|
|
513
|
+
userConfig
|
|
186
514
|
};
|
|
187
515
|
}
|
|
188
516
|
|
|
189
517
|
async function main() {
|
|
190
518
|
const consola$1 = consola.withTag(constants.toolName);
|
|
191
|
-
const
|
|
519
|
+
const config = await loadTowlesToolConfig({ cwd: process.cwd() });
|
|
192
520
|
const program = new Command();
|
|
193
521
|
program.name(constants.toolName).description("One off quality of life scripts that I use on a daily basis").version(version);
|
|
194
522
|
program.command("today").description("Create and open a weekly journal file based on Monday of current week").action(async () => {
|
|
195
|
-
await todayCommand(
|
|
523
|
+
await todayCommand(config.userConfig);
|
|
524
|
+
});
|
|
525
|
+
program.command("git-commit").alias("gc").description("Git commit command").action(async () => {
|
|
526
|
+
await gitCommitCommand(config);
|
|
196
527
|
});
|
|
197
528
|
program.command("config").description("set or show configuration file.").action(async () => {
|
|
198
529
|
consola$1.log(colors.green("Showing configuration..."));
|
|
199
|
-
consola$1.log("
|
|
200
|
-
printJson(
|
|
530
|
+
consola$1.log("Settings File:", config.configFile);
|
|
531
|
+
printJson(config);
|
|
201
532
|
});
|
|
202
533
|
program.parse();
|
|
203
534
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towles/tool",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.11-beta.2",
|
|
5
5
|
"description": "One off quality of life scripts that I use on a daily basis.",
|
|
6
6
|
"author": "Chris Towles <Chris.Towles.Dev@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,28 +33,37 @@
|
|
|
33
33
|
"dist"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@anthropic-ai/claude-code": "^1.0.51",
|
|
37
|
+
"@anthropic-ai/sdk": "^0.56.0",
|
|
38
|
+
"@clack/prompts": "^0.11.0",
|
|
36
39
|
"c12": "^3.0.4",
|
|
40
|
+
"changelogen": "^0.6.2",
|
|
37
41
|
"commander": "^14.0.0",
|
|
38
|
-
"consola": "^3.4.2"
|
|
42
|
+
"consola": "^3.4.2",
|
|
43
|
+
"fzf": "^0.5.2",
|
|
44
|
+
"magicast": "^0.3.5",
|
|
45
|
+
"neverthrow": "^8.2.0",
|
|
46
|
+
"prompts": "^2.4.2",
|
|
47
|
+
"zod": "^4.0.5"
|
|
39
48
|
},
|
|
40
49
|
"devDependencies": {
|
|
41
|
-
"@antfu/eslint-config": "^4.
|
|
42
|
-
"@antfu/ni": "^
|
|
43
|
-
"@antfu/utils": "^9.
|
|
44
|
-
"@types/node": "^22.
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"simple-git-hooks": "^2.
|
|
50
|
+
"@antfu/eslint-config": "^4.16.2",
|
|
51
|
+
"@antfu/ni": "^25.0.0",
|
|
52
|
+
"@antfu/utils": "^9.2.0",
|
|
53
|
+
"@types/node": "^22.16.3",
|
|
54
|
+
"@types/prompts": "^2.4.9",
|
|
55
|
+
"bumpp": "^10.2.0",
|
|
56
|
+
"eslint": "^9.31.0",
|
|
57
|
+
"lint-staged": "^15.5.2",
|
|
58
|
+
"simple-git-hooks": "^2.13.0",
|
|
50
59
|
"tinyexec": "^0.3.2",
|
|
51
|
-
"tsx": "^4.
|
|
52
|
-
"typescript": "^5.8.
|
|
60
|
+
"tsx": "^4.20.3",
|
|
61
|
+
"typescript": "^5.8.3",
|
|
53
62
|
"unbuild": "^3.5.0",
|
|
54
|
-
"vite": "^6.
|
|
55
|
-
"vitest": "^3.
|
|
63
|
+
"vite": "^6.3.5",
|
|
64
|
+
"vitest": "^3.2.4",
|
|
56
65
|
"vitest-package-exports": "^0.1.1",
|
|
57
|
-
"yaml": "^2.
|
|
66
|
+
"yaml": "^2.8.0"
|
|
58
67
|
},
|
|
59
68
|
"simple-git-hooks": {
|
|
60
69
|
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged"
|
|
@@ -69,6 +78,8 @@
|
|
|
69
78
|
"build": "unbuild",
|
|
70
79
|
"dev": "unbuild --stub",
|
|
71
80
|
"lint": "eslint",
|
|
81
|
+
"lint:fix": "eslint --fix",
|
|
82
|
+
"lint:fix_all": "eslint --fix .",
|
|
72
83
|
"release:local": "bumpp && pnpm publish --no-git-checks -r --access public",
|
|
73
84
|
"release": "bumpp && echo \"github action will run and publish to npm\"",
|
|
74
85
|
"start": "tsx src/index.ts",
|