stageflow 0.1.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 (180) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/agent/activity.d.ts +76 -0
  4. package/dist/agent/activity.js +89 -0
  5. package/dist/agent/activityObserver.d.ts +17 -0
  6. package/dist/agent/activityObserver.js +66 -0
  7. package/dist/agent/cursorExtension.d.ts +12 -0
  8. package/dist/agent/cursorExtension.js +88 -0
  9. package/dist/agent/cursorProvider.d.ts +14 -0
  10. package/dist/agent/cursorProvider.js +139 -0
  11. package/dist/agent/fakeAgent.d.ts +29 -0
  12. package/dist/agent/fakeAgent.js +295 -0
  13. package/dist/agent/oauthSessionManager.d.ts +33 -0
  14. package/dist/agent/oauthSessionManager.js +214 -0
  15. package/dist/agent/piAdapter.d.ts +128 -0
  16. package/dist/agent/piAdapter.js +967 -0
  17. package/dist/agent/port.d.ts +63 -0
  18. package/dist/agent/port.js +46 -0
  19. package/dist/agent/providerAuth.d.ts +101 -0
  20. package/dist/agent/providerAuth.js +282 -0
  21. package/dist/agent/providerSupport.d.ts +28 -0
  22. package/dist/agent/providerSupport.js +19 -0
  23. package/dist/cli/providersCommand.d.ts +11 -0
  24. package/dist/cli/providersCommand.js +275 -0
  25. package/dist/cli/terminalAuthInteraction.d.ts +8 -0
  26. package/dist/cli/terminalAuthInteraction.js +65 -0
  27. package/dist/cli/terminalSecret.d.ts +2 -0
  28. package/dist/cli/terminalSecret.js +67 -0
  29. package/dist/cli/validateCommand.d.ts +11 -0
  30. package/dist/cli/validateCommand.js +81 -0
  31. package/dist/cli/validateOutput.d.ts +8 -0
  32. package/dist/cli/validateOutput.js +75 -0
  33. package/dist/cli.d.ts +2 -0
  34. package/dist/cli.js +273 -0
  35. package/dist/config/createPipeline.d.ts +31 -0
  36. package/dist/config/createPipeline.js +255 -0
  37. package/dist/config/createStage.d.ts +25 -0
  38. package/dist/config/createStage.js +181 -0
  39. package/dist/config/listConfig.d.ts +33 -0
  40. package/dist/config/listConfig.js +195 -0
  41. package/dist/config/listExtensions.d.ts +26 -0
  42. package/dist/config/listExtensions.js +50 -0
  43. package/dist/config/listSkills.d.ts +29 -0
  44. package/dist/config/listSkills.js +55 -0
  45. package/dist/config/loadOutcome.d.ts +27 -0
  46. package/dist/config/loadOutcome.js +6 -0
  47. package/dist/config/loadPipeline.d.ts +25 -0
  48. package/dist/config/loadPipeline.js +196 -0
  49. package/dist/config/loadStage.d.ts +4 -0
  50. package/dist/config/loadStage.js +127 -0
  51. package/dist/config/loadTask.d.ts +7 -0
  52. package/dist/config/loadTask.js +69 -0
  53. package/dist/config/readYamlObject.d.ts +1 -0
  54. package/dist/config/readYamlObject.js +5 -0
  55. package/dist/config/resolvePipelineDag.d.ts +12 -0
  56. package/dist/config/resolvePipelineDag.js +282 -0
  57. package/dist/config/validateCatalog.d.ts +36 -0
  58. package/dist/config/validateCatalog.js +322 -0
  59. package/dist/envelope/check.d.ts +3 -0
  60. package/dist/envelope/check.js +52 -0
  61. package/dist/envelope/payloadSchema.d.ts +10 -0
  62. package/dist/envelope/payloadSchema.js +91 -0
  63. package/dist/hitl/qaTrail.d.ts +31 -0
  64. package/dist/hitl/qaTrail.js +65 -0
  65. package/dist/index.d.ts +13 -0
  66. package/dist/index.js +7 -0
  67. package/dist/mcp/projectRun.d.ts +37 -0
  68. package/dist/mcp/projectRun.js +57 -0
  69. package/dist/mcp/readArtifact.d.ts +2 -0
  70. package/dist/mcp/readArtifact.js +46 -0
  71. package/dist/mcp/server.d.ts +7 -0
  72. package/dist/mcp/server.js +26 -0
  73. package/dist/mcp/tools.d.ts +9 -0
  74. package/dist/mcp/tools.js +96 -0
  75. package/dist/package-meta.d.ts +1 -0
  76. package/dist/package-meta.js +1 -0
  77. package/dist/prompt/priorEnvelope.d.ts +2 -0
  78. package/dist/prompt/priorEnvelope.js +5 -0
  79. package/dist/runstore/catalog.d.ts +8 -0
  80. package/dist/runstore/catalog.js +16 -0
  81. package/dist/runstore/createStore.d.ts +15 -0
  82. package/dist/runstore/createStore.js +23 -0
  83. package/dist/runstore/disk/DiskRunStore.d.ts +30 -0
  84. package/dist/runstore/disk/DiskRunStore.js +238 -0
  85. package/dist/runstore/disk/catalogHelpers.d.ts +11 -0
  86. package/dist/runstore/disk/catalogHelpers.js +102 -0
  87. package/dist/runstore/layout.d.ts +22 -0
  88. package/dist/runstore/layout.js +58 -0
  89. package/dist/runstore/paths.d.ts +7 -0
  90. package/dist/runstore/paths.js +60 -0
  91. package/dist/runstore/pipelineDagSnapshot.d.ts +5 -0
  92. package/dist/runstore/pipelineDagSnapshot.js +43 -0
  93. package/dist/runstore/port.d.ts +146 -0
  94. package/dist/runstore/port.js +28 -0
  95. package/dist/runstore/runProjection.d.ts +6 -0
  96. package/dist/runstore/runProjection.js +102 -0
  97. package/dist/runstore/sqlite/SqliteRunStore.d.ts +48 -0
  98. package/dist/runstore/sqlite/SqliteRunStore.js +460 -0
  99. package/dist/runstore/sqlite/migrateFromDisk.d.ts +5 -0
  100. package/dist/runstore/sqlite/migrateFromDisk.js +108 -0
  101. package/dist/runstore/sqlite/schema.d.ts +1 -0
  102. package/dist/runstore/sqlite/schema.js +59 -0
  103. package/dist/runstore/stageExecution.d.ts +13 -0
  104. package/dist/runstore/stageExecution.js +22 -0
  105. package/dist/runstore/stageSnapshot.d.ts +2 -0
  106. package/dist/runstore/stageSnapshot.js +46 -0
  107. package/dist/runstore/syntheticStageSnapshot.d.ts +2 -0
  108. package/dist/runstore/syntheticStageSnapshot.js +10 -0
  109. package/dist/runstore/trackProjection.d.ts +7 -0
  110. package/dist/runstore/trackProjection.js +94 -0
  111. package/dist/runstore/workspaceLayout.d.ts +31 -0
  112. package/dist/runstore/workspaceLayout.js +172 -0
  113. package/dist/runtime/answerResume.d.ts +16 -0
  114. package/dist/runtime/answerResume.js +22 -0
  115. package/dist/runtime/credentialBinding.d.ts +16 -0
  116. package/dist/runtime/credentialBinding.js +80 -0
  117. package/dist/runtime/dagTraversal.d.ts +3 -0
  118. package/dist/runtime/dagTraversal.js +26 -0
  119. package/dist/runtime/envelopeRouting.d.ts +20 -0
  120. package/dist/runtime/envelopeRouting.js +59 -0
  121. package/dist/runtime/hitlSeams.d.ts +38 -0
  122. package/dist/runtime/hitlSeams.js +2 -0
  123. package/dist/runtime/pipelineRunner.d.ts +77 -0
  124. package/dist/runtime/pipelineRunner.js +141 -0
  125. package/dist/runtime/pipelineScheduler.d.ts +90 -0
  126. package/dist/runtime/pipelineScheduler.js +494 -0
  127. package/dist/runtime/pipelineValidationError.d.ts +6 -0
  128. package/dist/runtime/pipelineValidationError.js +8 -0
  129. package/dist/runtime/resumeReconstruct.d.ts +33 -0
  130. package/dist/runtime/resumeReconstruct.js +144 -0
  131. package/dist/runtime/runManager.d.ts +129 -0
  132. package/dist/runtime/runManager.js +818 -0
  133. package/dist/runtime/runRetryCoordinator.d.ts +85 -0
  134. package/dist/runtime/runRetryCoordinator.js +372 -0
  135. package/dist/runtime/settingsFile.d.ts +17 -0
  136. package/dist/runtime/settingsFile.js +88 -0
  137. package/dist/runtime/stageAttemptBootstrap.d.ts +40 -0
  138. package/dist/runtime/stageAttemptBootstrap.js +82 -0
  139. package/dist/runtime/stageAttemptContext.d.ts +11 -0
  140. package/dist/runtime/stageAttemptContext.js +26 -0
  141. package/dist/runtime/stageConcurrency.d.ts +10 -0
  142. package/dist/runtime/stageConcurrency.js +51 -0
  143. package/dist/runtime/stageHitl.d.ts +95 -0
  144. package/dist/runtime/stageHitl.js +198 -0
  145. package/dist/runtime/stageProcessLauncher.d.ts +45 -0
  146. package/dist/runtime/stageProcessLauncher.js +199 -0
  147. package/dist/runtime/stageRecovery.d.ts +10 -0
  148. package/dist/runtime/stageRecovery.js +43 -0
  149. package/dist/runtime/stageRoots.d.ts +22 -0
  150. package/dist/runtime/stageRoots.js +93 -0
  151. package/dist/runtime/stageRunner.d.ts +59 -0
  152. package/dist/runtime/stageRunner.js +221 -0
  153. package/dist/runtime/stageWorker.d.ts +6 -0
  154. package/dist/runtime/stageWorker.js +109 -0
  155. package/dist/runtime/stageWorkerProtocol.d.ts +27 -0
  156. package/dist/runtime/stageWorkerProtocol.js +16 -0
  157. package/dist/runtime/taskInput.d.ts +15 -0
  158. package/dist/runtime/taskInput.js +38 -0
  159. package/dist/server/http.d.ts +31 -0
  160. package/dist/server/http.js +561 -0
  161. package/dist/server/providerRoutes.d.ts +15 -0
  162. package/dist/server/providerRoutes.js +186 -0
  163. package/dist/tools/askOperator.d.ts +185 -0
  164. package/dist/tools/askOperator.js +409 -0
  165. package/dist/tools/emitStageEnvelope.d.ts +42 -0
  166. package/dist/tools/emitStageEnvelope.js +61 -0
  167. package/dist/tools/writeStageArtifact.d.ts +26 -0
  168. package/dist/tools/writeStageArtifact.js +103 -0
  169. package/dist/types/envelope.d.ts +12 -0
  170. package/dist/types/envelope.js +6 -0
  171. package/dist/types/pipeline.d.ts +25 -0
  172. package/dist/types/pipeline.js +1 -0
  173. package/dist/types/stage.d.ts +12 -0
  174. package/dist/types/stage.js +6 -0
  175. package/dist/types/task.d.ts +7 -0
  176. package/dist/types/task.js +1 -0
  177. package/dist/ui/assets/index-Cry0Tpfx.js +118 -0
  178. package/dist/ui/assets/index-DCsDopak.css +1 -0
  179. package/dist/ui/index.html +21 -0
  180. package/package.json +72 -0
