stageflow 0.2.0 → 0.8.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.
Files changed (224) hide show
  1. package/README.md +179 -14
  2. package/dist/agent/activity.d.ts +12 -1
  3. package/dist/agent/activity.js +37 -2
  4. package/dist/agent/activityObserver.d.ts +5 -1
  5. package/dist/agent/activityObserver.js +174 -11
  6. package/dist/agent/cursorProvider.d.ts +1 -10
  7. package/dist/agent/cursorProvider.js +18 -10
  8. package/dist/agent/fakeAgent.js +15 -7
  9. package/dist/agent/piAdapter.d.ts +18 -1
  10. package/dist/agent/piAdapter.js +115 -45
  11. package/dist/agent/port.d.ts +7 -0
  12. package/dist/agent/port.js +3 -0
  13. package/dist/agent/providerAuth.js +12 -7
  14. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  15. package/dist/catalog/displayCatalogPath.js +28 -0
  16. package/dist/cli/artifactCommand.d.ts +10 -0
  17. package/dist/cli/artifactCommand.js +120 -0
  18. package/dist/cli/envelopeCommand.d.ts +32 -0
  19. package/dist/cli/envelopeCommand.js +285 -0
  20. package/dist/cli/exportRunCommand.d.ts +10 -0
  21. package/dist/cli/exportRunCommand.js +150 -0
  22. package/dist/cli/handoffFormat.d.ts +22 -0
  23. package/dist/cli/handoffFormat.js +61 -0
  24. package/dist/cli/initCommand.d.ts +9 -0
  25. package/dist/cli/initCommand.js +71 -0
  26. package/dist/cli/initTemplates.d.ts +3 -0
  27. package/dist/cli/initTemplates.js +19 -0
  28. package/dist/cli/operatorCatalog.d.ts +10 -0
  29. package/dist/cli/operatorCatalog.js +16 -0
  30. package/dist/cli/runCommand.d.ts +7 -1
  31. package/dist/cli/runCommand.js +83 -11
  32. package/dist/cli/runOutput.d.ts +4 -1
  33. package/dist/cli/runOutput.js +36 -8
  34. package/dist/cli/skillsCommand.d.ts +16 -0
  35. package/dist/cli/skillsCommand.js +459 -0
  36. package/dist/cli/validateCommand.d.ts +1 -1
  37. package/dist/cli/validateCommand.js +20 -3
  38. package/dist/cli.d.ts +1 -0
  39. package/dist/cli.js +128 -22
  40. package/dist/config/browseCatalog.d.ts +45 -0
  41. package/dist/config/browseCatalog.js +170 -0
  42. package/dist/config/createPipeline.d.ts +12 -3
  43. package/dist/config/createPipeline.js +292 -100
  44. package/dist/config/createStage.d.ts +10 -4
  45. package/dist/config/createStage.js +44 -14
  46. package/dist/config/listConfig.d.ts +14 -19
  47. package/dist/config/listConfig.js +26 -191
  48. package/dist/config/listModelsFromManifest.d.ts +2 -0
  49. package/dist/config/listModelsFromManifest.js +4 -0
  50. package/dist/config/loadOutcome.d.ts +4 -0
  51. package/dist/config/loadPipeline.d.ts +1 -15
  52. package/dist/config/loadPipeline.js +88 -142
  53. package/dist/config/loadStage.d.ts +4 -0
  54. package/dist/config/loadStage.js +60 -32
  55. package/dist/config/loadStageflowManifest.d.ts +5 -0
  56. package/dist/config/loadStageflowManifest.js +157 -0
  57. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  58. package/dist/config/mergePipelineIncludes.js +141 -0
  59. package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
  60. package/dist/config/normalizePipelineStageEntry.js +210 -0
  61. package/dist/config/pipelineStageKeys.d.ts +4 -0
  62. package/dist/config/pipelineStageKeys.js +14 -0
  63. package/dist/config/resolveCatalogContext.d.ts +13 -0
  64. package/dist/config/resolveCatalogContext.js +13 -0
  65. package/dist/config/resolveForkEmitContext.d.ts +4 -0
  66. package/dist/config/resolveForkEmitContext.js +34 -0
  67. package/dist/config/resolvePipelineDag.d.ts +5 -1
  68. package/dist/config/resolvePipelineDag.js +86 -36
  69. package/dist/config/scanCatalogPaths.d.ts +4 -0
  70. package/dist/config/scanCatalogPaths.js +83 -0
  71. package/dist/config/validateCatalog.d.ts +22 -4
  72. package/dist/config/validateCatalog.js +169 -132
  73. package/dist/envelope/check.d.ts +2 -0
  74. package/dist/envelope/check.js +58 -0
  75. package/dist/envelope/cloneForks.d.ts +2 -0
  76. package/dist/envelope/cloneForks.js +62 -0
  77. package/dist/envelope/forkChoice.d.ts +3 -0
  78. package/dist/envelope/forkChoice.js +47 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/mcp/catalogTools.d.ts +3 -0
  82. package/dist/mcp/catalogTools.js +187 -0
  83. package/dist/mcp/controlTools.d.ts +3 -0
  84. package/dist/mcp/controlTools.js +147 -0
  85. package/dist/mcp/deps.d.ts +11 -0
  86. package/dist/mcp/deps.js +1 -0
  87. package/dist/mcp/projectRun.d.ts +1 -37
  88. package/dist/mcp/projectRun.js +1 -57
  89. package/dist/mcp/resources.d.ts +5 -0
  90. package/dist/mcp/resources.js +45 -0
  91. package/dist/mcp/server.d.ts +16 -3
  92. package/dist/mcp/server.js +159 -3
  93. package/dist/mcp/toolResults.d.ts +7 -0
  94. package/dist/mcp/toolResults.js +6 -0
  95. package/dist/mcp/tools.d.ts +2 -7
  96. package/dist/mcp/tools.js +52 -86
  97. package/dist/mcp/waitRun.d.ts +46 -0
  98. package/dist/mcp/waitRun.js +146 -0
  99. package/dist/mcp/waitingGates.d.ts +12 -0
  100. package/dist/mcp/waitingGates.js +83 -0
  101. package/dist/project/findProjectRoot.d.ts +2 -0
  102. package/dist/project/findProjectRoot.js +51 -0
  103. package/dist/project/globalHome.d.ts +2 -0
  104. package/dist/project/globalHome.js +19 -0
  105. package/dist/project/resolveProjectContext.d.ts +9 -0
  106. package/dist/project/resolveProjectContext.js +38 -0
  107. package/dist/project/resolveStageflowContext.d.ts +11 -0
  108. package/dist/project/resolveStageflowContext.js +66 -0
  109. package/dist/projection/projectRun.d.ts +44 -0
  110. package/dist/projection/projectRun.js +71 -0
  111. package/dist/prompt/priorEnvelope.d.ts +1 -1
  112. package/dist/prompt/priorEnvelope.js +4 -1
  113. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  114. package/dist/runstore/normalizeCatalogPath.js +4 -0
  115. package/dist/runstore/paths.js +2 -0
  116. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  117. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  118. package/dist/runstore/port.d.ts +22 -2
  119. package/dist/runstore/port.js +4 -1
  120. package/dist/runstore/runProjection.d.ts +1 -1
  121. package/dist/runstore/runProjection.js +25 -4
  122. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  123. package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
  124. package/dist/runstore/sqlite/schema.d.ts +1 -1
  125. package/dist/runstore/sqlite/schema.js +4 -1
  126. package/dist/runstore/stageInstanceId.d.ts +4 -0
  127. package/dist/runstore/stageInstanceId.js +23 -0
  128. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  129. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  130. package/dist/runstore/trackProjection.js +28 -12
  131. package/dist/runstore/workspaceLayout.d.ts +1 -0
  132. package/dist/runstore/workspaceLayout.js +1 -1
  133. package/dist/runtime/cloneSchedule.d.ts +18 -0
  134. package/dist/runtime/cloneSchedule.js +233 -0
  135. package/dist/runtime/credentialBinding.d.ts +8 -6
  136. package/dist/runtime/credentialBinding.js +18 -24
  137. package/dist/runtime/envelopeRouting.d.ts +2 -1
  138. package/dist/runtime/envelopeRouting.js +80 -13
  139. package/dist/runtime/pipelineRunner.d.ts +3 -0
  140. package/dist/runtime/pipelineRunner.js +15 -1
  141. package/dist/runtime/pipelineScheduler.d.ts +8 -2
  142. package/dist/runtime/pipelineScheduler.js +215 -39
  143. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  144. package/dist/runtime/reloadRunCatalog.js +44 -0
  145. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  146. package/dist/runtime/resumeReconstruct.js +31 -10
  147. package/dist/runtime/runChangeBus.d.ts +18 -0
  148. package/dist/runtime/runChangeBus.js +83 -0
  149. package/dist/runtime/runManager.d.ts +4 -0
  150. package/dist/runtime/runManager.js +36 -16
  151. package/dist/runtime/settingsFile.d.ts +12 -0
  152. package/dist/runtime/settingsFile.js +87 -6
  153. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  154. package/dist/runtime/stageAttemptBootstrap.js +18 -7
  155. package/dist/runtime/stageProcessLauncher.js +3 -0
  156. package/dist/runtime/stageRecovery.js +3 -1
  157. package/dist/runtime/stageRunner.d.ts +1 -0
  158. package/dist/runtime/stageRunner.js +17 -15
  159. package/dist/runtime/stageWorker.js +13 -10
  160. package/dist/server/bootstrap.d.ts +32 -0
  161. package/dist/server/bootstrap.js +56 -0
  162. package/dist/server/createHttpHost.d.ts +34 -0
  163. package/dist/server/createHttpHost.js +70 -0
  164. package/dist/server/http.d.ts +6 -13
  165. package/dist/server/http.js +297 -352
  166. package/dist/server/mcpHost.d.ts +22 -0
  167. package/dist/server/mcpHost.js +21 -0
  168. package/dist/server/operatorResults.d.ts +13 -0
  169. package/dist/server/operatorResults.js +22 -0
  170. package/dist/tools/emitStageEnvelope.d.ts +12 -1
  171. package/dist/tools/emitStageEnvelope.js +27 -1
  172. package/dist/types/envelope.d.ts +3 -0
  173. package/dist/types/forkChoice.d.ts +30 -0
  174. package/dist/types/forkChoice.js +1 -0
  175. package/dist/types/pipeline.d.ts +59 -2
  176. package/dist/types/stageflowManifest.d.ts +23 -0
  177. package/dist/types/stageflowManifest.js +1 -0
  178. package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
  179. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  180. package/dist/ui/index.html +3 -2
  181. package/dist/ui/stageflow-icon.svg +12 -0
  182. package/package.json +8 -5
  183. package/skills/install-suite.sh +107 -0
  184. package/skills/stageflow/SKILL.md +26 -0
  185. package/skills/stageflow/references/control-surface.md +26 -0
  186. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  187. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  188. package/skills/stageflow-author/SKILL.md +58 -0
  189. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  190. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  191. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  192. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  193. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  194. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  195. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  196. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  197. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  198. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  199. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  200. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  201. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  202. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  203. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  204. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  205. package/skills/stageflow-delegate/SKILL.md +18 -0
  206. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  207. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  208. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  209. package/skills/stageflow-run/SKILL.md +151 -0
  210. package/skills/stageflow-run/references/mcp-call.md +17 -0
  211. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  212. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  213. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  214. package/skills/stageflow-session-capture/SKILL.md +94 -0
  215. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  216. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  217. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  218. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  219. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  220. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  221. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  222. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  223. package/skills/stageflow-setup/SKILL.md +110 -0
  224. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
