@mnmal-ai/substrate-tui 1.0.0-alpha.13 → 1.0.0-alpha.14
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 +1 -0
- package/dist/{app-xXFNoDVu.js → app-9LqYVY4G.js} +105 -16
- package/dist/bin/substrate-tui.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Without an admin identity it is read-only.
|
|
|
9
9
|
- Free text is a natural-language question, answered by `substrate-nl` (streamed). Tab completes types, fields and values.
|
|
10
10
|
- `/ns <namespace>` switches the namespace that questions, completion and the status row use. The schema browser always lists every namespace.
|
|
11
11
|
- `/frame` shows the Substrate frame behind the last answer.
|
|
12
|
+
- `/asof <date-time>` reads relative phrases ("yesterday", "past 2 days") as of that moment, for the questions that follow. It must be ISO-8601 with an offset, e.g. `2026-10-24T23:59:59-05:00`. `/asof off` returns to the server clock, and `/asof` alone shows which is in use. `/frame` shows the clock the server read the last question against.
|
|
12
13
|
- `/schema [namespace] [type]` opens the schema browser: namespaces, then types, then fields.
|
|
13
14
|
- `/dash` shows service, traffic and engine metrics, live while open.
|
|
14
15
|
- `/doctor` runs live checks against this deployment.
|
|
@@ -252,6 +252,44 @@ function outcomeChips(o) {
|
|
|
252
252
|
}
|
|
253
253
|
];
|
|
254
254
|
}
|
|
255
|
+
/** The first label-like field a row has: the usual handles, then any `…Name` / `…Title` string. */
|
|
256
|
+
const LABELS = [
|
|
257
|
+
"title",
|
|
258
|
+
"synopsis",
|
|
259
|
+
"name",
|
|
260
|
+
"topic",
|
|
261
|
+
"subject"
|
|
262
|
+
];
|
|
263
|
+
function labelField(r) {
|
|
264
|
+
const own = LABELS.find((k) => typeof r[k] === "string" && r[k] !== "");
|
|
265
|
+
if (own !== void 0) return own;
|
|
266
|
+
return Object.keys(r).find((k) => /(Name|Title)$/.test(k) && typeof r[k] === "string" && r[k] !== "");
|
|
267
|
+
}
|
|
268
|
+
/** Fields the question filtered or ordered on, in the order they appear in the frame. */
|
|
269
|
+
function askedFields(frame) {
|
|
270
|
+
const params = Object.values(frame)[0]?.params ?? {};
|
|
271
|
+
const out = [];
|
|
272
|
+
const add = (k) => {
|
|
273
|
+
if (!out.includes(k)) out.push(k);
|
|
274
|
+
};
|
|
275
|
+
const walk = (w) => {
|
|
276
|
+
if (w === null || typeof w !== "object") return;
|
|
277
|
+
for (const [k, v] of Object.entries(w)) if (k === "AND" || k === "OR" || k === "NOT") for (const c of Array.isArray(v) ? v : [v]) walk(c);
|
|
278
|
+
else if (!k.startsWith("_")) add(k);
|
|
279
|
+
};
|
|
280
|
+
for (const o of params.orderBy ?? []) Object.keys(o).forEach(add);
|
|
281
|
+
walk(params.where);
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
const SKIP = /* @__PURE__ */ new Set(["id", "shortId"]);
|
|
285
|
+
/** `field=value` for a row's scalar fields: the asked-about ones first, then the rest. */
|
|
286
|
+
function details(r, asked, label) {
|
|
287
|
+
const scalar = (k) => {
|
|
288
|
+
const v = r[k];
|
|
289
|
+
return v !== null && v !== void 0 && v !== "" && typeof v !== "object";
|
|
290
|
+
};
|
|
291
|
+
return [...asked.filter((k) => k in r), ...Object.keys(r).filter((k) => !asked.includes(k))].filter((k) => !SKIP.has(k) && k !== label && scalar(k)).map((k) => `${sanitise(k)}=${str(r[k])}`).join(" ");
|
|
292
|
+
}
|
|
255
293
|
const COLS = {
|
|
256
294
|
id: 6,
|
|
257
295
|
status: 10,
|
|
@@ -289,6 +327,33 @@ const rows = ({ data, theme, size }) => {
|
|
|
289
327
|
color: theme.color.dim,
|
|
290
328
|
children: "no rows"
|
|
291
329
|
});
|
|
330
|
+
if (!list.some((r) => "status" in r || "project" in r)) {
|
|
331
|
+
const asked = askedFields(o.frame);
|
|
332
|
+
const labelCols = Math.min(40, Math.max(COLS.minTitle, Math.floor(size.columns / 3)));
|
|
333
|
+
const detailCols = Math.max(COLS.minTitle, size.columns - (COLS.id + labelCols + 4));
|
|
334
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
335
|
+
flexDirection: "column",
|
|
336
|
+
children: [list.map((r, i) => {
|
|
337
|
+
const label = labelField(r);
|
|
338
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
339
|
+
/* @__PURE__ */ jsx(Text, {
|
|
340
|
+
color: theme.color.dim,
|
|
341
|
+
children: padCells(str(r.shortId ?? r.id), COLS.id, true)
|
|
342
|
+
}),
|
|
343
|
+
" ",
|
|
344
|
+
/* @__PURE__ */ jsx(Text, {
|
|
345
|
+
color: theme.color.text,
|
|
346
|
+
children: padCells(label !== void 0 ? str(r[label]) : "", labelCols)
|
|
347
|
+
}),
|
|
348
|
+
" ",
|
|
349
|
+
/* @__PURE__ */ jsx(Text, {
|
|
350
|
+
color: theme.color.dimMid,
|
|
351
|
+
children: padCells(details(r, asked, label), detailCols)
|
|
352
|
+
})
|
|
353
|
+
] }, i);
|
|
354
|
+
}), lost]
|
|
355
|
+
});
|
|
356
|
+
}
|
|
292
357
|
return /* @__PURE__ */ jsxs(Box, {
|
|
293
358
|
flexDirection: "column",
|
|
294
359
|
children: [
|
|
@@ -443,10 +508,14 @@ function explain(err, endpoint) {
|
|
|
443
508
|
if (e.status === 401 || e.status === 403) return `not authorised (${e.status}) — check SUBSTRATE_AGENT_KEY_ID / SUBSTRATE_AGENT_KEY_PATH`;
|
|
444
509
|
return e.message ?? String(err);
|
|
445
510
|
}
|
|
511
|
+
/** What the server accepts for `asOf`: a date-time with an explicit offset. */
|
|
512
|
+
const ISO_WITH_OFFSET = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
446
513
|
function askModule(deps) {
|
|
447
514
|
let namespace = deps.namespace;
|
|
448
515
|
let pack = null;
|
|
449
516
|
let forceType;
|
|
517
|
+
/** The clock relative phrases are read against; undefined = the server's. */
|
|
518
|
+
let asOf;
|
|
450
519
|
let last = { lines: ["no question asked yet"] };
|
|
451
520
|
let lastPackNote = "";
|
|
452
521
|
const loadPack = (ctx) => {
|
|
@@ -474,23 +543,41 @@ function askModule(deps) {
|
|
|
474
543
|
loadPack(ctx);
|
|
475
544
|
ctx.emit("substrate/ns", { namespace });
|
|
476
545
|
},
|
|
477
|
-
commands: [
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
546
|
+
commands: [
|
|
547
|
+
{
|
|
548
|
+
name: "ns",
|
|
549
|
+
summary: "switch the namespace questions go to",
|
|
550
|
+
args: [{ name: "namespace" }],
|
|
551
|
+
run([ns], ctx) {
|
|
552
|
+
if (ns === void 0 || ns === "") return ctx.print(`namespace: ${namespace}`);
|
|
553
|
+
namespace = ns;
|
|
554
|
+
loadPack(ctx);
|
|
555
|
+
ctx.emit("substrate/ns", { namespace });
|
|
556
|
+
ctx.print(`namespace → ${namespace}`);
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: "asof",
|
|
561
|
+
summary: "read \"yesterday\", \"past 2 days\" … as of a date-time (ISO-8601 with an offset); off for the server clock",
|
|
562
|
+
args: [{ name: "datetime|off" }],
|
|
563
|
+
run([v], ctx) {
|
|
564
|
+
if (v === void 0 || v === "") return ctx.print(`asOf: ${asOf ?? "server clock"}`);
|
|
565
|
+
if (v === "off") {
|
|
566
|
+
asOf = void 0;
|
|
567
|
+
return ctx.print("asOf → server clock");
|
|
568
|
+
}
|
|
569
|
+
if (!ISO_WITH_OFFSET.test(v) || Number.isNaN(Date.parse(v))) return ctx.print("asOf needs an ISO-8601 date-time with an offset, e.g. 2026-10-24T23:59:59-05:00");
|
|
570
|
+
asOf = v;
|
|
571
|
+
ctx.print(`asOf → ${asOf}`);
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: "frame",
|
|
576
|
+
summary: "show the last answer's query frame, evidence and judge trail",
|
|
577
|
+
args: [],
|
|
578
|
+
run: (_a, ctx) => ctx.open("frame", last)
|
|
487
579
|
}
|
|
488
|
-
|
|
489
|
-
name: "frame",
|
|
490
|
-
summary: "show the last answer's query frame, evidence and judge trail",
|
|
491
|
-
args: [],
|
|
492
|
-
run: (_a, ctx) => ctx.open("frame", last)
|
|
493
|
-
}],
|
|
580
|
+
],
|
|
494
581
|
panels: {
|
|
495
582
|
clarify: {
|
|
496
583
|
view: ({ state }) => [
|
|
@@ -525,11 +612,13 @@ function askModule(deps) {
|
|
|
525
612
|
namespace,
|
|
526
613
|
question: text,
|
|
527
614
|
project: deps.project,
|
|
615
|
+
...asOf !== void 0 ? { asOf } : {},
|
|
528
616
|
...type !== void 0 ? { type } : {}
|
|
529
617
|
}, { signal })) switch (ev.event) {
|
|
530
618
|
case "parsed": {
|
|
531
619
|
const key = ev.data.frame !== void 0 ? Object.keys(ev.data.frame)[0] : void 0;
|
|
532
620
|
lines.push(`parse: ${ev.data.status}${ev.data.reason !== void 0 ? ` — ${ev.data.reason}` : ""}`);
|
|
621
|
+
if (ev.data.asOf !== void 0) lines.push(`asOf: ${ev.data.asOf}`);
|
|
533
622
|
if (ev.data.frame !== void 0) lines.push(JSON.stringify(ev.data.frame, null, 2));
|
|
534
623
|
for (const e of ev.data.evidence) lines.push(` ${e.span} → ${e.role} (${e.detail})`);
|
|
535
624
|
yield {
|
|
@@ -51,7 +51,7 @@ async function main() {
|
|
|
51
51
|
}
|
|
52
52
|
Object.assign(process.env, colorDepthEnv(process.env));
|
|
53
53
|
const [{ createTui }, { makeDeps }, { prepareTerminal }, { terminalOptions }] = await Promise.all([
|
|
54
|
-
import("../app-
|
|
54
|
+
import("../app-9LqYVY4G.js"),
|
|
55
55
|
import("../deps-WzqsHHiC.js"),
|
|
56
56
|
import("@mnmal-ai/surface-core/pure"),
|
|
57
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.14",
|
|
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": {
|
|
@@ -20,8 +20,8 @@
|
|
|
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.
|
|
23
|
+
"@mnmal-ai/substrate": "1.0.0-alpha.14",
|
|
24
|
+
"@mnmal-ai/substrate-nl": "1.0.0-alpha.14",
|
|
25
25
|
"@mnmal-ai/surface-core": "0.15.0",
|
|
26
26
|
"@mnmal-ai/surface-kit": "0.15.0",
|
|
27
27
|
"@mnmal-ai/surface-theme": "0.15.0",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@biomejs/biome": "2.6.4",
|
|
33
|
-
"@mnmal-ai/substrate-config": "1.0.0-alpha.
|
|
33
|
+
"@mnmal-ai/substrate-config": "1.0.0-alpha.14",
|
|
34
34
|
"@types/node": "^26.6.4",
|
|
35
35
|
"@types/react": "^19.2.0",
|
|
36
36
|
"ink-testing-library": "^4.0.0",
|