@harness-lab/cli 0.1.2 → 0.1.4
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 +21 -1
- package/package.json +1 -1
- package/src/run-cli.js +60 -5
- package/src/skill-install.js +82 -0
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Harness CLI
|
|
2
2
|
|
|
3
|
-
Small
|
|
3
|
+
Small Harness Lab CLI for facilitator auth, workshop operations, and repo-local skill installation.
|
|
4
4
|
|
|
5
5
|
Current shipped scope:
|
|
6
6
|
|
|
7
|
+
- `harness version`
|
|
8
|
+
- `harness skill install`
|
|
7
9
|
- `harness auth login`
|
|
8
10
|
- `harness auth logout`
|
|
9
11
|
- `harness auth status`
|
|
@@ -33,6 +35,7 @@ npm install -g @harness-lab/cli
|
|
|
33
35
|
Verify the binary:
|
|
34
36
|
|
|
35
37
|
```bash
|
|
38
|
+
harness --version
|
|
36
39
|
harness --help
|
|
37
40
|
```
|
|
38
41
|
|
|
@@ -49,6 +52,22 @@ cd harness-cli
|
|
|
49
52
|
npm link
|
|
50
53
|
```
|
|
51
54
|
|
|
55
|
+
Verify the local install:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
harness version
|
|
59
|
+
harness --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Install the repo-local workshop skill bundle for Codex/OpenCode discovery:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
harness skill install
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This creates `.agents/skills/harness-lab-workshop` in the current Harness Lab repo checkout.
|
|
69
|
+
After install, the CLI prints the first recommended agent commands, starting with `/workshop reference`.
|
|
70
|
+
|
|
52
71
|
Default device/browser login:
|
|
53
72
|
|
|
54
73
|
```bash
|
|
@@ -81,6 +100,7 @@ Workshop commands:
|
|
|
81
100
|
|
|
82
101
|
```bash
|
|
83
102
|
harness auth status
|
|
103
|
+
harness skill install
|
|
84
104
|
harness workshop status
|
|
85
105
|
harness workshop phase set rotation
|
|
86
106
|
harness workshop archive --notes "Manual archive"
|
package/package.json
CHANGED
package/src/run-cli.js
CHANGED
|
@@ -2,6 +2,11 @@ import { getDefaultDashboardUrl } from "./config.js";
|
|
|
2
2
|
import { createHarnessClient, HarnessApiError } from "./client.js";
|
|
3
3
|
import { prompt, writeLine } from "./io.js";
|
|
4
4
|
import { deleteSession, readSession, sanitizeSession, writeSession, getSessionStorageMode, SessionStoreError } from "./session-store.js";
|
|
5
|
+
import { installWorkshopSkill, SkillInstallError } from "./skill-install.js";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { version } = require("../package.json");
|
|
5
10
|
|
|
6
11
|
function sleep(ms) {
|
|
7
12
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -13,6 +18,14 @@ function parseArgs(argv) {
|
|
|
13
18
|
|
|
14
19
|
for (let index = 0; index < argv.length; index += 1) {
|
|
15
20
|
const value = argv[index];
|
|
21
|
+
if (value === "-h") {
|
|
22
|
+
flags.help = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (value === "-v") {
|
|
26
|
+
flags.version = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
16
29
|
if (value.startsWith("--")) {
|
|
17
30
|
const key = value.slice(2);
|
|
18
31
|
const next = argv[index + 1];
|
|
@@ -52,14 +65,42 @@ async function readJson(response) {
|
|
|
52
65
|
|
|
53
66
|
function printUsage(io) {
|
|
54
67
|
writeLine(io.stdout, "Usage:");
|
|
68
|
+
writeLine(io.stdout, " harness --help");
|
|
69
|
+
writeLine(io.stdout, " harness --version");
|
|
70
|
+
writeLine(io.stdout, " harness version");
|
|
55
71
|
writeLine(io.stdout, " harness auth login [--auth device|basic|neon] [--dashboard-url URL] [--username USER] [--email EMAIL] [--password PASS] [--no-open]");
|
|
56
72
|
writeLine(io.stdout, " harness auth logout");
|
|
57
73
|
writeLine(io.stdout, " harness auth status");
|
|
74
|
+
writeLine(io.stdout, " harness skill install [--force]");
|
|
58
75
|
writeLine(io.stdout, " harness workshop status");
|
|
59
76
|
writeLine(io.stdout, " harness workshop archive [--notes TEXT]");
|
|
60
77
|
writeLine(io.stdout, " harness workshop phase set <phase-id>");
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
function printVersion(io) {
|
|
81
|
+
writeLine(io.stdout, `harness ${version}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function handleSkillInstall(io, deps, flags) {
|
|
85
|
+
try {
|
|
86
|
+
const result = await installWorkshopSkill(deps.cwd ?? process.cwd(), { force: flags.force === true });
|
|
87
|
+
writeLine(io.stdout, `Installed Harness Lab workshop skill to ${result.installPath}`);
|
|
88
|
+
writeLine(io.stdout, "Codex and OpenCode should now discover it from this repo via .agents/skills.");
|
|
89
|
+
writeLine(io.stdout, "Next steps:");
|
|
90
|
+
writeLine(io.stdout, " 1. Open Codex or OpenCode in this repo.");
|
|
91
|
+
writeLine(io.stdout, " 2. Run `/workshop reference` for the command overview.");
|
|
92
|
+
writeLine(io.stdout, " 3. Run `/workshop setup` if your environment is not ready yet.");
|
|
93
|
+
writeLine(io.stdout, " 4. Run `/workshop` to get the current phase or fallback guidance.");
|
|
94
|
+
return 0;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof SkillInstallError) {
|
|
97
|
+
writeLine(io.stderr, `Skill install failed: ${error.message}`);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
function formatStorageError(error) {
|
|
64
105
|
if (error instanceof SessionStoreError) {
|
|
65
106
|
return error.message;
|
|
@@ -471,11 +512,7 @@ async function handleWorkshopPhaseSet(io, env, positionals, deps) {
|
|
|
471
512
|
|
|
472
513
|
export async function runCli(argv, io, deps = {}) {
|
|
473
514
|
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
474
|
-
|
|
475
|
-
throw new Error("Fetch is required to run the harness CLI.");
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const mergedDeps = { fetchFn, sleepFn: deps.sleepFn, openUrl: deps.openUrl };
|
|
515
|
+
const mergedDeps = { fetchFn, sleepFn: deps.sleepFn, openUrl: deps.openUrl, cwd: deps.cwd };
|
|
479
516
|
const { positionals, flags } = parseArgs(argv);
|
|
480
517
|
const [scope, action, subaction] = positionals;
|
|
481
518
|
|
|
@@ -484,11 +521,25 @@ export async function runCli(argv, io, deps = {}) {
|
|
|
484
521
|
return 0;
|
|
485
522
|
}
|
|
486
523
|
|
|
524
|
+
if (flags.version === true) {
|
|
525
|
+
printVersion(io);
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (scope === "version") {
|
|
530
|
+
printVersion(io);
|
|
531
|
+
return 0;
|
|
532
|
+
}
|
|
533
|
+
|
|
487
534
|
if (!scope) {
|
|
488
535
|
printUsage(io);
|
|
489
536
|
return 1;
|
|
490
537
|
}
|
|
491
538
|
|
|
539
|
+
if (typeof fetchFn !== "function") {
|
|
540
|
+
throw new Error("Fetch is required to run the harness CLI.");
|
|
541
|
+
}
|
|
542
|
+
|
|
492
543
|
if (scope === "auth" && action === "login") {
|
|
493
544
|
return handleAuthLogin(io, io.env, flags, mergedDeps);
|
|
494
545
|
}
|
|
@@ -501,6 +552,10 @@ export async function runCli(argv, io, deps = {}) {
|
|
|
501
552
|
return handleAuthStatus(io, io.env, mergedDeps);
|
|
502
553
|
}
|
|
503
554
|
|
|
555
|
+
if (scope === "skill" && action === "install") {
|
|
556
|
+
return handleSkillInstall(io, mergedDeps, flags);
|
|
557
|
+
}
|
|
558
|
+
|
|
504
559
|
if (scope === "workshop" && action === "status") {
|
|
505
560
|
return handleWorkshopStatus(io, io.env, mergedDeps);
|
|
506
561
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export class SkillInstallError extends Error {
|
|
5
|
+
constructor(message, options = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "SkillInstallError";
|
|
8
|
+
this.code = options.code ?? "skill_install_failed";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SKILL_NAME = "harness-lab-workshop";
|
|
13
|
+
|
|
14
|
+
async function pathExists(targetPath) {
|
|
15
|
+
try {
|
|
16
|
+
await fs.access(targetPath);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function findHarnessLabRepoRoot(startDir) {
|
|
24
|
+
let currentDir = path.resolve(startDir);
|
|
25
|
+
|
|
26
|
+
while (true) {
|
|
27
|
+
if (await pathExists(path.join(currentDir, "workshop-skill", "SKILL.md"))) {
|
|
28
|
+
return currentDir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const parentDir = path.dirname(currentDir);
|
|
32
|
+
if (parentDir === currentDir) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
currentDir = parentDir;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getInstalledSkillPath(repoRoot) {
|
|
41
|
+
return path.join(repoRoot, ".agents", "skills", SKILL_NAME);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function installWorkshopSkill(startDir, options = {}) {
|
|
45
|
+
const repoRoot = await findHarnessLabRepoRoot(startDir);
|
|
46
|
+
if (!repoRoot) {
|
|
47
|
+
throw new SkillInstallError(
|
|
48
|
+
"Harness CLI could not find `workshop-skill/SKILL.md`. Run this command inside a Harness Lab repo checkout.",
|
|
49
|
+
{ code: "repo_not_found" },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const installPath = getInstalledSkillPath(repoRoot);
|
|
54
|
+
if ((await pathExists(installPath)) && options.force !== true) {
|
|
55
|
+
throw new SkillInstallError(
|
|
56
|
+
`Skill already installed at ${installPath}. Re-run with --force to replace it.`,
|
|
57
|
+
{ code: "already_installed" },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (options.force === true) {
|
|
62
|
+
await fs.rm(installPath, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await fs.mkdir(installPath, { recursive: true });
|
|
66
|
+
await fs.cp(path.join(repoRoot, "workshop-skill"), path.join(installPath, "workshop-skill"), { recursive: true });
|
|
67
|
+
await fs.cp(path.join(repoRoot, "content"), path.join(installPath, "content"), { recursive: true });
|
|
68
|
+
await fs.cp(path.join(repoRoot, "workshop-blueprint"), path.join(installPath, "workshop-blueprint"), { recursive: true });
|
|
69
|
+
await fs.rm(path.join(installPath, "workshop-skill", "SKILL.md"), { force: true });
|
|
70
|
+
await fs.mkdir(path.join(installPath, "docs"), { recursive: true });
|
|
71
|
+
await fs.copyFile(path.join(repoRoot, "workshop-skill", "SKILL.md"), path.join(installPath, "SKILL.md"));
|
|
72
|
+
await fs.copyFile(
|
|
73
|
+
path.join(repoRoot, "docs", "workshop-event-context-contract.md"),
|
|
74
|
+
path.join(installPath, "docs", "workshop-event-context-contract.md"),
|
|
75
|
+
);
|
|
76
|
+
await fs.copyFile(
|
|
77
|
+
path.join(repoRoot, "docs", "harness-cli-foundation.md"),
|
|
78
|
+
path.join(installPath, "docs", "harness-cli-foundation.md"),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return { repoRoot, installPath, skillName: SKILL_NAME };
|
|
82
|
+
}
|