@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
@@ -9,6 +9,8 @@ export interface LoginOptions {
9
9
  routingKey?: string;
10
10
  /** Force device authorization flow regardless of environment */
11
11
  device?: boolean;
12
+ /** Skip the post-login "attach the local dev plane?" prompt (scripted logins). */
13
+ noAttachPrompt?: boolean;
12
14
  }
13
15
  /**
14
16
  * Authenticate with KiCI.
@@ -2,6 +2,7 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { getConfigPath, loadGlobalConfig, mergeGlobalConfig, saveGlobalConfig } from "../remote/config.js";
3
3
  import { deviceFlow, exchangeTokenForPat, pkceFlow } from "../remote/oauth.js";
4
4
  import "../remote/prod-defaults.js";
5
+ import { attachPlane, planeStatus } from "../local-plane/plane-manager.js";
5
6
  import { isHeadless } from "../auth/headless-detect.js";
6
7
  import pc from "picocolors";
7
8
  import { toErrorMessage } from "@kici-dev/core";
@@ -52,7 +53,7 @@ async function oauthLogin(options) {
52
53
  const clientId = process.env.KICI_OIDC_CLIENT_ID || "kici-cli";
53
54
  console.log(pc.cyan("\n Step 1/4: Detecting environment..."));
54
55
  const browserCmdSet = !!process.env.KICI_BROWSER_CMD;
55
- const useDeviceFlow = options.device || !browserCmdSet && isHeadless();
56
+ const useDeviceFlow = options.device || !browserCmdSet && await isHeadless();
56
57
  const flowName = useDeviceFlow ? "device authorization" : "PKCE (browser)";
57
58
  console.log(pc.gray(` Using ${flowName} flow`));
58
59
  console.log(pc.cyan(`\n Step 2/4: Authenticating with IdP (${flowName})...`));
@@ -92,9 +93,48 @@ async function oauthLogin(options) {
92
93
  console.log(pc.gray(` PAT expires: ${expiryDate}`));
93
94
  console.log(pc.gray(` Config saved to ${configPath}`));
94
95
  checkPatExpiry(patResult.expiresAt);
96
+ await maybePromptAttach(options, {
97
+ apiBase: platformUrl,
98
+ pat: patResult.token,
99
+ orgId: next.activeOrgId
100
+ });
95
101
  return true;
96
102
  }
97
103
  /**
104
+ * After a successful login, prompt an interactive user to attach their local
105
+ * dev plane to the Platform. Skipped entirely for non-TTY / piped input, when
106
+ * `--no-attach` is set, when no org is selected (nothing to attach against), or
107
+ * when a plane is already attached. Any failure here is non-fatal — login has
108
+ * already succeeded.
109
+ */
110
+ async function maybePromptAttach(options, ctx) {
111
+ try {
112
+ if (options.noAttachPrompt) return;
113
+ if (!process.stdin.isTTY) return;
114
+ if (!ctx.orgId) {
115
+ console.log(pc.gray("\n Tip: select an org (`kici org use <org>`) then `kici local attach` to use real Platform identity for `kici run --local`."));
116
+ return;
117
+ }
118
+ if ((await planeStatus()).mode === "hybrid") return;
119
+ console.log("\n You don't have your local dev plane attached to the Platform.\n Attach it so `kici run --local` uses real Platform secrets & identity?");
120
+ const answer = (await promptInput(" Attach now? [Y/n] ")).toLowerCase();
121
+ if (answer === "n" || answer === "no") {
122
+ console.log(pc.gray(" Left offline. Re-attach later with `kici local attach`."));
123
+ return;
124
+ }
125
+ console.log(pc.cyan("\n Attaching the local dev plane…"));
126
+ const status = await attachPlane({
127
+ apiBase: ctx.apiBase,
128
+ pat: ctx.pat,
129
+ orgId: ctx.orgId
130
+ });
131
+ console.log(pc.green(` ✓ Attached (hybrid) at ${status.url ?? ""} — \`kici run --local\` is now connected.`));
132
+ } catch (err) {
133
+ console.log(pc.yellow(` Could not attach the local dev plane: ${toErrorMessage(err)}`));
134
+ console.log(pc.gray(" Login succeeded; attach later with `kici local attach`."));
135
+ }
136
+ }
137
+ /**
98
138
  * Authenticate with KiCI.
99
139
  *
100
140
  * - With `--token`, saves the API key directly (legacy flow)
@@ -1,5 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { getConfigPath, loadGlobalConfig, saveGlobalConfig } from "../remote/config.js";
3
+ import { detachPlane, planeStatus } from "../local-plane/plane-manager.js";
3
4
  import pc from "picocolors";
4
5
  //#region src/commands/logout.ts
5
6
  /**
@@ -37,6 +38,12 @@ async function logoutCommand() {
37
38
  } catch {
38
39
  console.log(pc.yellow("Warning: could not revoke PAT on server (will expire automatically)."));
39
40
  }
41
+ try {
42
+ if ((await planeStatus()).mode === "hybrid") {
43
+ await detachPlane({ pat: config.pat });
44
+ console.log(pc.gray("Local dev plane detached (back to offline)."));
45
+ }
46
+ } catch {}
40
47
  const cleanConfig = { ...config };
41
48
  delete cleanConfig.pat;
42
49
  delete cleanConfig.patId;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * kici notifications commands
3
+ *
4
+ * Customer self-service management of an org's notification configuration —
5
+ * channels, subscriptions, and the Layer 1 Slack-identity roster — under the
6
+ * logged-in user's PAT against the hosted Platform's org-scoped HTTP API. This
7
+ * is a tenant-plane developer operation, so it lives in the public `kici` CLI
8
+ * (like `kici org` / `kici verify-attestation`), never the DB-direct
9
+ * `kici-platform-admin`.
10
+ */
11
+ /** Options common to every notifications subcommand. */
12
+ export interface NotificationsCommonOptions {
13
+ /** Target organization id (overrides the saved active org). */
14
+ org?: string;
15
+ }
16
+ /** Options for the `list` leaves. */
17
+ export interface NotificationsListOptions extends NotificationsCommonOptions {
18
+ json?: boolean;
19
+ }
20
+ /** Options for `channels add`. */
21
+ export interface ChannelsAddOptions extends NotificationsCommonOptions {
22
+ type?: string;
23
+ name?: string;
24
+ connection?: string;
25
+ slackChannel?: string;
26
+ fromName?: string;
27
+ replyTo?: string;
28
+ }
29
+ /** Options for `subscriptions add`. */
30
+ export interface SubscriptionsAddOptions extends NotificationsCommonOptions {
31
+ channel?: string;
32
+ level?: string;
33
+ scope?: string;
34
+ scopeId?: string;
35
+ onStatus?: string;
36
+ repoGlob?: string;
37
+ workflowGlob?: string;
38
+ jobGlob?: string;
39
+ mentions?: string;
40
+ recipientOverride?: string;
41
+ onFailureClass?: string;
42
+ accumulateFor?: string;
43
+ }
44
+ /** Options for `roster add`. */
45
+ export interface RosterAddOptions extends NotificationsCommonOptions {
46
+ connection?: string;
47
+ subjectKind?: string;
48
+ subject?: string;
49
+ inputForm?: string;
50
+ value?: string;
51
+ }
52
+ export declare function notificationsChannelsListCommand(opts: NotificationsListOptions): Promise<boolean>;
53
+ export declare function notificationsChannelsAddCommand(opts: ChannelsAddOptions): Promise<boolean>;
54
+ export declare function notificationsChannelsRemoveCommand(id: string, opts: NotificationsCommonOptions): Promise<boolean>;
55
+ export declare function notificationsSubscriptionsListCommand(opts: NotificationsListOptions): Promise<boolean>;
56
+ export declare function notificationsSubscriptionsAddCommand(opts: SubscriptionsAddOptions): Promise<boolean>;
57
+ export declare function notificationsSubscriptionsRemoveCommand(id: string, opts: NotificationsCommonOptions): Promise<boolean>;
58
+ export declare function notificationsRosterListCommand(opts: NotificationsListOptions): Promise<boolean>;
59
+ export declare function notificationsRosterAddCommand(opts: RosterAddOptions): Promise<boolean>;
60
+ export declare function notificationsRosterRemoveCommand(id: string, opts: NotificationsCommonOptions): Promise<boolean>;
61
+ //# sourceMappingURL=notifications.d.ts.map
@@ -0,0 +1,274 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { DashboardClientError } from "../remote/dashboard-client.js";
3
+ import { NotificationChannelKind, NotificationScopeKind, NotificationSubLevel, NotificationsClient, RosterInputForm, RosterSubjectKind } from "../remote/notifications-client.js";
4
+ import pc from "picocolors";
5
+ import { toErrorMessage } from "@kici-dev/core";
6
+ //#region src/commands/notifications.ts
7
+ /**
8
+ * kici notifications commands
9
+ *
10
+ * Customer self-service management of an org's notification configuration —
11
+ * channels, subscriptions, and the Layer 1 Slack-identity roster — under the
12
+ * logged-in user's PAT against the hosted Platform's org-scoped HTTP API. This
13
+ * is a tenant-plane developer operation, so it lives in the public `kici` CLI
14
+ * (like `kici org` / `kici verify-attestation`), never the DB-direct
15
+ * `kici-platform-admin`.
16
+ */
17
+ /** Split a comma-separated flag value into a trimmed, non-empty list. */
18
+ function parseCsv(value) {
19
+ if (!value) return [];
20
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
21
+ }
22
+ /** Print a simple padded table. */
23
+ function printTable(headers, rows) {
24
+ if (rows.length === 0) {
25
+ console.log(pc.gray("None."));
26
+ return;
27
+ }
28
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
29
+ console.log(pc.bold(headers.map((h, i) => h.padEnd(widths[i])).join(" ")));
30
+ for (const row of rows) console.log(row.map((c, i) => c.padEnd(widths[i])).join(" "));
31
+ }
32
+ /**
33
+ * Load the client and run `fn`, turning any {@link DashboardClientError} into a
34
+ * red console message and a `false` return (the CLI exit-code convention).
35
+ */
36
+ async function withClient(opts, fn) {
37
+ try {
38
+ return await fn(await NotificationsClient.load({ org: opts.org }));
39
+ } catch (err) {
40
+ if (err instanceof DashboardClientError) {
41
+ console.error(pc.red(err.message));
42
+ return false;
43
+ }
44
+ console.error(pc.red(`Notifications command failed: ${toErrorMessage(err)}`));
45
+ return false;
46
+ }
47
+ }
48
+ /** Validate a value against a Zod enum, printing an actionable error on miss. */
49
+ function requireEnum(label, value, options) {
50
+ if (value === void 0) {
51
+ console.error(pc.red(`--${label} is required (one of: ${options.join(", ")})`));
52
+ return null;
53
+ }
54
+ if (!options.includes(value)) {
55
+ console.error(pc.red(`Invalid --${label} "${value}" (one of: ${options.join(", ")})`));
56
+ return null;
57
+ }
58
+ return value;
59
+ }
60
+ async function notificationsChannelsListCommand(opts) {
61
+ return withClient(opts, async (client) => {
62
+ const channels = await client.listChannels();
63
+ if (opts.json) {
64
+ console.log(JSON.stringify(channels, null, 2));
65
+ return true;
66
+ }
67
+ printTable([
68
+ "ID",
69
+ "TYPE",
70
+ "NAME",
71
+ "MANAGED"
72
+ ], channels.map((c) => [
73
+ c.id,
74
+ c.type,
75
+ c.name,
76
+ c.managed ? "yes" : "no"
77
+ ]));
78
+ return true;
79
+ });
80
+ }
81
+ async function notificationsChannelsAddCommand(opts) {
82
+ const type = requireEnum("type", opts.type, NotificationChannelKind.options);
83
+ if (!type) return false;
84
+ if (!opts.name) {
85
+ console.error(pc.red("--name is required"));
86
+ return false;
87
+ }
88
+ let body;
89
+ if (type === NotificationChannelKind.enum.slack) {
90
+ if (!opts.connection || !opts.slackChannel) {
91
+ console.error(pc.red("slack channels require --connection <id> and --slack-channel <id>"));
92
+ return false;
93
+ }
94
+ body = {
95
+ type: "slack",
96
+ connection_id: opts.connection,
97
+ slack_channel_id: opts.slackChannel,
98
+ name: opts.name
99
+ };
100
+ } else body = {
101
+ type: "email",
102
+ name: opts.name,
103
+ ...opts.fromName ? { from_name: opts.fromName } : {},
104
+ ...opts.replyTo ? { reply_to: opts.replyTo } : {}
105
+ };
106
+ return withClient(opts, async (client) => {
107
+ const channel = await client.createChannel(body);
108
+ console.log(pc.green(`Channel created: ${channel.id}`));
109
+ return true;
110
+ });
111
+ }
112
+ async function notificationsChannelsRemoveCommand(id, opts) {
113
+ return withClient(opts, async (client) => {
114
+ await client.deleteChannel(id);
115
+ console.log(pc.green(`Channel removed: ${id}`));
116
+ return true;
117
+ });
118
+ }
119
+ async function notificationsSubscriptionsListCommand(opts) {
120
+ return withClient(opts, async (client) => {
121
+ const subs = await client.listSubscriptions();
122
+ if (opts.json) {
123
+ console.log(JSON.stringify(subs, null, 2));
124
+ return true;
125
+ }
126
+ printTable([
127
+ "ID",
128
+ "LEVEL",
129
+ "SCOPE",
130
+ "CHANNEL",
131
+ "ON STATUS",
132
+ "ENABLED"
133
+ ], subs.map((s) => [
134
+ s.id,
135
+ s.level,
136
+ s.scope_id ? `${s.scope_type}:${s.scope_id}` : s.scope_type,
137
+ s.channel_id,
138
+ (s.on_status ?? []).join(","),
139
+ s.enabled ? "yes" : "no"
140
+ ]));
141
+ return true;
142
+ });
143
+ }
144
+ /** Build a create-subscription body from parsed flags (null on validation error). */
145
+ function buildSubscriptionBody(opts) {
146
+ if (!opts.channel) {
147
+ console.error(pc.red("--channel <id> is required"));
148
+ return null;
149
+ }
150
+ const level = requireEnum("level", opts.level ?? "run", NotificationSubLevel.options);
151
+ if (!level) return null;
152
+ const scope = requireEnum("scope", opts.scope ?? "org", NotificationScopeKind.options);
153
+ if (!scope) return null;
154
+ const onStatus = parseCsv(opts.onStatus);
155
+ if (onStatus.length === 0) {
156
+ console.error(pc.red("--on-status <csv> is required (e.g. failed,success)"));
157
+ return null;
158
+ }
159
+ const needsScopeId = scope === NotificationScopeKind.enum.team || scope === NotificationScopeKind.enum.user;
160
+ if (needsScopeId && !opts.scopeId) {
161
+ console.error(pc.red(`--scope-id is required for ${scope} scope`));
162
+ return null;
163
+ }
164
+ let accumulateFor = null;
165
+ if (opts.accumulateFor !== void 0) {
166
+ accumulateFor = Number(opts.accumulateFor);
167
+ if (!Number.isFinite(accumulateFor)) {
168
+ console.error(pc.red("--accumulate-for must be a number of milliseconds"));
169
+ return null;
170
+ }
171
+ }
172
+ const mentions = parseCsv(opts.mentions);
173
+ const recipientOverride = parseCsv(opts.recipientOverride);
174
+ const onFailureClass = parseCsv(opts.onFailureClass);
175
+ return {
176
+ level,
177
+ channel_id: opts.channel,
178
+ scope_type: scope,
179
+ scope_id: needsScopeId ? opts.scopeId : null,
180
+ on_status: onStatus,
181
+ repo_glob: opts.repoGlob ?? null,
182
+ workflow_glob: opts.workflowGlob ?? null,
183
+ job_glob: opts.jobGlob ?? null,
184
+ mentions: mentions.length > 0 ? mentions : null,
185
+ recipient_override: recipientOverride.length > 0 ? recipientOverride : null,
186
+ on_failure_class: onFailureClass.length > 0 ? onFailureClass : null,
187
+ accumulate_for: accumulateFor
188
+ };
189
+ }
190
+ async function notificationsSubscriptionsAddCommand(opts) {
191
+ const body = buildSubscriptionBody(opts);
192
+ if (!body) return false;
193
+ return withClient(opts, async (client) => {
194
+ const sub = await client.createSubscription(body);
195
+ console.log(pc.green(`Subscription created: ${sub.id}`));
196
+ return true;
197
+ });
198
+ }
199
+ async function notificationsSubscriptionsRemoveCommand(id, opts) {
200
+ return withClient(opts, async (client) => {
201
+ await client.deleteSubscription(id);
202
+ console.log(pc.green(`Subscription removed: ${id}`));
203
+ return true;
204
+ });
205
+ }
206
+ async function notificationsRosterListCommand(opts) {
207
+ return withClient(opts, async (client) => {
208
+ const identities = await client.listIdentities();
209
+ if (opts.json) {
210
+ console.log(JSON.stringify(identities, null, 2));
211
+ return true;
212
+ }
213
+ printTable([
214
+ "ID",
215
+ "SUBJECT KIND",
216
+ "SUBJECT",
217
+ "SLACK MEMBER",
218
+ "STATUS"
219
+ ], identities.map((r) => [
220
+ r.id,
221
+ r.subject_kind,
222
+ r.subject_value,
223
+ r.slack_member_id,
224
+ r.status
225
+ ]));
226
+ return true;
227
+ });
228
+ }
229
+ /** Build an add-identity body from parsed flags (null on validation error). */
230
+ function buildIdentityBody(opts) {
231
+ if (!opts.connection) {
232
+ console.error(pc.red("--connection <id> is required"));
233
+ return null;
234
+ }
235
+ const subjectKind = requireEnum("subject-kind", opts.subjectKind, RosterSubjectKind.options);
236
+ if (!subjectKind) return null;
237
+ if (!opts.subject) {
238
+ console.error(pc.red("--subject <value> is required (the KiCI user sub, git login, or email)"));
239
+ return null;
240
+ }
241
+ const inputForm = requireEnum("input-form", opts.inputForm ?? "id", RosterInputForm.options);
242
+ if (!inputForm) return null;
243
+ if (!opts.value) {
244
+ console.error(pc.red("--value <slack id | email | @handle> is required"));
245
+ return null;
246
+ }
247
+ return {
248
+ connection_id: opts.connection,
249
+ subject_kind: subjectKind,
250
+ subject_value: opts.subject,
251
+ input_form: inputForm,
252
+ value: opts.value
253
+ };
254
+ }
255
+ async function notificationsRosterAddCommand(opts) {
256
+ const body = buildIdentityBody(opts);
257
+ if (!body) return false;
258
+ return withClient(opts, async (client) => {
259
+ const row = await client.addIdentity(body);
260
+ console.log(pc.green(`Roster identity added: ${row.id} → ${row.slack_member_id}`));
261
+ return true;
262
+ });
263
+ }
264
+ async function notificationsRosterRemoveCommand(id, opts) {
265
+ return withClient(opts, async (client) => {
266
+ await client.deleteIdentity(id);
267
+ console.log(pc.green(`Roster identity removed: ${id}`));
268
+ return true;
269
+ });
270
+ }
271
+ //#endregion
272
+ export { notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand };
273
+
274
+ //# sourceMappingURL=notifications.js.map
@@ -2,6 +2,8 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
3
3
  import "../execution/index.js";
4
4
  import { transformTriggers } from "../lockfile/generator.js";
5
+ import { analyzeJobPurity } from "../lockfile/purity-diagnostics.js";
6
+ import "../lockfile/index.js";
5
7
  import { displayDryRun } from "../test-runner/dry-run.js";
6
8
  import { parseEventArg } from "../test-runner/event-types.js";
7
9
  import { buildEventPayload } from "../test-runner/payload-builder.js";
@@ -35,7 +37,7 @@ async function previewCommand(event, options) {
35
37
  logger.info(pc.gray(" kici preview schedule"));
36
38
  logger.info(pc.gray(" kici preview lifecycle:workflow_complete\n"));
37
39
  logger.info(pc.gray("For remote fixture execution, use: kici run remote [fixture] [options]"));
38
- logger.info(pc.gray("For local workflow execution, use: kici run local <event> [options]\n"));
40
+ logger.info(pc.gray("For local workflow execution, use: kici run <event> --local\n"));
39
41
  return true;
40
42
  }
41
43
  if (!isKnownEventArg(event)) {
@@ -88,10 +90,15 @@ async function previewEvent(event, options) {
88
90
  return false;
89
91
  }
90
92
  }
93
+ const purityWarnings = [];
94
+ for (const w of workflows) for (const j of w.jobs) {
95
+ if (typeof j === "function") continue;
96
+ purityWarnings.push(...analyzeJobPurity(j, w.name));
97
+ }
91
98
  displayDryRun(lockWorkflows, decisions, {
92
99
  workflow: options.workflow,
93
100
  job: options.job
94
- });
101
+ }, purityWarnings);
95
102
  return true;
