@vincemakes/kiso-code 0.50.0 → 0.51.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.
package/dist/chat.js CHANGED
@@ -9,7 +9,7 @@ import { escapeTerminal, cacheHitPct, decodeRate, palette, renderEvent, renderRe
9
9
  import { askView, coldResumeLine, coldResumeView, deletionRiskHint, editFileHunksDiff, hunksOf, writeFileDiff } from "@vincemakes/kiso-tui";
10
10
  import { canonicalTargetPath, isProtectedPath, protectedIdentity, shellProgressPath } from "@vincemakes/kiso-tools-node";
11
11
  import { deliveryLines, liveTasks, lostToldOf, mergedConfig, queuedSwitchLines, setExitTasks, tasksFor, taskWhatOf } from "./state.js";
12
- import { budgetSpentLine, taskCounts, taskNoticeLines, taskNoticeRows, tasksForDisplay } from "./task-notice.js";
12
+ import { budgetSpentLine, cappedWaitLine, taskCounts, taskNoticeLines, taskNoticeRows, tasksForDisplay } from "./task-notice.js";
13
13
  import { current as inRunCompaction, takeKept as takeKeptCompaction } from "./in-run-compaction.js";
14
14
  import { echoText } from "@vincemakes/kiso-tui-cells/render";
15
15
  import { canonicalizeUsage, RunClosedError } from "@vincemakes/kiso-runtime";
@@ -1063,6 +1063,9 @@ onInputLanded) {
1063
1063
  reason = m[1];
1064
1064
  }
1065
1065
  body.toolResult(ev.callId, { content: text, isError: ev.isError, reason, ...((ev.tags ?? []).includes("created") ? { created: true } : {}) });
1066
+ const capped = ev.isError ? null : cappedWaitLine(text);
1067
+ if (capped !== null)
1068
+ body.notice(capped);
1066
1069
  break;
1067
1070
  }
1068
1071
  case "text_delta":
@@ -1642,7 +1645,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1642
1645
  dock.setLive(liveRow(compactingStatus(runGlyph, summary.rounds, summary.tokens, Math.round((Date.now() - summary.since) / 1000), W - 20, retryOnRow(), summary.progress, "auto"), ["esc stops"], W));
1643
1646
  return;
1644
1647
  }
1645
- dock.setLive(workingRow(runGlyph, runStart, runUsage.out, lastTokPerSec, rowWidth(), retryOnRow(), detachableNow()));
1648
+ dock.setLive(workingRow(runGlyph, runStart, runUsage.out, rowWidth(), retryOnRow(), detachableNow()));
1646
1649
  dock.setBar(barFor(session, { tokPerSec: lastTokPerSec, tasks: taskCountsNow() }));
1647
1650
  };
1648
1651
  // W19: under plan the idle row makes the posture unmistakable — the W4
package/dist/config.d.ts CHANGED
@@ -31,6 +31,20 @@
31
31
  * with an invalid value is likewise loud; unknown top-level keys are
32
32
  * ignored (forward compatibility).
33
33
  */
34
+ /** The reasoning-effort levels a profile may declare: the registry's own
35
+ * vocabulary (without `default`, which is "send nothing"). */
36
+ declare const EFFORT_LEVELS: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
37
+ type EffortLevel = (typeof EFFORT_LEVELS)[number];
38
+ /** A profile's own claim about its route's effort levels (the same kind of
39
+ * claim as `contextWindow`: yours, not ours). */
40
+ export interface ProfileEffort {
41
+ readonly levels: readonly EffortLevel[];
42
+ /** the level the route uses when none is sent; omit or null = unknown */
43
+ readonly default?: EffortLevel | null;
44
+ /** the route's spelling where it differs from the level's name, e.g.
45
+ * `{ "none": "off" }`; each key must be one of `levels` */
46
+ readonly values?: Readonly<Partial<Record<EffortLevel, string>>>;
47
+ }
34
48
  /** OR-1: `openai-responses` is a DIALECT, not a vendor — the same kind
35
49
  * drives the first-party Responses API and the ChatGPT subscription
36
50
  * backend, and which one it is follows the profile's baseUrl (and so
@@ -67,6 +81,15 @@ export interface ModelProfile {
67
81
  * registry, where every figure has to carry a source.
68
82
  */
69
83
  readonly contextWindow?: number;
84
+ /**
85
+ * The reasoning-effort levels this profile's route accepts, for a route
86
+ * the registry has no row for (a gateway, a new model). It enables the
87
+ * effort picker and the `/model <profile> <level>` form; the levels are
88
+ * YOUR claim. A route that refuses a level answers 400, and nothing here
89
+ * hides it. `kiso`'s own table wins nowhere over this: your declaration
90
+ * replaces the registry's levels for this model at this endpoint.
91
+ */
92
+ readonly effort?: ProfileEffort;
70
93
  /**
71
94
  * What this endpoint FORWARDS to, when `baseUrl` is a local proxy — a URL
72
95
  * (`https://gateway.example/v1`) or a name (`my gateway`). It never
@@ -88,6 +111,10 @@ export interface ModelProfile {
88
111
  */
89
112
  readonly headers?: Readonly<Record<string, string>>;
90
113
  }
114
+ /** Hand every profile's declared effort levels to the registry's lookup
115
+ * (called once, right after the config is merged). A profile without a
116
+ * declaration is not touched, and one that declares nothing changes nothing. */
117
+ export declare function applyEffortDeclarations(cfg: KisoConfig): void;
91
118
  export interface AutoCompactConfig {
92
119
  readonly thresholdRatio: number;
93
120
  }
@@ -228,3 +255,4 @@ export declare function resolveAutoCompact(merged: KisoConfig): AutoCompactConfi
228
255
  * gate, and it could not anyway, since an untrusted project's config is
229
256
  * never read. */
230
257
  export declare function resolveProjectTrustPolicy(merged: KisoConfig): "ask" | "never";
258
+ export {};
package/dist/config.js CHANGED
@@ -37,7 +37,10 @@ import { join, win32 } from "node:path";
37
37
  import { kisoHome } from "./state.js";
38
38
  import { MODE_VALUES, parseMode } from "./mode.js";
39
39
  import { AuthError, effectiveBaseUrl, endpointCredentialId, getCredential, providerIdOf } from "./auth/credentials.js";
40
- import { BUILTIN_MANIFESTS } from "@vincemakes/kiso-runtime/internal";
40
+ import { BUILTIN_MANIFESTS, declareEffort } from "@vincemakes/kiso-runtime/internal";
41
+ /** The reasoning-effort levels a profile may declare: the registry's own
42
+ * vocabulary (without `default`, which is "send nothing"). */
43
+ const EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
41
44
  /** Header names a profile may not set: credentials (a key never lives in the
42
45
  * config file) and the framing the adapter owns. */
43
46
  const REFUSED_HEADERS = new Set(["authorization", "proxy-authorization", "x-api-key", "api-key", "cookie", "host", "content-length", "content-type", "transfer-encoding", "connection"]);
@@ -59,6 +62,61 @@ function parseHeaders(field, raw, fail) {
59
62
  }
60
63
  return out;
61
64
  }
