ikie-cli 0.1.0 → 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 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
- const readline = require('readline');
471
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
472
- rl.on('line', (answer) => {
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.setRawMode(true);
536
- process.stdin.resume();
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.setRawMode(wasRaw);
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
- - Be concise: explain what you did and why in a sentence or two, and show
608
- code/results rather than narrating. Use \`ask_user\` only when genuinely blocked.
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
@@ -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: 'utf8',
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, 'binary');
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: 'utf8',
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, 'binary');
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
- insertPasteBlock(text);
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
- insertPasteBlock(pasteBuffer);
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
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.0";
1
+ export declare const VERSION = "0.1.1";
2
2
  export interface Theme {
3
3
  name: string;
4
4
  description: string;
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.0';
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
- import('./memory.js').then(({ appendProjectMemory, appendGlobalMemory }) => {
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
- return `Saved to ${scope} memory.`;
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ikie-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Agentic coding CLI — your terminal AI pair programmer",
5
5
  "type": "module",
6
6
  "bin": {