@towles/tool 0.0.96 ā 0.0.104
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 +2 -2
- package/bin/run.ts +4 -3
- package/package.json +10 -37
- package/src/cli.ts +19 -0
- package/src/commands/agentboard.ts +386 -214
- package/src/commands/auto-claude/index.ts +74 -91
- package/src/commands/auto-claude/list.ts +33 -43
- package/src/commands/auto-claude/retry.test.ts +10 -6
- package/src/commands/auto-claude/retry.ts +26 -39
- package/src/commands/auto-claude/status.ts +10 -17
- package/src/commands/config.test.ts +4 -10
- package/src/commands/config.ts +14 -28
- package/src/commands/doctor.ts +156 -178
- package/src/commands/gh/branch-clean.ts +28 -43
- package/src/commands/gh/branch.ts +22 -37
- package/src/commands/gh/index.ts +10 -0
- package/src/commands/gh/pr.ts +82 -100
- package/src/commands/graph/index.ts +59 -70
- package/src/commands/install.ts +91 -115
- package/src/commands/journal/daily-notes.ts +16 -24
- package/src/commands/journal/index.ts +10 -0
- package/src/commands/journal/meeting.ts +16 -34
- package/src/commands/journal/note.ts +16 -34
- package/src/commands/shared.ts +21 -0
- package/src/lib/auto-claude/templates.test.ts +16 -11
- package/src/lib/graph/parser.test.ts +11 -10
- package/src/utils/git/gh-cli-wrapper.test.ts +6 -5
- package/src/commands/base.ts +0 -32
package/src/commands/install.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
2
|
import { colors } from "consola/utils";
|
|
3
3
|
import consola from "consola";
|
|
4
|
-
import {
|
|
4
|
+
import { debugArg } from "./shared.js";
|
|
5
5
|
import {
|
|
6
6
|
CLAUDE_SETTINGS_PATH,
|
|
7
7
|
loadClaudeSettings,
|
|
@@ -9,159 +9,135 @@ import {
|
|
|
9
9
|
saveClaudeSettings,
|
|
10
10
|
} from "../lib/install/claude-settings.js";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
command: "<%= config.bin %> <%= command.id %>",
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
description: "Include OTEL setup instructions",
|
|
26
|
-
command: "<%= config.bin %> <%= command.id %> --observability",
|
|
27
|
-
},
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
static override flags = {
|
|
31
|
-
...BaseCommand.baseFlags,
|
|
32
|
-
observability: Flags.boolean({
|
|
33
|
-
char: "o",
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
meta: {
|
|
14
|
+
name: "install",
|
|
15
|
+
description: "Configure Claude Code settings and optionally enable observability",
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
debug: debugArg,
|
|
19
|
+
observability: {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
alias: "o",
|
|
34
22
|
description: "Show OTEL setup instructions and configure SubagentStop hook",
|
|
35
23
|
default: false,
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const { flags } = await this.parse(Install);
|
|
41
|
-
|
|
42
|
-
this.log(colors.bold("\nš§ towles-tool install\n"));
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
async run({ args }) {
|
|
27
|
+
consola.log(colors.bold("\nš§ towles-tool install\n"));
|
|
43
28
|
|
|
44
|
-
// Load or create Claude settings
|
|
45
29
|
const existing = loadClaudeSettings(CLAUDE_SETTINGS_PATH);
|
|
46
30
|
if (Object.keys(existing).length > 0) {
|
|
47
|
-
|
|
31
|
+
consola.log(colors.dim(`Found existing Claude settings at ${CLAUDE_SETTINGS_PATH}`));
|
|
48
32
|
} else {
|
|
49
|
-
|
|
33
|
+
consola.log(colors.dim(`No Claude settings file found, will create one`));
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
// Apply recommended settings
|
|
53
36
|
const { settings, changes } = applyRecommendedSettings(existing);
|
|
54
37
|
|
|
55
38
|
for (const change of changes) {
|
|
56
|
-
|
|
39
|
+
consola.log(colors.green(`ā ${change}`));
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
// Report already-correct settings
|
|
60
42
|
if (!changes.some((c) => c.includes("cleanupPeriodDays"))) {
|
|
61
|
-
|
|
43
|
+
consola.log(colors.dim("ā cleanupPeriodDays already set to 99999"));
|
|
62
44
|
}
|
|
63
45
|
if (!changes.some((c) => c.includes("alwaysThinkingEnabled"))) {
|
|
64
|
-
|
|
46
|
+
consola.log(colors.dim("ā alwaysThinkingEnabled already set to true"));
|
|
65
47
|
}
|
|
66
48
|
|
|
67
|
-
// Save settings if anything changed
|
|
68
49
|
if (changes.length > 0) {
|
|
69
50
|
saveClaudeSettings(CLAUDE_SETTINGS_PATH, settings);
|
|
70
|
-
|
|
51
|
+
consola.log(colors.green(`\nā Saved Claude settings to ${CLAUDE_SETTINGS_PATH}`));
|
|
71
52
|
}
|
|
72
53
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.showOtelInstructions();
|
|
54
|
+
if (args.observability) {
|
|
55
|
+
consola.log(colors.bold("\nš Observability Setup\n"));
|
|
56
|
+
showOtelInstructions();
|
|
77
57
|
}
|
|
78
58
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
await this.ensureClaudePlugins();
|
|
59
|
+
consola.log(colors.bold("\nš¦ Claude Plugins\n"));
|
|
60
|
+
await ensureClaudePlugins();
|
|
82
61
|
|
|
83
|
-
|
|
62
|
+
consola.log(colors.bold(colors.green("\nā
Installation complete!\n")));
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
async function ensureClaudePlugins(): Promise<void> {
|
|
67
|
+
const { x } = await import("tinyexec");
|
|
68
|
+
|
|
69
|
+
const requiredPlugins = [
|
|
70
|
+
{
|
|
71
|
+
id: "tt@towles-tool",
|
|
72
|
+
name: "tt-core",
|
|
73
|
+
marketplaceUrl: "https://github.com/ChrisTowles/towles-tool",
|
|
74
|
+
marketplace: "towles-tool",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "code-simplifier@claude-plugins-official",
|
|
78
|
+
name: "code-simplifier",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
let installedIds = new Set<string>();
|
|
83
|
+
try {
|
|
84
|
+
const result = await x("claude", ["plugin", "list", "--json"]);
|
|
85
|
+
const plugins: { id: string }[] = JSON.parse(result.stdout);
|
|
86
|
+
installedIds = new Set(plugins.map((p) => p.id));
|
|
87
|
+
} catch {
|
|
88
|
+
consola.log(colors.yellow("ā Could not list Claude plugins"));
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
marketplace: "towles-tool",
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "code-simplifier@claude-plugins-official",
|
|
98
|
-
name: "code-simplifier",
|
|
99
|
-
},
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
// Get installed plugins
|
|
103
|
-
let installedIds = new Set<string>();
|
|
104
|
-
try {
|
|
105
|
-
const result = await x("claude", ["plugin", "list", "--json"]);
|
|
106
|
-
const plugins: { id: string }[] = JSON.parse(result.stdout);
|
|
107
|
-
installedIds = new Set(plugins.map((p) => p.id));
|
|
108
|
-
} catch {
|
|
109
|
-
this.log(colors.yellow("ā Could not list Claude plugins"));
|
|
91
|
+
for (const plugin of requiredPlugins) {
|
|
92
|
+
if (plugin.marketplaceUrl && !installedIds.has(plugin.id)) {
|
|
93
|
+
try {
|
|
94
|
+
await x("claude", ["plugin", "marketplace", "add", plugin.marketplaceUrl]);
|
|
95
|
+
consola.log(colors.dim(` Added marketplace: ${plugin.marketplace}`));
|
|
96
|
+
} catch {
|
|
97
|
+
// marketplace may already be added
|
|
98
|
+
}
|
|
110
99
|
}
|
|
100
|
+
}
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await x("claude", ["plugin", "marketplace", "add", plugin.marketplaceUrl]);
|
|
117
|
-
this.log(colors.dim(` Added marketplace: ${plugin.marketplace}`));
|
|
118
|
-
} catch {
|
|
119
|
-
// marketplace may already be added
|
|
120
|
-
}
|
|
121
|
-
}
|
|
102
|
+
for (const plugin of requiredPlugins) {
|
|
103
|
+
if (installedIds.has(plugin.id)) {
|
|
104
|
+
consola.log(colors.dim(`ā ${plugin.name} already installed`));
|
|
105
|
+
continue;
|
|
122
106
|
}
|
|
123
107
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
108
|
+
const answer = await consola.prompt(`Install ${plugin.name} plugin?`, {
|
|
109
|
+
type: "confirm",
|
|
110
|
+
initial: true,
|
|
111
|
+
});
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (answer) {
|
|
137
|
-
const result = await x("claude", ["plugin", "install", plugin.id, "--scope", "user"]);
|
|
138
|
-
if (result.exitCode === 0) {
|
|
139
|
-
this.log(colors.green(`ā ${plugin.name} installed`));
|
|
140
|
-
} else {
|
|
141
|
-
if (result.stdout) this.log(result.stdout);
|
|
142
|
-
if (result.stderr) this.log(colors.dim(result.stderr));
|
|
143
|
-
this.log(colors.yellow(`ā ${plugin.name} install exited with code ${result.exitCode}`));
|
|
144
|
-
}
|
|
113
|
+
if (answer) {
|
|
114
|
+
const result = await x("claude", ["plugin", "install", plugin.id, "--scope", "user"]);
|
|
115
|
+
if (result.exitCode === 0) {
|
|
116
|
+
consola.log(colors.green(`ā ${plugin.name} installed`));
|
|
145
117
|
} else {
|
|
146
|
-
|
|
118
|
+
if (result.stdout) consola.log(result.stdout);
|
|
119
|
+
if (result.stderr) consola.log(colors.dim(result.stderr));
|
|
120
|
+
consola.log(colors.yellow(`ā ${plugin.name} install exited with code ${result.exitCode}`));
|
|
147
121
|
}
|
|
122
|
+
} else {
|
|
123
|
+
consola.log(colors.dim(` Skipped ${plugin.name}`));
|
|
148
124
|
}
|
|
149
125
|
}
|
|
126
|
+
}
|
|
150
127
|
|
|
151
|
-
|
|
152
|
-
|
|
128
|
+
function showOtelInstructions(): void {
|
|
129
|
+
consola.log(colors.cyan("Add these environment variables to your shell profile:\n"));
|
|
153
130
|
|
|
154
|
-
|
|
131
|
+
consola.box(`export CLAUDE_CODE_ENABLE_TELEMETRY=1
|
|
155
132
|
export OTEL_METRICS_EXPORTER=otlp
|
|
156
133
|
export OTEL_LOGS_EXPORTER=otlp
|
|
157
134
|
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317`);
|
|
158
135
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
136
|
+
consola.log("");
|
|
137
|
+
consola.log(
|
|
138
|
+
colors.dim("For more info, see: https://github.com/anthropics/claude-code-monitoring-guide"),
|
|
139
|
+
);
|
|
140
|
+
consola.log("");
|
|
141
|
+
consola.log(colors.cyan("Quick cost analysis (no setup required):"));
|
|
142
|
+
consola.log(colors.dim(" npx ccusage@latest --breakdown"));
|
|
167
143
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
4
5
|
import consola from "consola";
|
|
5
6
|
import { colors } from "consola/utils";
|
|
6
|
-
import {
|
|
7
|
+
import { withSettings, debugArg } from "../shared.js";
|
|
7
8
|
import { JOURNAL_TYPES } from "../../types/journal.js";
|
|
8
9
|
import {
|
|
9
10
|
createJournalContent,
|
|
@@ -13,29 +14,21 @@ import {
|
|
|
13
14
|
openInEditor,
|
|
14
15
|
} from "../../lib/journal/index.js";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
command: "<%= config.bin %> <%= command.id %>",
|
|
27
|
-
},
|
|
28
|
-
{ description: "Using alias", command: "<%= config.bin %> today" },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
async run(): Promise<void> {
|
|
32
|
-
await this.parse(DailyNotes);
|
|
17
|
+
export default defineCommand({
|
|
18
|
+
meta: {
|
|
19
|
+
name: "daily-notes",
|
|
20
|
+
description: "Weekly files with daily sections for ongoing work and notes",
|
|
21
|
+
},
|
|
22
|
+
args: {
|
|
23
|
+
debug: debugArg,
|
|
24
|
+
},
|
|
25
|
+
async run({ args }) {
|
|
26
|
+
const { settings } = await withSettings(args.debug);
|
|
33
27
|
|
|
34
28
|
try {
|
|
35
|
-
const journalSettings =
|
|
29
|
+
const journalSettings = settings.journalSettings;
|
|
36
30
|
const templateDir = journalSettings.templateDir;
|
|
37
31
|
|
|
38
|
-
// Ensure templates exist on first run
|
|
39
32
|
ensureTemplatesExist(templateDir);
|
|
40
33
|
|
|
41
34
|
const currentDate = new Date();
|
|
@@ -46,7 +39,6 @@ export default class DailyNotes extends BaseCommand {
|
|
|
46
39
|
title: "",
|
|
47
40
|
});
|
|
48
41
|
|
|
49
|
-
// Ensure journal directory exists
|
|
50
42
|
ensureDirectoryExists(path.dirname(fileInfo.fullPath));
|
|
51
43
|
|
|
52
44
|
if (existsSync(fileInfo.fullPath)) {
|
|
@@ -58,7 +50,7 @@ export default class DailyNotes extends BaseCommand {
|
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
await openInEditor({
|
|
61
|
-
editor:
|
|
53
|
+
editor: settings.preferredEditor,
|
|
62
54
|
filePath: fileInfo.fullPath,
|
|
63
55
|
folderPath: journalSettings.baseFolder,
|
|
64
56
|
});
|
|
@@ -66,5 +58,5 @@ export default class DailyNotes extends BaseCommand {
|
|
|
66
58
|
consola.warn(`Error creating daily-notes file:`, error);
|
|
67
59
|
process.exit(1);
|
|
68
60
|
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
|
|
3
|
+
export default defineCommand({
|
|
4
|
+
meta: { name: "journal", description: "Journal and note-taking commands" },
|
|
5
|
+
subCommands: {
|
|
6
|
+
"daily-notes": () => import("./daily-notes.js").then((m) => m.default),
|
|
7
|
+
note: () => import("./note.js").then((m) => m.default),
|
|
8
|
+
meeting: () => import("./meeting.js").then((m) => m.default),
|
|
9
|
+
},
|
|
10
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import {
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import { colors } from "consola/utils";
|
|
7
|
-
import {
|
|
7
|
+
import { withSettings, debugArg } from "../shared.js";
|
|
8
8
|
import { JOURNAL_TYPES } from "../../types/journal.js";
|
|
9
9
|
import {
|
|
10
10
|
createMeetingContent,
|
|
@@ -14,42 +14,25 @@ import {
|
|
|
14
14
|
openInEditor,
|
|
15
15
|
} from "../../lib/journal/index.js";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
static override args = {
|
|
24
|
-
title: Args.string({
|
|
25
|
-
description: "Meeting title",
|
|
17
|
+
export default defineCommand({
|
|
18
|
+
meta: { name: "meeting", description: "Structured meeting notes with agenda and action items" },
|
|
19
|
+
args: {
|
|
20
|
+
debug: debugArg,
|
|
21
|
+
title: {
|
|
22
|
+
type: "positional",
|
|
26
23
|
required: false,
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
static override examples = [
|
|
31
|
-
{
|
|
32
|
-
description: "Create meeting note (prompts for title)",
|
|
33
|
-
command: "<%= config.bin %> <%= command.id %>",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
description: "Create with title",
|
|
37
|
-
command: '<%= config.bin %> <%= command.id %> "Sprint Planning"',
|
|
24
|
+
description: "Meeting title",
|
|
38
25
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async run(): Promise<void> {
|
|
43
|
-
const { args } = await this.parse(Meeting);
|
|
26
|
+
},
|
|
27
|
+
async run({ args }) {
|
|
28
|
+
const { settings } = await withSettings(args.debug);
|
|
44
29
|
|
|
45
30
|
try {
|
|
46
|
-
const journalSettings =
|
|
31
|
+
const journalSettings = settings.journalSettings;
|
|
47
32
|
const templateDir = journalSettings.templateDir;
|
|
48
33
|
|
|
49
|
-
// Ensure templates exist on first run
|
|
50
34
|
ensureTemplatesExist(templateDir);
|
|
51
35
|
|
|
52
|
-
// Prompt for title if not provided
|
|
53
36
|
let title = args.title || "";
|
|
54
37
|
if (title.trim().length === 0) {
|
|
55
38
|
title = await consola.prompt(`Enter meeting title:`, {
|
|
@@ -65,7 +48,6 @@ export default class Meeting extends BaseCommand {
|
|
|
65
48
|
title,
|
|
66
49
|
});
|
|
67
50
|
|
|
68
|
-
// Ensure journal directory exists
|
|
69
51
|
ensureDirectoryExists(path.dirname(fileInfo.fullPath));
|
|
70
52
|
|
|
71
53
|
if (existsSync(fileInfo.fullPath)) {
|
|
@@ -77,7 +59,7 @@ export default class Meeting extends BaseCommand {
|
|
|
77
59
|
}
|
|
78
60
|
|
|
79
61
|
await openInEditor({
|
|
80
|
-
editor:
|
|
62
|
+
editor: settings.preferredEditor,
|
|
81
63
|
filePath: fileInfo.fullPath,
|
|
82
64
|
folderPath: journalSettings.baseFolder,
|
|
83
65
|
});
|
|
@@ -85,5 +67,5 @@ export default class Meeting extends BaseCommand {
|
|
|
85
67
|
consola.warn(`Error creating meeting file:`, error);
|
|
86
68
|
process.exit(1);
|
|
87
69
|
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import {
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import { colors } from "consola/utils";
|
|
7
|
-
import {
|
|
7
|
+
import { withSettings, debugArg } from "../shared.js";
|
|
8
8
|
import { JOURNAL_TYPES } from "../../types/journal.js";
|
|
9
9
|
import {
|
|
10
10
|
createNoteContent,
|
|
@@ -14,42 +14,25 @@ import {
|
|
|
14
14
|
openInEditor,
|
|
15
15
|
} from "../../lib/journal/index.js";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
static override args = {
|
|
24
|
-
title: Args.string({
|
|
25
|
-
description: "Note title",
|
|
17
|
+
export default defineCommand({
|
|
18
|
+
meta: { name: "note", description: "General-purpose notes with structured sections" },
|
|
19
|
+
args: {
|
|
20
|
+
debug: debugArg,
|
|
21
|
+
title: {
|
|
22
|
+
type: "positional",
|
|
26
23
|
required: false,
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
static override examples = [
|
|
31
|
-
{
|
|
32
|
-
description: "Create note (prompts for title)",
|
|
33
|
-
command: "<%= config.bin %> <%= command.id %>",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
description: "Create with title",
|
|
37
|
-
command: '<%= config.bin %> <%= command.id %> "Research Notes"',
|
|
24
|
+
description: "Note title",
|
|
38
25
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async run(): Promise<void> {
|
|
43
|
-
const { args } = await this.parse(Note);
|
|
26
|
+
},
|
|
27
|
+
async run({ args }) {
|
|
28
|
+
const { settings } = await withSettings(args.debug);
|
|
44
29
|
|
|
45
30
|
try {
|
|
46
|
-
const journalSettings =
|
|
31
|
+
const journalSettings = settings.journalSettings;
|
|
47
32
|
const templateDir = journalSettings.templateDir;
|
|
48
33
|
|
|
49
|
-
// Ensure templates exist on first run
|
|
50
34
|
ensureTemplatesExist(templateDir);
|
|
51
35
|
|
|
52
|
-
// Prompt for title if not provided
|
|
53
36
|
let title = args.title || "";
|
|
54
37
|
if (title.trim().length === 0) {
|
|
55
38
|
title = await consola.prompt(`Enter note title:`, {
|
|
@@ -65,7 +48,6 @@ export default class Note extends BaseCommand {
|
|
|
65
48
|
title,
|
|
66
49
|
});
|
|
67
50
|
|
|
68
|
-
// Ensure journal directory exists
|
|
69
51
|
ensureDirectoryExists(path.dirname(fileInfo.fullPath));
|
|
70
52
|
|
|
71
53
|
if (existsSync(fileInfo.fullPath)) {
|
|
@@ -77,7 +59,7 @@ export default class Note extends BaseCommand {
|
|
|
77
59
|
}
|
|
78
60
|
|
|
79
61
|
await openInEditor({
|
|
80
|
-
editor:
|
|
62
|
+
editor: settings.preferredEditor,
|
|
81
63
|
filePath: fileInfo.fullPath,
|
|
82
64
|
folderPath: journalSettings.baseFolder,
|
|
83
65
|
});
|
|
@@ -85,5 +67,5 @@ export default class Note extends BaseCommand {
|
|
|
85
67
|
consola.warn(`Error creating note file:`, error);
|
|
86
68
|
process.exit(1);
|
|
87
69
|
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SettingsFile } from "../config/settings.js";
|
|
2
|
+
import { loadSettings } from "../config/settings.js";
|
|
3
|
+
|
|
4
|
+
export interface CommandContext {
|
|
5
|
+
settingsFile: SettingsFile;
|
|
6
|
+
settings: SettingsFile["settings"];
|
|
7
|
+
debug: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function withSettings(debug = false): Promise<CommandContext> {
|
|
11
|
+
const settingsFile = await loadSettings();
|
|
12
|
+
return { settingsFile, settings: settingsFile.settings, debug };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Common debug flag definition for citty args */
|
|
16
|
+
export const debugArg = {
|
|
17
|
+
type: "boolean" as const,
|
|
18
|
+
alias: "d",
|
|
19
|
+
description: "Enable debug output",
|
|
20
|
+
default: false,
|
|
21
|
+
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
2
3
|
|
|
3
4
|
import { resolveTemplate } from "./templates";
|
|
4
5
|
import type { TemplateFsDeps, TokenValues } from "./templates";
|
|
5
6
|
|
|
6
|
-
function createMockFs(): TemplateFsDeps {
|
|
7
|
+
function createMockFs(): TemplateFsDeps & {
|
|
8
|
+
readFileSync: Mock;
|
|
9
|
+
writeFileSync: Mock;
|
|
10
|
+
mkdirSync: Mock;
|
|
11
|
+
} {
|
|
7
12
|
return {
|
|
8
13
|
readFileSync: vi.fn().mockReturnValue(""),
|
|
9
14
|
writeFileSync: vi.fn(),
|
|
@@ -12,7 +17,7 @@ function createMockFs(): TemplateFsDeps {
|
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
describe("resolveTemplate", () => {
|
|
15
|
-
let mockFs:
|
|
20
|
+
let mockFs: ReturnType<typeof createMockFs>;
|
|
16
21
|
|
|
17
22
|
beforeEach(() => {
|
|
18
23
|
mockFs = createMockFs();
|
|
@@ -25,13 +30,13 @@ describe("resolveTemplate", () => {
|
|
|
25
30
|
};
|
|
26
31
|
|
|
27
32
|
it("replaces all token placeholders in template", () => {
|
|
28
|
-
|
|
33
|
+
mockFs.readFileSync.mockReturnValue(
|
|
29
34
|
"Scope: {{SCOPE_PATH}}\nDir: {{ISSUE_DIR}}\nBranch: {{MAIN_BRANCH}}",
|
|
30
35
|
);
|
|
31
36
|
|
|
32
37
|
resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
|
|
33
38
|
|
|
34
|
-
const writtenContent =
|
|
39
|
+
const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
|
|
35
40
|
expect(writtenContent).toContain("/home/user/project");
|
|
36
41
|
expect(writtenContent).toContain("/tmp/issues/42");
|
|
37
42
|
expect(writtenContent).toContain("main");
|
|
@@ -43,16 +48,16 @@ describe("resolveTemplate", () => {
|
|
|
43
48
|
...tokens,
|
|
44
49
|
REVIEW_FEEDBACK: "Needs more tests",
|
|
45
50
|
};
|
|
46
|
-
|
|
51
|
+
mockFs.readFileSync.mockReturnValue("Feedback: {{REVIEW_FEEDBACK}}");
|
|
47
52
|
|
|
48
53
|
resolveTemplate("review.md", tokensWithFeedback, "/tmp/issues/42", mockFs);
|
|
49
54
|
|
|
50
|
-
const writtenContent =
|
|
55
|
+
const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
|
|
51
56
|
expect(writtenContent).toContain("Needs more tests");
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
it("creates output directory recursively", () => {
|
|
55
|
-
|
|
60
|
+
mockFs.readFileSync.mockReturnValue("template content");
|
|
56
61
|
|
|
57
62
|
resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
|
|
58
63
|
|
|
@@ -60,7 +65,7 @@ describe("resolveTemplate", () => {
|
|
|
60
65
|
});
|
|
61
66
|
|
|
62
67
|
it("writes resolved template to issue dir", () => {
|
|
63
|
-
|
|
68
|
+
mockFs.readFileSync.mockReturnValue("simple content");
|
|
64
69
|
|
|
65
70
|
resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
|
|
66
71
|
|
|
@@ -72,7 +77,7 @@ describe("resolveTemplate", () => {
|
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
it("returns relative path from cwd", () => {
|
|
75
|
-
|
|
80
|
+
mockFs.readFileSync.mockReturnValue("content");
|
|
76
81
|
|
|
77
82
|
const result = resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
|
|
78
83
|
// Should be a relative path (not starting with /)
|
|
@@ -80,11 +85,11 @@ describe("resolveTemplate", () => {
|
|
|
80
85
|
});
|
|
81
86
|
|
|
82
87
|
it("handles multiple occurrences of the same token", () => {
|
|
83
|
-
|
|
88
|
+
mockFs.readFileSync.mockReturnValue("{{MAIN_BRANCH}} and {{MAIN_BRANCH}} again");
|
|
84
89
|
|
|
85
90
|
resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
|
|
86
91
|
|
|
87
|
-
const writtenContent =
|
|
92
|
+
const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
|
|
88
93
|
expect(writtenContent).toBe("main and main again");
|
|
89
94
|
});
|
|
90
95
|
});
|