65
+ function parseEffort(field, raw, fail) {
66
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
67
+ fail(field, "expected an object: { levels: [...], default?: ..., values?: { ... } }");
68
+ const o = raw;
69
+ for (const k of Object.keys(o))
70
+ if (k !== "levels" && k !== "default" && k !== "values")
71
+ fail(`${field}.${k}`, "unknown key (levels, default and values are the only ones)");
72
+ const known = (v) => typeof v === "string" && EFFORT_LEVELS.includes(v);
73
+ if (!Array.isArray(o.levels) || o.levels.length === 0 || o.levels.length > EFFORT_LEVELS.length)
74
+ fail(`${field}.levels`, `expected 1 to ${EFFORT_LEVELS.length} levels`);
75
+ const levels = [];
76
+ for (const l of o.levels) {
77
+ if (!known(l))
78
+ fail(`${field}.levels`, `${JSON.stringify(l)} is not a level (${EFFORT_LEVELS.join(", ")})`);
79
+ if (levels.includes(l))
80
+ fail(`${field}.levels`, `${l} is listed twice`);
81
+ levels.push(l);
82
+ }
83
+ let def;
84
+ if (o.default !== undefined && o.default !== null) {
85
+ if (!known(o.default) || !levels.includes(o.default))
86
+ fail(`${field}.default`, "expected null or one of the listed levels");
87
+ def = o.default;
88
+ }
89
+ else if (o.default === null)
90
+ def = null;
91
+ let values;
92
+ if (o.values !== undefined) {
93
+ if (o.values === null || typeof o.values !== "object" || Array.isArray(o.values))
94
+ fail(`${field}.values`, "expected an object of level names to the route's spelling");
95
+ values = {};
96
+ for (const [k, v] of Object.entries(o.values)) {
97
+ if (!known(k) || !levels.includes(k))
98
+ fail(`${field}.values.${k}`, "expected one of the listed levels");
99
+ if (typeof v !== "string" || !/^[A-Za-z0-9_.-]{1,24}$/.test(v))
100
+ fail(`${field}.values.${k}`, "expected a short word (letters, digits, . _ -)");
101
+ values[k] = v;
102
+ }
103
+ }
104
+ return { levels, ...(def !== undefined ? { default: def } : {}), ...(values !== undefined ? { values } : {}) };
105
+ }
106
+ /** Hand every profile's declared effort levels to the registry's lookup
107
+ * (called once, right after the config is merged). A profile without a
108
+ * declaration is not touched, and one that declares nothing changes nothing. */
109
+ export function applyEffortDeclarations(cfg) {
110
+ for (const profile of Object.values(cfg.models ?? {})) {
111
+ if (profile.effort === undefined)
112
+ continue;
113
+ declareEffort(profile.model, profile.baseUrl, {
114
+ levels: profile.effort.levels,
115
+ default: profile.effort.default ?? null,
116
+ ...(profile.effort.values !== undefined ? { values: profile.effort.values } : {}),
117
+ });
118
+ }
119
+ }
62
120
  const KINDS = ["openai-compat", "anthropic", "openai-responses"];
63
121
  export class ConfigError extends Error {
64
122
  }
@@ -192,6 +250,7 @@ export function parseConfig(text, source) {
192
250
  if (p.upstream !== undefined && (typeof p.upstream !== "string" || p.upstream.trim() === ""))
193
251
  fail(`models.${name}.upstream`, "expected the URL or name of what this endpoint forwards to");
194
252
  const headers = p.headers === undefined ? undefined : parseHeaders(`models.${name}.headers`, p.headers, fail);
253
+ const effort = p.effort === undefined ? undefined : parseEffort(`models.${name}.effort`, p.effort, fail);
195
254
  models[name] = {
196
255
  kind: p.kind,
197
256
  model: p.model,
@@ -202,6 +261,7 @@ export function parseConfig(text, source) {
202
261
  ...(typeof p.contextWindow === "number" ? { contextWindow: p.contextWindow } : {}),
203
262
  ...(typeof p.upstream === "string" ? { upstream: p.upstream.trim() } : {}),
204
263
  ...(headers !== undefined ? { headers } : {}),
264
+ ...(effort !== undefined ? { effort } : {}),
205
265
  };
206
266
  }
207
267
  out.models = models;
@@ -14,7 +14,7 @@ import { migrationNotice, pendingLegacyIds, planMigration, runMigration } from "
14
14
  import { askUi, resolveProjectTrust } from "./trust-ui.js";
15
15
  import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
16
16
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
17
- import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveContextWindow, resolveModel } from "./config.js";
17
+ import { applyEffortDeclarations, loadProjectConfig, loadUserConfig, mergeConfigs, resolveContextWindow, resolveModel } from "./config.js";
18
18
  import { builtInLayer } from "./builtin.js";
19
19
  import { recordLearnedWindow, useLearnedWindows } from "./learned-windows.js";
