@towles/tool 0.0.13 → 0.0.14
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/dist/index.mjs +205 -169
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,192 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process$1 from 'node:process';
|
|
3
|
-
import yargs from 'yargs';
|
|
4
|
-
import { hideBin } from 'yargs/helpers';
|
|
5
3
|
import consola from 'consola';
|
|
6
|
-
import
|
|
7
|
-
import { execSync, exec } from 'node:child_process';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
8
5
|
import * as fs from 'node:fs';
|
|
9
6
|
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
10
7
|
import * as path from 'node:path';
|
|
11
8
|
import path__default from 'node:path';
|
|
12
|
-
import {
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import * as commentJson from 'comment-json';
|
|
13
11
|
import { colors } from 'consola/utils';
|
|
12
|
+
import yargs from 'yargs';
|
|
13
|
+
import { hideBin } from 'yargs/helpers';
|
|
14
|
+
import prompts from 'prompts';
|
|
15
|
+
import { execSync, exec } from 'node:child_process';
|
|
16
|
+
import { promisify } from 'node:util';
|
|
14
17
|
import { DateTime } from 'luxon';
|
|
15
|
-
import { z } from 'zod/v4';
|
|
16
|
-
import { homedir } from 'node:os';
|
|
17
|
-
import stripJsonComments from 'strip-json-comments';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
async function loadTowlesToolContext({
|
|
20
|
+
cwd,
|
|
21
|
+
settingsFile,
|
|
22
|
+
debug = false
|
|
23
|
+
}) {
|
|
24
|
+
return {
|
|
25
|
+
cwd,
|
|
26
|
+
settingsFile,
|
|
27
|
+
args: [],
|
|
28
|
+
// TODO: Load args from yargs
|
|
29
|
+
debug
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const AppInfo = {
|
|
34
|
+
toolName: "towles-tool"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const USER_SETTINGS_DIR = path.join(homedir(), ".config", AppInfo.toolName);
|
|
38
|
+
const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, `${AppInfo.toolName}.settings.json`);
|
|
39
|
+
const JournalSettingsSchema = z.object({
|
|
40
|
+
// https://moment.github.io/luxon/#/formatting?id=table-of-tokens
|
|
41
|
+
dailyPathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/daily-notes/{yyyy}-{MM}-{dd}-daily-notes.md")),
|
|
42
|
+
meetingPathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/meetings/{yyyy}-{MM}-{dd}-{title}.md")),
|
|
43
|
+
notePathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/notes/{yyyy}-{MM}-{dd}-{title}.md"))
|
|
44
|
+
});
|
|
45
|
+
const UserSettingsSchema = z.object({
|
|
46
|
+
preferredEditor: z.string().default("code"),
|
|
47
|
+
journalSettings: JournalSettingsSchema
|
|
48
|
+
});
|
|
49
|
+
class LoadedSettings {
|
|
50
|
+
constructor(settingsFile) {
|
|
51
|
+
this.settingsFile = settingsFile;
|
|
52
|
+
}
|
|
53
|
+
settingsFile;
|
|
54
|
+
}
|
|
55
|
+
function createSettingsFile() {
|
|
56
|
+
let userSettings = UserSettingsSchema.parse({});
|
|
57
|
+
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
58
|
+
const userContent = fs.readFileSync(USER_SETTINGS_PATH, "utf-8");
|
|
59
|
+
const parsedUserSettings = commentJson.parse(userContent);
|
|
60
|
+
userSettings = UserSettingsSchema.parse(parsedUserSettings);
|
|
61
|
+
}
|
|
62
|
+
return userSettings;
|
|
63
|
+
}
|
|
64
|
+
function saveSettings(settingsFile) {
|
|
65
|
+
try {
|
|
66
|
+
const dirPath = path.dirname(settingsFile.path);
|
|
67
|
+
if (!fs.existsSync(dirPath)) {
|
|
68
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
settingsFile.path,
|
|
72
|
+
commentJson.stringify(settingsFile.settings, null, 2),
|
|
73
|
+
"utf-8"
|
|
74
|
+
);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
consola.error("Error saving user settings file:", error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function loadSettings() {
|
|
80
|
+
let userSettings = null;
|
|
81
|
+
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
82
|
+
const userContent = fs.readFileSync(USER_SETTINGS_PATH, "utf-8");
|
|
83
|
+
const parsedUserSettings = commentJson.parse(userContent);
|
|
84
|
+
userSettings = UserSettingsSchema.parse(parsedUserSettings);
|
|
85
|
+
if (JSON.stringify(parsedUserSettings) !== JSON.stringify(userSettings)) {
|
|
86
|
+
consola.warn(`Settings file ${USER_SETTINGS_PATH} has been updated with default values.`);
|
|
87
|
+
const tempSettingsFile = {
|
|
88
|
+
path: USER_SETTINGS_PATH,
|
|
89
|
+
settings: userSettings
|
|
90
|
+
};
|
|
91
|
+
saveSettings(tempSettingsFile);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
const confirmed = await consola.prompt(`Settings file not found. Create ${colors.cyan(USER_SETTINGS_PATH)}?`, {
|
|
95
|
+
type: "confirm"
|
|
96
|
+
});
|
|
97
|
+
if (!confirmed) {
|
|
98
|
+
throw new Error(`Settings file not found and user chose not to create it.`);
|
|
99
|
+
}
|
|
100
|
+
userSettings = createSettingsFile();
|
|
101
|
+
}
|
|
102
|
+
return new LoadedSettings(
|
|
103
|
+
{
|
|
104
|
+
path: USER_SETTINGS_PATH,
|
|
105
|
+
settings: userSettings
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const version = "0.0.14";
|
|
111
|
+
|
|
112
|
+
async function parseArguments(argv) {
|
|
113
|
+
let parsedResult = null;
|
|
114
|
+
const parser = yargs(hideBin(argv)).scriptName(AppInfo.toolName).usage("Usage: $0 <command> [options]").version(version).demandCommand(1, "You need at least one command").recommendCommands().strict().help().wrap(yargs().terminalWidth());
|
|
115
|
+
parser.command(
|
|
116
|
+
["journal", "j"],
|
|
117
|
+
"quickly create md files from templates files like daily-notes, meeting, notes, etc.",
|
|
118
|
+
(yargs2) => {
|
|
119
|
+
return yargs2.command(
|
|
120
|
+
["daily-notes", "today"],
|
|
121
|
+
"Weekly files with daily sections for ongoing work and notes",
|
|
122
|
+
{},
|
|
123
|
+
(argv2) => {
|
|
124
|
+
parsedResult = { command: "journal", args: { subcommand: "daily-notes", title: "" } };
|
|
125
|
+
}
|
|
126
|
+
).command(
|
|
127
|
+
["meeting [title]", "m"],
|
|
128
|
+
"Structured meeting notes with agenda and action items",
|
|
129
|
+
{
|
|
130
|
+
title: {
|
|
131
|
+
type: "string",
|
|
132
|
+
describe: "Meeting title"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
(argv2) => {
|
|
136
|
+
parsedResult = { command: "journal", args: { subcommand: "meeting", title: argv2.title || "" } };
|
|
137
|
+
}
|
|
138
|
+
).command(
|
|
139
|
+
["note [title]", "n"],
|
|
140
|
+
"General-purpose notes with structured sections",
|
|
141
|
+
{
|
|
142
|
+
title: {
|
|
143
|
+
type: "string",
|
|
144
|
+
describe: "Note title"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
(argv2) => {
|
|
148
|
+
parsedResult = { command: "journal", args: { subcommand: "note", title: argv2.title || "" } };
|
|
149
|
+
}
|
|
150
|
+
).demandCommand(1, "You need to specify a journal subcommand").help();
|
|
151
|
+
},
|
|
152
|
+
() => {
|
|
153
|
+
parser.showHelp();
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
parser.command(
|
|
157
|
+
["git-commit [message...]", "gc"],
|
|
158
|
+
"Git commit command with optional message",
|
|
159
|
+
{
|
|
160
|
+
message: {
|
|
161
|
+
type: "string",
|
|
162
|
+
array: true,
|
|
163
|
+
describe: "Commit message words"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
(argv2) => {
|
|
167
|
+
parsedResult = { command: "git-commit", args: { message: argv2.message } };
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
parser.command(
|
|
171
|
+
["config", "cfg"],
|
|
172
|
+
"set or show configuration file.",
|
|
173
|
+
{},
|
|
174
|
+
() => {
|
|
175
|
+
parsedResult = { command: "config", args: {} };
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
await parser.parse();
|
|
179
|
+
if (!parsedResult) {
|
|
180
|
+
throw new Error("No command was parsed");
|
|
181
|
+
}
|
|
182
|
+
return parsedResult;
|
|
183
|
+
}
|
|
20
184
|
|
|
21
185
|
function execCommand(cmd, cwd) {
|
|
22
186
|
return execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
23
187
|
}
|
|
24
188
|
|
|
25
|
-
async function gitCommitCommand(context,
|
|
189
|
+
async function gitCommitCommand(context, commitMessage) {
|
|
26
190
|
try {
|
|
27
191
|
const status = await getGitStatus(context.cwd);
|
|
28
192
|
if (!status || status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0) {
|
|
@@ -45,7 +209,6 @@ async function gitCommitCommand(context, messageArgs) {
|
|
|
45
209
|
await execCommand("git add .", context.cwd);
|
|
46
210
|
consola.success("Files staged");
|
|
47
211
|
}
|
|
48
|
-
let commitMessage = messageArgs?.join(" ") || "";
|
|
49
212
|
if (!commitMessage) {
|
|
50
213
|
const response = await prompts({
|
|
51
214
|
type: "text",
|
|
@@ -309,24 +472,6 @@ async function createJournalFile({ context, type, title }) {
|
|
|
309
472
|
}
|
|
310
473
|
}
|
|
311
474
|
|
|
312
|
-
async function loadTowlesToolContext({
|
|
313
|
-
cwd,
|
|
314
|
-
settingsFile,
|
|
315
|
-
debug = false
|
|
316
|
-
}) {
|
|
317
|
-
return {
|
|
318
|
-
cwd,
|
|
319
|
-
settingsFile,
|
|
320
|
-
args: [],
|
|
321
|
-
// TODO: Load args from yargs
|
|
322
|
-
debug
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const AppInfo = {
|
|
327
|
-
toolName: "towles-tool"
|
|
328
|
-
};
|
|
329
|
-
|
|
330
475
|
async function configCommand(context) {
|
|
331
476
|
consola.info("Configuration");
|
|
332
477
|
consola.log("");
|
|
@@ -342,84 +487,38 @@ async function configCommand(context) {
|
|
|
342
487
|
consola.log(` ${context.cwd}`);
|
|
343
488
|
}
|
|
344
489
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// https://github.com/colinhacks/zod/discussions/1953
|
|
367
|
-
journalSettings: JournalSettingsSchema.parse({})
|
|
368
|
-
});
|
|
369
|
-
const settingsFile = {
|
|
370
|
-
path: USER_SETTINGS_PATH,
|
|
371
|
-
settings: defaultSettings
|
|
372
|
-
};
|
|
373
|
-
saveSettings(settingsFile);
|
|
374
|
-
consola.success(`Created settings file: ${USER_SETTINGS_PATH}`);
|
|
375
|
-
return defaultSettings;
|
|
376
|
-
}
|
|
377
|
-
function saveSettings(settingsFile) {
|
|
378
|
-
try {
|
|
379
|
-
const dirPath = path.dirname(settingsFile.path);
|
|
380
|
-
if (!fs.existsSync(dirPath)) {
|
|
381
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
382
|
-
}
|
|
383
|
-
fs.writeFileSync(
|
|
384
|
-
settingsFile.path,
|
|
385
|
-
JSON.stringify(settingsFile.settings, null, 2),
|
|
386
|
-
"utf-8"
|
|
387
|
-
);
|
|
388
|
-
} catch (error) {
|
|
389
|
-
consola.error("Error saving user settings file:", error);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
async function loadSettings() {
|
|
393
|
-
let userSettings = null;
|
|
394
|
-
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
395
|
-
const userContent = fs.readFileSync(USER_SETTINGS_PATH, "utf-8");
|
|
396
|
-
const parsedUserSettings = JSON.parse(stripJsonComments(userContent));
|
|
397
|
-
userSettings = UserSettingsSchema.parse(parsedUserSettings);
|
|
398
|
-
if (JSON.stringify(parsedUserSettings) !== JSON.stringify(userSettings)) {
|
|
399
|
-
consola.warn(`Settings file ${USER_SETTINGS_PATH} has been updated with default values.`);
|
|
400
|
-
const tempSettingsFile = {
|
|
401
|
-
path: USER_SETTINGS_PATH,
|
|
402
|
-
settings: userSettings
|
|
403
|
-
};
|
|
404
|
-
saveSettings(tempSettingsFile);
|
|
405
|
-
}
|
|
406
|
-
} else {
|
|
407
|
-
const confirmed = await consola.prompt(`Settings file not found. Create ${colors.cyan(USER_SETTINGS_PATH)}?`, {
|
|
408
|
-
type: "confirm"
|
|
409
|
-
});
|
|
410
|
-
if (!confirmed) {
|
|
411
|
-
throw new Error(`Settings file not found and user chose not to create it.`);
|
|
490
|
+
async function executeCommand(parsedArgs, context) {
|
|
491
|
+
switch (parsedArgs.command) {
|
|
492
|
+
case "journal": {
|
|
493
|
+
const { subcommand, title } = parsedArgs.args;
|
|
494
|
+
let journalType;
|
|
495
|
+
switch (subcommand) {
|
|
496
|
+
case "daily-notes":
|
|
497
|
+
case "today":
|
|
498
|
+
journalType = JOURNAL_TYPES.DAILY_NOTES;
|
|
499
|
+
break;
|
|
500
|
+
case "meeting":
|
|
501
|
+
journalType = JOURNAL_TYPES.MEETING;
|
|
502
|
+
break;
|
|
503
|
+
case "note":
|
|
504
|
+
journalType = JOURNAL_TYPES.NOTE;
|
|
505
|
+
break;
|
|
506
|
+
default:
|
|
507
|
+
throw new Error(`Unknown journal subcommand: ${subcommand}`);
|
|
508
|
+
}
|
|
509
|
+
await createJournalFile({ context, type: journalType, title: title || "" });
|
|
510
|
+
break;
|
|
412
511
|
}
|
|
413
|
-
|
|
512
|
+
case "git-commit":
|
|
513
|
+
await gitCommitCommand(context, parsedArgs.args.message);
|
|
514
|
+
break;
|
|
515
|
+
case "config":
|
|
516
|
+
await configCommand(context);
|
|
517
|
+
break;
|
|
518
|
+
default:
|
|
519
|
+
throw new Error(`Unknown command: ${parsedArgs.command}`);
|
|
414
520
|
}
|
|
415
|
-
return new LoadedSettings(
|
|
416
|
-
{
|
|
417
|
-
path: USER_SETTINGS_PATH,
|
|
418
|
-
settings: userSettings
|
|
419
|
-
}
|
|
420
|
-
);
|
|
421
521
|
}
|
|
422
|
-
|
|
423
522
|
async function main() {
|
|
424
523
|
const settings = await loadSettings();
|
|
425
524
|
const context = await loadTowlesToolContext({
|
|
@@ -429,71 +528,8 @@ async function main() {
|
|
|
429
528
|
// later can be set to false in production or when not debugging
|
|
430
529
|
});
|
|
431
530
|
consola.info(`Using configuration from ${settings.settingsFile.path}`);
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
["journal", "j"],
|
|
435
|
-
"quickly create md files from templates files like daily-notes, meeting, notes, etc.",
|
|
436
|
-
(yargs2) => {
|
|
437
|
-
return yargs2.command(
|
|
438
|
-
["daily-notes", "today"],
|
|
439
|
-
"Weekly files with daily sections for ongoing work and notes",
|
|
440
|
-
{},
|
|
441
|
-
async () => {
|
|
442
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.DAILY_NOTES, title: "" });
|
|
443
|
-
}
|
|
444
|
-
).command(
|
|
445
|
-
["meeting [title]", "m"],
|
|
446
|
-
"Structured meeting notes with agenda and action items",
|
|
447
|
-
(yargs3) => {
|
|
448
|
-
return yargs3.positional("title", {
|
|
449
|
-
type: "string",
|
|
450
|
-
describe: "Meeting title"
|
|
451
|
-
});
|
|
452
|
-
},
|
|
453
|
-
async (argv) => {
|
|
454
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.MEETING, title: argv.title || "" });
|
|
455
|
-
}
|
|
456
|
-
).command(
|
|
457
|
-
["note [title]", "n"],
|
|
458
|
-
"General-purpose notes with structured sections",
|
|
459
|
-
(yargs3) => {
|
|
460
|
-
return yargs3.positional("title", {
|
|
461
|
-
type: "string",
|
|
462
|
-
describe: "Note title"
|
|
463
|
-
});
|
|
464
|
-
},
|
|
465
|
-
async (argv) => {
|
|
466
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.NOTE, title: argv.title || "" });
|
|
467
|
-
}
|
|
468
|
-
).demandCommand(1, "You need to specify a journal subcommand").help();
|
|
469
|
-
},
|
|
470
|
-
() => {
|
|
471
|
-
parser.showHelp();
|
|
472
|
-
}
|
|
473
|
-
);
|
|
474
|
-
parser.command(
|
|
475
|
-
["git-commit [message...]", "gc"],
|
|
476
|
-
"Git commit command with optional message",
|
|
477
|
-
(yargs2) => {
|
|
478
|
-
return yargs2.positional("message", {
|
|
479
|
-
type: "string",
|
|
480
|
-
array: true,
|
|
481
|
-
describe: "Commit message words"
|
|
482
|
-
});
|
|
483
|
-
},
|
|
484
|
-
async (argv) => {
|
|
485
|
-
await gitCommitCommand(context, argv.message || []);
|
|
486
|
-
}
|
|
487
|
-
);
|
|
488
|
-
parser.command(
|
|
489
|
-
["config", "cfg"],
|
|
490
|
-
"set or show configuration file.",
|
|
491
|
-
{},
|
|
492
|
-
async () => {
|
|
493
|
-
await configCommand(context);
|
|
494
|
-
}
|
|
495
|
-
);
|
|
496
|
-
await parser.parse();
|
|
531
|
+
const parsedArgs = await parseArguments(process$1.argv);
|
|
532
|
+
await executeCommand(parsedArgs, context);
|
|
497
533
|
}
|
|
498
534
|
main().catch((error) => {
|
|
499
535
|
consola.error("An unexpected critical error occurred:");
|
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.14",
|
|
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",
|
|
@@ -35,12 +35,13 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@anthropic-ai/claude-code": "^1.0.51",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
38
|
-
"
|
|
38
|
+
"@clack/prompts": "^0.11.0",
|
|
39
|
+
"comment-json": "^4.2.5",
|
|
39
40
|
"fzf": "^0.5.2",
|
|
40
41
|
"luxon": "^3.7.1",
|
|
41
42
|
"neverthrow": "^8.2.0",
|
|
42
43
|
"prompts": "^2.4.2",
|
|
43
|
-
"
|
|
44
|
+
"consola": "^3.4.2",
|
|
44
45
|
"yargs": "^17.7.2",
|
|
45
46
|
"zod": "^4.0.5"
|
|
46
47
|
},
|