@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.
Files changed (3) hide show
  1. package/README.md +8 -4
  2. package/dist/index.mjs +374 -43
  3. 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
- - [ ] 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
- - [ ] git commit with message prompt
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 { exec } from 'node:child_process';
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.7";
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(filePath, config) {
416
+ async function openInEditor({ editor, filePath }) {
70
417
  try {
71
- await execAsync(`"${config.editor}" "${filePath}"`);
418
+ await execAsync(`"${editor}" "${filePath}"`);
72
419
  } catch (ex) {
73
- consola.warn(`Could not open in editor : '${config.editor}'. Modify your editor in the config: examples include 'code', 'code-insiders', etc...`, ex);
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(config) {
423
+ async function todayCommand(userConfig) {
77
424
  try {
78
425
  const fileInfo = generateJournalFileInfo();
79
- const filePath = path__default.join(config.journalDir, ...fileInfo.pathPrefix, fileInfo.fileName);
80
- ensureDirectoryExists(path__default.join(config.journalDir, ...fileInfo.pathPrefix));
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(filePath, config);
435
+ await openInEditor({ editor: userConfig.editor, filePath });
89
436
  } catch (error) {
90
- consola.error("Error creating journal file:", error);
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 printJson(obj) {
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 = getDefaultConfig();
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
- config,
185
- cwd
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 configWrapper = await loadTowlesToolConfig({ cwd: process.cwd() });
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(configWrapper.config);
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("Config File:", configWrapper.configFile);
200
- printJson(configWrapper.config);
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.7",
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.10.1",
42
- "@antfu/ni": "^24.1.0",
43
- "@antfu/utils": "^9.1.0",
44
- "@types/node": "^22.13.10",
45
- "bumpp": "^10.1.0",
46
- "eslint": "^9.22.0",
47
- "lint-staged": "^15.5.0",
48
- "magicast": "^0.3.5",
49
- "simple-git-hooks": "^2.11.1",
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.19.3",
52
- "typescript": "^5.8.2",
60
+ "tsx": "^4.20.3",
61
+ "typescript": "^5.8.3",
53
62
  "unbuild": "^3.5.0",
54
- "vite": "^6.2.1",
55
- "vitest": "^3.0.8",
63
+ "vite": "^6.3.5",
64
+ "vitest": "^3.2.4",
56
65
  "vitest-package-exports": "^0.1.1",
57
- "yaml": "^2.7.0"
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",