@towles/tool 0.0.109 → 0.0.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/package.json +9 -4
  2. package/{plugins/tt-agentboard → packages/agentboard}/README.md +1 -1
  3. package/{plugins/tt-agentboard → packages/agentboard}/apps/server/package.json +2 -1
  4. package/{plugins/tt-agentboard → packages/agentboard}/apps/server/src/main.ts +6 -20
  5. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/package.json +4 -0
  6. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DetailPanel.tsx +3 -2
  7. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/StatusBar.tsx +35 -0
  8. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/constants.ts +1 -0
  9. package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/index.tsx +206 -226
  10. package/packages/agentboard/apps/tui/src/session-status.test.ts +70 -0
  11. package/packages/agentboard/apps/tui/src/session-status.ts +19 -0
  12. package/{plugins/tt-agentboard → packages/agentboard}/package.json +2 -6
  13. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/package.json +3 -0
  14. package/{plugins/tt-agentboard/packages/runtime/test → packages/agentboard/packages/runtime/src/agents}/tracker.test.ts +2 -2
  15. package/packages/agentboard/packages/runtime/src/agents/watchers/claude-code.test.ts +63 -0
  16. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/claude-code.ts +26 -2
  17. package/packages/agentboard/packages/runtime/src/config.test.ts +107 -0
  18. package/packages/agentboard/packages/runtime/src/config.ts +80 -0
  19. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/index.ts +1 -1
  20. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/plugins/loader.ts +1 -33
  21. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/git-info.ts +3 -2
  22. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/index.ts +23 -37
  23. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/launcher.ts +6 -18
  24. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/pane-scanner.ts +6 -0
  25. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/shared.ts +7 -2
  26. package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/tsconfig.json +1 -1
  27. package/packages/shared/package.json +15 -0
  28. package/packages/shared/src/git/exec.ts +41 -0
  29. package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.ts +13 -18
  30. package/packages/shared/src/index.ts +8 -0
  31. package/packages/shared/tsconfig.json +16 -0
  32. package/src/cli.ts +1 -1
  33. package/src/commands/agentboard.ts +51 -67
  34. package/src/{lib → commands}/auto-claude/claude-cli.ts +1 -1
  35. package/src/commands/auto-claude/config-init-helpers.ts +79 -0
  36. package/src/commands/auto-claude/config-init.test.ts +137 -0
  37. package/src/commands/auto-claude/config-init.ts +159 -0
  38. package/src/{lib → commands}/auto-claude/config.ts +4 -8
  39. package/src/{lib → commands}/auto-claude/e2e.test.ts +6 -6
  40. package/src/commands/auto-claude/explain.test.ts +58 -0
  41. package/src/commands/auto-claude/explain.ts +97 -0
  42. package/src/commands/auto-claude/index.ts +37 -14
  43. package/src/{lib → commands}/auto-claude/labels.ts +1 -1
  44. package/src/commands/auto-claude/list.ts +5 -4
  45. package/src/{lib → commands}/auto-claude/pipeline-execution.test.ts +1 -1
  46. package/src/{lib → commands}/auto-claude/pipeline.ts +1 -3
  47. package/src/commands/auto-claude/retry.test.ts +2 -2
  48. package/src/commands/auto-claude/retry.ts +5 -5
  49. package/src/commands/auto-claude/shell.ts +3 -0
  50. package/src/commands/auto-claude/status.test.ts +2 -2
  51. package/src/commands/auto-claude/status.ts +4 -4
  52. package/src/{lib → commands}/auto-claude/steps/create-pr.ts +1 -3
  53. package/src/{lib → commands}/auto-claude/steps/fetch-issues.ts +1 -1
  54. package/src/{lib → commands}/auto-claude/steps/implement.ts +1 -2
  55. package/src/{lib → commands}/auto-claude/utils-execution.test.ts +6 -6
  56. package/src/{lib → commands}/auto-claude/utils.ts +10 -4
  57. package/src/{lib/install → commands}/claude-settings.ts +1 -1
  58. package/src/commands/config/config.test.ts +129 -0
  59. package/src/commands/config/index.ts +11 -0
  60. package/src/commands/config/reset.ts +53 -0
  61. package/src/commands/config/schema.ts +19 -0
  62. package/src/commands/{config.ts → config/show.ts} +2 -2
  63. package/src/commands/config/validate.ts +51 -0
  64. package/src/commands/doctor/checks.ts +167 -0
  65. package/src/commands/doctor/format.test.ts +63 -0
  66. package/src/commands/doctor/format.ts +5 -0
  67. package/src/commands/doctor/history.test.ts +161 -0
  68. package/src/commands/doctor/history.ts +130 -0
  69. package/src/commands/doctor.ts +80 -151
  70. package/src/commands/gh/branch-clean.ts +4 -4
  71. package/src/commands/gh/branch.test.ts +4 -5
  72. package/src/commands/gh/branch.ts +10 -5
  73. package/src/commands/gh/pr.ts +6 -7
  74. package/src/{lib → commands}/graph/analyzer.test.ts +4 -4
  75. package/src/commands/graph/format.test.ts +130 -0
  76. package/src/commands/graph/format.ts +94 -0
  77. package/src/commands/graph/index.ts +69 -41
  78. package/src/{lib → commands}/graph/labels.ts +4 -4
  79. package/src/{lib → commands}/graph/server.ts +2 -2
  80. package/src/{lib → commands}/graph/types.ts +2 -0
  81. package/src/commands/graph.test.ts +1 -1
  82. package/src/commands/install.ts +6 -6
  83. package/src/commands/journal/daily-notes.ts +4 -7
  84. package/src/{lib → commands}/journal/fs.ts +1 -1
  85. package/src/commands/journal/index.ts +2 -0
  86. package/src/commands/journal/list.test.ts +174 -0
  87. package/src/commands/journal/list.ts +213 -0
  88. package/src/commands/journal/meeting.ts +4 -7
  89. package/src/commands/journal/note.ts +4 -7
  90. package/src/{lib → commands}/journal/paths.ts +1 -1
  91. package/src/commands/journal/search.test.ts +156 -0
  92. package/src/commands/journal/search.ts +256 -0
  93. package/src/{lib → commands}/journal/templates.ts +1 -1
  94. package/src/config/settings.ts +35 -26
  95. package/plugins/tt-agentboard/bun.lock +0 -444
  96. package/plugins/tt-agentboard/packages/runtime/src/config.ts +0 -70
  97. package/plugins/tt-agentboard/packages/runtime/test/config.test.ts +0 -83
  98. package/plugins/tt-auto-claude/.claude-plugin/plugin.json +0 -8
  99. package/plugins/tt-auto-claude/commands/create-issue.md +0 -20
  100. package/plugins/tt-auto-claude/commands/list.md +0 -21
  101. package/plugins/tt-auto-claude/skills/auto-claude/SKILL.md +0 -71
  102. package/plugins/tt-core/promptfooconfig.interview-me.yaml +0 -155
  103. package/plugins/tt-core/promptfooconfig.refine-text.yaml +0 -242
  104. package/plugins/tt-core/promptfooconfig.tdd.yaml +0 -144
  105. package/plugins/tt-core/promptfooconfig.write-prd.yaml +0 -145
  106. package/src/commands/config.test.ts +0 -9
  107. package/src/lib/auto-claude/index.ts +0 -15
  108. package/src/lib/auto-claude/shell.ts +0 -6
  109. package/src/lib/graph/index.ts +0 -24
  110. package/src/lib/journal/index.ts +0 -11
  111. package/src/utils/git/exec.ts +0 -18
  112. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/build.ts +0 -0
  113. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/bunfig.toml +0 -0
  114. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/scripts/sessionizer.sh +0 -0
  115. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DiffStats.tsx +0 -0
  116. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/SessionCard.tsx +0 -0
  117. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/detail-panel-height.ts +0 -0
  118. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/mux-context.ts +0 -0
  119. /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/tsconfig.json +0 -0
  120. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/package.json +0 -0
  121. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/client.ts +0 -0
  122. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/index.ts +0 -0
  123. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/provider.ts +0 -0
  124. /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/tsconfig.json +0 -0
  125. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/tracker.ts +0 -0
  126. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/amp.ts +0 -0
  127. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/codex.ts +0 -0
  128. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/opencode.ts +0 -0
  129. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent-watcher.ts +0 -0
  130. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent.ts +0 -0
  131. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/index.ts +0 -0
  132. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/mux.ts +0 -0
  133. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/debug.ts +0 -0
  134. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/detect.ts +0 -0
  135. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/registry.ts +0 -0
  136. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/context.ts +0 -0
  137. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/metadata-store.ts +0 -0
  138. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/port-scanner.ts +0 -0
  139. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/session-order.ts +0 -0
  140. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-manager.ts +0 -0
  141. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-width-sync.ts +0 -0
  142. /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/themes.ts +0 -0
  143. /package/{plugins/tt-agentboard → packages/agentboard}/tsconfig.json +0 -0
  144. /package/{plugins/tt-core → packages/core}/.claude-plugin/plugin.json +0 -0
  145. /package/{plugins/tt-core → packages/core}/README.md +0 -0
  146. /package/{plugins/tt-core → packages/core}/commands/improve-architecture.md +0 -0
  147. /package/{plugins/tt-core → packages/core}/commands/interview-me.md +0 -0
  148. /package/{plugins/tt-core → packages/core}/commands/prd-to-issues.md +0 -0
  149. /package/{plugins/tt-core → packages/core}/commands/refine-text.md +0 -0
  150. /package/{plugins/tt-core → packages/core}/commands/task.md +0 -0
  151. /package/{plugins/tt-core → packages/core}/commands/tdd.md +0 -0
  152. /package/{plugins/tt-core → packages/core}/commands/write-prd.md +0 -0
  153. /package/{plugins/tt-core → packages/core}/skills/towles-tool/SKILL.md +0 -0
  154. /package/{src/utils → packages/shared/src}/date-utils.test.ts +0 -0
  155. /package/{src/utils → packages/shared/src}/date-utils.ts +0 -0
  156. /package/{src/utils → packages/shared/src}/fs.ts +0 -0
  157. /package/{src/utils → packages/shared/src}/git/branch-name.test.ts +0 -0
  158. /package/{src/utils → packages/shared/src}/git/branch-name.ts +0 -0
  159. /package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.test.ts +0 -0
  160. /package/{src/utils → packages/shared/src}/render.test.ts +0 -0
  161. /package/{src/utils → packages/shared/src}/render.ts +0 -0
  162. /package/src/{lib → commands}/auto-claude/config.test.ts +0 -0
  163. /package/src/{lib → commands}/auto-claude/labels.test.ts +0 -0
  164. /package/src/{lib → commands}/auto-claude/pipeline.test.ts +0 -0
  165. /package/src/{lib → commands}/auto-claude/prompt-templates/01_plan.prompt.md +0 -0
  166. /package/src/{lib → commands}/auto-claude/prompt-templates/02_implement.prompt.md +0 -0
  167. /package/src/{lib → commands}/auto-claude/prompt-templates/03_simplify.prompt.md +0 -0
  168. /package/src/{lib → commands}/auto-claude/prompt-templates/04_review.prompt.md +0 -0
  169. /package/src/{lib → commands}/auto-claude/prompt-templates/CLAUDE.md +0 -0
  170. /package/src/{lib → commands}/auto-claude/prompt-templates/index.test.ts +0 -0
  171. /package/src/{lib → commands}/auto-claude/prompt-templates/index.ts +0 -0
  172. /package/src/{lib → commands}/auto-claude/run-claude.test.ts +0 -0
  173. /package/src/{lib → commands}/auto-claude/spawn-claude.ts +0 -0
  174. /package/src/{lib → commands}/auto-claude/steps/simple-steps.ts +0 -0
  175. /package/src/{lib → commands}/auto-claude/steps/steps.test.ts +0 -0
  176. /package/src/{lib → commands}/auto-claude/stream-parser.test.ts +0 -0
  177. /package/src/{lib → commands}/auto-claude/stream-parser.ts +0 -0
  178. /package/src/{lib → commands}/auto-claude/templates.test.ts +0 -0
  179. /package/src/{lib → commands}/auto-claude/templates.ts +0 -0
  180. /package/src/{lib → commands}/auto-claude/test-helpers.ts +0 -0
  181. /package/src/{lib → commands}/auto-claude/utils.test.ts +0 -0
  182. /package/src/{lib → commands}/graph/analyzer.ts +0 -0
  183. /package/src/{lib → commands}/graph/graph-template.html +0 -0
  184. /package/src/{lib → commands}/graph/parser.test.ts +0 -0
  185. /package/src/{lib → commands}/graph/parser.ts +0 -0
  186. /package/src/{lib → commands}/graph/render.ts +0 -0
  187. /package/src/{lib → commands}/graph/sessions.ts +0 -0
  188. /package/src/{lib → commands}/graph/tools.ts +0 -0
  189. /package/src/{lib → commands}/graph/treemap.ts +0 -0
  190. /package/src/{lib → commands}/journal/editor.ts +0 -0
