@staff0rd/assist 0.94.1 → 0.96.0

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 CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.94.1",
9
+ version: "0.96.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -15,7 +15,8 @@ var package_default = {
15
15
  },
16
16
  files: [
17
17
  "dist",
18
- "claude"
18
+ "claude",
19
+ "assist.cli-reads"
19
20
  ],
20
21
  publishConfig: {
21
22
  access: "public"
@@ -23,7 +24,8 @@ var package_default = {
23
24
  scripts: {
24
25
  build: "tsup",
25
26
  start: "node dist/index.js",
26
- "check:types": "tsc --noEmit"
27
+ "check:types": "tsc --noEmit",
28
+ test: "vitest run"
27
29
  },
28
30
  keywords: [],
29
31
  author: "",
@@ -65,7 +67,8 @@ var package_default = {
65
67
  react: "^19.2.4",
66
68
  "react-dom": "^19.2.4",
67
69
  "semantic-release": "^25.0.2",
68
- tsup: "^8.5.1"
70
+ tsup: "^8.5.1",
71
+ vitest: "^4.0.18"
69
72
  }
70
73
  };
71
74
 
@@ -73,11 +76,24 @@ var package_default = {
73
76
  import { execSync } from "child_process";
74
77
 
75
78
  // src/shared/loadConfig.ts
76
- import { existsSync, readFileSync, writeFileSync } from "fs";
79
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
77
80
  import { homedir } from "os";
78
81
  import { basename, join } from "path";
79
82
  import chalk from "chalk";
80
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
83
+ import { stringify as stringifyYaml } from "yaml";
84
+
85
+ // src/shared/loadRawYaml.ts
86
+ import { existsSync, readFileSync } from "fs";
87
+ import { parse as parseYaml } from "yaml";
88
+ function loadRawYaml(path31) {
89
+ if (!existsSync(path31)) return {};
90
+ try {
91
+ const content = readFileSync(path31, "utf-8");
92
+ return parseYaml(content) || {};
93
+ } catch {
94
+ return {};
95
+ }
96
+ }
81
97
 
82
98
  // src/shared/types.ts
83
99
  import { z } from "zod";
@@ -129,6 +145,7 @@ var assistConfigSchema = z.strictObject({
129
145
  }).optional(),
130
146
  run: z.array(runConfigSchema).optional(),
131
147
  transcript: transcriptConfigSchema.optional(),
148
+ cliReadVerbs: z.record(z.string(), z.array(z.string())).optional(),
132
149
  voice: z.strictObject({
133
150
  wakeWords: z.array(z.string()).default(DEFAULT_WAKE_WORDS),
134
151
  mic: z.string().optional(),
@@ -150,7 +167,7 @@ var assistConfigSchema = z.strictObject({
150
167
  // src/shared/loadConfig.ts
151
168
  function getConfigPath() {
152
169
  const claudeConfigPath = join(process.cwd(), ".claude", "assist.yml");
153
- if (existsSync(claudeConfigPath)) {
170
+ if (existsSync2(claudeConfigPath)) {
154
171
  return claudeConfigPath;
155
172
  }
156
173
  return join(process.cwd(), "assist.yml");
@@ -158,26 +175,17 @@ function getConfigPath() {
158
175
  function getGlobalConfigPath() {
159
176
  return join(homedir(), ".assist.yml");
160
177
  }
161
- function loadRawConfig(path31) {
162
- if (!existsSync(path31)) return {};
163
- try {
164
- const content = readFileSync(path31, "utf-8");
165
- return parseYaml(content) || {};
166
- } catch {
167
- return {};
168
- }
169
- }
170
178
  function loadConfig() {
171
- const globalRaw = loadRawConfig(getGlobalConfigPath());
172
- const projectRaw = loadRawConfig(getConfigPath());
179
+ const globalRaw = loadRawYaml(getGlobalConfigPath());
180
+ const projectRaw = loadRawYaml(getConfigPath());
173
181
  const merged = { ...globalRaw, ...projectRaw };
174
182
  return assistConfigSchema.parse(merged);
175
183
  }
176
184
  function loadProjectConfig() {
177
- return loadRawConfig(getConfigPath());
185
+ return loadRawYaml(getConfigPath());
178
186
  }
179
187
  function loadGlobalConfigRaw() {
180
- return loadRawConfig(getGlobalConfigPath());
188
+ return loadRawYaml(getGlobalConfigPath());
181
189
  }
182
190
  function saveGlobalConfig(config) {
183
191
  writeFileSync(getGlobalConfigPath(), stringifyYaml(config, { lineWidth: 0 }));
@@ -192,9 +200,9 @@ function getRepoName() {
192
200
  return config.devlog.name;
193
201
  }
194
202
  const packageJsonPath = join(process.cwd(), "package.json");
195
- if (existsSync(packageJsonPath)) {
203
+ if (existsSync2(packageJsonPath)) {
196
204
  try {
197
- const content = readFileSync(packageJsonPath, "utf-8");
205
+ const content = readFileSync2(packageJsonPath, "utf-8");
198
206
  const pkg = JSON.parse(content);
199
207
  if (pkg.name) {
200
208
  return pkg.name;
@@ -646,7 +654,7 @@ import chalk12 from "chalk";
646
654
 
647
655
  // src/commands/lint/init.ts
648
656
  import { execSync as execSync4 } from "child_process";
649
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
657
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
650
658
  import { dirname as dirname5, join as join4 } from "path";
651
659
  import { fileURLToPath } from "url";
652
660
  import chalk11 from "chalk";
@@ -670,10 +678,10 @@ async function promptConfirm(message, initial = true) {
670
678
 
671
679
  // src/shared/removeEslint/index.ts
672
680
  import { execSync as execSync3 } from "child_process";
673
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
681
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
674
682
 
675
683
  // src/shared/removeEslint/removeEslintConfigFiles.ts
676
- import { existsSync as existsSync4, unlinkSync } from "fs";
684
+ import { existsSync as existsSync5, unlinkSync } from "fs";
677
685
  var ESLINT_CONFIG_FILES = [
678
686
  "eslint.config.js",
679
687
  "eslint.config.mjs",
@@ -689,7 +697,7 @@ var ESLINT_CONFIG_FILES = [
689
697
  function removeEslintConfigFiles() {
690
698
  let removed = false;
691
699
  for (const configFile of ESLINT_CONFIG_FILES) {
692
- if (existsSync4(configFile)) {
700
+ if (existsSync5(configFile)) {
693
701
  unlinkSync(configFile);
694
702
  console.log(`Removed ${configFile}`);
695
703
  removed = true;
@@ -711,10 +719,10 @@ function removeEslint(options2 = {}) {
711
719
  }
712
720
  function removeEslintFromPackageJson(options2) {
713
721
  const packageJsonPath = "package.json";
714
- if (!existsSync5(packageJsonPath)) {
722
+ if (!existsSync6(packageJsonPath)) {
715
723
  return false;
716
724
  }
717
- const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
725
+ const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
718
726
  let modified = false;
719
727
  modified = removeEslintDeps(packageJson.dependencies) || modified;
720
728
  modified = removeEslintDeps(packageJson.devDependencies) || modified;
@@ -784,17 +792,17 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
784
792
  async function init() {
785
793
  removeEslint();
786
794
  const biomeConfigPath = "biome.json";
787
- if (!existsSync6(biomeConfigPath)) {
795
+ if (!existsSync7(biomeConfigPath)) {
788
796
  console.log("Initializing Biome...");
789
797
  execSync4("npx @biomejs/biome init", { stdio: "inherit" });
790
798
  }
791
- if (!existsSync6(biomeConfigPath)) {
799
+ if (!existsSync7(biomeConfigPath)) {
792
800
  console.log("No biome.json found, skipping linter config");
793
801
  return;
794
802
  }
795
803
  const linterConfigPath = join4(__dirname2, "commands/lint/biome.linter.json");
796
- const linterConfig = JSON.parse(readFileSync5(linterConfigPath, "utf-8"));
797
- const biomeConfig = JSON.parse(readFileSync5(biomeConfigPath, "utf-8"));
804
+ const linterConfig = JSON.parse(readFileSync6(linterConfigPath, "utf-8"));
805
+ const biomeConfig = JSON.parse(readFileSync6(biomeConfigPath, "utf-8"));
798
806
  const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
799
807
  `;
800
808
  biomeConfig.linter = linterConfig.linter;
@@ -1617,14 +1625,14 @@ function flushIfFailed(exitCode, chunks) {
1617
1625
 
1618
1626
  // src/commands/verify/run/runAllEntries.ts
1619
1627
  function runEntry(entry, onComplete) {
1620
- return new Promise((resolve3) => {
1628
+ return new Promise((resolve5) => {
1621
1629
  const child = spawnCommand(entry.fullCommand, entry.cwd, entry.env);
1622
1630
  const chunks = collectOutput(child);
1623
1631
  child.on("close", (code) => {
1624
1632
  const exitCode = code ?? 1;
1625
1633
  flushIfFailed(exitCode, chunks);
1626
1634
  onComplete?.(exitCode);
1627
- resolve3({ script: entry.name, code: exitCode });
1635
+ resolve5({ script: entry.name, code: exitCode });
1628
1636
  });
1629
1637
  });
1630
1638
  }
@@ -1769,7 +1777,7 @@ async function newCli() {
1769
1777
 
1770
1778
  // src/commands/new/registerNew/newProject.ts
1771
1779
  import { execSync as execSync12 } from "child_process";
1772
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
1780
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
1773
1781
 
1774
1782
  // src/commands/deploy/init/index.ts
1775
1783
  import { execSync as execSync11 } from "child_process";
@@ -1777,33 +1785,33 @@ import chalk21 from "chalk";
1777
1785
  import enquirer3 from "enquirer";
1778
1786
 
1779
1787
  // src/commands/deploy/init/updateWorkflow.ts
1780
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
1788
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
1781
1789
  import { dirname as dirname10, join as join7 } from "path";
1782
1790
  import { fileURLToPath as fileURLToPath2 } from "url";
1783
1791
  import chalk20 from "chalk";
1784
1792
  var WORKFLOW_PATH = ".github/workflows/build.yml";
1785
1793
  var __dirname3 = dirname10(fileURLToPath2(import.meta.url));
1786
1794
  function getExistingSiteId() {
1787
- if (!existsSync9(WORKFLOW_PATH)) {
1795
+ if (!existsSync10(WORKFLOW_PATH)) {
1788
1796
  return null;
1789
1797
  }
1790
- const content = readFileSync7(WORKFLOW_PATH, "utf-8");
1798
+ const content = readFileSync8(WORKFLOW_PATH, "utf-8");
1791
1799
  const match = content.match(/-s\s+([a-f0-9-]{36})/);
1792
1800
  return match ? match[1] : null;
1793
1801
  }
1794
1802
  function getTemplateContent(siteId) {
1795
1803
  const templatePath = join7(__dirname3, "commands/deploy/build.yml");
1796
- const template = readFileSync7(templatePath, "utf-8");
1804
+ const template = readFileSync8(templatePath, "utf-8");
1797
1805
  return template.replace("{{NETLIFY_SITE_ID}}", siteId);
1798
1806
  }
1799
1807
  async function updateWorkflow(siteId) {
1800
1808
  const newContent = getTemplateContent(siteId);
1801
1809
  const workflowDir = ".github/workflows";
1802
- if (!existsSync9(workflowDir)) {
1810
+ if (!existsSync10(workflowDir)) {
1803
1811
  mkdirSync3(workflowDir, { recursive: true });
1804
1812
  }
1805
- if (existsSync9(WORKFLOW_PATH)) {
1806
- const oldContent = readFileSync7(WORKFLOW_PATH, "utf-8");
1813
+ if (existsSync10(WORKFLOW_PATH)) {
1814
+ const oldContent = readFileSync8(WORKFLOW_PATH, "utf-8");
1807
1815
  if (oldContent === newContent) {
1808
1816
  console.log(chalk20.green("build.yml is already up to date"));
1809
1817
  return;
@@ -1897,11 +1905,11 @@ async function newProject() {
1897
1905
  }
1898
1906
  function addViteBaseConfig() {
1899
1907
  const viteConfigPath = "vite.config.ts";
1900
- if (!existsSync10(viteConfigPath)) {
1908
+ if (!existsSync11(viteConfigPath)) {
1901
1909
  console.log("No vite.config.ts found, skipping base config");
1902
1910
  return;
1903
1911
  }
1904
- const content = readFileSync8(viteConfigPath, "utf-8");
1912
+ const content = readFileSync9(viteConfigPath, "utf-8");
1905
1913
  if (content.includes("base:")) {
1906
1914
  console.log("vite.config.ts already has base config");
1907
1915
  return;
@@ -2044,11 +2052,11 @@ async function notify() {
2044
2052
  }
2045
2053
 
2046
2054
  // src/commands/backlog/add/index.ts
2047
- import { existsSync as existsSync12 } from "fs";
2055
+ import { existsSync as existsSync13 } from "fs";
2048
2056
  import chalk23 from "chalk";
2049
2057
 
2050
2058
  // src/commands/backlog/shared.ts
2051
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
2059
+ import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
2052
2060
  import { join as join8 } from "path";
2053
2061
  import chalk22 from "chalk";
2054
2062
  import { parse as parseYaml2, stringify as stringifyYaml3 } from "yaml";
@@ -2073,11 +2081,11 @@ function getBacklogPath() {
2073
2081
  }
2074
2082
  function loadBacklog() {
2075
2083
  const backlogPath = getBacklogPath();
2076
- if (!existsSync11(backlogPath)) {
2084
+ if (!existsSync12(backlogPath)) {
2077
2085
  return [];
2078
2086
  }
2079
2087
  try {
2080
- const content = readFileSync9(backlogPath, "utf-8");
2088
+ const content = readFileSync10(backlogPath, "utf-8");
2081
2089
  const raw = parseYaml2(content) || [];
2082
2090
  return backlogFileSchema.parse(raw);
2083
2091
  } catch {
@@ -2092,7 +2100,7 @@ function findItem(items, id) {
2092
2100
  return items.find((item) => item.id === id);
2093
2101
  }
2094
2102
  function loadAndFindItem(id) {
2095
- if (!existsSync11(getBacklogPath())) {
2103
+ if (!existsSync12(getBacklogPath())) {
2096
2104
  console.log(
2097
2105
  chalk22.yellow(
2098
2106
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2128,7 +2136,7 @@ function getNextId(items) {
2128
2136
 
2129
2137
  // src/commands/backlog/add/shared.ts
2130
2138
  import { spawnSync } from "child_process";
2131
- import { mkdtempSync, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
2139
+ import { mkdtempSync, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
2132
2140
  import { tmpdir } from "os";
2133
2141
  import { join as join9 } from "path";
2134
2142
  import enquirer4 from "enquirer";
@@ -2178,7 +2186,7 @@ function openEditor() {
2178
2186
  unlinkSync2(filePath);
2179
2187
  return void 0;
2180
2188
  }
2181
- const content = readFileSync10(filePath, "utf-8").trim();
2189
+ const content = readFileSync11(filePath, "utf-8").trim();
2182
2190
  unlinkSync2(filePath);
2183
2191
  return content || void 0;
2184
2192
  }
@@ -2199,7 +2207,7 @@ async function promptAcceptanceCriteria() {
2199
2207
  // src/commands/backlog/add/index.ts
2200
2208
  async function add() {
2201
2209
  const backlogPath = getBacklogPath();
2202
- if (!existsSync12(backlogPath)) {
2210
+ if (!existsSync13(backlogPath)) {
2203
2211
  console.log(
2204
2212
  chalk23.yellow(
2205
2213
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2244,11 +2252,11 @@ async function done(id) {
2244
2252
  }
2245
2253
 
2246
2254
  // src/commands/backlog/init/index.ts
2247
- import { existsSync as existsSync13 } from "fs";
2255
+ import { existsSync as existsSync14 } from "fs";
2248
2256
  import chalk26 from "chalk";
2249
2257
  async function init6() {
2250
2258
  const backlogPath = getBacklogPath();
2251
- if (existsSync13(backlogPath)) {
2259
+ if (existsSync14(backlogPath)) {
2252
2260
  console.log(chalk26.yellow("assist.backlog.yml already exists."));
2253
2261
  return;
2254
2262
  }
@@ -2257,7 +2265,7 @@ async function init6() {
2257
2265
  }
2258
2266
 
2259
2267
  // src/commands/backlog/list/index.ts
2260
- import { existsSync as existsSync14 } from "fs";
2268
+ import { existsSync as existsSync15 } from "fs";
2261
2269
  import chalk27 from "chalk";
2262
2270
  function statusIcon(status2) {
2263
2271
  switch (status2) {
@@ -2296,7 +2304,7 @@ function filterItems(items, options2) {
2296
2304
  }
2297
2305
  async function list2(options2) {
2298
2306
  const backlogPath = getBacklogPath();
2299
- if (!existsSync14(backlogPath)) {
2307
+ if (!existsSync15(backlogPath)) {
2300
2308
  console.log(
2301
2309
  chalk27.yellow(
2302
2310
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2334,7 +2342,7 @@ import { createServer } from "http";
2334
2342
  import chalk29 from "chalk";
2335
2343
 
2336
2344
  // src/commands/backlog/web/handleRequest.ts
2337
- import { readFileSync as readFileSync11 } from "fs";
2345
+ import { readFileSync as readFileSync12 } from "fs";
2338
2346
  import { dirname as dirname11, join as join10 } from "path";
2339
2347
  import { fileURLToPath as fileURLToPath3 } from "url";
2340
2348
 
@@ -2367,12 +2375,12 @@ function respondJson(res, status2, data) {
2367
2375
  res.end(JSON.stringify(data));
2368
2376
  }
2369
2377
  function readBody(req) {
2370
- return new Promise((resolve3, reject) => {
2378
+ return new Promise((resolve5, reject) => {
2371
2379
  let body = "";
2372
2380
  req.on("data", (chunk) => {
2373
2381
  body += chunk.toString();
2374
2382
  });
2375
- req.on("end", () => resolve3(body));
2383
+ req.on("end", () => resolve5(body));
2376
2384
  req.on("error", reject);
2377
2385
  });
2378
2386
  }
@@ -2437,7 +2445,7 @@ var __dirname4 = dirname11(fileURLToPath3(import.meta.url));
2437
2445
  var bundleCache;
2438
2446
  function serveBundle(_req, res) {
2439
2447
  if (!bundleCache) {
2440
- bundleCache = readFileSync11(
2448
+ bundleCache = readFileSync12(
2441
2449
  join10(__dirname4, "commands/backlog/web/bundle.js"),
2442
2450
  "utf-8"
2443
2451
  );
@@ -2509,6 +2517,167 @@ function registerBacklog(program2) {
2509
2517
  backlogCommand.command("web").description("Start a web view of the backlog").option("-p, --port <number>", "Port to listen on", "3000").action(web);
2510
2518
  }
2511
2519
 
2520
+ // src/shared/tokenize.ts
2521
+ function tokenize(command) {
2522
+ const tokens = [];
2523
+ let current = "";
2524
+ let inSingle = false;
2525
+ let inDouble = false;
2526
+ for (let i = 0; i < command.length; i++) {
2527
+ const ch = command[i];
2528
+ if (ch === "'" && !inDouble) {
2529
+ inSingle = !inSingle;
2530
+ current += ch;
2531
+ } else if (ch === '"' && !inSingle) {
2532
+ inDouble = !inDouble;
2533
+ current += ch;
2534
+ } else if (ch === "\\" && inDouble && i + 1 < command.length) {
2535
+ current += ch + command[++i];
2536
+ } else if (/\s/.test(ch) && !inSingle && !inDouble) {
2537
+ if (current) {
2538
+ tokens.push(current);
2539
+ current = "";
2540
+ }
2541
+ } else {
2542
+ current += ch;
2543
+ }
2544
+ }
2545
+ if (current) tokens.push(current);
2546
+ return tokens;
2547
+ }
2548
+
2549
+ // src/shared/isGhApiRead.ts
2550
+ var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
2551
+ var BODY_FLAGS = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
2552
+ var FIELD_FLAGS = /* @__PURE__ */ new Set(["-f", "--field", "-F"]);
2553
+ function isGhApiRead(command) {
2554
+ const tokens = tokenize(command);
2555
+ if (tokens.length < 2 || tokens[0] !== "gh" || tokens[1] !== "api") {
2556
+ return false;
2557
+ }
2558
+ const args = tokens.slice(2);
2559
+ if (args.includes("graphql")) {
2560
+ return isGraphqlRead(args);
2561
+ }
2562
+ const method = extractMethod(args);
2563
+ if (method) return READ_METHODS.has(method.toUpperCase());
2564
+ if (args.some((t) => BODY_FLAGS.has(t))) return false;
2565
+ return true;
2566
+ }
2567
+ function extractMethod(args) {
2568
+ for (let i = 0; i < args.length; i++) {
2569
+ const arg = args[i];
2570
+ if (arg.startsWith("--method=")) return arg.slice("--method=".length);
2571
+ if (arg.startsWith("-X=")) return arg.slice("-X=".length);
2572
+ if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
2573
+ return args[i + 1];
2574
+ }
2575
+ }
2576
+ return void 0;
2577
+ }
2578
+ function isGraphqlRead(args) {
2579
+ const method = extractMethod(args);
2580
+ if (method) return READ_METHODS.has(method.toUpperCase());
2581
+ const queryBody = extractGraphqlQuery(args);
2582
+ if (!queryBody) return false;
2583
+ const trimmed = queryBody.replace(/^['"]|['"]$/g, "").trimStart();
2584
+ if (trimmed.startsWith("mutation")) return false;
2585
+ if (trimmed.startsWith("query") || trimmed.startsWith("{")) return true;
2586
+ return false;
2587
+ }
2588
+ function extractGraphqlQuery(args) {
2589
+ for (let i = 0; i < args.length; i++) {
2590
+ if (FIELD_FLAGS.has(args[i]) && i + 1 < args.length) {
2591
+ const match = args[i + 1].match(/^query=(.*)/s);
2592
+ if (match) return match[1];
2593
+ }
2594
+ }
2595
+ return void 0;
2596
+ }
2597
+
2598
+ // src/shared/loadCliReads.ts
2599
+ import { execFileSync } from "child_process";
2600
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
2601
+ import { dirname as dirname12, resolve as resolve2 } from "path";
2602
+ import { fileURLToPath as fileURLToPath4 } from "url";
2603
+ var __filename2 = fileURLToPath4(import.meta.url);
2604
+ var __dirname5 = dirname12(__filename2);
2605
+ function getCliReadsPath() {
2606
+ return resolve2(__dirname5, "..", "assist.cli-reads");
2607
+ }
2608
+ function loadCliReads() {
2609
+ const path31 = getCliReadsPath();
2610
+ if (!existsSync16(path31)) return [];
2611
+ return readFileSync13(path31, "utf-8").split("\n").filter((line) => line.trim() !== "");
2612
+ }
2613
+ function saveCliReads(commands) {
2614
+ writeFileSync12(getCliReadsPath(), `${commands.join("\n")}
2615
+ `);
2616
+ }
2617
+ function findCliRead(command) {
2618
+ const filePath = getCliReadsPath();
2619
+ if (!existsSync16(filePath)) return void 0;
2620
+ const words = command.split(/\s+/);
2621
+ if (words.length < 2) return void 0;
2622
+ const prefix2 = `${words[0]} ${words[1]}`;
2623
+ let candidates;
2624
+ try {
2625
+ const output = execFileSync("grep", ["-E", `^${prefix2}( |$)`, filePath], {
2626
+ encoding: "utf-8"
2627
+ });
2628
+ candidates = output.split("\n").filter((l) => l !== "");
2629
+ } catch {
2630
+ return void 0;
2631
+ }
2632
+ return candidates.sort((a, b) => b.length - a.length).find((rc) => command === rc || command.startsWith(`${rc} `));
2633
+ }
2634
+
2635
+ // src/commands/cliHook/index.ts
2636
+ async function cliHook() {
2637
+ const inputData = await readStdin();
2638
+ let data;
2639
+ try {
2640
+ data = JSON.parse(inputData);
2641
+ } catch {
2642
+ return;
2643
+ }
2644
+ if (data.tool_name !== "Bash" || !data.tool_input?.command) {
2645
+ return;
2646
+ }
2647
+ const command = data.tool_input.command.trim();
2648
+ const matched = findCliRead(command);
2649
+ if (matched) {
2650
+ console.log(
2651
+ JSON.stringify({
2652
+ hookSpecificOutput: {
2653
+ hookEventName: "PreToolUse",
2654
+ permissionDecision: "allow",
2655
+ permissionDecisionReason: `Read-only CLI command: ${matched}`
2656
+ }
2657
+ })
2658
+ );
2659
+ return;
2660
+ }
2661
+ if (isGhApiRead(command)) {
2662
+ console.log(
2663
+ JSON.stringify({
2664
+ hookSpecificOutput: {
2665
+ hookEventName: "PreToolUse",
2666
+ permissionDecision: "allow",
2667
+ permissionDecisionReason: "Read-only gh api command"
2668
+ }
2669
+ })
2670
+ );
2671
+ }
2672
+ }
2673
+
2674
+ // src/commands/registerCliHook.ts
2675
+ function registerCliHook(program2) {
2676
+ program2.command("cli-hook").description("PreToolUse hook for auto-approving read-only CLI commands").action(() => {
2677
+ cliHook();
2678
+ });
2679
+ }
2680
+
2512
2681
  // src/commands/complexity/analyze.ts
2513
2682
  import chalk35 from "chalk";
2514
2683
 
@@ -2595,8 +2764,8 @@ function getNodeName(node) {
2595
2764
  return getIdentifierText(node.name);
2596
2765
  if (ts.isArrowFunction(node)) return getArrowFunctionName(node);
2597
2766
  if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
2598
- const prefix = ts.isGetAccessor(node) ? "get " : "set ";
2599
- return `${prefix}${getIdentifierText(node.name)}`;
2767
+ const prefix2 = ts.isGetAccessor(node) ? "get " : "set ";
2768
+ return `${prefix2}${getIdentifierText(node.name)}`;
2600
2769
  }
2601
2770
  if (ts.isConstructorDeclaration(node)) return "constructor";
2602
2771
  return "<unknown>";
@@ -3003,7 +3172,7 @@ function registerComplexity(program2) {
3003
3172
  }
3004
3173
 
3005
3174
  // src/commands/deploy/redirect.ts
3006
- import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
3175
+ import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
3007
3176
  import chalk36 from "chalk";
3008
3177
  var TRAILING_SLASH_SCRIPT = ` <script>
3009
3178
  if (!window.location.pathname.endsWith('/')) {
@@ -3012,11 +3181,11 @@ var TRAILING_SLASH_SCRIPT = ` <script>
3012
3181
  </script>`;
3013
3182
  function redirect() {
3014
3183
  const indexPath = "index.html";
3015
- if (!existsSync15(indexPath)) {
3184
+ if (!existsSync17(indexPath)) {
3016
3185
  console.log(chalk36.yellow("No index.html found"));
3017
3186
  return;
3018
3187
  }
3019
- const content = readFileSync12(indexPath, "utf-8");
3188
+ const content = readFileSync14(indexPath, "utf-8");
3020
3189
  if (content.includes("window.location.pathname.endsWith('/')")) {
3021
3190
  console.log(chalk36.dim("Trailing slash script already present"));
3022
3191
  return;
@@ -3027,7 +3196,7 @@ function redirect() {
3027
3196
  return;
3028
3197
  }
3029
3198
  const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
3030
- writeFileSync12(indexPath, newContent);
3199
+ writeFileSync13(indexPath, newContent);
3031
3200
  console.log(chalk36.green("Added trailing slash redirect to index.html"));
3032
3201
  }
3033
3202
 
@@ -3047,7 +3216,7 @@ import { execSync as execSync13 } from "child_process";
3047
3216
  import chalk37 from "chalk";
3048
3217
 
3049
3218
  // src/commands/devlog/loadDevlogEntries.ts
3050
- import { readdirSync, readFileSync as readFileSync13 } from "fs";
3219
+ import { readdirSync, readFileSync as readFileSync15 } from "fs";
3051
3220
  import { homedir as homedir3 } from "os";
3052
3221
  import { join as join11 } from "path";
3053
3222
  var DEVLOG_DIR = join11(homedir3(), "git/blog/src/content/devlog");
@@ -3056,7 +3225,7 @@ function loadDevlogEntries(repoName) {
3056
3225
  try {
3057
3226
  const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
3058
3227
  for (const file of files) {
3059
- const content = readFileSync13(join11(DEVLOG_DIR, file), "utf-8");
3228
+ const content = readFileSync15(join11(DEVLOG_DIR, file), "utf-8");
3060
3229
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
3061
3230
  if (frontmatterMatch) {
3062
3231
  const frontmatter = frontmatterMatch[1];
@@ -3404,14 +3573,366 @@ function registerDevlog(program2) {
3404
3573
  devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
3405
3574
  }
3406
3575
 
3576
+ // src/commands/permitCliReads/index.ts
3577
+ import { existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
3578
+ import { homedir as homedir4 } from "os";
3579
+ import { join as join12 } from "path";
3580
+
3581
+ // src/shared/getInstallDir.ts
3582
+ import { execSync as execSync17 } from "child_process";
3583
+ import { dirname as dirname13, resolve as resolve3 } from "path";
3584
+ import { fileURLToPath as fileURLToPath5 } from "url";
3585
+ var __filename3 = fileURLToPath5(import.meta.url);
3586
+ var __dirname6 = dirname13(__filename3);
3587
+ function getInstallDir() {
3588
+ return resolve3(__dirname6, "..");
3589
+ }
3590
+ function isGitRepo(dir) {
3591
+ try {
3592
+ const result = execSync17("git rev-parse --show-toplevel", {
3593
+ cwd: dir,
3594
+ stdio: "pipe"
3595
+ }).toString().trim();
3596
+ return resolve3(result) === resolve3(dir);
3597
+ } catch {
3598
+ return false;
3599
+ }
3600
+ }
3601
+
3602
+ // src/commands/permitCliReads/assertCliExists.ts
3603
+ import { execSync as execSync18 } from "child_process";
3604
+ function assertCliExists(cli) {
3605
+ const binary = cli.split(/\s+/)[0];
3606
+ const opts = {
3607
+ encoding: "utf-8",
3608
+ stdio: ["ignore", "pipe", "pipe"]
3609
+ };
3610
+ try {
3611
+ execSync18(`command -v ${binary}`, opts);
3612
+ } catch {
3613
+ try {
3614
+ execSync18(`where ${binary}`, opts);
3615
+ } catch {
3616
+ console.error(`CLI "${cli}" not found in PATH`);
3617
+ process.exit(1);
3618
+ }
3619
+ }
3620
+ }
3621
+
3622
+ // src/commands/permitCliReads/colorize.ts
3623
+ import chalk43 from "chalk";
3624
+ function colorize(plainOutput) {
3625
+ return plainOutput.split("\n").map((line) => {
3626
+ if (line.startsWith(" R ")) return chalk43.green(line);
3627
+ if (line.startsWith(" W ")) return chalk43.red(line);
3628
+ return line;
3629
+ }).join("\n");
3630
+ }
3631
+
3632
+ // src/lib/isClaudeCode.ts
3633
+ function isClaudeCode() {
3634
+ return process.env.CLAUDECODE !== void 0;
3635
+ }
3636
+
3637
+ // src/commands/permitCliReads/mapAsync.ts
3638
+ async function mapAsync(items, concurrency, fn) {
3639
+ const results = new Array(items.length);
3640
+ let next2 = 0;
3641
+ async function worker() {
3642
+ while (next2 < items.length) {
3643
+ const idx = next2++;
3644
+ results[idx] = await fn(items[idx]);
3645
+ }
3646
+ }
3647
+ const workers = Array.from(
3648
+ { length: Math.min(concurrency, items.length) },
3649
+ () => worker()
3650
+ );
3651
+ await Promise.all(workers);
3652
+ return results;
3653
+ }
3654
+
3655
+ // src/commands/permitCliReads/parseCommands.ts
3656
+ var COMMAND_SECTION_RE = /^((?:core |general |available |additional |other |management |targeted |alias |github actions )?(?:commands|subgroups)):?$/i;
3657
+ function isSkippable(name) {
3658
+ return name.startsWith("-") || name.startsWith("<") || name.startsWith("[");
3659
+ }
3660
+ function parseCommandLine(trimmed) {
3661
+ const azMatch = trimmed.match(/^(\S+)\s+(?:\[.*?]\s+)?:\s*(.+)/);
3662
+ if (azMatch && !isSkippable(azMatch[1])) {
3663
+ return { name: azMatch[1], description: azMatch[2].trim() };
3664
+ }
3665
+ const colonMatch = trimmed.match(/^(\S+?):\s{2,}(.+)/);
3666
+ if (colonMatch && !isSkippable(colonMatch[1])) {
3667
+ return { name: colonMatch[1], description: colonMatch[2].trim() };
3668
+ }
3669
+ const spaceMatch = trimmed.match(/^(\S+)(?:,\s*\S+)?\s{2,}(.+)/);
3670
+ if (spaceMatch && !isSkippable(spaceMatch[1])) {
3671
+ return { name: spaceMatch[1], description: spaceMatch[2].trim() };
3672
+ }
3673
+ if (/^\S+$/.test(trimmed) && !isSkippable(trimmed)) {
3674
+ return { name: trimmed, description: "" };
3675
+ }
3676
+ return void 0;
3677
+ }
3678
+ function parseCommands(helpText) {
3679
+ const commands = [];
3680
+ let inCommandSection = false;
3681
+ for (const line of helpText.split("\n")) {
3682
+ const trimmed = line.trim();
3683
+ if (COMMAND_SECTION_RE.test(trimmed)) {
3684
+ inCommandSection = true;
3685
+ continue;
3686
+ }
3687
+ if (inCommandSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
3688
+ inCommandSection = false;
3689
+ continue;
3690
+ }
3691
+ if (!inCommandSection || !trimmed) continue;
3692
+ if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
3693
+ const parsed = parseCommandLine(trimmed);
3694
+ if (parsed) commands.push(parsed);
3695
+ }
3696
+ return commands;
3697
+ }
3698
+ var COMMAND_SECTION_MULTILINE_RE = new RegExp(
3699
+ COMMAND_SECTION_RE.source,
3700
+ "im"
3701
+ );
3702
+ function hasSubcommands(helpText) {
3703
+ return COMMAND_SECTION_MULTILINE_RE.test(helpText);
3704
+ }
3705
+
3706
+ // src/commands/permitCliReads/runHelp.ts
3707
+ import { exec as exec2 } from "child_process";
3708
+ function runHelp(args) {
3709
+ return new Promise((resolve5) => {
3710
+ exec2(
3711
+ `${args.join(" ")} --help`,
3712
+ { encoding: "utf-8", timeout: 3e4 },
3713
+ (_err, stdout, stderr) => {
3714
+ resolve5(stdout || stderr || "");
3715
+ }
3716
+ );
3717
+ });
3718
+ }
3719
+
3720
+ // src/commands/permitCliReads/discoverAll.ts
3721
+ var SAFETY_DEPTH = 10;
3722
+ var CONCURRENCY = 8;
3723
+ var interactive = !isClaudeCode();
3724
+ function showProgress(p, label2) {
3725
+ if (!interactive) return;
3726
+ const pct = Math.round(p.done / p.total * 100);
3727
+ process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
3728
+ }
3729
+ async function resolveCommand(cli, path31, description, depth, p) {
3730
+ showProgress(p, path31.join(" "));
3731
+ const subHelp = await runHelp([cli, ...path31]);
3732
+ if (!subHelp || !hasSubcommands(subHelp)) {
3733
+ return [{ path: path31, description }];
3734
+ }
3735
+ const children = await discoverAt(cli, path31, depth + 1, p);
3736
+ return children.length > 0 ? children : [{ path: path31, description }];
3737
+ }
3738
+ async function discoverAt(cli, parentPath, depth, p) {
3739
+ if (depth > SAFETY_DEPTH) return [];
3740
+ const helpText = await runHelp([cli, ...parentPath]);
3741
+ if (!helpText) return [];
3742
+ const cmds = parseCommands(helpText);
3743
+ const results = await mapAsync(
3744
+ cmds,
3745
+ CONCURRENCY,
3746
+ (cmd) => resolveCommand(cli, [...parentPath, cmd.name], cmd.description, depth, p)
3747
+ );
3748
+ return results.flat();
3749
+ }
3750
+ async function discoverAll(cli) {
3751
+ const topLevel = parseCommands(await runHelp([cli]));
3752
+ const p = { done: 0, total: topLevel.length };
3753
+ const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
3754
+ showProgress(p, cmd.name);
3755
+ const resolved = await resolveCommand(
3756
+ cli,
3757
+ [cmd.name],
3758
+ cmd.description,
3759
+ 1,
3760
+ p
3761
+ );
3762
+ p.done++;
3763
+ showProgress(p, cmd.name);
3764
+ return resolved;
3765
+ });
3766
+ if (interactive) process.stderr.write("\r\x1B[K");
3767
+ return results.flat();
3768
+ }
3769
+
3770
+ // src/commands/permitCliReads/classifyVerb.ts
3771
+ var READ_VERBS = /* @__PURE__ */ new Set([
3772
+ "list",
3773
+ "show",
3774
+ "view",
3775
+ "export",
3776
+ "get",
3777
+ "diff",
3778
+ "status",
3779
+ "search",
3780
+ "checks",
3781
+ "describe",
3782
+ "inspect",
3783
+ "logs",
3784
+ "cat",
3785
+ "top",
3786
+ "explain",
3787
+ "exists",
3788
+ "browse",
3789
+ "watch"
3790
+ ]);
3791
+ var WRITE_VERBS = /* @__PURE__ */ new Set([
3792
+ "create",
3793
+ "delete",
3794
+ "import",
3795
+ "set",
3796
+ "update",
3797
+ "merge",
3798
+ "close",
3799
+ "reopen",
3800
+ "edit",
3801
+ "apply",
3802
+ "patch",
3803
+ "drain",
3804
+ "cordon",
3805
+ "taint",
3806
+ "push",
3807
+ "deploy",
3808
+ "add",
3809
+ "remove",
3810
+ "assign",
3811
+ "unassign",
3812
+ "lock",
3813
+ "unlock",
3814
+ "start",
3815
+ "stop",
3816
+ "restart",
3817
+ "enable",
3818
+ "disable",
3819
+ "revoke",
3820
+ "rotate"
3821
+ ]);
3822
+ function classifyVerb(verb) {
3823
+ if (READ_VERBS.has(verb)) return "r";
3824
+ if (WRITE_VERBS.has(verb)) return "w";
3825
+ return "?";
3826
+ }
3827
+
3828
+ // src/commands/permitCliReads/formatHuman.ts
3829
+ function prefix(kind) {
3830
+ if (kind === "r") return " R ";
3831
+ if (kind === "w") return " W ";
3832
+ return " ? ";
3833
+ }
3834
+ function formatHuman(cli, commands) {
3835
+ const sorted = [...commands].sort(
3836
+ (a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
3837
+ );
3838
+ const lines = [`Discovered ${commands.length} commands for "${cli}":
3839
+ `];
3840
+ for (const cmd of sorted) {
3841
+ const verb = cmd.path[cmd.path.length - 1];
3842
+ const full = `${cli} ${cmd.path.join(" ")}`;
3843
+ const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
3844
+ lines.push(`${prefix(classifyVerb(verb))}${text}`);
3845
+ }
3846
+ return lines.join("\n");
3847
+ }
3848
+
3849
+ // src/commands/permitCliReads/parseCached.ts
3850
+ function parseCached(cli, cached) {
3851
+ const prefix2 = `${cli} `;
3852
+ const commands = [];
3853
+ for (const line of cached.split("\n")) {
3854
+ const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
3855
+ if (!trimmed.startsWith(prefix2)) continue;
3856
+ const rest = trimmed.slice(prefix2.length);
3857
+ const dashIdx = rest.indexOf(" \u2014 ");
3858
+ const pathStr = dashIdx >= 0 ? rest.slice(0, dashIdx) : rest;
3859
+ const description = dashIdx >= 0 ? rest.slice(dashIdx + 3) : "";
3860
+ commands.push({ path: pathStr.split(" "), description });
3861
+ }
3862
+ return commands;
3863
+ }
3864
+
3865
+ // src/commands/permitCliReads/updateSettings.ts
3866
+ function updateSettings(cli, commands) {
3867
+ const existing = loadCliReads();
3868
+ const readEntries = commands.filter((cmd) => classifyVerb(cmd.path[cmd.path.length - 1]) === "r").map((cmd) => `${cli} ${cmd.path.join(" ")}`);
3869
+ const merged = [.../* @__PURE__ */ new Set([...existing, ...readEntries])].sort();
3870
+ if (merged.length === existing.length && merged.every((e, i) => e === existing[i]))
3871
+ return;
3872
+ saveCliReads(merged);
3873
+ }
3874
+
3875
+ // src/commands/permitCliReads/index.ts
3876
+ function logPath(cli) {
3877
+ const safeName = cli.replace(/\s+/g, "-");
3878
+ return join12(homedir4(), ".assist", `cli-discover-${safeName}.log`);
3879
+ }
3880
+ function readCache(cli) {
3881
+ const path31 = logPath(cli);
3882
+ if (!existsSync18(path31)) return void 0;
3883
+ return readFileSync16(path31, "utf-8");
3884
+ }
3885
+ function writeCache(cli, output) {
3886
+ const dir = join12(homedir4(), ".assist");
3887
+ mkdirSync4(dir, { recursive: true });
3888
+ writeFileSync14(logPath(cli), output);
3889
+ }
3890
+ async function permitCliReads(cli, options2 = { noCache: false }) {
3891
+ if (!cli) {
3892
+ console.error("Usage: assist permit-cli-reads <cli>");
3893
+ process.exit(1);
3894
+ }
3895
+ const installDir = getInstallDir();
3896
+ if (!isGitRepo(installDir)) {
3897
+ console.error(
3898
+ "permit-cli-reads must be run from the assist git repo, not a global install."
3899
+ );
3900
+ process.exit(1);
3901
+ }
3902
+ if (!options2.noCache) {
3903
+ const cached = readCache(cli);
3904
+ if (cached) {
3905
+ console.log(colorize(cached));
3906
+ updateSettings(cli, parseCached(cli, cached));
3907
+ return;
3908
+ }
3909
+ }
3910
+ assertCliExists(cli);
3911
+ const commands = await discoverAll(cli);
3912
+ const output = formatHuman(cli, commands);
3913
+ console.log(colorize(output));
3914
+ writeCache(cli, output);
3915
+ updateSettings(cli, commands);
3916
+ }
3917
+
3918
+ // src/commands/registerPermitCliReads.ts
3919
+ function registerPermitCliReads(program2) {
3920
+ program2.command("permit-cli-reads").description("Discover a CLI's commands and auto-permit read-only ones").argument(
3921
+ "<cli...>",
3922
+ "CLI binary and optional subcommand (e.g. gh, az, acli jira)"
3923
+ ).option("--no-cache", "Force fresh discovery, ignoring cached results").action((cli, options2) => {
3924
+ permitCliReads(cli.join(" "), { noCache: !options2.cache });
3925
+ });
3926
+ }
3927
+
3407
3928
  // src/commands/prs/comment.ts
3408
3929
  import { spawnSync as spawnSync2 } from "child_process";
3409
- import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "fs";
3930
+ import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
3410
3931
  import { tmpdir as tmpdir2 } from "os";
3411
- import { join as join12 } from "path";
3932
+ import { join as join13 } from "path";
3412
3933
 
3413
3934
  // src/commands/prs/shared.ts
3414
- import { execSync as execSync17 } from "child_process";
3935
+ import { execSync as execSync19 } from "child_process";
3415
3936
  function isGhNotInstalled(error) {
3416
3937
  if (error instanceof Error) {
3417
3938
  const msg = error.message.toLowerCase();
@@ -3427,14 +3948,14 @@ function isNotFound(error) {
3427
3948
  }
3428
3949
  function getRepoInfo() {
3429
3950
  const repoInfo = JSON.parse(
3430
- execSync17("gh repo view --json owner,name", { encoding: "utf-8" })
3951
+ execSync19("gh repo view --json owner,name", { encoding: "utf-8" })
3431
3952
  );
3432
3953
  return { org: repoInfo.owner.login, repo: repoInfo.name };
3433
3954
  }
3434
3955
  function getCurrentPrNumber() {
3435
3956
  try {
3436
3957
  const prInfo = JSON.parse(
3437
- execSync17("gh pr view --json number", { encoding: "utf-8" })
3958
+ execSync19("gh pr view --json number", { encoding: "utf-8" })
3438
3959
  );
3439
3960
  return prInfo.number;
3440
3961
  } catch (error) {
@@ -3448,7 +3969,7 @@ function getCurrentPrNumber() {
3448
3969
  function getCurrentPrNodeId() {
3449
3970
  try {
3450
3971
  const prInfo = JSON.parse(
3451
- execSync17("gh pr view --json id", { encoding: "utf-8" })
3972
+ execSync19("gh pr view --json id", { encoding: "utf-8" })
3452
3973
  );
3453
3974
  return prInfo.id;
3454
3975
  } catch (error) {
@@ -3480,8 +4001,8 @@ function comment(path31, line, body) {
3480
4001
  validateLine(line);
3481
4002
  try {
3482
4003
  const prId = getCurrentPrNodeId();
3483
- const queryFile = join12(tmpdir2(), `gh-query-${Date.now()}.graphql`);
3484
- writeFileSync13(queryFile, MUTATION);
4004
+ const queryFile = join13(tmpdir2(), `gh-query-${Date.now()}.graphql`);
4005
+ writeFileSync15(queryFile, MUTATION);
3485
4006
  try {
3486
4007
  const result = spawnSync2(
3487
4008
  "gh",
@@ -3519,32 +4040,32 @@ function comment(path31, line, body) {
3519
4040
  }
3520
4041
 
3521
4042
  // src/commands/prs/fixed.ts
3522
- import { execSync as execSync19 } from "child_process";
4043
+ import { execSync as execSync21 } from "child_process";
3523
4044
 
3524
4045
  // src/commands/prs/resolveCommentWithReply.ts
3525
- import { execSync as execSync18 } from "child_process";
3526
- import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "fs";
4046
+ import { execSync as execSync20 } from "child_process";
4047
+ import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
3527
4048
  import { tmpdir as tmpdir3 } from "os";
3528
- import { join as join14 } from "path";
4049
+ import { join as join15 } from "path";
3529
4050
 
3530
4051
  // src/commands/prs/loadCommentsCache.ts
3531
- import { existsSync as existsSync16, readFileSync as readFileSync14, unlinkSync as unlinkSync4 } from "fs";
3532
- import { join as join13 } from "path";
4052
+ import { existsSync as existsSync19, readFileSync as readFileSync17, unlinkSync as unlinkSync4 } from "fs";
4053
+ import { join as join14 } from "path";
3533
4054
  import { parse } from "yaml";
3534
4055
  function getCachePath(prNumber) {
3535
- return join13(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
4056
+ return join14(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
3536
4057
  }
3537
4058
  function loadCommentsCache(prNumber) {
3538
4059
  const cachePath = getCachePath(prNumber);
3539
- if (!existsSync16(cachePath)) {
4060
+ if (!existsSync19(cachePath)) {
3540
4061
  return null;
3541
4062
  }
3542
- const content = readFileSync14(cachePath, "utf-8");
4063
+ const content = readFileSync17(cachePath, "utf-8");
3543
4064
  return parse(content);
3544
4065
  }
3545
4066
  function deleteCommentsCache(prNumber) {
3546
4067
  const cachePath = getCachePath(prNumber);
3547
- if (existsSync16(cachePath)) {
4068
+ if (existsSync19(cachePath)) {
3548
4069
  unlinkSync4(cachePath);
3549
4070
  console.log("No more unresolved line comments. Cache dropped.");
3550
4071
  }
@@ -3552,17 +4073,17 @@ function deleteCommentsCache(prNumber) {
3552
4073
 
3553
4074
  // src/commands/prs/resolveCommentWithReply.ts
3554
4075
  function replyToComment(org, repo, prNumber, commentId, message) {
3555
- execSync18(
4076
+ execSync20(
3556
4077
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
3557
4078
  { stdio: "inherit" }
3558
4079
  );
3559
4080
  }
3560
4081
  function resolveThread(threadId) {
3561
4082
  const mutation = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }`;
3562
- const queryFile = join14(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
3563
- writeFileSync14(queryFile, mutation);
4083
+ const queryFile = join15(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
4084
+ writeFileSync16(queryFile, mutation);
3564
4085
  try {
3565
- execSync18(
4086
+ execSync20(
3566
4087
  `gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
3567
4088
  { stdio: "inherit" }
3568
4089
  );
@@ -3614,7 +4135,7 @@ function resolveCommentWithReply(commentId, message) {
3614
4135
  // src/commands/prs/fixed.ts
3615
4136
  function verifySha(sha) {
3616
4137
  try {
3617
- return execSync19(`git rev-parse --verify ${sha}`, {
4138
+ return execSync21(`git rev-parse --verify ${sha}`, {
3618
4139
  encoding: "utf-8"
3619
4140
  }).trim();
3620
4141
  } catch {
@@ -3640,26 +4161,21 @@ function fixed(commentId, sha) {
3640
4161
  }
3641
4162
 
3642
4163
  // src/commands/prs/listComments/index.ts
3643
- import { existsSync as existsSync17, mkdirSync as mkdirSync4, writeFileSync as writeFileSync16 } from "fs";
3644
- import { join as join16 } from "path";
4164
+ import { existsSync as existsSync20, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
4165
+ import { join as join17 } from "path";
3645
4166
  import { stringify } from "yaml";
3646
4167
 
3647
- // src/lib/isClaudeCode.ts
3648
- function isClaudeCode() {
3649
- return process.env.CLAUDECODE !== void 0;
3650
- }
3651
-
3652
4168
  // src/commands/prs/fetchThreadIds.ts
3653
- import { execSync as execSync20 } from "child_process";
3654
- import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "fs";
4169
+ import { execSync as execSync22 } from "child_process";
4170
+ import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
3655
4171
  import { tmpdir as tmpdir4 } from "os";
3656
- import { join as join15 } from "path";
4172
+ import { join as join16 } from "path";
3657
4173
  var THREAD_QUERY = `query($owner: String!, $repo: String!, $prNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $prNumber) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 100) { nodes { databaseId } } } } } } }`;
3658
4174
  function fetchThreadIds(org, repo, prNumber) {
3659
- const queryFile = join15(tmpdir4(), `gh-query-${Date.now()}.graphql`);
3660
- writeFileSync15(queryFile, THREAD_QUERY);
4175
+ const queryFile = join16(tmpdir4(), `gh-query-${Date.now()}.graphql`);
4176
+ writeFileSync17(queryFile, THREAD_QUERY);
3661
4177
  try {
3662
- const result = execSync20(
4178
+ const result = execSync22(
3663
4179
  `gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
3664
4180
  { encoding: "utf-8" }
3665
4181
  );
@@ -3681,9 +4197,9 @@ function fetchThreadIds(org, repo, prNumber) {
3681
4197
  }
3682
4198
 
3683
4199
  // src/commands/prs/listComments/fetchReviewComments.ts
3684
- import { execSync as execSync21 } from "child_process";
4200
+ import { execSync as execSync23 } from "child_process";
3685
4201
  function fetchJson(endpoint) {
3686
- const result = execSync21(`gh api --paginate ${endpoint}`, {
4202
+ const result = execSync23(`gh api --paginate ${endpoint}`, {
3687
4203
  encoding: "utf-8"
3688
4204
  });
3689
4205
  if (!result.trim()) return [];
@@ -3725,20 +4241,20 @@ function fetchLineComments(org, repo, prNumber, threadInfo) {
3725
4241
  }
3726
4242
 
3727
4243
  // src/commands/prs/listComments/formatForHuman.ts
3728
- import chalk43 from "chalk";
4244
+ import chalk44 from "chalk";
3729
4245
  function formatForHuman(comment2) {
3730
4246
  if (comment2.type === "review") {
3731
- const stateColor = comment2.state === "APPROVED" ? chalk43.green : comment2.state === "CHANGES_REQUESTED" ? chalk43.red : chalk43.yellow;
4247
+ const stateColor = comment2.state === "APPROVED" ? chalk44.green : comment2.state === "CHANGES_REQUESTED" ? chalk44.red : chalk44.yellow;
3732
4248
  return [
3733
- `${chalk43.cyan("Review")} by ${chalk43.bold(comment2.user)} ${stateColor(`[${comment2.state}]`)}`,
4249
+ `${chalk44.cyan("Review")} by ${chalk44.bold(comment2.user)} ${stateColor(`[${comment2.state}]`)}`,
3734
4250
  comment2.body,
3735
4251
  ""
3736
4252
  ].join("\n");
3737
4253
  }
3738
4254
  const location = comment2.line ? `:${comment2.line}` : "";
3739
4255
  return [
3740
- `${chalk43.cyan("Line comment")} by ${chalk43.bold(comment2.user)} on ${chalk43.dim(`${comment2.path}${location}`)}`,
3741
- chalk43.dim(comment2.diff_hunk.split("\n").slice(-3).join("\n")),
4256
+ `${chalk44.cyan("Line comment")} by ${chalk44.bold(comment2.user)} on ${chalk44.dim(`${comment2.path}${location}`)}`,
4257
+ chalk44.dim(comment2.diff_hunk.split("\n").slice(-3).join("\n")),
3742
4258
  comment2.body,
3743
4259
  ""
3744
4260
  ].join("\n");
@@ -3761,17 +4277,17 @@ function printComments(comments) {
3761
4277
  }
3762
4278
  }
3763
4279
  function writeCommentsCache(prNumber, comments) {
3764
- const assistDir = join16(process.cwd(), ".assist");
3765
- if (!existsSync17(assistDir)) {
3766
- mkdirSync4(assistDir, { recursive: true });
4280
+ const assistDir = join17(process.cwd(), ".assist");
4281
+ if (!existsSync20(assistDir)) {
4282
+ mkdirSync5(assistDir, { recursive: true });
3767
4283
  }
3768
4284
  const cacheData = {
3769
4285
  prNumber,
3770
4286
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
3771
4287
  comments
3772
4288
  };
3773
- const cachePath = join16(assistDir, `pr-${prNumber}-comments.yaml`);
3774
- writeFileSync16(cachePath, stringify(cacheData));
4289
+ const cachePath = join17(assistDir, `pr-${prNumber}-comments.yaml`);
4290
+ writeFileSync18(cachePath, stringify(cacheData));
3775
4291
  }
3776
4292
  function handleKnownErrors(error) {
3777
4293
  if (isGhNotInstalled(error)) {
@@ -3811,19 +4327,19 @@ async function listComments() {
3811
4327
  }
3812
4328
 
3813
4329
  // src/commands/prs/prs/index.ts
3814
- import { execSync as execSync22 } from "child_process";
4330
+ import { execSync as execSync24 } from "child_process";
3815
4331
 
3816
4332
  // src/commands/prs/prs/displayPaginated/index.ts
3817
4333
  import enquirer5 from "enquirer";
3818
4334
 
3819
4335
  // src/commands/prs/prs/displayPaginated/printPr.ts
3820
- import chalk44 from "chalk";
4336
+ import chalk45 from "chalk";
3821
4337
  var STATUS_MAP = {
3822
- MERGED: (pr) => pr.mergedAt ? { label: chalk44.magenta("merged"), date: pr.mergedAt } : null,
3823
- CLOSED: (pr) => pr.closedAt ? { label: chalk44.red("closed"), date: pr.closedAt } : null
4338
+ MERGED: (pr) => pr.mergedAt ? { label: chalk45.magenta("merged"), date: pr.mergedAt } : null,
4339
+ CLOSED: (pr) => pr.closedAt ? { label: chalk45.red("closed"), date: pr.closedAt } : null
3824
4340
  };
3825
4341
  function defaultStatus(pr) {
3826
- return { label: chalk44.green("opened"), date: pr.createdAt };
4342
+ return { label: chalk45.green("opened"), date: pr.createdAt };
3827
4343
  }
3828
4344
  function getStatus(pr) {
3829
4345
  return STATUS_MAP[pr.state]?.(pr) ?? defaultStatus(pr);
@@ -3832,11 +4348,11 @@ function formatDate(dateStr) {
3832
4348
  return new Date(dateStr).toISOString().split("T")[0];
3833
4349
  }
3834
4350
  function formatPrHeader(pr, status2) {
3835
- return `${chalk44.cyan(`#${pr.number}`)} ${pr.title} ${chalk44.dim(`(${pr.author.login},`)} ${status2.label} ${chalk44.dim(`${formatDate(status2.date)})`)}`;
4351
+ return `${chalk45.cyan(`#${pr.number}`)} ${pr.title} ${chalk45.dim(`(${pr.author.login},`)} ${status2.label} ${chalk45.dim(`${formatDate(status2.date)})`)}`;
3836
4352
  }
3837
4353
  function logPrDetails(pr) {
3838
4354
  console.log(
3839
- chalk44.dim(` ${pr.changedFiles.toLocaleString()} files | ${pr.url}`)
4355
+ chalk45.dim(` ${pr.changedFiles.toLocaleString()} files | ${pr.url}`)
3840
4356
  );
3841
4357
  console.log();
3842
4358
  }
@@ -3917,7 +4433,7 @@ async function displayPaginated(pullRequests) {
3917
4433
  async function prs(options2) {
3918
4434
  const state = options2.open ? "open" : options2.closed ? "closed" : "all";
3919
4435
  try {
3920
- const result = execSync22(
4436
+ const result = execSync24(
3921
4437
  `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
3922
4438
  { encoding: "utf-8" }
3923
4439
  );
@@ -3940,7 +4456,7 @@ async function prs(options2) {
3940
4456
  }
3941
4457
 
3942
4458
  // src/commands/prs/wontfix.ts
3943
- import { execSync as execSync23 } from "child_process";
4459
+ import { execSync as execSync25 } from "child_process";
3944
4460
  function validateReason(reason) {
3945
4461
  const lowerReason = reason.toLowerCase();
3946
4462
  if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
@@ -3957,7 +4473,7 @@ function validateShaReferences(reason) {
3957
4473
  const invalidShas = [];
3958
4474
  for (const sha of shas) {
3959
4475
  try {
3960
- execSync23(`git cat-file -t ${sha}`, { stdio: "pipe" });
4476
+ execSync25(`git cat-file -t ${sha}`, { stdio: "pipe" });
3961
4477
  } catch {
3962
4478
  invalidShas.push(sha);
3963
4479
  }
@@ -4006,7 +4522,7 @@ import { spawn as spawn3 } from "child_process";
4006
4522
  import * as path17 from "path";
4007
4523
 
4008
4524
  // src/commands/refactor/logViolations.ts
4009
- import chalk45 from "chalk";
4525
+ import chalk46 from "chalk";
4010
4526
  var DEFAULT_MAX_LINES = 100;
4011
4527
  function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
4012
4528
  if (violations.length === 0) {
@@ -4015,43 +4531,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
4015
4531
  }
4016
4532
  return;
4017
4533
  }
4018
- console.error(chalk45.red(`
4534
+ console.error(chalk46.red(`
4019
4535
  Refactor check failed:
4020
4536
  `));
4021
- console.error(chalk45.red(` The following files exceed ${maxLines} lines:
4537
+ console.error(chalk46.red(` The following files exceed ${maxLines} lines:
4022
4538
  `));
4023
4539
  for (const violation of violations) {
4024
- console.error(chalk45.red(` ${violation.file} (${violation.lines} lines)`));
4540
+ console.error(chalk46.red(` ${violation.file} (${violation.lines} lines)`));
4025
4541
  }
4026
4542
  console.error(
4027
- chalk45.yellow(
4543
+ chalk46.yellow(
4028
4544
  `
4029
4545
  Each file needs to be sensibly refactored, or if there is no sensible
4030
4546
  way to refactor it, ignore it with:
4031
4547
  `
4032
4548
  )
4033
4549
  );
4034
- console.error(chalk45.gray(` assist refactor ignore <file>
4550
+ console.error(chalk46.gray(` assist refactor ignore <file>
4035
4551
  `));
4036
4552
  if (process.env.CLAUDECODE) {
4037
- console.error(chalk45.cyan(`
4553
+ console.error(chalk46.cyan(`
4038
4554
  ## Extracting Code to New Files
4039
4555
  `));
4040
4556
  console.error(
4041
- chalk45.cyan(
4557
+ chalk46.cyan(
4042
4558
  ` When extracting logic from one file to another, consider where the extracted code belongs:
4043
4559
  `
4044
4560
  )
4045
4561
  );
4046
4562
  console.error(
4047
- chalk45.cyan(
4563
+ chalk46.cyan(
4048
4564
  ` 1. Keep related logic together: If the extracted code is tightly coupled to the
4049
4565
  original file's domain, create a new folder containing both the original and extracted files.
4050
4566
  `
4051
4567
  )
4052
4568
  );
4053
4569
  console.error(
4054
- chalk45.cyan(
4570
+ chalk46.cyan(
4055
4571
  ` 2. Share common utilities: If the extracted code can be reused across multiple
4056
4572
  domains, move it to a common/shared folder.
4057
4573
  `
@@ -4061,7 +4577,7 @@ Refactor check failed:
4061
4577
  }
4062
4578
 
4063
4579
  // src/commands/refactor/check/getViolations/index.ts
4064
- import { execSync as execSync24 } from "child_process";
4580
+ import { execSync as execSync26 } from "child_process";
4065
4581
  import fs15 from "fs";
4066
4582
  import { minimatch as minimatch4 } from "minimatch";
4067
4583
 
@@ -4111,7 +4627,7 @@ function getGitFiles(options2) {
4111
4627
  }
4112
4628
  const files = /* @__PURE__ */ new Set();
4113
4629
  if (options2.staged || options2.modified) {
4114
- const staged = execSync24("git diff --cached --name-only", {
4630
+ const staged = execSync26("git diff --cached --name-only", {
4115
4631
  encoding: "utf-8"
4116
4632
  });
4117
4633
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -4119,7 +4635,7 @@ function getGitFiles(options2) {
4119
4635
  }
4120
4636
  }
4121
4637
  if (options2.unstaged || options2.modified) {
4122
- const unstaged = execSync24("git diff --name-only", { encoding: "utf-8" });
4638
+ const unstaged = execSync26("git diff --name-only", { encoding: "utf-8" });
4123
4639
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
4124
4640
  files.add(file);
4125
4641
  }
@@ -4149,7 +4665,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
4149
4665
 
4150
4666
  // src/commands/refactor/check/index.ts
4151
4667
  function runScript(script, cwd) {
4152
- return new Promise((resolve3) => {
4668
+ return new Promise((resolve5) => {
4153
4669
  const child = spawn3("npm", ["run", script], {
4154
4670
  stdio: "pipe",
4155
4671
  shell: true,
@@ -4163,7 +4679,7 @@ function runScript(script, cwd) {
4163
4679
  output += data.toString();
4164
4680
  });
4165
4681
  child.on("close", (code) => {
4166
- resolve3({ script, code: code ?? 1, output });
4682
+ resolve5({ script, code: code ?? 1, output });
4167
4683
  });
4168
4684
  });
4169
4685
  }
@@ -4207,11 +4723,11 @@ async function check(pattern2, options2) {
4207
4723
 
4208
4724
  // src/commands/refactor/ignore.ts
4209
4725
  import fs16 from "fs";
4210
- import chalk46 from "chalk";
4726
+ import chalk47 from "chalk";
4211
4727
  var REFACTOR_YML_PATH2 = "refactor.yml";
4212
4728
  function ignore(file) {
4213
4729
  if (!fs16.existsSync(file)) {
4214
- console.error(chalk46.red(`Error: File does not exist: ${file}`));
4730
+ console.error(chalk47.red(`Error: File does not exist: ${file}`));
4215
4731
  process.exit(1);
4216
4732
  }
4217
4733
  const content = fs16.readFileSync(file, "utf-8");
@@ -4227,7 +4743,7 @@ function ignore(file) {
4227
4743
  fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
4228
4744
  }
4229
4745
  console.log(
4230
- chalk46.green(
4746
+ chalk47.green(
4231
4747
  `Added ${file} to refactor ignore list (max ${maxLines} lines)`
4232
4748
  )
4233
4749
  );
@@ -4235,7 +4751,7 @@ function ignore(file) {
4235
4751
 
4236
4752
  // src/commands/refactor/restructure/index.ts
4237
4753
  import path26 from "path";
4238
- import chalk49 from "chalk";
4754
+ import chalk50 from "chalk";
4239
4755
 
4240
4756
  // src/commands/refactor/restructure/buildImportGraph/index.ts
4241
4757
  import path18 from "path";
@@ -4478,50 +4994,50 @@ function computeRewrites(moves, edges, allProjectFiles) {
4478
4994
 
4479
4995
  // src/commands/refactor/restructure/displayPlan.ts
4480
4996
  import path22 from "path";
4481
- import chalk47 from "chalk";
4997
+ import chalk48 from "chalk";
4482
4998
  function relPath(filePath) {
4483
4999
  return path22.relative(process.cwd(), filePath);
4484
5000
  }
4485
5001
  function displayMoves(plan) {
4486
5002
  if (plan.moves.length === 0) return;
4487
- console.log(chalk47.bold("\nFile moves:"));
5003
+ console.log(chalk48.bold("\nFile moves:"));
4488
5004
  for (const move of plan.moves) {
4489
5005
  console.log(
4490
- ` ${chalk47.red(relPath(move.from))} \u2192 ${chalk47.green(relPath(move.to))}`
5006
+ ` ${chalk48.red(relPath(move.from))} \u2192 ${chalk48.green(relPath(move.to))}`
4491
5007
  );
4492
- console.log(chalk47.dim(` ${move.reason}`));
5008
+ console.log(chalk48.dim(` ${move.reason}`));
4493
5009
  }
4494
5010
  }
4495
5011
  function displayRewrites(rewrites) {
4496
5012
  if (rewrites.length === 0) return;
4497
5013
  const affectedFiles = new Set(rewrites.map((r) => r.file));
4498
- console.log(chalk47.bold(`
5014
+ console.log(chalk48.bold(`
4499
5015
  Import rewrites (${affectedFiles.size} files):`));
4500
5016
  for (const file of affectedFiles) {
4501
- console.log(` ${chalk47.cyan(relPath(file))}:`);
5017
+ console.log(` ${chalk48.cyan(relPath(file))}:`);
4502
5018
  for (const { oldSpecifier, newSpecifier } of rewrites.filter(
4503
5019
  (r) => r.file === file
4504
5020
  )) {
4505
5021
  console.log(
4506
- ` ${chalk47.red(`"${oldSpecifier}"`)} \u2192 ${chalk47.green(`"${newSpecifier}"`)}`
5022
+ ` ${chalk48.red(`"${oldSpecifier}"`)} \u2192 ${chalk48.green(`"${newSpecifier}"`)}`
4507
5023
  );
4508
5024
  }
4509
5025
  }
4510
5026
  }
4511
5027
  function displayPlan(plan) {
4512
5028
  if (plan.warnings.length > 0) {
4513
- console.log(chalk47.yellow("\nWarnings:"));
4514
- for (const w of plan.warnings) console.log(chalk47.yellow(` ${w}`));
5029
+ console.log(chalk48.yellow("\nWarnings:"));
5030
+ for (const w of plan.warnings) console.log(chalk48.yellow(` ${w}`));
4515
5031
  }
4516
5032
  if (plan.newDirectories.length > 0) {
4517
- console.log(chalk47.bold("\nNew directories:"));
5033
+ console.log(chalk48.bold("\nNew directories:"));
4518
5034
  for (const dir of plan.newDirectories)
4519
- console.log(chalk47.green(` ${dir}/`));
5035
+ console.log(chalk48.green(` ${dir}/`));
4520
5036
  }
4521
5037
  displayMoves(plan);
4522
5038
  displayRewrites(plan.rewrites);
4523
5039
  console.log(
4524
- chalk47.dim(
5040
+ chalk48.dim(
4525
5041
  `
4526
5042
  Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rewritten`
4527
5043
  )
@@ -4531,18 +5047,18 @@ Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rew
4531
5047
  // src/commands/refactor/restructure/executePlan.ts
4532
5048
  import fs18 from "fs";
4533
5049
  import path23 from "path";
4534
- import chalk48 from "chalk";
5050
+ import chalk49 from "chalk";
4535
5051
  function executePlan(plan) {
4536
5052
  const updatedContents = applyRewrites(plan.rewrites);
4537
5053
  for (const [file, content] of updatedContents) {
4538
5054
  fs18.writeFileSync(file, content, "utf-8");
4539
5055
  console.log(
4540
- chalk48.cyan(` Rewrote imports in ${path23.relative(process.cwd(), file)}`)
5056
+ chalk49.cyan(` Rewrote imports in ${path23.relative(process.cwd(), file)}`)
4541
5057
  );
4542
5058
  }
4543
5059
  for (const dir of plan.newDirectories) {
4544
5060
  fs18.mkdirSync(dir, { recursive: true });
4545
- console.log(chalk48.green(` Created ${path23.relative(process.cwd(), dir)}/`));
5061
+ console.log(chalk49.green(` Created ${path23.relative(process.cwd(), dir)}/`));
4546
5062
  }
4547
5063
  for (const move of plan.moves) {
4548
5064
  const targetDir = path23.dirname(move.to);
@@ -4551,7 +5067,7 @@ function executePlan(plan) {
4551
5067
  }
4552
5068
  fs18.renameSync(move.from, move.to);
4553
5069
  console.log(
4554
- chalk48.white(
5070
+ chalk49.white(
4555
5071
  ` Moved ${path23.relative(process.cwd(), move.from)} \u2192 ${path23.relative(process.cwd(), move.to)}`
4556
5072
  )
4557
5073
  );
@@ -4566,7 +5082,7 @@ function removeEmptyDirectories(dirs) {
4566
5082
  if (entries.length === 0) {
4567
5083
  fs18.rmdirSync(dir);
4568
5084
  console.log(
4569
- chalk48.dim(
5085
+ chalk49.dim(
4570
5086
  ` Removed empty directory ${path23.relative(process.cwd(), dir)}`
4571
5087
  )
4572
5088
  );
@@ -4697,22 +5213,22 @@ async function restructure(pattern2, options2 = {}) {
4697
5213
  const targetPattern = pattern2 ?? "src";
4698
5214
  const files = findSourceFiles2(targetPattern);
4699
5215
  if (files.length === 0) {
4700
- console.log(chalk49.yellow("No files found matching pattern"));
5216
+ console.log(chalk50.yellow("No files found matching pattern"));
4701
5217
  return;
4702
5218
  }
4703
5219
  const tsConfigPath = path26.resolve("tsconfig.json");
4704
5220
  const plan = buildPlan(files, tsConfigPath);
4705
5221
  if (plan.moves.length === 0) {
4706
- console.log(chalk49.green("No restructuring needed"));
5222
+ console.log(chalk50.green("No restructuring needed"));
4707
5223
  return;
4708
5224
  }
4709
5225
  displayPlan(plan);
4710
5226
  if (options2.apply) {
4711
- console.log(chalk49.bold("\nApplying changes..."));
5227
+ console.log(chalk50.bold("\nApplying changes..."));
4712
5228
  executePlan(plan);
4713
- console.log(chalk49.green("\nRestructuring complete"));
5229
+ console.log(chalk50.green("\nRestructuring complete"));
4714
5230
  } else {
4715
- console.log(chalk49.dim("\nDry run. Use --apply to execute."));
5231
+ console.log(chalk50.dim("\nDry run. Use --apply to execute."));
4716
5232
  }
4717
5233
  }
4718
5234
 
@@ -4735,8 +5251,8 @@ function registerRefactor(program2) {
4735
5251
  }
4736
5252
 
4737
5253
  // src/commands/transcript/shared.ts
4738
- import { existsSync as existsSync18, readdirSync as readdirSync2, statSync } from "fs";
4739
- import { basename as basename4, join as join17, relative } from "path";
5254
+ import { existsSync as existsSync21, readdirSync as readdirSync2, statSync } from "fs";
5255
+ import { basename as basename4, join as join18, relative } from "path";
4740
5256
  import * as readline2 from "readline";
4741
5257
  var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
4742
5258
  function getDatePrefix(daysOffset = 0) {
@@ -4751,10 +5267,10 @@ function isValidDatePrefix(filename) {
4751
5267
  return DATE_PREFIX_REGEX.test(filename);
4752
5268
  }
4753
5269
  function collectFiles(dir, extension) {
4754
- if (!existsSync18(dir)) return [];
5270
+ if (!existsSync21(dir)) return [];
4755
5271
  const results = [];
4756
5272
  for (const entry of readdirSync2(dir)) {
4757
- const fullPath = join17(dir, entry);
5273
+ const fullPath = join18(dir, entry);
4758
5274
  if (statSync(fullPath).isDirectory()) {
4759
5275
  results.push(...collectFiles(fullPath, extension));
4760
5276
  } else if (entry.endsWith(extension)) {
@@ -4786,9 +5302,9 @@ function createReadlineInterface() {
4786
5302
  });
4787
5303
  }
4788
5304
  function askQuestion(rl, question) {
4789
- return new Promise((resolve3) => {
5305
+ return new Promise((resolve5) => {
4790
5306
  rl.question(question, (answer) => {
4791
- resolve3(answer.trim());
5307
+ resolve5(answer.trim());
4792
5308
  });
4793
5309
  });
4794
5310
  }
@@ -4848,14 +5364,14 @@ async function configure() {
4848
5364
  }
4849
5365
 
4850
5366
  // src/commands/transcript/format/index.ts
4851
- import { existsSync as existsSync20 } from "fs";
5367
+ import { existsSync as existsSync23 } from "fs";
4852
5368
 
4853
5369
  // src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
4854
- import { dirname as dirname13, join as join19 } from "path";
5370
+ import { dirname as dirname15, join as join20 } from "path";
4855
5371
 
4856
5372
  // src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
4857
5373
  import { renameSync } from "fs";
4858
- import { join as join18 } from "path";
5374
+ import { join as join19 } from "path";
4859
5375
  async function resolveDate(rl, choice) {
4860
5376
  if (choice === "1") return getDatePrefix(0);
4861
5377
  if (choice === "2") return getDatePrefix(-1);
@@ -4868,9 +5384,9 @@ async function resolveDate(rl, choice) {
4868
5384
  console.log("Cancelled.");
4869
5385
  return null;
4870
5386
  }
4871
- function renameWithPrefix(vttDir, vttFile, prefix) {
4872
- const newFilename = `${prefix}.${vttFile}`;
4873
- renameSync(join18(vttDir, vttFile), join18(vttDir, newFilename));
5387
+ function renameWithPrefix(vttDir, vttFile, prefix2) {
5388
+ const newFilename = `${prefix2}.${vttFile}`;
5389
+ renameSync(join19(vttDir, vttFile), join19(vttDir, newFilename));
4874
5390
  console.log(`Renamed to: ${newFilename}`);
4875
5391
  return newFilename;
4876
5392
  }
@@ -4887,9 +5403,9 @@ Error: File "${vttFile}" does not start with YYYY-MM-DD format.`
4887
5403
  console.log(" 4. Cancel");
4888
5404
  try {
4889
5405
  const choice = await askQuestion(rl, "\nSelect an option (1/2/3/4): ");
4890
- const prefix = await resolveDate(rl, choice);
5406
+ const prefix2 = await resolveDate(rl, choice);
4891
5407
  rl.close();
4892
- return prefix ? renameWithPrefix(vttDir, vttFile, prefix) : null;
5408
+ return prefix2 ? renameWithPrefix(vttDir, vttFile, prefix2) : null;
4893
5409
  } catch (error) {
4894
5410
  rl.close();
4895
5411
  throw error;
@@ -4901,15 +5417,15 @@ async function fixInvalidDatePrefixes(vttFiles) {
4901
5417
  for (let i = 0; i < vttFiles.length; i++) {
4902
5418
  const vttFile = vttFiles[i];
4903
5419
  if (!isValidDatePrefix(vttFile.filename)) {
4904
- const vttFileDir = dirname13(vttFile.absolutePath);
5420
+ const vttFileDir = dirname15(vttFile.absolutePath);
4905
5421
  const newFilename = await promptForDateFix(vttFile.filename, vttFileDir);
4906
5422
  if (newFilename) {
4907
- const newRelativePath = join19(
4908
- dirname13(vttFile.relativePath),
5423
+ const newRelativePath = join20(
5424
+ dirname15(vttFile.relativePath),
4909
5425
  newFilename
4910
5426
  );
4911
5427
  vttFiles[i] = {
4912
- absolutePath: join19(vttFileDir, newFilename),
5428
+ absolutePath: join20(vttFileDir, newFilename),
4913
5429
  relativePath: newRelativePath,
4914
5430
  filename: newFilename
4915
5431
  };
@@ -4922,8 +5438,8 @@ async function fixInvalidDatePrefixes(vttFiles) {
4922
5438
  }
4923
5439
 
4924
5440
  // src/commands/transcript/format/processVttFile/index.ts
4925
- import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync17 } from "fs";
4926
- import { basename as basename5, dirname as dirname14, join as join20 } from "path";
5441
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync19 } from "fs";
5442
+ import { basename as basename5, dirname as dirname16, join as join21 } from "path";
4927
5443
 
4928
5444
  // src/commands/transcript/cleanText.ts
4929
5445
  function cleanText(text) {
@@ -4993,8 +5509,8 @@ function removeSubstringDuplicates(cues) {
4993
5509
  function findWordOverlap(currentWords, nextWords) {
4994
5510
  for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
4995
5511
  const suffix = currentWords.slice(-j).join(" ");
4996
- const prefix = nextWords.slice(0, j).join(" ");
4997
- if (suffix === prefix) return j;
5512
+ const prefix2 = nextWords.slice(0, j).join(" ");
5513
+ if (suffix === prefix2) return j;
4998
5514
  }
4999
5515
  return 0;
5000
5516
  }
@@ -5133,22 +5649,22 @@ function toMdFilename(vttFilename) {
5133
5649
  return `${basename5(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
5134
5650
  }
5135
5651
  function resolveOutputDir(relativeDir, transcriptsDir) {
5136
- return relativeDir === "." ? transcriptsDir : join20(transcriptsDir, relativeDir);
5652
+ return relativeDir === "." ? transcriptsDir : join21(transcriptsDir, relativeDir);
5137
5653
  }
5138
5654
  function buildOutputPaths(vttFile, transcriptsDir) {
5139
5655
  const mdFile = toMdFilename(vttFile.filename);
5140
- const relativeDir = dirname14(vttFile.relativePath);
5656
+ const relativeDir = dirname16(vttFile.relativePath);
5141
5657
  const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
5142
- const outputPath = join20(outputDir, mdFile);
5658
+ const outputPath = join21(outputDir, mdFile);
5143
5659
  return { outputDir, outputPath, mdFile, relativeDir };
5144
5660
  }
5145
5661
  function logSkipped(relativeDir, mdFile) {
5146
- console.log(`Skipping (already exists): ${join20(relativeDir, mdFile)}`);
5662
+ console.log(`Skipping (already exists): ${join21(relativeDir, mdFile)}`);
5147
5663
  return "skipped";
5148
5664
  }
5149
5665
  function ensureDirectory(dir, label2) {
5150
- if (!existsSync19(dir)) {
5151
- mkdirSync5(dir, { recursive: true });
5666
+ if (!existsSync22(dir)) {
5667
+ mkdirSync6(dir, { recursive: true });
5152
5668
  console.log(`Created ${label2}: ${dir}`);
5153
5669
  }
5154
5670
  }
@@ -5170,10 +5686,10 @@ function logReduction(cueCount, messageCount) {
5170
5686
  }
5171
5687
  function readAndParseCues(inputPath) {
5172
5688
  console.log(`Reading: ${inputPath}`);
5173
- return processCues(readFileSync15(inputPath, "utf-8"));
5689
+ return processCues(readFileSync18(inputPath, "utf-8"));
5174
5690
  }
5175
5691
  function writeFormatted(outputPath, content) {
5176
- writeFileSync17(outputPath, content, "utf-8");
5692
+ writeFileSync19(outputPath, content, "utf-8");
5177
5693
  console.log(`Written: ${outputPath}`);
5178
5694
  }
5179
5695
  function convertVttToMarkdown(inputPath, outputPath) {
@@ -5183,7 +5699,7 @@ function convertVttToMarkdown(inputPath, outputPath) {
5183
5699
  logReduction(cues.length, chatMessages.length);
5184
5700
  }
5185
5701
  function tryProcessVtt(vttFile, paths) {
5186
- if (existsSync19(paths.outputPath))
5702
+ if (existsSync22(paths.outputPath))
5187
5703
  return logSkipped(paths.relativeDir, paths.mdFile);
5188
5704
  convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
5189
5705
  return "processed";
@@ -5209,7 +5725,7 @@ function processAllFiles(vttFiles, transcriptsDir) {
5209
5725
  logSummary(counts);
5210
5726
  }
5211
5727
  function requireVttDir(vttDir) {
5212
- if (!existsSync20(vttDir)) {
5728
+ if (!existsSync23(vttDir)) {
5213
5729
  console.error(`VTT directory not found: ${vttDir}`);
5214
5730
  process.exit(1);
5215
5731
  }
@@ -5241,28 +5757,28 @@ async function format() {
5241
5757
  }
5242
5758
 
5243
5759
  // src/commands/transcript/summarise/index.ts
5244
- import { existsSync as existsSync22 } from "fs";
5245
- import { basename as basename6, dirname as dirname16, join as join22, relative as relative2 } from "path";
5760
+ import { existsSync as existsSync25 } from "fs";
5761
+ import { basename as basename6, dirname as dirname18, join as join23, relative as relative2 } from "path";
5246
5762
 
5247
5763
  // src/commands/transcript/summarise/processStagedFile/index.ts
5248
5764
  import {
5249
- existsSync as existsSync21,
5250
- mkdirSync as mkdirSync6,
5251
- readFileSync as readFileSync16,
5765
+ existsSync as existsSync24,
5766
+ mkdirSync as mkdirSync7,
5767
+ readFileSync as readFileSync19,
5252
5768
  renameSync as renameSync2,
5253
5769
  rmSync
5254
5770
  } from "fs";
5255
- import { dirname as dirname15, join as join21 } from "path";
5771
+ import { dirname as dirname17, join as join22 } from "path";
5256
5772
 
5257
5773
  // src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
5258
- import chalk50 from "chalk";
5774
+ import chalk51 from "chalk";
5259
5775
  var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
5260
5776
  function validateStagedContent(filename, content) {
5261
5777
  const firstLine = content.split("\n")[0];
5262
5778
  const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
5263
5779
  if (!match) {
5264
5780
  console.error(
5265
- chalk50.red(
5781
+ chalk51.red(
5266
5782
  `Staged file ${filename} missing [Full Transcript](<path>) link on first line.`
5267
5783
  )
5268
5784
  );
@@ -5271,7 +5787,7 @@ function validateStagedContent(filename, content) {
5271
5787
  const contentAfterLink = content.slice(firstLine.length).trim();
5272
5788
  if (!contentAfterLink) {
5273
5789
  console.error(
5274
- chalk50.red(
5790
+ chalk51.red(
5275
5791
  `Staged file ${filename} has no summary content after the transcript link.`
5276
5792
  )
5277
5793
  );
@@ -5281,9 +5797,9 @@ function validateStagedContent(filename, content) {
5281
5797
  }
5282
5798
 
5283
5799
  // src/commands/transcript/summarise/processStagedFile/index.ts
5284
- var STAGING_DIR = join21(process.cwd(), ".assist", "transcript");
5800
+ var STAGING_DIR = join22(process.cwd(), ".assist", "transcript");
5285
5801
  function processStagedFile() {
5286
- if (!existsSync21(STAGING_DIR)) {
5802
+ if (!existsSync24(STAGING_DIR)) {
5287
5803
  return false;
5288
5804
  }
5289
5805
  const stagedFiles = findMdFilesRecursive(STAGING_DIR);
@@ -5292,7 +5808,7 @@ function processStagedFile() {
5292
5808
  }
5293
5809
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
5294
5810
  const stagedFile = stagedFiles[0];
5295
- const content = readFileSync16(stagedFile.absolutePath, "utf-8");
5811
+ const content = readFileSync19(stagedFile.absolutePath, "utf-8");
5296
5812
  validateStagedContent(stagedFile.filename, content);
5297
5813
  const stagedBaseName = getTranscriptBaseName(stagedFile.filename);
5298
5814
  const transcriptFiles = findMdFilesRecursive(transcriptsDir);
@@ -5305,10 +5821,10 @@ function processStagedFile() {
5305
5821
  );
5306
5822
  process.exit(1);
5307
5823
  }
5308
- const destPath = join21(summaryDir, matchingTranscript.relativePath);
5309
- const destDir = dirname15(destPath);
5310
- if (!existsSync21(destDir)) {
5311
- mkdirSync6(destDir, { recursive: true });
5824
+ const destPath = join22(summaryDir, matchingTranscript.relativePath);
5825
+ const destDir = dirname17(destPath);
5826
+ if (!existsSync24(destDir)) {
5827
+ mkdirSync7(destDir, { recursive: true });
5312
5828
  }
5313
5829
  renameSync2(stagedFile.absolutePath, destPath);
5314
5830
  const remaining = findMdFilesRecursive(STAGING_DIR);
@@ -5320,8 +5836,8 @@ function processStagedFile() {
5320
5836
 
5321
5837
  // src/commands/transcript/summarise/index.ts
5322
5838
  function buildRelativeKey(relativePath, baseName) {
5323
- const relDir = dirname16(relativePath);
5324
- return relDir === "." ? baseName : join22(relDir, baseName);
5839
+ const relDir = dirname18(relativePath);
5840
+ return relDir === "." ? baseName : join23(relDir, baseName);
5325
5841
  }
5326
5842
  function buildSummaryIndex(summaryDir) {
5327
5843
  const summaryFiles = findMdFilesRecursive(summaryDir);
@@ -5334,7 +5850,7 @@ function buildSummaryIndex(summaryDir) {
5334
5850
  function summarise() {
5335
5851
  processStagedFile();
5336
5852
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
5337
- if (!existsSync22(transcriptsDir)) {
5853
+ if (!existsSync25(transcriptsDir)) {
5338
5854
  console.log("No transcripts directory found.");
5339
5855
  return;
5340
5856
  }
@@ -5355,8 +5871,8 @@ function summarise() {
5355
5871
  }
5356
5872
  const next2 = missing[0];
5357
5873
  const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
5358
- const outputPath = join22(STAGING_DIR, outputFilename);
5359
- const summaryFileDir = join22(summaryDir, dirname16(next2.relativePath));
5874
+ const outputPath = join23(STAGING_DIR, outputFilename);
5875
+ const summaryFileDir = join23(summaryDir, dirname18(next2.relativePath));
5360
5876
  const relativeTranscriptPath = encodeURI(
5361
5877
  relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
5362
5878
  );
@@ -5402,50 +5918,50 @@ function registerVerify(program2) {
5402
5918
 
5403
5919
  // src/commands/voice/devices.ts
5404
5920
  import { spawnSync as spawnSync3 } from "child_process";
5405
- import { join as join24 } from "path";
5921
+ import { join as join25 } from "path";
5406
5922
 
5407
5923
  // src/commands/voice/shared.ts
5408
- import { homedir as homedir4 } from "os";
5409
- import { dirname as dirname17, join as join23 } from "path";
5410
- import { fileURLToPath as fileURLToPath4 } from "url";
5411
- var __dirname5 = dirname17(fileURLToPath4(import.meta.url));
5412
- var VOICE_DIR = join23(homedir4(), ".assist", "voice");
5924
+ import { homedir as homedir5 } from "os";
5925
+ import { dirname as dirname19, join as join24 } from "path";
5926
+ import { fileURLToPath as fileURLToPath6 } from "url";
5927
+ var __dirname7 = dirname19(fileURLToPath6(import.meta.url));
5928
+ var VOICE_DIR = join24(homedir5(), ".assist", "voice");
5413
5929
  var voicePaths = {
5414
5930
  dir: VOICE_DIR,
5415
- pid: join23(VOICE_DIR, "voice.pid"),
5416
- log: join23(VOICE_DIR, "voice.log"),
5417
- venv: join23(VOICE_DIR, ".venv"),
5418
- lock: join23(VOICE_DIR, "voice.lock")
5931
+ pid: join24(VOICE_DIR, "voice.pid"),
5932
+ log: join24(VOICE_DIR, "voice.log"),
5933
+ venv: join24(VOICE_DIR, ".venv"),
5934
+ lock: join24(VOICE_DIR, "voice.lock")
5419
5935
  };
5420
5936
  function getPythonDir() {
5421
- return join23(__dirname5, "commands", "voice", "python");
5937
+ return join24(__dirname7, "commands", "voice", "python");
5422
5938
  }
5423
5939
  function getVenvPython() {
5424
- return process.platform === "win32" ? join23(voicePaths.venv, "Scripts", "python.exe") : join23(voicePaths.venv, "bin", "python");
5940
+ return process.platform === "win32" ? join24(voicePaths.venv, "Scripts", "python.exe") : join24(voicePaths.venv, "bin", "python");
5425
5941
  }
5426
5942
  function getLockDir() {
5427
5943
  const config = loadConfig();
5428
5944
  return config.voice?.lockDir ?? VOICE_DIR;
5429
5945
  }
5430
5946
  function getLockFile() {
5431
- return join23(getLockDir(), "voice.lock");
5947
+ return join24(getLockDir(), "voice.lock");
5432
5948
  }
5433
5949
 
5434
5950
  // src/commands/voice/devices.ts
5435
5951
  function devices() {
5436
- const script = join24(getPythonDir(), "list_devices.py");
5952
+ const script = join25(getPythonDir(), "list_devices.py");
5437
5953
  spawnSync3(getVenvPython(), [script], { stdio: "inherit" });
5438
5954
  }
5439
5955
 
5440
5956
  // src/commands/voice/logs.ts
5441
- import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
5957
+ import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
5442
5958
  function logs(options2) {
5443
- if (!existsSync23(voicePaths.log)) {
5959
+ if (!existsSync26(voicePaths.log)) {
5444
5960
  console.log("No voice log file found");
5445
5961
  return;
5446
5962
  }
5447
5963
  const count = Number.parseInt(options2.lines ?? "150", 10);
5448
- const content = readFileSync17(voicePaths.log, "utf-8").trim();
5964
+ const content = readFileSync20(voicePaths.log, "utf-8").trim();
5449
5965
  if (!content) {
5450
5966
  console.log("Voice log is empty");
5451
5967
  return;
@@ -5467,13 +5983,13 @@ function logs(options2) {
5467
5983
 
5468
5984
  // src/commands/voice/setup.ts
5469
5985
  import { spawnSync as spawnSync4 } from "child_process";
5470
- import { mkdirSync as mkdirSync8 } from "fs";
5471
- import { join as join26 } from "path";
5986
+ import { mkdirSync as mkdirSync9 } from "fs";
5987
+ import { join as join27 } from "path";
5472
5988
 
5473
5989
  // src/commands/voice/checkLockFile.ts
5474
- import { execSync as execSync25 } from "child_process";
5475
- import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
5476
- import { join as join25 } from "path";
5990
+ import { execSync as execSync27 } from "child_process";
5991
+ import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync20 } from "fs";
5992
+ import { join as join26 } from "path";
5477
5993
  function isProcessAlive(pid) {
5478
5994
  try {
5479
5995
  process.kill(pid, 0);
@@ -5484,9 +6000,9 @@ function isProcessAlive(pid) {
5484
6000
  }
5485
6001
  function checkLockFile() {
5486
6002
  const lockFile = getLockFile();
5487
- if (!existsSync24(lockFile)) return;
6003
+ if (!existsSync27(lockFile)) return;
5488
6004
  try {
5489
- const lock = JSON.parse(readFileSync18(lockFile, "utf-8"));
6005
+ const lock = JSON.parse(readFileSync21(lockFile, "utf-8"));
5490
6006
  if (lock.pid && isProcessAlive(lock.pid)) {
5491
6007
  console.error(
5492
6008
  `Voice daemon already running (PID ${lock.pid}, env: ${lock.env}). Stop it first with: assist voice stop`
@@ -5497,10 +6013,10 @@ function checkLockFile() {
5497
6013
  }
5498
6014
  }
5499
6015
  function bootstrapVenv() {
5500
- if (existsSync24(getVenvPython())) return;
6016
+ if (existsSync27(getVenvPython())) return;
5501
6017
  console.log("Setting up Python environment...");
5502
6018
  const pythonDir = getPythonDir();
5503
- execSync25(
6019
+ execSync27(
5504
6020
  `uv sync --project "${pythonDir}" --extra runtime --no-install-project`,
5505
6021
  {
5506
6022
  stdio: "inherit",
@@ -5510,8 +6026,8 @@ function bootstrapVenv() {
5510
6026
  }
5511
6027
  function writeLockFile(pid) {
5512
6028
  const lockFile = getLockFile();
5513
- mkdirSync7(join25(lockFile, ".."), { recursive: true });
5514
- writeFileSync18(
6029
+ mkdirSync8(join26(lockFile, ".."), { recursive: true });
6030
+ writeFileSync20(
5515
6031
  lockFile,
5516
6032
  JSON.stringify({
5517
6033
  pid,
@@ -5523,10 +6039,10 @@ function writeLockFile(pid) {
5523
6039
 
5524
6040
  // src/commands/voice/setup.ts
5525
6041
  function setup() {
5526
- mkdirSync8(voicePaths.dir, { recursive: true });
6042
+ mkdirSync9(voicePaths.dir, { recursive: true });
5527
6043
  bootstrapVenv();
5528
6044
  console.log("\nDownloading models...\n");
5529
- const script = join26(getPythonDir(), "setup_models.py");
6045
+ const script = join27(getPythonDir(), "setup_models.py");
5530
6046
  const result = spawnSync4(getVenvPython(), [script], {
5531
6047
  stdio: "inherit",
5532
6048
  env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
@@ -5539,8 +6055,8 @@ function setup() {
5539
6055
 
5540
6056
  // src/commands/voice/start.ts
5541
6057
  import { spawn as spawn4 } from "child_process";
5542
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
5543
- import { join as join27 } from "path";
6058
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync21 } from "fs";
6059
+ import { join as join28 } from "path";
5544
6060
 
5545
6061
  // src/commands/voice/buildDaemonEnv.ts
5546
6062
  function buildDaemonEnv(options2) {
@@ -5568,17 +6084,17 @@ function spawnBackground(python, script, env) {
5568
6084
  console.error("Failed to start voice daemon");
5569
6085
  process.exit(1);
5570
6086
  }
5571
- writeFileSync19(voicePaths.pid, String(pid));
6087
+ writeFileSync21(voicePaths.pid, String(pid));
5572
6088
  writeLockFile(pid);
5573
6089
  console.log(`Voice daemon started (PID ${pid})`);
5574
6090
  }
5575
6091
  function start2(options2) {
5576
- mkdirSync9(voicePaths.dir, { recursive: true });
6092
+ mkdirSync10(voicePaths.dir, { recursive: true });
5577
6093
  checkLockFile();
5578
6094
  bootstrapVenv();
5579
6095
  const debug = options2.debug || options2.foreground || process.platform === "win32";
5580
6096
  const env = buildDaemonEnv({ debug });
5581
- const script = join27(getPythonDir(), "voice_daemon.py");
6097
+ const script = join28(getPythonDir(), "voice_daemon.py");
5582
6098
  const python = getVenvPython();
5583
6099
  if (options2.foreground) {
5584
6100
  spawnForeground(python, script, env);
@@ -5588,7 +6104,7 @@ function start2(options2) {
5588
6104
  }
5589
6105
 
5590
6106
  // src/commands/voice/status.ts
5591
- import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
6107
+ import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
5592
6108
  function isProcessAlive2(pid) {
5593
6109
  try {
5594
6110
  process.kill(pid, 0);
@@ -5598,16 +6114,16 @@ function isProcessAlive2(pid) {
5598
6114
  }
5599
6115
  }
5600
6116
  function readRecentLogs(count) {
5601
- if (!existsSync25(voicePaths.log)) return [];
5602
- const lines = readFileSync19(voicePaths.log, "utf-8").trim().split("\n");
6117
+ if (!existsSync28(voicePaths.log)) return [];
6118
+ const lines = readFileSync22(voicePaths.log, "utf-8").trim().split("\n");
5603
6119
  return lines.slice(-count);
5604
6120
  }
5605
6121
  function status() {
5606
- if (!existsSync25(voicePaths.pid)) {
6122
+ if (!existsSync28(voicePaths.pid)) {
5607
6123
  console.log("Voice daemon: not running (no PID file)");
5608
6124
  return;
5609
6125
  }
5610
- const pid = Number.parseInt(readFileSync19(voicePaths.pid, "utf-8").trim(), 10);
6126
+ const pid = Number.parseInt(readFileSync22(voicePaths.pid, "utf-8").trim(), 10);
5611
6127
  const alive = isProcessAlive2(pid);
5612
6128
  console.log(`Voice daemon: ${alive ? "running" : "dead"} (PID ${pid})`);
5613
6129
  const recent = readRecentLogs(5);
@@ -5626,13 +6142,13 @@ function status() {
5626
6142
  }
5627
6143
 
5628
6144
  // src/commands/voice/stop.ts
5629
- import { existsSync as existsSync26, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
6145
+ import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync7 } from "fs";
5630
6146
  function stop() {
5631
- if (!existsSync26(voicePaths.pid)) {
6147
+ if (!existsSync29(voicePaths.pid)) {
5632
6148
  console.log("Voice daemon is not running (no PID file)");
5633
6149
  return;
5634
6150
  }
5635
- const pid = Number.parseInt(readFileSync20(voicePaths.pid, "utf-8").trim(), 10);
6151
+ const pid = Number.parseInt(readFileSync23(voicePaths.pid, "utf-8").trim(), 10);
5636
6152
  try {
5637
6153
  process.kill(pid, "SIGTERM");
5638
6154
  console.log(`Sent SIGTERM to voice daemon (PID ${pid})`);
@@ -5645,7 +6161,7 @@ function stop() {
5645
6161
  }
5646
6162
  try {
5647
6163
  const lockFile = getLockFile();
5648
- if (existsSync26(lockFile)) unlinkSync7(lockFile);
6164
+ if (existsSync29(lockFile)) unlinkSync7(lockFile);
5649
6165
  } catch {
5650
6166
  }
5651
6167
  console.log("Voice daemon stopped");
@@ -5664,14 +6180,14 @@ function registerVoice(program2) {
5664
6180
 
5665
6181
  // src/commands/roam/auth.ts
5666
6182
  import { randomBytes } from "crypto";
5667
- import chalk51 from "chalk";
6183
+ import chalk52 from "chalk";
5668
6184
 
5669
6185
  // src/lib/openBrowser.ts
5670
- import { execSync as execSync26 } from "child_process";
6186
+ import { execSync as execSync28 } from "child_process";
5671
6187
  function tryExec(commands) {
5672
6188
  for (const cmd of commands) {
5673
6189
  try {
5674
- execSync26(cmd);
6190
+ execSync28(cmd);
5675
6191
  return true;
5676
6192
  } catch {
5677
6193
  }
@@ -5729,7 +6245,7 @@ function extractCode(url, expectedState) {
5729
6245
  return code;
5730
6246
  }
5731
6247
  function waitForCallback(port, expectedState) {
5732
- return new Promise((resolve3, reject) => {
6248
+ return new Promise((resolve5, reject) => {
5733
6249
  const timeout = setTimeout(() => {
5734
6250
  server.close();
5735
6251
  reject(new Error("Authorization timed out after 120 seconds"));
@@ -5746,7 +6262,7 @@ function waitForCallback(port, expectedState) {
5746
6262
  const code = extractCode(url, expectedState);
5747
6263
  respondHtml(res, 200, "Authorization successful!");
5748
6264
  server.close();
5749
- resolve3(code);
6265
+ resolve5(code);
5750
6266
  } catch (err) {
5751
6267
  respondHtml(res, 400, err.message);
5752
6268
  server.close();
@@ -5839,13 +6355,13 @@ async function auth() {
5839
6355
  saveGlobalConfig(config);
5840
6356
  const state = randomBytes(16).toString("hex");
5841
6357
  console.log(
5842
- chalk51.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
6358
+ chalk52.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
5843
6359
  );
5844
- console.log(chalk51.white("http://localhost:14523/callback\n"));
5845
- console.log(chalk51.blue("Opening browser for authorization..."));
5846
- console.log(chalk51.dim("Waiting for authorization callback..."));
6360
+ console.log(chalk52.white("http://localhost:14523/callback\n"));
6361
+ console.log(chalk52.blue("Opening browser for authorization..."));
6362
+ console.log(chalk52.dim("Waiting for authorization callback..."));
5847
6363
  const { code, redirectUri } = await authorizeInBrowser(clientId, state);
5848
- console.log(chalk51.dim("Exchanging code for tokens..."));
6364
+ console.log(chalk52.dim("Exchanging code for tokens..."));
5849
6365
  const tokens = await exchangeToken({
5850
6366
  code,
5851
6367
  clientId,
@@ -5861,7 +6377,7 @@ async function auth() {
5861
6377
  };
5862
6378
  saveGlobalConfig(config);
5863
6379
  console.log(
5864
- chalk51.green("Roam credentials and tokens saved to ~/.assist.yml")
6380
+ chalk52.green("Roam credentials and tokens saved to ~/.assist.yml")
5865
6381
  );
5866
6382
  }
5867
6383
 
@@ -5875,8 +6391,8 @@ function registerRoam(program2) {
5875
6391
  import { spawn as spawn5 } from "child_process";
5876
6392
 
5877
6393
  // src/commands/run/add.ts
5878
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync20 } from "fs";
5879
- import { join as join28 } from "path";
6394
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync22 } from "fs";
6395
+ import { join as join29 } from "path";
5880
6396
  function findAddIndex() {
5881
6397
  const addIndex = process.argv.indexOf("add");
5882
6398
  if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
@@ -5930,16 +6446,16 @@ function saveNewRunConfig(name, command, args) {
5930
6446
  saveConfig(config);
5931
6447
  }
5932
6448
  function createCommandFile(name) {
5933
- const dir = join28(".claude", "commands");
5934
- mkdirSync10(dir, { recursive: true });
6449
+ const dir = join29(".claude", "commands");
6450
+ mkdirSync11(dir, { recursive: true });
5935
6451
  const content = `---
5936
6452
  description: Run ${name}
5937
6453
  ---
5938
6454
 
5939
6455
  Run \`assist run ${name} $ARGUMENTS 2>&1\`.
5940
6456
  `;
5941
- const filePath = join28(dir, `${name}.md`);
5942
- writeFileSync20(filePath, content);
6457
+ const filePath = join29(dir, `${name}.md`);
6458
+ writeFileSync22(filePath, content);
5943
6459
  console.log(`Created command file: ${filePath}`);
5944
6460
  }
5945
6461
  function add2() {
@@ -6009,14 +6525,14 @@ function run2(name, args) {
6009
6525
  }
6010
6526
 
6011
6527
  // src/commands/statusLine.ts
6012
- import chalk52 from "chalk";
6528
+ import chalk53 from "chalk";
6013
6529
  function formatNumber(num) {
6014
6530
  return num.toLocaleString("en-US");
6015
6531
  }
6016
6532
  function colorizePercent(pct) {
6017
6533
  const label2 = `${pct}%`;
6018
- if (pct > 80) return chalk52.red(label2);
6019
- if (pct > 40) return chalk52.yellow(label2);
6534
+ if (pct > 80) return chalk53.red(label2);
6535
+ if (pct > 40) return chalk53.yellow(label2);
6020
6536
  return label2;
6021
6537
  }
6022
6538
  async function statusLine() {
@@ -6037,12 +6553,12 @@ async function statusLine() {
6037
6553
  import * as fs23 from "fs";
6038
6554
  import * as os from "os";
6039
6555
  import * as path29 from "path";
6040
- import { fileURLToPath as fileURLToPath5 } from "url";
6556
+ import { fileURLToPath as fileURLToPath7 } from "url";
6041
6557
 
6042
6558
  // src/commands/sync/syncClaudeMd.ts
6043
6559
  import * as fs21 from "fs";
6044
6560
  import * as path27 from "path";
6045
- import chalk53 from "chalk";
6561
+ import chalk54 from "chalk";
6046
6562
  async function syncClaudeMd(claudeDir, targetBase) {
6047
6563
  const source = path27.join(claudeDir, "CLAUDE.md");
6048
6564
  const target = path27.join(targetBase, "CLAUDE.md");
@@ -6051,12 +6567,12 @@ async function syncClaudeMd(claudeDir, targetBase) {
6051
6567
  const targetContent = fs21.readFileSync(target, "utf-8");
6052
6568
  if (sourceContent !== targetContent) {
6053
6569
  console.log(
6054
- chalk53.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
6570
+ chalk54.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
6055
6571
  );
6056
6572
  console.log();
6057
6573
  printDiff(targetContent, sourceContent);
6058
6574
  const confirm = await promptConfirm(
6059
- chalk53.red("Overwrite existing CLAUDE.md?"),
6575
+ chalk54.red("Overwrite existing CLAUDE.md?"),
6060
6576
  false
6061
6577
  );
6062
6578
  if (!confirm) {
@@ -6072,26 +6588,30 @@ async function syncClaudeMd(claudeDir, targetBase) {
6072
6588
  // src/commands/sync/syncSettings.ts
6073
6589
  import * as fs22 from "fs";
6074
6590
  import * as path28 from "path";
6075
- import chalk54 from "chalk";
6591
+ import chalk55 from "chalk";
6076
6592
  async function syncSettings(claudeDir, targetBase, options2) {
6077
6593
  const source = path28.join(claudeDir, "settings.json");
6078
6594
  const target = path28.join(targetBase, "settings.json");
6079
6595
  const sourceContent = fs22.readFileSync(source, "utf-8");
6080
- const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
6596
+ const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
6081
6597
  if (fs22.existsSync(target)) {
6082
6598
  const targetContent = fs22.readFileSync(target, "utf-8");
6083
- const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
6084
- if (normalizedSource !== normalizedTarget) {
6599
+ const normalizedTarget = JSON.stringify(
6600
+ JSON.parse(targetContent),
6601
+ null,
6602
+ " "
6603
+ );
6604
+ if (mergedContent !== normalizedTarget) {
6085
6605
  if (!options2?.yes) {
6086
6606
  console.log(
6087
- chalk54.yellow(
6607
+ chalk55.yellow(
6088
6608
  "\n\u26A0\uFE0F Warning: settings.json differs from existing file"
6089
6609
  )
6090
6610
  );
6091
6611
  console.log();
6092
- printDiff(targetContent, sourceContent);
6612
+ printDiff(targetContent, mergedContent);
6093
6613
  const confirm = await promptConfirm(
6094
- chalk54.red("Overwrite existing settings.json?"),
6614
+ chalk55.red("Overwrite existing settings.json?"),
6095
6615
  false
6096
6616
  );
6097
6617
  if (!confirm) {
@@ -6101,15 +6621,15 @@ async function syncSettings(claudeDir, targetBase, options2) {
6101
6621
  }
6102
6622
  }
6103
6623
  }
6104
- fs22.copyFileSync(source, target);
6624
+ fs22.writeFileSync(target, mergedContent);
6105
6625
  console.log("Copied settings.json to ~/.claude/settings.json");
6106
6626
  }
6107
6627
 
6108
6628
  // src/commands/sync.ts
6109
- var __filename2 = fileURLToPath5(import.meta.url);
6110
- var __dirname6 = path29.dirname(__filename2);
6629
+ var __filename4 = fileURLToPath7(import.meta.url);
6630
+ var __dirname8 = path29.dirname(__filename4);
6111
6631
  async function sync(options2) {
6112
- const claudeDir = path29.join(__dirname6, "..", "claude");
6632
+ const claudeDir = path29.join(__dirname8, "..", "claude");
6113
6633
  const targetBase = path29.join(os.homedir(), ".claude");
6114
6634
  syncCommands(claudeDir, targetBase);
6115
6635
  await syncSettings(claudeDir, targetBase, { yes: options2?.yes });
@@ -6128,28 +6648,11 @@ function syncCommands(claudeDir, targetBase) {
6128
6648
  }
6129
6649
 
6130
6650
  // src/commands/update.ts
6131
- import { execSync as execSync27 } from "child_process";
6651
+ import { execSync as execSync29 } from "child_process";
6132
6652
  import * as path30 from "path";
6133
- import { fileURLToPath as fileURLToPath6 } from "url";
6134
- var __filename3 = fileURLToPath6(import.meta.url);
6135
- var __dirname7 = path30.dirname(__filename3);
6136
- function getInstallDir() {
6137
- return path30.resolve(__dirname7, "..");
6138
- }
6139
- function isGitRepo(dir) {
6140
- try {
6141
- const result = execSync27("git rev-parse --show-toplevel", {
6142
- cwd: dir,
6143
- stdio: "pipe"
6144
- }).toString().trim();
6145
- return path30.resolve(result) === path30.resolve(dir);
6146
- } catch {
6147
- return false;
6148
- }
6149
- }
6150
6653
  function isGlobalNpmInstall(dir) {
6151
6654
  try {
6152
- const globalPrefix = execSync27("npm prefix -g", { stdio: "pipe" }).toString().trim();
6655
+ const globalPrefix = execSync29("npm prefix -g", { stdio: "pipe" }).toString().trim();
6153
6656
  return path30.resolve(dir).toLowerCase().startsWith(path30.resolve(globalPrefix).toLowerCase());
6154
6657
  } catch {
6155
6658
  return false;
@@ -6160,16 +6663,16 @@ async function update() {
6160
6663
  console.log(`Assist is installed at: ${installDir}`);
6161
6664
  if (isGitRepo(installDir)) {
6162
6665
  console.log("Detected git repo installation, pulling latest...");
6163
- execSync27("git pull", { cwd: installDir, stdio: "inherit" });
6666
+ execSync29("git pull", { cwd: installDir, stdio: "inherit" });
6164
6667
  console.log("Building...");
6165
- execSync27("npm run build", { cwd: installDir, stdio: "inherit" });
6668
+ execSync29("npm run build", { cwd: installDir, stdio: "inherit" });
6166
6669
  console.log("Syncing commands...");
6167
- execSync27("assist sync", { stdio: "inherit" });
6670
+ execSync29("assist sync", { stdio: "inherit" });
6168
6671
  } else if (isGlobalNpmInstall(installDir)) {
6169
6672
  console.log("Detected global npm installation, updating...");
6170
- execSync27("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6673
+ execSync29("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6171
6674
  console.log("Syncing commands...");
6172
- execSync27("assist sync", { stdio: "inherit" });
6675
+ execSync29("assist sync", { stdio: "inherit" });
6173
6676
  } else {
6174
6677
  console.error(
6175
6678
  "Could not determine installation method. Expected a git repo or global npm install."
@@ -6203,6 +6706,8 @@ program.command("notify").description(
6203
6706
  "Show notification from Claude Code hook (reads JSON from stdin)"
6204
6707
  ).action(notify);
6205
6708
  program.command("update").description("Update assist to the latest version and sync commands").action(update);
6709
+ registerPermitCliReads(program);
6710
+ registerCliHook(program);
6206
6711
  registerPrs(program);
6207
6712
  registerRoam(program);
6208
6713
  registerBacklog(program);