@kici-dev/compiler 0.1.26 → 0.2.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 (199) hide show
  1. package/dist/auth/headless-detect.d.ts +14 -5
  2. package/dist/auth/headless-detect.js +50 -9
  3. package/dist/cli-banner.d.ts +6 -3
  4. package/dist/cli-banner.js +13 -5
  5. package/dist/cli.js +180 -48
  6. package/dist/commands/check-mode.d.ts +1 -1
  7. package/dist/commands/compile.js +31 -7
  8. package/dist/commands/diagnostics.js +14 -2
  9. package/dist/commands/doctor.d.ts +64 -0
  10. package/dist/commands/doctor.js +413 -0
  11. package/dist/commands/endpoints.js +6 -0
  12. package/dist/commands/fixture.js +13 -0
  13. package/dist/commands/hook.js +3 -2
  14. package/dist/commands/index.d.ts +13 -1
  15. package/dist/commands/index.js +10 -3
  16. package/dist/commands/init-host-os.d.ts +34 -0
  17. package/dist/commands/init-host-os.js +44 -0
  18. package/dist/commands/init.d.ts +4 -0
  19. package/dist/commands/init.js +160 -37
  20. package/dist/commands/local-trust-root.d.ts +6 -0
  21. package/dist/commands/local-trust-root.js +52 -0
  22. package/dist/commands/local.d.ts +65 -0
  23. package/dist/commands/local.js +206 -0
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +41 -1
  26. package/dist/commands/logout.js +7 -0
  27. package/dist/commands/notifications.d.ts +61 -0
  28. package/dist/commands/notifications.js +274 -0
  29. package/dist/commands/preview.js +9 -2
  30. package/dist/commands/run-banner.d.ts +31 -0
  31. package/dist/commands/run-banner.js +25 -0
  32. package/dist/commands/run-routed.d.ts +53 -0
  33. package/dist/commands/run-routed.js +185 -0
  34. package/dist/commands/run.d.ts +0 -17
  35. package/dist/commands/run.js +44 -58
  36. package/dist/commands/runs/artifacts/download.d.ts +24 -0
  37. package/dist/commands/runs/artifacts/download.js +154 -0
  38. package/dist/commands/runs/artifacts/list.d.ts +5 -0
  39. package/dist/commands/runs/artifacts/list.js +47 -0
  40. package/dist/commands/runs/cancel.js +2 -2
  41. package/dist/commands/runs/list.js +8 -2
  42. package/dist/commands/secrets-list.d.ts +2 -2
  43. package/dist/commands/secrets-list.js +4 -4
  44. package/dist/commands/types.d.ts +3 -3
  45. package/dist/commands/types.js +4 -4
  46. package/dist/commands/verify-attestation.d.ts +3 -1
  47. package/dist/commands/verify-attestation.js +18 -7
  48. package/dist/errors/formatter.d.ts +14 -1
  49. package/dist/errors/formatter.js +13 -3
  50. package/dist/errors/index.d.ts +2 -1
  51. package/dist/errors/index.js +3 -2
  52. package/dist/errors/source-location.d.ts +20 -0
  53. package/dist/errors/source-location.js +45 -0
  54. package/dist/execution/executor.js +32 -12
  55. package/dist/fixtures/defaults/index.js +2 -1
  56. package/dist/generators/secrets-dts.d.ts +9 -9
  57. package/dist/generators/secrets-dts.js +12 -12
  58. package/dist/llm-context/llms-architecture.txt +50 -21
  59. package/dist/llm-context/llms-cli.txt +2667 -1454
  60. package/dist/llm-context/llms-features.txt +484 -414
  61. package/dist/llm-context/llms-full.txt +6703 -4687
  62. package/dist/llm-context/llms-getting-started.txt +311 -55
  63. package/dist/llm-context/llms-patterns.txt +17 -29
  64. package/dist/llm-context/llms-providers.txt +10 -1
  65. package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
  66. package/dist/llm-context/llms-sdk.txt +596 -2157
  67. package/dist/llm-context/llms.txt +31 -17
  68. package/dist/local-plane/orchestrator-process.d.ts +70 -0
  69. package/dist/local-plane/orchestrator-process.js +129 -0
  70. package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
  71. package/dist/local-plane/os-mismatch-hint.js +34 -0
  72. package/dist/local-plane/paths.d.ts +39 -0
  73. package/dist/local-plane/paths.js +54 -0
  74. package/dist/local-plane/plane-liveness.d.ts +68 -0
  75. package/dist/local-plane/plane-liveness.js +145 -0
  76. package/dist/local-plane/plane-manager.d.ts +193 -0
  77. package/dist/local-plane/plane-manager.js +480 -0
  78. package/dist/local-plane/plane-seed.d.ts +76 -0
  79. package/dist/local-plane/plane-seed.js +59 -0
  80. package/dist/local-plane/plane-trigger.d.ts +71 -0
  81. package/dist/local-plane/plane-trigger.js +110 -0
  82. package/dist/local-plane/platform-attach.d.ts +53 -0
  83. package/dist/local-plane/platform-attach.js +107 -0
  84. package/dist/local-plane/port-holder.d.ts +78 -0
  85. package/dist/local-plane/port-holder.js +227 -0
  86. package/dist/local-plane/postgres.d.ts +56 -0
  87. package/dist/local-plane/postgres.js +187 -0
  88. package/dist/local-plane/resolve-plane.d.ts +37 -0
  89. package/dist/local-plane/resolve-plane.js +76 -0
  90. package/dist/local-plane/run-follow.d.ts +61 -0
  91. package/dist/local-plane/run-follow.js +151 -0
  92. package/dist/local-plane/scaler-config.d.ts +68 -0
  93. package/dist/local-plane/scaler-config.js +132 -0
  94. package/dist/local-plane/secret-seed.d.ts +59 -0
  95. package/dist/local-plane/secret-seed.js +112 -0
  96. package/dist/local-plane/source-provider.d.ts +43 -0
  97. package/dist/local-plane/source-provider.js +159 -0
  98. package/dist/local-plane/trusted-routing.d.ts +32 -0
  99. package/dist/local-plane/trusted-routing.js +47 -0
  100. package/dist/lockfile/generator.d.ts +14 -1
  101. package/dist/lockfile/generator.js +103 -37
  102. package/dist/lockfile/index.d.ts +3 -1
  103. package/dist/lockfile/index.js +3 -2
  104. package/dist/lockfile/purity-diagnostics.d.ts +31 -0
  105. package/dist/lockfile/purity-diagnostics.js +52 -0
  106. package/dist/postinstall.js +2 -1
  107. package/dist/remote/artifact-extract.d.ts +8 -0
  108. package/dist/remote/artifact-extract.js +58 -0
  109. package/dist/remote/config.d.ts +21 -3
  110. package/dist/remote/config.js +24 -5
  111. package/dist/remote/dashboard-client.d.ts +46 -9
  112. package/dist/remote/dashboard-client.js +54 -11
  113. package/dist/remote/fs-case.d.ts +21 -0
  114. package/dist/remote/fs-case.js +45 -0
  115. package/dist/remote/history.d.ts +9 -3
  116. package/dist/remote/history.js +79 -10
  117. package/dist/remote/local-repo-identity.d.ts +1 -1
  118. package/dist/remote/local-repo-identity.js +2 -1
  119. package/dist/remote/notifications-client.d.ts +149 -0
  120. package/dist/remote/notifications-client.js +103 -0
  121. package/dist/remote/oauth.d.ts +13 -0
  122. package/dist/remote/oauth.js +31 -5
  123. package/dist/remote/output/summary.js +76 -14
  124. package/dist/remote/platform-client.d.ts +1 -1
  125. package/dist/remote/render.js +30 -9
  126. package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
  127. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  128. package/dist/remote/secret-upload.d.ts +1 -1
  129. package/dist/remote/secret-upload.js +2 -2
  130. package/dist/remote/uploader.d.ts +1 -1
  131. package/dist/remote/uploader.js +7 -6
  132. package/dist/templates/agents-md.d.ts +1 -1
  133. package/dist/templates/agents-md.js +8 -8
  134. package/dist/templates/index.d.ts +1 -1
  135. package/dist/templates/index.js +2 -2
  136. package/dist/templates/package-json.d.ts +15 -0
  137. package/dist/templates/package-json.js +26 -5
  138. package/dist/test-runner/dry-run.d.ts +2 -1
  139. package/dist/test-runner/dry-run.js +18 -1
  140. package/dist/test-runner/event-types.d.ts +2 -0
  141. package/dist/test-runner/event-types.js +3 -0
  142. package/dist/test-runner/job-executor.d.ts +1 -0
  143. package/dist/test-runner/job-executor.js +82 -44
  144. package/dist/test-runner/output-formatter.d.ts +5 -2
  145. package/dist/test-runner/output-formatter.js +6 -4
  146. package/dist/test-runner/payload-builder.d.ts +7 -1
  147. package/dist/test-runner/payload-builder.js +11 -2
  148. package/dist/test-runner/rule-evaluator.d.ts +7 -3
  149. package/dist/test-runner/rule-evaluator.js +13 -12
  150. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  151. package/dist/test-runner/step-context.d.ts +23 -5
  152. package/dist/test-runner/step-context.js +29 -119
  153. package/dist/types.d.ts +26 -7
  154. package/dist/types.js +4 -2
  155. package/dist/validation/index.d.ts +2 -0
  156. package/dist/validation/index.js +2 -1
  157. package/dist/validation/typecheck.d.ts +28 -0
  158. package/dist/validation/typecheck.js +110 -0
  159. package/dist/validation/validator.d.ts +9 -4
  160. package/dist/validation/validator.js +38 -44
  161. package/package.json +13 -13
  162. package/sbom.spdx.json +9311 -1528
  163. package/dist/assets/api-TJJVHrjC.json +0 -118
  164. package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
  165. package/dist/assets/package-BpQF9kR8.json +0 -74
  166. package/dist/assets/package-Ceo2h27X.json +0 -89
  167. package/dist/assets/source_context-D0atuL28.json +0 -20
  168. package/dist/assets/type-BFqO8SCZ.json +0 -202
  169. package/dist/commands/cancel.d.ts +0 -22
  170. package/dist/commands/detect-package-manager.d.ts +0 -42
  171. package/dist/commands/held-run-resolve.d.ts +0 -50
  172. package/dist/commands/status.d.ts +0 -34
  173. package/dist/commands/test.d.ts +0 -88
  174. package/dist/errors/codes.d.ts +0 -25
  175. package/dist/local-executor/dag-scheduler.d.ts +0 -44
  176. package/dist/local-executor/dag-scheduler.js +0 -183
  177. package/dist/local-executor/index.d.ts +0 -23
  178. package/dist/local-executor/index.js +0 -378
  179. package/dist/local-executor/job-runner.d.ts +0 -47
  180. package/dist/local-executor/job-runner.js +0 -411
  181. package/dist/local-executor/materializer.d.ts +0 -44
  182. package/dist/local-executor/materializer.js +0 -132
  183. package/dist/local-executor/output-streamer.d.ts +0 -31
  184. package/dist/local-executor/output-streamer.js +0 -168
  185. package/dist/local-executor/payload-generator.d.ts +0 -16
  186. package/dist/local-executor/payload-generator.js +0 -138
  187. package/dist/local-executor/picker.d.ts +0 -33
  188. package/dist/local-executor/picker.js +0 -109
  189. package/dist/local-executor/to-event-payload.d.ts +0 -16
  190. package/dist/local-executor/to-event-payload.js +0 -21
  191. package/dist/local-executor/types.d.ts +0 -93
  192. package/dist/local-executor/types.js +0 -2
  193. package/dist/local-executor/workflow-lock.d.ts +0 -82
  194. package/dist/local-executor/workflow-lock.js +0 -0
  195. package/dist/package-F7UXSDHW.json +0 -74
  196. package/dist/remote/client.d.ts +0 -210
  197. package/dist/remote/observer.d.ts +0 -81
  198. package/dist/test-runner/summary.d.ts +0 -7
  199. /package/dist/{local-executor → test-runner}/runs-on-display.d.ts +0 -0
