@khanhcan148/mk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # Claude Code Skills, Agents & Workflows
2
+
3
+ [![CI](https://github.com/khanhtran148/mk-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/khanhtran148/mk-kit/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@khanhcan148/mk)](https://www.npmjs.com/package/@khanhcan148/mk)
5
+
6
+ **Built by** TRAN Le Khanh — developer and AI tooling author.
7
+
8
+ Modular packages that extend Claude Code with specialized knowledge, workflows, and tool integrations.
9
+
10
+ **Quick Navigation:** [Quick Reference](docs/QUICK-REFERENCE.md) | [Common Workflows](docs/COMMON-WORKFLOWS.md) | [Skill Index](docs/SKILL-INDEX.md)
11
+
12
+ ## Quick Start
13
+
14
+ ### CLI Installation (Recommended)
15
+
16
+ ```bash
17
+ # Install globally
18
+ npm install -g @khanhcan148/mk
19
+
20
+ # Install kit to current project (./.claude/)
21
+ mk init
22
+
23
+ # Install kit globally (~/.claude/ — available in all projects)
24
+ mk init --global
25
+
26
+ # Preview what would be installed without writing files
27
+ mk init --dry-run
28
+ ```
29
+
30
+ ### CLI Commands
31
+
32
+ ```
33
+ mk init Install kit files to current project (./.claude/)
34
+ mk init --global Install kit globally (~/.claude/)
35
+ mk init --dry-run Preview what would be installed
36
+ mk update Update kit files (preserves your edits by default)
37
+ mk update --force Update and overwrite user-modified files
38
+ mk remove Remove all kit files tracked by manifest
39
+ mk --version Show installed CLI version
40
+ mk --help Show help
41
+ ```
42
+
43
+ ### Manual Installation (Alternative)
44
+
45
+ ```bash
46
+ # Clone the repository
47
+ git clone https://github.com/khanhtran148/mk-kit.git
48
+ cd mk-kit
49
+ ```
50
+
51
+ **Project-level usage** (skills available in one project):
52
+ ```bash
53
+ cp -r .claude /path/to/your/project/
54
+ ```
55
+
56
+ **Global-level usage** (skills available across all projects):
57
+ ```bash
58
+ cp -r .claude ~/.claude/
59
+ ```
60
+
61
+ ### Usage
62
+
63
+ ```bash
64
+ # Use a workflow command
65
+ /mk-init # Bootstrap new project
66
+ /mk-brainstorm # Explore options, debate trade-offs
67
+ /mk-plan # Create implementation plan
68
+ /mk-implement # End-to-end feature delivery
69
+ /mk-test # Run tests and validate
70
+ /mk-review # Code quality review
71
+ /mk-debug # Debug issues
72
+ /mk-security # Security scan and audit
73
+ /mk-db # Database operations
74
+ /mk-docs # Generate documentation
75
+ /mk-git # Git operations
76
+ ```
77
+
78
+ ## Structure
79
+
80
+ ```
81
+ ├── .claude/
82
+ │ ├── agents/ # 29 agents (5 primary + 24 utility: implementers, quality, docs, specialized, concerns)
83
+ │ ├── skills/ # 53 skill packages (SKILL.md + scripts/references/assets)
84
+ │ │ ├── mk-*/ # 14 workflow commands (/mk-brainstorm, /mk-selftest, etc.)
85
+ │ │ └── ... # Domain skills (frontend, backend, testing, etc.)
86
+ │ └── workflows/ # Development protocols
87
+ ├── bin/ # CLI entry point (mk command)
88
+ ├── src/ # CLI source code
89
+ ├── docs/ # Project documentation
90
+ │ └── plans/ # Implementation plans (created during development)
91
+ ├── package.json # npm package metadata
92
+ ├── CLAUDE.md # Claude Code guidance
93
+ └── README.md # This file
94
+ ```
95
+
96
+ ## Primary Workflow
97
+
98
+ Orchestration is handled by `/mk-*` skills which spawn utility agents as needed. See [Common Workflows](docs/COMMON-WORKFLOWS.md) for practical examples.
99
+
100
+ **Architecture:**
101
+
102
+ ```
103
+ User → /mk-* command (skill) → spawns utility agents → agents use knowledge skills
104
+ ```
105
+
106
+ **Sequential chaining:**
107
+ ```
108
+ /mk-plan → /mk-implement → /mk-test → /mk-review
109
+ ```
110
+
111
+ **Entry point commands:**
112
+
113
+ | Command | Purpose |
114
+ |---------|---------|
115
+ | `/mk-init` | Full project bootstrap from conception to deployment; includes brownfield detection (Phase 0 gate) and adoption workflow (B1-B4.5 stages) |
116
+ | `/mk-brainstorm` | Ideation, requirements exploration, structured debate (analyzer opus agent); use `--domain` flag to extract and document domain knowledge |
117
+ | `/mk-research` | Deep multi-source research on technical topics |
118
+ | `/mk-plan` | Create implementation plans with phases and tasks (opus) |
119
+ | `/mk-design` | UI/UX wireframes, design systems, mockups |
120
+ | `/mk-implement` | End-to-end feature delivery (sonnet) |
121
+ | `/mk-test` | Run tests, analyze coverage, validate builds |
122
+ | `/mk-review` | Code review for quality, security, performance |
123
+ | `/mk-debug` | Debugging workflow: investigate, diagnose, fix, verify |
124
+ | `/mk-security` | Security-first workflow: dependency scan, secrets detection |
125
+ | `/mk-db` | Database operations: diagnose, optimize, design, migrate |
126
+ | `/mk-docs` | Generate and update project documentation |
127
+ | `/mk-git` | Git operations: branch, commit, push, PR, merge |
128
+ | `/mk-selftest` | Run self-validation checks on kit agents, skills, docs, and workflows |
129
+
130
+ ## Primary Agents
131
+
132
+ Invoked directly via `/mk-*` skills:
133
+
134
+ | Agent | Purpose | Model |
135
+ |-------|---------|-------|
136
+ | **analyzer** | Deep research, decision matrix, architecture diagrams, adversarial debate (Phases 5-8 of /mk-brainstorm) | opus |
137
+ | **planner** | Implementation planning with phases, tasks, dependencies | opus |
138
+ | **implementer** | End-to-end feature implementation | sonnet |
139
+ | **database-admin** | Database operations: diagnose, optimize, design, migrate | sonnet |
140
+ | **git-manager** | Git operations: commit, push, PR, merge | haiku |
141
+
142
+ ## Utility Agents
143
+
144
+ Spawned by primary agents for domain-specific work:
145
+
146
+ **Implementation & Quality (8)**
147
+ | Agent | Purpose |
148
+ |-------|---------|
149
+ | **backend-implementer** | API and domain logic implementation |
150
+ | **frontend-implementer** | UI components, consuming API contract |
151
+ | **tester** | Test execution and validation |
152
+ | **debugger** | Issue investigation and diagnosis |
153
+ | **refactorer** | Refactoring with TFD-first safety workflow |
154
+
155
+ **Docs & Project Management (3)**
156
+ | Agent | Purpose |
157
+ |-------|---------|
158
+ | **project-manager** | Progress tracking, roadmap updates |
159
+ | **docs-manager** | Documentation maintenance |
160
+ | **journal-writer** | Technical difficulty documentation |
161
+
162
+ **Research & Design (3)**
163
+ | Agent | Purpose |
164
+ |-------|---------|
165
+ | **researcher** | Technical research |
166
+ | **ui-ux-designer** | Frontend design |
167
+ | **copywriter** | Marketing copy and content |
168
+
169
+ **Utilities & Search (2)**
170
+ | Agent | Purpose |
171
+ |-------|---------|
172
+ | **scout** | Codebase file search (internal) |
173
+ | **scout-external** | Codebase search via external tools |
174
+ | **mcp-manager** | MCP server integrations |
175
+
176
+ **Parallel Concern Review Agents (10)**
177
+ | Agent | Purpose |
178
+ |-------|---------|
179
+ | **quality-reviewer** | Code quality, readability, maintainability (mk-review concern) |
180
+ | **security-reviewer** | Security vulnerabilities, threat analysis (mk-review concern) |
181
+ | **performance-reviewer** | Performance profiling, optimization opportunities (mk-review concern) |
182
+ | **tfd-reviewer** | Test-first development verification (mk-review concern) |
183
+ | **dep-scanner** | Dependency vulnerability scanning (mk-security concern) |
184
+ | **secret-scanner** | Secrets and credential detection (mk-security concern) |
185
+ | **code-security-reviewer** | Code-level security analysis (mk-security concern) |
186
+ | **infra-reviewer** | Infrastructure security review (mk-security concern) |
187
+ | **log-analyzer** | Log analysis and diagnostics (mk-debug concern) |
188
+ | **hypothesis-generator** | Root cause hypothesis generation (mk-debug concern) |
189
+
190
+ [Full agent details →](.claude/agents/README.md)
191
+
192
+ ## Skills
193
+
194
+ See [Skill Index](docs/SKILL-INDEX.md) for a complete categorized list of all skills.
195
+
196
+ Each skill contains:
197
+ - `SKILL.md` (required) - Instructions (<100 lines)
198
+ - `scripts/` (optional) - Executable code with tests
199
+ - `references/` (optional) - Documentation chunks
200
+ - `assets/` (optional) - Templates, images
201
+
202
+ ```bash
203
+ # Initialize new skill
204
+ .claude/skills/skill-creator/scripts/init_skill.py <skill-name> --path <output-directory>
205
+
206
+ # Package for distribution
207
+ .claude/skills/skill-creator/scripts/package_skill.py <path/to/skill-folder>
208
+ ```
209
+
210
+ ## Documentation
211
+
212
+ | Document | Description |
213
+ |----------|-------------|
214
+ | [Skill Index](docs/SKILL-INDEX.md) | Categorized list of all skills and workflows |
215
+ | [mk-* Workflows & Agents](docs/mk-workflow-agents.md) | How each /mk-* command works and which agents it uses |
216
+ | [Project Overview & PDR](docs/project-overview-pdr.md) | Requirements, constraints, success metrics |
217
+ | [Project Roadmap](docs/project-roadmap.md) | Phases, milestones, and risk register |
218
+ | [Changelog](docs/changelog.md) | Version history and release notes |
219
+ | [Code Standards](docs/code-standards.md) | Conventions, naming, quality gates |
220
+ | [System Architecture](docs/system-architecture.md) | Component diagrams, data flow |
221
+ | [Product Domain Glossary](docs/product-domain-glossary.md) | Core domain concepts, terminology, and ecosystem definitions |
222
+ | [Quick Reference](docs/QUICK-REFERENCE.md) | Common commands and quick lookup |
223
+ | [Common Workflows](docs/COMMON-WORKFLOWS.md) | Practical workflow examples |
224
+
225
+ ## Core Principles
226
+
227
+ - **YAGNI** - Don't add functionality until necessary
228
+ - **KISS** - Prefer simple solutions
229
+ - **DRY** - Extract common patterns
230
+ - **SOLID** - For OOP/enterprise projects
231
+
232
+ ## Key Constraints
233
+
234
+ - SKILL.md must be under 100 lines
235
+ - Referenced markdown files also under 100 lines
236
+ - Scripts must include tests
237
+ - **Cross-platform compatibility (Windows + macOS/Linux) is mandatory**: Scripts use Python or Node.js only — no bash/shell scripts
238
+ - Token efficiency - minimize context usage
239
+
240
+ ## Compatibility
241
+
242
+ This skill kit works in both Claude Code CLI and VSCode GitHub Copilot (1.108+).
243
+
244
+ ## Cross-Platform Compatibility
245
+
246
+ This kit is designed to work on Windows, macOS, and Linux:
247
+
248
+ - **Scripts**: All executable scripts use Python or Node.js (no bash/shell scripts)
249
+ - **Claude Code Bash tool**: Provides a Unix-like shell on all platforms, including Windows — git commands and other Bash tool invocations work everywhere
250
+ - **External tool installs**: Reference files include install instructions for macOS (`brew`), Ubuntu/Debian (`apt-get`), and Windows (`winget`/`choco`) where applicable
251
+ - **Agent fallbacks**: Agents using CLI-only tools (TodoWrite, BashOutput, MultiEdit) document VSCode Copilot fallback behavior inline
252
+
253
+ | Feature | Claude Code CLI | VSCode Copilot (1.108+) |
254
+ |---------|----------------|------------------------|
255
+ | Skills (SKILL.md) | Full support | Full support |
256
+ | Agents (.md) | Full support | Full support |
257
+ | CLAUDE.md | Full support | Full support |
258
+ | Model field | Shorthand (opus/sonnet/haiku) | Shorthand (recommended) |
259
+ | Task tool | Native Task() syntax | Natural language delegation* |
260
+ | color | Supported | Ignored (harmless) |
261
+ | MCP tools | Full support | Limited |
262
+ | AskUserQuestion | Native UI | Natural language fallback |
263
+
264
+ *VSCode Copilot users: Use natural language to invoke agents (e.g., "Use the planner agent to create a plan"). The explicit Task() syntax is Claude Code CLI-specific.
265
+
266
+ **CLI-only tools**: TodoWrite, BashOutput, KillShell, MultiEdit are Claude Code-specific. Agents gracefully fall back to standard tools (Write, Bash, sequential Edit) when these are unavailable.
package/bin/mk.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+ import { initAction } from '../src/commands/init.js';
7
+ import { updateAction } from '../src/commands/update.js';
8
+ import { removeAction } from '../src/commands/remove.js';
9
+ import { loginAction, logoutAction, statusAction } from '../src/commands/auth.js';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
13
+
14
+ program
15
+ .name('mk')
16
+ .description('Install and manage MyClaudeKit (.claude/) in your projects')
17
+ .version(pkg.version);
18
+
19
+ program.command('init')
20
+ .description('Install .claude/ kit files to current project or globally')
21
+ .option('--global', 'Install to ~/.claude/ for all projects')
22
+ .option('--dry-run', 'Print planned changes without writing files')
23
+ .action((options) => initAction(options));
24
+
25
+ program.command('update')
26
+ .description('Update kit files to the latest version, preserving user modifications')
27
+ .option('--force', 'Overwrite user-modified files without warning')
28
+ .option('--global', 'Update global installation in ~/.claude/')
29
+ .action((options) => updateAction(options));
30
+
31
+ program.command('remove')
32
+ .description('Remove all kit files tracked by the manifest')
33
+ .option('--global', 'Remove global installation from ~/.claude/')
34
+ .action((options) => removeAction(options));
35
+
36
+ // Auth command group
37
+ const auth = program.command('auth')
38
+ .description('Manage GitHub authentication for kit downloads');
39
+
40
+ auth.command('login')
41
+ .description('Authenticate with GitHub via OAuth Device Flow')
42
+ .option('--token <token>', 'Use a Personal Access Token instead of device flow')
43
+ .action((options) => loginAction(options));
44
+
45
+ auth.command('logout')
46
+ .description('Remove stored GitHub authentication token')
47
+ .action(() => logoutAction());
48
+
49
+ auth.command('status')
50
+ .description('Show current authentication status')
51
+ .action(() => statusAction());
52
+
53
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@khanhcan148/mk",
3
+ "version": "0.1.0",
4
+ "description": "CLI to install and manage MyClaudeKit (.claude/) in your projects",
5
+ "type": "module",
6
+ "bin": {
7
+ "mk": "./bin/mk.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "scripts": {
17
+ "test": "node --test test/lib/*.test.js test/commands/*.test.js test/integration/*.test.js",
18
+ "lint": "node --check src/**/*.js bin/**/*.js 2>/dev/null || true",
19
+ "selftest": "python3 .claude/skills/mk-selftest/scripts/validate_kit.py"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^12.0.0",
23
+ "fs-extra": "^11.0.0",
24
+ "chalk": "^5.0.0"
25
+ },
26
+ "devDependencies": {},
27
+ "keywords": ["claude", "claude-code", "skills", "agents", "cli"],
28
+ "author": "khanhcan148",
29
+ "license": "UNLICENSED",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/khanhtran148/mk-kit.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/khanhtran148/mk-kit/issues"
36
+ },
37
+ "homepage": "https://github.com/khanhtran148/mk-kit#readme"
38
+ }
@@ -0,0 +1,143 @@
1
+ import chalk from 'chalk';
2
+ import {
3
+ resolveToken as _resolveToken,
4
+ tokenSource as _tokenSource,
5
+ validateToken as _validateToken,
6
+ checkRepoAccess as _checkRepoAccess,
7
+ startDeviceFlow as _startDeviceFlow
8
+ } from '../lib/auth.js';
9
+ import { writeToken as _writeToken, deleteToken as _deleteToken, readStoredToken as _readStoredToken } from '../lib/config.js';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // loginAction
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /**
16
+ * Handle 'mk auth login'.
17
+ * Options: { token?: string } — if token provided, store it as PAT
18
+ *
19
+ * @param {{ token?: string }} options
20
+ * @param {object} [deps] - Injected dependencies (for testing)
21
+ */
22
+ export async function loginAction(options = {}, deps = {}) {
23
+ const {
24
+ resolveToken = _resolveToken,
25
+ validateToken = _validateToken,
26
+ startDeviceFlow = (opts) => _startDeviceFlow(opts),
27
+ writeToken = _writeToken,
28
+ readStoredToken = _readStoredToken
29
+ } = deps;
30
+
31
+ // PAT mode: --token flag provided
32
+ if (options.token) {
33
+ process.stdout.write('Validating token...\n');
34
+ const validation = await validateToken(options.token);
35
+ if (!validation.valid) {
36
+ process.stderr.write(chalk.red('Error: Token is invalid or has expired. Please check and try again.\n'));
37
+ return;
38
+ }
39
+ writeToken(options.token);
40
+ process.stdout.write(chalk.green(`Authenticated as ${validation.username}\n`));
41
+ return;
42
+ }
43
+
44
+ // Check if already authenticated
45
+ const existing = resolveToken({ readStored: readStoredToken });
46
+ if (existing) {
47
+ const validation = await validateToken(existing);
48
+ if (validation.valid) {
49
+ process.stdout.write(chalk.green(`Already authenticated as ${validation.username}\n`));
50
+ return;
51
+ }
52
+ // Token invalid — clear it and re-auth
53
+ process.stdout.write(chalk.yellow('Stored token is no longer valid. Re-authenticating...\n'));
54
+ }
55
+
56
+ // Device flow
57
+ process.stdout.write('Starting GitHub OAuth Device Flow...\n');
58
+ let newToken;
59
+ try {
60
+ newToken = await startDeviceFlow({
61
+ display: ({ userCode, verificationUri, expiresIn }) => {
62
+ process.stdout.write('\n');
63
+ process.stdout.write(chalk.bold('To authenticate, visit: ') + chalk.cyan(verificationUri) + '\n');
64
+ process.stdout.write(chalk.bold('And enter the code: ') + chalk.yellow(userCode) + '\n');
65
+ process.stdout.write(`(Code expires in ${Math.round(expiresIn / 60)} minutes)\n\n`);
66
+ process.stdout.write('Waiting for authorization...\n');
67
+ },
68
+ writeStored: writeToken
69
+ });
70
+ } catch (err) {
71
+ process.stderr.write(chalk.red(`Error: ${err.message}\n`));
72
+ return;
73
+ }
74
+
75
+ const validation = await validateToken(newToken);
76
+ if (validation.valid) {
77
+ process.stdout.write(chalk.green(`\nAuthenticated as ${validation.username}\n`));
78
+ } else {
79
+ process.stdout.write(chalk.green('\nAuthentication successful.\n'));
80
+ }
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // logoutAction
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Handle 'mk auth logout'.
89
+ *
90
+ * @param {object} [deps] - Injected dependencies (for testing)
91
+ */
92
+ export async function logoutAction(deps = {}) {
93
+ const { deleteToken = _deleteToken } = deps;
94
+ deleteToken();
95
+ process.stdout.write(chalk.green('Logged out. Stored token removed.\n'));
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // statusAction
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * Handle 'mk auth status'.
104
+ *
105
+ * @param {object} [deps] - Injected dependencies (for testing)
106
+ */
107
+ export async function statusAction(deps = {}) {
108
+ const {
109
+ resolveToken = _resolveToken,
110
+ tokenSource = _tokenSource,
111
+ validateToken = _validateToken,
112
+ checkRepoAccess = _checkRepoAccess,
113
+ readStoredToken = _readStoredToken
114
+ } = deps;
115
+
116
+ const token = resolveToken({ readStored: readStoredToken });
117
+
118
+ if (!token) {
119
+ process.stdout.write(chalk.yellow('Not authenticated. Run \'mk auth login\' to authenticate.\n'));
120
+ return;
121
+ }
122
+
123
+ const source = tokenSource(token, { readStored: readStoredToken });
124
+ process.stdout.write(`Token source: ${source}\n`);
125
+
126
+ const validation = await validateToken(token);
127
+ if (!validation.valid) {
128
+ process.stdout.write(chalk.red('Token: invalid or expired\n'));
129
+ process.stdout.write(chalk.yellow('Run \'mk auth login\' to re-authenticate.\n'));
130
+ return;
131
+ }
132
+
133
+ process.stdout.write(chalk.green(`Logged in as: ${validation.username}\n`));
134
+ process.stdout.write('Token: valid\n');
135
+
136
+ const access = await checkRepoAccess(token);
137
+ if (access.accessible) {
138
+ process.stdout.write(chalk.green('Repo access: granted\n'));
139
+ } else {
140
+ process.stdout.write(chalk.red('Repo access: denied\n'));
141
+ process.stdout.write('Contact the repository owner for collaborator access.\n');
142
+ }
143
+ }
@@ -0,0 +1,144 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { copyKitFiles } from '../lib/copy.js';
6
+ import { writeManifest } from '../lib/manifest.js';
7
+ import { computeChecksum } from '../lib/checksum.js';
8
+ import { resolveTargetDir, resolveManifestPath } from '../lib/paths.js';
9
+ import { MANIFEST_FILENAME } from '../lib/constants.js';
10
+ import { resolveTokenOrLogin } from '../lib/auth.js';
11
+ import { writeToken, readStoredToken } from '../lib/config.js';
12
+ import { downloadAndExtractKit, cleanupTempDir } from '../lib/download.js';
13
+
14
+ /**
15
+ * Run the init command.
16
+ *
17
+ * @param {{
18
+ * sourceDir?: string,
19
+ * targetDir?: string,
20
+ * manifestPath?: string,
21
+ * scope?: 'project'|'global',
22
+ * dryRun?: boolean
23
+ * }} params
24
+ * @returns {Promise<{ files: Array, totalSize: number }>}
25
+ */
26
+ export async function runInit(params = {}) {
27
+ const {
28
+ sourceDir = resolveSourceDir(),
29
+ targetDir = resolveTargetDir({ global: false }),
30
+ manifestPath = resolveManifestPath({ global: false }),
31
+ scope = 'project',
32
+ dryRun = false
33
+ } = params;
34
+
35
+ // Guard: abort if already installed
36
+ if (existsSync(manifestPath)) {
37
+ throw new Error(
38
+ `Already installed. Use 'mk update' to update kit files. (Manifest found at: ${manifestPath})`
39
+ );
40
+ }
41
+
42
+ // Copy files (dry-run or real)
43
+ const fileList = copyKitFiles(sourceDir, targetDir, { dryRun });
44
+
45
+ if (dryRun) {
46
+ return { files: fileList, totalSize: fileList.reduce((s, f) => s + f.size, 0), dryRun: true };
47
+ }
48
+
49
+ // Compute checksums and build manifest files map
50
+ // fileURLToPath handles Windows drive-letter prefix correctly (avoids leading /C:/ from .pathname)
51
+ const pkg = JSON.parse(readFileSync(
52
+ fileURLToPath(new URL('../../package.json', import.meta.url)),
53
+ 'utf8'
54
+ ));
55
+
56
+ const files = {};
57
+ for (const entry of fileList) {
58
+ if (existsSync(entry.absolutePath)) {
59
+ const checksum = computeChecksum(entry.absolutePath);
60
+ files[entry.relativePath] = { checksum, size: entry.size };
61
+ }
62
+ }
63
+
64
+ // Write manifest
65
+ writeManifest(manifestPath, files, pkg.version, scope);
66
+
67
+ const totalSize = fileList.reduce((s, f) => s + f.size, 0);
68
+ return { files: fileList, totalSize, fileCount: fileList.length };
69
+ }
70
+
71
+ /**
72
+ * CLI action handler for 'mk init'.
73
+ * Downloads kit from GitHub and installs .claude/ to target directory.
74
+ *
75
+ * @param {{ global: boolean, dryRun: boolean }} options
76
+ * @param {object} [deps] - Injected dependencies (for testing)
77
+ */
78
+ export async function initAction(options = {}, deps = {}) {
79
+ const {
80
+ resolveTokenOrLogin: resolveAndLogin = resolveTokenOrLogin,
81
+ downloadAndExtractKit: download = downloadAndExtractKit,
82
+ cleanupTempDir: cleanup = cleanupTempDir,
83
+ writeToken: storeToken = writeToken,
84
+ readStoredToken: readToken = readStoredToken
85
+ } = deps;
86
+
87
+ const targetDir = resolveTargetDir(options);
88
+ const manifestPath = resolveManifestPath(options);
89
+ const scope = options.global ? 'global' : 'project';
90
+
91
+ // Pre-flight: warn if target exists with files
92
+ if (existsSync(targetDir)) {
93
+ process.stdout.write(
94
+ chalk.yellow(`Warning: ${targetDir} already exists. Kit files will be added to existing structure.\n`)
95
+ );
96
+ }
97
+
98
+ if (options.dryRun) {
99
+ process.stdout.write(chalk.cyan('Dry run — no files will be written.\n\n'));
100
+ }
101
+
102
+ let tempDir = null;
103
+ try {
104
+ // Authenticate and download kit from GitHub
105
+ process.stdout.write('Authenticating with GitHub...\n');
106
+ const token = await resolveAndLogin({
107
+ readStored: readToken,
108
+ writeStored: storeToken,
109
+ display: ({ userCode, verificationUri, expiresIn }) => {
110
+ process.stdout.write('\n');
111
+ process.stdout.write(chalk.bold('To authenticate, visit: ') + chalk.cyan(verificationUri) + '\n');
112
+ process.stdout.write(chalk.bold('And enter the code: ') + chalk.yellow(userCode) + '\n');
113
+ process.stdout.write(`(Code expires in ${Math.round(expiresIn / 60)} minutes)\n\n`);
114
+ process.stdout.write('Waiting for authorization...\n');
115
+ }
116
+ });
117
+
118
+ process.stdout.write('Downloading kit from GitHub...\n');
119
+ tempDir = await download(token);
120
+ const sourceDir = join(tempDir, '.claude');
121
+
122
+ const result = await runInit({ sourceDir, targetDir, manifestPath, scope, dryRun: options.dryRun });
123
+
124
+ if (options.dryRun) {
125
+ process.stdout.write(`Would install ${result.files.length} files:\n`);
126
+ for (const f of result.files) {
127
+ process.stdout.write(` ${f.relativePath}\n`);
128
+ }
129
+ } else {
130
+ const sizeKB = Math.round(result.totalSize / 1024);
131
+ process.stdout.write(
132
+ chalk.green(`Installed ${result.fileCount} files (${sizeKB} KB) to ${targetDir}\n`)
133
+ );
134
+ process.stdout.write(`Manifest written to: ${manifestPath}\n`);
135
+ }
136
+ } catch (err) {
137
+ process.stderr.write(chalk.red(`Error: ${err.message}\n`));
138
+ process.exit(1);
139
+ } finally {
140
+ if (tempDir) {
141
+ cleanup(tempDir);
142
+ }
143
+ }
144
+ }