@staff0rd/assist 0.10.1 → 0.13.3

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 CHANGED
@@ -1,44 +1,45 @@
1
- # assist
2
-
3
- A CLI tool for enforcing determinism in LLM development workflow automation.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- # Clone the repository
9
- git clone git@github.com:staff0rd/assist.git
10
- cd assist
11
-
12
- # Install dependencies
13
- npm install
14
-
15
- # Build the project
16
- npm run build
17
-
18
- # Install globally
19
- npm install -g .
20
- ```
21
-
22
- After installation, the `assist` command will be available globally.
23
-
24
- ## Commands
25
-
26
- - `assist init` - Initialize project with VS Code and verify configurations
27
- - `assist new` - Initialize a new Vite React TypeScript project
28
- - `assist sync` - Copy command files to `~/.claude/commands`
29
- - `assist commit <message>` - Create a git commit with validation
30
- - `assist update` - Update claude-code to the latest version
31
- - `assist verify` - Run all verify:* scripts from package.json in parallel
32
- - `assist verify init` - Add verify scripts to a project
33
- - `assist verify hardcoded-colors` - Check for hardcoded hex colors in src/
34
- - `assist lint` - Run lint checks for conventions not enforced by biomejs
35
- - `assist lint init` - Initialize Biome with standard linter config
36
- - `assist refactor check [pattern]` - Check for files that exceed the maximum line count
37
- - `assist refactor ignore <file>` - Add a file to the refactor ignore list
38
- - `assist devlog list` - Group git commits by date
39
- - `assist devlog next` - Show commits for the day after the last versioned entry
40
- - `assist devlog skip <date>` - Add a date to the skip list
41
- - `assist devlog version` - Show current repo name and version info
42
- - `assist vscode init` - Add VS Code configuration files
43
- - `assist deploy init` - Initialize Netlify project and configure deployment
44
-
1
+ # assist
2
+
3
+ A CLI tool for enforcing determinism in LLM development workflow automation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Clone the repository
9
+ git clone git@github.com:staff0rd/assist.git
10
+ cd assist
11
+
12
+ # Install dependencies
13
+ npm install
14
+
15
+ # Build the project
16
+ npm run build
17
+
18
+ # Install globally
19
+ npm install -g .
20
+ ```
21
+
22
+ After installation, the `assist` command will be available globally.
23
+
24
+ ## Commands
25
+
26
+ - `assist init` - Initialize project with VS Code and verify configurations
27
+ - `assist new` - Initialize a new Vite React TypeScript project
28
+ - `assist sync` - Copy command files to `~/.claude/commands`
29
+ - `assist commit <message>` - Create a git commit with validation
30
+ - `assist update` - Update claude-code to the latest version
31
+ - `assist verify` - Run all verify:* scripts from package.json in parallel
32
+ - `assist verify init` - Add verify scripts to a project
33
+ - `assist verify hardcoded-colors` - Check for hardcoded hex colors in src/
34
+ - `assist lint` - Run lint checks for conventions not enforced by biomejs
35
+ - `assist lint init` - Initialize Biome with standard linter config
36
+ - `assist refactor check [pattern]` - Check for files that exceed the maximum line count
37
+ - `assist refactor ignore <file>` - Add a file to the refactor ignore list
38
+ - `assist devlog list` - Group git commits by date
39
+ - `assist devlog next` - Show commits for the day after the last versioned entry
40
+ - `assist devlog skip <date>` - Add a date to the skip list
41
+ - `assist devlog version` - Show current repo name and version info
42
+ - `assist vscode init` - Add VS Code configuration files
43
+ - `assist deploy init` - Initialize Netlify project and configure deployment
44
+ - `assist status-line` - Format Claude Code status line from JSON stdin
45
+
@@ -1,25 +1,25 @@
1
- name: Build and Deploy to Netlify
2
- on:
3
- push:
4
- branches:
5
- - main
6
- jobs:
7
- build_and_deploy:
8
- runs-on: ubuntu-latest
9
- steps:
10
- - uses: actions/checkout@v3
11
- name: Checkout
12
-
13
- - uses: actions/setup-node@v4
14
- name: Setup Node.js
15
- with:
16
- node-version: "22"
17
-
18
- - run: npm ci
19
- name: Install dependencies
20
-
21
- - run: npm run build
22
- name: Build project
23
-
24
- - run: npx netlify-cli deploy --no-build --dir=dist --prod -s {{NETLIFY_SITE_ID}} --auth ${{ secrets.NETLIFY_AUTH_TOKEN }} --message "Deployed commit ${{ github.sha }}"
25
- name: Deploy to Netlify
1
+ name: Build and Deploy to Netlify
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ build_and_deploy:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v3
11
+ name: Checkout
12
+
13
+ - uses: actions/setup-node@v4
14
+ name: Setup Node.js
15
+ with:
16
+ node-version: "22"
17
+
18
+ - run: npm ci
19
+ name: Install dependencies
20
+
21
+ - run: npm run build
22
+ name: Build project
23
+
24
+ - run: npx netlify-cli deploy --no-build --dir=dist --prod -s {{NETLIFY_SITE_ID}} --auth ${{ secrets.NETLIFY_AUTH_TOKEN }} --message "Deployed commit ${{ github.sha }}"
25
+ name: Deploy to Netlify
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import chalk from "chalk";
6
6
  import enquirer from "enquirer";
