@staff0rd/assist 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2003 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { execSync as execSync12 } from "child_process";
5
+ import { Command } from "commander";
6
+
7
+ // src/commands/commit.ts
8
+ import { execSync } from "child_process";
9
+ var MAX_MESSAGE_LENGTH = 50;
10
+ function commit(message) {
11
+ if (message.toLowerCase().includes("claude")) {
12
+ console.error("Error: Commit message must not reference Claude");
13
+ process.exit(1);
14
+ }
15
+ if (message.length > MAX_MESSAGE_LENGTH) {
16
+ console.error(
17
+ `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message.length})`
18
+ );
19
+ process.exit(1);
20
+ }
21
+ try {
22
+ execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
23
+ stdio: "inherit"
24
+ });
25
+ process.exit(0);
26
+ } catch (_error) {
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ // src/commands/deploy/init.ts
32
+ import { execSync as execSync2 } from "child_process";
33
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
34
+ import { dirname, join } from "path";
35
+ import { fileURLToPath } from "url";
36
+ import chalk2 from "chalk";
37
+ import enquirer from "enquirer";
38
+
39
+ // src/utils/printDiff.ts
40
+ import chalk from "chalk";
41
+ import * as diff from "diff";
42
+ function normalizeJson(content) {
43
+ try {
44
+ return JSON.stringify(JSON.parse(content), null, 2);
45
+ } catch {
46
+ return content;
47
+ }
48
+ }
49
+ function printDiff(oldContent, newContent) {
50
+ const changes = diff.diffLines(
51
+ normalizeJson(oldContent),
52
+ normalizeJson(newContent)
53
+ );
54
+ for (const change of changes) {
55
+ const lines = change.value.replace(/\n$/, "").split("\n");
56
+ for (const line of lines) {
57
+ if (change.added) {
58
+ console.log(chalk.green(`+ ${line}`));
59
+ } else if (change.removed) {
60
+ console.log(chalk.red(`- ${line}`));
61
+ } else {
62
+ console.log(chalk.dim(` ${line}`));
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ // src/commands/deploy/init.ts
69
+ var WORKFLOW_PATH = ".github/workflows/build.yml";
70
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
71
+ function getExistingSiteId() {
72
+ if (!existsSync(WORKFLOW_PATH)) {
73
+ return null;
74
+ }
75
+ const content = readFileSync(WORKFLOW_PATH, "utf-8");
76
+ const match = content.match(/-s\s+([a-f0-9-]{36})/);
77
+ return match ? match[1] : null;
78
+ }
79
+ function getTemplateContent(siteId) {
80
+ const templatePath = join(__dirname2, "commands/deploy/build.yml");
81
+ const template = readFileSync(templatePath, "utf-8");
82
+ return template.replace("{{NETLIFY_SITE_ID}}", siteId);
83
+ }
84
+ async function updateWorkflow(siteId) {
85
+ const newContent = getTemplateContent(siteId);
86
+ const workflowDir = ".github/workflows";
87
+ if (!existsSync(workflowDir)) {
88
+ mkdirSync(workflowDir, { recursive: true });
89
+ }
90
+ if (existsSync(WORKFLOW_PATH)) {
91
+ const oldContent = readFileSync(WORKFLOW_PATH, "utf-8");
92
+ if (oldContent === newContent) {
93
+ console.log(chalk2.green("build.yml is already up to date"));
94
+ return;
95
+ }
96
+ console.log(chalk2.yellow("\nbuild.yml will be updated:"));
97
+ console.log();
98
+ printDiff(oldContent, newContent);
99
+ const { confirm } = await enquirer.prompt({
100
+ type: "confirm",
101
+ name: "confirm",
102
+ message: chalk2.red("Update build.yml?"),
103
+ initial: true
104
+ });
105
+ if (!confirm) {
106
+ console.log("Skipped build.yml update");
107
+ return;
108
+ }
109
+ }
110
+ writeFileSync(WORKFLOW_PATH, newContent);
111
+ console.log(chalk2.green(`
112
+ Created ${WORKFLOW_PATH}`));
113
+ }
114
+ async function init() {
115
+ console.log(chalk2.bold("Initializing Netlify deployment...\n"));
116
+ const existingSiteId = getExistingSiteId();
117
+ if (existingSiteId) {
118
+ console.log(chalk2.dim(`Using existing site ID: ${existingSiteId}
119
+ `));
120
+ await updateWorkflow(existingSiteId);
121
+ return;
122
+ }
123
+ console.log("Creating Netlify site...\n");
124
+ execSync2("netlify sites:create --disable-linking", {
125
+ stdio: "inherit"
126
+ });
127
+ const { siteId } = await enquirer.prompt({
128
+ type: "input",
129
+ name: "siteId",
130
+ message: "Enter the Site ID from above:",
131
+ validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
132
+ });
133
+ await updateWorkflow(siteId);
134
+ console.log(chalk2.bold("\nDeployment initialized successfully!"));
135
+ console.log(
136
+ chalk2.yellow("\nTo complete setup, create a personal access token at:")
137
+ );
138
+ console.log(
139
+ chalk2.cyan(
140
+ "https://app.netlify.com/user/applications#personal-access-tokens"
141
+ )
142
+ );
143
+ console.log(
144
+ chalk2.yellow(
145
+ "\nThen add it as NETLIFY_AUTH_TOKEN in your GitHub repository secrets."
146
+ )
147
+ );
148
+ }
149
+
150
+ // src/commands/devlog/list.ts
151
+ import { execSync as execSync4 } from "child_process";
152
+ import { basename as basename2 } from "path";
153
+ import chalk4 from "chalk";
154
+
155
+ // src/commands/devlog/shared.ts
156
+ import { execSync as execSync3 } from "child_process";
157
+ import chalk3 from "chalk";
158
+
159
+ // src/shared/loadConfig.ts
160
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
161
+ import { basename, join as join2 } from "path";
162
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
163
+ function getConfigPath() {
164
+ const claudeConfigPath = join2(process.cwd(), ".claude", "assist.yml");
165
+ if (existsSync2(claudeConfigPath)) {
166
+ return claudeConfigPath;
167
+ }
168
+ return join2(process.cwd(), "assist.yml");
169
+ }
170
+ function loadConfig() {
171
+ const configPath = getConfigPath();
172
+ if (!existsSync2(configPath)) {
173
+ return {};
174
+ }
175
+ try {
176
+ const content = readFileSync2(configPath, "utf-8");
177
+ return parseYaml(content) || {};
178
+ } catch {
179
+ return {};
180
+ }
181
+ }
182
+ function saveConfig(config) {
183
+ const configPath = getConfigPath();
184
+ writeFileSync2(configPath, stringifyYaml(config, { lineWidth: 0 }));
185
+ }
186
+ function getRepoName() {
187
+ const config = loadConfig();
188
+ if (config.devlog?.name) {
189
+ return config.devlog.name;
190
+ }
191
+ const packageJsonPath = join2(process.cwd(), "package.json");
192
+ if (existsSync2(packageJsonPath)) {
193
+ try {
194
+ const content = readFileSync2(packageJsonPath, "utf-8");
195
+ const pkg = JSON.parse(content);
196
+ if (pkg.name) {
197
+ return pkg.name;
198
+ }
199
+ } catch {
200
+ }
201
+ }
202
+ return basename(process.cwd());
203
+ }
204
+
205
+ // src/commands/devlog/loadDevlogEntries.ts
206
+ import { readdirSync, readFileSync as readFileSync3 } from "fs";
207
+ import { homedir } from "os";
208
+ import { join as join3 } from "path";
209
+ var DEVLOG_DIR = join3(homedir(), "git/blog/src/content/devlog");
210
+ function loadDevlogEntries(repoName) {
211
+ const entries = /* @__PURE__ */ new Map();
212
+ try {
213
+ const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
214
+ for (const file of files) {
215
+ const content = readFileSync3(join3(DEVLOG_DIR, file), "utf-8");
216
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
217
+ if (frontmatterMatch) {
218
+ const frontmatter = frontmatterMatch[1];
219
+ const dateMatch = frontmatter.match(/date:\s*"?(\d{4}-\d{2}-\d{2})"?/);
220
+ const versionMatch = frontmatter.match(/version:\s*(.+)/);
221
+ const titleMatch = frontmatter.match(/title:\s*(.+)/);
222
+ const tagsMatch = frontmatter.match(/tags:\s*\[([^\]]*)\]/);
223
+ if (dateMatch && versionMatch && titleMatch && tagsMatch) {
224
+ const tags = tagsMatch[1].split(",").map((t) => t.trim());
225
+ const firstTag = tags[0];
226
+ if (firstTag !== repoName) {
227
+ continue;
228
+ }
229
+ const date = dateMatch[1];
230
+ const version2 = versionMatch[1].trim();
231
+ const title = titleMatch[1].trim();
232
+ const existing = entries.get(date) || [];
233
+ existing.push({ version: version2, title, filename: file });
234
+ entries.set(date, existing);
235
+ }
236
+ }
237
+ }
238
+ } catch {
239
+ }
240
+ return entries;
241
+ }
242
+
243
+ // src/commands/devlog/shared.ts
244
+ function getCommitFiles(hash) {
245
+ try {
246
+ const output = execSync3(`git show --name-only --format="" ${hash}`, {
247
+ encoding: "utf-8"
248
+ });
249
+ return output.trim().split("\n").filter(Boolean);
250
+ } catch {
251
+ return [];
252
+ }
253
+ }
254
+ function shouldIgnoreCommit(files, ignorePaths) {
255
+ if (ignorePaths.length === 0 || files.length === 0) {
256
+ return false;
257
+ }
258
+ return files.every(
259
+ (file) => ignorePaths.some((ignorePath) => file.startsWith(ignorePath))
260
+ );
261
+ }
262
+ function printCommitsWithFiles(commits, ignore2, verbose) {
263
+ for (const commit2 of commits) {
264
+ console.log(` ${chalk3.yellow(commit2.hash)} ${commit2.message}`);
265
+ if (verbose) {
266
+ const visibleFiles = commit2.files.filter(
267
+ (file) => !ignore2.some((p) => file.startsWith(p))
268
+ );
269
+ for (const file of visibleFiles) {
270
+ console.log(` ${chalk3.dim(file)}`);
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ // src/commands/devlog/list.ts
277
+ function list(options) {
278
+ const config = loadConfig();
279
+ const days = options.days ?? 30;
280
+ const ignore2 = options.ignore ?? config.devlog?.ignore ?? [];
281
+ const skipDays = new Set(config.devlog?.skip?.days ?? []);
282
+ const repoName = basename2(process.cwd());
283
+ const devlogEntries = loadDevlogEntries(repoName);
284
+ const reverseFlag = options.reverse ? "--reverse " : "";
285
+ const limitFlag = options.reverse ? "" : "-n 500 ";
286
+ const output = execSync4(
287
+ `git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
288
+ { encoding: "utf-8" }
289
+ );
290
+ const lines = output.trim().split("\n");
291
+ const commits = [];
292
+ for (const line of lines) {
293
+ const [date, hash, ...messageParts] = line.split("|");
294
+ const message = messageParts.join("|");
295
+ const files = getCommitFiles(hash);
296
+ if (!shouldIgnoreCommit(files, ignore2)) {
297
+ commits.push({ date, hash, message, files });
298
+ }
299
+ }
300
+ const commitsByDate = /* @__PURE__ */ new Map();
301
+ for (const commit2 of commits) {
302
+ const existing = commitsByDate.get(commit2.date) || [];
303
+ existing.push(commit2);
304
+ commitsByDate.set(commit2.date, existing);
305
+ }
306
+ let dateCount = 0;
307
+ let isFirst = true;
308
+ for (const [date, dateCommits] of commitsByDate) {
309
+ if (options.since) {
310
+ if (date < options.since) {
311
+ break;
312
+ }
313
+ } else if (dateCount >= days) {
314
+ break;
315
+ }
316
+ dateCount++;
317
+ if (!isFirst) {
318
+ console.log();
319
+ }
320
+ isFirst = false;
321
+ const entries = devlogEntries.get(date);
322
+ if (skipDays.has(date)) {
323
+ console.log(`${chalk4.bold.blue(date)} ${chalk4.dim("skipped")}`);
324
+ } else if (entries && entries.length > 0) {
325
+ const entryInfo = entries.map((e) => `${chalk4.green(e.version)} ${e.title}`).join(" | ");
326
+ console.log(`${chalk4.bold.blue(date)} ${entryInfo}`);
327
+ } else {
328
+ console.log(`${chalk4.bold.blue(date)} ${chalk4.red("\u26A0 devlog missing")}`);
329
+ }
330
+ printCommitsWithFiles(dateCommits, ignore2, options.verbose ?? false);
331
+ }
332
+ }
333
+
334
+ // src/commands/devlog/next.ts
335
+ import { execSync as execSync5 } from "child_process";
336
+ import chalk5 from "chalk";
337
+
338
+ // src/commands/devlog/getLastVersionInfo.ts
339
+ import semver from "semver";
340
+ function getLastVersionInfo(repoName) {
341
+ const entries = loadDevlogEntries(repoName);
342
+ if (entries.size === 0) {
343
+ return null;
344
+ }
345
+ const dates = Array.from(entries.keys()).sort().reverse();
346
+ const lastDate = dates[0];
347
+ if (!lastDate) {
348
+ return null;
349
+ }
350
+ const lastEntries = entries.get(lastDate);
351
+ const lastVersion = lastEntries?.[0]?.version;
352
+ if (!lastVersion) {
353
+ return null;
354
+ }
355
+ return { date: lastDate, version: lastVersion };
356
+ }
357
+ function bumpVersion(version2, type) {
358
+ const cleaned = semver.clean(version2) ?? semver.coerce(version2)?.version;
359
+ if (!cleaned) {
360
+ return version2;
361
+ }
362
+ const bumped = semver.inc(cleaned, type);
363
+ if (!bumped) {
364
+ return version2;
365
+ }
366
+ if (type === "minor") {
367
+ const parsed = semver.parse(bumped);
368
+ return parsed ? `v${parsed.major}.${parsed.minor}` : `v${bumped}`;
369
+ }
370
+ return `v${bumped}`;
371
+ }
372
+
373
+ // src/commands/devlog/next.ts
374
+ function next(options) {
375
+ const config = loadConfig();
376
+ const ignore2 = options.ignore ?? config.devlog?.ignore ?? [];
377
+ const skipDays = new Set(config.devlog?.skip?.days ?? []);
378
+ const repoName = getRepoName();
379
+ const lastInfo = getLastVersionInfo(repoName);
380
+ const lastDate = lastInfo?.date ?? null;
381
+ const patchVersion = lastInfo ? bumpVersion(lastInfo.version, "patch") : null;
382
+ const minorVersion = lastInfo ? bumpVersion(lastInfo.version, "minor") : null;
383
+ const output = execSync5(
384
+ "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
385
+ { encoding: "utf-8" }
386
+ );
387
+ const lines = output.trim().split("\n");
388
+ const commitsByDate = /* @__PURE__ */ new Map();
389
+ for (const line of lines) {
390
+ const [date, hash, ...messageParts] = line.split("|");
391
+ const message = messageParts.join("|");
392
+ if (lastDate && date <= lastDate) {
393
+ continue;
394
+ }
395
+ const files = getCommitFiles(hash);
396
+ if (!shouldIgnoreCommit(files, ignore2)) {
397
+ const existing = commitsByDate.get(date) || [];
398
+ existing.push({ date, hash, message, files });
399
+ commitsByDate.set(date, existing);
400
+ }
401
+ }
402
+ const dates = Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort();
403
+ const targetDate = dates[0];
404
+ if (!targetDate) {
405
+ if (lastInfo) {
406
+ console.log(chalk5.dim("No commits after last versioned entry"));
407
+ } else {
408
+ console.log(chalk5.dim("No commits found"));
409
+ }
410
+ return;
411
+ }
412
+ const commits = commitsByDate.get(targetDate) ?? [];
413
+ console.log(`${chalk5.bold("name:")} ${repoName}`);
414
+ if (patchVersion && minorVersion) {
415
+ console.log(
416
+ `${chalk5.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
417
+ );
418
+ } else {
419
+ console.log(`${chalk5.bold("version:")} v0.1 (initial)`);
420
+ }
421
+ console.log(`${chalk5.bold.blue(targetDate)}`);
422
+ printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
423
+ }
424
+
425
+ // src/commands/devlog/skip.ts
426
+ import chalk6 from "chalk";
427
+ function skip(date) {
428
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
429
+ console.log(chalk6.red("Invalid date format. Use YYYY-MM-DD"));
430
+ process.exit(1);
431
+ }
432
+ const config = loadConfig();
433
+ const skipDays = config.devlog?.skip?.days ?? [];
434
+ if (skipDays.includes(date)) {
435
+ console.log(chalk6.yellow(`${date} is already in skip list`));
436
+ return;
437
+ }
438
+ skipDays.push(date);
439
+ skipDays.sort();
440
+ config.devlog = {
441
+ ...config.devlog,
442
+ skip: {
443
+ ...config.devlog?.skip,
444
+ days: skipDays
445
+ }
446
+ };
447
+ saveConfig(config);
448
+ console.log(chalk6.green(`Added ${date} to skip list`));
449
+ }
450
+
451
+ // src/commands/devlog/version.ts
452
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
453
+ import { join as join4 } from "path";
454
+ import chalk7 from "chalk";
455
+ import semver2 from "semver";
456
+ function getLatestVersion(repoName) {
457
+ try {
458
+ const files = readdirSync2(DEVLOG_DIR).filter((f) => f.endsWith(".md")).sort().reverse();
459
+ for (const file of files) {
460
+ const content = readFileSync4(join4(DEVLOG_DIR, file), "utf-8");
461
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
462
+ if (frontmatterMatch) {
463
+ const frontmatter = frontmatterMatch[1];
464
+ const versionMatch = frontmatter.match(/version:\s*(.+)/);
465
+ const tagsMatch = frontmatter.match(/tags:\s*\[([^\]]*)\]/);
466
+ if (versionMatch && tagsMatch) {
467
+ const tags = tagsMatch[1].split(",").map((t) => t.trim());
468
+ const firstTag = tags[0];
469
+ if (firstTag === repoName) {
470
+ return versionMatch[1].trim();
471
+ }
472
+ }
473
+ }
474
+ }
475
+ } catch {
476
+ }
477
+ return null;
478
+ }
479
+ function bumpPatchVersion(version2) {
480
+ const cleaned = semver2.clean(version2) ?? semver2.coerce(version2)?.version;
481
+ if (!cleaned) {
482
+ return version2;
483
+ }
484
+ const bumped = semver2.inc(cleaned, "patch");
485
+ return bumped ? `v${bumped}` : version2;
486
+ }
487
+ function version() {
488
+ const name = getRepoName();
489
+ const lastVersion = getLatestVersion(name);
490
+ const nextVersion = lastVersion ? bumpPatchVersion(lastVersion) : null;
491
+ console.log(`${chalk7.bold("name:")} ${name}`);
492
+ console.log(`${chalk7.bold("last:")} ${lastVersion ?? chalk7.dim("none")}`);
493
+ console.log(`${chalk7.bold("next:")} ${nextVersion ?? chalk7.dim("none")}`);
494
+ }
495
+
496
+ // src/commands/enable-ralph/index.ts
497
+ import * as fs from "fs";
498
+ import * as path from "path";
499
+ import { fileURLToPath as fileURLToPath2 } from "url";
500
+ import chalk8 from "chalk";
501
+ import enquirer2 from "enquirer";
502
+ var __dirname3 = path.dirname(fileURLToPath2(import.meta.url));
503
+ function deepMerge(target, source) {
504
+ const result = { ...target };
505
+ for (const key of Object.keys(source)) {
506
+ const sourceVal = source[key];
507
+ const targetVal = result[key];
508
+ if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) {
509
+ result[key] = deepMerge(
510
+ targetVal,
511
+ sourceVal
512
+ );
513
+ } else {
514
+ result[key] = sourceVal;
515
+ }
516
+ }
517
+ return result;
518
+ }
519
+ async function enableRalph() {
520
+ const sourcePath = path.join(
521
+ __dirname3,
522
+ "commands/enable-ralph/settings.local.json"
523
+ );
524
+ const targetPath = path.join(process.cwd(), ".claude/settings.local.json");
525
+ const sourceData = JSON.parse(fs.readFileSync(sourcePath, "utf-8"));
526
+ const targetDir = path.dirname(targetPath);
527
+ if (!fs.existsSync(targetDir)) {
528
+ fs.mkdirSync(targetDir, { recursive: true });
529
+ }
530
+ let targetData = {};
531
+ let targetContent = "{}";
532
+ if (fs.existsSync(targetPath)) {
533
+ targetContent = fs.readFileSync(targetPath, "utf-8");
534
+ targetData = JSON.parse(targetContent);
535
+ }
536
+ const merged = deepMerge(targetData, sourceData);
537
+ const mergedContent = JSON.stringify(merged, null, " ") + "\n";
538
+ if (mergedContent === targetContent) {
539
+ console.log(chalk8.green("settings.local.json already has ralph enabled"));
540
+ return;
541
+ }
542
+ console.log(chalk8.yellow("\nChanges to settings.local.json:"));
543
+ console.log();
544
+ printDiff(targetContent, mergedContent);
545
+ const { confirm } = await enquirer2.prompt({
546
+ type: "confirm",
547
+ name: "confirm",
548
+ message: "Apply these changes?",
549
+ initial: true
550
+ });
551
+ if (!confirm) {
552
+ console.log("Skipped");
553
+ return;
554
+ }
555
+ fs.writeFileSync(targetPath, mergedContent);
556
+ console.log(`Updated ${targetPath}`);
557
+ }
558
+
559
+ // src/commands/verify/init.ts
560
+ import chalk19 from "chalk";
561
+
562
+ // src/shared/promptMultiselect.ts
563
+ import chalk9 from "chalk";
564
+ import enquirer3 from "enquirer";
565
+ async function promptMultiselect(message, options) {
566
+ const { selected } = await enquirer3.prompt({
567
+ type: "multiselect",
568
+ name: "selected",
569
+ message,
570
+ choices: options.map((opt) => ({
571
+ name: opt.value,
572
+ message: `${opt.name} - ${chalk9.dim(opt.description)}`
573
+ }))
574
+ });
575
+ return selected;
576
+ }
577
+
578
+ // src/shared/readPackageJson.ts
579
+ import * as fs2 from "fs";
580
+ import * as path2 from "path";
581
+ import chalk10 from "chalk";
582
+ function findPackageJson() {
583
+ const packageJsonPath = path2.join(process.cwd(), "package.json");
584
+ if (fs2.existsSync(packageJsonPath)) {
585
+ return packageJsonPath;
586
+ }
587
+ return null;
588
+ }
589
+ function readPackageJson(filePath) {
590
+ return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
591
+ }
592
+ function requirePackageJson() {
593
+ const packageJsonPath = findPackageJson();
594
+ if (!packageJsonPath) {
595
+ console.error(chalk10.red("No package.json found in current directory"));
596
+ process.exit(1);
597
+ }
598
+ const pkg = readPackageJson(packageJsonPath);
599
+ return { packageJsonPath, pkg };
600
+ }
601
+ function findPackageJsonWithVerifyScripts(startDir) {
602
+ let currentDir = startDir;
603
+ while (true) {
604
+ const packageJsonPath = path2.join(currentDir, "package.json");
605
+ if (fs2.existsSync(packageJsonPath)) {
606
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
607
+ const scripts = packageJson.scripts || {};
608
+ const verifyScripts = Object.keys(scripts).filter(
609
+ (name) => name.startsWith("verify:")
610
+ );
611
+ if (verifyScripts.length > 0) {
612
+ return { packageJsonPath, verifyScripts };
613
+ }
614
+ }
615
+ const parentDir = path2.dirname(currentDir);
616
+ if (parentDir === currentDir) {
617
+ return null;
618
+ }
619
+ currentDir = parentDir;
620
+ }
621
+ }
622
+
623
+ // src/commands/verify/setup/EXPECTED_SCRIPTS.ts
624
+ var EXPECTED_SCRIPTS = {
625
+ "verify:knip": "knip --no-progress",
626
+ "verify:lint": "biome check --write .",
627
+ "verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
628
+ "verify:test": "vitest run --reporter=dot --silent",
629
+ "verify:hardcoded-colors": "assist verify hardcoded-colors"
630
+ };
631
+
632
+ // src/commands/verify/setup/setupBuild.ts
633
+ import chalk12 from "chalk";
634
+
635
+ // src/commands/verify/installPackage.ts
636
+ import { execSync as execSync6 } from "child_process";
637
+ import * as fs3 from "fs";
638
+ import * as path3 from "path";
639
+ import chalk11 from "chalk";
640
+ function writePackageJson(filePath, pkg) {
641
+ fs3.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
642
+ `);
643
+ }
644
+ function addScript(pkg, name, command) {
645
+ return {
646
+ ...pkg,
647
+ scripts: {
648
+ ...pkg.scripts,
649
+ [name]: command
650
+ }
651
+ };
652
+ }
653
+ function installPackage(name, cwd) {
654
+ console.log(chalk11.dim(`Installing ${name}...`));
655
+ try {
656
+ execSync6(`npm install -D ${name}`, { stdio: "inherit", cwd });
657
+ return true;
658
+ } catch {
659
+ console.error(chalk11.red(`Failed to install ${name}`));
660
+ return false;
661
+ }
662
+ }
663
+ function addToKnipIgnoreBinaries(cwd, binary) {
664
+ const knipJsonPath = path3.join(cwd, "knip.json");
665
+ try {
666
+ let knipConfig;
667
+ if (fs3.existsSync(knipJsonPath)) {
668
+ knipConfig = JSON.parse(fs3.readFileSync(knipJsonPath, "utf-8"));
669
+ } else {
670
+ knipConfig = { $schema: "https://unpkg.com/knip@5/schema.json" };
671
+ }
672
+ const ignoreBinaries = knipConfig.ignoreBinaries ?? [];
673
+ if (!ignoreBinaries.includes(binary)) {
674
+ knipConfig.ignoreBinaries = [...ignoreBinaries, binary];
675
+ fs3.writeFileSync(
676
+ knipJsonPath,
677
+ `${JSON.stringify(knipConfig, null, " ")}
678
+ `
679
+ );
680
+ console.log(chalk11.dim(`Added '${binary}' to knip.json ignoreBinaries`));
681
+ }
682
+ } catch {
683
+ console.log(chalk11.yellow("Warning: Could not update knip.json"));
684
+ }
685
+ }
686
+ function setupVerifyScript(packageJsonPath, scriptName, command) {
687
+ writePackageJson(
688
+ packageJsonPath,
689
+ addScript(readPackageJson(packageJsonPath), scriptName, command)
690
+ );
691
+ }
692
+
693
+ // src/commands/verify/setup/setupBuild.ts
694
+ async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
695
+ console.log(chalk12.blue("\nSetting up build verification..."));
696
+ let command;
697
+ if (hasVite && hasTypescript) {
698
+ command = "tsc -b && vite build --logLevel error";
699
+ } else if (hasVite) {
700
+ command = "vite build --logLevel error";
701
+ } else {
702
+ command = "tsc --noEmit";
703
+ }
704
+ console.log(chalk12.dim(`Using: ${command}`));
705
+ const pkg = readPackageJson(packageJsonPath);
706
+ writePackageJson(packageJsonPath, addScript(pkg, "verify:build", command));
707
+ }
708
+
709
+ // src/commands/verify/setup/setupDuplicateCode.ts
710
+ import * as path4 from "path";
711
+ import chalk13 from "chalk";
712
+ async function setupDuplicateCode(packageJsonPath) {
713
+ console.log(chalk13.blue("\nSetting up jscpd..."));
714
+ const cwd = path4.dirname(packageJsonPath);
715
+ const pkg = readPackageJson(packageJsonPath);
716
+ const hasJscpd = !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd;
717
+ if (!hasJscpd && !installPackage("jscpd", cwd)) {
718
+ return;
719
+ }
720
+ setupVerifyScript(
721
+ packageJsonPath,
722
+ "verify:duplicate-code",
723
+ EXPECTED_SCRIPTS["verify:duplicate-code"]
724
+ );
725
+ }
726
+
727
+ // src/commands/verify/setup/setupHardcodedColors.ts
728
+ import * as path5 from "path";
729
+ import chalk14 from "chalk";
730
+ async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
731
+ console.log(chalk14.blue("\nSetting up hardcoded colors check..."));
732
+ const cwd = path5.dirname(packageJsonPath);
733
+ if (!hasOpenColor) {
734
+ installPackage("open-color", cwd);
735
+ }
736
+ addToKnipIgnoreBinaries(cwd, "assist");
737
+ setupVerifyScript(
738
+ packageJsonPath,
739
+ "verify:hardcoded-colors",
740
+ EXPECTED_SCRIPTS["verify:hardcoded-colors"]
741
+ );
742
+ }
743
+
744
+ // src/commands/verify/setup/setupKnip.ts
745
+ import * as path6 from "path";
746
+ import chalk15 from "chalk";
747
+ async function setupKnip(packageJsonPath) {
748
+ console.log(chalk15.blue("\nSetting up knip..."));
749
+ const cwd = path6.dirname(packageJsonPath);
750
+ const pkg = readPackageJson(packageJsonPath);
751
+ if (!pkg.devDependencies?.knip && !installPackage("knip", cwd)) {
752
+ return;
753
+ }
754
+ setupVerifyScript(
755
+ packageJsonPath,
756
+ "verify:knip",
757
+ EXPECTED_SCRIPTS["verify:knip"]
758
+ );
759
+ }
760
+
761
+ // src/commands/verify/setup/setupLint.ts
762
+ import * as path7 from "path";
763
+ import chalk17 from "chalk";
764
+
765
+ // src/commands/lint/init.ts
766
+ import { execSync as execSync7 } from "child_process";
767
+ import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
768
+ import { dirname as dirname7, join as join8 } from "path";
769
+ import { fileURLToPath as fileURLToPath3 } from "url";
770
+ import chalk16 from "chalk";
771
+ import enquirer4 from "enquirer";
772
+ var __dirname4 = dirname7(fileURLToPath3(import.meta.url));
773
+ async function init2() {
774
+ const biomeConfigPath = "biome.json";
775
+ if (!existsSync6(biomeConfigPath)) {
776
+ console.log("Initializing Biome...");
777
+ execSync7("npx @biomejs/biome init", { stdio: "inherit" });
778
+ }
779
+ if (!existsSync6(biomeConfigPath)) {
780
+ console.log("No biome.json found, skipping linter config");
781
+ return;
782
+ }
783
+ const linterConfigPath = join8(__dirname4, "commands/lint/biome.linter.json");
784
+ const linterConfig = JSON.parse(readFileSync8(linterConfigPath, "utf-8"));
785
+ const biomeConfig = JSON.parse(readFileSync8(biomeConfigPath, "utf-8"));
786
+ const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
787
+ `;
788
+ biomeConfig.linter = linterConfig.linter;
789
+ if (linterConfig.overrides) {
790
+ biomeConfig.overrides = linterConfig.overrides;
791
+ }
792
+ const newContent = `${JSON.stringify(biomeConfig, null, 2)}
793
+ `;
794
+ if (oldContent === newContent) {
795
+ console.log("biome.json already has the correct linter config");
796
+ return;
797
+ }
798
+ console.log(chalk16.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
799
+ console.log();
800
+ printDiff(oldContent, newContent);
801
+ const { confirm } = await enquirer4.prompt({
802
+ type: "confirm",
803
+ name: "confirm",
804
+ message: chalk16.red("Update biome.json?"),
805
+ initial: true
806
+ });
807
+ if (!confirm) {
808
+ console.log("Skipped biome.json update");
809
+ return;
810
+ }
811
+ writeFileSync5(biomeConfigPath, newContent);
812
+ console.log("Updated biome.json with linter config");
813
+ }
814
+
815
+ // src/commands/verify/setup/setupLint.ts
816
+ async function setupLint(packageJsonPath) {
817
+ console.log(chalk17.blue("\nSetting up biome..."));
818
+ const cwd = path7.dirname(packageJsonPath);
819
+ const pkg = readPackageJson(packageJsonPath);
820
+ if (!pkg.devDependencies?.["@biomejs/biome"]) {
821
+ if (!installPackage("@biomejs/biome", cwd)) {
822
+ return;
823
+ }
824
+ }
825
+ await init2();
826
+ setupVerifyScript(
827
+ packageJsonPath,
828
+ "verify:lint",
829
+ EXPECTED_SCRIPTS["verify:lint"]
830
+ );
831
+ }
832
+
833
+ // src/commands/verify/setup/setupTest.ts
834
+ import * as path8 from "path";
835
+ import chalk18 from "chalk";
836
+ async function setupTest(packageJsonPath) {
837
+ console.log(chalk18.blue("\nSetting up vitest..."));
838
+ const cwd = path8.dirname(packageJsonPath);
839
+ const pkg = readPackageJson(packageJsonPath);
840
+ if (!pkg.devDependencies?.vitest && !installPackage("vitest", cwd)) {
841
+ return;
842
+ }
843
+ setupVerifyScript(
844
+ packageJsonPath,
845
+ "verify:test",
846
+ EXPECTED_SCRIPTS["verify:test"]
847
+ );
848
+ }
849
+
850
+ // src/commands/verify/needsSetup.ts
851
+ function needsSetup(status) {
852
+ return !status.hasScript || !status.hasPackage || status.isOutdated;
853
+ }
854
+ function getStatusLabel(status) {
855
+ if (status.isOutdated) return " (outdated)";
856
+ if (!status.hasScript) return "";
857
+ if (!status.hasPackage) return " (package missing)";
858
+ return "";
859
+ }
860
+
861
+ // src/commands/verify/detectExistingSetup.ts
862
+ function isScriptOutdated(pkg, scriptName, expectedCommand) {
863
+ const currentScript = pkg.scripts?.[scriptName];
864
+ if (!currentScript || !expectedCommand) return false;
865
+ return currentScript !== expectedCommand;
866
+ }
867
+ function detectExistingSetup(pkg) {
868
+ return {
869
+ knip: {
870
+ hasPackage: !!pkg.devDependencies?.knip,
871
+ hasScript: !!pkg.scripts?.["verify:knip"],
872
+ isOutdated: isScriptOutdated(
873
+ pkg,
874
+ "verify:knip",
875
+ EXPECTED_SCRIPTS["verify:knip"]
876
+ )
877
+ },
878
+ biome: {
879
+ hasPackage: !!pkg.devDependencies?.["@biomejs/biome"],
880
+ hasScript: !!pkg.scripts?.["verify:lint"],
881
+ isOutdated: isScriptOutdated(
882
+ pkg,
883
+ "verify:lint",
884
+ EXPECTED_SCRIPTS["verify:lint"]
885
+ )
886
+ },
887
+ jscpd: {
888
+ hasPackage: !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd,
889
+ hasScript: !!pkg.scripts?.["verify:duplicate-code"],
890
+ isOutdated: isScriptOutdated(
891
+ pkg,
892
+ "verify:duplicate-code",
893
+ EXPECTED_SCRIPTS["verify:duplicate-code"]
894
+ )
895
+ },
896
+ test: {
897
+ hasPackage: !!pkg.devDependencies?.vitest,
898
+ hasScript: !!pkg.scripts?.["verify:test"],
899
+ isOutdated: isScriptOutdated(
900
+ pkg,
901
+ "verify:test",
902
+ EXPECTED_SCRIPTS["verify:test"]
903
+ )
904
+ },
905
+ hasVite: !!pkg.devDependencies?.vite || !!pkg.dependencies?.vite,
906
+ hasTypescript: !!pkg.devDependencies?.typescript,
907
+ build: {
908
+ hasPackage: true,
909
+ hasScript: !!pkg.scripts?.["verify:build"],
910
+ isOutdated: false
911
+ },
912
+ hardcodedColors: {
913
+ hasPackage: true,
914
+ hasScript: !!pkg.scripts?.["verify:hardcoded-colors"],
915
+ isOutdated: isScriptOutdated(
916
+ pkg,
917
+ "verify:hardcoded-colors",
918
+ EXPECTED_SCRIPTS["verify:hardcoded-colors"]
919
+ )
920
+ },
921
+ hasOpenColor: !!pkg.dependencies?.["open-color"] || !!pkg.devDependencies?.["open-color"]
922
+ };
923
+ }
924
+
925
+ // src/commands/verify/getAvailableOptions.ts
926
+ function getAvailableOptions(setup) {
927
+ const options = [];
928
+ if (needsSetup(setup.knip)) {
929
+ options.push({
930
+ name: `knip${getStatusLabel(setup.knip)}`,
931
+ value: "knip",
932
+ description: "Dead code and unused dependency detection"
933
+ });
934
+ }
935
+ if (needsSetup(setup.biome)) {
936
+ options.push({
937
+ name: `lint${getStatusLabel(setup.biome)}`,
938
+ value: "lint",
939
+ description: "Code linting and formatting with Biome"
940
+ });
941
+ }
942
+ if (needsSetup(setup.jscpd)) {
943
+ options.push({
944
+ name: `duplicate-code${getStatusLabel(setup.jscpd)}`,
945
+ value: "duplicate-code",
946
+ description: "Duplicate code detection with jscpd"
947
+ });
948
+ }
949
+ if (needsSetup(setup.test) && setup.test.hasPackage) {
950
+ options.push({
951
+ name: `test${getStatusLabel(setup.test)}`,
952
+ value: "test",
953
+ description: "Run tests with vitest"
954
+ });
955
+ }
956
+ if (needsSetup(setup.build) && (setup.hasTypescript || setup.hasVite)) {
957
+ const description = setup.hasVite ? setup.hasTypescript ? "TypeScript + Vite build verification" : "Vite build verification" : "TypeScript type checking";
958
+ options.push({
959
+ name: `build${getStatusLabel(setup.build)}`,
960
+ value: "build",
961
+ description
962
+ });
963
+ }
964
+ if (needsSetup(setup.hardcodedColors)) {
965
+ options.push({
966
+ name: `hardcoded-colors${getStatusLabel(setup.hardcodedColors)}`,
967
+ value: "hardcoded-colors",
968
+ description: "Detect hardcoded hex colors (use open-color instead)"
969
+ });
970
+ }
971
+ return options;
972
+ }
973
+
974
+ // src/commands/verify/init.ts
975
+ async function init3() {
976
+ const { packageJsonPath, pkg } = requirePackageJson();
977
+ const setup = detectExistingSetup(pkg);
978
+ const availableOptions = getAvailableOptions(setup);
979
+ if (availableOptions.length === 0) {
980
+ console.log(chalk19.green("All verify scripts are already configured!"));
981
+ return;
982
+ }
983
+ console.log(chalk19.bold("Available verify scripts to add:\n"));
984
+ const selected = await promptMultiselect(
985
+ "Select verify scripts to add:",
986
+ availableOptions
987
+ );
988
+ if (selected.length === 0) {
989
+ console.log(chalk19.yellow("No scripts selected"));
990
+ return;
991
+ }
992
+ for (const choice of selected) {
993
+ switch (choice) {
994
+ case "knip":
995
+ await setupKnip(packageJsonPath);
996
+ break;
997
+ case "lint":
998
+ await setupLint(packageJsonPath);
999
+ break;
1000
+ case "duplicate-code":
1001
+ await setupDuplicateCode(packageJsonPath);
1002
+ break;
1003
+ case "test":
1004
+ await setupTest(packageJsonPath);
1005
+ break;
1006
+ case "build":
1007
+ await setupBuild(packageJsonPath, setup.hasVite, setup.hasTypescript);
1008
+ break;
1009
+ case "hardcoded-colors":
1010
+ await setupHardcodedColors(packageJsonPath, setup.hasOpenColor);
1011
+ break;
1012
+ }
1013
+ }
1014
+ console.log(chalk19.green(`
1015
+ Added ${selected.length} verify script(s):`));
1016
+ for (const choice of selected) {
1017
+ console.log(chalk19.green(` - verify:${choice}`));
1018
+ }
1019
+ console.log(chalk19.dim("\nRun 'assist verify' to run all verify scripts"));
1020
+ }
1021
+
1022
+ // src/commands/vscode/init.ts
1023
+ import * as fs5 from "fs";
1024
+ import * as path10 from "path";
1025
+ import chalk21 from "chalk";
1026
+
1027
+ // src/commands/vscode/createLaunchJson.ts
1028
+ import * as fs4 from "fs";
1029
+ import * as path9 from "path";
1030
+ import chalk20 from "chalk";
1031
+ function ensureVscodeFolder() {
1032
+ const vscodeDir = path9.join(process.cwd(), ".vscode");
1033
+ if (!fs4.existsSync(vscodeDir)) {
1034
+ fs4.mkdirSync(vscodeDir);
1035
+ console.log(chalk20.dim("Created .vscode folder"));
1036
+ }
1037
+ }
1038
+ function removeVscodeFromGitignore() {
1039
+ const gitignorePath = path9.join(process.cwd(), ".gitignore");
1040
+ if (!fs4.existsSync(gitignorePath)) {
1041
+ return;
1042
+ }
1043
+ const content = fs4.readFileSync(gitignorePath, "utf-8");
1044
+ const lines = content.split("\n");
1045
+ const filteredLines = lines.filter(
1046
+ (line) => !line.trim().toLowerCase().includes(".vscode")
1047
+ );
1048
+ if (filteredLines.length !== lines.length) {
1049
+ fs4.writeFileSync(gitignorePath, filteredLines.join("\n"));
1050
+ console.log(chalk20.dim("Removed .vscode references from .gitignore"));
1051
+ }
1052
+ }
1053
+ function createLaunchJson() {
1054
+ const launchConfig = {
1055
+ version: "0.2.0",
1056
+ configurations: [
1057
+ {
1058
+ name: "npm run dev",
1059
+ type: "node-terminal",
1060
+ request: "launch",
1061
+ command: "npm run dev -- --open"
1062
+ }
1063
+ ]
1064
+ };
1065
+ const launchPath = path9.join(process.cwd(), ".vscode", "launch.json");
1066
+ fs4.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
1067
+ `);
1068
+ console.log(chalk20.green("Created .vscode/launch.json"));
1069
+ }
1070
+ function createSettingsJson() {
1071
+ const settings = {
1072
+ "editor.defaultFormatter": "biomejs.biome",
1073
+ "editor.formatOnSave": true,
1074
+ "[json]": {
1075
+ "editor.defaultFormatter": "biomejs.biome"
1076
+ },
1077
+ "[typescript]": {
1078
+ "editor.defaultFormatter": "biomejs.biome"
1079
+ },
1080
+ "[typescriptreact]": {
1081
+ "editor.defaultFormatter": "biomejs.biome"
1082
+ }
1083
+ };
1084
+ const settingsPath = path9.join(process.cwd(), ".vscode", "settings.json");
1085
+ fs4.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1086
+ `);
1087
+ console.log(chalk20.green("Created .vscode/settings.json"));
1088
+ }
1089
+ function createExtensionsJson() {
1090
+ const extensions = {
1091
+ recommendations: ["biomejs.biome"]
1092
+ };
1093
+ const extensionsPath = path9.join(process.cwd(), ".vscode", "extensions.json");
1094
+ fs4.writeFileSync(
1095
+ extensionsPath,
1096
+ `${JSON.stringify(extensions, null, " ")}
1097
+ `
1098
+ );
1099
+ console.log(chalk20.green("Created .vscode/extensions.json"));
1100
+ }
1101
+
1102
+ // src/commands/vscode/init.ts
1103
+ function detectExistingSetup2(pkg) {
1104
+ const vscodeDir = path10.join(process.cwd(), ".vscode");
1105
+ return {
1106
+ hasVscodeFolder: fs5.existsSync(vscodeDir),
1107
+ hasLaunchJson: fs5.existsSync(path10.join(vscodeDir, "launch.json")),
1108
+ hasSettingsJson: fs5.existsSync(path10.join(vscodeDir, "settings.json")),
1109
+ hasVite: !!pkg.devDependencies?.vite || !!pkg.dependencies?.vite
1110
+ };
1111
+ }
1112
+ async function init4() {
1113
+ const { pkg } = requirePackageJson();
1114
+ const setup = detectExistingSetup2(pkg);
1115
+ const availableOptions = [];
1116
+ if (!setup.hasLaunchJson && setup.hasVite) {
1117
+ availableOptions.push({
1118
+ name: "launch",
1119
+ value: "launch",
1120
+ description: "Debug configuration for Vite dev server"
1121
+ });
1122
+ }
1123
+ if (!setup.hasSettingsJson) {
1124
+ availableOptions.push({
1125
+ name: "settings",
1126
+ value: "settings",
1127
+ description: "Biome formatter configuration"
1128
+ });
1129
+ }
1130
+ if (availableOptions.length === 0) {
1131
+ console.log(chalk21.green("VS Code configuration already exists!"));
1132
+ return;
1133
+ }
1134
+ console.log(chalk21.bold("Available VS Code configurations to add:\n"));
1135
+ const selected = await promptMultiselect(
1136
+ "Select configurations to add:",
1137
+ availableOptions
1138
+ );
1139
+ if (selected.length === 0) {
1140
+ console.log(chalk21.yellow("No configurations selected"));
1141
+ return;
1142
+ }
1143
+ removeVscodeFromGitignore();
1144
+ ensureVscodeFolder();
1145
+ for (const choice of selected) {
1146
+ switch (choice) {
1147
+ case "launch":
1148
+ createLaunchJson();
1149
+ break;
1150
+ case "settings":
1151
+ createSettingsJson();
1152
+ createExtensionsJson();
1153
+ break;
1154
+ }
1155
+ }
1156
+ console.log(
1157
+ chalk21.green(`
1158
+ Added ${selected.length} VS Code configuration(s)`)
1159
+ );
1160
+ }
1161
+
1162
+ // src/commands/init.ts
1163
+ async function init5() {
1164
+ await init4();
1165
+ await init3();
1166
+ }
1167
+
1168
+ // src/commands/lint/runFileNameCheck.ts
1169
+ import fs7 from "fs";
1170
+ import path12 from "path";
1171
+ import chalk22 from "chalk";
1172
+
1173
+ // src/shared/findSourceFiles.ts
1174
+ import fs6 from "fs";
1175
+ import path11 from "path";
1176
+ var EXTENSIONS = [".ts", ".tsx"];
1177
+ function findSourceFiles(dir, options = {}) {
1178
+ const { includeTests = true } = options;
1179
+ const results = [];
1180
+ if (!fs6.existsSync(dir)) {
1181
+ return results;
1182
+ }
1183
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
1184
+ for (const entry of entries) {
1185
+ const fullPath = path11.join(dir, entry.name);
1186
+ if (entry.isDirectory() && entry.name !== "node_modules") {
1187
+ results.push(...findSourceFiles(fullPath, options));
1188
+ } else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
1189
+ if (!includeTests && entry.name.includes(".test.")) {
1190
+ continue;
1191
+ }
1192
+ results.push(fullPath);
1193
+ }
1194
+ }
1195
+ return results;
1196
+ }
1197
+
1198
+ // src/commands/lint/runFileNameCheck.ts
1199
+ function hasClassOrComponent(content) {
1200
+ const classPattern = /^(export\s+)?(abstract\s+)?class\s+\w+/m;
1201
+ const functionComponentPattern = /^(export\s+)?(default\s+)?function\s+[A-Z]\w*\s*\(/m;
1202
+ const arrowComponentPattern = /^(export\s+)?(const|let)\s+[A-Z]\w*\s*=.*=>/m;
1203
+ return classPattern.test(content) || functionComponentPattern.test(content) || arrowComponentPattern.test(content);
1204
+ }
1205
+ function checkFileNames() {
1206
+ const sourceFiles = findSourceFiles("src");
1207
+ const violations = [];
1208
+ for (const filePath of sourceFiles) {
1209
+ const fileName = path12.basename(filePath);
1210
+ const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
1211
+ if (/^[A-Z]/.test(nameWithoutExt)) {
1212
+ const content = fs7.readFileSync(filePath, "utf-8");
1213
+ if (!hasClassOrComponent(content)) {
1214
+ violations.push({ filePath, fileName });
1215
+ }
1216
+ }
1217
+ }
1218
+ return violations;
1219
+ }
1220
+ function runFileNameCheck() {
1221
+ const violations = checkFileNames();
1222
+ if (violations.length > 0) {
1223
+ console.error(chalk22.red("\nFile name check failed:\n"));
1224
+ console.error(
1225
+ chalk22.red(
1226
+ " Files without classes or React components should not start with a capital letter.\n"
1227
+ )
1228
+ );
1229
+ for (const violation of violations) {
1230
+ console.error(chalk22.red(` ${violation.filePath}`));
1231
+ console.error(
1232
+ chalk22.gray(
1233
+ ` Rename to: ${violation.fileName.charAt(0).toLowerCase()}${violation.fileName.slice(1)}
1234
+ `
1235
+ )
1236
+ );
1237
+ }
1238
+ return false;
1239
+ }
1240
+ if (!process.env.CLAUDECODE) {
1241
+ console.log(
1242
+ "File name check passed. All PascalCase files contain classes or components."
1243
+ );
1244
+ }
1245
+ return true;
1246
+ }
1247
+
1248
+ // src/commands/lint/runStaticImportCheck.ts
1249
+ import fs8 from "fs";
1250
+ import chalk23 from "chalk";
1251
+ function checkForDynamicImports(filePath) {
1252
+ const content = fs8.readFileSync(filePath, "utf-8");
1253
+ const lines = content.split("\n");
1254
+ const violations = [];
1255
+ const requirePattern = /\brequire\s*\(/;
1256
+ const dynamicImportPattern = /\bimport\s*\(/;
1257
+ for (let i = 0; i < lines.length; i++) {
1258
+ const line = lines[i];
1259
+ if (requirePattern.test(line) || dynamicImportPattern.test(line)) {
1260
+ violations.push({
1261
+ filePath,
1262
+ line: i + 1,
1263
+ content: line.trim()
1264
+ });
1265
+ }
1266
+ }
1267
+ return violations;
1268
+ }
1269
+ function checkStaticImports() {
1270
+ const sourceFiles = findSourceFiles("src");
1271
+ const violations = [];
1272
+ for (const filePath of sourceFiles) {
1273
+ violations.push(...checkForDynamicImports(filePath));
1274
+ }
1275
+ return violations;
1276
+ }
1277
+ function runStaticImportCheck() {
1278
+ const violations = checkStaticImports();
1279
+ if (violations.length > 0) {
1280
+ console.error(chalk23.red("\nStatic import check failed:\n"));
1281
+ console.error(
1282
+ chalk23.red(
1283
+ " Dynamic imports (require() and import()) are not allowed. Use static imports instead.\n"
1284
+ )
1285
+ );
1286
+ for (const violation of violations) {
1287
+ console.error(chalk23.red(` ${violation.filePath}:${violation.line}`));
1288
+ console.error(chalk23.gray(` ${violation.content}
1289
+ `));
1290
+ }
1291
+ return false;
1292
+ }
1293
+ if (!process.env.CLAUDECODE) {
1294
+ console.log("Static import check passed. No dynamic imports found.");
1295
+ }
1296
+ return true;
1297
+ }
1298
+
1299
+ // src/commands/lint/lint.ts
1300
+ function lint() {
1301
+ const fileNamePassed = runFileNameCheck();
1302
+ const staticImportPassed = runStaticImportCheck();
1303
+ if (!fileNamePassed || !staticImportPassed) {
1304
+ process.exit(1);
1305
+ }
1306
+ }
1307
+
1308
+ // src/commands/new/newProject.ts
1309
+ import { execSync as execSync8 } from "child_process";
1310
+ import { existsSync as existsSync9, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
1311
+ async function newProject() {
1312
+ console.log("Initializing Vite with react-ts template...");
1313
+ execSync8("npm create vite@latest . -- --template react-ts", {
1314
+ stdio: "inherit"
1315
+ });
1316
+ removeEslintFromPackageJson();
1317
+ removeEslintConfigFile();
1318
+ addViteBaseConfig();
1319
+ await init5();
1320
+ await init();
1321
+ }
1322
+ function removeEslintFromPackageJson() {
1323
+ const packageJsonPath = "package.json";
1324
+ if (!existsSync9(packageJsonPath)) {
1325
+ console.log("No package.json found, skipping eslint removal");
1326
+ return;
1327
+ }
1328
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
1329
+ let modified = false;
1330
+ if (packageJson.dependencies) {
1331
+ for (const key of Object.keys(packageJson.dependencies)) {
1332
+ if (key.includes("eslint")) {
1333
+ delete packageJson.dependencies[key];
1334
+ modified = true;
1335
+ }
1336
+ }
1337
+ }
1338
+ if (packageJson.devDependencies) {
1339
+ for (const key of Object.keys(packageJson.devDependencies)) {
1340
+ if (key.includes("eslint")) {
1341
+ delete packageJson.devDependencies[key];
1342
+ modified = true;
1343
+ }
1344
+ }
1345
+ }
1346
+ if (packageJson.scripts) {
1347
+ for (const key of Object.keys(packageJson.scripts)) {
1348
+ if (key.includes("eslint") || key.includes("lint") || packageJson.scripts[key].includes("eslint")) {
1349
+ delete packageJson.scripts[key];
1350
+ modified = true;
1351
+ }
1352
+ }
1353
+ }
1354
+ if (modified) {
1355
+ writeFileSync7(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
1356
+ `);
1357
+ console.log("Removed eslint references from package.json");
1358
+ }
1359
+ }
1360
+ function addViteBaseConfig() {
1361
+ const viteConfigPath = "vite.config.ts";
1362
+ if (!existsSync9(viteConfigPath)) {
1363
+ console.log("No vite.config.ts found, skipping base config");
1364
+ return;
1365
+ }
1366
+ const content = readFileSync10(viteConfigPath, "utf-8");
1367
+ if (content.includes("base:")) {
1368
+ console.log("vite.config.ts already has base config");
1369
+ return;
1370
+ }
1371
+ const updated = content.replace(
1372
+ /defineConfig\(\{/,
1373
+ 'defineConfig({\n base: "./",'
1374
+ );
1375
+ if (updated !== content) {
1376
+ writeFileSync7(viteConfigPath, updated);
1377
+ console.log('Added base: "./" to vite.config.ts');
1378
+ }
1379
+ }
1380
+ function removeEslintConfigFile() {
1381
+ const eslintConfigFiles = [
1382
+ "eslint.config.js",
1383
+ "eslint.config.mjs",
1384
+ "eslint.config.cjs",
1385
+ ".eslintrc",
1386
+ ".eslintrc.js",
1387
+ ".eslintrc.cjs",
1388
+ ".eslintrc.json",
1389
+ ".eslintrc.yaml",
1390
+ ".eslintrc.yml"
1391
+ ];
1392
+ for (const configFile of eslintConfigFiles) {
1393
+ if (existsSync9(configFile)) {
1394
+ unlinkSync(configFile);
1395
+ console.log(`Removed ${configFile}`);
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ // src/commands/prs.ts
1401
+ import { execSync as execSync9 } from "child_process";
1402
+ import chalk24 from "chalk";
1403
+ import enquirer5 from "enquirer";
1404
+ var PAGE_SIZE = 10;
1405
+ async function prs(options) {
1406
+ const state = options.open ? "open" : options.closed ? "closed" : "all";
1407
+ try {
1408
+ const result = execSync9(
1409
+ `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
1410
+ { encoding: "utf-8" }
1411
+ );
1412
+ const pullRequests = JSON.parse(result);
1413
+ if (pullRequests.length === 0) {
1414
+ console.log(
1415
+ `No ${state === "all" ? "" : `${state} `}pull requests found.`
1416
+ );
1417
+ return;
1418
+ }
1419
+ await displayPaginated(pullRequests);
1420
+ } catch (error) {
1421
+ if (error instanceof Error) {
1422
+ const msg = error.message.toLowerCase();
1423
+ if (msg.includes("enoent") || msg.includes("command not found")) {
1424
+ console.error("Error: GitHub CLI (gh) is not installed.");
1425
+ console.error("Install it from https://cli.github.com/");
1426
+ return;
1427
+ }
1428
+ }
1429
+ throw error;
1430
+ }
1431
+ }
1432
+ async function displayPaginated(pullRequests) {
1433
+ const totalPages = Math.ceil(pullRequests.length / PAGE_SIZE);
1434
+ let currentPage = 0;
1435
+ const getStatus = (pr) => {
1436
+ if (pr.state === "MERGED" && pr.mergedAt) {
1437
+ return { label: chalk24.magenta("merged"), date: pr.mergedAt };
1438
+ }
1439
+ if (pr.state === "CLOSED" && pr.closedAt) {
1440
+ return { label: chalk24.red("closed"), date: pr.closedAt };
1441
+ }
1442
+ return { label: chalk24.green("opened"), date: pr.createdAt };
1443
+ };
1444
+ const displayPage = (page) => {
1445
+ const start = page * PAGE_SIZE;
1446
+ const end = Math.min(start + PAGE_SIZE, pullRequests.length);
1447
+ const pagePrs = pullRequests.slice(start, end);
1448
+ console.log(
1449
+ `
1450
+ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
1451
+ `
1452
+ );
1453
+ for (const pr of pagePrs) {
1454
+ const status = getStatus(pr);
1455
+ const formattedDate = new Date(status.date).toISOString().split("T")[0];
1456
+ const fileCount = pr.changedFiles.toLocaleString();
1457
+ console.log(
1458
+ `${chalk24.cyan(`#${pr.number}`)} ${pr.title} ${chalk24.dim(`(${pr.author.login},`)} ${status.label} ${chalk24.dim(`${formattedDate})`)}`
1459
+ );
1460
+ console.log(chalk24.dim(` ${fileCount} files | ${pr.url}`));
1461
+ console.log();
1462
+ }
1463
+ };
1464
+ displayPage(currentPage);
1465
+ if (totalPages <= 1) {
1466
+ return;
1467
+ }
1468
+ while (true) {
1469
+ const hasNext = currentPage < totalPages - 1;
1470
+ const hasPrev = currentPage > 0;
1471
+ const choices = [];
1472
+ if (hasNext) choices.push({ name: "Next page", value: "next" });
1473
+ if (hasPrev) choices.push({ name: "Previous page", value: "prev" });
1474
+ choices.push({ name: "Quit", value: "quit" });
1475
+ const { action } = await enquirer5.prompt({
1476
+ type: "select",
1477
+ name: "action",
1478
+ message: "Navigate",
1479
+ choices
1480
+ });
1481
+ if (action === "Next page") {
1482
+ currentPage++;
1483
+ displayPage(currentPage);
1484
+ } else if (action === "Previous page") {
1485
+ currentPage--;
1486
+ displayPage(currentPage);
1487
+ } else {
1488
+ break;
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ // src/commands/refactor/check.ts
1494
+ import { spawn } from "child_process";
1495
+ import * as path13 from "path";
1496
+
1497
+ // src/commands/refactor/getViolations.ts
1498
+ import { execSync as execSync10 } from "child_process";
1499
+ import fs10 from "fs";
1500
+ import { minimatch } from "minimatch";
1501
+
1502
+ // src/commands/refactor/getIgnoredFiles.ts
1503
+ import fs9 from "fs";
1504
+ var REFACTOR_YML_PATH = "refactor.yml";
1505
+ function parseRefactorYml() {
1506
+ if (!fs9.existsSync(REFACTOR_YML_PATH)) {
1507
+ return [];
1508
+ }
1509
+ const content = fs9.readFileSync(REFACTOR_YML_PATH, "utf-8");
1510
+ const entries = [];
1511
+ const lines = content.split("\n");
1512
+ let currentEntry = {};
1513
+ for (const line of lines) {
1514
+ const trimmed = line.trim();
1515
+ if (trimmed.startsWith("- file:")) {
1516
+ if (currentEntry.file) {
1517
+ entries.push(currentEntry);
1518
+ }
1519
+ currentEntry = { file: trimmed.replace("- file:", "").trim() };
1520
+ } else if (trimmed.startsWith("maxLines:")) {
1521
+ currentEntry.maxLines = parseInt(
1522
+ trimmed.replace("maxLines:", "").trim(),
1523
+ 10
1524
+ );
1525
+ }
1526
+ }
1527
+ if (currentEntry.file) {
1528
+ entries.push(currentEntry);
1529
+ }
1530
+ return entries;
1531
+ }
1532
+ function getIgnoredFiles() {
1533
+ const entries = parseRefactorYml();
1534
+ return new Map(entries.map((e) => [e.file, e.maxLines]));
1535
+ }
1536
+
1537
+ // src/commands/refactor/logViolations.ts
1538
+ import chalk25 from "chalk";
1539
+ var DEFAULT_MAX_LINES = 100;
1540
+ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
1541
+ if (violations.length === 0) {
1542
+ if (!process.env.CLAUDECODE) {
1543
+ console.log(`Refactor check passed. No files exceed ${maxLines} lines.`);
1544
+ }
1545
+ return;
1546
+ }
1547
+ console.error(chalk25.red(`
1548
+ Refactor check failed:
1549
+ `));
1550
+ console.error(chalk25.red(` The following files exceed ${maxLines} lines:
1551
+ `));
1552
+ for (const violation of violations) {
1553
+ console.error(chalk25.red(` ${violation.file} (${violation.lines} lines)`));
1554
+ }
1555
+ console.error(
1556
+ chalk25.yellow(
1557
+ `
1558
+ Each file needs to be sensibly refactored, or if there is no sensible
1559
+ way to refactor it, ignore it with:
1560
+ `
1561
+ )
1562
+ );
1563
+ console.error(chalk25.gray(` assist refactor ignore <file>
1564
+ `));
1565
+ if (process.env.CLAUDECODE) {
1566
+ console.error(chalk25.cyan(`
1567
+ ## Extracting Code to New Files
1568
+ `));
1569
+ console.error(
1570
+ chalk25.cyan(
1571
+ ` When extracting logic from one file to another, consider where the extracted code belongs:
1572
+ `
1573
+ )
1574
+ );
1575
+ console.error(
1576
+ chalk25.cyan(
1577
+ ` 1. Keep related logic together: If the extracted code is tightly coupled to the
1578
+ original file's domain, create a new folder containing both the original and extracted files.
1579
+ `
1580
+ )
1581
+ );
1582
+ console.error(
1583
+ chalk25.cyan(
1584
+ ` 2. Share common utilities: If the extracted code can be reused across multiple
1585
+ domains, move it to a common/shared folder.
1586
+ `
1587
+ )
1588
+ );
1589
+ }
1590
+ }
1591
+
1592
+ // src/commands/refactor/getViolations.ts
1593
+ function countLines(filePath) {
1594
+ const content = fs10.readFileSync(filePath, "utf-8");
1595
+ return content.split("\n").length;
1596
+ }
1597
+ function getGitFiles(options) {
1598
+ if (!options.modified && !options.staged && !options.unstaged) {
1599
+ return null;
1600
+ }
1601
+ const files = /* @__PURE__ */ new Set();
1602
+ if (options.staged || options.modified) {
1603
+ const staged = execSync10("git diff --cached --name-only", {
1604
+ encoding: "utf-8"
1605
+ });
1606
+ for (const file of staged.trim().split("\n").filter(Boolean)) {
1607
+ files.add(file);
1608
+ }
1609
+ }
1610
+ if (options.unstaged || options.modified) {
1611
+ const unstaged = execSync10("git diff --name-only", { encoding: "utf-8" });
1612
+ for (const file of unstaged.trim().split("\n").filter(Boolean)) {
1613
+ files.add(file);
1614
+ }
1615
+ }
1616
+ return files;
1617
+ }
1618
+ function getViolations(pattern2, options = {}, maxLines = DEFAULT_MAX_LINES) {
1619
+ let sourceFiles = findSourceFiles("src", { includeTests: false });
1620
+ const ignoredFiles = getIgnoredFiles();
1621
+ const gitFiles = getGitFiles(options);
1622
+ if (pattern2) {
1623
+ sourceFiles = sourceFiles.filter((f) => minimatch(f, pattern2));
1624
+ }
1625
+ if (gitFiles) {
1626
+ sourceFiles = sourceFiles.filter((f) => gitFiles.has(f));
1627
+ }
1628
+ const violations = [];
1629
+ for (const filePath of sourceFiles) {
1630
+ const lineCount = countLines(filePath);
1631
+ const maxAllowed = ignoredFiles.get(filePath) ?? maxLines;
1632
+ if (lineCount > maxAllowed) {
1633
+ violations.push({ file: filePath, lines: lineCount });
1634
+ }
1635
+ }
1636
+ return violations;
1637
+ }
1638
+
1639
+ // src/commands/refactor/check.ts
1640
+ async function runVerifyQuietly() {
1641
+ const result = findPackageJsonWithVerifyScripts(process.cwd());
1642
+ if (!result) {
1643
+ return true;
1644
+ }
1645
+ const { packageJsonPath, verifyScripts } = result;
1646
+ const packageDir = path13.dirname(packageJsonPath);
1647
+ const results = await Promise.all(
1648
+ verifyScripts.map(
1649
+ (script) => new Promise(
1650
+ (resolve) => {
1651
+ const child = spawn("npm", ["run", script], {
1652
+ stdio: "pipe",
1653
+ shell: true,
1654
+ cwd: packageDir
1655
+ });
1656
+ let output = "";
1657
+ child.stdout?.on("data", (data) => {
1658
+ output += data.toString();
1659
+ });
1660
+ child.stderr?.on("data", (data) => {
1661
+ output += data.toString();
1662
+ });
1663
+ child.on("close", (code) => {
1664
+ resolve({ script, code: code ?? 1, output });
1665
+ });
1666
+ }
1667
+ )
1668
+ )
1669
+ );
1670
+ const failed = results.filter((r) => r.code !== 0);
1671
+ if (failed.length > 0) {
1672
+ for (const f of failed) {
1673
+ console.error(f.output);
1674
+ }
1675
+ console.error(`
1676
+ ${failed.length} verify script(s) failed:`);
1677
+ for (const f of failed) {
1678
+ console.error(` - ${f.script} (exit code ${f.code})`);
1679
+ }
1680
+ return false;
1681
+ }
1682
+ return true;
1683
+ }
1684
+ async function check(pattern2, options) {
1685
+ const verifyPassed = await runVerifyQuietly();
1686
+ if (!verifyPassed) {
1687
+ process.exit(1);
1688
+ }
1689
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
1690
+ const violations = getViolations(pattern2, options, maxLines);
1691
+ violations.sort((a, b) => b.lines - a.lines);
1692
+ logViolations(violations, maxLines);
1693
+ if (violations.length > 0) {
1694
+ process.exit(1);
1695
+ }
1696
+ }
1697
+
1698
+ // src/commands/refactor/ignore.ts
1699
+ import fs11 from "fs";
1700
+ import chalk26 from "chalk";
1701
+ var REFACTOR_YML_PATH2 = "refactor.yml";
1702
+ function ignore(file) {
1703
+ if (!fs11.existsSync(file)) {
1704
+ console.error(chalk26.red(`Error: File does not exist: ${file}`));
1705
+ process.exit(1);
1706
+ }
1707
+ const content = fs11.readFileSync(file, "utf-8");
1708
+ const lineCount = content.split("\n").length;
1709
+ const maxLines = lineCount + 10;
1710
+ const entry = `- file: ${file}
1711
+ maxLines: ${maxLines}
1712
+ `;
1713
+ if (fs11.existsSync(REFACTOR_YML_PATH2)) {
1714
+ const existing = fs11.readFileSync(REFACTOR_YML_PATH2, "utf-8");
1715
+ fs11.writeFileSync(REFACTOR_YML_PATH2, existing + entry);
1716
+ } else {
1717
+ fs11.writeFileSync(REFACTOR_YML_PATH2, entry);
1718
+ }
1719
+ console.log(
1720
+ chalk26.green(
1721
+ `Added ${file} to refactor ignore list (max ${maxLines} lines)`
1722
+ )
1723
+ );
1724
+ }
1725
+
1726
+ // src/commands/run.ts
1727
+ import { spawn as spawn2 } from "child_process";
1728
+ function run(name, args) {
1729
+ const config = loadConfig();
1730
+ if (!config.run || config.run.length === 0) {
1731
+ console.error("No run configurations found in assist.yml");
1732
+ process.exit(1);
1733
+ }
1734
+ const runConfig = config.run.find((r) => r.name === name);
1735
+ if (!runConfig) {
1736
+ console.error(`No run configuration found with name: ${name}`);
1737
+ console.error("Available configurations:");
1738
+ for (const r of config.run) {
1739
+ console.error(` - ${r.name}`);
1740
+ }
1741
+ process.exit(1);
1742
+ }
1743
+ const command = runConfig.command.includes(" ") ? `"${runConfig.command}"` : runConfig.command;
1744
+ const allArgs = [...runConfig.args ?? [], ...args];
1745
+ const quotedArgs = allArgs.map(
1746
+ (arg) => arg.includes(" ") ? `"${arg}"` : arg
1747
+ );
1748
+ const fullCommand = [command, ...quotedArgs].join(" ");
1749
+ const child = spawn2(fullCommand, [], {
1750
+ stdio: "inherit",
1751
+ shell: true
1752
+ });
1753
+ child.on("close", (code) => {
1754
+ process.exit(code ?? 0);
1755
+ });
1756
+ child.on("error", (err) => {
1757
+ console.error(`Failed to execute command: ${err.message}`);
1758
+ process.exit(1);
1759
+ });
1760
+ }
1761
+ function add() {
1762
+ const addIndex = process.argv.indexOf("add");
1763
+ if (addIndex === -1 || addIndex + 2 >= process.argv.length) {
1764
+ console.error("Usage: assist run add <name> <command> [args...]");
1765
+ process.exit(1);
1766
+ }
1767
+ const name = process.argv[addIndex + 1];
1768
+ const command = process.argv[addIndex + 2];
1769
+ const args = process.argv.slice(addIndex + 3);
1770
+ const config = loadConfig();
1771
+ if (!config.run) {
1772
+ config.run = [];
1773
+ }
1774
+ const existing = config.run.find((r) => r.name === name);
1775
+ if (existing) {
1776
+ console.error(`Run configuration with name "${name}" already exists`);
1777
+ process.exit(1);
1778
+ }
1779
+ const entry = {
1780
+ name,
1781
+ command
1782
+ };
1783
+ if (args.length > 0) {
1784
+ entry.args = args;
1785
+ }
1786
+ config.run.push(entry);
1787
+ saveConfig(config);
1788
+ const display = args.length > 0 ? `${command} ${args.join(" ")}` : command;
1789
+ console.log(`Added run configuration: ${name} -> ${display}`);
1790
+ }
1791
+
1792
+ // src/commands/sync.ts
1793
+ import * as fs13 from "fs";
1794
+ import * as os from "os";
1795
+ import * as path15 from "path";
1796
+ import { fileURLToPath as fileURLToPath4 } from "url";
1797
+
1798
+ // src/commands/sync/syncSettings.ts
1799
+ import * as fs12 from "fs";
1800
+ import * as path14 from "path";
1801
+ import chalk27 from "chalk";
1802
+ import enquirer6 from "enquirer";
1803
+ async function syncSettings(claudeDir, targetBase) {
1804
+ const source = path14.join(claudeDir, "settings.json");
1805
+ const target = path14.join(targetBase, "settings.json");
1806
+ const sourceContent = fs12.readFileSync(source, "utf-8");
1807
+ const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
1808
+ if (fs12.existsSync(target)) {
1809
+ const targetContent = fs12.readFileSync(target, "utf-8");
1810
+ const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
1811
+ if (normalizedSource !== normalizedTarget) {
1812
+ console.log(
1813
+ chalk27.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
1814
+ );
1815
+ console.log();
1816
+ printDiff(targetContent, sourceContent);
1817
+ const { confirm } = await enquirer6.prompt({
1818
+ type: "confirm",
1819
+ name: "confirm",
1820
+ message: chalk27.red("Overwrite existing settings.json?"),
1821
+ initial: false
1822
+ });
1823
+ if (!confirm) {
1824
+ console.log("Skipped settings.json");
1825
+ return;
1826
+ }
1827
+ }
1828
+ }
1829
+ fs12.copyFileSync(source, target);
1830
+ console.log("Copied settings.json to ~/.claude/settings.json");
1831
+ }
1832
+
1833
+ // src/commands/sync.ts
1834
+ var __filename2 = fileURLToPath4(import.meta.url);
1835
+ var __dirname5 = path15.dirname(__filename2);
1836
+ async function sync() {
1837
+ const claudeDir = path15.join(__dirname5, "..", "claude");
1838
+ const targetBase = path15.join(os.homedir(), ".claude");
1839
+ syncCommands(claudeDir, targetBase);
1840
+ await syncSettings(claudeDir, targetBase);
1841
+ }
1842
+ function syncCommands(claudeDir, targetBase) {
1843
+ const sourceDir = path15.join(claudeDir, "commands");
1844
+ const targetDir = path15.join(targetBase, "commands");
1845
+ fs13.mkdirSync(targetDir, { recursive: true });
1846
+ const files = fs13.readdirSync(sourceDir);
1847
+ for (const file of files) {
1848
+ fs13.copyFileSync(path15.join(sourceDir, file), path15.join(targetDir, file));
1849
+ console.log(`Copied ${file} to ${targetDir}`);
1850
+ }
1851
+ console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
1852
+ }
1853
+
1854
+ // src/commands/verify/hardcodedColors.ts
1855
+ import { execSync as execSync11 } from "child_process";
1856
+ var pattern = "0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,6}";
1857
+ function hardcodedColors() {
1858
+ try {
1859
+ const output = execSync11(`grep -rEnH '${pattern}' src/`, {
1860
+ encoding: "utf-8"
1861
+ });
1862
+ const lines = output.trim().split("\n");
1863
+ console.log("Hardcoded colors found:\n");
1864
+ for (const line of lines) {
1865
+ const match = line.match(/^(.+):(\d+):(.+)$/);
1866
+ if (match) {
1867
+ const [, file, lineNum, content] = match;
1868
+ const colorMatch = content.match(/0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,6}/);
1869
+ const color = colorMatch?.[0] ?? "unknown";
1870
+ console.log(`${file}:${lineNum} \u2192 ${color}`);
1871
+ }
1872
+ }
1873
+ console.log(`
1874
+ Total: ${lines.length} hardcoded color(s)`);
1875
+ console.log("\nUse colors from the 'open-color' (oc) library instead.");
1876
+ console.log("\nExample fix:");
1877
+ console.log(" Before: color: '#228be6'");
1878
+ console.log(" After: color: oc.blue[6]");
1879
+ console.log("\nImport open-color with: import oc from 'open-color'");
1880
+ process.exit(1);
1881
+ } catch {
1882
+ console.log("No hardcoded colors found.");
1883
+ process.exit(0);
1884
+ }
1885
+ }
1886
+
1887
+ // src/commands/verify/run.ts
1888
+ import { spawn as spawn3 } from "child_process";
1889
+ import * as path16 from "path";
1890
+ function formatDuration(ms) {
1891
+ if (ms < 1e3) {
1892
+ return `${ms}ms`;
1893
+ }
1894
+ const seconds = (ms / 1e3).toFixed(1);
1895
+ return `${seconds}s`;
1896
+ }
1897
+ function printTaskStatuses(tasks) {
1898
+ console.log("\n--- Task Status ---");
1899
+ for (const task of tasks) {
1900
+ if (task.endTime !== void 0) {
1901
+ const duration = formatDuration(task.endTime - task.startTime);
1902
+ const status = task.code === 0 ? "\u2713" : "\u2717";
1903
+ console.log(` ${status} ${task.script}: ${duration}`);
1904
+ } else {
1905
+ const elapsed = formatDuration(Date.now() - task.startTime);
1906
+ console.log(` \u22EF ${task.script}: running (${elapsed})`);
1907
+ }
1908
+ }
1909
+ console.log("-------------------\n");
1910
+ }
1911
+ async function run2(options = {}) {
1912
+ const { timer = false } = options;
1913
+ const result = findPackageJsonWithVerifyScripts(process.cwd());
1914
+ if (!result) {
1915
+ console.log("No package.json with verify:* scripts found");
1916
+ return;
1917
+ }
1918
+ const { packageJsonPath, verifyScripts } = result;
1919
+ const packageDir = path16.dirname(packageJsonPath);
1920
+ console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
1921
+ for (const script of verifyScripts) {
1922
+ console.log(` - ${script}`);
1923
+ }
1924
+ const taskStatuses = verifyScripts.map((script) => ({
1925
+ script,
1926
+ startTime: Date.now()
1927
+ }));
1928
+ const results = await Promise.all(
1929
+ verifyScripts.map(
1930
+ (script, index) => new Promise((resolve) => {
1931
+ const child = spawn3("npm", ["run", script], {
1932
+ stdio: "inherit",
1933
+ shell: true,
1934
+ cwd: packageDir
1935
+ });
1936
+ child.on("close", (code) => {
1937
+ const exitCode = code ?? 1;
1938
+ if (timer) {
1939
+ taskStatuses[index].endTime = Date.now();
1940
+ taskStatuses[index].code = exitCode;
1941
+ printTaskStatuses(taskStatuses);
1942
+ }
1943
+ resolve({ script, code: exitCode });
1944
+ });
1945
+ })
1946
+ )
1947
+ );
1948
+ const failed = results.filter((r) => r.code !== 0);
1949
+ if (failed.length > 0) {
1950
+ console.error(`
1951
+ ${failed.length} script(s) failed:`);
1952
+ for (const f of failed) {
1953
+ console.error(` - ${f.script} (exit code ${f.code})`);
1954
+ }
1955
+ process.exit(1);
1956
+ }
1957
+ console.log(`
1958
+ All ${verifyScripts.length} verify script(s) passed`);
1959
+ }
1960
+
1961
+ // src/index.ts
1962
+ var program = new Command();
1963
+ program.name("assist").description("CLI application").version("1.0.0");
1964
+ program.command("sync").description("Copy command files to ~/.claude/commands").action(sync);
1965
+ program.command("init").description("Initialize VS Code and verify configurations").action(init5);
1966
+ program.command("commit <message>").description("Create a git commit with validation").action(commit);
1967
+ program.command("update").description("Update claude-code to the latest version").action(() => {
1968
+ console.log("Updating claude-code...");
1969
+ execSync12("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
1970
+ });
1971
+ program.command("prs").description("List pull requests for the current repository").option("--open", "List only open pull requests").option("--closed", "List only closed pull requests").action(prs);
1972
+ var runCommand = program.command("run").description("Run a configured command from assist.yml").argument("<name>", "Name of the configured command").argument("[args...]", "Arguments to pass to the command").allowUnknownOption().action((name, args) => {
1973
+ run(name, args);
1974
+ });
1975
+ runCommand.command("add").description("Add a new run configuration to assist.yml").allowUnknownOption().allowExcessArguments().action(() => add());
1976
+ program.command("new").description("Initialize a new Vite React TypeScript project").action(newProject);
1977
+ var verifyCommand = program.command("verify").description("Run all verify:* scripts from package.json in parallel").option("--timer", "Show timing information for each task as they complete").action((options) => run2(options));
1978
+ verifyCommand.command("init").description("Add verify scripts to a project").action(init3);
1979
+ verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
1980
+ var lintCommand = program.command("lint").description("Run lint checks for conventions not enforced by biomejs").action(lint);
1981
+ lintCommand.command("init").description("Initialize Biome with standard linter config").action(init2);
1982
+ var refactorCommand = program.command("refactor").description("Run refactoring checks for code quality");
1983
+ refactorCommand.command("check [pattern]").description("Check for files that exceed the maximum line count").option("--modified", "Check only staged and unstaged files").option("--staged", "Check only staged files").option("--unstaged", "Check only unstaged files").option(
1984
+ "--max-lines <number>",
1985
+ "Maximum lines allowed per file (default: 100)",
1986
+ Number.parseInt
1987
+ ).action(check);
1988
+ refactorCommand.command("ignore <file>").description("Add a file to the refactor ignore list").action(ignore);
1989
+ var devlogCommand = program.command("devlog").description("Development log utilities");
1990
+ devlogCommand.command("list").description("Group git commits by date").option(
1991
+ "--days <number>",
1992
+ "Number of days to show (default: 30)",
1993
+ Number.parseInt
1994
+ ).option("--since <date>", "Only show commits since this date (YYYY-MM-DD)").option("-r, --reverse", "Show earliest commits first").option("-v, --verbose", "Show file names for each commit").action(list);
1995
+ devlogCommand.command("version").description("Show current repo name and version info").action(version);
1996
+ devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
1997
+ devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
1998
+ var vscodeCommand = program.command("vscode").description("VS Code configuration utilities");
1999
+ vscodeCommand.command("init").description("Add VS Code configuration files").action(init4);
2000
+ var deployCommand = program.command("deploy").description("Netlify deployment utilities");
2001
+ deployCommand.command("init").description("Initialize Netlify project and configure deployment").action(init);
2002
+ program.command("enable-ralph").description("Enable ralph-wiggum plugin for spacetraders").action(enableRalph);
2003
+ program.parse();