@vincemakes/kiso-code 0.40.5 → 0.40.6
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/auth/credentials.d.ts +12 -0
- package/dist/auth/credentials.js +25 -0
- package/dist/chat.d.ts +3 -4
- package/dist/chat.js +4 -7
- package/dist/config.js +24 -2
- package/dist/dispatch.js +45 -24
- package/dist/index.js +41 -4
- package/dist/preferences.d.ts +14 -0
- package/dist/preferences.js +64 -0
- package/dist/settings.d.ts +37 -0
- package/dist/settings.js +79 -0
- package/dist/state.d.ts +10 -0
- package/dist/state.js +4 -0
- package/package.json +15 -15
|
@@ -49,6 +49,18 @@ export declare function effectiveBaseUrl(kind: string, baseUrl?: string): string
|
|
|
49
49
|
/** The provider identity a profile's credential is stored under, or null
|
|
50
50
|
* for an origin nobody recognizes (custom: env var only). */
|
|
51
51
|
export declare function providerIdOf(kind: string, baseUrl?: string): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* 0.40.6 — the store id for a GATEWAY's own key: `endpoint:<origin>`.
|
|
54
|
+
*
|
|
55
|
+
* A vendor's credential is stored under the vendor and only ever reaches
|
|
56
|
+
* the vendor's origin (R1 above). A gateway has no vendor identity, so its
|
|
57
|
+
* key is stored under the one thing that names where it may go — its
|
|
58
|
+
* ORIGIN (scheme, host, port). `kiso login --endpoint <url>` writes it; a
|
|
59
|
+
* profile whose endpoint has exactly that origin reads it; no other origin
|
|
60
|
+
* ever does, a local forwarder in front of the gateway included. Null for
|
|
61
|
+
* anything that is not an http(s) URL — never a guess.
|
|
62
|
+
*/
|
|
63
|
+
export declare function endpointCredentialId(url: string | undefined): string | null;
|
|
52
64
|
export declare function authPath(): string;
|
|
53
65
|
export declare function readAuthFile(path?: string): AuthFile;
|
|
54
66
|
export declare function getCredential(providerId: string, path?: string): Credential | undefined;
|
package/dist/auth/credentials.js
CHANGED
|
@@ -133,6 +133,31 @@ function originOf(baseUrl) {
|
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* 0.40.6 — the store id for a GATEWAY's own key: `endpoint:<origin>`.
|
|
138
|
+
*
|
|
139
|
+
* A vendor's credential is stored under the vendor and only ever reaches
|
|
140
|
+
* the vendor's origin (R1 above). A gateway has no vendor identity, so its
|
|
141
|
+
* key is stored under the one thing that names where it may go — its
|
|
142
|
+
* ORIGIN (scheme, host, port). `kiso login --endpoint <url>` writes it; a
|
|
143
|
+
* profile whose endpoint has exactly that origin reads it; no other origin
|
|
144
|
+
* ever does, a local forwarder in front of the gateway included. Null for
|
|
145
|
+
* anything that is not an http(s) URL — never a guess.
|
|
146
|
+
*/
|
|
147
|
+
export function endpointCredentialId(url) {
|
|
148
|
+
if (url === undefined)
|
|
149
|
+
return null;
|
|
150
|
+
let u;
|
|
151
|
+
try {
|
|
152
|
+
u = new URL(url);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (u.protocol !== "https:" && u.protocol !== "http:")
|
|
158
|
+
return null;
|
|
159
|
+
return `endpoint:${u.origin}`;
|
|
160
|
+
}
|
|
136
161
|
export function authPath() {
|
|
137
162
|
return join(kisoHome(), "auth.json");
|
|
138
163
|
}
|
package/dist/chat.d.ts
CHANGED
|
@@ -87,10 +87,9 @@ export declare function statedContextWindow(of?: {
|
|
|
87
87
|
/** CW-1: a window as the displays write it — `1M`, `1.05M`, `272K`,
|
|
88
88
|
* `1,048,576` (a figure that is not round is shown whole). */
|
|
89
89
|
export declare function windowLabel(tokens: number): string;
|
|
90
|
-
/** CW-1: the source, said
|
|
91
|
-
* form
|
|
92
|
-
|
|
93
|
-
export declare function windowSourceNote(w: StatedWindow | null, form: "short" | "long"): string;
|
|
90
|
+
/** CW-1: the window and its source, said — /status's line (0.40.6: the
|
|
91
|
+
* short form that rode each /model row is gone with its caller). */
|
|
92
|
+
export declare function windowSourceNote(w: StatedWindow | null): string;
|
|
94
93
|
export declare function contextWindowTokens(of?: {
|
|
95
94
|
readonly model: string;
|
|
96
95
|
readonly baseUrl?: string;
|
package/dist/chat.js
CHANGED
|
@@ -122,15 +122,12 @@ export function windowLabel(tokens) {
|
|
|
122
122
|
return `${tokens / 1000}K`;
|
|
123
123
|
return tokens.toLocaleString("en-US");
|
|
124
124
|
}
|
|
125
|
-
/** CW-1: the source, said
|
|
126
|
-
* form
|
|
127
|
-
|
|
128
|
-
export function windowSourceNote(w, form) {
|
|
125
|
+
/** CW-1: the window and its source, said — /status's line (0.40.6: the
|
|
126
|
+
* short form that rode each /model row is gone with its caller). */
|
|
127
|
+
export function windowSourceNote(w) {
|
|
129
128
|
if (w === null)
|
|
130
|
-
return
|
|
129
|
+
return `window unknown — compaction assumes ${windowLabel(DEFAULT_CONTEXT_WINDOW)}; set contextWindow on the profile to state it`;
|
|
131
130
|
const size = windowLabel(w.tokens);
|
|
132
|
-
if (form === "short")
|
|
133
|
-
return w.source === "model" ? `ctx ${size} inferred` : `ctx ${size}`;
|
|
134
131
|
switch (w.source) {
|
|
135
132
|
case "set":
|
|
136
133
|
return `window ${size}, as you set it`;
|
package/dist/config.js
CHANGED
|
@@ -32,7 +32,7 @@ import { readFileSync, statSync } from "node:fs";
|
|
|
32
32
|
import { homedir } from "node:os";
|
|
33
33
|
import { join } from "node:path";
|
|
34
34
|
import { kisoHome } from "./state.js";
|
|
35
|
-
import { AuthError, getCredential, providerIdOf } from "./auth/credentials.js";
|
|
35
|
+
import { AuthError, effectiveBaseUrl, endpointCredentialId, getCredential, providerIdOf } from "./auth/credentials.js";
|
|
36
36
|
import { BUILTIN_MANIFESTS } from "@vincemakes/kiso-runtime/internal";
|
|
37
37
|
/** Header names a profile may not set: credentials (a key never lives in the
|
|
38
38
|
* config file) and the framing the adapter owns. */
|
|
@@ -294,12 +294,34 @@ export function authForProfile(name, p) {
|
|
|
294
294
|
if (providerId !== null && !manifestTakesKey(providerId)) {
|
|
295
295
|
throw new ConfigError(`model ${name}: unavailable — not signed in: run \`kiso login ${providerId}\` (this provider takes no API key)`);
|
|
296
296
|
}
|
|
297
|
+
// 0.40.6: a GATEWAY's own key, stored for exactly this profile's origin
|
|
298
|
+
// (`kiso login --endpoint <url>`). The rule a vendor credential follows:
|
|
299
|
+
// stored first, the env var only when nothing is stored — and it goes to
|
|
300
|
+
// that origin alone (credentials.ts endpointCredentialId).
|
|
301
|
+
const endpointId = providerId === null ? endpointCredentialId(effectiveBaseUrl(p.kind, p.baseUrl)) : null;
|
|
302
|
+
if (endpointId !== null) {
|
|
303
|
+
let stored;
|
|
304
|
+
try {
|
|
305
|
+
stored = getCredential(endpointId);
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
if (err instanceof AuthError)
|
|
309
|
+
throw new ConfigError(`model ${name}: ${err.message}`);
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
if (stored?.type === "api-key")
|
|
313
|
+
return { type: "api-key", apiKey: stored.key, source: "store" };
|
|
314
|
+
}
|
|
297
315
|
if (p.apiKeyEnv === undefined)
|
|
298
316
|
return { type: "api-key", apiKey: "none", source: "none" };
|
|
299
317
|
const fromEnv = process.env[p.apiKeyEnv];
|
|
300
318
|
if (fromEnv !== undefined)
|
|
301
319
|
return { type: "api-key", apiKey: fromEnv, source: "env" };
|
|
302
|
-
const hint = providerId !== null
|
|
320
|
+
const hint = providerId !== null
|
|
321
|
+
? `run \`kiso login ${providerId}\` or set the env var ${p.apiKeyEnv}`
|
|
322
|
+
: endpointId !== null
|
|
323
|
+
? `set the env var ${p.apiKeyEnv}, or run \`kiso login --endpoint ${endpointId.slice("endpoint:".length)}\``
|
|
324
|
+
: `set the env var ${p.apiKeyEnv}`;
|
|
303
325
|
throw new ConfigError(`model ${name}: unavailable — no credential: ${hint} (configs never store keys, only the env-var name)`);
|
|
304
326
|
}
|
|
305
327
|
/** OR-2: does the provider's manifest admit an API key (or no auth at all)?
|
package/dist/dispatch.js
CHANGED
|
@@ -13,6 +13,10 @@ import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelNa
|
|
|
13
13
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
14
14
|
import { profileProviderLabel, providerLabel } from "./provider-label.js";
|
|
15
15
|
import { installedVersion, versionStatusLine } from "./stale-version.js";
|
|
16
|
+
import { preferences, setPreference } from "./preferences.js";
|
|
17
|
+
import { settingsRows } from "./settings.js";
|
|
18
|
+
import { floorOn, settingsLayers } from "./state.js";
|
|
19
|
+
import { currentGround } from "@vincemakes/kiso-tui-cells/render";
|
|
16
20
|
import { queuedSwitchLines } from "./state.js";
|
|
17
21
|
import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner, statedContextWindow, windowSourceNote } from "./chat.js";
|
|
18
22
|
import { authForProfile, directWriteProfile, profileAvailable, resolveContextWindow, unavailableReason } from "./config.js";
|
|
@@ -110,14 +114,6 @@ function signInNote(p) {
|
|
|
110
114
|
}
|
|
111
115
|
return p.apiKeyEnv ?? "no key";
|
|
112
116
|
}
|
|
113
|
-
/** CW-1 (owner, 2026-09-23): a profile's window on its /model row — the
|
|
114
|
-
* profile's own stated figure first, then the registry's chain for its
|
|
115
|
-
* model, endpoint and upstream; `inferred` marks the one no row states
|
|
116
|
-
* for that endpoint. */
|
|
117
|
-
function windowNoteOf(p) {
|
|
118
|
-
const w = statedContextWindow({ model: p.model, ...(p.baseUrl !== undefined ? { baseUrl: p.baseUrl } : {}), ...(p.upstream !== undefined ? { upstream: p.upstream } : {}) }, { configured: resolveContextWindow(mergedConfig, p) });
|
|
119
|
-
return windowSourceNote(w, "short");
|
|
120
|
-
}
|
|
121
117
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
122
118
|
* replay routes through it too — a queued "/last" must never become a
|
|
123
119
|
* user turn (v2c: the rl lives in main, so lines arrive earlier and the
|
|
@@ -383,18 +379,15 @@ export function dispatch(line, ctx) {
|
|
|
383
379
|
return;
|
|
384
380
|
}
|
|
385
381
|
if (trimmed === "\x14think") {
|
|
386
|
-
// §2.3 — ctrl+t
|
|
387
|
-
//
|
|
388
|
-
// session is reprinted, so the blocks already on
|
|
389
|
-
// switch
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
// and the pipe's byte-identity gate is this row's gate too.
|
|
394
|
-
//
|
|
395
|
-
// The live `thinking…` placeholder is NOT touched: it belongs to
|
|
396
|
-
// the live region, and this switch is about committed blocks.
|
|
382
|
+
// §2.3 / 0.40.6 — ctrl+t hides thinking (one italic line per block,
|
|
383
|
+
// the open one included) and shows it again. DC-50's mechanism: one
|
|
384
|
+
// boolean, then the session is reprinted, so the blocks already on
|
|
385
|
+
// screen obey the switch. The choice is REMEMBERED (preferences.json)
|
|
386
|
+
// — the owner, 2026-09-23, after the reference implementation's
|
|
387
|
+
// persisted toggle; shown stays the default. The pipe keeps its own
|
|
388
|
+
// one-line fold whatever the choice.
|
|
397
389
|
body.toggleThinking();
|
|
390
|
+
setPreference("thinking", body.thinkingHidden() ? "hidden" : "shown");
|
|
398
391
|
ctx.input.prompt();
|
|
399
392
|
return;
|
|
400
393
|
}
|
|
@@ -487,6 +480,31 @@ export function dispatch(line, ctx) {
|
|
|
487
480
|
});
|
|
488
481
|
return;
|
|
489
482
|
}
|
|
483
|
+
if (trimmed === "/settings") {
|
|
484
|
+
// 0.40.6 — what kiso runs with, each value's layer, and how to change
|
|
485
|
+
// it (settings.ts). Read-only: the config file stays the human's.
|
|
486
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
487
|
+
const w = statedContextWindow();
|
|
488
|
+
const profile = currentProfileName;
|
|
489
|
+
bodyLog(settingsRows({
|
|
490
|
+
user: settingsLayers.user,
|
|
491
|
+
project: settingsLayers.project,
|
|
492
|
+
env: process.env,
|
|
493
|
+
...(settingsLayers.modeFlag !== undefined ? { modeFlag: settingsLayers.modeFlag } : {}),
|
|
494
|
+
...(settingsLayers.modelFlag !== undefined ? { modelFlag: settingsLayers.modelFlag } : {}),
|
|
495
|
+
mode: getMode(),
|
|
496
|
+
model: { label: `${agentModel}${providerLabel(agentBaseUrl, upstreamOf(agentBaseUrl))}${profile === null ? "" : ` · profile ${profile}`}`, profile, switched: settingsLayers.modelSwitched },
|
|
497
|
+
ground: currentGround(),
|
|
498
|
+
floorOn,
|
|
499
|
+
window: windowSourceNote(w).replace(/^window /, ""),
|
|
500
|
+
thinkingHidden: body.thinkingHidden(),
|
|
501
|
+
thinkingRemembered: preferences().thinking !== undefined,
|
|
502
|
+
version: versionStatusLine(installedVersion(), VERSION).replace(/^version /, ""),
|
|
503
|
+
}).join("\n\n"), "words");
|
|
504
|
+
ctx.input.prompt();
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
490
508
|
if (trimmed === "/status") {
|
|
491
509
|
// B area: session id, durable event count, and the ~ context
|
|
492
510
|
// estimate — all read straight from the live session, nothing
|
|
@@ -499,7 +517,7 @@ export function dispatch(line, ctx) {
|
|
|
499
517
|
// CW-1: the percentage names its denominator and who stated it — a
|
|
500
518
|
// window inferred from the model reads differently from one the
|
|
501
519
|
// registry states for this endpoint.
|
|
502
|
-
bodyLog(`ctx ${ctxPct} · ${windowSourceNote(statedContextWindow()
|
|
520
|
+
bodyLog(`ctx ${ctxPct} · ${windowSourceNote(statedContextWindow())}`);
|
|
503
521
|
// The owner, 2026-09-21: /status is where "what am I actually
|
|
504
522
|
// running on" is answered, so the identity is spelled out HERE —
|
|
505
523
|
// the model, the host the request goes to, the profile it came
|
|
@@ -622,9 +640,11 @@ export function dispatch(line, ctx) {
|
|
|
622
640
|
// profile key is what `/model <name>` takes, not what a chooser
|
|
623
641
|
// reading a list needs. What remains is what the chooser CANNOT
|
|
624
642
|
// infer: availability, and which one is live.
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
643
|
+
// 0.40.6 (the owner, 2026-09-23: "who told you to write ctx"):
|
|
644
|
+
// the window rode here in 0.40.3–0.40.5 and cut `unavailable`
|
|
645
|
+
// to `unavailabl…` on the owner's rows. It lives on /status and
|
|
646
|
+
// /settings, where there is room to say it and its source.
|
|
647
|
+
const marks = [...(profileAvailable(profile) ? [] : ["unavailable"]), ...(name === currentProfileName ? ["current"] : [])];
|
|
628
648
|
const host = profileProviderLabel(profile.kind, profile.baseUrl, profile.upstream);
|
|
629
649
|
return { label: `${profile.kind}/${profile.model}${host === "" ? "" : ` ${host}`}`, note: marks.join(" · "), ...effortAxis(profile) };
|
|
630
650
|
}),
|
|
@@ -676,7 +696,7 @@ export function dispatch(line, ctx) {
|
|
|
676
696
|
for (const name of names) {
|
|
677
697
|
const p = configModels[name];
|
|
678
698
|
const hostOf = profileProviderLabel(p.kind, p.baseUrl, p.upstream);
|
|
679
|
-
bodyLog(` ${name} → ${p.kind}/${p.model}${hostOf === "" ? "" : ` ${hostOf}`} · ${signInNote(p)} ${profileAvailable(p) ? "(available)" : "(unavailable)"} · ${effortNote(p)}
|
|
699
|
+
bodyLog(` ${name} → ${p.kind}/${p.model}${hostOf === "" ? "" : ` ${hostOf}`} · ${signInNote(p)} ${profileAvailable(p) ? "(available)" : "(unavailable)"} · ${effortNote(p)}`);
|
|
680
700
|
}
|
|
681
701
|
}
|
|
682
702
|
bodyLog("switch with /model <profile-name|provider/model>");
|
|
@@ -773,6 +793,7 @@ export function dispatch(line, ctx) {
|
|
|
773
793
|
setAgentModel(profile.model, profile.baseUrl);
|
|
774
794
|
setCurrentModelName(arg);
|
|
775
795
|
setCurrentProfileName(direct === null ? null : profName);
|
|
796
|
+
settingsLayers.modelSwitched = true;
|
|
776
797
|
// §2.5: the ONE source a reload reads for the model — a
|
|
777
798
|
// switch made here must survive the rebuild.
|
|
778
799
|
setModelChoice(arg);
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,8 @@ import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
|
53
53
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
54
54
|
import { chat, compactionDiscardedNotice, contextWindowTokens, displayCtxRatio, knownContextWindow, microcompactThresholdFor, statusModelLabel, unknownWindowNotice, windowLearnedNotice } from "./chat.js";
|
|
55
55
|
import { recordLearnedWindow, useLearnedWindows } from "./learned-windows.js";
|
|
56
|
+
import { preferences, usePreferences } from "./preferences.js";
|
|
57
|
+
import { settingsLayers } from "./state.js";
|
|
56
58
|
import { providerHost } from "./provider-label.js";
|
|
57
59
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
58
60
|
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
|
|
@@ -759,6 +761,10 @@ async function makeAgent(sessionId, input, modelFlag) {
|
|
|
759
761
|
const userCfg = loadUserConfig();
|
|
760
762
|
const projectCfg = loadProjectConfig(process.cwd(), project !== null);
|
|
761
763
|
const merged = mergeConfigs(userCfg, projectCfg);
|
|
764
|
+
// 0.40.6: /settings names each value's layer from these
|
|
765
|
+
settingsLayers.user = userCfg;
|
|
766
|
+
settingsLayers.project = projectCfg;
|
|
767
|
+
settingsLayers.modelFlag = modelFlag;
|
|
762
768
|
const secretEnvNames = secretEnvNamesOf(merged.models ?? {});
|
|
763
769
|
const builtIn = await builtInLayer(user, proj, input !== undefined && process.stdin.isTTY ? askUi(input) : undefined, secretEnvNames);
|
|
764
770
|
setExtensionLists(builtIn, user, proj, [...builtIn, ...user, ...proj]);
|
|
@@ -1643,6 +1649,7 @@ async function main() {
|
|
|
1643
1649
|
process.exit(2);
|
|
1644
1650
|
}
|
|
1645
1651
|
setMode(m);
|
|
1652
|
+
settingsLayers.modeFlag = m;
|
|
1646
1653
|
args.splice(modeFlag, 2);
|
|
1647
1654
|
}
|
|
1648
1655
|
else {
|
|
@@ -1790,6 +1797,10 @@ async function main() {
|
|
|
1790
1797
|
editCol: () => dock.editCol(),
|
|
1791
1798
|
onDock: () => dock.redraw(), // v2d-B: the freeze scrolls the dock up — re-pin it
|
|
1792
1799
|
}));
|
|
1800
|
+
// 0.40.6: the choices kiso remembers (preferences.json) — today ctrl+t's
|
|
1801
|
+
// thinking display, restored before the first block can be drawn.
|
|
1802
|
+
usePreferences();
|
|
1803
|
+
body.setThinkingHidden(preferences().thinking === "hidden");
|
|
1793
1804
|
// REL-0152-D11: pasting an image sends no bytes, so an empty paste is
|
|
1794
1805
|
// the signal to go and look at the clipboard. What comes back is a
|
|
1795
1806
|
// PATH, which the turn's attachment scan then picks up exactly as it
|
|
@@ -2001,8 +2012,33 @@ async function main() {
|
|
|
2001
2012
|
// `login <provider>` reads the key from a hidden prompt on a TTY
|
|
2002
2013
|
// or from stdin otherwise (never from an argument — shell history);
|
|
2003
2014
|
// `logout <provider>` deletes; `auth` lists, keys masked.
|
|
2004
|
-
const { KNOWN_PROVIDERS, OAUTH_PROVIDERS, authPath, deleteCredential, maskSecret, readAuthFile, setCredential } = await import("./auth/credentials.js");
|
|
2015
|
+
const { KNOWN_PROVIDERS, OAUTH_PROVIDERS, authPath, deleteCredential, endpointCredentialId, maskSecret, providerIdOf, readAuthFile, setCredential } = await import("./auth/credentials.js");
|
|
2005
2016
|
const provider = arg;
|
|
2017
|
+
if (provider === "--endpoint" && command !== "auth") {
|
|
2018
|
+
// 0.40.6: a gateway's own key, stored for its ORIGIN and sent
|
|
2019
|
+
// there alone (credentials.ts endpointCredentialId) — so a
|
|
2020
|
+
// gateway profile starts from plain `kiso`, no env var on the
|
|
2021
|
+
// command line. The key still never comes from an argument.
|
|
2022
|
+
const url = args[2];
|
|
2023
|
+
const id = endpointCredentialId(url);
|
|
2024
|
+
if (id === null)
|
|
2025
|
+
throw new CliUsageError(`kiso ${command} --endpoint <url> — an http(s) URL, e.g. https://gateway.example/v1`);
|
|
2026
|
+
const origin = id.slice("endpoint:".length);
|
|
2027
|
+
const vendor = ["openai-compat", "anthropic", "openai-responses"].map((k) => providerIdOf(k, url)).find((v) => v !== null);
|
|
2028
|
+
if (vendor !== undefined && vendor !== null)
|
|
2029
|
+
throw new CliUsageError(`${origin} is ${vendor}'s own endpoint — run \`kiso ${command} ${vendor}\``);
|
|
2030
|
+
if (command === "logout") {
|
|
2031
|
+
const had = deleteCredential(id);
|
|
2032
|
+
process.stdout.write(had ? `removed the key for ${origin}\n` : `nothing stored for ${origin}\n`);
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
const key = (await readSecret(`API key for ${origin}: `)).trim();
|
|
2036
|
+
if (key === "")
|
|
2037
|
+
throw new CliUsageError(`kiso login --endpoint ${origin}: no key given`);
|
|
2038
|
+
setCredential(id, { type: "api-key", key, savedAt: Date.now() });
|
|
2039
|
+
process.stdout.write(`stored an API key for ${origin} (${maskSecret(key)}) in ${authPath()} — sent to that origin only; every profile whose baseUrl is on it uses this key before its env var\n`);
|
|
2040
|
+
break;
|
|
2041
|
+
}
|
|
2006
2042
|
if (command === "auth") {
|
|
2007
2043
|
const file = readAuthFile();
|
|
2008
2044
|
const rows = Object.entries(file.credentials);
|
|
@@ -2019,7 +2055,7 @@ async function main() {
|
|
|
2019
2055
|
break;
|
|
2020
2056
|
}
|
|
2021
2057
|
if (provider === undefined || !KNOWN_PROVIDERS.includes(provider)) {
|
|
2022
|
-
throw new CliUsageError(`kiso ${command} <provider> — one of: ${KNOWN_PROVIDERS.join(", ")}`);
|
|
2058
|
+
throw new CliUsageError(`kiso ${command} <provider> — one of: ${KNOWN_PROVIDERS.join(", ")}; or kiso ${command} --endpoint <url> for a gateway`);
|
|
2023
2059
|
}
|
|
2024
2060
|
if (command === "logout") {
|
|
2025
2061
|
const had = deleteCredential(provider);
|
|
@@ -2096,7 +2132,8 @@ async function main() {
|
|
|
2096
2132
|
" kiso sessions [--all|--current] list durable sessions (a terminal shows this workspace's by default)\n" +
|
|
2097
2133
|
" kiso login <provider> anthropic|openai|deepseek|zai: store an API key (hidden prompt, or stdin when piped);\n" +
|
|
2098
2134
|
" chatgpt: sign in with a ChatGPT subscription (browser; unofficial third-party flow)\n" +
|
|
2099
|
-
" kiso
|
|
2135
|
+
" kiso login --endpoint <url> a gateway: store its API key for that URL's origin only\n" +
|
|
2136
|
+
" kiso logout <provider> remove the stored credential (or --endpoint <url>)\n" +
|
|
2100
2137
|
" kiso auth list stored credentials (keys masked)\n" +
|
|
2101
2138
|
" kiso update install the latest release (npm i -g @vincemakes/kiso-code@latest)\n" +
|
|
2102
2139
|
" kiso help this help\n\n" +
|
|
@@ -2109,7 +2146,7 @@ async function main() {
|
|
|
2109
2146
|
" OPENAI_API_KEY OpenAI-compatible (OPENAI_MODEL, default gpt-4o;\n" +
|
|
2110
2147
|
" OPENAI_BASE_URL for DeepSeek/compat endpoints) — checked first\n" +
|
|
2111
2148
|
" ANTHROPIC_API_KEY Anthropic (ANTHROPIC_MODEL, default claude-sonnet-5)\n" +
|
|
2112
|
-
" ~/.kiso/config.json named model profiles (keys stay in env vars; see the README)\n");
|
|
2149
|
+
" ~/.kiso/config.json named model profiles (keys stay in env vars or kiso login; see the README)\n");
|
|
2113
2150
|
break;
|
|
2114
2151
|
}
|
|
2115
2152
|
case undefined:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Preferences {
|
|
2
|
+
/** ctrl+t: the thinking blocks as one italic line each. Absent = shown. */
|
|
3
|
+
readonly thinking?: "shown" | "hidden";
|
|
4
|
+
}
|
|
5
|
+
export declare function preferencesPath(home?: string): string;
|
|
6
|
+
/** Startup: read the file, and keep choices made from here on in it. */
|
|
7
|
+
export declare function usePreferences(path?: string): void;
|
|
8
|
+
/** The remembered choices; empty before startup names the file. */
|
|
9
|
+
export declare function preferences(): Preferences;
|
|
10
|
+
/** Remember a choice. Returns false when no file was named. The file is
|
|
11
|
+
* re-read before the write, so another process's choice is kept. */
|
|
12
|
+
export declare function setPreference<K extends keyof Preferences>(key: K, value: NonNullable<Preferences[K]>): boolean;
|
|
13
|
+
/** Tests: back to the state before startup. */
|
|
14
|
+
export declare function resetPreferences(): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.40.6 — the choices kiso remembers for you, in
|
|
3
|
+
* `<KISO_HOME>/preferences.json`.
|
|
4
|
+
*
|
|
5
|
+
* `config.json` is the human's file: kiso reads it and never writes it. A
|
|
6
|
+
* choice made with a key — ctrl+t's thinking display, today the only one —
|
|
7
|
+
* has to survive a restart, so it lands here instead: kiso-owned, private
|
|
8
|
+
* (0600, tmp + rename), and read back at start-up. A missing or unreadable
|
|
9
|
+
* file is an empty one; an unknown value is ignored rather than guessed.
|
|
10
|
+
*
|
|
11
|
+
* Nothing is read until the CLI's startup names the file
|
|
12
|
+
* (`usePreferences`) — the same rule as learned-windows.ts, so in-process
|
|
13
|
+
* tests never read a home directory.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { kisoHome } from "./state.js";
|
|
18
|
+
let prefs = null;
|
|
19
|
+
let prefsPath = null;
|
|
20
|
+
export function preferencesPath(home = kisoHome()) {
|
|
21
|
+
return join(home, "preferences.json");
|
|
22
|
+
}
|
|
23
|
+
function read(path) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
26
|
+
return raw.thinking === "shown" || raw.thinking === "hidden" ? { thinking: raw.thinking } : {};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Startup: read the file, and keep choices made from here on in it. */
|
|
33
|
+
export function usePreferences(path = preferencesPath()) {
|
|
34
|
+
prefsPath = path;
|
|
35
|
+
prefs = read(path);
|
|
36
|
+
}
|
|
37
|
+
/** The remembered choices; empty before startup names the file. */
|
|
38
|
+
export function preferences() {
|
|
39
|
+
return prefs ?? {};
|
|
40
|
+
}
|
|
41
|
+
/** Remember a choice. Returns false when no file was named. The file is
|
|
42
|
+
* re-read before the write, so another process's choice is kept. */
|
|
43
|
+
export function setPreference(key, value) {
|
|
44
|
+
const path = prefsPath;
|
|
45
|
+
if (path === null)
|
|
46
|
+
return false;
|
|
47
|
+
const next = { ...read(path), [key]: value };
|
|
48
|
+
prefs = next;
|
|
49
|
+
try {
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
52
|
+
writeFileSync(tmp, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
53
|
+
renameSync(tmp, path);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// an unwritable home: the choice still holds for this process
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/** Tests: back to the state before startup. */
|
|
61
|
+
export function resetPreferences() {
|
|
62
|
+
prefs = null;
|
|
63
|
+
prefsPath = null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.40.6 — `/settings`: what kiso is running with, where each value came
|
|
3
|
+
* from, and how to change it.
|
|
4
|
+
*
|
|
5
|
+
* The owner, 2026-09-23, asked whether kiso has a help for what can be
|
|
6
|
+
* configured. `/help` lists the commands and `?` the keys; the settings
|
|
7
|
+
* lived only in docs/configuration.md and in files the user had to open.
|
|
8
|
+
*
|
|
9
|
+
* READ-ONLY by design: the config file stays the human's. The layers are
|
|
10
|
+
* the documented precedence — flag > env > project config > user config >
|
|
11
|
+
* default — and a value that no layer explains was set in this session
|
|
12
|
+
* (`/mode`, `/model`). Pure: every input is passed in, so each row is
|
|
13
|
+
* testable without a session.
|
|
14
|
+
*/
|
|
15
|
+
import type { KisoConfig } from "./config.js";
|
|
16
|
+
export interface SettingsInput {
|
|
17
|
+
readonly user: KisoConfig | null;
|
|
18
|
+
readonly project: KisoConfig | null;
|
|
19
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
20
|
+
/** the --mode / --model values on the command line, when given */
|
|
21
|
+
readonly modeFlag?: string;
|
|
22
|
+
readonly modelFlag?: string;
|
|
23
|
+
/** what is live now */
|
|
24
|
+
readonly mode: string;
|
|
25
|
+
readonly model: {
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly profile: string | null;
|
|
28
|
+
readonly switched: boolean;
|
|
29
|
+
};
|
|
30
|
+
readonly ground: string;
|
|
31
|
+
readonly floorOn: boolean;
|
|
32
|
+
readonly window: string;
|
|
33
|
+
readonly thinkingHidden: boolean;
|
|
34
|
+
readonly thinkingRemembered: boolean;
|
|
35
|
+
readonly version: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function settingsRows(i: SettingsInput): string[];
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
function modeRow(i) {
|
|
2
|
+
const change = "/mode or shift+tab; \"mode\" in ~/.kiso/config.json";
|
|
3
|
+
const from = i.modeFlag === i.mode
|
|
4
|
+
? "--mode"
|
|
5
|
+
: i.env.KISO_MODE === i.mode
|
|
6
|
+
? "env KISO_MODE"
|
|
7
|
+
: i.project?.mode === i.mode
|
|
8
|
+
? "project config"
|
|
9
|
+
: i.user?.mode === i.mode
|
|
10
|
+
? "user config"
|
|
11
|
+
: i.mode === "default"
|
|
12
|
+
? "default"
|
|
13
|
+
: "set in this session";
|
|
14
|
+
return { name: "mode", value: i.mode, from, change };
|
|
15
|
+
}
|
|
16
|
+
function modelRow(i) {
|
|
17
|
+
const change = "/model; \"model\" in ~/.kiso/config.json";
|
|
18
|
+
const p = i.model.profile;
|
|
19
|
+
const from = i.model.switched
|
|
20
|
+
? "set in this session"
|
|
21
|
+
: i.modelFlag !== undefined
|
|
22
|
+
? "--model"
|
|
23
|
+
: p !== null && i.project?.model === p
|
|
24
|
+
? "project config"
|
|
25
|
+
: p !== null && i.user?.model === p
|
|
26
|
+
? "user config"
|
|
27
|
+
: i.env.OPENAI_API_KEY !== undefined && p === null
|
|
28
|
+
? "env OPENAI_API_KEY"
|
|
29
|
+
: "default";
|
|
30
|
+
return { name: "model", value: i.model.label, from, change };
|
|
31
|
+
}
|
|
32
|
+
function themeRow(i) {
|
|
33
|
+
const change = "\"theme\": \"dark\" | \"light\" in ~/.kiso/config.json; KISO_THEME for one run";
|
|
34
|
+
if (i.env.KISO_THEME !== undefined)
|
|
35
|
+
return { name: "theme", value: i.env.KISO_THEME, from: "env KISO_THEME", change };
|
|
36
|
+
if (i.user?.theme !== undefined)
|
|
37
|
+
return { name: "theme", value: i.user.theme, from: "user config", change };
|
|
38
|
+
return { name: "theme", value: i.ground, from: "the terminal (detected)", change };
|
|
39
|
+
}
|
|
40
|
+
function layered(i, key) {
|
|
41
|
+
if (i.project?.[key] !== undefined)
|
|
42
|
+
return { value: i.project[key], from: "project config" };
|
|
43
|
+
if (i.user?.[key] !== undefined)
|
|
44
|
+
return { value: i.user[key], from: "user config" };
|
|
45
|
+
return { value: undefined, from: "default" };
|
|
46
|
+
}
|
|
47
|
+
export function settingsRows(i) {
|
|
48
|
+
const trust = layered(i, "projectTrust");
|
|
49
|
+
const auto = i.env.KISO_AUTO_COMPACT !== undefined ? { value: i.env.KISO_AUTO_COMPACT, from: "env KISO_AUTO_COMPACT" } : layered(i, "autoCompact");
|
|
50
|
+
const rows = [
|
|
51
|
+
modelRow(i),
|
|
52
|
+
modeRow(i),
|
|
53
|
+
themeRow(i),
|
|
54
|
+
{
|
|
55
|
+
name: "floor",
|
|
56
|
+
value: i.floorOn ? "on — irrecoverable deletes are refused in every mode" : "off",
|
|
57
|
+
from: i.user?.floor !== undefined ? "user config" : "default",
|
|
58
|
+
change: "\"floor\": \"off\" in ~/.kiso/config.json (user config only)",
|
|
59
|
+
},
|
|
60
|
+
{ name: "window", value: i.window, from: "the window chain (see /status)", change: "\"contextWindow\" on the profile; KISO_CONTEXT_WINDOW for one run" },
|
|
61
|
+
{ name: "compaction", value: "in-run: past half the window at a phase end, past 80% at once", from: "built in", change: "/compact on demand" },
|
|
62
|
+
{
|
|
63
|
+
name: "auto-compact",
|
|
64
|
+
value: auto.value === undefined ? "off" : typeof auto.value === "object" ? `at ${auto.value.thresholdRatio} of the window, at a run's start` : String(auto.value),
|
|
65
|
+
from: auto.from,
|
|
66
|
+
change: "\"autoCompact\" in config; KISO_AUTO_COMPACT",
|
|
67
|
+
},
|
|
68
|
+
{ name: "project trust", value: trust.value ?? "ask", from: trust.from, change: "\"projectTrust\": \"ask\" | \"never\" in config" },
|
|
69
|
+
{
|
|
70
|
+
name: "thinking",
|
|
71
|
+
value: i.thinkingHidden ? "hidden — one line per block" : "shown",
|
|
72
|
+
from: i.thinkingRemembered ? "ctrl+t (remembered)" : "default",
|
|
73
|
+
change: "ctrl+t",
|
|
74
|
+
},
|
|
75
|
+
{ name: "version", value: i.version, from: "running", change: "kiso update" },
|
|
76
|
+
];
|
|
77
|
+
const width = Math.max(...rows.map((r) => r.name.length)) + 2;
|
|
78
|
+
return rows.map((r) => `${r.name.padEnd(width)}${r.value}\n${" ".repeat(width)}from ${r.from} · change: ${r.change}`);
|
|
79
|
+
}
|
package/dist/state.d.ts
CHANGED
|
@@ -295,6 +295,16 @@ export declare const queuedSwitchLines: string[];
|
|
|
295
295
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
296
296
|
/** 0.40.0: whether the catastrophe floor is on (floor.ts) — the user
|
|
297
297
|
* config's `floor`, read where the chain is assembled. */
|
|
298
|
+
/** 0.40.6: what /settings needs to name each value's layer — the two config
|
|
299
|
+
* files as read, the command-line flags as given, and whether /model
|
|
300
|
+
* changed the binding in this session. */
|
|
301
|
+
export declare const settingsLayers: {
|
|
302
|
+
user: import("./config.js").KisoConfig | null;
|
|
303
|
+
project: import("./config.js").KisoConfig | null;
|
|
304
|
+
modeFlag: string | undefined;
|
|
305
|
+
modelFlag: string | undefined;
|
|
306
|
+
modelSwitched: boolean;
|
|
307
|
+
};
|
|
298
308
|
export declare let floorOn: boolean;
|
|
299
309
|
export declare function setFloorOn(value: boolean): void;
|
|
300
310
|
/** 0.40.0: the calls a saved allow never carries (protected-writes.ts) —
|
package/dist/state.js
CHANGED
|
@@ -397,6 +397,10 @@ export const queuedSwitchLines = [];
|
|
|
397
397
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
398
398
|
/** 0.40.0: whether the catastrophe floor is on (floor.ts) — the user
|
|
399
399
|
* config's `floor`, read where the chain is assembled. */
|
|
400
|
+
/** 0.40.6: what /settings needs to name each value's layer — the two config
|
|
401
|
+
* files as read, the command-line flags as given, and whether /model
|
|
402
|
+
* changed the binding in this session. */
|
|
403
|
+
export const settingsLayers = { user: null, project: null, modeFlag: undefined, modelFlag: undefined, modelSwitched: false };
|
|
400
404
|
export let floorOn = true;
|
|
401
405
|
export function setFloorOn(value) {
|
|
402
406
|
floorOn = value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.6",
|
|
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,20 +18,20 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-ask-ext": "0.40.
|
|
22
|
-
"@vincemakes/kiso-core": "0.40.
|
|
23
|
-
"@vincemakes/kiso-evals": "0.40.
|
|
24
|
-
"@vincemakes/kiso-mcp-ext": "0.40.
|
|
25
|
-
"@vincemakes/kiso-provider-anthropic": "0.40.
|
|
26
|
-
"@vincemakes/kiso-provider-openai": "0.40.
|
|
27
|
-
"@vincemakes/kiso-provider-openai-responses": "0.40.
|
|
28
|
-
"@vincemakes/kiso-runtime": "0.40.
|
|
29
|
-
"@vincemakes/kiso-skills-ext": "0.40.
|
|
30
|
-
"@vincemakes/kiso-subagent-ext": "0.40.
|
|
31
|
-
"@vincemakes/kiso-task-ext": "0.40.
|
|
32
|
-
"@vincemakes/kiso-tools-node": "0.40.
|
|
33
|
-
"@vincemakes/kiso-tui": "0.40.
|
|
34
|
-
"@vincemakes/kiso-tui-cells": "0.40.
|
|
21
|
+
"@vincemakes/kiso-ask-ext": "0.40.6",
|
|
22
|
+
"@vincemakes/kiso-core": "0.40.6",
|
|
23
|
+
"@vincemakes/kiso-evals": "0.40.6",
|
|
24
|
+
"@vincemakes/kiso-mcp-ext": "0.40.6",
|
|
25
|
+
"@vincemakes/kiso-provider-anthropic": "0.40.6",
|
|
26
|
+
"@vincemakes/kiso-provider-openai": "0.40.6",
|
|
27
|
+
"@vincemakes/kiso-provider-openai-responses": "0.40.6",
|
|
28
|
+
"@vincemakes/kiso-runtime": "0.40.6",
|
|
29
|
+
"@vincemakes/kiso-skills-ext": "0.40.6",
|
|
30
|
+
"@vincemakes/kiso-subagent-ext": "0.40.6",
|
|
31
|
+
"@vincemakes/kiso-task-ext": "0.40.6",
|
|
32
|
+
"@vincemakes/kiso-tools-node": "0.40.6",
|
|
33
|
+
"@vincemakes/kiso-tui": "0.40.6",
|
|
34
|
+
"@vincemakes/kiso-tui-cells": "0.40.6"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^26.1.2",
|