ikie-cli 0.1.1 → 0.1.3
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/agent.js +38 -10
- package/dist/attachments.js +6 -6
- package/dist/repl.js +24 -4
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +1 -1
- package/dist/tools.js +9 -4
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
2
3
|
import { TOOL_DEFS, SAFE_TOOLS, formatToolArgs, executeTool } from './tools.js';
|
|
3
4
|
import { renderMarkdown, extractThinkTags } from './renderer.js';
|
|
4
5
|
import { c, toolLine, permissionPrompt, toolSuccessLine, toolErrorLine, InlineSpinner } from './theme.js';
|
|
@@ -452,14 +453,13 @@ export class Agent {
|
|
|
452
453
|
const question = (input.question ?? '').trim();
|
|
453
454
|
if (!question)
|
|
454
455
|
return 'Error: ask_user requires a question.';
|
|
455
|
-
process.stdout.write(`\n${this.indent}${c.info('[?]')} ${c.white.bold(question)}\n` +
|
|
456
|
-
`${this.indent}${c.primary('╰─❯')} `);
|
|
457
456
|
return new Promise((resolve) => {
|
|
458
457
|
if (!process.stdin.isTTY) {
|
|
459
458
|
process.stdout.write(chalk.dim('(non-interactive, skipping)\n'));
|
|
460
459
|
resolve('(no answer — non-interactive mode)');
|
|
461
460
|
return;
|
|
462
461
|
}
|
|
462
|
+
// Save current stdin state
|
|
463
463
|
const savedDataListeners = process.stdin.rawListeners('data').slice();
|
|
464
464
|
const savedKeypressListeners = process.stdin.rawListeners('keypress').slice();
|
|
465
465
|
process.stdin.removeAllListeners('data');
|
|
@@ -467,16 +467,27 @@ export class Agent {
|
|
|
467
467
|
const wasRaw = process.stdin.isRaw ?? false;
|
|
468
468
|
if (wasRaw)
|
|
469
469
|
process.stdin.setRawMode(false);
|
|
470
|
-
|
|
471
|
-
const rl = readline.createInterface({
|
|
472
|
-
|
|
470
|
+
// Create readline interface
|
|
471
|
+
const rl = readline.createInterface({
|
|
472
|
+
input: process.stdin,
|
|
473
|
+
output: process.stdout,
|
|
474
|
+
terminal: false // Let us handle the prompt ourselves
|
|
475
|
+
});
|
|
476
|
+
// Display the question with custom formatting
|
|
477
|
+
process.stdout.write(`\n${this.indent}${c.info('[?]')} ${c.white.bold(question)}\n` +
|
|
478
|
+
`${this.indent}${c.primary('╰─❯')} `);
|
|
479
|
+
// Wait for user input
|
|
480
|
+
rl.once('line', (answer) => {
|
|
473
481
|
rl.close();
|
|
482
|
+
// Restore stdin state
|
|
474
483
|
if (wasRaw)
|
|
475
484
|
process.stdin.setRawMode(true);
|
|
476
485
|
restoreStdinListeners(savedDataListeners, savedKeypressListeners);
|
|
477
486
|
const trimmed = answer.trim();
|
|
478
487
|
resolve(trimmed || '(no answer)');
|
|
479
488
|
});
|
|
489
|
+
// Explicitly resume stdin to ensure it's listening
|
|
490
|
+
process.stdin.resume();
|
|
480
491
|
});
|
|
481
492
|
}
|
|
482
493
|
// ── Subagent ──────────────────────────────────────────────────────────────
|
|
@@ -532,12 +543,16 @@ export class Agent {
|
|
|
532
543
|
const savedKeypressListeners = process.stdin.rawListeners('keypress').slice();
|
|
533
544
|
process.stdin.removeAllListeners('data');
|
|
534
545
|
process.stdin.removeAllListeners('keypress');
|
|
535
|
-
process.stdin.
|
|
536
|
-
|
|
546
|
+
if (process.stdin.isTTY) {
|
|
547
|
+
process.stdin.setRawMode(true);
|
|
548
|
+
process.stdin.resume();
|
|
549
|
+
}
|
|
537
550
|
const onData = (data) => {
|
|
538
551
|
process.stdin.removeListener('data', onData);
|
|
539
552
|
// Restore raw mode to what it was (keeps REPL's ESC handler working)
|
|
540
|
-
process.stdin.
|
|
553
|
+
if (process.stdin.isTTY) {
|
|
554
|
+
process.stdin.setRawMode(wasRaw);
|
|
555
|
+
}
|
|
541
556
|
restoreStdinListeners(savedDataListeners, savedKeypressListeners);
|
|
542
557
|
// Only pause if nobody else was listening (no REPL ESC handler)
|
|
543
558
|
if (!savedDataListeners.length) {
|
|
@@ -585,6 +600,17 @@ Do not speculate about your underlying model.
|
|
|
585
600
|
You help developers write, debug, understand, and refactor code. You work autonomously
|
|
586
601
|
using your tools to accomplish tasks. Be direct, concise, and practical.
|
|
587
602
|
|
|
603
|
+
## Response Formatting
|
|
604
|
+
- Use **markdown formatting** in all your responses for better readability
|
|
605
|
+
- Use **bold** for emphasis: **important text**
|
|
606
|
+
- Use *italics* for secondary emphasis: *note*
|
|
607
|
+
- Use \`inline code\` for: variable names, file names, short code snippets, commands
|
|
608
|
+
- Use code blocks with language tags for multi-line code (triple backticks)
|
|
609
|
+
- Use bullet points (-, *, +) or numbered lists for steps and options
|
|
610
|
+
- Use headers (##, ###) to organize longer responses
|
|
611
|
+
- Use tables for structured data comparisons
|
|
612
|
+
- Keep responses **concise but well-formatted** - the terminal renderer supports rich markdown
|
|
613
|
+
|
|
588
614
|
## Working Style
|
|
589
615
|
- ALWAYS provide complete, valid arguments to tools. Never omit required fields like \`path\`.
|
|
590
616
|
- When creating files, use the FULL file path (relative or absolute) — not just a filename.
|
|
@@ -604,8 +630,10 @@ using your tools to accomplish tasks. Be direct, concise, and practical.
|
|
|
604
630
|
- Verify your work: after edits, re-read the changed regions and run the build,
|
|
605
631
|
tests, or linter. Fix what you broke before you call a task done.
|
|
606
632
|
- Delegate isolated or parallelizable investigation to \`spawn_agent\` to stay focused.
|
|
607
|
-
-
|
|
608
|
-
code
|
|
633
|
+
- **Format your responses beautifully**: Use markdown syntax consistently - headers, lists,
|
|
634
|
+
code blocks, bold/italics. The terminal has excellent markdown rendering.
|
|
635
|
+
- Be concise but clear: explain what you did and why in well-formatted points.
|
|
636
|
+
Show code/results rather than narrating. Use \`ask_user\` only when genuinely blocked.
|
|
609
637
|
- Never leave a task half-finished or claim a success you have not verified.
|
|
610
638
|
|
|
611
639
|
## Tools Available
|
package/dist/attachments.js
CHANGED
|
@@ -127,12 +127,12 @@ function loadClipboardImageMacOS(outPath, id) {
|
|
|
127
127
|
function loadClipboardImageLinux(outPath, id) {
|
|
128
128
|
// Try xclip first (X11)
|
|
129
129
|
let result = spawnSync('xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o'], {
|
|
130
|
-
encoding: '
|
|
130
|
+
encoding: 'buffer',
|
|
131
131
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
132
132
|
});
|
|
133
|
-
if (result.status === 0 && result.stdout) {
|
|
133
|
+
if (result.status === 0 && Buffer.isBuffer(result.stdout) && result.stdout.length > 0) {
|
|
134
134
|
try {
|
|
135
|
-
writeFileSync(outPath, result.stdout
|
|
135
|
+
writeFileSync(outPath, result.stdout);
|
|
136
136
|
if (existsSync(outPath) && statSync(outPath).size > 0) {
|
|
137
137
|
return loadImageAttachment(outPath, id);
|
|
138
138
|
}
|
|
@@ -141,12 +141,12 @@ function loadClipboardImageLinux(outPath, id) {
|
|
|
141
141
|
}
|
|
142
142
|
// Try wl-paste for Wayland
|
|
143
143
|
result = spawnSync('wl-paste', ['--type', 'image/png'], {
|
|
144
|
-
encoding: '
|
|
144
|
+
encoding: 'buffer',
|
|
145
145
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
146
146
|
});
|
|
147
|
-
if (result.status === 0 && result.stdout) {
|
|
147
|
+
if (result.status === 0 && Buffer.isBuffer(result.stdout) && result.stdout.length > 0) {
|
|
148
148
|
try {
|
|
149
|
-
writeFileSync(outPath, result.stdout
|
|
149
|
+
writeFileSync(outPath, result.stdout);
|
|
150
150
|
if (existsSync(outPath) && statSync(outPath).size > 0) {
|
|
151
151
|
return loadImageAttachment(outPath, id);
|
|
152
152
|
}
|
package/dist/repl.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import readline from 'readline';
|
|
1
|
+
import * as readline from 'node:readline';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
import { restoreStdinListeners } from './agent.js';
|
|
4
4
|
import { c, PROMPT, CONTINUE_PROMPT, printPromptHeader, drawBanner, infoLine, successLine, errorLine, THEMES, setTheme, stripAnsi, } from './theme.js';
|
|
5
5
|
import { renderMarkdown } from './renderer.js';
|
|
6
6
|
import { loadAllMemory } from './memory.js';
|
|
7
|
-
import { HOME_DIR, saveConfig, DEFAULT_MODEL, FIREWORKS_BASE_URL, IKIE_HOST } from './config.js';
|
|
7
|
+
import { HOME_DIR, saveConfig, DEFAULT_MODEL, FIREWORKS_BASE_URL, IKIE_HOST, isLoggedIn } from './config.js';
|
|
8
|
+
import { login, logout } from './auth.js';
|
|
8
9
|
import { join as pathJoin } from 'path';
|
|
9
10
|
import { deleteSession, listSessions, loadSession, normalizeSessionName, saveSession } from './session.js';
|
|
10
11
|
import { buildUserContent, formatBytes, loadClipboardImageAttachment, loadImageAttachment, hasClipboardImage } from './attachments.js';
|
|
@@ -93,6 +94,8 @@ const SLASH_CMDS = [
|
|
|
93
94
|
{ name: 'theme', desc: 'Change visual theme', args: '[name]' },
|
|
94
95
|
{ name: 'rpm', desc: 'Set request limit', args: '[number]' },
|
|
95
96
|
{ name: 'tokens', desc: 'Token estimate' },
|
|
97
|
+
{ name: 'login', desc: 'Sign in to ikie account' },
|
|
98
|
+
{ name: 'logout', desc: 'Sign out of ikie account' },
|
|
96
99
|
{ name: 'exit', desc: 'Exit Ikie' },
|
|
97
100
|
];
|
|
98
101
|
function slashCompleter(line) {
|
|
@@ -145,6 +148,8 @@ ${c.primary.bold('Ikie Commands')}
|
|
|
145
148
|
${c.warning('/theme')} Pick a theme interactively
|
|
146
149
|
${c.warning('/rpm [number]')} Show or set request limit
|
|
147
150
|
${c.warning('/tokens')} Show conversation token estimate
|
|
151
|
+
${c.warning('/login')} Sign in to ikie account
|
|
152
|
+
${c.warning('/logout')} Sign out of ikie account
|
|
148
153
|
${c.warning('/exit')} Exit Ikie
|
|
149
154
|
|
|
150
155
|
${c.primary.bold('Shortcuts')}
|
|
@@ -340,6 +345,19 @@ async function handleSlashCommand(input, agent, config, projectContext, rl, sess
|
|
|
340
345
|
}
|
|
341
346
|
return true;
|
|
342
347
|
}
|
|
348
|
+
case 'login': {
|
|
349
|
+
await login();
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
case 'logout': {
|
|
353
|
+
if (!isLoggedIn(config)) {
|
|
354
|
+
console.log(infoLine('Not signed in.'));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
logout();
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
343
361
|
case 'settings': {
|
|
344
362
|
const sub = (args[0] ?? 'show').toLowerCase();
|
|
345
363
|
const value = args.slice(1).join(' ').trim();
|
|
@@ -738,7 +756,8 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
738
756
|
return;
|
|
739
757
|
}
|
|
740
758
|
if (!pasteMode && !text.includes('\x1b[200~') && /[\r\n]/.test(text.trim()) && text.length > 3) {
|
|
741
|
-
|
|
759
|
+
const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
760
|
+
rl.write(normalized);
|
|
742
761
|
return;
|
|
743
762
|
}
|
|
744
763
|
let rest = text;
|
|
@@ -750,7 +769,8 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
750
769
|
return;
|
|
751
770
|
}
|
|
752
771
|
pasteBuffer += rest.slice(0, end);
|
|
753
|
-
|
|
772
|
+
const normalized = pasteBuffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
773
|
+
rl.write(normalized);
|
|
754
774
|
pasteBuffer = '';
|
|
755
775
|
pasteMode = false;
|
|
756
776
|
rest = rest.slice(end + '\x1b[201~'.length);
|
package/dist/theme.d.ts
CHANGED
package/dist/theme.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'os';
|
|
|
3
3
|
import { join as pathJoin, basename } from 'path';
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
-
export const VERSION = '0.1.
|
|
6
|
+
export const VERSION = '0.1.1';
|
|
7
7
|
const IKIE_BANNER = [
|
|
8
8
|
' ██╗██╗ ██╗██╗███████╗',
|
|
9
9
|
' ██║██║ ██╔╝██║██╔════╝',
|
package/dist/tools.js
CHANGED
|
@@ -451,15 +451,20 @@ function globToRegExp(pattern) {
|
|
|
451
451
|
.replace(/\u0000/g, '.*');
|
|
452
452
|
return new RegExp(`(^|/)${escaped}$`, 'i');
|
|
453
453
|
}
|
|
454
|
-
function memoryWrite(input) {
|
|
454
|
+
async function memoryWrite(input) {
|
|
455
455
|
const scope = input.scope ?? 'project';
|
|
456
|
-
|
|
456
|
+
try {
|
|
457
|
+
const { appendProjectMemory, appendGlobalMemory } = await import('./memory.js');
|
|
457
458
|
if (scope === 'global')
|
|
458
459
|
appendGlobalMemory(input.content);
|
|
459
460
|
else
|
|
460
461
|
appendProjectMemory(input.content);
|
|
461
|
-
|
|
462
|
-
|
|
462
|
+
return `Saved to ${scope} memory.`;
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
466
|
+
return `Error saving to ${scope} memory: ${msg}`;
|
|
467
|
+
}
|
|
463
468
|
}
|
|
464
469
|
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
|
465
470
|
export async function executeTool(name, input) {
|