@vincemakes/kiso-code 0.1.36 → 0.1.38
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/dist/chat.js +41 -2
- package/dist/dispatch.js +91 -5
- package/dist/index.js +34 -28
- package/dist/state.d.ts +3 -0
- package/package.json +2 -2
package/dist/chat.js
CHANGED
|
@@ -177,9 +177,23 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
|
|
|
177
177
|
const turnStart = Date.now();
|
|
178
178
|
let toolCount = 0;
|
|
179
179
|
let editCount = 0;
|
|
180
|
+
// W14: the thinking event carries NO timestamp — the CLI wall-clocks
|
|
181
|
+
// the thinking window: it opens at the first thinking event and closes
|
|
182
|
+
// at the first non-thinking event (the fold needs the seconds).
|
|
183
|
+
let thoughtSeconds = 0;
|
|
184
|
+
let thinkingSince = null;
|
|
180
185
|
try {
|
|
181
186
|
for await (const ev of run) {
|
|
182
187
|
last = ev;
|
|
188
|
+
if (ev.type !== "thinking") {
|
|
189
|
+
if (thinkingSince !== null) {
|
|
190
|
+
thoughtSeconds += (Date.now() - thinkingSince) / 1000;
|
|
191
|
+
thinkingSince = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (thinkingSince === null) {
|
|
195
|
+
thinkingSince = Date.now();
|
|
196
|
+
}
|
|
183
197
|
// v2a (the double echo): the interactive echo was already rendered by the
|
|
184
198
|
// input source — rendering the event again is the double echo.
|
|
185
199
|
// v2b: DOCKED — the echo lives in the input row (H), NOT the body;
|
|
@@ -218,7 +232,18 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
|
|
|
218
232
|
break;
|
|
219
233
|
case "tool_result": {
|
|
220
234
|
const text = typeof ev.content === "string" ? ev.content : "";
|
|
221
|
-
|
|
235
|
+
// W19: a DENIED call carries its reason — extracted from the
|
|
236
|
+
// result's "[Permission denied] " prefix, keyed on the
|
|
237
|
+
// "denied" tag (the parseChecklist discipline: the tag
|
|
238
|
+
// declares, the prefix confirms). The ToolCell renders the
|
|
239
|
+
// pinned row (full name, target, reason, no timing).
|
|
240
|
+
let reason = null;
|
|
241
|
+
if ((ev.tags ?? []).includes("denied")) {
|
|
242
|
+
const m = /^\[Permission denied\] (.*)$/.exec(text);
|
|
243
|
+
if (m !== null)
|
|
244
|
+
reason = m[1];
|
|
245
|
+
}
|
|
246
|
+
body.toolResult(ev.callId, { content: text, isError: ev.isError, reason });
|
|
222
247
|
// round 6 (the todo round): a result tagged do-not-compact whose content
|
|
223
248
|
// follows the checklist shape also renders as the durable
|
|
224
249
|
// checklist cell (the CLI translates Event → the tui's own
|
|
@@ -271,12 +296,19 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
|
|
|
271
296
|
// events (zero tokens). The dock's status bar still paints.
|
|
272
297
|
statusCb?.(usage, estimateCtxRatio(session));
|
|
273
298
|
const ratio = estimateCtxRatio(session);
|
|
299
|
+
// W14: the turn record closes HERE — before the recap logs, so
|
|
300
|
+
// the commit loop folds the quiet turn's held cells first (the
|
|
301
|
+
// fold line lands above the recap, natural cell order).
|
|
302
|
+
body.endTurn(Math.round(thoughtSeconds));
|
|
274
303
|
bodyLog(renderRecap({
|
|
275
304
|
seconds: Math.round((Date.now() - turnStart) / 1000),
|
|
276
305
|
tools: toolCount,
|
|
277
306
|
edits: editCount,
|
|
278
307
|
usage,
|
|
279
308
|
ctxLeftPct: Number.isFinite(ratio) ? (1 - ratio) * 100 : null,
|
|
309
|
+
// W19: under plan the recap becomes the way-forward row
|
|
310
|
+
// (the /mode hints are the mode's exits).
|
|
311
|
+
mode: getMode(),
|
|
280
312
|
}));
|
|
281
313
|
break;
|
|
282
314
|
}
|
|
@@ -436,7 +468,10 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
436
468
|
return;
|
|
437
469
|
const ratio = estimateCtxRatio(session);
|
|
438
470
|
const pct = Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
439
|
-
|
|
471
|
+
// W19: under plan the idle row makes the posture unmistakable —
|
|
472
|
+
// the W4 parentheses idiom names the read-only constraint.
|
|
473
|
+
const tier = getMode() === "plan" ? "plan (read-only)" : getMode();
|
|
474
|
+
dock.setStatus(`▸ ${tier} · /mode to switch · ${agentModel} · ctx left ~${pct}%`);
|
|
440
475
|
};
|
|
441
476
|
const statusCb = (u, ctx) => {
|
|
442
477
|
runUsage = u;
|
|
@@ -479,6 +514,10 @@ export async function chat(session, faux, input, autoCompact) {
|
|
|
479
514
|
}
|
|
480
515
|
dispatch(line, dispatchCtx);
|
|
481
516
|
});
|
|
517
|
+
// W15: the expand key — the editor forwards ctrl+r; dispatch runs the
|
|
518
|
+
// chain action (the sentinel's control char marks the key, so a typed
|
|
519
|
+
// "expand" turn is never intercepted).
|
|
520
|
+
input.onExpand(() => dispatch("\x12expand", dispatchCtx));
|
|
482
521
|
// Recovery first: a session with a dangling pause or uncertain
|
|
483
522
|
// executions must resolve them BEFORE the REPL accepts new turns —
|
|
484
523
|
// otherwise the interrupted run dangles while a new one starts.
|
package/dist/dispatch.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* turns. The bodies moved verbatim from chat()'s closure; chat provides
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
|
-
import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
|
|
6
|
+
import { escapeTerminal, kUnit, palette } from "@vincemakes/kiso-tui";
|
|
7
7
|
import { buildAdapter } from "@vincemakes/kiso-runtime";
|
|
8
8
|
import { MODES, getMode, setMode } from "./mode.js";
|
|
9
|
-
import { agentModel, body, bodyLog, configModels, setAgentModel, setCurrentModelName } from "./state.js";
|
|
9
|
+
import { agentModel, body, bodyLog, configModels, dock, setAgentModel, setCurrentModelName } from "./state.js";
|
|
10
10
|
import { directWriteProfile, profileAvailable } from "./config.js";
|
|
11
11
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
12
12
|
* replay routes through it too — a queued "/last" must never become a
|
|
@@ -66,6 +66,35 @@ export function dispatch(line, ctx) {
|
|
|
66
66
|
});
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
+
if (trimmed === "\x12expand") {
|
|
70
|
+
// W15: the expand key (ctrl+r) — /last aimed at a chosen cell.
|
|
71
|
+
// The TARGET is picked at press time: a LIVE tool cell toggles
|
|
72
|
+
// IMMEDIATELY in place (the compositor owns those rows and
|
|
73
|
+
// redraws them — the approval pause is exactly when the user
|
|
74
|
+
// reads a cut diff, and the key must answer then, never after
|
|
75
|
+
// the run). A COMMITTED cell can never toggle — history is never
|
|
76
|
+
// rewritten (ADR-0046) — so its expanded block and the empty
|
|
77
|
+
// answer queue on the chain like /last: the block lands as new
|
|
78
|
+
// content after any in-flight turn. The sentinel carries the
|
|
79
|
+
// control char so a typed "expand" turn is never intercepted.
|
|
80
|
+
const r = body.expandNext();
|
|
81
|
+
if (r.kind === "toggled") {
|
|
82
|
+
ctx.input.prompt(); // in place — the frame already repainted
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const land = async () => {
|
|
86
|
+
if (r.kind === "appended") {
|
|
87
|
+
for (const line of r.lines)
|
|
88
|
+
bodyLog(line);
|
|
89
|
+
}
|
|
90
|
+
else if (r.kind === "none") {
|
|
91
|
+
bodyLog("[nothing to expand]");
|
|
92
|
+
}
|
|
93
|
+
ctx.input.prompt();
|
|
94
|
+
};
|
|
95
|
+
ctx.chainRef.current = ctx.chainRef.current.then(land);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
69
98
|
if (trimmed === "/status") {
|
|
70
99
|
// B area: session id, durable event count, and the ~ context
|
|
71
100
|
// estimate — all read straight from the live session, nothing
|
|
@@ -164,19 +193,76 @@ export function dispatch(line, ctx) {
|
|
|
164
193
|
return;
|
|
165
194
|
}
|
|
166
195
|
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
196
|
+
// W18: the compaction indicator — the whole call runs under
|
|
197
|
+
// a live status row (the INDETERMINATE form: the summary is
|
|
198
|
+
// ONE adapter call, no fraction exists — kiso never invents
|
|
199
|
+
// a percentage), with a REAL cancel: esc aborts the signal,
|
|
200
|
+
// which the runtime observes at every phase boundary.
|
|
201
|
+
const abort = new AbortController();
|
|
202
|
+
let compactCancelled = false;
|
|
203
|
+
const onEscape = () => {
|
|
204
|
+
if (abort.signal.aborted)
|
|
205
|
+
return;
|
|
206
|
+
compactCancelled = true;
|
|
207
|
+
abort.abort();
|
|
208
|
+
};
|
|
209
|
+
ctx.input.onEscape(onEscape);
|
|
210
|
+
let compactStart = 0;
|
|
211
|
+
let compactTimer = null;
|
|
212
|
+
// the `as` on the initializer keeps the flow type the full union —
|
|
213
|
+
// onStart fills this during the call, but a closure assignment
|
|
214
|
+
// never re-narrows the outer scope (it would read `never`)
|
|
215
|
+
let compactInfo = null;
|
|
216
|
+
// the ctx estimate BEFORE the summarized event lands (the used
|
|
217
|
+
// fraction — the recap's "ctx 91% → 34%" drops after compacting)
|
|
218
|
+
let ctxBefore = null;
|
|
219
|
+
const compacting = (info) => {
|
|
220
|
+
const text = (elapsed) => `▘ compacting · ${info.rounds} rounds · ~${kUnit(info.tokens)} tokens · ${Math.max(0, elapsed)}s`;
|
|
221
|
+
compactStart = Date.now();
|
|
222
|
+
ctxBefore = Math.round(ctx.estimateCtx() * 100);
|
|
223
|
+
dock.setStatus(text(0), "esc to cancel");
|
|
224
|
+
compactTimer = setInterval(() => {
|
|
225
|
+
dock.setStatus(text(Math.round((Date.now() - compactStart) / 1000)), "esc to cancel");
|
|
226
|
+
}, 1000);
|
|
227
|
+
};
|
|
167
228
|
try {
|
|
168
|
-
const result = await ctx.session.summarize(
|
|
229
|
+
const result = await ctx.session.summarize({
|
|
230
|
+
signal: abort.signal,
|
|
231
|
+
onStart: (info) => {
|
|
232
|
+
compactInfo = info;
|
|
233
|
+
compacting(info);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
169
236
|
if (result === null) {
|
|
170
237
|
body.notice("[/compact] nothing to compact — fewer than 5 rounds yet");
|
|
171
238
|
}
|
|
172
239
|
else {
|
|
173
|
-
|
|
240
|
+
// W18: the settled RECAP replaces the bare saved-token
|
|
241
|
+
// notice — it names what actually happened: the covered
|
|
242
|
+
// rounds, the one summary, the savings, the ctx drop
|
|
243
|
+
// (the estimate BEFORE vs AFTER — the same chars/4
|
|
244
|
+
// proxy the status bar shows, marked ~), and the time.
|
|
245
|
+
const ctxAfter = Math.round(ctx.estimateCtx() * 100);
|
|
246
|
+
const elapsed = compactStart > 0 ? ((Date.now() - compactStart) / 1000).toFixed(1) : "?";
|
|
247
|
+
// a non-null result implies onStart ran — the "?" is
|
|
248
|
+
// reachable only at the type level
|
|
249
|
+
body.notice(`[/compact] ▞ compacted · ${compactInfo?.rounds ?? "?"} rounds → 1 summary · saved ~${kUnit(result.savedTokens)} · ctx ${ctxBefore ?? "?"}% → ${ctxAfter}% · ${elapsed}s`);
|
|
174
250
|
}
|
|
175
251
|
}
|
|
176
252
|
catch (err) {
|
|
177
253
|
// Honest failure: nothing was persisted, the session
|
|
178
254
|
// is unchanged (ADR-0044 crash semantics).
|
|
179
|
-
|
|
255
|
+
if (compactCancelled) {
|
|
256
|
+
body.notice("[/compact] cancelled — nothing was persisted");
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
body.notice(`[/compact] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
if (compactTimer !== null)
|
|
264
|
+
clearInterval(compactTimer);
|
|
265
|
+
ctx.paintIdle();
|
|
180
266
|
}
|
|
181
267
|
ctx.input.prompt();
|
|
182
268
|
});
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { readFileSync, rmSync } from "node:fs";
|
|
26
26
|
import { createInterface } from "node:readline";
|
|
27
27
|
import { join } from "node:path";
|
|
28
|
-
import { Body, Editor,
|
|
28
|
+
import { Body, Editor, bannerLines, palette, renderSessionLine } from "@vincemakes/kiso-tui";
|
|
29
29
|
import { escapeTerminal } from "@vincemakes/kiso-tui";
|
|
30
30
|
import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
|
|
31
31
|
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
@@ -40,24 +40,6 @@ import { resume } from "./resume.js";
|
|
|
40
40
|
// The moved exports stay reachable from this entry — the test imports
|
|
41
41
|
// (project-trust, coding-agent) never change (B4: zero assertion changes).
|
|
42
42
|
export { applyProjectMerges } from "./trust-ui.js";
|
|
43
|
-
/** The banner: the block-letter logo (design fixed). TTY only — pipes, e2e
|
|
44
|
-
* drivers, and CI see byte-for-byte the old output; the extensions line
|
|
45
|
-
* merges into the third row on TTY and stays a standalone line off-TTY.
|
|
46
|
-
* v2a: the logo rows stay dim; the TAGLINE (row 2) is the blue identity
|
|
47
|
-
* accent. */
|
|
48
|
-
function startupBanner() {
|
|
49
|
-
// v3 §01: the banner is block-split — three independent logo rows
|
|
50
|
-
// (TOP / tagline / BOTTOM), then TWO info rows (version,
|
|
51
|
-
// extensions), each truncated at the window width; < 40 columns
|
|
52
|
-
// skips the logo. The historical `[N extensions: names]` text rides
|
|
53
|
-
// the extensions row verbatim (the e2e assertions keep matching).
|
|
54
|
-
const p = palette();
|
|
55
|
-
// A pty without a winsize reports columns = 0 (not undefined) — treat
|
|
56
|
-
// it as the default width, never as a 0-column truncation.
|
|
57
|
-
const W = process.stdout.columns ?? 0;
|
|
58
|
-
const rows = bannerLines(W > 0 ? W : 80, VERSION, bannerExtensionText().replace(/^ · /, ""));
|
|
59
|
-
return `${rows.map((r) => `${p.dim}${r}${p.reset}`).join("\n")}\n`;
|
|
60
|
-
}
|
|
61
43
|
/** The v2b behavior, unchanged: readline owns the line, SIGINT, and the
|
|
62
44
|
* prompt. Only ever constructed when stdin is NOT a TTY. The rl starts
|
|
63
45
|
* consuming stdin at construction (main), so 'line' events are buffered
|
|
@@ -88,6 +70,10 @@ function readlineInput(rl) {
|
|
|
88
70
|
onEscape() {
|
|
89
71
|
/* readline has no bare-Esc semantics — ignored. */
|
|
90
72
|
},
|
|
73
|
+
onExpand() {
|
|
74
|
+
/* readline has no ctrl+r binding — ignored (W15 rides the
|
|
75
|
+
* editor path only). */
|
|
76
|
+
},
|
|
91
77
|
question(query, cb) {
|
|
92
78
|
rl.question(query, cb);
|
|
93
79
|
},
|
|
@@ -130,6 +116,9 @@ function editorInput(editor) {
|
|
|
130
116
|
onEscape(cb) {
|
|
131
117
|
editor.onEscape(cb);
|
|
132
118
|
},
|
|
119
|
+
onExpand(cb) {
|
|
120
|
+
editor.onExpand(cb);
|
|
121
|
+
},
|
|
133
122
|
question(query, cb) {
|
|
134
123
|
editor.question(query, cb);
|
|
135
124
|
},
|
|
@@ -163,8 +152,10 @@ function makeLineInput() {
|
|
|
163
152
|
if (process.stdin.isTTY) {
|
|
164
153
|
const editor = new Editor(() => (dock.active ? dock.redraw() : editor.selfRender()));
|
|
165
154
|
editor.enter();
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
// W6: the box's prompt goes light — "› " (the box already says
|
|
156
|
+
// "input lives here"; the line-mode path keeps the brick ▌, so
|
|
157
|
+
// pipe bytes do not change)
|
|
158
|
+
dock.bindInput(() => editor.dockState(), "› ");
|
|
168
159
|
dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
|
|
169
160
|
return editorInput(editor);
|
|
170
161
|
}
|
|
@@ -187,11 +178,13 @@ function bannerExtensionText() {
|
|
|
187
178
|
parts.push(`project: ${projectExtensions.map(label).join(", ")}`);
|
|
188
179
|
return ` · [${total} extension${total === 1 ? "" : "s"}: ${parts.join(" · ")}]`;
|
|
189
180
|
}
|
|
190
|
-
/** E1: the startup banner line(s) — TTY: logo + merged extensions
|
|
191
|
-
*
|
|
192
|
-
|
|
181
|
+
/** E1: the startup banner line(s) — TTY: logo + merged extensions + the
|
|
182
|
+
* W5 resume list as a LIVE banner cell (W1: the tier re-derives on
|
|
183
|
+
* resize; the resume list re-gates with the tier); off-TTY: the
|
|
184
|
+
* historical `[N extensions: ...]` standalone line (zero change). */
|
|
185
|
+
function extensionsBanner(resume = []) {
|
|
193
186
|
if (process.stdout.isTTY) {
|
|
194
|
-
|
|
187
|
+
body.banner(VERSION, bannerExtensionText().replace(/^ · /, ""), resume);
|
|
195
188
|
return;
|
|
196
189
|
}
|
|
197
190
|
const text = bannerExtensionText();
|
|
@@ -199,6 +192,19 @@ function extensionsBanner() {
|
|
|
199
192
|
return;
|
|
200
193
|
bodyLog(`${text}\n`);
|
|
201
194
|
}
|
|
195
|
+
/** W5: the opening-screen resume list — up to 3 recent sessions, newest
|
|
196
|
+
* first, the CURRENT session excluded (it is not something to pick back
|
|
197
|
+
* up). Every field already exists behind the store's SessionMeta (the
|
|
198
|
+
* `kiso sessions` line shows the same data) — only the projection and
|
|
199
|
+
* the sort live here. */
|
|
200
|
+
function recentSessions(id, agent) {
|
|
201
|
+
return agent
|
|
202
|
+
.sessions()
|
|
203
|
+
.filter((m) => m.id !== id)
|
|
204
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
205
|
+
.slice(0, 3)
|
|
206
|
+
.map(({ title, events, runs, updatedAt }) => ({ title, events, runs, updatedAt }));
|
|
207
|
+
}
|
|
202
208
|
/**
|
|
203
209
|
* A area: the coding-agent system prompt — ONE constant, byte-stable for the
|
|
204
210
|
* session's lifetime (D area). Kept under ~80 lines; no template engine.
|
|
@@ -409,7 +415,7 @@ async function main() {
|
|
|
409
415
|
applyConfigMode();
|
|
410
416
|
const session = await agent.session({ id });
|
|
411
417
|
bodyLog(`session ${id}\n`);
|
|
412
|
-
extensionsBanner();
|
|
418
|
+
extensionsBanner(recentSessions(id, agent));
|
|
413
419
|
faux = currentFaux;
|
|
414
420
|
await chat(session, faux, input, resolveAutoCompact(mergedConfig));
|
|
415
421
|
break;
|
|
@@ -439,7 +445,7 @@ async function main() {
|
|
|
439
445
|
}
|
|
440
446
|
case "help": {
|
|
441
447
|
const p = palette();
|
|
442
|
-
console.log(`${p.dim}${bannerLines(80, VERSION, "").join("\n")}${p.reset}\n\n` +
|
|
448
|
+
console.log(`${p.dim}${bannerLines(80, process.stdout.rows ?? 0, VERSION, "").join("\n")}${p.reset}\n\n` +
|
|
443
449
|
"kiso — the coding agent that survives kill -9\n\n" +
|
|
444
450
|
" kiso [sessionId] interactive session (default command)\n" +
|
|
445
451
|
" kiso chat [sessionId] same as above\n" +
|
|
@@ -457,7 +463,7 @@ async function main() {
|
|
|
457
463
|
const agent = await makeAgent(fauxSkip(id));
|
|
458
464
|
const session = await agent.session({ id });
|
|
459
465
|
bodyLog(`session ${id}\n`);
|
|
460
|
-
extensionsBanner();
|
|
466
|
+
extensionsBanner(recentSessions(id, agent));
|
|
461
467
|
await chat(session, faux, input, autoCompactFromEnv());
|
|
462
468
|
break;
|
|
463
469
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface LineInput {
|
|
|
27
27
|
onSigint(cb: () => void): void;
|
|
28
28
|
onEot(cb: () => void): void;
|
|
29
29
|
onEscape(cb: () => void): void;
|
|
30
|
+
/** W15: the expand key (ctrl+r) — the chain-level action, never the
|
|
31
|
+
* editor's own interpretation. */
|
|
32
|
+
onExpand(cb: () => void): void;
|
|
30
33
|
question(query: string, cb: (answer: string) => void): void;
|
|
31
34
|
cancelQuestion(): void;
|
|
32
35
|
emitLine(line: string): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "kiso CLI — the coding-agent reference product: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@vincemakes/kiso-provider-openai": "0.1.31",
|
|
25
25
|
"@vincemakes/kiso-runtime": "0.1.31",
|
|
26
26
|
"@vincemakes/kiso-tools-node": "0.1.31",
|
|
27
|
-
"@vincemakes/kiso-tui": "0.1.
|
|
27
|
+
"@vincemakes/kiso-tui": "0.1.38"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^26.1.2",
|