ikie-cli 0.1.6 → 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 +10 -0
- package/dist/repl.js +17 -1
- package/dist/tools.js +170 -2
- package/package.json +1 -1
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,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}`;
|