@kici-dev/compiler 0.1.21 → 0.1.23
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.
- package/dist/cli.js +25 -7
- package/dist/commands/compile.d.ts +6 -0
- package/dist/commands/compile.js +6 -3
- package/dist/commands/docs.d.ts +8 -8
- package/dist/commands/docs.js +35 -16
- package/dist/commands/held-run-client.d.ts +7 -2
- package/dist/commands/held-run-client.js +9 -3
- package/dist/commands/held-run-resolve.d.ts +5 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +15 -7
- package/dist/commands/org.js +2 -2
- package/dist/commands/run-hold-watch.d.ts +57 -0
- package/dist/commands/run-hold-watch.js +87 -0
- package/dist/commands/run.d.ts +23 -0
- package/dist/commands/run.js +150 -17
- package/dist/commands/test.d.ts +14 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.js +1 -1
- package/dist/fixtures/describe-event.d.ts +6 -0
- package/dist/fixtures/describe-event.js +18 -0
- package/dist/fixtures/picker.d.ts +19 -0
- package/dist/fixtures/picker.js +64 -0
- package/dist/llm-context/llms-architecture.txt +1440 -0
- package/dist/llm-context/llms-cli.txt +2386 -0
- package/dist/llm-context/llms-features.txt +2389 -0
- package/dist/llm-context/llms-full.txt +1304 -349
- package/dist/llm-context/llms-getting-started.txt +519 -0
- package/dist/llm-context/llms-patterns.txt +1324 -0
- package/dist/llm-context/llms-providers.txt +805 -0
- package/dist/llm-context/llms-sdk.txt +3725 -0
- package/dist/llm-context/llms.txt +15 -1
- package/dist/local-executor/index.js +40 -3
- package/dist/local-executor/job-runner.d.ts +2 -0
- package/dist/local-executor/job-runner.js +37 -4
- package/dist/local-executor/types.d.ts +2 -0
- package/dist/lockfile/generator.js +46 -20
- package/dist/remote/config.d.ts +2 -0
- package/dist/remote/config.js +1 -0
- package/dist/remote/platform-client.d.ts +12 -1
- package/dist/remote/uploader.js +1 -0
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/rule-evaluator.d.ts +1 -1
- package/dist/test-runner/rule-evaluator.js +2 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +8 -2
- package/dist/types.d.ts +15 -6
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
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.
|
|
10
|
+
const version = "0.1.23";
|
|
11
11
|
/**
|
|
12
12
|
* Build the kici Commander program with every command registered. Exported so
|
|
13
13
|
* the surface registry can walk the real command tree without parsing argv (no
|
|
@@ -44,7 +44,7 @@ function buildProgram() {
|
|
|
44
44
|
await fixtureCommand(event, options);
|
|
45
45
|
});
|
|
46
46
|
const runCommand = program.command("run").description("Execute workflows locally or remotely");
|
|
47
|
-
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("--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) => {
|
|
47
|
+
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) => {
|
|
48
48
|
if (options.pick && options.workflow) {
|
|
49
49
|
console.error("Error: --pick is mutually exclusive with --workflow.");
|
|
50
50
|
process.exit(2);
|
|
@@ -78,6 +78,7 @@ function buildProgram() {
|
|
|
78
78
|
keepGoing: options.keepGoing,
|
|
79
79
|
container: options.container,
|
|
80
80
|
env: options.env,
|
|
81
|
+
inputs: options.input,
|
|
81
82
|
quiet: options.quiet,
|
|
82
83
|
json: options.json,
|
|
83
84
|
junit: options.junit,
|
|
@@ -89,7 +90,19 @@ function buildProgram() {
|
|
|
89
90
|
});
|
|
90
91
|
process.exit(success ? 0 : 1);
|
|
91
92
|
});
|
|
92
|
-
runCommand.command("remote").argument("[fixture]", "Fixture name or glob pattern (omit to list available)").description("Execute fixtures remotely via orchestrator").option("--workflow <name>", "Run a specific workflow directly (bypass triggers)").option("--all", "Run all available fixtures", false).option("--parallel", "Run matching fixtures concurrently", false).option("--no-wait", "Fire and forget (print runIds, don't stream)").option("--quiet", "Suppress output except final result", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--history", "Show recent run history", false).option("--routing-key <key>", "Override routing key for this run").option("--org <id>", "Target organization (overrides the active org)").option("--orchestrator <name>", "Target orchestrator cluster (overrides the per-org default)").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--context <ctx.key=value>", "Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable)", (val, prev) => [...prev, val], []).option("--env <KEY=VALUE>", "Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator", (val, prev) => [...prev, val], []).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 (fixture, options) => {
|
|
93
|
+
runCommand.command("remote").argument("[fixture]", "Fixture name or glob pattern (omit to list available)").description("Execute fixtures remotely via orchestrator").option("--workflow <name>", "Run a specific workflow directly (bypass triggers)").option("--all", "Run all available fixtures", false).option("-p, --pick", "Interactively pick fixtures to run", false).option("--parallel", "Run matching fixtures concurrently", false).option("--no-wait", "Fire and forget (print runIds, don't stream)").option("--quiet", "Suppress output except final result", false).option("--json", "Output structured JSON result", false).option("--junit <path>", "Output JUnit XML result").option("--history", "Show recent run history", false).option("--routing-key <key>", "Override routing key for this run").option("--org <id>", "Target organization (overrides the active org)").option("--orchestrator <name>", "Target orchestrator cluster (overrides the per-org default)").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--context <ctx.key=value>", "Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable)", (val, prev) => [...prev, val], []).option("--env <KEY=VALUE>", "Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator", (val, prev) => [...prev, val], []).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).option("--target <selector>", "Narrow runsOnAll jobs to hosts matching this label selector (repeatable, AND-combined)", (val, prev) => [...prev, val], []).option("--target-allow-empty", "A --target that narrows a runsOnAll job to zero hosts skips it instead of failing", false).option("--input <KEY=VALUE>", "Typed workflow-dispatch input (repeatable)", (val, prev) => [...prev, val], []).option("--approve-all, --yes", "Auto-approve every approval gate this run holds on (run-scoped; eligibility still enforced)", false).action(async (fixture, options) => {
|
|
94
|
+
if (options.pick && fixture) {
|
|
95
|
+
console.error("Error: --pick selects fixtures interactively; do not also pass a fixture name.");
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
if (options.pick && options.all) {
|
|
99
|
+
console.error("Error: --pick is mutually exclusive with --all.");
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
if (options.pick && options.workflow) {
|
|
103
|
+
console.error("Error: --pick is mutually exclusive with --workflow.");
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
93
106
|
const { runRemoteCommand } = await import("./commands/index.js");
|
|
94
107
|
const { resolveCheckMode } = await import("./commands/check-mode.js");
|
|
95
108
|
let checkMode;
|
|
@@ -105,7 +118,11 @@ function buildProgram() {
|
|
|
105
118
|
const success = await runRemoteCommand(fixture, {
|
|
106
119
|
...options,
|
|
107
120
|
checkMode,
|
|
108
|
-
envFlags: options.env
|
|
121
|
+
envFlags: options.env,
|
|
122
|
+
targets: options.target,
|
|
123
|
+
targetAllowEmpty: options.targetAllowEmpty,
|
|
124
|
+
approveAll: options.approveAll,
|
|
125
|
+
inputs: options.input
|
|
109
126
|
});
|
|
110
127
|
process.exit(success ? 0 : 1);
|
|
111
128
|
});
|
|
@@ -127,7 +144,7 @@ function buildProgram() {
|
|
|
127
144
|
const success = await hookInstallCommand({ git: options.git });
|
|
128
145
|
process.exit(success ? 0 : 1);
|
|
129
146
|
});
|
|
130
|
-
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("--routing-key <key>", "Routing key for webhook source identification").addHelpText("after", `
|
|
147
|
+
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", `
|
|
131
148
|
Environment variables:
|
|
132
149
|
KICI_BROWSER_CMD Custom browser command (use {url} placeholder, or 'none' to suppress)
|
|
133
150
|
KICI_CALLBACK_PORT Fixed port for OAuth PKCE callback server (default: random)
|
|
@@ -140,6 +157,7 @@ Environment variables:
|
|
|
140
157
|
token: options.token,
|
|
141
158
|
device: options.device,
|
|
142
159
|
platformEndpoint: options.platformEndpoint,
|
|
160
|
+
oidcIssuer: options.oidcIssuer,
|
|
143
161
|
routingKey: options.routingKey
|
|
144
162
|
});
|
|
145
163
|
process.exit(success ? 0 : 1);
|
|
@@ -264,10 +282,10 @@ Environment variables:
|
|
|
264
282
|
const { docsCommand } = await import("./commands/index.js");
|
|
265
283
|
const success = await docsCommand({ open: options.open });
|
|
266
284
|
process.exit(success ? 0 : 1);
|
|
267
|
-
}).command("llm").description("Print
|
|
285
|
+
}).command("llm [topic]").description("Print KiCI LLM docs bundles. No topic prints the llms.txt index; <topic> prints a task bundle (e.g. sdk, cli, patterns, features, providers, architecture, getting-started); \"full\" prints the complete bundle.").option("--out <path>", "Write the bundle to a file instead of stdout").action(async (topic, options) => {
|
|
268
286
|
const { docsLlmCommand } = await import("./commands/index.js");
|
|
269
287
|
const success = await docsLlmCommand({
|
|
270
|
-
|
|
288
|
+
topic,
|
|
271
289
|
out: options.out
|
|
272
290
|
});
|
|
273
291
|
process.exit(success ? 0 : 1);
|
|
@@ -6,6 +6,12 @@ export interface CompileOptions {
|
|
|
6
6
|
check: boolean;
|
|
7
7
|
/** Verbose output */
|
|
8
8
|
verbose: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Suppress the success line on stdout (and the auto-types success line) so a
|
|
11
|
+
* caller emitting machine-readable output keeps stdout pure. Validation
|
|
12
|
+
* errors are still reported.
|
|
13
|
+
*/
|
|
14
|
+
quiet?: boolean;
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Execute the compile command.
|
package/dist/commands/compile.js
CHANGED
|
@@ -76,7 +76,7 @@ async function compileCommand(options) {
|
|
|
76
76
|
const lockJson = serializeLockFile(generateLockFile(workflowsWithSource));
|
|
77
77
|
if (!options.check) {
|
|
78
78
|
await fs.writeFile(lockPath, lockJson, "utf-8");
|
|
79
|
-
logger.info(pc.green("✓") + ` Compiled workflows → .kici/kici.lock.json` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
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");
|
|
82
82
|
const config = await loadGlobalConfig();
|
|
@@ -84,12 +84,15 @@ async function compileCommand(options) {
|
|
|
84
84
|
const hasEndpoint = Boolean(config.platformEndpoint ?? config.endpoint);
|
|
85
85
|
if (hasToken && hasEndpoint && config.activeOrgId) {
|
|
86
86
|
const { typesCommand } = await import("./types.js");
|
|
87
|
-
await typesCommand({
|
|
87
|
+
await typesCommand({
|
|
88
|
+
kiciDir,
|
|
89
|
+
quiet: options.quiet
|
|
90
|
+
});
|
|
88
91
|
}
|
|
89
92
|
} catch {
|
|
90
93
|
logger.warn(pc.yellow("Could not refresh types (Platform unreachable). Compilation succeeded."));
|
|
91
94
|
}
|
|
92
|
-
} else logger.info(pc.green("✓") + ` Workflows are valid` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
95
|
+
} else if (!options.quiet) logger.info(pc.green("✓") + ` Workflows are valid` + pc.dim(` (${workflowsWithSource.length} workflow${workflowsWithSource.length !== 1 ? "s" : ""})`));
|
|
93
96
|
return true;
|
|
94
97
|
} catch (error) {
|
|
95
98
|
if (isCompilerError(error)) logger.error(formatError(error));
|
package/dist/commands/docs.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export interface DocsOptions {
|
|
|
3
3
|
open?: boolean;
|
|
4
4
|
}
|
|
5
5
|
export interface DocsLlmOptions {
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/** Task bundle id to print. Undefined → the llms.txt index; 'full' → llms-full.txt. */
|
|
7
|
+
topic?: string;
|
|
8
8
|
/** Override the bundled output destination (overrides default stdout). */
|
|
9
9
|
out?: string;
|
|
10
|
-
/** Override the directory holding llms
|
|
10
|
+
/** Override the directory holding the llms*.txt files (test seam). */
|
|
11
11
|
bundleDir?: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -15,12 +15,12 @@ export interface DocsLlmOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function docsCommand(options?: DocsOptions): Promise<boolean>;
|
|
17
17
|
/**
|
|
18
|
-
* Print
|
|
18
|
+
* Print a KiCI LLM docs bundle to stdout (or a file).
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* brief
|
|
20
|
+
* No topic prints the llms.txt index (a router listing every task bundle).
|
|
21
|
+
* A topic prints that task bundle (e.g. `sdk`, `cli`, `patterns`); `full`
|
|
22
|
+
* prints the everything-bundle. Pipe straight into a coding agent's context
|
|
23
|
+
* to brief it on KiCI authoring without an internet round-trip.
|
|
24
24
|
*/
|
|
25
25
|
export declare function docsLlmCommand(options?: DocsLlmOptions): Promise<boolean>;
|
|
26
26
|
//# sourceMappingURL=docs.d.ts.map
|
package/dist/commands/docs.js
CHANGED
|
@@ -2,12 +2,24 @@ import "../chunk-BTugEXQM.js";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import pc from "picocolors";
|
|
5
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import { logger, toErrorMessage } from "@kici-dev/core";
|
|
7
7
|
import open from "open";
|
|
8
8
|
//#region src/commands/docs.ts
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const DOCS_HOME_URL = "https://kici.dev/docs/";
|
|
11
|
+
function bundleFilenameForTopic(topic) {
|
|
12
|
+
if (!topic) return "llms.txt";
|
|
13
|
+
if (topic === "full") return "llms-full.txt";
|
|
14
|
+
return `llms-${topic}.txt`;
|
|
15
|
+
}
|
|
16
|
+
async function availableTopics(bundleDir) {
|
|
17
|
+
try {
|
|
18
|
+
return [...(await readdir(bundleDir)).filter((n) => n.startsWith("llms-") && n.endsWith(".txt") && n !== "llms-full.txt").map((n) => n.slice(5, -4)).sort(), "full"];
|
|
19
|
+
} catch {
|
|
20
|
+
return ["full"];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
11
23
|
/**
|
|
12
24
|
* Open the published documentation site in the user's default browser.
|
|
13
25
|
*/
|
|
@@ -27,32 +39,39 @@ async function docsCommand(options = {}) {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
/**
|
|
30
|
-
* Print
|
|
42
|
+
* Print a KiCI LLM docs bundle to stdout (or a file).
|
|
31
43
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* brief
|
|
44
|
+
* No topic prints the llms.txt index (a router listing every task bundle).
|
|
45
|
+
* A topic prints that task bundle (e.g. `sdk`, `cli`, `patterns`); `full`
|
|
46
|
+
* prints the everything-bundle. Pipe straight into a coding agent's context
|
|
47
|
+
* to brief it on KiCI authoring without an internet round-trip.
|
|
36
48
|
*/
|
|
37
49
|
async function docsLlmCommand(options = {}) {
|
|
38
|
-
const filename = options.
|
|
50
|
+
const filename = bundleFilenameForTopic(options.topic);
|
|
39
51
|
const bundleDir = options.bundleDir ?? path.join(__dirname, "..", "llm-context");
|
|
40
52
|
const bundlePath = path.join(bundleDir, filename);
|
|
53
|
+
let content;
|
|
41
54
|
try {
|
|
42
|
-
|
|
43
|
-
if (options.out) {
|
|
44
|
-
await writeFile(options.out, content, "utf-8");
|
|
45
|
-
logger.info(pc.gray(`Wrote ${filename} to ${options.out}`));
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
process.stdout.write(content);
|
|
49
|
-
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
50
|
-
return true;
|
|
55
|
+
content = await readFile(bundlePath, "utf-8");
|
|
51
56
|
} catch (error) {
|
|
57
|
+
if (options.topic && options.topic !== "full") {
|
|
58
|
+
const topics = await availableTopics(bundleDir);
|
|
59
|
+
logger.error(pc.red(`Unknown docs bundle "${options.topic}".`));
|
|
60
|
+
logger.info(pc.gray(`Available topics: ${topics.join(", ")} (no topic prints the index).`));
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
52
63
|
logger.error(pc.red(`Error reading bundled ${filename}: ${toErrorMessage(error)}`));
|
|
53
64
|
logger.info(pc.gray("The bundle ships with the @kici-dev/compiler package. If you built the package locally, run `pnpm build` in packages/compiler/."));
|
|
54
65
|
return false;
|
|
55
66
|
}
|
|
67
|
+
if (options.out) {
|
|
68
|
+
await writeFile(options.out, content, "utf-8");
|
|
69
|
+
logger.info(pc.gray(`Wrote ${filename} to ${options.out}`));
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
process.stdout.write(content);
|
|
73
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
74
|
+
return true;
|
|
56
75
|
}
|
|
57
76
|
//#endregion
|
|
58
77
|
export { docsCommand, docsLlmCommand };
|
|
@@ -19,8 +19,13 @@ export interface HeldRunContext {
|
|
|
19
19
|
export declare function resolveHeldRunContext(): Promise<HeldRunContext | null>;
|
|
20
20
|
/** List the held runs for a single run id. */
|
|
21
21
|
export declare function listHeldRunsForRun(ctx: HeldRunContext, runId: string): Promise<HeldRunSummary[]>;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* POST an approve decision for a held run. Returns true on success. When
|
|
24
|
+
* `autoApprove` is set, marks the approval as a `kici run --approve-all`
|
|
25
|
+
* breakglass so the orchestrator audits it as `held_run.auto_approve`
|
|
26
|
+
* (eligibility is still enforced server-side — never a bypass).
|
|
27
|
+
*/
|
|
28
|
+
export declare function postApprove(ctx: HeldRunContext, heldRunId: string, autoApprove?: boolean): Promise<boolean>;
|
|
24
29
|
/** POST a reject decision (with reason) for a held run. Returns true on success. */
|
|
25
30
|
export declare function postReject(ctx: HeldRunContext, heldRunId: string, reason: string): Promise<boolean>;
|
|
26
31
|
//# sourceMappingURL=held-run-client.d.ts.map
|
|
@@ -52,9 +52,15 @@ async function listHeldRunsForRun(ctx, runId) {
|
|
|
52
52
|
if (!response.ok) throw new Error(await describeError(response));
|
|
53
53
|
return (await response.json()).heldRuns ?? [];
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* POST an approve decision for a held run. Returns true on success. When
|
|
57
|
+
* `autoApprove` is set, marks the approval as a `kici run --approve-all`
|
|
58
|
+
* breakglass so the orchestrator audits it as `held_run.auto_approve`
|
|
59
|
+
* (eligibility is still enforced server-side — never a bypass).
|
|
60
|
+
*/
|
|
61
|
+
async function postApprove(ctx, heldRunId, autoApprove = false) {
|
|
62
|
+
const query = autoApprove ? "?auto=1" : "";
|
|
63
|
+
const url = `${ctx.endpoint}/api/v1/orgs/${ctx.orgId}/held-runs/${heldRunId}/approve${query}`;
|
|
58
64
|
const response = await fetch(url, {
|
|
59
65
|
method: "POST",
|
|
60
66
|
headers: authHeaders(ctx.token)
|
|
@@ -16,6 +16,11 @@ export interface HeldRunSummary {
|
|
|
16
16
|
holdScope?: HeldRunScope;
|
|
17
17
|
stepIndex?: number | null;
|
|
18
18
|
status: string;
|
|
19
|
+
/** Computed drift payload for a `when: 'drift'` step hold; absent otherwise. */
|
|
20
|
+
payload?: {
|
|
21
|
+
summaryMarkdown: string;
|
|
22
|
+
drift?: unknown;
|
|
23
|
+
} | null;
|
|
19
24
|
}
|
|
20
25
|
/** Filters supplied on the command line. */
|
|
21
26
|
export interface HeldRunFilter {
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export interface LoginOptions {
|
|
|
3
3
|
token?: string;
|
|
4
4
|
/** Platform relay URL */
|
|
5
5
|
platformEndpoint?: string;
|
|
6
|
+
/** OIDC issuer URL override */
|
|
7
|
+
oidcIssuer?: string;
|
|
6
8
|
/** Routing key for webhook source identification */
|
|
7
9
|
routingKey?: string;
|
|
8
10
|
/** Force device authorization flow regardless of environment */
|
package/dist/commands/login.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../chunk-BTugEXQM.js";
|
|
2
|
-
import { getConfigPath, mergeGlobalConfig } from "../remote/config.js";
|
|
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
5
|
import { isHeadless } from "../auth/headless-detect.js";
|
|
@@ -46,8 +46,9 @@ function checkPatExpiry(expiresAt) {
|
|
|
46
46
|
* 4. Save PAT to global config
|
|
47
47
|
*/
|
|
48
48
|
async function oauthLogin(options) {
|
|
49
|
+
const existing = await loadGlobalConfig();
|
|
49
50
|
const platformUrl = options.platformEndpoint || process.env.KICI_PLATFORM_URL || "https://api.kici.dev";
|
|
50
|
-
const issuer = process.env.KICI_OIDC_ISSUER || "https://auth.kici.dev/realms/kici-internal";
|
|
51
|
+
const issuer = options.oidcIssuer || process.env.KICI_OIDC_ISSUER || "https://auth.kici.dev/realms/kici-internal";
|
|
51
52
|
const clientId = process.env.KICI_OIDC_CLIENT_ID || "kici-cli";
|
|
52
53
|
console.log(pc.cyan("\n Step 1/4: Detecting environment..."));
|
|
53
54
|
const browserCmdSet = !!process.env.KICI_BROWSER_CMD;
|
|
@@ -70,14 +71,21 @@ async function oauthLogin(options) {
|
|
|
70
71
|
machineName
|
|
71
72
|
});
|
|
72
73
|
console.log(pc.cyan("\n Step 4/4: Saving credentials..."));
|
|
73
|
-
const
|
|
74
|
+
const endpointChanged = existing.platformEndpoint !== platformUrl;
|
|
75
|
+
const next = {
|
|
76
|
+
...existing,
|
|
74
77
|
pat: patResult.token,
|
|
75
78
|
patId: patResult.id,
|
|
76
|
-
patExpiresAt: patResult.expiresAt
|
|
79
|
+
patExpiresAt: patResult.expiresAt,
|
|
80
|
+
platformEndpoint: platformUrl,
|
|
81
|
+
oidcIssuer: issuer
|
|
77
82
|
};
|
|
78
|
-
if (options.
|
|
79
|
-
if (
|
|
80
|
-
|
|
83
|
+
if (options.routingKey) next.routingKey = options.routingKey;
|
|
84
|
+
if (endpointChanged) {
|
|
85
|
+
delete next.activeOrgId;
|
|
86
|
+
delete next.defaultClusters;
|
|
87
|
+
}
|
|
88
|
+
await saveGlobalConfig(next);
|
|
81
89
|
const configPath = getConfigPath();
|
|
82
90
|
const expiryDate = new Date(patResult.expiresAt).toLocaleDateString();
|
|
83
91
|
console.log(pc.green(`\n Authenticated successfully!`));
|
package/dist/commands/org.js
CHANGED
|
@@ -65,9 +65,9 @@ async function orgListCommand() {
|
|
|
65
65
|
const isActive = org.id === config.activeOrgId;
|
|
66
66
|
const marker = isActive ? pc.green("* ") : " ";
|
|
67
67
|
const name = isActive ? pc.bold(org.displayName) : org.displayName;
|
|
68
|
-
const
|
|
68
|
+
const ownership = pc.gray(`(${org.isOwner ? "owner" : "member"})`);
|
|
69
69
|
const id = pc.gray(org.id);
|
|
70
|
-
console.log(` ${marker}${name.padEnd(nameWidth)} ${
|
|
70
|
+
console.log(` ${marker}${name.padEnd(nameWidth)} ${ownership} ${id}`);
|
|
71
71
|
}
|
|
72
72
|
console.log("");
|
|
73
73
|
return true;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hold-visibility + inline approve/reject for `kici run remote`.
|
|
3
|
+
*
|
|
4
|
+
* While a run is being watched, the orchestrator may pause it on an approval
|
|
5
|
+
* gate (a `when: 'always'` step/job/workflow gate, or a `when: 'drift'` step
|
|
6
|
+
* gate that carries a computed-drift payload). The poll loop calls
|
|
7
|
+
* `handleNewHolds` each tick with the run's pending holds. For each hold not
|
|
8
|
+
* seen before it prints the payload (if any) and, in a TTY, prompts the
|
|
9
|
+
* operator to approve/reject inline — reusing the same Platform held-run HTTP
|
|
10
|
+
* path as `kici approve` / `kici reject`. In a non-TTY it prints guidance and
|
|
11
|
+
* keeps polling (the run stays held until resolved out-of-band).
|
|
12
|
+
*/
|
|
13
|
+
import type { HeldRunSummary } from './held-run-resolve.js';
|
|
14
|
+
import { type HeldRunContext } from './held-run-client.js';
|
|
15
|
+
/** A yes/no prompt; injected so the poll branch is unit-testable. */
|
|
16
|
+
export type ConfirmPrompt = (message: string) => Promise<boolean>;
|
|
17
|
+
/** What to do with a newly-observed hold. Pure — no IO. */
|
|
18
|
+
export type HoldAction = {
|
|
19
|
+
kind: 'prompt';
|
|
20
|
+
hold: HeldRunSummary;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'notify';
|
|
23
|
+
hold: HeldRunSummary;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Classify the holds observed this tick against the set already seen. Returns
|
|
27
|
+
* one action per genuinely-new pending hold (TTY ⇒ prompt, non-TTY ⇒ notify),
|
|
28
|
+
* and the updated seen-set. Pure: the caller performs the IO.
|
|
29
|
+
*/
|
|
30
|
+
export declare function classifyNewHolds(holds: readonly HeldRunSummary[], seen: Set<string>, isTty: boolean): {
|
|
31
|
+
actions: HoldAction[];
|
|
32
|
+
seen: Set<string>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Process the new holds observed this tick: print each, and in a TTY prompt the
|
|
36
|
+
* operator to approve/reject inline (posting the decision via the shared
|
|
37
|
+
* held-run HTTP path). Returns the updated seen-set. A resolved context is
|
|
38
|
+
* resolved lazily on first need and cached for the run.
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleNewHolds(args: {
|
|
41
|
+
holds: readonly HeldRunSummary[];
|
|
42
|
+
seen: Set<string>;
|
|
43
|
+
isTty: boolean;
|
|
44
|
+
confirm: ConfirmPrompt;
|
|
45
|
+
/**
|
|
46
|
+
* `--approve-all` breakglass: auto-approve each hold (run-scoped) instead of
|
|
47
|
+
* prompting, marking the approval as `auto_approve` so the orchestrator
|
|
48
|
+
* audits it distinctly. Eligibility is still enforced server-side.
|
|
49
|
+
*/
|
|
50
|
+
approveAll?: boolean;
|
|
51
|
+
/** Override for tests; defaults to the shared Platform held-run context. */
|
|
52
|
+
resolveContext?: () => Promise<HeldRunContext | null>;
|
|
53
|
+
/** Override for tests. */
|
|
54
|
+
approve?: (ctx: HeldRunContext, heldRunId: string, autoApprove?: boolean) => Promise<boolean>;
|
|
55
|
+
reject?: (ctx: HeldRunContext, heldRunId: string, reason: string) => Promise<boolean>;
|
|
56
|
+
}): Promise<Set<string>>;
|
|
57
|
+
//# sourceMappingURL=run-hold-watch.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import "../chunk-BTugEXQM.js";
|
|
2
|
+
import { postApprove, postReject, resolveHeldRunContext } from "./held-run-client.js";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { logger } from "@kici-dev/core";
|
|
5
|
+
//#region src/commands/run-hold-watch.ts
|
|
6
|
+
/**
|
|
7
|
+
* Hold-visibility + inline approve/reject for `kici run remote`.
|
|
8
|
+
*
|
|
9
|
+
* While a run is being watched, the orchestrator may pause it on an approval
|
|
10
|
+
* gate (a `when: 'always'` step/job/workflow gate, or a `when: 'drift'` step
|
|
11
|
+
* gate that carries a computed-drift payload). The poll loop calls
|
|
12
|
+
* `handleNewHolds` each tick with the run's pending holds. For each hold not
|
|
13
|
+
* seen before it prints the payload (if any) and, in a TTY, prompts the
|
|
14
|
+
* operator to approve/reject inline — reusing the same Platform held-run HTTP
|
|
15
|
+
* path as `kici approve` / `kici reject`. In a non-TTY it prints guidance and
|
|
16
|
+
* keeps polling (the run stays held until resolved out-of-band).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Classify the holds observed this tick against the set already seen. Returns
|
|
20
|
+
* one action per genuinely-new pending hold (TTY ⇒ prompt, non-TTY ⇒ notify),
|
|
21
|
+
* and the updated seen-set. Pure: the caller performs the IO.
|
|
22
|
+
*/
|
|
23
|
+
function classifyNewHolds(holds, seen, isTty) {
|
|
24
|
+
const next = new Set(seen);
|
|
25
|
+
const actions = [];
|
|
26
|
+
for (const hold of holds) {
|
|
27
|
+
if (hold.status !== "pending") continue;
|
|
28
|
+
if (next.has(hold.id)) continue;
|
|
29
|
+
next.add(hold.id);
|
|
30
|
+
actions.push({
|
|
31
|
+
kind: isTty ? "prompt" : "notify",
|
|
32
|
+
hold
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
actions,
|
|
37
|
+
seen: next
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Print a hold's identity + its drift payload (when present) to stdout. */
|
|
41
|
+
function printHold(hold) {
|
|
42
|
+
const scope = hold.holdScope ?? "job";
|
|
43
|
+
const where = scope === "step" && hold.stepIndex != null ? `step #${hold.stepIndex}${hold.jobId ? ` of job '${hold.jobId}'` : ""}` : hold.jobId ? `job '${hold.jobId}'` : scope;
|
|
44
|
+
logger.info(pc.yellow(`\n[kici] Run held for approval (${where}).`));
|
|
45
|
+
if (hold.payload?.summaryMarkdown) {
|
|
46
|
+
logger.info(pc.dim("Computed drift — review before approving:"));
|
|
47
|
+
for (const line of hold.payload.summaryMarkdown.split("\n")) logger.info(pc.dim(` ${line}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Process the new holds observed this tick: print each, and in a TTY prompt the
|
|
52
|
+
* operator to approve/reject inline (posting the decision via the shared
|
|
53
|
+
* held-run HTTP path). Returns the updated seen-set. A resolved context is
|
|
54
|
+
* resolved lazily on first need and cached for the run.
|
|
55
|
+
*/
|
|
56
|
+
async function handleNewHolds(args) {
|
|
57
|
+
const { actions, seen } = classifyNewHolds(args.holds, args.seen, args.isTty);
|
|
58
|
+
if (actions.length === 0) return seen;
|
|
59
|
+
const resolveCtx = args.resolveContext ?? resolveHeldRunContext;
|
|
60
|
+
const doApprove = args.approve ?? postApprove;
|
|
61
|
+
const doReject = args.reject ?? postReject;
|
|
62
|
+
let ctx = null;
|
|
63
|
+
if (args.approveAll) logger.info(pc.yellow("[kici] --approve-all: auto-approving every gate for this run (eligibility enforced)."));
|
|
64
|
+
for (const action of actions) {
|
|
65
|
+
printHold(action.hold);
|
|
66
|
+
ctx = ctx ?? await resolveCtx();
|
|
67
|
+
if (!ctx) {
|
|
68
|
+
logger.info(pc.dim(`Run held; approve via \`kici approve ${action.hold.runId}\`.`));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (args.approveAll) {
|
|
72
|
+
if (await doApprove(ctx, action.hold.id, true)) logger.info(pc.green("[kici] Auto-approved."));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (action.kind === "notify") {
|
|
76
|
+
logger.info(pc.dim(`Run held; approve via the dashboard or \`kici approve ${action.hold.runId}\`.`));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const approved = await args.confirm("Approve this gate?");
|
|
80
|
+
if (approved ? await doApprove(ctx, action.hold.id) : await doReject(ctx, action.hold.id, "rejected via kici run")) logger.info(approved ? pc.green("[kici] Approved.") : pc.red("[kici] Rejected."));
|
|
81
|
+
}
|
|
82
|
+
return seen;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { classifyNewHolds, handleNewHolds };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=run-hold-watch.js.map
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
+
import { type HostTargetSelector, type InputsDescriptorMap } from '@kici-dev/engine';
|
|
1
2
|
import type { RunLocalOptions } from '../local-executor/types.js';
|
|
2
3
|
import type { RemoteRunOptions } from './test.js';
|
|
4
|
+
/**
|
|
5
|
+
* Compile `--target` selector strings into a {@link HostTargetSelector}. Each
|
|
6
|
+
* string becomes one AND value (its own include set); repeated values
|
|
7
|
+
* AND-combine. Returns undefined when no `--target` is given. Throws when
|
|
8
|
+
* `--target-allow-empty` is set without at least one `--target`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildTargetSelector(targets: string[] | undefined, allowEmpty: boolean): HostTargetSelector | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Look up the dispatch-trigger `inputs` descriptor for a workflow from a parsed
|
|
13
|
+
* inline lock file. When `workflowName` is given, only that workflow's dispatch
|
|
14
|
+
* triggers are considered; otherwise descriptors across all workflows are merged
|
|
15
|
+
* (best-effort fast-fail — the orchestrator re-validates against the matched
|
|
16
|
+
* workflow authoritatively). Returns undefined when no dispatch inputs declared.
|
|
17
|
+
*/
|
|
18
|
+
export declare function lookupDispatchInputsDescriptor(inlineLockFile: string | undefined, workflowName: string | undefined): InputsDescriptorMap | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Validate raw `--input KEY=VALUE` pairs against the (optional) lock descriptor
|
|
21
|
+
* and return the raw operator pairs verbatim. The CLI fast-fails on malformed /
|
|
22
|
+
* invalid input for UX, but forwards the **raw** strings — the orchestrator is
|
|
23
|
+
* authoritative and applies coercion + defaults exactly once.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildDispatchInputs(pairs: string[], descriptor: InputsDescriptorMap | undefined): Record<string, string>;
|
|
3
26
|
/**
|
|
4
27
|
* Run a workflow locally using the local executor.
|
|
5
28
|
* Thin wrapper that delegates to executeLocal from local-executor.
|