@harness-lab/cli 0.2.1 → 0.2.2
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 +14 -5
- package/assets/workshop-bundle/SKILL.md +295 -0
- package/assets/workshop-bundle/bundle-manifest.json +172 -0
- package/assets/workshop-bundle/content/challenge-cards/.gitkeep +0 -0
- package/assets/workshop-bundle/content/challenge-cards/deck.md +38 -0
- package/assets/workshop-bundle/content/challenge-cards/print-spec.md +28 -0
- package/assets/workshop-bundle/content/facilitation/.gitkeep +0 -0
- package/assets/workshop-bundle/content/facilitation/codex-setup-verification.md +54 -0
- package/assets/workshop-bundle/content/facilitation/master-guide.md +131 -0
- package/assets/workshop-bundle/content/project-briefs/.gitkeep +0 -0
- package/assets/workshop-bundle/content/project-briefs/code-review-helper.md +31 -0
- package/assets/workshop-bundle/content/project-briefs/devtoolbox-cli.md +31 -0
- package/assets/workshop-bundle/content/project-briefs/doc-generator.md +31 -0
- package/assets/workshop-bundle/content/project-briefs/metrics-dashboard.md +31 -0
- package/assets/workshop-bundle/content/project-briefs/standup-bot.md +31 -0
- package/assets/workshop-bundle/content/style-examples.md +127 -0
- package/assets/workshop-bundle/content/style-guide.md +106 -0
- package/assets/workshop-bundle/content/talks/.gitkeep +0 -0
- package/assets/workshop-bundle/content/talks/codex-demo-script.md +43 -0
- package/assets/workshop-bundle/content/talks/context-is-king.md +42 -0
- package/assets/workshop-bundle/docs/harness-cli-foundation.md +112 -0
- package/assets/workshop-bundle/docs/learner-reference-gallery.md +82 -0
- package/assets/workshop-bundle/docs/learner-resource-kit.md +126 -0
- package/assets/workshop-bundle/docs/workshop-event-context-contract.md +123 -0
- package/assets/workshop-bundle/materials/participant-resource-kit.md +72 -0
- package/assets/workshop-bundle/workshop-blueprint/README.md +48 -0
- package/assets/workshop-bundle/workshop-blueprint/agenda.json +70 -0
- package/assets/workshop-bundle/workshop-blueprint/control-surfaces.md +101 -0
- package/assets/workshop-bundle/workshop-blueprint/day-structure.md +129 -0
- package/assets/workshop-bundle/workshop-blueprint/edit-boundaries.md +64 -0
- package/assets/workshop-bundle/workshop-blueprint/operator-guide.md +74 -0
- package/assets/workshop-bundle/workshop-blueprint/teaching-spine.md +134 -0
- package/assets/workshop-bundle/workshop-skill/.gitkeep +0 -0
- package/assets/workshop-bundle/workshop-skill/analyze-checklist.md +21 -0
- package/assets/workshop-bundle/workshop-skill/closing-skill.md +30 -0
- package/assets/workshop-bundle/workshop-skill/commands.md +44 -0
- package/assets/workshop-bundle/workshop-skill/facilitator.md +416 -0
- package/assets/workshop-bundle/workshop-skill/follow-up-package.md +35 -0
- package/assets/workshop-bundle/workshop-skill/install.md +58 -0
- package/assets/workshop-bundle/workshop-skill/recap.md +22 -0
- package/assets/workshop-bundle/workshop-skill/reference.md +80 -0
- package/assets/workshop-bundle/workshop-skill/setup.md +84 -0
- package/assets/workshop-bundle/workshop-skill/template-agents.md +35 -0
- package/package.json +8 -3
- package/src/run-cli.js +16 -9
- package/src/skill-install.js +98 -57
- package/src/workshop-bundle.js +233 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# AGENTS.md Starter
|
|
2
|
+
|
|
3
|
+
Použijte to jako krátkou mapu repa. Ne jako encyklopedii. Když detail patří jinam, odkažte na konkrétní soubor místo kopírování dlouhého textu.
|
|
4
|
+
|
|
5
|
+
Nejdůležitější pravidlo:
|
|
6
|
+
- napište, kam má agent sáhnout jako první
|
|
7
|
+
- napište, co je source of truth
|
|
8
|
+
- napište, jak se práce ověří
|
|
9
|
+
- když se text nafukuje, přidejte deeper doc a odkažte na něj
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
|
|
13
|
+
Popiš, co má agent v tomto repozitáři vytvořit nebo udržovat.
|
|
14
|
+
|
|
15
|
+
## Context
|
|
16
|
+
|
|
17
|
+
- Klíčové soubory a složky
|
|
18
|
+
- Rozhodnutí, která už padla
|
|
19
|
+
- Systémy nebo integrace, na které se navazuje
|
|
20
|
+
- Kam má agent sáhnout jako první
|
|
21
|
+
- Které docs nebo runbooky jsou source of truth
|
|
22
|
+
|
|
23
|
+
## Constraints
|
|
24
|
+
|
|
25
|
+
- Build/test/lint příkazy
|
|
26
|
+
- Jazykové, architektonické a bezpečnostní standardy
|
|
27
|
+
- Co agent nesmí dělat bez explicitního souhlasu
|
|
28
|
+
- Public/private nebo auth boundary, pokud existuje
|
|
29
|
+
|
|
30
|
+
## Done When
|
|
31
|
+
|
|
32
|
+
- Jak poznáme, že je práce hotová
|
|
33
|
+
- Jaké ověření musí proběhnout
|
|
34
|
+
- Jak má vypadat handoff pro dalšího člověka nebo agenta
|
|
35
|
+
- Jaký bude další safe move, pokud práce zůstane rozdělaná
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-lab/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Participant-facing Harness Lab CLI for facilitator auth and workshop operations",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
"ai-agents"
|
|
21
21
|
],
|
|
22
22
|
"engines": {
|
|
23
|
-
"node": "
|
|
23
|
+
"node": "22.x"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
+
"assets",
|
|
29
30
|
"bin",
|
|
30
31
|
"src",
|
|
31
32
|
"README.md"
|
|
@@ -37,6 +38,10 @@
|
|
|
37
38
|
"chalk": "^5.6.2"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
|
-
"
|
|
41
|
+
"preverify:workshop-bundle": "node ./scripts/sync-workshop-bundle.mjs --with-repo-bundle",
|
|
42
|
+
"prepack": "node ./scripts/sync-workshop-bundle.mjs",
|
|
43
|
+
"sync:workshop-bundle": "node ./scripts/sync-workshop-bundle.mjs --with-repo-bundle",
|
|
44
|
+
"verify:workshop-bundle": "node ./scripts/verify-workshop-bundle.mjs",
|
|
45
|
+
"test": "npm run verify:workshop-bundle && node --test"
|
|
41
46
|
}
|
|
42
47
|
}
|
package/src/run-cli.js
CHANGED
|
@@ -173,7 +173,7 @@ function printUsage(io, ui) {
|
|
|
173
173
|
"harness auth login [--auth device|basic|neon] [--dashboard-url URL] [--username USER] [--email EMAIL] [--password PASS] [--no-open]",
|
|
174
174
|
"harness auth logout",
|
|
175
175
|
"harness auth status",
|
|
176
|
-
"harness skill install [--force]",
|
|
176
|
+
"harness skill install [--target PATH] [--force]",
|
|
177
177
|
"harness workshop status",
|
|
178
178
|
"harness workshop archive [--notes TEXT]",
|
|
179
179
|
"harness workshop create-instance [<instance-id>] [--template-id ID] [--event-title TEXT] [--city CITY]",
|
|
@@ -190,23 +190,30 @@ function printVersion(io) {
|
|
|
190
190
|
|
|
191
191
|
async function handleSkillInstall(io, ui, deps, flags) {
|
|
192
192
|
try {
|
|
193
|
-
const result = await installWorkshopSkill(deps.cwd ?? process.cwd(), {
|
|
193
|
+
const result = await installWorkshopSkill(deps.cwd ?? process.cwd(), {
|
|
194
|
+
force: flags.force === true,
|
|
195
|
+
target: readStringFlag(flags, "target"),
|
|
196
|
+
});
|
|
194
197
|
ui.heading("Workshop Skill");
|
|
195
|
-
if (result.mode === "
|
|
196
|
-
ui.status("ok", "Harness Lab workshop skill is already
|
|
198
|
+
if (result.mode === "already_current") {
|
|
199
|
+
ui.status("ok", "Harness Lab workshop skill is already current at the target path.");
|
|
200
|
+
} else if (result.mode === "refreshed") {
|
|
201
|
+
ui.status("ok", "Refreshed the installed Harness Lab workshop skill bundle.");
|
|
197
202
|
} else {
|
|
198
203
|
ui.status("ok", "Installed the Harness Lab workshop skill bundle.");
|
|
199
204
|
}
|
|
205
|
+
ui.keyValue("Target", result.targetRoot);
|
|
200
206
|
ui.keyValue("Location", result.installPath);
|
|
201
207
|
ui.keyValue("Discovery", ".agents/skills");
|
|
208
|
+
ui.keyValue("Bundle source", result.sourceMode === "packaged_bundle" ? "packaged portable bundle" : "source checkout fallback");
|
|
202
209
|
ui.blank();
|
|
203
210
|
ui.section("Next steps");
|
|
204
211
|
ui.numberedList([
|
|
205
|
-
"Open Codex or pi in
|
|
206
|
-
"Start with the workshop
|
|
207
|
-
"Codex: `$workshop
|
|
208
|
-
"pi: `/skill:workshop`, then ask for the workshop
|
|
209
|
-
"
|
|
212
|
+
"Open Codex or pi in the target repo.",
|
|
213
|
+
"Start with the workshop command menu.",
|
|
214
|
+
"Codex: `$workshop commands`.",
|
|
215
|
+
"pi: `/skill:workshop`, then ask for the workshop commands.",
|
|
216
|
+
"Next: `$workshop reference`, `$workshop brief`, and `$workshop resources`.",
|
|
210
217
|
]);
|
|
211
218
|
return 0;
|
|
212
219
|
} catch (error) {
|
package/src/skill-install.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
createWorkshopBundleManifestFromDirectory,
|
|
5
|
+
createWorkshopBundleManifestFromSource,
|
|
6
|
+
createWorkshopBundleFromSource,
|
|
7
|
+
getInstalledSkillPath,
|
|
8
|
+
getPackagedWorkshopBundlePath,
|
|
9
|
+
getRepoWorkshopSourceRoot,
|
|
10
|
+
pathExists,
|
|
11
|
+
readWorkshopBundleManifest,
|
|
12
|
+
WORKSHOP_SKILL_NAME,
|
|
13
|
+
} from "./workshop-bundle.js";
|
|
3
14
|
|
|
4
15
|
export class SkillInstallError extends Error {
|
|
5
16
|
constructor(message, options = {}) {
|
|
@@ -9,94 +20,124 @@ export class SkillInstallError extends Error {
|
|
|
9
20
|
}
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
async function resolveBundleSource() {
|
|
24
|
+
const packagedBundlePath = getPackagedWorkshopBundlePath();
|
|
25
|
+
if (await pathExists(path.join(packagedBundlePath, "SKILL.md"))) {
|
|
26
|
+
return {
|
|
27
|
+
mode: "packaged_bundle",
|
|
28
|
+
sourcePath: packagedBundlePath,
|
|
29
|
+
sourceRoot: packagedBundlePath,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
const repoSourceRoot = getRepoWorkshopSourceRoot();
|
|
34
|
+
if (await pathExists(path.join(repoSourceRoot, "workshop-skill", "SKILL.md"))) {
|
|
35
|
+
return {
|
|
36
|
+
mode: "repo_source",
|
|
37
|
+
sourcePath: repoSourceRoot,
|
|
38
|
+
sourceRoot: repoSourceRoot,
|
|
39
|
+
};
|
|
20
40
|
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
21
43
|
}
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
async function ensureDirectory(targetPath) {
|
|
46
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
47
|
+
const stat = await fs.stat(targetPath);
|
|
48
|
+
if (!stat.isDirectory()) {
|
|
49
|
+
throw new SkillInstallError(`Install target is not a directory: ${targetPath}`, {
|
|
50
|
+
code: "invalid_target",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
25
54
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
55
|
+
async function hasInstalledSkill(targetRoot) {
|
|
56
|
+
return pathExists(path.join(getInstalledSkillPath(targetRoot), "SKILL.md"));
|
|
57
|
+
}
|
|
30
58
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
async function getSourceBundleManifest(resolvedBundle) {
|
|
60
|
+
if (resolvedBundle.mode === "packaged_bundle") {
|
|
61
|
+
const manifest = await readWorkshopBundleManifest(resolvedBundle.sourcePath);
|
|
62
|
+
if (manifest) {
|
|
63
|
+
return manifest;
|
|
34
64
|
}
|
|
35
65
|
|
|
36
|
-
|
|
66
|
+
return createWorkshopBundleManifestFromDirectory(resolvedBundle.sourcePath);
|
|
37
67
|
}
|
|
38
|
-
}
|
|
39
68
|
|
|
40
|
-
|
|
41
|
-
return path.join(repoRoot, ".agents", "skills", SKILL_NAME);
|
|
69
|
+
return createWorkshopBundleManifestFromSource(resolvedBundle.sourceRoot);
|
|
42
70
|
}
|
|
43
71
|
|
|
44
|
-
async function
|
|
45
|
-
|
|
72
|
+
async function installFromResolvedBundle(resolvedBundle, installPath) {
|
|
73
|
+
if (resolvedBundle.mode === "packaged_bundle") {
|
|
74
|
+
await fs.cp(resolvedBundle.sourcePath, installPath, { recursive: true });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await createWorkshopBundleFromSource(resolvedBundle.sourceRoot, installPath);
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
export async function installWorkshopSkill(startDir, options = {}) {
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
82
|
+
const resolvedBundle = await resolveBundleSource();
|
|
83
|
+
if (!resolvedBundle) {
|
|
51
84
|
throw new SkillInstallError(
|
|
52
|
-
"Harness CLI could not find
|
|
53
|
-
{ code: "
|
|
85
|
+
"Harness CLI could not find a portable workshop bundle. Reinstall `@harness-lab/cli` or run from a Harness Lab source checkout.",
|
|
86
|
+
{ code: "bundle_not_found" },
|
|
54
87
|
);
|
|
55
88
|
}
|
|
56
89
|
|
|
57
|
-
const
|
|
58
|
-
|
|
90
|
+
const targetRoot = path.resolve(startDir, options.target ?? ".");
|
|
91
|
+
await ensureDirectory(targetRoot);
|
|
92
|
+
|
|
93
|
+
const installPath = getInstalledSkillPath(targetRoot);
|
|
94
|
+
const existingInstall = await hasInstalledSkill(targetRoot);
|
|
95
|
+
const sourceManifest = await getSourceBundleManifest(resolvedBundle);
|
|
96
|
+
|
|
97
|
+
if (existingInstall && options.force !== true) {
|
|
98
|
+
const installedManifest = await createWorkshopBundleManifestFromDirectory(installPath);
|
|
99
|
+
if (installedManifest.contentHash === sourceManifest.contentHash) {
|
|
100
|
+
return {
|
|
101
|
+
installPath,
|
|
102
|
+
skillName: WORKSHOP_SKILL_NAME,
|
|
103
|
+
mode: "already_current",
|
|
104
|
+
sourceMode: resolvedBundle.mode,
|
|
105
|
+
targetRoot,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await fs.rm(installPath, { recursive: true, force: true });
|
|
59
110
|
|
|
60
|
-
if (bundledRepoSkill) {
|
|
61
111
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
skillName: SKILL_NAME,
|
|
65
|
-
mode: "already_bundled",
|
|
112
|
+
...(await installFreshBundle(resolvedBundle, installPath, targetRoot)),
|
|
113
|
+
mode: "refreshed",
|
|
66
114
|
};
|
|
67
115
|
}
|
|
68
116
|
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
`Skill already installed at ${installPath}. Re-run with --force to replace it.`,
|
|
72
|
-
{ code: "already_installed" },
|
|
73
|
-
);
|
|
117
|
+
if (existingInstall && options.force === true) {
|
|
118
|
+
await fs.rm(installPath, { recursive: true, force: true });
|
|
74
119
|
}
|
|
75
120
|
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
const result = await installFreshBundle(resolvedBundle, installPath, targetRoot);
|
|
122
|
+
if (existingInstall && options.force === true) {
|
|
123
|
+
return {
|
|
124
|
+
...result,
|
|
125
|
+
mode: "refreshed",
|
|
126
|
+
};
|
|
78
127
|
}
|
|
79
128
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
await fs.
|
|
85
|
-
await
|
|
86
|
-
await fs.copyFile(path.join(repoRoot, "workshop-skill", "SKILL.md"), path.join(installPath, "SKILL.md"));
|
|
87
|
-
await fs.copyFile(
|
|
88
|
-
path.join(repoRoot, "docs", "workshop-event-context-contract.md"),
|
|
89
|
-
path.join(installPath, "docs", "workshop-event-context-contract.md"),
|
|
90
|
-
);
|
|
91
|
-
await fs.copyFile(
|
|
92
|
-
path.join(repoRoot, "docs", "harness-cli-foundation.md"),
|
|
93
|
-
path.join(installPath, "docs", "harness-cli-foundation.md"),
|
|
94
|
-
);
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function installFreshBundle(resolvedBundle, installPath, targetRoot) {
|
|
133
|
+
await fs.mkdir(path.dirname(installPath), { recursive: true });
|
|
134
|
+
await installFromResolvedBundle(resolvedBundle, installPath);
|
|
95
135
|
|
|
96
136
|
return {
|
|
97
|
-
repoRoot,
|
|
98
137
|
installPath,
|
|
99
|
-
skillName:
|
|
138
|
+
skillName: WORKSHOP_SKILL_NAME,
|
|
100
139
|
mode: "installed",
|
|
140
|
+
sourceMode: resolvedBundle.mode,
|
|
141
|
+
targetRoot,
|
|
101
142
|
};
|
|
102
143
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const WORKSHOP_SKILL_NAME = "harness-lab-workshop";
|
|
7
|
+
export const WORKSHOP_BUNDLE_MANIFEST = "bundle-manifest.json";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
12
|
+
|
|
13
|
+
const DIRECTORY_COPIES = [
|
|
14
|
+
["workshop-skill", "workshop-skill"],
|
|
15
|
+
["content", "content"],
|
|
16
|
+
["workshop-blueprint", "workshop-blueprint"],
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const FILE_COPIES = [
|
|
20
|
+
["workshop-skill/SKILL.md", "SKILL.md"],
|
|
21
|
+
["docs/workshop-event-context-contract.md", "docs/workshop-event-context-contract.md"],
|
|
22
|
+
["docs/harness-cli-foundation.md", "docs/harness-cli-foundation.md"],
|
|
23
|
+
["docs/learner-resource-kit.md", "docs/learner-resource-kit.md"],
|
|
24
|
+
["docs/learner-reference-gallery.md", "docs/learner-reference-gallery.md"],
|
|
25
|
+
["materials/participant-resource-kit.md", "materials/participant-resource-kit.md"],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function getPackageRoot() {
|
|
29
|
+
return packageRoot;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getPackagedWorkshopBundlePath() {
|
|
33
|
+
return path.join(packageRoot, "assets", "workshop-bundle");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getRepoWorkshopSourceRoot() {
|
|
37
|
+
return path.resolve(packageRoot, "..");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getRepoBundledWorkshopSkillPath() {
|
|
41
|
+
return path.join(getRepoWorkshopSourceRoot(), ".agents", "skills", WORKSHOP_SKILL_NAME);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getInstalledSkillPath(targetRoot) {
|
|
45
|
+
return path.join(targetRoot, ".agents", "skills", WORKSHOP_SKILL_NAME);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getBundleManifestPath(bundleRoot) {
|
|
49
|
+
return path.join(bundleRoot, WORKSHOP_BUNDLE_MANIFEST);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function pathExists(targetPath) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(targetPath);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function copyDirectoryTree(sourceRoot, targetRoot) {
|
|
62
|
+
for (const [sourceRelativePath, targetRelativePath] of DIRECTORY_COPIES) {
|
|
63
|
+
await fs.cp(path.join(sourceRoot, sourceRelativePath), path.join(targetRoot, targetRelativePath), { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function copyBundleFiles(sourceRoot, targetRoot) {
|
|
68
|
+
for (const [sourceRelativePath, targetRelativePath] of FILE_COPIES) {
|
|
69
|
+
const targetPath = path.join(targetRoot, targetRelativePath);
|
|
70
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
71
|
+
await fs.copyFile(path.join(sourceRoot, sourceRelativePath), targetPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizePathForManifest(targetPath) {
|
|
76
|
+
return targetPath.split(path.sep).join("/");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function listFilesRecursive(rootPath) {
|
|
80
|
+
const entries = [];
|
|
81
|
+
|
|
82
|
+
async function walk(currentPath, relativePrefix = "") {
|
|
83
|
+
const dirEntries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
84
|
+
dirEntries.sort((left, right) => left.name.localeCompare(right.name));
|
|
85
|
+
|
|
86
|
+
for (const entry of dirEntries) {
|
|
87
|
+
const entryRelativePath = relativePrefix ? path.join(relativePrefix, entry.name) : entry.name;
|
|
88
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
89
|
+
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
await walk(entryPath, entryRelativePath);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (entry.isFile()) {
|
|
96
|
+
entries.push({
|
|
97
|
+
absolutePath: entryPath,
|
|
98
|
+
relativePath: normalizePathForManifest(entryRelativePath),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await walk(rootPath);
|
|
105
|
+
return entries;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function readPackageVersion() {
|
|
109
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
110
|
+
return String(packageJson.version);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function createManifestFromEntries(entries) {
|
|
114
|
+
const bundleVersion = await readPackageVersion();
|
|
115
|
+
const files = [];
|
|
116
|
+
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const contents = await fs.readFile(entry.absolutePath);
|
|
119
|
+
const sha256 = crypto.createHash("sha256").update(contents).digest("hex");
|
|
120
|
+
files.push({
|
|
121
|
+
path: entry.relativePath,
|
|
122
|
+
sha256,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
127
|
+
|
|
128
|
+
const contentHash = crypto
|
|
129
|
+
.createHash("sha256")
|
|
130
|
+
.update(
|
|
131
|
+
files
|
|
132
|
+
.map((file) => `${file.path}:${file.sha256}`)
|
|
133
|
+
.join("\n"),
|
|
134
|
+
)
|
|
135
|
+
.digest("hex");
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
manifestVersion: 1,
|
|
139
|
+
bundleName: WORKSHOP_SKILL_NAME,
|
|
140
|
+
bundleVersion,
|
|
141
|
+
contentHash,
|
|
142
|
+
files,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function createWorkshopBundleManifestFromDirectory(bundleRoot) {
|
|
147
|
+
const files = await listFilesRecursive(bundleRoot);
|
|
148
|
+
return createManifestFromEntries(
|
|
149
|
+
files.filter((file) => file.relativePath !== WORKSHOP_BUNDLE_MANIFEST),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function createWorkshopBundleManifestFromSource(sourceRoot) {
|
|
154
|
+
const entries = [];
|
|
155
|
+
|
|
156
|
+
for (const [sourceRelativePath, targetRelativePath] of DIRECTORY_COPIES) {
|
|
157
|
+
const sourceDirectory = path.join(sourceRoot, sourceRelativePath);
|
|
158
|
+
const sourceFiles = await listFilesRecursive(sourceDirectory);
|
|
159
|
+
|
|
160
|
+
for (const file of sourceFiles) {
|
|
161
|
+
const targetRelative = normalizePathForManifest(path.join(targetRelativePath, file.relativePath));
|
|
162
|
+
if (targetRelative === "workshop-skill/SKILL.md") {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
entries.push({
|
|
167
|
+
absolutePath: file.absolutePath,
|
|
168
|
+
relativePath: targetRelative,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const [sourceRelativePath, targetRelativePath] of FILE_COPIES) {
|
|
174
|
+
entries.push({
|
|
175
|
+
absolutePath: path.join(sourceRoot, sourceRelativePath),
|
|
176
|
+
relativePath: normalizePathForManifest(targetRelativePath),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return createManifestFromEntries(entries);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function readWorkshopBundleManifest(bundleRoot) {
|
|
184
|
+
try {
|
|
185
|
+
const manifest = JSON.parse(await fs.readFile(getBundleManifestPath(bundleRoot), "utf8"));
|
|
186
|
+
if (!manifest || typeof manifest.contentHash !== "string") {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return manifest;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function writeWorkshopBundleManifest(bundleRoot, manifest) {
|
|
196
|
+
await fs.writeFile(
|
|
197
|
+
getBundleManifestPath(bundleRoot),
|
|
198
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
199
|
+
"utf8",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function createWorkshopBundleFromSource(sourceRoot, targetRoot, options = {}) {
|
|
204
|
+
if (options.clean === true) {
|
|
205
|
+
await fs.rm(targetRoot, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
await fs.mkdir(targetRoot, { recursive: true });
|
|
208
|
+
await copyDirectoryTree(sourceRoot, targetRoot);
|
|
209
|
+
await fs.rm(path.join(targetRoot, "workshop-skill", "SKILL.md"), { force: true });
|
|
210
|
+
await copyBundleFiles(sourceRoot, targetRoot);
|
|
211
|
+
const manifest = await createWorkshopBundleManifestFromSource(sourceRoot);
|
|
212
|
+
await writeWorkshopBundleManifest(targetRoot, manifest);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function syncPackagedWorkshopBundle() {
|
|
216
|
+
const sourceRoot = getRepoWorkshopSourceRoot();
|
|
217
|
+
const bundleRoot = getPackagedWorkshopBundlePath();
|
|
218
|
+
await createWorkshopBundleFromSource(sourceRoot, bundleRoot, { clean: true });
|
|
219
|
+
return {
|
|
220
|
+
sourceRoot,
|
|
221
|
+
bundleRoot,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function syncRepoBundledWorkshopSkill() {
|
|
226
|
+
const sourceRoot = getRepoWorkshopSourceRoot();
|
|
227
|
+
const bundleRoot = getRepoBundledWorkshopSkillPath();
|
|
228
|
+
await createWorkshopBundleFromSource(sourceRoot, bundleRoot, { clean: true });
|
|
229
|
+
return {
|
|
230
|
+
sourceRoot,
|
|
231
|
+
bundleRoot,
|
|
232
|
+
};
|
|
233
|
+
}
|