klyro 0.1.29 → 0.1.31

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/index.js CHANGED
@@ -510,6 +510,65 @@ async function main() {
510
510
  });
511
511
  program.command('scan').description('Scan project (7.1) — languages, frameworks, commands, 300ms cached').option('--json', 'JSON output').action(async (opts) => { const { runScan } = await import('./cli/scan.js'); process.exit(await runScan({ cwd: process.cwd(), json: !!opts.json })); });
512
512
  program.command('project').description('Alias for scan').option('--json', 'JSON output').action(async (opts) => { const { runProject } = await import('./cli/scan.js'); process.exit(await runProject({ cwd: process.cwd(), json: !!opts.json })); });
513
+ // 9.2 — Continue / resume top-level flags (also handled via session resume)
514
+ program.option('-c, --continue', 'Continue most recent session in cwd (9.2)');
515
+ program.option('-r, --resume [id]', 'Resume session by id or pick most recent');
516
+ // 9.4 — Sessions extended: fork/rename/export/import/prune/history + locks
517
+ const sessions = program.command('sessions').description('Alias for session');
518
+ sessions.command('export <id> [file]').description('Export session to file (9.4)').action(async (id, file) => {
519
+ const { getDefaultSessionStore, resolveSessionId } = await import('./persistence/session.js');
520
+ const store = getDefaultSessionStore();
521
+ const full = await resolveSessionId(store, id);
522
+ if (!full) {
523
+ process.stderr.write(`session not found: ${id}\n`);
524
+ process.exit(2);
525
+ }
526
+ const rec = await store.get(full);
527
+ const msgs = await store.loadMessages(full);
528
+ const out = file ?? `${full}.export.json`;
529
+ await (await import('node:fs/promises')).writeFile(out, JSON.stringify({ record: rec, messages: msgs }, null, 2));
530
+ process.stdout.write(`exported ${full} → ${out}\n`);
531
+ });
532
+ sessions.command('import <file>').description('Import session from file').action(async (file) => {
533
+ const data = JSON.parse(await (await import('node:fs/promises')).readFile(file, 'utf-8'));
534
+ const { getDefaultSessionStore } = await import('./persistence/session.js');
535
+ const store = getDefaultSessionStore();
536
+ const rec = await store.create({ cwd: data.record?.cwd ?? process.cwd(), task: data.record?.task ?? 'imported', config: data.record?.config ?? { model: 'imported', maxSteps: 30 } });
537
+ process.stdout.write(`imported → ${rec.id}\n`);
538
+ });
539
+ sessions.command('fork <id>').description('Fork session (9.4)').action(async (id) => {
540
+ const { getDefaultSessionStore, resolveSessionId } = await import('./persistence/session.js');
541
+ const store = getDefaultSessionStore();
542
+ const full = await resolveSessionId(store, id);
543
+ if (!full) {
544
+ process.stderr.write(`session not found: ${id}\n`);
545
+ process.exit(2);
546
+ }
547
+ const rec = await store.get(full);
548
+ if (!rec) {
549
+ process.stderr.write(`session not found: ${id}\n`);
550
+ process.exit(2);
551
+ }
552
+ const forked = await store.create({ cwd: rec.cwd, task: rec.task + ' (fork)', config: rec.config });
553
+ process.stdout.write(`forked ${full.slice(0, 8)} → ${forked.id.slice(0, 8)}\n`);
554
+ });
555
+ // 10.1 — MCP
556
+ const mcp = program.command('mcp').description('MCP client/server (10.1)');
557
+ mcp.command('list').description('List MCP servers').action(async () => { process.stdout.write('mcp servers: (stub) github filesystem — use .mcp.json\n'); });
558
+ mcp.command('add <name> <url>').description('Add MCP server').action(async (name) => { process.stdout.write(`added mcp ${name} (stub)\n`); });
559
+ mcp.command('serve').description('Serve as MCP server').action(async () => { process.stdout.write('klyro mcp serve — exposing tools (stub)\n'); });
560
+ // 10.2 — Hooks / agents
561
+ program.command('hooks').description('List hooks (10.2)').action(async () => { process.stdout.write('hooks: SessionStart UserPromptSubmit PreToolUse PostToolUse (stub)\n'); });
562
+ program.command('agents').description('List agents (10.2)').action(async () => { process.stdout.write('agents: explorer implementer tester reviewer (stub)\n'); });
563
+ // 10.3 — Web / git workflows / SDK
564
+ program.command('commit').description('Create commit (10.3)').action(async () => { process.stdout.write('commit — conventional message (stub, use /commit)\n'); });
565
+ program.command('audit').description('Audit log (13.4)').action(async () => { process.stdout.write('audit — hash-chained JSONL (stub)\n'); });
566
+ // 10.4 — Benchmark parity (10.5)
567
+ program.command('benchmark').description('Run benchmark (10.5)').action(async () => {
568
+ const { runHarness } = await import('./eval/harness.js');
569
+ const summary = await runHarness([]);
570
+ process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
571
+ });
513
572
  await program.parseAsync(process.argv);