7
+ import { promptConfirm } from "../../shared/promptConfirm";
7
8
  import { printDiff } from "../../utils/printDiff";
8
9
 
9
10
  const WORKFLOW_PATH = ".github/workflows/build.yml";
@@ -44,12 +45,7 @@ async function updateWorkflow(siteId: string): Promise<void> {
44
45
  console.log();
45
46
  printDiff(oldContent, newContent);
46
47
 
47
- const { confirm } = await enquirer.prompt<{ confirm: boolean }>({
48
- type: "confirm",
49
- name: "confirm",
50
- message: chalk.red("Update build.yml?"),
51
- initial: true,
52
- });
48
+ const confirm = await promptConfirm(chalk.red("Update build.yml?"));
53
49
 
54
50
  if (!confirm) {
55
51
  console.log("Skipped build.yml update");
@@ -73,9 +69,33 @@ export async function init(): Promise<void> {
73
69
  }
74
70
 
75
71
  console.log("Creating Netlify site...\n");
76
- execSync("netlify sites:create --disable-linking", {
77
- stdio: "inherit",
78
- });
72
+ try {
73
+ execSync("netlify sites:create --disable-linking", {
74
+ stdio: "inherit",
75
+ });
76
+ } catch (error) {
77
+ if (error instanceof Error && error.message.includes("command not found")) {
78
+ console.error(chalk.red("\nNetlify CLI is not installed.\n"));
79
+ const install = await promptConfirm("Would you like to install it now?");
80
+ if (install) {
81
+ console.log(chalk.dim("\nInstalling netlify-cli...\n"));
82
+ execSync("npm install -g netlify-cli", { stdio: "inherit" });
83
+ console.log();
84
+ execSync("netlify sites:create --disable-linking", {
85
+ stdio: "inherit",
86
+ });
87
+ } else {
88
+ console.log(
89
+ chalk.yellow(
90
+ "\nInstall it manually with: npm install -g netlify-cli\n",
91
+ ),
92
+ );
93
+ process.exit(1);
94
+ }
95
+ } else {
96
+ throw error;
97
+ }
98
+ }
79
99
 
80
100
  const { siteId } = await enquirer.prompt<{ siteId: string }>({
81
101
  type: "input",
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import chalk from "chalk";
5
- import enquirer from "enquirer";
5
+ import { promptConfirm } from "../../shared/promptConfirm";
6
6
  import { printDiff } from "../../utils/printDiff";
7
7
 
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -66,12 +66,7 @@ export async function enableRalph(): Promise<void> {
66
66
  console.log();
67
67
  printDiff(targetContent, mergedContent);
68
68
 
69
- const { confirm } = await enquirer.prompt<{ confirm: boolean }>({
70
- type: "confirm",
71
- name: "confirm",
72
- message: "Apply these changes?",
73
- initial: true,
74
- });
69
+ const confirm = await promptConfirm("Apply these changes?");
75
70
 
76
71
  if (!confirm) {
77
72
  console.log("Skipped");
@@ -3,12 +3,15 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import chalk from "chalk";
6
- import enquirer from "enquirer";
6
+ import { promptConfirm } from "../../shared/promptConfirm";
7
+ import { removeEslint } from "../../shared/removeEslint";
7
8
  import { printDiff } from "../../utils/printDiff";
8
9
 
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
11
 
11
12
  export async function init(): Promise<void> {
13
+ removeEslint();
14
+
12
15
  const biomeConfigPath = "biome.json";
13
16
 
14
17
  if (!existsSync(biomeConfigPath)) {
@@ -41,12 +44,7 @@ export async function init(): Promise<void> {
41
44
  console.log();
42
45
  printDiff(oldContent, newContent);
43
46
 
44
- const { confirm } = await enquirer.prompt<{ confirm: boolean }>({
45
- type: "confirm",
46
- name: "confirm",
47
- message: chalk.red("Update biome.json?"),
48
- initial: true,
49
- });
47
+ const confirm = await promptConfirm(chalk.red("Update biome.json?"));
50
48
 
51
49
  if (!confirm) {
52
50
  console.log("Skipped biome.json update");
package/dist/index.js CHANGED
@@ -1,17 +1,73 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { execSync as execSync12 } from "child_process";
4
+ import { execSync as execSync13 } from "child_process";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/commit.ts
8
8
  import { execSync } from "child_process";
9
+
10
+ // src/shared/loadConfig.ts
11
+ import { existsSync, readFileSync, writeFileSync } from "fs";
12
+ import { basename, join } from "path";
13
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
14
+ function getConfigPath() {
15
+ const claudeConfigPath = join(process.cwd(), ".claude", "assist.yml");
16
+ if (existsSync(claudeConfigPath)) {
17
+ return claudeConfigPath;
18
+ }
19
+ return join(process.cwd(), "assist.yml");
20
+ }
21
+ function loadConfig() {
22
+ const configPath = getConfigPath();
23
+ if (!existsSync(configPath)) {
24
+ return {};
25
+ }
26
+ try {
27
+ const content = readFileSync(configPath, "utf-8");
28
+ return parseYaml(content) || {};
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+ function saveConfig(config) {
34
+ const configPath = getConfigPath();
35
+ writeFileSync(configPath, stringifyYaml(config, { lineWidth: 0 }));
36
+ }
37
+ function getRepoName() {
38
+ const config = loadConfig();
39
+ if (config.devlog?.name) {
40
+ return config.devlog.name;
41
+ }
42
+ const packageJsonPath = join(process.cwd(), "package.json");
43
+ if (existsSync(packageJsonPath)) {
44
+ try {
45
+ const content = readFileSync(packageJsonPath, "utf-8");
46
+ const pkg = JSON.parse(content);
47
+ if (pkg.name) {
48
+ return pkg.name;
49
+ }
50
+ } catch {
51
+ }
52
+ }
53
+ return basename(process.cwd());
54
+ }
55
+
56
+ // src/commands/commit.ts
9
57
  var MAX_MESSAGE_LENGTH = 50;
58
+ var CONVENTIONAL_COMMIT_REGEX = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!)?(\(.+\))?!?: .+$/;
10
59
  function commit(message) {
60
+ const config = loadConfig();
11
61
  if (message.toLowerCase().includes("claude")) {
12
62
  console.error("Error: Commit message must not reference Claude");
13
63
  process.exit(1);
14
64
  }
65
+ if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message)) {
66
+ console.error(
67
+ "Error: Commit message must follow conventional commit format (e.g., 'feat: add feature', 'fix(scope): fix bug')"
68
+ );
69
+ process.exit(1);
70
+ }
15
71
  if (message.length > MAX_MESSAGE_LENGTH) {
16
72
  console.error(
17
73
  `Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message.length})`
@@ -19,9 +75,16 @@ function commit(message) {
19
75
  process.exit(1);
20
76
  }
21
77
  try {
78
+ if (config.commit?.pull) {
79
+ execSync("git pull", { stdio: "inherit" });
80
+ }
22
81
  execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
23
82
  stdio: "inherit"
24
83
  });
84
+ if (config.commit?.push) {
85
+ execSync("git push", { stdio: "inherit" });
86
+ console.log("Pushed to remote");
87
+ }
25
88
  process.exit(0);
26
89
  } catch (_error) {
27
90
  process.exit(1);
@@ -30,11 +93,28 @@ function commit(message) {
30
93
 
31
94
  // src/commands/deploy/init.ts
32
95
  import { execSync as execSync2 } from "child_process";
33
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
34
- import { dirname, join } from "path";
96
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
97
+ import { dirname, join as join2 } from "path";
35
98
  import { fileURLToPath } from "url";
36
99
  import chalk2 from "chalk";
100
+ import enquirer2 from "enquirer";
101
+
102
+ // src/shared/promptConfirm.ts
37
103
  import enquirer from "enquirer";
104
+ async function promptConfirm(message, initial = true) {
105
+ const { confirmed } = await enquirer.prompt({
106
+ type: "confirm",
107
+ name: "confirmed",
108
+ message,
109
+ initial,
110
+ // @ts-expect-error - enquirer types don't include symbols but it's supported
111
+ symbols: {
112
+ on: "[x]",
113
+ off: "[ ]"
114
+ }
115
+ });
116
+ return confirmed;
117
+ }
38
118
 
39
119
  // src/utils/printDiff.ts
40
120
  import chalk from "chalk";
@@ -69,26 +149,26 @@ function printDiff(oldContent, newContent) {
69
149
  var WORKFLOW_PATH = ".github/workflows/build.yml";
70
150
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
71
151
  function getExistingSiteId() {
72
- if (!existsSync(WORKFLOW_PATH)) {
152
+ if (!existsSync2(WORKFLOW_PATH)) {
73
153
  return null;
74
154
  }
75
- const content = readFileSync(WORKFLOW_PATH, "utf-8");
155
+ const content = readFileSync2(WORKFLOW_PATH, "utf-8");
76
156
  const match = content.match(/-s\s+([a-f0-9-]{36})/);
77
157
  return match ? match[1] : null;
78
158
  }
79
159
  function getTemplateContent(siteId) {
80
- const templatePath = join(__dirname2, "commands/deploy/build.yml");
81
- const template = readFileSync(templatePath, "utf-8");
160
+ const templatePath = join2(__dirname2, "commands/deploy/build.yml");
161
+ const template = readFileSync2(templatePath, "utf-8");
82
162
  return template.replace("{{NETLIFY_SITE_ID}}", siteId);
83
163
  }
84
164
  async function updateWorkflow(siteId) {
85
165
  const newContent = getTemplateContent(siteId);
86
166
  const workflowDir = ".github/workflows";
87
- if (!existsSync(workflowDir)) {
167
+ if (!existsSync2(workflowDir)) {
88
168
  mkdirSync(workflowDir, { recursive: true });
89
169
  }
90
- if (existsSync(WORKFLOW_PATH)) {
91
- const oldContent = readFileSync(WORKFLOW_PATH, "utf-8");
170
+ if (existsSync2(WORKFLOW_PATH)) {
171
+ const oldContent = readFileSync2(WORKFLOW_PATH, "utf-8");
92
172
  if (oldContent === newContent) {
93
173
  console.log(chalk2.green("build.yml is already up to date"));
94
174
  return;
@@ -96,18 +176,13 @@ async function updateWorkflow(siteId) {
96
176
  console.log(chalk2.yellow("\nbuild.yml will be updated:"));
97
177
  console.log();
98
178
  printDiff(oldContent, newContent);
99
- const { confirm } = await enquirer.prompt({
100
- type: "confirm",
101
- name: "confirm",
102
- message: chalk2.red("Update build.yml?"),
103
- initial: true
104
- });
179
+ const confirm = await promptConfirm(chalk2.red("Update build.yml?"));
105
180
  if (!confirm) {
106
181
  console.log("Skipped build.yml update");
107
182
  return;
108
183
  }
109
184
  }
110
- writeFileSync(WORKFLOW_PATH, newContent);
185
+ writeFileSync2(WORKFLOW_PATH, newContent);
111
186
  console.log(chalk2.green(`
112
187
  Created ${WORKFLOW_PATH}`));
113
188
  }
@@ -121,10 +196,34 @@ async function init() {
121
196
  return;
122
197
  }
123
198
  console.log("Creating Netlify site...\n");
124
- execSync2("netlify sites:create --disable-linking", {
125
- stdio: "inherit"
126
- });
127
- const { siteId } = await enquirer.prompt({
199
+ try {
200
+ execSync2("netlify sites:create --disable-linking", {
201
+ stdio: "inherit"
202
+ });
203
+ } catch (error) {
204
+ if (error instanceof Error && error.message.includes("command not found")) {
205
+ console.error(chalk2.red("\nNetlify CLI is not installed.\n"));
206
+ const install = await promptConfirm("Would you like to install it now?");
207
+ if (install) {
208
+ console.log(chalk2.dim("\nInstalling netlify-cli...\n"));
209
+ execSync2("npm install -g netlify-cli", { stdio: "inherit" });
210
+ console.log();
211
+ execSync2("netlify sites:create --disable-linking", {
212
+ stdio: "inherit"
213
+ });
214
+ } else {
215
+ console.log(
216
+ chalk2.yellow(
217
+ "\nInstall it manually with: npm install -g netlify-cli\n"
218
+ )
219
+ );
220
+ process.exit(1);
221
+ }
222
+ } else {
223
+ throw error;
224
+ }
225
+ }
226
+ const { siteId } = await enquirer2.prompt({
128
227
  type: "input",
129
228
  name: "siteId",
130
229
  message: "Enter the Site ID from above:",
@@ -156,52 +255,6 @@ import chalk4 from "chalk";
156
255
  import { execSync as execSync3 } from "child_process";
157
256
  import chalk3 from "chalk";
158
257
 
159
- // src/shared/loadConfig.ts
160
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
161
- import { basename, join as join2 } from "path";
162
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
163
- function getConfigPath() {
164
- const claudeConfigPath = join2(process.cwd(), ".claude", "assist.yml");
165
- if (existsSync2(claudeConfigPath)) {
166
- return claudeConfigPath;
167
- }
168
- return join2(process.cwd(), "assist.yml");
169
- }
170
- function loadConfig() {
171
- const configPath = getConfigPath();
172
- if (!existsSync2(configPath)) {
173
- return {};
174
- }
175
- try {
176
- const content = readFileSync2(configPath, "utf-8");
177
- return parseYaml(content) || {};
178
- } catch {
179
- return {};
180
- }
181
- }
182
- function saveConfig(config) {
183
- const configPath = getConfigPath();
184
- writeFileSync2(configPath, stringifyYaml(config, { lineWidth: 0 }));
185
- }
186
- function getRepoName() {
187
- const config = loadConfig();
188
- if (config.devlog?.name) {
189
- return config.devlog.name;
190
- }
191
- const packageJsonPath = join2(process.cwd(), "package.json");
192
- if (existsSync2(packageJsonPath)) {
193
- try {
194
- const content = readFileSync2(packageJsonPath, "utf-8");
195
- const pkg = JSON.parse(content);
196
- if (pkg.name) {
197
- return pkg.name;
198
- }
199
- } catch {
200
- }
201
- }
202
- return basename(process.cwd());
203
- }
204
-
205
258
  // src/commands/devlog/loadDevlogEntries.ts
206
259
  import { readdirSync, readFileSync as readFileSync3 } from "fs";
207
260
  import { homedir } from "os";
@@ -498,7 +551,6 @@ import * as fs from "fs";
498
551
  import * as path from "path";
499
552
  import { fileURLToPath as fileURLToPath2 } from "url";
500
553
  import chalk8 from "chalk";
501
- import enquirer2 from "enquirer";
502
554
  var __dirname3 = path.dirname(fileURLToPath2(import.meta.url));
503
555
  function deepMerge(target, source) {
504
556
  const result = { ...target };
@@ -542,12 +594,7 @@ async function enableRalph() {
542
594
  console.log(chalk8.yellow("\nChanges to settings.local.json:"));
543
595
  console.log();
544
596
  printDiff(targetContent, mergedContent);
545
- const { confirm } = await enquirer2.prompt({
546
- type: "confirm",
547
- name: "confirm",
548
- message: "Apply these changes?",
549
- initial: true
550
- });
597
+ const confirm = await promptConfirm("Apply these changes?");
551
598
  if (!confirm) {
552
599
  console.log("Skipped");
553
600
  return;
@@ -570,7 +617,14 @@ async function promptMultiselect(message, options) {
570
617
  choices: options.map((opt) => ({
571
618
  name: opt.value,
572
619
  message: `${opt.name} - ${chalk9.dim(opt.description)}`
573
- }))
620
+ })),
621
+ // @ts-expect-error - enquirer types don't include symbols but it's supported
622
+ symbols: {
623
+ indicator: {
624
+ on: "[x]",
625
+ off: "[ ]"
626
+ }
627
+ }
574
628
  });
575
629
  return selected;
576
630
  }
@@ -763,26 +817,104 @@ import * as path7 from "path";
763
817
  import chalk17 from "chalk";
764
818
 
765
819
  // src/commands/lint/init.ts
766
- import { execSync as execSync7 } from "child_process";
767
- import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
820
+ import { execSync as execSync8 } from "child_process";
821
+ import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
768
822
  import { dirname as dirname7, join as join8 } from "path";
769
823
  import { fileURLToPath as fileURLToPath3 } from "url";
770
824
  import chalk16 from "chalk";
771
- import enquirer4 from "enquirer";
825
+
826
+ // src/shared/removeEslint.ts
827
+ import { execSync as execSync7 } from "child_process";
828
+ import { existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
829
+ function removeEslint(options = {}) {
830
+ const removedFromPackageJson = removeEslintFromPackageJson(options);
831
+ const removedConfigFiles = removeEslintConfigFiles();
832
+ if (removedFromPackageJson || removedConfigFiles) {
833
+ console.log("Running npm install...");
834
+ execSync7("npm install", { stdio: "inherit" });
835
+ return true;
836
+ }
837
+ return false;
838
+ }
839
+ function removeEslintFromPackageJson(options) {
840
+ const packageJsonPath = "package.json";
841
+ if (!existsSync6(packageJsonPath)) {
842
+ return false;
843
+ }
844
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
845
+ let modified = false;
846
+ if (packageJson.dependencies) {
847
+ for (const key of Object.keys(packageJson.dependencies)) {
848
+ if (key.includes("eslint")) {
849
+ delete packageJson.dependencies[key];
850
+ modified = true;
851
+ }
852
+ }
853
+ }
854
+ if (packageJson.devDependencies) {
855
+ for (const key of Object.keys(packageJson.devDependencies)) {
856
+ if (key.includes("eslint")) {
857
+ delete packageJson.devDependencies[key];
858
+ modified = true;
859
+ }
860
+ }
861
+ }
862
+ if (packageJson.scripts) {
863
+ for (const key of Object.keys(packageJson.scripts)) {
864
+ const isEslintScript = key.includes("eslint") || packageJson.scripts[key].includes("eslint") || options.removeLintScripts && key.includes("lint");
865
+ if (isEslintScript) {
866
+ delete packageJson.scripts[key];
867
+ modified = true;
868
+ }
869
+ }
870
+ }
871
+ if (modified) {
872
+ writeFileSync5(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
873
+ `);
874
+ console.log("Removed eslint references from package.json");
875
+ }
876
+ return modified;
877
+ }
878
+ function removeEslintConfigFiles() {
879
+ const eslintConfigFiles = [
880
+ "eslint.config.js",
881
+ "eslint.config.mjs",
882
+ "eslint.config.cjs",
883
+ ".eslintrc",
884
+ ".eslintrc.js",
885
+ ".eslintrc.cjs",
886
+ ".eslintrc.json",
887
+ ".eslintrc.yaml",
888
+ ".eslintrc.yml",
889
+ ".eslintignore"
890
+ ];
891
+ let removed = false;
892
+ for (const configFile of eslintConfigFiles) {
893
+ if (existsSync6(configFile)) {
894
+ unlinkSync(configFile);
895
+ console.log(`Removed ${configFile}`);
896
+ removed = true;
897
+ }
898
+ }
899
+ return removed;
900
+ }
901
+
902
+ // src/commands/lint/init.ts
772
903
  var __dirname4 = dirname7(fileURLToPath3(import.meta.url));
773
904
  async function init2() {
905
+ removeEslint();
774
906
  const biomeConfigPath = "biome.json";
775
- if (!existsSync6(biomeConfigPath)) {
907
+ if (!existsSync7(biomeConfigPath)) {
776
908
  console.log("Initializing Biome...");
777
- execSync7("npx @biomejs/biome init", { stdio: "inherit" });
909
+ execSync8("npx @biomejs/biome init", { stdio: "inherit" });
778
910
  }
779
- if (!existsSync6(biomeConfigPath)) {
911
+ if (!existsSync7(biomeConfigPath)) {
780
912
  console.log("No biome.json found, skipping linter config");
781
913
  return;
782
914
  }
783
915
  const linterConfigPath = join8(__dirname4, "commands/lint/biome.linter.json");
784
- const linterConfig = JSON.parse(readFileSync8(linterConfigPath, "utf-8"));
785
- const biomeConfig = JSON.parse(readFileSync8(biomeConfigPath, "utf-8"));
916
+ const linterConfig = JSON.parse(readFileSync9(linterConfigPath, "utf-8"));
917
+ const biomeConfig = JSON.parse(readFileSync9(biomeConfigPath, "utf-8"));
786
918
  const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
787
919
  `;
788
920
  biomeConfig.linter = linterConfig.linter;
@@ -798,17 +930,12 @@ async function init2() {
798
930
  console.log(chalk16.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
799
931
  console.log();
800
932
  printDiff(oldContent, newContent);
801
- const { confirm } = await enquirer4.prompt({
802
- type: "confirm",
803
- name: "confirm",
804
- message: chalk16.red("Update biome.json?"),
805
- initial: true
806
- });
933
+ const confirm = await promptConfirm(chalk16.red("Update biome.json?"));
807
934
  if (!confirm) {
808
935
  console.log("Skipped biome.json update");
809
936
  return;
810
937
  }
811
- writeFileSync5(biomeConfigPath, newContent);
938
+ writeFileSync6(biomeConfigPath, newContent);
812
939
  console.log("Updated biome.json with linter config");
813
940
  }
814
941
 
@@ -1306,64 +1433,25 @@ function lint() {
1306
1433
  }
1307
1434
 
1308
1435
  // src/commands/new/newProject.ts
1309
- import { execSync as execSync8 } from "child_process";
1310
- import { existsSync as existsSync9, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
1436
+ import { execSync as execSync9 } from "child_process";
1437
+ import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
1311
1438
  async function newProject() {
1312
1439
  console.log("Initializing Vite with react-ts template...");
1313
- execSync8("npm create vite@latest . -- --template react-ts", {
1440
+ execSync9("npm create vite@latest . -- --template react-ts", {
1314
1441
  stdio: "inherit"
1315
1442
  });
1316
- removeEslintFromPackageJson();
1317
- removeEslintConfigFile();
1443
+ removeEslint({ removeLintScripts: true });
1318
1444
  addViteBaseConfig();
1319
1445
  await init5();
1320
1446
  await init();
1321
1447
  }
1322
- function removeEslintFromPackageJson() {
1323
- const packageJsonPath = "package.json";
1324
- if (!existsSync9(packageJsonPath)) {
1325
- console.log("No package.json found, skipping eslint removal");
1326
- return;
1327
- }
1328
- const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
1329
- let modified = false;
1330
- if (packageJson.dependencies) {
1331
- for (const key of Object.keys(packageJson.dependencies)) {
1332
- if (key.includes("eslint")) {
1333
- delete packageJson.dependencies[key];
1334
- modified = true;
1335
- }
1336
- }
1337
- }
1338
- if (packageJson.devDependencies) {
1339
- for (const key of Object.keys(packageJson.devDependencies)) {
1340
- if (key.includes("eslint")) {
1341
- delete packageJson.devDependencies[key];
1342
- modified = true;
1343
- }
1344
- }
1345
- }
1346
- if (packageJson.scripts) {
1347
- for (const key of Object.keys(packageJson.scripts)) {
1348
- if (key.includes("eslint") || key.includes("lint") || packageJson.scripts[key].includes("eslint")) {
1349
- delete packageJson.scripts[key];
1350
- modified = true;
1351
- }
1352
- }
1353
- }
1354
- if (modified) {
1355
- writeFileSync7(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
1356
- `);
1357
- console.log("Removed eslint references from package.json");
1358
- }
1359
- }
1360
1448
  function addViteBaseConfig() {
1361
1449
  const viteConfigPath = "vite.config.ts";
1362
- if (!existsSync9(viteConfigPath)) {
1450
+ if (!existsSync10(viteConfigPath)) {
1363
1451
  console.log("No vite.config.ts found, skipping base config");
1364
1452
  return;
1365
1453
  }
1366
- const content = readFileSync10(viteConfigPath, "utf-8");
1454
+ const content = readFileSync11(viteConfigPath, "utf-8");
1367
1455
  if (content.includes("base:")) {
1368
1456
  console.log("vite.config.ts already has base config");
1369
1457
  return;
@@ -1373,39 +1461,20 @@ function addViteBaseConfig() {
1373
1461
  'defineConfig({\n base: "./",'
1374
1462
  );
1375
1463
  if (updated !== content) {
1376
- writeFileSync7(viteConfigPath, updated);
1464
+ writeFileSync8(viteConfigPath, updated);
1377
1465
  console.log('Added base: "./" to vite.config.ts');
1378
1466
  }
1379
1467
  }
1380
- function removeEslintConfigFile() {
1381
- const eslintConfigFiles = [
1382
- "eslint.config.js",
1383
- "eslint.config.mjs",
1384
- "eslint.config.cjs",
1385
- ".eslintrc",
1386
- ".eslintrc.js",
1387
- ".eslintrc.cjs",
1388
- ".eslintrc.json",
1389
- ".eslintrc.yaml",
1390
- ".eslintrc.yml"
1391
- ];
1392
- for (const configFile of eslintConfigFiles) {
1393
- if (existsSync9(configFile)) {
1394
- unlinkSync(configFile);
1395
- console.log(`Removed ${configFile}`);
1396
- }
1397
- }
1398
- }
1399
1468
 
1400
1469
  // src/commands/prs.ts
1401
- import { execSync as execSync9 } from "child_process";
1470
+ import { execSync as execSync10 } from "child_process";
1402
1471
  import chalk24 from "chalk";
1403
- import enquirer5 from "enquirer";
1472
+ import enquirer4 from "enquirer";
1404
1473
  var PAGE_SIZE = 10;
1405
1474
  async function prs(options) {
1406
1475
  const state = options.open ? "open" : options.closed ? "closed" : "all";
1407
1476
  try {
1408
- const result = execSync9(
1477
+ const result = execSync10(
1409
1478
  `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
1410
1479
  { encoding: "utf-8" }
1411
1480
  );
@@ -1472,7 +1541,7 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
1472
1541
  if (hasNext) choices.push({ name: "Next page", value: "next" });
1473
1542
  if (hasPrev) choices.push({ name: "Previous page", value: "prev" });
1474
1543
  choices.push({ name: "Quit", value: "quit" });
1475
- const { action } = await enquirer5.prompt({
1544
+ const { action } = await enquirer4.prompt({
1476
1545
  type: "select",
1477
1546
  name: "action",
1478
1547
  message: "Navigate",
@@ -1495,7 +1564,7 @@ import { spawn } from "child_process";
1495
1564
  import * as path13 from "path";
1496
1565
 
1497
1566
  // src/commands/refactor/getViolations.ts
1498
- import { execSync as execSync10 } from "child_process";
1567
+ import { execSync as execSync11 } from "child_process";
1499
1568
  import fs10 from "fs";
1500
1569
  import { minimatch } from "minimatch";
1501
1570
 
@@ -1600,7 +1669,7 @@ function getGitFiles(options) {
1600
1669
  }
1601
1670
  const files = /* @__PURE__ */ new Set();
1602
1671
  if (options.staged || options.modified) {
1603
- const staged = execSync10("git diff --cached --name-only", {
1672
+ const staged = execSync11("git diff --cached --name-only", {
1604
1673
  encoding: "utf-8"
1605
1674
  });
1606
1675
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -1608,7 +1677,7 @@ function getGitFiles(options) {
1608
1677
  }
1609
1678
  }
1610
1679
  if (options.unstaged || options.modified) {
1611
- const unstaged = execSync10("git diff --name-only", { encoding: "utf-8" });
1680
+ const unstaged = execSync11("git diff --name-only", { encoding: "utf-8" });
1612
1681
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
1613
1682
  files.add(file);
1614
1683
  }
@@ -1789,6 +1858,33 @@ function add() {
1789
1858
  console.log(`Added run configuration: ${name} -> ${display}`);
1790
1859
  }
1791
1860
 
1861
+ // src/commands/statusLine.ts
1862
+ import * as readline from "readline";
1863
+ function formatNumber(num) {
1864
+ return num.toLocaleString("en-US");
1865
+ }
1866
+ async function statusLine() {
1867
+ const rl = readline.createInterface({
1868
+ input: process.stdin,
1869
+ output: process.stdout,
1870
+ terminal: false
1871
+ });
1872
+ let inputData = "";
1873
+ for await (const line of rl) {
1874
+ inputData += line;
1875
+ }
1876
+ const data = JSON.parse(inputData);
1877
+ const model = data.model.display_name;
1878
+ const totalInput = data.context_window.total_input_tokens;
1879
+ const totalOutput = data.context_window.total_output_tokens;
1880
+ const usedPct = data.context_window.used_percentage ?? 0;
1881
+ const formattedInput = formatNumber(totalInput);
1882
+ const formattedOutput = formatNumber(totalOutput);
1883
+ console.log(
1884
+ `${model} | Tokens - ${formattedOutput} \u2191 : ${formattedInput} \u2193 | Context - ${usedPct}%`
1885
+ );
1886
+ }
1887
+
1792
1888
  // src/commands/sync.ts
1793
1889
  import * as fs13 from "fs";
1794
1890
  import * as os from "os";
@@ -1799,7 +1895,6 @@ import { fileURLToPath as fileURLToPath4 } from "url";
1799
1895
  import * as fs12 from "fs";
1800
1896
  import * as path14 from "path";
1801
1897
  import chalk27 from "chalk";
1802
- import enquirer6 from "enquirer";
1803
1898
  async function syncSettings(claudeDir, targetBase) {
1804
1899
  const source = path14.join(claudeDir, "settings.json");
1805
1900
  const target = path14.join(targetBase, "settings.json");
@@ -1814,12 +1909,10 @@ async function syncSettings(claudeDir, targetBase) {
1814
1909
  );
1815
1910
  console.log();
1816
1911
  printDiff(targetContent, sourceContent);
1817
- const { confirm } = await enquirer6.prompt({
1818
- type: "confirm",
1819
- name: "confirm",
1820
- message: chalk27.red("Overwrite existing settings.json?"),
1821
- initial: false
1822
- });
1912
+ const confirm = await promptConfirm(
1913
+ chalk27.red("Overwrite existing settings.json?"),
1914
+ false
1915
+ );
1823
1916
  if (!confirm) {
1824
1917
  console.log("Skipped settings.json");
1825
1918
  return;
@@ -1852,11 +1945,11 @@ function syncCommands(claudeDir, targetBase) {
1852
1945
  }
1853
1946
 
1854
1947
  // src/commands/verify/hardcodedColors.ts
1855
- import { execSync as execSync11 } from "child_process";
1948
+ import { execSync as execSync12 } from "child_process";
1856
1949
  var pattern = "0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,6}";
1857
1950
  function hardcodedColors() {
1858
1951
  try {
1859
- const output = execSync11(`grep -rEnH '${pattern}' src/`, {
1952
+ const output = execSync12(`grep -rEnH '${pattern}' src/`, {
1860
1953
  encoding: "utf-8"
1861
1954
  });
1862
1955
  const lines = output.trim().split("\n");
@@ -1966,7 +2059,7 @@ program.command("init").description("Initialize VS Code and verify configuration
1966
2059
  program.command("commit <message>").description("Create a git commit with validation").action(commit);
1967
2060
  program.command("update").description("Update claude-code to the latest version").action(() => {
1968
2061
  console.log("Updating claude-code...");
1969
- execSync12("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
2062
+ execSync13("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
1970
2063
  });
1971
2064
  program.command("prs").description("List pull requests for the current repository").option("--open", "List only open pull requests").option("--closed", "List only closed pull requests").action(prs);
1972
2065
  var runCommand = program.command("run").description("Run a configured command from assist.yml").argument("<name>", "Name of the configured command").argument("[args...]", "Arguments to pass to the command").allowUnknownOption().action((name, args) => {
@@ -2000,4 +2093,5 @@ vscodeCommand.command("init").description("Add VS Code configuration files").act
2000
2093
  var deployCommand = program.command("deploy").description("Netlify deployment utilities");
2001
2094
  deployCommand.command("init").description("Initialize Netlify project and configure deployment").action(init);
2002
2095
  program.command("enable-ralph").description("Enable ralph-wiggum plugin for spacetraders").action(enableRalph);
2096
+ program.command("status-line").description("Format Claude Code status line from JSON stdin").action(statusLine);
2003
2097
  program.parse();
package/package.json CHANGED
@@ -1,49 +1,53 @@
1
- {
2
- "name": "@staff0rd/assist",
3
- "version": "0.10.1",
4
- "type": "module",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "assist": "./dist/index.js"
8
- },
9
- "files": [
10
- "dist"
11
- ],
12
- "publishConfig": {
13
- "access": "public"
14
- },
15
- "scripts": {
16
- "build": "tsup",
17
- "start": "node dist/index.js",
18
- "verify:lint": "biome check --write .",
19
- "verify:build": "tsup",
20
- "verify:types": "tsc --noEmit",
21
- "verify:knip": "knip --no-progress",
22
- "verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src"
23
- },
24
- "keywords": [],
25
- "author": "",
26
- "license": "ISC",
27
- "description": "",
28
- "dependencies": {
29
- "chalk": "^5.6.2",
30
- "commander": "^14.0.2",
31
- "diff": "^8.0.2",
32
- "enquirer": "^2.4.1",
33
- "minimatch": "^10.1.1",
34
- "semver": "^7.7.3",
35
- "yaml": "^2.8.2"
36
- },
37
- "devDependencies": {
38
- "@biomejs/biome": "^2.3.8",
39
- "@semantic-release/changelog": "^6.0.3",
40
- "@semantic-release/git": "^10.0.1",
41
- "@types/node": "^24.10.1",
42
- "@types/semver": "^7.7.1",
43
- "jscpd": "^4.0.5",
44
- "knip": "^5.71.0",
45
- "semantic-release": "^25.0.2",
46
- "tsup": "^8.5.1",
47
- "typescript": "^5.9.3"
48
- }
49
- }
1
+ {
2
+ "name": "@staff0rd/assist",
3
+ "version": "0.13.3",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "assist": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "start": "node dist/index.js",
18
+ "verify:lint": "biome check --write .",
19
+ "verify:build": "tsup",
20
+ "verify:types": "tsc --noEmit",
21
+ "verify:knip": "knip --no-progress",
22
+ "verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src"
23
+ },
24
+ "keywords": [],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "description": "",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/staff0rd/assist"
31
+ },
32
+ "dependencies": {
33
+ "chalk": "^5.6.2",
34
+ "commander": "^14.0.2",
35
+ "diff": "^8.0.2",
36
+ "enquirer": "^2.4.1",
37
+ "minimatch": "^10.1.1",
38
+ "semver": "^7.7.3",
39
+ "yaml": "^2.8.2"
40
+ },
41
+ "devDependencies": {
42
+ "@biomejs/biome": "^2.3.8",
43
+ "@semantic-release/changelog": "^6.0.3",
44
+ "@semantic-release/git": "^10.0.1",
45
+ "@types/node": "^24.10.1",
46
+ "@types/semver": "^7.7.1",
47
+ "jscpd": "^4.0.5",
48
+ "knip": "^5.71.0",
49
+ "semantic-release": "^25.0.2",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }