ikie-cli 0.1.5 → 0.1.7

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
@@ -47,6 +47,11 @@ function toolPhaseLabel(name) {
47
47
  case 'list_dir': return 'Listing directory';
48
48
  case 'search_files': return 'Searching';
49
49
  case 'grep': return 'Searching';
50
+ case 'git_status': return 'Git status';
51
+ case 'git_diff': return 'Git diff';
52
+ case 'git_log': return 'Git log';
53
+ case 'git_commit': return 'Committing';
54
+ case 'git_branch': return 'Git branch';
50
55
  case 'fetch_url': return 'Fetching';
51
56
  case 'web_search': return 'Searching web';
52
57
  default: return `Preparing ${name}`;
@@ -717,11 +722,28 @@ changes they didn't ask for.
717
722
  - \`search_files\`: Find files by glob pattern
718
723
  - \`grep\`: Search file contents by regex
719
724
  - \`memory_write\`: Persist important notes across sessions
725
+ - \`git_status\`: Show working tree status (staged, unstaged, untracked files)
726
+ - \`git_diff\`: Show file diffs — use staged:true for what's ready to commit
727
+ - \`git_log\`: Show recent commit history
728
+ - \`git_commit\`: Stage files and create a commit (stages all changes by default)
729
+ - \`git_branch\`: List branches or create a new one (checkout:true to switch immediately)
720
730
  - \`fetch_url\`: Fetch a web page or URL and return its readable text (HTML is stripped to plain text).
721
731
  Use it to read docs, articles, or API responses when you have a URL.
722
732
  - \`web_search\`: Search the web for a query and get back titles, URLs, and snippets. Works
723
733
  automatically for logged-in ikie users — no separate API key needed. Combine with \`fetch_url\`
724
734
  to read a promising result in full.
735
+
736
+ ## Using web_search correctly
737
+ - **Always search when the user asks about anything current** — versions, prices, news, docs,
738
+ release dates. Never answer from memory alone for time-sensitive facts.
739
+ - **Check dates in snippets.** Results are ordered by relevance, not recency. If snippets show
740
+ conflicting versions or dates, fetch the most promising URL to get the ground truth.
741
+ - **For "latest X" queries**, include the current year in your search (e.g. "latest Node.js version 2026")
742
+ to surface recent results over older cached pages.
743
+ - **If a fetch returns 403/blocked**, try a different URL from the search results — don't give up.
744
+ - **Trust fetched page content over snippet summaries** — snippets can be stale; the live page is authoritative.
745
+ - **Never state a version, date, or fact as definitive if your search results conflict** — say what
746
+ the most recent source says and link it.
725
747
  - \`ask_user\`: Ask the user a clarifying question when you need more info to proceed.
726
748
  The user's answer is returned as the tool result. Use sparingly — only when genuinely
727
749
  unsure. Don't ask for confirmation on safe operations.
package/dist/repl.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as readline from 'node:readline';
2
- import { execSync } from 'child_process';
2
+ import { execSync, exec } from 'child_process';
3
3
  import { restoreStdinListeners } from './agent.js';
4
4
  import { c, PROMPT, CONTINUE_PROMPT, printPromptHeader, modeTag, drawBanner, infoLine, successLine, errorLine, THEMES, setTheme, stripAnsi, } from './theme.js';
5
5
  import { renderMarkdown } from './renderer.js';
@@ -782,6 +782,18 @@ function selectThemeInteractively(rl, config) {
782
782
  prompt();
783
783
  });
784
784
  }
785
+ // Notify the user when a long task finishes (> 10s). Terminal bell always;
786
+ // OS notification best-effort depending on platform.
787
+ function notify(message) {
788
+ process.stdout.write('\x07');
789
+ const safe = message.replace(/"/g, "'");
790
+ if (process.platform === 'darwin') {
791
+ exec(`osascript -e 'display notification "${safe}" with title "ikie"'`, () => { });
792
+ }
793
+ else if (process.platform === 'linux') {
794
+ exec(`notify-send "ikie" "${safe}" 2>/dev/null`, () => { });
795
+ }
796
+ }
785
797
  export async function startREPL(agent, config, projectContext, oneShot) {
786
798
  void HISTORY_FILE;
787
799
  // A `/`-prefixed launch arg (e.g. `ikie /session load foo`) is a slash
@@ -1099,6 +1111,10 @@ export async function startREPL(agent, config, projectContext, oneShot) {
1099
1111
  // TTY doesn't also echo input — otherwise every line is echoed twice.
1100
1112
  if (process.stdin.isTTY)
1101
1113
  process.stdin.setRawMode(true);
1114
+ const elapsedMs = Date.now() - taskStartedAt;
1115
+ if (elapsedMs > 10000 && !abortController.signal.aborted && !taskFailed) {
1116
+ notify(`Done in ${elapsed}`);
1117
+ }
1102
1118
  const status = abortController.signal.aborted
1103
1119
  ? c.warning(formatTaskTimeline(agent, elapsed, 'cancelled'))
1104
1120
  : taskFailed
package/dist/tools.js CHANGED
@@ -167,6 +167,79 @@ export const TOOL_DEFS = [
167
167
  },
168
168
  },
169
169
  },
170
+ {
171
+ type: 'function',
172
+ function: {
173
+ name: 'git_status',
174
+ description: 'Show the working tree status (staged, unstaged, untracked files).',
175
+ parameters: {
176
+ type: 'object',
177
+ properties: {
178
+ path: { type: 'string', description: 'Repository path (default: cwd)' },
179
+ },
180
+ required: [],
181
+ },
182
+ },
183
+ },
184
+ {
185
+ type: 'function',
186
+ function: {
187
+ name: 'git_diff',
188
+ description: 'Show file diffs. Use staged:true for what is staged for commit.',
189
+ parameters: {
190
+ type: 'object',
191
+ properties: {
192
+ staged: { type: 'boolean', description: 'Show staged diff (default: unstaged)' },
193
+ path: { type: 'string', description: 'Limit diff to this file or directory' },
194
+ },
195
+ required: [],
196
+ },
197
+ },
198
+ },
199
+ {
200
+ type: 'function',
201
+ function: {
202
+ name: 'git_log',
203
+ description: 'Show recent commit history.',
204
+ parameters: {
205
+ type: 'object',
206
+ properties: {
207
+ limit: { type: 'number', description: 'Number of commits to show (default 10)' },
208
+ },
209
+ required: [],
210
+ },
211
+ },
212
+ },
213
+ {
214
+ type: 'function',
215
+ function: {
216
+ name: 'git_commit',
217
+ description: 'Stage files and create a commit. Stages all changes by default, or specific files if provided.',
218
+ parameters: {
219
+ type: 'object',
220
+ properties: {
221
+ message: { type: 'string', description: 'Commit message' },
222
+ files: { type: 'array', items: { type: 'string' }, description: 'Files to stage (default: all changed files)' },
223
+ },
224
+ required: ['message'],
225
+ },
226
+ },
227
+ },
228
+ {
229
+ type: 'function',
230
+ function: {
231
+ name: 'git_branch',
232
+ description: 'List branches, or create a new branch. Set checkout:true to switch to it immediately.',
233
+ parameters: {
234
+ type: 'object',
235
+ properties: {
236
+ name: { type: 'string', description: 'Branch name to create. Omit to list branches.' },
237
+ checkout: { type: 'boolean', description: 'Switch to the new branch after creating it' },
238
+ },
239
+ required: [],
240
+ },
241
+ },
242
+ },
170
243
  {
171
244
  type: 'function',
172
245
  function: {
@@ -201,11 +274,11 @@ export const TOOL_DEFS = [
201
274
  // ─── Safe tools (auto-approved) ───────────────────────────────────────────────
202
275
  // spawn_agent is "safe" at the dispatch layer — the tools the sub-agent itself
203
276
  // runs go through their own approval inside the sub-agent loop.
204
- export const SAFE_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'memory_write', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search']);
277
+ export const SAFE_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'memory_write', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search', 'git_status', 'git_diff', 'git_log', 'git_branch']);
205
278
  // Tools available in PLAN mode — read-only exploration plus delegation/questions.
206
279
  // Everything that mutates the filesystem or runs commands (write_file, edit_file,
207
280
  // bash, memory_write) is intentionally excluded so plan mode can only research.
208
- export const PLAN_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search']);
281
+ export const PLAN_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search', 'git_status', 'git_diff', 'git_log', 'git_branch']);
209
282
  // ─── Display helpers ──────────────────────────────────────────────────────────
210
283
  export function formatToolArgs(name, input) {
211
284
  const p = (v) => v != null && v !== 'undefined' ? String(v) : '(missing)';
@@ -236,6 +309,18 @@ export function formatToolArgs(name, input) {
236
309
  const task = String(input.task ?? '');
237
310
  return `"${task.length > 56 ? task.slice(0, 56) + '…' : task}"`;
238
311
  }
312
+ case 'git_status':
313
+ return input.path ? `"${p(input.path)}"` : '(cwd)';
314
+ case 'git_diff':
315
+ return `${input.staged ? 'staged' : 'unstaged'}${input.path ? ` "${p(input.path)}"` : ''}`;
316
+ case 'git_log':
317
+ return `last ${input.limit ?? 10}`;
318
+ case 'git_commit': {
319
+ const msg = String(input.message ?? '');
320
+ return `"${msg.length > 56 ? msg.slice(0, 56) + '…' : msg}"`;
321
+ }
322
+ case 'git_branch':
323
+ return input.name ? `"${p(input.name)}"${input.checkout ? ' --checkout' : ''}` : '(list)';
239
324
  case 'fetch_url':
240
325
  return `"${p(input.url)}"`;
241
326
  case 'web_search': {
@@ -506,6 +591,84 @@ async function memoryWrite(input) {
506
591
  return `Error saving to ${scope} memory: ${msg}`;
507
592
  }
508
593
  }
594
+ // ─── Git ──────────────────────────────────────────────────────────────────────
595
+ async function gitStatus(input) {
596
+ const cwd = input.path ? resolve(input.path) : process.cwd();
597
+ try {
598
+ const { stdout } = await execAsync('git status', { cwd });
599
+ return stdout.trim() || '(nothing to show)';
600
+ }
601
+ catch (err) {
602
+ const e = err;
603
+ return `Error: ${e.stderr?.trim() ?? e.message ?? err}`;
604
+ }
605
+ }
606
+ async function gitDiff(input) {
607
+ const cwd = process.cwd();
608
+ const stagedFlag = input.staged ? '--staged' : '';
609
+ const pathArg = input.path ? `-- "${input.path}"` : '';
610
+ try {
611
+ const { stdout } = await execAsync(`git diff ${stagedFlag} ${pathArg}`.trim(), { cwd, maxBuffer: 2 * 1024 * 1024 });
612
+ return stdout.trim() || '(no diff)';
613
+ }
614
+ catch (err) {
615
+ const e = err;
616
+ return `Error: ${e.stderr?.trim() ?? e.message ?? err}`;
617
+ }
618
+ }
619
+ async function gitLog(input) {
620
+ const limit = Math.min(input.limit ?? 10, 100);
621
+ try {
622
+ const { stdout } = await execAsync(`git log --oneline --decorate -${limit}`, { cwd: process.cwd() });
623
+ return stdout.trim() || '(no commits)';
624
+ }
625
+ catch (err) {
626
+ const e = err;
627
+ return `Error: ${e.stderr?.trim() ?? e.message ?? err}`;
628
+ }
629
+ }
630
+ async function gitCommit(input) {
631
+ const msg = (input.message ?? '').trim();
632
+ if (!msg)
633
+ return 'Error: message is required for git_commit';
634
+ const cwd = process.cwd();
635
+ try {
636
+ const addTarget = input.files?.length
637
+ ? input.files.map(f => `"${f}"`).join(' ')
638
+ : '-A';
639
+ await execAsync(`git add ${addTarget}`, { cwd });
640
+ const { stdout } = await execAsync(`git commit -m ${JSON.stringify(msg)}`, { cwd });
641
+ return stdout.trim();
642
+ }
643
+ catch (err) {
644
+ const e = err;
645
+ const parts = [];
646
+ if (e.stdout?.trim())
647
+ parts.push(e.stdout.trim());
648
+ if (e.stderr?.trim())
649
+ parts.push(e.stderr.trim());
650
+ return `Error: ${parts.join('\n') || (e.message ?? String(err))}`;
651
+ }
652
+ }
653
+ async function gitBranch(input) {
654
+ const cwd = process.cwd();
655
+ try {
656
+ if (!input.name) {
657
+ const { stdout } = await execAsync('git branch -a', { cwd });
658
+ return stdout.trim() || '(no branches)';
659
+ }
660
+ if (input.checkout) {
661
+ const { stdout, stderr } = await execAsync(`git checkout -b "${input.name}"`, { cwd });
662
+ return (stdout + stderr).trim() || `Switched to new branch '${input.name}'`;
663
+ }
664
+ await execAsync(`git branch "${input.name}"`, { cwd });
665
+ return `Created branch '${input.name}'`;
666
+ }
667
+ catch (err) {
668
+ const e = err;
669
+ return `Error: ${e.stderr?.trim() ?? e.message ?? err}`;
670
+ }
671
+ }
509
672
  // ─── Web ──────────────────────────────────────────────────────────────────────
510
673
  function htmlToText(html) {
511
674
  return html
@@ -623,6 +786,11 @@ export async function executeTool(name, input) {
623
786
  case 'search_files': return searchFiles(input);
624
787
  case 'grep': return grepFiles(input);
625
788
  case 'memory_write': return memoryWrite(input);
789
+ case 'git_status': return gitStatus(input);
790
+ case 'git_diff': return gitDiff(input);
791
+ case 'git_log': return gitLog(input);
792
+ case 'git_commit': return gitCommit(input);
793
+ case 'git_branch': return gitBranch(input);
626
794
  case 'fetch_url': return fetchUrl(input);
627
795
  case 'web_search': return webSearch(input);
628
796
  default: return `Unknown tool: ${name}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ikie-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Agentic coding CLI — your terminal AI pair programmer",
5
5
  "type": "module",
6
6
  "bin": {