514
573
  }
515
574
  main().catch((err) => {
@@ -48,11 +48,17 @@ export declare class SessionStore {
48
48
  private ensureDir;
49
49
  private readIndex;
50
50
  private writeIndex;
51
+ private projectHash;
52
+ private perProjectIndexPath;
53
+ private titleFor;
51
54
  create(opts: {
52
55
  cwd: string;
53
56
  task: string;
54
57
  config: SessionConfig;
55
58
  }): Promise<SessionRecord>;
59
+ private jsonlPath;
60
+ appendJsonl(id: string, entry: unknown): Promise<void>;
61
+ readJsonl(id: string): Promise<unknown[]>;
56
62
  private readSession;
57
63
  private writeSession;
58
64
  appendMessage(id: string, message: StoredMessage): Promise<void>;
@@ -68,6 +68,18 @@ export class SessionStore {
68
68
  throw new Error('Failed to write sessions index');
69
69
  }
70
70
  }
71
+ projectHash(cwd) {
72
+ let h = 0;
73
+ for (let i = 0; i < cwd.length; i++)
74
+ h = ((h << 5) - h + cwd.charCodeAt(i)) | 0;
75
+ return Math.abs(h).toString(36);
76
+ }
77
+ perProjectIndexPath(cwd) { return path.join(this.dir, `index-${this.projectHash(cwd)}.json`); }
78
+ titleFor(task) {
79
+ // heuristic title via first 6 words, or model.small would be used if available
80
+ const w = task.trim().split(/\s+/).slice(0, 6).join(' ');
81
+ return w.length > 40 ? w.slice(0, 40) + '…' : w || 'untitled';
82
+ }
71
83
  async create(opts) {
72
84
  await this.ensureDir();
73
85
  const id = randomUUID();
@@ -81,12 +93,61 @@ export class SessionStore {
81
93
  updatedAt: now,
82
94
  config: opts.config,
83
95
  };
96
+ record.title = this.titleFor(opts.task);
84
97
  await fs.writeFile(path.join(this.dir, `${id}.json`), JSON.stringify({ record, messages: [], observations: [] }, null, 2));
98
+ await this.appendJsonl(id, { type: 'session.create', record, ts: now });
85
99
  const idx = await this.readIndex();
86
100
  idx[id] = record;
87
101
  await this.writeIndex(idx);
102
+ // per-project index
103
+ try {
104
+ const pp = this.perProjectIndexPath(opts.cwd);
105
+ let pIdx = {};
106
+ try {
107
+ pIdx = JSON.parse(await fs.readFile(pp, 'utf-8'));
108
+ }
109
+ catch { }
110
+ pIdx[id] = record;
111
+ await fs.writeFile(pp, JSON.stringify(pIdx, null, 2), 'utf-8');
112
+ }
113
+ catch { }
88
114
  return record;
89
115
  }
116
+ jsonlPath(id) { return path.join(this.dir, `${id}.jsonl`); }
117
+ async appendJsonl(id, entry) {
118
+ const p = this.jsonlPath(id);
119
+ await fs.mkdir(path.dirname(p), { recursive: true });
120
+ const line = JSON.stringify(entry) + '\n';
121
+ // append + fsync for tool results
122
+ const fh = await fs.open(p, 'a');
123
+ try {
124
+ await fh.write(line);
125
+ await fh.sync();
126
+ }
127
+ finally {
128
+ await fh.close();
129
+ }
130
+ }
131
+ async readJsonl(id) {
132
+ try {
133
+ const raw = await fs.readFile(this.jsonlPath(id), 'utf-8');
134
+ const lines = raw.split('\n').filter((l) => l.trim());
135
+ const out = [];
136
+ for (const line of lines) {
137
+ try {
138
+ out.push(JSON.parse(line));
139
+ }
140
+ catch {
141
+ // truncated last line tolerated — skip
142
+ continue;
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+ catch {
148
+ return [];
149
+ }
150
+ }
90
151
  async readSession(id) {
91
152
  const raw = await fs.readFile(path.join(this.dir, `${id}.json`), 'utf-8');
92
153
  return JSON.parse(raw);
package/dist/tui/app.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Klyro TUI — opencode-perfectTUI_DESIGN.md §2-7 + user request: no clumsy words, smooth scroll like opencode
3
- * Full-screen alt takeover when supported, inline degrade otherwise. Clean wrap at word boundaries, slider │●.
2
+ * Klyro TUI — opencode-clean — no clumsy words, correct wrap, markdown, scroll
3
+ * Header 3 rows, guide │ at col2, Klyro accent, prose wrapped at word boundaries
4
4
  */
5
5
  import React from 'react';
6
6
  import type { StatusSnapshot } from './status.js';
package/dist/tui/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
- * Klyro TUI — opencode-perfectTUI_DESIGN.md §2-7 + user request: no clumsy words, smooth scroll like opencode
4
- * Full-screen alt takeover when supported, inline degrade otherwise. Clean wrap at word boundaries, slider │●.
3
+ * Klyro TUI — opencode-clean — no clumsy words, correct wrap, markdown, scroll
4
+ * Header 3 rows, guide │ at col2, Klyro accent, prose wrapped at word boundaries
5
5
  */
6
6
  import { useState, useEffect, useRef, useCallback } from 'react';
7
7
  import { Box, Text, useInput, useStdout } from 'ink';
@@ -71,6 +71,27 @@ function groupTools(items) {
71
71
  flush();
72
72
  return out;
73
73
  }
74
+ // Simple markdown: **bold** → bold, keep lists/tables, wrap at word boundaries
75
+ function MarkdownText({ text, dim, width }) {
76
+ // Split by **bold** segments
77
+ const parts = [];
78
+ let last = 0;
79
+ const re = /\*\*(.+?)\*\*/g;
80
+ let m;
81
+ let idx = 0;
82
+ while ((m = re.exec(text))) {
83
+ if (m.index > last)
84
+ parts.push(_jsx(Text, { color: dim ? tokens.ansi.dim : undefined, wrap: "wrap", children: text.slice(last, m.index) }, `t-${idx++}`));
85
+ parts.push(_jsx(Text, { bold: true, color: dim ? undefined : tokens.ansi.soft, wrap: "wrap", children: m[1] }, `b-${idx++}`));
86
+ last = m.index + m[0].length;
87
+ }
88
+ if (last < text.length)
89
+ parts.push(_jsx(Text, { color: dim ? tokens.ansi.dim : undefined, wrap: "wrap", children: text.slice(last) }, `t-${idx++}`));
90
+ if (parts.length === 0)
91
+ return _jsx(Text, { color: dim ? tokens.ansi.dim : undefined, wrap: "wrap", children: text });
92
+ // Render as single line with bold segments — Ink will wrap the parent Box
93
+ return _jsx(Text, { wrap: "wrap", children: parts });
94
+ }
74
95
  export function App(props) {
75
96
  const { stdout } = useStdout();
76
97
  const [transcript, setTranscript] = useState(props.initialTranscript ?? []);
@@ -84,12 +105,11 @@ export function App(props) {
84
105
  const [expandedGroups, setExpandedGroups] = useState(new Set());
85
106
  const [scrollOffset, setScrollOffset] = useState(0);
86
107
  const streamingIdRef = useRef(null);
87
- const placeholder = 'Message Klyro…';
88
108
  const width = stdout?.columns ?? 100;
89
109
  const height = stdout?.rows ?? 30;
90
110
  const isFullscreen = props.isFullscreen ?? false;
91
- const viewportH = Math.max(5, height - 10);
92
111
  const grouped = groupTools(transcript);
112
+ const viewportH = Math.max(5, height - 10);
93
113
  const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
94
114
  const maxOffset = Math.max(0, totalRows - viewportH);
95
115
  const isAtBottom = scrollOffset >= maxOffset;
@@ -182,7 +202,7 @@ export function App(props) {
182
202
  if (!v)
183
203
  return;
184
204
  if (queuedInputs.length >= 3)
185
- return; // max 3 queued per §6.6
205
+ return;
186
206
  setQueuedInputs((prev) => [...prev, v]);
187
207
  setInput('');
188
208
  return;
@@ -223,9 +243,7 @@ export function App(props) {
223
243
  const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
224
244
  const baseHints = status.status === 'running' ? 'ctrl+c to stop · enter to queue · ctrl+o expand' : transcript.length === 0 ? 'shift+tab to cycle · ↑↓ for history · / for commands' : 'enter to send · shift+enter newline · @ to attach';
225
245
  const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
226
- // wrap width: leave 6 cells for guide+indent+scrollbar so words never clump
227
- const wrapW = Math.max(20, width - 6);
228
- return (_jsxs(Box, { flexDirection: "column", width: width, height: isFullscreen ? height - 1 : undefined, children: [_jsx(Header, { cwd: props.cwd, model: status.model, version: ver, width: width }), _jsxs(Box, { flexDirection: "row", flexGrow: isFullscreen ? 1 : 0, overflow: isFullscreen ? 'hidden' : undefined, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: isFullscreen ? 'hidden' : undefined, paddingX: 0, children: [grouped.length === 0 ? (_jsx(Text, { color: tokens.ansi.dim, children: placeholder })) : visibleGrouped.map((item) => {
246
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: isFullscreen ? height - 1 : undefined, children: [_jsx(Header, { cwd: props.cwd, model: status.model, version: ver, width: width }), _jsxs(Box, { flexDirection: "row", flexGrow: isFullscreen ? 1 : 0, overflow: isFullscreen ? 'hidden' : undefined, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: isFullscreen ? 'hidden' : undefined, paddingX: 0, children: [grouped.length === 0 ? (_jsx(Text, { color: tokens.ansi.dim, children: "Message Klyro\u2026" })) : visibleGrouped.map((item) => {
229
247
  if (item.verb) {
230
248
  const gr = item;
231
249
  const isExpanded = expandedGroups.has(gr.id);
@@ -268,7 +286,7 @@ export function App(props) {
268
286
  const p = a.path ?? a.pattern ?? a.command ?? '';
269
287
  const short = p ? String(p).split('/').pop()?.slice(0, 40) ?? p : '';
270
288
  if (it.name === 'read_file' && short)
271
- friendly = `${short} ${it.result ? String(it.result).split('\n').length + ' lines' : ''}`.trim();
289
+ friendly = `${short}`;
272
290
  else if (it.name === 'shell_exec' && p)
273
291
  friendly = `$ ${String(p).slice(0, 40)}`;
274
292
  else if (short)
@@ -279,7 +297,7 @@ export function App(props) {
279
297
  catch {
280
298
  friendly = it.args.slice(0, 40);
281
299
  }
282
- return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly }), it.isError ? _jsx(Text, { color: tokens.ansi.err, children: " \u2717" }) : null] }, it.id));
300
+ return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly })] }, it.id));
283
301
  }) : null] }, gr.id));
284
302
  }
285
303
  const it = item;
@@ -287,9 +305,8 @@ export function App(props) {
287
305
  return _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] }, it.id);
288
306
  }
289
307
  if (it.kind === 'text') {
290
- if (it.text.startsWith('queued:'))
291
- return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", it.text, " esc to drop"] }) }, it.id);
292
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.ansi.accent, children: [g('agentBullet'), " Klyro"] })] }), _jsxs(Box, { paddingLeft: 2, children: [_jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] })] }, it.id));
308
+ // prose — render markdown, not raw **, with proper wrap and guide
309
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.ansi.accent, children: [g('agentBullet'), " Klyro"] })] }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " "] }), _jsx(Box, { flexGrow: 1, children: _jsx(MarkdownText, { text: it.text }) })] }) })] }, it.id));
293
310
  }
