@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/README.md +2 -0
- package/assist.cli-reads +1927 -0
- package/claude/settings.json +8 -0
- package/dist/index.js +828 -323
- package/package.json +7 -4
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.
|
|
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 {
|
|
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 (
|
|
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 =
|
|
172
|
-
const projectRaw =
|
|
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
|
|
185
|
+
return loadRawYaml(getConfigPath());
|
|
178
186
|
}
|
|
179
187
|
function loadGlobalConfigRaw() {
|
|
180
|
-
return
|
|
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 (
|
|
203
|
+
if (existsSync2(packageJsonPath)) {
|
|
196
204
|
try {
|
|
197
|
-
const content =
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 (!
|
|
722
|
+
if (!existsSync6(packageJsonPath)) {
|
|
715
723
|
return false;
|
|
716
724
|
}
|
|
717
|
-
const packageJson = JSON.parse(
|
|
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 (!
|
|
795
|
+
if (!existsSync7(biomeConfigPath)) {
|
|
788
796
|
console.log("Initializing Biome...");
|
|
789
797
|
execSync4("npx @biomejs/biome init", { stdio: "inherit" });
|
|
790
798
|
}
|
|
791
|
-
if (!
|
|
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(
|
|
797
|
-
const biomeConfig = JSON.parse(
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
1795
|
+
if (!existsSync10(WORKFLOW_PATH)) {
|
|
1788
1796
|
return null;
|
|
1789
1797
|
}
|
|
1790
|
-
const content =
|
|
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 =
|
|
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 (!
|
|
1810
|
+
if (!existsSync10(workflowDir)) {
|
|
1803
1811
|
mkdirSync3(workflowDir, { recursive: true });
|
|
1804
1812
|
}
|
|
1805
|
-
if (
|
|
1806
|
-
const oldContent =
|
|
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 (!
|
|
1908
|
+
if (!existsSync11(viteConfigPath)) {
|
|
1901
1909
|
console.log("No vite.config.ts found, skipping base config");
|
|
1902
1910
|
return;
|
|
1903
1911
|
}
|
|
1904
|
-
const content =
|
|
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
|
|
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
|
|
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 (!
|
|
2084
|
+
if (!existsSync12(backlogPath)) {
|
|
2077
2085
|
return [];
|
|
2078
2086
|
}
|
|
2079
2087
|
try {
|
|
2080
|
-
const content =
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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((
|
|
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", () =>
|
|
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 =
|
|
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
|
|
2599
|
-
return `${
|
|
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
|
|
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 (!
|
|
3184
|
+
if (!existsSync17(indexPath)) {
|
|
3016
3185
|
console.log(chalk36.yellow("No index.html found"));
|
|
3017
3186
|
return;
|
|
3018
3187
|
}
|
|
3019
|
-
const content =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
3930
|
+
import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
|
|
3410
3931
|
import { tmpdir as tmpdir2 } from "os";
|
|
3411
|
-
import { join as
|
|
3932
|
+
import { join as join13 } from "path";
|
|
3412
3933
|
|
|
3413
3934
|
// src/commands/prs/shared.ts
|
|
3414
|
-
import { execSync as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
3484
|
-
|
|
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
|
|
4043
|
+
import { execSync as execSync21 } from "child_process";
|
|
3523
4044
|
|
|
3524
4045
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3525
|
-
import { execSync as
|
|
3526
|
-
import { unlinkSync as unlinkSync5, writeFileSync as
|
|
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
|
|
4049
|
+
import { join as join15 } from "path";
|
|
3529
4050
|
|
|
3530
4051
|
// src/commands/prs/loadCommentsCache.ts
|
|
3531
|
-
import { existsSync as
|
|
3532
|
-
import { join as
|
|
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
|
|
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 (!
|
|
4060
|
+
if (!existsSync19(cachePath)) {
|
|
3540
4061
|
return null;
|
|
3541
4062
|
}
|
|
3542
|
-
const content =
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
3563
|
-
|
|
4083
|
+
const queryFile = join15(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
|
|
4084
|
+
writeFileSync16(queryFile, mutation);
|
|
3564
4085
|
try {
|
|
3565
|
-
|
|
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
|
|
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
|
|
3644
|
-
import { join as
|
|
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
|
|
3654
|
-
import { unlinkSync as unlinkSync6, writeFileSync as
|
|
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
|
|
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 =
|
|
3660
|
-
|
|
4175
|
+
const queryFile = join16(tmpdir4(), `gh-query-${Date.now()}.graphql`);
|
|
4176
|
+
writeFileSync17(queryFile, THREAD_QUERY);
|
|
3661
4177
|
try {
|
|
3662
|
-
const result =
|
|
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
|
|
4200
|
+
import { execSync as execSync23 } from "child_process";
|
|
3685
4201
|
function fetchJson(endpoint) {
|
|
3686
|
-
const result =
|
|
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
|
|
4244
|
+
import chalk44 from "chalk";
|
|
3729
4245
|
function formatForHuman(comment2) {
|
|
3730
4246
|
if (comment2.type === "review") {
|
|
3731
|
-
const stateColor = comment2.state === "APPROVED" ?
|
|
4247
|
+
const stateColor = comment2.state === "APPROVED" ? chalk44.green : comment2.state === "CHANGES_REQUESTED" ? chalk44.red : chalk44.yellow;
|
|
3732
4248
|
return [
|
|
3733
|
-
`${
|
|
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
|
-
`${
|
|
3741
|
-
|
|
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 =
|
|
3765
|
-
if (!
|
|
3766
|
-
|
|
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 =
|
|
3774
|
-
|
|
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
|
|
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
|
|
4336
|
+
import chalk45 from "chalk";
|
|
3821
4337
|
var STATUS_MAP = {
|
|
3822
|
-
MERGED: (pr) => pr.mergedAt ? { label:
|
|
3823
|
-
CLOSED: (pr) => pr.closedAt ? { label:
|
|
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:
|
|
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 `${
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
4534
|
+
console.error(chalk46.red(`
|
|
4019
4535
|
Refactor check failed:
|
|
4020
4536
|
`));
|
|
4021
|
-
console.error(
|
|
4537
|
+
console.error(chalk46.red(` The following files exceed ${maxLines} lines:
|
|
4022
4538
|
`));
|
|
4023
4539
|
for (const violation of violations) {
|
|
4024
|
-
console.error(
|
|
4540
|
+
console.error(chalk46.red(` ${violation.file} (${violation.lines} lines)`));
|
|
4025
4541
|
}
|
|
4026
4542
|
console.error(
|
|
4027
|
-
|
|
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(
|
|
4550
|
+
console.error(chalk46.gray(` assist refactor ignore <file>
|
|
4035
4551
|
`));
|
|
4036
4552
|
if (process.env.CLAUDECODE) {
|
|
4037
|
-
console.error(
|
|
4553
|
+
console.error(chalk46.cyan(`
|
|
4038
4554
|
## Extracting Code to New Files
|
|
4039
4555
|
`));
|
|
4040
4556
|
console.error(
|
|
4041
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
5003
|
+
console.log(chalk48.bold("\nFile moves:"));
|
|
4488
5004
|
for (const move of plan.moves) {
|
|
4489
5005
|
console.log(
|
|
4490
|
-
` ${
|
|
5006
|
+
` ${chalk48.red(relPath(move.from))} \u2192 ${chalk48.green(relPath(move.to))}`
|
|
4491
5007
|
);
|
|
4492
|
-
console.log(
|
|
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(
|
|
5014
|
+
console.log(chalk48.bold(`
|
|
4499
5015
|
Import rewrites (${affectedFiles.size} files):`));
|
|
4500
5016
|
for (const file of affectedFiles) {
|
|
4501
|
-
console.log(` ${
|
|
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
|
-
` ${
|
|
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(
|
|
4514
|
-
for (const w of plan.warnings) console.log(
|
|
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(
|
|
5033
|
+
console.log(chalk48.bold("\nNew directories:"));
|
|
4518
5034
|
for (const dir of plan.newDirectories)
|
|
4519
|
-
console.log(
|
|
5035
|
+
console.log(chalk48.green(` ${dir}/`));
|
|
4520
5036
|
}
|
|
4521
5037
|
displayMoves(plan);
|
|
4522
5038
|
displayRewrites(plan.rewrites);
|
|
4523
5039
|
console.log(
|
|
4524
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
5227
|
+
console.log(chalk50.bold("\nApplying changes..."));
|
|
4712
5228
|
executePlan(plan);
|
|
4713
|
-
console.log(
|
|
5229
|
+
console.log(chalk50.green("\nRestructuring complete"));
|
|
4714
5230
|
} else {
|
|
4715
|
-
console.log(
|
|
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
|
|
4739
|
-
import { basename as basename4, join as
|
|
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 (!
|
|
5270
|
+
if (!existsSync21(dir)) return [];
|
|
4755
5271
|
const results = [];
|
|
4756
5272
|
for (const entry of readdirSync2(dir)) {
|
|
4757
|
-
const fullPath =
|
|
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((
|
|
5305
|
+
return new Promise((resolve5) => {
|
|
4790
5306
|
rl.question(question, (answer) => {
|
|
4791
|
-
|
|
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
|
|
5367
|
+
import { existsSync as existsSync23 } from "fs";
|
|
4852
5368
|
|
|
4853
5369
|
// src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
|
|
4854
|
-
import { dirname as
|
|
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
|
|
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,
|
|
4872
|
-
const newFilename = `${
|
|
4873
|
-
renameSync(
|
|
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
|
|
5406
|
+
const prefix2 = await resolveDate(rl, choice);
|
|
4891
5407
|
rl.close();
|
|
4892
|
-
return
|
|
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 =
|
|
5420
|
+
const vttFileDir = dirname15(vttFile.absolutePath);
|
|
4905
5421
|
const newFilename = await promptForDateFix(vttFile.filename, vttFileDir);
|
|
4906
5422
|
if (newFilename) {
|
|
4907
|
-
const newRelativePath =
|
|
4908
|
-
|
|
5423
|
+
const newRelativePath = join20(
|
|
5424
|
+
dirname15(vttFile.relativePath),
|
|
4909
5425
|
newFilename
|
|
4910
5426
|
);
|
|
4911
5427
|
vttFiles[i] = {
|
|
4912
|
-
absolutePath:
|
|
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
|
|
4926
|
-
import { basename as basename5, dirname as
|
|
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
|
|
4997
|
-
if (suffix ===
|
|
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 :
|
|
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 =
|
|
5656
|
+
const relativeDir = dirname16(vttFile.relativePath);
|
|
5141
5657
|
const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
|
|
5142
|
-
const outputPath =
|
|
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): ${
|
|
5662
|
+
console.log(`Skipping (already exists): ${join21(relativeDir, mdFile)}`);
|
|
5147
5663
|
return "skipped";
|
|
5148
5664
|
}
|
|
5149
5665
|
function ensureDirectory(dir, label2) {
|
|
5150
|
-
if (!
|
|
5151
|
-
|
|
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(
|
|
5689
|
+
return processCues(readFileSync18(inputPath, "utf-8"));
|
|
5174
5690
|
}
|
|
5175
5691
|
function writeFormatted(outputPath, content) {
|
|
5176
|
-
|
|
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 (
|
|
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 (!
|
|
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
|
|
5245
|
-
import { basename as basename6, dirname as
|
|
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
|
|
5250
|
-
mkdirSync as
|
|
5251
|
-
readFileSync as
|
|
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
|
|
5771
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
5256
5772
|
|
|
5257
5773
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
5258
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5800
|
+
var STAGING_DIR = join22(process.cwd(), ".assist", "transcript");
|
|
5285
5801
|
function processStagedFile() {
|
|
5286
|
-
if (!
|
|
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 =
|
|
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 =
|
|
5309
|
-
const destDir =
|
|
5310
|
-
if (!
|
|
5311
|
-
|
|
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 =
|
|
5324
|
-
return 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 (!
|
|
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 =
|
|
5359
|
-
const summaryFileDir =
|
|
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
|
|
5921
|
+
import { join as join25 } from "path";
|
|
5406
5922
|
|
|
5407
5923
|
// src/commands/voice/shared.ts
|
|
5408
|
-
import { homedir as
|
|
5409
|
-
import { dirname as
|
|
5410
|
-
import { fileURLToPath as
|
|
5411
|
-
var
|
|
5412
|
-
var VOICE_DIR =
|
|
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:
|
|
5416
|
-
log:
|
|
5417
|
-
venv:
|
|
5418
|
-
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
|
|
5937
|
+
return join24(__dirname7, "commands", "voice", "python");
|
|
5422
5938
|
}
|
|
5423
5939
|
function getVenvPython() {
|
|
5424
|
-
return process.platform === "win32" ?
|
|
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
|
|
5947
|
+
return join24(getLockDir(), "voice.lock");
|
|
5432
5948
|
}
|
|
5433
5949
|
|
|
5434
5950
|
// src/commands/voice/devices.ts
|
|
5435
5951
|
function devices() {
|
|
5436
|
-
const script =
|
|
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
|
|
5957
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
|
|
5442
5958
|
function logs(options2) {
|
|
5443
|
-
if (!
|
|
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 =
|
|
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
|
|
5471
|
-
import { join as
|
|
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
|
|
5475
|
-
import { existsSync as
|
|
5476
|
-
import { join as
|
|
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 (!
|
|
6003
|
+
if (!existsSync27(lockFile)) return;
|
|
5488
6004
|
try {
|
|
5489
|
-
const lock = JSON.parse(
|
|
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 (
|
|
6016
|
+
if (existsSync27(getVenvPython())) return;
|
|
5501
6017
|
console.log("Setting up Python environment...");
|
|
5502
6018
|
const pythonDir = getPythonDir();
|
|
5503
|
-
|
|
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
|
-
|
|
5514
|
-
|
|
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
|
-
|
|
6042
|
+
mkdirSync9(voicePaths.dir, { recursive: true });
|
|
5527
6043
|
bootstrapVenv();
|
|
5528
6044
|
console.log("\nDownloading models...\n");
|
|
5529
|
-
const script =
|
|
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
|
|
5543
|
-
import { join as
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 (!
|
|
5602
|
-
const lines =
|
|
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 (!
|
|
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(
|
|
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
|
|
6145
|
+
import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync7 } from "fs";
|
|
5630
6146
|
function stop() {
|
|
5631
|
-
if (!
|
|
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(
|
|
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 (
|
|
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
|
|
6183
|
+
import chalk52 from "chalk";
|
|
5668
6184
|
|
|
5669
6185
|
// src/lib/openBrowser.ts
|
|
5670
|
-
import { execSync as
|
|
6186
|
+
import { execSync as execSync28 } from "child_process";
|
|
5671
6187
|
function tryExec(commands) {
|
|
5672
6188
|
for (const cmd of commands) {
|
|
5673
6189
|
try {
|
|
5674
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
6358
|
+
chalk52.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
|
|
5843
6359
|
);
|
|
5844
|
-
console.log(
|
|
5845
|
-
console.log(
|
|
5846
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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
|
|
5879
|
-
import { join as
|
|
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 =
|
|
5934
|
-
|
|
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 =
|
|
5942
|
-
|
|
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
|
|
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
|
|
6019
|
-
if (pct > 40) return
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
6084
|
-
|
|
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
|
-
|
|
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,
|
|
6612
|
+
printDiff(targetContent, mergedContent);
|
|
6093
6613
|
const confirm = await promptConfirm(
|
|
6094
|
-
|
|
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.
|
|
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
|
|
6110
|
-
var
|
|
6629
|
+
var __filename4 = fileURLToPath7(import.meta.url);
|
|
6630
|
+
var __dirname8 = path29.dirname(__filename4);
|
|
6111
6631
|
async function sync(options2) {
|
|
6112
|
-
const claudeDir = path29.join(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
6666
|
+
execSync29("git pull", { cwd: installDir, stdio: "inherit" });
|
|
6164
6667
|
console.log("Building...");
|
|
6165
|
-
|
|
6668
|
+
execSync29("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
6166
6669
|
console.log("Syncing commands...");
|
|
6167
|
-
|
|
6670
|
+
execSync29("assist sync", { stdio: "inherit" });
|
|
6168
6671
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
6169
6672
|
console.log("Detected global npm installation, updating...");
|
|
6170
|
-
|
|
6673
|
+
execSync29("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
6171
6674
|
console.log("Syncing commands...");
|
|
6172
|
-
|
|
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);
|