omkit 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +66 -42
  2. package/dist/actions/browser.d.ts +28 -0
  3. package/dist/actions/browser.js +36 -0
  4. package/dist/actions/command.d.ts +11 -7
  5. package/dist/actions/command.js +16 -10
  6. package/dist/actions/index.d.ts +2 -0
  7. package/dist/actions/index.js +1 -0
  8. package/dist/actions/prompt.js +43 -17
  9. package/dist/cli/client/channel.d.ts +14 -0
  10. package/dist/cli/client/channel.js +48 -0
  11. package/dist/cli/client/check.d.ts +3 -0
  12. package/dist/cli/client/check.js +32 -0
  13. package/dist/cli/client/discovery.d.ts +7 -0
  14. package/dist/cli/client/discovery.js +147 -0
  15. package/dist/cli/client/index.d.ts +9 -0
  16. package/dist/cli/client/index.js +11 -0
  17. package/dist/cli/client/registry.d.ts +29 -0
  18. package/dist/cli/client/registry.js +1 -0
  19. package/dist/cli/client/runner.d.ts +15 -0
  20. package/dist/cli/client/runner.js +61 -0
  21. package/dist/cli/client/types.d.ts +53 -0
  22. package/dist/cli/client/types.js +1 -0
  23. package/dist/cli/commands/check.d.ts +6 -0
  24. package/dist/cli/commands/check.js +11 -0
  25. package/dist/cli/commands/init.d.ts +5 -0
  26. package/dist/cli/commands/init.js +54 -0
  27. package/dist/cli/commands/ls.d.ts +5 -0
  28. package/dist/cli/commands/ls.js +25 -0
  29. package/dist/cli/commands/run.d.ts +10 -0
  30. package/dist/cli/commands/run.js +12 -0
  31. package/dist/cli/commands/ui.d.ts +7 -0
  32. package/dist/cli/commands/ui.js +30 -0
  33. package/dist/cli/index.d.ts +9 -0
  34. package/dist/cli/index.js +91 -0
  35. package/dist/cli/ui/Spinner.d.ts +10 -0
  36. package/dist/cli/ui/Spinner.js +20 -0
  37. package/dist/cli/ui/app.d.ts +9 -0
  38. package/dist/cli/ui/app.js +93 -0
  39. package/dist/cli/ui/views/OmList.d.ts +9 -0
  40. package/dist/cli/ui/views/OmList.js +36 -0
  41. package/dist/cli/ui/views/RunView.d.ts +14 -0
  42. package/dist/cli/ui/views/RunView.js +47 -0
  43. package/dist/core/ActionRun.d.ts +2 -4
  44. package/dist/core/ActionRun.js +27 -32
  45. package/dist/core/ExecutionTree.d.ts +1 -0
  46. package/dist/core/ExecutionTree.js +25 -4
  47. package/dist/core/interaction.d.ts +63 -0
  48. package/dist/core/interaction.js +82 -0
  49. package/dist/core/step.js +2 -7
  50. package/dist/core/types.d.ts +12 -17
  51. package/dist/index.d.ts +1 -1
  52. package/dist/output/LiveRenderer.d.ts +7 -17
  53. package/dist/output/LiveRenderer.js +8 -82
  54. package/dist/output/milestones.d.ts +8 -0
  55. package/dist/output/milestones.js +39 -0
  56. package/package.json +9 -1
@@ -1,28 +1,18 @@
1
1
  import type { LogStore } from "./log/LogStore.ts";
2
- /** The terminal surface the renderer writes to (process.stdout satisfies it). */
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
- * Renders a **curated** stream of milestones to the terminal, live — action launches,
10
- * completions, and errors, plus events, asserts, and snapshots. The bulk output (console
11
- * lines, proc stdout/stderr) is intentionally *not* shown live; it lives in the per-action
12
- * `.log` files.
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
- /** True while a transient status line is on screen (needs clearing or a closing newline). */
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
- /** Return to column 0 and clear the whole line — the in-place status-line reset. */
2
- const CLEAR = "\r\x1b[2K";
1
+ import { format } from "./milestones.js";
3
2
  /**
4
- * Renders a **curated** stream of milestones to the terminal, live — action launches,
5
- * completions, and errors, plus events, asserts, and snapshots. The bulk output (console
6
- * lines, proc stdout/stderr) is intentionally *not* shown live; it lives in the per-action
7
- * `.log` files.
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
- /** True while a transient status line is on screen (needs clearing or a closing newline). */
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
- if (!tty) {
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.2.1",
3
+ "version": "0.4.0",
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
  }