20
20
  import { compactionDiscardedNotice, contextWindowTokens, knownContextWindow, unknownWindowNotice, windowLearnedNotice } from "./chat.js";
@@ -163,6 +163,9 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
163
163
  const userCfg = loadUserConfig();
164
164
  const projectCfg = loadProjectConfig(process.cwd(), project !== null);
165
165
  const merged = mergeConfigs(userCfg, projectCfg);
166
+ // a profile's own effort levels reach the registry's lookup before the
167
+ // picker, `/model <profile> <level>` or a resumed session reads them
168
+ applyEffortDeclarations(merged);
166
169
  // 0.40.6: /settings names each value's layer from these
167
170
  settingsLayers.user = userCfg;
168
171
  settingsLayers.project = projectCfg;
package/dist/dispatch.js CHANGED
@@ -100,7 +100,9 @@ function effortAxis(p) {
100
100
  // cursor could NOT land where the previous selection asked.
101
101
  ...(carried !== undefined && carried !== "default" && carriedIdx < 0 && defaultIdx >= 0
102
102
  ? { levelNote: `effort ${carried} \u2192 ${levels[defaultIdx]}: this model's default (it has no ${carried})` }
103
- : {}),
103
+ : effort.declared === true
104
+ ? { levelNote: "levels declared in your profile" }
105
+ : {}),
104
106
  };
105
107
  }
106
108
  /** What signs a profile in, for the listing: `stored key` when a key in
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ import { activeStoreDir, setActiveStoreDir, agentModel, atFiles, body, bodyLog,
45
45
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
46
46
  import { barFor, chat, contextWindowTokens, displayCtxRatio, microcompactThresholdFor, statusModelLabel } from "./chat.js";
47
47
  import { preferences, usePreferences } from "./preferences.js";
48
- import { settingsLayers, setChildTurnBudget, stopAllTasks, tasksFor } from "./state.js";
48
+ import { settingsLayers, setChildTurnBudget, setPrintRun, stopAllTasks, tasksFor } from "./state.js";
49
49
  import { tasksForDisplay } from "./task-notice.js";
50
50
  import { finishChild } from "./child-result.js";
51
51
  import { loadUserConfig, resolveAutoCompact } from "./config.js";
@@ -1305,6 +1305,7 @@ async function main() {
1305
1305
  process.exit(2);
1306
1306
  }
1307
1307
  args.splice(printIdx, 2);
1308
+ setPrintRun();
1308
1309
  }
1309
1310
  // merge round B: --model <profile|provider/model> — the top of the model
1310
1311
  // precedence chain; the value flows into createCodingAgent's config resolution.
package/dist/replay.js CHANGED
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import { echoText } from "@vincemakes/kiso-tui-cells/render";
23
23
  import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
24
- import { taskNoticeLines, taskNoticeRows, taskWhat } from "./task-notice.js";
24
+ import { cappedWaitLine, taskNoticeLines, taskNoticeRows, taskWhat } from "./task-notice.js";
25
25
  /** How many turns stay on screen in full. */
26
26
  const FULL_TURNS = 2;
27
27
  /** A user turn's opening line, or null when this input does not open one
@@ -101,6 +101,10 @@ function replayTurn(body, turn, tasks) {
101
101
  const created = (e.tags ?? []).includes("created");
102
102
  body.toolResult(String(e.callId), { content: text, isError: e.isError === true, reason, untimed: true, ...(created ? { created: true } : {}) });
103
103
  results.add(String(e.callId));
104
+ // 0.50.1: the capped wait's TASK row, as the live run drew it
105
+ const capped = e.isError === true ? null : cappedWaitLine(text);
106
+ if (capped !== null)
107
+ body.notice(capped);
104
108
  break;
105
109
  }
106
110
  case "text_delta":
package/dist/resume.js CHANGED
@@ -32,7 +32,7 @@ export async function resume(session, prompt, faux, input) {
32
32
  runUsage = u;
33
33
  if (!dock.active)
34
34
  return;
35
- dock.setLive(workingRow(runGlyph, runStart, u.out, null, width(), retryOnRow()));
35
+ dock.setLive(workingRow(runGlyph, runStart, u.out, width(), retryOnRow()));
36
36
  dock.setBar(barFor(session, { tokPerSec: null }));
37
37
  };
38
38
  const paintIdle = () => {
package/dist/state.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare function kisoHome(): string;
29
29
  * these state setters have run.
30
30
  */
