@towles/tool 0.0.11 → 0.0.12
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 +9 -9
- package/dist/index.mjs +510 -210
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ if that works, then you need to add the pnpm global bin directory to your PATH.
|
|
|
74
74
|
- [rolldown-vite](https://voidzero.dev/posts/announcing-rolldown-vite) - A Vite plugin for rolling down your code
|
|
75
75
|
- ~~[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
76
|
- [prompts](https://github.com/terkelg/prompts) - A library for creating beautiful command-line prompts, with fuzzy search and other features.
|
|
77
|
-
- [
|
|
77
|
+
- [yargs](https://github.com/yargs/yargs) - A modern, feature-rich command-line argument parser with enhanced error handling, TypeScript support, and flexible command configuration.
|
|
78
78
|
|
|
79
79
|
## Document verbose and debug options
|
|
80
80
|
|
|
@@ -98,13 +98,13 @@ I'm using a lot of inspiration from [Anthony Fu](https://github.com/antfu) for t
|
|
|
98
98
|
|
|
99
99
|
<!-- Badges -->
|
|
100
100
|
|
|
101
|
-
[npm-version-src]: https://img.shields.io/npm/v/
|
|
102
|
-
[npm-version-href]: https://npmjs.com/package/
|
|
103
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/
|
|
104
|
-
[npm-downloads-href]: https://npmjs.com/package/
|
|
105
|
-
[bundle-src]: https://img.shields.io/bundlephobia/minzip/
|
|
106
|
-
[bundle-href]: https://bundlephobia.com/result?p
|
|
107
|
-
[license-src]: https://img.shields.io/github/license/
|
|
101
|
+
[npm-version-src]: https://img.shields.io/npm/v/@towles/tool?style=flat&colorA=080f12&colorB=1fa669
|
|
102
|
+
[npm-version-href]: https://npmjs.com/package/@towles/tool
|
|
103
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@towles/tool?style=flat&colorA=080f12&colorB=1fa669
|
|
104
|
+
[npm-downloads-href]: https://npmjs.com/package/@towles/tool
|
|
105
|
+
[bundle-src]: https://img.shields.io/bundlephobia/minzip/@towles/tool?style=flat&colorA=080f12&colorB=1fa669&label=minzip
|
|
106
|
+
[bundle-href]: https://bundlephobia.com/result?p=@towles/tool
|
|
107
|
+
[license-src]: https://img.shields.io/github/license/ChrisTowles/towles-tool.svg?style=flat&colorA=080f12&colorB=1fa669
|
|
108
108
|
[license-href]: https://github.com/ChrisTowles/towles-tool/blob/main/LICENSE.md
|
|
109
109
|
[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
|
|
110
|
-
[jsdocs-href]: https://www.jsdocs.io/package/
|
|
110
|
+
[jsdocs-href]: https://www.jsdocs.io/package/@towles/tool
|
package/dist/index.mjs
CHANGED
|
@@ -1,115 +1,313 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import process from 'node:process';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
2
|
+
import process$1 from 'node:process';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import { useState, createContext, Component, useEffect } from 'react';
|
|
6
|
+
import { Box, Text, useInput, render } from 'ink';
|
|
7
7
|
import { execSync, exec } from 'node:child_process';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
8
9
|
import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
9
10
|
import * as path from 'node:path';
|
|
10
11
|
import path__default from 'node:path';
|
|
11
|
-
import
|
|
12
|
+
import { promisify } from 'node:util';
|
|
13
|
+
import consola from 'consola';
|
|
14
|
+
import { colors } from 'consola/utils';
|
|
15
|
+
import { DateTime } from 'luxon';
|
|
16
|
+
import { z } from 'zod/v4';
|
|
12
17
|
import { homedir } from 'node:os';
|
|
13
|
-
import
|
|
14
|
-
import { updateConfig } from 'c12/update';
|
|
18
|
+
import stripJsonComments from 'strip-json-comments';
|
|
15
19
|
|
|
16
|
-
const version = "0.0.
|
|
20
|
+
const version = "0.0.12";
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const AppContext = createContext(null);
|
|
23
|
+
function AppProvider({ children, initialCommandContext }) {
|
|
24
|
+
const [appState, setAppState] = useState({
|
|
25
|
+
isLoading: false,
|
|
26
|
+
error: null,
|
|
27
|
+
currentCommand: null
|
|
28
|
+
});
|
|
29
|
+
const [commandContext, setCommandContext] = useState({
|
|
30
|
+
mode: "interactive",
|
|
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 }));
|
|
40
|
+
};
|
|
41
|
+
return /* @__PURE__ */ React.createElement(AppContext.Provider, { value: {
|
|
42
|
+
appState,
|
|
43
|
+
updateAppState,
|
|
44
|
+
commandContext,
|
|
45
|
+
updateCommandContext
|
|
46
|
+
} }, children);
|
|
20
47
|
}
|
|
21
48
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
const ConfigContext = createContext(null);
|
|
50
|
+
function ConfigProvider({ children, context }) {
|
|
51
|
+
return /* @__PURE__ */ React.createElement(ConfigContext.Provider, { value: { context } }, children);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const AppInfo = {
|
|
55
|
+
toolName: "towles-tool"
|
|
56
|
+
};
|
|
57
|
+
const DEFAULT_THEME = {
|
|
58
|
+
primary: "cyan",
|
|
59
|
+
warning: "yellow",
|
|
60
|
+
error: "red",
|
|
61
|
+
dim: "dim"
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
class ErrorBoundary extends Component {
|
|
65
|
+
constructor(props) {
|
|
66
|
+
super(props);
|
|
67
|
+
this.state = { hasError: false };
|
|
37
68
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
consola.info(colors.green("Staged files:"));
|
|
41
|
-
stagedFiles.forEach((file) => consola.info(` ${colors.green(file)}`));
|
|
69
|
+
static getDerivedStateFromError(error) {
|
|
70
|
+
return { hasError: true, error };
|
|
42
71
|
}
|
|
43
|
-
|
|
44
|
-
consola.info(colors.yellow("Modified files (not staged):"));
|
|
45
|
-
unstagedFiles.forEach((file) => consola.info(` ${colors.yellow(file)}`));
|
|
72
|
+
componentDidCatch(error, errorInfo) {
|
|
46
73
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
render() {
|
|
75
|
+
if (this.state.hasError) {
|
|
76
|
+
if (this.props.fallback) {
|
|
77
|
+
return this.props.fallback;
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: DEFAULT_THEME.error }, "Application Error"), /* @__PURE__ */ React.createElement(Text, { color: DEFAULT_THEME.error }, this.state.error?.message || "An unexpected error occurred"), /* @__PURE__ */ React.createElement(Text, { color: DEFAULT_THEME.dim }, "Press ESC to exit"));
|
|
80
|
+
}
|
|
81
|
+
return this.props.children;
|
|
50
82
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function execCommand(cmd, cwd) {
|
|
86
|
+
return execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function useGitOperations(cwd) {
|
|
90
|
+
const [loading, setLoading] = useState(false);
|
|
91
|
+
const [error, setError] = useState(null);
|
|
92
|
+
const getGitStatus = async () => {
|
|
93
|
+
try {
|
|
94
|
+
setLoading(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
const statusOutput = execCommand("git status --porcelain", cwd);
|
|
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);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const stageFiles = async (files) => {
|
|
110
|
+
try {
|
|
111
|
+
setLoading(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
if (files.length === 0) return false;
|
|
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);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const commit = async (message) => {
|
|
125
|
+
try {
|
|
126
|
+
setLoading(true);
|
|
127
|
+
setError(null);
|
|
128
|
+
const escapedMessage = message.replace(/"/g, '\\"');
|
|
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);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
getGitStatus,
|
|
140
|
+
stageFiles,
|
|
141
|
+
commit,
|
|
142
|
+
loading,
|
|
143
|
+
error,
|
|
144
|
+
clearError: () => setError(null)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function GitCommit({ context, messageArgs, onExit }) {
|
|
149
|
+
const { getGitStatus, stageFiles, commit, loading, error } = useGitOperations(context.cwd);
|
|
150
|
+
const [step, setStep] = useState("loading");
|
|
151
|
+
const [gitStatus, setGitStatus] = useState(null);
|
|
152
|
+
const [commitMessage, setCommitMessage] = useState(messageArgs?.join(" ") || "");
|
|
153
|
+
const [userInput, setUserInput] = useState("");
|
|
154
|
+
const [waitingForInput, setWaitingForInput] = useState(false);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
async function init() {
|
|
157
|
+
const status = await getGitStatus();
|
|
158
|
+
if (status) {
|
|
159
|
+
setGitStatus(status);
|
|
160
|
+
if (status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0) {
|
|
161
|
+
setStep("success");
|
|
162
|
+
} else if (status.staged.length === 0) {
|
|
163
|
+
setStep("staging");
|
|
164
|
+
setWaitingForInput(true);
|
|
165
|
+
} else if (messageArgs && messageArgs.length > 0) {
|
|
166
|
+
setStep("commit");
|
|
167
|
+
setCommitMessage(messageArgs.join(" "));
|
|
74
168
|
} else {
|
|
75
|
-
|
|
76
|
-
|
|
169
|
+
setStep("message");
|
|
170
|
+
setWaitingForInput(true);
|
|
77
171
|
}
|
|
78
172
|
} else {
|
|
79
|
-
|
|
80
|
-
process.exit(1);
|
|
173
|
+
setStep("error");
|
|
81
174
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
175
|
+
}
|
|
176
|
+
init();
|
|
177
|
+
}, []);
|
|
178
|
+
useInput((input, key) => {
|
|
179
|
+
if (key.escape) {
|
|
180
|
+
onExit();
|
|
84
181
|
return;
|
|
85
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);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
setStep("error");
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (step === "commit" && commitMessage && !waitingForInput) {
|
|
221
|
+
async function performCommit() {
|
|
222
|
+
const success = await commit(commitMessage);
|
|
223
|
+
if (success) {
|
|
224
|
+
setStep("success");
|
|
225
|
+
} else {
|
|
226
|
+
setStep("error");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
performCommit();
|
|
230
|
+
}
|
|
231
|
+
}, [step, commitMessage, waitingForInput]);
|
|
232
|
+
if (step === "loading" || loading) {
|
|
233
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, null, "Loading git status..."));
|
|
86
234
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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;
|
|
235
|
+
if (step === "error" || error) {
|
|
236
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "Error: ", error || "An error occurred"), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press ESC to exit"));
|
|
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"));
|
|
100
243
|
}
|
|
101
|
-
commitMessage = message.trim();
|
|
102
244
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
245
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "Git Commit"), gitStatus && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Current Status:"), gitStatus.staged.length > 0 && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2713 Staged files (", gitStatus.staged.length, "):"), gitStatus.staged.slice(0, 5).map((file) => /* @__PURE__ */ React.createElement(Text, { key: file, color: "green" }, " ", file)), gitStatus.staged.length > 5 && /* @__PURE__ */ React.createElement(Text, { color: "green" }, " ... and ", gitStatus.staged.length - 5, " more")), gitStatus.unstaged.length > 0 && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "M Modified files (", gitStatus.unstaged.length, "):"), gitStatus.unstaged.slice(0, 3).map((file) => /* @__PURE__ */ React.createElement(Text, { key: file, color: "yellow" }, " ", file)), gitStatus.unstaged.length > 3 && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, " ... and ", gitStatus.unstaged.length - 3, " more")), gitStatus.untracked.length > 0 && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "? Untracked files (", gitStatus.untracked.length, "):"), gitStatus.untracked.slice(0, 3).map((file) => /* @__PURE__ */ React.createElement(Text, { key: file, color: "red" }, " ", file)), gitStatus.untracked.length > 3 && /* @__PURE__ */ React.createElement(Text, { color: "red" }, " ... and ", gitStatus.untracked.length - 3, " more"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 2, flexDirection: "column" }, step === "staging" && waitingForInput && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "No files are staged. Add all modified and untracked files? (y/N)"), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press y for yes, n for no, or ESC to cancel")), step === "message" && waitingForInput && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Enter commit message:"), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "> "), /* @__PURE__ */ React.createElement(Text, null, userInput), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "\u2588")), /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press Enter to commit, ESC to cancel")), step === "commit" && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Committing changes...")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "dim" }, "Press ESC to cancel")));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function ConfigDisplay({ context }) {
|
|
249
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "green" }, "Configuration"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "Settings File: "), /* @__PURE__ */ React.createElement(Text, null, context.settingsFile.path)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "User Config:"), /* @__PURE__ */ React.createElement(Box, { marginLeft: 2, marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "Daily Path Template: "), /* @__PURE__ */ React.createElement(Text, null, context.settingsFile.settings.journalSettings.dailyPathTemplate)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "Meeting Path Template: "), /* @__PURE__ */ React.createElement(Text, null, context.settingsFile.settings.journalSettings.meetingPathTemplate)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "Note Path Template: "), /* @__PURE__ */ React.createElement(Text, null, context.settingsFile.settings.journalSettings.notePathTemplate)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "Editor: "), /* @__PURE__ */ React.createElement(Text, null, context.settingsFile.settings.preferredEditor)))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "Working Directory:"), /* @__PURE__ */ React.createElement(Box, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, context.cwd))));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function useTerminalSize() {
|
|
253
|
+
const [size, setSize] = useState({
|
|
254
|
+
columns: process.stdout.columns || 80,
|
|
255
|
+
rows: process.stdout.rows || 24
|
|
256
|
+
});
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
const updateSize = () => {
|
|
259
|
+
setSize({
|
|
260
|
+
columns: process.stdout.columns || 80,
|
|
261
|
+
rows: process.stdout.rows || 24
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
process.stdout.on("resize", updateSize);
|
|
265
|
+
return () => {
|
|
266
|
+
process.stdout.off("resize", updateSize);
|
|
267
|
+
};
|
|
268
|
+
}, []);
|
|
269
|
+
return size;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function AppContent({ context, command, commandArgs }) {
|
|
273
|
+
const [isExiting, setIsExiting] = useState(false);
|
|
274
|
+
const terminalSize = useTerminalSize();
|
|
275
|
+
const handleExit = (code = 0) => {
|
|
276
|
+
setIsExiting(true);
|
|
277
|
+
setTimeout(() => process.exit(code), 100);
|
|
278
|
+
};
|
|
279
|
+
if (isExiting) {
|
|
280
|
+
return null;
|
|
112
281
|
}
|
|
282
|
+
if (command === "git-commit") {
|
|
283
|
+
return /* @__PURE__ */ React.createElement(
|
|
284
|
+
GitCommit,
|
|
285
|
+
{
|
|
286
|
+
context,
|
|
287
|
+
messageArgs: commandArgs,
|
|
288
|
+
onExit: handleExit
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (command === "config") {
|
|
293
|
+
return /* @__PURE__ */ React.createElement(ConfigDisplay, { context });
|
|
294
|
+
}
|
|
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();
|
|
113
311
|
}
|
|
114
312
|
|
|
115
313
|
function getMondayOfWeek(date) {
|
|
@@ -214,44 +412,71 @@ async function openInEditor({ editor, filePath }) {
|
|
|
214
412
|
consola.warn(`Could not open in editor : '${editor}'. Modify your editor in the config: examples include 'code', 'code-insiders', etc...`, ex);
|
|
215
413
|
}
|
|
216
414
|
}
|
|
217
|
-
function
|
|
415
|
+
function resolvePathTemplate(template, title, date) {
|
|
416
|
+
const dateTime = DateTime.fromJSDate(date, { zone: "utc" });
|
|
417
|
+
return template.replace(/\{([^}]+)\}/g, (match, token) => {
|
|
418
|
+
try {
|
|
419
|
+
if (token === "title") {
|
|
420
|
+
return title.toLowerCase().replace(/\s+/g, "-");
|
|
421
|
+
}
|
|
422
|
+
const result = dateTime.toFormat(token);
|
|
423
|
+
const isLikelyInvalid = token.includes("invalid") || result.length > 20 || // Very long results are likely garbage
|
|
424
|
+
result.length > token.length * 2 && /\d{10,}/.test(result) || // Contains very long numbers
|
|
425
|
+
result.includes("UTC");
|
|
426
|
+
if (isLikelyInvalid) {
|
|
427
|
+
consola.warn(`Invalid date format token: ${token}`);
|
|
428
|
+
return match;
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
consola.warn(`Invalid date format token: ${token}`);
|
|
433
|
+
return match;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function generateJournalFileInfoByType({ journalSettings, date = /* @__PURE__ */ new Date(), type = JOURNAL_TYPES.DAILY_NOTES, title }) {
|
|
218
438
|
const currentDate = new Date(date);
|
|
219
|
-
|
|
439
|
+
let templatePath = "";
|
|
440
|
+
let mondayDate = getMondayOfWeek(currentDate);
|
|
220
441
|
switch (type) {
|
|
221
442
|
case JOURNAL_TYPES.DAILY_NOTES: {
|
|
222
443
|
const monday = getMondayOfWeek(currentDate);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
444
|
+
templatePath = journalSettings.dailyPathTemplate;
|
|
445
|
+
mondayDate = monday;
|
|
446
|
+
break;
|
|
226
447
|
}
|
|
227
448
|
case JOURNAL_TYPES.MEETING: {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const fileName = `${dateStr}-${timeStr}-meeting${titleSlug}.md`;
|
|
232
|
-
const pathPrefix = [year, "meetings"];
|
|
233
|
-
return { pathPrefix, fileName, mondayDate: currentDate };
|
|
449
|
+
templatePath = journalSettings.meetingPathTemplate;
|
|
450
|
+
mondayDate = currentDate;
|
|
451
|
+
break;
|
|
234
452
|
}
|
|
235
453
|
case JOURNAL_TYPES.NOTE: {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const fileName = `${dateStr}-${timeStr}-note${titleSlug}.md`;
|
|
240
|
-
const pathPrefix = [year, "notes"];
|
|
241
|
-
return { pathPrefix, fileName, mondayDate: currentDate };
|
|
454
|
+
templatePath = journalSettings.notePathTemplate;
|
|
455
|
+
mondayDate = currentDate;
|
|
456
|
+
break;
|
|
242
457
|
}
|
|
243
458
|
default:
|
|
244
459
|
throw new Error(`Unknown journal type: ${type}`);
|
|
245
460
|
}
|
|
461
|
+
const resolvedPath = resolvePathTemplate(templatePath, title, currentDate);
|
|
462
|
+
return {
|
|
463
|
+
currentDate,
|
|
464
|
+
fullPath: resolvedPath,
|
|
465
|
+
mondayDate
|
|
466
|
+
};
|
|
246
467
|
}
|
|
247
|
-
async function createJournalFile({
|
|
468
|
+
async function createJournalFile({ context, type, title }) {
|
|
248
469
|
try {
|
|
249
470
|
const currentDate = /* @__PURE__ */ new Date();
|
|
250
|
-
const fileInfo = generateJournalFileInfoByType({
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
471
|
+
const fileInfo = generateJournalFileInfoByType({
|
|
472
|
+
journalSettings: context.settingsFile.settings.journalSettings,
|
|
473
|
+
date: currentDate,
|
|
474
|
+
type,
|
|
475
|
+
title
|
|
476
|
+
});
|
|
477
|
+
ensureDirectoryExists(path__default.dirname(fileInfo.fullPath));
|
|
478
|
+
if (existsSync(fileInfo.fullPath)) {
|
|
479
|
+
consola.info(`Opening existing ${type} file: ${colors.cyan(fileInfo.fullPath)}`);
|
|
255
480
|
} else {
|
|
256
481
|
let content;
|
|
257
482
|
switch (type) {
|
|
@@ -267,122 +492,197 @@ async function createJournalFile({ userConfig, type, title }) {
|
|
|
267
492
|
default:
|
|
268
493
|
throw new Error(`Unknown journal type: ${type}`);
|
|
269
494
|
}
|
|
270
|
-
|
|
271
|
-
|
|
495
|
+
consola.info(`Creating new ${type} file: ${colors.cyan(fileInfo.fullPath)}`);
|
|
496
|
+
writeFileSync(fileInfo.fullPath, content, "utf8");
|
|
272
497
|
}
|
|
273
|
-
await openInEditor({ editor:
|
|
498
|
+
await openInEditor({ editor: context.settingsFile.settings.preferredEditor, filePath: fileInfo.fullPath });
|
|
274
499
|
} catch (error) {
|
|
275
500
|
consola.warn(`Error creating ${type} file:`, error);
|
|
276
|
-
process.exit(1);
|
|
501
|
+
process$1.exit(1);
|
|
277
502
|
}
|
|
278
503
|
}
|
|
279
504
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
}
|
|
283
518
|
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}));
|
|
519
|
+
async function configCommand(context) {
|
|
520
|
+
const { waitUntilExit } = renderApp({
|
|
521
|
+
context,
|
|
522
|
+
command: "config"
|
|
523
|
+
});
|
|
524
|
+
await waitUntilExit();
|
|
291
525
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
526
|
+
|
|
527
|
+
const USER_SETTINGS_DIR = path.join(homedir(), ".config", AppInfo.toolName);
|
|
528
|
+
const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, `${AppInfo.toolName}.settings.json`);
|
|
529
|
+
const JournalSettingsSchema = z.object({
|
|
530
|
+
// https://moment.github.io/luxon/#/formatting?id=table-of-tokens
|
|
531
|
+
dailyPathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/daily-notes/{yyyy}-{MM}-{dd}-daily-notes.md")),
|
|
532
|
+
meetingPathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/meetings/{yyyy}-{MM}-{dd}-{title}.md")),
|
|
533
|
+
notePathTemplate: z.string().default(path.join(homedir(), "journal", "{yyyy}/{MM}/notes/{yyyy}-{MM}-{dd}-{title}.md"))
|
|
534
|
+
});
|
|
535
|
+
const UserSettingsSchema = z.object({
|
|
536
|
+
preferredEditor: z.string().default("code"),
|
|
537
|
+
journalSettings: JournalSettingsSchema
|
|
538
|
+
});
|
|
539
|
+
class LoadedSettings {
|
|
540
|
+
constructor(settingsFile) {
|
|
541
|
+
this.settingsFile = settingsFile;
|
|
295
542
|
}
|
|
543
|
+
settingsFile;
|
|
296
544
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
545
|
+
function createSettingsFile() {
|
|
546
|
+
const defaultSettings = UserSettingsSchema.parse({
|
|
547
|
+
// its odd, but but we have to get default value from each schema object, if we don't it failes.
|
|
548
|
+
// https://github.com/colinhacks/zod/discussions/1953
|
|
549
|
+
journalSettings: JournalSettingsSchema.parse({})
|
|
550
|
+
});
|
|
551
|
+
const settingsFile = {
|
|
552
|
+
path: USER_SETTINGS_PATH,
|
|
553
|
+
settings: defaultSettings
|
|
302
554
|
};
|
|
555
|
+
saveSettings(settingsFile);
|
|
556
|
+
consola.success(`Created settings file: ${USER_SETTINGS_PATH}`);
|
|
557
|
+
return defaultSettings;
|
|
303
558
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const defaults = getDefaultUserConfig();
|
|
310
|
-
const defaultConfigFolder = path.join(homedir(), ".config", constants.toolName);
|
|
311
|
-
const updateResult = await updateConfig({
|
|
312
|
-
cwd: defaultConfigFolder,
|
|
313
|
-
configFile: `${constants.toolName}.config`,
|
|
314
|
-
createExtension: ".ts",
|
|
315
|
-
async onCreate({ configFile: configFile2 }) {
|
|
316
|
-
const shallCreate = await consola.prompt(
|
|
317
|
-
`Do you want to initialize a new config in ${colors.cyan(configFile2)}?`,
|
|
318
|
-
{
|
|
319
|
-
type: "confirm",
|
|
320
|
-
default: true
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
if (shallCreate !== true) {
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
return `
|
|
327
|
-
// Default configuration for Towles Tool
|
|
328
|
-
// You can customize these values to fit your needs
|
|
329
|
-
// cwd: null means it will use the current working directory
|
|
330
|
-
export default ${JSON.stringify(defaults, null, 2)};
|
|
331
|
-
`;
|
|
332
|
-
},
|
|
333
|
-
async onUpdate(config) {
|
|
334
|
-
return config;
|
|
559
|
+
function saveSettings(settingsFile) {
|
|
560
|
+
try {
|
|
561
|
+
const dirPath = path.dirname(settingsFile.path);
|
|
562
|
+
if (!fs.existsSync(dirPath)) {
|
|
563
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
335
564
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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);
|
|
343
572
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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);
|
|
353
587
|
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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.`);
|
|
594
|
+
}
|
|
595
|
+
userSettings = createSettingsFile();
|
|
596
|
+
}
|
|
597
|
+
return new LoadedSettings(
|
|
598
|
+
{
|
|
599
|
+
path: USER_SETTINGS_PATH,
|
|
600
|
+
settings: userSettings
|
|
601
|
+
}
|
|
602
|
+
);
|
|
361
603
|
}
|
|
362
604
|
|
|
363
605
|
async function main() {
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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 });
|
|
377
|
-
});
|
|
378
|
-
program.command("git-commit [message...]").alias("gc").description("Git commit command with optional message").action(async (message) => {
|
|
379
|
-
await gitCommitCommand(config, message);
|
|
606
|
+
const settings = await loadSettings();
|
|
607
|
+
const context = await loadTowlesToolContext({
|
|
608
|
+
cwd: process$1.cwd(),
|
|
609
|
+
settingsFile: settings.settingsFile,
|
|
610
|
+
debug: true
|
|
611
|
+
// later can be set to false in production or when not debugging
|
|
380
612
|
});
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
613
|
+
consola.info(`Using configuration from ${settings.settingsFile.path}`);
|
|
614
|
+
const parser = yargs(hideBin(process$1.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());
|
|
615
|
+
parser.command(
|
|
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();
|
|
387
679
|
}
|
|
388
|
-
|
|
680
|
+
main().catch((error) => {
|
|
681
|
+
consola.error("An unexpected critical error occurred:");
|
|
682
|
+
if (error instanceof Error) {
|
|
683
|
+
consola.error(error.stack);
|
|
684
|
+
} else {
|
|
685
|
+
consola.error(String(error));
|
|
686
|
+
}
|
|
687
|
+
process$1.exit(1);
|
|
688
|
+
});
|
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.12",
|
|
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,22 +36,31 @@
|
|
|
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
|
+
"@inkjs/ui": "^2.0.0",
|
|
39
40
|
"c12": "^3.0.4",
|
|
40
41
|
"changelogen": "^0.6.2",
|
|
41
|
-
"commander": "^14.0.0",
|
|
42
42
|
"consola": "^3.4.2",
|
|
43
43
|
"fzf": "^0.5.2",
|
|
44
|
+
"ink": "^5.0.1",
|
|
45
|
+
"luxon": "^3.7.1",
|
|
44
46
|
"magicast": "^0.3.5",
|
|
45
47
|
"neverthrow": "^8.2.0",
|
|
46
48
|
"prompts": "^2.4.2",
|
|
49
|
+
"react": "^18.3.1",
|
|
50
|
+
"strip-json-comments": "^5.0.2",
|
|
51
|
+
"yargs": "^17.7.2",
|
|
47
52
|
"zod": "^4.0.5"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"@antfu/ni": "^25.0.0",
|
|
51
56
|
"@antfu/utils": "^9.2.0",
|
|
57
|
+
"@types/luxon": "^3.6.2",
|
|
52
58
|
"@types/node": "^22.16.3",
|
|
53
59
|
"@types/prompts": "^2.4.9",
|
|
60
|
+
"@types/react": "^18.3.12",
|
|
61
|
+
"@types/yargs": "^17.0.32",
|
|
54
62
|
"bumpp": "^10.2.0",
|
|
63
|
+
"ink-testing-library": "^4.0.0",
|
|
55
64
|
"lint-staged": "^15.5.2",
|
|
56
65
|
"oxlint": "^1.7.0",
|
|
57
66
|
"simple-git-hooks": "^2.13.0",
|
|
@@ -81,9 +90,9 @@
|
|
|
81
90
|
"lint:fix_all": "oxlint --fix .",
|
|
82
91
|
"release:local": "bumpp && pnpm publish --no-git-checks -r --access public",
|
|
83
92
|
"release": "bumpp && echo \"github action will run and publish to npm\"",
|
|
84
|
-
"start": "tsx src/index.
|
|
93
|
+
"start": "tsx src/index.tsx",
|
|
85
94
|
"test": "vitest --run",
|
|
86
|
-
"test:watch": "vitest",
|
|
95
|
+
"test:watch": "CI=DisableCallingClaude vitest --watch",
|
|
87
96
|
"typecheck": "tsc --noEmit"
|
|
88
97
|
}
|
|
89
98
|
}
|