runcastle 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/bin/runcastle.js +5580 -0
- package/drizzle/0000_amused_bruce_banner.sql +74 -0
- package/drizzle/0001_mapped_feature_flag.sql +1 -0
- package/drizzle/0002_waypoints.sql +14 -0
- package/drizzle/0003_project_closed_at.sql +1 -0
- package/drizzle/0004_events_project_id.sql +28 -0
- package/drizzle/0005_project_setting_overrides.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +466 -0
- package/drizzle/meta/0001_snapshot.json +474 -0
- package/drizzle/meta/0002_snapshot.json +568 -0
- package/drizzle/meta/0003_snapshot.json +575 -0
- package/drizzle/meta/0004_snapshot.json +582 -0
- package/drizzle/meta/0005_snapshot.json +596 -0
- package/drizzle/meta/_journal.json +48 -0
- package/hook-client.ts +67 -0
- package/index.js +5424 -0
- package/package.json +24 -30
- package/pty-host.cjs +186 -0
- package/sandcastle-template/Containerfile +33 -0
- package/sandcastle-template/Dockerfile +33 -0
- package/skills/burner/implement-ticket.md +42 -0
- package/skills/burner/research-waypoint.md +45 -0
- package/skills/packs/README.md +50 -0
- package/skills/packs/runcastle/.claude-plugin/plugin.json +6 -0
- package/skills/packs/runcastle/skills/converge/SKILL.md +51 -0
- package/skills/packs/runcastle/skills/ideate/SKILL.md +81 -0
- package/skills/packs/runcastle/skills/qa/SKILL.md +30 -0
- package/skills/packs/runcastle/skills/spec/SKILL.md +43 -0
- package/skills/packs/runcastle/skills/tickets/SKILL.md +52 -0
- package/skills/packs/runcastle/skills/waypoint/SKILL.md +54 -0
- package/web/assets/index-CKZ5Z7vc.css +1 -0
- package/web/assets/index-YBuT7tTz.js +108 -0
- package/web/index.html +13 -0
- package/dist/add-PRGLHAVL.js +0 -298
- package/dist/add-PRGLHAVL.js.map +0 -1
- package/dist/chunk-44KDPUYJ.js +0 -49
- package/dist/chunk-44KDPUYJ.js.map +0 -1
- package/dist/chunk-5VPKMXSV.js +0 -190
- package/dist/chunk-5VPKMXSV.js.map +0 -1
- package/dist/chunk-6CWCR5SO.js +0 -24
- package/dist/chunk-6CWCR5SO.js.map +0 -1
- package/dist/chunk-AHRTPV2C.js +0 -1082
- package/dist/chunk-AHRTPV2C.js.map +0 -1
- package/dist/chunk-LP5MV76P.js +0 -33
- package/dist/chunk-LP5MV76P.js.map +0 -1
- package/dist/chunk-OJ356IMJ.js +0 -136
- package/dist/chunk-OJ356IMJ.js.map +0 -1
- package/dist/chunk-PRNBOQHW.js +0 -32
- package/dist/chunk-PRNBOQHW.js.map +0 -1
- package/dist/chunk-SNUH2UT6.js +0 -95
- package/dist/chunk-SNUH2UT6.js.map +0 -1
- package/dist/chunk-UCYVC3H6.js +0 -108
- package/dist/chunk-UCYVC3H6.js.map +0 -1
- package/dist/chunk-Y5P5BQWF.js +0 -136
- package/dist/chunk-Y5P5BQWF.js.map +0 -1
- package/dist/chunk-ZQ3BK4ZD.js +0 -134
- package/dist/chunk-ZQ3BK4ZD.js.map +0 -1
- package/dist/index.js +0 -94
- package/dist/index.js.map +0 -1
- package/dist/info-U6AZZ2TV.js +0 -78
- package/dist/info-U6AZZ2TV.js.map +0 -1
- package/dist/init-MOJNLWXK.js +0 -212
- package/dist/init-MOJNLWXK.js.map +0 -1
- package/dist/logout-YPGZ6BB6.js +0 -27
- package/dist/logout-YPGZ6BB6.js.map +0 -1
- package/dist/publish-W37Y6TO6.js +0 -211
- package/dist/publish-W37Y6TO6.js.map +0 -1
- package/dist/search-2KNAXPM5.js +0 -55
- package/dist/search-2KNAXPM5.js.map +0 -1
- package/dist/update-NIIUPL2E.js +0 -123
- package/dist/update-NIIUPL2E.js.map +0 -1
- package/dist/validate-GFDSEVPV.js +0 -12
- package/dist/validate-GFDSEVPV.js.map +0 -1
package/web/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>runcastle</title>
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-YBuT7tTz.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CKZ5Z7vc.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/dist/add-PRGLHAVL.js
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
compatibilityNote,
|
|
3
|
-
detectSandcastle,
|
|
4
|
-
renderPreview
|
|
5
|
-
} from "./chunk-ZQ3BK4ZD.js";
|
|
6
|
-
import {
|
|
7
|
-
downloadFromRegistry,
|
|
8
|
-
writeLockEntry
|
|
9
|
-
} from "./chunk-UCYVC3H6.js";
|
|
10
|
-
import {
|
|
11
|
-
hasConflicts,
|
|
12
|
-
planInstall,
|
|
13
|
-
postInstallNotes,
|
|
14
|
-
renderDiff,
|
|
15
|
-
sha256Hex,
|
|
16
|
-
toPreviewPlan,
|
|
17
|
-
writePlan
|
|
18
|
-
} from "./chunk-Y5P5BQWF.js";
|
|
19
|
-
import {
|
|
20
|
-
RealGitHubClient
|
|
21
|
-
} from "./chunk-OJ356IMJ.js";
|
|
22
|
-
import {
|
|
23
|
-
RegistryClient,
|
|
24
|
-
getFetch
|
|
25
|
-
} from "./chunk-5VPKMXSV.js";
|
|
26
|
-
import {
|
|
27
|
-
configDir,
|
|
28
|
-
resolveRegistry
|
|
29
|
-
} from "./chunk-LP5MV76P.js";
|
|
30
|
-
import {
|
|
31
|
-
PAYLOAD_DIR,
|
|
32
|
-
parseManifest
|
|
33
|
-
} from "./chunk-AHRTPV2C.js";
|
|
34
|
-
import {
|
|
35
|
-
CliError
|
|
36
|
-
} from "./chunk-6CWCR5SO.js";
|
|
37
|
-
import {
|
|
38
|
-
emitJson,
|
|
39
|
-
isJsonMode,
|
|
40
|
-
log,
|
|
41
|
-
pc
|
|
42
|
-
} from "./chunk-PRNBOQHW.js";
|
|
43
|
-
|
|
44
|
-
// src/commands/add.ts
|
|
45
|
-
import "path";
|
|
46
|
-
|
|
47
|
-
// src/lib/install-id.ts
|
|
48
|
-
import fsp from "fs/promises";
|
|
49
|
-
import path from "path";
|
|
50
|
-
import { randomUUID } from "crypto";
|
|
51
|
-
function idPath() {
|
|
52
|
-
return path.join(configDir(), "install-id");
|
|
53
|
-
}
|
|
54
|
-
async function getInstallId() {
|
|
55
|
-
const file = idPath();
|
|
56
|
-
try {
|
|
57
|
-
const existing = (await fsp.readFile(file, "utf8")).trim();
|
|
58
|
-
if (existing) return existing;
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
61
|
-
const id = randomUUID();
|
|
62
|
-
try {
|
|
63
|
-
await fsp.mkdir(configDir(), { recursive: true });
|
|
64
|
-
await fsp.writeFile(file, id, "utf8");
|
|
65
|
-
} catch {
|
|
66
|
-
}
|
|
67
|
-
return id;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// src/commands/add.ts
|
|
71
|
-
function parseAddSpec(spec) {
|
|
72
|
-
if (spec.startsWith("github:")) {
|
|
73
|
-
const rest = spec.slice("github:".length);
|
|
74
|
-
const atIdx2 = rest.indexOf("@");
|
|
75
|
-
const pathPart = atIdx2 === -1 ? rest : rest.slice(0, atIdx2);
|
|
76
|
-
const ref = atIdx2 === -1 ? "" : rest.slice(atIdx2 + 1);
|
|
77
|
-
const segs2 = pathPart.split("/").filter(Boolean);
|
|
78
|
-
if (segs2.length < 2) {
|
|
79
|
-
throw new CliError(`invalid github: spec "${spec}" \u2014 expected github:owner/repo[/dir][@sha]`, {
|
|
80
|
-
code: "E_BAD_SPEC"
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
const owner = segs2[0];
|
|
84
|
-
const repo = segs2[1];
|
|
85
|
-
const dirSegs = segs2.slice(2);
|
|
86
|
-
return {
|
|
87
|
-
githubMode: true,
|
|
88
|
-
owner,
|
|
89
|
-
repo,
|
|
90
|
-
...dirSegs.length ? { directory: dirSegs.join("/") } : {},
|
|
91
|
-
...ref ? { ref } : {}
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const atIdx = spec.indexOf("@");
|
|
95
|
-
const namepart = atIdx === -1 ? spec : spec.slice(0, atIdx);
|
|
96
|
-
const version = atIdx === -1 ? "" : spec.slice(atIdx + 1);
|
|
97
|
-
const segs = namepart.split("/").filter(Boolean);
|
|
98
|
-
if (segs.length !== 2) {
|
|
99
|
-
throw new CliError(`invalid spec "${spec}" \u2014 expected author/slug[@version] or github:owner/repo[/dir][@sha]`, {
|
|
100
|
-
code: "E_BAD_SPEC"
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
githubMode: false,
|
|
105
|
-
author: segs[0],
|
|
106
|
-
slug: segs[1],
|
|
107
|
-
...version ? { version } : {}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
async function resolveFromRegistry(parsed, registry, github, fetchImpl) {
|
|
111
|
-
const record = await registry.getWorkflow(parsed.author, parsed.slug, parsed.version);
|
|
112
|
-
const downloaded = await downloadFromRegistry(record, registry, github, fetchImpl);
|
|
113
|
-
return {
|
|
114
|
-
manifest: record.manifest,
|
|
115
|
-
author: record.author,
|
|
116
|
-
slug: record.slug,
|
|
117
|
-
version: record.version,
|
|
118
|
-
commitSha: record.commitSha,
|
|
119
|
-
files: downloaded.map((d) => ({ path: d.path, bytes: d.bytes, sha256: d.sha256 })),
|
|
120
|
-
githubMode: false,
|
|
121
|
-
source: `${record.author}/${record.slug}`,
|
|
122
|
-
registry
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
async function resolveFromGitHub(parsed, github) {
|
|
126
|
-
const { owner, repo } = parsed;
|
|
127
|
-
const dirPrefix = parsed.directory ? `${parsed.directory.replace(/\/+$/, "")}/` : "";
|
|
128
|
-
const sha = await github.resolveRef(owner, repo, parsed.ref ?? "HEAD");
|
|
129
|
-
const manifestBytes = await github.fetchRawFile(owner, repo, sha, `${dirPrefix}workflow.json`);
|
|
130
|
-
let manifest;
|
|
131
|
-
try {
|
|
132
|
-
const parsedManifest = parseManifest(JSON.parse(new TextDecoder().decode(manifestBytes)));
|
|
133
|
-
if (!parsedManifest.ok) {
|
|
134
|
-
throw new CliError(`workflow.json at ${owner}/${repo}@${sha.slice(0, 7)} is invalid`, {
|
|
135
|
-
code: "E_SCHEMA_INVALID",
|
|
136
|
-
details: parsedManifest.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
manifest = parsedManifest.manifest;
|
|
140
|
-
} catch (e) {
|
|
141
|
-
if (e instanceof CliError) throw e;
|
|
142
|
-
throw new CliError(`could not parse workflow.json from ${owner}/${repo}`, {
|
|
143
|
-
code: "E_SCHEMA_INVALID",
|
|
144
|
-
details: [e.message]
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
const files = [];
|
|
148
|
-
for (const rel of manifest.files) {
|
|
149
|
-
const bytes = await github.fetchRawFile(owner, repo, sha, `${dirPrefix}${rel}`);
|
|
150
|
-
files.push({ path: rel, bytes, sha256: sha256Hex(bytes) });
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
manifest,
|
|
154
|
-
author: `github:${owner}/${repo}`,
|
|
155
|
-
slug: manifest.slug,
|
|
156
|
-
version: manifest.version,
|
|
157
|
-
commitSha: sha,
|
|
158
|
-
files,
|
|
159
|
-
githubMode: true,
|
|
160
|
-
source: `github:${owner}/${repo}${parsed.directory ? `/${parsed.directory}` : ""}`
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
async function decideInstallDir(projectRoot, slug, files, opts) {
|
|
164
|
-
if (opts.dir) {
|
|
165
|
-
return { installDir: `${PAYLOAD_DIR}/${opts.dir.replace(/^[./]+/, "")}`, namespaced: true };
|
|
166
|
-
}
|
|
167
|
-
const flatPlan = planInstall(projectRoot, files, PAYLOAD_DIR);
|
|
168
|
-
if (!hasConflicts(flatPlan)) return { installDir: PAYLOAD_DIR, namespaced: false };
|
|
169
|
-
const namespacedDir = `${PAYLOAD_DIR}/${slug}`;
|
|
170
|
-
const interactive = !opts.yes && !isJsonMode() && process.stdin.isTTY;
|
|
171
|
-
if (!interactive) return { installDir: namespacedDir, namespaced: true };
|
|
172
|
-
const clack = await import("@clack/prompts");
|
|
173
|
-
const choice = await clack.select({
|
|
174
|
-
message: `Existing .sandcastle/ files would be overwritten. Install where?`,
|
|
175
|
-
options: [
|
|
176
|
-
{ value: "namespaced", label: `Namespaced \u2014 ${namespacedDir}/ (recommended)` },
|
|
177
|
-
{ value: "overwrite", label: `Overwrite files in ${PAYLOAD_DIR}/` }
|
|
178
|
-
],
|
|
179
|
-
initialValue: "namespaced"
|
|
180
|
-
});
|
|
181
|
-
if (clack.isCancel(choice)) throw new CliError("", { exitCode: 1, code: "E_CANCELLED" });
|
|
182
|
-
return choice === "overwrite" ? { installDir: PAYLOAD_DIR, namespaced: false } : { installDir: namespacedDir, namespaced: true };
|
|
183
|
-
}
|
|
184
|
-
async function addCommand(spec, opts) {
|
|
185
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
186
|
-
const fetchImpl = getFetch(opts.fetchImpl);
|
|
187
|
-
const github = opts.github ?? new RealGitHubClient({ fetchImpl });
|
|
188
|
-
const parsed = parseAddSpec(spec);
|
|
189
|
-
let resolved;
|
|
190
|
-
if (parsed.githubMode) {
|
|
191
|
-
resolved = await resolveFromGitHub(parsed, github);
|
|
192
|
-
} else {
|
|
193
|
-
const registry = new RegistryClient({ baseUrl: resolveRegistry(opts.registry), fetchImpl });
|
|
194
|
-
resolved = await resolveFromRegistry(parsed, registry, github, fetchImpl);
|
|
195
|
-
}
|
|
196
|
-
const { installDir } = await decideInstallDir(cwd, resolved.slug, resolved.files, opts);
|
|
197
|
-
const plan = planInstall(cwd, resolved.files, installDir);
|
|
198
|
-
const detected = await detectSandcastle(cwd);
|
|
199
|
-
const note = compatibilityNote(resolved.manifest.sandcastle.versionRange, detected);
|
|
200
|
-
const previewText = renderPreview({
|
|
201
|
-
manifest: resolved.manifest,
|
|
202
|
-
author: resolved.author,
|
|
203
|
-
version: resolved.version,
|
|
204
|
-
compatibilityNote: note,
|
|
205
|
-
filePlan: toPreviewPlan(plan),
|
|
206
|
-
installDir,
|
|
207
|
-
githubMode: resolved.githubMode,
|
|
208
|
-
colorize: pc.isColorSupported && !isJsonMode()
|
|
209
|
-
});
|
|
210
|
-
if (isJsonMode()) {
|
|
211
|
-
if (opts.dryRun || !opts.yes) {
|
|
212
|
-
emitJson({
|
|
213
|
-
ok: true,
|
|
214
|
-
action: "preview",
|
|
215
|
-
author: resolved.author,
|
|
216
|
-
slug: resolved.slug,
|
|
217
|
-
version: resolved.version,
|
|
218
|
-
commitSha: resolved.commitSha,
|
|
219
|
-
installDir,
|
|
220
|
-
githubMode: resolved.githubMode,
|
|
221
|
-
compatibilityNote: note ?? null,
|
|
222
|
-
disclosures: resolved.manifest.disclosures,
|
|
223
|
-
env: resolved.manifest.env,
|
|
224
|
-
files: plan.map((p) => ({ target: p.target, action: p.action, sha256: p.sha256 }))
|
|
225
|
-
});
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
log(previewText);
|
|
230
|
-
}
|
|
231
|
-
const interactive = !opts.yes && !isJsonMode() && process.stdin.isTTY;
|
|
232
|
-
if (opts.dryRun) {
|
|
233
|
-
if (!isJsonMode()) log(pc.dim("\n(dry run \u2014 nothing written)"));
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const conflicts = plan.filter((p) => p.action === "overwrite");
|
|
237
|
-
if (interactive && conflicts.length > 0) {
|
|
238
|
-
const clack = await import("@clack/prompts");
|
|
239
|
-
const showDiff = await clack.confirm({
|
|
240
|
-
message: `${conflicts.length} file(s) will be overwritten. Show diffs?`,
|
|
241
|
-
initialValue: false
|
|
242
|
-
});
|
|
243
|
-
if (!clack.isCancel(showDiff) && showDiff) {
|
|
244
|
-
for (const p of conflicts) {
|
|
245
|
-
log("");
|
|
246
|
-
log(renderDiff(cwd, p));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (interactive) {
|
|
251
|
-
const clack = await import("@clack/prompts");
|
|
252
|
-
const ok = await clack.confirm({ message: `Write ${plan.filter((p) => p.action !== "identical").length} file(s)?`, initialValue: true });
|
|
253
|
-
if (clack.isCancel(ok) || !ok) {
|
|
254
|
-
throw new CliError("aborted \u2014 nothing written", { exitCode: 1, code: "E_ABORTED" });
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
await writePlan(cwd, plan);
|
|
258
|
-
const filesRecord = {};
|
|
259
|
-
for (const p of plan) filesRecord[p.target] = p.sha256;
|
|
260
|
-
await writeLockEntry(cwd, {
|
|
261
|
-
source: resolved.source,
|
|
262
|
-
slug: resolved.slug,
|
|
263
|
-
...resolved.githubMode ? {} : { author: resolved.author },
|
|
264
|
-
version: resolved.version,
|
|
265
|
-
sha: resolved.commitSha,
|
|
266
|
-
...resolved.registry ? { registry: resolved.registry.baseUrl } : {},
|
|
267
|
-
dir: installDir,
|
|
268
|
-
files: filesRecord,
|
|
269
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
|
-
});
|
|
271
|
-
if (!isJsonMode()) {
|
|
272
|
-
const written = plan.filter((p) => p.action !== "identical").length;
|
|
273
|
-
log("");
|
|
274
|
-
log(pc.green(`\u2713 installed ${resolved.slug}@${resolved.version} (${written} file(s) into ${installDir}/)`));
|
|
275
|
-
log("");
|
|
276
|
-
log(pc.bold("Next steps:"));
|
|
277
|
-
for (const line of postInstallNotes(resolved.manifest, installDir)) log(` ${line}`);
|
|
278
|
-
} else {
|
|
279
|
-
emitJson({
|
|
280
|
-
ok: true,
|
|
281
|
-
action: "install",
|
|
282
|
-
slug: resolved.slug,
|
|
283
|
-
version: resolved.version,
|
|
284
|
-
installDir,
|
|
285
|
-
written: plan.filter((p) => p.action !== "identical").map((p) => p.target)
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
if (resolved.registry) {
|
|
289
|
-
const anonId = await getInstallId();
|
|
290
|
-
void resolved.registry.ping(resolved.author, resolved.slug, anonId).catch(() => {
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
export {
|
|
295
|
-
addCommand,
|
|
296
|
-
parseAddSpec
|
|
297
|
-
};
|
|
298
|
-
//# sourceMappingURL=add-PRGLHAVL.js.map
|
package/dist/add-PRGLHAVL.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/add.ts","../src/lib/install-id.ts"],"sourcesContent":["// `runcastle add <author/slug>[@version]` and `add github:owner/repo[/dir][@sha]`\n// (spec §5.4). The critical path. Order is exact: resolve → download → verify\n// checksums → preview → confirm → write → post-install → lockfile → ping.\n\nimport path from \"node:path\";\nimport {\n parseManifest,\n PAYLOAD_DIR,\n type Manifest,\n type WorkflowRecord,\n} from \"@castellan/schema\";\nimport { CliError } from \"../lib/errors.js\";\nimport { emitJson, isJsonMode, log, pc } from \"../lib/output.js\";\nimport { resolveRegistry } from \"../lib/config.js\";\nimport { RegistryClient } from \"../lib/registry.js\";\nimport { RealGitHubClient, type GitHubClient } from \"../lib/github.js\";\nimport { downloadFromRegistry } from \"../lib/download.js\";\nimport { sha256Hex } from \"../lib/checksum.js\";\nimport { getFetch, type FetchLike } from \"../lib/http.js\";\nimport { detectSandcastle, compatibilityNote } from \"../lib/sandcastle-version.js\";\nimport { renderPreview } from \"../lib/preview.js\";\nimport {\n planInstall,\n hasConflicts,\n toPreviewPlan,\n renderDiff,\n writePlan,\n postInstallNotes,\n type PlannedFile,\n} from \"../lib/installer.js\";\nimport { writeLockEntry } from \"../lib/lockfile.js\";\nimport { getInstallId } from \"../lib/install-id.js\";\n\nexport interface AddOptions {\n dir?: string;\n dryRun?: boolean;\n yes?: boolean;\n json?: boolean;\n registry?: string;\n /** Test seams. */\n fetchImpl?: FetchLike;\n github?: GitHubClient;\n cwd?: string;\n}\n\ninterface ResolvedInstall {\n manifest: Manifest;\n author: string;\n slug: string;\n version: string;\n commitSha: string;\n files: { path: string; bytes: Uint8Array; sha256: string }[];\n githubMode: boolean;\n source: string;\n registry?: RegistryClient;\n}\n\n// --- Spec parsing ------------------------------------------------------------\n\ninterface ParsedRef {\n githubMode: boolean;\n author?: string;\n slug?: string;\n version?: string;\n owner?: string;\n repo?: string;\n directory?: string;\n ref?: string;\n}\n\nexport function parseAddSpec(spec: string): ParsedRef {\n if (spec.startsWith(\"github:\")) {\n const rest = spec.slice(\"github:\".length);\n const atIdx = rest.indexOf(\"@\");\n const pathPart = atIdx === -1 ? rest : rest.slice(0, atIdx);\n const ref = atIdx === -1 ? \"\" : rest.slice(atIdx + 1);\n const segs = pathPart.split(\"/\").filter(Boolean);\n if (segs.length < 2) {\n throw new CliError(`invalid github: spec \"${spec}\" — expected github:owner/repo[/dir][@sha]`, {\n code: \"E_BAD_SPEC\",\n });\n }\n const owner = segs[0]!;\n const repo = segs[1]!;\n const dirSegs = segs.slice(2);\n return {\n githubMode: true,\n owner,\n repo,\n ...(dirSegs.length ? { directory: dirSegs.join(\"/\") } : {}),\n ...(ref ? { ref } : {}),\n };\n }\n const atIdx = spec.indexOf(\"@\");\n const namepart = atIdx === -1 ? spec : spec.slice(0, atIdx);\n const version = atIdx === -1 ? \"\" : spec.slice(atIdx + 1);\n const segs = namepart.split(\"/\").filter(Boolean);\n if (segs.length !== 2) {\n throw new CliError(`invalid spec \"${spec}\" — expected author/slug[@version] or github:owner/repo[/dir][@sha]`, {\n code: \"E_BAD_SPEC\",\n });\n }\n return {\n githubMode: false,\n author: segs[0]!,\n slug: segs[1]!,\n ...(version ? { version } : {}),\n };\n}\n\n// --- Resolution --------------------------------------------------------------\n\nasync function resolveFromRegistry(\n parsed: ParsedRef,\n registry: RegistryClient,\n github: GitHubClient,\n fetchImpl: FetchLike,\n): Promise<ResolvedInstall> {\n const record: WorkflowRecord = await registry.getWorkflow(parsed.author!, parsed.slug!, parsed.version);\n const downloaded = await downloadFromRegistry(record, registry, github, fetchImpl);\n return {\n manifest: record.manifest,\n author: record.author,\n slug: record.slug,\n version: record.version,\n commitSha: record.commitSha,\n files: downloaded.map((d) => ({ path: d.path, bytes: d.bytes, sha256: d.sha256 })),\n githubMode: false,\n source: `${record.author}/${record.slug}`,\n registry,\n };\n}\n\nasync function resolveFromGitHub(\n parsed: ParsedRef,\n github: GitHubClient,\n): Promise<ResolvedInstall> {\n const { owner, repo } = parsed;\n const dirPrefix = parsed.directory ? `${parsed.directory.replace(/\\/+$/, \"\")}/` : \"\";\n const sha = await github.resolveRef(owner!, repo!, parsed.ref ?? \"HEAD\");\n\n // Fetch and parse the manifest at the pinned SHA.\n const manifestBytes = await github.fetchRawFile(owner!, repo!, sha, `${dirPrefix}workflow.json`);\n let manifest: Manifest;\n try {\n const parsedManifest = parseManifest(JSON.parse(new TextDecoder().decode(manifestBytes)));\n if (!parsedManifest.ok) {\n throw new CliError(`workflow.json at ${owner}/${repo}@${sha.slice(0, 7)} is invalid`, {\n code: \"E_SCHEMA_INVALID\",\n details: parsedManifest.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n });\n }\n manifest = parsedManifest.manifest;\n } catch (e) {\n if (e instanceof CliError) throw e;\n throw new CliError(`could not parse workflow.json from ${owner}/${repo}`, {\n code: \"E_SCHEMA_INVALID\",\n details: [(e as Error).message],\n });\n }\n\n // Fetch every listed file and compute checksums CLIENT-SIDE (M1: no tamper\n // evidence — surfaced in the preview).\n const files: ResolvedInstall[\"files\"] = [];\n for (const rel of manifest.files) {\n const bytes = await github.fetchRawFile(owner!, repo!, sha, `${dirPrefix}${rel}`);\n files.push({ path: rel, bytes, sha256: sha256Hex(bytes) });\n }\n\n return {\n manifest,\n author: `github:${owner}/${repo}`,\n slug: manifest.slug,\n version: manifest.version,\n commitSha: sha,\n files,\n githubMode: true,\n source: `github:${owner}/${repo}${parsed.directory ? `/${parsed.directory}` : \"\"}`,\n };\n}\n\n// --- Install-dir decision ----------------------------------------------------\n\nasync function decideInstallDir(\n projectRoot: string,\n slug: string,\n files: ResolvedInstall[\"files\"],\n opts: AddOptions,\n): Promise<{ installDir: string; namespaced: boolean }> {\n if (opts.dir) {\n return { installDir: `${PAYLOAD_DIR}/${opts.dir.replace(/^[./]+/, \"\")}`, namespaced: true };\n }\n // Default target: .sandcastle. Detect conflicts against existing files.\n const flatPlan = planInstall(projectRoot, files, PAYLOAD_DIR);\n if (!hasConflicts(flatPlan)) return { installDir: PAYLOAD_DIR, namespaced: false };\n\n // Conflict: default to a namespaced install (spec §11.4). Offer the choice\n // interactively; non-interactive falls back to the safe (namespaced) default.\n const namespacedDir = `${PAYLOAD_DIR}/${slug}`;\n const interactive = !opts.yes && !isJsonMode() && process.stdin.isTTY;\n if (!interactive) return { installDir: namespacedDir, namespaced: true };\n\n const clack = await import(\"@clack/prompts\");\n const choice = await clack.select({\n message: `Existing .sandcastle/ files would be overwritten. Install where?`,\n options: [\n { value: \"namespaced\", label: `Namespaced — ${namespacedDir}/ (recommended)` },\n { value: \"overwrite\", label: `Overwrite files in ${PAYLOAD_DIR}/` },\n ],\n initialValue: \"namespaced\",\n });\n if (clack.isCancel(choice)) throw new CliError(\"\", { exitCode: 1, code: \"E_CANCELLED\" });\n return choice === \"overwrite\"\n ? { installDir: PAYLOAD_DIR, namespaced: false }\n : { installDir: namespacedDir, namespaced: true };\n}\n\n// --- Command -----------------------------------------------------------------\n\nexport async function addCommand(spec: string, opts: AddOptions): Promise<void> {\n const cwd = opts.cwd ?? process.cwd();\n const fetchImpl = getFetch(opts.fetchImpl);\n const github = opts.github ?? new RealGitHubClient({ fetchImpl });\n const parsed = parseAddSpec(spec);\n\n // 1–2. Resolve + download + verify.\n let resolved: ResolvedInstall;\n if (parsed.githubMode) {\n resolved = await resolveFromGitHub(parsed, github);\n } else {\n const registry = new RegistryClient({ baseUrl: resolveRegistry(opts.registry), fetchImpl });\n resolved = await resolveFromRegistry(parsed, registry, github, fetchImpl);\n }\n\n // Install-dir decision (conflict → namespaced default).\n const { installDir } = await decideInstallDir(cwd, resolved.slug, resolved.files, opts);\n const plan = planInstall(cwd, resolved.files, installDir);\n\n // 3. Preview.\n const detected = await detectSandcastle(cwd);\n const note = compatibilityNote(resolved.manifest.sandcastle.versionRange, detected);\n const previewText = renderPreview({\n manifest: resolved.manifest,\n author: resolved.author,\n version: resolved.version,\n compatibilityNote: note,\n filePlan: toPreviewPlan(plan),\n installDir,\n githubMode: resolved.githubMode,\n colorize: pc.isColorSupported && !isJsonMode(),\n });\n\n if (isJsonMode()) {\n // In --json mode we only write when explicitly told to (--yes) and not\n // dry-run. When we're not writing, emit the preview plan and stop; when we\n // are, stay silent here — a single install result is emitted after writing.\n if (opts.dryRun || !opts.yes) {\n emitJson({\n ok: true,\n action: \"preview\",\n author: resolved.author,\n slug: resolved.slug,\n version: resolved.version,\n commitSha: resolved.commitSha,\n installDir,\n githubMode: resolved.githubMode,\n compatibilityNote: note ?? null,\n disclosures: resolved.manifest.disclosures,\n env: resolved.manifest.env,\n files: plan.map((p) => ({ target: p.target, action: p.action, sha256: p.sha256 })),\n });\n return;\n }\n } else {\n log(previewText);\n }\n\n // 3e. Diffs on conflict (interactive, non-json).\n const interactive = !opts.yes && !isJsonMode() && process.stdin.isTTY;\n if (opts.dryRun) {\n if (!isJsonMode()) log(pc.dim(\"\\n(dry run — nothing written)\"));\n return;\n }\n\n const conflicts = plan.filter((p) => p.action === \"overwrite\");\n if (interactive && conflicts.length > 0) {\n const clack = await import(\"@clack/prompts\");\n const showDiff = await clack.confirm({\n message: `${conflicts.length} file(s) will be overwritten. Show diffs?`,\n initialValue: false,\n });\n if (!clack.isCancel(showDiff) && showDiff) {\n for (const p of conflicts) {\n log(\"\");\n log(renderDiff(cwd, p));\n }\n }\n }\n\n // 3/4. Confirm + write.\n if (interactive) {\n const clack = await import(\"@clack/prompts\");\n const ok = await clack.confirm({ message: `Write ${plan.filter((p) => p.action !== \"identical\").length} file(s)?`, initialValue: true });\n if (clack.isCancel(ok) || !ok) {\n throw new CliError(\"aborted — nothing written\", { exitCode: 1, code: \"E_ABORTED\" });\n }\n }\n\n await writePlan(cwd, plan);\n\n // 6. Lockfile.\n const filesRecord: Record<string, string> = {};\n for (const p of plan) filesRecord[p.target] = p.sha256;\n await writeLockEntry(cwd, {\n source: resolved.source,\n slug: resolved.slug,\n ...(resolved.githubMode ? {} : { author: resolved.author }),\n version: resolved.version,\n sha: resolved.commitSha,\n ...(resolved.registry ? { registry: resolved.registry.baseUrl } : {}),\n dir: installDir,\n files: filesRecord,\n installedAt: new Date().toISOString(),\n });\n\n // 5. Post-install output.\n if (!isJsonMode()) {\n const written = plan.filter((p) => p.action !== \"identical\").length;\n log(\"\");\n log(pc.green(`✓ installed ${resolved.slug}@${resolved.version} (${written} file(s) into ${installDir}/)`));\n log(\"\");\n log(pc.bold(\"Next steps:\"));\n for (const line of postInstallNotes(resolved.manifest, installDir)) log(` ${line}`);\n } else {\n emitJson({\n ok: true,\n action: \"install\",\n slug: resolved.slug,\n version: resolved.version,\n installDir,\n written: plan.filter((p) => p.action !== \"identical\").map((p) => p.target),\n });\n }\n\n // 6. Download ping (registry only, fire-and-forget).\n if (resolved.registry) {\n const anonId = await getInstallId();\n void resolved.registry.ping(resolved.author, resolved.slug, anonId).catch(() => {});\n }\n}\n","// A persistent random anonymous install id for download pings (spec §5.4.6 —\n// \"no PII beyond a random anonymous install id\"; dedupe by id per day). Stored\n// alongside the auth token; generated once on first use.\n\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { configDir } from \"./config.js\";\n\nfunction idPath(): string {\n return path.join(configDir(), \"install-id\");\n}\n\nexport async function getInstallId(): Promise<string> {\n const file = idPath();\n try {\n const existing = (await fsp.readFile(file, \"utf8\")).trim();\n if (existing) return existing;\n } catch {\n /* generate below */\n }\n const id = randomUUID();\n try {\n await fsp.mkdir(configDir(), { recursive: true });\n await fsp.writeFile(file, id, \"utf8\");\n } catch {\n /* non-fatal — a transient id is fine for a ping */\n }\n return id;\n}\n"],"mappings":"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","names":["atIdx","segs"]}
|
package/dist/chunk-44KDPUYJ.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
authFilePath,
|
|
3
|
-
configDir
|
|
4
|
-
} from "./chunk-LP5MV76P.js";
|
|
5
|
-
|
|
6
|
-
// src/lib/auth.ts
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import fsp from "fs/promises";
|
|
9
|
-
import path from "path";
|
|
10
|
-
async function saveAuth(record) {
|
|
11
|
-
const dir = configDir();
|
|
12
|
-
await fsp.mkdir(dir, { recursive: true });
|
|
13
|
-
const file = authFilePath();
|
|
14
|
-
const data = JSON.stringify(record, null, 2);
|
|
15
|
-
await fsp.writeFile(file, data, { encoding: "utf8", mode: 384 });
|
|
16
|
-
if (process.platform !== "win32") {
|
|
17
|
-
await fsp.chmod(file, 384).catch(() => {
|
|
18
|
-
});
|
|
19
|
-
await fsp.chmod(dir, 448).catch(() => {
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function loadAuth() {
|
|
24
|
-
try {
|
|
25
|
-
const raw = await fsp.readFile(authFilePath(), "utf8");
|
|
26
|
-
const parsed = JSON.parse(raw);
|
|
27
|
-
if (parsed && typeof parsed.token === "string" && parsed.token) return parsed;
|
|
28
|
-
return null;
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async function clearAuth() {
|
|
34
|
-
const file = authFilePath();
|
|
35
|
-
if (!fs.existsSync(file)) return false;
|
|
36
|
-
await fsp.rm(file, { force: true });
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
function authPathForDisplay() {
|
|
40
|
-
return path.normalize(authFilePath());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export {
|
|
44
|
-
saveAuth,
|
|
45
|
-
loadAuth,
|
|
46
|
-
clearAuth,
|
|
47
|
-
authPathForDisplay
|
|
48
|
-
};
|
|
49
|
-
//# sourceMappingURL=chunk-44KDPUYJ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/auth.ts"],"sourcesContent":["// Auth token store (spec §5.3). Cached at ~/.config/castellan/auth.json with\n// 0600 perms on POSIX; on Windows the file is written without chmod (best-effort\n// per spec). `logout` deletes it.\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { authFilePath, configDir } from \"./config.js\";\n\nexport interface AuthRecord {\n token: string;\n login?: string;\n savedAt: string;\n}\n\nexport async function saveAuth(record: AuthRecord): Promise<void> {\n const dir = configDir();\n await fsp.mkdir(dir, { recursive: true });\n const file = authFilePath();\n const data = JSON.stringify(record, null, 2);\n // Write then tighten perms (POSIX). On Windows chmod is a no-op / ignored.\n await fsp.writeFile(file, data, { encoding: \"utf8\", mode: 0o600 });\n if (process.platform !== \"win32\") {\n await fsp.chmod(file, 0o600).catch(() => {});\n await fsp.chmod(dir, 0o700).catch(() => {});\n }\n}\n\nexport async function loadAuth(): Promise<AuthRecord | null> {\n try {\n const raw = await fsp.readFile(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as AuthRecord;\n if (parsed && typeof parsed.token === \"string\" && parsed.token) return parsed;\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function clearAuth(): Promise<boolean> {\n const file = authFilePath();\n if (!fs.existsSync(file)) return false;\n await fsp.rm(file, { force: true });\n return true;\n}\n\nexport function authPathForDisplay(): string {\n return path.normalize(authFilePath());\n}\n"],"mappings":";;;;;;AAIA,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAO,UAAU;AASjB,eAAsB,SAAS,QAAmC;AAChE,QAAM,MAAM,UAAU;AACtB,QAAM,IAAI,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACxC,QAAM,OAAO,aAAa;AAC1B,QAAM,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AAE3C,QAAM,IAAI,UAAU,MAAM,MAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACjE,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,IAAI,MAAM,MAAM,GAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC3C,UAAM,IAAI,MAAM,KAAK,GAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC5C;AACF;AAEA,eAAsB,WAAuC;AAC3D,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,SAAS,aAAa,GAAG,MAAM;AACrD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,UAAU,OAAO,OAAO,UAAU,YAAY,OAAO,MAAO,QAAO;AACvE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,YAA8B;AAClD,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,GAAG,WAAW,IAAI,EAAG,QAAO;AACjC,QAAM,IAAI,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAClC,SAAO;AACT;AAEO,SAAS,qBAA6B;AAC3C,SAAO,KAAK,UAAU,aAAa,CAAC;AACtC;","names":[]}
|
package/dist/chunk-5VPKMXSV.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
USER_AGENT
|
|
3
|
-
} from "./chunk-LP5MV76P.js";
|
|
4
|
-
import {
|
|
5
|
-
BrowseResponseSchema,
|
|
6
|
-
ErrorEnvelopeSchema,
|
|
7
|
-
WorkflowRecordSchema
|
|
8
|
-
} from "./chunk-AHRTPV2C.js";
|
|
9
|
-
import {
|
|
10
|
-
CliError
|
|
11
|
-
} from "./chunk-6CWCR5SO.js";
|
|
12
|
-
|
|
13
|
-
// src/lib/http.ts
|
|
14
|
-
function getFetch(inject) {
|
|
15
|
-
const f = inject ?? globalThis.fetch;
|
|
16
|
-
if (!f) {
|
|
17
|
-
throw new CliError(
|
|
18
|
-
"global fetch is unavailable \u2014 Node 20+ is required",
|
|
19
|
-
{ code: "E_NO_FETCH" }
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
return f;
|
|
23
|
-
}
|
|
24
|
-
async function doFetch(fetchImpl, url, init) {
|
|
25
|
-
try {
|
|
26
|
-
return await fetchImpl(url, init);
|
|
27
|
-
} catch (e) {
|
|
28
|
-
throw new CliError(`network request failed: ${url}`, {
|
|
29
|
-
code: "E_NETWORK",
|
|
30
|
-
details: [e.message]
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/lib/registry.ts
|
|
36
|
-
var RegistryClient = class {
|
|
37
|
-
baseUrl;
|
|
38
|
-
fetchImpl;
|
|
39
|
-
constructor(opts) {
|
|
40
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
41
|
-
this.fetchImpl = getFetch(opts.fetchImpl);
|
|
42
|
-
}
|
|
43
|
-
url(pathname, params) {
|
|
44
|
-
const u = new URL(this.baseUrl + pathname);
|
|
45
|
-
if (params) {
|
|
46
|
-
for (const [k, v] of Object.entries(params)) {
|
|
47
|
-
if (v !== void 0 && v !== "") u.searchParams.set(k, v);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return u.toString();
|
|
51
|
-
}
|
|
52
|
-
/** GET /api/workflows/:author/:slug?version= — resolved workflow record. */
|
|
53
|
-
async getWorkflow(author, slug, version) {
|
|
54
|
-
const url = this.url(
|
|
55
|
-
`/api/workflows/${encodeURIComponent(author)}/${encodeURIComponent(slug)}`,
|
|
56
|
-
version ? { version } : void 0
|
|
57
|
-
);
|
|
58
|
-
const res = await doFetch(this.fetchImpl, url, {
|
|
59
|
-
headers: { accept: "application/json", "user-agent": USER_AGENT }
|
|
60
|
-
});
|
|
61
|
-
if (!res.ok) throw await this.httpError(res, url);
|
|
62
|
-
const json = await this.json(res, url);
|
|
63
|
-
const parsed = WorkflowRecordSchema.safeParse(json);
|
|
64
|
-
if (!parsed.success) {
|
|
65
|
-
throw new CliError(`registry returned an unexpected workflow record: ${url}`, {
|
|
66
|
-
code: "E_BAD_RESPONSE",
|
|
67
|
-
details: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
return parsed.data;
|
|
71
|
-
}
|
|
72
|
-
/** GET /api/workflows?q=&... — browse/search cards. */
|
|
73
|
-
async search(query, opts = {}) {
|
|
74
|
-
const url = this.url("/api/workflows", {
|
|
75
|
-
q: query || void 0,
|
|
76
|
-
sort: opts.sort,
|
|
77
|
-
page: opts.page ? String(opts.page) : void 0,
|
|
78
|
-
tags: opts.tags,
|
|
79
|
-
agentProvider: opts.agentProvider,
|
|
80
|
-
sandboxProvider: opts.sandboxProvider
|
|
81
|
-
});
|
|
82
|
-
const res = await doFetch(this.fetchImpl, url, {
|
|
83
|
-
headers: { accept: "application/json", "user-agent": USER_AGENT }
|
|
84
|
-
});
|
|
85
|
-
if (!res.ok) throw await this.httpError(res, url);
|
|
86
|
-
const json = await this.json(res, url);
|
|
87
|
-
const parsed = BrowseResponseSchema.safeParse(json);
|
|
88
|
-
if (!parsed.success) {
|
|
89
|
-
throw new CliError(`registry returned an unexpected browse response: ${url}`, {
|
|
90
|
-
code: "E_BAD_RESPONSE",
|
|
91
|
-
details: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return parsed.data;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* URL of the registry's cached file endpoint (spec §6 — the CLI's primary
|
|
98
|
-
* download source): GET /api/workflows/:author/:slug/files/:version/*path
|
|
99
|
-
*/
|
|
100
|
-
fileUrl(author, slug, version, filePath) {
|
|
101
|
-
const segs = filePath.split("/").map(encodeURIComponent).join("/");
|
|
102
|
-
return `${this.baseUrl}/api/workflows/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/files/${encodeURIComponent(version)}/${segs}`;
|
|
103
|
-
}
|
|
104
|
-
/** POST /api/workflows — publish (auth: GitHub OAuth bearer token). */
|
|
105
|
-
async publish(body, token) {
|
|
106
|
-
const url = this.url("/api/workflows");
|
|
107
|
-
const res = await doFetch(this.fetchImpl, url, {
|
|
108
|
-
method: "POST",
|
|
109
|
-
headers: {
|
|
110
|
-
"content-type": "application/json",
|
|
111
|
-
accept: "application/json",
|
|
112
|
-
authorization: `Bearer ${token}`,
|
|
113
|
-
"user-agent": USER_AGENT
|
|
114
|
-
},
|
|
115
|
-
body: JSON.stringify(body)
|
|
116
|
-
});
|
|
117
|
-
const json = await res.json().catch(() => void 0);
|
|
118
|
-
if (res.status === 409) {
|
|
119
|
-
throw new CliError(
|
|
120
|
-
`version ${body.manifest.version} of ${body.manifest.slug} is already published (versions are immutable)`,
|
|
121
|
-
{ code: "E_DUPLICATE_VERSION" }
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
if (!res.ok) {
|
|
125
|
-
const env = ErrorEnvelopeSchema.safeParse(json);
|
|
126
|
-
const detail = env.success ? `${env.data.error.code}: ${env.data.error.message}` : `HTTP ${res.status}`;
|
|
127
|
-
throw new CliError(`publish failed: ${url}`, {
|
|
128
|
-
code: "E_PUBLISH_FAILED",
|
|
129
|
-
details: [detail]
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return { status: res.status, body: json };
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* POST /api/workflows/:author/:slug/downloads — fire-and-forget install ping.
|
|
136
|
-
* Never throws; a failed ping must not affect a successful install (spec
|
|
137
|
-
* §5.4.6). Author and slug are separate path segments (encoding "author/slug"
|
|
138
|
-
* as one component collapses the "/" to %2F and the two-segment route 405s).
|
|
139
|
-
*/
|
|
140
|
-
async ping(author, slug, anonInstallId) {
|
|
141
|
-
const url = this.url(
|
|
142
|
-
`/api/workflows/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/downloads`
|
|
143
|
-
);
|
|
144
|
-
try {
|
|
145
|
-
await this.fetchImpl(url, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
148
|
-
body: JSON.stringify({ anonInstallId })
|
|
149
|
-
});
|
|
150
|
-
} catch {
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
async json(res, url) {
|
|
154
|
-
try {
|
|
155
|
-
return await res.json();
|
|
156
|
-
} catch {
|
|
157
|
-
throw new CliError(`registry returned invalid JSON: ${url}`, {
|
|
158
|
-
code: "E_BAD_RESPONSE"
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
async httpError(res, url) {
|
|
163
|
-
const json = await res.json().catch(() => void 0);
|
|
164
|
-
const env = ErrorEnvelopeSchema.safeParse(json);
|
|
165
|
-
if (res.status === 404) {
|
|
166
|
-
return new CliError(`not found: ${url}`, {
|
|
167
|
-
code: "E_NOT_FOUND",
|
|
168
|
-
details: env.success ? [env.data.error.message] : []
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
if (res.status === 410) {
|
|
172
|
-
return new CliError(`this workflow version has been withdrawn (410): ${url}`, {
|
|
173
|
-
code: "E_GONE",
|
|
174
|
-
details: env.success ? [env.data.error.message] : []
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
const detail = env.success ? `${env.data.error.code}: ${env.data.error.message}` : `HTTP ${res.status} ${res.statusText}`;
|
|
178
|
-
return new CliError(`registry request failed: ${url}`, {
|
|
179
|
-
code: "E_HTTP",
|
|
180
|
-
details: [detail]
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
export {
|
|
186
|
-
getFetch,
|
|
187
|
-
doFetch,
|
|
188
|
-
RegistryClient
|
|
189
|
-
};
|
|
190
|
-
//# sourceMappingURL=chunk-5VPKMXSV.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/http.ts","../src/lib/registry.ts"],"sourcesContent":["// Thin fetch wrapper. Every network failure must be actionable and include the\n// URL tried (spec §5.7). Injectable fetch keeps clients testable without a\n// global mock, though msw intercepts the real global fetch in e2e tests.\n\nimport { CliError } from \"./errors.js\";\n\nexport type FetchLike = typeof fetch;\n\nexport function getFetch(inject?: FetchLike): FetchLike {\n const f = inject ?? globalThis.fetch;\n if (!f) {\n throw new CliError(\n \"global fetch is unavailable — Node 20+ is required\",\n { code: \"E_NO_FETCH\" },\n );\n }\n return f;\n}\n\n/** Perform a fetch, converting network-layer failures into a CliError with the URL. */\nexport async function doFetch(\n fetchImpl: FetchLike,\n url: string,\n init?: RequestInit,\n): Promise<Response> {\n try {\n return await fetchImpl(url, init);\n } catch (e) {\n throw new CliError(`network request failed: ${url}`, {\n code: \"E_NETWORK\",\n details: [(e as Error).message],\n });\n }\n}\n","// Registry API client (spec §6). Built from the frozen zod schemas in\n// @castellan/schema so request/response shapes are never restated. Every error\n// includes the URL tried and, when present, the server's error envelope.\n\nimport {\n BrowseResponseSchema,\n ErrorEnvelopeSchema,\n WorkflowRecordSchema,\n type BrowseResponse,\n type PublishRequest,\n type WorkflowRecord,\n} from \"@castellan/schema\";\nimport { CliError } from \"./errors.js\";\nimport { doFetch, getFetch, type FetchLike } from \"./http.js\";\nimport { USER_AGENT } from \"./config.js\";\n\nexport interface RegistryClientOptions {\n baseUrl: string;\n fetchImpl?: FetchLike;\n}\n\nexport class RegistryClient {\n readonly baseUrl: string;\n private readonly fetchImpl: FetchLike;\n\n constructor(opts: RegistryClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.fetchImpl = getFetch(opts.fetchImpl);\n }\n\n private url(pathname: string, params?: Record<string, string | undefined>): string {\n const u = new URL(this.baseUrl + pathname);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") u.searchParams.set(k, v);\n }\n }\n return u.toString();\n }\n\n /** GET /api/workflows/:author/:slug?version= — resolved workflow record. */\n async getWorkflow(\n author: string,\n slug: string,\n version?: string,\n ): Promise<WorkflowRecord> {\n const url = this.url(\n `/api/workflows/${encodeURIComponent(author)}/${encodeURIComponent(slug)}`,\n version ? { version } : undefined,\n );\n const res = await doFetch(this.fetchImpl, url, {\n headers: { accept: \"application/json\", \"user-agent\": USER_AGENT },\n });\n if (!res.ok) throw await this.httpError(res, url);\n const json = await this.json(res, url);\n const parsed = WorkflowRecordSchema.safeParse(json);\n if (!parsed.success) {\n throw new CliError(`registry returned an unexpected workflow record: ${url}`, {\n code: \"E_BAD_RESPONSE\",\n details: parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n });\n }\n return parsed.data;\n }\n\n /** GET /api/workflows?q=&... — browse/search cards. */\n async search(\n query: string,\n opts: { sort?: string; page?: number; tags?: string; agentProvider?: string; sandboxProvider?: string } = {},\n ): Promise<BrowseResponse> {\n const url = this.url(\"/api/workflows\", {\n q: query || undefined,\n sort: opts.sort,\n page: opts.page ? String(opts.page) : undefined,\n tags: opts.tags,\n agentProvider: opts.agentProvider,\n sandboxProvider: opts.sandboxProvider,\n });\n const res = await doFetch(this.fetchImpl, url, {\n headers: { accept: \"application/json\", \"user-agent\": USER_AGENT },\n });\n if (!res.ok) throw await this.httpError(res, url);\n const json = await this.json(res, url);\n const parsed = BrowseResponseSchema.safeParse(json);\n if (!parsed.success) {\n throw new CliError(`registry returned an unexpected browse response: ${url}`, {\n code: \"E_BAD_RESPONSE\",\n details: parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n });\n }\n return parsed.data;\n }\n\n /**\n * URL of the registry's cached file endpoint (spec §6 — the CLI's primary\n * download source): GET /api/workflows/:author/:slug/files/:version/*path\n */\n fileUrl(author: string, slug: string, version: string, filePath: string): string {\n const segs = filePath.split(\"/\").map(encodeURIComponent).join(\"/\");\n return (\n `${this.baseUrl}/api/workflows/${encodeURIComponent(author)}/` +\n `${encodeURIComponent(slug)}/files/${encodeURIComponent(version)}/${segs}`\n );\n }\n\n /** POST /api/workflows — publish (auth: GitHub OAuth bearer token). */\n async publish(\n body: PublishRequest,\n token: string,\n ): Promise<{ status: number; body: unknown }> {\n const url = this.url(\"/api/workflows\");\n const res = await doFetch(this.fetchImpl, url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${token}`,\n \"user-agent\": USER_AGENT,\n },\n body: JSON.stringify(body),\n });\n const json = await res.json().catch(() => undefined);\n if (res.status === 409) {\n throw new CliError(\n `version ${body.manifest.version} of ${body.manifest.slug} is already published (versions are immutable)`,\n { code: \"E_DUPLICATE_VERSION\" },\n );\n }\n if (!res.ok) {\n const env = ErrorEnvelopeSchema.safeParse(json);\n const detail = env.success ? `${env.data.error.code}: ${env.data.error.message}` : `HTTP ${res.status}`;\n throw new CliError(`publish failed: ${url}`, {\n code: \"E_PUBLISH_FAILED\",\n details: [detail],\n });\n }\n return { status: res.status, body: json };\n }\n\n /**\n * POST /api/workflows/:author/:slug/downloads — fire-and-forget install ping.\n * Never throws; a failed ping must not affect a successful install (spec\n * §5.4.6). Author and slug are separate path segments (encoding \"author/slug\"\n * as one component collapses the \"/\" to %2F and the two-segment route 405s).\n */\n async ping(author: string, slug: string, anonInstallId: string): Promise<void> {\n const url = this.url(\n `/api/workflows/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/downloads`,\n );\n try {\n await this.fetchImpl(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"user-agent\": USER_AGENT },\n body: JSON.stringify({ anonInstallId }),\n });\n } catch {\n /* fire-and-forget */\n }\n }\n\n private async json(res: Response, url: string): Promise<unknown> {\n try {\n return await res.json();\n } catch {\n throw new CliError(`registry returned invalid JSON: ${url}`, {\n code: \"E_BAD_RESPONSE\",\n });\n }\n }\n\n private async httpError(res: Response, url: string): Promise<CliError> {\n const json = await res.json().catch(() => undefined);\n const env = ErrorEnvelopeSchema.safeParse(json);\n if (res.status === 404) {\n return new CliError(`not found: ${url}`, {\n code: \"E_NOT_FOUND\",\n details: env.success ? [env.data.error.message] : [],\n });\n }\n if (res.status === 410) {\n return new CliError(`this workflow version has been withdrawn (410): ${url}`, {\n code: \"E_GONE\",\n details: env.success ? [env.data.error.message] : [],\n });\n }\n const detail = env.success\n ? `${env.data.error.code}: ${env.data.error.message}`\n : `HTTP ${res.status} ${res.statusText}`;\n return new CliError(`registry request failed: ${url}`, {\n code: \"E_HTTP\",\n details: [detail],\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-6CWCR5SO.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// src/lib/errors.ts
|
|
2
|
-
var CliError = class extends Error {
|
|
3
|
-
exitCode;
|
|
4
|
-
/** Optional stable machine code (e.g. a validation code or "E_CHECKSUM"). */
|
|
5
|
-
code;
|
|
6
|
-
/** Extra lines printed under the message (hints, URLs tried). */
|
|
7
|
-
details;
|
|
8
|
-
constructor(message, opts = {}) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "CliError";
|
|
11
|
-
this.exitCode = opts.exitCode ?? 1;
|
|
12
|
-
this.code = opts.code;
|
|
13
|
-
this.details = opts.details ?? [];
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
function isCliError(e) {
|
|
17
|
-
return e instanceof CliError;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export {
|
|
21
|
-
CliError,
|
|
22
|
-
isCliError
|
|
23
|
-
};
|
|
24
|
-
//# sourceMappingURL=chunk-6CWCR5SO.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/errors.ts"],"sourcesContent":["// A typed error the top-level handler renders cleanly (no stack) and maps to a\n// process exit code. Everything the user should see as an actionable failure is\n// thrown as a CliError.\n\nexport class CliError extends Error {\n readonly exitCode: number;\n /** Optional stable machine code (e.g. a validation code or \"E_CHECKSUM\"). */\n readonly code: string | undefined;\n /** Extra lines printed under the message (hints, URLs tried). */\n readonly details: string[];\n\n constructor(\n message: string,\n opts: { exitCode?: number; code?: string; details?: string[] } = {},\n ) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = opts.exitCode ?? 1;\n this.code = opts.code;\n this.details = opts.details ?? [];\n }\n}\n\n/** True for our own actionable errors (rendered without a stack trace). */\nexport function isCliError(e: unknown): e is CliError {\n return e instanceof CliError;\n}\n"],"mappings":";AAIO,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YACE,SACA,OAAiE,CAAC,GAClE;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW,KAAK,YAAY;AACjC,SAAK,OAAO,KAAK;AACjB,SAAK,UAAU,KAAK,WAAW,CAAC;AAAA,EAClC;AACF;AAGO,SAAS,WAAW,GAA2B;AACpD,SAAO,aAAa;AACtB;","names":[]}
|