@kici-dev/compiler 0.1.25 → 0.1.27

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 (96) hide show
  1. package/dist/cli-banner.d.ts +6 -3
  2. package/dist/cli-banner.js +13 -5
  3. package/dist/cli.js +66 -43
  4. package/dist/commands/check-mode.d.ts +1 -1
  5. package/dist/commands/compile.js +3 -3
  6. package/dist/commands/index.d.ts +5 -1
  7. package/dist/commands/index.js +6 -3
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/local-trust-root.d.ts +6 -0
  10. package/dist/commands/local-trust-root.js +52 -0
  11. package/dist/commands/local.d.ts +39 -0
  12. package/dist/commands/local.js +137 -0
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +40 -0
  15. package/dist/commands/logout.js +7 -0
  16. package/dist/commands/preview.js +1 -1
  17. package/dist/commands/run-banner.d.ts +31 -0
  18. package/dist/commands/run-banner.js +25 -0
  19. package/dist/commands/run-routed.d.ts +53 -0
  20. package/dist/commands/run-routed.js +176 -0
  21. package/dist/commands/run.d.ts +0 -17
  22. package/dist/commands/run.js +7 -50
  23. package/dist/commands/secrets-list.d.ts +2 -2
  24. package/dist/commands/secrets-list.js +4 -4
  25. package/dist/commands/types.d.ts +3 -3
  26. package/dist/commands/types.js +4 -4
  27. package/dist/execution/executor.js +7 -7
  28. package/dist/generators/secrets-dts.d.ts +9 -9
  29. package/dist/generators/secrets-dts.js +12 -12
  30. package/dist/llm-context/llms-architecture.txt +2 -2
  31. package/dist/llm-context/llms-cli.txt +674 -340
  32. package/dist/llm-context/llms-features.txt +334 -336
  33. package/dist/llm-context/llms-full.txt +1207 -892
  34. package/dist/llm-context/llms-getting-started.txt +7 -15
  35. package/dist/llm-context/llms-patterns.txt +5 -23
  36. package/dist/llm-context/llms-providers.txt +10 -1
  37. package/dist/llm-context/llms-sdk.txt +12 -12
  38. package/dist/llm-context/llms.txt +5 -5
  39. package/dist/local-executor/secret-loader.d.ts +1 -1
  40. package/dist/local-plane/orchestrator-process.d.ts +65 -0
  41. package/dist/local-plane/orchestrator-process.js +115 -0
  42. package/dist/local-plane/paths.d.ts +39 -0
  43. package/dist/local-plane/paths.js +54 -0
  44. package/dist/local-plane/plane-manager.d.ts +151 -0
  45. package/dist/local-plane/plane-manager.js +340 -0
  46. package/dist/local-plane/plane-seed.d.ts +76 -0
  47. package/dist/local-plane/plane-seed.js +59 -0
  48. package/dist/local-plane/plane-trigger.d.ts +62 -0
  49. package/dist/local-plane/plane-trigger.js +97 -0
  50. package/dist/local-plane/platform-attach.d.ts +53 -0
  51. package/dist/local-plane/platform-attach.js +111 -0
  52. package/dist/local-plane/postgres.d.ts +44 -0
  53. package/dist/local-plane/postgres.js +165 -0
  54. package/dist/local-plane/resolve-plane.d.ts +37 -0
  55. package/dist/local-plane/resolve-plane.js +76 -0
  56. package/dist/local-plane/run-follow.d.ts +41 -0
  57. package/dist/local-plane/run-follow.js +104 -0
  58. package/dist/local-plane/scaler-config.d.ts +54 -0
  59. package/dist/local-plane/scaler-config.js +114 -0
  60. package/dist/local-plane/secret-seed.d.ts +59 -0
  61. package/dist/local-plane/secret-seed.js +112 -0
  62. package/dist/local-plane/source-provider.d.ts +43 -0
  63. package/dist/local-plane/source-provider.js +165 -0
  64. package/dist/local-plane/trusted-routing.d.ts +32 -0
  65. package/dist/local-plane/trusted-routing.js +47 -0
  66. package/dist/lockfile/generator.js +7 -7
  67. package/dist/remote/dashboard-client.d.ts +7 -7
  68. package/dist/remote/dashboard-client.js +7 -7
  69. package/dist/remote/platform-client.d.ts +1 -1
  70. package/dist/remote/secret-loader.d.ts +18 -0
  71. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  72. package/dist/remote/secret-upload.d.ts +1 -1
  73. package/dist/remote/secret-upload.js +2 -2
  74. package/dist/remote/uploader.d.ts +1 -1
  75. package/dist/remote/uploader.js +2 -2
  76. package/dist/templates/agents-md.d.ts +1 -1
  77. package/dist/templates/agents-md.js +2 -2
  78. package/dist/templates/package-json.js +1 -1
  79. package/dist/test-runner/job-executor.js +1 -1
  80. package/dist/test-runner/runs-on-display.d.ts +9 -0
  81. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  82. package/dist/test-runner/step-context.d.ts +1 -1
  83. package/dist/test-runner/step-context.js +2 -2
  84. package/dist/types.d.ts +6 -6
  85. package/package.json +7 -4
  86. package/sbom.spdx.json +9884 -1459
  87. package/dist/local-executor/dag-scheduler.js +0 -183
  88. package/dist/local-executor/index.js +0 -378
  89. package/dist/local-executor/job-runner.js +0 -411
  90. package/dist/local-executor/materializer.js +0 -132
  91. package/dist/local-executor/output-streamer.js +0 -168
  92. package/dist/local-executor/payload-generator.js +0 -138
  93. package/dist/local-executor/picker.js +0 -109
  94. package/dist/local-executor/to-event-payload.js +0 -21
  95. package/dist/local-executor/types.js +0 -2
  96. package/dist/local-executor/workflow-lock.js +0 -0