@@ -0,0 +1,459 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import { access, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { inflateRawSync } from "node:zlib";
6
+ import path from "node:path";
7
+ import { findProjectRoot } from "../project/findProjectRoot.js";
8
+ export const SKILLS_USAGE = `Usage:
9
+ sf skills list
10
+ sf skills install --from-path <dir> [--skill-name <name>]
11
+ sf skills install --from-zip <url-or-path> [--skill-name <name>] [--checksum sha256:<hex>]`;
12
+ const defaultIo = {
13
+ log: (line) => console.log(line),
14
+ error: (line) => console.error(line),
15
+ fetch: globalThis.fetch.bind(globalThis),
16
+ runDoctor: (scriptPath, cwd) => {
17
+ const result = spawnSync(process.execPath, [scriptPath, "doctor"], {
18
+ cwd,
19
+ stdio: "pipe",
20
+ });
21
+ return result.status ?? 1;
22
+ },
23
+ };
24
+ function parseSkillsArgs(args) {
25
+ if (args.length === 0) {
26
+ return { help: false };
27
+ }
28
+ if (args[0] === "--help" || args[0] === "-h") {
29
+ return { help: true };
30
+ }
31
+ const subcommand = args[0];
32
+ if (subcommand !== "list" && subcommand !== "install") {
33
+ throw new Error(`Unknown skills subcommand: ${subcommand}`);
34
+ }
35
+ let fromPath;
36
+ let fromZip;
37
+ let skillName;
38
+ let checksum;
39
+ let help = false;
40
+ for (let i = 1; i < args.length; i++) {
41
+ const arg = args[i];
42
+ if (arg === "--help" || arg === "-h") {
43
+ help = true;
44
+ }
45
+ else if (arg === "--from-path") {
46
+ const value = args[++i];
47
+ if (value === undefined || value.length === 0) {
48
+ throw new Error("Missing value for --from-path");
49
+ }
50
+ fromPath = value;
51
+ }
52
+ else if (arg === "--from-zip") {
53
+ const value = args[++i];
54
+ if (value === undefined || value.length === 0) {
55
+ throw new Error("Missing value for --from-zip");
56
+ }
57
+ fromZip = value;
58
+ }
59
+ else if (arg === "--skill-name") {
60
+ const value = args[++i];
61
+ if (value === undefined || value.length === 0) {
62
+ throw new Error("Missing value for --skill-name");
63
+ }
64
+ skillName = value;
65
+ }
66
+ else if (arg === "--checksum") {
67
+ const value = args[++i];
68
+ if (value === undefined || value.length === 0) {
69
+ throw new Error("Missing value for --checksum");
70
+ }
71
+ checksum = value;
72
+ }
73
+ else if (arg.startsWith("-")) {
74
+ throw new Error(`Unknown flag: ${arg}`);
75
+ }
76
+ else {
77
+ throw new Error(`Unexpected argument: ${arg}`);
78
+ }
79
+ }
80
+ return {
81
+ help,
82
+ subcommand,
83
+ fromPath,
84
+ fromZip,
85
+ skillName,
86
+ checksum,
87
+ };
88
+ }
89
+ function skillsDirForRoot(projectRoot) {
90
+ return path.join(projectRoot, ".pi", "skills");
91
+ }
92
+ function shouldCopyRelative(rel) {
93
+ const normalized = rel.split(path.sep).join("/");
94
+ if (normalized === "" || normalized === ".") {
95
+ return true;
96
+ }
97
+ const segments = normalized.split("/");
98
+ if (segments.some((segment) => segment === "test" || segment === "node_modules")) {
99
+ return false;
100
+ }
101
+ if (segments[0] === "scripts") {
102
+ const base = segments[segments.length - 1] ?? "";
103
+ if (base.startsWith("generate-")) {
104
+ return false;
105
+ }
106
+ }
107
+ return true;
108
+ }
109
+ async function copySkillTree(sourceRoot, destRoot) {
110
+ await rm(destRoot, { recursive: true, force: true });
111
+ await mkdir(destRoot, { recursive: true });
112
+ await cp(sourceRoot, destRoot, {
113
+ recursive: true,
114
+ filter: (src) => {
115
+ const rel = path.relative(sourceRoot, src);
116
+ return shouldCopyRelative(rel);
117
+ },
118
+ });
119
+ }
120
+ function inferSkillNameFromPath(sourcePath) {
121
+ return path.basename(path.resolve(sourcePath));
122
+ }
123
+ function inferSkillNameFromZipRef(zipRef) {
124
+ const base = path.basename(zipRef.split("?")[0] ?? zipRef);
125
+ return base.replace(/\.zip$/i, "");
126
+ }
127
+ function parseChecksum(value) {
128
+ const match = /^sha256:([a-f0-9]{64})$/i.exec(value);
129
+ if (!match) {
130
+ throw new Error("--checksum must be sha256:<64 hex chars>");
131
+ }
132
+ return match[1].toLowerCase();
133
+ }
134
+ function sha256Hex(data) {
135
+ return createHash("sha256").update(data).digest("hex");
136
+ }
137
+ function isZipSlipEntry(name) {
138
+ const normalized = name.replace(/\\/g, "/");
139
+ if (path.posix.isAbsolute(normalized)) {
140
+ return true;
141
+ }
142
+ return normalized.split("/").some((segment) => segment === "..");
143
+ }
144
+ function findEndOfCentralDirectory(data) {
145
+ for (let i = data.length - 22; i >= 0; i--) {
146
+ if (data.readUInt32LE(i) === 0x06054b50) {
147
+ return i;
148
+ }
149
+ }
150
+ throw new Error("invalid zip archive: end of central directory not found");
151
+ }
152
+ function readZipEntries(data) {
153
+ const eocdOffset = findEndOfCentralDirectory(data);
154
+ const centralDirOffset = data.readUInt32LE(eocdOffset + 16);
155
+ const totalEntries = data.readUInt16LE(eocdOffset + 10);
156
+ const entries = [];
157
+ let offset = centralDirOffset;
158
+ for (let i = 0; i < totalEntries; i++) {
159
+ if (data.readUInt32LE(offset) !== 0x02014b50) {
160
+ throw new Error("invalid zip archive: central directory entry missing");
161
+ }
162
+ const compressionMethod = data.readUInt16LE(offset + 10);
163
+ const compressedSize = data.readUInt32LE(offset + 20);
164
+ const uncompressedSize = data.readUInt32LE(offset + 24);
165
+ const nameLength = data.readUInt16LE(offset + 28);
166
+ const extraLength = data.readUInt16LE(offset + 30);
167
+ const commentLength = data.readUInt16LE(offset + 32);
168
+ const localHeaderOffset = data.readUInt32LE(offset + 42);
169
+ const name = data.toString("utf8", offset + 46, offset + 46 + nameLength);
170
+ entries.push({
171
+ name,
172
+ compressionMethod,
173
+ compressedSize,
174
+ uncompressedSize,
175
+ localHeaderOffset,
176
+ });
177
+ offset += 46 + nameLength + extraLength + commentLength;
178
+ }
179
+ return entries;
180
+ }
181
+ async function extractZipBuffer(data, destDir) {
182
+ await mkdir(destDir, { recursive: true });
183
+ const entries = readZipEntries(data);
184
+ for (const entry of entries) {
185
+ if (entry.name.endsWith("/")) {
186
+ continue;
187
+ }
188
+ if (isZipSlipEntry(entry.name)) {
189
+ throw new Error("zip entry path rejected (zip-slip)");
190
+ }
191
+ const localOffset = entry.localHeaderOffset;
192
+ if (data.readUInt32LE(localOffset) !== 0x04034b50) {
193
+ throw new Error("invalid zip archive: local file header missing");
194
+ }
195
+ const nameLength = data.readUInt16LE(localOffset + 26);
196
+ const extraLength = data.readUInt16LE(localOffset + 28);
197
+ const dataOffset = localOffset + 30 + nameLength + extraLength;
198
+ const compressed = data.subarray(dataOffset, dataOffset + entry.compressedSize);
199
+ let content;
200
+ if (entry.compressionMethod === 0) {
201
+ content = compressed;
202
+ }
203
+ else if (entry.compressionMethod === 8) {
204
+ content = inflateRawSync(compressed);
205
+ }
206
+ else {
207
+ throw new Error(`unsupported zip compression method: ${entry.compressionMethod}`);
208
+ }
209
+ if (content.length !== entry.uncompressedSize) {
210
+ throw new Error("invalid zip archive: uncompressed size mismatch");
211
+ }
212
+ const outPath = path.join(destDir, entry.name);
213
+ await mkdir(path.dirname(outPath), { recursive: true });
214
+ await writeFile(outPath, content);
215
+ }
216
+ }
217
+ async function resolveZipBytes(zipRef, io) {
218
+ if (zipRef.startsWith("https://")) {
219
+ const response = await io.fetch(zipRef);
220
+ if (!response.ok) {
221
+ throw new Error(`failed to fetch zip: HTTP ${response.status}`);
222
+ }
223
+ const arrayBuffer = await response.arrayBuffer();
224
+ return Buffer.from(arrayBuffer);
225
+ }
226
+ if (/^https?:\/\//i.test(zipRef)) {
227
+ throw new Error("only HTTPS URLs are supported for --from-zip");
228
+ }
229
+ return readFile(path.resolve(zipRef));
230
+ }
231
+ async function pathExists(filePath) {
232
+ try {
233
+ await access(filePath);
234
+ return true;
235
+ }
236
+ catch {
237
+ return false;
238
+ }
239
+ }
240
+ async function findSkillRoot(extractRoot, skillName) {
241
+ const directBin = path.join(extractRoot, "bin", `${skillName}.mjs`);
242
+ if (await pathExists(directBin)) {
243
+ return extractRoot;
244
+ }
245
+ const nestedRoot = path.join(extractRoot, skillName);
246
+ const nestedBin = path.join(nestedRoot, "bin", `${skillName}.mjs`);
247
+ if (await pathExists(nestedBin)) {
248
+ return nestedRoot;
249
+ }
250
+ const found = await findSkillRootByBin(extractRoot, skillName);
251
+ if (found !== undefined) {
252
+ return found;
253
+ }
254
+ const bySkillMd = await findSkillRootBySkillMd(extractRoot);
255
+ if (bySkillMd !== undefined) {
256
+ return bySkillMd;
257
+ }
258
+ throw new Error(`could not locate skill root in extracted archive`);
259
+ }
260
+ async function findSkillRootByBin(dir, skillName) {
261
+ const entries = await readdir(dir, { withFileTypes: true });
262
+ for (const entry of entries) {
263
+ if (!entry.isDirectory()) {
264
+ continue;
265
+ }
266
+ const fullPath = path.join(dir, entry.name);
267
+ const binScript = path.join(fullPath, "bin", `${skillName}.mjs`);
268
+ if (await pathExists(binScript)) {
269
+ return fullPath;
270
+ }
271
+ const nested = await findSkillRootByBin(fullPath, skillName);
272
+ if (nested !== undefined) {
273
+ return nested;
274
+ }
275
+ }
276
+ return undefined;
277
+ }
278
+ async function findSkillRootBySkillMd(dir) {
279
+ const entries = await readdir(dir, { withFileTypes: true });
280
+ for (const entry of entries) {
281
+ const fullPath = path.join(dir, entry.name);
282
+ if (entry.isFile() && entry.name === "SKILL.md") {
283
+ return dir;
284
+ }
285
+ if (entry.isDirectory()) {
286
+ const nested = await findSkillRootBySkillMd(fullPath);
287
+ if (nested !== undefined) {
288
+ return nested;
289
+ }
290
+ }
291
+ }
292
+ return undefined;
293
+ }
294
+ async function resolveInstallSource(parsed, io) {
295
+ const hasPath = parsed.fromPath !== undefined;
296
+ const hasZip = parsed.fromZip !== undefined;
297
+ if (hasPath === hasZip) {
298
+ throw new Error("specify exactly one of --from-path or --from-zip");
299
+ }
300
+ if (parsed.fromPath !== undefined) {
301
+ const sourceRoot = path.resolve(parsed.fromPath);
302
+ try {
303
+ const sourceStat = await stat(sourceRoot);
304
+ if (!sourceStat.isDirectory()) {
305
+ throw new Error("source path not found");
306
+ }
307
+ }
308
+ catch {
309
+ throw new Error("source path not found");
310
+ }
311
+ const skillName = parsed.skillName ?? inferSkillNameFromPath(parsed.fromPath);
312
+ return { sourceRoot, skillName };
313
+ }
314
+ const zipRef = parsed.fromZip;
315
+ const zipBytes = await resolveZipBytes(zipRef, io);
316
+ if (parsed.checksum !== undefined) {
317
+ const expected = parseChecksum(parsed.checksum);
318
+ const actual = sha256Hex(zipBytes);
319
+ if (actual !== expected) {
320
+ throw new Error("checksum mismatch");
321
+ }
322
+ }
323
+ const extractDir = await mkdtemp(path.join(tmpdir(), "sf-skill-zip-"));
324
+ await extractZipBuffer(zipBytes, extractDir);
325
+ const skillName = parsed.skillName ?? inferSkillNameFromZipRef(zipRef);
326
+ const sourceRoot = await findSkillRoot(extractDir, skillName);
327
+ return {
328
+ sourceRoot,
329
+ skillName,
330
+ cleanup: async () => {
331
+ await rm(extractDir, { recursive: true, force: true });
332
+ },
333
+ };
334
+ }
335
+ async function listSkills(projectRoot, io) {
336
+ const skillsDir = skillsDirForRoot(projectRoot);
337
+ let names;
338
+ try {
339
+ names = await readdir(skillsDir);
340
+ }
341
+ catch (err) {
342
+ if (err.code === "ENOENT") {
343
+ return;
344
+ }
345
+ throw err;
346
+ }
347
+ for (const name of names.sort()) {
348
+ const skillDir = path.join(skillsDir, name);
349
+ const skillStat = await stat(skillDir);
350
+ if (!skillStat.isDirectory()) {
351
+ continue;
352
+ }
353
+ let version = "—";
354
+ try {
355
+ const pkgRaw = await readFile(path.join(skillDir, "package.json"), "utf8");
356
+ const pkg = JSON.parse(pkgRaw);
357
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
358
+ version = pkg.version;
359
+ }
360
+ }
361
+ catch {
362
+ // no package.json
363
+ }
364
+ io.log(`${name}\t${version}\tbin/${name}.mjs`);
365
+ }
366
+ }
367
+ async function installSkill(projectRoot, parsed, io) {
368
+ const resolved = await resolveInstallSource(parsed, io);
369
+ try {
370
+ const destRoot = path.join(skillsDirForRoot(projectRoot), resolved.skillName);
371
+ await copySkillTree(resolved.sourceRoot, destRoot);
372
+ const doctorScript = path.join(destRoot, "bin", `${resolved.skillName}.mjs`);
373
+ const doctorCode = io.runDoctor(doctorScript, projectRoot);
374
+ if (doctorCode !== 0) {
375
+ io.error("skill doctor failed");
376
+ return 1;
377
+ }
378
+ io.log(`Installed skill ${resolved.skillName} to ${destRoot}`);
379
+ return 0;
380
+ }
381
+ finally {
382
+ if (resolved.cleanup) {
383
+ await resolved.cleanup();
384
+ }
385
+ }
386
+ }
387
+ export async function runSkillsCommand(args, options = {}) {
388
+ const cwd = options.cwd ?? process.cwd();
389
+ const projectRoot = options.projectRoot ?? findProjectRoot(cwd) ?? path.resolve(cwd);
390
+ const io = { ...defaultIo, ...options.io };
391
+ try {
392
+ const parsed = parseSkillsArgs(args);
393
+ if (parsed.help) {
394
+ io.error(SKILLS_USAGE);
395
+ return 0;
396
+ }
397
+ if (parsed.subcommand === undefined) {
398
+ io.error(SKILLS_USAGE);
399
+ return 1;
400
+ }
401
+ if (parsed.subcommand === "list") {
402
+ await listSkills(projectRoot, io);
403
+ return 0;
404
+ }
405
+ return await installSkill(projectRoot, parsed, io);
406
+ }
407
+ catch (err) {
408
+ io.error(err instanceof Error ? err.message : String(err));
409
+ return 1;
410
+ }
411
+ }
412
+ export function createStoredZip(files) {
413
+ const localParts = [];
414
+ const centralParts = [];
415
+ let offset = 0;
416
+ for (const file of files) {
417
+ const nameBuf = Buffer.from(file.name, "utf8");
418
+ const content = Buffer.isBuffer(file.content)
419
+ ? file.content
420
+ : Buffer.from(file.content, "utf8");
421
+ const localHeader = Buffer.alloc(30);
422
+ localHeader.writeUInt32LE(0x04034b50, 0);
423
+ localHeader.writeUInt16LE(20, 4);
424
+ localHeader.writeUInt16LE(0, 6);
425
+ localHeader.writeUInt16LE(0, 8);
426
+ localHeader.writeUInt32LE(0, 14);
427
+ localHeader.writeUInt32LE(content.length, 18);
428
+ localHeader.writeUInt32LE(content.length, 22);
429
+ localHeader.writeUInt16LE(nameBuf.length, 26);
430
+ localHeader.writeUInt16LE(0, 28);
431
+ localParts.push(localHeader, nameBuf, content);
432
+ const central = Buffer.alloc(46);
433
+ central.writeUInt32LE(0x02014b50, 0);
434
+ central.writeUInt16LE(20, 4);
435
+ central.writeUInt16LE(20, 6);
436
+ central.writeUInt16LE(0, 8);
437
+ central.writeUInt16LE(0, 10);
438
+ central.writeUInt32LE(0, 16);
439
+ central.writeUInt32LE(content.length, 20);
440
+ central.writeUInt32LE(content.length, 24);
441
+ central.writeUInt16LE(nameBuf.length, 28);
442
+ central.writeUInt16LE(0, 30);
443
+ central.writeUInt16LE(0, 32);
444
+ central.writeUInt32LE(offset, 42);
445
+ centralParts.push(central, nameBuf);
446
+ offset += 30 + nameBuf.length + content.length;
447
+ }
448
+ const centralDir = Buffer.concat(centralParts);
449
+ const eocd = Buffer.alloc(22);
450
+ eocd.writeUInt32LE(0x06054b50, 0);
451
+ eocd.writeUInt16LE(0, 4);
452
+ eocd.writeUInt16LE(0, 6);
453
+ eocd.writeUInt16LE(files.length, 8);
454
+ eocd.writeUInt16LE(files.length, 10);
455
+ eocd.writeUInt32LE(centralDir.length, 12);
456
+ eocd.writeUInt32LE(offset, 16);
457
+ eocd.writeUInt16LE(0, 20);
458
+ return Buffer.concat([...localParts, centralDir, eocd]);
459
+ }
@@ -1,5 +1,5 @@
1
1
  import { validateCatalog as defaultValidateCatalog, type ValidationResult } from "../config/validateCatalog.js";