@@ -0,0 +1,185 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { resolveKiciDir } from "../execution/executor.js";
3
+ import "../execution/index.js";
4
+ import { formatSummary } from "../remote/output/summary.js";
5
+ import { detectOsMismatchHints } from "../local-plane/os-mismatch-hint.js";
6
+ import { IN_PLACE_ROUTING_LABEL, TRUSTED_ROUTING_LABEL } from "../local-plane/scaler-config.js";
7
+ import { planeLogPath } from "../local-plane/plane-manager.js";
8
+ import { ensureLocalSource } from "../local-plane/plane-seed.js";
9
+ import { triggerRun } from "../local-plane/plane-trigger.js";
10
+ import { resolvePlaneForRun } from "../local-plane/resolve-plane.js";
11
+ import { followRun } from "../local-plane/run-follow.js";
12
+ import { seedLocalSecrets } from "../local-plane/secret-seed.js";
13
+ import { resolveWorkdir } from "../local-plane/source-provider.js";
14
+ import { injectRunsOnLabel } from "../local-plane/trusted-routing.js";
15
+ import { compileCommand } from "./compile.js";
16
+ import { withStdoutOnStderr } from "./run.js";
17
+ import { renderRunBanner } from "./run-banner.js";
18
+ import path from "node:path";
19
+ import { readFileSync } from "node:fs";
20
+ import pc from "picocolors";
21
+ import { logger, toErrorMessage } from "@kici-dev/core";
22
+ //#region src/commands/run-routed.ts
23
+ /**
24
+ * `kici run --local [event]` — a real routed dispatch with this machine as the
25
+ * ephemeral agent, through the warm local dev plane. In this phase only the
26
+ * offline (independent) plane is wired; Platform attachment (`--connected` /
27
+ * auto-select hybrid) lands with the attachment work.
28
+ *
29
+ * The offline path reuses the real orchestrator + agent pipeline end-to-end, so
30
+ * the execution parity dividend (emit / cache / needs / parallel / `KICI_SOURCE_*`)
31
+ * holds by construction — there is no second engine.
32
+ */
33
+ /**
34
+ * Entry point for the action-bearing `kici run`. Validates the routing flags and
35
+ * drives the offline routed run. Returns true on run success.
36
+ */
37
+ async function runRoutedCommand(options) {
38
+ if (options.debug) process.env.KICI_DEBUG = "true";
39
+ if (!options.local) {
40
+ logger.error(pc.red("kici run (connected cloud run) is not yet available. Use `kici run --local` for a routed run on this machine, or `kici run remote <fixture>` for the fixture path."));
41
+ return false;
42
+ }
43
+ if (!options.event) {
44
+ logger.error(pc.red("Provide an event to run, e.g. `kici run --local push`."));
45
+ return false;
46
+ }
47
+ return runRouted({
48
+ ...options,
49
+ event: options.event
50
+ });
51
+ }
52
+ /**
53
+ * Read a dispatch payload file `{ action?, client_payload? }`. Returns the
54
+ * action + client_payload the routed dispatch trigger carries. An absent
55
+ * `--payload` yields an empty action (a bare `dispatch()` workflow still
56
+ * matches); a present file is parsed as JSON.
57
+ */
58
+ function readDispatchPayload(payloadPath) {
59
+ if (!payloadPath) return {};
60
+ const raw = JSON.parse(readFileSync(payloadPath, "utf-8"));
61
+ return {
62
+ action: raw.action,
63
+ clientPayload: raw.client_payload
64
+ };
65
+ }
66
+ /** Drive the full routed run against the warm local plane (offline or attached). */
67
+ async function runRouted(options) {
68
+ const quiet = Boolean(options.quiet);
69
+ const kiciDir = resolveKiciDir(options.kiciDir);
70
+ const repoRoot = path.resolve(kiciDir, "..");
71
+ if (!(quiet ? await withStdoutOnStderr(() => compileCommand({
72
+ kiciDir,
73
+ check: false,
74
+ verbose: options.debug ?? false,
75
+ quiet: true
76
+ })) : await compileCommand({
77
+ kiciDir,
78
+ check: false,
79
+ verbose: options.debug ?? false
80
+ }))) return false;
81
+ if (!quiet) try {
82
+ const os = await import("node:os");
83
+ const lockRaw = readFileSync(path.join(kiciDir, "kici.lock.json"), "utf-8");
84
+ const hints = detectOsMismatchHints(JSON.parse(lockRaw), os.platform(), os.arch());
85
+ for (const hint of hints) logger.warn(pc.yellow(hint));
86
+ } catch {}
87
+ let cleanup;
88
+ const restoreLocks = [];
89
+ try {
90
+ if (!quiet) logger.info(pc.dim("Starting the local dev plane…"));
91
+ const resolved = await resolvePlaneForRun({
92
+ offline: options.offline,
93
+ connected: options.connected
94
+ });
95
+ if ("error" in resolved) {
96
+ logger.error(pc.red(resolved.error));
97
+ return false;
98
+ }
99
+ const plane = resolved.plane;
100
+ if (!plane.url || !plane.adminToken) {
101
+ logger.error(pc.red("Local dev plane did not report a URL + admin token."));
102
+ return false;
103
+ }
104
+ const trusted = Boolean(options.trusted);
105
+ if (!quiet) logger.info(renderRunBanner(resolved.kind === "attached" ? {
106
+ mode: "attached",
107
+ planeUrl: plane.url,
108
+ orgId: resolved.orgId,
109
+ trusted
110
+ } : resolved.kind === "fallback" ? {
111
+ mode: "fallback",
112
+ planeUrl: plane.url,
113
+ fallbackReason: resolved.reason,
114
+ trusted
115
+ } : {
116
+ mode: "offline",
117
+ planeUrl: plane.url,
118
+ trusted
119
+ }));
120
+ const workdir = await resolveWorkdir({
121
+ inPlace: Boolean(options.inPlace),
122
+ repoRoot
123
+ });
124
+ cleanup = workdir.cleanup;
125
+ if (trusted) {
126
+ const lockPath = path.join(workdir.dir, ".kici", "kici.lock.json");
127
+ restoreLocks.push(injectRunsOnLabel(lockPath, TRUSTED_ROUTING_LABEL).restore);
128
+ if (options.inPlace) restoreLocks.push(injectRunsOnLabel(lockPath, IN_PLACE_ROUTING_LABEL).restore);
129
+ }
130
+ const seeded = await ensureLocalSource(plane.url, plane.adminToken, {
131
+ repoDir: workdir.dir,
132
+ inPlace: Boolean(options.inPlace)
133
+ });
134
+ const seededSecrets = await seedLocalSecrets(plane.url, plane.adminToken, {
135
+ orgId: seeded.orgId,
136
+ kiciDir
137
+ });
138
+ if (!quiet && seededSecrets.contexts.length > 0) logger.info(pc.dim(`Seeded ${seededSecrets.secretCount} local secret(s) across ${seededSecrets.contexts.length} context(s): ${seededSecrets.contexts.join(", ")}`));
139
+ if (!quiet) logger.info(pc.dim(`Triggering ${options.event} …`));
140
+ const dispatch = options.event === "dispatch" ? readDispatchPayload(options.payload) : void 0;
141
+ const runId = await triggerRun(plane.url, plane.adminToken, {
142
+ orgId: seeded.orgId,
143
+ sourceId: seeded.sourceName,
144
+ repoFullName: ".",
145
+ event: options.event === "dispatch" ? "dispatch" : options.event === "pull_request" ? "pull_request" : "push",
146
+ ref: workdir.ref,
147
+ sha: workdir.sha,
148
+ defaultBranch: workdir.branch,
149
+ ...dispatch && {
150
+ action: dispatch.action,
151
+ clientPayload: dispatch.clientPayload
152
+ }
153
+ });
154
+ if (!quiet) logger.info(pc.green(`Run started: ${runId}`));
155
+ const outcome = await followRun(plane.url, plane.adminToken, runId, {
156
+ quiet,
157
+ onLine: (line) => process.stdout.write(line + "\n"),
158
+ hintLogPath: planeLogPath()
159
+ });
160
+ if (!quiet) {
161
+ const result = {
162
+ fixtureId: options.event,
163
+ runId,
164
+ status: outcome.status === "success" ? "success" : "failed",
165
+ totalDurationMs: 0,
166
+ jobs: outcome.jobs
167
+ };
168
+ process.stdout.write("\n" + formatSummary(result) + "\n");
169
+ }
170
+ return outcome.status === "success";
171
+ } catch (error) {
172
+ logger.error(pc.red(`\nError: ${toErrorMessage(error)}\n`));
173
+ if (options.debug && error instanceof Error && error.stack) logger.error(pc.gray(error.stack));
174
+ return false;
175
+ } finally {
176
+ for (const restore of restoreLocks.reverse()) try {
177
+ restore();
178
+ } catch {}
179
+ if (cleanup) await cleanup().catch(() => void 0);
180
+ }
181
+ }
182
+ //#endregion
183
+ export { readDispatchPayload, runRoutedCommand };
184
+
185
+ //# sourceMappingURL=run-routed.js.map
@@ -1,15 +1,5 @@
1
1
  import { type HostTargetSelector, type InputsDescriptorMap } from '@kici-dev/engine';
