@vincemakes/kiso-code 0.1.43 → 0.1.45

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.
@@ -0,0 +1,2 @@
1
+ import type { KisoExtension } from "@vincemakes/kiso-runtime";
2
+ export declare function builtInLayer(user: readonly KisoExtension[], project: readonly KisoExtension[]): Promise<readonly KisoExtension[]>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * R-D 0.1.45 — the built-in layer: the four official extensions (mcp,
3
+ * skills, subagent, task), shipped in the cli package and registered by
4
+ * MODULE IMPORT — never a disk scan (the user layer's loadExtensions stays
5
+ * word-for-word untouched). The cascade, base → top: built-in → user
6
+ * (~/.kiso/extensions) → project (.kiso/extensions, trust-gated).
7
+ *
8
+ * - a user extension may SHADOW a built-in by name — the user's deliberate
9
+ * install wins (a built-in cannot be uninstalled), loudly, and the
10
+ * shadowed built-in leaves the loaded set and the banner;
11
+ * - the project layer may NOT shadow anything below — the same-name
12
+ * refusal the loader already applies to the user layer, spelled out
13
+ * for the built-in layer too.
14
+ */
15
+ import createMcp from "@vincemakes/kiso-mcp-ext";
16
+ import createSkills from "@vincemakes/kiso-skills-ext";
17
+ import createSubagent from "@vincemakes/kiso-subagent-ext";
18
+ import createTask from "@vincemakes/kiso-task-ext";
19
+ export async function builtInLayer(user, project) {
20
+ const all = await Promise.all([createMcp(), createSkills(), createSubagent(), createTask()]);
21
+ const shadowed = all.filter((b) => user.some((u) => u.name === b.name));
22
+ for (const s of shadowed) {
23
+ console.error(`[extensions] user extension "${s.name}" shadows the built-in — the built-in is not loaded`);
24
+ }
25
+ for (const p of project) {
26
+ if (all.some((b) => b.name === p.name)) {
27
+ throw new Error(`[extensions] extension name "${p.name}" exists in both the built-in and the project-level extensions — refusing to shadow`);
28
+ }
29
+ }
30
+ return all.filter((b) => !shadowed.includes(b));
31
+ }
@@ -0,0 +1,9 @@
1
+ /** First-run detection. Called only post-gate, so the verdict precedes
2
+ * even the first home READ. */
3
+ export declare function isFirstRun(): boolean;
4
+ /** Materialize the config surface (never clobbering a user's own config)
5
+ * and stamp the sentinel — the sequence's second home write. SILENT on
6
+ * purpose: every stream line must match a known cell format (the v2d
7
+ * lint), and the PTY grids pin the startup stream byte-for-byte — the
8
+ * first-run sequence is evidenced by the FILES, not an announcement. */
9
+ export declare function scaffoldFirstRun(): void;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * R-D 0.1.45 (deliverable B) — the first-run scaffold. A fresh install's
3
+ * FIRST startup materializes the config surface (the file the faux-mode
4
+ * notice names) and stamps the sentinel that ends first-run.
5
+ *
6
+ * Runs AFTER the E3 trust gate: pre-trust zero-read/write/scan is
7
+ * absolute — the trust question is the process's first home access of
8
+ * any kind, the trust record the first home write, the scaffold the
9
+ * second. First-run detection never precedes the verdict.
10
+ */
11
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { kisoHome } from "./state.js";
14
+ /** The sentinel: present = the home has seen the 0.1.45 first-run
15
+ * sequence (its config surface exists — no re-scaffold, no re-note). */
16
+ const SENTINEL = "first-run";
17
+ /** The minimal config surface — an empty models layer changes nothing
18
+ * (faux stays the default), but the file the notice points at exists. */
19
+ const CONFIG_SCAFFOLD = `${JSON.stringify({ models: {} }, null, 2)}\n`;
20
+ /** First-run detection. Called only post-gate, so the verdict precedes
21
+ * even the first home READ. */
22
+ export function isFirstRun() {
23
+ return !existsSync(join(kisoHome(), SENTINEL));
24
+ }
25
+ /** Materialize the config surface (never clobbering a user's own config)
26
+ * and stamp the sentinel — the sequence's second home write. SILENT on
27
+ * purpose: every stream line must match a known cell format (the v2d
28
+ * lint), and the PTY grids pin the startup stream byte-for-byte — the
29
+ * first-run sequence is evidenced by the FILES, not an announcement. */
30
+ export function scaffoldFirstRun() {
31
+ const home = kisoHome();
32
+ mkdirSync(home, { recursive: true });
33
+ const config = join(home, "config.json");
34
+ if (!existsSync(config))
35
+ writeFileSync(config, CONFIG_SCAFFOLD, "utf8");
36
+ writeFileSync(join(home, SENTINEL), "", "utf8");
37
+ }
package/dist/index.js CHANGED
@@ -22,8 +22,9 @@
22
22
  * index.ts keeps the entry: banner, input sources, the A area prompt,
