klyro 0.1.60 → 0.1.62
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/READ.md +69 -1
- package/dist/agent/anthropic-adapter.js +1 -1
- package/dist/cli/repl.js +4 -12
- package/dist/context/klyro-md.js +1 -1
- package/dist/index.js +1 -43
- package/dist/tui/app.js +3 -1
- package/dist/tui/app.test.js +12 -2
- package/dist/tui/snapshot.test.js +3 -3
- package/dist/version.d.ts +8 -0
- package/dist/version.js +47 -0
- package/package.json +1 -1
package/READ.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|-------|--------|-----|
|
|
23
23
|
| Language | TypeScript 5.5, Node 20+ | Strict, `NodeNext`, `tsc` → `dist/` |
|
|
24
24
|
| CLI | `commander 12.1` | Stable, `InvalidArgumentError` for `exit 2` |
|
|
25
|
-
| TUI | `ink 7.1` + `react 19` + `ink-spinner 5` | React for terminal
|
|
25
|
+
| TUI | `ink 7.1` + `react 19` + `ink-spinner 5` | React renderer for the terminal |
|
|
26
26
|
| Schema | `zod 4.5` | Tool input validation + config schema |
|
|
27
27
|
| Test | `vitest 4.1` `fileParallelism:false` `10s timeout` | `node` env, deterministic mocks |
|
|
28
28
|
| Build | `tsc` (not `tsup`) | `tsc --noEmit` `typecheck`, `tsc` `build` |
|
|
@@ -465,3 +465,71 @@ Global flags `src/index.ts:60` `--cwd/--config/--debug/--verbose/--quiet/--json/
|
|
|
465
465
|
|
|
466
466
|
Run: `pnpm build && pnpm test && node dist/index.js --help` → `klyro -p "fix login test" --output json` → `klyro doctor`.
|
|
467
467
|
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 20. Session Ledger v0.1.39 → v0.1.61 (what was done, what failed)
|
|
471
|
+
|
|
472
|
+
All work below was done live against user-reported production issues, verified with
|
|
473
|
+
`typecheck 0` + full `vitest` + built-`dist` smoke tests before every publish.
|
|
474
|
+
Current: **`klyro@0.1.61` `latest`, 51 test files, 442/442 tests, commits as
|
|
475
|
+
`siddu-lingampelli`** (history rewritten from `Mythicai-lab`, tags moved, §20.6).
|
|
476
|
+
|
|
477
|
+
### 20.1 Release history
|
|
478
|
+
|
|
479
|
+
| Version | Commit | What shipped |
|
|
480
|
+
|---|---|---|
|
|
481
|
+
| 0.1.39 | base | Single-column Klyro TUI, true `#E8843C` orange via 24-bit, hello Direct-LLM (no tools) |
|
|
482
|
+
| 0.1.40 | `ca85d56` | True `#FF6B1A` orange, clean single-column, no `g`-letter corruption |
|
|
483
|
+
| 0.1.41 | `1884807` | `useChatScroll` hook (pin/unpin, snap-to-message, new-messages pill) |
|
|
484
|
+
| 0.1.42 | `50c84dc` | Audit batch: git-blame argv+path validation, `NODE_OPTIONS`/`LD_PRELOAD` block, ordered transcript, configurable `rerunOnce` timeout, model-aware `estimateCost`, Buffer-based (non-O(n²)) verify collectors, deleted empty `packages/cli+core`, `snippets.ts`, `util/log.ts`, `stuckCount`; TUI ellipsis `…`→`...`, mojibake purge |
|
|
485
|
+
| 0.1.43 | `04bd24e` | Slash groundwork: `/provider` `/effort` `/model` `/compact` `/plan` `/login` `/logout` `/init` handlers |
|
|
486
|
+
| 0.1.44 | `81d9627` | **Priority 1 per `commands.md`** (~39 cmds): session/model/project/perms/auth groups; TUI orphan-file reconciliation (deleted `app.inline/banner/input-box/activity-line/thinking-block/components/Header`) |
|
|
487
|
+
| 0.1.45 | `feb30ff` | **Priority 2 per `commands.md`** (~70 cmds): review/build/git/files/agents/config/misc; `/` autocomplete popup + Tab |
|
|
488
|
+
| 0.1.46 | `b8947d1` | **`scroll.md` S1–S9 Ink adaptation**: `measure.ts` (display-width/wrap/cache/line-index), `scroll-model.ts` (anchor reducer), input history + contextual ↑/↓, run-length activity merge, console-patch, exit transcript replay |
|
|
489
|
+
| 0.1.47 | `c440bc3` | **Dead scroll trap fix** (directional epsilon — line/wheel up from bottom could never escape) + mouse-wheel capture + `Ctrl+F/B` pager keys |
|
|
490
|
+
| 0.1.48 | `1e7f85c` | **Set-once auth**: persisted provider resolution (env → config+credentials → probe), first-run setup, `/model` `/provider` persist, `/reload` rebuilds adapter |
|
|
491
|
+
| 0.1.49 | `f0e840d` + `262b8fc` | Plain-HTTP endpoints explain+confirm with persisted `allowInsecure` opt-in, loud rejections (`lastProviderError`), prefilled setup, keep-saved-key; (+ separate commit preserving a concurrent working-tree `scroll.md` rewrite) |
|
|
492
|
+
| 0.1.50 | `5b3d3ee` | **`design.md` conformance**: half-page keys, Esc/Ctrl+C/Shift+Enter semantics, full Markdown renderer, header/statusbar/plan restyle |
|
|
493
|
+
| 0.1.51 | `6ba47de` | **`scroll.md` command architecture**: `transcript-commands.ts` keybind mapper + 4-command handle, submit-key settle effect |
|
|
494
|
+
| 0.1.52 | `db09e5f` | **Markdown columns bug**: `MarkdownText` fragment rendered lines as side-by-side columns — back to single `<Text>` |
|
|
495
|
+
| 0.1.53 | `5f9fa84` | Root `overflow:hidden` I1 guard (input can never be pushed out), tool results patch start-items in place (no forever-`running` spinners), narrow-terminal hints |
|
|
496
|
+
| 0.1.54 | `1ec3d0c` | **Full audit application** (P0+P1, §20.3) |
|
|
497
|
+
| 0.1.55 | `eb8776f` | Live working spinner (Thinking row, running tool groups, status bar) |
|
|
498
|
+
| 0.1.56 | `cf1a94d` | **Thinking channel**: `reasoning_content`/Anthropic-thinking capture → dim display → cleared on response |
|
|
499
|
+
| 0.1.57 | `f6fd749` | Composer audit: `flexShrink={0}` chrome, position/multiline/aggregation tests |
|
|
500
|
+
| 0.1.58 | `a57d526` | Abort clears thinking, `/clear` resets scroll anchor, badge overlap fix |
|
|
501
|
+
| 0.1.59 | `bbe9ba6` | Scroll position readout (`⇅ top/max`) in status bar |
|
|
502
|
+
| 0.1.60 | `6627b5b` | **Wheel tap fix**: intercept `stdin.read` (Ink7 paused mode), not `data` events that never fire |
|
|
503
|
+
| 0.1.61 | `5a1a97f` | Brand sweep: Claude naming removed, `CLAUDE.md` fallback dropped (Klyro-only) |
|
|
504
|
+
|
|
505
|
+
### 20.2 What was built (by area)
|
|
506
|
+
|
|
507
|
+
- **Slash commands (113, all smoke-tested on built `dist`)**: session (`/new` `/resume` `/sessions` `/rename` `/fork` `/branch` `/export` `/copy`), model (`/model` `/models` `/provider` `/effort` `/fast`), project (`/init` `/status` `/context` `/diff` `/plan` `/todos` `/memory`), perms (`/permissions` `/mode` `/sandbox` `/approve` `/deny`), app (`/login` `/logout` `/auth` `/version` `/update` `/cancel` `/shell` `!` `/mention` `@` `/tools`), workflow (`/review` `/code-review` `/security-review` `/simplify` `/test` `/lint` `/build` `/run` `/fix` `/explain` `/format` `/ask`), checkpoints (`/undo` `/redo`→honest-noop `/rewind` `/checkpoint` `/accept` `/reject`), activity (`/details` `/verbose` `/raw` `/activity` `/tasks` `/ps` `/stop` `/queue` `/retry` `/kill`), agents+MCP, files (`/attach`→in-context `/drop` `/files` `/image` `/paste` `/ls` `/tree` `/search` `/web` `/read` `/map` `/tokens`), git (`/commit` via temp file `/push` `/pull` `/pr` `/issue`), config UI (`/editor` argv-spawn `/keymap` `/vim` `/theme` `/statusline` `/output-style`), diagnostics (`/doctor` `/debug` `/whoami` `/reload` `/reset` `/bug` `/changelog`), library (`/prompt` `/alias`→auto-expand `/commands` `/env` (injection keys blocked) `/deps` `/install`).
|
|
508
|
+
- **Scroll system** (`src/tui/scroll-model.ts` + `measure.ts` + `mouse.ts` + `transcript-commands.ts`): anchor `{itemId,lineInItem}` (never raw rows), measured wrapped display lines (CJK=2/emoji=2/combining=0), `MeasureCache`, follow-at-bottom / freeze-when-pinned / `↓ N new` badge, `End`/`Ctrl+G`/empty-Enter dismiss, wheel ±3, PageUp/Dn half-page, Home/End first/last, exit transcript replay, degraded tiny-terminal mode.
|
|
509
|
+
- **Auth persistence**: `~/.klyro/settings.json` + `credentials.json` (0600); resolution env → config → stored keys → local probe; `KLYRO_CONFIG`/`KLYRO_CONFIG_DIR`/`KLYRO_CREDENTIALS_FILE` overrides (also used by tests); persisted `allowInsecure` replaces per-terminal `KLYRO_ALLOW_INSECURE=1`.
|
|
510
|
+
- **Verification**: denylist (destructive-only; metachars allowed since commands are user/config-sourced), sanity checks fail-fast *before* the suite, scoped→full, flaky rerun with configured timeout, `hasEdits` drives `--require-verify` (exit 8 only when edits exist).
|
|
511
|
+
- **Safety**: `resolveAndFollowSymlinks` everywhere incl. checkpoints (traversal-contained undo that also restores deletions), `wasRead` guards on all four mutators with normalized + per-task history, `readFileSizeRule` stats real files, `clonePolicyConfig` per engine, `setByPath` proto-pollution guard, shell full-output caps + POSIX process-group kill, `startBackgroundArgv` (shell:false), secret redaction before model/summary paths.
|
|
512
|
+
|
|
513
|
+
### 20.3 Audit application (the big diagnostic)
|
|
514
|
+
|
|
515
|
+
Every claim was verified against code before touching. **Confirmed + fixed (P0)**: ApprovalModal never rendered (+ single-listener bridge deafened it — also the flake root cause); Anthropic `role:tool` flattened to empty turns; duplicate `message_end`; edit-file fuzzy rewriting whole files (plus a dead line-window tier); apply-patch append-only stub → real hunk applier; `require()` in ESM (`run-verify`, later all touched files → static imports). **Confirmed + fixed (P1)**: shared policy config; `inferFileChanged` gaps; tokenizer pair orphans; klyro-md import escape + no caps; verify-order waste + `$()` overblock; unbounded full buffers; `/web` caps; checkpoint traversal/undo; store locking + `delete()`/`fork()` with messages; SIGINT-once; silent-mode newline; retry sleep ignores abort; read-history normalization; dotfile flag; `read_fileBody` shape bug in `/attach` `/mention` `/read` `/review` `/explain`; Ctrl+C-safe login/setup; `isToolErrorCode` swallowing custom codes; read-size rule via stat; multi-edit guards; `last.diff` writer; dry-run real prompt; debug.log rotation; `/cd` cwd reset; cost/context single source; headless URL policy. **Deliberately not changed**: `isSimpleChat` fast-path (intended UX); Ink-vs-OpenTUI architecture (approved); eval/MCP/L10–L20 (roadmap); `$EDITOR`/user-typed shell passthrough (user's own intent); trace paths; `isToolErrorCode`-adjacent churn that worked.
|
|
516
|
+
|
|
517
|
+
### 20.4 What FAILED along the way (honest log)
|
|
518
|
+
|
|
519
|
+
- **Markdown columns regression (shipped 0.1.50, caught by user screenshot, fixed 0.1.52)**: fragment of `<Text>` in a row `Box` laid lines out as columns. Lesson: frame-level regression test added (fails on old code, passes on new).
|
|
520
|
+
- **Dead scroll trap (mine)**: symmetric follow-epsilon made line/wheel-up from bottom re-stick forever. Found by tracing, fixed directional, regression-tested.
|
|
521
|
+
- **Deaf wheel tap (mine)**: intercepted `data` events; Ink7 only uses `readable`+`read()`. Found by reading installed Ink source, rewrote + tested the wrapper contract.
|
|
522
|
+
- **Silent setup loop (mine)**: saved-but-rejected base URLs were swallowed → infinite first-run. Fixed with recorded reasons + prefilled recovery.
|
|
523
|
+
- **`require()` in ESM**: would have thrown `ReferenceError` in production `dist` while passing under `tsx`/`vitest` shims. Converted everything touched to static imports.
|
|
524
|
+
- **My own test-math errors**: indent tier (4sp→2 tabs), wheel maxTop, badge line counts, half-page expectations — each caught by running tests, fixed, and (for scroll-reset) proven discriminating by noop-check.
|
|
525
|
+
- **Infra friction**: npm `E409` staging + `E404` propagation delays (60–150s waits per publish); PowerShell mangling inline `python`/`node` quoting (rule learned: script files only); `git add -A` twice scooped a concurrent agent's doc rewrites (split into separate commits; own files only since); vitest load flakes under parallel sessions (fixed sleeps → poll-with-timeout; one residual publish-gate flake at 0.1.59, green on rerun).
|
|
526
|
+
- **Authorship**: 49 commits + 10 tags rewritten to `siddu-lingampelli` (force-with-lease, trees verified identical); repo-local git identity set.
|
|
527
|
+
|
|
528
|
+
### 20.5 Environment notes for future sessions
|
|
529
|
+
|
|
530
|
+
- Windows PowerShell 5.1: chain with `;`, never `&&`; quote paths; never inline `python -c` with brackets/quotes (write temp scripts under `C:\Users\L.Siddhartha\AppData\Local\Temp\opencode`).
|
|
531
|
+
- Never `git add -A` blindly — another agent rewrites `design.md`/`scroll.md`/`TUI_DESIGN.md`/`commands.md` concurrently; stage own files explicitly.
|
|
532
|
+
- `npm publish` runs `typecheck && test && build`; expect CDN delays; `latest` flips ~1–3 min after `+ klyro@x`.
|
|
533
|
+
- Test isolation for anything reading `~/.klyro`: set `KLYRO_CONFIG` + `KLYRO_CONFIG_DIR` + `KLYRO_CREDENTIALS_FILE` to temp dirs.
|
|
534
|
+
- `ink-testing-library` has fixed terminal geometry (no resize tests) and normalizes keys like production Ink; production-only paths (stdin tap, alt-screen) need contract tests, not frame tests.
|
|
535
|
+
|
|
@@ -106,7 +106,7 @@ async function* streamAnthropic(req, opts) {
|
|
|
106
106
|
}
|
|
107
107
|
yield { kind: 'message_start' };
|
|
108
108
|
// Stream SSE: lines are `event: <type>\ndata: <json>\n\n`.
|
|
109
|
-
// We use a simple incremental parser;
|
|
109
|
+
// We use a simple incremental parser; Anthropic's API guarantees
|
|
110
110
|
// well-formed SSE.
|
|
111
111
|
const reader = resp.body.getReader();
|
|
112
112
|
const decoder = new TextDecoder('utf-8');
|
package/dist/cli/repl.js
CHANGED
|
@@ -21,6 +21,7 @@ import { TuiApprovalBridge } from '../tui/approval.js';
|
|
|
21
21
|
import { parseUnifiedDiff } from '../tui/diff-parser.js';
|
|
22
22
|
import { parse } from './slash/parser.js';
|
|
23
23
|
import { resolveProvider, providerHelp, lastProviderError } from '../providers.js';
|
|
24
|
+
import { readVersion } from '../version.js';
|
|
24
25
|
import { MouseFilter, MOUSE_ENABLE, MOUSE_DISABLE, createReadWrapper } from '../tui/mouse.js';
|
|
25
26
|
import { inferProviderFromBaseURL } from '../agent/registry.js';
|
|
26
27
|
import { getDefaultSessionStore } from '../persistence/session.js';
|
|
@@ -464,6 +465,7 @@ export async function startRepl(opts = {}) {
|
|
|
464
465
|
initialModel: model,
|
|
465
466
|
maxSteps: currentMaxSteps,
|
|
466
467
|
cwd,
|
|
468
|
+
version: readVersion(),
|
|
467
469
|
initialStatus: { status: 'idle' },
|
|
468
470
|
approvalBridge: tuiBridge,
|
|
469
471
|
isFullscreen: isAltScreen,
|
|
@@ -533,7 +535,7 @@ export async function startRepl(opts = {}) {
|
|
|
533
535
|
const rec = await tuiStore.create({ cwd, task: taskText, config: { model, maxSteps: currentMaxSteps } });
|
|
534
536
|
sessionId = rec.id;
|
|
535
537
|
tuiSessionId = rec.id;
|
|
536
|
-
// Session info goes to status bar, not transcript (clean
|
|
538
|
+
// Session info goes to status bar, not transcript (clean Klyro transcript)
|
|
537
539
|
queuedStatus({ status: 'running', step: 0, model });
|
|
538
540
|
}
|
|
539
541
|
catch {
|
|
@@ -793,17 +795,7 @@ export async function startRepl(opts = {}) {
|
|
|
793
795
|
return;
|
|
794
796
|
}
|
|
795
797
|
case 'version': {
|
|
796
|
-
|
|
797
|
-
const { resolve, dirname } = await import('node:path');
|
|
798
|
-
const { fileURLToPath } = await import('node:url');
|
|
799
|
-
try {
|
|
800
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
801
|
-
const pkg = JSON.parse(readFileSync(resolve(here, '../../package.json'), 'utf-8'));
|
|
802
|
-
queuedAppend({ id: `ver-${Date.now()}`, kind: 'text', text: `klyro ${pkg.version ?? '0.0.0'}`, role: 'assistant' });
|
|
803
|
-
}
|
|
804
|
-
catch {
|
|
805
|
-
queuedAppend({ id: `ver-${Date.now()}`, kind: 'text', text: 'klyro (version unknown)', role: 'assistant' });
|
|
806
|
-
}
|
|
798
|
+
queuedAppend({ id: `ver-${Date.now()}`, kind: 'text', text: `klyro ${readVersion()}`, role: 'assistant' });
|
|
807
799
|
return;
|
|
808
800
|
}
|
|
809
801
|
case 'cost': {
|
package/dist/context/klyro-md.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function loadKlyroMd(cwd) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
// Root (imports resolved relative to each file, contained to cwd)
|
|
40
|
-
for (const name of ['KLYRO.md', 'KLYRO.local.md', 'AGENTS.md', '
|
|
40
|
+
for (const name of ['KLYRO.md', 'KLYRO.local.md', 'AGENTS.md', '.cursorrules']) {
|
|
41
41
|
const p = path.join(cwd, name);
|
|
42
42
|
try {
|
|
43
43
|
const t = await fs.readFile(p, 'utf-8');
|
package/dist/index.js
CHANGED
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
* Configure via env: KLYRO_BASE_URL, KLYRO_API_KEY, KLYRO_MODEL.
|
|
13
13
|
*/
|
|
14
14
|
import { Command, InvalidArgumentError } from 'commander';
|
|
15
|
-
import { readFileSync } from 'node:fs';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { dirname, resolve } from 'node:path';
|
|
18
15
|
import { chat } from './chat.js';
|
|
19
16
|
import { repl } from './repl.js';
|
|
20
17
|
import { startRepl } from './cli/repl.js';
|
|
@@ -25,46 +22,7 @@ import { runDoctor } from './cli/doctor.js';
|
|
|
25
22
|
import { runCompletion } from './cli/completion.js';
|
|
26
23
|
import { runUpdate } from './cli/update.js';
|
|
27
24
|
import { runLogin, runLogout } from './cli/auth.js';
|
|
28
|
-
|
|
29
|
-
// published version. Walks up from this file (src/index.ts) to find the
|
|
30
|
-
// nearest package.json.
|
|
31
|
-
function readVersion() {
|
|
32
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
const candidates = [
|
|
34
|
-
resolve(here, '..', 'package.json'),
|
|
35
|
-
resolve(here, '../..', 'package.json'),
|
|
36
|
-
resolve(here, '../../package.json'),
|
|
37
|
-
];
|
|
38
|
-
for (const pkgPath of candidates) {
|
|
39
|
-
try {
|
|
40
|
-
const raw = readFileSync(pkgPath, 'utf8');
|
|
41
|
-
const pkg = JSON.parse(raw);
|
|
42
|
-
if (typeof pkg.version === 'string' && pkg.version.length > 0)
|
|
43
|
-
return pkg.version;
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
const e = err;
|
|
47
|
-
// Only ignore missing file; surface JSON parse errors
|
|
48
|
-
if (e.code === 'ENOENT')
|
|
49
|
-
continue;
|
|
50
|
-
// Malformed JSON — warn but don't crash version output
|
|
51
|
-
process.stderr.write(`klyro: warning: malformed ${pkgPath}: ${e.message}\n`);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Fallback: try require-style resolution
|
|
56
|
-
try {
|
|
57
|
-
const pkgPath = resolve(process.cwd(), 'package.json');
|
|
58
|
-
const raw = readFileSync(pkgPath, 'utf8');
|
|
59
|
-
const pkg = JSON.parse(raw);
|
|
60
|
-
if (pkg.name === 'klyro' && typeof pkg.version === 'string')
|
|
61
|
-
return pkg.version;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// ignore
|
|
65
|
-
}
|
|
66
|
-
return '0.0.0';
|
|
67
|
-
}
|
|
25
|
+
import { readVersion } from './version.js';
|
|
68
26
|
const VERSION = readVersion();
|
|
69
27
|
function parsePositiveInt(name, v) {
|
|
70
28
|
const n = Number(v);
|
package/dist/tui/app.js
CHANGED
|
@@ -13,6 +13,7 @@ import { tokens, g } from './tokens.js';
|
|
|
13
13
|
import { initialScroll, scrollReducer, resolveTopRow, maxTopFor, } from './scroll-model.js';
|
|
14
14
|
import { buildIndex, itemAtRow, MeasureCache } from './measure.js';
|
|
15
15
|
import { renderMarkdownLines } from './markdown.js';
|
|
16
|
+
import { readVersion } from '../version.js';
|
|
16
17
|
import { getTranscriptCommand, } from './transcript-commands.js';
|
|
17
18
|
import { estimateCost, getModelInfo } from '../providers/model-info.js';
|
|
18
19
|
let _id = 0;
|
|
@@ -674,7 +675,8 @@ export function App(props) {
|
|
|
674
675
|
setHistIdx(null);
|
|
675
676
|
}
|
|
676
677
|
});
|
|
677
|
-
|
|
678
|
+
// Single source of truth: package.json via version.ts — never hardcoded.
|
|
679
|
+
const ver = props.version ?? readVersion();
|
|
678
680
|
const rule = g('rule').repeat(Math.max(10, width - 2));
|
|
679
681
|
// Single cost/context source: model-aware registry rates + window.
|
|
680
682
|
const cost = estimateCost(status.model, status.usageInput, status.usageOutput);
|
package/dist/tui/app.test.js
CHANGED
|
@@ -6,8 +6,8 @@ describe('App', () => {
|
|
|
6
6
|
it('shows the empty-state hint and the status line', () => {
|
|
7
7
|
const { lastFrame } = render(_jsx(App, { initialModel: "mock", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { } }));
|
|
8
8
|
const out = lastFrame();
|
|
9
|
-
//
|
|
10
|
-
expect(out).toMatch(/
|
|
9
|
+
// Top bar shows KLYRO + sessions, center shows Message Klyro placeholder
|
|
10
|
+
expect(out).toMatch(/Sessions|Message Klyro/i);
|
|
11
11
|
expect(out).toMatch(/Message Klyro|Type a message/i);
|
|
12
12
|
});
|
|
13
13
|
it('renders initial transcript items', () => {
|
|
@@ -565,6 +565,16 @@ describe('App', () => {
|
|
|
565
565
|
const top = await waitForMatch(lastFrame, /⇅ 0\/\d+/);
|
|
566
566
|
expect(top).toMatch(/MSG-00-tag/);
|
|
567
567
|
});
|
|
568
|
+
it('header shows the passed version, defaulting to package.json (never stale)', async () => {
|
|
569
|
+
const { readVersion } = await import('../version.js');
|
|
570
|
+
const { lastFrame: f1 } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, version: "9.9.9-test" }));
|
|
571
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
572
|
+
expect(f1() ?? '').toContain('9.9.9-test');
|
|
573
|
+
const { lastFrame: f2 } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { } }));
|
|
574
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
575
|
+
expect(f2() ?? '').toContain(readVersion());
|
|
576
|
+
expect(f2() ?? '').not.toContain('0.1.27');
|
|
577
|
+
});
|
|
568
578
|
it('Shift+Up / Shift+Down scroll by one line', async () => {
|
|
569
579
|
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true, initialTranscript: makeInitialTranscript(25) }));
|
|
570
580
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -16,11 +16,11 @@ describe('App visual snapshot', () => {
|
|
|
16
16
|
it('renders header + statusline + transcript + input at idle', () => {
|
|
17
17
|
const { lastFrame } = render(_jsx(App, { ...DEFAULT_PROPS, initialModel: "gpt-4o-mini", maxSteps: 30, initialStatus: { status: 'idle', model: 'gpt-4o-mini', step: 0, maxSteps: 30, usageInput: 0, usageOutput: 0, repairs: 0 } }));
|
|
18
18
|
const frame = lastFrame();
|
|
19
|
-
//
|
|
20
|
-
expect(frame).toMatch(/
|
|
19
|
+
// Top bar shows KLYRO, transcript shows sessions/files context
|
|
20
|
+
expect(frame).toMatch(/KLYRO/i);
|
|
21
21
|
expect(frame).toMatch(/demo|Sessions|Files/i);
|
|
22
22
|
expect(frame).toMatch(/shift\+tab|for history|Message Klyro|Type a message/i);
|
|
23
|
-
expect(frame).toMatch(/Message Klyro
|
|
23
|
+
expect(frame).toMatch(/Message Klyro|>/i);
|
|
24
24
|
});
|
|
25
25
|
it('renders a transcript with assistant text', () => {
|
|
26
26
|
const { lastFrame } = render(_jsx(App, { ...DEFAULT_PROPS, initialModel: "gpt-4o-mini", maxSteps: 30, initialStatus: { status: 'idle', model: 'gpt-4o-mini', step: 0, maxSteps: 30, usageInput: 0, usageOutput: 0, repairs: 0 }, initialTranscript: [
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the Klyro version (read from package.json).
|
|
3
|
+
*
|
|
4
|
+
* Every surface — `klyro --version`, the TUI header, `/version` — reads
|
|
5
|
+
* through here, so a version bump in package.json shows up everywhere with
|
|
6
|
+
* no hardcoded fallbacks rotting behind.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readVersion(): string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the Klyro version (read from package.json).
|
|
3
|
+
*
|
|
4
|
+
* Every surface — `klyro --version`, the TUI header, `/version` — reads
|
|
5
|
+
* through here, so a version bump in package.json shows up everywhere with
|
|
6
|
+
* no hardcoded fallbacks rotting behind.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, resolve } from 'node:path';
|
|
11
|
+
export function readVersion() {
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const candidates = [
|
|
14
|
+
resolve(here, '..', 'package.json'),
|
|
15
|
+
resolve(here, '../..', 'package.json'),
|
|
16
|
+
resolve(here, '../../package.json'),
|
|
17
|
+
];
|
|
18
|
+
for (const pkgPath of candidates) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
21
|
+
const pkg = JSON.parse(raw);
|
|
22
|
+
if (typeof pkg.version === 'string' && pkg.version.length > 0)
|
|
23
|
+
return pkg.version;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const e = err;
|
|
27
|
+
// Only ignore missing file; surface JSON parse errors
|
|
28
|
+
if (e.code === 'ENOENT')
|
|
29
|
+
continue;
|
|
30
|
+
// Malformed JSON — warn but don't crash version output
|
|
31
|
+
process.stderr.write(`klyro: warning: malformed ${pkgPath}: ${e.message}\n`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Fallback: try require-style resolution
|
|
36
|
+
try {
|
|
37
|
+
const pkgPath = resolve(process.cwd(), 'package.json');
|
|
38
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
39
|
+
const pkg = JSON.parse(raw);
|
|
40
|
+
if (pkg.name === 'klyro' && typeof pkg.version === 'string')
|
|
41
|
+
return pkg.version;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// ignore
|
|
45
|
+
}
|
|
46
|
+
return '0.0.0';
|
|
47
|
+
}
|
package/package.json
CHANGED