@oscharko-dev/keiko-cli 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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +103 -0
- package/dist/doctor.d.ts +24 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +108 -0
- package/dist/evaluate.d.ts +8 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +270 -0
- package/dist/evidence.d.ts +9 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +129 -0
- package/dist/gateway-config.d.ts +12 -0
- package/dist/gateway-config.d.ts.map +1 -0
- package/dist/gateway-config.js +19 -0
- package/dist/gen-tests.d.ts +8 -0
- package/dist/gen-tests.d.ts.map +1 -0
- package/dist/gen-tests.js +216 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/init.d.ts +9 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +122 -0
- package/dist/install-layout.d.ts +19 -0
- package/dist/install-layout.d.ts.map +1 -0
- package/dist/install-layout.js +76 -0
- package/dist/investigate.d.ts +9 -0
- package/dist/investigate.d.ts.map +1 -0
- package/dist/investigate.js +249 -0
- package/dist/launcher-paths.d.ts +4 -0
- package/dist/launcher-paths.d.ts.map +1 -0
- package/dist/launcher-paths.js +69 -0
- package/dist/launcher-platforms.d.ts +25 -0
- package/dist/launcher-platforms.d.ts.map +1 -0
- package/dist/launcher-platforms.js +131 -0
- package/dist/launcher-state.d.ts +25 -0
- package/dist/launcher-state.d.ts.map +1 -0
- package/dist/launcher-state.js +228 -0
- package/dist/launcher.d.ts +21 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +439 -0
- package/dist/lifecycle.d.ts +22 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +425 -0
- package/dist/memory.d.ts +14 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +290 -0
- package/dist/models.d.ts +4 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +62 -0
- package/dist/prompt-enhancer.d.ts +13 -0
- package/dist/prompt-enhancer.d.ts.map +1 -0
- package/dist/prompt-enhancer.js +261 -0
- package/dist/repair.d.ts +10 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +402 -0
- package/dist/run.d.ts +10 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +269 -0
- package/dist/runner.d.ts +7 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +108 -0
- package/dist/state-paths.d.ts +43 -0
- package/dist/state-paths.d.ts.map +1 -0
- package/dist/state-paths.js +396 -0
- package/dist/ui.d.ts +39 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +450 -0
- package/dist/uninstall.d.ts +10 -0
- package/dist/uninstall.d.ts.map +1 -0
- package/dist/uninstall.js +345 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +108 -0
- package/package.json +42 -0
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EnvSource } from "@oscharko-dev/keiko-model-gateway";
|
|
2
|
+
export interface CliIo {
|
|
3
|
+
readonly out: (text: string) => void;
|
|
4
|
+
readonly err: (text: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function runCli(args: readonly string[], io: CliIo, env?: EnvSource): number | Promise<number>;
|
|
7
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAKnE,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAuFD,wBAAgB,MAAM,CACpB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,GAAG,GAAE,SAAc,GAClB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB1B"}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { runModelsCli } from "./models.js";
|
|
2
|
+
import { runAgentCli } from "./run.js";
|
|
3
|
+
import { runContextCli } from "./context.js";
|
|
4
|
+
import { runVerifyCli } from "./verify.js";
|
|
5
|
+
import { runGenTestsCli } from "./gen-tests.js";
|
|
6
|
+
import { runInvestigateCli } from "./investigate.js";
|
|
7
|
+
import { runEvidenceCli } from "./evidence.js";
|
|
8
|
+
import { runEvaluateCli } from "./evaluate.js";
|
|
9
|
+
import { runPromptEnhancerCli } from "./prompt-enhancer.js";
|
|
10
|
+
import { runMemoryCli } from "./memory.js";
|
|
11
|
+
import { runInitCli } from "./init.js";
|
|
12
|
+
import { runLifecycleCli } from "./lifecycle.js";
|
|
13
|
+
import { runUiCli } from "./ui.js";
|
|
14
|
+
import { runLauncherCli } from "./launcher.js";
|
|
15
|
+
import { runUninstallCli } from "./uninstall.js";
|
|
16
|
+
import { runRepairCli } from "./repair.js";
|
|
17
|
+
import { emitDoctorWarning, runDoctorCli } from "./doctor.js";
|
|
18
|
+
import { SDK_VERSION } from "@oscharko-dev/keiko-sdk";
|
|
19
|
+
const HELP_TEXT = `keiko ${SDK_VERSION}
|
|
20
|
+
Enterprise model-agnostic developer-assist coding agent.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
keiko [--help | -h] Print this help and exit.
|
|
24
|
+
keiko [--version | -v] Print the version and exit.
|
|
25
|
+
keiko init [OPTIONS] Add local package.json start/stop scripts.
|
|
26
|
+
keiko doctor Diagnose stale global-vs-local launch paths.
|
|
27
|
+
keiko repair [OPTIONS] Repair a broken local install (offline remediation pass).
|
|
28
|
+
keiko uninstall [OPTIONS] Remove Keiko's runtime artifacts (state, shortcuts, scripts).
|
|
29
|
+
keiko start|stop|status|restart Manage the local Keiko UI process.
|
|
30
|
+
keiko models list List registered model capabilities.
|
|
31
|
+
keiko models validate Validate gateway configuration.
|
|
32
|
+
keiko run <task> Run a bounded dry-run task through the agent harness.
|
|
33
|
+
keiko context [OPTIONS] Print a redacted workspace context summary (dry-run).
|
|
34
|
+
keiko verify [OPTIONS] Run the project's gates and print a redacted evidence summary.
|
|
35
|
+
keiko gen-tests [OPTIONS] Generate a reviewable unit-test patch (dry-run by default).
|
|
36
|
+
keiko investigate [OPTIONS] Investigate a bug and propose a fix + regression test (dry-run by default).
|
|
37
|
+
keiko evidence <list|show> Inspect redacted evidence manifests written by \`keiko run\`.
|
|
38
|
+
keiko evaluate [OPTIONS] Run the evaluation harness (offline by default; --live for live model).
|
|
39
|
+
keiko prompt-enhancer [OPTIONS] Enhance a raw prompt into a governed, reviewable Enhanced Prompt.
|
|
40
|
+
keiko memory <maintain|stats> Run a memory maintenance pass or print vault stats (#204).
|
|
41
|
+
keiko ui [OPTIONS] Launch the local UI on 127.0.0.1 and print its URL.
|
|
42
|
+
keiko launcher <install|remove|status> [OPTIONS]
|
|
43
|
+
Manage a user-local OS shortcut for \`keiko start --open\`.
|
|
44
|
+
|
|
45
|
+
Exit codes:
|
|
46
|
+
0 Success
|
|
47
|
+
1 Runtime error
|
|
48
|
+
2 Usage error
|
|
49
|
+
`;
|
|
50
|
+
const COMMAND_HANDLERS = {
|
|
51
|
+
models: runModelsCli,
|
|
52
|
+
run: runAgentCli,
|
|
53
|
+
context: (rest, io) => runContextCli(rest, io),
|
|
54
|
+
verify: (rest, io) => runVerifyCli(rest, io),
|
|
55
|
+
"gen-tests": runGenTestsCli,
|
|
56
|
+
investigate: runInvestigateCli,
|
|
57
|
+
evidence: (rest, io, env) => runEvidenceCli(rest, io, { env }),
|
|
58
|
+
evaluate: (rest, io, env) => runEvaluateCli(rest, io, env, {}),
|
|
59
|
+
"prompt-enhancer": (rest, io, env) => runPromptEnhancerCli(rest, io, env, {}),
|
|
60
|
+
memory: (rest, io, env) => runMemoryCli(rest, io, env),
|
|
61
|
+
init: runInitCli,
|
|
62
|
+
doctor: runDoctorCli,
|
|
63
|
+
repair: runRepairCli,
|
|
64
|
+
uninstall: runUninstallCli,
|
|
65
|
+
start: (rest, io, env) => runLifecycleCli("start", rest, io, env),
|
|
66
|
+
stop: (rest, io, env) => runLifecycleCli("stop", rest, io, env),
|
|
67
|
+
status: (rest, io, env) => runLifecycleCli("status", rest, io, env),
|
|
68
|
+
restart: (rest, io, env) => runLifecycleCli("restart", rest, io, env),
|
|
69
|
+
ui: runUiCli,
|
|
70
|
+
launcher: runLauncherCli,
|
|
71
|
+
};
|
|
72
|
+
// Dispatches named subcommands; returns undefined when the name is not recognised.
|
|
73
|
+
function dispatchCommand(name, rest, io, env) {
|
|
74
|
+
return COMMAND_HANDLERS[name]?.(rest, io, env);
|
|
75
|
+
}
|
|
76
|
+
function handleMetaCommand(first, io) {
|
|
77
|
+
if (first === undefined || first === "--help" || first === "-h") {
|
|
78
|
+
io.out(HELP_TEXT);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
if (first === "--version" || first === "-v") {
|
|
82
|
+
io.out(`keiko ${SDK_VERSION}\n`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
// Returns a number for synchronous commands; the async `run` command returns a Promise.
|
|
88
|
+
// The process shim in index.ts awaits the union before calling process.exit.
|
|
89
|
+
export function runCli(args, io, env = {}) {
|
|
90
|
+
const first = args[0];
|
|
91
|
+
const meta = handleMetaCommand(first, io);
|
|
92
|
+
if (meta !== undefined)
|
|
93
|
+
return meta;
|
|
94
|
+
if (first === undefined) {
|
|
95
|
+
io.err("keiko: internal error while dispatching command.\n");
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
if (first === "start" || first === "ui") {
|
|
99
|
+
emitDoctorWarning(io);
|
|
100
|
+
}
|
|
101
|
+
const result = dispatchCommand(first, args.slice(1), io, env);
|
|
102
|
+
if (result !== undefined) {
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
io.err(`keiko: unknown ${first.startsWith("-") ? "option" : "command"}: ${first}\n`);
|
|
106
|
+
io.err("Run `keiko --help` for usage.\n");
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EnvSource } from "@oscharko-dev/keiko-model-gateway";
|
|
2
|
+
export declare const DEFAULT_STATE_DIR_NAME = ".keiko";
|
|
3
|
+
export declare const KEIKO_STATE_FILES: readonly ["ui.pid", "ui.log", "launcher-state.json"];
|
|
4
|
+
export declare const LAUNCHER_STATE_TMP_PREFIX = ".launcher-state-";
|
|
5
|
+
export declare function resolveStateDir(cwd: string, env: EnvSource, stateDirArg?: string): string;
|
|
6
|
+
export declare function readPidFile(path: string): number | undefined;
|
|
7
|
+
export declare function defaultIsProcessAlive(pid: number): boolean;
|
|
8
|
+
export type PidState = "absent" | "stale" | "running";
|
|
9
|
+
export interface PidClassification {
|
|
10
|
+
readonly state: PidState;
|
|
11
|
+
readonly pid: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function classifyPid(pidFilePath: string, isAlive: (pid: number) => boolean): PidClassification;
|
|
14
|
+
export declare const RUNTIME_STATE_DIR_MODE = 448;
|
|
15
|
+
export declare const RUNTIME_STATE_FILE_MODE = 384;
|
|
16
|
+
export type RuntimeStateCategory = "lifecycle" | "launcher" | "ui-database" | "gateway-config" | "credential-vault" | "memory-vault" | "local-knowledge" | "evidence" | "quality-intelligence";
|
|
17
|
+
export interface RuntimeStateNode {
|
|
18
|
+
readonly relPath: string;
|
|
19
|
+
readonly absPath: string;
|
|
20
|
+
readonly category: RuntimeStateCategory;
|
|
21
|
+
}
|
|
22
|
+
export interface RetainedNode {
|
|
23
|
+
readonly relPath: string;
|
|
24
|
+
readonly absPath: string;
|
|
25
|
+
readonly reason: "unknown" | "symlink" | "hardlink";
|
|
26
|
+
readonly owned: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface RuntimeStateScan {
|
|
29
|
+
readonly root: StateRootInspection;
|
|
30
|
+
readonly present: boolean;
|
|
31
|
+
readonly files: readonly RuntimeStateNode[];
|
|
32
|
+
readonly directories: readonly RuntimeStateNode[];
|
|
33
|
+
readonly retained: readonly RetainedNode[];
|
|
34
|
+
}
|
|
35
|
+
export type StateRootStatus = "absent" | "directory" | "symlink" | "not-directory";
|
|
36
|
+
export interface StateRootInspection {
|
|
37
|
+
readonly status: StateRootStatus;
|
|
38
|
+
readonly absPath: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function inspectStateRoot(stateDir: string): StateRootInspection;
|
|
41
|
+
export declare function scanRuntimeState(stateDir: string): RuntimeStateScan;
|
|
42
|
+
export declare function isInsidePath(ancestor: string, descendant: string): boolean;
|
|
43
|
+
//# sourceMappingURL=state-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-paths.d.ts","sourceRoot":"","sources":["../src/state-paths.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAI/C,eAAO,MAAM,iBAAiB,sDAAuD,CAAC;AAItF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAMzF;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK5D;AAMD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO1D;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAID,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAChC,iBAAiB,CAInB;AAsCD,eAAO,MAAM,sBAAsB,MAAQ,CAAC;AAC5C,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAmC7C,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,UAAU,GACV,aAAa,GACb,gBAAgB,GAChB,kBAAkB,GAClB,cAAc,GACd,iBAAiB,GACjB,UAAU,GACV,sBAAsB,CAAC;AAyM3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;CACzC;AAOD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACpD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,eAAe,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAWtE;AAsFD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAWnE;AAID,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE1E"}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// Shared resolution of Keiko's runtime state directory and the set of files Keiko
|
|
2
|
+
// itself writes there. Consumed by `keiko uninstall` (to reverse them) and
|
|
3
|
+
// `keiko repair` (to detect and clean stale ones). Resolution mirrors `lifecycle.ts`
|
|
4
|
+
// (`--state-dir` > KEIKO_STATE_DIR > <cwd>/.keiko) so `start`, `uninstall`, and
|
|
5
|
+
// `repair` all operate on the same directory.
|
|
6
|
+
//
|
|
7
|
+
// DELETION SAFETY CONTRACT: callers remove ONLY the explicitly enumerated state
|
|
8
|
+
// artifacts below (the fixed `KEIKO_STATE_FILES` plus the launcher's
|
|
9
|
+
// `.launcher-state-*` mkdtemp dirs) and then rmdir the state directory when it is
|
|
10
|
+
// empty. Nothing here recursively deletes an arbitrary directory, so a mis-pointed
|
|
11
|
+
// `--state-dir`/KEIKO_STATE_DIR can never escalate into data loss outside Keiko's
|
|
12
|
+
// own files.
|
|
13
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
|
|
14
|
+
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
15
|
+
import { assertValidRunId } from "@oscharko-dev/keiko-security";
|
|
16
|
+
export const DEFAULT_STATE_DIR_NAME = ".keiko";
|
|
17
|
+
// Runtime files Keiko writes under the state dir. `ui.pid`/`ui.log` come from
|
|
18
|
+
// `lifecycle.ts`; `launcher-state.json` from `launcher-state.ts`.
|
|
19
|
+
export const KEIKO_STATE_FILES = ["ui.pid", "ui.log", "launcher-state.json"];
|
|
20
|
+
// `launcher-state.ts` writes ephemeral mkdtemp dirs with this prefix during atomic
|
|
21
|
+
// state saves; a crash can leave one behind, so uninstall/repair sweep them by prefix.
|
|
22
|
+
export const LAUNCHER_STATE_TMP_PREFIX = ".launcher-state-";
|
|
23
|
+
// Resolves the state directory the same way `keiko start` does. An explicit
|
|
24
|
+
// `--state-dir` argument wins, then `KEIKO_STATE_DIR`, then `<cwd>/.keiko`. Relative
|
|
25
|
+
// values resolve against `cwd`.
|
|
26
|
+
export function resolveStateDir(cwd, env, stateDirArg) {
|
|
27
|
+
const value = stateDirArg !== undefined && stateDirArg.length > 0
|
|
28
|
+
? stateDirArg
|
|
29
|
+
: (env.KEIKO_STATE_DIR ?? DEFAULT_STATE_DIR_NAME);
|
|
30
|
+
return isAbsolute(value) ? value : resolve(cwd, value);
|
|
31
|
+
}
|
|
32
|
+
// Reads a pid file written by `lifecycle.ts`. Returns the integer pid, or undefined
|
|
33
|
+
// when the file is absent or does not contain a positive integer.
|
|
34
|
+
export function readPidFile(path) {
|
|
35
|
+
if (!existsSync(path))
|
|
36
|
+
return undefined;
|
|
37
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
38
|
+
if (!/^[1-9]\d*$/.test(raw))
|
|
39
|
+
return undefined;
|
|
40
|
+
return Number(raw);
|
|
41
|
+
}
|
|
42
|
+
// Liveness probe identical in semantics to the lifecycle handler: a successful
|
|
43
|
+
// signal-0 means alive; an EPERM error means the process exists but is owned by
|
|
44
|
+
// another user (still alive); any other error means it is gone. Kept local to this
|
|
45
|
+
// leaf module so uninstall/repair do not pull in lifecycle's heavy spawn/net imports.
|
|
46
|
+
export function defaultIsProcessAlive(pid) {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EPERM";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Classifies the UI pid file: `absent` (missing or malformed), `stale` (a pid is
|
|
56
|
+
// recorded but the process is gone), or `running` (recorded and alive).
|
|
57
|
+
export function classifyPid(pidFilePath, isAlive) {
|
|
58
|
+
const pid = readPidFile(pidFilePath);
|
|
59
|
+
if (pid === undefined)
|
|
60
|
+
return { state: "absent", pid: undefined };
|
|
61
|
+
return isAlive(pid) ? { state: "running", pid } : { state: "stale", pid };
|
|
62
|
+
}
|
|
63
|
+
// ── Runtime-state confidentiality manifest (Issue #1321) ──────────────────────
|
|
64
|
+
//
|
|
65
|
+
// `keiko repair` and `keiko uninstall --state` must reason about EVERY Keiko-owned
|
|
66
|
+
// sensitive artifact under the state directory, not only the three lifecycle/launcher
|
|
67
|
+
// files above. Rather than a blanket recursive chmod/delete of arbitrary content under
|
|
68
|
+
// `.keiko` (which could touch a customer file that merely happens to live there), this
|
|
69
|
+
// manifest is an explicit allowlist describing the on-disk layout each Keiko store
|
|
70
|
+
// writes:
|
|
71
|
+
//
|
|
72
|
+
// <stateDir>/
|
|
73
|
+
// ui.pid, ui.log lifecycle.ts
|
|
74
|
+
// launcher-state.json launcher-state.ts
|
|
75
|
+
// .launcher-state-*/ launcher-state.ts atomic-save temp dirs
|
|
76
|
+
// keiko-ui.db[-wal|-shm|.corrupt.*] keiko-server store/paths.ts (UI_DB_FILENAME)
|
|
77
|
+
// keiko.config.json keiko-server deps.ts (model-gateway config)
|
|
78
|
+
// credentials/*.vault, *.key keiko-server credentialVault.ts (sealed apiKeys + keyfile)
|
|
79
|
+
// memory/keiko-memory.db[-wal|-shm|.*] keiko-memory-vault paths.ts (MEMORY_DB_FILENAME)
|
|
80
|
+
// local-knowledge/<ns>/capsules.db[…] keiko-local-knowledge store-paths.ts
|
|
81
|
+
// evidence/<runId>.json|.lock|… keiko-evidence store.ts
|
|
82
|
+
// evidence/figma/*.vault, *.key keiko-server figmaTokenStore.ts (sealed Figma PAT + keyfile)
|
|
83
|
+
// evidence/qi/<runId>.qi.json|… keiko-evidence qualityIntelligence/*
|
|
84
|
+
// evidence/qi/figma-snapshots/<runId>/… keiko-evidence figmaSnapshot side files
|
|
85
|
+
//
|
|
86
|
+
// The sealed `*.vault` ciphertext and its `*.key` keyfile (the env/keychain-tier fallback,
|
|
87
|
+
// ADR-0046) are the most confidentiality-critical artifacts here, so they are first-class
|
|
88
|
+
// manifest entries rather than incidental files.
|
|
89
|
+
//
|
|
90
|
+
// The filenames are duplicated here ON PURPOSE: keiko-cli is a leaf consumer and must
|
|
91
|
+
// not take a package-graph edge onto keiko-local-knowledge / keiko-evidence internals
|
|
92
|
+
// just to learn a constant. Each descriptor cites its source of truth so drift stays
|
|
93
|
+
// auditable. The walker classifies entries WITHOUT mutating anything — `repair` chmods
|
|
94
|
+
// and `uninstall` unlinks, but only entries this allowlist recognizes as Keiko-owned;
|
|
95
|
+
// every unrecognized entry is reported as `retained` and left in place.
|
|
96
|
+
// POSIX modes Keiko enforces on its own runtime state: private directories and private
|
|
97
|
+
// files. NTFS has no equivalent; callers no-op on win32 and emit a diagnostic instead.
|
|
98
|
+
export const RUNTIME_STATE_DIR_MODE = 0o700;
|
|
99
|
+
export const RUNTIME_STATE_FILE_MODE = 0o600;
|
|
100
|
+
const UI_DB_FILENAME = "keiko-ui.db"; // keiko-server/src/store/paths.ts
|
|
101
|
+
const MEMORY_DB_FILENAME = "keiko-memory.db"; // keiko-memory-vault/src/paths.ts
|
|
102
|
+
const CAPSULES_DB_FILENAME = "capsules.db"; // keiko-local-knowledge/src/store-paths.ts
|
|
103
|
+
const GATEWAY_CONFIG_FILENAME = "keiko.config.json"; // keiko-server/src/deps.ts
|
|
104
|
+
const CREDENTIALS_SUBDIR = "credentials"; // keiko-server/src/credentialVault.ts (CREDENTIALS_SUBDIR)
|
|
105
|
+
const MEMORY_SUBDIR = "memory"; // keiko-memory-vault/src/paths.ts (MEMORY_DIR_NAME)
|
|
106
|
+
const LOCAL_KNOWLEDGE_SUBDIR = "local-knowledge"; // keiko-local-knowledge store-paths.ts (SUBSYSTEM_DIR)
|
|
107
|
+
const EVIDENCE_SUBDIR = "evidence"; // keiko-evidence/src/store.ts (DEFAULT_EVIDENCE_DIR)
|
|
108
|
+
const FIGMA_VAULT_SUBDIR = "figma"; // keiko-server figmaTokenStore.ts (Figma PAT vault dir, under evidence)
|
|
109
|
+
const QI_SUBDIR = "qi"; // keiko-evidence/src/qualityIntelligence/store.ts (QI_SUBDIR)
|
|
110
|
+
const FIGMA_SNAPSHOTS_SUBDIR = "figma-snapshots"; // keiko-evidence figmaSnapshot/store.ts (SIDE_FILE_SUBDIR)
|
|
111
|
+
const PROVIDER_CREDENTIALS_VAULT = "provider-credentials.vault"; // keiko-server/src/credentialVault.ts
|
|
112
|
+
const PROVIDER_CREDENTIALS_KEYFILE = "provider-credentials-vault.key"; // keiko-server/src/credentialVault.ts
|
|
113
|
+
const FIGMA_TOKEN_VAULT = "figma-token.vault"; // keiko-server figmaTokenStore.ts
|
|
114
|
+
const FIGMA_TOKEN_KEYFILE = "figma-vault.key"; // keiko-server figmaTokenStore.ts
|
|
115
|
+
const EVIDENCE_MANIFEST_SUFFIX = ".json"; // keiko-evidence/src/store.ts
|
|
116
|
+
const EVIDENCE_LOCK_SUFFIX = ".lock"; // keiko-evidence/src/store.ts
|
|
117
|
+
const PRODUCER_TEMP_SUFFIX = ".tmp"; // atomic-save temp files (`<target>.<random>.tmp`)
|
|
118
|
+
const PRODUCER_TEMP_TOKEN = /^[A-Za-z0-9._-]{8,}$/u;
|
|
119
|
+
const SECRET_VAULT_TEMP_FILE = /^\.secret-vault\.[1-9][0-9]*\.[0-9a-f]{16}\.tmp$/u;
|
|
120
|
+
const QI_OWNED_SUFFIXES = [
|
|
121
|
+
".qi.json", // keiko-evidence/src/qualityIntelligence/store.ts
|
|
122
|
+
".candidates.json", // keiko-evidence/src/qualityIntelligence/candidatesArtifact.ts
|
|
123
|
+
".review.json", // keiko-server/src/qualityIntelligence/reviewStore.ts
|
|
124
|
+
".figma-codegen.json", // keiko-server/src/qualityIntelligence/retentionRoutes.ts
|
|
125
|
+
".figma-audit.json", // keiko-server/src/qualityIntelligence/retentionRoutes.ts
|
|
126
|
+
".figma-consent.json", // keiko-server/src/qualityIntelligence/retentionRoutes.ts
|
|
127
|
+
".figma-snapshot.json", // keiko-evidence/src/qualityIntelligence/figmaSnapshot/store.ts
|
|
128
|
+
".figma-snapshot.management.json", // keiko-evidence/src/qualityIntelligence/figmaSnapshot/store.ts
|
|
129
|
+
];
|
|
130
|
+
// A SQLite store file plus its exact WAL/SHM sidecars and `.corrupt.<ts>` quarantine copies
|
|
131
|
+
// — and ONLY those. Matching is exact-name or a known dotted suffix, never a bare `${base}-`
|
|
132
|
+
// or `${base}.` prefix, so a customer file such as `keiko-ui.db.backup` or `keiko-ui.db-old`
|
|
133
|
+
// is left as an unrecognized entry and is never chmod-ed or deleted.
|
|
134
|
+
// `<base>` the live database
|
|
135
|
+
// `<base>-wal`, `<base>-shm` WAL/SHM sidecars
|
|
136
|
+
// `<base>-wal.corrupt.<ts>`, `-shm.corrupt` quarantined sidecars (keiko-memory-vault db.ts)
|
|
137
|
+
// `<base>.corrupt.<ts>` quarantined database (keiko-server store/db.ts)
|
|
138
|
+
function isSqliteFamily(base, name) {
|
|
139
|
+
return (name === base ||
|
|
140
|
+
name === `${base}-wal` ||
|
|
141
|
+
name === `${base}-shm` ||
|
|
142
|
+
name.startsWith(`${base}-wal.corrupt.`) ||
|
|
143
|
+
name.startsWith(`${base}-shm.corrupt.`) ||
|
|
144
|
+
name.startsWith(`${base}.corrupt.`));
|
|
145
|
+
}
|
|
146
|
+
// Evidence and Quality-Intelligence records are run-id-derived artifacts, never arbitrary
|
|
147
|
+
// suffix matches. Matching by the producer suffix vocabulary prevents a customer lookalike
|
|
148
|
+
// such as `manual export.json` or `backup.key` from being chmod-ed or deleted.
|
|
149
|
+
function isValidRunId(value) {
|
|
150
|
+
if (value.length === 0)
|
|
151
|
+
return false;
|
|
152
|
+
try {
|
|
153
|
+
assertValidRunId(value);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function hasRunIdSuffix(name, suffix) {
|
|
161
|
+
if (!name.endsWith(suffix))
|
|
162
|
+
return false;
|
|
163
|
+
return isValidRunId(name.slice(0, -suffix.length));
|
|
164
|
+
}
|
|
165
|
+
function hasRunIdProducerTempSuffix(name, suffix) {
|
|
166
|
+
if (!name.endsWith(PRODUCER_TEMP_SUFFIX))
|
|
167
|
+
return false;
|
|
168
|
+
const withoutTmp = name.slice(0, -PRODUCER_TEMP_SUFFIX.length);
|
|
169
|
+
const marker = `${suffix}.`;
|
|
170
|
+
const markerIndex = withoutTmp.lastIndexOf(marker);
|
|
171
|
+
if (markerIndex < 0)
|
|
172
|
+
return false;
|
|
173
|
+
const runId = withoutTmp.slice(0, markerIndex);
|
|
174
|
+
const token = withoutTmp.slice(runId.length + marker.length);
|
|
175
|
+
return PRODUCER_TEMP_TOKEN.test(token) && isValidRunId(runId);
|
|
176
|
+
}
|
|
177
|
+
function hasRunIdArtifactSuffix(name, suffix) {
|
|
178
|
+
return hasRunIdSuffix(name, suffix) || hasRunIdProducerTempSuffix(name, suffix);
|
|
179
|
+
}
|
|
180
|
+
function isEvidenceRecord(name) {
|
|
181
|
+
return (hasRunIdArtifactSuffix(name, EVIDENCE_MANIFEST_SUFFIX) ||
|
|
182
|
+
hasRunIdArtifactSuffix(name, EVIDENCE_LOCK_SUFFIX));
|
|
183
|
+
}
|
|
184
|
+
function isQiRecord(name) {
|
|
185
|
+
return QI_OWNED_SUFFIXES.some((suffix) => hasRunIdArtifactSuffix(name, suffix));
|
|
186
|
+
}
|
|
187
|
+
// Sealed credential material: the AES-256-GCM `*.vault` ciphertext and the `*.key` keyfile
|
|
188
|
+
// (the env/keychain-tier fallback). Both must stay owner-only (ADR-0046).
|
|
189
|
+
function isProviderCredentialVaultFile(name) {
|
|
190
|
+
return (name === PROVIDER_CREDENTIALS_VAULT ||
|
|
191
|
+
name === PROVIDER_CREDENTIALS_KEYFILE ||
|
|
192
|
+
SECRET_VAULT_TEMP_FILE.test(name));
|
|
193
|
+
}
|
|
194
|
+
function isFigmaVaultFile(name) {
|
|
195
|
+
return name === FIGMA_TOKEN_VAULT || name === FIGMA_TOKEN_KEYFILE;
|
|
196
|
+
}
|
|
197
|
+
function dirHasSqliteFamilyArtifact(absDir, base) {
|
|
198
|
+
try {
|
|
199
|
+
return readdirSync(absDir).some((name) => isSqliteFamily(base, name));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const NO_CHILD = () => undefined;
|
|
206
|
+
const OWNS_NO_FILE = () => false;
|
|
207
|
+
const figmaSnapshotRunSubtree = {
|
|
208
|
+
category: "quality-intelligence",
|
|
209
|
+
whole: true,
|
|
210
|
+
ownsFile: OWNS_NO_FILE,
|
|
211
|
+
childSubtree: NO_CHILD,
|
|
212
|
+
};
|
|
213
|
+
const figmaSnapshotsSubtree = {
|
|
214
|
+
category: "quality-intelligence",
|
|
215
|
+
whole: false,
|
|
216
|
+
ownsFile: OWNS_NO_FILE,
|
|
217
|
+
childSubtree: (name) => (isValidRunId(name) ? figmaSnapshotRunSubtree : undefined),
|
|
218
|
+
};
|
|
219
|
+
const qiSubtree = {
|
|
220
|
+
category: "quality-intelligence",
|
|
221
|
+
whole: false,
|
|
222
|
+
ownsFile: isQiRecord,
|
|
223
|
+
childSubtree: (name) => (name === FIGMA_SNAPSHOTS_SUBDIR ? figmaSnapshotsSubtree : undefined),
|
|
224
|
+
};
|
|
225
|
+
// `evidence/figma/` holds the sealed Figma PAT vault + keyfile (figmaTokenStore.ts).
|
|
226
|
+
const figmaVaultSubtree = {
|
|
227
|
+
category: "credential-vault",
|
|
228
|
+
whole: false,
|
|
229
|
+
ownsFile: isFigmaVaultFile,
|
|
230
|
+
childSubtree: NO_CHILD,
|
|
231
|
+
};
|
|
232
|
+
function evidenceChildSubtree(name) {
|
|
233
|
+
if (name === QI_SUBDIR)
|
|
234
|
+
return qiSubtree;
|
|
235
|
+
if (name === FIGMA_VAULT_SUBDIR)
|
|
236
|
+
return figmaVaultSubtree;
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const evidenceSubtree = {
|
|
240
|
+
category: "evidence",
|
|
241
|
+
whole: false,
|
|
242
|
+
ownsFile: isEvidenceRecord,
|
|
243
|
+
childSubtree: evidenceChildSubtree,
|
|
244
|
+
};
|
|
245
|
+
// `credentials/` holds the sealed provider-credential vault + keyfile (credentialVault.ts).
|
|
246
|
+
const credentialsSubtree = {
|
|
247
|
+
category: "credential-vault",
|
|
248
|
+
whole: false,
|
|
249
|
+
ownsFile: isProviderCredentialVaultFile,
|
|
250
|
+
childSubtree: NO_CHILD,
|
|
251
|
+
};
|
|
252
|
+
const memorySubtree = {
|
|
253
|
+
category: "memory-vault",
|
|
254
|
+
whole: false,
|
|
255
|
+
ownsFile: (name) => isSqliteFamily(MEMORY_DB_FILENAME, name),
|
|
256
|
+
childSubtree: NO_CHILD,
|
|
257
|
+
};
|
|
258
|
+
const knowledgeNamespaceSubtree = {
|
|
259
|
+
category: "local-knowledge",
|
|
260
|
+
whole: false,
|
|
261
|
+
ownsFile: (name) => isSqliteFamily(CAPSULES_DB_FILENAME, name),
|
|
262
|
+
childSubtree: NO_CHILD,
|
|
263
|
+
};
|
|
264
|
+
// `local-knowledge/` holds one directory per namespace; each namespace directory is a
|
|
265
|
+
// Keiko-owned store root (`local-knowledge/<ns>/capsules.db`).
|
|
266
|
+
const localKnowledgeSubtree = {
|
|
267
|
+
category: "local-knowledge",
|
|
268
|
+
whole: false,
|
|
269
|
+
ownsFile: OWNS_NO_FILE,
|
|
270
|
+
childSubtree: (_name, absPath) => dirHasSqliteFamilyArtifact(absPath, CAPSULES_DB_FILENAME) ? knowledgeNamespaceSubtree : undefined,
|
|
271
|
+
};
|
|
272
|
+
const launcherTmpSubtree = {
|
|
273
|
+
category: "launcher",
|
|
274
|
+
whole: true,
|
|
275
|
+
ownsFile: OWNS_NO_FILE,
|
|
276
|
+
childSubtree: NO_CHILD,
|
|
277
|
+
};
|
|
278
|
+
function topLevelFileCategory(name) {
|
|
279
|
+
if (name === "ui.pid" || name === "ui.log")
|
|
280
|
+
return "lifecycle";
|
|
281
|
+
if (name === "launcher-state.json")
|
|
282
|
+
return "launcher";
|
|
283
|
+
if (name === GATEWAY_CONFIG_FILENAME)
|
|
284
|
+
return "gateway-config";
|
|
285
|
+
if (isSqliteFamily(UI_DB_FILENAME, name))
|
|
286
|
+
return "ui-database";
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
function topLevelChildSubtree(name) {
|
|
290
|
+
if (name === CREDENTIALS_SUBDIR)
|
|
291
|
+
return credentialsSubtree;
|
|
292
|
+
if (name === MEMORY_SUBDIR)
|
|
293
|
+
return memorySubtree;
|
|
294
|
+
if (name === LOCAL_KNOWLEDGE_SUBDIR)
|
|
295
|
+
return localKnowledgeSubtree;
|
|
296
|
+
if (name === EVIDENCE_SUBDIR)
|
|
297
|
+
return evidenceSubtree;
|
|
298
|
+
if (name.startsWith(LAUNCHER_STATE_TMP_PREFIX))
|
|
299
|
+
return launcherTmpSubtree;
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
export function inspectStateRoot(stateDir) {
|
|
303
|
+
try {
|
|
304
|
+
const stat = lstatSync(stateDir);
|
|
305
|
+
if (stat.isSymbolicLink())
|
|
306
|
+
return { status: "symlink", absPath: stateDir };
|
|
307
|
+
if (stat.isDirectory())
|
|
308
|
+
return { status: "directory", absPath: stateDir };
|
|
309
|
+
return { status: "not-directory", absPath: stateDir };
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
|
|
313
|
+
return { status: "absent", absPath: stateDir };
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function childRelPath(relDir, name) {
|
|
318
|
+
return relDir === "" ? name : `${relDir}/${name}`;
|
|
319
|
+
}
|
|
320
|
+
function ownedFileCategory(scope, name) {
|
|
321
|
+
if (scope === "root")
|
|
322
|
+
return topLevelFileCategory(name);
|
|
323
|
+
if (scope.whole)
|
|
324
|
+
return scope.category;
|
|
325
|
+
return scope.ownsFile(name) ? scope.category : undefined;
|
|
326
|
+
}
|
|
327
|
+
function ownedChildSubtree(scope, name, absPath) {
|
|
328
|
+
if (scope === "root")
|
|
329
|
+
return topLevelChildSubtree(name);
|
|
330
|
+
if (scope.whole)
|
|
331
|
+
return scope; // a whole subtree owns all of its descendant directories
|
|
332
|
+
return scope.childSubtree(name, absPath);
|
|
333
|
+
}
|
|
334
|
+
function classifyEntry(absDir, relDir, name, scope, acc) {
|
|
335
|
+
const absPath = join(absDir, name);
|
|
336
|
+
const relPath = childRelPath(relDir, name);
|
|
337
|
+
const stat = lstatSync(absPath);
|
|
338
|
+
if (stat.isSymbolicLink()) {
|
|
339
|
+
// Never follow a symlink in any position: chmod-through and delete-through a symlink can
|
|
340
|
+
// escape `.keiko`. Flag it when it occupies a slot the manifest would otherwise own.
|
|
341
|
+
const owned = ownedFileCategory(scope, name) !== undefined ||
|
|
342
|
+
ownedChildSubtree(scope, name, absPath) !== undefined;
|
|
343
|
+
acc.retained.push({ relPath, absPath, reason: "symlink", owned });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (stat.isDirectory()) {
|
|
347
|
+
const child = ownedChildSubtree(scope, name, absPath);
|
|
348
|
+
if (child === undefined) {
|
|
349
|
+
acc.retained.push({ relPath, absPath, reason: "unknown", owned: false });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
acc.directories.push({ relPath, absPath, category: child.category });
|
|
353
|
+
walkOwnedDir(absPath, relPath, child, acc);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (stat.isFile()) {
|
|
357
|
+
const category = ownedFileCategory(scope, name);
|
|
358
|
+
if (category === undefined) {
|
|
359
|
+
acc.retained.push({ relPath, absPath, reason: "unknown", owned: false });
|
|
360
|
+
}
|
|
361
|
+
else if (stat.nlink > 1) {
|
|
362
|
+
acc.retained.push({ relPath, absPath, reason: "hardlink", owned: true });
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
acc.files.push({ relPath, absPath, category });
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
// Sockets, FIFOs, devices: not a Keiko artifact — retain untouched.
|
|
370
|
+
acc.retained.push({ relPath, absPath, reason: "unknown", owned: false });
|
|
371
|
+
}
|
|
372
|
+
function walkOwnedDir(absDir, relDir, scope, acc) {
|
|
373
|
+
for (const name of readdirSync(absDir)) {
|
|
374
|
+
classifyEntry(absDir, relDir, name, scope, acc);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Classifies every entry under `stateDir` against the runtime-state manifest. Pure
|
|
378
|
+
// read-only traversal (lstat-based, never follows symlinks); the directories list is
|
|
379
|
+
// ordered shallowest-first so callers can reverse it to remove leaves before parents.
|
|
380
|
+
export function scanRuntimeState(stateDir) {
|
|
381
|
+
const root = inspectStateRoot(stateDir);
|
|
382
|
+
if (root.status === "absent") {
|
|
383
|
+
return { root, present: false, files: [], directories: [], retained: [] };
|
|
384
|
+
}
|
|
385
|
+
if (root.status !== "directory") {
|
|
386
|
+
return { root, present: true, files: [], directories: [], retained: [] };
|
|
387
|
+
}
|
|
388
|
+
const acc = { files: [], directories: [], retained: [] };
|
|
389
|
+
walkOwnedDir(stateDir, "", "root", acc);
|
|
390
|
+
return { root, present: true, files: acc.files, directories: acc.directories, retained: acc.retained };
|
|
391
|
+
}
|
|
392
|
+
// True when `descendant` lies inside `ancestor` (used to decide whether an owned directory
|
|
393
|
+
// can be removed: it can only be removed when no retained entry survives beneath it).
|
|
394
|
+
export function isInsidePath(ancestor, descendant) {
|
|
395
|
+
return descendant === ancestor || descendant.startsWith(`${ancestor}${sep}`);
|
|
396
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
import { type SpawnOptions, type ChildProcess } from "node:child_process";
|
|
3
|
+
import { type UiHandlerDeps } from "@oscharko-dev/keiko-server";
|
|
4
|
+
import type { EnvSource } from "@oscharko-dev/keiko-model-gateway";
|
|
5
|
+
import type { CliIo } from "./runner.js";
|
|
6
|
+
export interface UiCliArgs {
|
|
7
|
+
readonly port: number;
|
|
8
|
+
readonly evidenceDir: string | undefined;
|
|
9
|
+
readonly config: string | undefined;
|
|
10
|
+
readonly uiDbPath: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
type UiParseResult = UiCliArgs | "help" | null;
|
|
13
|
+
export interface UiCliDeps {
|
|
14
|
+
readonly createServer?: (deps: {
|
|
15
|
+
staticRoot: string;
|
|
16
|
+
csp: string;
|
|
17
|
+
port: number;
|
|
18
|
+
handlerDeps: UiHandlerDeps;
|
|
19
|
+
}) => Server | Promise<Server>;
|
|
20
|
+
readonly staticRoot?: string;
|
|
21
|
+
readonly hashesFile?: string;
|
|
22
|
+
readonly sqliteProbe?: () => boolean;
|
|
23
|
+
readonly spawnFn?: SpawnFn;
|
|
24
|
+
readonly currentExecArgv?: () => readonly string[];
|
|
25
|
+
readonly cwd?: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
interface LiveCspSource {
|
|
28
|
+
readonly csp: () => string;
|
|
29
|
+
readonly dispose: () => void;
|
|
30
|
+
}
|
|
31
|
+
export type SpawnFn = (command: string, args: readonly string[], opts: SpawnOptions) => ChildProcess;
|
|
32
|
+
export declare function parseUiArgs(args: readonly string[]): UiParseResult;
|
|
33
|
+
export declare function applyServerTimeouts(server: Server): void;
|
|
34
|
+
export declare function waitForShutdown(server: Server): Promise<void>;
|
|
35
|
+
export declare function reExecWithSqliteFlag(_env: EnvSource, spawnFn: SpawnFn, cwd: string): Promise<number>;
|
|
36
|
+
export declare function createLiveCspSource(staticRoot: string, hashesFile: string, io: CliIo): Promise<LiveCspSource>;
|
|
37
|
+
export declare function runUiCli(args: readonly string[], io: CliIo, env: EnvSource, deps?: UiCliDeps): Promise<number>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAMxC,OAAO,EAAS,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAQL,KAAK,aAAa,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAmBzC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC;AAa/C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAC7B,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,aAAa,CAAC;KAC5B,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAI7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAG3B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAEnD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAOD,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,YAAY,KACf,YAAY,CAAC;AAuGlB,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAYlE;AAoFD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGxD;AAWD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB7D;AA0BD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAyGD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,KAAK,GACR,OAAO,CAAC,aAAa,CAAC,CAqCxB;AAED,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,SAAS,EACd,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,MAAM,CAAC,CA+BjB"}
|