@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,91 @@
1
+ /**
2
+ * Local run history manager.
3
+ *
4
+ * Persists test run history in ~/.kici/history/runs.json so developers
5
+ * can review past runs (kici test --history) and inspect specific runs
6
+ * (kici status <run-id>).
7
+ */
8
+ /** Status of a test run */
9
+ type RunStatus = 'queued' | 'running' | 'success' | 'failed' | 'cancelled';
10
+ /** A single run history entry */
11
+ export interface HistoryEntry {
12
+ /** Unique run identifier */
13
+ runId: string;
14
+ /** Fixture that was run */
15
+ fixtureId: string;
16
+ /** Current status of the run */
17
+ status: RunStatus;
18
+ /** ISO timestamp when the run started */
19
+ startedAt: string;
20
+ /** ISO timestamp when the run completed (if finished) */
21
+ completedAt?: string;
22
+ /** Total duration in milliseconds (if finished) */
23
+ durationMs?: number;
24
+ /** Orchestrator endpoint used */
25
+ endpoint: string;
26
+ /** Job-level status breakdown */
27
+ jobs?: Array<{
28
+ name: string;
29
+ status: string;
30
+ durationMs?: number;
31
+ }>;
32
+ }
33
+ /** Filter criteria for querying history entries */
34
+ interface HistoryFilter {
35
+ /** Exact status match */
36
+ status?: string;
37
+ /** Fixture glob pattern (via picomatch) */
38
+ fixture?: string;
39
+ /** Maximum number of entries to return */
40
+ limit?: number;
41
+ }
42
+ /**
43
+ * Manages local run history stored in ~/.kici/history/runs.json.
44
+ *
45
+ * Entries are capped at 200 (oldest removed first when exceeded).
46
+ * The history file is created lazily on first write.
47
+ */
48
+ export declare class RunHistory {
49
+ private readonly historyDir;
50
+ private readonly historyFile;
51
+ private entries;
52
+ constructor(historyDir?: string);
53
+ /**
54
+ * Add a new entry to the history.
55
+ * Creates the history file and directory if they don't exist.
56
+ * Caps history at MAX_ENTRIES by removing the oldest entries.
57
+ */
58
+ addEntry(entry: HistoryEntry): Promise<void>;
59
+ /**
60
+ * Update an existing entry by runId.
61
+ * Merges the provided updates into the existing entry.
62
+ * No-op if the runId is not found.
63
+ */
64
+ updateEntry(runId: string, updates: Partial<HistoryEntry>): Promise<void>;
65
+ /**
66
+ * Get entries with optional filtering.
67
+ * Returns entries in most-recent-first order.
68
+ */
69
+ getEntries(filter?: HistoryFilter): HistoryEntry[];
70
+ /**
71
+ * Get a single entry by runId.
72
+ */
73
+ getEntry(runId: string): HistoryEntry | undefined;
74
+ /**
75
+ * Format entries as a table string for terminal display.
76
+ *
77
+ * Columns: Run ID | Fixture | Status | Duration | Started
78
+ * Status is color-coded, Started uses relative time.
79
+ */
80
+ formatTable(entries: HistoryEntry[]): string;
81
+ /**
82
+ * Load entries from disk. Caches in memory for subsequent reads.
83
+ */
84
+ load(): Promise<HistoryEntry[]>;
85
+ /**
86
+ * Write entries to disk.
87
+ */
88
+ private save;
89
+ }
90
+ export {};
91
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1,146 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ import { getConfigDir } from "./config.js";
3
+ import { formatRelativeTime } from "../format.js";
4
+ import pc from "picocolors";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { formatDuration } from "@kici-dev/shared";
8
+ import picomatch from "picomatch";
9
+ //#region src/remote/history.ts
10
+ /**
11
+ * Local run history manager.
12
+ *
13
+ * Persists test run history in ~/.kici/history/runs.json so developers
14
+ * can review past runs (kici test --history) and inspect specific runs
15
+ * (kici status <run-id>).
16
+ */
17
+ /** Maximum number of entries to keep in history */
18
+ const MAX_ENTRIES = 200;
19
+ /**
20
+ * Manages local run history stored in ~/.kici/history/runs.json.
21
+ *
22
+ * Entries are capped at 200 (oldest removed first when exceeded).
23
+ * The history file is created lazily on first write.
24
+ */
25
+ var RunHistory = class {
26
+ historyDir;
27
+ historyFile;
28
+ entries = null;
29
+ constructor(historyDir) {
30
+ this.historyDir = historyDir ?? path.join(getConfigDir(), "history");
31
+ this.historyFile = path.join(this.historyDir, "runs.json");
32
+ }
33
+ /**
34
+ * Add a new entry to the history.
35
+ * Creates the history file and directory if they don't exist.
36
+ * Caps history at MAX_ENTRIES by removing the oldest entries.
37
+ */
38
+ async addEntry(entry) {
39
+ const entries = await this.load();
40
+ entries.push(entry);
41
+ while (entries.length > MAX_ENTRIES) entries.shift();
42
+ await this.save(entries);
43
+ }
44
+ /**
45
+ * Update an existing entry by runId.
46
+ * Merges the provided updates into the existing entry.
47
+ * No-op if the runId is not found.
48
+ */
49
+ async updateEntry(runId, updates) {
50
+ const entries = await this.load();
51
+ const index = entries.findIndex((e) => e.runId === runId);
52
+ if (index === -1) return;
53
+ entries[index] = {
54
+ ...entries[index],
55
+ ...updates
56
+ };
57
+ await this.save(entries);
58
+ }
59
+ /**
60
+ * Get entries with optional filtering.
61
+ * Returns entries in most-recent-first order.
62
+ */
63
+ getEntries(filter) {
64
+ const entries = this.entries ? [...this.entries] : [];
65
+ entries.reverse();
66
+ let filtered = entries;
67
+ if (filter?.status) filtered = filtered.filter((e) => e.status === filter.status);
68
+ if (filter?.fixture) {
69
+ const matcher = picomatch(filter.fixture);
70
+ filtered = filtered.filter((e) => matcher(e.fixtureId));
71
+ }
72
+ if (filter?.limit && filter.limit > 0) filtered = filtered.slice(0, filter.limit);
73
+ return filtered;
74
+ }
75
+ /**
76
+ * Get a single entry by runId.
77
+ */
78
+ getEntry(runId) {
79
+ if (!this.entries) return void 0;
80
+ return this.entries.find((e) => e.runId === runId);
81
+ }
82
+ /**
83
+ * Format entries as a table string for terminal display.
84
+ *
85
+ * Columns: Run ID | Fixture | Status | Duration | Started
86
+ * Status is color-coded, Started uses relative time.
87
+ */
88
+ formatTable(entries) {
89
+ if (entries.length === 0) return pc.gray("No run history found.");
90
+ const idWidth = Math.max(10, ...entries.map((e) => e.runId.length)) + 2;
91
+ const fixtureWidth = Math.max(10, ...entries.map((e) => e.fixtureId.length)) + 2;
92
+ const lines = [];
93
+ lines.push(`${"Run ID".padEnd(idWidth)}${"Fixture".padEnd(fixtureWidth)}${"Status".padEnd(12)}${"Duration".padEnd(12)}Started`);
94
+ lines.push(`${"".padEnd(idWidth - 1, "-")} ${"".padEnd(fixtureWidth - 1, "-")} ${"".padEnd(10, "-")} ${"".padEnd(10, "-")} ${"".padEnd(15, "-")}`);
95
+ for (const entry of entries) {
96
+ const id = entry.runId.padEnd(idWidth);
97
+ const fixture = entry.fixtureId.padEnd(fixtureWidth);
98
+ const status = formatStatus(entry.status).padEnd(12);
99
+ const duration = entry.durationMs ? formatDuration(entry.durationMs).padEnd(12) : "-".padEnd(12);
100
+ const started = formatRelativeTime(entry.startedAt);
101
+ lines.push(`${id}${fixture}${status}${duration}${started}`);
102
+ }
103
+ return lines.join("\n");
104
+ }
105
+ /**
106
+ * Load entries from disk. Caches in memory for subsequent reads.
107
+ */
108
+ async load() {
109
+ if (this.entries !== null) return this.entries;
110
+ try {
111
+ const content = await fs.readFile(this.historyFile, "utf-8");
112
+ this.entries = JSON.parse(content);
113
+ return this.entries;
114
+ } catch (err) {
115
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
116
+ this.entries = [];
117
+ return this.entries;
118
+ }
119
+ throw err;
120
+ }
121
+ }
122
+ /**
123
+ * Write entries to disk.
124
+ */
125
+ async save(entries) {
126
+ await fs.mkdir(this.historyDir, { recursive: true });
127
+ await fs.writeFile(this.historyFile, JSON.stringify(entries, null, 2) + "\n");
128
+ this.entries = entries;
129
+ }
130
+ };
131
+ /**
132
+ * Format a run status with appropriate color and symbol.
133
+ */
134
+ function formatStatus(status) {
135
+ switch (status) {
136
+ case "success": return pc.green("pass");
137
+ case "failed": return pc.red("fail");
138
+ case "cancelled": return pc.yellow("cancel");
139
+ case "running": return pc.blue("running");
140
+ case "queued": return pc.gray("queued");
141
+ }
142
+ }
143
+ //#endregion
144
+ export { RunHistory };
145
+
146
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1,65 @@
1
+ /** Options for the PKCE authorization code flow. */
2
+ interface PkceFlowOptions {
3
+ /** OIDC issuer URL (e.g., https://auth.your-domain.example.com) */
4
+ issuer: string;
5
+ /** OAuth client ID for the CLI application */
6
+ clientId: string;
7
+ /** OIDC project ID (adds audience scope for JWT access tokens) */
8
+ projectId?: string;
9
+ }
10
+ /** Options for the RFC 8628 device authorization flow. */
11
+ interface DeviceFlowOptions {
12
+ /** OIDC issuer URL */
13
+ issuer: string;
14
+ /** OAuth client ID for the CLI application */
15
+ clientId: string;
16
+ /** OIDC project ID (adds audience scope for JWT access tokens) */
17
+ projectId?: string;
18
+ }
19
+ /** Options for exchanging an OIDC token for a PAT. */
20
+ interface ExchangeTokenOptions {
21
+ /** Platform API base URL */
22
+ platformUrl: string;
23
+ /** OIDC access token */
24
+ accessToken: string;
25
+ /** Machine hostname for PAT naming */
26
+ machineName: string;
27
+ }
28
+ /** Response from the PAT exchange endpoint. */
29
+ interface ExchangeTokenResult {
30
+ /** PAT identifier */
31
+ id: string;
32
+ /** Raw PAT value (kici_pat_...) */
33
+ token: string;
34
+ /** ISO 8601 expiry timestamp */
35
+ expiresAt: string;
36
+ }
37
+ /**
38
+ * PKCE authorization code flow with localhost callback.
39
+ *
40
+ * Spins up a temporary HTTP server on a random port, opens the browser
41
+ * to the ZITADEL authorization endpoint, captures the callback with the
42
+ * authorization code, and exchanges it for tokens.
43
+ *
44
+ * @returns OIDC access token
45
+ */
46
+ export declare function pkceFlow(opts: PkceFlowOptions): Promise<string>;
47
+ /**
48
+ * RFC 8628 device authorization flow.
49
+ *
50
+ * Requests a device code from ZITADEL, displays the user code and
51
+ * verification URI, then polls the token endpoint until the user
52
+ * authorizes the device or the code expires.
53
+ *
54
+ * @returns OIDC access token
55
+ */
56
+ export declare function deviceFlow(opts: DeviceFlowOptions): Promise<string>;
57
+ /**
58
+ * Exchange an OIDC access token for a personal access token (PAT).
59
+ *
60
+ * POSTs to the Platform /api/v1/cli/exchange-token endpoint with the
61
+ * OIDC token as Bearer auth. Returns the generated PAT details.
62
+ */
63
+ export declare function exchangeTokenForPat(opts: ExchangeTokenOptions): Promise<ExchangeTokenResult>;
64
+ export {};
65
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1,302 @@
1
+ import "../chunk-gOLHoazu.js";
2
+ import pc from "picocolors";
3
+ import { exec } from "node:child_process";
4
+ import { toErrorMessage } from "@kici-dev/shared";
5
+ import { createHash, randomBytes } from "node:crypto";
6
+ import { createServer } from "node:http";
7
+ import open from "open";
8
+ //#region src/remote/oauth.ts
9
+ /**
10
+ * Wrap a fetch call so that transport errors (DNS failure, connection refused,
11
+ * TLS handshake failure) surface with the URL that failed and a hint about
12
+ * which env var the user should check, instead of Node's bare "fetch failed".
13
+ */
14
+ async function fetchOrThrow(url, init, kind) {
15
+ try {
16
+ return await fetch(url, init);
17
+ } catch (err) {
18
+ const envHint = kind === "Platform" ? "KICI_PLATFORM_URL" : "KICI_OIDC_ISSUER";
19
+ throw new Error(`Could not reach ${kind} at ${url} (${toErrorMessage(err)}). Check ${envHint}.`);
20
+ }
21
+ }
22
+ /**
23
+ * Generate a PKCE code verifier and challenge.
24
+ *
25
+ * Verifier: 32 random bytes, base64url encoded.
26
+ * Challenge: SHA-256 of verifier, base64url encoded.
27
+ */
28
+ function generatePkceChallenge() {
29
+ const verifier = randomBytes(32).toString("base64url");
30
+ return {
31
+ verifier,
32
+ challenge: createHash("sha256").update(verifier).digest("base64url")
33
+ };
34
+ }
35
+ /** HTML page served after successful PKCE callback. */
36
+ const SUCCESS_HTML = `<!DOCTYPE html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="utf-8">
40
+ <meta name="viewport" content="width=device-width, initial-scale=1">
41
+ <title>KiCI - Login successful</title>
42
+ <style>
43
+ body {
44
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ min-height: 100vh;
49
+ margin: 0;
50
+ background: #0f1117;
51
+ color: #e1e1e6;
52
+ }
53
+ .card {
54
+ text-align: center;
55
+ padding: 3rem 2rem;
56
+ background: #1a1b23;
57
+ border-radius: 12px;
58
+ border: 1px solid #2d2e3a;
59
+ max-width: 420px;
60
+ }
61
+ .logo { font-size: 2.5rem; font-weight: 700; margin-bottom: 1rem; }
62
+ .logo span { color: #4c6ef5; }
63
+ .check { font-size: 3rem; margin-bottom: 1rem; }
64
+ h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
65
+ p { color: #8b8c99; margin: 0.5rem 0; }
66
+ .hint { font-size: 0.875rem; color: #6b6c7a; margin-top: 1.5rem; }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <div class="card">
71
+ <div class="logo">Ki<span>CI</span></div>
72
+ <div class="check">&#10003;</div>
73
+ <h1>Login success!</h1>
74
+ <p>You can close this tab and return to your terminal.</p>
75
+ <p class="hint">Your CLI session has been authenticated.</p>
76
+ </div>
77
+ </body>
78
+ </html>`;
79
+ /**
80
+ * PKCE authorization code flow with localhost callback.
81
+ *
82
+ * Spins up a temporary HTTP server on a random port, opens the browser
83
+ * to the ZITADEL authorization endpoint, captures the callback with the
84
+ * authorization code, and exchanges it for tokens.
85
+ *
86
+ * @returns OIDC access token
87
+ */
88
+ async function pkceFlow(opts) {
89
+ const { issuer, clientId, projectId } = opts;
90
+ const { verifier, challenge } = generatePkceChallenge();
91
+ const state = randomBytes(16).toString("hex");
92
+ let listenPort = 0;
93
+ const callbackPortEnv = process.env.KICI_CALLBACK_PORT;
94
+ if (callbackPortEnv) {
95
+ const parsed = parseInt(callbackPortEnv, 10);
96
+ if (isNaN(parsed) || parsed < 0) throw new Error(`KICI_CALLBACK_PORT must be a valid non-negative integer, got: "${callbackPortEnv}"`);
97
+ listenPort = parsed;
98
+ }
99
+ const TIMEOUT_MS = 300 * 1e3;
100
+ return new Promise((resolve, reject) => {
101
+ let timer;
102
+ const server = createServer(async (req, res) => {
103
+ const url = new URL(req.url, `http://127.0.0.1`);
104
+ if (url.pathname !== "/callback") {
105
+ res.writeHead(404);
106
+ res.end("Not found");
107
+ return;
108
+ }
109
+ const code = url.searchParams.get("code");
110
+ const returnedState = url.searchParams.get("state");
111
+ const error = url.searchParams.get("error");
112
+ if (error) {
113
+ const description = url.searchParams.get("error_description") || error;
114
+ res.writeHead(400, { "Content-Type": "text/plain" });
115
+ res.end(`Authentication error: ${description}`);
116
+ cleanup();
117
+ reject(/* @__PURE__ */ new Error(`Authentication error: ${description}`));
118
+ return;
119
+ }
120
+ if (returnedState !== state) {
121
+ res.writeHead(400, { "Content-Type": "text/plain" });
122
+ res.end("State mismatch - possible CSRF attack");
123
+ cleanup();
124
+ reject(/* @__PURE__ */ new Error("State mismatch - possible CSRF attack"));
125
+ return;
126
+ }
127
+ if (!code) {
128
+ res.writeHead(400, { "Content-Type": "text/plain" });
129
+ res.end("Missing authorization code");
130
+ cleanup();
131
+ reject(/* @__PURE__ */ new Error("Missing authorization code in callback"));
132
+ return;
133
+ }
134
+ res.writeHead(200, { "Content-Type": "text/html" });
135
+ res.end(SUCCESS_HTML);
136
+ const port = server.address().port;
137
+ try {
138
+ const tokenRes = await fetchOrThrow(`${issuer}/oauth/v2/token`, {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
141
+ body: new URLSearchParams({
142
+ grant_type: "authorization_code",
143
+ client_id: clientId,
144
+ code,
145
+ redirect_uri: `http://127.0.0.1:${port}/callback`,
146
+ code_verifier: verifier
147
+ })
148
+ }, "Zitadel");
149
+ if (!tokenRes.ok) {
150
+ const errorBody = await tokenRes.text();
151
+ cleanup();
152
+ reject(/* @__PURE__ */ new Error(`Token exchange failed: ${errorBody}`));
153
+ return;
154
+ }
155
+ const tokenData = await tokenRes.json();
156
+ cleanup();
157
+ resolve(tokenData.access_token);
158
+ } catch (err) {
159
+ cleanup();
160
+ reject(err);
161
+ }
162
+ });
163
+ function cleanup() {
164
+ if (timer) clearTimeout(timer);
165
+ if (server.listening) server.close();
166
+ }
167
+ server.listen(listenPort, "127.0.0.1", () => {
168
+ const actualPort = server.address().port;
169
+ const redirectUri = `http://127.0.0.1:${actualPort}/callback`;
170
+ const authUrl = new URL(`${issuer}/oauth/v2/authorize`);
171
+ authUrl.searchParams.set("client_id", clientId);
172
+ authUrl.searchParams.set("redirect_uri", redirectUri);
173
+ authUrl.searchParams.set("response_type", "code");
174
+ const scopes = [
175
+ "openid",
176
+ "profile",
177
+ "email",
178
+ "offline_access"
179
+ ];
180
+ if (projectId) scopes.push(`urn:zitadel:iam:org:project:id:${projectId}:aud`);
181
+ authUrl.searchParams.set("scope", scopes.join(" "));
182
+ authUrl.searchParams.set("code_challenge", challenge);
183
+ authUrl.searchParams.set("code_challenge_method", "S256");
184
+ authUrl.searchParams.set("state", state);
185
+ const browserCmd = process.env.KICI_BROWSER_CMD;
186
+ if (browserCmd === "none") {
187
+ console.log(`KICI_AUTH_URL=${authUrl.toString()}`);
188
+ console.log(pc.cyan(` Waiting for browser callback on port ${actualPort}...`));
189
+ } else if (browserCmd) exec(browserCmd.replace("{url}", authUrl.toString()), (err) => {
190
+ if (err) console.log(pc.yellow(` Custom browser command failed: ${err.message}\n Please visit:\n ${authUrl.toString()}`));
191
+ });
192
+ else {
193
+ console.log(pc.cyan(" Opening browser for authentication..."));
194
+ open(authUrl.toString()).catch(() => {
195
+ console.log(pc.yellow(` Could not open browser automatically. Please visit:\n ${authUrl.toString()}`));
196
+ });
197
+ }
198
+ });
199
+ timer = setTimeout(() => {
200
+ cleanup();
201
+ reject(/* @__PURE__ */ new Error("Authentication timed out after 5 minutes. Please try again with `kici login`."));
202
+ }, TIMEOUT_MS);
203
+ });
204
+ }
205
+ /** Sleep helper for device flow polling. */
206
+ function sleep(ms) {
207
+ return new Promise((resolve) => setTimeout(resolve, ms));
208
+ }
209
+ /**
210
+ * RFC 8628 device authorization flow.
211
+ *
212
+ * Requests a device code from ZITADEL, displays the user code and
213
+ * verification URI, then polls the token endpoint until the user
214
+ * authorizes the device or the code expires.
215
+ *
216
+ * @returns OIDC access token
217
+ */
218
+ async function deviceFlow(opts) {
219
+ const { issuer, clientId, projectId } = opts;
220
+ const scopes = [
221
+ "openid",
222
+ "profile",
223
+ "email",
224
+ "offline_access"
225
+ ];
226
+ if (projectId) scopes.push(`urn:zitadel:iam:org:project:id:${projectId}:aud`);
227
+ const deviceAuthRes = await fetchOrThrow(`${issuer}/oauth/v2/device_authorization`, {
228
+ method: "POST",
229
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
230
+ body: new URLSearchParams({
231
+ client_id: clientId,
232
+ scope: scopes.join(" ")
233
+ })
234
+ }, "Zitadel");
235
+ if (!deviceAuthRes.ok) {
236
+ const errorBody = await deviceAuthRes.text();
237
+ throw new Error(`Device authorization request failed: ${errorBody}`);
238
+ }
239
+ const deviceAuth = await deviceAuthRes.json();
240
+ console.log();
241
+ console.log(pc.bold(" Device authorization"));
242
+ if (deviceAuth.verification_uri_complete) {
243
+ console.log(` Open ${pc.cyan(deviceAuth.verification_uri_complete)}`);
244
+ console.log(pc.gray(" The code is pre-filled; approve on the Zitadel screen."));
245
+ console.log(pc.gray(` Enter code: ${pc.bold(pc.yellow(deviceAuth.user_code))} (if prompted)`));
246
+ } else {
247
+ console.log(` Open ${pc.cyan(deviceAuth.verification_uri)}`);
248
+ console.log(` Enter code: ${pc.bold(pc.yellow(deviceAuth.user_code))}`);
249
+ }
250
+ console.log(pc.gray(` Code expires in ${Math.floor(deviceAuth.expires_in / 60)} minutes`));
251
+ console.log();
252
+ let intervalMs = (deviceAuth.interval ?? 5) * 1e3;
253
+ while (true) {
254
+ await sleep(intervalMs);
255
+ const tokenRes = await fetchOrThrow(`${issuer}/oauth/v2/token`, {
256
+ method: "POST",
257
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
258
+ body: new URLSearchParams({
259
+ client_id: clientId,
260
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
261
+ device_code: deviceAuth.device_code
262
+ })
263
+ }, "Zitadel");
264
+ if (tokenRes.ok) return (await tokenRes.json()).access_token;
265
+ const errorData = await tokenRes.json();
266
+ switch (errorData.error) {
267
+ case "authorization_pending": continue;
268
+ case "slow_down":
269
+ intervalMs += 5e3;
270
+ continue;
271
+ case "expired_token": throw new Error("Device code expired. Please run `kici login` again to restart authentication.");
272
+ case "access_denied": throw new Error(errorData.error_description || "Access denied by user");
273
+ default: throw new Error(errorData.error_description || `Device flow error: ${errorData.error}`);
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * Exchange an OIDC access token for a personal access token (PAT).
279
+ *
280
+ * POSTs to the Platform /api/v1/cli/exchange-token endpoint with the
281
+ * OIDC token as Bearer auth. Returns the generated PAT details.
282
+ */
283
+ async function exchangeTokenForPat(opts) {
284
+ const { platformUrl, accessToken, machineName } = opts;
285
+ const res = await fetchOrThrow(`${platformUrl}/api/v1/cli/exchange-token`, {
286
+ method: "POST",
287
+ headers: {
288
+ Authorization: `Bearer ${accessToken}`,
289
+ "Content-Type": "application/json"
290
+ },
291
+ body: JSON.stringify({ machineName })
292
+ }, "Platform");
293
+ if (!res.ok) {
294
+ const errorBody = await res.text();
295
+ throw new Error(`Token exchange failed (${res.status}): ${errorBody}`);
296
+ }
297
+ return await res.json();
298
+ }
299
+ //#endregion
300
+ export { deviceFlow, exchangeTokenForPat, pkceFlow };
301
+
302
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1,81 @@
1
+ /**
2
+ * WebSocket observer client for real-time run streaming.
3
+ *
4
+ * Connects to the orchestrator's observer WS endpoint, subscribes to a
5
+ * specific run, and relays real-time events (logs, step changes, status
6
+ * updates, completion) to registered callbacks.
7
+ *
8
+ * Features:
9
+ * - Auto-reconnect with lastSeenSequence for backfill (up to 5 attempts)
10
+ * - Promise-based waitForCompletion()
11
+ * - Clean disconnect
12
+ */
13
+ import type { ObserveLog, ObserveStep, ObserveStatus, ObserveComplete } from '@kici-dev/engine';
14
+ /** Configuration for ObserverClient. */
15
+ interface ObserverClientConfig {
16
+ /** WebSocket URL for the observer endpoint. */
17
+ observeUrl: string;
18
+ /** Authentication token. */
19
+ token: string;
20
+ /** Run ID to observe. */
21
+ runId: string;
22
+ /** Callback for log messages. */
23
+ onLog: (msg: ObserveLog & {
24
+ sequence: number;
25
+ }) => void;
26
+ /** Callback for step lifecycle changes. */
27
+ onStep: (msg: ObserveStep & {
28
+ sequence: number;
29
+ }) => void;
30
+ /** Callback for status changes. */
31
+ onStatus: (msg: ObserveStatus & {
32
+ sequence: number;
33
+ }) => void;
34
+ /** Callback for run completion. */
35
+ onComplete: (msg: ObserveComplete & {
36
+ sequence: number;
37
+ }) => void;
38
+ /** Callback for errors (WS errors or auth failures). */
39
+ onError: (err: Error) => void;
40
+ /** Maximum reconnection attempts. Default: 5. */
41
+ maxReconnectAttempts?: number;
42
+ /** Reconnection delay in ms. Default: 1000. */
43
+ reconnectDelayMs?: number;
44
+ }
45
+ export declare class ObserverClient {
46
+ private ws;
47
+ private lastSeenSequence;
48
+ private reconnectAttempts;
49
+ private intentionalDisconnect;
50
+ private initialConnectCompleted;
51
+ private reconnectTimer;
52
+ private completionResolve;
53
+ private completionReject;
54
+ private completionResult;
55
+ private readonly config;
56
+ constructor(config: ObserverClientConfig);
57
+ /**
58
+ * Connect to the observer endpoint and send the subscribe message.
59
+ * Resolves when the WS connection is open and subscribe is sent.
60
+ */
61
+ connect(): Promise<void>;
62
+ /**
63
+ * Returns a promise that resolves when the run completes.
64
+ * If the run already completed (from a prior observe.complete),
65
+ * resolves immediately.
66
+ */
67
+ waitForCompletion(): Promise<ObserveComplete & {
68
+ sequence: number;
69
+ }>;
70
+ /**
71
+ * Gracefully disconnect from the observer endpoint.
72
+ */
73
+ disconnect(): void;
74
+ /** Current lastSeenSequence (for testing). */
75
+ getLastSeenSequence(): number;
76
+ private doConnect;
77
+ private handleMessage;
78
+ private scheduleReconnect;
79
+ }
80
+ export {};
81
+ //# sourceMappingURL=observer.d.ts.map