@@ -3,9 +3,12 @@ import type { Command } from 'commander';
3
3
  * Whether the version banner should be suppressed for the command about to run.
4
4
  *
5
5
  * Structured-output (`--json`) and quiet (`--quiet`) invocations must keep stdout
6
- * free of human-facing chrome so callers can parse stdout directly. Uses
7
- * `optsWithGlobals()` so the check stays correct if either flag is ever promoted
8
- * to a global option.
6
+ * free of human-facing chrome so callers can parse stdout directly. Walks the
7
+ * command chain checking each command's OWN opts (not `optsWithGlobals()`): a
8
+ * flag set anywhere in the chain suppresses the banner, and reading own opts
9
+ * avoids a parent's default value shadowing a subcommand's explicit flag —
10
+ * `kici run` defines `--quiet` for its routed action, which under
11
+ * `optsWithGlobals()` would mask `kici run remote --quiet`.
9
12
  */
10
13
  export declare function shouldSuppressBanner(actionCommand: Command): boolean;
11
14
  //# sourceMappingURL=cli-banner.d.ts.map
@@ -4,13 +4,21 @@ import "./rolldown-runtime-ClRpJifh.js";
4
4
  * Whether the version banner should be suppressed for the command about to run.
5
5
  *
6
6
  * Structured-output (`--json`) and quiet (`--quiet`) invocations must keep stdout
7
- * free of human-facing chrome so callers can parse stdout directly. Uses
8
- * `optsWithGlobals()` so the check stays correct if either flag is ever promoted
9
- * to a global option.
7
+ * free of human-facing chrome so callers can parse stdout directly. Walks the
8
+ * command chain checking each command's OWN opts (not `optsWithGlobals()`): a
9
+ * flag set anywhere in the chain suppresses the banner, and reading own opts
10
+ * avoids a parent's default value shadowing a subcommand's explicit flag —
11
+ * `kici run` defines `--quiet` for its routed action, which under
12
+ * `optsWithGlobals()` would mask `kici run remote --quiet`.
10
13
  */
11
14
  function shouldSuppressBanner(actionCommand) {
12
- const opts = actionCommand.optsWithGlobals();
13
- return Boolean(opts.json) || Boolean(opts.quiet);
15
+ let cmd = actionCommand;
16
+ while (cmd) {
17
+ const opts = cmd.opts();
18
+ if (opts.json === true || opts.quiet === true) return true;
19
+ cmd = cmd.parent;
20
+ }
21
+ return false;
14
22
  }
15
23
  //#endregion
16
24
  export { shouldSuppressBanner };
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { realpathSync } from "node:fs";
7
7
  import { Argument, Command, Option } from "commander";
8
8
  import pc from "picocolors";
9
9
  //#region src/cli.ts
10
- const version = "0.1.25";
10
+ const version = "0.1.27";
11
11
  /**
12
12
  * Top-level commands that were removed, mapped to their current equivalent.
13
13
  * Consulted when the CLI hits an unknown command so the user gets a precise
@@ -16,7 +16,8 @@ const version = "0.1.25";
16
16
  */
17
17
  const RETIRED_COMMANDS = {
18
18
  status: "`kici status` is no longer a command. Use `kici runs list` / `kici runs show <run-id>` to inspect runs, or `kici diagnostics` for orchestrator, scaler, and agent health.",
19
- cancel: "`kici cancel` is no longer a command. Use `kici runs cancel <run-id>`."
19
+ cancel: "`kici cancel` is no longer a command. Use `kici runs cancel <run-id>`.",
20
+ "run local": "`kici run local` is retired. Every run is now a real routed dispatch — use `kici run <event> --local` (e.g. `kici run push --local`), which runs this machine as an ephemeral agent through the local dev plane."
20
21
  };