@@ -0,0 +1,256 @@
1
+ import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { defineCommand } from "citty";
5
+ import consola from "consola";
6
+ import { colors } from "consola/utils";
7
+ import { withSettings, debugArg } from "../shared.js";
8
+ import type { JournalType } from "../../types/journal.js";
9
+ import { JOURNAL_TYPES } from "../../types/journal.js";
10
+
11
+ export interface SearchMatch {
12
+ filePath: string;
13
+ lineNumber: number;
14
+ line: string;
15
+ context: string[];
16
+ }
17
+
18
+ export interface SearchOptions {
19
+ query: string;
20
+ type?: JournalType;
21
+ startDate?: Date;
22
+ endDate?: Date;
23
+ contextLines?: number;
24
+ }
25
+
26
+ /**
27
+ * Recursively collect all .md files under a directory.
28
+ */
29
+ export function collectMarkdownFiles(dir: string): string[] {
30
+ const results: string[] = [];
31
+ let entries: string[];
32
+ try {
33
+ entries = readdirSync(dir);
34
+ } catch {
35
+ return results;
36
+ }
37
+ for (const entry of entries) {
38
+ const full = path.join(dir, entry);
39
+ let stat;
40
+ try {
41
+ stat = statSync(full);
42
+ } catch {
43
+ continue;
44
+ }
45
+ if (stat.isDirectory()) {
46
+ results.push(...collectMarkdownFiles(full));
47
+ } else if (entry.endsWith(".md")) {
48
+ results.push(full);
49
+ }
50
+ }
51
+ return results;
52
+ }
53
+
54
+ /**
55
+ * Determine journal type from a file path based on directory names.
56
+ */
57
+ export function inferTypeFromPath(filePath: string): JournalType | null {
58
+ const lower = filePath.toLowerCase();
59
+ if (lower.includes("/daily-notes/") || lower.includes("daily-notes")) {
60
+ return JOURNAL_TYPES.DAILY_NOTES;
61
+ }
62
+ if (lower.includes("/meetings/") || lower.includes("/meeting/")) {
63
+ return JOURNAL_TYPES.MEETING;
64
+ }
65
+ if (lower.includes("/notes/") || lower.includes("/note/")) {
66
+ return JOURNAL_TYPES.NOTE;
67
+ }
68
+ return null;
69
+ }
70
+
71
+ /**
72
+ * Extract a date from a filename like `2026-03-15-*.md`.
73
+ * Returns null if no date pattern is found.
74
+ */
75
+ export function extractDateFromFilename(filePath: string): Date | null {
76
+ const basename = path.basename(filePath);
77
+ const match = basename.match(/^(\d{4})-(\d{2})-(\d{2})/);
78
+ if (!match) return null;
79
+ const [, year, month, day] = match;
80
+ return new Date(Number(year), Number(month) - 1, Number(day));
81
+ }
82
+
83
+ /**
84
+ * Search journal files for a query string, returning matches with context.
85
+ */
86
+ export function searchJournalFiles(files: string[], options: SearchOptions): SearchMatch[] {
87
+ const { query, type, startDate, endDate, contextLines = 2 } = options;
88
+ const lowerQuery = query.toLowerCase();
89
+ const matches: SearchMatch[] = [];
90
+
91
+ for (const filePath of files) {
92
+ // Filter by type
93
+ if (type) {
94
+ const fileType = inferTypeFromPath(filePath);
95
+ if (fileType !== type) continue;
96
+ }
97
+
98
+ // Filter by date range
99
+ if (startDate || endDate) {
100
+ const fileDate = extractDateFromFilename(filePath);
101
+ if (fileDate) {
102
+ if (startDate && fileDate < startDate) continue;
103
+ if (endDate && fileDate > endDate) continue;
104
+ }
105
+ }
106
+
107
+ let content: string;
108
+ try {
109
+ content = readFileSync(filePath, "utf8");
110
+ } catch {
111
+ continue;
112
+ }
113
+
114
+ const lines = content.split("\n");
115
+ for (let i = 0; i < lines.length; i++) {
116
+ if (lines[i].toLowerCase().includes(lowerQuery)) {
117
+ const ctxStart = Math.max(0, i - contextLines);
118
+ const ctxEnd = Math.min(lines.length - 1, i + contextLines);
119
+ const context: string[] = [];
120
+ for (let j = ctxStart; j <= ctxEnd; j++) {
121
+ const prefix = j === i ? ">" : " ";
122
+ context.push(`${prefix} ${j + 1}: ${lines[j]}`);
123
+ }
124
+ matches.push({
125
+ filePath,
126
+ lineNumber: i + 1,
127
+ line: lines[i],
128
+ context,
129
+ });
130
+ }
131
+ }
132
+ }
133
+
134
+ return matches;
135
+ }
136
+
137
+ /**
138
+ * Parse a date range string like `2026-01-01..2026-03-01`.
139
+ */
140
+ export function parseDateRange(range: string): { startDate: Date; endDate: Date } {
141
+ const parts = range.split("..");
142
+ if (parts.length !== 2) {
143
+ throw new Error(`Invalid date range format: "${range}". Expected: YYYY-MM-DD..YYYY-MM-DD`);
144
+ }
145
+ const [startStr, endStr] = parts;
146
+ const startDate = new Date(startStr);
147
+ const endDate = new Date(endStr);
148
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
149
+ throw new TypeError(`Invalid dates in range: "${range}". Expected: YYYY-MM-DD..YYYY-MM-DD`);
150
+ }
151
+ return { startDate, endDate };
152
+ }
153
+
154
+ const VALID_TYPES = new Set<string>([
155
+ JOURNAL_TYPES.DAILY_NOTES,
156
+ JOURNAL_TYPES.MEETING,
157
+ JOURNAL_TYPES.NOTE,
158
+ ]);
159
+
160
+ export default defineCommand({
161
+ meta: {
162
+ name: "search",
163
+ description: "Search journal entries for matching text",
164
+ },
165
+ args: {
166
+ debug: debugArg,
167
+ query: {
168
+ type: "positional",
169
+ required: true,
170
+ description: "Text to search for",
171
+ },
172
+ type: {
173
+ type: "string",
174
+ alias: "t",
175
+ description: "Filter by entry type: daily-notes, meeting, note",
176
+ },
177
+ range: {
178
+ type: "string",
179
+ alias: "r",
180
+ description: "Filter by date range: YYYY-MM-DD..YYYY-MM-DD",
181
+ },
182
+ },
183
+ async run({ args }) {
184
+ const { settings } = await withSettings(args.debug);
185
+
186
+ try {
187
+ const baseFolder = settings.journalSettings.baseFolder;
188
+ const journalDir = path.join(baseFolder, "journal");
189
+
190
+ // Validate --type
191
+ let typeFilter: JournalType | undefined;
192
+ if (args.type) {
193
+ if (!VALID_TYPES.has(args.type)) {
194
+ consola.error(
195
+ `Invalid type "${args.type}". Must be one of: ${[...VALID_TYPES].join(", ")}`,
196
+ );
197
+ process.exit(1);
198
+ }
199
+ typeFilter = args.type as JournalType;
200
+ }
201
+
202
+ // Parse --range
203
+ let startDate: Date | undefined;
204
+ let endDate: Date | undefined;
205
+ if (args.range) {
206
+ const parsed = parseDateRange(args.range);
207
+ startDate = parsed.startDate;
208
+ endDate = parsed.endDate;
209
+ }
210
+
211
+ const files = collectMarkdownFiles(journalDir);
212
+ if (files.length === 0) {
213
+ consola.info(`No journal files found in ${colors.cyan(journalDir)}`);
214
+ return;
215
+ }
216
+
217
+ const matches = searchJournalFiles(files, {
218
+ query: args.query,
219
+ type: typeFilter,
220
+ startDate,
221
+ endDate,
222
+ });
223
+
224
+ if (matches.length === 0) {
225
+ consola.info(`No matches found for "${colors.cyan(args.query)}"`);
226
+ return;
227
+ }
228
+
229
+ consola.info(
230
+ `Found ${colors.green(String(matches.length))} match(es) for "${colors.cyan(args.query)}":\n`,
231
+ );
232
+
233
+ // Group matches by file
234
+ const byFile = new Map<string, SearchMatch[]>();
235
+ for (const m of matches) {
236
+ const existing = byFile.get(m.filePath) ?? [];
237
+ existing.push(m);
238
+ byFile.set(m.filePath, existing);
239
+ }
240
+
241
+ for (const [filePath, fileMatches] of byFile) {
242
+ const relative = path.relative(baseFolder, filePath);
243
+ console.log(colors.bold(colors.cyan(relative)));
244
+ for (const m of fileMatches) {
245
+ for (const line of m.context) {
246
+ console.log(line);
247
+ }
248
+ console.log("");
249
+ }
250
+ }
251
+ } catch (error) {
252
+ consola.error(`Search failed:`, error);
253
+ process.exit(1);
254
+ }
255
+ },
256
+ });
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import consola from "consola";
4
4
  import { colors } from "consola/utils";
