@kici-dev/compiler 0.1.27 → 0.3.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 (153) 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.js +120 -11
  4. package/dist/commands/compile.js +28 -4
  5. package/dist/commands/diagnostics.js +14 -2
  6. package/dist/commands/doctor.d.ts +64 -0
  7. package/dist/commands/doctor.js +413 -0
  8. package/dist/commands/endpoints.js +6 -0
  9. package/dist/commands/fixture.js +13 -0
  10. package/dist/commands/hook.js +3 -2
  11. package/dist/commands/index.d.ts +8 -0
  12. package/dist/commands/index.js +5 -1
  13. package/dist/commands/init-host-os.d.ts +34 -0
  14. package/dist/commands/init-host-os.js +44 -0
  15. package/dist/commands/init.d.ts +4 -0
  16. package/dist/commands/init.js +159 -36
  17. package/dist/commands/local.d.ts +28 -2
  18. package/dist/commands/local.js +74 -5
  19. package/dist/commands/login.js +1 -1
  20. package/dist/commands/notifications.d.ts +61 -0
  21. package/dist/commands/notifications.js +274 -0
  22. package/dist/commands/preview.js +8 -1
  23. package/dist/commands/run-routed.js +10 -1
  24. package/dist/commands/run.js +41 -12
  25. package/dist/commands/runs/artifacts/download.d.ts +24 -0
  26. package/dist/commands/runs/artifacts/download.js +154 -0
  27. package/dist/commands/runs/artifacts/list.d.ts +5 -0
  28. package/dist/commands/runs/artifacts/list.js +47 -0
  29. package/dist/commands/runs/cancel.js +2 -2
  30. package/dist/commands/runs/list.js +8 -2
  31. package/dist/commands/verify-attestation.d.ts +3 -1
  32. package/dist/commands/verify-attestation.js +18 -7
  33. package/dist/errors/formatter.d.ts +14 -1
  34. package/dist/errors/formatter.js +13 -3
  35. package/dist/errors/index.d.ts +2 -1
  36. package/dist/errors/index.js +3 -2
  37. package/dist/errors/source-location.d.ts +20 -0
  38. package/dist/errors/source-location.js +45 -0
  39. package/dist/execution/executor.js +25 -5
  40. package/dist/fixtures/defaults/index.js +2 -1
  41. package/dist/llm-context/llms-architecture.txt +51 -22
  42. package/dist/llm-context/llms-cli.txt +2461 -1582
  43. package/dist/llm-context/llms-features.txt +178 -106
  44. package/dist/llm-context/llms-full.txt +6127 -4426
  45. package/dist/llm-context/llms-getting-started.txt +306 -42
  46. package/dist/llm-context/llms-patterns.txt +13 -7
  47. package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
  48. package/dist/llm-context/llms-sdk.txt +591 -2152
  49. package/dist/llm-context/llms.txt +30 -16
  50. package/dist/local-plane/orchestrator-process.d.ts +12 -7
  51. package/dist/local-plane/orchestrator-process.js +28 -14
  52. package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
  53. package/dist/local-plane/os-mismatch-hint.js +34 -0
  54. package/dist/local-plane/plane-liveness.d.ts +68 -0
  55. package/dist/local-plane/plane-liveness.js +145 -0
  56. package/dist/local-plane/plane-manager.d.ts +60 -18
  57. package/dist/local-plane/plane-manager.js +210 -70
  58. package/dist/local-plane/plane-trigger.d.ts +13 -4
  59. package/dist/local-plane/plane-trigger.js +27 -14
  60. package/dist/local-plane/platform-attach.js +1 -5
  61. package/dist/local-plane/port-holder.d.ts +78 -0
  62. package/dist/local-plane/port-holder.js +227 -0
  63. package/dist/local-plane/postgres.d.ts +15 -3
  64. package/dist/local-plane/postgres.js +34 -12
  65. package/dist/local-plane/run-follow.d.ts +20 -0
  66. package/dist/local-plane/run-follow.js +51 -4
  67. package/dist/local-plane/scaler-config.d.ts +28 -14
  68. package/dist/local-plane/scaler-config.js +59 -41
  69. package/dist/local-plane/source-provider.js +4 -10
  70. package/dist/lockfile/generator.d.ts +14 -1
  71. package/dist/lockfile/generator.js +99 -33
  72. package/dist/lockfile/index.d.ts +3 -1
  73. package/dist/lockfile/index.js +3 -2
  74. package/dist/lockfile/purity-diagnostics.d.ts +31 -0
  75. package/dist/lockfile/purity-diagnostics.js +52 -0
  76. package/dist/postinstall.js +2 -1
  77. package/dist/remote/artifact-extract.d.ts +8 -0
  78. package/dist/remote/artifact-extract.js +58 -0
  79. package/dist/remote/config.d.ts +21 -3
  80. package/dist/remote/config.js +24 -5
  81. package/dist/remote/dashboard-client.d.ts +39 -2
  82. package/dist/remote/dashboard-client.js +47 -4
  83. package/dist/remote/fs-case.d.ts +21 -0
  84. package/dist/remote/fs-case.js +45 -0
  85. package/dist/remote/history.d.ts +9 -3
  86. package/dist/remote/history.js +79 -10
  87. package/dist/remote/local-repo-identity.d.ts +1 -1
  88. package/dist/remote/local-repo-identity.js +2 -1
  89. package/dist/remote/notifications-client.d.ts +149 -0
  90. package/dist/remote/notifications-client.js +103 -0
  91. package/dist/remote/oauth.d.ts +13 -0
  92. package/dist/remote/oauth.js +31 -5
  93. package/dist/remote/output/summary.js +76 -14
  94. package/dist/remote/render.js +30 -9
  95. package/dist/remote/uploader.js +5 -4
  96. package/dist/templates/agents-md.d.ts +1 -1
  97. package/dist/templates/agents-md.js +6 -6
  98. package/dist/templates/index.d.ts +1 -1
  99. package/dist/templates/index.js +2 -2
  100. package/dist/templates/package-json.d.ts +15 -0
  101. package/dist/templates/package-json.js +26 -5
  102. package/dist/test-runner/dry-run.d.ts +2 -1
  103. package/dist/test-runner/dry-run.js +18 -1
  104. package/dist/test-runner/event-types.d.ts +2 -0
  105. package/dist/test-runner/event-types.js +3 -0
  106. package/dist/test-runner/job-executor.d.ts +1 -0
  107. package/dist/test-runner/job-executor.js +81 -43
  108. package/dist/test-runner/output-formatter.d.ts +5 -2
  109. package/dist/test-runner/output-formatter.js +6 -4
  110. package/dist/test-runner/payload-builder.d.ts +7 -1
  111. package/dist/test-runner/payload-builder.js +11 -2
  112. package/dist/test-runner/rule-evaluator.d.ts +7 -3
  113. package/dist/test-runner/rule-evaluator.js +13 -12
  114. package/dist/test-runner/step-context.d.ts +23 -5
  115. package/dist/test-runner/step-context.js +28 -118
  116. package/dist/types.d.ts +21 -2
  117. package/dist/types.js +4 -2
  118. package/dist/validation/index.d.ts +2 -0
  119. package/dist/validation/index.js +2 -1
  120. package/dist/validation/typecheck.d.ts +28 -0
  121. package/dist/validation/typecheck.js +110 -0
  122. package/dist/validation/validator.d.ts +9 -4
  123. package/dist/validation/validator.js +38 -44
  124. package/package.json +12 -15
  125. package/sbom.spdx.json +1322 -1964
  126. package/dist/assets/api-TJJVHrjC.json +0 -118
  127. package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
  128. package/dist/assets/package-BpQF9kR8.json +0 -74
  129. package/dist/assets/package-Ceo2h27X.json +0 -89
  130. package/dist/assets/source_context-D0atuL28.json +0 -20
  131. package/dist/assets/type-BFqO8SCZ.json +0 -202
  132. package/dist/commands/cancel.d.ts +0 -22
  133. package/dist/commands/detect-package-manager.d.ts +0 -42
  134. package/dist/commands/held-run-resolve.d.ts +0 -50
  135. package/dist/commands/status.d.ts +0 -34
  136. package/dist/commands/test.d.ts +0 -88
  137. package/dist/errors/codes.d.ts +0 -25
  138. package/dist/local-executor/dag-scheduler.d.ts +0 -44
  139. package/dist/local-executor/index.d.ts +0 -23
  140. package/dist/local-executor/job-runner.d.ts +0 -47
  141. package/dist/local-executor/materializer.d.ts +0 -44
  142. package/dist/local-executor/output-streamer.d.ts +0 -31
  143. package/dist/local-executor/payload-generator.d.ts +0 -16
  144. package/dist/local-executor/picker.d.ts +0 -33
  145. package/dist/local-executor/runs-on-display.d.ts +0 -9
  146. package/dist/local-executor/secret-loader.d.ts +0 -18
  147. package/dist/local-executor/to-event-payload.d.ts +0 -16
  148. package/dist/local-executor/types.d.ts +0 -93
  149. package/dist/local-executor/workflow-lock.d.ts +0 -82
  150. package/dist/package-F7UXSDHW.json +0 -74
  151. package/dist/remote/client.d.ts +0 -210
  152. package/dist/remote/observer.d.ts +0 -81
  153. package/dist/test-runner/summary.d.ts +0 -7
