ikie-cli 0.1.6 → 0.1.8

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,6 +722,11 @@ 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
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,29 @@ 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
+ else if (process.platform === 'win32') {
797
+ const ps = `Add-Type -AssemblyName System.Windows.Forms;`
798
+ + `$n=New-Object System.Windows.Forms.NotifyIcon;`
799
+ + `$n.Icon=[System.Drawing.SystemIcons]::Application;`
800
+ + `$n.BalloonTipTitle='ikie';`
801
+ + `$n.BalloonTipText='${safe}';`
802
+ + `$n.Visible=$true;`
803
+ + `$n.ShowBalloonTip(5000);`
804
+ + `Start-Sleep 2;$n.Dispose()`;
805
+ exec(`powershell -NoProfile -NonInteractive -Command "& {${ps}}"`, () => { });
806
+ }
807
+ }
785
808
  export async function startREPL(agent, config, projectContext, oneShot) {
786
809
  void HISTORY_FILE;
787
810
  // A `/`-prefixed launch arg (e.g. `ikie /session load foo`) is a slash
@@ -1099,6 +1122,10 @@ export async function startREPL(agent, config, projectContext, oneShot) {
1099
1122
  // TTY doesn't also echo input — otherwise every line is echoed twice.
1100
1123
  if (process.stdin.isTTY)
1101
1124
  process.stdin.setRawMode(true);
1125
+ const elapsedMs = Date.now() - taskStartedAt;
1126
+ if (elapsedMs > 10000 && !abortController.signal.aborted && !taskFailed) {
1127
+ notify(`Done in ${elapsed}`);
1128
+ }
1102
1129
  const status = abortController.signal.aborted
1103
1130
  ? c.warning(formatTaskTimeline(agent, elapsed, 'cancelled'))
1104
1131
  : 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.6",
3
+ "version": "0.1.8",
4
4
  "description": "Agentic coding CLI — your terminal AI pair programmer",
5
5
  "type": "module",
6
6
  "bin": {