2
- export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <name-or-path>] [--strict] [--json]";
2
+ export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]\n\n With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).\n --pipeline validates one pipeline file (includes uses:/include: transitively).\n --task validates one task file.";
3
3
  export type ValidateCommandIo = {
4
4
  log: (line: string) => void;
5
5
  error: (line: string) => void;
@@ -1,7 +1,11 @@
1
1
  import { validateCatalog as defaultValidateCatalog, } from "../config/validateCatalog.js";
2
2
  import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
3
3
  export const VALIDATE_USAGE = `Usage:
4
- sf validate [--pipeline <name-or-path>] [--strict] [--json]`;
4
+ sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
5
+
6
+ With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).
7
+ --pipeline validates one pipeline file (includes uses:/include: transitively).
8
+ --task validates one task file.`;
5
9
  const defaultIo = {
6
10
  log: (line) => console.log(line),
7
11
  error: (line) => console.error(line),
@@ -14,6 +18,7 @@ function parseValidateArgs(args) {
14
18
  return { help: true, strict: false, json: false };
15
19
  }
16
20
  let pipeline;
21
+ let task;
17
22
  let strict = false;
18
23
  let json = false;
19
24
  let help = false;
@@ -29,6 +34,13 @@ function parseValidateArgs(args) {
29
34
  }
30
35
  pipeline = value;
31
36
  }
37
+ else if (arg === "--task") {
38
+ const value = args[++i];
39
+ if (value === undefined || value.length === 0) {
40
+ throw new Error("Missing value for --task");
41
+ }
42
+ task = value;
43
+ }
32
44
  else if (arg === "--strict") {
33
45
  strict = true;
34
46
  }
@@ -42,7 +54,10 @@ function parseValidateArgs(args) {
42
54
  throw new Error(`Unexpected argument: ${arg}`);
43
55
  }
44
56
  }
45
- return { help, pipeline, strict, json };
57
+ if (pipeline !== undefined && task !== undefined) {
58
+ throw new Error("Use at most one of --pipeline or --task");
59
+ }
60
+ return { help, pipeline, task, strict, json };
46
61
  }
47
62
  export async function runValidateCommand(args, options = {}) {
48
63
  const cwd = options.cwd ?? process.cwd();
@@ -54,10 +69,12 @@ export async function runValidateCommand(args, options = {}) {
54
69
  out.error(VALIDATE_USAGE);
55
70
  return 0;
56
71
  }
72
+ const scope = parsed.pipeline ? "pipeline" : parsed.task ? "task" : "full";
57
73
  const result = await validateCatalogFn({
58
- scope: parsed.pipeline ? "pipeline" : "full",
74
+ scope,
59
75
  cwd,
60
76
  pipeline: parsed.pipeline,
77
+ task: parsed.task,
61
78
  strict: parsed.strict,
62
79
  });
63
80
  if (parsed.json) {
package/dist/cli.d.ts CHANGED
@@ -10,3 +10,4 @@ export declare function parseRunStageArgs(argv: string[]): {
10
10
  operatorCatalog?: OperatorCatalog;
11
11
  skipGates?: boolean;
12
12
  };
13
+ export declare function sameCliEntry(a: string, b: string): boolean;