@thebushidocollective/han 1.56.4 → 3.4.2
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 +18 -223
- package/bin.js +63 -0
- package/package.json +27 -76
- package/LICENSE +0 -21
- package/bin/han.js +0 -261
- package/schemas/han-config-override.schema.json +0 -62
- package/schemas/han-config.schema.json +0 -55
package/README.md
CHANGED
|
@@ -1,234 +1,29 @@
|
|
|
1
1
|
# Han CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for installing and managing curated Claude Code plugins from the [Han marketplace](https://han.guru).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
### Quick Install (Recommended)
|
|
10
|
-
|
|
11
|
-
Install the han binary for fastest execution and automatic hook support:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
curl -fsSL https://han.guru/install.sh | bash
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
This installs to `~/.claude/bin/han`, which is automatically added to PATH by Claude Code.
|
|
18
|
-
|
|
19
|
-
### Alternative: Homebrew
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
brew install thebushidocollective/tap/han
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Note:** The `core` plugin automatically installs the han binary when you start a Claude Code session, so manual installation is optional.
|
|
26
|
-
|
|
27
|
-
## Plugin Categories
|
|
28
|
-
|
|
29
|
-
Han organizes plugins into five categories inspired by Japanese samurai traditions:
|
|
30
|
-
|
|
31
|
-
- **Core** (⚙️) - **Required infrastructure** - Auto-installs han binary, provides hook system, MCP servers, and universal principles
|
|
32
|
-
- **Bushido** (武士道) - Core principles, enforcement hooks, and foundational quality skills (strongly recommended)
|
|
33
|
-
- **Do** (道 - The Way) - Specialized agents for development disciplines and practices
|
|
34
|
-
- **Jutsu** (術 - Techniques) - Language and tool skills with validation hooks for quality
|
|
35
|
-
- **Hashi** (橋 - Bridges) - MCP servers providing external knowledge and integrations
|
|
36
|
-
|
|
37
|
-
> **Important:** The `core` plugin is always required and automatically included with `--auto` installation.
|
|
38
|
-
|
|
39
|
-
## Commands
|
|
40
|
-
|
|
41
|
-
### plugin install
|
|
42
|
-
|
|
43
|
-
Install plugins interactively or automatically.
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
# Interactive mode - browse and select plugins
|
|
47
|
-
han plugin install
|
|
48
|
-
|
|
49
|
-
# Auto-detect mode - AI analyzes codebase and recommends plugins
|
|
50
|
-
han plugin install --auto
|
|
51
|
-
|
|
52
|
-
# Install specific plugin by name
|
|
53
|
-
han plugin install <plugin-name>
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Options:**
|
|
57
|
-
|
|
58
|
-
- `--auto` - Use AI to analyze your codebase and recommend plugins:
|
|
59
|
-
- Shows installed and recommended plugins only
|
|
60
|
-
- Recommended plugins marked with ⭐ and pre-selected
|
|
61
|
-
- Installed but no longer recommended plugins marked as "(installed)" and deselected
|
|
62
|
-
- Other plugins discoverable via "🔍 Search for more plugins"
|
|
63
|
-
- Based on: Programming languages, frameworks, git platform, testing tools
|
|
64
|
-
- `--scope <project|local>` - Installation scope (default: `project`)
|
|
65
|
-
- `project`: Install to `.claude/settings.json` (shared via git)
|
|
66
|
-
- `local`: Install to `.claude/settings.local.json` (git-ignored)
|
|
67
|
-
|
|
68
|
-
### plugin uninstall
|
|
69
|
-
|
|
70
|
-
Remove a specific plugin.
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
han plugin uninstall <plugin-name> [--scope <project|local>]
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### plugin search
|
|
77
|
-
|
|
78
|
-
Search for plugins in the Han marketplace.
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
han plugin search [query]
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### plugin update-marketplace
|
|
85
|
-
|
|
86
|
-
Update the local marketplace cache from GitHub.
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
han plugin update-marketplace
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
The marketplace cache is automatically refreshed every 24 hours when using `han plugin install` or `han plugin search`. Use this command to manually force a refresh if you want to see the latest plugins immediately.
|
|
93
|
-
|
|
94
|
-
**Features:**
|
|
95
|
-
|
|
96
|
-
- Caches marketplace data locally in `~/.claude/cache/han-marketplace.json`
|
|
97
|
-
- Automatically refreshes after 24 hours
|
|
98
|
-
- Falls back to stale cache if network is unavailable
|
|
99
|
-
- Shows cache age and available categories after update
|
|
100
|
-
|
|
101
|
-
### hook run
|
|
102
|
-
|
|
103
|
-
Run a hook command defined by a plugin.
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
# New format (recommended) - uses plugin's han-config.json
|
|
107
|
-
han hook run <plugin-name> <hook-name> [options]
|
|
108
|
-
|
|
109
|
-
# Legacy format - run arbitrary commands in matching directories
|
|
110
|
-
han hook run --dirs-with <pattern> -- <command>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Options:**
|
|
114
|
-
|
|
115
|
-
- `--fail-fast` - Stop on first failure (default: true)
|
|
116
|
-
- `--cached` - Skip if no relevant files have changed since last successful run
|
|
117
|
-
- `--only=<dir>` - Only run in the specified directory
|
|
118
|
-
- `--verbose` - Show full command output
|
|
119
|
-
|
|
120
|
-
**Examples:**
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
# Run TypeScript type checking using plugin config
|
|
124
|
-
han hook run jutsu-typescript typecheck
|
|
125
|
-
|
|
126
|
-
# Run with caching (skip if no changes)
|
|
127
|
-
han hook run jutsu-elixir test --cached
|
|
128
|
-
|
|
129
|
-
# Run in a specific directory only
|
|
130
|
-
han hook run jutsu-biome lint --only=packages/core
|
|
131
|
-
|
|
132
|
-
# Legacy: Run npm test in all directories with package.json
|
|
133
|
-
han hook run --dirs-with package.json -- npm test
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### hook explain
|
|
137
|
-
|
|
138
|
-
Show comprehensive information about configured hooks.
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
han hook explain [hookType] [--all]
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
**Options:**
|
|
145
|
-
|
|
146
|
-
- `[hookType]` - Filter by hook type (e.g., `Stop`, `SessionStart`)
|
|
147
|
-
- `--all` - Include hooks from Claude Code settings (not just Han plugins)
|
|
148
|
-
|
|
149
|
-
**Examples:**
|
|
5
|
+
## Usage
|
|
150
6
|
|
|
151
7
|
```bash
|
|
152
|
-
#
|
|
153
|
-
han
|
|
154
|
-
|
|
155
|
-
# Show only Stop hooks
|
|
156
|
-
han hook explain Stop
|
|
8
|
+
# Run directly via npx
|
|
9
|
+
npx @thebushidocollective/han plugin install --auto
|
|
157
10
|
|
|
158
|
-
#
|
|
159
|
-
|
|
11
|
+
# Or install globally
|
|
12
|
+
npm install -g @thebushidocollective/han
|
|
13
|
+
han plugin list
|
|
160
14
|
```
|
|
161
15
|
|
|
162
|
-
|
|
16
|
+
## MCP Server Usage
|
|
163
17
|
|
|
164
|
-
|
|
18
|
+
Configure in your Claude Code plugin:
|
|
165
19
|
|
|
166
|
-
```
|
|
167
|
-
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"han": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "@thebushidocollective/han", "mcp"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
168
29
|
```
|
|
169
|
-
|
|
170
|
-
This is useful for triggering hooks manually or for workarounds when plugin hook output needs to be passed to the agent.
|
|
171
|
-
|
|
172
|
-
**Examples:**
|
|
173
|
-
|
|
174
|
-
```bash
|
|
175
|
-
# Dispatch SessionStart hooks
|
|
176
|
-
han hook dispatch SessionStart
|
|
177
|
-
|
|
178
|
-
# Dispatch Stop hooks including settings hooks
|
|
179
|
-
han hook dispatch Stop --all
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### hook test
|
|
183
|
-
|
|
184
|
-
Validate hook configurations for all installed plugins.
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
# Validate hook structure and syntax only
|
|
188
|
-
han hook test
|
|
189
|
-
|
|
190
|
-
# Validate AND execute hooks to verify they run successfully
|
|
191
|
-
han hook test --execute
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### mcp
|
|
195
|
-
|
|
196
|
-
Start the Han MCP server for natural language hook execution.
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
han mcp
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
The MCP server dynamically exposes tools based on your installed plugins. Once installed via `hashi-han`, you can run hooks using natural language like "run the elixir tests" instead of remembering exact commands.
|
|
203
|
-
|
|
204
|
-
**Generated tools include:**
|
|
205
|
-
|
|
206
|
-
- `jutsu_elixir_test` - Run tests for Elixir projects
|
|
207
|
-
- `jutsu_typescript_typecheck` - Run TypeScript type checking
|
|
208
|
-
- `jutsu_biome_lint` - Run Biome linting
|
|
209
|
-
|
|
210
|
-
See the [hashi-han plugin](/hashi/hashi-han) for installation and configuration.
|
|
211
|
-
|
|
212
|
-
### uninstall
|
|
213
|
-
|
|
214
|
-
Remove all Han plugins and marketplace configuration.
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
han uninstall
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Philosophy
|
|
221
|
-
|
|
222
|
-
> "Beginning is easy - continuing is hard." - Japanese Proverb
|
|
223
|
-
|
|
224
|
-
Walk the way of Bushido. Practice with Discipline. Build with Honor.
|
|
225
|
-
|
|
226
|
-
## Links
|
|
227
|
-
|
|
228
|
-
- [Han Marketplace](https://han.guru)
|
|
229
|
-
- [GitHub](https://github.com/thebushidocollective/han)
|
|
230
|
-
- [The Bushido Collective](https://thebushido.co)
|
|
231
|
-
|
|
232
|
-
## License
|
|
233
|
-
|
|
234
|
-
MIT
|
package/bin.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { execFileSync } = require('child_process');
|
|
3
|
+
const { existsSync } = require('fs');
|
|
4
|
+
const { join } = require('path');
|
|
5
|
+
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
const arch = process.arch;
|
|
8
|
+
|
|
9
|
+
// Map Node.js platform/arch to our package names
|
|
10
|
+
const platformMap = {
|
|
11
|
+
'darwin-arm64': 'darwin-arm64',
|
|
12
|
+
'darwin-x64': 'darwin-x64',
|
|
13
|
+
'linux-arm64': 'linux-arm64',
|
|
14
|
+
'linux-x64': 'linux-x64',
|
|
15
|
+
'win32-x64': 'win32-x64',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const key = `${platform}-${arch}`;
|
|
19
|
+
const pkgName = platformMap[key];
|
|
20
|
+
|
|
21
|
+
if (!pkgName) {
|
|
22
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Try to find the binary in node_modules
|
|
27
|
+
const binName = platform === 'win32' ? 'han.exe' : 'han';
|
|
28
|
+
const paths = [
|
|
29
|
+
// Installed as dependency
|
|
30
|
+
join(__dirname, '..', '@thebushidocollective', `han-${pkgName}`, binName),
|
|
31
|
+
// npx cache location
|
|
32
|
+
join(__dirname, 'node_modules', '@thebushidocollective', `han-${pkgName}`, binName),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
let binPath = paths.find(p => existsSync(p));
|
|
36
|
+
|
|
37
|
+
if (!binPath) {
|
|
38
|
+
// Try to install the platform package on-demand
|
|
39
|
+
try {
|
|
40
|
+
const pkg = `@thebushidocollective/han-${pkgName}`;
|
|
41
|
+
console.error(`Installing ${pkg}...`);
|
|
42
|
+
execFileSync('npm', ['install', '--no-save', pkg], {
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
cwd: __dirname
|
|
45
|
+
});
|
|
46
|
+
binPath = join(__dirname, 'node_modules', '@thebushidocollective', `han-${pkgName}`, binName);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error(`Failed to install platform package: ${e.message}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!existsSync(binPath)) {
|
|
54
|
+
console.error(`Binary not found at ${binPath}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Execute the binary with all arguments
|
|
59
|
+
try {
|
|
60
|
+
execFileSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
process.exit(e.status || 1);
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,78 +1,29 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"keywords": [
|
|
30
|
-
"bushido",
|
|
31
|
-
"validation",
|
|
32
|
-
"hooks",
|
|
33
|
-
"claude",
|
|
34
|
-
"claude-code",
|
|
35
|
-
"testing",
|
|
36
|
-
"ci"
|
|
37
|
-
],
|
|
38
|
-
"author": "The Bushido Collective",
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
"engines": {
|
|
41
|
-
"node": ">=20"
|
|
42
|
-
},
|
|
43
|
-
"files": [
|
|
44
|
-
"bin/",
|
|
45
|
-
"schemas/",
|
|
46
|
-
"README.md"
|
|
47
|
-
],
|
|
48
|
-
"optionalDependencies": {
|
|
49
|
-
"@thebushidocollective/han-darwin-arm64": "1.56.4",
|
|
50
|
-
"@thebushidocollective/han-darwin-x64": "1.56.4",
|
|
51
|
-
"@thebushidocollective/han-linux-arm64": "1.56.4",
|
|
52
|
-
"@thebushidocollective/han-linux-x64": "1.56.4",
|
|
53
|
-
"@thebushidocollective/han-win32-x64": "1.56.4"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@anthropic-ai/claude-agent-sdk": "0.1.50",
|
|
57
|
-
"@biomejs/biome": "2.3.7",
|
|
58
|
-
"@types/marked-terminal": "6.1.1",
|
|
59
|
-
"@types/node": "24.10.1",
|
|
60
|
-
"@types/react": "19.2.6",
|
|
61
|
-
"@types/sentiment": "^5.0.4",
|
|
62
|
-
"commander": "14.0.2",
|
|
63
|
-
"dotenv": "^17.2.3",
|
|
64
|
-
"esbuild": "^0.27.0",
|
|
65
|
-
"ink": "6.5.1",
|
|
66
|
-
"ink-select-input": "^6.2.0",
|
|
67
|
-
"ink-spinner": "5.0.0",
|
|
68
|
-
"ink-text-input": "^6.0.0",
|
|
69
|
-
"marked-terminal": "7.3.0",
|
|
70
|
-
"react": "19.2.0",
|
|
71
|
-
"react-devtools-core": "^6.1.5",
|
|
72
|
-
"typescript": "5.9.3",
|
|
73
|
-
"yaml": "^2.8.1"
|
|
74
|
-
},
|
|
75
|
-
"dependencies": {
|
|
76
|
-
"sentiment": "^5.0.2"
|
|
77
|
-
}
|
|
2
|
+
"name": "@thebushidocollective/han",
|
|
3
|
+
"version": "3.4.2",
|
|
4
|
+
"description": "CLI for installing and managing curated Claude Code plugins from the Han marketplace",
|
|
5
|
+
"homepage": "https://han.guru",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TheBushidoCollective/han"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"han": "bin.js"
|
|
13
|
+
},
|
|
14
|
+
"optionalDependencies": {
|
|
15
|
+
"@thebushidocollective/han-darwin-arm64": "3.4.2",
|
|
16
|
+
"@thebushidocollective/han-darwin-x64": "3.4.2",
|
|
17
|
+
"@thebushidocollective/han-linux-arm64": "3.4.2",
|
|
18
|
+
"@thebushidocollective/han-linux-x64": "3.4.2",
|
|
19
|
+
"@thebushidocollective/han-win32-x64": "3.4.2"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"claude",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"plugins",
|
|
25
|
+
"mcp",
|
|
26
|
+
"han",
|
|
27
|
+
"bushido"
|
|
28
|
+
]
|
|
78
29
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 The Bushido Collective
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/bin/han.js
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
|
|
9
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
|
|
11
|
-
// Self-repair: detect stale npx cache and fix it
|
|
12
|
-
async function selfRepairIfNeeded() {
|
|
13
|
-
// Skip if already in repair mode (prevent infinite loops)
|
|
14
|
-
if (process.env.HAN_SELF_REPAIR === "1") {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Skip for version/help commands (let user see current version)
|
|
19
|
-
const args = process.argv.slice(2);
|
|
20
|
-
if (
|
|
21
|
-
args.includes("--version") ||
|
|
22
|
-
args.includes("-V") ||
|
|
23
|
-
args.includes("--help") ||
|
|
24
|
-
args.includes("-h") ||
|
|
25
|
-
args.length === 0
|
|
26
|
-
) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Only run for npx invocations (check if in _npx cache directory)
|
|
31
|
-
const scriptPath = fileURLToPath(import.meta.url);
|
|
32
|
-
if (!scriptPath.includes("_npx")) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
// Get current version
|
|
38
|
-
const packageJsonPath = join(__dirname, "..", "package.json");
|
|
39
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
40
|
-
const currentVersion = packageJson.version;
|
|
41
|
-
|
|
42
|
-
// Fetch latest version from npm (with short timeout)
|
|
43
|
-
const controller = new AbortController();
|
|
44
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
45
|
-
|
|
46
|
-
const response = await fetch(
|
|
47
|
-
"https://registry.npmjs.org/@thebushidocollective/han",
|
|
48
|
-
{ signal: controller.signal },
|
|
49
|
-
);
|
|
50
|
-
clearTimeout(timeout);
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const data = await response.json();
|
|
57
|
-
const latestVersion = data["dist-tags"]?.latest;
|
|
58
|
-
|
|
59
|
-
if (!latestVersion || currentVersion === latestVersion) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Compare versions (simple semver comparison)
|
|
64
|
-
const current = currentVersion.split(".").map(Number);
|
|
65
|
-
const latest = latestVersion.split(".").map(Number);
|
|
66
|
-
|
|
67
|
-
const isBehind =
|
|
68
|
-
current[0] < latest[0] ||
|
|
69
|
-
(current[0] === latest[0] && current[1] < latest[1]) ||
|
|
70
|
-
(current[0] === latest[0] &&
|
|
71
|
-
current[1] === latest[1] &&
|
|
72
|
-
current[2] < latest[2]);
|
|
73
|
-
|
|
74
|
-
if (!isBehind) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.error(
|
|
79
|
-
`\x1b[33m⚠ Stale npx cache detected (v${currentVersion} < v${latestVersion}), repairing...\x1b[0m`,
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// Clear npx cache
|
|
83
|
-
try {
|
|
84
|
-
execSync("npx clear-npx-cache", { stdio: "pipe" });
|
|
85
|
-
} catch {
|
|
86
|
-
// Try manual clear if clear-npx-cache not available
|
|
87
|
-
const { homedir } = await import("node:os");
|
|
88
|
-
const npxCachePath = join(homedir(), ".npm", "_npx");
|
|
89
|
-
if (existsSync(npxCachePath)) {
|
|
90
|
-
const { rmSync } = await import("node:fs");
|
|
91
|
-
rmSync(npxCachePath, { recursive: true, force: true });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Re-exec with the exact version we fetched
|
|
96
|
-
const result = spawnSync(
|
|
97
|
-
"npx",
|
|
98
|
-
["-y", `@thebushidocollective/han@${latestVersion}`, ...args],
|
|
99
|
-
{
|
|
100
|
-
stdio: "inherit",
|
|
101
|
-
env: { ...process.env, HAN_SELF_REPAIR: "1" },
|
|
102
|
-
shell: true,
|
|
103
|
-
},
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
process.exit(result.status ?? 0);
|
|
107
|
-
} catch {
|
|
108
|
-
// Silently continue if self-repair fails (network issues, etc.)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await selfRepairIfNeeded();
|
|
113
|
-
const require = createRequire(import.meta.url);
|
|
114
|
-
|
|
115
|
-
// Platform package mapping
|
|
116
|
-
const platformPackages = {
|
|
117
|
-
"darwin-arm64": "@thebushidocollective/han-darwin-arm64",
|
|
118
|
-
"darwin-x64": "@thebushidocollective/han-darwin-x64",
|
|
119
|
-
"linux-x64": "@thebushidocollective/han-linux-x64",
|
|
120
|
-
"linux-arm64": "@thebushidocollective/han-linux-arm64",
|
|
121
|
-
"win32-x64": "@thebushidocollective/han-win32-x64",
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
function getPlatformKey() {
|
|
125
|
-
return `${process.platform}-${process.arch}`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function tryRunBinary() {
|
|
129
|
-
const platformKey = getPlatformKey();
|
|
130
|
-
const packageName = platformPackages[platformKey];
|
|
131
|
-
|
|
132
|
-
if (!packageName) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
// Try to resolve the platform package
|
|
138
|
-
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
139
|
-
const pkgDir = dirname(pkgPath);
|
|
140
|
-
const binaryName = process.platform === "win32" ? "han.exe" : "han";
|
|
141
|
-
const binaryPath = join(pkgDir, binaryName);
|
|
142
|
-
|
|
143
|
-
if (!existsSync(binaryPath)) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Run the binary
|
|
148
|
-
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
149
|
-
stdio: "inherit",
|
|
150
|
-
windowsHide: true,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (result.error) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
process.exit(result.status ?? 0);
|
|
158
|
-
} catch {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Retry logic for CDN propagation delays
|
|
164
|
-
async function waitForPackageAvailability(packageName, maxRetries = 5) {
|
|
165
|
-
const baseDelay = 2000; // 2 seconds
|
|
166
|
-
|
|
167
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
168
|
-
try {
|
|
169
|
-
const controller = new AbortController();
|
|
170
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
171
|
-
|
|
172
|
-
const response = await fetch(
|
|
173
|
-
`https://registry.npmjs.org/${packageName}`,
|
|
174
|
-
{ signal: controller.signal },
|
|
175
|
-
);
|
|
176
|
-
clearTimeout(timeout);
|
|
177
|
-
|
|
178
|
-
if (response.ok) {
|
|
179
|
-
const data = await response.json();
|
|
180
|
-
if (data["dist-tags"]?.latest) {
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
} catch {
|
|
185
|
-
// Network error, continue retrying
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (attempt < maxRetries) {
|
|
189
|
-
const delay = baseDelay * 2 ** (attempt - 1); // Exponential backoff
|
|
190
|
-
console.error(
|
|
191
|
-
`\x1b[33m⏳ Waiting for package availability (attempt ${attempt}/${maxRetries})...\x1b[0m`,
|
|
192
|
-
);
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Run binary - no JS fallback in production
|
|
201
|
-
if (!tryRunBinary()) {
|
|
202
|
-
const platformKey = getPlatformKey();
|
|
203
|
-
const packageName = platformPackages[platformKey];
|
|
204
|
-
|
|
205
|
-
if (!packageName) {
|
|
206
|
-
console.error(`\x1b[31mError: Unsupported platform: ${platformKey}\x1b[0m`);
|
|
207
|
-
console.error(
|
|
208
|
-
"Supported platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64",
|
|
209
|
-
);
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Check if this might be a CDN propagation issue and retry
|
|
214
|
-
const scriptPath = fileURLToPath(import.meta.url);
|
|
215
|
-
if (scriptPath.includes("_npx") && !process.env.HAN_NO_RETRY) {
|
|
216
|
-
console.error(
|
|
217
|
-
`\x1b[33m⚠ Platform binary not found, checking for CDN propagation delay...\x1b[0m`,
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
const isAvailable = await waitForPackageAvailability(packageName);
|
|
221
|
-
|
|
222
|
-
if (isAvailable) {
|
|
223
|
-
// Clear cache and retry once more
|
|
224
|
-
try {
|
|
225
|
-
execSync("npx clear-npx-cache", { stdio: "pipe" });
|
|
226
|
-
} catch {
|
|
227
|
-
const { homedir } = await import("node:os");
|
|
228
|
-
const npxCachePath = join(homedir(), ".npm", "_npx");
|
|
229
|
-
if (existsSync(npxCachePath)) {
|
|
230
|
-
const { rmSync } = await import("node:fs");
|
|
231
|
-
rmSync(npxCachePath, { recursive: true, force: true });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
console.error(`\x1b[32m✓ Package available, retrying...\x1b[0m`);
|
|
236
|
-
|
|
237
|
-
const args = process.argv.slice(2);
|
|
238
|
-
const result = spawnSync(
|
|
239
|
-
"npx",
|
|
240
|
-
["-y", "@thebushidocollective/han@latest", ...args],
|
|
241
|
-
{
|
|
242
|
-
stdio: "inherit",
|
|
243
|
-
env: { ...process.env, HAN_NO_RETRY: "1" },
|
|
244
|
-
shell: true,
|
|
245
|
-
},
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
process.exit(result.status ?? 0);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
console.error(
|
|
253
|
-
`\x1b[31mError: Platform binary not found for ${platformKey}\x1b[0m`,
|
|
254
|
-
);
|
|
255
|
-
console.error(`Expected package: ${packageName}`);
|
|
256
|
-
console.error("\nTry reinstalling:");
|
|
257
|
-
console.error(" npm cache clean --force");
|
|
258
|
-
console.error(" npx clear-npx-cache");
|
|
259
|
-
console.error(" han@latest --version");
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://han.guru/schemas/han-config-override.schema.json",
|
|
4
|
-
"title": "Han User Override Configuration",
|
|
5
|
-
"description": "Configuration schema for han-config.yml files that override plugin defaults",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"additionalProperties": {
|
|
8
|
-
"$ref": "#/definitions/pluginOverrides"
|
|
9
|
-
},
|
|
10
|
-
"definitions": {
|
|
11
|
-
"pluginOverrides": {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"description": "Overrides for a specific plugin (keyed by plugin name, e.g., 'jutsu-biome')",
|
|
14
|
-
"additionalProperties": {
|
|
15
|
-
"$ref": "#/definitions/hookOverride"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"hookOverride": {
|
|
19
|
-
"type": "object",
|
|
20
|
-
"description": "Override settings for a specific hook",
|
|
21
|
-
"properties": {
|
|
22
|
-
"enabled": {
|
|
23
|
-
"type": "boolean",
|
|
24
|
-
"description": "Whether this hook is enabled for this directory. Set to false to disable.",
|
|
25
|
-
"default": true
|
|
26
|
-
},
|
|
27
|
-
"command": {
|
|
28
|
-
"type": "string",
|
|
29
|
-
"description": "Override the command to execute for this hook"
|
|
30
|
-
},
|
|
31
|
-
"if_changed": {
|
|
32
|
-
"type": "array",
|
|
33
|
-
"items": {
|
|
34
|
-
"type": "string"
|
|
35
|
-
},
|
|
36
|
-
"description": "Additional glob patterns for change detection. These patterns are merged with (added to) the plugin's ifChanged patterns."
|
|
37
|
-
},
|
|
38
|
-
"idle_timeout": {
|
|
39
|
-
"oneOf": [
|
|
40
|
-
{
|
|
41
|
-
"type": "integer",
|
|
42
|
-
"minimum": 1,
|
|
43
|
-
"description": "Override the idle timeout in milliseconds"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"type": "boolean",
|
|
47
|
-
"const": false,
|
|
48
|
-
"description": "Set to false to disable idle timeout checking"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"type": "integer",
|
|
52
|
-
"const": 0,
|
|
53
|
-
"description": "Set to 0 to disable idle timeout checking"
|
|
54
|
-
}
|
|
55
|
-
],
|
|
56
|
-
"description": "Override the idle timeout. Set to a number (milliseconds), false, or 0 to disable."
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
"additionalProperties": false
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://han.guru/schemas/han-config.schema.json",
|
|
4
|
-
"title": "Han Plugin Configuration",
|
|
5
|
-
"description": "Configuration schema for han-config.json files in Han plugins",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"properties": {
|
|
8
|
-
"hooks": {
|
|
9
|
-
"type": "object",
|
|
10
|
-
"description": "Hook definitions for the plugin",
|
|
11
|
-
"additionalProperties": {
|
|
12
|
-
"$ref": "#/definitions/hookDefinition"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"required": ["hooks"],
|
|
17
|
-
"additionalProperties": false,
|
|
18
|
-
"definitions": {
|
|
19
|
-
"hookDefinition": {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"description": "Definition for a single hook",
|
|
22
|
-
"properties": {
|
|
23
|
-
"command": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"description": "The command to execute for this hook"
|
|
26
|
-
},
|
|
27
|
-
"dirsWith": {
|
|
28
|
-
"type": "array",
|
|
29
|
-
"items": {
|
|
30
|
-
"type": "string"
|
|
31
|
-
},
|
|
32
|
-
"description": "Glob patterns for marker files. The hook runs in directories containing these files."
|
|
33
|
-
},
|
|
34
|
-
"dirTest": {
|
|
35
|
-
"type": "string",
|
|
36
|
-
"description": "Shell command that must exit 0 for a directory to be included"
|
|
37
|
-
},
|
|
38
|
-
"ifChanged": {
|
|
39
|
-
"type": "array",
|
|
40
|
-
"items": {
|
|
41
|
-
"type": "string"
|
|
42
|
-
},
|
|
43
|
-
"description": "Glob patterns relative to each target directory. When --cache is enabled, the hook will only run if files matching these patterns have changed."
|
|
44
|
-
},
|
|
45
|
-
"idleTimeout": {
|
|
46
|
-
"type": "integer",
|
|
47
|
-
"minimum": 0,
|
|
48
|
-
"description": "Maximum time in milliseconds to wait for output before considering the hook as hanging. If no output is received within this period, the hook will be terminated and reported as failed."
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"required": ["command"],
|
|
52
|
-
"additionalProperties": false
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|