2
- import type { RunLocalOptions } from '../local-executor/types.js';
3
2
  import type { RemoteRunOptions } from './preview.js';
4
- /**
5
- * Print a usage block for `kici run local` when the event argument is missing.
6
- *
7
- * Mirrors the discoverable help `kici preview` prints for a missing event: a
8
- * bold synopsis line, a one-line description, and a few concrete event
9
- * examples. The caller exits non-zero after printing — a missing event is a
10
- * usage error, unlike `kici preview` which treats no-event as help.
11
- */
12
- export declare function printRunLocalUsage(): void;
13
3
  /**
14
4
  * Compile `--target` selector strings into a {@link HostTargetSelector}. Each
15
5
  * string becomes one AND value (its own include set); repeated values
@@ -42,13 +32,6 @@ export declare function buildDispatchInputs(pairs: string[], descriptor: InputsD
42
32
  * stderr regardless.
43
33
  */
44
34
  export declare function withStdoutOnStderr<T>(fn: () => Promise<T>): Promise<T>;
45
- /**
46
- * Run a workflow locally using the local executor.
47
- * Thin wrapper that delegates to executeLocal from local-executor.
48
- *
49
- * Exit codes: 0 = success, 1 = job failure, 2 = config/compilation error
50
- */
51
- export declare function runLocalCommand(options: RunLocalOptions): Promise<boolean>;
52
35
  /**
53
36
  * Run fixtures remotely, routed through the Platform.
54
37
  */
