@mnmal-ai/substrate-tui 1.0.0-alpha.1 → 1.0.0-alpha.11
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 +2 -2
- package/dist/{app-BLOyJcBh.js → app-xXFNoDVu.js} +72 -27
- package/dist/bin/substrate-tui.js +8 -4
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mnmal-ai/substrate-tui
|
|
2
2
|
|
|
3
|
-
A terminal UI for Substrate: ask questions in natural language, browse the federated schema, watch service status and metrics, and, with an unlocked admin identity, run admin operations. Install it with `pnpm add -g @mnmal-ai/substrate-tui`, then run `substrate-tui` in an interactive terminal with `SUBSTRATE_URL` set.
|
|
3
|
+
A terminal UI for Substrate: ask questions in natural language, browse the federated schema, watch service status and metrics, and, with an unlocked admin identity, run admin operations. Install it with `pnpm add -g @mnmal-ai/substrate-tui@next`, then run `substrate-tui` in an interactive terminal with `SUBSTRATE_URL` set.
|
|
4
4
|
|
|
5
5
|
Without an admin identity it is read-only.
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ Without an admin identity it is read-only.
|
|
|
25
25
|
|
|
26
26
|
## Status row
|
|
27
27
|
|
|
28
|
-
`● LIVE`, `● OFFLINE` or `● RECONNECTING` (polled from `/health` every 10 s), the service version, the agent kid (`anon` when unauthenticated, `anon?` when whoami fails) and the active namespace.
|
|
28
|
+
`● LIVE`, `● OFFLINE` or `● RECONNECTING` (polled from `/health` every 10 s), the service version, the agent kid (key id; `anon` when unauthenticated, `anon?` when whoami fails) and the active namespace.
|
|
29
29
|
|
|
30
30
|
## Environment
|
|
31
31
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as isKeyRejection, t as adminCapabilityOf } from "./session-C1D5Ermr.js";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { AdminBackfillError, AuthError, CURRENT_MARK, NetworkError, STEP_MARKS, adminClaimCapabilities, capabilitiesFor, checkRevocable, checkUpdatable, currentStep, decodeList, describeGrant, diagnose, encodeGrant, flattenGrant, grantEntriesConfer, isActiveAdminKeyRow, isEncryptedKey, makeTokenProvider, parseGrantEntries, planOnboarding, resolveAdminCredential } from "@mnmal-ai/substrate/client";
|
|
4
|
+
import { AdminBackfillError, AuthError, CURRENT_MARK, NetworkError, STEP_MARKS, adminClaimCapabilities, capabilitiesFor, checkRevocable, checkUpdatable, currentStep, decodeList, describeGrant, diagnose, encodeGrant, flattenGrant, grantEntriesConfer, isActiveAdminKeyRow, isEncryptedKey, makeTokenProvider, parseGrantEntries, planOnboarding, resolveAdminCredential, tokenAudience } from "@mnmal-ai/substrate/client";
|
|
5
5
|
import { createApp, defineModule, sanitise } from "@mnmal-ai/surface-core";
|
|
6
6
|
import { marathon } from "@mnmal-ai/surface-theme";
|
|
7
7
|
import { complete } from "@mnmal-ai/substrate-nl/complete";
|
|
@@ -76,6 +76,7 @@ function adminModule(deps, io = {}) {
|
|
|
76
76
|
kid
|
|
77
77
|
}, {
|
|
78
78
|
capabilities: adminClaimCapabilities(deps.adminEnv),
|
|
79
|
+
audience: tokenAudience(deps.endpoint),
|
|
79
80
|
...passphrase !== void 0 ? { passphrase } : {}
|
|
80
81
|
});
|
|
81
82
|
return verifyAndUnlock(() => `Bearer ${getToken()}`, keyPath, kid, getToken);
|
|
@@ -223,6 +224,7 @@ const KIND = {
|
|
|
223
224
|
recall: "substrate.recall",
|
|
224
225
|
clarify: "substrate.clarify",
|
|
225
226
|
refuse: "substrate.refuse",
|
|
227
|
+
unknown: "substrate.unknown",
|
|
226
228
|
error: "substrate.error"
|
|
227
229
|
};
|
|
228
230
|
const str = (v) => v === void 0 || v === null ? "" : sanitise(String(v));
|
|
@@ -231,9 +233,10 @@ const errorLines = (errors) => (errors ?? []).map((e) => {
|
|
|
231
233
|
const x = e;
|
|
232
234
|
return sanitise(`${x.code ?? "error"}${x.message !== void 0 ? `: ${x.message}` : ""}`);
|
|
233
235
|
});
|
|
236
|
+
const droppedLines = (dropped) => (dropped ?? []).map((d) => sanitise(`dropped "${d.span}": ${d.reason}`));
|
|
234
237
|
function outcomeChips(o) {
|
|
235
238
|
if (o.kind !== "rows") return [];
|
|
236
|
-
const rejected =
|
|
239
|
+
const rejected = o.data === void 0;
|
|
237
240
|
return [
|
|
238
241
|
{
|
|
239
242
|
label: `▸ ${frameKey(o)}`,
|
|
@@ -244,7 +247,7 @@ function outcomeChips(o) {
|
|
|
244
247
|
tone: o.source === "handoff" ? "warn" : "dim"
|
|
245
248
|
},
|
|
246
249
|
{
|
|
247
|
-
label: rejected ? "rejected" : `${o.data.length} rows`,
|
|
250
|
+
label: rejected ? "rejected" : `${o.count ?? (Array.isArray(o.data) ? o.data.length : 0)} rows`,
|
|
248
251
|
tone: rejected ? "error" : "dim"
|
|
249
252
|
}
|
|
250
253
|
];
|
|
@@ -260,6 +263,17 @@ const titleWidth = (columns) => Math.max(COLS.minTitle, columns - (COLS.id + COL
|
|
|
260
263
|
const rows = ({ data, theme, size }) => {
|
|
261
264
|
const titleCols = titleWidth(size.columns);
|
|
262
265
|
const o = data;
|
|
266
|
+
const lost = droppedLines(o.dropped).map((l, i) => /* @__PURE__ */ jsx(Text, {
|
|
267
|
+
color: theme.color.warn,
|
|
268
|
+
children: ` ${l}`
|
|
269
|
+
}, `d${i}`));
|
|
270
|
+
if (!Array.isArray(o.data) && typeof o.count === "number") return /* @__PURE__ */ jsxs(Box, {
|
|
271
|
+
flexDirection: "column",
|
|
272
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
273
|
+
color: theme.color.text,
|
|
274
|
+
children: o.count
|
|
275
|
+
}), lost]
|
|
276
|
+
});
|
|
263
277
|
if (!Array.isArray(o.data)) return /* @__PURE__ */ jsxs(Box, {
|
|
264
278
|
flexDirection: "column",
|
|
265
279
|
children: [/* @__PURE__ */ jsxs(Text, {
|
|
@@ -271,33 +285,53 @@ const rows = ({ data, theme, size }) => {
|
|
|
271
285
|
}, i))]
|
|
272
286
|
});
|
|
273
287
|
const list = o.data;
|
|
274
|
-
if (list.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
288
|
+
if (list.length === 0 && lost.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
275
289
|
color: theme.color.dim,
|
|
276
290
|
children: "no rows"
|
|
277
291
|
});
|
|
278
|
-
return /* @__PURE__ */
|
|
292
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
279
293
|
flexDirection: "column",
|
|
280
|
-
children:
|
|
281
|
-
/* @__PURE__ */ jsx(Text, {
|
|
294
|
+
children: [
|
|
295
|
+
list.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
282
296
|
color: theme.color.dim,
|
|
283
|
-
children:
|
|
284
|
-
}),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
children: "no rows"
|
|
298
|
+
}) : null,
|
|
299
|
+
list.map((r, i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
300
|
+
/* @__PURE__ */ jsx(Text, {
|
|
301
|
+
color: theme.color.dim,
|
|
302
|
+
children: padCells(str(r.shortId ?? r.id), COLS.id, true)
|
|
303
|
+
}),
|
|
304
|
+
" ",
|
|
305
|
+
/* @__PURE__ */ jsx(Text, {
|
|
306
|
+
color: theme.color.text,
|
|
307
|
+
children: padCells(str(r.title ?? r.synopsis ?? r.name ?? r.topic ?? r.subject), titleCols)
|
|
308
|
+
}),
|
|
309
|
+
" ",
|
|
310
|
+
/* @__PURE__ */ jsx(Text, {
|
|
311
|
+
color: theme.color.info,
|
|
312
|
+
children: padCells(str(r.status), COLS.status)
|
|
313
|
+
}),
|
|
314
|
+
" ",
|
|
315
|
+
/* @__PURE__ */ jsx(Text, {
|
|
316
|
+
color: theme.color.dimMid,
|
|
317
|
+
children: padCells(str(r.project), COLS.project)
|
|
318
|
+
})
|
|
319
|
+
] }, i)),
|
|
320
|
+
lost
|
|
321
|
+
]
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
const unknown = ({ data, theme }) => {
|
|
325
|
+
const o = data;
|
|
326
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
327
|
+
flexDirection: "column",
|
|
328
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
329
|
+
color: theme.color.warn,
|
|
330
|
+
children: "? unknown: not answered, because part of it was dropped"
|
|
331
|
+
}), droppedLines(o.dropped).map((l, i) => /* @__PURE__ */ jsx(Text, {
|
|
332
|
+
color: theme.color.warn,
|
|
333
|
+
children: ` ${l}`
|
|
334
|
+
}, i))]
|
|
301
335
|
});
|
|
302
336
|
};
|
|
303
337
|
const recall = ({ data, theme }) => {
|
|
@@ -385,6 +419,7 @@ const renderers = {
|
|
|
385
419
|
[KIND.recall]: recall,
|
|
386
420
|
[KIND.clarify]: clarify,
|
|
387
421
|
[KIND.refuse]: refuse,
|
|
422
|
+
[KIND.unknown]: unknown,
|
|
388
423
|
[KIND.error]: error
|
|
389
424
|
};
|
|
390
425
|
|
|
@@ -524,6 +559,7 @@ function askModule(deps) {
|
|
|
524
559
|
case "recall":
|
|
525
560
|
case "clarify":
|
|
526
561
|
case "refuse":
|
|
562
|
+
case "unknown":
|
|
527
563
|
last = { lines };
|
|
528
564
|
if (ev.event === "clarify" && ev.data.options.length > 0) ctx.open("clarify", ev.data);
|
|
529
565
|
else ctx.close();
|
|
@@ -3660,6 +3696,14 @@ function surgeryModule(deps, io = {}) {
|
|
|
3660
3696
|
|
|
3661
3697
|
//#endregion
|
|
3662
3698
|
//#region src/app.ts
|
|
3699
|
+
/** marathon with no overlay fill: an empty `raised` means Ink paints no background, so panels sit on the ground. */
|
|
3700
|
+
const substrateTheme = {
|
|
3701
|
+
...marathon,
|
|
3702
|
+
color: {
|
|
3703
|
+
...marathon.color,
|
|
3704
|
+
raised: ""
|
|
3705
|
+
}
|
|
3706
|
+
};
|
|
3663
3707
|
function buildModules(deps, opts = {}) {
|
|
3664
3708
|
return [
|
|
3665
3709
|
statusModule(deps, opts),
|
|
@@ -3674,11 +3718,12 @@ function buildModules(deps, opts = {}) {
|
|
|
3674
3718
|
}
|
|
3675
3719
|
function substrateAppOptions(deps, opts = {}) {
|
|
3676
3720
|
return {
|
|
3677
|
-
theme:
|
|
3721
|
+
theme: substrateTheme,
|
|
3678
3722
|
modules: buildModules(deps, opts),
|
|
3679
3723
|
defaultTurn: "substrate.ask:ask",
|
|
3680
3724
|
band: true,
|
|
3681
3725
|
gutter: 2,
|
|
3726
|
+
anchored: true,
|
|
3682
3727
|
terminal: false
|
|
3683
3728
|
};
|
|
3684
3729
|
}
|
|
@@ -3687,4 +3732,4 @@ function createTui(deps, opts = {}) {
|
|
|
3687
3732
|
}
|
|
3688
3733
|
|
|
3689
3734
|
//#endregion
|
|
3690
|
-
export { buildModules, createTui, substrateAppOptions };
|
|
3735
|
+
export { buildModules, createTui, substrateAppOptions, substrateTheme };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as colorDepthEnv } from "../terminal-XfKFiD7r.js";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
|
-
import { DEFAULT_CAPABILITIES, makeTokenProvider } from "@mnmal-ai/substrate/client";
|
|
4
|
+
import { DEFAULT_CAPABILITIES, makeTokenProvider, tokenAudience } from "@mnmal-ai/substrate/client";
|
|
5
|
+
import { trimTrailingSlashes } from "@mnmal-ai/substrate/sdk";
|
|
5
6
|
|
|
6
7
|
//#region src/config.ts
|
|
7
8
|
const message = (err) => err instanceof Error ? err.message : String(err);
|
|
8
9
|
function readConfig(env, cwd) {
|
|
9
|
-
const endpoint = (env.SUBSTRATE_URL ?? "")
|
|
10
|
+
const endpoint = trimTrailingSlashes(env.SUBSTRATE_URL ?? "");
|
|
10
11
|
if (endpoint === "") return { error: "SUBSTRATE_URL is required (e.g. https://substrate.example.com:3100)" };
|
|
11
12
|
const namespace = env.SUBSTRATE_NAMESPACE ?? "";
|
|
12
13
|
if (namespace === "") return { error: "SUBSTRATE_NAMESPACE is required (the namespace to watch)" };
|
|
@@ -19,7 +20,10 @@ function readConfig(env, cwd) {
|
|
|
19
20
|
token = makeTokenProvider({
|
|
20
21
|
keyPath,
|
|
21
22
|
kid
|
|
22
|
-
}, {
|
|
23
|
+
}, {
|
|
24
|
+
capabilities: raw !== void 0 && raw.length > 0 ? raw : DEFAULT_CAPABILITIES,
|
|
25
|
+
audience: tokenAudience(endpoint)
|
|
26
|
+
});
|
|
23
27
|
} catch (err) {
|
|
24
28
|
return { error: `agent identity: ${message(err)}` };
|
|
25
29
|
}
|
|
@@ -47,7 +51,7 @@ async function main() {
|
|
|
47
51
|
}
|
|
48
52
|
Object.assign(process.env, colorDepthEnv(process.env));
|
|
49
53
|
const [{ createTui }, { makeDeps }, { prepareTerminal }, { terminalOptions }] = await Promise.all([
|
|
50
|
-
import("../app-
|
|
54
|
+
import("../app-xXFNoDVu.js"),
|
|
51
55
|
import("../deps-WzqsHHiC.js"),
|
|
52
56
|
import("@mnmal-ai/surface-core/pure"),
|
|
53
57
|
import("../terminal-CVX2CKWu.js")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnmal-ai/substrate-tui",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
4
4
|
"description": "Terminal UI for Substrate: ask in natural language, browse the schema, see the service status.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"bin": {
|
|
13
|
-
"substrate-tui": "dist/bin/tui.js"
|
|
13
|
+
"substrate-tui": "dist/bin/substrate-tui.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@mnmal-ai/substrate": "1.0.0-alpha.
|
|
24
|
-
"@mnmal-ai/substrate-nl": "1.0.0-alpha.
|
|
25
|
-
"@mnmal-ai/surface-core": "0.
|
|
26
|
-
"@mnmal-ai/surface-kit": "0.
|
|
27
|
-
"@mnmal-ai/surface-theme": "0.
|
|
28
|
-
"ink": "^
|
|
23
|
+
"@mnmal-ai/substrate": "1.0.0-alpha.11",
|
|
24
|
+
"@mnmal-ai/substrate-nl": "1.0.0-alpha.11",
|
|
25
|
+
"@mnmal-ai/surface-core": "0.15.0",
|
|
26
|
+
"@mnmal-ai/surface-kit": "0.15.0",
|
|
27
|
+
"@mnmal-ai/surface-theme": "0.15.0",
|
|
28
|
+
"ink": "^8.0.0",
|
|
29
29
|
"react": "^19.3.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@biomejs/biome": "
|
|
33
|
-
"@mnmal-ai/substrate-config": "1.0.0-alpha.
|
|
32
|
+
"@biomejs/biome": "2.6.4",
|
|
33
|
+
"@mnmal-ai/substrate-config": "1.0.0-alpha.11",
|
|
34
34
|
"@types/node": "^26.6.4",
|
|
35
35
|
"@types/react": "^19.2.0",
|
|
36
36
|
"ink-testing-library": "^4.0.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"vitest": "^5.0.3"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=26.
|
|
43
|
+
"node": ">=26.12.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsdown",
|