create-harness-vibe-coding 0.8.17 → 0.8.19
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/CHANGELOG.md +22 -0
- package/README-CN.md +2 -0
- package/README.md +3 -0
- package/package.json +5 -2
- package/src/generator.js +613 -97
- package/src/index.js +556 -309
- package/src/prompts.js +18 -0
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js +17 -0
- package/src/ui/dist/assets/AgentsRoute-CuSKYmFu.js.map +1 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js +7 -0
- package/src/ui/dist/assets/RolesRoute-DOzTYVRm.js.map +1 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js +12 -0
- package/src/ui/dist/assets/SettingsRoute-DPU7jSQm.js.map +1 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js +50 -0
- package/src/ui/dist/assets/TaskList-BN4r8s1i.js.map +1 -0
- package/src/ui/dist/assets/TerminalDrawer-6GBZ9nXN.css +32 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js +74 -0
- package/src/ui/dist/assets/TerminalDrawer-Cej4PxsH.js.map +1 -0
- package/src/ui/dist/assets/WorkflowRoute-BnuhLJ6X.css +1 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js +39 -0
- package/src/ui/dist/assets/WorkflowRoute-C-clKe-j.js.map +1 -0
- package/src/ui/dist/assets/index-BOVYWntB.js +117 -0
- package/src/ui/dist/assets/index-BOVYWntB.js.map +1 -0
- package/src/ui/dist/assets/index-DaUObq0H.css +1 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js +7 -0
- package/src/ui/dist/assets/maximize-2-LNrr_rKr.js.map +1 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js +7 -0
- package/src/ui/dist/assets/plus-QuTJyoT3.js.map +1 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js +2 -0
- package/src/ui/dist/assets/proxy-D0vUbds5.js.map +1 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js +7 -0
- package/src/ui/dist/assets/refresh-cw-JNmExijd.js.map +1 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js +7 -0
- package/src/ui/dist/assets/terminal-BBQvnOAK.js.map +1 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js +7 -0
- package/src/ui/dist/assets/useReducedMotion-BqmH0Tf7.js.map +1 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js +7 -0
- package/src/ui/dist/assets/x-DoIOGAJz.js.map +1 -0
- package/src/ui/dist/index.html +18 -0
- package/src/ui/index.html +17 -0
- package/src/ui/package.json +33 -0
- package/src/ui/pnpm-lock.yaml +1582 -0
- package/src/ui/pnpm-workspace.yaml +2 -0
- package/src/ui/src/App.tsx +86 -0
- package/src/ui/src/api.ts +93 -0
- package/src/ui/src/components/AgentsRoute.tsx +503 -0
- package/src/ui/src/components/Footer.tsx +69 -0
- package/src/ui/src/components/Header.tsx +175 -0
- package/src/ui/src/components/LoadingView.tsx +22 -0
- package/src/ui/src/components/RolesRoute.tsx +169 -0
- package/src/ui/src/components/SettingsRoute.tsx +166 -0
- package/src/ui/src/components/TaskList.tsx +388 -0
- package/src/ui/src/components/TerminalDrawer.tsx +611 -0
- package/src/ui/src/components/WorkflowRoute.tsx +670 -0
- package/src/ui/src/hooks/useReducedMotion.ts +17 -0
- package/src/ui/src/hooks/useServerConnection.ts +114 -0
- package/src/ui/src/index.css +281 -0
- package/src/ui/src/main.tsx +11 -0
- package/src/ui/src/types.ts +108 -0
- package/src/ui/tsconfig.json +21 -0
- package/src/ui/vite.config.ts +19 -0
- package/src/wf-ui-server/__tests__/a2a-store.test.mjs +79 -0
- package/src/wf-ui-server/__tests__/peer-capsule.test.mjs +268 -0
- package/src/wf-ui-server/__tests__/pty-adapter.test.mjs +70 -0
- package/src/wf-ui-server/__tests__/runtime-config.test.mjs +43 -0
- package/src/wf-ui-server/__tests__/runtime-detector.test.mjs +90 -0
- package/src/wf-ui-server/__tests__/security.test.mjs +59 -0
- package/src/wf-ui-server/__tests__/server.integration.test.mjs +150 -0
- package/src/wf-ui-server/__tests__/session-registry.test.mjs +238 -0
- package/src/wf-ui-server/__tests__/settings.test.mjs +135 -0
- package/src/wf-ui-server/__tests__/task-parser.test.mjs +200 -0
- package/src/wf-ui-server/__tests__/terminal-store.test.mjs +138 -0
- package/src/wf-ui-server/__tests__/token.test.mjs +48 -0
- package/src/wf-ui-server/__tests__/ws-events.integration.test.mjs +419 -0
- package/src/wf-ui-server/__tests__/ws-terminal.integration.test.mjs +383 -0
- package/src/wf-ui-server/a2a-store.mjs +296 -0
- package/src/wf-ui-server/peer-capsule.mjs +213 -0
- package/src/wf-ui-server/pty-adapter.mjs +155 -0
- package/src/wf-ui-server/runtime-config.mjs +153 -0
- package/src/wf-ui-server/runtime-detector.mjs +374 -0
- package/src/wf-ui-server/security.mjs +67 -0
- package/src/wf-ui-server/server.mjs +849 -0
- package/src/wf-ui-server/session-registry.mjs +204 -0
- package/src/wf-ui-server/settings.mjs +100 -0
- package/src/wf-ui-server/task-parser.mjs +133 -0
- package/src/wf-ui-server/terminal-store.mjs +225 -0
- package/src/wf-ui-server/token.mjs +41 -0
- package/src/wf-ui-server/ws-events.mjs +354 -0
- package/src/wf-ui-server/ws-terminal.mjs +473 -0
- package/templates/common/.claude/commands/wf-command-create.md +58 -0
- package/templates/common/.claude/commands/wf-help.md +5 -0
- package/templates/common/.claude/commands/wf-task-archive.md +26 -0
- package/templates/common/.claude/commands/wf-task-list.md +26 -0
- package/templates/common/.claude/commands/wf-task-record.md +24 -0
- package/templates/common/.claude/commands/wf-ui.md +26 -0
- package/templates/common/.claude/commands/wf-update.md +54 -9
- package/templates/common/.claude/rules/ecc/common.md +1 -1
- package/templates/common/.claude/skills/wf-agents-docs/SKILL.md +15 -30
- package/templates/common/.claude/skills/wf-auto/SKILL.md +3 -3
- package/templates/common/.claude/skills/wf-auto-spark/SKILL.md +2 -2
- package/templates/common/.claude/skills/wf-command-create/SKILL.md +37 -0
- package/templates/common/.claude/skills/wf-max/SKILL.md +1 -1
- package/templates/common/.claude/skills/wf-review/SKILL.md +29 -2
- package/templates/common/.claude/skills/wf-task-archive/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-list/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-task-record/SKILL.md +28 -0
- package/templates/common/.claude/skills/wf-ui/SKILL.md +78 -0
- package/templates/common/.claude/skills/wf-update/SKILL.md +55 -58
- package/templates/common/.harness-version +105 -47
- package/templates/common/.opencode/commands/wf-command-create.md +61 -0
- package/templates/common/.opencode/commands/wf-help.md +5 -0
- package/templates/common/.opencode/commands/wf-task-archive.md +29 -0
- package/templates/common/.opencode/commands/wf-task-list.md +29 -0
- package/templates/common/.opencode/commands/wf-task-record.md +27 -0
- package/templates/common/.opencode/commands/wf-ui.md +26 -0
- package/templates/common/.opencode/commands/wf-update.md +54 -9
- package/templates/common/CLAUDE.md +8 -6
- package/templates/common/Harness/MEMORY.md +11 -0
- package/templates/common/Harness/README.md +21 -38
- package/templates/common/Harness/a2a/role-graph.json +79 -0
- package/templates/common/Harness/a2a/runtime-registry.json +5 -0
- package/templates/common/Harness/a2a/skills/terminal-control.json +15 -0
- package/templates/common/Harness/ownership.manifest.json +142 -2
- package/templates/common/Harness/scripts/README.md +134 -0
- package/templates/common/Harness/scripts/a2a-terminal.mjs +191 -0
- package/templates/common/Harness/scripts/context-budget.mjs +1 -1
- package/templates/common/Harness/scripts/sync-host-global.mjs +278 -0
- package/templates/common/Harness/scripts/task-state.mjs +949 -26
- package/templates/common/Harness/scripts/validate-harness.mjs +637 -62
- package/templates/common/Harness/scripts/wf-remove.mjs +42 -5
- package/templates/common/Harness/scripts/wf-update-check.mjs +37 -9
- package/templates/common/Harness/scripts/wf-update-runner.mjs +325 -0
- package/templates/common/Harness/settings.json +35 -0
- package/templates/common/Harness/specs/guides/SETUP.md +8 -0
- package/templates/common/Harness/specs/protocols/MEMORY_PROTOCOL.md +15 -0
- package/templates/common/Harness/specs/protocols/TASK_ARCHIVE.md +16 -5
- package/templates/common/Harness/specs/runtime/command-surface.json +229 -0
- package/templates/common/Harness/specs/runtime/subagents.md +6 -0
- package/templates/common/Harness/specs/workflows/WF-AUTO-SPARK.md +8 -8
- package/templates/common/Harness/specs/workflows/WF-AUTO.md +12 -12
- package/templates/common/Harness/specs/workflows/WF-MAX.md +5 -0
- package/templates/common/Harness/specs/workflows/WF-STATE.md +66 -0
- package/templates/common/Harness/tasks/_template/NAMING.md +16 -16
- package/templates/common/Harness/tasks/_template/PLAN.md +17 -60
- package/templates/common/Harness/tasks/_template/PROBLEM.md +18 -0
- package/templates/common/Harness/tasks/_template/PROGRESS.md +9 -14
- package/templates/common/Harness/tasks/_template/REFERENCES.md +26 -0
- package/templates/common/Harness/tasks/_template/STATE.json +6 -0
- package/templates/common/Harness/tasks/_template/ARTIFACTS.md +0 -3
- package/templates/common/Harness/tasks/_template/NOTES.md +0 -3
package/src/index.js
CHANGED
|
@@ -2,90 +2,115 @@
|
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
-
import { askConflictPolicy, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
7
|
+
import { askConflictPolicy, askInstallScope, askOptionalSelections, askProjectName, askTargetDir } from './prompts.js';
|
|
8
8
|
import { generate, getOptionalCatalog } from './generator.js';
|
|
9
9
|
|
|
10
10
|
const UPDATE_SUCCESS_STATUSES = new Set(['up-to-date', 'update-available', 'partial-update']);
|
|
11
11
|
const UPDATE_FAILURE_STATUSES = new Set(['error', 'offline', 'template-remote', 'downgrade-refused']);
|
|
12
12
|
const CANONICAL_UPDATE_SOURCE_BASE = 'https://raw.githubusercontent.com/LiWeny16/create-harness-vibe-coding/main/templates/common/';
|
|
13
|
-
|
|
14
|
-
// ── CLI flags ──────────────────────────────────────────────
|
|
15
|
-
const raw = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
// ── CLI flags ──────────────────────────────────────────────
|
|
15
|
+
const raw = process.argv.slice(2);
|
|
16
|
+
function extractFlag(args, flag) {
|
|
17
|
+
const idx = args.indexOf(flag);
|
|
18
|
+
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
|
19
|
+
const withEquals = args.find(arg => arg.startsWith(`${flag}=`));
|
|
20
|
+
if (withEquals) return withEquals.slice(flag.length + 1);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function hasFlag(args, flag) {
|
|
24
|
+
return args.includes(flag);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// wf-ui subcommand: handle before parseArgs so --project/--port/--open flags
|
|
28
|
+
// never fall through to the scaffold installer.
|
|
29
|
+
if (raw[0] === 'wf-ui') {
|
|
30
|
+
await runWfUi(raw.slice(1));
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
const parsed = parseArgs(raw);
|
|
17
35
|
const showHelp = parsed.flags.help || parsed.flags.h;
|
|
18
36
|
const skipPrompts = parsed.flags.yes || parsed.flags.y;
|
|
19
37
|
const conflictPolicyProvided = parsed.flags.onConflict !== undefined;
|
|
20
|
-
|
|
21
|
-
if (parsed.errors.length > 0) {
|
|
22
|
-
for (const err of parsed.errors) {
|
|
23
|
-
console.error(pc.red(`Error: ${err}`));
|
|
24
|
-
}
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (showHelp) {
|
|
29
|
-
console.log('');
|
|
30
|
-
console.log(' create-harness-vibe-coding');
|
|
31
|
-
console.log('');
|
|
32
|
-
console.log(' Usage:');
|
|
33
|
-
console.log(' npx create-harness-vibe-coding@latest [project-name] [target-dir] [flags]');
|
|
34
|
-
console.log('');
|
|
35
|
-
console.log(' Arguments:');
|
|
36
|
-
console.log(' project-name Name for the new project (default: my-vibe-project)');
|
|
37
|
-
console.log(' target-dir Directory to create the project in (default: ./<project-name>)');
|
|
38
|
-
console.log('');
|
|
39
|
-
console.log(' Flags:');
|
|
40
|
-
console.log(' -y, --yes Skip all prompts, use defaults or provided args');
|
|
41
|
-
console.log(' -h, --help Show this help');
|
|
42
|
-
console.log(' --dry-run Print the planned writes without creating files');
|
|
43
|
-
console.log(' --on-conflict <policy> fail, skip, backup, or overwrite (default: fail)');
|
|
38
|
+
|
|
39
|
+
if (parsed.errors.length > 0 && raw[0] !== 'wf-ui') {
|
|
40
|
+
for (const err of parsed.errors) {
|
|
41
|
+
console.error(pc.red(`Error: ${err}`));
|
|
42
|
+
}
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (showHelp) {
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(' create-harness-vibe-coding');
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' Usage:');
|
|
51
|
+
console.log(' npx create-harness-vibe-coding@latest [project-name] [target-dir] [flags]');
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(' Arguments:');
|
|
54
|
+
console.log(' project-name Name for the new project (default: my-vibe-project)');
|
|
55
|
+
console.log(' target-dir Directory to create the project in (default: ./<project-name>)');
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(' Flags:');
|
|
58
|
+
console.log(' -y, --yes Skip all prompts, use defaults or provided args');
|
|
59
|
+
console.log(' -h, --help Show this help');
|
|
60
|
+
console.log(' --dry-run Print the planned writes without creating files');
|
|
61
|
+
console.log(' --on-conflict <policy> fail, skip, backup, or overwrite (default: fail)');
|
|
44
62
|
console.log(' --with <id,id> Add optional local workflow skills');
|
|
45
63
|
console.log(' --without <id,id> Remove optional workflow skills selected by --preset or --with');
|
|
46
64
|
console.log(' --recommend <id,id> Record recommendation-only external capabilities');
|
|
47
65
|
console.log(' --preset <name> Add a built-in optional workflow preset');
|
|
66
|
+
console.log(' --install-scope <scope> project or global (default: project)');
|
|
67
|
+
console.log(' --global-dir <dir> Global Harness runtime directory for --install-scope global');
|
|
68
|
+
console.log(' --host-global-dir <dir> Base directory for Claude/Codex/OpenCode global copies');
|
|
48
69
|
console.log(' --list-options Print optional workflow skills and presets');
|
|
49
70
|
console.log(' --json Output machine-readable JSON (use with --dry-run for planning)');
|
|
50
71
|
console.log('');
|
|
51
72
|
console.log(' Interactive mode offers checkbox selection for optional workflows and external recommendations.');
|
|
52
|
-
console.log('');
|
|
53
|
-
console.log(' Examples:');
|
|
54
|
-
console.log(' npx create-harness-vibe-coding@latest');
|
|
55
|
-
console.log(' npx create-harness-vibe-coding@latest -y');
|
|
56
|
-
console.log(' npx create-harness-vibe-coding@latest my-project');
|
|
57
|
-
console.log(' npx create-harness-vibe-coding@latest my-project ./dist/my-project -y');
|
|
58
|
-
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
59
|
-
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(' Examples:');
|
|
75
|
+
console.log(' npx create-harness-vibe-coding@latest');
|
|
76
|
+
console.log(' npx create-harness-vibe-coding@latest -y');
|
|
77
|
+
console.log(' npx create-harness-vibe-coding@latest my-project');
|
|
78
|
+
console.log(' npx create-harness-vibe-coding@latest my-project ./dist/my-project -y');
|
|
79
|
+
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --dry-run');
|
|
80
|
+
console.log(' npx create-harness-vibe-coding@latest legacy ./legacy -y --on-conflict skip');
|
|
60
81
|
console.log(' npx create-harness-vibe-coding@latest web ./web -y --with ts-react-frontend,ui-ux-review');
|
|
61
|
-
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
62
|
-
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
63
|
-
console.log('');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
console.log(' npx create-harness-vibe-coding@latest web ./web -y --preset web-app');
|
|
83
|
+
console.log(' npx create-harness-vibe-coding@latest api ./api -y --preset fullstack --without github-pr-review');
|
|
84
|
+
console.log(' npx create-harness-vibe-coding@latest app ./app -y --install-scope global');
|
|
85
|
+
console.log('');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (parsed.flags.listOptions) {
|
|
90
|
+
printOptions();
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Positional args (non-flag)
|
|
95
|
+
const positional = parsed.positionals;
|
|
96
|
+
const argName = positional[0];
|
|
97
|
+
const argDir = positional[1];
|
|
98
|
+
const generationOptions = {
|
|
99
|
+
dryRun: Boolean(parsed.flags.dryRun),
|
|
100
|
+
onConflict: parsed.flags.onConflict || 'fail',
|
|
101
|
+
withOptions: parsed.flags.with || [],
|
|
80
102
|
withoutOptions: parsed.flags.without || [],
|
|
81
103
|
externalOptions: parsed.flags.recommend || [],
|
|
82
104
|
preset: parsed.flags.preset,
|
|
105
|
+
installScope: parsed.flags.installScope || 'project',
|
|
106
|
+
globalDir: parsed.flags.globalDir,
|
|
107
|
+
hostGlobalDir: parsed.flags.hostGlobalDir,
|
|
83
108
|
json: Boolean(parsed.flags.json),
|
|
84
109
|
};
|
|
85
|
-
|
|
86
|
-
const DEFAULT_NAME = 'my-vibe-project';
|
|
87
|
-
|
|
88
|
-
// --json: machine-readable output, no prompts, no decorative output
|
|
110
|
+
|
|
111
|
+
const DEFAULT_NAME = 'my-vibe-project';
|
|
112
|
+
|
|
113
|
+
// --json: machine-readable output, no prompts, no decorative output
|
|
89
114
|
if (generationOptions.json) {
|
|
90
115
|
const projectName = argName || DEFAULT_NAME;
|
|
91
116
|
const targetDir = argDir || `./${projectName}`;
|
|
@@ -109,20 +134,22 @@ if (generationOptions.json) {
|
|
|
109
134
|
scan,
|
|
110
135
|
});
|
|
111
136
|
printJsonResult(result);
|
|
112
|
-
// printJsonResult exits with 1 on failure; we only reach here on success
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.log('');
|
|
117
|
-
console.log(pc.magenta('╔══════════════════════════════════════════╗'));
|
|
118
|
-
console.log(pc.magenta('║ create-harness-vibe-coding ║'));
|
|
119
|
-
console.log(pc.magenta('║ 0-1 Product Harness Scaffold ║'));
|
|
120
|
-
console.log(pc.magenta('╚══════════════════════════════════════════╝'));
|
|
121
|
-
console.log('');
|
|
122
|
-
|
|
123
|
-
let projectName, targetDir;
|
|
124
|
-
|
|
125
|
-
//
|
|
137
|
+
// printJsonResult exits with 1 on failure; we only reach here on success
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(pc.magenta('╔══════════════════════════════════════════╗'));
|
|
143
|
+
console.log(pc.magenta('║ create-harness-vibe-coding ║'));
|
|
144
|
+
console.log(pc.magenta('║ 0-1 Product Harness Scaffold ║'));
|
|
145
|
+
console.log(pc.magenta('╚══════════════════════════════════════════╝'));
|
|
146
|
+
console.log('');
|
|
147
|
+
|
|
148
|
+
let projectName, targetDir;
|
|
149
|
+
|
|
150
|
+
// wf-ui: server already running, skip the scaffold/update flow
|
|
151
|
+
if (raw[0] !== 'wf-ui') {
|
|
152
|
+
// Non-interactive: positionals provided OR -y/--yes flag set
|
|
126
153
|
if (argName || skipPrompts) {
|
|
127
154
|
projectName = argName || DEFAULT_NAME;
|
|
128
155
|
targetDir = argDir || `./${projectName}`;
|
|
@@ -137,47 +164,64 @@ if (argName || skipPrompts) {
|
|
|
137
164
|
}
|
|
138
165
|
|
|
139
166
|
console.log(pc.dim('────────────────────────────────────────────'));
|
|
140
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
167
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
141
168
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
142
|
-
console.log(`
|
|
143
|
-
if (generationOptions.
|
|
169
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
170
|
+
if (generationOptions.globalDir) {
|
|
171
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
172
|
+
}
|
|
173
|
+
if (generationOptions.hostGlobalDir) {
|
|
174
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
175
|
+
}
|
|
176
|
+
const creates = generationOptions.installScope === 'global'
|
|
177
|
+
? 'project bridge/state/settings + global runtime + Claude/Codex/OpenCode host copies'
|
|
178
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
179
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
180
|
+
if (generationOptions.dryRun) {
|
|
144
181
|
console.log(` Mode ${pc.yellow('dry-run')}`);
|
|
145
|
-
}
|
|
146
|
-
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
147
|
-
if (generationOptions.withOptions.length > 0) {
|
|
148
|
-
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
149
|
-
}
|
|
182
|
+
}
|
|
183
|
+
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
184
|
+
if (generationOptions.withOptions.length > 0) {
|
|
185
|
+
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
186
|
+
}
|
|
150
187
|
if (generationOptions.withoutOptions.length > 0) {
|
|
151
188
|
console.log(` Without ${pc.cyan(generationOptions.withoutOptions.join(','))}`);
|
|
152
189
|
}
|
|
153
190
|
if (generationOptions.externalOptions.length > 0) {
|
|
154
191
|
console.log(` Recommend ${pc.cyan(generationOptions.externalOptions.join(','))}`);
|
|
155
192
|
}
|
|
156
|
-
if (generationOptions.preset) {
|
|
157
|
-
console.log(` Preset ${pc.cyan(generationOptions.preset)}`);
|
|
158
|
-
}
|
|
159
|
-
if (skipPrompts) {
|
|
160
|
-
console.log(` Mode ${pc.dim('non-interactive (-y)')}`);
|
|
161
|
-
}
|
|
162
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
163
|
-
console.log('');
|
|
164
|
-
|
|
165
|
-
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
166
|
-
printResult(result, targetDir);
|
|
167
|
-
} else {
|
|
168
|
-
// Interactive mode
|
|
169
|
-
try {
|
|
170
|
-
projectName = await askProjectName();
|
|
171
|
-
} catch {
|
|
172
|
-
projectName = 'my-vibe-project';
|
|
173
|
-
console.log(pc.dim(` Project: ${projectName} (default)`));
|
|
174
|
-
}
|
|
175
|
-
|
|
193
|
+
if (generationOptions.preset) {
|
|
194
|
+
console.log(` Preset ${pc.cyan(generationOptions.preset)}`);
|
|
195
|
+
}
|
|
196
|
+
if (skipPrompts) {
|
|
197
|
+
console.log(` Mode ${pc.dim('non-interactive (-y)')}`);
|
|
198
|
+
}
|
|
199
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
200
|
+
console.log('');
|
|
201
|
+
|
|
202
|
+
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
203
|
+
printResult(result, targetDir);
|
|
204
|
+
} else {
|
|
205
|
+
// Interactive mode
|
|
206
|
+
try {
|
|
207
|
+
projectName = await askProjectName();
|
|
208
|
+
} catch {
|
|
209
|
+
projectName = 'my-vibe-project';
|
|
210
|
+
console.log(pc.dim(` Project: ${projectName} (default)`));
|
|
211
|
+
}
|
|
212
|
+
|
|
176
213
|
try {
|
|
177
214
|
targetDir = await askTargetDir(projectName);
|
|
178
215
|
} catch {
|
|
179
|
-
targetDir = `./${projectName}`;
|
|
180
|
-
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
216
|
+
targetDir = `./${projectName}`;
|
|
217
|
+
console.log(pc.dim(` Directory: ${targetDir} (default)`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
generationOptions.installScope = await askInstallScope();
|
|
222
|
+
} catch {
|
|
223
|
+
generationOptions.installScope = 'project';
|
|
224
|
+
console.log(pc.dim(' Scope: project (default)'));
|
|
181
225
|
}
|
|
182
226
|
|
|
183
227
|
const scan = scanTarget(targetDir);
|
|
@@ -216,12 +260,22 @@ if (argName || skipPrompts) {
|
|
|
216
260
|
console.log(pc.dim(' Optional: none (default)'));
|
|
217
261
|
}
|
|
218
262
|
}
|
|
219
|
-
|
|
220
|
-
console.log('');
|
|
221
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
222
|
-
console.log(` Project ${pc.green(projectName)}`);
|
|
263
|
+
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
266
|
+
console.log(` Project ${pc.green(projectName)}`);
|
|
223
267
|
console.log(` Directory ${pc.green(targetDir)}`);
|
|
224
|
-
console.log(`
|
|
268
|
+
console.log(` Scope ${pc.green(generationOptions.installScope)}`);
|
|
269
|
+
if (generationOptions.globalDir) {
|
|
270
|
+
console.log(` Global dir ${pc.green(generationOptions.globalDir)}`);
|
|
271
|
+
}
|
|
272
|
+
if (generationOptions.hostGlobalDir) {
|
|
273
|
+
console.log(` Host dir ${pc.green(generationOptions.hostGlobalDir)}`);
|
|
274
|
+
}
|
|
275
|
+
const creates = generationOptions.installScope === 'global'
|
|
276
|
+
? 'project bridge/state + global runtime + Claude/Codex/OpenCode host copies'
|
|
277
|
+
: 'CLAUDE.md, README.md, Harness/PROGRESS.md, Harness/, .claude/, .agents/, .opencode/, opencode.json, tests/';
|
|
278
|
+
console.log(` Creates ${pc.cyan(creates)}`);
|
|
225
279
|
console.log(` Conflicts ${pc.cyan(generationOptions.onConflict)}`);
|
|
226
280
|
if (generationOptions.withOptions.length > 0) {
|
|
227
281
|
console.log(` Optional ${pc.cyan(generationOptions.withOptions.join(','))}`);
|
|
@@ -229,145 +283,264 @@ if (argName || skipPrompts) {
|
|
|
229
283
|
if (generationOptions.externalOptions.length > 0) {
|
|
230
284
|
console.log(` Recommend ${pc.cyan(generationOptions.externalOptions.join(','))}`);
|
|
231
285
|
}
|
|
232
|
-
console.log(pc.dim('────────────────────────────────────────────'));
|
|
233
|
-
console.log('');
|
|
234
|
-
|
|
235
|
-
const preview = generate({ projectName, targetDir, ...generationOptions, dryRun: true });
|
|
236
|
-
if (!preview.success) {
|
|
237
|
-
printResult(preview, targetDir);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
console.log('');
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
286
|
+
console.log(pc.dim('────────────────────────────────────────────'));
|
|
287
|
+
console.log('');
|
|
288
|
+
|
|
289
|
+
const preview = generate({ projectName, targetDir, ...generationOptions, dryRun: true });
|
|
290
|
+
if (!preview.success) {
|
|
291
|
+
printResult(preview, targetDir);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log(pc.yellow('Planned changes: no files have been written yet.'));
|
|
295
|
+
console.log(pc.bold('Project plan:'));
|
|
296
|
+
printSummary(preview.summary);
|
|
297
|
+
printPlan(preview.plan);
|
|
298
|
+
if (preview.globalPlan) {
|
|
299
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
300
|
+
console.log(` Directory ${pc.cyan(preview.globalDir)}`);
|
|
301
|
+
printSummary(preview.globalSummary);
|
|
302
|
+
printPlan(preview.globalPlan);
|
|
303
|
+
}
|
|
304
|
+
printHostPlans(preview.hostPlans, preview.hostSummary);
|
|
305
|
+
printWarnings(preview);
|
|
306
|
+
console.log('');
|
|
307
|
+
|
|
308
|
+
if (generationOptions.dryRun) {
|
|
309
|
+
process.exit(0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let proceed = true;
|
|
313
|
+
try {
|
|
314
|
+
proceed = await p.confirm({
|
|
315
|
+
message: 'Confirm generation with this plan?',
|
|
316
|
+
initialValue: true,
|
|
317
|
+
});
|
|
318
|
+
if (p.isCancel(proceed)) proceed = false;
|
|
319
|
+
} catch {
|
|
320
|
+
proceed = true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!proceed) {
|
|
324
|
+
p.cancel('Cancelled');
|
|
325
|
+
process.exit(0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
console.log('');
|
|
329
|
+
const result = generate({ projectName, targetDir, ...generationOptions });
|
|
330
|
+
printResult(result, targetDir);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function printResult(result, targetDir) {
|
|
334
|
+
if (result.success) {
|
|
335
|
+
if (result.dryRun) {
|
|
336
|
+
console.log(pc.yellow('\nDry run: no files or directories were written.'));
|
|
337
|
+
console.log(pc.bold('Project plan:'));
|
|
338
|
+
printSummary(result.summary);
|
|
339
|
+
printPlan(result.plan);
|
|
340
|
+
if (result.globalPlan) {
|
|
341
|
+
console.log(pc.bold('\nGlobal runtime plan:'));
|
|
342
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
343
|
+
printSummary(result.globalSummary);
|
|
344
|
+
printPlan(result.globalPlan);
|
|
345
|
+
}
|
|
346
|
+
printHostPlans(result.hostPlans, result.hostSummary);
|
|
347
|
+
if (result.warnings.length > 0) {
|
|
348
|
+
console.log(pc.yellow('\nWarning(s):'));
|
|
349
|
+
for (const warning of result.warnings) {
|
|
350
|
+
console.log(pc.yellow(` - ${warning}`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
console.log('');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log(pc.green('\nGeneration complete.\n'));
|
|
358
|
+
console.log(pc.bold('Project install:'));
|
|
359
|
+
printSummary(result.summary);
|
|
360
|
+
if (result.globalSummary) {
|
|
361
|
+
console.log(pc.bold('Global runtime:'));
|
|
362
|
+
console.log(` Directory ${pc.cyan(result.globalDir)}`);
|
|
363
|
+
printSummary(result.globalSummary);
|
|
364
|
+
}
|
|
365
|
+
printHostPlans(result.hostPlans, result.hostSummary, { summaryOnly: true });
|
|
366
|
+
|
|
367
|
+
printWarnings(result);
|
|
368
|
+
|
|
292
369
|
console.log(pc.bold('Next steps:'));
|
|
293
370
|
console.log(` ${pc.cyan(`cd ${targetDir}`)}`);
|
|
294
|
-
console.log(` ${pc.cyan('claude')} # Start Claude Code`);
|
|
295
|
-
console.log(` ${pc.cyan('codex')} # Or start Codex`);
|
|
296
|
-
console.log(` ${pc.cyan('opencode')} # Or start OpenCode`);
|
|
371
|
+
console.log(` ${pc.cyan('claude')} # Start Claude Code`);
|
|
372
|
+
console.log(` ${pc.cyan('codex')} # Or start Codex`);
|
|
373
|
+
console.log(` ${pc.cyan('opencode')} # Or start OpenCode`);
|
|
297
374
|
console.log(` Tell your agent: "${pc.yellow('Read Harness/specs/guides/SETUP.md. Bootstrap this project from idea to first vertical slice.')}"`);
|
|
298
|
-
console.log('');
|
|
375
|
+
console.log('');
|
|
299
376
|
console.log(pc.dim(' Keep Harness/specs/guides/SETUP.md as a setup reference; normal sessions start at CLAUDE.md, with Harness/README.md as the routed workflow router.'));
|
|
300
|
-
console.log('');
|
|
301
|
-
|
|
302
|
-
} else {
|
|
303
|
-
console.log(pc.red('\nGeneration failed:'));
|
|
304
|
-
for (const err of result.errors) {
|
|
305
|
-
console.log(pc.red(` - ${err}`));
|
|
306
|
-
}
|
|
307
|
-
process.exit(1);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
377
|
+
console.log('');
|
|
378
|
+
|
|
379
|
+
} else {
|
|
380
|
+
console.log(pc.red('\nGeneration failed:'));
|
|
381
|
+
for (const err of result.errors) {
|
|
382
|
+
console.log(pc.red(` - ${err}`));
|
|
383
|
+
}
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} // wf-ui skip block end — functions below are module-scoped
|
|
388
|
+
|
|
389
|
+
async function runWfUi(args) {
|
|
390
|
+
const projectRoot = extractFlag(args, '--project') || process.cwd();
|
|
391
|
+
const host = extractFlag(args, '--host') || '127.0.0.1';
|
|
392
|
+
const rawPort = extractFlag(args, '--port') || '0';
|
|
393
|
+
const port = Number.parseInt(rawPort, 10);
|
|
394
|
+
const openBrowser = !hasFlag(args, '--no-open');
|
|
395
|
+
|
|
396
|
+
if (host !== '127.0.0.1') {
|
|
397
|
+
console.error('[wf-ui] host must be 127.0.0.1');
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
401
|
+
console.error('[wf-ui] port must be an integer from 0 to 65535');
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const serverModule = await import('./wf-ui-server/server.mjs');
|
|
407
|
+
const { SessionRegistry } = await import('./wf-ui-server/session-registry.mjs');
|
|
408
|
+
const { attachEventsWs } = await import('./wf-ui-server/ws-events.mjs');
|
|
409
|
+
const { attachTerminalWs } = await import('./wf-ui-server/ws-terminal.mjs');
|
|
410
|
+
const { warmRuntimeCache } = await import('./wf-ui-server/runtime-detector.mjs');
|
|
411
|
+
const { appendSessionEvent, appendTerminalData, persistSession, recordInputRequest } =
|
|
412
|
+
await import('./wf-ui-server/terminal-store.mjs');
|
|
413
|
+
const registry = new SessionRegistry();
|
|
414
|
+
const terminalHub = {};
|
|
415
|
+
const started = await serverModule.startServer({
|
|
416
|
+
projectRoot,
|
|
417
|
+
host,
|
|
418
|
+
port,
|
|
419
|
+
sessionRegistry: registry,
|
|
420
|
+
terminalHub,
|
|
421
|
+
});
|
|
422
|
+
attachEventsWs(started.server, started.token, projectRoot);
|
|
423
|
+
Object.assign(terminalHub, attachTerminalWs(started.server, started.token, registry, {
|
|
424
|
+
onTerminalInput(session, data) {
|
|
425
|
+
try {
|
|
426
|
+
recordInputRequest(projectRoot, session.sessionId, data);
|
|
427
|
+
} catch {
|
|
428
|
+
// The session may not have been persisted yet; stdout/stderr capture still remains authoritative.
|
|
429
|
+
}
|
|
430
|
+
appendTerminalData(projectRoot, session, data, 'stdin');
|
|
431
|
+
},
|
|
432
|
+
onAttachModeChange(session, attachMode) {
|
|
433
|
+
persistSession(projectRoot, session);
|
|
434
|
+
appendSessionEvent(projectRoot, session, { type: 'session.attach-mode', attachMode });
|
|
435
|
+
},
|
|
436
|
+
onSessionState(session, state) {
|
|
437
|
+
persistSession(projectRoot, session);
|
|
438
|
+
appendSessionEvent(projectRoot, session, { type: 'session.state', state });
|
|
439
|
+
},
|
|
440
|
+
}));
|
|
441
|
+
warmRuntimeCache();
|
|
442
|
+
console.log(`[wf-ui] ${started.url}`);
|
|
443
|
+
if (openBrowser) openLocalUrl(started.url);
|
|
444
|
+
await waitForWfUiShutdown(started.server);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
console.error(`[wf-ui] ${err.message}`);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function waitForWfUiShutdown(server) {
|
|
452
|
+
return new Promise((resolve) => {
|
|
453
|
+
let closing = false;
|
|
454
|
+
const shutdown = () => {
|
|
455
|
+
if (closing) return;
|
|
456
|
+
closing = true;
|
|
457
|
+
server.close(() => resolve());
|
|
458
|
+
};
|
|
459
|
+
process.once('SIGINT', shutdown);
|
|
460
|
+
process.once('SIGTERM', shutdown);
|
|
461
|
+
server.once('close', resolve);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function openLocalUrl(url) {
|
|
466
|
+
const command = process.platform === 'win32'
|
|
467
|
+
? { file: 'cmd', args: ['/c', 'start', '', url] }
|
|
468
|
+
: process.platform === 'darwin'
|
|
469
|
+
? { file: 'open', args: [url] }
|
|
470
|
+
: { file: 'xdg-open', args: [url] };
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const child = spawn(command.file, command.args, {
|
|
474
|
+
detached: true,
|
|
475
|
+
stdio: 'ignore',
|
|
476
|
+
windowsHide: true,
|
|
477
|
+
});
|
|
478
|
+
child.unref();
|
|
479
|
+
} catch (err) {
|
|
480
|
+
console.error(`[wf-ui] could not open browser automatically: ${err.message}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function parseArgs(args) {
|
|
485
|
+
const flags = {
|
|
486
|
+
with: [],
|
|
487
|
+
without: [],
|
|
488
|
+
};
|
|
489
|
+
const positionals = [];
|
|
490
|
+
const errors = [];
|
|
491
|
+
|
|
492
|
+
function readValue(flagName, index) {
|
|
493
|
+
const value = args[index + 1];
|
|
494
|
+
if (!value || value.startsWith('-')) {
|
|
495
|
+
errors.push(`${flagName} requires a value`);
|
|
496
|
+
return { value: undefined, nextIndex: index };
|
|
497
|
+
}
|
|
498
|
+
return { value, nextIndex: index + 1 };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function readEqualsValue(flagName, value) {
|
|
502
|
+
if (!value || value.startsWith('-')) {
|
|
503
|
+
errors.push(`${flagName} requires a value`);
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
510
|
+
const arg = args[i];
|
|
511
|
+
|
|
512
|
+
if (arg === '-h') {
|
|
513
|
+
flags.h = true;
|
|
514
|
+
} else if (arg === '--help') {
|
|
515
|
+
flags.help = true;
|
|
516
|
+
} else if (arg === '-y') {
|
|
517
|
+
flags.y = true;
|
|
518
|
+
} else if (arg === '--yes') {
|
|
519
|
+
flags.yes = true;
|
|
520
|
+
} else if (arg === '--dry-run') {
|
|
521
|
+
flags.dryRun = true;
|
|
349
522
|
} else if (arg === '--list-options') {
|
|
350
523
|
flags.listOptions = true;
|
|
351
524
|
} else if (arg === '--json') {
|
|
352
525
|
flags.json = true;
|
|
353
|
-
} else if (arg === '--on-conflict') {
|
|
354
|
-
const parsedValue = readValue('--on-conflict', i);
|
|
355
|
-
flags.onConflict = parsedValue.value;
|
|
356
|
-
i = parsedValue.nextIndex;
|
|
357
|
-
} else if (arg.startsWith('--on-conflict=')) {
|
|
358
|
-
flags.onConflict = readEqualsValue('--on-conflict', arg.slice('--on-conflict='.length));
|
|
359
|
-
} else if (arg === '--with') {
|
|
360
|
-
const parsedValue = readValue('--with', i);
|
|
361
|
-
if (parsedValue.value !== undefined) flags.with.push(parsedValue.value);
|
|
362
|
-
i = parsedValue.nextIndex;
|
|
363
|
-
} else if (arg.startsWith('--with=')) {
|
|
364
|
-
const value = readEqualsValue('--with', arg.slice('--with='.length));
|
|
365
|
-
if (value !== undefined) flags.with.push(value);
|
|
366
|
-
} else if (arg === '--without') {
|
|
367
|
-
const parsedValue = readValue('--without', i);
|
|
368
|
-
if (parsedValue.value !== undefined) flags.without.push(parsedValue.value);
|
|
369
|
-
i = parsedValue.nextIndex;
|
|
370
|
-
} else if (arg.startsWith('--without=')) {
|
|
526
|
+
} else if (arg === '--on-conflict') {
|
|
527
|
+
const parsedValue = readValue('--on-conflict', i);
|
|
528
|
+
flags.onConflict = parsedValue.value;
|
|
529
|
+
i = parsedValue.nextIndex;
|
|
530
|
+
} else if (arg.startsWith('--on-conflict=')) {
|
|
531
|
+
flags.onConflict = readEqualsValue('--on-conflict', arg.slice('--on-conflict='.length));
|
|
532
|
+
} else if (arg === '--with') {
|
|
533
|
+
const parsedValue = readValue('--with', i);
|
|
534
|
+
if (parsedValue.value !== undefined) flags.with.push(parsedValue.value);
|
|
535
|
+
i = parsedValue.nextIndex;
|
|
536
|
+
} else if (arg.startsWith('--with=')) {
|
|
537
|
+
const value = readEqualsValue('--with', arg.slice('--with='.length));
|
|
538
|
+
if (value !== undefined) flags.with.push(value);
|
|
539
|
+
} else if (arg === '--without') {
|
|
540
|
+
const parsedValue = readValue('--without', i);
|
|
541
|
+
if (parsedValue.value !== undefined) flags.without.push(parsedValue.value);
|
|
542
|
+
i = parsedValue.nextIndex;
|
|
543
|
+
} else if (arg.startsWith('--without=')) {
|
|
371
544
|
const value = readEqualsValue('--without', arg.slice('--without='.length));
|
|
372
545
|
if (value !== undefined) flags.without.push(value);
|
|
373
546
|
} else if (arg === '--recommend') {
|
|
@@ -384,30 +557,48 @@ function parseArgs(args) {
|
|
|
384
557
|
flags.recommend.push(value);
|
|
385
558
|
}
|
|
386
559
|
} else if (arg === '--preset') {
|
|
387
|
-
const parsedValue = readValue('--preset', i);
|
|
388
|
-
flags.preset = parsedValue.value;
|
|
389
|
-
i = parsedValue.nextIndex;
|
|
390
|
-
} else if (arg.startsWith('--preset=')) {
|
|
391
|
-
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
392
|
-
} else if (arg
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
560
|
+
const parsedValue = readValue('--preset', i);
|
|
561
|
+
flags.preset = parsedValue.value;
|
|
562
|
+
i = parsedValue.nextIndex;
|
|
563
|
+
} else if (arg.startsWith('--preset=')) {
|
|
564
|
+
flags.preset = readEqualsValue('--preset', arg.slice('--preset='.length));
|
|
565
|
+
} else if (arg === '--install-scope') {
|
|
566
|
+
const parsedValue = readValue('--install-scope', i);
|
|
567
|
+
flags.installScope = parsedValue.value;
|
|
568
|
+
i = parsedValue.nextIndex;
|
|
569
|
+
} else if (arg.startsWith('--install-scope=')) {
|
|
570
|
+
flags.installScope = readEqualsValue('--install-scope', arg.slice('--install-scope='.length));
|
|
571
|
+
} else if (arg === '--global-dir') {
|
|
572
|
+
const parsedValue = readValue('--global-dir', i);
|
|
573
|
+
flags.globalDir = parsedValue.value;
|
|
574
|
+
i = parsedValue.nextIndex;
|
|
575
|
+
} else if (arg.startsWith('--global-dir=')) {
|
|
576
|
+
flags.globalDir = readEqualsValue('--global-dir', arg.slice('--global-dir='.length));
|
|
577
|
+
} else if (arg === '--host-global-dir') {
|
|
578
|
+
const parsedValue = readValue('--host-global-dir', i);
|
|
579
|
+
flags.hostGlobalDir = parsedValue.value;
|
|
580
|
+
i = parsedValue.nextIndex;
|
|
581
|
+
} else if (arg.startsWith('--host-global-dir=')) {
|
|
582
|
+
flags.hostGlobalDir = readEqualsValue('--host-global-dir', arg.slice('--host-global-dir='.length));
|
|
583
|
+
} else if (arg.startsWith('-')) {
|
|
584
|
+
errors.push(`Unknown flag "${arg}"`);
|
|
585
|
+
} else {
|
|
586
|
+
positionals.push(arg);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return { flags, positionals, errors };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function printOptions() {
|
|
594
|
+
const catalog = getOptionalCatalog();
|
|
595
|
+
|
|
596
|
+
console.log('');
|
|
597
|
+
console.log(pc.bold('Optional workflow skills:'));
|
|
598
|
+
for (const skill of catalog.skills) {
|
|
599
|
+
console.log(` ${pc.cyan(skill.id)} - ${skill.description}`);
|
|
600
|
+
}
|
|
601
|
+
|
|
411
602
|
console.log('');
|
|
412
603
|
console.log(pc.bold('Presets:'));
|
|
413
604
|
for (const [name, skills] of Object.entries(catalog.presets)) {
|
|
@@ -423,42 +614,55 @@ function printOptions() {
|
|
|
423
614
|
}
|
|
424
615
|
console.log('');
|
|
425
616
|
}
|
|
426
|
-
|
|
427
|
-
function printSummary(summary) {
|
|
428
|
-
console.log(` created ${pc.green(summary.created)}`);
|
|
429
|
-
console.log(` skipped ${pc.yellow(summary.skipped)}`);
|
|
430
|
-
console.log(` backed up ${pc.cyan(summary.backedUp)}`);
|
|
431
|
-
console.log(` overwritten ${pc.cyan(summary.overwritten)}`);
|
|
432
|
-
console.log(` conflicts ${summary.conflicts > 0 ? pc.red(summary.conflicts) : pc.dim(summary.conflicts)}`);
|
|
433
|
-
console.log(` directories ${pc.dim(summary.mkdir)}`);
|
|
434
|
-
console.log('');
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function printPlan(plan) {
|
|
438
|
-
for (const [label, files] of Object.entries(plan)) {
|
|
439
|
-
if (!files.length) continue;
|
|
440
|
-
console.log(` ${label}:`);
|
|
441
|
-
for (const file of files) {
|
|
442
|
-
console.log(` - ${file}`);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function
|
|
448
|
-
if (!
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
617
|
+
|
|
618
|
+
function printSummary(summary) {
|
|
619
|
+
console.log(` created ${pc.green(summary.created)}`);
|
|
620
|
+
console.log(` skipped ${pc.yellow(summary.skipped)}`);
|
|
621
|
+
console.log(` backed up ${pc.cyan(summary.backedUp)}`);
|
|
622
|
+
console.log(` overwritten ${pc.cyan(summary.overwritten)}`);
|
|
623
|
+
console.log(` conflicts ${summary.conflicts > 0 ? pc.red(summary.conflicts) : pc.dim(summary.conflicts)}`);
|
|
624
|
+
console.log(` directories ${pc.dim(summary.mkdir)}`);
|
|
625
|
+
console.log('');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function printPlan(plan) {
|
|
629
|
+
for (const [label, files] of Object.entries(plan)) {
|
|
630
|
+
if (!files.length) continue;
|
|
631
|
+
console.log(` ${label}:`);
|
|
632
|
+
for (const file of files) {
|
|
633
|
+
console.log(` - ${file}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function printHostPlans(hostPlans = [], hostSummary = [], { summaryOnly = false } = {}) {
|
|
639
|
+
if (!hostPlans?.length) return;
|
|
640
|
+
|
|
641
|
+
const summariesByHost = new Map((hostSummary || []).map(item => [item.host, item.summary]));
|
|
642
|
+
console.log(pc.bold('\nHost-global copies:'));
|
|
643
|
+
for (const hostPlan of hostPlans) {
|
|
644
|
+
console.log(` ${hostPlan.host} ${pc.cyan(hostPlan.root)}`);
|
|
645
|
+
const summary = summariesByHost.get(hostPlan.host);
|
|
646
|
+
if (summary) printSummary(summary);
|
|
647
|
+
if (!summaryOnly) printPlan(hostPlan.plan);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function printWarnings(result) {
|
|
652
|
+
if (!result.warnings.length) return;
|
|
653
|
+
|
|
654
|
+
console.log(pc.yellow('\nWarning(s):'));
|
|
655
|
+
for (const warning of result.warnings) {
|
|
656
|
+
console.log(pc.yellow(` - ${warning}`));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
456
660
|
function printJsonResult(result) {
|
|
457
661
|
// Remove `created` array from output — it is already in the plan, avoid duplication
|
|
458
|
-
const { created, ...rest } = result;
|
|
459
|
-
console.log(JSON.stringify(rest, null, 2));
|
|
460
|
-
if (!result.success) {
|
|
461
|
-
process.exit(1);
|
|
662
|
+
const { created, globalCreated, hostCreated, ...rest } = result;
|
|
663
|
+
console.log(JSON.stringify(rest, null, 2));
|
|
664
|
+
if (!result.success) {
|
|
665
|
+
process.exit(1);
|
|
462
666
|
}
|
|
463
667
|
}
|
|
464
668
|
|
|
@@ -641,8 +845,8 @@ function scanTarget(targetDir) {
|
|
|
641
845
|
const hasClaude = isDirectory && fs.existsSync(path.join(resolvedDir, 'CLAUDE.md'));
|
|
642
846
|
const hasAgents = isDirectory && fs.existsSync(path.join(resolvedDir, 'AGENTS.md'));
|
|
643
847
|
const hasAgentSkills = isDirectory && fs.existsSync(path.join(resolvedDir, '.agents'));
|
|
644
|
-
const hasCodex = isDirectory && fs.existsSync(path.join(resolvedDir, '.codex'));
|
|
645
|
-
const hasOpencode = isDirectory && (fs.existsSync(path.join(resolvedDir, '.opencode')) || fs.existsSync(path.join(resolvedDir, 'opencode.json')));
|
|
848
|
+
const hasCodex = isDirectory && fs.existsSync(path.join(resolvedDir, '.codex'));
|
|
849
|
+
const hasOpencode = isDirectory && (fs.existsSync(path.join(resolvedDir, '.opencode')) || fs.existsSync(path.join(resolvedDir, 'opencode.json')));
|
|
646
850
|
const hasDocs = isDirectory && fs.existsSync(path.join(resolvedDir, 'docs'));
|
|
647
851
|
const hasReadme = isDirectory && fs.existsSync(path.join(resolvedDir, 'README.md'));
|
|
648
852
|
const hasPackageJson = isDirectory && fs.existsSync(path.join(resolvedDir, 'package.json'));
|
|
@@ -660,8 +864,8 @@ function scanTarget(targetDir) {
|
|
|
660
864
|
hasClaude,
|
|
661
865
|
hasAgents,
|
|
662
866
|
hasAgentSkills,
|
|
663
|
-
hasCodex,
|
|
664
|
-
hasOpencode,
|
|
867
|
+
hasCodex,
|
|
868
|
+
hasOpencode,
|
|
665
869
|
hasDocs,
|
|
666
870
|
hasReadme,
|
|
667
871
|
hasPackageJson,
|
|
@@ -689,8 +893,8 @@ function createJsonScan(scan) {
|
|
|
689
893
|
hasClaude: scan.hasClaude,
|
|
690
894
|
hasAgents: scan.hasAgents,
|
|
691
895
|
hasAgentSkills: scan.hasAgentSkills,
|
|
692
|
-
hasCodex: scan.hasCodex,
|
|
693
|
-
hasOpencode: scan.hasOpencode,
|
|
896
|
+
hasCodex: scan.hasCodex,
|
|
897
|
+
hasOpencode: scan.hasOpencode,
|
|
694
898
|
hasDocs: scan.hasDocs,
|
|
695
899
|
hasReadme: scan.hasReadme,
|
|
696
900
|
hasPackageJson: scan.hasPackageJson,
|
|
@@ -703,12 +907,44 @@ function createJsonScan(scan) {
|
|
|
703
907
|
}
|
|
704
908
|
|
|
705
909
|
function createAgentGuidance(result, { projectName, targetDir, options, scan }) {
|
|
706
|
-
const
|
|
910
|
+
const projectAttentionFiles = [...new Set([
|
|
707
911
|
...(result.plan?.conflict || []),
|
|
708
912
|
...(result.plan?.skip || []),
|
|
709
913
|
])].sort();
|
|
710
|
-
const
|
|
711
|
-
|
|
914
|
+
const globalAttentionFiles = [...new Set([
|
|
915
|
+
...(result.globalPlan?.conflict || []),
|
|
916
|
+
...(result.globalPlan?.skip || []),
|
|
917
|
+
])].sort();
|
|
918
|
+
const hostAttentionFiles = (result.hostPlans || []).flatMap(hostPlan => (
|
|
919
|
+
[...new Set([
|
|
920
|
+
...(hostPlan.plan?.conflict || []),
|
|
921
|
+
...(hostPlan.plan?.skip || []),
|
|
922
|
+
])].sort().map(file => ({ host: hostPlan.host, root: hostPlan.root, file }))
|
|
923
|
+
));
|
|
924
|
+
const aiMergeRequired = [
|
|
925
|
+
...projectAttentionFiles.map(file => createFileGuidance(file)),
|
|
926
|
+
...globalAttentionFiles.map(file => {
|
|
927
|
+
const guidance = createFileGuidance(file);
|
|
928
|
+
return {
|
|
929
|
+
...guidance,
|
|
930
|
+
file: `global:${guidance.file}`,
|
|
931
|
+
scope: 'global-runtime',
|
|
932
|
+
reason: `Global runtime file in ${result.globalDir || 'the selected global directory'} needs review. ${guidance.reason}`,
|
|
933
|
+
};
|
|
934
|
+
}),
|
|
935
|
+
...hostAttentionFiles.map(({ host, root, file }) => {
|
|
936
|
+
const guidance = createFileGuidance(file);
|
|
937
|
+
return {
|
|
938
|
+
...guidance,
|
|
939
|
+
file: `host:${host}:${guidance.file}`,
|
|
940
|
+
scope: `host-global:${host}`,
|
|
941
|
+
reason: `Host-global ${host} file in ${root} needs review. ${guidance.reason}`,
|
|
942
|
+
};
|
|
943
|
+
}),
|
|
944
|
+
];
|
|
945
|
+
const hasBlockingConflicts = (result.plan?.conflict || []).length > 0
|
|
946
|
+
|| (result.globalPlan?.conflict || []).length > 0
|
|
947
|
+
|| (result.hostPlans || []).some(hostPlan => (hostPlan.plan?.conflict || []).length > 0);
|
|
712
948
|
const safeMergeCommand = commandFor(projectName, targetDir, {
|
|
713
949
|
...options,
|
|
714
950
|
dryRun: false,
|
|
@@ -769,6 +1005,14 @@ function createAgentGuidance(result, { projectName, targetDir, options, scan })
|
|
|
769
1005
|
mkdir: result.plan?.mkdir?.length || 0,
|
|
770
1006
|
backup: result.plan?.backup?.length || 0,
|
|
771
1007
|
overwrite: result.plan?.overwrite?.length || 0,
|
|
1008
|
+
globalCreate: result.globalPlan?.create?.length || 0,
|
|
1009
|
+
globalMkdir: result.globalPlan?.mkdir?.length || 0,
|
|
1010
|
+
globalBackup: result.globalPlan?.backup?.length || 0,
|
|
1011
|
+
globalOverwrite: result.globalPlan?.overwrite?.length || 0,
|
|
1012
|
+
hostCreate: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.create?.length || 0), 0),
|
|
1013
|
+
hostMkdir: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.mkdir?.length || 0), 0),
|
|
1014
|
+
hostBackup: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.backup?.length || 0), 0),
|
|
1015
|
+
hostOverwrite: (result.hostPlans || []).reduce((sum, hostPlan) => sum + (hostPlan.plan?.overwrite?.length || 0), 0),
|
|
772
1016
|
},
|
|
773
1017
|
aiMergeRequired,
|
|
774
1018
|
next,
|
|
@@ -839,6 +1083,9 @@ function commandFor(projectName, targetDir, options) {
|
|
|
839
1083
|
if (options.withoutOptions?.length) args.push('--without', options.withoutOptions.join(','));
|
|
840
1084
|
if (options.externalOptions?.length) args.push('--recommend', options.externalOptions.join(','));
|
|
841
1085
|
if (options.preset) args.push('--preset', options.preset);
|
|
1086
|
+
if (options.installScope && options.installScope !== 'project') args.push('--install-scope', options.installScope);
|
|
1087
|
+
if (options.globalDir) args.push('--global-dir', options.globalDir);
|
|
1088
|
+
if (options.hostGlobalDir) args.push('--host-global-dir', options.hostGlobalDir);
|
|
842
1089
|
if (options.json) args.push('--json');
|
|
843
1090
|
|
|
844
1091
|
return args.map(shellQuoteArg).join(' ');
|
|
@@ -864,7 +1111,7 @@ function printScan(scan) {
|
|
|
864
1111
|
if (scan.hasClaude) console.log(` CLAUDE.md ${pc.yellow('exists')}`);
|
|
865
1112
|
if (scan.hasAgents) console.log(` AGENTS.md ${pc.yellow('exists')}`);
|
|
866
1113
|
if (scan.hasAgentSkills) console.log(` .agents/ ${pc.yellow('exists')}`);
|
|
867
|
-
if (scan.hasCodex) console.log(` .codex/ ${pc.yellow('exists')}`);
|
|
868
|
-
if (scan.hasOpencode) console.log(` opencode ${pc.yellow('exists')} ${pc.dim('(.opencode/ or opencode.json)')}`);
|
|
1114
|
+
if (scan.hasCodex) console.log(` .codex/ ${pc.yellow('exists')}`);
|
|
1115
|
+
if (scan.hasOpencode) console.log(` opencode ${pc.yellow('exists')} ${pc.dim('(.opencode/ or opencode.json)')}`);
|
|
869
1116
|
if (scan.hasDocs) console.log(` docs/ ${pc.dim('project-owned; Harness stays in Harness/')}`);
|
|
870
1117
|
}
|