@staff0rd/assist 0.95.0 → 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 -3
- package/assist.cli-reads +1927 -0
- package/claude/settings.json +5 -1942
- package/dist/index.js +932 -831
- package/package.json +7 -4
- package/claude/commands/generate-cli-read-verbs.md +0 -22
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";
|
|
@@ -151,7 +167,7 @@ var assistConfigSchema = z.strictObject({
|
|
|
151
167
|
// src/shared/loadConfig.ts
|
|
152
168
|
function getConfigPath() {
|
|
153
169
|
const claudeConfigPath = join(process.cwd(), ".claude", "assist.yml");
|
|
154
|
-
if (
|
|
170
|
+
if (existsSync2(claudeConfigPath)) {
|
|
155
171
|
return claudeConfigPath;
|
|
156
172
|
}
|
|
157
173
|
return join(process.cwd(), "assist.yml");
|
|
@@ -159,26 +175,17 @@ function getConfigPath() {
|
|
|
159
175
|
function getGlobalConfigPath() {
|
|
160
176
|
return join(homedir(), ".assist.yml");
|
|
161
177
|
}
|
|
162
|
-
function loadRawConfig(path31) {
|
|
163
|
-
if (!existsSync(path31)) return {};
|
|
164
|
-
try {
|
|
165
|
-
const content = readFileSync(path31, "utf-8");
|
|
166
|
-
return parseYaml(content) || {};
|
|
167
|
-
} catch {
|
|
168
|
-
return {};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
178
|
function loadConfig() {
|
|
172
|
-
const globalRaw =
|
|
173
|
-
const projectRaw =
|
|
179
|
+
const globalRaw = loadRawYaml(getGlobalConfigPath());
|
|
180
|
+
const projectRaw = loadRawYaml(getConfigPath());
|
|
174
181
|
const merged = { ...globalRaw, ...projectRaw };
|
|
175
182
|
return assistConfigSchema.parse(merged);
|
|
176
183
|
}
|
|
177
184
|
function loadProjectConfig() {
|
|
178
|
-
return
|
|
185
|
+
return loadRawYaml(getConfigPath());
|
|
179
186
|
}
|
|
180
187
|
function loadGlobalConfigRaw() {
|
|
181
|
-
return
|
|
188
|
+
return loadRawYaml(getGlobalConfigPath());
|
|
182
189
|
}
|
|
183
190
|
function saveGlobalConfig(config) {
|
|
184
191
|
writeFileSync(getGlobalConfigPath(), stringifyYaml(config, { lineWidth: 0 }));
|
|
@@ -193,9 +200,9 @@ function getRepoName() {
|
|
|
193
200
|
return config.devlog.name;
|
|
194
201
|
}
|
|
195
202
|
const packageJsonPath = join(process.cwd(), "package.json");
|
|
196
|
-
if (
|
|
203
|
+
if (existsSync2(packageJsonPath)) {
|
|
197
204
|
try {
|
|
198
|
-
const content =
|
|
205
|
+
const content = readFileSync2(packageJsonPath, "utf-8");
|
|
199
206
|
const pkg = JSON.parse(content);
|
|
200
207
|
if (pkg.name) {
|
|
201
208
|
return pkg.name;
|
|
@@ -647,7 +654,7 @@ import chalk12 from "chalk";
|
|
|
647
654
|
|
|
648
655
|
// src/commands/lint/init.ts
|
|
649
656
|
import { execSync as execSync4 } from "child_process";
|
|
650
|
-
import { existsSync as
|
|
657
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
651
658
|
import { dirname as dirname5, join as join4 } from "path";
|
|
652
659
|
import { fileURLToPath } from "url";
|
|
653
660
|
import chalk11 from "chalk";
|
|
@@ -671,10 +678,10 @@ async function promptConfirm(message, initial = true) {
|
|
|
671
678
|
|
|
672
679
|
// src/shared/removeEslint/index.ts
|
|
673
680
|
import { execSync as execSync3 } from "child_process";
|
|
674
|
-
import { existsSync as
|
|
681
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
675
682
|
|
|
676
683
|
// src/shared/removeEslint/removeEslintConfigFiles.ts
|
|
677
|
-
import { existsSync as
|
|
684
|
+
import { existsSync as existsSync5, unlinkSync } from "fs";
|
|
678
685
|
var ESLINT_CONFIG_FILES = [
|
|
679
686
|
"eslint.config.js",
|
|
680
687
|
"eslint.config.mjs",
|
|
@@ -690,7 +697,7 @@ var ESLINT_CONFIG_FILES = [
|
|
|
690
697
|
function removeEslintConfigFiles() {
|
|
691
698
|
let removed = false;
|
|
692
699
|
for (const configFile of ESLINT_CONFIG_FILES) {
|
|
693
|
-
if (
|
|
700
|
+
if (existsSync5(configFile)) {
|
|
694
701
|
unlinkSync(configFile);
|
|
695
702
|
console.log(`Removed ${configFile}`);
|
|
696
703
|
removed = true;
|
|
@@ -712,10 +719,10 @@ function removeEslint(options2 = {}) {
|
|
|
712
719
|
}
|
|
713
720
|
function removeEslintFromPackageJson(options2) {
|
|
714
721
|
const packageJsonPath = "package.json";
|
|
715
|
-
if (!
|
|
722
|
+
if (!existsSync6(packageJsonPath)) {
|
|
716
723
|
return false;
|
|
717
724
|
}
|
|
718
|
-
const packageJson = JSON.parse(
|
|
725
|
+
const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
719
726
|
let modified = false;
|
|
720
727
|
modified = removeEslintDeps(packageJson.dependencies) || modified;
|
|
721
728
|
modified = removeEslintDeps(packageJson.devDependencies) || modified;
|
|
@@ -785,17 +792,17 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
|
|
|
785
792
|
async function init() {
|
|
786
793
|
removeEslint();
|
|
787
794
|
const biomeConfigPath = "biome.json";
|
|
788
|
-
if (!
|
|
795
|
+
if (!existsSync7(biomeConfigPath)) {
|
|
789
796
|
console.log("Initializing Biome...");
|
|
790
797
|
execSync4("npx @biomejs/biome init", { stdio: "inherit" });
|
|
791
798
|
}
|
|
792
|
-
if (!
|
|
799
|
+
if (!existsSync7(biomeConfigPath)) {
|
|
793
800
|
console.log("No biome.json found, skipping linter config");
|
|
794
801
|
return;
|
|
795
802
|
}
|
|
796
803
|
const linterConfigPath = join4(__dirname2, "commands/lint/biome.linter.json");
|
|
797
|
-
const linterConfig = JSON.parse(
|
|
798
|
-
const biomeConfig = JSON.parse(
|
|
804
|
+
const linterConfig = JSON.parse(readFileSync6(linterConfigPath, "utf-8"));
|
|
805
|
+
const biomeConfig = JSON.parse(readFileSync6(biomeConfigPath, "utf-8"));
|
|
799
806
|
const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
|
|
800
807
|
`;
|
|
801
808
|
biomeConfig.linter = linterConfig.linter;
|
|
@@ -1099,8 +1106,8 @@ function createSettingsJson() {
|
|
|
1099
1106
|
"source.organizeImports.biome": "explicit"
|
|
1100
1107
|
}
|
|
1101
1108
|
};
|
|
1102
|
-
const
|
|
1103
|
-
fs3.writeFileSync(
|
|
1109
|
+
const settingsPath = path9.join(process.cwd(), ".vscode", "settings.json");
|
|
1110
|
+
fs3.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
|
|
1104
1111
|
`);
|
|
1105
1112
|
console.log(chalk16.green("Created .vscode/settings.json"));
|
|
1106
1113
|
}
|
|
@@ -1618,14 +1625,14 @@ function flushIfFailed(exitCode, chunks) {
|
|
|
1618
1625
|
|
|
1619
1626
|
// src/commands/verify/run/runAllEntries.ts
|
|
1620
1627
|
function runEntry(entry, onComplete) {
|
|
1621
|
-
return new Promise((
|
|
1628
|
+
return new Promise((resolve5) => {
|
|
1622
1629
|
const child = spawnCommand(entry.fullCommand, entry.cwd, entry.env);
|
|
1623
1630
|
const chunks = collectOutput(child);
|
|
1624
1631
|
child.on("close", (code) => {
|
|
1625
1632
|
const exitCode = code ?? 1;
|
|
1626
1633
|
flushIfFailed(exitCode, chunks);
|
|
1627
1634
|
onComplete?.(exitCode);
|
|
1628
|
-
|
|
1635
|
+
resolve5({ script: entry.name, code: exitCode });
|
|
1629
1636
|
});
|
|
1630
1637
|
});
|
|
1631
1638
|
}
|
|
@@ -1770,7 +1777,7 @@ async function newCli() {
|
|
|
1770
1777
|
|
|
1771
1778
|
// src/commands/new/registerNew/newProject.ts
|
|
1772
1779
|
import { execSync as execSync12 } from "child_process";
|
|
1773
|
-
import { existsSync as
|
|
1780
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
1774
1781
|
|
|
1775
1782
|
// src/commands/deploy/init/index.ts
|
|
1776
1783
|
import { execSync as execSync11 } from "child_process";
|
|
@@ -1778,33 +1785,33 @@ import chalk21 from "chalk";
|
|
|
1778
1785
|
import enquirer3 from "enquirer";
|
|
1779
1786
|
|
|
1780
1787
|
// src/commands/deploy/init/updateWorkflow.ts
|
|
1781
|
-
import { existsSync as
|
|
1788
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
1782
1789
|
import { dirname as dirname10, join as join7 } from "path";
|
|
1783
1790
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1784
1791
|
import chalk20 from "chalk";
|
|
1785
1792
|
var WORKFLOW_PATH = ".github/workflows/build.yml";
|
|
1786
1793
|
var __dirname3 = dirname10(fileURLToPath2(import.meta.url));
|
|
1787
1794
|
function getExistingSiteId() {
|
|
1788
|
-
if (!
|
|
1795
|
+
if (!existsSync10(WORKFLOW_PATH)) {
|
|
1789
1796
|
return null;
|
|
1790
1797
|
}
|
|
1791
|
-
const content =
|
|
1798
|
+
const content = readFileSync8(WORKFLOW_PATH, "utf-8");
|
|
1792
1799
|
const match = content.match(/-s\s+([a-f0-9-]{36})/);
|
|
1793
1800
|
return match ? match[1] : null;
|
|
1794
1801
|
}
|
|
1795
1802
|
function getTemplateContent(siteId) {
|
|
1796
1803
|
const templatePath = join7(__dirname3, "commands/deploy/build.yml");
|
|
1797
|
-
const template =
|
|
1804
|
+
const template = readFileSync8(templatePath, "utf-8");
|
|
1798
1805
|
return template.replace("{{NETLIFY_SITE_ID}}", siteId);
|
|
1799
1806
|
}
|
|
1800
1807
|
async function updateWorkflow(siteId) {
|
|
1801
1808
|
const newContent = getTemplateContent(siteId);
|
|
1802
1809
|
const workflowDir = ".github/workflows";
|
|
1803
|
-
if (!
|
|
1810
|
+
if (!existsSync10(workflowDir)) {
|
|
1804
1811
|
mkdirSync3(workflowDir, { recursive: true });
|
|
1805
1812
|
}
|
|
1806
|
-
if (
|
|
1807
|
-
const oldContent =
|
|
1813
|
+
if (existsSync10(WORKFLOW_PATH)) {
|
|
1814
|
+
const oldContent = readFileSync8(WORKFLOW_PATH, "utf-8");
|
|
1808
1815
|
if (oldContent === newContent) {
|
|
1809
1816
|
console.log(chalk20.green("build.yml is already up to date"));
|
|
1810
1817
|
return;
|
|
@@ -1898,11 +1905,11 @@ async function newProject() {
|
|
|
1898
1905
|
}
|
|
1899
1906
|
function addViteBaseConfig() {
|
|
1900
1907
|
const viteConfigPath = "vite.config.ts";
|
|
1901
|
-
if (!
|
|
1908
|
+
if (!existsSync11(viteConfigPath)) {
|
|
1902
1909
|
console.log("No vite.config.ts found, skipping base config");
|
|
1903
1910
|
return;
|
|
1904
1911
|
}
|
|
1905
|
-
const content =
|
|
1912
|
+
const content = readFileSync9(viteConfigPath, "utf-8");
|
|
1906
1913
|
if (content.includes("base:")) {
|
|
1907
1914
|
console.log("vite.config.ts already has base config");
|
|
1908
1915
|
return;
|
|
@@ -2045,11 +2052,11 @@ async function notify() {
|
|
|
2045
2052
|
}
|
|
2046
2053
|
|
|
2047
2054
|
// src/commands/backlog/add/index.ts
|
|
2048
|
-
import { existsSync as
|
|
2055
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2049
2056
|
import chalk23 from "chalk";
|
|
2050
2057
|
|
|
2051
2058
|
// src/commands/backlog/shared.ts
|
|
2052
|
-
import { existsSync as
|
|
2059
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
2053
2060
|
import { join as join8 } from "path";
|
|
2054
2061
|
import chalk22 from "chalk";
|
|
2055
2062
|
import { parse as parseYaml2, stringify as stringifyYaml3 } from "yaml";
|
|
@@ -2074,11 +2081,11 @@ function getBacklogPath() {
|
|
|
2074
2081
|
}
|
|
2075
2082
|
function loadBacklog() {
|
|
2076
2083
|
const backlogPath = getBacklogPath();
|
|
2077
|
-
if (!
|
|
2084
|
+
if (!existsSync12(backlogPath)) {
|
|
2078
2085
|
return [];
|
|
2079
2086
|
}
|
|
2080
2087
|
try {
|
|
2081
|
-
const content =
|
|
2088
|
+
const content = readFileSync10(backlogPath, "utf-8");
|
|
2082
2089
|
const raw = parseYaml2(content) || [];
|
|
2083
2090
|
return backlogFileSchema.parse(raw);
|
|
2084
2091
|
} catch {
|
|
@@ -2093,7 +2100,7 @@ function findItem(items, id) {
|
|
|
2093
2100
|
return items.find((item) => item.id === id);
|
|
2094
2101
|
}
|
|
2095
2102
|
function loadAndFindItem(id) {
|
|
2096
|
-
if (!
|
|
2103
|
+
if (!existsSync12(getBacklogPath())) {
|
|
2097
2104
|
console.log(
|
|
2098
2105
|
chalk22.yellow(
|
|
2099
2106
|
"No backlog found. Run 'assist backlog init' to create one."
|
|
@@ -2129,7 +2136,7 @@ function getNextId(items) {
|
|
|
2129
2136
|
|
|
2130
2137
|
// src/commands/backlog/add/shared.ts
|
|
2131
2138
|
import { spawnSync } from "child_process";
|
|
2132
|
-
import { mkdtempSync, readFileSync as
|
|
2139
|
+
import { mkdtempSync, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
2133
2140
|
import { tmpdir } from "os";
|
|
2134
2141
|
import { join as join9 } from "path";
|
|
2135
2142
|
import enquirer4 from "enquirer";
|
|
@@ -2179,7 +2186,7 @@ function openEditor() {
|
|
|
2179
2186
|
unlinkSync2(filePath);
|
|
2180
2187
|
return void 0;
|
|
2181
2188
|
}
|
|
2182
|
-
const content =
|
|
2189
|
+
const content = readFileSync11(filePath, "utf-8").trim();
|
|
2183
2190
|
unlinkSync2(filePath);
|
|
2184
2191
|
return content || void 0;
|
|
2185
2192
|
}
|
|
@@ -2200,7 +2207,7 @@ async function promptAcceptanceCriteria() {
|
|
|
2200
2207
|
// src/commands/backlog/add/index.ts
|
|
2201
2208
|
async function add() {
|
|
2202
2209
|
const backlogPath = getBacklogPath();
|
|
2203
|
-
if (!
|
|
2210
|
+
if (!existsSync13(backlogPath)) {
|
|
2204
2211
|
console.log(
|
|
2205
2212
|
chalk23.yellow(
|
|
2206
2213
|
"No backlog found. Run 'assist backlog init' to create one."
|
|
@@ -2245,11 +2252,11 @@ async function done(id) {
|
|
|
2245
2252
|
}
|
|
2246
2253
|
|
|
2247
2254
|
// src/commands/backlog/init/index.ts
|
|
2248
|
-
import { existsSync as
|
|
2255
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2249
2256
|
import chalk26 from "chalk";
|
|
2250
2257
|
async function init6() {
|
|
2251
2258
|
const backlogPath = getBacklogPath();
|
|
2252
|
-
if (
|
|
2259
|
+
if (existsSync14(backlogPath)) {
|
|
2253
2260
|
console.log(chalk26.yellow("assist.backlog.yml already exists."));
|
|
2254
2261
|
return;
|
|
2255
2262
|
}
|
|
@@ -2258,7 +2265,7 @@ async function init6() {
|
|
|
2258
2265
|
}
|
|
2259
2266
|
|
|
2260
2267
|
// src/commands/backlog/list/index.ts
|
|
2261
|
-
import { existsSync as
|
|
2268
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2262
2269
|
import chalk27 from "chalk";
|
|
2263
2270
|
function statusIcon(status2) {
|
|
2264
2271
|
switch (status2) {
|
|
@@ -2297,7 +2304,7 @@ function filterItems(items, options2) {
|
|
|
2297
2304
|
}
|
|
2298
2305
|
async function list2(options2) {
|
|
2299
2306
|
const backlogPath = getBacklogPath();
|
|
2300
|
-
if (!
|
|
2307
|
+
if (!existsSync15(backlogPath)) {
|
|
2301
2308
|
console.log(
|
|
2302
2309
|
chalk27.yellow(
|
|
2303
2310
|
"No backlog found. Run 'assist backlog init' to create one."
|
|
@@ -2335,7 +2342,7 @@ import { createServer } from "http";
|
|
|
2335
2342
|
import chalk29 from "chalk";
|
|
2336
2343
|
|
|
2337
2344
|
// src/commands/backlog/web/handleRequest.ts
|
|
2338
|
-
import { readFileSync as
|
|
2345
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2339
2346
|
import { dirname as dirname11, join as join10 } from "path";
|
|
2340
2347
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2341
2348
|
|
|
@@ -2368,12 +2375,12 @@ function respondJson(res, status2, data) {
|
|
|
2368
2375
|
res.end(JSON.stringify(data));
|
|
2369
2376
|
}
|
|
2370
2377
|
function readBody(req) {
|
|
2371
|
-
return new Promise((
|
|
2378
|
+
return new Promise((resolve5, reject) => {
|
|
2372
2379
|
let body = "";
|
|
2373
2380
|
req.on("data", (chunk) => {
|
|
2374
2381
|
body += chunk.toString();
|
|
2375
2382
|
});
|
|
2376
|
-
req.on("end", () =>
|
|
2383
|
+
req.on("end", () => resolve5(body));
|
|
2377
2384
|
req.on("error", reject);
|
|
2378
2385
|
});
|
|
2379
2386
|
}
|
|
@@ -2438,7 +2445,7 @@ var __dirname4 = dirname11(fileURLToPath3(import.meta.url));
|
|
|
2438
2445
|
var bundleCache;
|
|
2439
2446
|
function serveBundle(_req, res) {
|
|
2440
2447
|
if (!bundleCache) {
|
|
2441
|
-
bundleCache =
|
|
2448
|
+
bundleCache = readFileSync12(
|
|
2442
2449
|
join10(__dirname4, "commands/backlog/web/bundle.js"),
|
|
2443
2450
|
"utf-8"
|
|
2444
2451
|
);
|
|
@@ -2510,466 +2517,221 @@ function registerBacklog(program2) {
|
|
|
2510
2517
|
backlogCommand.command("web").description("Start a web view of the backlog").option("-p, --port <number>", "Port to listen on", "3000").action(web);
|
|
2511
2518
|
}
|
|
2512
2519
|
|
|
2513
|
-
// src/
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
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;
|
|
2534
2543
|
}
|
|
2535
2544
|
}
|
|
2545
|
+
if (current) tokens.push(current);
|
|
2546
|
+
return tokens;
|
|
2536
2547
|
}
|
|
2537
2548
|
|
|
2538
|
-
// src/
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
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;
|
|
2546
2566
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
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;
|
|
2551
2577
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
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];
|
|
2561
2593
|
}
|
|
2562
2594
|
}
|
|
2563
|
-
|
|
2564
|
-
{ length: Math.min(concurrency, items.length) },
|
|
2565
|
-
() => worker()
|
|
2566
|
-
);
|
|
2567
|
-
await Promise.all(workers);
|
|
2568
|
-
return results;
|
|
2595
|
+
return void 0;
|
|
2569
2596
|
}
|
|
2570
2597
|
|
|
2571
|
-
// src/
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
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
|
+
`);
|
|
2575
2616
|
}
|
|
2576
|
-
function
|
|
2577
|
-
const
|
|
2578
|
-
if (
|
|
2579
|
-
|
|
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;
|
|
2580
2631
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
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;
|
|
2584
2643
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
return { name: spaceMatch[1], description: spaceMatch[2].trim() };
|
|
2644
|
+
if (data.tool_name !== "Bash" || !data.tool_input?.command) {
|
|
2645
|
+
return;
|
|
2588
2646
|
}
|
|
2589
|
-
|
|
2590
|
-
|
|
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;
|
|
2591
2660
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
}
|
|
2603
|
-
if (inCommandSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
2604
|
-
inCommandSection = false;
|
|
2605
|
-
continue;
|
|
2606
|
-
}
|
|
2607
|
-
if (!inCommandSection || !trimmed) continue;
|
|
2608
|
-
if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
|
|
2609
|
-
const parsed = parseCommandLine(trimmed);
|
|
2610
|
-
if (parsed) commands.push(parsed);
|
|
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
|
+
);
|
|
2611
2671
|
}
|
|
2612
|
-
return commands;
|
|
2613
|
-
}
|
|
2614
|
-
var COMMAND_SECTION_MULTILINE_RE = new RegExp(
|
|
2615
|
-
COMMAND_SECTION_RE.source,
|
|
2616
|
-
"im"
|
|
2617
|
-
);
|
|
2618
|
-
function hasSubcommands(helpText) {
|
|
2619
|
-
return COMMAND_SECTION_MULTILINE_RE.test(helpText);
|
|
2620
2672
|
}
|
|
2621
2673
|
|
|
2622
|
-
// src/commands/
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
exec2(
|
|
2627
|
-
`${args.join(" ")} --help`,
|
|
2628
|
-
{ encoding: "utf-8", timeout: 3e4 },
|
|
2629
|
-
(_err, stdout, stderr) => {
|
|
2630
|
-
resolve3(stdout || stderr || "");
|
|
2631
|
-
}
|
|
2632
|
-
);
|
|
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();
|
|
2633
2678
|
});
|
|
2634
2679
|
}
|
|
2635
2680
|
|
|
2636
|
-
// src/commands/
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2681
|
+
// src/commands/complexity/analyze.ts
|
|
2682
|
+
import chalk35 from "chalk";
|
|
2683
|
+
|
|
2684
|
+
// src/commands/complexity/cyclomatic.ts
|
|
2685
|
+
import chalk31 from "chalk";
|
|
2686
|
+
|
|
2687
|
+
// src/commands/complexity/shared/index.ts
|
|
2688
|
+
import fs11 from "fs";
|
|
2689
|
+
import path16 from "path";
|
|
2690
|
+
import chalk30 from "chalk";
|
|
2691
|
+
import ts5 from "typescript";
|
|
2692
|
+
|
|
2693
|
+
// src/commands/complexity/findSourceFiles.ts
|
|
2694
|
+
import fs10 from "fs";
|
|
2695
|
+
import path15 from "path";
|
|
2696
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
2697
|
+
function applyIgnoreGlobs(files) {
|
|
2698
|
+
const { complexity } = loadConfig();
|
|
2699
|
+
return files.filter(
|
|
2700
|
+
(f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
|
|
2701
|
+
);
|
|
2644
2702
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2703
|
+
function walk(dir, results) {
|
|
2704
|
+
if (!fs10.existsSync(dir)) {
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const extensions = [".ts", ".tsx"];
|
|
2708
|
+
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
2709
|
+
for (const entry of entries) {
|
|
2710
|
+
const fullPath = path15.join(dir, entry.name);
|
|
2711
|
+
if (entry.isDirectory()) {
|
|
2712
|
+
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
2713
|
+
walk(fullPath, results);
|
|
2714
|
+
}
|
|
2715
|
+
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
2716
|
+
results.push(fullPath);
|
|
2717
|
+
}
|
|
2650
2718
|
}
|
|
2651
|
-
const children = await discoverAt(cli, path31, depth + 1, p);
|
|
2652
|
-
return children.length > 0 ? children : [{ path: path31, description }];
|
|
2653
2719
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
)
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
|
|
2670
|
-
showProgress(p, cmd.name);
|
|
2671
|
-
const resolved = await resolveCommand(
|
|
2672
|
-
cli,
|
|
2673
|
-
[cmd.name],
|
|
2674
|
-
cmd.description,
|
|
2675
|
-
1,
|
|
2676
|
-
p
|
|
2677
|
-
);
|
|
2678
|
-
p.done++;
|
|
2679
|
-
showProgress(p, cmd.name);
|
|
2680
|
-
return resolved;
|
|
2681
|
-
});
|
|
2682
|
-
if (interactive) process.stderr.write("\r\x1B[K");
|
|
2683
|
-
return results.flat();
|
|
2684
|
-
}
|
|
2685
|
-
|
|
2686
|
-
// src/commands/cliDiscover/classifyVerb.ts
|
|
2687
|
-
var READ_VERBS = /* @__PURE__ */ new Set([
|
|
2688
|
-
"list",
|
|
2689
|
-
"show",
|
|
2690
|
-
"view",
|
|
2691
|
-
"export",
|
|
2692
|
-
"get",
|
|
2693
|
-
"diff",
|
|
2694
|
-
"status",
|
|
2695
|
-
"search",
|
|
2696
|
-
"checks",
|
|
2697
|
-
"describe",
|
|
2698
|
-
"inspect",
|
|
2699
|
-
"logs",
|
|
2700
|
-
"cat",
|
|
2701
|
-
"top",
|
|
2702
|
-
"explain",
|
|
2703
|
-
"exists",
|
|
2704
|
-
"browse",
|
|
2705
|
-
"watch"
|
|
2706
|
-
]);
|
|
2707
|
-
var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
2708
|
-
"create",
|
|
2709
|
-
"delete",
|
|
2710
|
-
"import",
|
|
2711
|
-
"set",
|
|
2712
|
-
"update",
|
|
2713
|
-
"merge",
|
|
2714
|
-
"close",
|
|
2715
|
-
"reopen",
|
|
2716
|
-
"edit",
|
|
2717
|
-
"apply",
|
|
2718
|
-
"patch",
|
|
2719
|
-
"drain",
|
|
2720
|
-
"cordon",
|
|
2721
|
-
"taint",
|
|
2722
|
-
"push",
|
|
2723
|
-
"deploy",
|
|
2724
|
-
"add",
|
|
2725
|
-
"remove",
|
|
2726
|
-
"assign",
|
|
2727
|
-
"unassign",
|
|
2728
|
-
"lock",
|
|
2729
|
-
"unlock",
|
|
2730
|
-
"start",
|
|
2731
|
-
"stop",
|
|
2732
|
-
"restart",
|
|
2733
|
-
"enable",
|
|
2734
|
-
"disable",
|
|
2735
|
-
"revoke",
|
|
2736
|
-
"rotate"
|
|
2737
|
-
]);
|
|
2738
|
-
function classifyVerb(verb) {
|
|
2739
|
-
if (READ_VERBS.has(verb)) return "r";
|
|
2740
|
-
if (WRITE_VERBS.has(verb)) return "w";
|
|
2741
|
-
return "?";
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
// src/commands/cliDiscover/formatHuman.ts
|
|
2745
|
-
function prefix(kind) {
|
|
2746
|
-
if (kind === "r") return " R ";
|
|
2747
|
-
if (kind === "w") return " W ";
|
|
2748
|
-
return " ? ";
|
|
2749
|
-
}
|
|
2750
|
-
function formatHuman(cli, commands) {
|
|
2751
|
-
const sorted = [...commands].sort(
|
|
2752
|
-
(a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
|
|
2753
|
-
);
|
|
2754
|
-
const lines = [`Discovered ${commands.length} commands for "${cli}":
|
|
2755
|
-
`];
|
|
2756
|
-
for (const cmd of sorted) {
|
|
2757
|
-
const verb = cmd.path[cmd.path.length - 1];
|
|
2758
|
-
const full = `${cli} ${cmd.path.join(" ")}`;
|
|
2759
|
-
const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
|
|
2760
|
-
lines.push(`${prefix(classifyVerb(verb))}${text}`);
|
|
2761
|
-
}
|
|
2762
|
-
return lines.join("\n");
|
|
2763
|
-
}
|
|
2764
|
-
|
|
2765
|
-
// src/commands/cliDiscover/parseCached.ts
|
|
2766
|
-
function parseCached(cli, cached) {
|
|
2767
|
-
const prefix2 = `${cli} `;
|
|
2768
|
-
const commands = [];
|
|
2769
|
-
for (const line of cached.split("\n")) {
|
|
2770
|
-
const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
|
|
2771
|
-
if (!trimmed.startsWith(prefix2)) continue;
|
|
2772
|
-
const rest = trimmed.slice(prefix2.length);
|
|
2773
|
-
const dashIdx = rest.indexOf(" \u2014 ");
|
|
2774
|
-
const pathStr = dashIdx >= 0 ? rest.slice(0, dashIdx) : rest;
|
|
2775
|
-
const description = dashIdx >= 0 ? rest.slice(dashIdx + 3) : "";
|
|
2776
|
-
commands.push({ path: pathStr.split(" "), description });
|
|
2777
|
-
}
|
|
2778
|
-
return commands;
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
// src/commands/cliDiscover/updateSettings.ts
|
|
2782
|
-
import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
2783
|
-
import { join as join11 } from "path";
|
|
2784
|
-
function settingsPath() {
|
|
2785
|
-
return join11(process.cwd(), "claude", "settings.json");
|
|
2786
|
-
}
|
|
2787
|
-
function updateSettings(cli, commands) {
|
|
2788
|
-
const path31 = settingsPath();
|
|
2789
|
-
if (!existsSync15(path31)) return;
|
|
2790
|
-
const settings = JSON.parse(readFileSync12(path31, "utf-8"));
|
|
2791
|
-
const allow = settings.permissions?.allow ?? [];
|
|
2792
|
-
const readCommands = commands.filter(
|
|
2793
|
-
(cmd) => classifyVerb(cmd.path[cmd.path.length - 1]) === "r"
|
|
2794
|
-
);
|
|
2795
|
-
const newEntries = readCommands.map(
|
|
2796
|
-
(cmd) => `Bash(${cli} ${cmd.path.join(" ")}:*)`
|
|
2797
|
-
);
|
|
2798
|
-
const existing = new Set(allow);
|
|
2799
|
-
const added = newEntries.filter((e) => !existing.has(e));
|
|
2800
|
-
if (added.length === 0) return;
|
|
2801
|
-
settings.permissions = settings.permissions ?? {};
|
|
2802
|
-
settings.permissions.allow = [...allow, ...added];
|
|
2803
|
-
writeFileSync12(path31, `${JSON.stringify(settings, null, " ")}
|
|
2804
|
-
`);
|
|
2805
|
-
}
|
|
2806
|
-
|
|
2807
|
-
// src/commands/cliDiscover/index.ts
|
|
2808
|
-
function logPath(cli) {
|
|
2809
|
-
const safeName = cli.replace(/\s+/g, "-");
|
|
2810
|
-
return join12(homedir3(), ".assist", `cli-discover-${safeName}.log`);
|
|
2811
|
-
}
|
|
2812
|
-
function readCache(cli) {
|
|
2813
|
-
const path31 = logPath(cli);
|
|
2814
|
-
if (!existsSync16(path31)) return void 0;
|
|
2815
|
-
return readFileSync13(path31, "utf-8");
|
|
2816
|
-
}
|
|
2817
|
-
function writeCache(cli, output) {
|
|
2818
|
-
const dir = join12(homedir3(), ".assist");
|
|
2819
|
-
mkdirSync4(dir, { recursive: true });
|
|
2820
|
-
writeFileSync13(logPath(cli), output);
|
|
2821
|
-
}
|
|
2822
|
-
async function cliDiscover(cli, options2 = { noCache: false }) {
|
|
2823
|
-
if (!cli) {
|
|
2824
|
-
console.error("Usage: assist cli-discover <cli>");
|
|
2825
|
-
process.exit(1);
|
|
2826
|
-
}
|
|
2827
|
-
if (!options2.noCache) {
|
|
2828
|
-
const cached = readCache(cli);
|
|
2829
|
-
if (cached) {
|
|
2830
|
-
console.log(colorize(cached));
|
|
2831
|
-
updateSettings(cli, parseCached(cli, cached));
|
|
2832
|
-
return;
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
assertCliExists(cli);
|
|
2836
|
-
const commands = await discoverAll(cli);
|
|
2837
|
-
const output = formatHuman(cli, commands);
|
|
2838
|
-
console.log(colorize(output));
|
|
2839
|
-
writeCache(cli, output);
|
|
2840
|
-
updateSettings(cli, commands);
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
// src/commands/registerCliDiscover.ts
|
|
2844
|
-
function registerCliDiscover(program2) {
|
|
2845
|
-
program2.command("cli-discover").description("Discover a CLI's command tree via recursive --help parsing").argument(
|
|
2846
|
-
"<cli...>",
|
|
2847
|
-
"CLI binary and optional subcommand (e.g. gh, az, acli jira)"
|
|
2848
|
-
).option("--no-cache", "Force fresh discovery, ignoring cached results").action((cli, options2) => {
|
|
2849
|
-
cliDiscover(cli.join(" "), { noCache: !options2.cache });
|
|
2850
|
-
});
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
// src/commands/cliHook/extractVerb.ts
|
|
2854
|
-
var SHELL_OPERATORS = /* @__PURE__ */ new Set(["|", ">", ">>", ";", "&&", "||"]);
|
|
2855
|
-
function isShellBreak(token) {
|
|
2856
|
-
return token.startsWith("-") || SHELL_OPERATORS.has(token);
|
|
2857
|
-
}
|
|
2858
|
-
function looksLikeArgument(token) {
|
|
2859
|
-
return /[/=.]/.test(token) || /^\d+$/.test(token);
|
|
2860
|
-
}
|
|
2861
|
-
function extractVerb(command, readVerbs) {
|
|
2862
|
-
const tokens = command.split(/\s+/);
|
|
2863
|
-
for (let i = 1; i < tokens.length; i++) {
|
|
2864
|
-
if (isShellBreak(tokens[i])) break;
|
|
2865
|
-
if (readVerbs.includes(tokens[i])) return tokens[i];
|
|
2866
|
-
}
|
|
2867
|
-
for (let i = tokens.length - 1; i >= 1; i--) {
|
|
2868
|
-
const token = tokens[i];
|
|
2869
|
-
if (isShellBreak(token) || looksLikeArgument(token)) continue;
|
|
2870
|
-
return token;
|
|
2871
|
-
}
|
|
2872
|
-
return void 0;
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
// src/commands/cliHook/index.ts
|
|
2876
|
-
async function cliHook() {
|
|
2877
|
-
const inputData = await readStdin();
|
|
2878
|
-
let data;
|
|
2879
|
-
try {
|
|
2880
|
-
data = JSON.parse(inputData);
|
|
2881
|
-
} catch {
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
if (data.tool_name !== "Bash" || !data.tool_input?.command) {
|
|
2885
|
-
return;
|
|
2886
|
-
}
|
|
2887
|
-
const command = data.tool_input.command.trim();
|
|
2888
|
-
const config = loadConfig();
|
|
2889
|
-
const cliReadVerbs = config.cliReadVerbs;
|
|
2890
|
-
if (!cliReadVerbs) return;
|
|
2891
|
-
const cliKeys = Object.keys(cliReadVerbs).sort((a, b) => b.length - a.length);
|
|
2892
|
-
const cli = cliKeys.find(
|
|
2893
|
-
(key) => command === key || command.startsWith(`${key} `)
|
|
2894
|
-
);
|
|
2895
|
-
if (!cli) return;
|
|
2896
|
-
const readVerbs = cliReadVerbs[cli];
|
|
2897
|
-
if (!readVerbs || readVerbs.length === 0) return;
|
|
2898
|
-
const verb = extractVerb(command, readVerbs);
|
|
2899
|
-
if (verb && readVerbs.includes(verb)) {
|
|
2900
|
-
console.log(
|
|
2901
|
-
JSON.stringify({
|
|
2902
|
-
hookSpecificOutput: {
|
|
2903
|
-
hookEventName: "PreToolUse",
|
|
2904
|
-
permissionDecision: "allow",
|
|
2905
|
-
permissionDecisionReason: `Read-only ${cli} command: ${verb}`
|
|
2906
|
-
}
|
|
2907
|
-
})
|
|
2908
|
-
);
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
|
-
// src/commands/registerCliHook.ts
|
|
2913
|
-
function registerCliHook(program2) {
|
|
2914
|
-
program2.command("cli-hook").description("PreToolUse hook for auto-approving read-only CLI commands").action(() => {
|
|
2915
|
-
cliHook();
|
|
2916
|
-
});
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
// src/commands/complexity/analyze.ts
|
|
2920
|
-
import chalk36 from "chalk";
|
|
2921
|
-
|
|
2922
|
-
// src/commands/complexity/cyclomatic.ts
|
|
2923
|
-
import chalk32 from "chalk";
|
|
2924
|
-
|
|
2925
|
-
// src/commands/complexity/shared/index.ts
|
|
2926
|
-
import fs11 from "fs";
|
|
2927
|
-
import path16 from "path";
|
|
2928
|
-
import chalk31 from "chalk";
|
|
2929
|
-
import ts5 from "typescript";
|
|
2930
|
-
|
|
2931
|
-
// src/commands/complexity/findSourceFiles.ts
|
|
2932
|
-
import fs10 from "fs";
|
|
2933
|
-
import path15 from "path";
|
|
2934
|
-
import { minimatch as minimatch3 } from "minimatch";
|
|
2935
|
-
function applyIgnoreGlobs(files) {
|
|
2936
|
-
const { complexity } = loadConfig();
|
|
2937
|
-
return files.filter(
|
|
2938
|
-
(f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
|
|
2939
|
-
);
|
|
2940
|
-
}
|
|
2941
|
-
function walk(dir, results) {
|
|
2942
|
-
if (!fs10.existsSync(dir)) {
|
|
2943
|
-
return;
|
|
2944
|
-
}
|
|
2945
|
-
const extensions = [".ts", ".tsx"];
|
|
2946
|
-
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
2947
|
-
for (const entry of entries) {
|
|
2948
|
-
const fullPath = path15.join(dir, entry.name);
|
|
2949
|
-
if (entry.isDirectory()) {
|
|
2950
|
-
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
2951
|
-
walk(fullPath, results);
|
|
2952
|
-
}
|
|
2953
|
-
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
2954
|
-
results.push(fullPath);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
function findSourceFiles2(pattern2, baseDir = ".") {
|
|
2959
|
-
const results = [];
|
|
2960
|
-
if (pattern2.includes("*")) {
|
|
2961
|
-
walk(baseDir, results);
|
|
2962
|
-
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2963
|
-
}
|
|
2964
|
-
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
|
|
2965
|
-
return [pattern2];
|
|
2966
|
-
}
|
|
2967
|
-
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isDirectory()) {
|
|
2968
|
-
walk(pattern2, results);
|
|
2969
|
-
return applyIgnoreGlobs(results);
|
|
2970
|
-
}
|
|
2971
|
-
walk(baseDir, results);
|
|
2972
|
-
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2720
|
+
function findSourceFiles2(pattern2, baseDir = ".") {
|
|
2721
|
+
const results = [];
|
|
2722
|
+
if (pattern2.includes("*")) {
|
|
2723
|
+
walk(baseDir, results);
|
|
2724
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2725
|
+
}
|
|
2726
|
+
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
|
|
2727
|
+
return [pattern2];
|
|
2728
|
+
}
|
|
2729
|
+
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isDirectory()) {
|
|
2730
|
+
walk(pattern2, results);
|
|
2731
|
+
return applyIgnoreGlobs(results);
|
|
2732
|
+
}
|
|
2733
|
+
walk(baseDir, results);
|
|
2734
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2973
2735
|
}
|
|
2974
2736
|
|
|
2975
2737
|
// src/commands/complexity/shared/getNodeName.ts
|
|
@@ -3171,7 +2933,7 @@ function createSourceFromFile(filePath) {
|
|
|
3171
2933
|
function withSourceFiles(pattern2, callback) {
|
|
3172
2934
|
const files = findSourceFiles2(pattern2);
|
|
3173
2935
|
if (files.length === 0) {
|
|
3174
|
-
console.log(
|
|
2936
|
+
console.log(chalk30.yellow("No files found matching pattern"));
|
|
3175
2937
|
return void 0;
|
|
3176
2938
|
}
|
|
3177
2939
|
return callback(files);
|
|
@@ -3204,11 +2966,11 @@ async function cyclomatic(pattern2 = "**/*.ts", options2 = {}) {
|
|
|
3204
2966
|
results.sort((a, b) => b.complexity - a.complexity);
|
|
3205
2967
|
for (const { file, name, complexity } of results) {
|
|
3206
2968
|
const exceedsThreshold = options2.threshold !== void 0 && complexity > options2.threshold;
|
|
3207
|
-
const color = exceedsThreshold ?
|
|
3208
|
-
console.log(`${color(`${file}:${name}`)} \u2192 ${
|
|
2969
|
+
const color = exceedsThreshold ? chalk31.red : chalk31.white;
|
|
2970
|
+
console.log(`${color(`${file}:${name}`)} \u2192 ${chalk31.cyan(complexity)}`);
|
|
3209
2971
|
}
|
|
3210
2972
|
console.log(
|
|
3211
|
-
|
|
2973
|
+
chalk31.dim(
|
|
3212
2974
|
`
|
|
3213
2975
|
Analyzed ${results.length} functions across ${files.length} files`
|
|
3214
2976
|
)
|
|
@@ -3220,7 +2982,7 @@ Analyzed ${results.length} functions across ${files.length} files`
|
|
|
3220
2982
|
}
|
|
3221
2983
|
|
|
3222
2984
|
// src/commands/complexity/halstead.ts
|
|
3223
|
-
import
|
|
2985
|
+
import chalk32 from "chalk";
|
|
3224
2986
|
async function halstead(pattern2 = "**/*.ts", options2 = {}) {
|
|
3225
2987
|
withSourceFiles(pattern2, (files) => {
|
|
3226
2988
|
const results = [];
|
|
@@ -3235,13 +2997,13 @@ async function halstead(pattern2 = "**/*.ts", options2 = {}) {
|
|
|
3235
2997
|
results.sort((a, b) => b.metrics.effort - a.metrics.effort);
|
|
3236
2998
|
for (const { file, name, metrics } of results) {
|
|
3237
2999
|
const exceedsThreshold = options2.threshold !== void 0 && metrics.volume > options2.threshold;
|
|
3238
|
-
const color = exceedsThreshold ?
|
|
3000
|
+
const color = exceedsThreshold ? chalk32.red : chalk32.white;
|
|
3239
3001
|
console.log(
|
|
3240
|
-
`${color(`${file}:${name}`)} \u2192 volume: ${
|
|
3002
|
+
`${color(`${file}:${name}`)} \u2192 volume: ${chalk32.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk32.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk32.magenta(metrics.effort.toFixed(1))}`
|
|
3241
3003
|
);
|
|
3242
3004
|
}
|
|
3243
3005
|
console.log(
|
|
3244
|
-
|
|
3006
|
+
chalk32.dim(
|
|
3245
3007
|
`
|
|
3246
3008
|
Analyzed ${results.length} functions across ${files.length} files`
|
|
3247
3009
|
)
|
|
@@ -3256,28 +3018,28 @@ Analyzed ${results.length} functions across ${files.length} files`
|
|
|
3256
3018
|
import fs12 from "fs";
|
|
3257
3019
|
|
|
3258
3020
|
// src/commands/complexity/maintainability/displayMaintainabilityResults.ts
|
|
3259
|
-
import
|
|
3021
|
+
import chalk33 from "chalk";
|
|
3260
3022
|
function displayMaintainabilityResults(results, threshold) {
|
|
3261
3023
|
const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
|
|
3262
3024
|
if (threshold !== void 0 && filtered.length === 0) {
|
|
3263
|
-
console.log(
|
|
3025
|
+
console.log(chalk33.green("All files pass maintainability threshold"));
|
|
3264
3026
|
} else {
|
|
3265
3027
|
for (const { file, avgMaintainability, minMaintainability } of filtered) {
|
|
3266
|
-
const color = threshold !== void 0 ?
|
|
3028
|
+
const color = threshold !== void 0 ? chalk33.red : chalk33.white;
|
|
3267
3029
|
console.log(
|
|
3268
|
-
`${color(file)} \u2192 avg: ${
|
|
3030
|
+
`${color(file)} \u2192 avg: ${chalk33.cyan(avgMaintainability.toFixed(1))}, min: ${chalk33.yellow(minMaintainability.toFixed(1))}`
|
|
3269
3031
|
);
|
|
3270
3032
|
}
|
|
3271
3033
|
}
|
|
3272
|
-
console.log(
|
|
3034
|
+
console.log(chalk33.dim(`
|
|
3273
3035
|
Analyzed ${results.length} files`));
|
|
3274
3036
|
if (filtered.length > 0 && threshold !== void 0) {
|
|
3275
3037
|
console.error(
|
|
3276
|
-
|
|
3038
|
+
chalk33.red(
|
|
3277
3039
|
`
|
|
3278
3040
|
Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.
|
|
3279
3041
|
|
|
3280
|
-
\u26A0\uFE0F ${
|
|
3042
|
+
\u26A0\uFE0F ${chalk33.bold("Diagnose and fix one file at a time")} \u2014 do not investigate or fix multiple files in parallel. Run 'assist complexity <file>' to see all metrics. For larger files, start by extracting responsibilities into smaller files.`
|
|
3281
3043
|
)
|
|
3282
3044
|
);
|
|
3283
3045
|
process.exit(1);
|
|
@@ -3334,7 +3096,7 @@ async function maintainability(pattern2 = "**/*.ts", options2 = {}) {
|
|
|
3334
3096
|
|
|
3335
3097
|
// src/commands/complexity/sloc.ts
|
|
3336
3098
|
import fs13 from "fs";
|
|
3337
|
-
import
|
|
3099
|
+
import chalk34 from "chalk";
|
|
3338
3100
|
async function sloc(pattern2 = "**/*.ts", options2 = {}) {
|
|
3339
3101
|
withSourceFiles(pattern2, (files) => {
|
|
3340
3102
|
const results = [];
|
|
@@ -3350,12 +3112,12 @@ async function sloc(pattern2 = "**/*.ts", options2 = {}) {
|
|
|
3350
3112
|
results.sort((a, b) => b.lines - a.lines);
|
|
3351
3113
|
for (const { file, lines } of results) {
|
|
3352
3114
|
const exceedsThreshold = options2.threshold !== void 0 && lines > options2.threshold;
|
|
3353
|
-
const color = exceedsThreshold ?
|
|
3354
|
-
console.log(`${color(file)} \u2192 ${
|
|
3115
|
+
const color = exceedsThreshold ? chalk34.red : chalk34.white;
|
|
3116
|
+
console.log(`${color(file)} \u2192 ${chalk34.cyan(lines)} lines`);
|
|
3355
3117
|
}
|
|
3356
3118
|
const total = results.reduce((sum, r) => sum + r.lines, 0);
|
|
3357
3119
|
console.log(
|
|
3358
|
-
|
|
3120
|
+
chalk34.dim(`
|
|
3359
3121
|
Total: ${total} lines across ${files.length} files`)
|
|
3360
3122
|
);
|
|
3361
3123
|
if (hasViolation) {
|
|
@@ -3369,21 +3131,21 @@ async function analyze(pattern2) {
|
|
|
3369
3131
|
const searchPattern = pattern2.includes("*") || pattern2.includes("/") ? pattern2 : `**/${pattern2}`;
|
|
3370
3132
|
const files = findSourceFiles2(searchPattern);
|
|
3371
3133
|
if (files.length === 0) {
|
|
3372
|
-
console.log(
|
|
3134
|
+
console.log(chalk35.yellow("No files found matching pattern"));
|
|
3373
3135
|
return;
|
|
3374
3136
|
}
|
|
3375
3137
|
if (files.length === 1) {
|
|
3376
3138
|
const file = files[0];
|
|
3377
|
-
console.log(
|
|
3139
|
+
console.log(chalk35.bold.underline("SLOC"));
|
|
3378
3140
|
await sloc(file);
|
|
3379
3141
|
console.log();
|
|
3380
|
-
console.log(
|
|
3142
|
+
console.log(chalk35.bold.underline("Cyclomatic Complexity"));
|
|
3381
3143
|
await cyclomatic(file);
|
|
3382
3144
|
console.log();
|
|
3383
|
-
console.log(
|
|
3145
|
+
console.log(chalk35.bold.underline("Halstead Metrics"));
|
|
3384
3146
|
await halstead(file);
|
|
3385
3147
|
console.log();
|
|
3386
|
-
console.log(
|
|
3148
|
+
console.log(chalk35.bold.underline("Maintainability Index"));
|
|
3387
3149
|
await maintainability(file);
|
|
3388
3150
|
return;
|
|
3389
3151
|
}
|
|
@@ -3410,8 +3172,8 @@ function registerComplexity(program2) {
|
|
|
3410
3172
|
}
|
|
3411
3173
|
|
|
3412
3174
|
// src/commands/deploy/redirect.ts
|
|
3413
|
-
import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as
|
|
3414
|
-
import
|
|
3175
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
3176
|
+
import chalk36 from "chalk";
|
|
3415
3177
|
var TRAILING_SLASH_SCRIPT = ` <script>
|
|
3416
3178
|
if (!window.location.pathname.endsWith('/')) {
|
|
3417
3179
|
window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
|
|
@@ -3420,22 +3182,22 @@ var TRAILING_SLASH_SCRIPT = ` <script>
|
|
|
3420
3182
|
function redirect() {
|
|
3421
3183
|
const indexPath = "index.html";
|
|
3422
3184
|
if (!existsSync17(indexPath)) {
|
|
3423
|
-
console.log(
|
|
3185
|
+
console.log(chalk36.yellow("No index.html found"));
|
|
3424
3186
|
return;
|
|
3425
3187
|
}
|
|
3426
3188
|
const content = readFileSync14(indexPath, "utf-8");
|
|
3427
3189
|
if (content.includes("window.location.pathname.endsWith('/')")) {
|
|
3428
|
-
console.log(
|
|
3190
|
+
console.log(chalk36.dim("Trailing slash script already present"));
|
|
3429
3191
|
return;
|
|
3430
3192
|
}
|
|
3431
3193
|
const headCloseIndex = content.indexOf("</head>");
|
|
3432
3194
|
if (headCloseIndex === -1) {
|
|
3433
|
-
console.log(
|
|
3195
|
+
console.log(chalk36.red("Could not find </head> tag in index.html"));
|
|
3434
3196
|
return;
|
|
3435
3197
|
}
|
|
3436
3198
|
const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
|
|
3437
|
-
|
|
3438
|
-
console.log(
|
|
3199
|
+
writeFileSync13(indexPath, newContent);
|
|
3200
|
+
console.log(chalk36.green("Added trailing slash redirect to index.html"));
|
|
3439
3201
|
}
|
|
3440
3202
|
|
|
3441
3203
|
// src/commands/registerDeploy.ts
|
|
@@ -3446,24 +3208,24 @@ function registerDeploy(program2) {
|
|
|
3446
3208
|
}
|
|
3447
3209
|
|
|
3448
3210
|
// src/commands/devlog/list/index.ts
|
|
3449
|
-
import { execSync as
|
|
3211
|
+
import { execSync as execSync14 } from "child_process";
|
|
3450
3212
|
import { basename as basename3 } from "path";
|
|
3451
3213
|
|
|
3452
3214
|
// src/commands/devlog/shared.ts
|
|
3453
|
-
import { execSync as
|
|
3454
|
-
import
|
|
3215
|
+
import { execSync as execSync13 } from "child_process";
|
|
3216
|
+
import chalk37 from "chalk";
|
|
3455
3217
|
|
|
3456
3218
|
// src/commands/devlog/loadDevlogEntries.ts
|
|
3457
3219
|
import { readdirSync, readFileSync as readFileSync15 } from "fs";
|
|
3458
|
-
import { homedir as
|
|
3459
|
-
import { join as
|
|
3460
|
-
var DEVLOG_DIR =
|
|
3220
|
+
import { homedir as homedir3 } from "os";
|
|
3221
|
+
import { join as join11 } from "path";
|
|
3222
|
+
var DEVLOG_DIR = join11(homedir3(), "git/blog/src/content/devlog");
|
|
3461
3223
|
function loadDevlogEntries(repoName) {
|
|
3462
3224
|
const entries = /* @__PURE__ */ new Map();
|
|
3463
3225
|
try {
|
|
3464
3226
|
const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
|
|
3465
3227
|
for (const file of files) {
|
|
3466
|
-
const content = readFileSync15(
|
|
3228
|
+
const content = readFileSync15(join11(DEVLOG_DIR, file), "utf-8");
|
|
3467
3229
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3468
3230
|
if (frontmatterMatch) {
|
|
3469
3231
|
const frontmatter = frontmatterMatch[1];
|
|
@@ -3494,7 +3256,7 @@ function loadDevlogEntries(repoName) {
|
|
|
3494
3256
|
// src/commands/devlog/shared.ts
|
|
3495
3257
|
function getCommitFiles(hash) {
|
|
3496
3258
|
try {
|
|
3497
|
-
const output =
|
|
3259
|
+
const output = execSync13(`git show --name-only --format="" ${hash}`, {
|
|
3498
3260
|
encoding: "utf-8"
|
|
3499
3261
|
});
|
|
3500
3262
|
return output.trim().split("\n").filter(Boolean);
|
|
@@ -3512,13 +3274,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
|
|
|
3512
3274
|
}
|
|
3513
3275
|
function printCommitsWithFiles(commits, ignore2, verbose) {
|
|
3514
3276
|
for (const commit2 of commits) {
|
|
3515
|
-
console.log(` ${
|
|
3277
|
+
console.log(` ${chalk37.yellow(commit2.hash)} ${commit2.message}`);
|
|
3516
3278
|
if (verbose) {
|
|
3517
3279
|
const visibleFiles = commit2.files.filter(
|
|
3518
3280
|
(file) => !ignore2.some((p) => file.startsWith(p))
|
|
3519
3281
|
);
|
|
3520
3282
|
for (const file of visibleFiles) {
|
|
3521
|
-
console.log(` ${
|
|
3283
|
+
console.log(` ${chalk37.dim(file)}`);
|
|
3522
3284
|
}
|
|
3523
3285
|
}
|
|
3524
3286
|
}
|
|
@@ -3543,15 +3305,15 @@ function parseGitLogCommits(output, ignore2, afterDate) {
|
|
|
3543
3305
|
}
|
|
3544
3306
|
|
|
3545
3307
|
// src/commands/devlog/list/printDateHeader.ts
|
|
3546
|
-
import
|
|
3308
|
+
import chalk38 from "chalk";
|
|
3547
3309
|
function printDateHeader(date, isSkipped, entries) {
|
|
3548
3310
|
if (isSkipped) {
|
|
3549
|
-
console.log(`${
|
|
3311
|
+
console.log(`${chalk38.bold.blue(date)} ${chalk38.dim("skipped")}`);
|
|
3550
3312
|
} else if (entries && entries.length > 0) {
|
|
3551
|
-
const entryInfo = entries.map((e) => `${
|
|
3552
|
-
console.log(`${
|
|
3313
|
+
const entryInfo = entries.map((e) => `${chalk38.green(e.version)} ${e.title}`).join(" | ");
|
|
3314
|
+
console.log(`${chalk38.bold.blue(date)} ${entryInfo}`);
|
|
3553
3315
|
} else {
|
|
3554
|
-
console.log(`${
|
|
3316
|
+
console.log(`${chalk38.bold.blue(date)} ${chalk38.red("\u26A0 devlog missing")}`);
|
|
3555
3317
|
}
|
|
3556
3318
|
}
|
|
3557
3319
|
|
|
@@ -3565,7 +3327,7 @@ function list3(options2) {
|
|
|
3565
3327
|
const devlogEntries = loadDevlogEntries(repoName);
|
|
3566
3328
|
const reverseFlag = options2.reverse ? "--reverse " : "";
|
|
3567
3329
|
const limitFlag = options2.reverse ? "" : "-n 500 ";
|
|
3568
|
-
const output =
|
|
3330
|
+
const output = execSync14(
|
|
3569
3331
|
`git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
|
|
3570
3332
|
{ encoding: "utf-8" }
|
|
3571
3333
|
);
|
|
@@ -3591,11 +3353,11 @@ function list3(options2) {
|
|
|
3591
3353
|
}
|
|
3592
3354
|
|
|
3593
3355
|
// src/commands/devlog/getLastVersionInfo.ts
|
|
3594
|
-
import { execSync as
|
|
3356
|
+
import { execSync as execSync15 } from "child_process";
|
|
3595
3357
|
import semver from "semver";
|
|
3596
3358
|
function getVersionAtCommit(hash) {
|
|
3597
3359
|
try {
|
|
3598
|
-
const content =
|
|
3360
|
+
const content = execSync15(`git show ${hash}:package.json`, {
|
|
3599
3361
|
encoding: "utf-8"
|
|
3600
3362
|
});
|
|
3601
3363
|
const pkg = JSON.parse(content);
|
|
@@ -3610,7 +3372,7 @@ function stripToMinor(version2) {
|
|
|
3610
3372
|
}
|
|
3611
3373
|
function getLastVersionInfoFromGit() {
|
|
3612
3374
|
try {
|
|
3613
|
-
const output =
|
|
3375
|
+
const output = execSync15(
|
|
3614
3376
|
"git log -1 --pretty=format:'%ad|%h' --date=short",
|
|
3615
3377
|
{
|
|
3616
3378
|
encoding: "utf-8"
|
|
@@ -3653,172 +3415,524 @@ function bumpVersion(version2, type) {
|
|
|
3653
3415
|
}
|
|
3654
3416
|
|
|
3655
3417
|
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3656
|
-
import { execSync as
|
|
3657
|
-
import
|
|
3418
|
+
import { execSync as execSync16 } from "child_process";
|
|
3419
|
+
import chalk40 from "chalk";
|
|
3658
3420
|
|
|
3659
3421
|
// src/commands/devlog/next/displayNextEntry/displayVersion.ts
|
|
3660
|
-
import
|
|
3422
|
+
import chalk39 from "chalk";
|
|
3661
3423
|
function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
|
|
3662
3424
|
if (conventional && firstHash) {
|
|
3663
3425
|
const version2 = getVersionAtCommit(firstHash);
|
|
3664
3426
|
if (version2) {
|
|
3665
|
-
console.log(`${
|
|
3427
|
+
console.log(`${chalk39.bold("version:")} ${stripToMinor(version2)}`);
|
|
3666
3428
|
} else {
|
|
3667
|
-
console.log(`${
|
|
3429
|
+
console.log(`${chalk39.bold("version:")} ${chalk39.red("unknown")}`);
|
|
3668
3430
|
}
|
|
3669
3431
|
} else if (patchVersion && minorVersion) {
|
|
3670
3432
|
console.log(
|
|
3671
|
-
`${
|
|
3433
|
+
`${chalk39.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
|
|
3672
3434
|
);
|
|
3673
3435
|
} else {
|
|
3674
|
-
console.log(`${
|
|
3436
|
+
console.log(`${chalk39.bold("version:")} v0.1 (initial)`);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3441
|
+
function computeVersions(lastInfo) {
|
|
3442
|
+
if (!lastInfo) return { patch: null, minor: null };
|
|
3443
|
+
return {
|
|
3444
|
+
patch: bumpVersion(lastInfo.version, "patch"),
|
|
3445
|
+
minor: bumpVersion(lastInfo.version, "minor")
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
function findTargetDate(commitsByDate, skipDays) {
|
|
3449
|
+
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
3450
|
+
}
|
|
3451
|
+
function fetchCommitsByDate(ignore2, lastDate) {
|
|
3452
|
+
const output = execSync16(
|
|
3453
|
+
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
3454
|
+
{ encoding: "utf-8" }
|
|
3455
|
+
);
|
|
3456
|
+
return parseGitLogCommits(output, ignore2, lastDate);
|
|
3457
|
+
}
|
|
3458
|
+
function printVersionInfo(config, lastInfo, firstHash) {
|
|
3459
|
+
const versions = computeVersions(lastInfo);
|
|
3460
|
+
displayVersion(
|
|
3461
|
+
!!config.commit?.conventional,
|
|
3462
|
+
firstHash,
|
|
3463
|
+
versions.patch,
|
|
3464
|
+
versions.minor
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
function resolveIgnoreList(options2, config) {
|
|
3468
|
+
return options2.ignore ?? config.devlog?.ignore ?? [];
|
|
3469
|
+
}
|
|
3470
|
+
function resolveSkipDays(config) {
|
|
3471
|
+
return new Set(config.devlog?.skip?.days ?? []);
|
|
3472
|
+
}
|
|
3473
|
+
function getLastDate(lastInfo) {
|
|
3474
|
+
return lastInfo?.date ?? null;
|
|
3475
|
+
}
|
|
3476
|
+
function getCommitsForDate(commitsByDate, date) {
|
|
3477
|
+
return commitsByDate.get(date) ?? [];
|
|
3478
|
+
}
|
|
3479
|
+
function noCommitsMessage(hasLastInfo) {
|
|
3480
|
+
return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
|
|
3481
|
+
}
|
|
3482
|
+
function logName(repoName) {
|
|
3483
|
+
console.log(`${chalk40.bold("name:")} ${repoName}`);
|
|
3484
|
+
}
|
|
3485
|
+
function displayNextEntry(ctx, targetDate, commits) {
|
|
3486
|
+
logName(ctx.repoName);
|
|
3487
|
+
printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
|
|
3488
|
+
console.log(chalk40.bold.blue(targetDate));
|
|
3489
|
+
printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
|
|
3490
|
+
}
|
|
3491
|
+
function logNoCommits(lastInfo) {
|
|
3492
|
+
console.log(chalk40.dim(noCommitsMessage(!!lastInfo)));
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// src/commands/devlog/next/index.ts
|
|
3496
|
+
function resolveContextData(config, options2) {
|
|
3497
|
+
const repoName = getRepoName();
|
|
3498
|
+
const lastInfo = getLastVersionInfo(repoName, config);
|
|
3499
|
+
return { repoName, lastInfo, ignore: resolveIgnoreList(options2, config) };
|
|
3500
|
+
}
|
|
3501
|
+
function buildContext(options2) {
|
|
3502
|
+
const config = loadConfig();
|
|
3503
|
+
const data = resolveContextData(config, options2);
|
|
3504
|
+
return { config, ...data, verbose: options2.verbose ?? false };
|
|
3505
|
+
}
|
|
3506
|
+
function fetchNextCommits(ctx) {
|
|
3507
|
+
const commitsByDate = fetchCommitsByDate(
|
|
3508
|
+
ctx.ignore,
|
|
3509
|
+
getLastDate(ctx.lastInfo)
|
|
3510
|
+
);
|
|
3511
|
+
const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
|
|
3512
|
+
return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
|
|
3513
|
+
}
|
|
3514
|
+
function showResult(ctx, found) {
|
|
3515
|
+
if (!found) {
|
|
3516
|
+
logNoCommits(ctx.lastInfo);
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
displayNextEntry(ctx, found.targetDate, found.commits);
|
|
3520
|
+
}
|
|
3521
|
+
function next(options2) {
|
|
3522
|
+
const ctx = buildContext(options2);
|
|
3523
|
+
showResult(ctx, fetchNextCommits(ctx));
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
// src/commands/devlog/skip.ts
|
|
3527
|
+
import chalk41 from "chalk";
|
|
3528
|
+
function skip(date) {
|
|
3529
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
3530
|
+
console.log(chalk41.red("Invalid date format. Use YYYY-MM-DD"));
|
|
3531
|
+
process.exit(1);
|
|
3532
|
+
}
|
|
3533
|
+
const config = loadProjectConfig();
|
|
3534
|
+
const devlog = config.devlog ?? {};
|
|
3535
|
+
const skip2 = devlog.skip ?? {};
|
|
3536
|
+
const skipDays = skip2.days ?? [];
|
|
3537
|
+
if (skipDays.includes(date)) {
|
|
3538
|
+
console.log(chalk41.yellow(`${date} is already in skip list`));
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
skipDays.push(date);
|
|
3542
|
+
skipDays.sort();
|
|
3543
|
+
skip2.days = skipDays;
|
|
3544
|
+
devlog.skip = skip2;
|
|
3545
|
+
config.devlog = devlog;
|
|
3546
|
+
saveConfig(config);
|
|
3547
|
+
console.log(chalk41.green(`Added ${date} to skip list`));
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
// src/commands/devlog/version.ts
|
|
3551
|
+
import chalk42 from "chalk";
|
|
3552
|
+
function version() {
|
|
3553
|
+
const config = loadConfig();
|
|
3554
|
+
const name = getRepoName();
|
|
3555
|
+
const lastInfo = getLastVersionInfo(name, config);
|
|
3556
|
+
const lastVersion = lastInfo?.version ?? null;
|
|
3557
|
+
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
3558
|
+
console.log(`${chalk42.bold("name:")} ${name}`);
|
|
3559
|
+
console.log(`${chalk42.bold("last:")} ${lastVersion ?? chalk42.dim("none")}`);
|
|
3560
|
+
console.log(`${chalk42.bold("next:")} ${nextVersion ?? chalk42.dim("none")}`);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// src/commands/registerDevlog.ts
|
|
3564
|
+
function registerDevlog(program2) {
|
|
3565
|
+
const devlogCommand = program2.command("devlog").description("Development log utilities");
|
|
3566
|
+
devlogCommand.command("list").description("Group git commits by date").option(
|
|
3567
|
+
"--days <number>",
|
|
3568
|
+
"Number of days to show (default: 30)",
|
|
3569
|
+
Number.parseInt
|
|
3570
|
+
).option("--since <date>", "Only show commits since this date (YYYY-MM-DD)").option("-r, --reverse", "Show earliest commits first").option("-v, --verbose", "Show file names for each commit").action(list3);
|
|
3571
|
+
devlogCommand.command("version").description("Show current repo name and version info").action(version);
|
|
3572
|
+
devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
|
|
3573
|
+
devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
|
|
3574
|
+
}
|
|
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
|
+
}
|
|
3675
3619
|
}
|
|
3676
3620
|
}
|
|
3677
3621
|
|
|
3678
|
-
// src/commands/
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
return {
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
function
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
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 " ? ";
|
|
3695
3833
|
}
|
|
3696
|
-
function
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
!!config.commit?.conventional,
|
|
3700
|
-
firstHash,
|
|
3701
|
-
versions.patch,
|
|
3702
|
-
versions.minor
|
|
3834
|
+
function formatHuman(cli, commands) {
|
|
3835
|
+
const sorted = [...commands].sort(
|
|
3836
|
+
(a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
|
|
3703
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");
|
|
3704
3847
|
}
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
function logName(repoName) {
|
|
3721
|
-
console.log(`${chalk41.bold("name:")} ${repoName}`);
|
|
3722
|
-
}
|
|
3723
|
-
function displayNextEntry(ctx, targetDate, commits) {
|
|
3724
|
-
logName(ctx.repoName);
|
|
3725
|
-
printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
|
|
3726
|
-
console.log(chalk41.bold.blue(targetDate));
|
|
3727
|
-
printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
|
|
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;
|
|
3728
3863
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
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);
|
|
3731
3873
|
}
|
|
3732
3874
|
|
|
3733
|
-
// src/commands/
|
|
3734
|
-
function
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
return { repoName, lastInfo, ignore: resolveIgnoreList(options2, config) };
|
|
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`);
|
|
3738
3879
|
}
|
|
3739
|
-
function
|
|
3740
|
-
const
|
|
3741
|
-
|
|
3742
|
-
return
|
|
3880
|
+
function readCache(cli) {
|
|
3881
|
+
const path31 = logPath(cli);
|
|
3882
|
+
if (!existsSync18(path31)) return void 0;
|
|
3883
|
+
return readFileSync16(path31, "utf-8");
|
|
3743
3884
|
}
|
|
3744
|
-
function
|
|
3745
|
-
const
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
);
|
|
3749
|
-
const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
|
|
3750
|
-
return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
|
|
3885
|
+
function writeCache(cli, output) {
|
|
3886
|
+
const dir = join12(homedir4(), ".assist");
|
|
3887
|
+
mkdirSync4(dir, { recursive: true });
|
|
3888
|
+
writeFileSync14(logPath(cli), output);
|
|
3751
3889
|
}
|
|
3752
|
-
function
|
|
3753
|
-
if (!
|
|
3754
|
-
|
|
3755
|
-
|
|
3890
|
+
async function permitCliReads(cli, options2 = { noCache: false }) {
|
|
3891
|
+
if (!cli) {
|
|
3892
|
+
console.error("Usage: assist permit-cli-reads <cli>");
|
|
3893
|
+
process.exit(1);
|
|
3756
3894
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
}
|
|
3763
|
-
|
|
3764
|
-
// src/commands/devlog/skip.ts
|
|
3765
|
-
import chalk42 from "chalk";
|
|
3766
|
-
function skip(date) {
|
|
3767
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
3768
|
-
console.log(chalk42.red("Invalid date format. Use YYYY-MM-DD"));
|
|
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
|
+
);
|
|
3769
3900
|
process.exit(1);
|
|
3770
3901
|
}
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
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
|
+
}
|
|
3778
3909
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
console.log(chalk42.green(`Added ${date} to skip list`));
|
|
3786
|
-
}
|
|
3787
|
-
|
|
3788
|
-
// src/commands/devlog/version.ts
|
|
3789
|
-
import chalk43 from "chalk";
|
|
3790
|
-
function version() {
|
|
3791
|
-
const config = loadConfig();
|
|
3792
|
-
const name = getRepoName();
|
|
3793
|
-
const lastInfo = getLastVersionInfo(name, config);
|
|
3794
|
-
const lastVersion = lastInfo?.version ?? null;
|
|
3795
|
-
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
3796
|
-
console.log(`${chalk43.bold("name:")} ${name}`);
|
|
3797
|
-
console.log(`${chalk43.bold("last:")} ${lastVersion ?? chalk43.dim("none")}`);
|
|
3798
|
-
console.log(`${chalk43.bold("next:")} ${nextVersion ?? chalk43.dim("none")}`);
|
|
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);
|
|
3799
3916
|
}
|
|
3800
3917
|
|
|
3801
|
-
// src/commands/
|
|
3802
|
-
function
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
"
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
)
|
|
3809
|
-
devlogCommand.command("version").description("Show current repo name and version info").action(version);
|
|
3810
|
-
devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
|
|
3811
|
-
devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
|
|
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
|
+
});
|
|
3812
3926
|
}
|
|
3813
3927
|
|
|
3814
3928
|
// src/commands/prs/comment.ts
|
|
3815
3929
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3816
3930
|
import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
|
|
3817
3931
|
import { tmpdir as tmpdir2 } from "os";
|
|
3818
|
-
import { join as
|
|
3932
|
+
import { join as join13 } from "path";
|
|
3819
3933
|
|
|
3820
3934
|
// src/commands/prs/shared.ts
|
|
3821
|
-
import { execSync as
|
|
3935
|
+
import { execSync as execSync19 } from "child_process";
|
|
3822
3936
|
function isGhNotInstalled(error) {
|
|
3823
3937
|
if (error instanceof Error) {
|
|
3824
3938
|
const msg = error.message.toLowerCase();
|
|
@@ -3834,14 +3948,14 @@ function isNotFound(error) {
|
|
|
3834
3948
|
}
|
|
3835
3949
|
function getRepoInfo() {
|
|
3836
3950
|
const repoInfo = JSON.parse(
|
|
3837
|
-
|
|
3951
|
+
execSync19("gh repo view --json owner,name", { encoding: "utf-8" })
|
|
3838
3952
|
);
|
|
3839
3953
|
return { org: repoInfo.owner.login, repo: repoInfo.name };
|
|
3840
3954
|
}
|
|
3841
3955
|
function getCurrentPrNumber() {
|
|
3842
3956
|
try {
|
|
3843
3957
|
const prInfo = JSON.parse(
|
|
3844
|
-
|
|
3958
|
+
execSync19("gh pr view --json number", { encoding: "utf-8" })
|
|
3845
3959
|
);
|
|
3846
3960
|
return prInfo.number;
|
|
3847
3961
|
} catch (error) {
|
|
@@ -3855,7 +3969,7 @@ function getCurrentPrNumber() {
|
|
|
3855
3969
|
function getCurrentPrNodeId() {
|
|
3856
3970
|
try {
|
|
3857
3971
|
const prInfo = JSON.parse(
|
|
3858
|
-
|
|
3972
|
+
execSync19("gh pr view --json id", { encoding: "utf-8" })
|
|
3859
3973
|
);
|
|
3860
3974
|
return prInfo.id;
|
|
3861
3975
|
} catch (error) {
|
|
@@ -3887,7 +4001,7 @@ function comment(path31, line, body) {
|
|
|
3887
4001
|
validateLine(line);
|
|
3888
4002
|
try {
|
|
3889
4003
|
const prId = getCurrentPrNodeId();
|
|
3890
|
-
const queryFile =
|
|
4004
|
+
const queryFile = join13(tmpdir2(), `gh-query-${Date.now()}.graphql`);
|
|
3891
4005
|
writeFileSync15(queryFile, MUTATION);
|
|
3892
4006
|
try {
|
|
3893
4007
|
const result = spawnSync2(
|
|
@@ -3926,32 +4040,32 @@ function comment(path31, line, body) {
|
|
|
3926
4040
|
}
|
|
3927
4041
|
|
|
3928
4042
|
// src/commands/prs/fixed.ts
|
|
3929
|
-
import { execSync as
|
|
4043
|
+
import { execSync as execSync21 } from "child_process";
|
|
3930
4044
|
|
|
3931
4045
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3932
|
-
import { execSync as
|
|
4046
|
+
import { execSync as execSync20 } from "child_process";
|
|
3933
4047
|
import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
3934
4048
|
import { tmpdir as tmpdir3 } from "os";
|
|
3935
|
-
import { join as
|
|
4049
|
+
import { join as join15 } from "path";
|
|
3936
4050
|
|
|
3937
4051
|
// src/commands/prs/loadCommentsCache.ts
|
|
3938
|
-
import { existsSync as
|
|
3939
|
-
import { join as
|
|
4052
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17, unlinkSync as unlinkSync4 } from "fs";
|
|
4053
|
+
import { join as join14 } from "path";
|
|
3940
4054
|
import { parse } from "yaml";
|
|
3941
4055
|
function getCachePath(prNumber) {
|
|
3942
|
-
return
|
|
4056
|
+
return join14(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
|
|
3943
4057
|
}
|
|
3944
4058
|
function loadCommentsCache(prNumber) {
|
|
3945
4059
|
const cachePath = getCachePath(prNumber);
|
|
3946
|
-
if (!
|
|
4060
|
+
if (!existsSync19(cachePath)) {
|
|
3947
4061
|
return null;
|
|
3948
4062
|
}
|
|
3949
|
-
const content =
|
|
4063
|
+
const content = readFileSync17(cachePath, "utf-8");
|
|
3950
4064
|
return parse(content);
|
|
3951
4065
|
}
|
|
3952
4066
|
function deleteCommentsCache(prNumber) {
|
|
3953
4067
|
const cachePath = getCachePath(prNumber);
|
|
3954
|
-
if (
|
|
4068
|
+
if (existsSync19(cachePath)) {
|
|
3955
4069
|
unlinkSync4(cachePath);
|
|
3956
4070
|
console.log("No more unresolved line comments. Cache dropped.");
|
|
3957
4071
|
}
|
|
@@ -3959,17 +4073,17 @@ function deleteCommentsCache(prNumber) {
|
|
|
3959
4073
|
|
|
3960
4074
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3961
4075
|
function replyToComment(org, repo, prNumber, commentId, message) {
|
|
3962
|
-
|
|
4076
|
+
execSync20(
|
|
3963
4077
|
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
|
|
3964
4078
|
{ stdio: "inherit" }
|
|
3965
4079
|
);
|
|
3966
4080
|
}
|
|
3967
4081
|
function resolveThread(threadId) {
|
|
3968
4082
|
const mutation = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }`;
|
|
3969
|
-
const queryFile =
|
|
4083
|
+
const queryFile = join15(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
|
|
3970
4084
|
writeFileSync16(queryFile, mutation);
|
|
3971
4085
|
try {
|
|
3972
|
-
|
|
4086
|
+
execSync20(
|
|
3973
4087
|
`gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
|
|
3974
4088
|
{ stdio: "inherit" }
|
|
3975
4089
|
);
|
|
@@ -4021,7 +4135,7 @@ function resolveCommentWithReply(commentId, message) {
|
|
|
4021
4135
|
// src/commands/prs/fixed.ts
|
|
4022
4136
|
function verifySha(sha) {
|
|
4023
4137
|
try {
|
|
4024
|
-
return
|
|
4138
|
+
return execSync21(`git rev-parse --verify ${sha}`, {
|
|
4025
4139
|
encoding: "utf-8"
|
|
4026
4140
|
}).trim();
|
|
4027
4141
|
} catch {
|
|
@@ -4047,21 +4161,21 @@ function fixed(commentId, sha) {
|
|
|
4047
4161
|
}
|
|
4048
4162
|
|
|
4049
4163
|
// src/commands/prs/listComments/index.ts
|
|
4050
|
-
import { existsSync as
|
|
4051
|
-
import { join as
|
|
4164
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
|
|
4165
|
+
import { join as join17 } from "path";
|
|
4052
4166
|
import { stringify } from "yaml";
|
|
4053
4167
|
|
|
4054
4168
|
// src/commands/prs/fetchThreadIds.ts
|
|
4055
|
-
import { execSync as
|
|
4169
|
+
import { execSync as execSync22 } from "child_process";
|
|
4056
4170
|
import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
|
|
4057
4171
|
import { tmpdir as tmpdir4 } from "os";
|
|
4058
|
-
import { join as
|
|
4172
|
+
import { join as join16 } from "path";
|
|
4059
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 } } } } } } }`;
|
|
4060
4174
|
function fetchThreadIds(org, repo, prNumber) {
|
|
4061
|
-
const queryFile =
|
|
4175
|
+
const queryFile = join16(tmpdir4(), `gh-query-${Date.now()}.graphql`);
|
|
4062
4176
|
writeFileSync17(queryFile, THREAD_QUERY);
|
|
4063
4177
|
try {
|
|
4064
|
-
const result =
|
|
4178
|
+
const result = execSync22(
|
|
4065
4179
|
`gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
|
|
4066
4180
|
{ encoding: "utf-8" }
|
|
4067
4181
|
);
|
|
@@ -4083,9 +4197,9 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
4083
4197
|
}
|
|
4084
4198
|
|
|
4085
4199
|
// src/commands/prs/listComments/fetchReviewComments.ts
|
|
4086
|
-
import { execSync as
|
|
4200
|
+
import { execSync as execSync23 } from "child_process";
|
|
4087
4201
|
function fetchJson(endpoint) {
|
|
4088
|
-
const result =
|
|
4202
|
+
const result = execSync23(`gh api --paginate ${endpoint}`, {
|
|
4089
4203
|
encoding: "utf-8"
|
|
4090
4204
|
});
|
|
4091
4205
|
if (!result.trim()) return [];
|
|
@@ -4163,8 +4277,8 @@ function printComments(comments) {
|
|
|
4163
4277
|
}
|
|
4164
4278
|
}
|
|
4165
4279
|
function writeCommentsCache(prNumber, comments) {
|
|
4166
|
-
const assistDir =
|
|
4167
|
-
if (!
|
|
4280
|
+
const assistDir = join17(process.cwd(), ".assist");
|
|
4281
|
+
if (!existsSync20(assistDir)) {
|
|
4168
4282
|
mkdirSync5(assistDir, { recursive: true });
|
|
4169
4283
|
}
|
|
4170
4284
|
const cacheData = {
|
|
@@ -4172,7 +4286,7 @@ function writeCommentsCache(prNumber, comments) {
|
|
|
4172
4286
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4173
4287
|
comments
|
|
4174
4288
|
};
|
|
4175
|
-
const cachePath =
|
|
4289
|
+
const cachePath = join17(assistDir, `pr-${prNumber}-comments.yaml`);
|
|
4176
4290
|
writeFileSync18(cachePath, stringify(cacheData));
|
|
4177
4291
|
}
|
|
4178
4292
|
function handleKnownErrors(error) {
|
|
@@ -4213,7 +4327,7 @@ async function listComments() {
|
|
|
4213
4327
|
}
|
|
4214
4328
|
|
|
4215
4329
|
// src/commands/prs/prs/index.ts
|
|
4216
|
-
import { execSync as
|
|
4330
|
+
import { execSync as execSync24 } from "child_process";
|
|
4217
4331
|
|
|
4218
4332
|
// src/commands/prs/prs/displayPaginated/index.ts
|
|
4219
4333
|
import enquirer5 from "enquirer";
|
|
@@ -4319,7 +4433,7 @@ async function displayPaginated(pullRequests) {
|
|
|
4319
4433
|
async function prs(options2) {
|
|
4320
4434
|
const state = options2.open ? "open" : options2.closed ? "closed" : "all";
|
|
4321
4435
|
try {
|
|
4322
|
-
const result =
|
|
4436
|
+
const result = execSync24(
|
|
4323
4437
|
`gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
|
|
4324
4438
|
{ encoding: "utf-8" }
|
|
4325
4439
|
);
|
|
@@ -4342,7 +4456,7 @@ async function prs(options2) {
|
|
|
4342
4456
|
}
|
|
4343
4457
|
|
|
4344
4458
|
// src/commands/prs/wontfix.ts
|
|
4345
|
-
import { execSync as
|
|
4459
|
+
import { execSync as execSync25 } from "child_process";
|
|
4346
4460
|
function validateReason(reason) {
|
|
4347
4461
|
const lowerReason = reason.toLowerCase();
|
|
4348
4462
|
if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
|
|
@@ -4359,7 +4473,7 @@ function validateShaReferences(reason) {
|
|
|
4359
4473
|
const invalidShas = [];
|
|
4360
4474
|
for (const sha of shas) {
|
|
4361
4475
|
try {
|
|
4362
|
-
|
|
4476
|
+
execSync25(`git cat-file -t ${sha}`, { stdio: "pipe" });
|
|
4363
4477
|
} catch {
|
|
4364
4478
|
invalidShas.push(sha);
|
|
4365
4479
|
}
|
|
@@ -4463,7 +4577,7 @@ Refactor check failed:
|
|
|
4463
4577
|
}
|
|
4464
4578
|
|
|
4465
4579
|
// src/commands/refactor/check/getViolations/index.ts
|
|
4466
|
-
import { execSync as
|
|
4580
|
+
import { execSync as execSync26 } from "child_process";
|
|
4467
4581
|
import fs15 from "fs";
|
|
4468
4582
|
import { minimatch as minimatch4 } from "minimatch";
|
|
4469
4583
|
|
|
@@ -4513,7 +4627,7 @@ function getGitFiles(options2) {
|
|
|
4513
4627
|
}
|
|
4514
4628
|
const files = /* @__PURE__ */ new Set();
|
|
4515
4629
|
if (options2.staged || options2.modified) {
|
|
4516
|
-
const staged =
|
|
4630
|
+
const staged = execSync26("git diff --cached --name-only", {
|
|
4517
4631
|
encoding: "utf-8"
|
|
4518
4632
|
});
|
|
4519
4633
|
for (const file of staged.trim().split("\n").filter(Boolean)) {
|
|
@@ -4521,7 +4635,7 @@ function getGitFiles(options2) {
|
|
|
4521
4635
|
}
|
|
4522
4636
|
}
|
|
4523
4637
|
if (options2.unstaged || options2.modified) {
|
|
4524
|
-
const unstaged =
|
|
4638
|
+
const unstaged = execSync26("git diff --name-only", { encoding: "utf-8" });
|
|
4525
4639
|
for (const file of unstaged.trim().split("\n").filter(Boolean)) {
|
|
4526
4640
|
files.add(file);
|
|
4527
4641
|
}
|
|
@@ -4551,7 +4665,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
|
|
|
4551
4665
|
|
|
4552
4666
|
// src/commands/refactor/check/index.ts
|
|
4553
4667
|
function runScript(script, cwd) {
|
|
4554
|
-
return new Promise((
|
|
4668
|
+
return new Promise((resolve5) => {
|
|
4555
4669
|
const child = spawn3("npm", ["run", script], {
|
|
4556
4670
|
stdio: "pipe",
|
|
4557
4671
|
shell: true,
|
|
@@ -4565,7 +4679,7 @@ function runScript(script, cwd) {
|
|
|
4565
4679
|
output += data.toString();
|
|
4566
4680
|
});
|
|
4567
4681
|
child.on("close", (code) => {
|
|
4568
|
-
|
|
4682
|
+
resolve5({ script, code: code ?? 1, output });
|
|
4569
4683
|
});
|
|
4570
4684
|
});
|
|
4571
4685
|
}
|
|
@@ -5137,8 +5251,8 @@ function registerRefactor(program2) {
|
|
|
5137
5251
|
}
|
|
5138
5252
|
|
|
5139
5253
|
// src/commands/transcript/shared.ts
|
|
5140
|
-
import { existsSync as
|
|
5141
|
-
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";
|
|
5142
5256
|
import * as readline2 from "readline";
|
|
5143
5257
|
var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
|
|
5144
5258
|
function getDatePrefix(daysOffset = 0) {
|
|
@@ -5153,10 +5267,10 @@ function isValidDatePrefix(filename) {
|
|
|
5153
5267
|
return DATE_PREFIX_REGEX.test(filename);
|
|
5154
5268
|
}
|
|
5155
5269
|
function collectFiles(dir, extension) {
|
|
5156
|
-
if (!
|
|
5270
|
+
if (!existsSync21(dir)) return [];
|
|
5157
5271
|
const results = [];
|
|
5158
5272
|
for (const entry of readdirSync2(dir)) {
|
|
5159
|
-
const fullPath =
|
|
5273
|
+
const fullPath = join18(dir, entry);
|
|
5160
5274
|
if (statSync(fullPath).isDirectory()) {
|
|
5161
5275
|
results.push(...collectFiles(fullPath, extension));
|
|
5162
5276
|
} else if (entry.endsWith(extension)) {
|
|
@@ -5188,9 +5302,9 @@ function createReadlineInterface() {
|
|
|
5188
5302
|
});
|
|
5189
5303
|
}
|
|
5190
5304
|
function askQuestion(rl, question) {
|
|
5191
|
-
return new Promise((
|
|
5305
|
+
return new Promise((resolve5) => {
|
|
5192
5306
|
rl.question(question, (answer) => {
|
|
5193
|
-
|
|
5307
|
+
resolve5(answer.trim());
|
|
5194
5308
|
});
|
|
5195
5309
|
});
|
|
5196
5310
|
}
|
|
@@ -5250,14 +5364,14 @@ async function configure() {
|
|
|
5250
5364
|
}
|
|
5251
5365
|
|
|
5252
5366
|
// src/commands/transcript/format/index.ts
|
|
5253
|
-
import { existsSync as
|
|
5367
|
+
import { existsSync as existsSync23 } from "fs";
|
|
5254
5368
|
|
|
5255
5369
|
// src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
|
|
5256
|
-
import { dirname as
|
|
5370
|
+
import { dirname as dirname15, join as join20 } from "path";
|
|
5257
5371
|
|
|
5258
5372
|
// src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
|
|
5259
5373
|
import { renameSync } from "fs";
|
|
5260
|
-
import { join as
|
|
5374
|
+
import { join as join19 } from "path";
|
|
5261
5375
|
async function resolveDate(rl, choice) {
|
|
5262
5376
|
if (choice === "1") return getDatePrefix(0);
|
|
5263
5377
|
if (choice === "2") return getDatePrefix(-1);
|
|
@@ -5272,7 +5386,7 @@ async function resolveDate(rl, choice) {
|
|
|
5272
5386
|
}
|
|
5273
5387
|
function renameWithPrefix(vttDir, vttFile, prefix2) {
|
|
5274
5388
|
const newFilename = `${prefix2}.${vttFile}`;
|
|
5275
|
-
renameSync(
|
|
5389
|
+
renameSync(join19(vttDir, vttFile), join19(vttDir, newFilename));
|
|
5276
5390
|
console.log(`Renamed to: ${newFilename}`);
|
|
5277
5391
|
return newFilename;
|
|
5278
5392
|
}
|
|
@@ -5303,15 +5417,15 @@ async function fixInvalidDatePrefixes(vttFiles) {
|
|
|
5303
5417
|
for (let i = 0; i < vttFiles.length; i++) {
|
|
5304
5418
|
const vttFile = vttFiles[i];
|
|
5305
5419
|
if (!isValidDatePrefix(vttFile.filename)) {
|
|
5306
|
-
const vttFileDir =
|
|
5420
|
+
const vttFileDir = dirname15(vttFile.absolutePath);
|
|
5307
5421
|
const newFilename = await promptForDateFix(vttFile.filename, vttFileDir);
|
|
5308
5422
|
if (newFilename) {
|
|
5309
|
-
const newRelativePath =
|
|
5310
|
-
|
|
5423
|
+
const newRelativePath = join20(
|
|
5424
|
+
dirname15(vttFile.relativePath),
|
|
5311
5425
|
newFilename
|
|
5312
5426
|
);
|
|
5313
5427
|
vttFiles[i] = {
|
|
5314
|
-
absolutePath:
|
|
5428
|
+
absolutePath: join20(vttFileDir, newFilename),
|
|
5315
5429
|
relativePath: newRelativePath,
|
|
5316
5430
|
filename: newFilename
|
|
5317
5431
|
};
|
|
@@ -5324,8 +5438,8 @@ async function fixInvalidDatePrefixes(vttFiles) {
|
|
|
5324
5438
|
}
|
|
5325
5439
|
|
|
5326
5440
|
// src/commands/transcript/format/processVttFile/index.ts
|
|
5327
|
-
import { existsSync as
|
|
5328
|
-
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";
|
|
5329
5443
|
|
|
5330
5444
|
// src/commands/transcript/cleanText.ts
|
|
5331
5445
|
function cleanText(text) {
|
|
@@ -5535,21 +5649,21 @@ function toMdFilename(vttFilename) {
|
|
|
5535
5649
|
return `${basename5(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
|
|
5536
5650
|
}
|
|
5537
5651
|
function resolveOutputDir(relativeDir, transcriptsDir) {
|
|
5538
|
-
return relativeDir === "." ? transcriptsDir :
|
|
5652
|
+
return relativeDir === "." ? transcriptsDir : join21(transcriptsDir, relativeDir);
|
|
5539
5653
|
}
|
|
5540
5654
|
function buildOutputPaths(vttFile, transcriptsDir) {
|
|
5541
5655
|
const mdFile = toMdFilename(vttFile.filename);
|
|
5542
|
-
const relativeDir =
|
|
5656
|
+
const relativeDir = dirname16(vttFile.relativePath);
|
|
5543
5657
|
const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
|
|
5544
|
-
const outputPath =
|
|
5658
|
+
const outputPath = join21(outputDir, mdFile);
|
|
5545
5659
|
return { outputDir, outputPath, mdFile, relativeDir };
|
|
5546
5660
|
}
|
|
5547
5661
|
function logSkipped(relativeDir, mdFile) {
|
|
5548
|
-
console.log(`Skipping (already exists): ${
|
|
5662
|
+
console.log(`Skipping (already exists): ${join21(relativeDir, mdFile)}`);
|
|
5549
5663
|
return "skipped";
|
|
5550
5664
|
}
|
|
5551
5665
|
function ensureDirectory(dir, label2) {
|
|
5552
|
-
if (!
|
|
5666
|
+
if (!existsSync22(dir)) {
|
|
5553
5667
|
mkdirSync6(dir, { recursive: true });
|
|
5554
5668
|
console.log(`Created ${label2}: ${dir}`);
|
|
5555
5669
|
}
|
|
@@ -5572,7 +5686,7 @@ function logReduction(cueCount, messageCount) {
|
|
|
5572
5686
|
}
|
|
5573
5687
|
function readAndParseCues(inputPath) {
|
|
5574
5688
|
console.log(`Reading: ${inputPath}`);
|
|
5575
|
-
return processCues(
|
|
5689
|
+
return processCues(readFileSync18(inputPath, "utf-8"));
|
|
5576
5690
|
}
|
|
5577
5691
|
function writeFormatted(outputPath, content) {
|
|
5578
5692
|
writeFileSync19(outputPath, content, "utf-8");
|
|
@@ -5585,7 +5699,7 @@ function convertVttToMarkdown(inputPath, outputPath) {
|
|
|
5585
5699
|
logReduction(cues.length, chatMessages.length);
|
|
5586
5700
|
}
|
|
5587
5701
|
function tryProcessVtt(vttFile, paths) {
|
|
5588
|
-
if (
|
|
5702
|
+
if (existsSync22(paths.outputPath))
|
|
5589
5703
|
return logSkipped(paths.relativeDir, paths.mdFile);
|
|
5590
5704
|
convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
|
|
5591
5705
|
return "processed";
|
|
@@ -5611,7 +5725,7 @@ function processAllFiles(vttFiles, transcriptsDir) {
|
|
|
5611
5725
|
logSummary(counts);
|
|
5612
5726
|
}
|
|
5613
5727
|
function requireVttDir(vttDir) {
|
|
5614
|
-
if (!
|
|
5728
|
+
if (!existsSync23(vttDir)) {
|
|
5615
5729
|
console.error(`VTT directory not found: ${vttDir}`);
|
|
5616
5730
|
process.exit(1);
|
|
5617
5731
|
}
|
|
@@ -5643,18 +5757,18 @@ async function format() {
|
|
|
5643
5757
|
}
|
|
5644
5758
|
|
|
5645
5759
|
// src/commands/transcript/summarise/index.ts
|
|
5646
|
-
import { existsSync as
|
|
5647
|
-
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";
|
|
5648
5762
|
|
|
5649
5763
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
5650
5764
|
import {
|
|
5651
|
-
existsSync as
|
|
5765
|
+
existsSync as existsSync24,
|
|
5652
5766
|
mkdirSync as mkdirSync7,
|
|
5653
|
-
readFileSync as
|
|
5767
|
+
readFileSync as readFileSync19,
|
|
5654
5768
|
renameSync as renameSync2,
|
|
5655
5769
|
rmSync
|
|
5656
5770
|
} from "fs";
|
|
5657
|
-
import { dirname as
|
|
5771
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
5658
5772
|
|
|
5659
5773
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
5660
5774
|
import chalk51 from "chalk";
|
|
@@ -5683,9 +5797,9 @@ function validateStagedContent(filename, content) {
|
|
|
5683
5797
|
}
|
|
5684
5798
|
|
|
5685
5799
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
5686
|
-
var STAGING_DIR =
|
|
5800
|
+
var STAGING_DIR = join22(process.cwd(), ".assist", "transcript");
|
|
5687
5801
|
function processStagedFile() {
|
|
5688
|
-
if (!
|
|
5802
|
+
if (!existsSync24(STAGING_DIR)) {
|
|
5689
5803
|
return false;
|
|
5690
5804
|
}
|
|
5691
5805
|
const stagedFiles = findMdFilesRecursive(STAGING_DIR);
|
|
@@ -5694,7 +5808,7 @@ function processStagedFile() {
|
|
|
5694
5808
|
}
|
|
5695
5809
|
const { transcriptsDir, summaryDir } = getTranscriptConfig();
|
|
5696
5810
|
const stagedFile = stagedFiles[0];
|
|
5697
|
-
const content =
|
|
5811
|
+
const content = readFileSync19(stagedFile.absolutePath, "utf-8");
|
|
5698
5812
|
validateStagedContent(stagedFile.filename, content);
|
|
5699
5813
|
const stagedBaseName = getTranscriptBaseName(stagedFile.filename);
|
|
5700
5814
|
const transcriptFiles = findMdFilesRecursive(transcriptsDir);
|
|
@@ -5707,9 +5821,9 @@ function processStagedFile() {
|
|
|
5707
5821
|
);
|
|
5708
5822
|
process.exit(1);
|
|
5709
5823
|
}
|
|
5710
|
-
const destPath =
|
|
5711
|
-
const destDir =
|
|
5712
|
-
if (!
|
|
5824
|
+
const destPath = join22(summaryDir, matchingTranscript.relativePath);
|
|
5825
|
+
const destDir = dirname17(destPath);
|
|
5826
|
+
if (!existsSync24(destDir)) {
|
|
5713
5827
|
mkdirSync7(destDir, { recursive: true });
|
|
5714
5828
|
}
|
|
5715
5829
|
renameSync2(stagedFile.absolutePath, destPath);
|
|
@@ -5722,8 +5836,8 @@ function processStagedFile() {
|
|
|
5722
5836
|
|
|
5723
5837
|
// src/commands/transcript/summarise/index.ts
|
|
5724
5838
|
function buildRelativeKey(relativePath, baseName) {
|
|
5725
|
-
const relDir =
|
|
5726
|
-
return relDir === "." ? baseName :
|
|
5839
|
+
const relDir = dirname18(relativePath);
|
|
5840
|
+
return relDir === "." ? baseName : join23(relDir, baseName);
|
|
5727
5841
|
}
|
|
5728
5842
|
function buildSummaryIndex(summaryDir) {
|
|
5729
5843
|
const summaryFiles = findMdFilesRecursive(summaryDir);
|
|
@@ -5736,7 +5850,7 @@ function buildSummaryIndex(summaryDir) {
|
|
|
5736
5850
|
function summarise() {
|
|
5737
5851
|
processStagedFile();
|
|
5738
5852
|
const { transcriptsDir, summaryDir } = getTranscriptConfig();
|
|
5739
|
-
if (!
|
|
5853
|
+
if (!existsSync25(transcriptsDir)) {
|
|
5740
5854
|
console.log("No transcripts directory found.");
|
|
5741
5855
|
return;
|
|
5742
5856
|
}
|
|
@@ -5757,8 +5871,8 @@ function summarise() {
|
|
|
5757
5871
|
}
|
|
5758
5872
|
const next2 = missing[0];
|
|
5759
5873
|
const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
|
|
5760
|
-
const outputPath =
|
|
5761
|
-
const summaryFileDir =
|
|
5874
|
+
const outputPath = join23(STAGING_DIR, outputFilename);
|
|
5875
|
+
const summaryFileDir = join23(summaryDir, dirname18(next2.relativePath));
|
|
5762
5876
|
const relativeTranscriptPath = encodeURI(
|
|
5763
5877
|
relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
|
|
5764
5878
|
);
|
|
@@ -5804,50 +5918,50 @@ function registerVerify(program2) {
|
|
|
5804
5918
|
|
|
5805
5919
|
// src/commands/voice/devices.ts
|
|
5806
5920
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5807
|
-
import { join as
|
|
5921
|
+
import { join as join25 } from "path";
|
|
5808
5922
|
|
|
5809
5923
|
// src/commands/voice/shared.ts
|
|
5810
5924
|
import { homedir as homedir5 } from "os";
|
|
5811
|
-
import { dirname as
|
|
5812
|
-
import { fileURLToPath as
|
|
5813
|
-
var
|
|
5814
|
-
var VOICE_DIR =
|
|
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");
|
|
5815
5929
|
var voicePaths = {
|
|
5816
5930
|
dir: VOICE_DIR,
|
|
5817
|
-
pid:
|
|
5818
|
-
log:
|
|
5819
|
-
venv:
|
|
5820
|
-
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")
|
|
5821
5935
|
};
|
|
5822
5936
|
function getPythonDir() {
|
|
5823
|
-
return
|
|
5937
|
+
return join24(__dirname7, "commands", "voice", "python");
|
|
5824
5938
|
}
|
|
5825
5939
|
function getVenvPython() {
|
|
5826
|
-
return process.platform === "win32" ?
|
|
5940
|
+
return process.platform === "win32" ? join24(voicePaths.venv, "Scripts", "python.exe") : join24(voicePaths.venv, "bin", "python");
|
|
5827
5941
|
}
|
|
5828
5942
|
function getLockDir() {
|
|
5829
5943
|
const config = loadConfig();
|
|
5830
5944
|
return config.voice?.lockDir ?? VOICE_DIR;
|
|
5831
5945
|
}
|
|
5832
5946
|
function getLockFile() {
|
|
5833
|
-
return
|
|
5947
|
+
return join24(getLockDir(), "voice.lock");
|
|
5834
5948
|
}
|
|
5835
5949
|
|
|
5836
5950
|
// src/commands/voice/devices.ts
|
|
5837
5951
|
function devices() {
|
|
5838
|
-
const script =
|
|
5952
|
+
const script = join25(getPythonDir(), "list_devices.py");
|
|
5839
5953
|
spawnSync3(getVenvPython(), [script], { stdio: "inherit" });
|
|
5840
5954
|
}
|
|
5841
5955
|
|
|
5842
5956
|
// src/commands/voice/logs.ts
|
|
5843
|
-
import { existsSync as
|
|
5957
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
|
|
5844
5958
|
function logs(options2) {
|
|
5845
|
-
if (!
|
|
5959
|
+
if (!existsSync26(voicePaths.log)) {
|
|
5846
5960
|
console.log("No voice log file found");
|
|
5847
5961
|
return;
|
|
5848
5962
|
}
|
|
5849
5963
|
const count = Number.parseInt(options2.lines ?? "150", 10);
|
|
5850
|
-
const content =
|
|
5964
|
+
const content = readFileSync20(voicePaths.log, "utf-8").trim();
|
|
5851
5965
|
if (!content) {
|
|
5852
5966
|
console.log("Voice log is empty");
|
|
5853
5967
|
return;
|
|
@@ -5870,12 +5984,12 @@ function logs(options2) {
|
|
|
5870
5984
|
// src/commands/voice/setup.ts
|
|
5871
5985
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
5872
5986
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
5873
|
-
import { join as
|
|
5987
|
+
import { join as join27 } from "path";
|
|
5874
5988
|
|
|
5875
5989
|
// src/commands/voice/checkLockFile.ts
|
|
5876
|
-
import { execSync as
|
|
5877
|
-
import { existsSync as
|
|
5878
|
-
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";
|
|
5879
5993
|
function isProcessAlive(pid) {
|
|
5880
5994
|
try {
|
|
5881
5995
|
process.kill(pid, 0);
|
|
@@ -5886,9 +6000,9 @@ function isProcessAlive(pid) {
|
|
|
5886
6000
|
}
|
|
5887
6001
|
function checkLockFile() {
|
|
5888
6002
|
const lockFile = getLockFile();
|
|
5889
|
-
if (!
|
|
6003
|
+
if (!existsSync27(lockFile)) return;
|
|
5890
6004
|
try {
|
|
5891
|
-
const lock = JSON.parse(
|
|
6005
|
+
const lock = JSON.parse(readFileSync21(lockFile, "utf-8"));
|
|
5892
6006
|
if (lock.pid && isProcessAlive(lock.pid)) {
|
|
5893
6007
|
console.error(
|
|
5894
6008
|
`Voice daemon already running (PID ${lock.pid}, env: ${lock.env}). Stop it first with: assist voice stop`
|
|
@@ -5899,10 +6013,10 @@ function checkLockFile() {
|
|
|
5899
6013
|
}
|
|
5900
6014
|
}
|
|
5901
6015
|
function bootstrapVenv() {
|
|
5902
|
-
if (
|
|
6016
|
+
if (existsSync27(getVenvPython())) return;
|
|
5903
6017
|
console.log("Setting up Python environment...");
|
|
5904
6018
|
const pythonDir = getPythonDir();
|
|
5905
|
-
|
|
6019
|
+
execSync27(
|
|
5906
6020
|
`uv sync --project "${pythonDir}" --extra runtime --no-install-project`,
|
|
5907
6021
|
{
|
|
5908
6022
|
stdio: "inherit",
|
|
@@ -5912,7 +6026,7 @@ function bootstrapVenv() {
|
|
|
5912
6026
|
}
|
|
5913
6027
|
function writeLockFile(pid) {
|
|
5914
6028
|
const lockFile = getLockFile();
|
|
5915
|
-
mkdirSync8(
|
|
6029
|
+
mkdirSync8(join26(lockFile, ".."), { recursive: true });
|
|
5916
6030
|
writeFileSync20(
|
|
5917
6031
|
lockFile,
|
|
5918
6032
|
JSON.stringify({
|
|
@@ -5928,7 +6042,7 @@ function setup() {
|
|
|
5928
6042
|
mkdirSync9(voicePaths.dir, { recursive: true });
|
|
5929
6043
|
bootstrapVenv();
|
|
5930
6044
|
console.log("\nDownloading models...\n");
|
|
5931
|
-
const script =
|
|
6045
|
+
const script = join27(getPythonDir(), "setup_models.py");
|
|
5932
6046
|
const result = spawnSync4(getVenvPython(), [script], {
|
|
5933
6047
|
stdio: "inherit",
|
|
5934
6048
|
env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
|
|
@@ -5942,7 +6056,7 @@ function setup() {
|
|
|
5942
6056
|
// src/commands/voice/start.ts
|
|
5943
6057
|
import { spawn as spawn4 } from "child_process";
|
|
5944
6058
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync21 } from "fs";
|
|
5945
|
-
import { join as
|
|
6059
|
+
import { join as join28 } from "path";
|
|
5946
6060
|
|
|
5947
6061
|
// src/commands/voice/buildDaemonEnv.ts
|
|
5948
6062
|
function buildDaemonEnv(options2) {
|
|
@@ -5980,7 +6094,7 @@ function start2(options2) {
|
|
|
5980
6094
|
bootstrapVenv();
|
|
5981
6095
|
const debug = options2.debug || options2.foreground || process.platform === "win32";
|
|
5982
6096
|
const env = buildDaemonEnv({ debug });
|
|
5983
|
-
const script =
|
|
6097
|
+
const script = join28(getPythonDir(), "voice_daemon.py");
|
|
5984
6098
|
const python = getVenvPython();
|
|
5985
6099
|
if (options2.foreground) {
|
|
5986
6100
|
spawnForeground(python, script, env);
|
|
@@ -5990,7 +6104,7 @@ function start2(options2) {
|
|
|
5990
6104
|
}
|
|
5991
6105
|
|
|
5992
6106
|
// src/commands/voice/status.ts
|
|
5993
|
-
import { existsSync as
|
|
6107
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
|
|
5994
6108
|
function isProcessAlive2(pid) {
|
|
5995
6109
|
try {
|
|
5996
6110
|
process.kill(pid, 0);
|
|
@@ -6000,16 +6114,16 @@ function isProcessAlive2(pid) {
|
|
|
6000
6114
|
}
|
|
6001
6115
|
}
|
|
6002
6116
|
function readRecentLogs(count) {
|
|
6003
|
-
if (!
|
|
6004
|
-
const lines =
|
|
6117
|
+
if (!existsSync28(voicePaths.log)) return [];
|
|
6118
|
+
const lines = readFileSync22(voicePaths.log, "utf-8").trim().split("\n");
|
|
6005
6119
|
return lines.slice(-count);
|
|
6006
6120
|
}
|
|
6007
6121
|
function status() {
|
|
6008
|
-
if (!
|
|
6122
|
+
if (!existsSync28(voicePaths.pid)) {
|
|
6009
6123
|
console.log("Voice daemon: not running (no PID file)");
|
|
6010
6124
|
return;
|
|
6011
6125
|
}
|
|
6012
|
-
const pid = Number.parseInt(
|
|
6126
|
+
const pid = Number.parseInt(readFileSync22(voicePaths.pid, "utf-8").trim(), 10);
|
|
6013
6127
|
const alive = isProcessAlive2(pid);
|
|
6014
6128
|
console.log(`Voice daemon: ${alive ? "running" : "dead"} (PID ${pid})`);
|
|
6015
6129
|
const recent = readRecentLogs(5);
|
|
@@ -6028,13 +6142,13 @@ function status() {
|
|
|
6028
6142
|
}
|
|
6029
6143
|
|
|
6030
6144
|
// src/commands/voice/stop.ts
|
|
6031
|
-
import { existsSync as
|
|
6145
|
+
import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync7 } from "fs";
|
|
6032
6146
|
function stop() {
|
|
6033
|
-
if (!
|
|
6147
|
+
if (!existsSync29(voicePaths.pid)) {
|
|
6034
6148
|
console.log("Voice daemon is not running (no PID file)");
|
|
6035
6149
|
return;
|
|
6036
6150
|
}
|
|
6037
|
-
const pid = Number.parseInt(
|
|
6151
|
+
const pid = Number.parseInt(readFileSync23(voicePaths.pid, "utf-8").trim(), 10);
|
|
6038
6152
|
try {
|
|
6039
6153
|
process.kill(pid, "SIGTERM");
|
|
6040
6154
|
console.log(`Sent SIGTERM to voice daemon (PID ${pid})`);
|
|
@@ -6047,7 +6161,7 @@ function stop() {
|
|
|
6047
6161
|
}
|
|
6048
6162
|
try {
|
|
6049
6163
|
const lockFile = getLockFile();
|
|
6050
|
-
if (
|
|
6164
|
+
if (existsSync29(lockFile)) unlinkSync7(lockFile);
|
|
6051
6165
|
} catch {
|
|
6052
6166
|
}
|
|
6053
6167
|
console.log("Voice daemon stopped");
|
|
@@ -6069,11 +6183,11 @@ import { randomBytes } from "crypto";
|
|
|
6069
6183
|
import chalk52 from "chalk";
|
|
6070
6184
|
|
|
6071
6185
|
// src/lib/openBrowser.ts
|
|
6072
|
-
import { execSync as
|
|
6186
|
+
import { execSync as execSync28 } from "child_process";
|
|
6073
6187
|
function tryExec(commands) {
|
|
6074
6188
|
for (const cmd of commands) {
|
|
6075
6189
|
try {
|
|
6076
|
-
|
|
6190
|
+
execSync28(cmd);
|
|
6077
6191
|
return true;
|
|
6078
6192
|
} catch {
|
|
6079
6193
|
}
|
|
@@ -6131,7 +6245,7 @@ function extractCode(url, expectedState) {
|
|
|
6131
6245
|
return code;
|
|
6132
6246
|
}
|
|
6133
6247
|
function waitForCallback(port, expectedState) {
|
|
6134
|
-
return new Promise((
|
|
6248
|
+
return new Promise((resolve5, reject) => {
|
|
6135
6249
|
const timeout = setTimeout(() => {
|
|
6136
6250
|
server.close();
|
|
6137
6251
|
reject(new Error("Authorization timed out after 120 seconds"));
|
|
@@ -6148,7 +6262,7 @@ function waitForCallback(port, expectedState) {
|
|
|
6148
6262
|
const code = extractCode(url, expectedState);
|
|
6149
6263
|
respondHtml(res, 200, "Authorization successful!");
|
|
6150
6264
|
server.close();
|
|
6151
|
-
|
|
6265
|
+
resolve5(code);
|
|
6152
6266
|
} catch (err) {
|
|
6153
6267
|
respondHtml(res, 400, err.message);
|
|
6154
6268
|
server.close();
|
|
@@ -6278,7 +6392,7 @@ import { spawn as spawn5 } from "child_process";
|
|
|
6278
6392
|
|
|
6279
6393
|
// src/commands/run/add.ts
|
|
6280
6394
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync22 } from "fs";
|
|
6281
|
-
import { join as
|
|
6395
|
+
import { join as join29 } from "path";
|
|
6282
6396
|
function findAddIndex() {
|
|
6283
6397
|
const addIndex = process.argv.indexOf("add");
|
|
6284
6398
|
if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
|
|
@@ -6332,7 +6446,7 @@ function saveNewRunConfig(name, command, args) {
|
|
|
6332
6446
|
saveConfig(config);
|
|
6333
6447
|
}
|
|
6334
6448
|
function createCommandFile(name) {
|
|
6335
|
-
const dir =
|
|
6449
|
+
const dir = join29(".claude", "commands");
|
|
6336
6450
|
mkdirSync11(dir, { recursive: true });
|
|
6337
6451
|
const content = `---
|
|
6338
6452
|
description: Run ${name}
|
|
@@ -6340,7 +6454,7 @@ description: Run ${name}
|
|
|
6340
6454
|
|
|
6341
6455
|
Run \`assist run ${name} $ARGUMENTS 2>&1\`.
|
|
6342
6456
|
`;
|
|
6343
|
-
const filePath =
|
|
6457
|
+
const filePath = join29(dir, `${name}.md`);
|
|
6344
6458
|
writeFileSync22(filePath, content);
|
|
6345
6459
|
console.log(`Created command file: ${filePath}`);
|
|
6346
6460
|
}
|
|
@@ -6439,7 +6553,7 @@ async function statusLine() {
|
|
|
6439
6553
|
import * as fs23 from "fs";
|
|
6440
6554
|
import * as os from "os";
|
|
6441
6555
|
import * as path29 from "path";
|
|
6442
|
-
import { fileURLToPath as
|
|
6556
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
6443
6557
|
|
|
6444
6558
|
// src/commands/sync/syncClaudeMd.ts
|
|
6445
6559
|
import * as fs21 from "fs";
|
|
@@ -6479,11 +6593,15 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
6479
6593
|
const source = path28.join(claudeDir, "settings.json");
|
|
6480
6594
|
const target = path28.join(targetBase, "settings.json");
|
|
6481
6595
|
const sourceContent = fs22.readFileSync(source, "utf-8");
|
|
6482
|
-
const
|
|
6596
|
+
const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
|
|
6483
6597
|
if (fs22.existsSync(target)) {
|
|
6484
6598
|
const targetContent = fs22.readFileSync(target, "utf-8");
|
|
6485
|
-
const normalizedTarget = JSON.stringify(
|
|
6486
|
-
|
|
6599
|
+
const normalizedTarget = JSON.stringify(
|
|
6600
|
+
JSON.parse(targetContent),
|
|
6601
|
+
null,
|
|
6602
|
+
" "
|
|
6603
|
+
);
|
|
6604
|
+
if (mergedContent !== normalizedTarget) {
|
|
6487
6605
|
if (!options2?.yes) {
|
|
6488
6606
|
console.log(
|
|
6489
6607
|
chalk55.yellow(
|
|
@@ -6491,7 +6609,7 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
6491
6609
|
)
|
|
6492
6610
|
);
|
|
6493
6611
|
console.log();
|
|
6494
|
-
printDiff(targetContent,
|
|
6612
|
+
printDiff(targetContent, mergedContent);
|
|
6495
6613
|
const confirm = await promptConfirm(
|
|
6496
6614
|
chalk55.red("Overwrite existing settings.json?"),
|
|
6497
6615
|
false
|
|
@@ -6503,15 +6621,15 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
6503
6621
|
}
|
|
6504
6622
|
}
|
|
6505
6623
|
}
|
|
6506
|
-
fs22.
|
|
6624
|
+
fs22.writeFileSync(target, mergedContent);
|
|
6507
6625
|
console.log("Copied settings.json to ~/.claude/settings.json");
|
|
6508
6626
|
}
|
|
6509
6627
|
|
|
6510
6628
|
// src/commands/sync.ts
|
|
6511
|
-
var
|
|
6512
|
-
var
|
|
6629
|
+
var __filename4 = fileURLToPath7(import.meta.url);
|
|
6630
|
+
var __dirname8 = path29.dirname(__filename4);
|
|
6513
6631
|
async function sync(options2) {
|
|
6514
|
-
const claudeDir = path29.join(
|
|
6632
|
+
const claudeDir = path29.join(__dirname8, "..", "claude");
|
|
6515
6633
|
const targetBase = path29.join(os.homedir(), ".claude");
|
|
6516
6634
|
syncCommands(claudeDir, targetBase);
|
|
6517
6635
|
await syncSettings(claudeDir, targetBase, { yes: options2?.yes });
|
|
@@ -6530,28 +6648,11 @@ function syncCommands(claudeDir, targetBase) {
|
|
|
6530
6648
|
}
|
|
6531
6649
|
|
|
6532
6650
|
// src/commands/update.ts
|
|
6533
|
-
import { execSync as
|
|
6651
|
+
import { execSync as execSync29 } from "child_process";
|
|
6534
6652
|
import * as path30 from "path";
|
|
6535
|
-
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
6536
|
-
var __filename3 = fileURLToPath6(import.meta.url);
|
|
6537
|
-
var __dirname7 = path30.dirname(__filename3);
|
|
6538
|
-
function getInstallDir() {
|
|
6539
|
-
return path30.resolve(__dirname7, "..");
|
|
6540
|
-
}
|
|
6541
|
-
function isGitRepo(dir) {
|
|
6542
|
-
try {
|
|
6543
|
-
const result = execSync28("git rev-parse --show-toplevel", {
|
|
6544
|
-
cwd: dir,
|
|
6545
|
-
stdio: "pipe"
|
|
6546
|
-
}).toString().trim();
|
|
6547
|
-
return path30.resolve(result) === path30.resolve(dir);
|
|
6548
|
-
} catch {
|
|
6549
|
-
return false;
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
6653
|
function isGlobalNpmInstall(dir) {
|
|
6553
6654
|
try {
|
|
6554
|
-
const globalPrefix =
|
|
6655
|
+
const globalPrefix = execSync29("npm prefix -g", { stdio: "pipe" }).toString().trim();
|
|
6555
6656
|
return path30.resolve(dir).toLowerCase().startsWith(path30.resolve(globalPrefix).toLowerCase());
|
|
6556
6657
|
} catch {
|
|
6557
6658
|
return false;
|
|
@@ -6562,16 +6663,16 @@ async function update() {
|
|
|
6562
6663
|
console.log(`Assist is installed at: ${installDir}`);
|
|
6563
6664
|
if (isGitRepo(installDir)) {
|
|
6564
6665
|
console.log("Detected git repo installation, pulling latest...");
|
|
6565
|
-
|
|
6666
|
+
execSync29("git pull", { cwd: installDir, stdio: "inherit" });
|
|
6566
6667
|
console.log("Building...");
|
|
6567
|
-
|
|
6668
|
+
execSync29("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
6568
6669
|
console.log("Syncing commands...");
|
|
6569
|
-
|
|
6670
|
+
execSync29("assist sync", { stdio: "inherit" });
|
|
6570
6671
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
6571
6672
|
console.log("Detected global npm installation, updating...");
|
|
6572
|
-
|
|
6673
|
+
execSync29("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
6573
6674
|
console.log("Syncing commands...");
|
|
6574
|
-
|
|
6675
|
+
execSync29("assist sync", { stdio: "inherit" });
|
|
6575
6676
|
} else {
|
|
6576
6677
|
console.error(
|
|
6577
6678
|
"Could not determine installation method. Expected a git repo or global npm install."
|
|
@@ -6605,7 +6706,7 @@ program.command("notify").description(
|
|
|
6605
6706
|
"Show notification from Claude Code hook (reads JSON from stdin)"
|
|
6606
6707
|
).action(notify);
|
|
6607
6708
|
program.command("update").description("Update assist to the latest version and sync commands").action(update);
|
|
6608
|
-
|
|
6709
|
+
registerPermitCliReads(program);
|
|
6609
6710
|
registerCliHook(program);
|
|
6610
6711
|
registerPrs(program);
|
|
6611
6712
|
registerRoam(program);
|