create-claude-workspace 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/README.md +161 -0
- package/dist/index.js +236 -0
- package/dist/template/.claude/CLAUDE.md +65 -0
- package/dist/template/.claude/agents/backend-ts-architect.md +156 -0
- package/dist/template/.claude/agents/deployment-engineer.md +496 -0
- package/dist/template/.claude/agents/devops-integrator.md +475 -0
- package/dist/template/.claude/agents/orchestrator.md +487 -0
- package/dist/template/.claude/agents/product-owner.md +193 -0
- package/dist/template/.claude/agents/project-initializer.md +284 -0
- package/dist/template/.claude/agents/senior-code-reviewer.md +183 -0
- package/dist/template/.claude/agents/technical-planner.md +200 -0
- package/dist/template/.claude/agents/test-engineer.md +304 -0
- package/dist/template/.claude/agents/ui-engineer.md +149 -0
- package/dist/template/.claude/scripts/autonomous.mjs +434 -0
- package/dist/template/.claude/scripts/docker-run.mjs +363 -0
- package/dist/template/.claude/templates/claude-md.md +697 -0
- package/dist/template/.dockerignore +8 -0
- package/dist/template/Dockerfile +27 -0
- package/dist/template/docker-compose.yml +15 -0
- package/dist/template/docker-entrypoint.sh +26 -0
- package/package.json +33 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Docker-based isolated runner for Claude Starter Kit.
|
|
5
|
+
* Single cross-platform script replacing docker-run.sh + docker-run.ps1.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node .claude/scripts/docker-run.mjs # autonomous (default)
|
|
9
|
+
* node .claude/scripts/docker-run.mjs --shell # interactive shell
|
|
10
|
+
* node .claude/scripts/docker-run.mjs --help # all options
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
15
|
+
import { join, resolve, dirname } from 'node:path';
|
|
16
|
+
import { homedir, platform as osPlatform, tmpdir } from 'node:os';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const PROJECT_DIR = resolve(__dirname, '../..');
|
|
21
|
+
const AUTH_COMPOSE = join(tmpdir(), 'claude-starter-kit-auth-compose.yml');
|
|
22
|
+
const IS_WIN = osPlatform() === 'win32';
|
|
23
|
+
const IS_MAC = osPlatform() === 'darwin';
|
|
24
|
+
|
|
25
|
+
// ─── Colors ───
|
|
26
|
+
|
|
27
|
+
const C = { r: '\x1b[31m', g: '\x1b[32m', y: '\x1b[33m', c: '\x1b[36m', n: '\x1b[0m' };
|
|
28
|
+
const info = (m) => console.log(`${C.g}[INFO]${C.n} ${m}`);
|
|
29
|
+
const warn = (m) => console.log(`${C.y}[WARN]${C.n} ${m}`);
|
|
30
|
+
const error = (m) => console.log(`${C.r}[ERROR]${C.n} ${m}`);
|
|
31
|
+
const step = (m) => console.log(`${C.c}[STEP]${C.n} ${m}`);
|
|
32
|
+
|
|
33
|
+
// ─── Helpers ───
|
|
34
|
+
|
|
35
|
+
function sleepSync(ms) {
|
|
36
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function run(cmd, args = [], opts = {}) {
|
|
40
|
+
return spawnSync(cmd, args, {
|
|
41
|
+
shell: true,
|
|
42
|
+
cwd: opts.cwd ?? PROJECT_DIR,
|
|
43
|
+
stdio: opts.capture ? 'pipe' : 'inherit',
|
|
44
|
+
timeout: opts.timeout,
|
|
45
|
+
env: { ...process.env, ...opts.env },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ok(cmd, args = []) {
|
|
50
|
+
return run(cmd, args, { capture: true }).status === 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasCmd(cmd) {
|
|
54
|
+
const check = IS_WIN ? 'where' : 'which';
|
|
55
|
+
return spawnSync(check, [cmd], { shell: true, stdio: 'pipe' }).status === 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Convert Windows backslash path to forward slashes for Docker */
|
|
59
|
+
function toDockerPath(p) {
|
|
60
|
+
return p.replace(/\\/g, '/');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run docker compose with the auth override file if it exists.
|
|
65
|
+
* Does NOT use shell: true — on Windows, cmd.exe splits arguments after
|
|
66
|
+
* '-c' into separate words, breaking 'bash -c "node ..."' commands.
|
|
67
|
+
* Without shell, Node.js passes each array element as a distinct argv entry.
|
|
68
|
+
*/
|
|
69
|
+
function compose(args, opts = {}) {
|
|
70
|
+
const files = ['-f', 'docker-compose.yml'];
|
|
71
|
+
if (existsSync(AUTH_COMPOSE)) files.push('-f', AUTH_COMPOSE);
|
|
72
|
+
return spawnSync('docker', ['compose', ...files, ...args], {
|
|
73
|
+
cwd: PROJECT_DIR,
|
|
74
|
+
stdio: opts.capture ? 'pipe' : 'inherit',
|
|
75
|
+
env: { ...process.env },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function cleanup() {
|
|
80
|
+
try { if (existsSync(AUTH_COMPOSE)) unlinkSync(AUTH_COMPOSE); } catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
process.on('exit', cleanup);
|
|
84
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
|
85
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
|
86
|
+
|
|
87
|
+
// ─── Parse args ───
|
|
88
|
+
|
|
89
|
+
function parseArgs() {
|
|
90
|
+
const args = process.argv.slice(2);
|
|
91
|
+
const opts = {
|
|
92
|
+
shell: false, rebuild: false, login: false, help: false, verbose: false,
|
|
93
|
+
maxIterations: '', maxTurns: '', delay: '', cooldown: '',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < args.length; i++) {
|
|
97
|
+
switch (args[i]) {
|
|
98
|
+
case '--help': case '-h': opts.help = true; break;
|
|
99
|
+
case '--shell': opts.shell = true; break;
|
|
100
|
+
case '--rebuild': opts.rebuild = true; break;
|
|
101
|
+
case '--login': opts.login = true; break;
|
|
102
|
+
case '--verbose': opts.verbose = true; break;
|
|
103
|
+
case '--max-iterations': case '--max-turns': case '--delay': case '--cooldown': {
|
|
104
|
+
const flag = args[i];
|
|
105
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('--')) {
|
|
106
|
+
error(`${flag} requires a value`); process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const key = flag.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
109
|
+
opts[key] = args[++i];
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
error(`Unknown option: ${args[i]}`); process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return opts;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function printHelp() {
|
|
120
|
+
console.log(`
|
|
121
|
+
Docker runner for Claude Starter Kit
|
|
122
|
+
|
|
123
|
+
One command to start safe, isolated autonomous development.
|
|
124
|
+
--skip-permissions is added automatically (the container is the sandbox).
|
|
125
|
+
|
|
126
|
+
Usage:
|
|
127
|
+
node .claude/scripts/docker-run.mjs [options]
|
|
128
|
+
|
|
129
|
+
Options:
|
|
130
|
+
--shell Interactive shell instead of autonomous loop
|
|
131
|
+
--max-iterations <n> Max iterations (default: 50)
|
|
132
|
+
--max-turns <n> Max turns per iteration (default: 50)
|
|
133
|
+
--delay <ms> Pause between tasks (default: 5000)
|
|
134
|
+
--cooldown <ms> Wait after rate limit (default: 60000)
|
|
135
|
+
--rebuild Force rebuild Docker image
|
|
136
|
+
--login Run 'claude auth login' on host before starting container
|
|
137
|
+
--verbose Show live Claude activity (tool calls, file edits, text)
|
|
138
|
+
--help, -h Show this help
|
|
139
|
+
|
|
140
|
+
Authentication:
|
|
141
|
+
The container uses your HOST machine's Claude auth automatically.
|
|
142
|
+
- Max plan: run 'claude auth login' on your host first (or use --login)
|
|
143
|
+
- API key: set ANTHROPIC_API_KEY=sk-... before running
|
|
144
|
+
|
|
145
|
+
npm Publishing (optional):
|
|
146
|
+
Set NPM_TOKEN=npm_XXXXX before running.
|
|
147
|
+
Optionally: NPM_REGISTRY=npm.pkg.github.com (default: registry.npmjs.org)
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
node .claude/scripts/docker-run.mjs
|
|
151
|
+
node .claude/scripts/docker-run.mjs --max-iterations 20
|
|
152
|
+
node .claude/scripts/docker-run.mjs --shell
|
|
153
|
+
node .claude/scripts/docker-run.mjs --login
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Docker installation ───
|
|
158
|
+
|
|
159
|
+
function installDocker() {
|
|
160
|
+
if (IS_WIN) {
|
|
161
|
+
if (hasCmd('winget')) {
|
|
162
|
+
info('Installing Docker Desktop via winget...');
|
|
163
|
+
run('winget', ['install', '-e', '--id', 'Docker.DockerDesktop',
|
|
164
|
+
'--accept-package-agreements', '--accept-source-agreements']);
|
|
165
|
+
warn('Docker Desktop installed. Restart terminal, start Docker Desktop, re-run.');
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
if (hasCmd('choco')) {
|
|
169
|
+
info('Installing Docker Desktop via Chocolatey...');
|
|
170
|
+
run('choco', ['install', 'docker-desktop', '-y']);
|
|
171
|
+
warn('Docker Desktop installed. Restart terminal, start Docker Desktop, re-run.');
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
error('Docker not found. Install Docker Desktop:');
|
|
175
|
+
error(' https://docs.docker.com/desktop/install/windows-install/');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
|
|
178
|
+
} else if (IS_MAC) {
|
|
179
|
+
if (hasCmd('brew')) {
|
|
180
|
+
info('Installing Docker Desktop via Homebrew...');
|
|
181
|
+
run('brew', ['install', '--cask', 'docker']);
|
|
182
|
+
info('Start Docker Desktop from Applications, then re-run.');
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
error('Install Docker Desktop: https://docs.docker.com/desktop/install/mac-install/');
|
|
186
|
+
process.exit(1);
|
|
187
|
+
|
|
188
|
+
} else {
|
|
189
|
+
// Linux — official convenience script
|
|
190
|
+
info('Installing Docker via get.docker.com...');
|
|
191
|
+
const result = run('sh', ['-c', 'curl -fsSL https://get.docker.com | sh']);
|
|
192
|
+
if (result.status !== 0) {
|
|
193
|
+
error('Docker installation failed. Install manually: https://docs.docker.com/engine/install/');
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
run('sudo', ['usermod', '-aG', 'docker', process.env.USER || ''], { capture: true });
|
|
197
|
+
warn('You may need "newgrp docker" or re-login for group changes.');
|
|
198
|
+
info('Docker installed.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function startDaemon() {
|
|
203
|
+
warn('Docker daemon not running. Starting...');
|
|
204
|
+
|
|
205
|
+
if (IS_WIN) {
|
|
206
|
+
const exe = join(process.env.ProgramFiles || 'C:\\Program Files',
|
|
207
|
+
'Docker', 'Docker', 'Docker Desktop.exe');
|
|
208
|
+
if (existsSync(exe)) {
|
|
209
|
+
spawnSync('cmd', ['/c', 'start', '', `"${exe}"`], { stdio: 'ignore' });
|
|
210
|
+
}
|
|
211
|
+
} else if (IS_MAC) {
|
|
212
|
+
run('open', ['-a', 'Docker'], { capture: true });
|
|
213
|
+
} else {
|
|
214
|
+
run('sudo', ['systemctl', 'start', 'docker'], { capture: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
info('Waiting for Docker daemon (up to 120s)...');
|
|
218
|
+
for (let i = 0; i < 60; i++) {
|
|
219
|
+
sleepSync(2000);
|
|
220
|
+
if (ok('docker', ['info'])) return;
|
|
221
|
+
if (i % 5 === 4) console.log(` ... waiting (${(i + 1) * 2}s)`);
|
|
222
|
+
}
|
|
223
|
+
error('Docker daemon not running. Start it manually and re-run.');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ─── Main ───
|
|
228
|
+
|
|
229
|
+
const opts = parseArgs();
|
|
230
|
+
if (opts.help) { printHelp(); process.exit(0); }
|
|
231
|
+
|
|
232
|
+
console.log(`\n${C.c}╔══════════════════════════════════════════╗${C.n}`);
|
|
233
|
+
console.log(`${C.c}║ Claude Starter Kit — Docker Runner ║${C.n}`);
|
|
234
|
+
console.log(`${C.c}╚══════════════════════════════════════════╝${C.n}\n`);
|
|
235
|
+
|
|
236
|
+
// 1. Docker
|
|
237
|
+
step('1/4 Checking Docker...');
|
|
238
|
+
if (!hasCmd('docker')) installDocker();
|
|
239
|
+
if (!ok('docker', ['info'])) startDaemon();
|
|
240
|
+
info('Docker is ready.');
|
|
241
|
+
|
|
242
|
+
// 2. Build
|
|
243
|
+
step('2/4 Building container image...');
|
|
244
|
+
compose(opts.rebuild ? ['build', '--no-cache'] : ['build', '-q']);
|
|
245
|
+
info('Image built.');
|
|
246
|
+
|
|
247
|
+
// 3. Auth
|
|
248
|
+
step('3/4 Setting up authentication...');
|
|
249
|
+
|
|
250
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
251
|
+
info('Using ANTHROPIC_API_KEY from environment.');
|
|
252
|
+
} else {
|
|
253
|
+
// --login: authenticate on the HOST where browser + TTY work
|
|
254
|
+
if (opts.login) {
|
|
255
|
+
if (hasCmd('claude')) {
|
|
256
|
+
info("Running 'claude auth login' on host...");
|
|
257
|
+
run('claude', ['auth', 'login']);
|
|
258
|
+
} else {
|
|
259
|
+
error('Claude CLI not found on host. Install it first:');
|
|
260
|
+
console.log(' npm i -g @anthropic-ai/claude-code');
|
|
261
|
+
console.log(' claude auth login');
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log('Or use an API key:');
|
|
264
|
+
console.log(' ANTHROPIC_API_KEY=sk-... node .claude/scripts/docker-run.mjs');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Mount ONLY auth credentials from the host, NOT the entire ~/.claude/.
|
|
270
|
+
// The ~/.claude/ directory contains plugins, settings, and marketplace
|
|
271
|
+
// configs with Windows paths (C:\Users\...). Mounting it into a Linux
|
|
272
|
+
// container causes Claude Code to misinterpret those paths and create
|
|
273
|
+
// junk directories in the project root. The named volume 'claude-home'
|
|
274
|
+
// from docker-compose.yml handles ~/.claude/ inside the container.
|
|
275
|
+
//
|
|
276
|
+
// Auth is stored in ~/.claude/.credentials.json (OAuth tokens).
|
|
277
|
+
// Do NOT mount ~/.claude.json — it contains host-specific settings
|
|
278
|
+
// (installMethod, tips, stats) that cause warnings in the container.
|
|
279
|
+
// Do NOT mount ~/.claude/ — it has plugin configs with Windows paths
|
|
280
|
+
// that create junk directories in the project root.
|
|
281
|
+
//
|
|
282
|
+
// We use a compose override YAML file instead of -v flags because
|
|
283
|
+
// 'docker compose run -v' on Windows misparses drive-letter paths.
|
|
284
|
+
const home = homedir();
|
|
285
|
+
const credentialsJson = join(home, '.claude', '.credentials.json');
|
|
286
|
+
|
|
287
|
+
if (existsSync(credentialsJson)) {
|
|
288
|
+
info('Mounting host Claude auth credentials into container.');
|
|
289
|
+
const vol = ` - ${toDockerPath(credentialsJson)}:/home/claude/.claude/.credentials.json:ro`;
|
|
290
|
+
writeFileSync(AUTH_COMPOSE, `services:\n claude:\n volumes:\n${vol}\n`);
|
|
291
|
+
} else {
|
|
292
|
+
error('No Claude authentication found. Options:');
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log(' Option 1 — Claude Max plan (OAuth):');
|
|
295
|
+
console.log(' npm i -g @anthropic-ai/claude-code');
|
|
296
|
+
console.log(' claude auth login');
|
|
297
|
+
console.log(' Then re-run this script.');
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log(' Option 2 — API key:');
|
|
300
|
+
console.log(' ANTHROPIC_API_KEY=sk-... node .claude/scripts/docker-run.mjs');
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log(' Option 3 — Use --login flag:');
|
|
303
|
+
console.log(' node .claude/scripts/docker-run.mjs --login');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 4. Interactive setup if project not initialized
|
|
309
|
+
const needsSetup = !existsSync(join(PROJECT_DIR, 'MEMORY.md'));
|
|
310
|
+
|
|
311
|
+
if (needsSetup && !opts.shell) {
|
|
312
|
+
step('4/5 Project not initialized — starting interactive setup...');
|
|
313
|
+
console.log('');
|
|
314
|
+
info('Answer the discovery questions to set up your project.');
|
|
315
|
+
info('This creates CLAUDE.md, PRODUCT.md, TODO.md, and MEMORY.md.');
|
|
316
|
+
info('After setup, the autonomous development loop starts automatically.');
|
|
317
|
+
console.log('');
|
|
318
|
+
|
|
319
|
+
// Interactive mode — no -T flag, TTY allocated for user input.
|
|
320
|
+
// The prompt argument starts the discovery conversation immediately
|
|
321
|
+
// while keeping the session interactive for follow-up questions.
|
|
322
|
+
const setupResult = compose(['run', '--rm', 'claude', '-c',
|
|
323
|
+
'claude --agent project-initializer --dangerously-skip-permissions "Initialize this project. First check if PLAN.md exists in the project root — if it does, use it as the primary source of information and only ask about details that are missing or unclear. If PLAN.md does not exist, start the full discovery conversation."']);
|
|
324
|
+
|
|
325
|
+
if (setupResult.status !== 0) {
|
|
326
|
+
warn('Setup exited with errors. Use --shell for manual setup.');
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!existsSync(join(PROJECT_DIR, 'MEMORY.md'))) {
|
|
331
|
+
warn('Setup did not complete (MEMORY.md not found).');
|
|
332
|
+
warn('Run again or use --shell for manual setup.');
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
info('Setup complete. Starting autonomous development...');
|
|
337
|
+
console.log('');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 5. Run
|
|
341
|
+
step(needsSetup ? '5/5 Starting Claude Code...' : '4/4 Starting Claude Code...');
|
|
342
|
+
|
|
343
|
+
if (opts.shell) {
|
|
344
|
+
console.log('');
|
|
345
|
+
info('Interactive shell. Examples:');
|
|
346
|
+
info(' claude # interactive Claude');
|
|
347
|
+
info(' node .claude/scripts/autonomous.mjs --skip-permissions # autonomous loop');
|
|
348
|
+
console.log('');
|
|
349
|
+
compose(['run', '--rm', 'claude']);
|
|
350
|
+
} else {
|
|
351
|
+
let autoArgs = '--skip-permissions';
|
|
352
|
+
if (opts.maxIterations) autoArgs += ` --max-iterations ${opts.maxIterations}`;
|
|
353
|
+
if (opts.maxTurns) autoArgs += ` --max-turns ${opts.maxTurns}`;
|
|
354
|
+
if (opts.delay) autoArgs += ` --delay ${opts.delay}`;
|
|
355
|
+
if (opts.cooldown) autoArgs += ` --cooldown ${opts.cooldown}`;
|
|
356
|
+
if (opts.verbose) autoArgs += ' --verbose';
|
|
357
|
+
|
|
358
|
+
console.log('');
|
|
359
|
+
info('Autonomous mode — isolated in Docker, --skip-permissions is safe.');
|
|
360
|
+
info('Press Ctrl+C to stop after current iteration.');
|
|
361
|
+
console.log('');
|
|
362
|
+
compose(['run', '--rm', '-T', 'claude', '-c', `node .claude/scripts/autonomous.mjs ${autoArgs}`]);
|
|
363
|
+
}
|