omkit 0.2.2 → 0.4.1
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/README.md +66 -43
- package/dist/actions/browser.d.ts +28 -0
- package/dist/actions/browser.js +36 -0
- package/dist/actions/command.d.ts +11 -7
- package/dist/actions/command.js +16 -10
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/prompt.js +43 -17
- package/dist/cli/client/channel.d.ts +14 -0
- package/dist/cli/client/channel.js +48 -0
- package/dist/cli/client/check.d.ts +3 -0
- package/dist/cli/client/check.js +32 -0
- package/dist/cli/client/discovery.d.ts +8 -0
- package/dist/cli/client/discovery.js +163 -0
- package/dist/cli/client/index.d.ts +9 -0
- package/dist/cli/client/index.js +11 -0
- package/dist/cli/client/registry.d.ts +29 -0
- package/dist/cli/client/registry.js +1 -0
- package/dist/cli/client/runner.d.ts +15 -0
- package/dist/cli/client/runner.js +61 -0
- package/dist/cli/client/types.d.ts +53 -0
- package/dist/cli/client/types.js +1 -0
- package/dist/cli/commands/check.d.ts +6 -0
- package/dist/cli/commands/check.js +11 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.js +54 -0
- package/dist/cli/commands/ls.d.ts +5 -0
- package/dist/cli/commands/ls.js +25 -0
- package/dist/cli/commands/run.d.ts +10 -0
- package/dist/cli/commands/run.js +12 -0
- package/dist/cli/commands/ui.d.ts +11 -0
- package/dist/cli/commands/ui.js +37 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +110 -0
- package/dist/cli/ui/Spinner.d.ts +10 -0
- package/dist/cli/ui/Spinner.js +20 -0
- package/dist/cli/ui/app.d.ts +12 -0
- package/dist/cli/ui/app.js +95 -0
- package/dist/cli/ui/views/OmList.d.ts +9 -0
- package/dist/cli/ui/views/OmList.js +36 -0
- package/dist/cli/ui/views/RunView.d.ts +14 -0
- package/dist/cli/ui/views/RunView.js +47 -0
- package/dist/core/ActionRun.d.ts +2 -4
- package/dist/core/ActionRun.js +32 -36
- package/dist/core/ExecutionTree.d.ts +1 -0
- package/dist/core/ExecutionTree.js +25 -4
- package/dist/core/interaction.d.ts +63 -0
- package/dist/core/interaction.js +82 -0
- package/dist/core/step.js +2 -7
- package/dist/core/types.d.ts +10 -20
- package/dist/index.d.ts +1 -1
- package/dist/output/LiveRenderer.d.ts +7 -17
- package/dist/output/LiveRenderer.js +8 -82
- package/dist/output/milestones.d.ts +8 -0
- package/dist/output/milestones.js +39 -0
- package/package.json +9 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -5,14 +5,6 @@ import type { Proc } from "../system/proc.ts";
|
|
|
5
5
|
export type Awaitable<T> = T | Promise<T>;
|
|
6
6
|
/** An action that declares no events. */
|
|
7
7
|
export type NoEvents = Record<never, never>;
|
|
8
|
-
/** An activity's terminal outcome: a value on success, an error on failure/cancel. Never thrown. */
|
|
9
|
-
export type Outcome<T> = {
|
|
10
|
-
ok: true;
|
|
11
|
-
value: T;
|
|
12
|
-
} | {
|
|
13
|
-
ok: false;
|
|
14
|
-
error: unknown;
|
|
15
|
-
};
|
|
16
8
|
/** A node's lifecycle state, mirrored into `result.json`. */
|
|
17
9
|
export type NodeStatus = "running" | "ok" | "failed" | "cancelled";
|
|
18
10
|
/**
|
|
@@ -73,8 +65,12 @@ export interface OmContext {
|
|
|
73
65
|
*/
|
|
74
66
|
export interface Activity<Result = unknown, Events extends object = NoEvents, Handle = void> {
|
|
75
67
|
readonly id: string;
|
|
76
|
-
/**
|
|
77
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The terminal outcome: resolves the value on success, **rejects** with the action's error on
|
|
70
|
+
* failure (or `CancelledError` on cancel). Reading it observes the activity — a handled failure,
|
|
71
|
+
* so it won't tear the run down. For fire-and-forget handling, `activity.result.catch(fn)`.
|
|
72
|
+
*/
|
|
73
|
+
readonly result: Promise<Result>;
|
|
78
74
|
/** The attached handle; rejects on failure/cancel. */
|
|
79
75
|
readonly ref: Promise<Handle>;
|
|
80
76
|
/**
|
|
@@ -84,19 +80,13 @@ export interface Activity<Result = unknown, Events extends object = NoEvents, Ha
|
|
|
84
80
|
* after the launching call; calling it once the body has started throws.
|
|
85
81
|
*/
|
|
86
82
|
withCache(...paths: string[]): Activity<Result | undefined, Events, Handle>;
|
|
87
|
-
/**
|
|
88
|
-
* Handle a fire-and-forget activity's failure: attach a handler so a crash runs it
|
|
89
|
-
* instead of tearing the run down. Observes the failure; chainable; attach in the
|
|
90
|
-
* same tick as the launching call. Not called on cancellation.
|
|
91
|
-
*/
|
|
92
|
-
handleFailure(handler: (error: unknown) => void): this;
|
|
93
83
|
/** Subscribe to every emit of a declared/system event (`done`/`error`/`attached`). */
|
|
94
84
|
on<K extends keyof InstanceEvents<Events, Result>>(key: K, handler: EventHandler<InstanceEvents<Events, Result>[K]>): void;
|
|
95
85
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
86
|
+
* Await the next emit of `key`. Resolves the event's payload when it fires; if the activity
|
|
87
|
+
* settles first, resolves `undefined` on success and **rejects** on failure/cancellation — so a
|
|
88
|
+
* gate on an event surfaces a failure instead of hanging or sailing past it. Observes the
|
|
89
|
+
* activity. `once("done")` is the outcome itself — identical to `.result`.
|
|
100
90
|
*/
|
|
101
91
|
once<K extends keyof InstanceEvents<Events, Result>>(key: K): Promise<InstanceEvents<Events, Result>[K] | undefined>;
|
|
102
92
|
/** Tag this run; chainable. */
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export { om } from "./core/om.ts";
|
|
|
11
11
|
export { action } from "./core/action.ts";
|
|
12
12
|
export { step } from "./core/step.ts";
|
|
13
13
|
export { CancelledError, isCancelled } from "./foundation/CancelledError.ts";
|
|
14
|
-
export type { Action, Activity, ActionContext, OmContext
|
|
14
|
+
export type { Action, Activity, ActionContext, OmContext } from "./core/types.ts";
|
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
import type { LogStore } from "./log/LogStore.ts";
|
|
2
|
-
/** The terminal surface the renderer
|
|
2
|
+
/** The terminal surface the renderer appends to (process.stdout satisfies it). */
|
|
3
3
|
export interface LiveTerminal {
|
|
4
4
|
write(text: string): unknown;
|
|
5
|
-
readonly isTTY?: boolean;
|
|
6
|
-
readonly columns?: number;
|
|
7
5
|
}
|
|
8
6
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* On a TTY it keeps a **single status line**, rewriting it in place as milestones arrive,
|
|
15
|
-
* so the terminal stays quiet — except lines worth keeping (errors, failures, run
|
|
16
|
-
* milestones), which are *committed* (printed with a newline) so they persist above the
|
|
17
|
-
* live line. Piped/redirected output (no TTY) falls back to appending every line, so logs
|
|
18
|
-
* stay readable. Writes through `process.stdout` directly, which `patch-console` doesn't
|
|
19
|
-
* intercept, so it can't feed back into console capture.
|
|
7
|
+
* Appends a **curated** stream of milestones — action launches, completions, errors, events,
|
|
8
|
+
* asserts, snapshots — one line each, as the run goes. The in-place status-line rendering is
|
|
9
|
+
* retired: the interactive surface is the Ink app (`omkit ui`); this reporter is the plain
|
|
10
|
+
* output for bare `omkit run`, CI, and pipes. Writes through the given terminal directly (for
|
|
11
|
+
* `process.stdout`, `patch-console` doesn't intercept it, so it can't feed console capture).
|
|
20
12
|
*/
|
|
21
13
|
export declare class LiveRenderer {
|
|
22
14
|
private readonly term;
|
|
23
|
-
/**
|
|
24
|
-
private pending;
|
|
25
|
-
/** Tags accumulated per node from `tag` entries, so milestones can show them by the name. */
|
|
15
|
+
/** Tags accumulated per node from `tag` entries, so milestones can show them by name. */
|
|
26
16
|
private readonly tagsByNode;
|
|
27
17
|
constructor(term: LiveTerminal);
|
|
28
18
|
run(store: LogStore): Promise<void>;
|
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
const CLEAR = "\r\x1b[2K";
|
|
1
|
+
import { format } from "./milestones.js";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* On a TTY it keeps a **single status line**, rewriting it in place as milestones arrive,
|
|
10
|
-
* so the terminal stays quiet — except lines worth keeping (errors, failures, run
|
|
11
|
-
* milestones), which are *committed* (printed with a newline) so they persist above the
|
|
12
|
-
* live line. Piped/redirected output (no TTY) falls back to appending every line, so logs
|
|
13
|
-
* stay readable. Writes through `process.stdout` directly, which `patch-console` doesn't
|
|
14
|
-
* intercept, so it can't feed back into console capture.
|
|
3
|
+
* Appends a **curated** stream of milestones — action launches, completions, errors, events,
|
|
4
|
+
* asserts, snapshots — one line each, as the run goes. The in-place status-line rendering is
|
|
5
|
+
* retired: the interactive surface is the Ink app (`omkit ui`); this reporter is the plain
|
|
6
|
+
* output for bare `omkit run`, CI, and pipes. Writes through the given terminal directly (for
|
|
7
|
+
* `process.stdout`, `patch-console` doesn't intercept it, so it can't feed console capture).
|
|
15
8
|
*/
|
|
16
9
|
export class LiveRenderer {
|
|
17
10
|
term;
|
|
18
|
-
/**
|
|
19
|
-
pending = false;
|
|
20
|
-
/** Tags accumulated per node from `tag` entries, so milestones can show them by the name. */
|
|
11
|
+
/** Tags accumulated per node from `tag` entries, so milestones can show them by name. */
|
|
21
12
|
tagsByNode = new Map();
|
|
22
13
|
constructor(term) {
|
|
23
14
|
this.term = term;
|
|
24
15
|
}
|
|
25
16
|
async run(store) {
|
|
26
|
-
const tty = Boolean(this.term.isTTY);
|
|
27
17
|
for await (const entry of store.subscribe({ replay: true })) {
|
|
28
18
|
if (entry.level === "tag" && entry.source === "tag") {
|
|
29
19
|
const list = this.tagsByNode.get(entry.nodeId) ?? [];
|
|
@@ -34,71 +24,7 @@ export class LiveRenderer {
|
|
|
34
24
|
const rendered = format(entry, this.tagsByNode.get(entry.nodeId));
|
|
35
25
|
if (rendered === null)
|
|
36
26
|
continue;
|
|
37
|
-
|
|
38
|
-
this.term.write(`${rendered.text}\n`); // no TTY: append, one line each
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (rendered.persist) {
|
|
42
|
-
// Commit a line that must survive: finish the transient line, then print permanently.
|
|
43
|
-
this.term.write(`${this.pending ? CLEAR : ""}${rendered.text}\n`);
|
|
44
|
-
this.pending = false;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
// Transient: overwrite the current status line in place, truncated to fit.
|
|
48
|
-
this.term.write(`${CLEAR}${truncate(rendered.text, this.term.columns)}`);
|
|
49
|
-
this.pending = true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Keep the last status line on screen for the summary that follows.
|
|
53
|
-
if (this.pending) {
|
|
54
|
-
this.term.write("\n");
|
|
55
|
-
this.pending = false;
|
|
27
|
+
this.term.write(`${rendered.text}\n`);
|
|
56
28
|
}
|
|
57
29
|
}
|
|
58
30
|
}
|
|
59
|
-
const firstLine = (message) => message.split("\n", 1)[0] ?? message;
|
|
60
|
-
/** ✓ ok · ⊘ cancelled (a clean stop) · ✗ failed. */
|
|
61
|
-
const doneIcon = (status) => status === "ok" ? "✓" : status === "cancelled" ? "⊘" : "✗";
|
|
62
|
-
/** Truncate to the terminal width so an in-place line never wraps and breaks the `\r`. */
|
|
63
|
-
const truncate = (text, columns) => !columns || text.length < columns ? text : `${text.slice(0, Math.max(0, columns - 1))}…`;
|
|
64
|
-
/** The node's path with its accumulated tags appended: `main/probe_9f3c [ready, gate]`. */
|
|
65
|
-
const named = (path, tags) => tags && tags.length ? `${path} [${tags.join(", ")}]` : path;
|
|
66
|
-
/** Map a curated entry to a terminal line (and whether it persists), or `null` to drop it. */
|
|
67
|
-
const format = (e, tags) => {
|
|
68
|
-
// Action launch — bubbled onto the parent as `→ <childId> · <log>`.
|
|
69
|
-
if (e.level === "child" && e.source === "launch") {
|
|
70
|
-
const child = e.message.replace(/^→\s*/, "").split(" · ")[0];
|
|
71
|
-
return { text: `▶ ${e.path}/${child}`, persist: false };
|
|
72
|
-
}
|
|
73
|
-
// Lifecycle events: `launched` is already shown via the launch pointer above, so drop
|
|
74
|
-
// the duplicate; `done · <status>` renders with the status icon — a failure persists.
|
|
75
|
-
if (e.level === "event" && e.source === "lifecycle") {
|
|
76
|
-
if (e.message === "launched")
|
|
77
|
-
return null;
|
|
78
|
-
const status = e.message.replace(/^done · /, "");
|
|
79
|
-
return {
|
|
80
|
-
text: `${doneIcon(status)} ${named(e.path, tags)} · ${status}`,
|
|
81
|
-
persist: status === "failed",
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
// Run milestones (started / tearing down / finished) — few, and worth keeping.
|
|
85
|
-
if (e.level === "run")
|
|
86
|
-
return { text: `· ${e.message}`, persist: true };
|
|
87
|
-
// An action's *own* failure — not proc stderr, console.error, or a teardown cancellation.
|
|
88
|
-
if (e.level === "error" && e.source === "error") {
|
|
89
|
-
return { text: `✗ ${named(e.path, tags)} · ${firstLine(e.message)}`, persist: true };
|
|
90
|
-
}
|
|
91
|
-
// A cancellation milestone (⊘) — persist it so it doesn't flash away during teardown.
|
|
92
|
-
if (e.level === "event" && e.source === "cancel")
|
|
93
|
-
return { text: `⊘ ${named(e.path, tags)} · ${e.message}`, persist: true };
|
|
94
|
-
// User-emitted events.
|
|
95
|
-
if (e.level === "event")
|
|
96
|
-
return { text: `⚡ ${named(e.path, tags)} · ${e.message}`, persist: false };
|
|
97
|
-
// Asserts: a failed one (⊭) persists, a pass is transient.
|
|
98
|
-
if (e.level === "assert")
|
|
99
|
-
return { text: ` ${named(e.path, tags)} · ${e.message}`, persist: e.message.includes("⊭") };
|
|
100
|
-
if (e.level === "snapshot")
|
|
101
|
-
return { text: `📸 ${named(e.path, tags)} · ${e.message}`, persist: false };
|
|
102
|
-
// Everything else (info / console / proc output / tag / bubbled child events) → files only.
|
|
103
|
-
return null;
|
|
104
|
-
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LogEntry } from "../foundation/LogEntry.ts";
|
|
2
|
+
/** A curated milestone line plus whether it must persist (vs. a transient status update). */
|
|
3
|
+
export interface Rendered {
|
|
4
|
+
text: string;
|
|
5
|
+
persist: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Map a curated log entry to a milestone line (and whether it persists), or `null` to drop it. */
|
|
8
|
+
export declare function format(e: LogEntry, tags?: readonly string[]): Rendered | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const firstLine = (message) => message.split("\n", 1)[0] ?? message;
|
|
2
|
+
/** ✓ ok · ⊘ cancelled (a clean stop) · ✗ failed. */
|
|
3
|
+
const doneIcon = (status) => status === "ok" ? "✓" : status === "cancelled" ? "⊘" : "✗";
|
|
4
|
+
/** The node's path with its accumulated tags appended: `main/probe_9f3c [ready, gate]`. */
|
|
5
|
+
const named = (path, tags) => tags && tags.length ? `${path} [${tags.join(", ")}]` : path;
|
|
6
|
+
/** Map a curated log entry to a milestone line (and whether it persists), or `null` to drop it. */
|
|
7
|
+
export function format(e, tags) {
|
|
8
|
+
if (e.level === "child" && e.source === "launch") {
|
|
9
|
+
const child = e.message.replace(/^→\s*/, "").split(" · ")[0];
|
|
10
|
+
return { text: `▶ ${e.path}/${child}`, persist: false };
|
|
11
|
+
}
|
|
12
|
+
if (e.level === "event" && e.source === "lifecycle") {
|
|
13
|
+
if (e.message === "launched")
|
|
14
|
+
return null;
|
|
15
|
+
const status = e.message.replace(/^done · /, "");
|
|
16
|
+
return {
|
|
17
|
+
text: `${doneIcon(status)} ${named(e.path, tags)} · ${status}`,
|
|
18
|
+
persist: status === "failed",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (e.level === "run")
|
|
22
|
+
return { text: `· ${e.message}`, persist: true };
|
|
23
|
+
if (e.level === "error" && e.source === "error") {
|
|
24
|
+
return { text: `✗ ${named(e.path, tags)} · ${firstLine(e.message)}`, persist: true };
|
|
25
|
+
}
|
|
26
|
+
if (e.level === "event" && e.source === "cancel") {
|
|
27
|
+
return { text: `⊘ ${named(e.path, tags)} · ${e.message}`, persist: true };
|
|
28
|
+
}
|
|
29
|
+
if (e.level === "event") {
|
|
30
|
+
return { text: `⚡ ${named(e.path, tags)} · ${e.message}`, persist: false };
|
|
31
|
+
}
|
|
32
|
+
if (e.level === "assert") {
|
|
33
|
+
return { text: ` ${named(e.path, tags)} · ${e.message}`, persist: e.message.includes("⊭") };
|
|
34
|
+
}
|
|
35
|
+
if (e.level === "snapshot") {
|
|
36
|
+
return { text: `📸 ${named(e.path, tags)} · ${e.message}`, persist: false };
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A tiny runtime for developer workflows: typed actions that pass runtime capabilities and emit typed events, under one run and one shared log. The operational/runtime layer to tskb's knowledge layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"omkit": "./dist/cli/index.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
12
|
"dist",
|
|
10
13
|
"LICENSE"
|
|
@@ -49,12 +52,17 @@
|
|
|
49
52
|
"developer-tools"
|
|
50
53
|
],
|
|
51
54
|
"dependencies": {
|
|
55
|
+
"ink": "^7.1.1",
|
|
52
56
|
"patch-console": "^2.0.0",
|
|
53
57
|
"playwright-core": "^1.49.0",
|
|
58
|
+
"react": "^19.2.8",
|
|
59
|
+
"tsx": "4.23.1",
|
|
54
60
|
"zx": "^8.8.5"
|
|
55
61
|
},
|
|
56
62
|
"devDependencies": {
|
|
57
63
|
"@types/node": "^24",
|
|
64
|
+
"@types/react": "^19.2.17",
|
|
65
|
+
"ink-testing-library": "^4.0.0",
|
|
58
66
|
"rimraf": "^6.0.1",
|
|
59
67
|
"typescript": "^5.9.3"
|
|
60
68
|
}
|