omkit 0.8.24 → 0.8.25
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 +14 -14
- package/dist/cli/ui/views/OmList.d.ts +2 -0
- package/dist/cli/ui/views/OmList.js +27 -2
- package/dist/cli/ui/views/shellLines.d.ts +3 -3
- package/dist/cli/ui/views/shellLines.js +5 -4
- package/dist/client/discovery.js +6 -3
- package/dist/client/outline.d.ts +8 -1
- package/dist/client/outline.js +44 -0
- package/dist/client/registry.d.ts +10 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/output/writers/report/report.css +1795 -361
- package/dist/output/writers/report/report.js +26 -1
- package/dist/output/writers/views.d.ts +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,12 +116,12 @@ om(name: string): OmBuilder
|
|
|
116
116
|
|
|
117
117
|
Returns a builder. Chain configuration, then finish with `.run(body)`, which launches the run.
|
|
118
118
|
|
|
119
|
-
| Method
|
|
120
|
-
|
|
|
121
|
-
| `.describe({ summary })` | A one-line summary, shown by `omkit ls --describe`, the skill file, MCP and the report.
|
|
122
|
-
| `.args(schema)`
|
|
123
|
-
| `.mcp({ mode? })`
|
|
124
|
-
| `.run(body)`
|
|
119
|
+
| Method | Purpose |
|
|
120
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
121
|
+
| `.describe({ summary, … })` | A one-line summary, shown by `omkit ls --describe`, the skill file, MCP and the report. Any further props (`owner`, `docsUrl`, `needs: [...]`) are listed under it in the interactive picker and the report. |
|
|
122
|
+
| `.args(schema)` | A zod schema for the run's inputs. **Resolved at runtime**; see below. |
|
|
123
|
+
| `.mcp({ mode? })` | Expose this om over MCP. `mode` is `"settling"` (default) or `"long-lived"`. |
|
|
124
|
+
| `.run(body)` | Launch the run. `body(ctx, args?)` receives an [`OmContext`](#contexts) and, after `.args()`, the typed args. |
|
|
125
125
|
|
|
126
126
|
`.run()` returns a promise that resolves once the run has torn down and written its record. It never rejects: failures are recorded in the run and set the process exit code. Whatever the body returns is recorded as the run's `value` in `result.json`.
|
|
127
127
|
|
|
@@ -156,14 +156,14 @@ import { action } from "omkit";
|
|
|
156
156
|
action(name: string): ActionBuilder
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
| Method
|
|
160
|
-
|
|
|
161
|
-
| `.describe({ summary })` | A one-line summary, carried onto every run of this action.
|
|
162
|
-
| `.emits<Events>()`
|
|
163
|
-
| `.ref<Handle>()`
|
|
164
|
-
| `.args(schema)`
|
|
165
|
-
| `.mcp({ mode? })`
|
|
166
|
-
| `.run(exec)`
|
|
159
|
+
| Method | Purpose |
|
|
160
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `.describe({ summary, … })` | A one-line summary, plus any further props, carried onto every run of this action and listed in the report. |
|
|
162
|
+
| `.emits<Events>()` | Declare the events this action emits, as a type: `{ ready: number; error: string }`. |
|
|
163
|
+
| `.ref<Handle>()` | Declare the type of the value this action publishes with `attach`. |
|
|
164
|
+
| `.args(schema)` | Pin `.run`'s argument to a zod schema's type. **Type-level only** in code; see below. |
|
|
165
|
+
| `.mcp({ mode? })` | Expose this action over MCP as a runnable tool. |
|
|
166
|
+
| `.run(exec)` | Bind the implementation. Returns an **Action**: a function you call to launch it. |
|
|
167
167
|
|
|
168
168
|
`exec(ctx, ...args)` receives an [`ActionContext`](#contexts) and the arguments the action was called with. Its return value is the activity's result.
|
|
169
169
|
|
|
@@ -20,3 +20,5 @@ export declare function OmList({ oms, onSelect, status, hint, }: {
|
|
|
20
20
|
status?: ReactNode;
|
|
21
21
|
hint?: string;
|
|
22
22
|
}): ReactElement;
|
|
23
|
+
/** A description key as a label: `docsUrl` / `docs-url` / `docs_url` → `Docs url`. */
|
|
24
|
+
export declare function labelOf(key: string): string;
|
|
@@ -36,7 +36,32 @@ export function OmList({ oms, onSelect, status, hint = "↑↓ move · enter run
|
|
|
36
36
|
const restWidth = Math.max(20, columns - nameWidth - 6);
|
|
37
37
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "omkit" }), _jsx(Text, { dimColor: true, children: ` · ${oms.length} om${oms.length === 1 ? "" : "s"}` }), status ? _jsx(Text, { dimColor: true, children: " " }) : null, status ?? null] }), _jsx(SearchableList, { items: sorted, getKey: (o) => `${o.file}\0${o.name}`, getSearchText: (o) => `${o.name} ${o.summary ?? ""}`, onSelect: onSelect, alwaysShowSearch: true, searchLabel: "search \u203A ", searchPlaceholder: "type to filter by name or summary", renderRow: (o, selected) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "❯ " : " " }), _jsx(Text, { bold: selected, color: selected ? "cyan" : undefined, children: o.name.padEnd(nameWidth) }), " ", _jsx(Text, { dimColor: true, children: truncate(o.summary ?? displayPath(o.file, root), restWidth) })] })), renderDetail: (o) => (o ? _jsx(OmDetail, { om: o, root: root }) : null) }), _jsx(Text, { dimColor: true, children: hint })] }));
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** Steps listed before the rest collapse into `+N more`, so the box keeps a steady height. */
|
|
40
|
+
const MAX_STEPS = 10;
|
|
41
|
+
/** A description key as a label: `docsUrl` / `docs-url` / `docs_url` → `Docs url`. */
|
|
42
|
+
export function labelOf(key) {
|
|
43
|
+
const words = key
|
|
44
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
45
|
+
.replace(/[-_]+/g, " ")
|
|
46
|
+
.trim()
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
49
|
+
}
|
|
50
|
+
/** One label/value row; the value wraps under itself, not under the label. */
|
|
51
|
+
function Field({ label, width, children, }) {
|
|
52
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: width + 2, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: label }) }), _jsx(Box, { flexGrow: 1, children: children })] }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The highlighted om in full: its name, then a field per thing it says about itself — the
|
|
56
|
+
* summary, any further `.describe(…)` props, where it is defined — then its steps, numbered.
|
|
57
|
+
* The steps are the static outline, an approximation (see `outlineBody`), and say so.
|
|
58
|
+
*/
|
|
40
59
|
function OmDetail({ om, root }) {
|
|
41
|
-
|
|
60
|
+
const details = (om.details ?? []).map((d) => ({ label: labelOf(d.key), value: d.value }));
|
|
61
|
+
const width = Math.max("Summary".length, "Defined".length, ...details.map((d) => d.label.length));
|
|
62
|
+
const steps = om.calls ?? [];
|
|
63
|
+
const shown = steps.slice(0, MAX_STEPS);
|
|
64
|
+
const stepWidth = Math.min(24, Math.max(0, ...shown.map((c) => c.name.length)));
|
|
65
|
+
const numWidth = String(shown.length).length;
|
|
66
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: om.name }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Field, { label: "Summary", width: width, children: om.summary ? (_jsx(Text, { wrap: "wrap", children: om.summary })) : (_jsxs(Text, { dimColor: true, italic: true, children: ["none \u00B7 add .describe(", "{ summary }", ") to the om"] })) }), details.map((d) => (_jsx(Field, { label: d.label, width: width, children: _jsx(Text, { wrap: "wrap", children: d.value }) }, d.label))), _jsx(Field, { label: "Defined", width: width, children: _jsx(Text, { dimColor: true, wrap: "truncate-start", children: `${displayPath(om.file, root)}:${om.line}` }) })] }), shown.length ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Steps" }), _jsx(Text, { dimColor: true, italic: true, children: " read from source · approximate" })] }), shown.map((c, i) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: ` ${String(i + 1).padStart(numWidth)} ` }), c.tag ? c.name.padEnd(stepWidth) : c.name, c.tag ? _jsx(Text, { color: "cyan", children: ` ${c.tag}` }) : null] }, `${c.name}\0${c.tag ?? ""}`))), steps.length > shown.length ? (_jsx(Text, { dimColor: true, children: ` ${" ".repeat(numWidth)} +${steps.length - shown.length} more` })) : null] })) : null] }));
|
|
42
67
|
}
|
|
@@ -13,9 +13,9 @@ import type { ScrollLine } from "./Scrollback.tsx";
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function shellLine(e: LogEntry, node: RunNode | undefined, tearingDown: boolean): ScrollLine | null;
|
|
15
15
|
/**
|
|
16
|
-
* The verdict's recap, cut to what you reach for next: the report, and the assert
|
|
17
|
-
* run asserted anything. The full block (every sibling log) is in the run folder
|
|
18
|
-
* of `main.log`. A summary of any other shape — the output of a child that died before it could
|
|
16
|
+
* The verdict's recap, cut to what you reach for next: `main.log` and the report, and the assert
|
|
17
|
+
* tally when the run asserted anything. The full block (every sibling log) is in the run folder
|
|
18
|
+
* and at the foot of `main.log`. A summary of any other shape — the output of a child that died before it could
|
|
19
19
|
* report — is the only account of what went wrong, so it is kept whole.
|
|
20
20
|
*/
|
|
21
21
|
export declare function recapLines(summary: readonly string[]): ScrollLine[];
|
|
@@ -34,9 +34,9 @@ export function shellLine(e, node, tearingDown) {
|
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* The verdict's recap, cut to what you reach for next: the report, and the assert
|
|
38
|
-
* run asserted anything. The full block (every sibling log) is in the run folder
|
|
39
|
-
* of `main.log`. A summary of any other shape — the output of a child that died before it could
|
|
37
|
+
* The verdict's recap, cut to what you reach for next: `main.log` and the report, and the assert
|
|
38
|
+
* tally when the run asserted anything. The full block (every sibling log) is in the run folder
|
|
39
|
+
* and at the foot of `main.log`. A summary of any other shape — the output of a child that died before it could
|
|
40
40
|
* report — is the only account of what went wrong, so it is kept whole.
|
|
41
41
|
*/
|
|
42
42
|
export function recapLines(summary) {
|
|
@@ -46,8 +46,9 @@ export function recapLines(summary) {
|
|
|
46
46
|
const lines = [];
|
|
47
47
|
for (const line of summary) {
|
|
48
48
|
const trimmed = line.trim();
|
|
49
|
-
if (trimmed.startsWith("report "))
|
|
49
|
+
if (trimmed.startsWith("main log ") || trimmed.startsWith("report ")) {
|
|
50
50
|
lines.push({ text: ` ${trimmed}`, dim: true });
|
|
51
|
+
}
|
|
51
52
|
const tally = /⊨ (\d+) passed · ⊭ (\d+) failed/.exec(trimmed);
|
|
52
53
|
if (tally && Number(tally[1]) + Number(tally[2]) > 0) {
|
|
53
54
|
lines.push({
|
package/dist/client/discovery.js
CHANGED
|
@@ -6,7 +6,7 @@ import { omHash } from "../foundation/ids.js";
|
|
|
6
6
|
import { fsSafe } from "../foundation/fsSafe.js";
|
|
7
7
|
import { canonicalPath } from "../foundation/canonicalPath.js";
|
|
8
8
|
import { tsxLoader } from "./runner.js";
|
|
9
|
-
import { chainRoot, importedNames, outlineBody, summaryOf } from "./outline.js";
|
|
9
|
+
import { chainRoot, detailsOf, importedNames, outlineBody, summaryOf } from "./outline.js";
|
|
10
10
|
import { checkedProgram } from "./tsCache.js";
|
|
11
11
|
/**
|
|
12
12
|
* Statically scan the project described by `tsconfigPath` for runnable oms and inspectable
|
|
@@ -43,12 +43,13 @@ export function discover(tsconfigPath) {
|
|
|
43
43
|
node.expression.text === names.om) {
|
|
44
44
|
const arg = node.arguments[0];
|
|
45
45
|
if (arg && ts.isStringLiteralLike(arg)) {
|
|
46
|
-
const { calls, summary } = outlines.get(arg.text) ?? {};
|
|
46
|
+
const { calls, summary, details } = outlines.get(arg.text) ?? {};
|
|
47
47
|
oms.push({
|
|
48
48
|
name: arg.text,
|
|
49
49
|
file,
|
|
50
50
|
line: lineOf(sf, node),
|
|
51
51
|
...(summary !== undefined ? { summary } : {}),
|
|
52
|
+
...(details?.length ? { details } : {}),
|
|
52
53
|
...(calls?.length ? { calls } : {}),
|
|
53
54
|
});
|
|
54
55
|
}
|
|
@@ -80,7 +81,7 @@ export function discover(tsconfigPath) {
|
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Outline every `om("name")….run(body)` chain in a file, keyed by om name, along with the
|
|
83
|
-
* chain's literal `.describe({ summary })`.
|
|
84
|
+
* chain's literal `.describe({ summary, … })`.
|
|
84
85
|
*
|
|
85
86
|
* Collected in its own pass rather than alongside the `om(...)` match, because a body is only
|
|
86
87
|
* reachable from the `.run` end of the chain and the om name only from the other — and the
|
|
@@ -99,9 +100,11 @@ function outlinesIn(sf, omName, imported) {
|
|
|
99
100
|
const name = root?.call.arguments[0];
|
|
100
101
|
if (root?.name === omName && name && ts.isStringLiteralLike(name)) {
|
|
101
102
|
const summary = summaryOf(root);
|
|
103
|
+
const details = detailsOf(root);
|
|
102
104
|
outlines.set(name.text, {
|
|
103
105
|
...(body && ts.isFunctionLike(body) ? { calls: outlineBody(body, imported) } : {}),
|
|
104
106
|
...(summary !== undefined ? { summary } : {}),
|
|
107
|
+
...(details.length ? { details } : {}),
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
}
|
package/dist/client/outline.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import type { OmCall } from "./registry.ts";
|
|
2
|
+
import type { DescriptionDetail, OmCall } from "./registry.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Every identifier a file binds through an import — the whole allow-list for the outline.
|
|
5
5
|
* An action always arrives by import (`omkit/actions`, or a local actions module), so
|
|
@@ -40,3 +40,10 @@ export declare function outlineBody(body: ts.Node, imported: ReadonlySet<string>
|
|
|
40
40
|
*/
|
|
41
41
|
/** The `summary` of a `.describe({ summary: "…" })` in the chain, when it is a string literal. */
|
|
42
42
|
export declare function summaryOf(chain: ChainRoot): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The props of a `.describe({ … })` in the chain besides `summary`, in source order, with each
|
|
45
|
+
* value rendered as text. Like {@link summaryOf}, only what the AST can read without evaluating:
|
|
46
|
+
* a string, number or boolean literal, or an array of those (joined with commas). A prop with
|
|
47
|
+
* any other value is left out rather than guessed at.
|
|
48
|
+
*/
|
|
49
|
+
export declare function detailsOf(chain: ChainRoot): DescriptionDetail[];
|
package/dist/client/outline.js
CHANGED
|
@@ -106,6 +106,50 @@ export function summaryOf(chain) {
|
|
|
106
106
|
}
|
|
107
107
|
return undefined;
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The props of a `.describe({ … })` in the chain besides `summary`, in source order, with each
|
|
111
|
+
* value rendered as text. Like {@link summaryOf}, only what the AST can read without evaluating:
|
|
112
|
+
* a string, number or boolean literal, or an array of those (joined with commas). A prop with
|
|
113
|
+
* any other value is left out rather than guessed at.
|
|
114
|
+
*/
|
|
115
|
+
export function detailsOf(chain) {
|
|
116
|
+
const details = [];
|
|
117
|
+
for (const call of chain.calls) {
|
|
118
|
+
const callee = call.expression;
|
|
119
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "describe")
|
|
120
|
+
continue;
|
|
121
|
+
const arg = call.arguments[0];
|
|
122
|
+
if (!arg || !ts.isObjectLiteralExpression(arg))
|
|
123
|
+
continue;
|
|
124
|
+
for (const prop of arg.properties) {
|
|
125
|
+
if (!ts.isPropertyAssignment(prop))
|
|
126
|
+
continue;
|
|
127
|
+
const key = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name)
|
|
128
|
+
? prop.name.text
|
|
129
|
+
: undefined;
|
|
130
|
+
if (key === undefined || key === "summary")
|
|
131
|
+
continue;
|
|
132
|
+
const value = literalText(prop.initializer);
|
|
133
|
+
if (value !== undefined)
|
|
134
|
+
details.push({ key, value });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return details;
|
|
138
|
+
}
|
|
139
|
+
/** A literal's value as text, or `undefined` when it needs evaluating. Arrays join with commas. */
|
|
140
|
+
function literalText(node) {
|
|
141
|
+
if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node))
|
|
142
|
+
return node.text;
|
|
143
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword)
|
|
144
|
+
return "true";
|
|
145
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword)
|
|
146
|
+
return "false";
|
|
147
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
148
|
+
const items = node.elements.map((e) => literalText(e));
|
|
149
|
+
return items.every((i) => i !== undefined) ? items.join(", ") : undefined;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
109
153
|
function tagOf(chain) {
|
|
110
154
|
let tag;
|
|
111
155
|
for (const call of chain.calls) {
|
|
@@ -5,6 +5,11 @@ export interface OmCall {
|
|
|
5
5
|
/** The `.tag("…")` literal from the same fluent chain, when the author wrote one. */
|
|
6
6
|
readonly tag?: string;
|
|
7
7
|
}
|
|
8
|
+
/** One `.describe(…)` prop besides `summary`: its key, and its literal value as text. */
|
|
9
|
+
export interface DescriptionDetail {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly value: string;
|
|
12
|
+
}
|
|
8
13
|
/** A runnable om discovered from a top-level `om("name", …)` call. */
|
|
9
14
|
export interface DiscoveredOm {
|
|
10
15
|
/** The om's name — its first string argument. */
|
|
@@ -19,6 +24,11 @@ export interface DiscoveredOm {
|
|
|
19
24
|
* the import-based pass, `discoverRegistrations`.
|
|
20
25
|
*/
|
|
21
26
|
readonly summary?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The `.describe(…)` props besides `summary`, in source order, each value rendered as text
|
|
29
|
+
* (a list joined with commas). Read statically like `summary`, so only literal values appear.
|
|
30
|
+
*/
|
|
31
|
+
readonly details?: readonly DescriptionDetail[];
|
|
22
32
|
/**
|
|
23
33
|
* A static approximation of what the body calls, in source order. Absent when the walk
|
|
24
34
|
* found nothing recognisable. Never a contract — see {@link import("./outline.ts").outlineBody}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -63,7 +63,14 @@ export interface ActionContext<Events extends object = NoEvents, Handle = void>
|
|
|
63
63
|
export interface OmDescription {
|
|
64
64
|
/** One line saying what this does. */
|
|
65
65
|
summary: string;
|
|
66
|
+
/**
|
|
67
|
+
* Anything else worth knowing before running it — an owner, a docs link, what it needs.
|
|
68
|
+
* Listed under the summary, keyed by name, in the interactive picker and the run report.
|
|
69
|
+
*/
|
|
70
|
+
readonly [prop: string]: DescriptionValue;
|
|
66
71
|
}
|
|
72
|
+
/** What a description prop can hold: values that read plainly in a terminal and a report. */
|
|
73
|
+
export type DescriptionValue = string | number | boolean | readonly string[] | undefined;
|
|
67
74
|
/** How an om or action behaves when a client runs it as an MCP tool. */
|
|
68
75
|
export type McpMode = "settling" | "long-lived";
|
|
69
76
|
/**
|