@@ -4,51 +4,38 @@ import "../execution/index.js";
4
4
  import { loadGlobalConfig } from "../remote/config.js";
5
5
  import { RunHistory } from "../remote/history.js";
6
6
  import { buildLocalRepoIdentity } from "../remote/local-repo-identity.js";
7
- import { AccessDeniedError, AmbiguousClusterError, AuthenticationError, ConnectionError, NoClusterError, PlatformRunClient } from "../remote/platform-client.js";
7
+ import { AccessDeniedError, AmbiguousClusterError, AuthenticationError, ConnectionError, NoClusterError, NotFoundError, PlatformRunClient } from "../remote/platform-client.js";
8
8
  import { buildEncryptedSecrets } from "../remote/secret-upload.js";
9
9
  import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/uploader.js";
10
10
  import { formatJsonResult } from "../remote/output/json.js";
11
11
  import { formatJunitResult } from "../remote/output/junit.js";
12
12
  import { formatErrorHighlight, formatMultiFixtureSummary, formatSummary } from "../remote/output/summary.js";
13
- import { compileCommand } from "./compile.js";
14
13
  import { compileFixtures, filterFixtures } from "../fixtures/compiler.js";
15
14
  import { describeEvent } from "../fixtures/describe-event.js";
16
15
  import { FixturePickerCancelledError, runFixturePicker } from "../fixtures/picker.js";
