@dzhechkov/harness-cli 0.2.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,27 @@
1
+ # @dzhechkov/harness-cli
2
+
3
+ The **`dz`** command-line interface for the DZ cross-platform harness — a thin
4
+ argv shell over [`@dzhechkov/harness-core`](../harness-core).
5
+
6
+ ## Commands
7
+
8
+ ```
9
+ dz init --target <name> [--skills-dir <dir>] [--project <dir>] [--force]
10
+ dz verify [--skills-dir <dir>] [--target <name>]
11
+ dz sync [--canonical <dir>] [--project <dir>] [--dry-run] [--force]
12
+ dz update (alias of sync)
13
+ dz doctor [--project <dir>]
14
+ dz help
15
+ ```
16
+
17
+ - `--target` — one of `claude-code`, `codex`, `opencode`, `hermes`
18
+ - `init` compiles a skills directory for a target and writes it **additively**
19
+ (existing files are never overwritten without `--force`)
20
+ - `sync` compares the canonical pack to the legacy `.claude/skills` tree
21
+ - `doctor` reports environment diagnostics
22
+
23
+ ## Status
24
+
25
+ `0.1.0` — alpha, part of the `extended-a-migration` feature (Phase 6).
26
+ `--preset <name>` (Phase 10) selects a named skill set — see
27
+ `@dzhechkov/harness-presets`. The `paperclip` target is planned for a later phase.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `dz` executable entry point.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG"}
package/dist/bin.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `dz` executable entry point.
4
+ */
5
+ import { runCli } from './cli.js';
6
+ process.exit(await runCli(process.argv.slice(2)));
7
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The `dz` CLI — argv parsing + dispatch over `@dzhechkov/harness-core`.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /** Output sink + working directory — injectable so the CLI is testable. */
7
+ export interface CliIo {
8
+ readonly cwd?: string;
9
+ readonly write?: (line: string) => void;
10
+ }
11
+ /**
12
+ * Run the `dz` CLI. Returns a process exit code; never calls `process.exit`,
13
+ * so it is safe to call from tests. Output and working directory are injectable.
14
+ */
15
+ export declare function runCli(argv: string[], io?: CliIo): Promise<number>;
16
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0CH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAqTD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwC5E"}
package/dist/cli.js ADDED
@@ -0,0 +1,358 @@
1
+ /**
2
+ * The `dz` CLI — argv parsing + dispatch over `@dzhechkov/harness-core`.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmdirSync, symlinkSync } from 'node:fs';
7
+ import { basename, dirname, join, resolve } from 'node:path';
8
+ import { execSync } from 'node:child_process';
9
+ import { homedir } from 'node:os';
10
+ import { getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, runMigrate, runSync, runVerify, TARGET_NAMES, WORKFLOW_NAMES, } from '@dzhechkov/harness-core';
11
+ import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
12
+ const USAGE = `dz - DZ cross-platform harness CLI
13
+
14
+ Usage:
15
+ dz init --target <name> [--skills-dir <dir>] [--project <dir>] [--preset <name>] [--select id,id,...] [--force]
16
+ dz verify [--skills-dir <dir>] [--target <name>]
17
+ dz sync [--canonical <dir>] [--project <dir>] [--dry-run] [--force]
18
+ dz update (alias of sync)
19
+ dz list [--skills-dir <dir>]
20
+ dz info <skill-id> [--skills-dir <dir>]
21
+ dz migrate [--project <dir>]
22
+ dz doctor [--project <dir>]
23
+ dz workflow <task> [--dry-run]
24
+ dz roam [--apply] [--slug <slug>]
25
+ dz help
26
+
27
+ Workflows: coverage-lift, mutation-kill, canonicalize, security-audit
28
+
29
+ Targets: ${TARGET_NAMES.join(', ')}
30
+ Presets: ${PRESET_NAMES.join(', ')}`;
31
+ /** Parse `<command> [--key value] [--flag]` argv. */
32
+ function parseArgs(argv) {
33
+ const options = new Map();
34
+ const flags = new Set();
35
+ const positional = [];
36
+ for (let index = 0; index < argv.length; index += 1) {
37
+ const arg = argv[index] ?? '';
38
+ if (arg.startsWith('--')) {
39
+ const key = arg.slice(2);
40
+ const next = argv[index + 1];
41
+ if (next !== undefined && !next.startsWith('--')) {
42
+ options.set(key, next);
43
+ index += 1;
44
+ }
45
+ else {
46
+ flags.add(key);
47
+ }
48
+ }
49
+ else {
50
+ positional.push(arg);
51
+ }
52
+ }
53
+ return { command: positional[0] ?? '', options, flags };
54
+ }
55
+ async function cmdInit(options, flags, cwd, write) {
56
+ const target = options.get('target');
57
+ if (target === undefined || !isTargetName(target)) {
58
+ write(`dz init: --target must be one of: ${TARGET_NAMES.join(', ')}`);
59
+ return 1;
60
+ }
61
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
62
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
63
+ const presetName = options.get('preset');
64
+ const selectArg = options.get('select');
65
+ let select;
66
+ if (presetName !== undefined) {
67
+ const preset = getPreset(presetName);
68
+ if (preset === undefined) {
69
+ write(`dz init: --preset must be one of: ${PRESET_NAMES.join(', ')}`);
70
+ return 1;
71
+ }
72
+ select = preset.skills;
73
+ }
74
+ else if (selectArg !== undefined) {
75
+ // --select skill1,skill2,skill3 — comma-separated skill ids
76
+ select = selectArg.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
77
+ if (select.length === 0) {
78
+ write('dz init: --select requires comma-separated skill ids');
79
+ return 1;
80
+ }
81
+ }
82
+ const report = await runInit({
83
+ target,
84
+ skillsDir,
85
+ projectRoot,
86
+ force: flags.has('force'),
87
+ ...(select !== undefined ? { select } : {}),
88
+ });
89
+ let written = 0;
90
+ let skipped = 0;
91
+ for (const skill of report.skills) {
92
+ written += skill.written.length;
93
+ skipped += skill.skipped.length;
94
+ }
95
+ write(`dz init --target ${target}: ${report.skills.length} skill(s), ${written} file(s) written, ${skipped} skipped`);
96
+ for (const skill of report.skills) {
97
+ for (const warning of skill.warnings)
98
+ write(` warning [${skill.id}]: ${warning}`);
99
+ }
100
+ return 0;
101
+ }
102
+ async function cmdVerify(options, cwd, write) {
103
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
104
+ const targetOpt = options.get('target');
105
+ if (targetOpt !== undefined && !isTargetName(targetOpt)) {
106
+ write(`dz verify: --target must be one of: ${TARGET_NAMES.join(', ')}`);
107
+ return 1;
108
+ }
109
+ const report = await runVerify({
110
+ skillsDir,
111
+ ...(targetOpt !== undefined ? { target: targetOpt } : {}),
112
+ });
113
+ write(`dz verify (${report.target}): ${report.valid}/${report.total} skill(s) valid`);
114
+ for (const skill of report.skills) {
115
+ if (!skill.ok)
116
+ write(` FAIL ${skill.id}: ${skill.errors.join('; ')}`);
117
+ }
118
+ return report.valid === report.total ? 0 : 1;
119
+ }
120
+ async function cmdSync(options, flags, cwd, write) {
121
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
122
+ const canonicalArg = options.get('canonical');
123
+ // Auto-discover all skills-* packs, or use explicit --canonical
124
+ let canonicalDirs;
125
+ if (canonicalArg !== undefined) {
126
+ canonicalDirs = [resolve(cwd, canonicalArg)];
127
+ }
128
+ else {
129
+ const baseDir = join(projectRoot, 'packages', '@dzhechkov');
130
+ canonicalDirs = existsSync(baseDir)
131
+ ? readdirSync(baseDir, { withFileTypes: true })
132
+ .filter((e) => e.isDirectory() && e.name.startsWith('skills-'))
133
+ .map((e) => join(baseDir, e.name))
134
+ : [resolve(cwd, 'packages/@dzhechkov/skills-meta')];
135
+ }
136
+ const report = await runSync({
137
+ canonicalDirs,
138
+ projectRoot,
139
+ dryRun: flags.has('dry-run'),
140
+ force: flags.has('force'),
141
+ });
142
+ const { total, inSync, missing, drift } = report.summary;
143
+ write(`dz sync${report.dryRun ? ' --dry-run' : ''}: ${inSync}/${total} in sync, ${missing} missing, ${drift} drift`);
144
+ return missing === 0 && drift === 0 ? 0 : 1;
145
+ }
146
+ function cmdList(options, cwd, write) {
147
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
148
+ const skills = listSkills(skillsDir);
149
+ if (skills.length === 0) {
150
+ write(`dz list: no skills found in ${skillsDir}`);
151
+ return 1;
152
+ }
153
+ write(`${skills.length} skill(s) in ${skillsDir}:\n`);
154
+ for (const skill of skills) {
155
+ const desc = skill.description.length > 80 ? skill.description.slice(0, 77) + '...' : skill.description;
156
+ write(` ${skill.id.padEnd(35)} ${desc}`);
157
+ }
158
+ return 0;
159
+ }
160
+ function cmdInfo(options, args, cwd, write) {
161
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
162
+ // The skill id is the second positional arg (first is 'info')
163
+ const skillId = args.command === 'info' ? (args.options.get('_positional') ?? '') : '';
164
+ // Actually, let's parse it from the raw argv approach — check options map
165
+ // The id might be passed as the value after 'info' in positional args
166
+ // Since parseArgs puts non-flag args in positional, and command is first,
167
+ // we need to find the skill id. Let's check if there's a value in options.
168
+ const id = options.get('id') ?? options.get('skill') ?? '';
169
+ if (id === '') {
170
+ write('dz info: specify a skill id — dz info --id <skill-id>');
171
+ return 1;
172
+ }
173
+ const info = getSkillInfo(skillsDir, id);
174
+ if (info === undefined) {
175
+ write(`dz info: skill ${JSON.stringify(id)} not found in ${skillsDir}`);
176
+ return 1;
177
+ }
178
+ write(`Skill: ${info.id}`);
179
+ write(`Name: ${info.name ?? info.id}`);
180
+ write(`Description: ${info.description}`);
181
+ if (info.trustTier !== undefined)
182
+ write(`Trust Tier: ${info.trustTier}`);
183
+ if (info.version !== undefined)
184
+ write(`Version: ${info.version}`);
185
+ write(`Assets: ${info.assetCount} file(s)`);
186
+ if (info.assetCount > 0) {
187
+ for (const path of info.assetPaths)
188
+ write(` ${path}`);
189
+ }
190
+ return 0;
191
+ }
192
+ function cmdWorkflow(options, flags, cwd, write) {
193
+ const taskName = options.get('task') ?? options.get('name') ?? '';
194
+ if (taskName === '' || !WORKFLOW_NAMES.includes(taskName)) {
195
+ write(`dz workflow: specify a task — one of: ${WORKFLOW_NAMES.join(', ')}`);
196
+ return 1;
197
+ }
198
+ const wf = getWorkflow(taskName);
199
+ if (wf === undefined) {
200
+ write(`dz workflow: unknown task ${JSON.stringify(taskName)}`);
201
+ return 1;
202
+ }
203
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
204
+ const script = wf.generate({ projectRoot, dryRun: flags.has('dry-run') });
205
+ write(`// Dynamic Workflow: ${wf.name}`);
206
+ write(`// ${wf.description}`);
207
+ write('');
208
+ write(script);
209
+ write('');
210
+ write(`// To execute: paste the above into Claude Code with Opus 4.8+ and "ultracode" enabled.`);
211
+ write(`// Or save to a .js file and run: claude "Execute this workflow" < workflow.js`);
212
+ return 0;
213
+ }
214
+ function cmdMigrate(options, cwd, write) {
215
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
216
+ const report = runMigrate({ projectRoot });
217
+ if (report.detections.length === 0) {
218
+ write('dz migrate: no legacy installations detected.');
219
+ }
220
+ else {
221
+ write(`dz migrate: found ${report.detections.length} legacy installation(s):\n`);
222
+ for (const det of report.detections) {
223
+ write(` ${det.manifest} — v${det.version}, ${det.components.length} components, ${det.fileCount} files`);
224
+ }
225
+ }
226
+ write(`\nSkills in .claude/skills/: ${report.skillsFound}`);
227
+ write(`\nRecommendation: ${report.recommendation}`);
228
+ return 0;
229
+ }
230
+ async function cmdDoctor(options, cwd, write) {
231
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
232
+ const report = await runDoctor({ projectRoot });
233
+ write(`dz doctor (${report.node}):`);
234
+ for (const check of report.checks) {
235
+ write(` [${check.ok ? 'OK' : 'XX'}] ${check.name} - ${check.detail}`);
236
+ }
237
+ return report.ok ? 0 : 1;
238
+ }
239
+ function cmdRoam(options, flags, cwd, write) {
240
+ const apply = flags.has('apply');
241
+ // Find repo root
242
+ let repoRoot;
243
+ try {
244
+ repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
245
+ }
246
+ catch {
247
+ write('dz roam: not a git repository');
248
+ return 1;
249
+ }
250
+ const roamDir = join(repoRoot, 'roam', 'claude-state');
251
+ const claudeProjectsDir = process.env['CLAUDE_PROJECTS_DIR'] ?? join(homedir(), '.claude', 'projects');
252
+ // Slug: replace every : \ / with -
253
+ const slug = options.get('slug') ?? repoRoot.replace(/[:\\/]/g, '-');
254
+ const target = join(claudeProjectsDir, slug);
255
+ write(`repo root : ${repoRoot}`);
256
+ write(`roam dir : ${roamDir}`);
257
+ write(`slug : ${slug}`);
258
+ write(`target : ${target}`);
259
+ write(`mode : ${apply ? 'APPLY' : 'DRY-RUN'}`);
260
+ write('');
261
+ // Ensure roam dir exists
262
+ if (apply) {
263
+ mkdirSync(roamDir, { recursive: true });
264
+ }
265
+ else {
266
+ write(' would: mkdir -p ' + roamDir);
267
+ }
268
+ // Check current state of target
269
+ if (existsSync(target) && lstatSync(target).isSymbolicLink()) {
270
+ const current = readlinkSync(target);
271
+ if (current === roamDir) {
272
+ write(' symlink already points at roam dir — nothing to do.');
273
+ return 0;
274
+ }
275
+ write(`WARN: ${target} symlinks elsewhere (${current}). Resolve manually.`);
276
+ return 3;
277
+ }
278
+ // Move existing content
279
+ if (existsSync(target) && lstatSync(target).isDirectory()) {
280
+ const entries = readdirSync(target);
281
+ if (entries.length > 0) {
282
+ write(` moving ${entries.length} entries from ${target} → ${roamDir}`);
283
+ if (apply) {
284
+ for (const entry of entries) {
285
+ const dest = join(roamDir, entry);
286
+ if (existsSync(dest)) {
287
+ write(` WARN: ${dest} already exists — skipping`);
288
+ continue;
289
+ }
290
+ renameSync(join(target, entry), dest);
291
+ }
292
+ }
293
+ }
294
+ if (apply) {
295
+ rmdirSync(target);
296
+ }
297
+ else {
298
+ write(' would: rmdir ' + target);
299
+ }
300
+ }
301
+ // Create symlink
302
+ if (apply) {
303
+ mkdirSync(dirname(target), { recursive: true });
304
+ symlinkSync(roamDir, target);
305
+ }
306
+ else {
307
+ write(' would: mkdir -p ' + dirname(target));
308
+ write(` would: ln -s ${roamDir} ${target}`);
309
+ }
310
+ write('');
311
+ write(`done. (${apply ? 'applied' : 'dry-run'})`);
312
+ return 0;
313
+ }
314
+ /**
315
+ * Run the `dz` CLI. Returns a process exit code; never calls `process.exit`,
316
+ * so it is safe to call from tests. Output and working directory are injectable.
317
+ */
318
+ export async function runCli(argv, io = {}) {
319
+ const cwd = io.cwd ?? process.cwd();
320
+ const write = io.write ?? ((line) => { console.log(line); });
321
+ const { command, options, flags } = parseArgs(argv);
322
+ if (command === '' || command === 'help' || flags.has('help')) {
323
+ write(USAGE);
324
+ return 0;
325
+ }
326
+ try {
327
+ switch (command) {
328
+ case 'init':
329
+ return await cmdInit(options, flags, cwd, write);
330
+ case 'verify':
331
+ return await cmdVerify(options, cwd, write);
332
+ case 'sync':
333
+ case 'update':
334
+ return await cmdSync(options, flags, cwd, write);
335
+ case 'list':
336
+ return cmdList(options, cwd, write);
337
+ case 'info':
338
+ return cmdInfo(options, { command, options, flags }, cwd, write);
339
+ case 'workflow':
340
+ return cmdWorkflow(options, flags, cwd, write);
341
+ case 'migrate':
342
+ return cmdMigrate(options, cwd, write);
343
+ case 'doctor':
344
+ return await cmdDoctor(options, cwd, write);
345
+ case 'roam':
346
+ return cmdRoam(options, flags, cwd, write);
347
+ default:
348
+ write(`dz: unknown command ${JSON.stringify(command)}`);
349
+ write(USAGE);
350
+ return 1;
351
+ }
352
+ }
353
+ catch (error) {
354
+ write(`dz: ${error instanceof Error ? error.message : String(error)}`);
355
+ return 1;
356
+ }
357
+ }
358
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@dzhechkov/harness-cli` — the `dz` CLI for the DZ cross-platform harness.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /** Package version. Kept in sync with `package.json`. */
7
+ export declare const HARNESS_CLI_VERSION = "0.1.0";
8
+ export { runCli } from './cli.js';
9
+ export type { CliIo } from './cli.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@dzhechkov/harness-cli` — the `dz` CLI for the DZ cross-platform harness.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /** Package version. Kept in sync with `package.json`. */
7
+ export const HARNESS_CLI_VERSION = '0.1.0';
8
+ export { runCli } from './cli.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@dzhechkov/harness-cli",
3
+ "version": "0.2.0",
4
+ "description": "The dz CLI - init / sync / verify / doctor for the DZ cross-platform harness.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "dzhechko",
8
+ "keywords": [
9
+ "agent-skills",
10
+ "harness",
11
+ "cli",
12
+ "claude-code"
13
+ ],
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "bin": {
23
+ "dz": "./dist/bin.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "README.md"
29
+ ],
30
+ "dependencies": {
31
+ "@dzhechkov/harness-core": "0.2.0",
32
+ "@dzhechkov/harness-presets": "0.2.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^25.6.0",
36
+ "typescript": "^5.7.0",
37
+ "vitest": "^3.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/djd1m/dz-harness-hub.git",
48
+ "directory": "packages/@dzhechkov/harness-cli"
49
+ },
50
+ "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme",
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.json",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "typecheck": "tsc -p tsconfig.json --noEmit",
56
+ "lint": "tsc -p tsconfig.json --noEmit"
57
+ }
58
+ }
package/src/bin.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `dz` executable entry point.
4
+ */
5
+
6
+ import { runCli } from './cli.js';
7
+
8
+ process.exit(await runCli(process.argv.slice(2)));
package/src/cli.ts ADDED
@@ -0,0 +1,404 @@
1
+ /**
2
+ * The `dz` CLI — argv parsing + dispatch over `@dzhechkov/harness-core`.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmdirSync, symlinkSync } from 'node:fs';
8
+ import { basename, dirname, join, resolve } from 'node:path';
9
+ import { execSync } from 'node:child_process';
10
+ import { homedir } from 'node:os';
11
+
12
+ import {
13
+ getSkillInfo,
14
+ getWorkflow,
15
+ isTargetName,
16
+ listSkills,
17
+ runDoctor,
18
+ runInit,
19
+ runMigrate,
20
+ runSync,
21
+ runVerify,
22
+ TARGET_NAMES,
23
+ WORKFLOW_NAMES,
24
+ } from '@dzhechkov/harness-core';
25
+ import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
26
+
27
+ const USAGE = `dz - DZ cross-platform harness CLI
28
+
29
+ Usage:
30
+ dz init --target <name> [--skills-dir <dir>] [--project <dir>] [--preset <name>] [--select id,id,...] [--force]
31
+ dz verify [--skills-dir <dir>] [--target <name>]
32
+ dz sync [--canonical <dir>] [--project <dir>] [--dry-run] [--force]
33
+ dz update (alias of sync)
34
+ dz list [--skills-dir <dir>]
35
+ dz info <skill-id> [--skills-dir <dir>]
36
+ dz migrate [--project <dir>]
37
+ dz doctor [--project <dir>]
38
+ dz workflow <task> [--dry-run]
39
+ dz roam [--apply] [--slug <slug>]
40
+ dz help
41
+
42
+ Workflows: coverage-lift, mutation-kill, canonicalize, security-audit
43
+
44
+ Targets: ${TARGET_NAMES.join(', ')}
45
+ Presets: ${PRESET_NAMES.join(', ')}`;
46
+
47
+ /** Output sink + working directory — injectable so the CLI is testable. */
48
+ export interface CliIo {
49
+ readonly cwd?: string;
50
+ readonly write?: (line: string) => void;
51
+ }
52
+
53
+ interface ParsedArgs {
54
+ readonly command: string;
55
+ readonly options: Map<string, string>;
56
+ readonly flags: Set<string>;
57
+ }
58
+
59
+ /** Parse `<command> [--key value] [--flag]` argv. */
60
+ function parseArgs(argv: string[]): ParsedArgs {
61
+ const options = new Map<string, string>();
62
+ const flags = new Set<string>();
63
+ const positional: string[] = [];
64
+
65
+ for (let index = 0; index < argv.length; index += 1) {
66
+ const arg = argv[index] ?? '';
67
+ if (arg.startsWith('--')) {
68
+ const key = arg.slice(2);
69
+ const next = argv[index + 1];
70
+ if (next !== undefined && !next.startsWith('--')) {
71
+ options.set(key, next);
72
+ index += 1;
73
+ } else {
74
+ flags.add(key);
75
+ }
76
+ } else {
77
+ positional.push(arg);
78
+ }
79
+ }
80
+
81
+ return { command: positional[0] ?? '', options, flags };
82
+ }
83
+
84
+ type Write = (line: string) => void;
85
+
86
+ async function cmdInit(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
87
+ const target = options.get('target');
88
+ if (target === undefined || !isTargetName(target)) {
89
+ write(`dz init: --target must be one of: ${TARGET_NAMES.join(', ')}`);
90
+ return 1;
91
+ }
92
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
93
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
94
+
95
+ const presetName = options.get('preset');
96
+ const selectArg = options.get('select');
97
+ let select: readonly string[] | undefined;
98
+ if (presetName !== undefined) {
99
+ const preset = getPreset(presetName);
100
+ if (preset === undefined) {
101
+ write(`dz init: --preset must be one of: ${PRESET_NAMES.join(', ')}`);
102
+ return 1;
103
+ }
104
+ select = preset.skills;
105
+ } else if (selectArg !== undefined) {
106
+ // --select skill1,skill2,skill3 — comma-separated skill ids
107
+ select = selectArg.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
108
+ if (select.length === 0) {
109
+ write('dz init: --select requires comma-separated skill ids');
110
+ return 1;
111
+ }
112
+ }
113
+
114
+ const report = await runInit({
115
+ target,
116
+ skillsDir,
117
+ projectRoot,
118
+ force: flags.has('force'),
119
+ ...(select !== undefined ? { select } : {}),
120
+ });
121
+
122
+ let written = 0;
123
+ let skipped = 0;
124
+ for (const skill of report.skills) {
125
+ written += skill.written.length;
126
+ skipped += skill.skipped.length;
127
+ }
128
+ write(`dz init --target ${target}: ${report.skills.length} skill(s), ${written} file(s) written, ${skipped} skipped`);
129
+ for (const skill of report.skills) {
130
+ for (const warning of skill.warnings) write(` warning [${skill.id}]: ${warning}`);
131
+ }
132
+ return 0;
133
+ }
134
+
135
+ async function cmdVerify(options: Map<string, string>, cwd: string, write: Write): Promise<number> {
136
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
137
+ const targetOpt = options.get('target');
138
+ if (targetOpt !== undefined && !isTargetName(targetOpt)) {
139
+ write(`dz verify: --target must be one of: ${TARGET_NAMES.join(', ')}`);
140
+ return 1;
141
+ }
142
+ const report = await runVerify({
143
+ skillsDir,
144
+ ...(targetOpt !== undefined ? { target: targetOpt } : {}),
145
+ });
146
+ write(`dz verify (${report.target}): ${report.valid}/${report.total} skill(s) valid`);
147
+ for (const skill of report.skills) {
148
+ if (!skill.ok) write(` FAIL ${skill.id}: ${skill.errors.join('; ')}`);
149
+ }
150
+ return report.valid === report.total ? 0 : 1;
151
+ }
152
+
153
+ async function cmdSync(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
154
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
155
+ const canonicalArg = options.get('canonical');
156
+
157
+ // Auto-discover all skills-* packs, or use explicit --canonical
158
+ let canonicalDirs: string[];
159
+ if (canonicalArg !== undefined) {
160
+ canonicalDirs = [resolve(cwd, canonicalArg)];
161
+ } else {
162
+ const baseDir = join(projectRoot, 'packages', '@dzhechkov');
163
+ canonicalDirs = existsSync(baseDir)
164
+ ? readdirSync(baseDir, { withFileTypes: true })
165
+ .filter((e) => e.isDirectory() && e.name.startsWith('skills-'))
166
+ .map((e) => join(baseDir, e.name))
167
+ : [resolve(cwd, 'packages/@dzhechkov/skills-meta')];
168
+ }
169
+
170
+ const report = await runSync({
171
+ canonicalDirs,
172
+ projectRoot,
173
+ dryRun: flags.has('dry-run'),
174
+ force: flags.has('force'),
175
+ });
176
+ const { total, inSync, missing, drift } = report.summary;
177
+ write(
178
+ `dz sync${report.dryRun ? ' --dry-run' : ''}: ${inSync}/${total} in sync, ${missing} missing, ${drift} drift`,
179
+ );
180
+ return missing === 0 && drift === 0 ? 0 : 1;
181
+ }
182
+
183
+ function cmdList(options: Map<string, string>, cwd: string, write: Write): number {
184
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
185
+ const skills = listSkills(skillsDir);
186
+ if (skills.length === 0) {
187
+ write(`dz list: no skills found in ${skillsDir}`);
188
+ return 1;
189
+ }
190
+ write(`${skills.length} skill(s) in ${skillsDir}:\n`);
191
+ for (const skill of skills) {
192
+ const desc = skill.description.length > 80 ? skill.description.slice(0, 77) + '...' : skill.description;
193
+ write(` ${skill.id.padEnd(35)} ${desc}`);
194
+ }
195
+ return 0;
196
+ }
197
+
198
+ function cmdInfo(options: Map<string, string>, args: ParsedArgs, cwd: string, write: Write): number {
199
+ const skillsDir = resolve(cwd, options.get('skills-dir') ?? '.claude/skills');
200
+ // The skill id is the second positional arg (first is 'info')
201
+ const skillId = args.command === 'info' ? (args.options.get('_positional') ?? '') : '';
202
+ // Actually, let's parse it from the raw argv approach — check options map
203
+ // The id might be passed as the value after 'info' in positional args
204
+ // Since parseArgs puts non-flag args in positional, and command is first,
205
+ // we need to find the skill id. Let's check if there's a value in options.
206
+ const id = options.get('id') ?? options.get('skill') ?? '';
207
+ if (id === '') {
208
+ write('dz info: specify a skill id — dz info --id <skill-id>');
209
+ return 1;
210
+ }
211
+ const info = getSkillInfo(skillsDir, id);
212
+ if (info === undefined) {
213
+ write(`dz info: skill ${JSON.stringify(id)} not found in ${skillsDir}`);
214
+ return 1;
215
+ }
216
+ write(`Skill: ${info.id}`);
217
+ write(`Name: ${info.name ?? info.id}`);
218
+ write(`Description: ${info.description}`);
219
+ if (info.trustTier !== undefined) write(`Trust Tier: ${info.trustTier}`);
220
+ if (info.version !== undefined) write(`Version: ${info.version}`);
221
+ write(`Assets: ${info.assetCount} file(s)`);
222
+ if (info.assetCount > 0) {
223
+ for (const path of info.assetPaths) write(` ${path}`);
224
+ }
225
+ return 0;
226
+ }
227
+
228
+ function cmdWorkflow(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
229
+ const taskName = options.get('task') ?? options.get('name') ?? '';
230
+ if (taskName === '' || !WORKFLOW_NAMES.includes(taskName)) {
231
+ write(`dz workflow: specify a task — one of: ${WORKFLOW_NAMES.join(', ')}`);
232
+ return 1;
233
+ }
234
+ const wf = getWorkflow(taskName);
235
+ if (wf === undefined) {
236
+ write(`dz workflow: unknown task ${JSON.stringify(taskName)}`);
237
+ return 1;
238
+ }
239
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
240
+ const script = wf.generate({ projectRoot, dryRun: flags.has('dry-run') });
241
+
242
+ write(`// Dynamic Workflow: ${wf.name}`);
243
+ write(`// ${wf.description}`);
244
+ write('');
245
+ write(script);
246
+ write('');
247
+ write(`// To execute: paste the above into Claude Code with Opus 4.8+ and "ultracode" enabled.`);
248
+ write(`// Or save to a .js file and run: claude "Execute this workflow" < workflow.js`);
249
+ return 0;
250
+ }
251
+
252
+ function cmdMigrate(options: Map<string, string>, cwd: string, write: Write): number {
253
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
254
+ const report = runMigrate({ projectRoot });
255
+
256
+ if (report.detections.length === 0) {
257
+ write('dz migrate: no legacy installations detected.');
258
+ } else {
259
+ write(`dz migrate: found ${report.detections.length} legacy installation(s):\n`);
260
+ for (const det of report.detections) {
261
+ write(` ${det.manifest} — v${det.version}, ${det.components.length} components, ${det.fileCount} files`);
262
+ }
263
+ }
264
+ write(`\nSkills in .claude/skills/: ${report.skillsFound}`);
265
+ write(`\nRecommendation: ${report.recommendation}`);
266
+ return 0;
267
+ }
268
+
269
+ async function cmdDoctor(options: Map<string, string>, cwd: string, write: Write): Promise<number> {
270
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
271
+ const report = await runDoctor({ projectRoot });
272
+ write(`dz doctor (${report.node}):`);
273
+ for (const check of report.checks) {
274
+ write(` [${check.ok ? 'OK' : 'XX'}] ${check.name} - ${check.detail}`);
275
+ }
276
+ return report.ok ? 0 : 1;
277
+ }
278
+
279
+ function cmdRoam(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
280
+ const apply = flags.has('apply');
281
+
282
+ // Find repo root
283
+ let repoRoot: string;
284
+ try {
285
+ repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim();
286
+ } catch {
287
+ write('dz roam: not a git repository');
288
+ return 1;
289
+ }
290
+
291
+ const roamDir = join(repoRoot, 'roam', 'claude-state');
292
+ const claudeProjectsDir = process.env['CLAUDE_PROJECTS_DIR'] ?? join(homedir(), '.claude', 'projects');
293
+
294
+ // Slug: replace every : \ / with -
295
+ const slug = options.get('slug') ?? repoRoot.replace(/[:\\/]/g, '-');
296
+ const target = join(claudeProjectsDir, slug);
297
+
298
+ write(`repo root : ${repoRoot}`);
299
+ write(`roam dir : ${roamDir}`);
300
+ write(`slug : ${slug}`);
301
+ write(`target : ${target}`);
302
+ write(`mode : ${apply ? 'APPLY' : 'DRY-RUN'}`);
303
+ write('');
304
+
305
+ // Ensure roam dir exists
306
+ if (apply) {
307
+ mkdirSync(roamDir, { recursive: true });
308
+ } else {
309
+ write(' would: mkdir -p ' + roamDir);
310
+ }
311
+
312
+ // Check current state of target
313
+ if (existsSync(target) && lstatSync(target).isSymbolicLink()) {
314
+ const current = readlinkSync(target);
315
+ if (current === roamDir) {
316
+ write(' symlink already points at roam dir — nothing to do.');
317
+ return 0;
318
+ }
319
+ write(`WARN: ${target} symlinks elsewhere (${current}). Resolve manually.`);
320
+ return 3;
321
+ }
322
+
323
+ // Move existing content
324
+ if (existsSync(target) && lstatSync(target).isDirectory()) {
325
+ const entries = readdirSync(target);
326
+ if (entries.length > 0) {
327
+ write(` moving ${entries.length} entries from ${target} → ${roamDir}`);
328
+ if (apply) {
329
+ for (const entry of entries) {
330
+ const dest = join(roamDir, entry);
331
+ if (existsSync(dest)) {
332
+ write(` WARN: ${dest} already exists — skipping`);
333
+ continue;
334
+ }
335
+ renameSync(join(target, entry), dest);
336
+ }
337
+ }
338
+ }
339
+ if (apply) {
340
+ rmdirSync(target);
341
+ } else {
342
+ write(' would: rmdir ' + target);
343
+ }
344
+ }
345
+
346
+ // Create symlink
347
+ if (apply) {
348
+ mkdirSync(dirname(target), { recursive: true });
349
+ symlinkSync(roamDir, target);
350
+ } else {
351
+ write(' would: mkdir -p ' + dirname(target));
352
+ write(` would: ln -s ${roamDir} ${target}`);
353
+ }
354
+
355
+ write('');
356
+ write(`done. (${apply ? 'applied' : 'dry-run'})`);
357
+ return 0;
358
+ }
359
+
360
+ /**
361
+ * Run the `dz` CLI. Returns a process exit code; never calls `process.exit`,
362
+ * so it is safe to call from tests. Output and working directory are injectable.
363
+ */
364
+ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
365
+ const cwd = io.cwd ?? process.cwd();
366
+ const write: Write = io.write ?? ((line) => { console.log(line); });
367
+ const { command, options, flags } = parseArgs(argv);
368
+
369
+ if (command === '' || command === 'help' || flags.has('help')) {
370
+ write(USAGE);
371
+ return 0;
372
+ }
373
+
374
+ try {
375
+ switch (command) {
376
+ case 'init':
377
+ return await cmdInit(options, flags, cwd, write);
378
+ case 'verify':
379
+ return await cmdVerify(options, cwd, write);
380
+ case 'sync':
381
+ case 'update':
382
+ return await cmdSync(options, flags, cwd, write);
383
+ case 'list':
384
+ return cmdList(options, cwd, write);
385
+ case 'info':
386
+ return cmdInfo(options, { command, options, flags }, cwd, write);
387
+ case 'workflow':
388
+ return cmdWorkflow(options, flags, cwd, write);
389
+ case 'migrate':
390
+ return cmdMigrate(options, cwd, write);
391
+ case 'doctor':
392
+ return await cmdDoctor(options, cwd, write);
393
+ case 'roam':
394
+ return cmdRoam(options, flags, cwd, write);
395
+ default:
396
+ write(`dz: unknown command ${JSON.stringify(command)}`);
397
+ write(USAGE);
398
+ return 1;
399
+ }
400
+ } catch (error) {
401
+ write(`dz: ${error instanceof Error ? error.message : String(error)}`);
402
+ return 1;
403
+ }
404
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@dzhechkov/harness-cli` — the `dz` CLI for the DZ cross-platform harness.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ /** Package version. Kept in sync with `package.json`. */
8
+ export const HARNESS_CLI_VERSION = '0.1.0';
9
+
10
+ export { runCli } from './cli.js';
11
+ export type { CliIo } from './cli.js';