5
- import { formatDate, getWeekInfo } from "../../utils/date-utils.js";
5
+ import { formatDate, getWeekInfo } from "@towles/shared";
6
6
  import { ensureDirectoryExists } from "./fs.js";
7
7
 
8
8
  // Default template file names
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod/v4";
2
- import * as fs from "node:fs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import consola from "consola";
@@ -36,11 +36,33 @@ export const JournalSettingsSchema = z.object({
36
36
 
37
37
  export type JournalSettings = z.infer<typeof JournalSettingsSchema>;
38
38
 
39
+ export const AgentboardSettingsSchema = z.object({
40
+ mux: z.string().optional(),
41
+ port: z.number().optional(),
42
+ theme: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
43
+ sidebarWidth: z.number().optional(),
44
+ sidebarPosition: z.enum(["left", "right"]).optional(),
45
+ keybinding: z.string().optional(),
46
+ detailPanelHeights: z.record(z.string(), z.number()).optional(),
47
+ });
48
+
49
+ export type AgentboardSettings = z.infer<typeof AgentboardSettingsSchema>;
50
+
39
51
  export const UserSettingsSchema = z.object({
40
52
  preferredEditor: z.string().default("code"),
41
53
  journalSettings: JournalSettingsSchema.optional().transform(
42
54
  (v) => v ?? JournalSettingsSchema.parse({}),
43
55
  ),
56
+ agentboard: AgentboardSettingsSchema.optional().transform(
57
+ (v) => v ?? AgentboardSettingsSchema.parse({}),
58
+ ),
59
+ });
60
+
61
+ /** Schema without transforms — safe for JSON Schema export */
62
+ export const UserSettingsRawSchema = z.object({
63
+ preferredEditor: z.string().default("code"),
64
+ journalSettings: JournalSettingsSchema.optional(),
65
+ agentboard: AgentboardSettingsSchema.optional(),
44
66
  });
45
67
 
46
68
  type UserSettings = z.infer<typeof UserSettingsSchema>;
@@ -54,24 +76,20 @@ function createDefaultSettings(): UserSettings {
54
76
  return UserSettingsSchema.parse({});
55
77
  }
56
78
 
57
- function createAndSaveDefaultSettings(): UserSettings {
79
+ async function createAndSaveDefaultSettings(): Promise<UserSettings> {
58
80
  const userSettings = createDefaultSettings();
59
- saveSettings({
81
+ await saveSettings({
60
82
  path: USER_SETTINGS_PATH,
61
83
  settings: userSettings,
62
84
  });
63
85
  return userSettings;
64
86
  }
65
87
 
66
- export function saveSettings(settingsFile: SettingsFile): void {
88
+ export async function saveSettings(settingsFile: SettingsFile): Promise<void> {
67
89
  try {
68
- // Ensure the directory exists
69
90
  const dirPath = path.dirname(settingsFile.path);
70
- if (!fs.existsSync(dirPath)) {
71
- fs.mkdirSync(dirPath, { recursive: true });
72
- }
73
-
74
- fs.writeFileSync(settingsFile.path, JSON.stringify(settingsFile.settings, null, 2), "utf-8");
91
+ await mkdir(dirPath, { recursive: true });
92
+ await writeFile(settingsFile.path, JSON.stringify(settingsFile.settings, null, 2), "utf-8");
75
93
  } catch (error) {
76
94
  consola.error("Error saving user settings file:", error);
77
95
  }
@@ -80,32 +98,23 @@ export function saveSettings(settingsFile: SettingsFile): void {
80
98
  export async function loadSettings(): Promise<SettingsFile> {
81
99
  let userSettings: UserSettings | null = null;
82
100
 
83
- // Load user settings
84
- if (fs.existsSync(USER_SETTINGS_PATH)) {
85
- const userContent = fs.readFileSync(USER_SETTINGS_PATH, "utf-8");
101
+ try {
102
+ const userContent = await readFile(USER_SETTINGS_PATH, "utf-8");
86
103
  const parsedUserSettings: unknown = JSON.parse(userContent);
87
104
 
88
105
  userSettings = UserSettingsSchema.parse(parsedUserSettings);
89
- // made add a save here if the default values differ from the current values
90
106
  if (JSON.stringify(parsedUserSettings) !== JSON.stringify(userSettings)) {
91
107
  consola.warn(`Settings file ${USER_SETTINGS_PATH} has been updated with default values.`);
92
- const tempSettingsFile: SettingsFile = {
93
- path: USER_SETTINGS_PATH,
94
- settings: userSettings,
95
- };
96
-
97
- saveSettings(tempSettingsFile);
108
+ await saveSettings({ path: USER_SETTINGS_PATH, settings: userSettings });
98
109
  }
99
- } else {
100
- // Settings file doesn't exist
110
+ } catch {
111
+ // Settings file doesn't exist — create it
101
112
  const isNonInteractive = process.env.CI || !process.stdout.isTTY;
102
113
 
103
114
  if (isNonInteractive) {
104
- // Auto-create in CI/non-TTY environments
105
115
  consola.info(`Creating settings file: ${USER_SETTINGS_PATH}`);
106
- userSettings = createAndSaveDefaultSettings();
116
+ userSettings = await createAndSaveDefaultSettings();
107
117
  } else {
108
- // Interactive: ask user if they want to create it
109
118
  const confirmed = await consola.prompt(
110
119
  `Settings file not found. Create ${colors.cyan(USER_SETTINGS_PATH)}?`,
111
120
  {
@@ -115,7 +124,7 @@ export async function loadSettings(): Promise<SettingsFile> {
115
124
  if (!confirmed) {
116
125
  throw new Error(`Settings file not found and user chose not to create it.`);
117
126
  }
118
- userSettings = createAndSaveDefaultSettings();
127
+ userSettings = await createAndSaveDefaultSettings();
119
128
  }
120
129
  }
121
130