ikie-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +634 -0
- package/dist/attachments.d.ts +33 -0
- package/dist/attachments.js +239 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +89 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +69 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -0
- package/dist/memory.d.ts +14 -0
- package/dist/memory.js +71 -0
- package/dist/renderer.d.ts +7 -0
- package/dist/renderer.js +203 -0
- package/dist/repl.d.ts +3 -0
- package/dist/repl.js +948 -0
- package/dist/session.d.ts +21 -0
- package/dist/session.js +85 -0
- package/dist/theme.d.ts +66 -0
- package/dist/theme.js +365 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +477 -0
- package/package.json +47 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { exec, spawn } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import { glob } from 'glob';
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
// ─── OpenAI-format Tool Definitions ──────────────────────────────────────────
|
|
8
|
+
export const TOOL_DEFS = [
|
|
9
|
+
{
|
|
10
|
+
type: 'function',
|
|
11
|
+
function: {
|
|
12
|
+
name: 'read_file',
|
|
13
|
+
description: 'Read the contents of a file with line numbers.',
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
path: { type: 'string', description: 'File path to read' },
|
|
18
|
+
start_line: { type: 'number', description: 'Start line (1-based, optional)' },
|
|
19
|
+
end_line: { type: 'number', description: 'End line (1-based, optional)' },
|
|
20
|
+
},
|
|
21
|
+
required: ['path'],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'function',
|
|
27
|
+
function: {
|
|
28
|
+
name: 'write_file',
|
|
29
|
+
description: 'Write content to a file, creating it or overwriting it.',
|
|
30
|
+
parameters: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
path: { type: 'string', description: 'File path' },
|
|
34
|
+
content: { type: 'string', description: 'Content to write' },
|
|
35
|
+
},
|
|
36
|
+
required: ['path', 'content'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'function',
|
|
42
|
+
function: {
|
|
43
|
+
name: 'edit_file',
|
|
44
|
+
description: 'Edit a file by replacing an exact string. Preferred over write_file for modifications.',
|
|
45
|
+
parameters: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
path: { type: 'string', description: 'File path' },
|
|
49
|
+
old_string: { type: 'string', description: 'Exact string to replace' },
|
|
50
|
+
new_string: { type: 'string', description: 'Replacement string' },
|
|
51
|
+
},
|
|
52
|
+
required: ['path', 'old_string', 'new_string'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: {
|
|
59
|
+
name: 'bash',
|
|
60
|
+
description: 'Execute a shell command and return stdout + stderr.',
|
|
61
|
+
parameters: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
command: { type: 'string', description: 'Shell command' },
|
|
65
|
+
timeout_ms: { type: 'number', description: 'Timeout ms (default 30000)' },
|
|
66
|
+
cwd: { type: 'string', description: 'Working directory' },
|
|
67
|
+
},
|
|
68
|
+
required: ['command'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'function',
|
|
74
|
+
function: {
|
|
75
|
+
name: 'list_dir',
|
|
76
|
+
description: 'List files and directories at a path.',
|
|
77
|
+
parameters: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
path: { type: 'string', description: 'Directory path (default ".")' },
|
|
81
|
+
recursive: { type: 'boolean', description: 'List recursively' },
|
|
82
|
+
max_depth: { type: 'number', description: 'Max depth (default 3)' },
|
|
83
|
+
},
|
|
84
|
+
required: [],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'function',
|
|
90
|
+
function: {
|
|
91
|
+
name: 'search_files',
|
|
92
|
+
description: 'Find files by glob pattern.',
|
|
93
|
+
parameters: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
pattern: { type: 'string', description: 'Glob pattern e.g. "**/*.ts"' },
|
|
97
|
+
cwd: { type: 'string', description: 'Search root (default ".")' },
|
|
98
|
+
},
|
|
99
|
+
required: ['pattern'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'function',
|
|
105
|
+
function: {
|
|
106
|
+
name: 'grep',
|
|
107
|
+
description: 'Search file contents for a regex or string pattern.',
|
|
108
|
+
parameters: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
pattern: { type: 'string', description: 'Regex or string to search for' },
|
|
112
|
+
path: { type: 'string', description: 'File or directory (default ".")' },
|
|
113
|
+
recursive: { type: 'boolean', description: 'Recursive (default true)' },
|
|
114
|
+
ignore_case: { type: 'boolean', description: 'Case-insensitive' },
|
|
115
|
+
include: { type: 'string', description: 'File glob filter e.g. "*.ts"' },
|
|
116
|
+
max_results: { type: 'number', description: 'Max results (default 50)' },
|
|
117
|
+
},
|
|
118
|
+
required: ['pattern'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'function',
|
|
124
|
+
function: {
|
|
125
|
+
name: 'memory_write',
|
|
126
|
+
description: 'Save a note to persistent memory for future sessions.',
|
|
127
|
+
parameters: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
content: { type: 'string', description: 'Note in markdown' },
|
|
131
|
+
scope: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
enum: ['project', 'global'],
|
|
134
|
+
description: '"project" for project-specific, "global" for cross-project (default: "project")',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ['content'],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'function',
|
|
143
|
+
function: {
|
|
144
|
+
name: 'ask_user',
|
|
145
|
+
description: 'Ask the user a clarifying question and wait for their answer. Use when you need more information to proceed — e.g. which approach they prefer, what file to target, or confirmation before a destructive action. Do NOT use this for yes/no tool permissions (those are handled automatically).',
|
|
146
|
+
parameters: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
properties: {
|
|
149
|
+
question: { type: 'string', description: 'The question to ask the user.' },
|
|
150
|
+
},
|
|
151
|
+
required: ['question'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'function',
|
|
157
|
+
function: {
|
|
158
|
+
name: 'spawn_agent',
|
|
159
|
+
description: 'Delegate a self-contained subtask to a focused, autonomous sub-agent that has the same tools (it cannot spawn further sub-agents). The sub-agent does NOT see the current conversation, so the task and context must be self-contained. It returns a concise summary of what it did. Use for isolating research or parallelizable chunks of work.',
|
|
160
|
+
parameters: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
task: { type: 'string', description: 'A clear, complete description of the task for the sub-agent to accomplish.' },
|
|
164
|
+
context: { type: 'string', description: 'Optional background the sub-agent needs (file paths, constraints, prior findings).' },
|
|
165
|
+
},
|
|
166
|
+
required: ['task'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
// ─── Safe tools (auto-approved) ───────────────────────────────────────────────
|
|
172
|
+
// spawn_agent is "safe" at the dispatch layer — the tools the sub-agent itself
|
|
173
|
+
// runs go through their own approval inside the sub-agent loop.
|
|
174
|
+
export const SAFE_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'memory_write', 'spawn_agent', 'ask_user']);
|
|
175
|
+
// ─── Display helpers ──────────────────────────────────────────────────────────
|
|
176
|
+
export function formatToolArgs(name, input) {
|
|
177
|
+
const p = (v) => v != null && v !== 'undefined' ? String(v) : '(missing)';
|
|
178
|
+
switch (name) {
|
|
179
|
+
case 'read_file':
|
|
180
|
+
return `"${p(input.path)}"${input.start_line ? `:${input.start_line}-${input.end_line}` : ''}`;
|
|
181
|
+
case 'write_file':
|
|
182
|
+
return `"${p(input.path)}" (${typeof input.content === 'string' ? input.content.split('\n').length + ' lines' : '?'})`;
|
|
183
|
+
case 'edit_file':
|
|
184
|
+
return `"${p(input.path)}"`;
|
|
185
|
+
case 'bash': {
|
|
186
|
+
const cmd = String(input.command ?? '');
|
|
187
|
+
return cmd.length > 60 ? cmd.slice(0, 60) + '…' : cmd;
|
|
188
|
+
}
|
|
189
|
+
case 'list_dir':
|
|
190
|
+
return `"${p(input.path) === '(missing)' ? '.' : p(input.path)}"${input.recursive ? ' --recursive' : ''}`;
|
|
191
|
+
case 'search_files':
|
|
192
|
+
return `"${p(input.pattern)}"${input.cwd ? ` in "${input.cwd}"` : ''}`;
|
|
193
|
+
case 'grep':
|
|
194
|
+
return `"${p(input.pattern)}"${input.path ? ` in "${input.path}"` : ''}`;
|
|
195
|
+
case 'memory_write':
|
|
196
|
+
return `[${input.scope ?? 'project'}]`;
|
|
197
|
+
case 'ask_user': {
|
|
198
|
+
const q = String(input.question ?? '');
|
|
199
|
+
return `"${q.length > 56 ? q.slice(0, 56) + '…' : q}"`;
|
|
200
|
+
}
|
|
201
|
+
case 'spawn_agent': {
|
|
202
|
+
const task = String(input.task ?? '');
|
|
203
|
+
return `"${task.length > 56 ? task.slice(0, 56) + '…' : task}"`;
|
|
204
|
+
}
|
|
205
|
+
default:
|
|
206
|
+
return JSON.stringify(input).slice(0, 80);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ─── Executors ────────────────────────────────────────────────────────────────
|
|
210
|
+
function readFile(input) {
|
|
211
|
+
if (!input.path || input.path === 'undefined')
|
|
212
|
+
return 'Error: path is required for read_file';
|
|
213
|
+
const abs = resolve(input.path);
|
|
214
|
+
if (!existsSync(abs))
|
|
215
|
+
return `Error: File not found: ${input.path}`;
|
|
216
|
+
try {
|
|
217
|
+
const content = readFileSync(abs, 'utf-8');
|
|
218
|
+
const lines = content.split('\n');
|
|
219
|
+
const start = (input.start_line ?? 1) - 1;
|
|
220
|
+
const end = input.end_line ?? lines.length;
|
|
221
|
+
return lines.slice(start, end).map((l, i) => `${String(i + start + 1).padStart(4)} │ ${l}`).join('\n');
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
return `Error: ${e}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function writeFile(input) {
|
|
228
|
+
if (!input.path || input.path === 'undefined')
|
|
229
|
+
return 'Error: path is required for write_file';
|
|
230
|
+
if (input.content == null)
|
|
231
|
+
return 'Error: content is required for write_file';
|
|
232
|
+
const abs = resolve(input.path);
|
|
233
|
+
const dir = dirname(abs);
|
|
234
|
+
if (!existsSync(dir))
|
|
235
|
+
mkdirSync(dir, { recursive: true });
|
|
236
|
+
try {
|
|
237
|
+
writeFileSync(abs, input.content);
|
|
238
|
+
return `Written ${input.content.split('\n').length} lines to ${input.path}`;
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
return `Error: ${e}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function editFile(input) {
|
|
245
|
+
if (!input.path || input.path === 'undefined')
|
|
246
|
+
return 'Error: path is required for edit_file';
|
|
247
|
+
const abs = resolve(input.path);
|
|
248
|
+
if (!existsSync(abs))
|
|
249
|
+
return `Error: File not found: ${input.path}`;
|
|
250
|
+
try {
|
|
251
|
+
const content = readFileSync(abs, 'utf-8');
|
|
252
|
+
const count = content.split(input.old_string).length - 1;
|
|
253
|
+
if (count === 0)
|
|
254
|
+
return `Error: String not found in ${input.path}`;
|
|
255
|
+
if (count > 1)
|
|
256
|
+
return `Error: String appears ${count} times — provide more context to make it unique`;
|
|
257
|
+
writeFileSync(abs, content.replace(input.old_string, input.new_string));
|
|
258
|
+
return `Edited ${input.path} — replaced 1 occurrence`;
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
return `Error: ${e}`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function bash(input) {
|
|
265
|
+
const cwd = input.cwd ? resolve(input.cwd) : process.cwd();
|
|
266
|
+
const command = input.command.trim();
|
|
267
|
+
// Background commands (ending with &) — detach properly so they survive
|
|
268
|
+
if (command.endsWith('&')) {
|
|
269
|
+
const bgCmd = command.slice(0, -1).trim();
|
|
270
|
+
try {
|
|
271
|
+
const isWindows = process.platform === 'win32';
|
|
272
|
+
const child = spawn(isWindows ? 'cmd.exe' : 'bash', isWindows ? ['/d', '/s', '/c', bgCmd] : ['-c', bgCmd], {
|
|
273
|
+
cwd,
|
|
274
|
+
detached: true,
|
|
275
|
+
stdio: 'ignore',
|
|
276
|
+
});
|
|
277
|
+
await new Promise((resolveSpawn, rejectSpawn) => {
|
|
278
|
+
child.once('spawn', resolveSpawn);
|
|
279
|
+
child.once('error', rejectSpawn);
|
|
280
|
+
});
|
|
281
|
+
child.unref();
|
|
282
|
+
return `Started background process (PID: ${child.pid})`;
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
return `Error starting background process: ${err}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
290
|
+
cwd,
|
|
291
|
+
timeout: input.timeout_ms ?? 30000,
|
|
292
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
293
|
+
});
|
|
294
|
+
const parts = [];
|
|
295
|
+
if (stdout.trim())
|
|
296
|
+
parts.push(stdout.trim());
|
|
297
|
+
if (stderr.trim())
|
|
298
|
+
parts.push(`[stderr]\n${stderr.trim()}`);
|
|
299
|
+
return parts.join('\n') || '(no output)';
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
const e = err;
|
|
303
|
+
const parts = [];
|
|
304
|
+
if (e.stdout?.trim())
|
|
305
|
+
parts.push(e.stdout.trim());
|
|
306
|
+
if (e.stderr?.trim())
|
|
307
|
+
parts.push(`[stderr]\n${e.stderr.trim()}`);
|
|
308
|
+
if (!parts.length)
|
|
309
|
+
parts.push(e.message ?? 'Command failed');
|
|
310
|
+
return `Exit ${e.code ?? 1}\n${parts.join('\n')}`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function listDir(input) {
|
|
314
|
+
const root = resolve(input.path ?? '.');
|
|
315
|
+
if (!existsSync(root))
|
|
316
|
+
return `Error: Not found: ${input.path}`;
|
|
317
|
+
const maxDepth = input.max_depth ?? 3;
|
|
318
|
+
const lines = [];
|
|
319
|
+
function walk(dir, depth, prefix) {
|
|
320
|
+
if (depth > maxDepth)
|
|
321
|
+
return;
|
|
322
|
+
let entries;
|
|
323
|
+
try {
|
|
324
|
+
entries = readdirSync(dir).filter(e => e !== 'node_modules' && e !== '.git' && !e.startsWith('.'));
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
entries.sort((a, b) => {
|
|
330
|
+
const aDir = statSync(`${dir}/${a}`).isDirectory();
|
|
331
|
+
const bDir = statSync(`${dir}/${b}`).isDirectory();
|
|
332
|
+
if (aDir !== bDir)
|
|
333
|
+
return aDir ? -1 : 1;
|
|
334
|
+
return a.localeCompare(b);
|
|
335
|
+
});
|
|
336
|
+
for (let i = 0; i < entries.length; i++) {
|
|
337
|
+
const e = entries[i];
|
|
338
|
+
const isLast = i === entries.length - 1;
|
|
339
|
+
const isDir = statSync(`${dir}/${e}`).isDirectory();
|
|
340
|
+
lines.push(`${prefix}${isLast ? '└── ' : '├── '}${e}${isDir ? '/' : ''}`);
|
|
341
|
+
if (isDir && input.recursive)
|
|
342
|
+
walk(`${dir}/${e}`, depth + 1, prefix + (isLast ? ' ' : '│ '));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
lines.push(input.path ?? '.');
|
|
346
|
+
walk(root, 1, '');
|
|
347
|
+
return lines.join('\n');
|
|
348
|
+
}
|
|
349
|
+
async function searchFiles(input) {
|
|
350
|
+
try {
|
|
351
|
+
const files = await glob(input.pattern, {
|
|
352
|
+
cwd: input.cwd ?? process.cwd(),
|
|
353
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
354
|
+
});
|
|
355
|
+
return files.length ? files.sort().join('\n') : 'No files found';
|
|
356
|
+
}
|
|
357
|
+
catch (e) {
|
|
358
|
+
return `Error: ${e}`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function grepFiles(input) {
|
|
362
|
+
const max = input.max_results ?? 50;
|
|
363
|
+
const root = input.path ? resolve(input.path) : process.cwd();
|
|
364
|
+
const flags = input.ignore_case ? 'i' : '';
|
|
365
|
+
let re;
|
|
366
|
+
try {
|
|
367
|
+
re = new RegExp(input.pattern, flags);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
re = new RegExp(input.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
|
|
371
|
+
}
|
|
372
|
+
const includeRe = input.include ? globToRegExp(input.include) : undefined;
|
|
373
|
+
const results = [];
|
|
374
|
+
function shouldSkip(name) {
|
|
375
|
+
return name === 'node_modules' || name === '.git' || name === 'dist';
|
|
376
|
+
}
|
|
377
|
+
function searchFile(file) {
|
|
378
|
+
if (includeRe && !includeRe.test(file.replace(/\\/g, '/')))
|
|
379
|
+
return;
|
|
380
|
+
let content;
|
|
381
|
+
try {
|
|
382
|
+
content = readFileSync(file, 'utf-8');
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (content.includes('\u0000'))
|
|
388
|
+
return;
|
|
389
|
+
const lines = content.split(/\r?\n/);
|
|
390
|
+
for (let i = 0; i < lines.length; i++) {
|
|
391
|
+
if (!re.test(lines[i]))
|
|
392
|
+
continue;
|
|
393
|
+
re.lastIndex = 0;
|
|
394
|
+
results.push(`${file}:${i + 1}: ${lines[i]}`);
|
|
395
|
+
if (results.length >= max)
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function walk(path) {
|
|
400
|
+
if (results.length >= max)
|
|
401
|
+
return;
|
|
402
|
+
let st;
|
|
403
|
+
try {
|
|
404
|
+
st = statSync(path);
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (st.isFile()) {
|
|
410
|
+
searchFile(path);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!st.isDirectory())
|
|
414
|
+
return;
|
|
415
|
+
let entries;
|
|
416
|
+
try {
|
|
417
|
+
entries = readdirSync(path);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
if (shouldSkip(entry))
|
|
424
|
+
continue;
|
|
425
|
+
const child = join(path, entry);
|
|
426
|
+
let childStat;
|
|
427
|
+
try {
|
|
428
|
+
childStat = statSync(child);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (childStat.isDirectory() && input.recursive === false)
|
|
434
|
+
continue;
|
|
435
|
+
if (childStat.isFile() || childStat.isDirectory())
|
|
436
|
+
walk(child);
|
|
437
|
+
if (results.length >= max)
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
walk(root);
|
|
442
|
+
return results.length ? results.join('\n') : 'No matches';
|
|
443
|
+
}
|
|
444
|
+
function globToRegExp(pattern) {
|
|
445
|
+
const normalized = pattern.replace(/\\/g, '/');
|
|
446
|
+
const escaped = normalized
|
|
447
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
448
|
+
.replace(/\*\*/g, '\u0000')
|
|
449
|
+
.replace(/\*/g, '[^/]*')
|
|
450
|
+
.replace(/\?/g, '[^/]')
|
|
451
|
+
.replace(/\u0000/g, '.*');
|
|
452
|
+
return new RegExp(`(^|/)${escaped}$`, 'i');
|
|
453
|
+
}
|
|
454
|
+
function memoryWrite(input) {
|
|
455
|
+
const scope = input.scope ?? 'project';
|
|
456
|
+
import('./memory.js').then(({ appendProjectMemory, appendGlobalMemory }) => {
|
|
457
|
+
if (scope === 'global')
|
|
458
|
+
appendGlobalMemory(input.content);
|
|
459
|
+
else
|
|
460
|
+
appendProjectMemory(input.content);
|
|
461
|
+
});
|
|
462
|
+
return `Saved to ${scope} memory.`;
|
|
463
|
+
}
|
|
464
|
+
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
|
465
|
+
export async function executeTool(name, input) {
|
|
466
|
+
switch (name) {
|
|
467
|
+
case 'read_file': return readFile(input);
|
|
468
|
+
case 'write_file': return writeFile(input);
|
|
469
|
+
case 'edit_file': return editFile(input);
|
|
470
|
+
case 'bash': return bash(input);
|
|
471
|
+
case 'list_dir': return listDir(input);
|
|
472
|
+
case 'search_files': return searchFiles(input);
|
|
473
|
+
case 'grep': return grepFiles(input);
|
|
474
|
+
case 'memory_write': return memoryWrite(input);
|
|
475
|
+
default: return `Unknown tool: ${name}`;
|
|
476
|
+
}
|
|
477
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ikie-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agentic coding CLI — your terminal AI pair programmer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ikie": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"coding",
|
|
18
|
+
"cli",
|
|
19
|
+
"pair-programmer",
|
|
20
|
+
"terminal"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/ikie-cli/ikie.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://ikie-cli.com",
|
|
27
|
+
"bugs": "https://github.com/anomalyco/opencode/issues",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"start": "node dist/index.js",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"openai": "^4.78.1",
|
|
36
|
+
"chalk": "^5.3.0",
|
|
37
|
+
"glob": "^11.0.0",
|
|
38
|
+
"minimist": "^1.2.8",
|
|
39
|
+
"ora": "^8.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/minimist": "^1.2.5",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"tsx": "^4.19.0",
|
|
45
|
+
"typescript": "^5.5.0"
|
|
46
|
+
}
|
|
47
|
+
}
|