17
16
  import { listHeldRunsForRun, resolveHeldRunContext } from "./held-run-client.js";
17
+ import { compileCommand } from "./compile.js";
18
18
  import { handleNewHolds } from "./run-hold-watch.js";
19
19
  import path from "node:path";
20
20
  import pc from "picocolors";
21
21
  import { readFile, writeFile } from "node:fs/promises";
22
22
  import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
23
- import { coerceDispatchInputs, parseInputPairs } from "@kici-dev/engine";
23
+ import { TERMINAL_RUN_STATES, coerceDispatchInputs, parseInputPairs, toCanonicalStatus } from "@kici-dev/engine";
24
24
  import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
25
25
  import { confirm } from "@inquirer/prompts";
26
26
  //#region src/commands/run.ts
27
27
  /**
28
- * Print a usage block for `kici run local` when the event argument is missing.
28
+ * True when the Platform run-status snapshot reports a terminal run status.
29
29
  *
30
- * Mirrors the discoverable help `kici preview` prints for a missing event: a
31
- * bold synopsis line, a one-line description, and a few concrete event
32
- * examples. The caller exits non-zero after printing a missing event is a
33
- * usage error, unlike `kici preview` which treats no-event as help.
30
+ * Resolves through the engine's canonical vocabulary rather than matching a
31
+ * hand-written literal set: that is what makes the legacy `error` spelling
32
+ * terminal without a second copy of the alias, and what stops a newly-added
33
+ * terminal status leaving `kici run` polling a finished run forever.
34
34
  */
35
- function printRunLocalUsage() {
36
- logger.info(pc.bold("\nUsage: kici run local <event> [options]\n"));
37
- logger.info(pc.gray("Execute workflows locally for a given event, without orchestrator infrastructure.\n"));
38
- logger.info(pc.gray("Examples:"));
39
- logger.info(pc.gray(" kici run local push"));
40
- logger.info(pc.gray(" kici run local pr:open"));
41
- logger.info(pc.gray(" kici run local schedule"));
42
- logger.info(pc.gray(" kici run local lifecycle:workflow_complete\n"));
43
- logger.info(pc.gray("Or pick a workflow interactively: kici run local --pick\n"));
35
+ function isTerminalRunStatus(status) {
36
+ const canonical = toCanonicalStatus(status.toLowerCase());
37
+ return canonical !== void 0 && TERMINAL_RUN_STATES.has(canonical);
44
38
  }
45
- /** Terminal run statuses returned by the Platform run-status snapshot. */
46
- const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
47
- "success",
48
- "failed",
49
- "cancelled",
50
- "error"
51
- ]);
52
39
  /**
53
40
  * Compile `--target` selector strings into a {@link HostTargetSelector}. Each
54
41
  * string becomes one AND value (its own include set); repeated values
@@ -109,6 +96,19 @@ function buildDispatchInputs(pairs, descriptor) {
109
96
  /** Interval between status/log polls while a run is active. */
110
97
  const POLL_INTERVAL_MS = 750;
111
98
  /**
99
+ * How long a freshly triggered run may still read as missing before a 404 is
100
+ * reported as a real error.
101
+ *
102
+ * The trigger call answers with the run id as soon as the orchestrator has
103
+ * created the run, while the Platform's view of that run arrives over the
104
+ * relay a moment later. The first poll therefore fires while the run is not
105
+ * yet readable and legitimately 404s. Within this budget — and only until the
106
+ * first successful read — a 404 means "not visible yet" and the poll retries;
107
+ * after it, or once the run has been read at least once, a 404 is surfaced as
108
+ * a genuinely missing run.
109
+ */
110
+ const RUN_VISIBILITY_GRACE_MS = 3e4;
111
+ /**
112
112
  * Run `fn` with everything written to `process.stdout` redirected to
113
113
  * `process.stderr`, then restore the original writer. Compiling a repo
114
114
  * evaluates every user workflow / fixture module, and a `console.log` at a
@@ -127,11 +127,11 @@ async function withStdoutOnStderr(fn) {
127
127
  }
128
128
  }
129
129
  /**
130
- * Recompile `.kici/workflows` → `kici.lock.json` before a remote run, mirroring
131
- * `kici run local`. The orchestrator matches triggers and dispatches against the
132
- * inline lock, so a stale lock would route an edited or newly-added workflow
133
- * incorrectly. Returns false on a compile/validation error so the caller can
134
- * abort before any upload or dispatch.
130
+ * Recompile `.kici/workflows` → `kici.lock.json` before a remote run. The
131
+ * orchestrator matches triggers and dispatches against the inline lock, so a
132
+ * stale lock would route an edited or newly-added workflow incorrectly. Returns
133
+ * false on a compile/validation error so the caller can abort before any upload
134
+ * or dispatch.
135
135
  */
