@kici-dev/compiler 0.0.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1 -6
  3. package/dist/assets/api-TJJVHrjC.json +118 -0
  4. package/dist/assets/descriptor-BTtjzN9L.json +1382 -0
  5. package/dist/assets/package-BpQF9kR8.json +74 -0
  6. package/dist/assets/package-Ceo2h27X.json +89 -0
  7. package/dist/assets/source_context-D0atuL28.json +20 -0
  8. package/dist/assets/type-BFqO8SCZ.json +202 -0
  9. package/dist/auth/headless-detect.d.ts +14 -0
  10. package/dist/auth/headless-detect.js +25 -0
  11. package/dist/chunk-gOLHoazu.js +4 -0
  12. package/dist/cli.d.ts +3 -0
  13. package/dist/cli.js +193 -0
  14. package/dist/commands/cancel.d.ts +22 -0
  15. package/dist/commands/cancel.js +124 -0
  16. package/dist/commands/compile.d.ts +17 -0
  17. package/dist/commands/compile.js +99 -0
  18. package/dist/commands/docs.d.ts +26 -0
  19. package/dist/commands/docs.js +60 -0
  20. package/dist/commands/drain-worker.d.ts +19 -0
  21. package/dist/commands/drain-worker.js +45 -0
  22. package/dist/commands/endpoints.d.ts +53 -0
  23. package/dist/commands/endpoints.js +185 -0
  24. package/dist/commands/fixture.d.ts +14 -0
  25. package/dist/commands/fixture.js +104 -0
  26. package/dist/commands/hook.d.ts +20 -0
  27. package/dist/commands/hook.js +97 -0
  28. package/dist/commands/index.d.ts +33 -0
  29. package/dist/commands/index.js +20 -0
  30. package/dist/commands/init.d.ts +35 -0
  31. package/dist/commands/init.js +393 -0
  32. package/dist/commands/login.d.ts +23 -0
  33. package/dist/commands/login.js +144 -0
  34. package/dist/commands/logout.d.ts +18 -0
  35. package/dist/commands/logout.js +55 -0
  36. package/dist/commands/org.d.ts +27 -0
  37. package/dist/commands/org.js +126 -0
  38. package/dist/commands/run.d.ts +21 -0
  39. package/dist/commands/run.js +562 -0
  40. package/dist/commands/secrets-list.d.ts +16 -0
  41. package/dist/commands/secrets-list.js +85 -0
  42. package/dist/commands/status.d.ts +30 -0
  43. package/dist/commands/status.js +210 -0
  44. package/dist/commands/test.d.ts +61 -0
  45. package/dist/commands/test.js +220 -0
  46. package/dist/commands/types.d.ts +18 -0
  47. package/dist/commands/types.js +74 -0
  48. package/dist/commands/watch.d.ts +14 -0
  49. package/dist/commands/watch.js +97 -0
  50. package/dist/commands/workflows.d.ts +28 -0
  51. package/dist/commands/workflows.js +154 -0
  52. package/dist/comment-created.json +30 -0
  53. package/dist/create-branch.json +17 -0
  54. package/dist/delete-branch.json +15 -0
  55. package/dist/dispatch.json +15 -0
  56. package/dist/errors/capability-gap.d.ts +50 -0
  57. package/dist/errors/capability-gap.js +54 -0
  58. package/dist/errors/codes.d.ts +25 -0
  59. package/dist/errors/formatter.d.ts +32 -0
  60. package/dist/errors/formatter.js +35 -0
  61. package/dist/errors/index.d.ts +5 -0
  62. package/dist/errors/index.js +4 -0
  63. package/dist/execution/executor.d.ts +61 -0
  64. package/dist/execution/executor.js +230 -0
  65. package/dist/execution/index.d.ts +4 -0
  66. package/dist/execution/index.js +3 -0
  67. package/dist/execution/sdk-alias.d.ts +33 -0
  68. package/dist/execution/sdk-alias.js +102 -0
  69. package/dist/fixtures/compiler.d.ts +47 -0
  70. package/dist/fixtures/compiler.js +177 -0
  71. package/dist/fixtures/defaults/comment-created.json +30 -0
  72. package/dist/fixtures/defaults/create-branch.json +17 -0
  73. package/dist/fixtures/defaults/delete-branch.json +15 -0
  74. package/dist/fixtures/defaults/dispatch.json +15 -0
  75. package/dist/fixtures/defaults/fork.json +19 -0
  76. package/dist/fixtures/defaults/index.d.ts +16 -0
  77. package/dist/fixtures/defaults/index.js +105 -0
  78. package/dist/fixtures/defaults/index.ts +145 -0
  79. package/dist/fixtures/defaults/pr-closed.json +28 -0
  80. package/dist/fixtures/defaults/pr-opened.json +27 -0
  81. package/dist/fixtures/defaults/pr-reopened.json +27 -0
  82. package/dist/fixtures/defaults/pr-synchronize.json +27 -0
  83. package/dist/fixtures/defaults/push.json +16 -0
  84. package/dist/fixtures/defaults/release-published.json +23 -0
  85. package/dist/fixtures/defaults/review-comment-created.json +36 -0
  86. package/dist/fixtures/defaults/review-submitted.json +35 -0
  87. package/dist/fixtures/defaults/star-created.json +15 -0
  88. package/dist/fixtures/defaults/status.json +26 -0
  89. package/dist/fixtures/defaults/tag-push.json +20 -0
  90. package/dist/fixtures/defaults/watch-started.json +15 -0
  91. package/dist/fixtures/defaults/workflow-run-completed.json +23 -0
  92. package/dist/fork.json +19 -0
  93. package/dist/format.d.ts +5 -0
  94. package/dist/format.js +20 -0
  95. package/dist/generators/secrets-dts.d.ts +29 -0
  96. package/dist/generators/secrets-dts.js +67 -0
  97. package/dist/hooks/detector.d.ts +39 -0
  98. package/dist/hooks/detector.js +133 -0
  99. package/dist/hooks/index.d.ts +6 -0
  100. package/dist/hooks/index.js +5 -0
  101. package/dist/hooks/installer.d.ts +24 -0
  102. package/dist/hooks/installer.js +328 -0
  103. package/dist/hooks/templates.d.ts +26 -0
  104. package/dist/hooks/templates.js +63 -0
  105. package/dist/index.d.ts +12 -0
  106. package/dist/index.js +12 -0
  107. package/dist/llm-context/llms-full.txt +9974 -0
  108. package/dist/llm-context/llms.txt +61 -0
  109. package/dist/local-executor/dag-scheduler.d.ts +44 -0
  110. package/dist/local-executor/dag-scheduler.js +183 -0
  111. package/dist/local-executor/index.d.ts +23 -0
  112. package/dist/local-executor/index.js +309 -0
  113. package/dist/local-executor/job-runner.d.ts +40 -0
  114. package/dist/local-executor/job-runner.js +307 -0
  115. package/dist/local-executor/output-streamer.d.ts +31 -0
  116. package/dist/local-executor/output-streamer.js +166 -0
  117. package/dist/local-executor/payload-generator.d.ts +16 -0
  118. package/dist/local-executor/payload-generator.js +138 -0
  119. package/dist/local-executor/picker.d.ts +33 -0
  120. package/dist/local-executor/picker.js +109 -0
  121. package/dist/local-executor/secret-loader.d.ts +18 -0
  122. package/dist/local-executor/secret-loader.js +105 -0
  123. package/dist/local-executor/types.d.ts +80 -0
  124. package/dist/local-executor/types.js +2 -0
  125. package/dist/local-executor/workflow-lock.d.ts +81 -0
  126. package/dist/local-executor/workflow-lock.js +0 -0
  127. package/dist/lockfile/generator.d.ts +39 -0
  128. package/dist/lockfile/generator.js +688 -0
  129. package/dist/lockfile/hash-files.d.ts +14 -0
  130. package/dist/lockfile/hash-files.js +50 -0
  131. package/dist/lockfile/hasher.d.ts +36 -0
  132. package/dist/lockfile/hasher.js +46 -0
  133. package/dist/lockfile/index.d.ts +3 -0
  134. package/dist/lockfile/index.js +4 -0
  135. package/dist/lockfile/purity-analyzer.d.ts +25 -0
  136. package/dist/lockfile/purity-analyzer.js +204 -0
  137. package/dist/package-F7UXSDHW.json +74 -0
  138. package/dist/postinstall.d.ts +9 -0
  139. package/dist/postinstall.js +60 -0
  140. package/dist/pr-closed.json +28 -0
  141. package/dist/pr-opened.json +27 -0
  142. package/dist/pr-reopened.json +27 -0
  143. package/dist/pr-synchronize.json +27 -0
  144. package/dist/push.json +16 -0
  145. package/dist/release-published.json +23 -0
  146. package/dist/remote/client.d.ts +204 -0
  147. package/dist/remote/client.js +203 -0
  148. package/dist/remote/config.d.ts +54 -0
  149. package/dist/remote/config.js +92 -0
  150. package/dist/remote/encryption.d.ts +35 -0
  151. package/dist/remote/encryption.js +87 -0
  152. package/dist/remote/history.d.ts +91 -0
  153. package/dist/remote/history.js +146 -0
  154. package/dist/remote/oauth.d.ts +65 -0
  155. package/dist/remote/oauth.js +302 -0
  156. package/dist/remote/observer.d.ts +81 -0
  157. package/dist/remote/observer.js +174 -0
  158. package/dist/remote/output/json.d.ts +13 -0
  159. package/dist/remote/output/json.js +36 -0
  160. package/dist/remote/output/junit.d.ts +18 -0
  161. package/dist/remote/output/junit.js +50 -0
  162. package/dist/remote/output/streaming.d.ts +57 -0
  163. package/dist/remote/output/streaming.js +124 -0
  164. package/dist/remote/output/summary.d.ts +39 -0
  165. package/dist/remote/output/summary.js +99 -0
  166. package/dist/remote/uploader.d.ts +87 -0
  167. package/dist/remote/uploader.js +197 -0
  168. package/dist/review-comment-created.json +36 -0
  169. package/dist/review-submitted.json +35 -0
  170. package/dist/star-created.json +15 -0
  171. package/dist/status.json +26 -0
  172. package/dist/tag-push.json +20 -0
  173. package/dist/templates/agents-md.d.ts +8 -0
  174. package/dist/templates/agents-md.js +130 -0
  175. package/dist/templates/index.d.ts +43 -0
  176. package/dist/templates/index.js +48 -0
  177. package/dist/templates/package-json.d.ts +18 -0
  178. package/dist/templates/package-json.js +34 -0
  179. package/dist/templates/tsconfig-json.d.ts +9 -0
  180. package/dist/templates/tsconfig-json.js +26 -0
  181. package/dist/templates/workflows/hello-world.d.ts +2 -0
  182. package/dist/templates/workflows/hello-world.js +16 -0
  183. package/dist/templates/workflows/hello-world.ts +21 -0
  184. package/dist/templates/workflows/pr-checks.d.ts +2 -0
  185. package/dist/templates/workflows/pr-checks.js +40 -0
  186. package/dist/templates/workflows/pr-checks.ts +57 -0
  187. package/dist/test-runner/dry-run.d.ts +12 -0
  188. package/dist/test-runner/dry-run.js +66 -0
  189. package/dist/test-runner/event-types.d.ts +86 -0
  190. package/dist/test-runner/event-types.js +330 -0
  191. package/dist/test-runner/git-detector.d.ts +15 -0
  192. package/dist/test-runner/git-detector.js +30 -0
  193. package/dist/test-runner/index.d.ts +13 -0
  194. package/dist/test-runner/index.js +11 -0
  195. package/dist/test-runner/job-executor.d.ts +48 -0
  196. package/dist/test-runner/job-executor.js +245 -0
  197. package/dist/test-runner/output-formatter.d.ts +47 -0
  198. package/dist/test-runner/output-formatter.js +92 -0
  199. package/dist/test-runner/payload-builder.d.ts +20 -0
  200. package/dist/test-runner/payload-builder.js +216 -0
  201. package/dist/test-runner/rule-evaluator.d.ts +14 -0
  202. package/dist/test-runner/rule-evaluator.js +31 -0
  203. package/dist/test-runner/secrets-file.d.ts +24 -0
  204. package/dist/test-runner/secrets-file.js +62 -0
  205. package/dist/test-runner/step-context.d.ts +15 -0
  206. package/dist/test-runner/step-context.js +140 -0
  207. package/dist/test-runner/summary.d.ts +7 -0
  208. package/dist/types.d.ts +525 -0
  209. package/dist/types.js +33 -0
  210. package/dist/validation/index.d.ts +3 -0
  211. package/dist/validation/index.js +3 -0
  212. package/dist/validation/validator.d.ts +26 -0
  213. package/dist/validation/validator.js +164 -0
  214. package/dist/watch-started.json +15 -0
  215. package/dist/workflow-run-completed.json +23 -0
  216. package/dist/workflows/hello-world.ts +21 -0
  217. package/dist/workflows/pr-checks.ts +57 -0
  218. package/hack/postinstall.mjs +105 -0
  219. package/package.json +60 -6
  220. package/sbom.spdx.json +10997 -0
  221. package/index.js +0 -3