21
22
  /**
22
23
  * Redirect message for a retired top-level command, or undefined if the name is
@@ -43,6 +44,7 @@ function attemptedCommand(program, argv) {
43
44
  function buildProgram() {
44
45
  const program = new Command();
45
46
  program.name("kici").description("KiCI workflow compiler").version(version);
47
+ program.enablePositionalOptions();
46
48
  program.hook("preAction", (_thisCommand, actionCommand) => {
47
49
  if (shouldSuppressBanner(actionCommand)) return;
48
50
  console.log(pc.gray(`kici v${version}`));
@@ -73,50 +75,26 @@ function buildProgram() {
73
75
  await fixtureCommand(event, options);
74
76
  });
75
77
  const runCommand = program.command("run").description("Execute workflows locally or remotely");
76
- runCommand.command("local").argument("[event]", "Event type (e.g., push, pr:open, schedule) — optional with --pick").description("Execute workflows locally without orchestrator infrastructure").option("-p, --pick", "Interactively pick a workflow and trigger to simulate", false).option("--workflow <name>", "Run only the specified workflow").option("--job <name>", "Run only the specified job (and its dependencies)").option("--branch <name>", "Override detected git branch").option("--sha <hash>", "Override detected git SHA").option("--payload <path>", "Path to explicit event payload JSON file").option("--concurrency <n>", "Max parallel jobs (default: CPU cores)", parseInt).option("--keep-going", "Continue after job failure", false).option("--container", "Use Podman container isolation", false).option("--env <KEY=VALUE>", "Environment variable override (repeatable)", (val, prev) => [...prev, val], []).option("--input <KEY=VALUE>", "Typed workflow-dispatch input (repeatable)", (val, prev) => [...prev, val], []).option("--quiet", "Suppress streaming output", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--files <path>", "Override changed file paths (repeatable, default: git diff)", (val, prev) => [...prev, val], []).option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--in-place", "Run against the real working directory instead of an isolated tmp checkout", false).option("--keep", "Always retain the isolated tmp checkout (default: keep only on failure)", false).option("--check", "Run in check mode: report drift, change nothing", false).option("--fail-on-drift", "In check mode, exit non-zero if any step reports drift", false).action(async (event, options) => {
77
- if (options.pick && options.workflow) {
78
- console.error("Error: --pick is mutually exclusive with --workflow.");
79
- process.exit(2);
80
- }
81
- if (!options.pick && !event) {
82
- const { printRunLocalUsage } = await import("./commands/index.js");
83
- printRunLocalUsage();
84
- process.exit(2);
85
- }
86
- const { runLocalCommand } = await import("./commands/index.js");
87
- const { resolveCheckMode } = await import("./commands/check-mode.js");
88
- let checkMode;
89
- try {
90
- checkMode = resolveCheckMode({
91
- check: options.check,
92
- failOnDrift: options.failOnDrift
93
- });
94
- } catch (err) {
95
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
78
+ runCommand.enablePositionalOptions();
79
+ runCommand.argument("[event]", "Event type for a routed local run (e.g. push, pr:open)").option("--local", "Route the run with this machine as the ephemeral agent", false).option("--offline", "Force the throwaway/independent plane (offline)", false).option("--connected", "Force the connected/hybrid plane (requires attachment)", false).option("--in-place", "Reuse the working tree directly instead of an isolated clone", false).option("--trusted", "Route to the trusted fleet agent profile: steps see the ambient host env (minus the agent identity). Alias: --no-sandbox", false).option("--no-sandbox", "Alias for --trusted (the bwrap sandbox is already off by default)").option("--env <KEY=VALUE>", "Per-run secret (repeatable)", (val, prev) => [...prev, val], []).option("--payload <path>", "Dispatch payload JSON { action?, client_payload? } for a routed dispatch run").option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--quiet", "Suppress the banner + streaming output", false).option("--debug", "Verbose internals", false).action(async (event, options) => {
80
+ const retired = retiredCommandHint(event ? `run ${event}` : void 0);
81
+ if (retired) {
82
+ process.stderr.write(pc.red(`${retired}\n`));
96
83
  process.exit(2);
97
84
  }
98
- const success = await runLocalCommand({
85
+ const { runRoutedCommand } = await import("./commands/index.js");
86
+ const success = await runRoutedCommand({
99
87
  event,
100
- checkMode,
101
- pick: options.pick,
102
- workflow: options.workflow,
103
- job: options.job,
104
- branch: options.branch,
105
- sha: options.sha,
106
- payload: options.payload,
107
- concurrency: options.concurrency,
108
- keepGoing: options.keepGoing,
109
- container: options.container,
88
+ local: options.local,
89
+ offline: options.offline,
90
+ connected: options.connected,
91
+ inPlace: options.inPlace,
92
+ trusted: Boolean(options.trusted) || options.sandbox === false,
110
93
  env: options.env,
111
- inputs: options.input,
112
- quiet: options.quiet,
113
- json: options.json,
114
- junit: options.junit,
115
- files: options.files,
116
- debug: options.debug,
94
+ payload: options.payload,
117
95
  kiciDir: options.kiciDir,
118
- inPlace: options.inPlace,
119
- keep: options.keep
96
+ quiet: options.quiet,
97
+ debug: options.debug
120
98
  });
121
99
  process.exit(success ? 0 : 1);
122
100
  });
@@ -174,7 +152,7 @@ function buildProgram() {
174
152
  const success = await hookInstallCommand({ git: options.git });
175
153
  process.exit(success ? 0 : 1);
176
154
  });
177
- program.command("login").description("Authenticate with KiCI via browser OAuth (default) or API key (--token)").option("--token <key>", "API key for direct authentication (legacy)").option("--device", "Force device authorization flow (for headless/SSH environments)").option("--platform-endpoint <url>", "Platform relay URL").option("--oidc-issuer <url>", "OIDC issuer URL (defaults to the hosted KiCI IdP unless a flag/env selects another)").option("--routing-key <key>", "Routing key for webhook source identification").addHelpText("after", `
155
+ program.command("login").description("Authenticate with KiCI via browser OAuth (default) or API key (--token)").option("--token <key>", "API key for direct authentication (legacy)").option("--device", "Force device authorization flow (for headless/SSH environments)").option("--platform-endpoint <url>", "Platform relay URL").option("--oidc-issuer <url>", "OIDC issuer URL (defaults to the hosted KiCI IdP unless a flag/env selects another)").option("--routing-key <key>", "Routing key for webhook source identification").option("--no-attach", "Skip the post-login prompt to attach the local dev plane").addHelpText("after", `
178
156
  Environment variables:
179
157
  KICI_BROWSER_CMD Custom browser command (use {url} placeholder, or 'none' to suppress)
180
158
  KICI_CALLBACK_PORT Fixed port for OAuth PKCE callback server (default: random)
@@ -188,7 +166,8 @@ Environment variables:
188
166
  device: options.device,
189
167
  platformEndpoint: options.platformEndpoint,
190
168
  oidcIssuer: options.oidcIssuer,
191
- routingKey: options.routingKey
169
+ routingKey: options.routingKey,
170
+ noAttachPrompt: options.attach === false
192
171
  });
193
172
  process.exit(success ? 0 : 1);
194
173
  });
@@ -224,6 +203,50 @@ Environment variables:
224
203
  const success = await orchestratorsUseCommand(name, { org: options.org });
225
204
  process.exit(success ? 0 : 1);
226
205
  });
206
+ const localCommand = program.command("local").description("Manage the local dev orchestrator plane");
207
+ localCommand.command("up").description("Start (or reuse) the local dev plane").option("--offline", "Force the independent (offline) plane (does not clear the attachment record)", false).option("--connected", "Force the connected/hybrid plane (requires an attached, reachable Platform)", false).action(async (options) => {
208
+ const { localUpCommand } = await import("./commands/index.js");
209
+ const ok = await localUpCommand({
210
+ offline: options.offline,
211
+ connected: options.connected
212
+ });
213
+ process.exit(ok ? 0 : 1);
214
+ });
215
+ for (const [name, desc, fn] of [
216
+ [
217
+ "status",
218
+ "Show local dev plane status and control commands",
219
+ "localStatusCommand"
220
+ ],
221
+ [
222
+ "down",
223
+ "Stop the local dev plane",
224
+ "localDownCommand"
225
+ ],
226
+ [
227
+ "logs",
228
+ "Print the local dev plane orchestrator log path",
229
+ "localLogsCommand"
230
+ ],
231
+ [
232
+ "attach",
233
+ "Attach the local dev plane to the Platform (hybrid)",
234
+ "localAttachCommand"
235
+ ],
236
+ [
237
+ "detach",
238
+ "Detach the local dev plane from the Platform (offline)",
239
+ "localDetachCommand"
240
+ ]
241
+ ]) localCommand.command(name).description(desc).action(async () => {
242
+ const ok = await (await import("./commands/index.js"))[fn]();
243
+ process.exit(ok ? 0 : 1);
244
+ });
245
+ localCommand.command("trust-root").description("Export the offline dev-signed identity trust root ({ issuer, jwks }) to a file").argument("<file>", "Output path for the { issuer, jwks } trust-root JSON").action(async (file) => {
246
+ const { localTrustRootCommand } = await import("./commands/index.js");
247
+ const ok = await localTrustRootCommand(file);
248
+ process.exit(ok ? 0 : 1);
249
+ });
227
250
  program.command("secrets").description("Manage secrets").command("list").description("List test-available secret contexts").action(async () => {
228
251
  const { secretsListCommand } = await import("./commands/index.js");
229
252
  const success = await secretsListCommand();
@@ -1,5 +1,5 @@
1
1
  import { CheckMode } from '@kici-dev/engine';
2
- /** The two run-mode flags shared by `kici run local` and `kici run remote`. */
2
+ /** The two check-mode flags accepted by `kici run remote`. */
3
3
  export interface CheckModeFlags {
4
4
  /** --check: report drift, change nothing. */
5
5
  check?: boolean;
@@ -10,7 +10,7 @@ import "../lockfile/index.js";
10
10
  import path from "node:path";
11
11
  import { existsSync } from "node:fs";
12
12
  import pc from "picocolors";
13
- import fs from "node:fs/promises";
13
+ import fs$1 from "node:fs/promises";
14
14
  import { logger, toErrorMessage } from "@kici-dev/core";
15
15
  import { PackageManager, detectPackageManagerSync } from "@kici-dev/core/package-manager";
16
16
  import { execSync } from "node:child_process";
@@ -20,7 +20,7 @@ import { execSync } from "node:child_process";
20
20
  */
21
21
  async function readExistingLockfileHash(lockPath) {
22
22
  try {
23
- const content = await fs.readFile(lockPath, "utf-8");
23
+ const content = await fs$1.readFile(lockPath, "utf-8");
24
24
  return JSON.parse(content).lockfileHash ?? void 0;
25
25
  } catch {
26
26
  return;
@@ -75,7 +75,7 @@ async function compileCommand(options) {
75
75
  if (options.verbose) logger.debug(pc.dim("Validation passed"));
76
76
  const lockJson = serializeLockFile(generateLockFile(workflowsWithSource));
77
77
  if (!options.check) {
78
- await fs.writeFile(lockPath, lockJson, "utf-8");
78
+ await fs$1.writeFile(lockPath, lockJson, "utf-8");
79
79
  if (!options.quiet) logger.info(pc.green("✓") + ` Compiled workflows → .kici/kici.lock.json` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
80
80
  try {
81
81
  const { loadGlobalConfig } = await import("../remote/config.js");
@@ -5,7 +5,9 @@ export { fixtureCommand } from './fixture.js';
5
5
  export type { FixtureOptions } from './fixture.js';
6
6
  export { previewCommand, previewEvent } from './preview.js';
7
7
  export type { PreviewOptions, RemoteRunOptions, RemoteRunResult } from './preview.js';
8
- export { runLocalCommand, runRemoteCommand, printRunLocalUsage } from './run.js';
8
+ export { runRemoteCommand } from './run.js';
9
+ export { runRoutedCommand } from './run-routed.js';
10
+ export type { RunRoutedOptions } from './run-routed.js';
9
11
  export { initCommand } from './init.js';
10
12
  export type { InitOptions } from './init.js';
11
13
  export { hookInstallCommand } from './hook.js';
@@ -35,6 +37,8 @@ export type { DiagnosticsOptions } from './diagnostics.js';
35
37
  export { orgListCommand, orgUseCommand, orgCurrentCommand } from './org.js';
36
38
  export { orchestratorsListCommand, orchestratorsUseCommand } from './orchestrators.js';
37
39
  export type { OrchestratorsOptions } from './orchestrators.js';
40
+ export { localUpCommand, localStatusCommand, localDownCommand, localLogsCommand, localAttachCommand, localDetachCommand, } from './local.js';
41
+ export { localTrustRootCommand } from './local-trust-root.js';
38
42
  export { logoutCommand } from './logout.js';
39
43
  export { approveCommand } from './approve.js';
40
44
  export type { ApproveOptions } from './approve.js';
@@ -1,6 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { compileCommand } from "./compile.js";
3
2
  import { approveCommand } from "./approve.js";
3
+ import { compileCommand } from "./compile.js";
4
4
  import { diagnosticsCommand } from "./diagnostics.js";
5
5
  import { docsCommand, docsLlmCommand } from "./docs.js";
6
6
  import { drainWorkerCommand } from "./drain-worker.js";
@@ -9,7 +9,8 @@ import { fixtureCommand } from "./fixture.js";
9
9
  import { hookInstallCommand } from "./hook.js";
10
10
  import { watchCommand } from "./watch.js";
11
11
  import { previewCommand, previewEvent } from "./preview.js";
12
- import { printRunLocalUsage, runLocalCommand, runRemoteCommand } from "./run.js";
12
+ import { runRemoteCommand } from "./run.js";
13
+ import { runRoutedCommand } from "./run-routed.js";
13
14
  import { initCommand } from "./init.js";
14
15
  import { loginCommand } from "./login.js";
15
16
  import { secretsListCommand } from "./secrets-list.js";
@@ -22,8 +23,10 @@ import { runsCancelCommand } from "./runs/cancel.js";
22
23
  import { typesCommand } from "./types.js";
23
24
  import { orgCurrentCommand, orgListCommand, orgUseCommand } from "./org.js";
24
25
  import { orchestratorsListCommand, orchestratorsUseCommand } from "./orchestrators.js";
26
+ import { localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localUpCommand } from "./local.js";
27
+ import { localTrustRootCommand } from "./local-trust-root.js";
25
28
  import { logoutCommand } from "./logout.js";
26
29
  import { rejectCommand } from "./reject.js";
27
30
  import { workflowsListCommand } from "./workflows.js";
28
31
  import { verifyAttestationCommand } from "./verify-attestation.js";
29
- export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, drainWorkerCommand, endpointsCommand, fixtureCommand, hookInstallCommand, initCommand, loginCommand, logoutCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, patCreateCommand, previewCommand, previewEvent, printRunLocalUsage, rejectCommand, runLocalCommand, runRemoteCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
32
+ export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, drainWorkerCommand, endpointsCommand, fixtureCommand, hookInstallCommand, initCommand, localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localTrustRootCommand, localUpCommand, loginCommand, logoutCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, patCreateCommand, previewCommand, previewEvent, rejectCommand, runRemoteCommand, runRoutedCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
@@ -280,7 +280,7 @@ async function writePrivateRegistryScaffold(kiciDir, args) {
280
280
  //
281
281
  // The agent renders these into the .kici/.npmrc before running \`npm install\`.
282
282
  // The token is resolved from the qualified secret reference at dispatch time
283
- // (the orchestrator looks it up under the named environment's scoped secrets).
283
+ // (the orchestrator looks it up under the named context's scoped secrets).
284
284
  import { workflow, job, step } from '@kici-dev/sdk';
285
285
 
286
286
  export default workflow('private-registry-example', {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Write the local dev plane's `{ issuer, jwks }` trust root to `outFile`.
3
+ * Returns true on success.
4
+ */
5
+ export declare function localTrustRootCommand(outFile: string): Promise<boolean>;
6
+ //# sourceMappingURL=local-trust-root.d.ts.map
@@ -0,0 +1,52 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { devIdentityPublicJwkFile } from "../local-plane/plane-manager.js";
3
+ import fs from "node:fs";
4
+ import pc from "picocolors";
5
+ import { logger } from "@kici-dev/core";
6
+ //#region src/commands/local-trust-root.ts
7
+ /**
8
+ * `kici local trust-root <file>` — export the offline local dev plane's
9
+ * dev-signed identity trust root as a self-contained `{ issuer, jwks }` file.
10
+ *
11
+ * The plane's dev identity mints tokens with issuer `kici-local` (clearly
12
+ * non-prod). To self-test a dev-signed attestation offline, `kici
13
+ * verify-attestation --trust-root <file>` needs the plane's public JWKS + its
14
+ * issuer, supplied out-of-band. This command writes exactly the shape
15
+ * `verify-attestation`'s `--trust-root` file path consumes.
16
+ *
17
+ * The exported issuer is the fixed sentinel `kici-local` — it can NEVER
18
+ * masquerade as the prod issuer (`https://api.kici.dev`), so verifying the same
19
+ * bundle against the default (prod) trust root still rejects structurally.
20
+ */
21
+ /** The plane's fixed non-prod dev-identity issuer. */
22
+ const KICI_LOCAL_ISSUER = "kici-local";
23
+ /**
24
+ * Write the local dev plane's `{ issuer, jwks }` trust root to `outFile`.
25
+ * Returns true on success.
26
+ */
27
+ async function localTrustRootCommand(outFile) {
28
+ if (!outFile) {
29
+ logger.error(pc.red("Error: an output file path is required (`kici local trust-root <file>`)."));
30
+ return false;
31
+ }
32
+ const pubFile = devIdentityPublicJwkFile();
33
+ let pubJwk;
34
+ try {
35
+ pubJwk = JSON.parse(fs.readFileSync(pubFile, "utf-8"));
36
+ } catch {
37
+ logger.error(pc.red("Error: no dev-signed identity found for the local plane. Boot the plane first (`kici local up`) so it generates + publishes its public key."));
38
+ return false;
39
+ }
40
+ const trustRoot = {
41
+ issuer: KICI_LOCAL_ISSUER,
42
+ jwks: { keys: [pubJwk] }
43
+ };
44
+ fs.writeFileSync(outFile, JSON.stringify(trustRoot, null, 2) + "\n");
45
+ logger.info(`${pc.green("✓")} Wrote local dev trust root to ${pc.bold(outFile)} ` + pc.dim(`(issuer ${KICI_LOCAL_ISSUER} — NOT prod)`));
46
+ logger.info(pc.dim(`Verify a dev-signed bundle offline with:\n kici verify-attestation --bundle <bundle> --trust-root ${outFile}`));
47
+ return true;
48
+ }
49
+ //#endregion
50
+ export { localTrustRootCommand };
51
+
52
+ //# sourceMappingURL=local-trust-root.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * kici local commands
3
+ *
4
+ * Manage the warm, per-user local dev orchestrator plane: a real independent
5
+ * orchestrator plus a local Postgres (embedded, with a podman fallback) that
6
+ * `kici run --local` dispatches through. `up` boots or reuses the plane,
7
+ * `status` reports it plus the control commands, `down` stops it, and `logs`
8
+ * prints the orchestrator log path.
9
+ */
10
+ /**
11
+ * Boot (or reuse) the local dev plane, honoring the durable attachment record.
12
+ *
13
+ * Routes through the same `resolvePlaneForRun` resolver `kici run --local` uses,
14
+ * so the two commands share one plane-resolution path and never diverge: an
15
+ * attached + reachable plane comes up hybrid, an attached-but-unreachable plane
16
+ * falls back to offline with a loud banner, and a never-attached plane comes up
17
+ * offline. `--offline` forces the independent boot (without clearing the durable
18
+ * attachment record — only `detach` does that); `--connected` requires an
19
+ * attached, reachable Platform.
20
+ */
21
+ export declare function localUpCommand(flags?: {
22
+ offline?: boolean;
23
+ connected?: boolean;
24
+ }): Promise<boolean>;
25
+ /** Show the local dev plane status and its control commands. */
26
+ export declare function localStatusCommand(): Promise<boolean>;
27
+ /** Stop the local dev plane. */
28
+ export declare function localDownCommand(): Promise<boolean>;
29
+ /** Print the local dev plane orchestrator log path. */
30
+ export declare function localLogsCommand(): Promise<boolean>;
31
+ /**
32
+ * Attach the local dev plane to the hosted Platform so `kici run --local` uses
33
+ * real Platform-minted OIDC + attestation. Mints an org-scoped orchestrator key
34
+ * with the logged-in PAT, then (re)boots the plane hybrid.
35
+ */
36
+ export declare function localAttachCommand(): Promise<boolean>;
37
+ /** Detach the local dev plane from the Platform and reboot it offline (independent). */
38
+ export declare function localDetachCommand(): Promise<boolean>;
39
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1,137 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { loadGlobalConfig } from "../remote/config.js";
3
+ import { attachPlane, detachPlane, planeDown, planeLogPath, planeStatus } from "../local-plane/plane-manager.js";
4
+ import { resolvePlaneForRun } from "../local-plane/resolve-plane.js";
5
+ import pc from "picocolors";
6
+ import { toErrorMessage } from "@kici-dev/core";
7
+ //#region src/commands/local.ts
8
+ /**
9
+ * kici local commands
10
+ *
11
+ * Manage the warm, per-user local dev orchestrator plane: a real independent
12
+ * orchestrator plus a local Postgres (embedded, with a podman fallback) that
13
+ * `kici run --local` dispatches through. `up` boots or reuses the plane,
14
+ * `status` reports it plus the control commands, `down` stops it, and `logs`
15
+ * prints the orchestrator log path.
16
+ */
17
+ /** Print the control commands a user needs once the plane is running. */
18
+ function printControlHints() {
19
+ console.log("");
20
+ console.log(pc.dim("Control commands:"));
21
+ console.log(` ${pc.cyan("kici local status")} Show plane status`);
22
+ console.log(` ${pc.cyan("kici local logs")} Print the orchestrator log path`);
23
+ console.log(` ${pc.cyan("kici local down")} Stop the plane`);
24
+ }
25
+ /**
26
+ * Boot (or reuse) the local dev plane, honoring the durable attachment record.
27
+ *
28
+ * Routes through the same `resolvePlaneForRun` resolver `kici run --local` uses,
29
+ * so the two commands share one plane-resolution path and never diverge: an
30
+ * attached + reachable plane comes up hybrid, an attached-but-unreachable plane
31
+ * falls back to offline with a loud banner, and a never-attached plane comes up
32
+ * offline. `--offline` forces the independent boot (without clearing the durable
33
+ * attachment record — only `detach` does that); `--connected` requires an
34
+ * attached, reachable Platform.
35
+ */
36
+ async function localUpCommand(flags = {}) {
37
+ console.log(pc.dim("Starting the local dev plane…"));
38
+ const resolved = await resolvePlaneForRun({
39
+ offline: flags.offline ?? false,
40
+ connected: flags.connected ?? false
41
+ });
42
+ if ("error" in resolved) {
43
+ console.log(pc.red(`✗ ${resolved.error}`));
44
+ return false;
45
+ }
46
+ const { plane } = resolved;
47
+ const url = plane.url ?? "";
48
+ const pg = pc.dim(`(postgres: ${plane.pgKind ?? "unknown"})`);
49
+ if (resolved.kind === "attached") console.log(pc.green("✓") + ` Local dev plane running (hybrid → Platform, org: ${pc.bold(resolved.orgId)}) at ${pc.bold(url)} ` + pg);
50
+ else {
51
+ console.log(pc.green("✓") + ` Local dev plane running (independent/offline) at ${pc.bold(url)} ` + pg);
52
+ if (resolved.kind === "fallback") console.log(pc.yellow(` ⚠ Attached, but booted offline: ${resolved.reason}`));
53
+ }
54
+ printControlHints();
55
+ return true;
56
+ }
57
+ /** Show the local dev plane status and its control commands. */
58
+ async function localStatusCommand() {
59
+ const status = await planeStatus();
60
+ if (!status.running) {
61
+ console.log(pc.yellow("Local dev plane is not running."));
62
+ console.log(`Start it with ${pc.cyan("kici local up")}.`);
63
+ return true;
64
+ }
65
+ console.log(pc.green("✓") + ` Local dev plane running at ${pc.bold(status.url ?? "")}`);
66
+ console.log(pc.dim(` port: ${status.port}`));
67
+ console.log(pc.dim(` pid: ${status.pid ?? "unknown"}`));
68
+ console.log(pc.dim(` postgres: ${status.pgKind ?? "unknown"}`));
69
+ if (status.mode === "hybrid" && status.attachment) console.log(pc.dim(` attached: hybrid → Platform (org: ${status.attachment.orgId})`));
70
+ else console.log(pc.dim(" attached: no (independent/offline)"));
71
+ printControlHints();
72
+ return true;
73
+ }
74
+ /** Stop the local dev plane. */
75
+ async function localDownCommand() {
76
+ console.log(pc.dim("Stopping the local dev plane…"));
77
+ await planeDown();
78
+ console.log(pc.green("✓") + " Local dev plane stopped.");
79
+ return true;
80
+ }
81
+ /** Print the local dev plane orchestrator log path. */
82
+ async function localLogsCommand() {
83
+ console.log(planeLogPath());
84
+ return true;
85
+ }
86
+ /**
87
+ * Attach the local dev plane to the hosted Platform so `kici run --local` uses
88
+ * real Platform-minted OIDC + attestation. Mints an org-scoped orchestrator key
89
+ * with the logged-in PAT, then (re)boots the plane hybrid.
90
+ */
91
+ async function localAttachCommand() {
92
+ const config = await loadGlobalConfig();
93
+ if (!config.pat) {
94
+ console.error(pc.red("Not authenticated. Run `kici login` first."));
95
+ return false;
96
+ }
97
+ if (!config.platformEndpoint) {
98
+ console.error(pc.red("No Platform endpoint configured. Run `kici login` first."));
99
+ return false;
100
+ }
101
+ const orgId = config.activeOrgId;
102
+ if (!orgId) {
103
+ console.error(pc.red("No active organization. Select one with `kici org use <org>` before attaching."));
104
+ return false;
105
+ }
106
+ try {
107
+ console.log(pc.dim("Attaching the local dev plane to the Platform…"));
108
+ const status = await attachPlane({
109
+ apiBase: config.platformEndpoint,
110
+ pat: config.pat,
111
+ orgId
112
+ });
113
+ console.log(pc.green("✓") + ` Local dev plane attached (hybrid) at ${pc.bold(status.url ?? "")} ` + pc.dim(`(org: ${orgId})`));
114
+ console.log(pc.dim("`kici run --local` now uses real Platform OIDC + attestation. Detach: ") + pc.cyan("kici local detach"));
115
+ return true;
116
+ } catch (err) {
117
+ console.error(pc.red(`Attach failed: ${toErrorMessage(err)}`));
118
+ return false;
119
+ }
120
+ }
121
+ /** Detach the local dev plane from the Platform and reboot it offline (independent). */
122
+ async function localDetachCommand() {
123
+ const config = await loadGlobalConfig();
124
+ try {
125
+ console.log(pc.dim("Detaching the local dev plane…"));
126
+ await detachPlane({ pat: config.pat });
127
+ console.log(pc.green("✓") + " Local dev plane detached (independent/offline).");
128
+ return true;
129
+ } catch (err) {
130
+ console.error(pc.red(`Detach failed: ${toErrorMessage(err)}`));
131
+ return false;
132
+ }
133
+ }
134
+ //#endregion
135
+ export { localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localUpCommand };
136
+
137
+ //# sourceMappingURL=local.js.map
@@ -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";
@@ -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;
@@ -35,7 +35,7 @@ async function previewCommand(event, options) {
35
35
  logger.info(pc.gray(" kici preview schedule"));
36
36
  logger.info(pc.gray(" kici preview lifecycle:workflow_complete\n"));
37
37
  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"));
38
+ logger.info(pc.gray("For local workflow execution, use: kici run <event> --local\n"));
39
39
  return true;
40
40
  }
41
41
  if (!isKnownEventArg(event)) {