@@ -4,7 +4,7 @@
4
4
  * only place in the CLI that knows these routes.
5
5
  */
6
6
  import { z } from 'zod';
7
- import { type RunListResponse, type RunListItem, type DiagnosticsInfrastructureResponse, type DiagnosticsSummaryResponse, type DashboardRunDetailApiResponse, type DashboardStepLogsApiResponse, type RegistrationItem } from '@kici-dev/engine';
7
+ import { type ArtifactListItem, type RunListResponse, type RunListItem, type DiagnosticsInfrastructureResponse, type DiagnosticsSummaryResponse, type DashboardRunDetailApiResponse, type DashboardStepLogsApiResponse, type RegistrationItem } from '@kici-dev/engine';
8
8
  import { type GlobalConfig } from './config.js';
9
9
  export type DashboardErrorKind = 'not_logged_in' | 'no_active_org' | 'unauthorized' | 'forbidden' | 'not_found' | 'conflict' | 'cooldown' | 'orchestrator_offline' | 'http';
10
10
  /** Typed error raised by every DashboardClient method on a non-2xx response. */
@@ -22,8 +22,21 @@ export interface RunsListFilters {
22
22
  trigger?: string;
23
23
  source?: string;
24
24
  since?: string;
25
- page?: number;
25
+ /** Opaque keyset cursor (from a prior page's `nextCursor`). Absent = first page. */
26
+ cursor?: string;
26
27
  }
