ikie-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +634 -0
- package/dist/attachments.d.ts +33 -0
- package/dist/attachments.js +239 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +89 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +69 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -0
- package/dist/memory.d.ts +14 -0
- package/dist/memory.js +71 -0
- package/dist/renderer.d.ts +7 -0
- package/dist/renderer.js +203 -0
- package/dist/repl.d.ts +3 -0
- package/dist/repl.js +948 -0
- package/dist/session.d.ts +21 -0
- package/dist/session.js +85 -0
- package/dist/theme.d.ts +66 -0
- package/dist/theme.js +365 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +477 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'process';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
import minimist from 'minimist';
|
|
5
|
+
import { loadConfig, getApiKey, FIREWORKS_BASE_URL, DEFAULT_MODEL, isLoggedIn, IKIE_API_BASE } from './config.js';
|
|
6
|
+
import { detectProjectContext, formatContextForPrompt } from './context.js';
|
|
7
|
+
import { loadAllMemory, formatMemoryForPrompt } from './memory.js';
|
|
8
|
+
import { buildSystemPrompt, Agent } from './agent.js';
|
|
9
|
+
import { startREPL } from './repl.js';
|
|
10
|
+
import { login, logout } from './auth.js';
|
|
11
|
+
import { c, errorLine } from './theme.js';
|
|
12
|
+
const argv = minimist(process.argv.slice(2), {
|
|
13
|
+
string: ['model', 'api-key', 'base-url', 'rpm'],
|
|
14
|
+
boolean: ['help', 'version', 'yes', 'verbose'],
|
|
15
|
+
alias: { h: 'help', v: 'version', y: 'yes', m: 'model' },
|
|
16
|
+
});
|
|
17
|
+
function printUsage() {
|
|
18
|
+
console.log(`
|
|
19
|
+
${c.primary.bold('ikie')} ${c.muted('— agentic coding CLI powered by Kimi K2')}
|
|
20
|
+
|
|
21
|
+
${c.primary('Usage:')}
|
|
22
|
+
${c.accent('ikie')} Start interactive REPL
|
|
23
|
+
${c.accent('ikie')} ${c.muted('"<message>"')} One-shot command
|
|
24
|
+
${c.accent('ikie login')} Sign in to your ikie account
|
|
25
|
+
${c.accent('ikie logout')} Sign out of your ikie account
|
|
26
|
+
${c.accent('ikie')} ${c.muted('--model <id>')} Use specific model
|
|
27
|
+
|
|
28
|
+
${c.primary('Options:')}
|
|
29
|
+
${c.accent('-m, --model')} ${c.muted('<id>')} Model ID (default: ${c.dim(DEFAULT_MODEL)})
|
|
30
|
+
${c.accent('-y, --yes')} Auto-approve all tool executions
|
|
31
|
+
${c.accent('--api-key')} ${c.muted('<key>')} Fireworks API key
|
|
32
|
+
${c.accent('--base-url')} ${c.muted('<url>')} API base URL (default: Fireworks AI)
|
|
33
|
+
${c.accent('--rpm')} ${c.muted('<number>')} Max model requests per minute (default: 10)
|
|
34
|
+
${c.accent('--verbose')} Debug output
|
|
35
|
+
${c.accent('-h, --help')} Show this help
|
|
36
|
+
${c.accent('-v, --version')} Show version
|
|
37
|
+
|
|
38
|
+
${c.primary('Environment:')}
|
|
39
|
+
${c.muted('FIREWORKS_API_KEY')} Fireworks AI API key
|
|
40
|
+
${c.muted('IKIE_API_KEY')} Alternative key env var
|
|
41
|
+
|
|
42
|
+
${c.primary('In-session commands:')}
|
|
43
|
+
${c.muted('/help /clear /memory /session /context /model /rpm /exit')}
|
|
44
|
+
${c.muted('!<cmd> Run shell command directly')}
|
|
45
|
+
${c.muted('Ctrl+V Paste image from clipboard')}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
if (argv.version) {
|
|
50
|
+
try {
|
|
51
|
+
const { readFileSync } = await import('fs');
|
|
52
|
+
const { fileURLToPath } = await import('url');
|
|
53
|
+
const { dirname, join } = await import('path');
|
|
54
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf-8'));
|
|
55
|
+
console.log(`ikie v${pkg.version}`);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.log('ikie v0.1.0');
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
if (argv.help) {
|
|
63
|
+
printUsage();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
// ── account subcommands ───────────────────────────────────────
|
|
67
|
+
const cmd = argv._[0];
|
|
68
|
+
if (cmd === 'login') {
|
|
69
|
+
await login();
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
if (cmd === 'logout') {
|
|
73
|
+
logout();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
const config = loadConfig();
|
|
77
|
+
if (argv.model)
|
|
78
|
+
config.model = argv.model;
|
|
79
|
+
if (argv.yes)
|
|
80
|
+
config.autoApprove = true;
|
|
81
|
+
if (argv['api-key'])
|
|
82
|
+
config.apiKey = argv['api-key'];
|
|
83
|
+
if (argv['base-url'])
|
|
84
|
+
config.baseURL = argv['base-url'];
|
|
85
|
+
if (argv.rpm) {
|
|
86
|
+
const rpm = Number(argv.rpm);
|
|
87
|
+
if (Number.isFinite(rpm) && rpm > 0)
|
|
88
|
+
config.requestsPerMinute = Math.floor(rpm);
|
|
89
|
+
}
|
|
90
|
+
// When signed into a hosted account, default to the ikie API endpoint
|
|
91
|
+
// unless the user explicitly overrode it on the CLI.
|
|
92
|
+
if (isLoggedIn(config) && !argv['base-url']) {
|
|
93
|
+
config.baseURL = IKIE_API_BASE;
|
|
94
|
+
}
|
|
95
|
+
const apiKey = getApiKey(config);
|
|
96
|
+
if (!apiKey) {
|
|
97
|
+
console.error(`
|
|
98
|
+
${errorLine('Not signed in.')}
|
|
99
|
+
|
|
100
|
+
Sign in to your ikie account (recommended):
|
|
101
|
+
${c.accent('ikie login')}
|
|
102
|
+
|
|
103
|
+
Or bring your own Fireworks key:
|
|
104
|
+
${c.accent('export FIREWORKS_API_KEY=fw_...')}
|
|
105
|
+
${c.accent('ikie --api-key fw_...')}
|
|
106
|
+
`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const client = new OpenAI({
|
|
110
|
+
apiKey,
|
|
111
|
+
baseURL: config.baseURL ?? FIREWORKS_BASE_URL,
|
|
112
|
+
});
|
|
113
|
+
const projectCtx = detectProjectContext();
|
|
114
|
+
const projectContextStr = formatContextForPrompt(projectCtx);
|
|
115
|
+
const memory = loadAllMemory();
|
|
116
|
+
const memoryStr = formatMemoryForPrompt(memory);
|
|
117
|
+
const systemPrompt = buildSystemPrompt(projectContextStr, memoryStr);
|
|
118
|
+
if (argv.verbose) {
|
|
119
|
+
console.log(c.muted('─'.repeat(60)));
|
|
120
|
+
console.log(c.muted('System prompt:'));
|
|
121
|
+
console.log(c.dim(systemPrompt.slice(0, 500) + '\n…'));
|
|
122
|
+
console.log(c.muted('─'.repeat(60)));
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
125
|
+
const agent = new Agent(client, config, systemPrompt);
|
|
126
|
+
const oneShot = argv._.length > 0 ? argv._.join(' ') : undefined;
|
|
127
|
+
await startREPL(agent, config, projectContextStr, oneShot);
|
|
128
|
+
}
|
|
129
|
+
main().catch((err) => {
|
|
130
|
+
console.error(errorLine(`Fatal: ${err instanceof Error ? err.message : String(err)}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function loadGlobalMemory(): string;
|
|
2
|
+
export declare function loadProjectMemory(): string;
|
|
3
|
+
export declare function saveGlobalMemory(content: string): void;
|
|
4
|
+
export declare function saveProjectMemory(content: string): void;
|
|
5
|
+
export declare function appendGlobalMemory(note: string): void;
|
|
6
|
+
export declare function appendProjectMemory(note: string): void;
|
|
7
|
+
export interface MemoryContext {
|
|
8
|
+
global: string;
|
|
9
|
+
project: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadAllMemory(): MemoryContext;
|
|
12
|
+
export declare function formatMemoryForPrompt(mem: MemoryContext): string;
|
|
13
|
+
export declare function getProjectMemoryPath(): string;
|
|
14
|
+
export declare function getGlobalMemoryPath(): string;
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import { GLOBAL_MEMORY_FILE } from './config.js';
|
|
4
|
+
const PROJECT_MEMORY_DIR = '.ikie';
|
|
5
|
+
const PROJECT_MEMORY_FILE = join(PROJECT_MEMORY_DIR, 'memory.md');
|
|
6
|
+
function ensureProjectDir() {
|
|
7
|
+
if (!existsSync(PROJECT_MEMORY_DIR)) {
|
|
8
|
+
mkdirSync(PROJECT_MEMORY_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function loadGlobalMemory() {
|
|
12
|
+
try {
|
|
13
|
+
if (existsSync(GLOBAL_MEMORY_FILE)) {
|
|
14
|
+
return readFileSync(GLOBAL_MEMORY_FILE, 'utf-8').trim();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
export function loadProjectMemory() {
|
|
21
|
+
try {
|
|
22
|
+
if (existsSync(PROJECT_MEMORY_FILE)) {
|
|
23
|
+
return readFileSync(PROJECT_MEMORY_FILE, 'utf-8').trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
export function saveGlobalMemory(content) {
|
|
30
|
+
writeFileSync(GLOBAL_MEMORY_FILE, content.trim() + '\n');
|
|
31
|
+
}
|
|
32
|
+
export function saveProjectMemory(content) {
|
|
33
|
+
ensureProjectDir();
|
|
34
|
+
writeFileSync(PROJECT_MEMORY_FILE, content.trim() + '\n');
|
|
35
|
+
}
|
|
36
|
+
export function appendGlobalMemory(note) {
|
|
37
|
+
const existing = loadGlobalMemory();
|
|
38
|
+
const updated = existing
|
|
39
|
+
? `${existing}\n\n${note.trim()}`
|
|
40
|
+
: note.trim();
|
|
41
|
+
saveGlobalMemory(updated);
|
|
42
|
+
}
|
|
43
|
+
export function appendProjectMemory(note) {
|
|
44
|
+
const existing = loadProjectMemory();
|
|
45
|
+
const updated = existing
|
|
46
|
+
? `${existing}\n\n${note.trim()}`
|
|
47
|
+
: note.trim();
|
|
48
|
+
saveProjectMemory(updated);
|
|
49
|
+
}
|
|
50
|
+
export function loadAllMemory() {
|
|
51
|
+
return {
|
|
52
|
+
global: loadGlobalMemory(),
|
|
53
|
+
project: loadProjectMemory(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function formatMemoryForPrompt(mem) {
|
|
57
|
+
const parts = [];
|
|
58
|
+
if (mem.global) {
|
|
59
|
+
parts.push(`## Global Memory\n${mem.global}`);
|
|
60
|
+
}
|
|
61
|
+
if (mem.project) {
|
|
62
|
+
parts.push(`## Project Memory\n${mem.project}`);
|
|
63
|
+
}
|
|
64
|
+
return parts.join('\n\n');
|
|
65
|
+
}
|
|
66
|
+
export function getProjectMemoryPath() {
|
|
67
|
+
return resolve(PROJECT_MEMORY_FILE);
|
|
68
|
+
}
|
|
69
|
+
export function getGlobalMemoryPath() {
|
|
70
|
+
return GLOBAL_MEMORY_FILE;
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function stripAnsi(str: string): string;
|
|
2
|
+
export declare function renderMarkdown(text: string): string;
|
|
3
|
+
export declare function renderThinkingBlock(thinking: string): string;
|
|
4
|
+
export declare function extractThinkTags(text: string): {
|
|
5
|
+
thinking: string;
|
|
6
|
+
response: string;
|
|
7
|
+
};
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { activeTheme } from './theme.js';
|
|
3
|
+
export function stripAnsi(str) {
|
|
4
|
+
return str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
5
|
+
}
|
|
6
|
+
function vlen(str) {
|
|
7
|
+
return stripAnsi(str).length;
|
|
8
|
+
}
|
|
9
|
+
function cols() {
|
|
10
|
+
return process.stdout.columns ?? 80;
|
|
11
|
+
}
|
|
12
|
+
const KW = {
|
|
13
|
+
typescript: ['const', 'let', 'var', 'function', 'class', 'interface', 'type', 'export', 'import', 'from', 'return', 'if', 'else', 'for', 'while', 'async', 'await', 'new', 'this', 'extends', 'implements', 'readonly', 'public', 'private', 'protected', 'static', 'void', 'null', 'undefined', 'true', 'false', 'throw', 'try', 'catch', 'finally', 'switch', 'case', 'break', 'continue', 'of', 'in', 'typeof', 'instanceof', 'enum', 'declare', 'namespace', 'as', 'abstract'],
|
|
14
|
+
javascript: ['const', 'let', 'var', 'function', 'class', 'export', 'import', 'from', 'return', 'if', 'else', 'for', 'while', 'async', 'await', 'new', 'this', 'null', 'undefined', 'true', 'false', 'throw', 'try', 'catch', 'finally', 'switch', 'case', 'break', 'continue', 'of', 'in', 'typeof', 'instanceof'],
|
|
15
|
+
python: ['def', 'class', 'import', 'from', 'return', 'if', 'elif', 'else', 'for', 'while', 'async', 'await', 'with', 'as', 'pass', 'break', 'continue', 'raise', 'try', 'except', 'finally', 'lambda', 'yield', 'None', 'True', 'False', 'and', 'or', 'not', 'in', 'is', 'del', 'global', 'nonlocal', 'assert'],
|
|
16
|
+
go: ['func', 'type', 'struct', 'interface', 'import', 'package', 'var', 'const', 'return', 'if', 'else', 'for', 'range', 'switch', 'case', 'break', 'continue', 'go', 'chan', 'select', 'defer', 'map', 'nil', 'true', 'false', 'make', 'new', 'append', 'len', 'cap', 'close', 'delete'],
|
|
17
|
+
rust: ['fn', 'let', 'mut', 'struct', 'enum', 'impl', 'trait', 'pub', 'use', 'mod', 'crate', 'super', 'return', 'if', 'else', 'for', 'while', 'loop', 'match', 'break', 'continue', 'async', 'await', 'move', 'ref', 'in', 'where', 'true', 'false', 'self', 'Self', 'Some', 'None', 'Ok', 'Err'],
|
|
18
|
+
bash: ['if', 'then', 'else', 'elif', 'fi', 'for', 'do', 'done', 'while', 'until', 'case', 'esac', 'function', 'return', 'export', 'local', 'echo', 'source', 'read', 'exit', 'true', 'false', 'set', 'unset', 'shift', 'eval'],
|
|
19
|
+
sql: ['SELECT', 'FROM', 'WHERE', 'AND', 'OR', 'NOT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'SET', 'DELETE', 'CREATE', 'TABLE', 'ALTER', 'DROP', 'INDEX', 'JOIN', 'INNER', 'LEFT', 'RIGHT', 'OUTER', 'ON', 'GROUP', 'BY', 'ORDER', 'HAVING', 'LIMIT', 'OFFSET', 'DISTINCT', 'AS', 'NULL', 'IS', 'IN', 'LIKE', 'BETWEEN', 'EXISTS', 'UNION', 'ALL'],
|
|
20
|
+
json: [],
|
|
21
|
+
html: ['DOCTYPE', 'html', 'head', 'body', 'div', 'span', 'p', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'form', 'input', 'button', 'script', 'style', 'link', 'meta', 'img', 'nav', 'header', 'footer', 'main', 'section', 'article'],
|
|
22
|
+
css: ['color', 'background', 'display', 'flex', 'grid', 'width', 'height', 'margin', 'padding', 'border', 'font', 'text', 'position', 'top', 'left', 'right', 'bottom', 'z-index', 'overflow', 'transform', 'transition', 'animation', 'opacity', 'box-shadow', 'border-radius'],
|
|
23
|
+
};
|
|
24
|
+
const ALIASES = {
|
|
25
|
+
js: 'javascript', ts: 'typescript', py: 'python',
|
|
26
|
+
sh: 'bash', shell: 'bash', zsh: 'bash', fish: 'bash',
|
|
27
|
+
jsx: 'javascript', tsx: 'typescript', rs: 'rust',
|
|
28
|
+
};
|
|
29
|
+
function syntaxHighlight(code, lang) {
|
|
30
|
+
const canonical = ALIASES[lang.toLowerCase()] ?? lang.toLowerCase();
|
|
31
|
+
const keywords = KW[canonical];
|
|
32
|
+
const colors = activeTheme.colors;
|
|
33
|
+
const keywordColor = chalk.hex(colors.secondary).bold;
|
|
34
|
+
const stringColor = chalk.hex(colors.success);
|
|
35
|
+
const numberColor = chalk.hex(colors.warning);
|
|
36
|
+
const functionColor = chalk.hex(colors.accent);
|
|
37
|
+
const operatorColor = chalk.hex(colors.primary);
|
|
38
|
+
return code.split('\n').map(line => {
|
|
39
|
+
if (/^\s*(#|\/\/|--\s)/.test(line))
|
|
40
|
+
return chalk.hex(colors.muted).italic(line);
|
|
41
|
+
let out = line.replace(/(["'`])((?:\\.|(?!\1)[\s\S])*?)\1/g, m => stringColor(m));
|
|
42
|
+
if (keywords?.length) {
|
|
43
|
+
out = out.replace(new RegExp(`\\b(${keywords.join('|')})\\b`, 'g'), m => keywordColor(m));
|
|
44
|
+
}
|
|
45
|
+
out = out.replace(/\b([a-zA-Z_]\w*)(?=\s*\()/g, m => functionColor(m));
|
|
46
|
+
out = out.replace(/\b(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g, m => numberColor(m));
|
|
47
|
+
out = out.replace(/([+\-*\/=<>!&|^%]+)/g, m => operatorColor(m));
|
|
48
|
+
return out;
|
|
49
|
+
}).join('\n');
|
|
50
|
+
}
|
|
51
|
+
function applyInline(text) {
|
|
52
|
+
const slots = [];
|
|
53
|
+
let s = text.replace(/`([^`\n]+)`/g, (_, code) => {
|
|
54
|
+
slots.push(chalk.bgHex(activeTheme.colors.bgBottomBar).hex(activeTheme.colors.secondary)(` ${code} `));
|
|
55
|
+
return `\x00S${slots.length - 1}\x00`;
|
|
56
|
+
});
|
|
57
|
+
s = s.replace(/\*\*\*(.+?)\*\*\*/gs, (_, t) => chalk.bold.italic.hex(activeTheme.colors.primary)(t));
|
|
58
|
+
s = s.replace(/\*\*(.+?)\*\*/gs, (_, t) => chalk.bold.hex(activeTheme.colors.primary)(t));
|
|
59
|
+
s = s.replace(/\*([^\n*]+?)\*/g, (_, t) => chalk.italic.hex(activeTheme.colors.secondary)(t));
|
|
60
|
+
s = s.replace(/~~(.+?)~~/g, (_, t) => chalk.strikethrough.hex(activeTheme.colors.muted)(t));
|
|
61
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, url) => chalk.hex(activeTheme.colors.info).underline(linkText) + chalk.hex(activeTheme.colors.muted)(` (${url})`));
|
|
62
|
+
s = s.replace(/!\[([^\]]*)\]\([^)]+\)/g, (_, alt) => chalk.hex(activeTheme.colors.muted)(`[image: ${alt || 'image'}]`));
|
|
63
|
+
return s.replace(/\x00S(\d+)\x00/g, (_, i) => slots[parseInt(i, 10)]);
|
|
64
|
+
}
|
|
65
|
+
function renderCodeBlock(code, lang) {
|
|
66
|
+
const width = Math.max(40, cols() - 6);
|
|
67
|
+
const borderCol = chalk.hex(activeTheme.colors.muted);
|
|
68
|
+
const tag = lang ? chalk.bgHex(activeTheme.colors.primary).black.bold(` ${lang.toUpperCase()} `) : '';
|
|
69
|
+
const rule = borderCol('-'.repeat(width));
|
|
70
|
+
const highlighted = lang ? syntaxHighlight(code.trimEnd(), lang) : chalk.hex(activeTheme.colors.primary)(code.trimEnd());
|
|
71
|
+
const lines = highlighted.split('\n');
|
|
72
|
+
const body = lines.map((line, i) => {
|
|
73
|
+
const lineNum = chalk.hex(activeTheme.colors.muted)((i + 1).toString().padStart(3) + ' | ');
|
|
74
|
+
return borderCol('| ') + lineNum + line;
|
|
75
|
+
}).join('\n');
|
|
76
|
+
return `\n${tag}${rule}\n${body}\n${rule}\n`;
|
|
77
|
+
}
|
|
78
|
+
function renderTable(tableLines) {
|
|
79
|
+
const rows = tableLines
|
|
80
|
+
.map(line => line.trim().replace(/^\||\|$/g, '').split('|').map(c => c.trim()))
|
|
81
|
+
.filter(row => !row.every(cell => /^:?-+:?$/.test(cell) || cell === ''));
|
|
82
|
+
if (!rows.length)
|
|
83
|
+
return tableLines.join('\n');
|
|
84
|
+
const colCount = Math.max(...rows.map(r => r.length));
|
|
85
|
+
const widths = Array.from({ length: colCount }, (_, i) => Math.max(3, ...rows.map(r => vlen(r[i] ?? ''))));
|
|
86
|
+
const t = (s) => chalk.hex(activeTheme.colors.primary)(s);
|
|
87
|
+
const topLine = () => t('┌─') + widths.map(w => t('─'.repeat(w))).join(t('─┬─')) + t('─┐');
|
|
88
|
+
const midLine = () => t('├─') + widths.map(w => t('─'.repeat(w))).join(t('─┼─')) + t('─┤');
|
|
89
|
+
const botLine = () => t('└─') + widths.map(w => t('─'.repeat(w))).join(t('─┴─')) + t('─┘');
|
|
90
|
+
const renderRow = (cells, isHeader) => {
|
|
91
|
+
const parts = Array.from({ length: colCount }, (_, i) => {
|
|
92
|
+
const cell = cells[i] ?? '';
|
|
93
|
+
const padded = cell + ' '.repeat(Math.max(0, widths[i] - vlen(cell)));
|
|
94
|
+
return isHeader ? chalk.hex(activeTheme.colors.secondary).bold(padded) : applyInline(padded);
|
|
95
|
+
});
|
|
96
|
+
return t('│ ') + parts.join(t(' │ ')) + t(' │');
|
|
97
|
+
};
|
|
98
|
+
const out = ['', topLine()];
|
|
99
|
+
rows.forEach((row, i) => {
|
|
100
|
+
out.push(renderRow(row, i === 0));
|
|
101
|
+
if (i === 0 && rows.length > 1) {
|
|
102
|
+
out.push(midLine());
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
out.push(botLine(), '');
|
|
106
|
+
return out.join('\n');
|
|
107
|
+
}
|
|
108
|
+
function renderLine(line) {
|
|
109
|
+
if (!line.trim())
|
|
110
|
+
return '';
|
|
111
|
+
if (/^#{4}\s/.test(line))
|
|
112
|
+
return chalk.hex(activeTheme.colors.accent).bold(applyInline(line.replace(/^#{4}\s/, '')));
|
|
113
|
+
if (/^#{3}\s/.test(line))
|
|
114
|
+
return '\n' + chalk.hex(activeTheme.colors.secondary).bold(applyInline(line.replace(/^#{3}\s/, '')));
|
|
115
|
+
if (/^#{2}\s/.test(line))
|
|
116
|
+
return '\n' + chalk.hex(activeTheme.colors.primary).bold.underline(applyInline(line.replace(/^#{2}\s/, '')));
|
|
117
|
+
if (/^#{1}\s/.test(line))
|
|
118
|
+
return '\n' + chalk.hex(activeTheme.colors.primary).bold.underline(applyInline(line.replace(/^#{1}\s/, ''))) + '\n';
|
|
119
|
+
if (/^[-*_]{3,}\s*$/.test(line.trim())) {
|
|
120
|
+
return chalk.hex(activeTheme.colors.muted)('-'.repeat(Math.min(60, cols() - 4)));
|
|
121
|
+
}
|
|
122
|
+
const taskM = line.match(/^(\s*)[-*+] \[([ xX])\] (.+)$/);
|
|
123
|
+
if (taskM) {
|
|
124
|
+
const done = taskM[2].toLowerCase() === 'x';
|
|
125
|
+
const box = done ? chalk.hex(activeTheme.colors.success)('[x]') : chalk.hex(activeTheme.colors.muted)('[ ]');
|
|
126
|
+
const text = done ? chalk.hex(activeTheme.colors.muted).strikethrough(taskM[3]) : applyInline(taskM[3]);
|
|
127
|
+
return ' '.repeat(Math.floor(taskM[1].length / 2)) + box + ' ' + text;
|
|
128
|
+
}
|
|
129
|
+
const bulletM = line.match(/^(\s*)[-*+] (.+)$/);
|
|
130
|
+
if (bulletM) {
|
|
131
|
+
const depth = Math.floor(bulletM[1].length / 2);
|
|
132
|
+
const syms = ['-', '*', '+'];
|
|
133
|
+
return ' '.repeat(depth) + chalk.hex(activeTheme.colors.primary)(syms[depth % syms.length]) + ' ' + applyInline(bulletM[2]);
|
|
134
|
+
}
|
|
135
|
+
const numM = line.match(/^(\s*)(\d+)[.)]\s+(.+)$/);
|
|
136
|
+
if (numM) {
|
|
137
|
+
const depth = Math.floor(numM[1].length / 2);
|
|
138
|
+
return ' '.repeat(depth) + chalk.hex(activeTheme.colors.secondary)(numM[2] + '.') + ' ' + applyInline(numM[3]);
|
|
139
|
+
}
|
|
140
|
+
return applyInline(line);
|
|
141
|
+
}
|
|
142
|
+
export function renderMarkdown(text) {
|
|
143
|
+
const lines = text.split('\n');
|
|
144
|
+
const out = [];
|
|
145
|
+
let i = 0;
|
|
146
|
+
while (i < lines.length) {
|
|
147
|
+
const line = lines[i];
|
|
148
|
+
const fenceM = line.match(/^(`{3,}|~{3,})(\w*)/);
|
|
149
|
+
if (fenceM) {
|
|
150
|
+
const fence = fenceM[1].slice(0, 3);
|
|
151
|
+
const lang = fenceM[2];
|
|
152
|
+
const code = [];
|
|
153
|
+
i++;
|
|
154
|
+
while (i < lines.length && !lines[i].startsWith(fence)) {
|
|
155
|
+
code.push(lines[i]);
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
out.push(renderCodeBlock(code.join('\n'), lang));
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (line.trimStart().startsWith('|') && /^\|[\s\-:|]+\|/.test(lines[i + 1]?.trimStart() ?? '')) {
|
|
163
|
+
const tableLines = [line];
|
|
164
|
+
i++;
|
|
165
|
+
while (i < lines.length && lines[i].trimStart().startsWith('|')) {
|
|
166
|
+
tableLines.push(lines[i]);
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
out.push(renderTable(tableLines));
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (line.startsWith('>')) {
|
|
173
|
+
const bqLines = [];
|
|
174
|
+
while (i < lines.length && lines[i].startsWith('>')) {
|
|
175
|
+
bqLines.push(lines[i].replace(/^>\s?/, ''));
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
const inner = renderMarkdown(bqLines.join('\n'));
|
|
179
|
+
const borderCol = chalk.hex(activeTheme.colors.accent);
|
|
180
|
+
out.push(inner.split('\n').map(l => borderCol('|') + ' ' + chalk.italic(l)).join('\n'));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
out.push(renderLine(line));
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
return out.join('\n');
|
|
187
|
+
}
|
|
188
|
+
export function renderThinkingBlock(thinking) {
|
|
189
|
+
const lines = thinking.trim().split('\n').filter(Boolean);
|
|
190
|
+
if (!lines.length)
|
|
191
|
+
return '';
|
|
192
|
+
const borderCol = chalk.hex(activeTheme.colors.muted);
|
|
193
|
+
const width = Math.min(cols() - 4, Math.max(24, ...lines.map(vlen), 'Thinking'.length) + 4);
|
|
194
|
+
const rule = borderCol('-'.repeat(width));
|
|
195
|
+
const body = lines.map(line => borderCol('| ') + chalk.hex(activeTheme.colors.muted)(line)).join('\n');
|
|
196
|
+
return `\n${rule}\n${chalk.hex(activeTheme.colors.accent).bold('Thinking')}\n${body}\n${rule}\n`;
|
|
197
|
+
}
|
|
198
|
+
export function extractThinkTags(text) {
|
|
199
|
+
const m = text.match(/^<think>([\s\S]*?)<\/think>\n?/);
|
|
200
|
+
if (m)
|
|
201
|
+
return { thinking: m[1].trim(), response: text.slice(m[0].length) };
|
|
202
|
+
return { thinking: '', response: text };
|
|
203
|
+
}
|
package/dist/repl.d.ts
ADDED