@towles/tool 0.0.12 → 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/README.md +6 -1
- package/dist/index.mjs +280 -426
- package/package.json +5 -13
package/README.md
CHANGED
|
@@ -70,11 +70,16 @@ if that works, then you need to add the pnpm global bin directory to your PATH.
|
|
|
70
70
|
- [@anthropic-ai/claude-code](https://github.com/anthropic-ai/claude-code) - A library for interacting with the Claude code
|
|
71
71
|
- [zod](https://github.com/colinhacks/zod) - TypeScript-first schema validation
|
|
72
72
|
- [Consola](https://github.com/unjs/consola) console wrapper and colors
|
|
73
|
-
- [c12](https://github.com/unjs/c12) configuration loader and utilities
|
|
73
|
+
- ~~[c12](https://github.com/unjs/c12) configuration loader and utilities~~
|
|
74
|
+
- referted stayed to json config
|
|
74
75
|
- [rolldown-vite](https://voidzero.dev/posts/announcing-rolldown-vite) - A Vite plugin for rolling down your code
|
|
75
76
|
- ~~[zx](https://github.com/google/zx) google created library to write shell scripts in a more powerful and expressive way via the Anthropic API.~~
|
|
76
77
|
- [prompts](https://github.com/terkelg/prompts) - A library for creating beautiful command-line prompts, with fuzzy search and other features.
|
|
77
78
|
- [yargs](https://github.com/yargs/yargs) - A modern, feature-rich command-line argument parser with enhanced error handling, TypeScript support, and flexible command configuration.
|
|
79
|
+
- ~~[ink](https://github.com/vadimdemedes/ink) - React for interactive command-line apps~~
|
|
80
|
+
- wanted hotkey support and more complex UI but this was overkill for this project.
|
|
81
|
+
- [publint](https://publint.dev/)
|
|
82
|
+
- [e18e.dev](https://e18e.dev/guide/resources.html)
|
|
78
83
|
|
|
79
84
|
## Document verbose and debug options
|
|
80
85
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,313 +1,283 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process$1 from 'node:process';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { useState, createContext, Component, useEffect } from 'react';
|
|
6
|
-
import { Box, Text, useInput, render } from 'ink';
|
|
7
|
-
import { execSync, exec } from 'node:child_process';
|
|
3
|
+
import consola from 'consola';
|
|
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 {
|
|
13
|
-
import
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import * as commentJson from 'comment-json';
|
|
14
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';
|
|
15
17
|
import { DateTime } from 'luxon';
|
|
16
|
-
import { z } from 'zod/v4';
|
|
17
|
-
import { homedir } from 'node:os';
|
|
18
|
-
import stripJsonComments from 'strip-json-comments';
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
exitCode: 0,
|
|
32
|
-
onExit: (code = 0) => process.exit(code),
|
|
33
|
-
...initialCommandContext
|
|
34
|
-
});
|
|
35
|
-
const updateAppState = (updates) => {
|
|
36
|
-
setAppState((prev) => ({ ...prev, ...updates }));
|
|
37
|
-
};
|
|
38
|
-
const updateCommandContext = (updates) => {
|
|
39
|
-
setCommandContext((prev) => ({ ...prev, ...updates }));
|
|
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
|
|
40
30
|
};
|
|
41
|
-
return /* @__PURE__ */ React.createElement(AppContext.Provider, { value: {
|
|
42
|
-
appState,
|
|
43
|
-
updateAppState,
|
|
44
|
-
commandContext,
|
|
45
|
-
updateCommandContext
|
|
46
|
-
} }, children);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const ConfigContext = createContext(null);
|
|
50
|
-
function ConfigProvider({ children, context }) {
|
|
51
|
-
return /* @__PURE__ */ React.createElement(ConfigContext.Provider, { value: { context } }, children);
|
|
52
31
|
}
|
|
53
32
|
|
|
54
33
|
const AppInfo = {
|
|
55
34
|
toolName: "towles-tool"
|
|
56
35
|
};
|
|
57
|
-
const DEFAULT_THEME = {
|
|
58
|
-
primary: "cyan",
|
|
59
|
-
warning: "yellow",
|
|
60
|
-
error: "red",
|
|
61
|
-
dim: "dim"
|
|
62
|
-
};
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
71
52
|
}
|
|
72
|
-
|
|
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);
|
|
73
61
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 });
|
|
80
69
|
}
|
|
81
|
-
|
|
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);
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const lines = statusOutput.trim().split("\n").filter((line) => line.length > 0);
|
|
98
|
-
const staged = lines.filter((line) => line[0] !== " " && line[0] !== "?").map((line) => line.slice(3));
|
|
99
|
-
const unstaged = lines.filter((line) => line[1] !== " " && line[1] !== "?").map((line) => line.slice(3));
|
|
100
|
-
const untracked = lines.filter((line) => line.startsWith("??")).map((line) => line.slice(3));
|
|
101
|
-
return { staged, unstaged, untracked };
|
|
102
|
-
} catch (err) {
|
|
103
|
-
setError("Failed to get git status");
|
|
104
|
-
return null;
|
|
105
|
-
} finally {
|
|
106
|
-
setLoading(false);
|
|
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);
|
|
107
92
|
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const command = files.includes(".") ? "git add ." : `git add ${files.map((f) => `"${f}"`).join(" ")}`;
|
|
115
|
-
execCommand(command, cwd);
|
|
116
|
-
return true;
|
|
117
|
-
} catch (err) {
|
|
118
|
-
setError("Failed to stage files");
|
|
119
|
-
return false;
|
|
120
|
-
} finally {
|
|
121
|
-
setLoading(false);
|
|
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.`);
|
|
122
99
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
execCommand(`git commit -m "${escapedMessage}"`, cwd);
|
|
130
|
-
return true;
|
|
131
|
-
} catch (err) {
|
|
132
|
-
setError("Failed to commit changes");
|
|
133
|
-
return false;
|
|
134
|
-
} finally {
|
|
135
|
-
setLoading(false);
|
|
100
|
+
userSettings = createSettingsFile();
|
|
101
|
+
}
|
|
102
|
+
return new LoadedSettings(
|
|
103
|
+
{
|
|
104
|
+
path: USER_SETTINGS_PATH,
|
|
105
|
+
settings: userSettings
|
|
136
106
|
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
getGitStatus,
|
|
140
|
-
stageFiles,
|
|
141
|
-
commit,
|
|
142
|
-
loading,
|
|
143
|
-
error,
|
|
144
|
-
clearError: () => setError(null)
|
|
145
|
-
};
|
|
107
|
+
);
|
|
146
108
|
}
|
|
147
109
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setStep("staging");
|
|
164
|
-
setWaitingForInput(true);
|
|
165
|
-
} else if (messageArgs && messageArgs.length > 0) {
|
|
166
|
-
setStep("commit");
|
|
167
|
-
setCommitMessage(messageArgs.join(" "));
|
|
168
|
-
} else {
|
|
169
|
-
setStep("message");
|
|
170
|
-
setWaitingForInput(true);
|
|
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: "" } };
|
|
171
125
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (step === "staging" && waitingForInput) {
|
|
184
|
-
if (input === "y" || input === "Y" || key.return) {
|
|
185
|
-
handleStageFiles();
|
|
186
|
-
} else if (input === "n" || input === "N") {
|
|
187
|
-
onExit();
|
|
188
|
-
}
|
|
189
|
-
} else if (step === "message" && waitingForInput) {
|
|
190
|
-
if (key.return && userInput.trim()) {
|
|
191
|
-
setCommitMessage(userInput.trim());
|
|
192
|
-
setStep("commit");
|
|
193
|
-
setWaitingForInput(false);
|
|
194
|
-
} else if (key.backspace || key.delete) {
|
|
195
|
-
setUserInput((prev) => prev.slice(0, -1));
|
|
196
|
-
} else if (input && !key.ctrl && !key.meta) {
|
|
197
|
-
setUserInput((prev) => prev + input);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
const handleStageFiles = async () => {
|
|
202
|
-
setWaitingForInput(false);
|
|
203
|
-
const success = await stageFiles(["."]);
|
|
204
|
-
if (success) {
|
|
205
|
-
const newStatus = await getGitStatus();
|
|
206
|
-
if (newStatus) {
|
|
207
|
-
setGitStatus(newStatus);
|
|
208
|
-
if (messageArgs && messageArgs.length > 0) {
|
|
209
|
-
setStep("commit");
|
|
210
|
-
} else {
|
|
211
|
-
setStep("message");
|
|
212
|
-
setWaitingForInput(true);
|
|
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 || "" } };
|
|
213
137
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
} else {
|
|
226
|
-
setStep("error");
|
|
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 || "" } };
|
|
227
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"
|
|
228
164
|
}
|
|
229
|
-
|
|
165
|
+
},
|
|
166
|
+
(argv2) => {
|
|
167
|
+
parsedResult = { command: "git-commit", args: { message: argv2.message } };
|
|
230
168
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (step === "success") {
|
|
239
|
-
if (!gitStatus || gitStatus.staged.length === 0 && gitStatus.unstaged.length === 0 && gitStatus.untracked.length === 0) {
|
|
240
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2713 Working tree clean - nothing to commit"), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press ESC to exit"));
|
|
241
|
-
} else {
|
|
242
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2713 Commit created successfully!"), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press ESC to exit"));
|
|
169
|
+
);
|
|
170
|
+
parser.command(
|
|
171
|
+
["config", "cfg"],
|
|
172
|
+
"set or show configuration file.",
|
|
173
|
+
{},
|
|
174
|
+
() => {
|
|
175
|
+
parsedResult = { command: "config", args: {} };
|
|
243
176
|
}
|
|
177
|
+
);
|
|
178
|
+
await parser.parse();
|
|
179
|
+
if (!parsedResult) {
|
|
180
|
+
throw new Error("No command was parsed");
|
|
244
181
|
}
|
|
245
|
-
return
|
|
182
|
+
return parsedResult;
|
|
246
183
|
}
|
|
247
184
|
|
|
248
|
-
function
|
|
249
|
-
return
|
|
185
|
+
function execCommand(cmd, cwd) {
|
|
186
|
+
return execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
250
187
|
}
|
|
251
188
|
|
|
252
|
-
function
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
189
|
+
async function gitCommitCommand(context, commitMessage) {
|
|
190
|
+
try {
|
|
191
|
+
const status = await getGitStatus(context.cwd);
|
|
192
|
+
if (!status || status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0) {
|
|
193
|
+
consola.success("\u2713 Working tree clean - nothing to commit");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
displayGitStatus(status);
|
|
197
|
+
if (status.staged.length === 0) {
|
|
198
|
+
const shouldStage = await prompts({
|
|
199
|
+
type: "confirm",
|
|
200
|
+
name: "stage",
|
|
201
|
+
message: "No files are staged. Add all modified and untracked files?",
|
|
202
|
+
initial: true
|
|
203
|
+
});
|
|
204
|
+
if (!shouldStage.stage) {
|
|
205
|
+
consola.info("Cancelled");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
consola.start("Staging files...");
|
|
209
|
+
await execCommand("git add .", context.cwd);
|
|
210
|
+
consola.success("Files staged");
|
|
211
|
+
}
|
|
212
|
+
if (!commitMessage) {
|
|
213
|
+
const response = await prompts({
|
|
214
|
+
type: "text",
|
|
215
|
+
name: "message",
|
|
216
|
+
message: "Enter commit message:",
|
|
217
|
+
validate: (value) => value.trim().length > 0 || "Commit message cannot be empty"
|
|
262
218
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
219
|
+
if (!response.message) {
|
|
220
|
+
consola.info("Cancelled");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
commitMessage = response.message;
|
|
224
|
+
}
|
|
225
|
+
consola.start("Creating commit...");
|
|
226
|
+
await execCommand(`git commit -m "${commitMessage}"`, context.cwd);
|
|
227
|
+
consola.success("\u2713 Commit created successfully!");
|
|
228
|
+
} catch (error) {
|
|
229
|
+
consola.error("Git commit failed:", error instanceof Error ? error.message : String(error));
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
270
232
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
233
|
+
async function getGitStatus(cwd) {
|
|
234
|
+
try {
|
|
235
|
+
const output = await execCommand("git status --porcelain", cwd);
|
|
236
|
+
const lines = output.trim().split("\n").filter((line) => line.trim());
|
|
237
|
+
const staged = [];
|
|
238
|
+
const unstaged = [];
|
|
239
|
+
const untracked = [];
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
const status = line.substring(0, 2);
|
|
242
|
+
const file = line.substring(3);
|
|
243
|
+
if (status[0] !== " " && status[0] !== "?") {
|
|
244
|
+
staged.push(file);
|
|
245
|
+
}
|
|
246
|
+
if (status[1] !== " " && status[1] !== "?") {
|
|
247
|
+
unstaged.push(file);
|
|
248
|
+
}
|
|
249
|
+
if (status === "??") {
|
|
250
|
+
untracked.push(file);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { staged, unstaged, untracked };
|
|
254
|
+
} catch {
|
|
280
255
|
return null;
|
|
281
256
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
257
|
+
}
|
|
258
|
+
function displayGitStatus(status) {
|
|
259
|
+
consola.info("Current git status:");
|
|
260
|
+
if (status.staged.length > 0) {
|
|
261
|
+
consola.success(`\u2713 Staged files (${status.staged.length}):`);
|
|
262
|
+
status.staged.slice(0, 5).forEach((file) => consola.log(` ${file}`));
|
|
263
|
+
if (status.staged.length > 5) {
|
|
264
|
+
consola.log(` ... and ${status.staged.length - 5} more`);
|
|
265
|
+
}
|
|
291
266
|
}
|
|
292
|
-
if (
|
|
293
|
-
|
|
267
|
+
if (status.unstaged.length > 0) {
|
|
268
|
+
consola.warn(`M Modified files (${status.unstaged.length}):`);
|
|
269
|
+
status.unstaged.slice(0, 3).forEach((file) => consola.log(` ${file}`));
|
|
270
|
+
if (status.unstaged.length > 3) {
|
|
271
|
+
consola.log(` ... and ${status.unstaged.length - 3} more`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (status.untracked.length > 0) {
|
|
275
|
+
consola.error(`? Untracked files (${status.untracked.length}):`);
|
|
276
|
+
status.untracked.slice(0, 3).forEach((file) => consola.log(` ${file}`));
|
|
277
|
+
if (status.untracked.length > 3) {
|
|
278
|
+
consola.log(` ... and ${status.untracked.length - 3} more`);
|
|
279
|
+
}
|
|
294
280
|
}
|
|
295
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: DEFAULT_THEME.primary }, "Towles Tool"), /* @__PURE__ */ React.createElement(Text, { color: DEFAULT_THEME.dim }, "Terminal size: ", terminalSize.columns, "x", terminalSize.rows), /* @__PURE__ */ React.createElement(Text, { color: DEFAULT_THEME.warning }, "No command specified"));
|
|
296
|
-
}
|
|
297
|
-
function App(props) {
|
|
298
|
-
return /* @__PURE__ */ React.createElement(ErrorBoundary, null, /* @__PURE__ */ React.createElement(AppProvider, null, /* @__PURE__ */ React.createElement(ConfigProvider, { context: props.context }, /* @__PURE__ */ React.createElement(AppContent, { ...props }))));
|
|
299
|
-
}
|
|
300
|
-
function renderApp(props) {
|
|
301
|
-
return render(/* @__PURE__ */ React.createElement(App, { ...props }));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async function gitCommitCommand(context, messageArgs) {
|
|
305
|
-
const { waitUntilExit } = renderApp({
|
|
306
|
-
context,
|
|
307
|
-
command: "git-commit",
|
|
308
|
-
commandArgs: messageArgs
|
|
309
|
-
});
|
|
310
|
-
await waitUntilExit();
|
|
311
281
|
}
|
|
312
282
|
|
|
313
283
|
function getMondayOfWeek(date) {
|
|
@@ -502,106 +472,53 @@ async function createJournalFile({ context, type, title }) {
|
|
|
502
472
|
}
|
|
503
473
|
}
|
|
504
474
|
|
|
505
|
-
async function loadTowlesToolContext({
|
|
506
|
-
cwd,
|
|
507
|
-
settingsFile,
|
|
508
|
-
debug = false
|
|
509
|
-
}) {
|
|
510
|
-
return {
|
|
511
|
-
cwd,
|
|
512
|
-
settingsFile,
|
|
513
|
-
args: [],
|
|
514
|
-
// TODO: Load args from yargs
|
|
515
|
-
debug
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
|
|
519
475
|
async function configCommand(context) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
476
|
+
consola.info("Configuration");
|
|
477
|
+
consola.log("");
|
|
478
|
+
consola.info(`Settings File: ${context.settingsFile.path}`);
|
|
479
|
+
consola.log("");
|
|
480
|
+
consola.warn("User Config:");
|
|
481
|
+
consola.log(` Daily Path Template: ${context.settingsFile.settings.journalSettings.dailyPathTemplate}`);
|
|
482
|
+
consola.log(` Meeting Path Template: ${context.settingsFile.settings.journalSettings.meetingPathTemplate}`);
|
|
483
|
+
consola.log(` Note Path Template: ${context.settingsFile.settings.journalSettings.notePathTemplate}`);
|
|
484
|
+
consola.log(` Editor: ${context.settingsFile.settings.preferredEditor}`);
|
|
485
|
+
consola.log("");
|
|
486
|
+
consola.warn("Working Directory:");
|
|
487
|
+
consola.log(` ${context.cwd}`);
|
|
525
488
|
}
|
|
526
489
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// https://github.com/colinhacks/zod/discussions/1953
|
|
549
|
-
journalSettings: JournalSettingsSchema.parse({})
|
|
550
|
-
});
|
|
551
|
-
const settingsFile = {
|
|
552
|
-
path: USER_SETTINGS_PATH,
|
|
553
|
-
settings: defaultSettings
|
|
554
|
-
};
|
|
555
|
-
saveSettings(settingsFile);
|
|
556
|
-
consola.success(`Created settings file: ${USER_SETTINGS_PATH}`);
|
|
557
|
-
return defaultSettings;
|
|
558
|
-
}
|
|
559
|
-
function saveSettings(settingsFile) {
|
|
560
|
-
try {
|
|
561
|
-
const dirPath = path.dirname(settingsFile.path);
|
|
562
|
-
if (!fs.existsSync(dirPath)) {
|
|
563
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
564
|
-
}
|
|
565
|
-
fs.writeFileSync(
|
|
566
|
-
settingsFile.path,
|
|
567
|
-
JSON.stringify(settingsFile.settings, null, 2),
|
|
568
|
-
"utf-8"
|
|
569
|
-
);
|
|
570
|
-
} catch (error) {
|
|
571
|
-
consola.error("Error saving user settings file:", error);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
async function loadSettings() {
|
|
575
|
-
let userSettings = null;
|
|
576
|
-
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
577
|
-
const userContent = fs.readFileSync(USER_SETTINGS_PATH, "utf-8");
|
|
578
|
-
const parsedUserSettings = JSON.parse(stripJsonComments(userContent));
|
|
579
|
-
userSettings = UserSettingsSchema.parse(parsedUserSettings);
|
|
580
|
-
if (JSON.stringify(parsedUserSettings) !== JSON.stringify(userSettings)) {
|
|
581
|
-
consola.warn(`Settings file ${USER_SETTINGS_PATH} has been updated with default values.`);
|
|
582
|
-
const tempSettingsFile = {
|
|
583
|
-
path: USER_SETTINGS_PATH,
|
|
584
|
-
settings: userSettings
|
|
585
|
-
};
|
|
586
|
-
saveSettings(tempSettingsFile);
|
|
587
|
-
}
|
|
588
|
-
} else {
|
|
589
|
-
const confirmed = await consola.prompt(`Settings file not found. Create ${colors.cyan(USER_SETTINGS_PATH)}?`, {
|
|
590
|
-
type: "confirm"
|
|
591
|
-
});
|
|
592
|
-
if (!confirmed) {
|
|
593
|
-
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;
|
|
594
511
|
}
|
|
595
|
-
|
|
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}`);
|
|
596
520
|
}
|
|
597
|
-
return new LoadedSettings(
|
|
598
|
-
{
|
|
599
|
-
path: USER_SETTINGS_PATH,
|
|
600
|
-
settings: userSettings
|
|
601
|
-
}
|
|
602
|
-
);
|
|
603
521
|
}
|
|
604
|
-
|
|
605
522
|
async function main() {
|
|
606
523
|
const settings = await loadSettings();
|
|
607
524
|
const context = await loadTowlesToolContext({
|
|
@@ -611,71 +528,8 @@ async function main() {
|
|
|
611
528
|
// later can be set to false in production or when not debugging
|
|
612
529
|
});
|
|
613
530
|
consola.info(`Using configuration from ${settings.settingsFile.path}`);
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
["journal", "j"],
|
|
617
|
-
"quickly create md files from templates files like daily-notes, meeting, notes, etc.",
|
|
618
|
-
(yargs2) => {
|
|
619
|
-
return yargs2.command(
|
|
620
|
-
["daily-notes", "today"],
|
|
621
|
-
"Weekly files with daily sections for ongoing work and notes",
|
|
622
|
-
{},
|
|
623
|
-
async () => {
|
|
624
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.DAILY_NOTES, title: "" });
|
|
625
|
-
}
|
|
626
|
-
).command(
|
|
627
|
-
["meeting [title]", "m"],
|
|
628
|
-
"Structured meeting notes with agenda and action items",
|
|
629
|
-
(yargs3) => {
|
|
630
|
-
return yargs3.positional("title", {
|
|
631
|
-
type: "string",
|
|
632
|
-
describe: "Meeting title"
|
|
633
|
-
});
|
|
634
|
-
},
|
|
635
|
-
async (argv) => {
|
|
636
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.MEETING, title: argv.title || "" });
|
|
637
|
-
}
|
|
638
|
-
).command(
|
|
639
|
-
["note [title]", "n"],
|
|
640
|
-
"General-purpose notes with structured sections",
|
|
641
|
-
(yargs3) => {
|
|
642
|
-
return yargs3.positional("title", {
|
|
643
|
-
type: "string",
|
|
644
|
-
describe: "Note title"
|
|
645
|
-
});
|
|
646
|
-
},
|
|
647
|
-
async (argv) => {
|
|
648
|
-
await createJournalFile({ context, type: JOURNAL_TYPES.NOTE, title: argv.title || "" });
|
|
649
|
-
}
|
|
650
|
-
).demandCommand(1, "You need to specify a journal subcommand").help();
|
|
651
|
-
},
|
|
652
|
-
() => {
|
|
653
|
-
parser.showHelp();
|
|
654
|
-
}
|
|
655
|
-
);
|
|
656
|
-
parser.command(
|
|
657
|
-
["git-commit [message...]", "gc"],
|
|
658
|
-
"Git commit command with optional message",
|
|
659
|
-
(yargs2) => {
|
|
660
|
-
return yargs2.positional("message", {
|
|
661
|
-
type: "string",
|
|
662
|
-
array: true,
|
|
663
|
-
describe: "Commit message words"
|
|
664
|
-
});
|
|
665
|
-
},
|
|
666
|
-
async (argv) => {
|
|
667
|
-
await gitCommitCommand(context, argv.message || []);
|
|
668
|
-
}
|
|
669
|
-
);
|
|
670
|
-
parser.command(
|
|
671
|
-
["config", "cfg"],
|
|
672
|
-
"set or show configuration file.",
|
|
673
|
-
{},
|
|
674
|
-
async () => {
|
|
675
|
-
await configCommand(context);
|
|
676
|
-
}
|
|
677
|
-
);
|
|
678
|
-
await parser.parse();
|
|
531
|
+
const parsedArgs = await parseArguments(process$1.argv);
|
|
532
|
+
await executeCommand(parsedArgs, context);
|
|
679
533
|
}
|
|
680
534
|
main().catch((error) => {
|
|
681
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",
|
|
@@ -36,31 +36,22 @@
|
|
|
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
|
-
"
|
|
40
|
-
"c12": "^3.0.4",
|
|
41
|
-
"changelogen": "^0.6.2",
|
|
42
|
-
"consola": "^3.4.2",
|
|
39
|
+
"comment-json": "^4.2.5",
|
|
43
40
|
"fzf": "^0.5.2",
|
|
44
|
-
"ink": "^5.0.1",
|
|
45
41
|
"luxon": "^3.7.1",
|
|
46
|
-
"magicast": "^0.3.5",
|
|
47
42
|
"neverthrow": "^8.2.0",
|
|
48
43
|
"prompts": "^2.4.2",
|
|
49
|
-
"
|
|
50
|
-
"strip-json-comments": "^5.0.2",
|
|
44
|
+
"consola": "^3.4.2",
|
|
51
45
|
"yargs": "^17.7.2",
|
|
52
46
|
"zod": "^4.0.5"
|
|
53
47
|
},
|
|
54
48
|
"devDependencies": {
|
|
55
49
|
"@antfu/ni": "^25.0.0",
|
|
56
|
-
"@antfu/utils": "^9.2.0",
|
|
57
50
|
"@types/luxon": "^3.6.2",
|
|
58
51
|
"@types/node": "^22.16.3",
|
|
59
52
|
"@types/prompts": "^2.4.9",
|
|
60
|
-
"@types/react": "^18.3.12",
|
|
61
53
|
"@types/yargs": "^17.0.32",
|
|
62
54
|
"bumpp": "^10.2.0",
|
|
63
|
-
"ink-testing-library": "^4.0.0",
|
|
64
55
|
"lint-staged": "^15.5.2",
|
|
65
56
|
"oxlint": "^1.7.0",
|
|
66
57
|
"simple-git-hooks": "^2.13.0",
|
|
@@ -88,9 +79,10 @@
|
|
|
88
79
|
"lint": "oxlint",
|
|
89
80
|
"lint:fix": "oxlint --fix",
|
|
90
81
|
"lint:fix_all": "oxlint --fix .",
|
|
82
|
+
"lint:package": "pnpm dlx publint --fix && pnpm dlx knip",
|
|
91
83
|
"release:local": "bumpp && pnpm publish --no-git-checks -r --access public",
|
|
92
84
|
"release": "bumpp && echo \"github action will run and publish to npm\"",
|
|
93
|
-
"start": "tsx src/index.
|
|
85
|
+
"start": "tsx src/index.ts",
|
|
94
86
|
"test": "vitest --run",
|
|
95
87
|
"test:watch": "CI=DisableCallingClaude vitest --watch",
|
|
96
88
|
"typecheck": "tsc --noEmit"
|