fuzzrunx 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,10 +13,15 @@ Install (npm):
13
13
  ```
14
14
  npm i -g fuzzrunx
15
15
  ```
16
-
17
- ### Bash/Zsh hook (auto-run on typos)
18
-
19
- Add to your shell rc:
16
+
17
+ On global install, FuzzRun auto-enables shell hooks and will print:
18
+ `FuzzRun is automatically enabled. Run "fuzzrun disable" to deactivate.`
19
+
20
+ If you want to skip auto-enable, set `FUZZRUN_SKIP_ENABLE=1` during install.
21
+
22
+ ### Bash/Zsh hook (auto-run on typos)
23
+
24
+ Add to your shell rc:
20
25
 
21
26
  ```bash
22
27
  FUZZRUN_BIN="/absolute/path/to/bin/fuzzrun.js"
@@ -38,8 +43,13 @@ $ExecutionContext.InvokeCommand.CommandNotFoundAction = {
38
43
  param($commandName, $eventArgs)
39
44
  fuzzrun $commandName @($eventArgs.Arguments)
40
45
  }
41
- function global:git { fuzzrun git @args } # optional git wrapper
42
- ```
46
+ function global:git { fuzzrun git @args } # optional git wrapper
47
+ ```
48
+
49
+ ### Manage hooks
50
+ - `fuzzrun enable` (add hooks to your shell profile)
51
+ - `fuzzrun disable` (remove hooks)
52
+ - `fuzzrun status` (show which profiles are enabled)
43
53
 
44
54
  ### How it works
45
55
  - Runs the command once; if it fails with "command not found" or "unknown subcommand", tries a one-edit-away fix or the CLI's own suggestion and re-runs automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuzzrunx",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Auto-correct mistyped commands and subcommands and re-run them safely.",
5
5
  "bin": {
6
6
  "fuzzrun": "bin/fuzzrun.js"
@@ -8,6 +8,7 @@
8
8
  "type": "commonjs",
9
9
  "license": "MIT",
10
10
  "scripts": {
11
- "start": "node bin/fuzzrun.js"
11
+ "start": "node bin/fuzzrun.js",
12
+ "postinstall": "node scripts/postinstall.js"
12
13
  }
13
14
  }
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const installer = require('../src/installer');
4
+
5
+ const skip = process.env.FUZZRUN_SKIP_ENABLE === '1';
6
+ const isGlobal = process.env.npm_config_global === 'true' || process.env.npm_config_global === '1';
7
+
8
+ if (skip || !isGlobal) {
9
+ process.stdout.write('fuzzrun: auto-enable skipped\n');
10
+ process.exit(0);
11
+ }
12
+
13
+ try {
14
+ installer.enable({});
15
+ process.stdout.write('FuzzRun is automatically enabled. Run "fuzzrun disable" to deactivate.\n');
16
+ } catch (err) {
17
+ process.stdout.write(`fuzzrun: auto-enable failed: ${err.message}\n`);
18
+ }
package/src/cli.js CHANGED
@@ -6,6 +6,7 @@
6
6
  const { spawnSync } = require('child_process');
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const installer = require('./installer');
9
10
 
10
11
  const MAX_DISTANCE = Number.isFinite(Number(process.env.FUZZRUN_MAX_DISTANCE))
11
12
  ? Math.max(1, Number(process.env.FUZZRUN_MAX_DISTANCE))
@@ -444,12 +445,33 @@ function main() {
444
445
  const argv = process.argv.slice(2);
445
446
  if (!argv.length) {
446
447
  process.stderr.write('Usage: fuzzrun <command> [args...]\n');
447
- process.exit(1);
448
- }
449
-
450
- const baseCommand = argv[0];
451
- const rest = argv.slice(1);
452
- const firstRun = run(baseCommand, rest);
448
+ process.exit(1);
449
+ }
450
+
451
+ const action = argv[0];
452
+ if (action === 'enable') {
453
+ const results = installer.enable({});
454
+ const updated = results.some((item) => item.updated);
455
+ process.stdout.write(updated ? 'FuzzRun enabled. Restart your shell to apply changes.\n' : 'FuzzRun already enabled.\n');
456
+ process.exit(0);
457
+ }
458
+ if (action === 'disable') {
459
+ const results = installer.disable();
460
+ const updated = results.some((item) => item.updated);
461
+ process.stdout.write(updated ? 'FuzzRun disabled. Restart your shell to apply changes.\n' : 'FuzzRun already disabled.\n');
462
+ process.exit(0);
463
+ }
464
+ if (action === 'status') {
465
+ const results = installer.status();
466
+ for (const item of results) {
467
+ process.stdout.write(`${item.enabled ? 'enabled' : 'disabled'}: ${item.path}\n`);
468
+ }
469
+ process.exit(0);
470
+ }
471
+
472
+ const baseCommand = argv[0];
473
+ const rest = argv.slice(1);
474
+ const firstRun = run(baseCommand, rest);
453
475
 
454
476
  if (firstRun.error && firstRun.error.code === 'ENOENT') {
455
477
  const corrected = tryBaseCorrection(baseCommand, rest);
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const MARKER_START = '# >>> fuzzrun start';
8
+ const MARKER_END = '# <<< fuzzrun end';
9
+
10
+ const WRAP_BASES = ['git', 'npm', 'yarn', 'pnpm', 'pip', 'docker', 'kubectl', 'gh'];
11
+
12
+ function escapeRegex(value) {
13
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
14
+ }
15
+
16
+ const BLOCK_REGEX = new RegExp(`${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\s*`, 'g');
17
+
18
+ function getPackageRoot(explicitRoot) {
19
+ if (explicitRoot) return explicitRoot;
20
+ return path.resolve(__dirname, '..');
21
+ }
22
+
23
+ function getBinPath(packageRoot) {
24
+ return path.resolve(packageRoot, 'bin', 'fuzzrun.js');
25
+ }
26
+
27
+ function getProfileTargets() {
28
+ const home = os.homedir();
29
+ if (process.platform === 'win32') {
30
+ return [
31
+ path.join(home, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
32
+ path.join(home, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')
33
+ ];
34
+ }
35
+ return [path.join(home, '.bashrc'), path.join(home, '.zshrc')];
36
+ }
37
+
38
+ function ensureDir(filePath) {
39
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
40
+ }
41
+
42
+ function stripBlock(content) {
43
+ return content.replace(BLOCK_REGEX, '').trimEnd();
44
+ }
45
+
46
+ function buildPowerShellSnippet(binPath) {
47
+ const lines = [
48
+ MARKER_START,
49
+ `$fuzzrun = "${binPath}"`,
50
+ 'function global:fuzzrun { node $fuzzrun @args }',
51
+ '$ExecutionContext.InvokeCommand.CommandNotFoundAction = {',
52
+ ' param($commandName, $eventArgs)',
53
+ ' fuzzrun $commandName @($eventArgs.Arguments)',
54
+ '}'
55
+ ];
56
+ for (const base of WRAP_BASES) {
57
+ lines.push(`function global:${base} { fuzzrun ${base} @args }`);
58
+ }
59
+ lines.push(MARKER_END, '');
60
+ return lines.join('\n');
61
+ }
62
+
63
+ function buildUnixSnippet(binPath) {
64
+ const lines = [
65
+ MARKER_START,
66
+ `FUZZRUN_BIN="${binPath}"`,
67
+ 'fuzzrun() { node "$FUZZRUN_BIN" "$@"; }',
68
+ 'command_not_found_handle() { fuzzrun "$@"; }',
69
+ 'command_not_found_handler() { fuzzrun "$@"; }'
70
+ ];
71
+ for (const base of WRAP_BASES) {
72
+ lines.push(`${base}() { fuzzrun ${base} "$@"; }`);
73
+ }
74
+ lines.push(MARKER_END, '');
75
+ return lines.join('\n');
76
+ }
77
+
78
+ function updateProfile(filePath, snippet) {
79
+ const exists = fs.existsSync(filePath);
80
+ const content = exists ? fs.readFileSync(filePath, 'utf8') : '';
81
+ const cleaned = stripBlock(content);
82
+ const spacer = cleaned.length ? '\n\n' : '';
83
+ const nextContent = `${cleaned}${spacer}${snippet}`;
84
+ ensureDir(filePath);
85
+ fs.writeFileSync(filePath, nextContent, 'utf8');
86
+ return { updated: content !== nextContent, path: filePath };
87
+ }
88
+
89
+ function removeProfileSnippet(filePath) {
90
+ if (!fs.existsSync(filePath)) {
91
+ return { updated: false, path: filePath };
92
+ }
93
+ const content = fs.readFileSync(filePath, 'utf8');
94
+ const cleaned = stripBlock(content);
95
+ if (cleaned === content) {
96
+ return { updated: false, path: filePath };
97
+ }
98
+ ensureDir(filePath);
99
+ fs.writeFileSync(filePath, cleaned ? `${cleaned}\n` : '', 'utf8');
100
+ return { updated: true, path: filePath };
101
+ }
102
+
103
+ function pickTargets() {
104
+ const targets = getProfileTargets();
105
+ const existing = targets.filter((target) => fs.existsSync(target));
106
+ if (existing.length) return existing;
107
+ return [targets[0]];
108
+ }
109
+
110
+ function enable({ packageRoot } = {}) {
111
+ const root = getPackageRoot(packageRoot);
112
+ const binPath = getBinPath(root);
113
+ const targets = pickTargets();
114
+ const snippet = process.platform === 'win32' ? buildPowerShellSnippet(binPath) : buildUnixSnippet(binPath);
115
+ return targets.map((target) => updateProfile(target, snippet));
116
+ }
117
+
118
+ function disable() {
119
+ const targets = getProfileTargets().filter((target) => fs.existsSync(target));
120
+ return targets.map((target) => removeProfileSnippet(target));
121
+ }
122
+
123
+ function status() {
124
+ const targets = getProfileTargets();
125
+ return targets.map((target) => {
126
+ if (!fs.existsSync(target)) {
127
+ return { path: target, enabled: false };
128
+ }
129
+ const content = fs.readFileSync(target, 'utf8');
130
+ return { path: target, enabled: content.includes(MARKER_START) && content.includes(MARKER_END) };
131
+ });
132
+ }
133
+
134
+ module.exports = {
135
+ enable,
136
+ disable,
137
+ status
138
+ };