96
103
  } catch (error) {
97
104
  const message = toErrorMessage(error);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Always-on startup banner for a routed `kici run --local` invocation. States
3
+ * the selected plane, where the agent runs, the secret + identity source, the
4
+ * control commands, and the flags that force a different behavior — so a
5
+ * developer always knows which plane a run used and why (design §5).
6
+ *
7
+ * Three variants:
8
+ * - `offline` — independent plane, local secrets, dev-signed identity.
9
+ * - `attached` — hybrid plane, real Platform-minted OIDC + attestation.
10
+ * - `fallback` — wanted attached but the Platform was unreachable, so the run
11
+ * fell back to offline; a LOUD first line makes the degradation obvious.
12
+ */
13
+ export type RunBannerMode = 'offline' | 'attached' | 'fallback';
14
+ export interface RunBannerInput {
15
+ mode: RunBannerMode;
16
+ /** Plane orchestrator URL (surfaced on the control line). */
17
+ planeUrl: string;
18
+ /** Org id (attached variant). */
19
+ orgId?: string;
20
+ /** Why the run fell back to offline (fallback variant). */
21
+ fallbackReason?: string;
22
+ /**
23
+ * Trusted fleet-agent profile (`--trusted`): the run routes to the trusted
24
+ * label set, so steps see the ambient host env (minus the agent's own KiCI
25
+ * identity secrets). Surfaced as a loud line so a trusted run is never silent.
26
+ */
27
+ trusted?: boolean;
28
+ }
29
+ /** Render the boxed run banner as a plain (uncolored) multi-line string. */
30
+ export declare function renderRunBanner(input: RunBannerInput): string;
31
+ //# sourceMappingURL=run-banner.d.ts.map
@@ -0,0 +1,25 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/commands/run-banner.ts
3
+ /** Render the boxed run banner as a plain (uncolored) multi-line string. */
4
+ function renderRunBanner(input) {
5
+ const title = input.mode === "attached" ? "kici run --local (connected)" : "kici run --local";
6
+ const rows = [];
7
+ if (input.mode === "fallback") rows.push(["⚠", `Platform unreachable — fell back to OFFLINE${input.fallbackReason ? ` (${input.fallbackReason})` : ""}`]);
8
+ if (input.mode === "attached") rows.push(["plane", "local dev orchestrator (hybrid, attached)"], ["agent", "this machine (bare-metal)"], ["secrets", `REAL scoped${input.orgId ? ` (org: ${input.orgId})` : ""}`], ["identity", "real Platform OIDC + attestation"], ["control", `kici local status | logs | detach (${input.planeUrl})`], ["force", "--offline (local plane) · --in-place (ambient)"]);
9
+ else rows.push(["plane", "local dev orchestrator (independent, offline)"], ["agent", "this machine (bare-metal)"], ["secrets", "LOCAL files (.kici/.secrets, .env.local, --env)"], ["identity", "DEV-SIGNED (iss=kici-local — NOT prod)"], ["control", `kici local status | logs | down (${input.planeUrl})`], ["force", "--connected (attach to Platform) · --in-place (ambient)"]);
10
+ if (input.trusted) rows.push(["execution", "TRUSTED — host env passthrough (NOT sandboxed)"]);
11
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
12
+ const bodyLines = rows.map(([label, value]) => `${(label + ":").padEnd(labelWidth + 1)} ${value}`);
13
+ const contentWidth = Math.max(title.length + 2, ...bodyLines.map((l) => l.length));
14
+ const top = `┌ ${title} ${"─".repeat(Math.max(0, contentWidth - title.length))}┐`;
15
+ const bottom = `└${"─".repeat(contentWidth + 2)}┘`;
16
+ return [
17
+ top,
18
+ ...bodyLines.map((l) => `│ ${l.padEnd(contentWidth)} │`),
19
+ bottom
20
+ ].join("\n");
21
+ }
22
+ //#endregion
23
+ export { renderRunBanner };
24
+
25
+ //# sourceMappingURL=run-banner.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `kici run --local [event]` — a real routed dispatch with this machine as the
3
+ * ephemeral agent, through the warm local dev plane. In this phase only the
4
+ * offline (independent) plane is wired; Platform attachment (`--connected` /
5
+ * auto-select hybrid) lands with the attachment work.
6
+ *
7
+ * The offline path reuses the real orchestrator + agent pipeline end-to-end, so
8
+ * the execution parity dividend (emit / cache / needs / parallel / `KICI_SOURCE_*`)
9
+ * holds by construction — there is no second engine.
10
+ */
11
+ export interface RunRoutedOptions {
12
+ event?: string;
13
+ local?: boolean;
14
+ offline?: boolean;
15
+ connected?: boolean;
16
+ inPlace?: boolean;
17
+ /**
18
+ * Trusted fleet-agent profile: route this run to the plane's trusted label
19
+ * set so steps run with the ambient host env passed through (minus the
20
+ * agent's own KiCI identity secrets). Selects a PRE-CONFIGURED trusted scaler
21
+ * label set by appending the `self-hosted` routing label to the run's job
22
+ * `runsOn` — it never injects `KICI_TRUSTED_ENV` onto a dispatch payload.
23
+ */
24
+ trusted?: boolean;
25
+ /** Flat per-run secrets (`--env KEY=VALUE`); delivery lands with the secret path. */
26
+ env?: string[];
27
+ /**
28
+ * Path to a dispatch payload JSON `{ action?, client_payload? }`. Only read
29
+ * when the event is `dispatch`: `action` becomes the dispatch `types` matcher
30
+ * key and `client_payload` reaches the workflow. This is how `packages/ci`
31
+ * targets a single `dispatch()` workflow (e.g. deploy-stg) on the routed path.
32
+ */
33
+ payload?: string;
34
+ kiciDir?: string;
35
+ quiet?: boolean;
36
+ debug?: boolean;
37
+ }
38
+ /**
39
+ * Entry point for the action-bearing `kici run`. Validates the routing flags and
40
+ * drives the offline routed run. Returns true on run success.
41
+ */
42
+ export declare function runRoutedCommand(options: RunRoutedOptions): Promise<boolean>;
43
+ /**
44
+ * Read a dispatch payload file `{ action?, client_payload? }`. Returns the
45
+ * action + client_payload the routed dispatch trigger carries. An absent
46
+ * `--payload` yields an empty action (a bare `dispatch()` workflow still
47
+ * matches); a present file is parsed as JSON.
48
+ */
49
+ export declare function readDispatchPayload(payloadPath?: string): {
50
+ action?: string;
51
+ clientPayload?: unknown;
52
+ };
53
+ //# sourceMappingURL=run-routed.d.ts.map