@@ -0,0 +1,275 @@
1
+ import { defaultContext, detectPiHome, getAuthStatus, getCredentialSourceSettings, listProviders, loginWithApiKey, loginWithOauth, logoutProvider, ProviderAuthError, setCredentialSource, } from "../agent/providerAuth.js";
2
+ import { createTerminalAuthInteraction } from "./terminalAuthInteraction.js";
3
+ import { promptSecret, readApiKeyFromEnv } from "./terminalSecret.js";
4
+ export const PROVIDERS_USAGE = `Usage:
5
+ sf providers list
6
+ sf providers status [--provider <id>]
7
+ sf providers detect
8
+ sf providers source [get | set <pi_home|sf_owned>]
9
+ sf providers login <providerId> [--type api_key|oauth] [--api-key-env <VAR>]
10
+ sf providers logout <providerId>`;
11
+ const defaultIo = {
12
+ log: (line) => console.log(line),
13
+ error: (line) => console.error(line),
14
+ env: process.env,
15
+ readSecret: promptSecret,
16
+ createAuthInteraction: (signal) => createTerminalAuthInteraction({ signal }),
17
+ };
18
+ function scrub(message, secrets) {
19
+ let out = message;
20
+ for (const secret of secrets) {
21
+ if (secret.length === 0)
22
+ continue;
23
+ out = out.split(secret).join("[redacted]");
24
+ }
25
+ return out;
26
+ }
27
+ function formatStatus(status) {
28
+ const parts = [
29
+ status.providerId,
30
+ status.configured ? "configured" : "disconnected",
31
+ ];
32
+ if (status.authKind !== undefined)
33
+ parts.push(`kind=${status.authKind}`);
34
+ if (status.source !== undefined)
35
+ parts.push(`source=${status.source}`);
36
+ return parts.join("\t");
37
+ }
38
+ function formatProvider(p) {
39
+ const caps = [];
40
+ if (p.supportsApiKey)
41
+ caps.push("api_key");
42
+ if (p.supportsOauth)
43
+ caps.push("oauth");
44
+ const label = p.oauthLabel !== undefined && p.oauthLabel.length > 0
45
+ ? `\t${p.oauthLabel}`
46
+ : "";
47
+ return `${p.id}\t${p.name}\t${caps.join(",") || "none"}${label}`;
48
+ }
49
+ function parseProvidersArgs(args) {
50
+ if (args.length === 0) {
51
+ return { help: false, positionals: [] };
52
+ }
53
+ if (args[0] === "--help" || args[0] === "-h") {
54
+ return { help: true, positionals: [] };
55
+ }
56
+ const subcommand = args[0];
57
+ let providerFlag;
58
+ let type;
59
+ let apiKeyEnv;
60
+ let help = false;
61
+ const positionals = [];
62
+ for (let i = 1; i < args.length; i++) {
63
+ const arg = args[i];
64
+ if (arg === "--help" || arg === "-h") {
65
+ help = true;
66
+ }
67
+ else if (arg === "--provider") {
68
+ const value = args[++i];
69
+ if (value === undefined || value.length === 0) {
70
+ throw new Error("Missing value for --provider");
71
+ }
72
+ providerFlag = value;
73
+ }
74
+ else if (arg === "--type") {
75
+ const value = args[++i];
76
+ if (value !== "api_key" && value !== "oauth") {
77
+ throw new Error("--type must be api_key or oauth");
78
+ }
79
+ type = value;
80
+ }
81
+ else if (arg === "--api-key-env") {
82
+ const value = args[++i];
83
+ if (value === undefined || value.length === 0) {
84
+ throw new Error("Missing value for --api-key-env");
85
+ }
86
+ apiKeyEnv = value;
87
+ }
88
+ else if (arg === "--api-key") {
89
+ throw new Error("Raw --api-key is not supported; use a prompt or --api-key-env <VAR>");
90
+ }
91
+ else if (arg.startsWith("-")) {
92
+ throw new Error(`Unknown flag: ${arg}`);
93
+ }
94
+ else {
95
+ positionals.push(arg);
96
+ }
97
+ }
98
+ return { subcommand, help, providerFlag, type, apiKeyEnv, positionals };
99
+ }
100
+ function resolveLoginType(provider, type) {
101
+ if (provider.supportsApiKey && provider.supportsOauth) {
102
+ if (type === undefined) {
103
+ throw new Error("Provider supports both api_key and oauth; pass --type api_key|oauth");
104
+ }
105
+ return type;
106
+ }
107
+ if (type === "api_key") {
108
+ if (!provider.supportsApiKey) {
109
+ throw new Error("Provider does not support api_key login");
110
+ }
111
+ return "api_key";
112
+ }
113
+ if (type === "oauth") {
114
+ if (!provider.supportsOauth) {
115
+ throw new Error("Provider does not support oauth login");
116
+ }
117
+ return "oauth";
118
+ }
119
+ if (provider.supportsOauth)
120
+ return "oauth";
121
+ if (provider.supportsApiKey)
122
+ return "api_key";
123
+ throw new Error("Provider does not support interactive login");
124
+ }
125
+ export async function runProvidersCommand(args, cwd, io = {}, ctx = defaultContext) {
126
+ const out = { ...defaultIo, ...io };
127
+ let secrets = [];
128
+ try {
129
+ const parsed = parseProvidersArgs(args);
130
+ if (parsed.help || !parsed.subcommand) {
131
+ out.error(PROVIDERS_USAGE);
132
+ return parsed.help ? 0 : 1;
133
+ }
134
+ switch (parsed.subcommand) {
135
+ case "list": {
136
+ const listed = await listProviders(cwd, ctx);
137
+ for (const provider of listed.providers) {
138
+ out.log(formatProvider(provider));
139
+ }
140
+ return 0;
141
+ }
142
+ case "status": {
143
+ try {
144
+ const status = await getAuthStatus(cwd, parsed.providerFlag, ctx);
145
+ if (Array.isArray(status)) {
146
+ for (const row of status)
147
+ out.log(formatStatus(row));
148
+ }
149
+ else {
150
+ out.log(formatStatus(status));
151
+ }
152
+ return 0;
153
+ }
154
+ catch (err) {
155
+ if (err instanceof ProviderAuthError && err.status === 404) {
156
+ out.error("Provider not found");
157
+ return 1;
158
+ }
159
+ throw err;
160
+ }
161
+ }
162
+ case "detect": {
163
+ const detected = detectPiHome(cwd);
164
+ const sourceLine = detected.credentialSource !== undefined
165
+ ? `credentialSource=${detected.credentialSource}`
166
+ : "credentialSource=(unset)";
167
+ out.log([
168
+ `piHomeUsable=${detected.piHomeUsable}`,
169
+ sourceLine,
170
+ `provisional=${detected.provisional}`,
171
+ `bindingSource=${detected.source}`,
172
+ ].join("\t"));
173
+ return 0;
174
+ }
175
+ case "source": {
176
+ const action = parsed.positionals[0] ?? "get";
177
+ if (action === "get") {
178
+ const settings = getCredentialSourceSettings(cwd);
179
+ if (settings.credentialSource !== undefined) {
180
+ out.log(settings.credentialSource);
181
+ }
182
+ else {
183
+ out.log(`(unset; binding=${settings.binding.source}${settings.binding.provisional ? ", provisional" : ""})`);
184
+ }
185
+ return 0;
186
+ }
187
+ if (action === "set") {
188
+ const value = parsed.positionals[1];
189
+ if (value !== "pi_home" && value !== "sf_owned") {
190
+ out.error('source set requires pi_home or sf_owned');
191
+ out.error(PROVIDERS_USAGE);
192
+ return 1;
193
+ }
194
+ const result = setCredentialSource(cwd, value);
195
+ out.log(result.credentialSource);
196
+ return 0;
197
+ }
198
+ out.error(`Unknown source action: ${action}`);
199
+ out.error(PROVIDERS_USAGE);
200
+ return 1;
201
+ }
202
+ case "login": {
203
+ const providerId = parsed.positionals[0];
204
+ if (!providerId) {
205
+ out.error("Missing providerId");
206
+ out.error(PROVIDERS_USAGE);
207
+ return 1;
208
+ }
209
+ const listed = await listProviders(cwd, ctx);
210
+ const provider = listed.providers.find((p) => p.id === providerId);
211
+ if (!provider) {
212
+ out.error("Provider not found");
213
+ return 1;
214
+ }
215
+ const loginType = resolveLoginType(provider, parsed.type);
216
+ if (loginType === "api_key") {
217
+ let apiKey;
218
+ if (parsed.apiKeyEnv !== undefined) {
219
+ apiKey = readApiKeyFromEnv(out.env, parsed.apiKeyEnv);
220
+ }
221
+ else {
222
+ apiKey = await out.readSecret(`API key for ${providerId}`);
223
+ }
224
+ secrets = [apiKey];
225
+ if (apiKey.trim().length === 0) {
226
+ out.error("API key is required");
227
+ return 1;
228
+ }
229
+ const status = await loginWithApiKey(cwd, providerId, apiKey, ctx);
230
+ out.log(formatStatus(status));
231
+ return 0;
232
+ }
233
+ const abort = new AbortController();
234
+ const onSigInt = () => abort.abort();
235
+ process.once("SIGINT", onSigInt);
236
+ try {
237
+ const interaction = out.createAuthInteraction(abort.signal);
238
+ const result = await loginWithOauth(cwd, providerId, interaction, ctx);
239
+ if (result.warning) {
240
+ out.error(result.warning);
241
+ }
242
+ out.log(formatStatus(result.provider));
243
+ return 0;
244
+ }
245
+ finally {
246
+ process.removeListener("SIGINT", onSigInt);
247
+ }
248
+ }
249
+ case "logout": {
250
+ const providerId = parsed.positionals[0];
251
+ if (!providerId) {
252
+ out.error("Missing providerId");
253
+ out.error(PROVIDERS_USAGE);
254
+ return 1;
255
+ }
256
+ const status = await logoutProvider(cwd, providerId, ctx);
257
+ out.log(formatStatus(status));
258
+ return 0;
259
+ }
260
+ default:
261
+ out.error(`Unknown providers subcommand: ${parsed.subcommand}`);
262
+ out.error(PROVIDERS_USAGE);
263
+ return 1;
264
+ }
265
+ }
266
+ catch (err) {
267
+ const raw = err instanceof Error
268
+ ? err.message
269
+ : typeof err === "string"
270
+ ? err
271
+ : "Provider command failed";
272
+ out.error(scrub(raw, secrets));
273
+ return 1;
274
+ }
275
+ }
@@ -0,0 +1,8 @@
1
+ import type { AuthInteraction } from "@earendil-works/pi-ai";
2
+ export type TerminalAuthInteractionOptions = {
3
+ signal?: AbortSignal;
4
+ write?: (line: string) => void;
5
+ promptLine?: (message: string) => Promise<string>;
6
+ promptSecretLine?: (message: string) => Promise<string>;
7
+ };
8
+ export declare function createTerminalAuthInteraction(options?: TerminalAuthInteractionOptions): AuthInteraction;
@@ -0,0 +1,65 @@
1
+ import * as readline from "node:readline";
2
+ import { promptSecret } from "./terminalSecret.js";
3
+ function defaultPromptLine(message) {
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stderr,
7
+ });
8
+ return new Promise((resolve, reject) => {
9
+ rl.question(`${message}: `, (answer) => {
10
+ rl.close();
11
+ resolve(answer);
12
+ });
13
+ rl.on("close", () => {
14
+ // no-op; question callback handles resolve
15
+ });
16
+ rl.on("SIGINT", () => {
17
+ rl.close();
18
+ reject(new Error("Login cancelled"));
19
+ });
20
+ });
21
+ }
22
+ export function createTerminalAuthInteraction(options = {}) {
23
+ const write = options.write ?? ((line) => {
24
+ process.stderr.write(`${line}\n`);
25
+ });
26
+ const promptLine = options.promptLine ?? defaultPromptLine;
27
+ const promptSecretLine = options.promptSecretLine ?? promptSecret;
28
+ return {
29
+ signal: options.signal,
30
+ notify(event) {
31
+ if (event.type === "info" || event.type === "progress") {
32
+ write(event.message);
33
+ return;
34
+ }
35
+ if (event.type === "auth_url") {
36
+ if (event.instructions)
37
+ write(event.instructions);
38
+ write(`Open: ${event.url}`);
39
+ return;
40
+ }
41
+ if (event.type === "device_code") {
42
+ write(`Visit: ${event.verificationUri}`);
43
+ write(`Code: ${event.userCode}`);
44
+ }
45
+ },
46
+ async prompt(prompt) {
47
+ if (options.signal?.aborted || prompt.signal?.aborted) {
48
+ throw new Error("Login cancelled");
49
+ }
50
+ if (prompt.type === "secret") {
51
+ return promptSecretLine(prompt.message);
52
+ }
53
+ if (prompt.type === "select") {
54
+ write(prompt.message);
55
+ for (const opt of prompt.options) {
56
+ const desc = opt.description !== undefined ? ` — ${opt.description}` : "";
57
+ write(` [${opt.id}] ${opt.label}${desc}`);
58
+ }
59
+ return promptLine("Select option id");
60
+ }
61
+ const suffix = prompt.placeholder !== undefined ? ` (${prompt.placeholder})` : "";
62
+ return promptLine(`${prompt.message}${suffix}`);
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,2 @@
1
+ export declare function readApiKeyFromEnv(env: NodeJS.ProcessEnv, varName: string): string;
2
+ export declare function promptSecret(message: string): Promise<string>;
@@ -0,0 +1,67 @@
1
+ import { stdin as input, stderr as output } from "node:process";
2
+ export function readApiKeyFromEnv(env, varName) {
3
+ if (typeof varName !== "string" || varName.length === 0) {
4
+ throw new Error("Missing value for --api-key-env");
5
+ }
6
+ const value = env[varName];
7
+ if (typeof value !== "string" || value.length === 0) {
8
+ throw new Error(`Environment variable ${varName} is not set`);
9
+ }
10
+ return value;
11
+ }
12
+ export function promptSecret(message) {
13
+ return new Promise((resolve, reject) => {
14
+ output.write(`${message}: `);
15
+ if (!input.isTTY || typeof input.setRawMode !== "function") {
16
+ let buf = "";
17
+ const onData = (chunk) => {
18
+ buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
19
+ if (buf.includes("\n") || buf.includes("\r")) {
20
+ cleanup();
21
+ resolve(buf.replace(/\r?\n$/, ""));
22
+ }
23
+ };
24
+ const onEnd = () => {
25
+ cleanup();
26
+ resolve(buf);
27
+ };
28
+ const cleanup = () => {
29
+ input.removeListener("data", onData);
30
+ input.removeListener("end", onEnd);
31
+ input.pause();
32
+ };
33
+ input.resume();
34
+ input.on("data", onData);
35
+ input.once("end", onEnd);
36
+ return;
37
+ }
38
+ input.setRawMode(true);
39
+ input.resume();
40
+ input.setEncoding("utf8");
41
+ let buf = "";
42
+ const onData = (ch) => {
43
+ if (ch === "\n" || ch === "\r" || ch === "\u0004") {
44
+ cleanup();
45
+ output.write("\n");
46
+ resolve(buf);
47
+ }
48
+ else if (ch === "\u0003") {
49
+ cleanup();
50
+ output.write("\n");
51
+ reject(new Error("Cancelled"));
52
+ }
53
+ else if (ch === "\u007f" || ch === "\b") {
54
+ buf = buf.slice(0, -1);
55
+ }
56
+ else if (ch >= " ") {
57
+ buf += ch;
58
+ }
59
+ };
60
+ const cleanup = () => {
61
+ input.setRawMode(false);
62
+ input.pause();
63
+ input.removeListener("data", onData);
64
+ };
65
+ input.on("data", onData);
66
+ });
67
+ }
@@ -0,0 +1,11 @@
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]";
3
+ export type ValidateCommandIo = {
4
+ log: (line: string) => void;
5
+ error: (line: string) => void;
6
+ };
7
+ export declare function runValidateCommand(args: string[], options?: {
8
+ cwd?: string;
9
+ io?: Partial<ValidateCommandIo>;
10
+ validateCatalog?: (options: Parameters<typeof defaultValidateCatalog>[0]) => Promise<ValidationResult>;
11
+ }): Promise<number>;
@@ -0,0 +1,81 @@
1
+ import { validateCatalog as defaultValidateCatalog, } from "../config/validateCatalog.js";
2
+ import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
3
+ export const VALIDATE_USAGE = `Usage:
4
+ sf validate [--pipeline <name-or-path>] [--strict] [--json]`;
5
+ const defaultIo = {
6
+ log: (line) => console.log(line),
7
+ error: (line) => console.error(line),
8
+ };
9
+ function parseValidateArgs(args) {
10
+ if (args.length === 0) {
11
+ return { help: false, strict: false, json: false };
12
+ }
13
+ if (args[0] === "--help" || args[0] === "-h") {
14
+ return { help: true, strict: false, json: false };
15
+ }
16
+ let pipeline;
17
+ let strict = false;
18
+ let json = false;
19
+ let help = false;
20
+ for (let i = 0; i < args.length; i++) {
21
+ const arg = args[i];
22
+ if (arg === "--help" || arg === "-h") {
23
+ help = true;
24
+ }
25
+ else if (arg === "--pipeline") {
26
+ const value = args[++i];
27
+ if (value === undefined || value.length === 0) {
28
+ throw new Error("Missing value for --pipeline");
29
+ }
30
+ pipeline = value;
31
+ }
32
+ else if (arg === "--strict") {
33
+ strict = true;
34
+ }
35
+ else if (arg === "--json") {
36
+ json = true;
37
+ }
38
+ else if (arg.startsWith("-")) {
39
+ throw new Error(`Unknown flag: ${arg}`);
40
+ }
41
+ else {
42
+ throw new Error(`Unexpected argument: ${arg}`);
43
+ }
44
+ }
45
+ return { help, pipeline, strict, json };
46
+ }
47
+ export async function runValidateCommand(args, options = {}) {
48
+ const cwd = options.cwd ?? process.cwd();
49
+ const out = { ...defaultIo, ...options.io };
50
+ const validateCatalogFn = options.validateCatalog ?? defaultValidateCatalog;
51
+ try {
52
+ const parsed = parseValidateArgs(args);
53
+ if (parsed.help) {
54
+ out.error(VALIDATE_USAGE);
55
+ return 0;
56
+ }
57
+ const result = await validateCatalogFn({
58
+ scope: parsed.pipeline ? "pipeline" : "full",
59
+ cwd,
60
+ pipeline: parsed.pipeline,
61
+ strict: parsed.strict,
62
+ });
63
+ if (parsed.json) {
64
+ out.log(formatValidationJson(result));
65
+ }
66
+ else {
67
+ out.log(formatValidationHuman(result, { strict: parsed.strict }));
68
+ }
69
+ return exitCodeForValidation(result);
70
+ }
71
+ catch (err) {
72
+ const message = err instanceof Error
73
+ ? err.message
74
+ : typeof err === "string"
75
+ ? err
76
+ : "Validate command failed";
77
+ out.error(message);
78
+ out.error(VALIDATE_USAGE);
79
+ return 1;
80
+ }
81
+ }
@@ -0,0 +1,8 @@
1
+ import type { ValidationResult } from "../config/validateCatalog.js";
2
+ export declare const VALIDATION_SCOPE_LINE = "Scope: pipeline and stage YAML only (task, checkout, providers not checked).";
3
+ export declare const VALIDATION_CHECKS = "pipeline and stage YAML only";
4
+ export declare function formatValidationHuman(result: ValidationResult, options?: {
5
+ strict?: boolean;
6
+ }): string;
7
+ export declare function formatValidationJson(result: ValidationResult): string;
8
+ export declare function exitCodeForValidation(result: ValidationResult): 0 | 1;
@@ -0,0 +1,75 @@
1
+ import { effectiveSeverity } from "../config/validateCatalog.js";
2
+ export const VALIDATION_SCOPE_LINE = "Scope: pipeline and stage YAML only (task, checkout, providers not checked).";
3
+ export const VALIDATION_CHECKS = "pipeline and stage YAML only";
4
+ function effectiveSeverityRank(finding, strict) {
5
+ return effectiveSeverity(finding, strict) === "error" ? 0 : 1;
6
+ }
7
+ function orderedPaths(findings) {
8
+ const paths = [];
9
+ const seen = new Set();
10
+ for (const finding of findings) {
11
+ if (seen.has(finding.path))
12
+ continue;
13
+ seen.add(finding.path);
14
+ paths.push(finding.path);
15
+ }
16
+ return paths;
17
+ }
18
+ function findingsForPath(findings, filePath, strict) {
19
+ return findings
20
+ .filter((finding) => finding.path === filePath)
21
+ .sort((a, b) => {
22
+ const sevCmp = effectiveSeverityRank(a, strict) - effectiveSeverityRank(b, strict);
23
+ if (sevCmp !== 0)
24
+ return sevCmp;
25
+ return a.message.localeCompare(b.message);
26
+ });
27
+ }
28
+ function formatSummary(result) {
29
+ const { errors, warnings } = result.summary;
30
+ if (result.ok) {
31
+ if (result.findings.length === 0) {
32
+ return "Validation passed.";
33
+ }
34
+ return `Validation passed: ${errors} error(s), ${warnings} warning(s).`;
35
+ }
36
+ return `Validation failed: ${errors} error(s), ${warnings} warning(s).`;
37
+ }
38
+ export function formatValidationHuman(result, options = {}) {
39
+ const strict = options.strict ?? false;
40
+ const lines = [VALIDATION_SCOPE_LINE];
41
+ if (result.findings.length > 0) {
42
+ lines.push("");
43
+ for (const filePath of orderedPaths(result.findings)) {
44
+ lines.push(filePath);
45
+ for (const finding of findingsForPath(result.findings, filePath, strict)) {
46
+ const severity = effectiveSeverity(finding, strict);
47
+ lines.push(`${severity}: ${finding.message}`);
48
+ }
49
+ lines.push("");
50
+ }
51
+ lines.pop();
52
+ }
53
+ lines.push("");
54
+ lines.push(formatSummary(result));
55
+ return lines.join("\n");
56
+ }
57
+ export function formatValidationJson(result) {
58
+ const payload = {
59
+ ok: result.ok,
60
+ scope: result.scope,
61
+ checks: VALIDATION_CHECKS,
62
+ summary: result.summary,
63
+ findings: result.findings.map((finding) => ({
64
+ severity: finding.severity,
65
+ category: finding.category,
66
+ code: finding.code,
67
+ file: finding.path,
68
+ message: finding.message,
69
+ })),
70
+ };
71
+ return JSON.stringify(payload, null, 2);
72
+ }
73
+ export function exitCodeForValidation(result) {
74
+ return result.ok ? 0 : 1;
75
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};