31
31
  export declare function secretEnvNamesOf(models: Readonly<Record<string, unknown>>): readonly string[];
32
+ export declare function setPrintRun(): void;
32
33
  export declare function codingToolOptions(): {
33
34
  readonly workspaceRoot: string;
34
35
  readonly excludeRoots: readonly string[];
@@ -37,7 +38,8 @@ export declare function codingToolOptions(): {
37
38
  readonly tasks: (sessionId: string | undefined) => ShellTasks | undefined;
38
39
  readonly observedRevision: (sessionId: string | undefined, full: string, kind?: "edit" | "write") => string | undefined;
39
40
  readonly limits?: {
40
- readonly minForegroundMs: number;
41
+ readonly minForegroundMs?: number;
42
+ readonly maxForegroundMs?: number;
41
43
  };
42
44
  };
43
45
  /** Amendment 8: what a session's task ran — a lost task's transcript row
package/dist/state.js CHANGED
@@ -43,11 +43,25 @@ export function secretEnvNamesOf(models) {
43
43
  .filter((n) => typeof n === "string" && n.length > 0)),
44
44
  ];
45
45
  }
46
+ /** 0.50.1 (ADR-0058 Amendment 16): `kiso -p` runs with nobody watching and
47
+ * stops its tasks when the one turn ends, so the foreground wait keeps the
48
+ * model's value — no ceiling. Set once, where `-p` is parsed. */
49
+ let printRun = false;
50
+ export function setPrintRun() {
51
+ printRun = true;
52
+ }
46
53
  export function codingToolOptions() {
47
54
  // KISO_MIN_FOREGROUND_MS — the test rigs' knob (like KISO_AUTO_DETACH_MS):
48
55
  // the least foreground wait (ADR-0058 Amendment 10, default 60 s). The
49
56
  // rigs that promote a command by a short wait lower it; nothing else does.
50
57
  const minForeground = Number.parseInt(process.env.KISO_MIN_FOREGROUND_MS ?? "", 10);
58
+ // KISO_MAX_FOREGROUND_MS — the rigs' knob for the ceiling (Amendment 15,
59
+ // default 5 min). `-p` has none whatever the knob says (Amendment 16).
60
+ const maxForeground = Number.parseInt(process.env.KISO_MAX_FOREGROUND_MS ?? "", 10);
61
+ const limits = {
62
+ ...(Number.isFinite(minForeground) && minForeground >= 0 ? { minForegroundMs: minForeground } : {}),
63
+ ...(printRun ? { maxForegroundMs: 0 } : Number.isFinite(maxForeground) && maxForeground >= 0 ? { maxForegroundMs: maxForeground } : {}),
64
+ };
51
65
  return {
52
66
  workspaceRoot: process.cwd(),
53
67
  excludeRoots: [kisoHome()],
@@ -55,7 +69,7 @@ export function codingToolOptions() {
55
69
  protectedFiles: protectedFiles(),
56
70
  tasks: tasksFor,
57
71
  observedRevision: observedRevisionFromLog,
58
- ...(Number.isFinite(minForeground) && minForeground >= 0 ? { limits: { minForegroundMs: minForeground } } : {}),
72
+ ...(Object.keys(limits).length > 0 ? { limits } : {}),
59
73
  };
60
74
  }
61
75
  /** ADR-0058 (3b): one TaskManager per session, its directory beside the
@@ -7,6 +7,13 @@ import type { NoticeMark } from "@vincemakes/kiso-tui";
7
7
  * (<what>) — ` — the parenthesis only when the command is known — and the
8
8
  * LAST ` — ` part opening with "it may still be running". One line each. */
9
9
  export declare function taskNoticeLines(items: readonly TaskDeliveryItem[], what?: (taskId: string) => string | undefined): string[];
10
+ /** 0.50.1: a command promoted at the foreground ceiling — its shell result
11
+ * opens `still running after <wait> ms (the limit; you asked <asked> ms);
12
+ * continued as background task <id>` — is said to the person as one TASK
13
+ * row: `t3 · moved to the background at the 5m limit · asked 6m 40s`. Only
14
+ * this case: every other promotion stays rowless (the task-start row is
15
+ * deferred, owner 2026-10-14). */
16
+ export declare function cappedWaitLine(result: string): string | null;
10
17
  /** Finding 0480-F9: the chain budget is spent — said to the person once,
11
18
  * so a chain that went quiet on its budget does not look idle. */
12
19
  export declare function budgetSpentLine(info: {
@@ -36,6 +36,19 @@ export function taskNoticeLines(items, what) {
36
36
  }
37
37
  return rows;
38
38
  }
39
+ /** 0.50.1: a command promoted at the foreground ceiling — its shell result
40
+ * opens `still running after <wait> ms (the limit; you asked <asked> ms);
41
+ * continued as background task <id>` — is said to the person as one TASK
42
+ * row: `t3 · moved to the background at the 5m limit · asked 6m 40s`. Only
43
+ * this case: every other promotion stays rowless (the task-start row is
44
+ * deferred, owner 2026-10-14). */
45
+ export function cappedWaitLine(result) {
46
+ const m = /^still running after (\d+) ms \(the limit; you asked (\d+) ms\); continued as background task (\S+) /.exec(result);
47
+ if (m === null)
48
+ return null;
49
+ const limit = elapsedLabel(Number(m[1]) / 1000).replace(/(?<=m) 0s$/, "");
50
+ return `✦ task ${m[3]} · moved to the background at the ${limit} limit · asked ${elapsedLabel(Number(m[2]) / 1000)}`;
51
+ }
39
52
  /** Finding 0480-F9: the chain budget is spent — said to the person once,
40
53
  * so a chain that went quiet on its budget does not look idle. */
41
54
  export function budgetSpentLine(info) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "kiso-code \u2014 the coding agent on kiso, the durable runtime for AI agents: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,19 +18,19 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.50.0",
22
- "@vincemakes/kiso-core": "0.50.0",
23
- "@vincemakes/kiso-evals": "0.50.0",
24
- "@vincemakes/kiso-mcp-ext": "0.50.0",
25
- "@vincemakes/kiso-provider-anthropic": "0.50.0",
26
- "@vincemakes/kiso-provider-openai": "0.50.0",
27
- "@vincemakes/kiso-provider-openai-responses": "0.50.0",
28
- "@vincemakes/kiso-runtime": "0.50.0",
29
- "@vincemakes/kiso-skills-ext": "0.50.0",
30
- "@vincemakes/kiso-subagent-ext": "0.50.0",
31
- "@vincemakes/kiso-tools-node": "0.50.0",
32
- "@vincemakes/kiso-tui": "0.50.0",
33
- "@vincemakes/kiso-tui-cells": "0.50.0"
21
+ "@vincemakes/kiso-ask-ext": "0.51.0",
22
+ "@vincemakes/kiso-core": "0.51.0",
23
+ "@vincemakes/kiso-evals": "0.51.0",
24
+ "@vincemakes/kiso-mcp-ext": "0.51.0",
25
+ "@vincemakes/kiso-provider-anthropic": "0.51.0",
26
+ "@vincemakes/kiso-provider-openai": "0.51.0",
27
+ "@vincemakes/kiso-provider-openai-responses": "0.51.0",
28
+ "@vincemakes/kiso-runtime": "0.51.0",
29
+ "@vincemakes/kiso-skills-ext": "0.51.0",
30
+ "@vincemakes/kiso-subagent-ext": "0.51.0",
31
+ "@vincemakes/kiso-tools-node": "0.51.0",
32
+ "@vincemakes/kiso-tui": "0.51.0",
33
+ "@vincemakes/kiso-tui-cells": "0.51.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",