23
23
  * makeAgent, and main.
24
24
  */
25
- import { readFileSync, rmSync } from "node:fs";
25
+ import { readFileSync, realpathSync, rmSync } from "node:fs";
26
26
  import { createInterface } from "node:readline";
27
+ import { fileURLToPath } from "node:url";
27
28
  import { join } from "node:path";
28
29
  import { Body, Editor, bannerLines, palette, renderSessionLine } from "@vincemakes/kiso-tui";
29
30
  import { escapeTerminal } from "@vincemakes/kiso-tui";
@@ -31,8 +32,10 @@ import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions,
31
32
  import { createFauxProvider } from "@vincemakes/kiso-evals";
32
33
  import { createCodingTools } from "@vincemakes/kiso-tools-node";
33
34
  import { MODES, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
34
- import { body, bodyLog, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
35
+ import { builtInLayer } from "./builtin.js";
36
+ import { body, bodyLog, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
35
37
  import { interactivePrompt, resolveProjectTrust } from "./trust-ui.js";
38
+ import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
36
39
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
37
40
  import { autoCompactFromEnv, chat, contextWindowTokens } from "./chat.js";
38
41
  import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
@@ -188,17 +191,21 @@ function makeLineInput() {
188
191
  }
189
192
  return readlineInput(createInterface({ input: process.stdin, output: process.stdout }));
190
193
  }
191
- /** E3: the `[N extensions: ...]` text — user-level names, then project-level
192
- * ones marked with `project:`. Byte-identical to the historical text when
193
- * no project extensions are loaded. */
194
+ /** R-D 0.1.45: the `[N extensions: ...]` text — the built-in column, then
195
+ * the user-level names, then the project-level ones marked `project:`.
196
+ * The built-in column is the banner's truthful face of the built-in layer:
197
+ * a fresh install reads `[4 extensions: built-in: mcp, skills, subagent,
198
+ * task]` with zero disk setup. */
194
199
  function bannerExtensionText() {
195
- const total = userExtensions.length + projectExtensions.length;
200
+ const total = builtInExtensions.length + userExtensions.length + projectExtensions.length;
196
201
  if (total === 0)
197
202
  return "";
198
203
  const parts = [];
199
204
  // 0.1.26 (MCP lazy connection): an extension with a live `connecting` flag shows
200
205
  // its in-flight state in the banner — "mcp (connecting…)".
201
206
  const label = (e) => e.connecting === true ? `${e.name} (connecting…)` : e.name;
207
+ if (builtInExtensions.length > 0)
208
+ parts.push(`built-in: ${builtInExtensions.map(label).join(", ")}`);
202
209
  if (userExtensions.length > 0)
203
210
  parts.push(userExtensions.map(label).join(", "));
204
211
  if (projectExtensions.length > 0)
@@ -290,22 +297,29 @@ export function composeSystemPrompt(cwd) {
290
297
  const injected = readProjectInstructions(cwd);
291
298
  return injected === "" ? SYSTEM_PROMPT : `${SYSTEM_PROMPT}\n${injected}`;
292
299
  }
293
- async function makeAgent(fauxSkipTurns = 0, input, modelFlag) {
294
- const store = new SessionStore(sessionsDir());
300
+ async function makeAgent(sessionId, input, modelFlag) {
295
301
  // E3: the project-level trust gate runs BEFORE any extension load (the
296
302
  // mcp/skills merges must be in the env when the user-level extensions
297
303
  // load). Untrusted project capability is never loaded — never silently.
298
304
  const project = input !== undefined ? await resolveProjectTrust(input) : await resolveProjectTrust(undefined);
305
+ // R-D 0.1.45 (deliverable B): the first-run scaffold lands AFTER the
306
+ // verdict — pre-trust zero-read/write/scan is absolute. The sessions
307
+ // dir (SessionStore's constructor mkdirs) moved behind the gate too:
308
+ // the trust record is the first home write, the scaffold the second.
309
+ if (isFirstRun())
310
+ scaffoldFirstRun();
311
+ const store = new SessionStore(sessionsDir());
312
+ // E area: the durable script position — computed AFTER the verdict
313
+ // (fauxSkip's session-log read is a home read: pre-trust zero-read).
314
+ const fauxSkipTurns = sessionId === undefined ? 0 : fauxSkip(sessionId);
299
315
  // E1: the startup extension scan — a broken extension fails the process
300
316
  // LOUDLY here (loadExtensions throws), never silently.
301
317
  const user = await loadExtensions(extensionsDir());
302
- if (project !== null) {
303
- const proj = await loadProjectExtensions(process.cwd(), user);
304
- setExtensionLists(user, proj, [...user, ...proj]);
305
- }
306
- else {
307
- setExtensionLists(user, [], user);
308
- }
318
+ const proj = project !== null ? await loadProjectExtensions(process.cwd(), user) : [];
319
+ // R-D 0.1.45: the built-in layer registers by module import (builtin.ts)
320
+ // — a user extension may shadow a built-in, a project one may not.
321
+ const builtIn = await builtInLayer(user, proj);
322
+ setExtensionLists(builtIn, user, proj, [...builtIn, ...user, ...proj]);
309
323
  // merge round B — the config surface: user config + (trusted) project config,
310
324
  // resolved with flags > env > project > user > default. The CLI never
311
325
  // imports provider SDKs directly — the runtime's lazy provider
@@ -445,7 +459,7 @@ async function main() {
445
459
  dock.enter();
446
460
  // E area: a resumed session continues the script at its durable
447
461
  // position — never restarts it (fauxSkip).
448
- const agent = await makeAgent(fauxSkip(id), input, modelFlag);
462
+ const agent = await makeAgent(id, input, modelFlag);
449
463
  applyConfigMode();
450
464
  const session = await agent.session({ id });
451
465
  bodyLog(`session ${id}\n`);
@@ -463,7 +477,7 @@ async function main() {
463
477
  // (argv = [node, script, resume, id, prompt?]).
464
478
  const prompt = process.argv[4];
465
479
  dock.enter();
466
- const agent = await makeAgent(fauxSkip(arg), input, modelFlag);
480
+ const agent = await makeAgent(arg, input, modelFlag);
467
481
  applyConfigMode();
468
482
  const session = await agent.session({ id: arg });
469
483
  faux = currentFaux;
@@ -471,7 +485,7 @@ async function main() {
471
485
  break;
472
486
  }
473
487
  case "sessions": {
474
- const agent = await makeAgent(0, undefined, modelFlag);
488
+ const agent = await makeAgent(undefined, undefined, modelFlag);
475
489
  for (const meta of agent.sessions()) {
476
490
  console.log(renderSessionLine(meta));
477
491
  }
@@ -494,7 +508,7 @@ async function main() {
494
508
  // chat — the first argument is the session id.
495
509
  const id = command ?? new Date().toISOString().replace(/[:.]/g, "-").slice(0, 16);
496
510
  dock.enter();
497
- const agent = await makeAgent(fauxSkip(id));
511
+ const agent = await makeAgent(id);
498
512
  const session = await agent.session({ id });
499
513
  bodyLog(`session ${id}\n`);
500
514
  extensionsBanner(recentSessions(id, agent));
@@ -528,14 +542,22 @@ async function main() {
528
542
  }
529
543
  }
530
544
  }
531
- main()
532
- .then(() => process.exit(0))
533
- .catch((err) => {
534
- // round 10: top-level errors are terminal-escaped. v2a: the exit is EXPLICIT
535
- // natural drain is racy on a TTY (readline leaves the stdio handles
536
- // active and the loop sometimes never drains). main's finally already
537
- // ran (agent.close, dispose, temp cleanup) nothing is skipped, no
538
- // lock is left behind; the exit code is honest.
539
- console.error(escapeTerminal(err instanceof Error ? err.message : String(err)));
540
- process.exit(1);
541
- });
545
+ // R-D 0.1.45 (deliverable B): main runs ONLY as the entry — the CLI is
546
+ // import-clean. The unconditional module-scope run executed the full
547
+ // startup under the importing process's argv and REAL home whenever a
548
+ // test imported src/index.js for its functions (harmless before the
549
+ // first-run scaffold existed; the scaffold WRITES the home). The bin is
550
+ // a symlink, so argv[1] is realpathed before the comparison.
551
+ if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
552
+ main()
553
+ .then(() => process.exit(0))
554
+ .catch((err) => {
555
+ // round 10: top-level errors are terminal-escaped. v2a: the exit is EXPLICIT
556
+ // — natural drain is racy on a TTY (readline leaves the stdio handles
557
+ // active and the loop sometimes never drains). main's finally already
558
+ // ran (agent.close, dispose, temp cleanup) — nothing is skipped, no
559
+ // lock is left behind; the exit code is honest.
560
+ console.error(escapeTerminal(err instanceof Error ? err.message : String(err)));
561
+ process.exit(1);
562
+ });
563
+ }
package/dist/state.d.ts CHANGED
@@ -87,13 +87,17 @@ export declare let currentModelName: string;
87
87
  export declare function setCurrentModelName(value: string): void;
88
88
  /** E1: the extensions loaded by makeAgent — their names feed the banner. */
89
89
  export declare let loadedExtensions: readonly KisoExtension[];
90
+ /** R-D 0.1.45: the BUILT-IN layer — the four official extensions, shipped
91
+ * with the cli (module imports, never a disk scan; builtin.ts). The
92
+ * banner's marked column; a user extension may shadow a built-in. */
93
+ export declare let builtInExtensions: readonly KisoExtension[];
90
94
  /** E1: the USER-level extensions alone — the banner's unmarked part (E3:
91
95
  * loadedExtensions later includes the project-level ones too). */
92
96
  export declare let userExtensions: readonly KisoExtension[];
93
97
  /** E3: the PROJECT-level extensions (loaded after the trust gate) — the
94
98
  * banner distinguishes them from the user-level ones. */
95
99
  export declare let projectExtensions: readonly KisoExtension[];
96
- export declare function setExtensionLists(user: readonly KisoExtension[], project: readonly KisoExtension[], loaded: readonly KisoExtension[]): void;
100
+ export declare function setExtensionLists(builtIn: readonly KisoExtension[], user: readonly KisoExtension[], project: readonly KisoExtension[], loaded: readonly KisoExtension[]): void;
97
101
  /** W21: the CURRENT agent's extensions array — set by makeAgent, the
98
102
  * don't-ask-again writer pushes the generated extension into it so a
99
103
  * first-time rule joins the chain at the NEXT run (the run's policies
package/dist/state.js CHANGED
@@ -78,13 +78,18 @@ export function setCurrentModelName(value) {
78
78
  }
79
79
  /** E1: the extensions loaded by makeAgent — their names feed the banner. */
80
80
  export let loadedExtensions = [];
81
+ /** R-D 0.1.45: the BUILT-IN layer — the four official extensions, shipped
82
+ * with the cli (module imports, never a disk scan; builtin.ts). The
83
+ * banner's marked column; a user extension may shadow a built-in. */
84
+ export let builtInExtensions = [];
81
85
  /** E1: the USER-level extensions alone — the banner's unmarked part (E3:
82
86
  * loadedExtensions later includes the project-level ones too). */
83
87
  export let userExtensions = [];
84
88
  /** E3: the PROJECT-level extensions (loaded after the trust gate) — the
85
89
  * banner distinguishes them from the user-level ones. */
86
90
  export let projectExtensions = [];
87
- export function setExtensionLists(user, project, loaded) {
91
+ export function setExtensionLists(builtIn, user, project, loaded) {
92
+ builtInExtensions = builtIn;
88
93
  userExtensions = user;
89
94
  projectExtensions = project;
90
95
  loadedExtensions = loaded;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "kiso CLI — the coding-agent reference product: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,12 +18,16 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-core": "0.1.34",
22
- "@vincemakes/kiso-evals": "0.1.35",
23
- "@vincemakes/kiso-provider-anthropic": "0.1.35",
24
- "@vincemakes/kiso-provider-openai": "0.1.35",
25
- "@vincemakes/kiso-runtime": "0.1.35",
26
- "@vincemakes/kiso-tools-node": "0.1.35",
21
+ "@vincemakes/kiso-core": "0.1.35",
22
+ "@vincemakes/kiso-evals": "0.1.36",
23
+ "@vincemakes/kiso-mcp-ext": "0.1.45",
24
+ "@vincemakes/kiso-provider-anthropic": "0.1.36",
25
+ "@vincemakes/kiso-provider-openai": "0.1.36",
26
+ "@vincemakes/kiso-runtime": "0.1.36",
27
+ "@vincemakes/kiso-skills-ext": "0.1.45",
28
+ "@vincemakes/kiso-subagent-ext": "0.1.45",
29
+ "@vincemakes/kiso-task-ext": "0.1.45",
30
+ "@vincemakes/kiso-tools-node": "0.1.36",
27
31
  "@vincemakes/kiso-tui": "0.1.42",
28
32
  "@vincemakes/kiso-tui-cells": "0.1.42"
29
33
  },