@@ -0,0 +1,562 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ import { resolveKiciDir } from "../execution/executor.js";
3
+ import "../execution/index.js";
4
+ import { loadGlobalConfig } from "../remote/config.js";
5
+ import { AuthenticationError, ConnectionError, OrchestratorClient } from "../remote/client.js";
6
+ import { compileFixtures, filterFixtures } from "../fixtures/compiler.js";
7
+ import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/uploader.js";
8
+ import { ObserverClient } from "../remote/observer.js";
9
+ import { StreamingFormatter } from "../remote/output/streaming.js";
10
+ import { formatErrorHighlight, formatMultiFixtureSummary, formatSummary } from "../remote/output/summary.js";
11
+ import { formatJsonResult } from "../remote/output/json.js";
12
+ import { formatJunitResult } from "../remote/output/junit.js";
13
+ import { RunHistory } from "../remote/history.js";
14
+ import pc from "picocolors";
15
+ import { readFile, writeFile } from "node:fs/promises";
16
+ import path from "node:path";
17
+ import { formatBytes, logger, toErrorMessage } from "@kici-dev/shared";
18
+ //#region src/commands/run.ts
19
+ /**
20
+ * Run a workflow locally using the local executor.
21
+ * Thin wrapper that delegates to executeLocal from local-executor.
22
+ *
23
+ * Exit codes: 0 = success, 1 = job failure, 2 = config/compilation error
24
+ */
25
+ async function runLocalCommand(options) {
26
+ if (options.debug) {
27
+ process.env.KICI_DEBUG = "true";
28
+ if (!options.quiet) logger.info(pc.gray("Debug mode enabled"));
29
+ }
30
+ try {
31
+ const { executeLocal } = await import("../local-executor/index.js");
32
+ return await executeLocal(options);
33
+ } catch (error) {
34
+ if (error instanceof Error && (error.message.includes("Cannot find module") || error.message.includes("ERR_MODULE_NOT_FOUND"))) {
35
+ logger.error(pc.red("Local executor not yet available. The local execution engine is under development."));
36
+ return false;
37
+ }
38
+ const message = toErrorMessage(error);
39
+ logger.error(pc.red(`\nError: ${message}\n`));
40
+ if (options.debug && error instanceof Error && error.stack) logger.error(pc.gray(error.stack));
41
+ return false;
42
+ }
43
+ }
44
+ /**
45
+ * Run fixtures remotely against the orchestrator.
46
+ * Migrated from test.ts — full remote execution functionality.
47
+ */
48
+ async function runRemoteCommand(fixture, options) {
49
+ if (options.debug) {
50
+ process.env.KICI_DEBUG = "true";
51
+ if (!options.quiet) logger.info(pc.gray("Debug mode enabled"));
52
+ }
53
+ try {
54
+ if (options.workflow && !fixture && !options.all) return await runDirectWorkflow(options.workflow, options);
55
+ const kiciDir = resolveKiciDir(options.kiciDir);
56
+ if (options.history) {
57
+ const history = new RunHistory();
58
+ await history.load();
59
+ const entries = history.getEntries({ limit: 20 });
60
+ console.log(history.formatTable(entries));
61
+ return true;
62
+ }
63
+ const fixtures = await compileFixtures(path.join(kiciDir, "tests"));
64
+ if (!fixture && !options.all) return listFixtures(fixtures);
65
+ let selected;
66
+ if (options.all) selected = fixtures;
67
+ else selected = filterFixtures(fixtures, fixture);
68
+ if (selected.length === 0) {
69
+ logger.info(pc.yellow(`No fixtures matched: ${fixture ?? "(none)"}`));
70
+ logger.info(pc.gray("Run `kici run remote` with no arguments to list available fixtures."));
71
+ return false;
72
+ }
73
+ return await runFixturesRemotely(selected, options);
74
+ } catch (error) {
75
+ const message = toErrorMessage(error);
76
+ logger.error(pc.red(`\nError: ${message}\n`));
77
+ if (options.debug && error instanceof Error && error.stack) logger.error(pc.gray(error.stack));
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * List available fixtures as a table.
83
+ */
84
+ function listFixtures(fixtures) {
85
+ if (fixtures.length === 0) {
86
+ logger.info(pc.yellow("No fixtures found."));
87
+ logger.info(pc.gray("Create fixture files in .kici/tests/ to get started."));
88
+ logger.info(pc.gray("Example: export const pushMain = fixture(\"push-main\", { event: push({ branches: [\"main\"] }) })"));
89
+ return true;
90
+ }
91
+ logger.info(pc.bold("\nAvailable fixtures:\n"));
92
+ const idWidth = Math.max(12, ...fixtures.map((f) => f.id.length)) + 2;
93
+ const header = ` ${pc.bold("ID".padEnd(idWidth))} ${pc.bold("Source".padEnd(40))} ${pc.bold("Event type")}`;
94
+ logger.info(header);
95
+ logger.info(` ${"".padEnd(idWidth, "-")} ${"".padEnd(40, "-")} ${"".padEnd(15, "-")}`);
96
+ for (const f of fixtures) {
97
+ const opts = typeof f.fixture.options === "function" ? null : f.fixture.options;
98
+ const eventType = opts?.event ? describeEvent(opts.event) : "(async)";
99
+ const source = path.relative(process.cwd(), f.sourceFile);
100
+ logger.info(` ${pc.cyan(f.id.padEnd(idWidth))} ${pc.gray(source.padEnd(40))} ${eventType}`);
101
+ }
102
+ logger.info(pc.gray(`\n ${fixtures.length} fixture(s) available`));
103
+ logger.info(pc.gray(" Run: kici run remote <fixture-name> or kici run remote --all\n"));
104
+ return true;
105
+ }
106
+ /**
107
+ * Describe the event type from a fixture's trigger config.
108
+ */
109
+ function describeEvent(event) {
110
+ if (!event || typeof event !== "object") return "unknown";
111
+ const e = event;
112
+ if (e._type === "push") return "push";
113
+ if (e._type === "pr") return `pr:${e.action ?? "open"}`;
114
+ if (typeof e._type === "string") return String(e._type);
115
+ return "custom";
116
+ }
117
+ /**
118
+ * Run fixtures remotely against the orchestrator.
119
+ */
120
+ async function runFixturesRemotely(fixtures, options) {
121
+ const config = await loadGlobalConfig();
122
+ if (!config.token) {
123
+ logger.error(pc.red("Not authenticated. Run `kici login` to set up your API key."));
124
+ return false;
125
+ }
126
+ const endpoint = config.endpoint ?? config.platformEndpoint;
127
+ if (!endpoint) {
128
+ logger.error(pc.red("No orchestrator endpoint configured. Run `kici login --endpoint <url>` to set one."));
129
+ return false;
130
+ }
131
+ if (options.json) options.quiet = true;
132
+ const client = new OrchestratorClient({
133
+ endpoint,
134
+ token: config.token
135
+ });
136
+ const history = new RunHistory();
137
+ await history.load();
138
+ if (!options.quiet) {
139
+ logger.info(pc.gray(`Orchestrator: ${endpoint}`));
140
+ logger.info(pc.gray(`Fixtures: ${fixtures.length} to run\n`));
141
+ }
142
+ const results = [];
143
+ if (options.parallel && fixtures.length > 1) {
144
+ const promises = fixtures.map((f) => runSingleFixture(f, client, options, config, history));
145
+ const settled = await Promise.allSettled(promises);
146
+ for (const s of settled) if (s.status === "fulfilled") results.push(s.value);
147
+ else results.push({
148
+ fixtureId: "unknown",
149
+ runId: "",
150
+ status: "error",
151
+ reason: s.reason instanceof Error ? s.reason.message : String(s.reason)
152
+ });
153
+ } else for (const f of fixtures) {
154
+ const result = await runSingleFixture(f, client, options, config, history);
155
+ results.push(result);
156
+ if (result.status === "failed" || result.status === "error") break;
157
+ }
158
+ const runResults = results.filter((r) => r.status === "success" || r.status === "failed" || r.status === "cancelled").map((r) => ({
159
+ fixtureId: r.fixtureId,
160
+ runId: r.runId,
161
+ status: r.status,
162
+ totalDurationMs: r.durationMs ?? 0,
163
+ jobs: r.jobs ?? []
164
+ }));
165
+ if (options.json) console.log(formatJsonResult(runResults));
166
+ else if (options.junit) {
167
+ const junitXml = formatJunitResult(runResults);
168
+ await writeFile(options.junit, junitXml);
169
+ if (!options.quiet) logger.info(pc.green(`JUnit XML written to ${options.junit}`));
170
+ } else if (!options.quiet || results.some((r) => r.status !== "success" && r.status !== "accepted")) if (runResults.length > 1) logger.info(formatMultiFixtureSummary(runResults));
171
+ else displayRemoteResults(results);
172
+ return results.every((r) => r.status === "accepted" || r.status === "success");
173
+ }
174
+ /**
175
+ * Run a single fixture remotely.
176
+ */
177
+ async function runSingleFixture(fixture, client, options, config, history) {
178
+ const opts = typeof fixture.fixture.options === "function" ? await fixture.fixture.options() : fixture.fixture.options;
179
+ if (!options.quiet) logger.info(pc.cyan(`\n--- ${fixture.id} ---`));
180
+ try {
181
+ const kiciDir = resolveKiciDir(options.kiciDir);
182
+ const repoRoot = path.resolve(kiciDir, "..");
183
+ if (!options.quiet) logger.info(pc.gray("Creating overlay tarball..."));
184
+ const { tarballPath, summary, hasRemote } = await createOverlayTarball(repoRoot);
185
+ let routingKey = options.routingKey ?? config.routingKey ?? "default";
186
+ if (!hasRemote) {
187
+ routingKey = `local:${path.basename(repoRoot)}`;
188
+ logger.warn("No remote detected -- steps that use git commands will fail (no .git directory)");
189
+ }
190
+ let inlineLockFile;
191
+ if (!hasRemote) inlineLockFile = await readFile(path.join(kiciDir, "kici.lock.json"), "utf-8");
192
+ if (!options.quiet) {
193
+ logger.info(pc.gray(`${summary.fileCount} files changed, ${summary.newFiles} new, ${summary.deletedFiles} deleted (${formatBytes(summary.compressedSize)} compressed)`));
194
+ const sizeWarning = getSizeWarning(summary.compressedSize);
195
+ if (sizeWarning) logger.info(pc.yellow(sizeWarning));
196
+ }
197
+ if (!options.quiet) logger.info(pc.gray("Initializing upload..."));
198
+ const upload = await client.initUpload({
199
+ routingKey,
200
+ sha: summary.sha,
201
+ fileCount: summary.fileCount,
202
+ compressedSize: summary.compressedSize
203
+ });
204
+ if (!options.quiet) logger.info(pc.gray("Uploading overlay..."));
205
+ const uploadResult = await uploadTarball({
206
+ tarballPath,
207
+ signedUrl: upload.signedUrl,
208
+ orchestratorPublicKey: Buffer.from(upload.publicKey, "base64")
209
+ });
210
+ const event = buildEventFromFixture(opts);
211
+ if (!hasRemote) {
212
+ const repoName = path.basename(repoRoot);
213
+ const repo = event.payload.repository;
214
+ event.payload.repository = {
215
+ ...repo ?? {},
216
+ full_name: `local/${repoName}`
217
+ };
218
+ }
219
+ if (!options.quiet) logger.info(pc.gray("Triggering test run..."));
220
+ const triggerResult = await client.triggerTest({
221
+ fixtureId: fixture.id,
222
+ event,
223
+ routingKey,
224
+ uploadId: upload.uploadId,
225
+ cliPublicKey: uploadResult.cliPublicKey.toString("base64"),
226
+ secrets: opts.secrets,
227
+ workflowName: opts.workflowName,
228
+ inlineLockFile,
229
+ fullRepo: !hasRemote || void 0
230
+ });
231
+ if (triggerResult.status === "rejected") {
232
+ if (!options.quiet) logger.info(pc.red(`Rejected: ${triggerResult.reason ?? "unknown reason"}`));
233
+ return {
234
+ fixtureId: fixture.id,
235
+ runId: triggerResult.runId,
236
+ status: "rejected",
237
+ reason: triggerResult.reason,
238
+ observeUrl: triggerResult.observeUrl
239
+ };
240
+ }
241
+ if (!options.quiet) {
242
+ logger.info(pc.green(`Run started: ${triggerResult.runId}`));
243
+ logger.info(pc.gray(`Observe: ${triggerResult.observeUrl}`));
244
+ }
245
+ const historyEndpoint = config.endpoint ?? config.platformEndpoint ?? "";
246
+ await history.addEntry({
247
+ runId: triggerResult.runId,
248
+ fixtureId: fixture.id,
249
+ status: "running",
250
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
251
+ endpoint: historyEndpoint
252
+ });
253
+ if (options.wait === false) return {
254
+ fixtureId: fixture.id,
255
+ runId: triggerResult.runId,
256
+ status: "accepted",
257
+ observeUrl: triggerResult.observeUrl
258
+ };
259
+ const result = await observeCompletion(fixture.id, triggerResult.runId, triggerResult.observeUrl, config.token, client, options);
260
+ await history.updateEntry(triggerResult.runId, {
261
+ status: result.status,
262
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
263
+ durationMs: result.durationMs,
264
+ jobs: result.jobs
265
+ });
266
+ return result;
267
+ } catch (error) {
268
+ if (error instanceof AuthenticationError) logger.error(pc.red("Authentication failed. Run `kici login` to re-authenticate."));
269
+ else if (error instanceof ConnectionError) logger.error(pc.red(`Connection failed: ${error.message}`));
270
+ return {
271
+ fixtureId: fixture.id,
272
+ runId: "",
273
+ status: "error",
274
+ reason: toErrorMessage(error)
275
+ };
276
+ }
277
+ }
278
+ /**
279
+ * Build a SimulatedEvent-compatible object from fixture options.
280
+ */
281
+ function buildEventFromFixture(opts) {
282
+ const eventObj = opts.event;
283
+ const type = String(eventObj._type ?? "push");
284
+ const action = eventObj.action ? String(eventObj.action) : void 0;
285
+ const targetBranch = opts.branch ?? "main";
286
+ return {
287
+ type,
288
+ action,
289
+ targetBranch,
290
+ sourceBranch: type === "pr" ? opts.branch ?? "feature/test" : void 0,
291
+ payload: {
292
+ ref: `refs/heads/${targetBranch}`,
293
+ repository: { full_name: opts.repo ?? "owner/repo" },
294
+ ...opts.sha && { after: opts.sha },
295
+ ...opts.pr && { number: opts.pr }
296
+ }
297
+ };
298
+ }
299
+ /**
300
+ * Observe run completion via WebSocket (primary path).
301
+ * Falls back to HTTP polling if the WS connection fails.
302
+ */
303
+ async function observeCompletion(fixtureId, runId, observeUrl, token, client, options) {
304
+ const formatter = new StreamingFormatter();
305
+ formatter.isQuiet = options.quiet ?? false;
306
+ const lastLinesByJob = /* @__PURE__ */ new Map();
307
+ const MAX_ERROR_LINES = 50;
308
+ let completionResult = null;
309
+ let fallbackToPolling = false;
310
+ const observer = new ObserverClient({
311
+ observeUrl,
312
+ token,
313
+ runId,
314
+ onLog: (msg) => {
315
+ for (const line of msg.lines) {
316
+ const output = formatter.formatLogLine(msg.jobName, msg.stepName, line);
317
+ if (output) process.stdout.write(output);
318
+ }
319
+ if (!lastLinesByJob.has(msg.jobName)) lastLinesByJob.set(msg.jobName, []);
320
+ const buffer = lastLinesByJob.get(msg.jobName);
321
+ buffer.push(...msg.lines);
322
+ while (buffer.length > MAX_ERROR_LINES) buffer.shift();
323
+ },
324
+ onStep: (msg) => {
325
+ const output = formatter.formatStepChange(msg.jobName, msg.stepName, msg.state, msg.durationMs);
326
+ if (output) process.stdout.write(output);
327
+ },
328
+ onStatus: (msg) => {
329
+ const output = formatter.formatStatusChange(msg.status, msg.jobName);
330
+ if (output) process.stdout.write(output);
331
+ },
332
+ onComplete: (msg) => {
333
+ completionResult = msg;
334
+ },
335
+ onError: (err) => {
336
+ if (options.debug) logger.error(pc.gray(`Observer error: ${err.message}`));
337
+ fallbackToPolling = true;
338
+ }
339
+ });
340
+ let cancelled = false;
341
+ const cancelHandler = async () => {
342
+ if (cancelled) return;
343
+ cancelled = true;
344
+ if (!options.quiet) logger.info(pc.yellow("\nCancelling test run..."));
345
+ observer.disconnect();
346
+ try {
347
+ const cancelResult = await client.cancelTest(runId);
348
+ if (!options.quiet) logger.info(pc.gray(`Cancel status: ${cancelResult.status}`));
349
+ } catch {}
350
+ };
351
+ process.on("SIGINT", cancelHandler);
352
+ try {
353
+ try {
354
+ await observer.connect();
355
+ } catch {
356
+ if (options.debug) logger.info(pc.gray("WebSocket connection failed, falling back to HTTP polling"));
357
+ process.removeListener("SIGINT", cancelHandler);
358
+ return await pollForCompletionFallback(fixtureId, runId, observeUrl, client, options);
359
+ }
360
+ formatter.startElapsedTimer();
361
+ let complete;
362
+ try {
363
+ complete = await observer.waitForCompletion();
364
+ } catch {
365
+ if (cancelled) return {
366
+ fixtureId,
367
+ runId,
368
+ status: "cancelled",
369
+ observeUrl
370
+ };
371
+ observer.disconnect();
372
+ process.removeListener("SIGINT", cancelHandler);
373
+ if (options.debug) logger.info(pc.gray("Observer disconnected, falling back to HTTP polling"));
374
+ return await pollForCompletionFallback(fixtureId, runId, observeUrl, client, options);
375
+ }
376
+ completionResult = complete;
377
+ formatter.stopElapsedTimer();
378
+ if (fallbackToPolling && !completionResult) {
379
+ observer.disconnect();
380
+ process.removeListener("SIGINT", cancelHandler);
381
+ if (options.debug) logger.info(pc.gray("Observer errored, falling back to HTTP polling"));
382
+ return await pollForCompletionFallback(fixtureId, runId, observeUrl, client, options);
383
+ }
384
+ const runResult = {
385
+ fixtureId,
386
+ runId,
387
+ status: complete.status,
388
+ totalDurationMs: complete.summary.totalDurationMs,
389
+ jobs: complete.summary.jobs
390
+ };
391
+ if (!options.quiet) process.stdout.write("\n" + formatSummary(runResult) + "\n");
392
+ if (complete.status === "failed") {
393
+ for (const job of complete.summary.jobs) if (job.status === "failed") {
394
+ const lines = lastLinesByJob.get(job.name);
395
+ if (lines && lines.length > 0 && !options.quiet) process.stdout.write("\n" + formatErrorHighlight(job.name, lines) + "\n");
396
+ }
397
+ }
398
+ observer.disconnect();
399
+ return {
400
+ fixtureId,
401
+ runId,
402
+ status: complete.status,
403
+ observeUrl,
404
+ durationMs: complete.summary.totalDurationMs,
405
+ jobs: complete.summary.jobs
406
+ };
407
+ } finally {
408
+ formatter.stopElapsedTimer();
409
+ process.removeListener("SIGINT", cancelHandler);
410
+ }
411
+ }
412
+ /**
413
+ * Poll orchestrator for run completion (HTTP fallback when WebSocket fails).
414
+ */
415
+ async function pollForCompletionFallback(fixtureId, runId, observeUrl, client, options) {
416
+ const POLL_INTERVAL_MS = 2e3;
417
+ const MAX_POLL_DURATION_MS = 1800 * 1e3;
418
+ const startTime = Date.now();
419
+ let cancelled = false;
420
+ const cancelHandler = async () => {
421
+ if (cancelled) return;
422
+ cancelled = true;
423
+ if (!options.quiet) logger.info(pc.yellow("\nCancelling test run..."));
424
+ try {
425
+ const cancelResult = await client.cancelTest(runId);
426
+ if (!options.quiet) logger.info(pc.gray(`Cancel status: ${cancelResult.status}`));
427
+ } catch {}
428
+ };
429
+ process.on("SIGINT", cancelHandler);
430
+ try {
431
+ while (!cancelled) {
432
+ if (Date.now() - startTime > MAX_POLL_DURATION_MS) return {
433
+ fixtureId,
434
+ runId,
435
+ status: "error",
436
+ reason: "Polling timeout exceeded (30 minutes)",
437
+ observeUrl
438
+ };
439
+ try {
440
+ const status = await client.getRunStatus(runId);
441
+ if (status.status === "success" || status.status === "failed" || status.status === "cancelled") {
442
+ const durationMs = Date.now() - startTime;
443
+ if (!options.quiet) {
444
+ const statusColor = status.status === "success" ? pc.green : pc.red;
445
+ logger.info(statusColor(`\nRun ${status.status} (${(durationMs / 1e3).toFixed(1)}s)`));
446
+ if (status.summary?.jobs) for (const job of status.summary.jobs) {
447
+ const jobColor = job.status === "success" ? pc.green : pc.red;
448
+ const duration = job.durationMs ? ` (${(job.durationMs / 1e3).toFixed(1)}s)` : "";
449
+ logger.info(` ${jobColor(job.status.padEnd(10))} ${job.name}${duration}`);
450
+ }
451
+ }
452
+ return {
453
+ fixtureId,
454
+ runId,
455
+ status: status.status,
456
+ observeUrl,
457
+ durationMs: Date.now() - startTime,
458
+ jobs: status.summary?.jobs
459
+ };
460
+ }
461
+ } catch {}
462
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
463
+ }
464
+ return {
465
+ fixtureId,
466
+ runId,
467
+ status: "cancelled",
468
+ observeUrl,
469
+ durationMs: Date.now() - startTime
470
+ };
471
+ } finally {
472
+ process.removeListener("SIGINT", cancelHandler);
473
+ }
474
+ }
475
+ /**
476
+ * Run a specific workflow directly (bypass trigger matching).
477
+ */
478
+ async function runDirectWorkflow(workflowName, options) {
479
+ const config = await loadGlobalConfig();
480
+ if (!config.token) {
481
+ logger.error(pc.red("Not authenticated. Run `kici login` to set up your API key."));
482
+ return false;
483
+ }
484
+ const endpoint = config.endpoint ?? config.platformEndpoint;
485
+ if (!endpoint) {
486
+ logger.error(pc.red("No orchestrator endpoint configured. Run `kici login --endpoint <url>` to set one."));
487
+ return false;
488
+ }
489
+ const client = new OrchestratorClient({
490
+ endpoint,
491
+ token: config.token
492
+ });
493
+ if (!options.quiet) logger.info(pc.gray(`Running workflow "${workflowName}" directly (bypassing triggers)`));
494
+ const kiciDir = resolveKiciDir(options.kiciDir);
495
+ const repoRoot = path.resolve(kiciDir, "..");
496
+ const { tarballPath, summary, hasRemote } = await createOverlayTarball(repoRoot);
497
+ let routingKey = options.routingKey ?? config.routingKey ?? "default";
498
+ if (!hasRemote) {
499
+ routingKey = `local:${path.basename(repoRoot)}`;
500
+ logger.warn("No remote detected -- steps that use git commands will fail (no .git directory)");
501
+ }
502
+ let inlineLockFile;
503
+ if (!hasRemote) inlineLockFile = await readFile(path.join(kiciDir, "kici.lock.json"), "utf-8");
504
+ if (!options.quiet) logger.info(pc.gray(`${summary.fileCount} files changed, ${summary.newFiles} new, ${summary.deletedFiles} deleted (${formatBytes(summary.compressedSize)} compressed)`));
505
+ const upload = await client.initUpload({
506
+ routingKey,
507
+ sha: summary.sha,
508
+ fileCount: summary.fileCount,
509
+ compressedSize: summary.compressedSize
510
+ });
511
+ const directUploadResult = await uploadTarball({
512
+ tarballPath,
513
+ signedUrl: upload.signedUrl,
514
+ orchestratorPublicKey: Buffer.from(upload.publicKey, "base64")
515
+ });
516
+ const repoName = !hasRemote ? path.basename(repoRoot) : void 0;
517
+ const payload = {};
518
+ if (!hasRemote) payload.repository = { full_name: `local/${repoName}` };
519
+ const result = await client.triggerTest({
520
+ fixtureId: `direct:${workflowName}`,
521
+ event: {
522
+ type: "manual",
523
+ targetBranch: "main",
524
+ payload
525
+ },
526
+ routingKey,
527
+ uploadId: upload.uploadId,
528
+ cliPublicKey: directUploadResult.cliPublicKey.toString("base64"),
529
+ workflowName,
530
+ inlineLockFile,
531
+ fullRepo: !hasRemote || void 0
532
+ });
533
+ if (!options.quiet) {
534
+ logger.info(pc.green(`Run started: ${result.runId}`));
535
+ logger.info(pc.gray(`Observe: ${result.observeUrl}`));
536
+ }
537
+ if (options.wait === false) return result.status === "accepted";
538
+ const observeResult = await observeCompletion(`direct:${workflowName}`, result.runId, result.observeUrl, config.token, client, options);
539
+ return observeResult.status === "success" || observeResult.status === "accepted";
540
+ }
541
+ /**
542
+ * Display remote run results as a summary table.
543
+ */
544
+ function displayRemoteResults(results) {
545
+ logger.info(pc.bold("\n--- Results ---\n"));
546
+ for (const r of results) {
547
+ const statusColor = r.status === "success" || r.status === "accepted" ? pc.green : r.status === "cancelled" ? pc.yellow : pc.red;
548
+ const duration = r.durationMs ? ` (${(r.durationMs / 1e3).toFixed(1)}s)` : "";
549
+ logger.info(` ${statusColor(r.status.padEnd(12))} ${r.fixtureId}${duration}`);
550
+ if (r.reason) logger.info(pc.gray(` ${r.reason}`));
551
+ }
552
+ const passed = results.filter((r) => r.status === "success" || r.status === "accepted").length;
553
+ const failed = results.length - passed;
554
+ logger.info("");
555
+ if (failed === 0) logger.info(pc.green(`All ${results.length} fixture(s) passed`));
556
+ else logger.info(pc.red(`${failed} of ${results.length} fixture(s) failed`));
557
+ logger.info("");
558
+ }
559
+ //#endregion
560
+ export { observeCompletion, runLocalCommand, runRemoteCommand };
561
+
562
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,16 @@
1
+ export interface SecretsListOptions {
2
+ /** Orchestrator endpoint override (defaults to global config). */
3
+ endpoint?: string;
4
+ }
5
+ /**
6
+ * List test-available secret contexts from the orchestrator.
7
+ *
8
+ * Loads the global config for auth token and active org, calls
9
+ * GET /api/v1/admin/environments?orgId=<org>&includeSecrets=true,
10
+ * filters to envs with allow_local_execution=true, and displays a
11
+ * table of names, key names, and flags.
12
+ *
13
+ * Values are never exposed -- only key names.
14
+ */
15
+ export declare function secretsListCommand(options: SecretsListOptions): Promise<boolean>;
16
+ //# sourceMappingURL=secrets-list.d.ts.map
@@ -0,0 +1,85 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ import { loadGlobalConfig } from "../remote/config.js";
3
+ import pc from "picocolors";
4
+ import { toErrorMessage } from "@kici-dev/shared";
5
+ //#region src/commands/secrets-list.ts
6
+ /**
7
+ * List test-available secret contexts from the orchestrator.
8
+ *
9
+ * Loads the global config for auth token and active org, calls
10
+ * GET /api/v1/admin/environments?orgId=<org>&includeSecrets=true,
11
+ * filters to envs with allow_local_execution=true, and displays a
12
+ * table of names, key names, and flags.
13
+ *
14
+ * Values are never exposed -- only key names.
15
+ */
16
+ async function secretsListCommand(options) {
17
+ try {
18
+ const config = await loadGlobalConfig();
19
+ if (!config.token) {
20
+ console.error(pc.red("Not authenticated. Run `kici login` first."));
21
+ return false;
22
+ }
23
+ const endpoint = options.endpoint ?? config.endpoint;
24
+ if (!endpoint) {
25
+ console.error(pc.red("No orchestrator endpoint configured. Run `kici login --endpoint <url>` first."));
26
+ return false;
27
+ }
28
+ if (!config.activeOrgId) {
29
+ console.error(pc.red("No active organization. Run `kici org list` then `kici org use <name>` first."));
30
+ return false;
31
+ }
32
+ const url = `${endpoint.replace(/\/+$/, "")}/api/v1/admin/environments?orgId=${encodeURIComponent(config.activeOrgId)}&includeSecrets=true`;
33
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${config.token}` } });
34
+ if (res.status === 401) {
35
+ console.error(pc.red("Authentication failed. Check your API key with `kici login`."));
36
+ return false;
37
+ }
38
+ if (res.status === 403) {
39
+ console.error(pc.red("Permission denied. Your API key does not have secret.read access."));
40
+ return false;
41
+ }
42
+ if (!res.ok) {
43
+ const text = await res.text();
44
+ console.error(pc.red(`Failed to fetch contexts: ${res.status} ${text}`));
45
+ return false;
46
+ }
47
+ const contexts = ((await res.json()).environments ?? []).filter((e) => e.allow_local_execution);
48
+ if (contexts.length === 0) {
49
+ console.log(pc.yellow("No test-available secret contexts found."));
50
+ console.log(pc.gray("Set allowLocalExecution=true on an environment to make its secrets available for test runs."));
51
+ return true;
52
+ }
53
+ console.log(pc.bold("\nTest-available secret contexts:\n"));
54
+ const ctxKeys = (c) => c.secret_keys ?? [];
55
+ const nameWidth = Math.max(7, ...contexts.map((c) => c.name.length));
56
+ const keysWidth = Math.max(4, ...contexts.map((c) => {
57
+ const k = ctxKeys(c);
58
+ return k.length > 0 ? k.join(", ").length : 4;
59
+ }));
60
+ const flagsWidth = Math.max(5, ...contexts.map((c) => c.enabled ? 0 : 8));
61
+ const pad = (s, w) => s.padEnd(w);
62
+ const hr = (w) => "─".repeat(w);
63
+ console.log(`\u250c${hr(nameWidth + 2)}\u252c${hr(keysWidth + 2)}\u252c${hr(flagsWidth + 2)}\u2510`);
64
+ console.log(`\u2502 ${pad("Context", nameWidth)} \u2502 ${pad("Keys", keysWidth)} \u2502 ${pad("Flags", flagsWidth)} \u2502`);
65
+ console.log(`\u251c${hr(nameWidth + 2)}\u253c${hr(keysWidth + 2)}\u253c${hr(flagsWidth + 2)}\u2524`);
66
+ for (const ctx of contexts) {
67
+ const k = ctxKeys(ctx);
68
+ const keys = k.length > 0 ? k.join(", ") : pc.gray("none");
69
+ const keysRaw = k.length > 0 ? k.join(", ") : "none";
70
+ const flagStr = ctx.enabled ? "" : "disabled";
71
+ console.log(`\u2502 ${pad(ctx.name, nameWidth)} \u2502 ${keys}${" ".repeat(Math.max(0, keysWidth - keysRaw.length))} \u2502 ${pad(flagStr, flagsWidth)} \u2502`);
72
+ }
73
+ console.log(`\u2514${hr(nameWidth + 2)}\u2534${hr(keysWidth + 2)}\u2534${hr(flagsWidth + 2)}\u2518`);
74
+ console.log("");
75
+ return true;
76
+ } catch (err) {
77
+ const message = toErrorMessage(err);
78
+ console.error(pc.red(`Failed to list secrets: ${message}`));
79
+ return false;
80
+ }
81
+ }
82
+ //#endregion
83
+ export { secretsListCommand };
84
+
85
+ //# sourceMappingURL=secrets-list.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * kici status command
3
+ *
4
+ * Shows the status and details of a test run, with optional log streaming.
5
+ * Looks up local history first, then fetches from the orchestrator for
6
+ * up-to-date status and logs.
7
+ */
8
+ /** Options for the status command */
9
+ export interface StatusOptions {
10
+ /** Stream full log replay to stdout */
11
+ logs?: boolean;
12
+ /** Filter logs to specific job */
13
+ job?: string;
14
+ /** Output raw JSON response */
15
+ json?: boolean;
16
+ }
17
+ /**
18
+ * Show status and details of a test run.
19
+ *
20
+ * Default mode: show run summary table from local history + orchestrator.
21
+ * --logs: stream full logs from the orchestrator.
22
+ * --job <name>: filter logs to a specific job.
23
+ * --json: output raw JSON.
24
+ *
25
+ * @param runId - The run ID to inspect
26
+ * @param options - Status display options
27
+ * @returns true on success, false on error
28
+ */
29
+ export declare function statusCommand(runId: string, options?: StatusOptions): Promise<boolean>;
30
+ //# sourceMappingURL=status.d.ts.map