28
+ export declare const webhookActivitySchema: z.ZodObject<{
29
+ windowMinutes: z.ZodNumber;
30
+ received: z.ZodNumber;
31
+ delivered: z.ZodNumber;
32
+ edgeRejected: z.ZodNumber;
33
+ failed: z.ZodNumber;
34
+ matched: z.ZodOptional<z.ZodNumber>;
35
+ unmatched: z.ZodOptional<z.ZodNumber>;
36
+ lockfileMissing: z.ZodOptional<z.ZodNumber>;
37
+ orchestratorUnavailable: z.ZodBoolean;
38
+ }, z.core.$strip>;
39
+ export type WebhookActivity = z.infer<typeof webhookActivitySchema>;
27
40
  /** Result of {@link DashboardClient.listRegistrations}. */
28
41
  export interface RegistrationsListResult {
29
42
  registrations: RegistrationItem[];
@@ -48,6 +61,14 @@ declare const contextContextSchema: z.ZodObject<{
48
61
  }, z.core.$strip>;
49
62
  /** A secret context (context) returned by {@link DashboardClient.listContexts}. */
50
63
  export type ContextContext = z.infer<typeof contextContextSchema>;
64
+ /**
65
+ * Parsed shape of `GET /runs/:runId/artifacts` for the CLI. The per-artifact
66
+ * shape is reused from `@kici-dev/engine` so the CLI and the server cannot drift.
67
+ */
68
+ export interface ArtifactsListResult {
69
+ artifacts: ArtifactListItem[];
70
+ downloadUrlExpiresInSeconds?: number;
71
+ }
51
72
  export declare class DashboardClient {
52
73
  private readonly endpoint;
53
74
  private readonly token;
@@ -62,17 +83,33 @@ export declare class DashboardClient {
62
83
  getJson(path: string): Promise<unknown>;
63
84
  /** Generic POST helper used by typed methods (and tests). */
64
85
  postJson(path: string, body?: unknown): Promise<unknown>;
86
+ /** Generic DELETE helper used by typed methods (and tests). */
87
+ deleteJson(path: string): Promise<unknown>;
65
88
  getDiagnosticsSummary(): Promise<DiagnosticsSummaryResponse>;
66
89
  getInfrastructure(): Promise<DiagnosticsInfrastructureResponse>;
67
90
  listRuns(filters?: RunsListFilters): Promise<RunListResponse>;
68
91
  getRun(runId: string): Promise<RunListItem>;
92
+ /**
93
+ * Fetch the org's recent webhook-activity counts. Returns null on ANY error
94
+ * (older Platform without the route, a network blip, an unexpected shape) so
95
+ * the `kici runs` empty-window hint degrades silently to today's output — the
96
+ * hint is a nicety, never a hard dependency.
97
+ */
98
+ getWebhookActivity(windowMinutes?: number): Promise<WebhookActivity | null>;
69
99
  getRunDetail(runId: string): Promise<DashboardRunDetailApiResponse>;
70
100
  getStepLogs(runId: string, jobId: string, stepIndex: number): Promise<DashboardStepLogsApiResponse>;
101
+ /**
102
+ * List a run's artifacts (`GET /runs/:runId/artifacts`). Each entry carries a
103
+ * presigned `downloadUrl` minted by the customer's orchestrator; the Platform
104
+ * relays it without re-signing, so the artifact bytes never transit Platform.
105
+ */
106
+ listArtifacts(runId: string): Promise<ArtifactsListResult>;
71
107
  rerunRun(runId: string): Promise<{
72
108
  newRunId: string;
73
109
  }>;
74
110
  cancelRun(runId: string, force: boolean): Promise<{
75
111
  cancelledJobs?: number;
112
+ alreadyTerminal?: boolean;
76
113
  }>;
77
114
  cancelByBranch(branch: string): Promise<{
78
115
  cancelledRuns?: number;
@@ -1,6 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { loadGlobalConfig } from "./config.js";
3
- import { dashboardRunDetailApiResponseSchema, dashboardStepLogsApiResponseSchema, diagnosticsInfrastructureResponseSchema, diagnosticsSummaryResponseSchema, registrationItemSchema, runListItemSchema, runListResponseSchema } from "@kici-dev/engine";
3
+ import { artifactListItemSchema, dashboardRunDetailApiResponseSchema, dashboardStepLogsApiResponseSchema, diagnosticsInfrastructureResponseSchema, diagnosticsSummaryResponseSchema, registrationItemSchema, runListItemSchema, runListResponseSchema } from "@kici-dev/engine";
4
4
  import { z } from "zod";
5
5
  //#region src/remote/dashboard-client.ts
6
6
  /**
@@ -20,8 +20,22 @@ var DashboardClientError = class extends Error {
20
20
  }
21
21
  };
22
22
  const singleRunSchema = z.object({ run: runListItemSchema });
23
+ const webhookActivitySchema = z.object({
24
+ windowMinutes: z.number(),
25
+ received: z.number(),
26
+ delivered: z.number(),
27
+ edgeRejected: z.number(),
28
+ failed: z.number(),
29
+ matched: z.number().optional(),
30
+ unmatched: z.number().optional(),
31
+ lockfileMissing: z.number().optional(),
32
+ orchestratorUnavailable: z.boolean()
33
+ });
23
34
  const rerunResponseSchema = z.object({ newRunId: z.string() });
24
- const cancelResponseSchema = z.object({ cancelledJobs: z.number().optional() });
35
+ const cancelResponseSchema = z.object({
36
+ cancelledJobs: z.number().optional(),
37
+ alreadyTerminal: z.boolean().optional()
38
+ });
25
39
  const cancelByBranchResponseSchema = z.object({ cancelledRuns: z.number().optional() });
26
40
  const registrationsListSchema = z.object({
27
41
  registrations: z.array(registrationItemSchema).default([]),
@@ -40,6 +54,10 @@ const contextContextSchema = z.object({
40
54
  secretKeys: z.array(z.string()).default([])
41
55
  });
42
56
  const contextsListSchema = z.object({ contexts: z.array(contextContextSchema).default([]) });
57
+ const artifactsListResultSchema = z.object({
58
+ artifacts: z.array(artifactListItemSchema),
59
+ downloadUrlExpiresInSeconds: z.number().int().positive().optional()
60
+ });
43
61
  const STATUS_ERROR_MAP = {
44
62
  401: ["unauthorized", "Authentication failed. Run `kici login` to re-authenticate."],
45
63
  403: ["forbidden", "Access denied."],
@@ -105,6 +123,10 @@ var DashboardClient = class DashboardClient {
105
123
  async postJson(path, body) {
106
124
  return this.request("POST", path, body);
107
125
  }
126
+ /** Generic DELETE helper used by typed methods (and tests). */
127
+ async deleteJson(path) {
128
+ return this.request("DELETE", path);
129
+ }
108
130
  async getDiagnosticsSummary() {
109
131
  return diagnosticsSummaryResponseSchema.parse(await this.getJson("/diagnostics"));
110
132
  }
@@ -120,19 +142,40 @@ var DashboardClient = class DashboardClient {
120
142
  if (filters.trigger) qs.set("triggerType", filters.trigger);
121
143
  if (filters.source) qs.set("source", filters.source);
122
144
  if (filters.since) qs.set("since", filters.since);
123
- if (filters.page) qs.set("page", String(filters.page));
145
+ if (filters.cursor) qs.set("cursor", filters.cursor);
124
146
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
125
147
  return runListResponseSchema.parse(await this.getJson(`/runs${suffix}`));
126
148
  }
127
149
  async getRun(runId) {
128
150
  return singleRunSchema.parse(await this.getJson(`/runs/${runId}`)).run;
129
151
  }
152
+ /**
153
+ * Fetch the org's recent webhook-activity counts. Returns null on ANY error
154
+ * (older Platform without the route, a network blip, an unexpected shape) so
155
+ * the `kici runs` empty-window hint degrades silently to today's output — the
156
+ * hint is a nicety, never a hard dependency.
157
+ */
158
+ async getWebhookActivity(windowMinutes = 60) {
159
+ try {
160
+ return webhookActivitySchema.parse(await this.getJson(`/webhook-activity?windowMinutes=${windowMinutes}`));
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
130
165
  async getRunDetail(runId) {
131
166
  return dashboardRunDetailApiResponseSchema.parse(await this.getJson(`/runs/${runId}/detail`));
132
167
  }
133
168
  async getStepLogs(runId, jobId, stepIndex) {
134
169
  return dashboardStepLogsApiResponseSchema.parse(await this.getJson(`/runs/${runId}/jobs/${jobId}/steps/${stepIndex}/logs`));
135
170
  }
171
+ /**
172
+ * List a run's artifacts (`GET /runs/:runId/artifacts`). Each entry carries a
173
+ * presigned `downloadUrl` minted by the customer's orchestrator; the Platform
174
+ * relays it without re-signing, so the artifact bytes never transit Platform.
175
+ */
176
+ async listArtifacts(runId) {
177
+ return artifactsListResultSchema.parse(await this.getJson(`/runs/${runId}/artifacts`));
178
+ }
136
179
  async rerunRun(runId) {
137
180
  return rerunResponseSchema.parse(await this.postJson(`/runs/${runId}/rerun`));
138
181
  }
@@ -163,6 +206,6 @@ var DashboardClient = class DashboardClient {
163
206
  }
164
207
  };
165
208
  //#endregion
166
- export { DashboardClient, DashboardClientError };
209
+ export { DashboardClient, DashboardClientError, webhookActivitySchema };
167
210
 
168
211
  //# sourceMappingURL=dashboard-client.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Report whether `dir` lives on a filesystem that ignores case in path lookups.
3
+ *
4
+ * Case sensitivity is a property of the mounted filesystem, not of the platform
5
+ * — a Linux host can mount a case-insensitive volume and macOS can format a
6
+ * case-sensitive one — so it is measured rather than inferred: an empty file is
7
+ * written under a random name and the same name is stat'd upper-cased. The
8
+ * directory is created if it is missing, and the probe file is removed on every
9
+ * path, including a throw.
10
+ *
11
+ * Rejects whenever the probe cannot produce an answer: the probe file cannot be
12
+ * written, or the twin lookup fails for any reason other than the twin's
13
+ * absence. Only `ENOENT` means "case-sensitive" — a permission or I/O error says
14
+ * nothing about case folding, and reporting it as case-sensitive would let a
15
+ * colliding set overwrite itself, so it is surfaced to the caller instead.
16
+ *
17
+ * It is its own module so callers can be tested against both answers with a
18
+ * cross-module spy, rather than against whatever filesystem the test runs on.
19
+ */
20
+ export declare function isCaseInsensitiveDir(dir: string): Promise<boolean>;
21
+ //# sourceMappingURL=fs-case.d.ts.map
@@ -0,0 +1,45 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { join } from "node:path";
3
+ import { mkdir, rm, stat, writeFile } from "node:fs/promises";
4
+ import { randomUUID } from "node:crypto";
5
+ //#region src/remote/fs-case.ts
6
+ /** Filesystem case-sensitivity probe for a target output directory. */
7
+ /**
8
+ * Report whether `dir` lives on a filesystem that ignores case in path lookups.
9
+ *
10
+ * Case sensitivity is a property of the mounted filesystem, not of the platform
11
+ * — a Linux host can mount a case-insensitive volume and macOS can format a
12
+ * case-sensitive one — so it is measured rather than inferred: an empty file is
13
+ * written under a random name and the same name is stat'd upper-cased. The
14
+ * directory is created if it is missing, and the probe file is removed on every
15
+ * path, including a throw.
16
+ *
17
+ * Rejects whenever the probe cannot produce an answer: the probe file cannot be
18
+ * written, or the twin lookup fails for any reason other than the twin's
19
+ * absence. Only `ENOENT` means "case-sensitive" — a permission or I/O error says
20
+ * nothing about case folding, and reporting it as case-sensitive would let a
21
+ * colliding set overwrite itself, so it is surfaced to the caller instead.
22
+ *
23
+ * It is its own module so callers can be tested against both answers with a
24
+ * cross-module spy, rather than against whatever filesystem the test runs on.
25
+ */
26
+ async function isCaseInsensitiveDir(dir) {
27
+ await mkdir(dir, { recursive: true });
28
+ const probe = `.kici-case-probe-${randomUUID()}`;
29
+ const twin = probe.toUpperCase();
30
+ const probePath = join(dir, probe);
31
+ await writeFile(probePath, "");
32
+ try {
33
+ await stat(join(dir, twin));
34
+ return true;
35
+ } catch (err) {
36
+ if (err?.code === "ENOENT") return false;
37
+ throw err;
38
+ } finally {
39
+ await rm(probePath, { force: true });
40
+ }
41
+ }
42
+ //#endregion
43
+ export { isCaseInsensitiveDir };
44
+
45
+ //# sourceMappingURL=fs-case.js.map
@@ -5,8 +5,7 @@
5
5
  * can review past runs (kici run remote --history) and inspect specific runs
6
6
  * (kici status <run-id>).
7
7
  */
8
- /** Status of a test run */
9
- type RunStatus = 'queued' | 'running' | 'success' | 'failed' | 'cancelled';
8
+ import { type CanonicalStatus } from '@kici-dev/engine';
10
9
  /** A single run history entry */
11
10
  export interface HistoryEntry {
12
11
  /** Unique run identifier */
@@ -14,7 +13,7 @@ export interface HistoryEntry {
14
13
  /** Fixture that was run */
15
14
  fixtureId: string;
16
15
  /** Current status of the run */
17
- status: RunStatus;
16
+ status: CanonicalStatus;
18
17
  /** ISO timestamp when the run started */
19
18
  startedAt: string;
20
19
  /** ISO timestamp when the run completed (if finished) */
@@ -87,5 +86,12 @@ export declare class RunHistory {
87
86
  */
88
87
  private save;
89
88
  }
89
+ /** The plain (uncoloured) width of a status cell, for column padding. */
90
+ export declare function statusLabelWidth(status: string): number;
91
+ /**
92
+ * Format a run status with its colour and short label. A status this CLI does
93
+ * not recognise — a history file written by a newer version — renders verbatim.
94
+ */
95
+ export declare function formatStatus(status: string): string;
90
96
  export {};
91
97
  //# sourceMappingURL=history.d.ts.map
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import pc from "picocolors";
6
6
  import fs from "node:fs/promises";
7
7
  import { formatDuration } from "@kici-dev/core";
8
+ import { ExecutionJobStatus, ExecutionRunStatus, toCanonicalStatus } from "@kici-dev/engine";
8
9
  import picomatch from "picomatch";
9
10
  //#region src/remote/history.ts
10
11
  /**
@@ -95,7 +96,8 @@ var RunHistory = class {
95
96
  for (const entry of entries) {
96
97
  const id = entry.runId.padEnd(idWidth);
97
98
  const fixture = entry.fixtureId.padEnd(fixtureWidth);
98
- const status = formatStatus(entry.status).padEnd(12);
99
+ const statusPad = Math.max(0, 12 - statusLabelWidth(entry.status));
100
+ const status = formatStatus(entry.status) + " ".repeat(statusPad);
99
101
  const duration = entry.durationMs ? formatDuration(entry.durationMs).padEnd(12) : "-".padEnd(12);
100
102
  const started = formatRelativeTime(entry.startedAt);
101
103
  lines.push(`${id}${fixture}${status}${duration}${started}`);
@@ -129,18 +131,85 @@ var RunHistory = class {
129
131
  }
130
132
  };
131
133
  /**
132
- * Format a run status with appropriate color and symbol.
134
+ * Short label and colour for every canonical status in the run-history table.
135
+ *
136
+ * Exhaustive by type, so a new engine status breaks this file's typecheck
137
+ * rather than making the history table print the literal string `undefined`.
138
+ *
139
+ * `label` and `color` are stored separately so the table's column width can be
140
+ * measured on the plain label: padding a colour-wrapped string measures the
141
+ * ANSI escape bytes too, which silently collapses the column.
133
142
  */
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");
143
+ const STATUS_DISPLAY = Object.freeze({
144
+ [ExecutionRunStatus.enum.success]: {
145
+ label: "pass",
146
+ color: pc.green
147
+ },
148
+ [ExecutionRunStatus.enum.failed]: {
149
+ label: "fail",
150
+ color: pc.red
151
+ },
152
+ [ExecutionJobStatus.enum.timed_out_stale]: {
153
+ label: "timeout",
154
+ color: pc.red
155
+ },
156
+ [ExecutionJobStatus.enum.drift_dropped]: {
157
+ label: "dropped",
158
+ color: pc.red
159
+ },
160
+ [ExecutionJobStatus.enum.unroutable]: {
161
+ label: "unroutable",
162
+ color: pc.red
163
+ },
164
+ [ExecutionRunStatus.enum.cancelled]: {
165
+ label: "cancel",
166
+ color: pc.yellow
167
+ },
168
+ [ExecutionRunStatus.enum.cancelling]: {
169
+ label: "cancelling",
170
+ color: pc.yellow
171
+ },
172
+ [ExecutionJobStatus.enum.recovering]: {
173
+ label: "recovering",
174
+ color: pc.yellow
175
+ },
176
+ [ExecutionRunStatus.enum.held]: {
177
+ label: "held",
178
+ color: pc.yellow
179
+ },
180
+ [ExecutionRunStatus.enum.running]: {
181
+ label: "running",
182
+ color: pc.blue
183
+ },
184
+ [ExecutionJobStatus.enum.queued]: {
185
+ label: "queued",
186
+ color: pc.dim
187
+ },
188
+ [ExecutionRunStatus.enum.pending]: {
189
+ label: "pending",
190
+ color: pc.dim
191
+ },
192
+ [ExecutionJobStatus.enum.skipped]: {
193
+ label: "skipped",
194
+ color: pc.dim
141
195
  }
196
+ });
197
+ /** The plain (uncoloured) width of a status cell, for column padding. */
198
+ function statusLabelWidth(status) {
199
+ const canonical = toCanonicalStatus(status.toLowerCase());
200
+ return canonical === void 0 ? status.length : STATUS_DISPLAY[canonical].label.length;
201
+ }
202
+ /**
203
+ * Format a run status with its colour and short label. A status this CLI does
204
+ * not recognise — a history file written by a newer version — renders verbatim.
205
+ */
206
+ function formatStatus(status) {
207
+ const canonical = toCanonicalStatus(status.toLowerCase());
208
+ if (canonical === void 0) return pc.gray(status);
209
+ const { label, color } = STATUS_DISPLAY[canonical];
210
+ return color(label);
142
211
  }
143
212
  //#endregion
144
- export { RunHistory };
213
+ export { RunHistory, formatStatus, statusLabelWidth };
145
214
 
146
215
  //# sourceMappingURL=history.js.map
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  /** Git hosts whose origin URL we can turn into a real `owner/repo` + web link. */
3
3
  export declare const RecognizedProvider: z.ZodEnum<{
4
+ bitbucket: "bitbucket";
4
5
  github: "github";
5
6
  gitlab: "gitlab";
6
- bitbucket: "bitbucket";
7
7
  }>;
8
8
  export type RecognizedProvider = z.infer<typeof RecognizedProvider>;
9
9
  /** The literal provider value stamped when no recognized git origin exists. */
@@ -29,7 +29,8 @@ function parseGitOrigin(url) {
29
29
  const urlStyle = /^[a-z]+:\/\/(?:[^@/]+@)?([^/]+)\/(.+?)(?:\.git)?$/.exec(trimmed);
30
30
  const match = scp ?? urlStyle;
31
31
  if (!match) return null;
32
- const provider = HOST_PROVIDER[match[1].toLowerCase()];
32
+ const host = match[1].toLowerCase();
33
+ const provider = HOST_PROVIDER[host];
33
34
  if (!provider) return null;
34
35
  const repoIdentifier = match[2].replace(/^\/+|\/+$/g, "");
35
36
  if (!repoIdentifier.includes("/")) return null;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Typed client for the Platform's org-scoped notification API — the routes the
3
+ * dashboard Notifications tab uses (channels, subscriptions, and the Layer 1
4
+ * Slack-identity roster). Authenticated with the stored PAT + active org,
5
+ * exactly like {@link DashboardClient}, and reachable programmatically via the
6
+ * `kici notifications …` CLI.
7
+ *
8
+ * It composes a {@link DashboardClient} (same auth / org-scoped-URL / error
9
+ * mapping) rather than re-implementing the transport: every path here is
10
+ * relative to `/api/v1/orgs/:orgId/notifications/…`.
11
+ */
12
+ import { z } from 'zod';
13
+ import { DashboardClientError } from './dashboard-client.js';
14
+ /**
15
+ * Notification protocol enums mirrored from the hosted Platform's notification
16
+ * route contract. The public `kici` CLI cannot import the private Platform
17
+ * package, so these values are re-declared here and reused across the client,
18
+ * the command arg parsing, and their tests (the single source of truth on the
19
+ * CLI side).
20
+ */
21
+ export declare const NotificationChannelKind: z.ZodEnum<{
22
+ email: "email";
23
+ slack: "slack";
24
+ }>;
25
+ export type NotificationChannelKind = z.infer<typeof NotificationChannelKind>;
26
+ export declare const NotificationSubLevel: z.ZodEnum<{
27
+ job: "job";
28
+ run: "run";
29
+ }>;
30
+ export type NotificationSubLevel = z.infer<typeof NotificationSubLevel>;
31
+ export declare const NotificationScopeKind: z.ZodEnum<{
32
+ actor: "actor";
33
+ org: "org";
34
+ team: "team";
35
+ user: "user";
36
+ }>;
37
+ export type NotificationScopeKind = z.infer<typeof NotificationScopeKind>;
38
+ export declare const RosterSubjectKind: z.ZodEnum<{
39
+ email: "email";
40
+ git_login: "git_login";
41
+ kici_user: "kici_user";
42
+ }>;
43
+ export type RosterSubjectKind = z.infer<typeof RosterSubjectKind>;
44
+ export declare const RosterInputForm: z.ZodEnum<{
45
+ email: "email";
46
+ id: "id";
47
+ username: "username";
48
+ }>;
49
+ export type RosterInputForm = z.infer<typeof RosterInputForm>;
50
+ /** A notification channel row as returned by the channels list route. */
51
+ export interface NotificationChannel {
52
+ id: string;
53
+ type: string;
54
+ name: string;
55
+ managed?: boolean;
56
+ created_at?: string;
57
+ }
58
+ /** A notification subscription row as returned by the subscriptions list route. */
59
+ export interface NotificationSubscription {
60
+ id: string;
61
+ level: string;
62
+ channel_id: string;
63
+ scope_type: string;
64
+ scope_id: string | null;
65
+ on_status: string[];
66
+ repo_glob: string | null;
67
+ workflow_glob: string | null;
68
+ job_glob: string | null;
69
+ mentions: string[] | null;
70
+ accumulate_for: number | null;
71
+ enabled: boolean;
72
+ source: string;
73
+ }
74
+ /** A Slack-identity roster row as returned by the roster list route. */
75
+ export interface SlackIdentity {
76
+ id: string;
77
+ connection_id: string;
78
+ subject_kind: string;
79
+ subject_value: string;
80
+ slack_member_id: string;
81
+ input_form: string;
82
+ status: string;
83
+ }
84
+ /** Body for creating a Slack channel. */
85
+ export interface CreateSlackChannelBody {
86
+ type: 'slack';
87
+ connection_id: string;
88
+ slack_channel_id: string;
89
+ name: string;
90
+ }
91
+ /** Body for creating an email channel. */
92
+ export interface CreateEmailChannelBody {
93
+ type: 'email';
94
+ name: string;
95
+ from_name?: string;
96
+ reply_to?: string;
97
+ }
98
+ export type CreateChannelBody = CreateSlackChannelBody | CreateEmailChannelBody;
99
+ /** Body for creating a subscription (mirrors the Platform create route schema). */
100
+ export interface CreateSubscriptionBody {
101
+ level: NotificationSubLevel;
102
+ channel_id: string;
103
+ scope_type: NotificationScopeKind;
104
+ scope_id?: string | null;
105
+ on_status: string[];
106
+ repo_glob?: string | null;
107
+ workflow_glob?: string | null;
108
+ job_glob?: string | null;
109
+ mentions?: string[] | null;
110
+ recipient_override?: string[] | null;
111
+ on_failure_class?: string[] | null;
112
+ accumulate_for?: number | null;
113
+ }
114
+ /** Body for adding a roster identity (mirrors the Platform add route schema). */
115
+ export interface AddIdentityBody {
116
+ connection_id: string;
117
+ subject_kind: RosterSubjectKind;
118
+ subject_value: string;
119
+ input_form: RosterInputForm;
120
+ value: string;
121
+ }
122
+ /**
123
+ * PAT + org-scoped client for the notification routes. Construct via
124
+ * {@link NotificationsClient.load}, which reads `~/.kici/config` and resolves
125
+ * the target org (`--org` override or the saved active org).
126
+ */
127
+ export declare class NotificationsClient {
128
+ private readonly dashboard;
129
+ private constructor();
130
+ /**
131
+ * Load the authenticated client. `opts.org` overrides the saved active org
132
+ * for this invocation; otherwise the saved `activeOrgId` is used. Throws a
133
+ * {@link DashboardClientError} when the PAT / endpoint / org is missing.
134
+ */
135
+ static load(opts?: {
136
+ org?: string;
137
+ }): Promise<NotificationsClient>;
138
+ listChannels(): Promise<NotificationChannel[]>;
139
+ createChannel(body: CreateChannelBody): Promise<NotificationChannel>;
140
+ deleteChannel(id: string): Promise<void>;
141
+ listSubscriptions(): Promise<NotificationSubscription[]>;
142
+ createSubscription(body: CreateSubscriptionBody): Promise<NotificationSubscription>;
143
+ deleteSubscription(id: string): Promise<void>;
144
+ listIdentities(): Promise<SlackIdentity[]>;
145
+ addIdentity(body: AddIdentityBody): Promise<SlackIdentity>;
146
+ deleteIdentity(id: string): Promise<void>;
147
+ }
148
+ export { DashboardClientError };
149
+ //# sourceMappingURL=notifications-client.d.ts.map
@@ -0,0 +1,103 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { loadGlobalConfig } from "./config.js";
3
+ import { DashboardClient, DashboardClientError } from "./dashboard-client.js";
4
+ import { z } from "zod";
5
+ //#region src/remote/notifications-client.ts
6
+ /**
7
+ * Typed client for the Platform's org-scoped notification API — the routes the
8
+ * dashboard Notifications tab uses (channels, subscriptions, and the Layer 1
9
+ * Slack-identity roster). Authenticated with the stored PAT + active org,
10
+ * exactly like {@link DashboardClient}, and reachable programmatically via the
11
+ * `kici notifications …` CLI.
12
+ *
13
+ * It composes a {@link DashboardClient} (same auth / org-scoped-URL / error
14
+ * mapping) rather than re-implementing the transport: every path here is
15
+ * relative to `/api/v1/orgs/:orgId/notifications/…`.
16
+ */
17
+ /**
18
+ * Notification protocol enums mirrored from the hosted Platform's notification
19
+ * route contract. The public `kici` CLI cannot import the private Platform
20
+ * package, so these values are re-declared here and reused across the client,
21
+ * the command arg parsing, and their tests (the single source of truth on the
22
+ * CLI side).
23
+ */
24
+ const NotificationChannelKind = z.enum(["slack", "email"]);
25
+ const NotificationSubLevel = z.enum(["run", "job"]);
26
+ const NotificationScopeKind = z.enum([
27
+ "org",
28
+ "team",
29
+ "user",
30
+ "actor"
31
+ ]);
32
+ const RosterSubjectKind = z.enum([
33
+ "kici_user",
34
+ "git_login",
35
+ "email"
36
+ ]);
37
+ const RosterInputForm = z.enum([
38
+ "id",
39
+ "username",
40
+ "email"
41
+ ]);
42
+ const rowSchema = z.record(z.string(), z.unknown());
43
+ const channelsListSchema = z.object({ channels: z.array(rowSchema).default([]) });
44
+ const subscriptionsListSchema = z.object({ subscriptions: z.array(rowSchema).default([]) });
45
+ const identitiesListSchema = z.object({ identities: z.array(rowSchema).default([]) });
46
+ /**
47
+ * PAT + org-scoped client for the notification routes. Construct via
48
+ * {@link NotificationsClient.load}, which reads `~/.kici/config` and resolves
49
+ * the target org (`--org` override or the saved active org).
50
+ */
51
+ var NotificationsClient = class NotificationsClient {
52
+ dashboard;
53
+ constructor(dashboard) {
54
+ this.dashboard = dashboard;
55
+ }
56
+ /**
57
+ * Load the authenticated client. `opts.org` overrides the saved active org
58
+ * for this invocation; otherwise the saved `activeOrgId` is used. Throws a
59
+ * {@link DashboardClientError} when the PAT / endpoint / org is missing.
60
+ */
61
+ static async load(opts = {}) {
62
+ const config = await loadGlobalConfig();
63
+ const effective = opts.org ? {
64
+ ...config,
65
+ activeOrgId: opts.org
66
+ } : config;
67
+ return new NotificationsClient(DashboardClient.fromConfig(effective));
68
+ }
69
+ async listChannels() {
70
+ const { channels } = channelsListSchema.parse(await this.dashboard.getJson("notifications/channels"));
71
+ return channels;
72
+ }
73
+ async createChannel(body) {
74
+ return await this.dashboard.postJson("notifications/channels", body);
75
+ }
76
+ async deleteChannel(id) {
77
+ await this.dashboard.deleteJson(`notifications/channels/${encodeURIComponent(id)}`);
78
+ }
79
+ async listSubscriptions() {
80
+ const { subscriptions } = subscriptionsListSchema.parse(await this.dashboard.getJson("notifications/subscriptions"));
81
+ return subscriptions;
82
+ }
83
+ async createSubscription(body) {
84
+ return await this.dashboard.postJson("notifications/subscriptions", body);
85
+ }
86
+ async deleteSubscription(id) {
87
+ await this.dashboard.deleteJson(`notifications/subscriptions/${encodeURIComponent(id)}`);
88
+ }
89
+ async listIdentities() {
90
+ const { identities } = identitiesListSchema.parse(await this.dashboard.getJson("notifications/slack/identities"));
91
+ return identities;
92
+ }
93
+ async addIdentity(body) {
94
+ return await this.dashboard.postJson("notifications/slack/identities", body);
95
+ }
96
+ async deleteIdentity(id) {
97
+ await this.dashboard.deleteJson(`notifications/slack/identities/${encodeURIComponent(id)}`);
98
+ }
99
+ };
100
+ //#endregion
101
+ export { DashboardClientError, NotificationChannelKind, NotificationScopeKind, NotificationSubLevel, NotificationsClient, RosterInputForm, RosterSubjectKind };
102
+
103
+ //# sourceMappingURL=notifications-client.js.map