@towles/tool 0.0.11-beta.2 → 0.0.11

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 +28 -1
  2. package/dist/index.mjs +200 -347
  3. package/package.json +6 -7
package/README.md CHANGED
@@ -8,8 +8,35 @@
8
8
 
9
9
  One off quality of life scripts that I use on a daily basis.
10
10
 
11
+ ## Journal Type System
12
+
13
+ The journal system supports three types of files with different templates and organization:
14
+
15
+ ### Daily Notes (`journal daily-notes`)
16
+ - **Purpose**: Weekly files with daily sections for ongoing work and notes
17
+ - **File structure**: `YYYY/daily-notes/YYYY-MM-DD-week-log.md` (Monday's date)
18
+ - **Template**: Includes sections for Monday through Friday with date headers
19
+ - **Use case**: Regular daily journaling, work logs, scratch pad for notes
20
+
21
+ ### Meeting Files (`journal meeting [title]`)
22
+ - **Purpose**: Structured meeting notes with agenda and action items
23
+ - **File structure**: `YYYY/meetings/YYYY-MM-DD-HHMM-meeting-[title].md`
24
+ - **Template**: Includes Date, Time, Attendees, Agenda, Notes, Action Items, and Follow-up sections
25
+ - **Use case**: Meeting preparation, note-taking, and action item tracking
26
+
27
+ ### Note Files (`journal note [title]`)
28
+ - **Purpose**: General-purpose notes with structured sections
29
+ - **File structure**: `YYYY/notes/YYYY-MM-DD-HHMM-note-[title].md`
30
+ - **Template**: Includes Summary, Details, and References sections
31
+ - **Use case**: Research notes, documentation, general information capture
32
+
33
+ ### Commands
34
+ - `journal` or `journal daily-notes` - Create/open current week's daily notes
35
+ - `journal meeting [title]` - Create a new meeting file with optional title
36
+ - `journal note [title]` - Create a new note file with optional title
37
+
11
38
  ## Tools to add
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.
39
+ - [x] Journal system - creates and opens markdown files with templates for daily-notes, meetings, and notes
13
40
  - [ ] use claude code to generate git commits with multiple options for the commit message.
14
41
 
15
42
  ## Install from repository
package/dist/index.mjs CHANGED
@@ -3,360 +3,111 @@ 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 { spinner } from '@clack/prompts';
7
- import { Fzf } from 'fzf';
8
6
  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
7
  import { execSync, exec } from 'node:child_process';
13
- import util, { promisify } from 'node:util';
14
8
  import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
15
9
  import * as path from 'node:path';
16
10
  import path__default from 'node:path';
11
+ import util, { promisify } from 'node:util';
17
12
  import { homedir } from 'node:os';
18
13
  import { setupDotenv, loadConfig } from 'c12';
19
14
  import { updateConfig } from 'c12/update';
20
15
 
21
- const version = "0.0.11-beta.2";
16
+ const version = "0.0.11";
22
17
 
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;
18
+ function execCommand(cmd, cwd) {
19
+ return execSync(cmd, { encoding: "utf8", cwd }).trim();
36
20
  }
37
21
 
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;
22
+ async function gitCommitCommand(config, messageArgs) {
23
+ let statusOutput;
24
+ try {
25
+ statusOutput = execCommand("git status --porcelain", config.cwd);
26
+ } catch (error) {
27
+ consola.error("Failed to get git status");
28
+ process.exit(1);
44
29
  }
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"));
30
+ const lines = statusOutput.trim().split("\n").filter((line) => line.length > 0);
31
+ const stagedFiles = lines.filter((line) => line[0] !== " " && line[0] !== "?");
32
+ const unstagedFiles = lines.filter((line) => line[1] !== " " && line[1] !== "?");
33
+ const untrackedFiles = lines.filter((line) => line.startsWith("??"));
34
+ if (lines.length === 0) {
35
+ consola.info("Working tree clean - nothing to commit");
36
+ return;
52
37
  }
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;
38
+ consola.info("Git status:");
39
+ if (stagedFiles.length > 0) {
40
+ consola.info(colors.green("Staged files:"));
41
+ stagedFiles.forEach((file) => consola.info(` ${colors.green(file)}`));
78
42
  }
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
- }
43
+ if (unstagedFiles.length > 0) {
44
+ consola.info(colors.yellow("Modified files (not staged):"));
45
+ unstagedFiles.forEach((file) => consola.info(` ${colors.yellow(file)}`));
115
46
  }
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);
47
+ if (untrackedFiles.length > 0) {
48
+ consola.info(colors.red("Untracked files:"));
49
+ untrackedFiles.forEach((file) => consola.info(` ${colors.red(file)}`));
136
50
  }
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
- );
51
+ if (stagedFiles.length === 0) {
52
+ if (unstagedFiles.length > 0 || untrackedFiles.length > 0) {
53
+ const { shouldStage } = await prompts({
54
+ type: "confirm",
55
+ name: "shouldStage",
56
+ message: "No files are staged. Would you like to add files first?",
57
+ initial: true
58
+ });
59
+ if (shouldStage) {
60
+ const { addAll } = await prompts({
61
+ type: "confirm",
62
+ name: "addAll",
63
+ message: "Add all modified and untracked files?",
64
+ initial: true
65
+ });
66
+ if (addAll) {
67
+ try {
68
+ execCommand("git add .", config.cwd);
69
+ consola.success("All files staged successfully");
70
+ } catch (error) {
71
+ consola.error("Failed to stage files");
72
+ process.exit(1);
73
+ }
74
+ } else {
75
+ consola.info(`Use ${colors.cyan("git add <file>...")} to stage specific files, then run the commit command again`);
76
+ return;
153
77
  }
78
+ } else {
79
+ consola.error(`No staged changes found to commit. Use ${colors.cyan("git add <file>...")} to stage changes before committing.`);
80
+ process.exit(1);
154
81
  }
155
- return ok(parsed.value);
82
+ } else {
83
+ consola.error("No changes to commit");
84
+ return;
156
85
  }
157
- return err(
158
- new ClaudeError(
159
- "User content must be a string or array of content blocks"
160
- )
161
- );
162
86
  }
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);
87
+ let commitMessage;
88
+ if (messageArgs && messageArgs.length > 0) {
89
+ commitMessage = messageArgs.join(" ");
90
+ } else {
91
+ const { message } = await prompts({
92
+ type: "text",
93
+ name: "message",
94
+ message: "Enter commit message:",
95
+ validate: (value) => value.trim().length > 0 || "Commit message cannot be empty"
96
+ });
97
+ if (!message) {
98
+ consola.info(colors.dim("Commit cancelled"));
99
+ return;
100
+ }
101
+ commitMessage = message.trim();
314
102
  }
