@thebushidocollective/han 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
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/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # @thebushidocollective/han
2
+
3
+ Monorepo validation tool and Claude Code plugin installer for Han (bushido) plugins.
4
+
5
+ ## Installation
6
+
7
+ ### Global Installation
8
+
9
+ ```bash
10
+ npm install -g @thebushidocollective/han
11
+ ```
12
+
13
+ ### Use with npx (No Installation)
14
+
15
+ ```bash
16
+ npx -y @thebushidocollective/han validate <command>
17
+ ```
18
+
19
+ ### Use in Claude Code Hooks
20
+
21
+ ```json
22
+ {
23
+ "type": "command",
24
+ "command": "npx -y @thebushidocollective/han validate --fail-fast --dirs-with Gemfile bundle exec rspec"
25
+ }
26
+ ```
27
+
28
+ ## Commands
29
+
30
+ ### install
31
+
32
+ Intelligently analyze your codebase and configure Claude Code with appropriate Han plugins using the Claude Agent SDK.
33
+
34
+ ```bash
35
+ npx @thebushidocollective/han install
36
+ ```
37
+
38
+ **How it works:**
39
+
40
+ - Spawns a Claude agent to analyze your codebase
41
+ - Uses Glob, Grep, and Read tools to understand your project
42
+ - Detects languages, frameworks, and testing tools
43
+ - Recommends appropriate Han plugins based on actual code, not just file patterns
44
+ - Displays real-time progress with a beautiful Ink-powered terminal UI
45
+ - Configures `.claude/settings.json` automatically
46
+
47
+ **What it detects:**
48
+
49
+ - Programming languages (TypeScript, Python, Go, Rust, Ruby, etc.)
50
+ - Frontend frameworks (React, Vue, Angular, Next.js, etc.)
51
+ - Backend frameworks (NestJS, Django, FastAPI, Rails, etc.)
52
+ - Testing frameworks (Jest, Pytest, RSpec, etc.)
53
+ - GraphQL implementations
54
+ - Monorepo tools (Nx, Turborepo, Lerna)
55
+
56
+ **After installation:**
57
+ Restart Claude Code to load the new plugins.
58
+
59
+ ### uninstall
60
+
61
+ Remove all Han plugins and marketplace configuration from Claude Code.
62
+
63
+ ```bash
64
+ npx @thebushidocollective/han uninstall
65
+ ```
66
+
67
+ ### validate
68
+
69
+ Run validation commands across monorepo packages.
70
+
71
+ ```bash
72
+ han validate [options] <command>
73
+ ```
74
+
75
+ #### Options
76
+
77
+ - `--fail-fast` - Stop on first failure
78
+ - `--dirs-with <file>` - Only run in directories containing the specified file
79
+
80
+ #### Examples
81
+
82
+ Run RSpec tests in all directories with a Gemfile:
83
+
84
+ ```bash
85
+ han validate --fail-fast --dirs-with Gemfile bundle exec rspec
86
+ ```
87
+
88
+ Run npm test in all directories with package.json:
89
+
90
+ ```bash
91
+ han validate --dirs-with package.json npm test
92
+ ```
93
+
94
+ Run go test in all directories:
95
+
96
+ ```bash
97
+ han validate go test ./...
98
+ ```
99
+
100
+ ## Exit Codes
101
+
102
+ - `0` - All validations passed
103
+ - `1` - Invalid usage or no directories found
104
+ - `2` - One or more validations failed
105
+
106
+ ## How It Works
107
+
108
+ 1. **Auto-discovers directories** - Uses git to find tracked directories,
109
+ falls back to filesystem scan
110
+ 2. **Filters by marker files** - Only runs in directories containing
111
+ the specified file (e.g., `Gemfile`, `package.json`)
112
+ 3. **Runs command in each** - Executes the command in each matching
113
+ directory
114
+ 4. **Reports failures** - Shows which directories failed with clear
115
+ error messages
116
+ 5. **Exits with code 2** - Hard failure prevents Claude from bypassing
117
+ validation
118
+
119
+ ## Use Cases
120
+
121
+ ### Monorepo Testing
122
+
123
+ Run tests across all packages in a monorepo:
124
+
125
+ ```bash
126
+ # JavaScript/TypeScript packages
127
+ npx -y @thebushidocollective/han validate --dirs-with package.json npm test
128
+
129
+ # Ruby packages
130
+ npx -y @thebushidocollective/han validate --dirs-with Gemfile bundle exec rspec
131
+
132
+ # Go modules
133
+ npx -y @thebushidocollective/han validate --dirs-with go.mod go test ./...
134
+
135
+ # Python packages
136
+ npx -y @thebushidocollective/han validate --dirs-with pyproject.toml pytest
137
+ ```
138
+
139
+ ### Claude Code Hooks
140
+
141
+ Perfect for enforcing quality in Claude Code plugins:
142
+
143
+ ```json
144
+ {
145
+ "hooks": {
146
+ "Stop": [
147
+ {
148
+ "hooks": [
149
+ {
150
+ "type": "command",
151
+ "command": "npx -y @thebushidocollective/han validate --fail-fast --dirs-with package.json npm test || (echo 'Please fix failing tests before continuing:\\n'; exit 2)"
152
+ }
153
+ ]
154
+ }
155
+ ]
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Development
161
+
162
+ This package is written in TypeScript and uses React (via Ink) for the terminal UI.
163
+
164
+ ### Prerequisites
165
+
166
+ - Node.js >= 24
167
+ - TypeScript 5.9+
168
+
169
+ ### Building
170
+
171
+ The build process compiles TypeScript to ES modules:
172
+
173
+ ```bash
174
+ npm run build # Compile TypeScript to dist/
175
+ npm run typecheck # Type-check without emitting
176
+ ```
177
+
178
+ The compiled output is in `dist/` and is what gets published to npm.
179
+
180
+ ### Testing
181
+
182
+ Tests run against the compiled JavaScript:
183
+
184
+ ```bash
185
+ npm test # Run tests from dist/test/
186
+ ```
187
+
188
+ ### Linting
189
+
190
+ ```bash
191
+ npm run lint # Check for issues
192
+ npm run lint:fix # Auto-fix issues
193
+ ```
194
+
195
+ ### UI Development
196
+
197
+ The install command uses [Ink](https://github.com/vadimdemedes/ink) for a rich terminal UI experience. The UI components are in:
198
+
199
+ - `lib/install-progress.tsx` - Main UI component
200
+ - `lib/install.ts` - Integration with Claude Agent SDK
201
+
202
+ ## Contributing
203
+
204
+ See [RELEASING.md](RELEASING.md) for information on publishing new
205
+ versions.
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { DetectPluginsCallbacks } from './install.js';
3
+ interface InstallProgressProps {
4
+ detectPlugins: (callbacks: DetectPluginsCallbacks) => Promise<void>;
5
+ onInstallComplete: (plugins: string[]) => void;
6
+ onInstallError: (error: Error) => void;
7
+ }
8
+ export declare const InstallProgress: React.FC<InstallProgressProps>;
9
+ export {};
10
+ //# sourceMappingURL=install-progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-progress.d.ts","sourceRoot":"","sources":["../../lib/install-progress.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAe,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAGxE,UAAU,oBAAoB;IAC5B,aAAa,EAAE,CAAC,SAAS,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC/C,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACxC;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA8M1D,CAAC"}
@@ -0,0 +1,114 @@
1
+ import { Box, Text } from 'ink';
2
+ import Spinner from 'ink-spinner';
3
+ import React, { useEffect, useState } from 'react';
4
+ import { MarkdownWrapper } from './markdown-wrapper.js';
5
+ export const InstallProgress = ({ detectPlugins, onInstallComplete, onInstallError, }) => {
6
+ const [phase, setPhase] = useState('analyzing');
7
+ const [plugins, setPlugins] = useState([]);
8
+ const [fullText, setFullText] = useState('');
9
+ const [currentTool, setCurrentTool] = useState(null);
10
+ const [error, setError] = useState(null);
11
+ const [toolsUsed, setToolsUsed] = useState(new Set());
12
+ useEffect(() => {
13
+ const callbacks = {
14
+ onUpdate: (update) => {
15
+ if (update.type === 'text') {
16
+ setFullText((prev) => prev + update.content);
17
+ }
18
+ else if (update.type === 'tool' && update.toolName) {
19
+ setCurrentTool(update.toolName);
20
+ setToolsUsed((prev) => new Set([...prev, update.toolName]));
21
+ }
22
+ },
23
+ onComplete: (detectedPlugins, analysisText) => {
24
+ setPlugins(detectedPlugins);
25
+ setFullText(analysisText);
26
+ setCurrentTool(null);
27
+ setPhase('analyzed');
28
+ setTimeout(() => {
29
+ setPhase('installing');
30
+ setTimeout(() => {
31
+ setPhase('complete');
32
+ onInstallComplete(detectedPlugins);
33
+ }, 500);
34
+ }, 1000);
35
+ },
36
+ onError: (err) => {
37
+ setError(err.message);
38
+ setPhase('error');
39
+ onInstallError(err);
40
+ },
41
+ };
42
+ detectPlugins(callbacks);
43
+ }, [detectPlugins, onInstallComplete, onInstallError]);
44
+ const getToolEmoji = (toolName) => {
45
+ const emojiMap = {
46
+ web_fetch: '🌐',
47
+ read_file: '📄',
48
+ glob: '🔍',
49
+ grep: '🔎',
50
+ };
51
+ return emojiMap[toolName] || '🔧';
52
+ };
53
+ return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
54
+ React.createElement(Box, { marginBottom: 1 },
55
+ React.createElement(Text, { bold: true, color: "cyan" }, "\uD83E\uDD16 Han Plugin Installer")),
56
+ phase === 'analyzing' && (React.createElement(Box, { flexDirection: "column" },
57
+ React.createElement(Box, { marginBottom: 1 },
58
+ React.createElement(Text, { color: "yellow" },
59
+ React.createElement(Spinner, { type: "dots" }),
60
+ " Analyzing codebase...")),
61
+ currentTool && (React.createElement(Box, { marginBottom: 1 },
62
+ React.createElement(Text, { color: "blue" },
63
+ getToolEmoji(currentTool),
64
+ " ",
65
+ currentTool))),
66
+ toolsUsed.size > 0 && (React.createElement(Box, { marginBottom: 1 },
67
+ React.createElement(Text, { dimColor: true },
68
+ "Tools used: ",
69
+ Array.from(toolsUsed).join(', ')))),
70
+ fullText && (React.createElement(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "gray", flexDirection: "column" },
71
+ React.createElement(Box, { marginBottom: 1 },
72
+ React.createElement(Text, { dimColor: true, bold: true },
73
+ React.createElement(Spinner, { type: "star" }),
74
+ " Agent thinking:")),
75
+ React.createElement(MarkdownWrapper, null, fullText))))),
76
+ (phase === 'analyzed' ||
77
+ phase === 'installing' ||
78
+ phase === 'complete') && (React.createElement(Box, { flexDirection: "column" },
79
+ React.createElement(Box, { marginBottom: 1 },
80
+ React.createElement(Text, { color: "green" }, "\u2705 Analysis complete")),
81
+ fullText && (React.createElement(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "cyan", flexDirection: "column" },
82
+ React.createElement(Box, { marginBottom: 1 },
83
+ React.createElement(Text, { bold: true, color: "cyan" }, "\uD83D\uDCCB Agent Analysis:")),
84
+ React.createElement(MarkdownWrapper, null, fullText))),
85
+ plugins.length > 0 && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
86
+ React.createElement(Text, { bold: true, color: "green" }, "\u2728 Adding recommended plugins:"),
87
+ plugins.sort().map((plugin) => (React.createElement(Box, { key: plugin, marginLeft: 2 },
88
+ React.createElement(Text, null,
89
+ "\u2022 ",
90
+ plugin)))))),
91
+ phase === 'installing' && (React.createElement(Box, { marginTop: 1 },
92
+ React.createElement(Text, { color: "yellow" },
93
+ React.createElement(Spinner, { type: "dots" }),
94
+ " Updating Claude Code settings..."))),
95
+ phase === 'complete' && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
96
+ React.createElement(Box, { marginBottom: 1 },
97
+ React.createElement(Text, { color: "green" }, "\u2705 Updated Claude Code settings")),
98
+ React.createElement(Box, { marginBottom: 1 },
99
+ React.createElement(Text, { color: "green", bold: true }, "\u2705 Installation complete!")),
100
+ React.createElement(Box, null,
101
+ React.createElement(Text, { color: "blue" }, "\uD83D\uDCA1 Restart Claude Code to load the new plugins.")))))),
102
+ phase === 'error' && (React.createElement(Box, { flexDirection: "column" },
103
+ React.createElement(Box, { marginBottom: 1 },
104
+ React.createElement(Text, { color: "red", bold: true }, "\u274C Error")),
105
+ React.createElement(Box, { marginBottom: 1 },
106
+ React.createElement(Text, { color: "red" }, error)),
107
+ fullText && (React.createElement(Box, { marginBottom: 1, paddingX: 1, borderStyle: "round", borderColor: "red", flexDirection: "column" },
108
+ React.createElement(Text, { bold: true, color: "red" }, "Partial Analysis:"),
109
+ React.createElement(Box, null,
110
+ React.createElement(MarkdownWrapper, null, fullText)))),
111
+ React.createElement(Box, { marginTop: 1 },
112
+ React.createElement(Text, { color: "yellow" }, "\u26A0\uFE0F Falling back to installing core bushido plugin..."))))));
113
+ };
114
+ //# sourceMappingURL=install-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-progress.js","sourceRoot":"","sources":["../../lib/install-progress.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface AgentUpdate {
2
+ type: 'text' | 'tool';
3
+ content: string;
4
+ toolName?: string;
5
+ }
6
+ export interface DetectPluginsCallbacks {
7
+ onUpdate: (update: AgentUpdate) => void;
8
+ onComplete: (plugins: string[], fullText: string) => void;
9
+ onError: (error: Error) => void;
10
+ }
11
+ /**
12
+ * SDK-based install command with Ink UI
13
+ */
14
+ export declare function install(): Promise<void>;
15
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../lib/install.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AA4KD;;GAEG;AACH,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAgC7C"}
@@ -0,0 +1,191 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { query } from '@anthropic-ai/claude-agent-sdk';
4
+ import { render } from 'ink';
5
+ import React from 'react';
6
+ const HAN_MARKETPLACE_REPO = 'thebushidocollective/han';
7
+ function getClaudeSettingsPath() {
8
+ const rootDir = process.cwd();
9
+ return join(rootDir, '.claude', 'settings.json');
10
+ }
11
+ function ensureClaudeDirectory() {
12
+ const rootDir = process.cwd();
13
+ const claudeDir = join(rootDir, '.claude');
14
+ if (!existsSync(claudeDir)) {
15
+ mkdirSync(claudeDir, { recursive: true });
16
+ }
17
+ }
18
+ function readOrCreateSettings() {
19
+ const settingsPath = getClaudeSettingsPath();
20
+ if (existsSync(settingsPath)) {
21
+ try {
22
+ return JSON.parse(readFileSync(settingsPath, 'utf8'));
23
+ }
24
+ catch (_error) {
25
+ console.error('Error reading settings.json, creating new one');
26
+ return {};
27
+ }
28
+ }
29
+ return {};
30
+ }
31
+ function writeSettings(settings) {
32
+ const settingsPath = getClaudeSettingsPath();
33
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
34
+ }
35
+ /**
36
+ * Use Claude Agent SDK to intelligently analyze codebase and recommend plugins
37
+ */
38
+ async function detectPluginsWithAgent(callbacks) {
39
+ const agentPrompt = `You are a Han plugin installer assistant. Your goal is to analyze the current codebase and recommend appropriate Claude Code plugins from the Han marketplace.
40
+
41
+ Available Plugin Categories:
42
+ - buki-* (武器 weapons): Skills for specific technologies (e.g., buki-typescript, buki-react, buki-go)
43
+ - do-* (道 disciplines): Specialized agents for development practices
44
+ - sensei-* (先生 teachers): MCP servers for external integrations
45
+ - bushido: Core quality and testing principles
46
+
47
+ Your Task:
48
+ 1. Use Glob to discover what files exist in the repository
49
+ 2. Use Grep to search for framework/library usage patterns
50
+ 3. Read key configuration files (package.json, Cargo.toml, go.mod, etc.)
51
+ 4. Get the latest han plugins from the marketplace https://raw.githubusercontent.com/TheBushidoCollective/han/refs/heads/main/.claude-plugin/marketplace.json
52
+ 5. Recommend Han plugins that match the detected technologies
53
+ 6. Return your recommendations as a JSON array of plugin names
54
+
55
+ Focus on detecting:
56
+ - Programming languages (TypeScript, Python, Go, Rust, Ruby, etc.)
57
+ - Frontend frameworks (React, Vue, Angular, Next.js, etc.)
58
+ - Backend frameworks (NestJS, Django, FastAPI, Rails, etc.)
59
+ - Testing frameworks (Jest, Pytest, RSpec, etc.)
60
+ - GraphQL implementations
61
+ - Monorepo tools (Nx, Turborepo, Lerna)
62
+
63
+ ALWAYS add the bushido plugin!
64
+
65
+ Return ONLY a JSON array of recommended plugin names, like:
66
+ ["bushido", "buki-typescript", "buki-react", "buki-jest"]
67
+
68
+ ULTRATHINK and Analyze this codebase and recommend appropriate Han plugins.`;
69
+ // Define allowed tools - only read-only operations
70
+ const allowedTools = ['web_fetch', 'read_file', 'glob', 'grep'];
71
+ const agent = query({
72
+ prompt: agentPrompt,
73
+ options: {
74
+ model: 'claude-sonnet-4-5-20250929',
75
+ includePartialMessages: true,
76
+ allowedTools,
77
+ permissionMode: 'bypassPermissions',
78
+ },
79
+ });
80
+ let responseContent = '';
81
+ try {
82
+ // Collect all messages from the agent with live updates
83
+ for await (const sdkMessage of agent) {
84
+ if (sdkMessage.type === 'assistant' && sdkMessage.message.content) {
85
+ for (const block of sdkMessage.message.content) {
86
+ if (block.type === 'text') {
87
+ // Send text updates
88
+ callbacks.onUpdate({ type: 'text', content: block.text });
89
+ responseContent += block.text;
90
+ }
91
+ else if (block.type === 'tool_use') {
92
+ // Send tool usage updates
93
+ callbacks.onUpdate({
94
+ type: 'tool',
95
+ content: `Using ${block.name}`,
96
+ toolName: block.name,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ // Extract plugin recommendations from agent response
103
+ const plugins = parsePluginRecommendations(responseContent);
104
+ const finalPlugins = plugins.length > 0 ? plugins : ['bushido'];
105
+ callbacks.onComplete(finalPlugins, responseContent);
106
+ }
107
+ catch (error) {
108
+ callbacks.onError(error);
109
+ }
110
+ }
111
+ /**
112
+ * Parse plugin recommendations from agent response
113
+ */
114
+ function parsePluginRecommendations(content) {
115
+ try {
116
+ // Try to find JSON array in the response
117
+ const jsonMatch = content.match(/\[[\s\S]*?\]/);
118
+ if (jsonMatch) {
119
+ const plugins = JSON.parse(jsonMatch[0]);
120
+ if (Array.isArray(plugins)) {
121
+ return plugins.filter((p) => typeof p === 'string');
122
+ }
123
+ }
124
+ // Fallback: look for plugin names mentioned
125
+ const pluginPattern = /(buki-[\w-]+|do-[\w-]+|sensei-[\w-]+|bushido)/g;
126
+ const matches = content.match(pluginPattern);
127
+ if (matches) {
128
+ return [...new Set(matches)];
129
+ }
130
+ return [];
131
+ }
132
+ catch (_error) {
133
+ return [];
134
+ }
135
+ }
136
+ /**
137
+ * Install plugins to Claude settings
138
+ */
139
+ function installPluginsToSettings(plugins) {
140
+ ensureClaudeDirectory();
141
+ const settings = readOrCreateSettings();
142
+ // Add Han marketplace to extraMarketplaces
143
+ if (!settings?.extraKnownMarketplaces?.han) {
144
+ settings.extraKnownMarketplaces = {
145
+ ...settings.extraKnownMarketplaces,
146
+ han: { source: { source: 'github', repo: HAN_MARKETPLACE_REPO } },
147
+ };
148
+ }
149
+ // Add plugins
150
+ for (const plugin of plugins) {
151
+ settings.enabledPlugins = {
152
+ ...settings.enabledPlugins,
153
+ [`${plugin}@han`]: true,
154
+ };
155
+ }
156
+ writeSettings(settings);
157
+ }
158
+ /**
159
+ * SDK-based install command with Ink UI
160
+ */
161
+ export async function install() {
162
+ // Import Ink UI component dynamically to avoid issues with React
163
+ const { InstallProgress } = await import('./install-progress.js');
164
+ let resolveCompletion;
165
+ let rejectCompletion;
166
+ const completionPromise = new Promise((resolve, reject) => {
167
+ resolveCompletion = resolve;
168
+ rejectCompletion = reject;
169
+ });
170
+ const { unmount } = render(React.createElement(InstallProgress, {
171
+ detectPlugins: detectPluginsWithAgent,
172
+ onInstallComplete: (plugins) => {
173
+ installPluginsToSettings(plugins);
174
+ if (resolveCompletion)
175
+ resolveCompletion();
176
+ },
177
+ onInstallError: (error) => {
178
+ if (rejectCompletion)
179
+ rejectCompletion(error);
180
+ },
181
+ }));
182
+ try {
183
+ await completionPromise;
184
+ // Wait a moment for the UI to show completion message
185
+ await new Promise((resolve) => setTimeout(resolve, 1500));
186
+ }
187
+ finally {
188
+ unmount();
189
+ }
190
+ }
191
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../lib/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../lib/main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ const program = new Command();
4
+ program
5
+ .name('han')
6
+ .description("Utilities for The Bushido Collective's Han Code Marketplace")
7
+ .version('1.0.0');
8
+ // Install command
9
+ program
10
+ .command('install')
11
+ .description('Install Han marketplace and plugins')
12
+ .action(async () => {
13
+ try {
14
+ const { install } = await import('./install.js');
15
+ await install();
16
+ process.exit(0);
17
+ }
18
+ catch (error) {
19
+ console.error('Error during installation:', error instanceof Error ? error.message : error);
20
+ process.exit(1);
21
+ }
22
+ });
23
+ // Uninstall command
24
+ program
25
+ .command('uninstall')
26
+ .description('Remove Han marketplace and plugins')
27
+ .action(async () => {
28
+ const { uninstall } = await import('./uninstall.js');
29
+ uninstall();
30
+ process.exit(0);
31
+ });
32
+ // Validate command
33
+ program
34
+ .command('validate')
35
+ .description('Validate directories')
36
+ .option('--fail-fast', 'Stop on first failure')
37
+ .option('--dirs-with <file>', 'Only run in directories containing the specified file')
38
+ .argument('<command...>', 'Command to run in each directory')
39
+ .action(async (commandArgs, options) => {
40
+ const { validate } = await import('./validate.js');
41
+ validate({
42
+ failFast: options.failFast || false,
43
+ dirsWith: options.dirsWith || null,
44
+ command: commandArgs.join(' '),
45
+ });
46
+ });
47
+ program.parse();
48
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../lib/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,KAAK,CAAC;KACX,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,kBAAkB;AAClB,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,qCAAqC,CAAC;KAClD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CACX,4BAA4B,EAC5B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC/C,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,oBAAoB;AACpB,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACrD,SAAS,EAAE,CAAC;IACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,mBAAmB;AACnB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,aAAa,EAAE,uBAAuB,CAAC;KAC9C,MAAM,CACL,oBAAoB,EACpB,uDAAuD,CACxD;KACA,QAAQ,CAAC,cAAc,EAAE,kCAAkC,CAAC;KAC5D,MAAM,CACL,KAAK,EACH,WAAqB,EACrB,OAAkD,EAClD,EAAE;IACF,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IACnD,QAAQ,CAAC;QACP,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QACnC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;QAClC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;KAC/B,CAAC,CAAC;AACL,CAAC,CACF,CAAC;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface MarkdownTextProps {
3
+ children: string;
4
+ }
5
+ /**
6
+ * Simple markdown-like formatter for terminal output
7
+ * Handles basic formatting without requiring heavy dependencies
8
+ */
9
+ export declare const MarkdownText: React.FC<MarkdownTextProps>;
10
+ export {};
11
+ //# sourceMappingURL=markdown-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-text.d.ts","sourceRoot":"","sources":["../../lib/markdown-text.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgHpD,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { Box, Text } from 'ink';
2
+ import React from 'react';
3
+ /**
4
+ * Simple markdown-like formatter for terminal output
5
+ * Handles basic formatting without requiring heavy dependencies
6
+ */
7
+ export const MarkdownText = ({ children }) => {
8
+ const lines = children.split('\n');
9
+ const elements = [];
10
+ for (let i = 0; i < lines.length; i++) {
11
+ const line = lines[i];
12
+ // Headers (# ## ###)
13
+ const headerMatch = line.match(/^(#{1,3})\s+(.+)$/);
14
+ if (headerMatch) {
15
+ const level = headerMatch[1].length;
16
+ const text = headerMatch[2];
17
+ elements.push(React.createElement(Box, { key: i, marginTop: level === 1 ? 1 : 0, marginBottom: 1 },
18
+ React.createElement(Text, { bold: true, color: level === 1 ? 'cyan' : level === 2 ? 'blue' : 'white' }, text)));
19
+ continue;
20
+ }
21
+ // Bullet lists (- or *)
22
+ const bulletMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
23
+ if (bulletMatch) {
24
+ const text = bulletMatch[1];
25
+ const indent = line.match(/^(\s*)/)?.[1].length || 0;
26
+ elements.push(React.createElement(Box, { key: i, marginLeft: Math.floor(indent / 2) },
27
+ React.createElement(Text, null,
28
+ "\u2022 ",
29
+ text)));
30
+ continue;
31
+ }
32
+ // Numbered lists (1. 2. etc)
33
+ const numberedMatch = line.match(/^[\s]*(\d+)\.\s+(.+)$/);
34
+ if (numberedMatch) {
35
+ const num = numberedMatch[1];
36
+ const text = numberedMatch[2];
37
+ const indent = line.match(/^(\s*)/)?.[1].length || 0;
38
+ elements.push(React.createElement(Box, { key: i, marginLeft: Math.floor(indent / 2) },
39
+ React.createElement(Text, null,
40
+ num,
41
+ ". ",
42
+ text)));
43
+ continue;
44
+ }
45
+ // Code blocks (```)
46
+ if (line.trim() === '```' || line.trim().startsWith('```')) {
47
+ // Skip code fence markers
48
+ continue;
49
+ }
50
+ // Inline code (`code`)
51
+ const codeMatch = line.match(/`([^`]+)`/g);
52
+ if (codeMatch) {
53
+ const parts = line.split(/(`[^`]+`)/);
54
+ elements.push(React.createElement(Box, { key: i },
55
+ React.createElement(Text, null, parts.map((part, idx) => {
56
+ if (part.startsWith('`') && part.endsWith('`')) {
57
+ return (React.createElement(Text, { key: idx, backgroundColor: "gray", color: "white" }, part.slice(1, -1)));
58
+ }
59
+ return part;
60
+ }))));
61
+ continue;
62
+ }
63
+ // Bold (**text**)
64
+ const boldMatch = line.match(/\*\*([^*]+)\*\*/g);
65
+ if (boldMatch) {
66
+ const parts = line.split(/(\*\*[^*]+\*\*)/);
67
+ elements.push(React.createElement(Box, { key: i },
68
+ React.createElement(Text, null, parts.map((part, idx) => {
69
+ if (part.startsWith('**') && part.endsWith('**')) {
70
+ return React.createElement(Text, { key: idx, bold: true }, part.slice(2, -2));
71
+ }
72
+ return part;
73
+ }))));
74
+ continue;
75
+ }
76
+ // Empty lines
77
+ if (line.trim() === '') {
78
+ elements.push(React.createElement(Box, { key: i, height: 1 }));
79
+ continue;
80
+ }
81
+ // Regular text
82
+ elements.push(React.createElement(Box, { key: i },
83
+ React.createElement(Text, { wrap: "wrap" }, line)));
84
+ }
85
+ return React.createElement(Box, { flexDirection: "column" }, elements);
86
+ };
87
+ //# sourceMappingURL=markdown-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-text.js","sourceRoot":"","sources":["../../lib/markdown-text.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface MarkdownWrapperProps {
3
+ children: string;
4
+ }
5
+ export declare const MarkdownWrapper: React.FC<MarkdownWrapperProps>;
6
+ export {};
7
+ //# sourceMappingURL=markdown-wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-wrapper.d.ts","sourceRoot":"","sources":["../../lib/markdown-wrapper.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAQD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAW1D,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { Text } from 'ink';
2
+ import { marked } from 'marked';
3
+ import TerminalRenderer from 'marked-terminal';
4
+ import React from 'react';
5
+ // Configure marked globally with terminal renderer
6
+ marked.setOptions({
7
+ // @ts-expect-error - marked-terminal types are not perfectly aligned
8
+ renderer: new TerminalRenderer(),
9
+ });
10
+ export const MarkdownWrapper = ({ children, }) => {
11
+ try {
12
+ const parsed = marked.parse(children);
13
+ const output = typeof parsed === 'string' ? parsed.trim() : '';
14
+ return React.createElement(Text, null, output);
15
+ }
16
+ catch (_error) {
17
+ // Fallback to plain text if markdown parsing fails
18
+ return React.createElement(Text, { wrap: "wrap" }, children);
19
+ }
20
+ };
21
+ //# sourceMappingURL=markdown-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-wrapper.js","sourceRoot":"","sources":["../../lib/markdown-wrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,gBAAgB,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,mDAAmD;AACnD,MAAM,CAAC,UAAU,CAAC;IAChB,qEAAqE;IACrE,QAAQ,EAAE,IAAI,gBAAgB,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAmC,CAAC,EAC9D,QAAQ,GACT,EAAE,EAAE;IACH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO,oBAAC,IAAI,QAAE,MAAM,CAAQ,CAAC;IAC/B,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,mDAAmD;QACnD,OAAO,oBAAC,IAAI,IAAC,IAAI,EAAC,MAAM,IAAE,QAAQ,CAAQ,CAAC;IAC7C,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function uninstall(): void;
2
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../../lib/uninstall.ts"],"names":[],"mappings":"AAqCA,wBAAgB,SAAS,IAAI,IAAI,CA+EhC"}
@@ -0,0 +1,90 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const HAN_MARKETPLACE_URL = 'https://github.com/thebushidocollective/sensei';
4
+ function getClaudeSettingsPath() {
5
+ const rootDir = process.cwd();
6
+ return join(rootDir, '.claude', 'settings.json');
7
+ }
8
+ function readSettings() {
9
+ const settingsPath = getClaudeSettingsPath();
10
+ if (!existsSync(settingsPath)) {
11
+ console.log('No .claude/settings.json found. Nothing to uninstall.');
12
+ return null;
13
+ }
14
+ try {
15
+ return JSON.parse(readFileSync(settingsPath, 'utf8'));
16
+ }
17
+ catch (error) {
18
+ console.error('Error reading settings.json:', error.message);
19
+ return null;
20
+ }
21
+ }
22
+ function writeSettings(settings) {
23
+ const settingsPath = getClaudeSettingsPath();
24
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
25
+ }
26
+ export function uninstall() {
27
+ console.log('🗑️ Removing Han marketplace and plugins from Claude Code settings...\n');
28
+ const settings = readSettings();
29
+ if (!settings) {
30
+ process.exit(0);
31
+ }
32
+ let changes = false;
33
+ // Remove Han marketplace from extraMarketplaces
34
+ if (settings.extraMarketplaces) {
35
+ const before = settings.extraMarketplaces.length;
36
+ settings.extraMarketplaces = settings.extraMarketplaces.filter((url) => url !== HAN_MARKETPLACE_URL);
37
+ const after = settings.extraMarketplaces.length;
38
+ if (before > after) {
39
+ console.log(`✓ Removed Han marketplace: ${HAN_MARKETPLACE_URL}`);
40
+ changes = true;
41
+ }
42
+ if (settings.extraMarketplaces.length === 0) {
43
+ delete settings.extraMarketplaces;
44
+ }
45
+ }
46
+ // Remove all Han plugins (those starting with buki-, do-, sensei-, or named bushido)
47
+ if (settings.plugins) {
48
+ const before = settings.plugins.length;
49
+ settings.plugins = settings.plugins.filter((plugin) => !plugin.startsWith('buki-') &&
50
+ !plugin.startsWith('do-') &&
51
+ !plugin.startsWith('sensei-') &&
52
+ plugin !== 'bushido');
53
+ const after = settings.plugins.length;
54
+ const removed = before - after;
55
+ if (removed > 0) {
56
+ console.log(`✓ Removed ${removed} Han plugin(s)`);
57
+ changes = true;
58
+ }
59
+ if (settings.plugins.length === 0) {
60
+ delete settings.plugins;
61
+ }
62
+ }
63
+ if (!changes) {
64
+ console.log('No Han marketplace or plugins found in settings.');
65
+ process.exit(0);
66
+ }
67
+ writeSettings(settings);
68
+ console.log('\n✅ Uninstallation complete!');
69
+ console.log('\nRemaining configuration in .claude/settings.json:');
70
+ if (settings.plugins && settings.plugins.length > 0) {
71
+ console.log(` Plugins: ${settings.plugins.length}`);
72
+ for (const plugin of settings.plugins) {
73
+ console.log(` - ${plugin}`);
74
+ }
75
+ }
76
+ else {
77
+ console.log(' No plugins configured');
78
+ }
79
+ if (settings.extraMarketplaces && settings.extraMarketplaces.length > 0) {
80
+ console.log(` Marketplaces: ${settings.extraMarketplaces.length}`);
81
+ for (const marketplace of settings.extraMarketplaces) {
82
+ console.log(` - ${marketplace}`);
83
+ }
84
+ }
85
+ else {
86
+ console.log(' No extra marketplaces configured');
87
+ }
88
+ console.log('\nRestart Claude Code to apply changes.');
89
+ }
90
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../lib/uninstall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ interface ValidateOptions {
2
+ failFast: boolean;
3
+ dirsWith: string | null;
4
+ command: string;
5
+ }
6
+ export declare function validate(options: ValidateOptions): void;
7
+ export {};
8
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../lib/validate.ts"],"names":[],"mappings":"AAIA,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAiED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAmDvD"}
@@ -0,0 +1,98 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ // Find directories
5
+ function findDirectories(rootDir) {
6
+ const dirs = [];
7
+ // Try to use git to find tracked directories
8
+ try {
9
+ const gitDirs = execSync('git ls-files', {
10
+ cwd: rootDir,
11
+ encoding: 'utf8',
12
+ })
13
+ .split('\n')
14
+ .map((file) => {
15
+ const parts = file.split('/');
16
+ return parts.length > 1 ? parts[0] : null;
17
+ })
18
+ .filter((dir) => dir !== null);
19
+ const uniqueDirs = [...new Set(gitDirs)];
20
+ uniqueDirs.forEach((dir) => {
21
+ const fullPath = join(rootDir, dir);
22
+ if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
23
+ dirs.push(fullPath);
24
+ }
25
+ });
26
+ }
27
+ catch (_e) {
28
+ // Git not available or not in git repo, use glob
29
+ const entries = readdirSync(rootDir, { withFileTypes: true });
30
+ entries.forEach((entry) => {
31
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
32
+ dirs.push(join(rootDir, entry.name));
33
+ }
34
+ });
35
+ }
36
+ return dirs;
37
+ }
38
+ // Filter directories by marker file
39
+ function filterDirectories(dirs, markerFile) {
40
+ if (!markerFile)
41
+ return dirs;
42
+ return dirs.filter((dir) => {
43
+ return existsSync(join(dir, markerFile));
44
+ });
45
+ }
46
+ // Run command in directory
47
+ function runCommand(dir, cmd) {
48
+ try {
49
+ execSync(cmd, {
50
+ cwd: dir,
51
+ stdio: 'inherit',
52
+ encoding: 'utf8',
53
+ });
54
+ return true;
55
+ }
56
+ catch (_e) {
57
+ return false;
58
+ }
59
+ }
60
+ export function validate(options) {
61
+ const { failFast, dirsWith, command: commandToRun } = options;
62
+ // Main execution
63
+ const rootDir = process.cwd();
64
+ const allDirs = findDirectories(rootDir);
65
+ const targetDirs = filterDirectories(allDirs, dirsWith);
66
+ if (targetDirs.length === 0) {
67
+ if (dirsWith) {
68
+ console.log(`No directories found with ${dirsWith}`);
69
+ process.exit(0);
70
+ }
71
+ else {
72
+ console.error('No directories found');
73
+ process.exit(1);
74
+ }
75
+ }
76
+ const failures = [];
77
+ for (const dir of targetDirs) {
78
+ const success = runCommand(dir, commandToRun);
79
+ if (!success) {
80
+ const relativePath = dir.replace(`${rootDir}/`, '');
81
+ failures.push(relativePath);
82
+ console.error(`\nFailed when trying to run \`${commandToRun}\` in directory: \`${relativePath}\`\n`);
83
+ if (failFast) {
84
+ process.exit(2);
85
+ }
86
+ }
87
+ }
88
+ if (failures.length > 0) {
89
+ console.error(`\n❌ ${failures.length} director${failures.length === 1 ? 'y' : 'ies'} failed validation:\n`);
90
+ for (const dir of failures) {
91
+ console.error(` - ${dir}`);
92
+ }
93
+ process.exit(2);
94
+ }
95
+ console.log(`\n✅ All ${targetDirs.length} director${targetDirs.length === 1 ? 'y' : 'ies'} passed validation`);
96
+ process.exit(0);
97
+ }
98
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../lib/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,mBAAmB;AACnB,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,6CAA6C;IAC7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,EAAE;YACvC,GAAG,EAAE,OAAO;YACZ,QAAQ,EAAE,MAAM;SACjB,CAAC;aACC,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC9B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;QAEhD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACzC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACpC,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC7D,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,iDAAiD;QACjD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACxB,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oCAAoC;AACpC,SAAS,iBAAiB,CACxB,IAAc,EACd,UAAyB;IAEzB,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACzB,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2BAA2B;AAC3B,SAAS,UAAU,CAAC,GAAW,EAAE,GAAW;IAC1C,IAAI,CAAC;QACH,QAAQ,CAAC,GAAG,EAAE;YACZ,GAAG,EAAE,GAAG;YACR,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAwB;IAC/C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAE9D,iBAAiB;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAExD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,6BAA6B,QAAQ,EAAE,CAAC,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAE9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,EAAE,EAAE,CAAC,CAAC;YACpD,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAE5B,OAAO,CAAC,KAAK,CACX,iCAAiC,YAAY,sBAAsB,YAAY,MAAM,CACtF,CAAC;YAEF,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CACX,OAAO,QAAQ,CAAC,MAAM,YAAY,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,uBAAuB,CAC7F,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CACT,WAAW,UAAU,CAAC,MAAM,YAAY,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,oBAAoB,CAClG,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=han.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"han.test.d.ts","sourceRoot":"","sources":["../../test/han.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,151 @@
1
+ import { strictEqual } from 'node:assert';
2
+ import { execSync, } from 'node:child_process';
3
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ // Get __dirname equivalent in ES modules
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ // Test runs from dist/test, so go up to dist, then to lib/main.js
10
+ const binPath = join(__dirname, '..', 'lib', 'main.js');
11
+ function setup() {
12
+ const testDir = join(__dirname, 'fixtures');
13
+ rmSync(testDir, { recursive: true, force: true });
14
+ mkdirSync(testDir, { recursive: true });
15
+ return testDir;
16
+ }
17
+ function teardown() {
18
+ const testDir = join(__dirname, 'fixtures');
19
+ rmSync(testDir, { recursive: true, force: true });
20
+ }
21
+ function test(name, fn) {
22
+ try {
23
+ fn();
24
+ console.log(`✓ ${name}`);
25
+ }
26
+ catch (error) {
27
+ console.error(`✗ ${name}`);
28
+ console.error(error.message);
29
+ process.exit(1);
30
+ }
31
+ }
32
+ // Test: shows help when no command provided
33
+ test('shows help when no command provided', () => {
34
+ try {
35
+ execSync(`node ${binPath} --help`, { encoding: 'utf8' });
36
+ // Help command should exit with 0
37
+ }
38
+ catch (error) {
39
+ const execError = error;
40
+ const stdout = execError.message || '';
41
+ strictEqual(stdout.includes('Usage:') || stdout.includes('han'), true);
42
+ }
43
+ });
44
+ // Test: shows error when validate has no command argument
45
+ test('shows error when validate has no command argument', () => {
46
+ try {
47
+ execSync(`node ${binPath} validate`, { encoding: 'utf8' });
48
+ throw new Error('Should have failed');
49
+ }
50
+ catch (error) {
51
+ const execError = error;
52
+ strictEqual(execError.status, 1);
53
+ const stderr = execError.stderr?.toString() || '';
54
+ strictEqual(stderr.includes('missing required argument') ||
55
+ stderr.includes('error') ||
56
+ stderr.length > 0, true);
57
+ }
58
+ });
59
+ // Test: passes when no directories match filter
60
+ test('passes when no directories match filter', () => {
61
+ const testDir = setup();
62
+ try {
63
+ const output = execSync(`node ${binPath} validate --dirs-with nonexistent.txt echo test`, { cwd: testDir, encoding: 'utf8' });
64
+ strictEqual(output.includes('No directories found with nonexistent.txt'), true);
65
+ }
66
+ finally {
67
+ teardown();
68
+ }
69
+ });
70
+ // Test: runs command in matching directories
71
+ test('runs command in matching directories', () => {
72
+ const testDir = setup();
73
+ try {
74
+ // Create test structure
75
+ mkdirSync(join(testDir, 'pkg1'));
76
+ mkdirSync(join(testDir, 'pkg2'));
77
+ writeFileSync(join(testDir, 'pkg1', 'package.json'), '{}');
78
+ writeFileSync(join(testDir, 'pkg2', 'package.json'), '{}');
79
+ // Initialize git repo so directories are discovered
80
+ execSync('git init', { cwd: testDir });
81
+ execSync('git add .', { cwd: testDir });
82
+ const output = execSync(`node ${binPath} validate --dirs-with package.json echo success`, {
83
+ cwd: testDir,
84
+ encoding: 'utf8',
85
+ stdio: ['pipe', 'pipe', 'pipe'],
86
+ });
87
+ strictEqual(output.includes('success'), true);
88
+ strictEqual(output.includes('passed validation'), true);
89
+ }
90
+ finally {
91
+ teardown();
92
+ }
93
+ });
94
+ // Test: fails with exit code 2 when command fails
95
+ test('fails with exit code 2 when command fails', () => {
96
+ const testDir = setup();
97
+ try {
98
+ mkdirSync(join(testDir, 'pkg1'));
99
+ writeFileSync(join(testDir, 'pkg1', 'package.json'), '{}');
100
+ // Initialize git repo
101
+ execSync('git init', { cwd: testDir });
102
+ execSync('git add .', { cwd: testDir });
103
+ try {
104
+ execSync(`node ${binPath} validate --dirs-with package.json exit 1`, {
105
+ cwd: testDir,
106
+ encoding: 'utf8',
107
+ stdio: 'pipe',
108
+ });
109
+ throw new Error('Should have failed');
110
+ }
111
+ catch (error) {
112
+ const execError = error;
113
+ const exitCode = execError.status || execError.code;
114
+ strictEqual(exitCode, 2, `Expected exit code 2, got ${exitCode}`);
115
+ const stderr = execError.stderr?.toString() || '';
116
+ strictEqual(stderr.includes('failed validation') || stderr.includes('Failed when'), true);
117
+ }
118
+ }
119
+ finally {
120
+ teardown();
121
+ }
122
+ });
123
+ // Test: stops on first failure with --fail-fast
124
+ test('stops on first failure with --fail-fast', () => {
125
+ const testDir = setup();
126
+ try {
127
+ mkdirSync(join(testDir, 'pkg1'));
128
+ mkdirSync(join(testDir, 'pkg2'));
129
+ writeFileSync(join(testDir, 'pkg1', 'package.json'), '{}');
130
+ writeFileSync(join(testDir, 'pkg2', 'package.json'), '{}');
131
+ // Initialize git repo
132
+ execSync('git init', { cwd: testDir });
133
+ execSync('git add .', { cwd: testDir });
134
+ try {
135
+ execSync(`node ${binPath} validate --fail-fast --dirs-with package.json exit 1`, { cwd: testDir, encoding: 'utf8', stdio: 'pipe' });
136
+ throw new Error('Should have failed');
137
+ }
138
+ catch (error) {
139
+ const execError = error;
140
+ const exitCode = execError.status || execError.code;
141
+ strictEqual(exitCode, 2, `Expected exit code 2, got ${exitCode}`);
142
+ const stderr = execError.stderr?.toString() || '';
143
+ strictEqual(stderr.includes('Failed when trying to run'), true);
144
+ }
145
+ }
146
+ finally {
147
+ teardown();
148
+ }
149
+ });
150
+ console.log('\nAll tests passed! ✓');
151
+ //# sourceMappingURL=han.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"han.test.js","sourceRoot":"","sources":["../../test/han.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@thebushidocollective/han",
3
+ "version": "1.0.0",
4
+ "description": "Monorepo validation tool for Claude Code hooks",
5
+ "main": "dist/lib/main.js",
6
+ "types": "dist/lib/main.d.ts",
7
+ "type": "module",
8
+ "bin": {
9
+ "han": "./dist/lib/main.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "postbuild": "chmod +x dist/lib/main.js",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "node dist/test/han.test.js",
16
+ "lint": "biome check .",
17
+ "lint:fix": "biome check --write .",
18
+ "prepack": "npm run build && npm run lint && npm test"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/thebushidocollective/han.git"
23
+ },
24
+ "homepage": "https://github.com/thebushidocollective/han",
25
+ "bugs": {
26
+ "url": "https://github.com/thebushidocollective/han/issues"
27
+ },
28
+ "keywords": [
29
+ "bushido",
30
+ "monorepo",
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": ">=24"
42
+ },
43
+ "files": [
44
+ "dist/",
45
+ "README.md"
46
+ ],
47
+ "devDependencies": {
48
+ "@biomejs/biome": "^2.3.6",
49
+ "@types/commander": "^2.12.0",
50
+ "@types/marked-terminal": "^6.1.1",
51
+ "@types/node": "^20.19.25",
52
+ "@types/react": "^19.2.6",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "dependencies": {
56
+ "@anthropic-ai/claude-agent-sdk": "^0.1.47",
57
+ "commander": "^14.0.2",
58
+ "ink": "^6.5.1",
59
+ "ink-spinner": "^5.0.0",
60
+ "marked": "^15.0.12",
61
+ "marked-terminal": "^7.3.0",
62
+ "react": "^19.2.0"
63
+ }
64
+ }