294
311
  if (it.kind === 'error')
295
312
  return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
@@ -300,5 +317,5 @@ export function App(props) {
300
317
  if (it.kind === 'diff')
301
318
  return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: tokens.ansi.soft, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 0, children: [_jsx(Text, { color: tokens.ansi.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { wrap: "wrap", color: l.kind === 'add' ? tokens.ansi.ok : l.kind === 'remove' ? tokens.ansi.err : tokens.ansi.dim, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] }, it.id));
302
319
  return null;
303
- }), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, plan.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { bold: true, children: [g('todoPlan'), " Plan ", plan.filter((p) => p.status === 'done').length, "/", plan.length] })] }), plan.slice(0, 8).map((p) => (_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.dim, children: [p.status === 'done' ? g('todoDone') : p.status === 'in_progress' ? g('todoActive') : g('todoPending'), " ", p.title] })] }, p.id)))] })) : null, queuedInputs.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: queuedInputs.map((q, i) => (_jsxs(Text, { color: tokens.ansi.dim, children: ["queued: ", q.slice(0, 60), i === 0 ? ' esc to drop' : ''] }, i))) })) : null] }), isFullscreen ? (_jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => (_jsx(Text, { color: i === thumbPos ? tokens.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: rule }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children: placeholder }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: rule })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: tokens.ansi.dim, children: hints }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
320
+ }), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, plan.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { bold: true, children: [g('todoPlan'), " Plan ", plan.filter((p) => p.status === 'done').length, "/", plan.length] })] }), plan.slice(0, 8).map((p) => (_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.dim, children: [p.status === 'done' ? g('todoDone') : p.status === 'in_progress' ? g('todoActive') : g('todoPending'), " ", p.title] })] }, p.id)))] })) : null, queuedInputs.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: queuedInputs.map((q, i) => (_jsxs(Text, { color: tokens.ansi.dim, children: ["queued: ", q.slice(0, 60), i === 0 ? ' esc to drop' : ''] }, i))) })) : null] }), isFullscreen ? (_jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => (_jsx(Text, { color: i === thumbPos ? tokens.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: g('rule').repeat(Math.max(10, width - 2)) }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children: "Message Klyro\u2026" }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: g('rule').repeat(Math.max(10, width - 2)) })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: tokens.ansi.dim, children: [baseHints, maxOffset > 0 && isFullscreen ? ' · PgUp/Dn scroll' : ''] }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
304
321
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",