136
136
  async function compileBeforeRemoteRun(options) {
137
137
  const pureStdout = Boolean(options.json || options.quiet);
@@ -144,31 +144,6 @@ async function compileBeforeRemoteRun(options) {
144
144
  return pureStdout ? withStdoutOnStderr(compile) : compile();
145
145
  }
146
146
  /**
147
- * Run a workflow locally using the local executor.
148
- * Thin wrapper that delegates to executeLocal from local-executor.
149
- *
150
- * Exit codes: 0 = success, 1 = job failure, 2 = config/compilation error
151
- */
152
- async function runLocalCommand(options) {
153
- if (options.debug) {
154
- process.env.KICI_DEBUG = "true";
155
- if (!options.quiet) logger.info(pc.gray("Debug mode enabled"));
156
- }
157
- try {
158
- const { executeLocal } = await import("../local-executor/index.js");
159
- return await executeLocal(options);
160
- } catch (error) {
161
- if (error instanceof Error && (error.message.includes("Cannot find module") || error.message.includes("ERR_MODULE_NOT_FOUND"))) {
162
- logger.error(pc.red("Local executor not yet available. The local execution engine is under development."));
163
- return false;
164
- }
165
- const message = toErrorMessage(error);
166
- logger.error(pc.red(`\nError: ${message}\n`));
167
- if (options.debug && error instanceof Error && error.stack) logger.error(pc.gray(error.stack));
168
- return false;
169
- }
170
- }
171
- /**
172
147
  * Run fixtures remotely, routed through the Platform.
173
148
  */
174
149
  async function runRemoteCommand(fixture, options) {
@@ -509,16 +484,27 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
509
484
  let lastStatus = null;
510
485
  const seenHolds = /* @__PURE__ */ new Set();
511
486
  let heldCtx;
487
+ let runSeen = false;
512
488
  while (!cancelled) {
513
- const logs = await ctx.client.runLogs(ctx.orgId, runId, cursor, ctx.target);
489
+ let logs;
490
+ try {
491
+ logs = await ctx.client.runLogs(ctx.orgId, runId, cursor, ctx.target);
492
+ lastStatus = await ctx.client.runStatus(ctx.orgId, runId, ctx.target);
493
+ } catch (err) {
494
+ if (err instanceof NotFoundError && !runSeen && Date.now() - startTime < RUN_VISIBILITY_GRACE_MS) {
495
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
496
+ continue;
497
+ }
498
+ throw err;
499
+ }
500
+ runSeen = true;
514
501
  for (const line of logs.lines) {
515
502
  if (!options.quiet) process.stdout.write(line + "\n");
516
503
  tailLines.push(line);
517
504
  while (tailLines.length > MAX_TAIL) tailLines.shift();
518
505
  }
519
506
  cursor = logs.nextCursor;
520
- lastStatus = await ctx.client.runStatus(ctx.orgId, runId, ctx.target);
521
- const terminal = lastStatus.done || TERMINAL_STATUSES.has(lastStatus.status);
507
+ const terminal = lastStatus.done || isTerminalRunStatus(lastStatus.status);
522
508
  if (terminal && logs.done) return finishRun(fixtureId, runId, lastStatus, startTime, tailLines, options);
523
509
  if (!terminal) await watchRunHolds(runId, seenHolds, () => heldCtx, (c) => heldCtx = c, options);
524
510
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
@@ -694,6 +680,6 @@ function displayRemoteResults(results) {
694
680
  logger.info("");
695
681
  }
696
682
  //#endregion
697
- export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, printRunLocalUsage, runLocalCommand, runRemoteCommand, withStdoutOnStderr };
683
+ export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, runRemoteCommand, withStdoutOnStderr };
698
684
 
699
685
  //# sourceMappingURL=run.js.map
@@ -0,0 +1,24 @@
1
+ export interface RunsArtifactsDownloadOptions {
2
+ archive?: boolean;
3
+ output?: string;
4
+ }
5
+ /** Basename an artifact takes under the output directory in the given mode. */
6
+ export declare function destBasename(name: string, archive: boolean): string;
7
+ /**
8
+ * Reduce a basename to the form a case-insensitive filesystem looks it up by.
9
+ *
10
+ * The NFC pass is deliberate defence for a widened artifact-name charset:
11
+ * `ArtifactNameSchema` accepts ASCII only today, so no live name can differ by
12
+ * normalisation — but HFS+ stores NFD and APFS folds case while preserving
13
+ * normalisation, so a non-ASCII name would collide on those volumes without it.
14
+ */
15
+ export declare function foldBasename(basename: string): string;
16
+ /**
17
+ * Group basenames that a case-insensitive filesystem resolves to one path.
18
+ *
19
+ * Groups appear in first-appearance order and each group is sorted; a basename
20
+ * with no partner is not reported.
21
+ */
22
+ export declare function findCaseCollisions(basenames: string[]): string[][];
23
+ export declare function runsArtifactsDownloadCommand(runId: string, name: string | undefined, options?: RunsArtifactsDownloadOptions): Promise<boolean>;
24
+ //# sourceMappingURL=download.d.ts.map
@@ -0,0 +1,154 @@
1
+ import "../../../rolldown-runtime-ClRpJifh.js";
2
+ import { extractArtifactTarball } from "../../../remote/artifact-extract.js";
3
+ import { DashboardClient, DashboardClientError } from "../../../remote/dashboard-client.js";
4
+ import { isCaseInsensitiveDir } from "../../../remote/fs-case.js";
5
+ import { join } from "node:path";
6
+ import pc from "picocolors";
7
+ import { mkdir, writeFile } from "node:fs/promises";
8
+ import { logger, sha256, toErrorMessage } from "@kici-dev/core";
9
+ import { ArtifactNameSchema } from "@kici-dev/engine";
10
+ //#region src/commands/runs/artifacts/download.ts
11
+ /** kici runs artifacts download — fetch one artifact (by name) or every artifact of a run. */
12
+ /** Extension of the raw artifact archive written by `--archive`. */
13
+ const ARCHIVE_EXTENSION = ".tar.gz";
14
+ /** Basename an artifact takes under the output directory in the given mode. */
15
+ function destBasename(name, archive) {
16
+ return archive ? `${name}${ARCHIVE_EXTENSION}` : name;
17
+ }
18
+ /**
19
+ * Reduce a basename to the form a case-insensitive filesystem looks it up by.
20
+ *
21
+ * The NFC pass is deliberate defence for a widened artifact-name charset:
22
+ * `ArtifactNameSchema` accepts ASCII only today, so no live name can differ by
23
+ * normalisation — but HFS+ stores NFD and APFS folds case while preserving
24
+ * normalisation, so a non-ASCII name would collide on those volumes without it.
25
+ */
26
+ function foldBasename(basename) {
27
+ return basename.normalize("NFC").toLowerCase();
28
+ }
29
+ /**
30
+ * Group basenames that a case-insensitive filesystem resolves to one path.
31
+ *
32
+ * Groups appear in first-appearance order and each group is sorted; a basename
33
+ * with no partner is not reported.
34
+ */
35
+ function findCaseCollisions(basenames) {
36
+ const byFold = /* @__PURE__ */ new Map();
37
+ for (const basename of basenames) {
38
+ const fold = foldBasename(basename);
39
+ const group = byFold.get(fold);
40
+ if (group) group.add(basename);
41
+ else byFold.set(fold, /* @__PURE__ */ new Set([basename]));
42
+ }
43
+ return [...byFold.values()].filter((g) => g.size > 1).map((g) => [...g].sort());
44
+ }
45
+ /**
46
+ * Fetch a presigned artifact URL into a Buffer and verify its sha-256.
47
+ *
48
+ * NO `Authorization` header: `downloadUrl` is a presigned object-storage GET,
49
+ * and the PAT authenticates to the Platform, not to object storage — attaching
50
+ * it would leak the credential to the storage host. The bytes therefore flow
51
+ * CLI <-> object storage directly and never transit the Platform.
52
+ */
53
+ async function fetchArtifactBytes(entry) {
54
+ if (!entry.downloadUrl) throw new Error(`artifact "${entry.name}" has no download URL (the stored object is missing or expired)`);
55
+ const res = await fetch(entry.downloadUrl);
56
+ if (!res.ok) throw new Error(`download of "${entry.name}" failed (HTTP ${res.status})`);
57
+ const buf = Buffer.from(await res.arrayBuffer());
58
+ const actual = sha256(buf);
59
+ if (actual !== entry.sha256) throw new Error(`artifact "${entry.name}" checksum mismatch: expected ${entry.sha256}, got ${actual}`);
60
+ return buf;
61
+ }
62
+ /**
63
+ * Refuse a listed name that is not a valid artifact name.
64
+ *
65
+ * Every name is validated against {@link ArtifactNameSchema} at upload, so a
66
+ * response carrying one outside that contract comes from a broken or hostile
67
+ * peer. The name becomes a path component under the output directory, so it is
68
+ * checked before it is joined — an unchecked `../…` would let a list response
69
+ * write anywhere on the caller's filesystem.
70
+ */
71
+ function assertPathSafeName(name) {
72
+ if (!ArtifactNameSchema.safeParse(name).success) throw new Error(`listed artifact "${name}" is not a valid artifact name — refusing to write it under the output directory`);
73
+ }
74
+ /**
75
+ * Refuse the whole invocation when two targets would land on the same path.
76
+ *
77
+ * Artifact names are case-sensitive, so a run can legitimately hold both
78
+ * `bundle` and `Bundle`. On a filesystem that ignores case in path lookups the
79
+ * two resolve to one path — the second extraction merges into the first's
80
+ * directory, or overwrites its archive under `--archive`. The filesystem is
81
+ * probed only once a fold collision is known to exist, so a case-sensitive
82
+ * output directory downloads both exactly as before. A probe that cannot answer
83
+ * is treated as case-insensitive: the collision is already certain, so the
84
+ * unknown half is resolved towards refusing rather than towards losing data.
85
+ */
86
+ async function assertNoCaseCollisions(targets, outDir, archive) {
87
+ const groups = findCaseCollisions(targets.map((t) => destBasename(t.name, archive)));
88
+ if (groups.length === 0) return;
89
+ let probeError;
90
+ let caseInsensitive = true;
91
+ try {
92
+ caseInsensitive = await isCaseInsensitiveDir(outDir);
93
+ } catch (err) {
94
+ probeError = err;
95
+ }
96
+ if (!caseInsensitive) return;
97
+ const pairs = groups.map((g) => g.map((basename) => `"${basename}"`).join(" and ")).join("; ");
98
+ const consequence = archive ? "writing both would overwrite one file with the other" : "extracting both would merge them into one directory";
99
+ const reason = probeError !== void 0 ? `the output directory ${outDir} could not be checked for case sensitivity (${toErrorMessage(probeError)})` : `the output directory ${outDir} is on a case-insensitive filesystem`;
100
+ throw new Error(`this run holds artifacts whose names differ only in case: ${pairs} — ${consequence}, and ${reason}. Download them one at a time into separate directories: kici runs artifacts download <run-id> <name> -o <dir>`);
101
+ }
102
+ /** Fetch one entry and write it to `outDir`, extracted or as the raw archive. */
103
+ async function writeOne(entry, outDir, archive) {
104
+ assertPathSafeName(entry.name);
105
+ const buf = await fetchArtifactBytes(entry);
106
+ await mkdir(outDir, { recursive: true });
107
+ const dest = join(outDir, destBasename(entry.name, archive));
108
+ if (archive) {
109
+ await writeFile(dest, buf);
110
+ console.log(pc.green(`✓ ${entry.name} → ${dest}`));
111
+ return;
112
+ }
113
+ await extractArtifactTarball(buf, dest);
114
+ console.log(pc.green(`✓ ${entry.name} → ${dest}/`));
115
+ }
116
+ /** Resolve which listed artifacts the invocation targets. */
117
+ function resolveTargets(artifacts, name, runId) {
118
+ if (!name) return artifacts;
119
+ const match = artifacts.find((a) => a.name === name);
120
+ if (match) return [match];
121
+ const names = artifacts.map((a) => a.name).join(", ") || "(none)";
122
+ throw new Error(`artifact "${name}" not found in run ${runId}. Available: ${names}`);
123
+ }
124
+ async function runsArtifactsDownloadCommand(runId, name, options = {}) {
125
+ try {
126
+ const { artifacts } = await (await DashboardClient.load()).listArtifacts(runId);
127
+ const outDir = options.output ?? process.cwd();
128
+ const downloadingAll = !name;
129
+ const targets = resolveTargets(artifacts, name, runId);
130
+ if (targets.length === 0) {
131
+ console.log(pc.gray("No artifacts for this run."));
132
+ return true;
133
+ }
134
+ for (const entry of targets) assertPathSafeName(entry.name);
135
+ if (downloadingAll) await assertNoCaseCollisions(targets.filter((t) => t.downloadUrl), outDir, options.archive ?? false);
136
+ let failures = 0;
137
+ for (const entry of targets) {
138
+ if (downloadingAll && !entry.downloadUrl) {
139
+ logger.warn(pc.yellow(`! ${entry.name} skipped — no download URL (object missing or expired)`));
140
+ failures++;
141
+ continue;
142
+ }
143
+ await writeOne(entry, outDir, options.archive ?? false);
144
+ }
145
+ return failures < targets.length;
146
+ } catch (err) {
147
+ logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
148
+ return false;
149
+ }
150
+ }
151
+ //#endregion
152
+ export { destBasename, findCaseCollisions, foldBasename, runsArtifactsDownloadCommand };
153
+
154
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1,5 @@
1
+ export interface RunsArtifactsListOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function runsArtifactsListCommand(runId: string, options?: RunsArtifactsListOptions): Promise<boolean>;
5
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,47 @@
1
+ import "../../../rolldown-runtime-ClRpJifh.js";
2
+ import { DashboardClient, DashboardClientError } from "../../../remote/dashboard-client.js";
3
+ import { relativeTime, renderTable } from "../../../remote/render.js";
4
+ import pc from "picocolors";
5
+ import { formatBytes, logger, toErrorMessage } from "@kici-dev/core";
6
+ //#region src/commands/runs/artifacts/list.ts
7
+ /** kici runs artifacts list — the artifacts a run uploaded (the rows the dashboard Artifacts tab shows). */
8
+ /** Number of leading sha-256 hex chars shown in the table (full digest via --json). */
9
+ const SHA_PREFIX_LENGTH = 12;
10
+ async function runsArtifactsListCommand(runId, options = {}) {
11
+ try {
12
+ const result = await (await DashboardClient.load()).listArtifacts(runId);
13
+ if (options.json) {
14
+ console.log(JSON.stringify(result, null, 2));
15
+ return true;
16
+ }
17
+ if (result.artifacts.length === 0) {
18
+ console.log(pc.gray("No artifacts for this run."));
19
+ return true;
20
+ }
21
+ const rows = result.artifacts.map((a) => [
22
+ a.name,
23
+ a.jobId,
24
+ formatBytes(a.sizeBytes),
25
+ a.sha256.slice(0, SHA_PREFIX_LENGTH),
26
+ relativeTime(a.createdAt),
27
+ a.downloadUrl ? "" : pc.yellow("unavailable")
28
+ ]);
29
+ console.log(renderTable([
30
+ "name",
31
+ "job",
32
+ "size",
33
+ "sha-256",
34
+ "created",
35
+ ""
36
+ ], rows));
37
+ console.log(pc.gray(`\n${result.artifacts.length} artifact(s). Download with kici runs artifacts download ${runId} <name>`));
38
+ return true;
39
+ } catch (err) {
40
+ logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
41
+ return false;
42
+ }
43
+ }
44
+ //#endregion
45
+ export { runsArtifactsListCommand };
46
+
47
+ //# sourceMappingURL=list.js.map
@@ -18,8 +18,8 @@ async function runsCancelCommand(runId, options = {}) {
18
18
  return false;
19
19
  }
20
20
  logger.info(`${options.force ? "Force-cancelling" : "Cancelling"} run ${pc.cyan(runId)}...`);
21
- const { cancelledJobs = 0 } = await client.cancelRun(runId, options.force ?? false);
22
- console.log(pc.green(`Run ${runId} cancelled (${cancelledJobs} job${cancelledJobs !== 1 ? "s" : ""} affected).`));
21
+ const { cancelledJobs = 0, alreadyTerminal } = await client.cancelRun(runId, options.force ?? false);
22
+ console.log(alreadyTerminal ? pc.yellow(`Run ${runId} had already finished; nothing was cancelled.`) : pc.green(`Run ${runId} cancelled (${cancelledJobs} job${cancelledJobs !== 1 ? "s" : ""} affected).`));
23
23
  return true;
24
24
  } catch (err) {
25
25
  logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));