315
- const choosenSuggestion = await promptUserForCommitMessage(suggestions.value);
316
- const commandWithArgs = `git commit -m "${choosenSuggestion.subject}"`;
317
- consola.info(`Running command: ${colors.bgCyan(commandWithArgs)}`);
103
+ const commandWithArgs = `git commit -m "${commitMessage.replace(/"/g, '\\"')}"`;
104
+ consola.info(`Running: ${colors.cyan(commandWithArgs)}`);
318
105
  try {
319
106
  execCommand(commandWithArgs, config.cwd);
107
+ consola.success("Commit created successfully!");
320
108
  } 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);
109
+ consola.error("Failed to commit changes:");
110
+ consola.error(error);
360
111
  process.exit(1);
361
112
  }
362
113
  }
@@ -391,6 +142,11 @@ function formatDate(date) {
391
142
  }
392
143
 
393
144
  const execAsync = promisify(exec);
145
+ const JOURNAL_TYPES = {
146
+ DAILY_NOTES: "daily-notes",
147
+ MEETING: "meeting",
148
+ NOTE: "note"
149
+ };
394
150
  function ensureDirectoryExists(folderPath) {
395
151
  if (!existsSync(folderPath)) {
396
152
  consola.info(`Creating journal directory: ${colors.cyan(folderPath)}`);
@@ -413,6 +169,44 @@ function createJournalContent({ mondayDate }) {
413
169
  content.push(``);
414
170
  return content.join("\n");
415
171
  }
172
+ function createMeetingContent({ title, date }) {
173
+ const dateStr = formatDate(date);
174
+ const timeStr = date.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" });
175
+ const content = [`# Meeting: ${title || "Meeting"}`];
176
+ content.push(``);
177
+ content.push(`**Date:** ${dateStr}`);
178
+ content.push(`**Time:** ${timeStr}`);
179
+ content.push(`**Attendees:** `);
180
+ content.push(``);
181
+ content.push(`## Agenda`);
182
+ content.push(``);
183
+ content.push(`- `);
184
+ content.push(``);
185
+ content.push(`## Notes`);
186
+ content.push(``);
187
+ content.push(`## Action Items`);
188
+ content.push(``);
189
+ content.push(`- [ ] `);
190
+ content.push(``);
191
+ content.push(`## Follow-up`);
192
+ content.push(``);
193
+ return content.join("\n");
194
+ }
195
+ function createNoteContent({ title, date }) {
196
+ const dateStr = formatDate(date);
197
+ const timeStr = date.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" });
198
+ const content = [`# ${title || "Note"}`];
199
+ content.push(``);
200
+ content.push(`**Created:** ${dateStr} ${timeStr}`);
201
+ content.push(``);
202
+ content.push(`## Summary`);
203
+ content.push(``);
204
+ content.push(`## Details`);
205
+ content.push(``);
206
+ content.push(`## References`);
207
+ content.push(``);
208
+ return content.join("\n");
209
+ }
416
210
  async function openInEditor({ editor, filePath }) {
417
211
  try {
418
212
  await execAsync(`"${editor}" "${filePath}"`);
@@ -420,35 +214,87 @@ async function openInEditor({ editor, filePath }) {
420
214
  consola.warn(`Could not open in editor : '${editor}'. Modify your editor in the config: examples include 'code', 'code-insiders', etc...`, ex);
421
215
  }
422
216
  }
423
- async function todayCommand(userConfig) {
217
+ function generateJournalFileInfoByType({ date = /* @__PURE__ */ new Date(), type = JOURNAL_TYPES.DAILY_NOTES, title }) {
218
+ const currentDate = new Date(date);
219
+ const year = currentDate.getFullYear().toString();
220
+ switch (type) {
221
+ case JOURNAL_TYPES.DAILY_NOTES: {
222
+ const monday = getMondayOfWeek(currentDate);
223
+ const fileName = `${formatDate(monday)}-week-log.md`;
224
+ const pathPrefix = [year, "daily-notes"];
225
+ return { pathPrefix, fileName, mondayDate: monday };
226
+ }
227
+ case JOURNAL_TYPES.MEETING: {
228
+ const dateStr = formatDate(currentDate);
229
+ const timeStr = currentDate.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" }).replace(":", "");
230
+ const titleSlug = title ? `-${title.toLowerCase().replace(/\s+/g, "-")}` : "";
231
+ const fileName = `${dateStr}-${timeStr}-meeting${titleSlug}.md`;
232
+ const pathPrefix = [year, "meetings"];
233
+ return { pathPrefix, fileName, mondayDate: currentDate };
234
+ }
235
+ case JOURNAL_TYPES.NOTE: {
236
+ const dateStr = formatDate(currentDate);
237
+ const timeStr = currentDate.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" }).replace(":", "");
238
+ const titleSlug = title ? `-${title.toLowerCase().replace(/\s+/g, "-")}` : "";
239
+ const fileName = `${dateStr}-${timeStr}-note${titleSlug}.md`;
240
+ const pathPrefix = [year, "notes"];
241
+ return { pathPrefix, fileName, mondayDate: currentDate };
242
+ }
243
+ default:
244
+ throw new Error(`Unknown journal type: ${type}`);
245
+ }
246
+ }
247
+ async function createJournalFile({ userConfig, type, title }) {
424
248
  try {
425
- const fileInfo = generateJournalFileInfo();
249
+ const currentDate = /* @__PURE__ */ new Date();
250
+ const fileInfo = generateJournalFileInfoByType({ date: currentDate, type, title });
426
251
  const filePath = path__default.join(userConfig.journalDir, ...fileInfo.pathPrefix, fileInfo.fileName);
427
252
  ensureDirectoryExists(path__default.join(userConfig.journalDir, ...fileInfo.pathPrefix));
428
- if (!existsSync(filePath)) {
429
- const content = createJournalContent({ mondayDate: fileInfo.mondayDate });
430
- writeFileSync(filePath, content, "utf8");
431
- consola.info(`Created new journal file: ${colors.cyan(filePath)}`);
253
+ if (existsSync(filePath)) {
254
+ consola.info(`Opening existing ${type} file: ${colors.cyan(filePath)}`);
432
255
  } else {
433
- consola.info(`Opening existing journal file: ${colors.cyan(filePath)}`);
256
+ let content;
257
+ switch (type) {
258
+ case JOURNAL_TYPES.DAILY_NOTES:
259
+ content = createJournalContent({ mondayDate: fileInfo.mondayDate });
260
+ break;
261
+ case JOURNAL_TYPES.MEETING:
262
+ content = createMeetingContent({ title, date: currentDate });
263
+ break;
264
+ case JOURNAL_TYPES.NOTE:
265
+ content = createNoteContent({ title, date: currentDate });
266
+ break;
267
+ default:
268
+ throw new Error(`Unknown journal type: ${type}`);
269
+ }
270
+ writeFileSync(filePath, content, "utf8");
271
+ consola.info(`Created new ${type} file: ${colors.cyan(filePath)}`);
434
272
  }
435
273
  await openInEditor({ editor: userConfig.editor, filePath });
436
274
  } catch (error) {
437
- consola.warn("Error creating journal file:", error);
275
+ consola.warn(`Error creating ${type} file:`, error);
438
276
  process.exit(1);
439
277
  }
440
278
  }
441
- function generateJournalFileInfo(date = /* @__PURE__ */ new Date()) {
442
- const monday = getMondayOfWeek(new Date(date));
443
- const fileName = `${formatDate(monday)}-week.md`;
444
- const pathPrefix = [`${date.getFullYear()}`, "journal"];
445
- return { pathPrefix, fileName, mondayDate: monday };
446
- }
447
279
 
448
280
  const constants = {
449
281
  toolName: "towles-tool"
450
282
  };
451
283
 
284
+ function printJson(obj) {
285
+ consola.log(util.inspect(obj, {
286
+ depth: 2,
287
+ colors: true,
288
+ showHidden: false,
289
+ compact: false
290
+ }));
291
+ }
292
+ function printDebug(message, ...args) {
293
+ if (process.env.DEBUG) {
294
+ consola.debug(`DEBUG: ${message}`, ...args);
295
+ }
296
+ }
297
+
452
298
  function getDefaultUserConfig() {
453
299
  return {
454
300
  journalDir: path.join(homedir(), "journal"),
@@ -519,11 +365,18 @@ async function main() {
519
365
  const config = await loadTowlesToolConfig({ cwd: process.cwd() });
520
366
  const program = new Command();
521
367
  program.name(constants.toolName).description("One off quality of life scripts that I use on a daily basis").version(version);
522
- program.command("today").description("Create and open a weekly journal file based on Monday of current week").action(async () => {
523
- await todayCommand(config.userConfig);
368
+ const journalCmd = program.command("journal").description("quickly create md files from templates files like daily-notes, meeting, notes, etc.");
369
+ journalCmd.command("daily-notes").description("Weekly files with daily sections for ongoing work and notes").action(async () => {
370
+ await createJournalFile({ userConfig: config.userConfig, type: JOURNAL_TYPES.DAILY_NOTES });
371
+ });
372
+ journalCmd.command("meeting [title]").description("Structured meeting notes with agenda and action items").action(async (title) => {
373
+ await createJournalFile({ userConfig: config.userConfig, type: JOURNAL_TYPES.MEETING, title });
374
+ });
375
+ journalCmd.command("note [title]").description("General-purpose notes with structured sections").action(async (title) => {
376
+ await createJournalFile({ userConfig: config.userConfig, type: JOURNAL_TYPES.NOTE, title });
524
377
  });
525
- program.command("git-commit").alias("gc").description("Git commit command").action(async () => {
526
- await gitCommitCommand(config);
378
+ program.command("git-commit [message...]").alias("gc").description("Git commit command with optional message").action(async (message) => {
379
+ await gitCommitCommand(config, message);
527
380
  });
528
381
  program.command("config").description("set or show configuration file.").action(async () => {
529
382
  consola$1.log(colors.green("Showing configuration..."));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@towles/tool",
3
3
  "type": "module",
4
- "version": "0.0.11-beta.2",
4
+ "version": "0.0.11",
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",
@@ -47,14 +47,13 @@
47
47
  "zod": "^4.0.5"
48
48
  },
49
49
  "devDependencies": {
50
- "@antfu/eslint-config": "^4.16.2",
51
50
  "@antfu/ni": "^25.0.0",
52
51
  "@antfu/utils": "^9.2.0",
53
52
  "@types/node": "^22.16.3",
54
53
  "@types/prompts": "^2.4.9",
55
54
  "bumpp": "^10.2.0",
56
- "eslint": "^9.31.0",
57
55
  "lint-staged": "^15.5.2",
56
+ "oxlint": "^1.7.0",
58
57
  "simple-git-hooks": "^2.13.0",
59
58
  "tinyexec": "^0.3.2",
60
59
  "tsx": "^4.20.3",
@@ -69,7 +68,7 @@
69
68
  "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged"
70
69
  },
71
70
  "lint-staged": {
72
- "*": "eslint --fix"
71
+ "*": "oxlint --fix"
73
72
  },
74
73
  "directories": {
75
74
  "test": "test"
@@ -77,9 +76,9 @@
77
76
  "scripts": {
78
77
  "build": "unbuild",
79
78
  "dev": "unbuild --stub",
80
- "lint": "eslint",
81
- "lint:fix": "eslint --fix",
82
- "lint:fix_all": "eslint --fix .",
79
+ "lint": "oxlint",
80
+ "lint:fix": "oxlint --fix",
81
+ "lint:fix_all": "oxlint --fix .",
83
82
  "release:local": "bumpp && pnpm publish --no-git-checks -r --access public",
84
83
  "release": "bumpp && echo \"github action will run and publish to npm\"",
85
84
  "start": "tsx src/index.ts",