@@ -15,7 +15,13 @@ async function runsListCommand(options = {}) {
15
15
  return true;
16
16
  }
17
17
  if (page.runs.length === 0) {
18
- console.log(pc.gray("No runs found."));
18
+ const activity = await client.getWebhookActivity().catch(() => null);
19
+ if (activity && activity.received > 0) {
20
+ const win = activity.windowMinutes === 60 ? "the last hour" : `the last ${activity.windowMinutes} minutes`;
21
+ const headline = activity.orchestratorUnavailable || activity.matched === void 0 ? `${activity.received} webhooks received in ${win} but none produced a run` : `${activity.received} webhooks received in ${win}, ${activity.matched} matched`;
22
+ console.log(pc.yellow(`${headline}.`));
23
+ console.log(pc.gray(`Run ${pc.cyan("kici preview push")} to test your triggers locally.`));
24
+ } else console.log(pc.gray("No runs found."));
19
25
  return true;
20
26
  }
21
27
  const rows = page.runs.map((r) => [
@@ -36,7 +42,7 @@ async function runsListCommand(options = {}) {
36
42
  "started",
37
43
  "duration"
38
44
  ], rows));
39
- console.log(pc.gray(`\nPage ${page.page} · ${page.runs.length} of ${page.total}${page.hasMore ? " (more — use --page)" : ""}`));
45
+ console.log(pc.gray(`\n${page.runs.length} of ~${page.approxTotal} runs${page.nextCursor ? ` (more — use --cursor ${page.nextCursor})` : ""}`));
40
46
  return true;
41
47
  } catch (err) {
42
48
  logger.error(pc.red(err instanceof DashboardClientError ? err.message : toErrorMessage(err)));