hyunjin 0.0.1 → 0.0.2
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/index.d.ts +2 -0
- package/dist/index.js +874 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -4
- package/readme.md +95 -1
- package/index.js +0 -1
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/agent/index.ts
|
|
7
|
+
import { streamText, stepCountIs } from "ai";
|
|
8
|
+
|
|
9
|
+
// src/provider/index.ts
|
|
10
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
11
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
12
|
+
|
|
13
|
+
// src/cli/config.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
import * as os from "os";
|
|
17
|
+
import * as readline from "readline";
|
|
18
|
+
|
|
19
|
+
// src/ui/index.ts
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
import { Marked } from "marked";
|
|
22
|
+
import { markedTerminal } from "marked-terminal";
|
|
23
|
+
import ora from "ora";
|
|
24
|
+
|
|
25
|
+
// src/version.ts
|
|
26
|
+
var VERSION = "0.1.0";
|
|
27
|
+
|
|
28
|
+
// src/ui/index.ts
|
|
29
|
+
var marked = new Marked(markedTerminal());
|
|
30
|
+
var UI;
|
|
31
|
+
((UI2) => {
|
|
32
|
+
function logo() {
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(chalk.bold.cyan(" \u2566 \u2566 \u252C \u252C \u252C \u252C \u250C\u2510\u250C \u250C\u2510\u250C \u250C\u2510\u250C"));
|
|
35
|
+
console.log(chalk.bold.cyan(" \u2560\u2550\u2563 \u2514\u252C\u2518 \u2502 \u2502 \u2502 \u2502 \u2502\u2502\u2502 \u2502\u2502\u2502 \u2502\u2502\u2502"));
|
|
36
|
+
console.log(chalk.bold.cyan(" \u2569 \u2569 \u2534 \u2514\u2500\u2518 \u2514\u2500\u2518 \u2518\u2514\u2518 \u2518\u2514\u2518 \u2518\u2514\u2518"));
|
|
37
|
+
console.log(chalk.dim(` AI Coding Agent v${VERSION}`));
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
UI2.logo = logo;
|
|
41
|
+
function info(message) {
|
|
42
|
+
console.log(chalk.blue("\u2139"), message);
|
|
43
|
+
}
|
|
44
|
+
UI2.info = info;
|
|
45
|
+
function success(message) {
|
|
46
|
+
console.log(chalk.green("\u2713"), message);
|
|
47
|
+
}
|
|
48
|
+
UI2.success = success;
|
|
49
|
+
function warn(message) {
|
|
50
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
51
|
+
}
|
|
52
|
+
UI2.warn = warn;
|
|
53
|
+
function error(message) {
|
|
54
|
+
console.log(chalk.red("\u2717"), message);
|
|
55
|
+
}
|
|
56
|
+
UI2.error = error;
|
|
57
|
+
function markdown(content) {
|
|
58
|
+
return marked.parse(content);
|
|
59
|
+
}
|
|
60
|
+
UI2.markdown = markdown;
|
|
61
|
+
function tool8(name, description) {
|
|
62
|
+
const colors = {
|
|
63
|
+
read: chalk.cyan,
|
|
64
|
+
write: chalk.green,
|
|
65
|
+
edit: chalk.yellow,
|
|
66
|
+
bash: chalk.red,
|
|
67
|
+
glob: chalk.magenta,
|
|
68
|
+
grep: chalk.blue
|
|
69
|
+
};
|
|
70
|
+
const color = colors[name] || chalk.gray;
|
|
71
|
+
console.log(color("\u2502"), chalk.bold(name.padEnd(8)), chalk.dim(description));
|
|
72
|
+
}
|
|
73
|
+
UI2.tool = tool8;
|
|
74
|
+
function thinking() {
|
|
75
|
+
return ora({
|
|
76
|
+
text: chalk.dim("Thinking..."),
|
|
77
|
+
spinner: "dots"
|
|
78
|
+
}).start();
|
|
79
|
+
}
|
|
80
|
+
UI2.thinking = thinking;
|
|
81
|
+
function divider() {
|
|
82
|
+
console.log(chalk.dim("\u2500".repeat(60)));
|
|
83
|
+
}
|
|
84
|
+
UI2.divider = divider;
|
|
85
|
+
function prompt(agent = "hyunjin") {
|
|
86
|
+
return chalk.bold.cyan(`${agent} > `);
|
|
87
|
+
}
|
|
88
|
+
UI2.prompt = prompt;
|
|
89
|
+
})(UI || (UI = {}));
|
|
90
|
+
|
|
91
|
+
// src/cli/config.ts
|
|
92
|
+
var CONFIG_DIR = path.join(os.homedir(), ".hyunjin");
|
|
93
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
94
|
+
function loadConfig() {
|
|
95
|
+
try {
|
|
96
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
97
|
+
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
98
|
+
return JSON.parse(content);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
return { providers: {} };
|
|
103
|
+
}
|
|
104
|
+
function saveConfig(config) {
|
|
105
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
106
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
109
|
+
}
|
|
110
|
+
async function configCommand(options) {
|
|
111
|
+
if (options.setKey) {
|
|
112
|
+
const provider = options.setKey.toLowerCase();
|
|
113
|
+
if (!["openai", "anthropic"].includes(provider)) {
|
|
114
|
+
UI.error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 provider\uC785\uB2C8\uB2E4: ${provider}`);
|
|
115
|
+
UI.info("\uC9C0\uC6D0\uD558\uB294 provider: openai, anthropic");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const rl = readline.createInterface({
|
|
119
|
+
input: process.stdin,
|
|
120
|
+
output: process.stdout
|
|
121
|
+
});
|
|
122
|
+
rl.question(`${provider} API \uD0A4\uB97C \uC785\uB825\uD558\uC138\uC694: `, (apiKey) => {
|
|
123
|
+
rl.close();
|
|
124
|
+
if (!apiKey.trim()) {
|
|
125
|
+
UI.error("API \uD0A4\uAC00 \uC785\uB825\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const config = loadConfig();
|
|
129
|
+
config.providers[provider] = { apiKey: apiKey.trim() };
|
|
130
|
+
saveConfig(config);
|
|
131
|
+
UI.success(`${provider} API \uD0A4\uAC00 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4`);
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
const config = loadConfig();
|
|
135
|
+
UI.info("\uD604\uC7AC \uC124\uC815:");
|
|
136
|
+
UI.divider();
|
|
137
|
+
const providers = Object.keys(config.providers);
|
|
138
|
+
if (providers.length === 0) {
|
|
139
|
+
console.log(" \uC124\uC815\uB41C provider\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
140
|
+
console.log();
|
|
141
|
+
UI.info("API \uD0A4 \uC124\uC815: hyunjin config --set-key <provider>");
|
|
142
|
+
UI.info("\uD658\uACBD\uBCC0\uC218 \uC0AC\uC6A9: OPENAI_API_KEY, ANTHROPIC_API_KEY");
|
|
143
|
+
} else {
|
|
144
|
+
providers.forEach((p) => {
|
|
145
|
+
console.log(` ${p}: ****\uC124\uC815\uB428****`);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (config.defaultModel) {
|
|
149
|
+
console.log(` \uAE30\uBCF8 \uBAA8\uB378: ${config.defaultModel}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/provider/index.ts
|
|
155
|
+
var PROVIDERS = {
|
|
156
|
+
openai: {
|
|
157
|
+
id: "openai",
|
|
158
|
+
name: "OpenAI",
|
|
159
|
+
models: ["gpt-5", "gpt-4o", "gpt-4-turbo", "gpt-4", "o1", "o1-mini", "o1-preview"],
|
|
160
|
+
defaultModel: "gpt-5"
|
|
161
|
+
},
|
|
162
|
+
anthropic: {
|
|
163
|
+
id: "anthropic",
|
|
164
|
+
name: "Anthropic",
|
|
165
|
+
models: ["claude-4-5-sonnet", "claude-4-5-haiku", "claude-4-opus", "claude-3-5-sonnet-latest"],
|
|
166
|
+
defaultModel: "claude-4-5-sonnet"
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function parseModelString(modelString) {
|
|
170
|
+
if (!modelString) {
|
|
171
|
+
const config = loadConfig();
|
|
172
|
+
const openaiKey = process.env.OPENAI_API_KEY || config.providers.openai?.apiKey;
|
|
173
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY || config.providers.anthropic?.apiKey;
|
|
174
|
+
if (anthropicKey) {
|
|
175
|
+
return { providerId: "anthropic", modelId: PROVIDERS.anthropic.defaultModel };
|
|
176
|
+
}
|
|
177
|
+
if (openaiKey) {
|
|
178
|
+
return { providerId: "openai", modelId: PROVIDERS.openai.defaultModel };
|
|
179
|
+
}
|
|
180
|
+
throw new Error(
|
|
181
|
+
"API \uD0A4\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n\uD658\uACBD\uBCC0\uC218(OPENAI_API_KEY \uB610\uB294 ANTHROPIC_API_KEY)\uB97C \uC124\uC815\uD558\uAC70\uB098\n'hyunjin config --set-key openai' \uBA85\uB839\uC5B4\uB85C \uC124\uC815\uD574\uC8FC\uC138\uC694."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const parts = modelString.split("/");
|
|
185
|
+
if (parts.length === 2) {
|
|
186
|
+
return { providerId: parts[0], modelId: parts[1] };
|
|
187
|
+
}
|
|
188
|
+
const modelId = modelString;
|
|
189
|
+
if (modelId.startsWith("gpt-") || modelId.startsWith("o1")) {
|
|
190
|
+
return { providerId: "openai", modelId };
|
|
191
|
+
}
|
|
192
|
+
if (modelId.startsWith("claude-")) {
|
|
193
|
+
return { providerId: "anthropic", modelId };
|
|
194
|
+
}
|
|
195
|
+
throw new Error(`\uC54C \uC218 \uC5C6\uB294 \uBAA8\uB378: ${modelString}`);
|
|
196
|
+
}
|
|
197
|
+
function getLanguageModel(providerId, modelId) {
|
|
198
|
+
const config = loadConfig();
|
|
199
|
+
switch (providerId) {
|
|
200
|
+
case "openai": {
|
|
201
|
+
const apiKey = process.env.OPENAI_API_KEY || config.providers.openai?.apiKey;
|
|
202
|
+
if (!apiKey) {
|
|
203
|
+
throw new Error("OpenAI API \uD0A4\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
204
|
+
}
|
|
205
|
+
const openai = createOpenAI({ apiKey });
|
|
206
|
+
return openai(modelId);
|
|
207
|
+
}
|
|
208
|
+
case "anthropic": {
|
|
209
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || config.providers.anthropic?.apiKey;
|
|
210
|
+
if (!apiKey) {
|
|
211
|
+
throw new Error("Anthropic API \uD0A4\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
212
|
+
}
|
|
213
|
+
const anthropic = createAnthropic({ apiKey });
|
|
214
|
+
return anthropic(modelId);
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 provider: ${providerId}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/tool/read.ts
|
|
222
|
+
import * as fs2 from "fs/promises";
|
|
223
|
+
import * as path2 from "path";
|
|
224
|
+
import { z } from "zod";
|
|
225
|
+
import { tool } from "ai";
|
|
226
|
+
function readFileTool(context) {
|
|
227
|
+
return tool({
|
|
228
|
+
description: "Read the contents of a file. Use this to examine code, configuration files, or any text file.",
|
|
229
|
+
inputSchema: z.object({
|
|
230
|
+
path: z.string().describe("The file path to read (relative to current directory)"),
|
|
231
|
+
offset: z.number().nullable().describe("Line number to start reading from (1-indexed)"),
|
|
232
|
+
limit: z.number().nullable().describe("Maximum number of lines to read")
|
|
233
|
+
}),
|
|
234
|
+
execute: async ({ path: filePath, offset, limit }) => {
|
|
235
|
+
try {
|
|
236
|
+
const fullPath = path2.resolve(context.cwd, filePath);
|
|
237
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
238
|
+
let lines = content.split("\n");
|
|
239
|
+
if (offset !== void 0 && offset > 0) {
|
|
240
|
+
lines = lines.slice(offset - 1);
|
|
241
|
+
}
|
|
242
|
+
if (limit !== void 0 && limit > 0) {
|
|
243
|
+
lines = lines.slice(0, limit);
|
|
244
|
+
}
|
|
245
|
+
const startLine = offset || 1;
|
|
246
|
+
const numberedLines = lines.map(
|
|
247
|
+
(line, i) => `${String(startLine + i).padStart(6)}|${line}`
|
|
248
|
+
);
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
path: filePath,
|
|
252
|
+
content: numberedLines.join("\n"),
|
|
253
|
+
totalLines: content.split("\n").length
|
|
254
|
+
};
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return {
|
|
257
|
+
success: false,
|
|
258
|
+
error: error instanceof Error ? error.message : "\uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/tool/write.ts
|
|
266
|
+
import * as fs3 from "fs/promises";
|
|
267
|
+
import * as path3 from "path";
|
|
268
|
+
import { z as z2 } from "zod";
|
|
269
|
+
import { tool as tool2 } from "ai";
|
|
270
|
+
function writeFileTool(context) {
|
|
271
|
+
return tool2({
|
|
272
|
+
description: "Write content to a file. Creates the file if it doesn't exist, or overwrites if it does. Creates parent directories if needed.",
|
|
273
|
+
inputSchema: z2.object({
|
|
274
|
+
path: z2.string().describe("The file path to write to (relative to current directory)"),
|
|
275
|
+
content: z2.string().describe("The content to write to the file")
|
|
276
|
+
}),
|
|
277
|
+
execute: async ({ path: filePath, content }) => {
|
|
278
|
+
try {
|
|
279
|
+
const fullPath = path3.resolve(context.cwd, filePath);
|
|
280
|
+
const dir = path3.dirname(fullPath);
|
|
281
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
282
|
+
await fs3.writeFile(fullPath, content, "utf-8");
|
|
283
|
+
return {
|
|
284
|
+
success: true,
|
|
285
|
+
path: filePath,
|
|
286
|
+
message: `\uD30C\uC77C\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC791\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${filePath}`
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
error: error instanceof Error ? error.message : "\uD30C\uC77C\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/tool/edit.ts
|
|
299
|
+
import * as fs4 from "fs/promises";
|
|
300
|
+
import * as path4 from "path";
|
|
301
|
+
import { z as z3 } from "zod";
|
|
302
|
+
import { tool as tool3 } from "ai";
|
|
303
|
+
function editFileTool(context) {
|
|
304
|
+
return tool3({
|
|
305
|
+
description: "Edit a file by replacing specific text. The old_string must match exactly (including whitespace and indentation).",
|
|
306
|
+
inputSchema: z3.object({
|
|
307
|
+
path: z3.string().describe("The file path to edit (relative to current directory)"),
|
|
308
|
+
old_string: z3.string().describe("The exact text to replace (must match exactly)"),
|
|
309
|
+
new_string: z3.string().describe("The new text to replace with")
|
|
310
|
+
}),
|
|
311
|
+
execute: async ({ path: filePath, old_string, new_string }) => {
|
|
312
|
+
try {
|
|
313
|
+
const fullPath = path4.resolve(context.cwd, filePath);
|
|
314
|
+
const content = await fs4.readFile(fullPath, "utf-8");
|
|
315
|
+
if (!content.includes(old_string)) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: "\uC9C0\uC815\uB41C \uD14D\uC2A4\uD2B8\uB97C \uD30C\uC77C\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC815\uD655\uD55C \uD14D\uC2A4\uD2B8\uC778\uC9C0 \uD655\uC778\uD574\uC8FC\uC138\uC694."
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const occurrences = content.split(old_string).length - 1;
|
|
322
|
+
if (occurrences > 1) {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
error: `${occurrences}\uAC1C\uC758 \uC77C\uCE58\uD558\uB294 \uD14D\uC2A4\uD2B8\uAC00 \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB354 \uAD6C\uCCB4\uC801\uC778 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uD3EC\uD568\uD574\uC8FC\uC138\uC694.`
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const newContent = content.replace(old_string, new_string);
|
|
329
|
+
await fs4.writeFile(fullPath, newContent, "utf-8");
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
path: filePath,
|
|
333
|
+
message: "\uD30C\uC77C\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4"
|
|
334
|
+
};
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: error instanceof Error ? error.message : "\uD30C\uC77C\uC744 \uC218\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/tool/bash.ts
|
|
346
|
+
import { spawn } from "child_process";
|
|
347
|
+
import { z as z4 } from "zod";
|
|
348
|
+
import { tool as tool4 } from "ai";
|
|
349
|
+
function bashTool(context) {
|
|
350
|
+
return tool4({
|
|
351
|
+
description: "Execute a bash command. Use this for running scripts, installing packages, git operations, etc. Be careful with destructive commands.",
|
|
352
|
+
inputSchema: z4.object({
|
|
353
|
+
command: z4.string().describe("The bash command to execute"),
|
|
354
|
+
timeout: z4.number().nullable().describe("Timeout in milliseconds (default: 30000)")
|
|
355
|
+
}),
|
|
356
|
+
execute: async ({ command, timeout: timeoutParam }) => {
|
|
357
|
+
const timeout = timeoutParam ?? 3e4;
|
|
358
|
+
return new Promise((resolve5) => {
|
|
359
|
+
let stdout = "";
|
|
360
|
+
let stderr = "";
|
|
361
|
+
const proc = spawn("bash", ["-c", command], {
|
|
362
|
+
cwd: context.cwd,
|
|
363
|
+
env: process.env
|
|
364
|
+
});
|
|
365
|
+
const timer = setTimeout(() => {
|
|
366
|
+
proc.kill("SIGTERM");
|
|
367
|
+
resolve5({
|
|
368
|
+
success: false,
|
|
369
|
+
error: `\uBA85\uB839\uC5B4 \uC2E4\uD589 \uC2DC\uAC04 \uCD08\uACFC (${timeout}ms)`,
|
|
370
|
+
stdout,
|
|
371
|
+
stderr
|
|
372
|
+
});
|
|
373
|
+
}, timeout);
|
|
374
|
+
proc.stdout.on("data", (data) => {
|
|
375
|
+
stdout += data.toString();
|
|
376
|
+
});
|
|
377
|
+
proc.stderr.on("data", (data) => {
|
|
378
|
+
stderr += data.toString();
|
|
379
|
+
});
|
|
380
|
+
proc.on("close", (code) => {
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
const maxLength = 5e4;
|
|
383
|
+
if (stdout.length > maxLength) {
|
|
384
|
+
stdout = stdout.slice(0, maxLength) + "\n... (\uCD9C\uB825\uC774 \uC798\uB838\uC2B5\uB2C8\uB2E4)";
|
|
385
|
+
}
|
|
386
|
+
if (stderr.length > maxLength) {
|
|
387
|
+
stderr = stderr.slice(0, maxLength) + "\n... (\uCD9C\uB825\uC774 \uC798\uB838\uC2B5\uB2C8\uB2E4)";
|
|
388
|
+
}
|
|
389
|
+
resolve5({
|
|
390
|
+
success: code === 0,
|
|
391
|
+
exitCode: code,
|
|
392
|
+
stdout: stdout.trim(),
|
|
393
|
+
stderr: stderr.trim()
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
proc.on("error", (error) => {
|
|
397
|
+
clearTimeout(timer);
|
|
398
|
+
resolve5({
|
|
399
|
+
success: false,
|
|
400
|
+
error: error.message
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/tool/glob.ts
|
|
409
|
+
import { glob } from "glob";
|
|
410
|
+
import { z as z5 } from "zod";
|
|
411
|
+
import { tool as tool5 } from "ai";
|
|
412
|
+
function globTool(context) {
|
|
413
|
+
return tool5({
|
|
414
|
+
description: "Find files matching a glob pattern. Use this to discover files in the project. Common patterns: **/*.ts, src/**/*.tsx, *.json",
|
|
415
|
+
inputSchema: z5.object({
|
|
416
|
+
pattern: z5.string().describe("Glob pattern to match files (e.g., **/*.ts, src/**/*.tsx)"),
|
|
417
|
+
ignore: z5.array(z5.string()).nullable().describe("Patterns to ignore (default: node_modules, .git)")
|
|
418
|
+
}),
|
|
419
|
+
execute: async ({ pattern, ignore: ignoreParam }) => {
|
|
420
|
+
try {
|
|
421
|
+
const ignorePatterns = ignoreParam ?? ["**/node_modules/**", "**/.git/**", "**/dist/**"];
|
|
422
|
+
const files = await glob(pattern, {
|
|
423
|
+
cwd: context.cwd,
|
|
424
|
+
ignore: ignorePatterns,
|
|
425
|
+
nodir: true
|
|
426
|
+
});
|
|
427
|
+
files.sort();
|
|
428
|
+
const maxResults = 500;
|
|
429
|
+
const truncated = files.length > maxResults;
|
|
430
|
+
return {
|
|
431
|
+
success: true,
|
|
432
|
+
pattern,
|
|
433
|
+
files: truncated ? files.slice(0, maxResults) : files,
|
|
434
|
+
totalCount: files.length,
|
|
435
|
+
truncated,
|
|
436
|
+
message: truncated ? `${maxResults}\uAC1C\uC758 \uACB0\uACFC\uB9CC \uD45C\uC2DC\uB429\uB2C8\uB2E4 (\uC804\uCCB4: ${files.length}\uAC1C)` : void 0
|
|
437
|
+
};
|
|
438
|
+
} catch (error) {
|
|
439
|
+
return {
|
|
440
|
+
success: false,
|
|
441
|
+
error: error instanceof Error ? error.message : "\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/tool/grep.ts
|
|
449
|
+
import { spawn as spawn2 } from "child_process";
|
|
450
|
+
import { z as z6 } from "zod";
|
|
451
|
+
import { tool as tool6 } from "ai";
|
|
452
|
+
function grepTool(context) {
|
|
453
|
+
return tool6({
|
|
454
|
+
description: "Search for text patterns in files using ripgrep (rg) or grep. Use this to find code, function definitions, imports, etc.",
|
|
455
|
+
inputSchema: z6.object({
|
|
456
|
+
pattern: z6.string().describe("The regex pattern to search for"),
|
|
457
|
+
path: z6.string().nullable().describe("Directory or file to search in (default: current directory)"),
|
|
458
|
+
include: z6.string().nullable().describe("File pattern to include (e.g., *.ts, *.tsx)"),
|
|
459
|
+
caseSensitive: z6.boolean().nullable().describe("Case sensitive search (default: false)")
|
|
460
|
+
}),
|
|
461
|
+
execute: async ({ pattern, path: searchPath, include, caseSensitive: caseSensitiveParam }) => {
|
|
462
|
+
const caseSensitive = caseSensitiveParam ?? false;
|
|
463
|
+
return new Promise((resolve5) => {
|
|
464
|
+
const targetPath = searchPath ?? ".";
|
|
465
|
+
const args = [];
|
|
466
|
+
const useRipgrep = true;
|
|
467
|
+
if (useRipgrep) {
|
|
468
|
+
args.push("--color=never", "--line-number", "--no-heading");
|
|
469
|
+
if (!caseSensitive) args.push("-i");
|
|
470
|
+
if (include) args.push("-g", include);
|
|
471
|
+
args.push("--", pattern, targetPath);
|
|
472
|
+
} else {
|
|
473
|
+
args.push("-rn");
|
|
474
|
+
if (!caseSensitive) args.push("-i");
|
|
475
|
+
if (include) args.push("--include", include);
|
|
476
|
+
args.push(pattern, targetPath);
|
|
477
|
+
}
|
|
478
|
+
let stdout = "";
|
|
479
|
+
let stderr = "";
|
|
480
|
+
const proc = spawn2("rg", args, {
|
|
481
|
+
cwd: context.cwd,
|
|
482
|
+
env: process.env
|
|
483
|
+
});
|
|
484
|
+
proc.stdout.on("data", (data) => {
|
|
485
|
+
stdout += data.toString();
|
|
486
|
+
});
|
|
487
|
+
proc.stderr.on("data", (data) => {
|
|
488
|
+
stderr += data.toString();
|
|
489
|
+
});
|
|
490
|
+
proc.on("close", (code) => {
|
|
491
|
+
if (code === 1 && !stderr) {
|
|
492
|
+
resolve5({
|
|
493
|
+
success: true,
|
|
494
|
+
matches: [],
|
|
495
|
+
message: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
496
|
+
});
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (code !== 0 && code !== 1) {
|
|
500
|
+
resolve5({
|
|
501
|
+
success: false,
|
|
502
|
+
error: stderr || "\uAC80\uC0C9 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4"
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
507
|
+
const maxResults = 200;
|
|
508
|
+
resolve5({
|
|
509
|
+
success: true,
|
|
510
|
+
matches: lines.slice(0, maxResults),
|
|
511
|
+
totalCount: lines.length,
|
|
512
|
+
truncated: lines.length > maxResults
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
proc.on("error", () => {
|
|
516
|
+
const grepProc = spawn2("grep", ["-rn", caseSensitive ? "" : "-i", pattern, targetPath].filter(Boolean), {
|
|
517
|
+
cwd: context.cwd
|
|
518
|
+
});
|
|
519
|
+
let grepStdout = "";
|
|
520
|
+
let grepStderr = "";
|
|
521
|
+
grepProc.stdout.on("data", (data) => {
|
|
522
|
+
grepStdout += data.toString();
|
|
523
|
+
});
|
|
524
|
+
grepProc.stderr.on("data", (data) => {
|
|
525
|
+
grepStderr += data.toString();
|
|
526
|
+
});
|
|
527
|
+
grepProc.on("close", (code) => {
|
|
528
|
+
if (code === 1 && !grepStderr) {
|
|
529
|
+
resolve5({
|
|
530
|
+
success: true,
|
|
531
|
+
matches: [],
|
|
532
|
+
message: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
533
|
+
});
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const lines = grepStdout.trim().split("\n").filter(Boolean);
|
|
537
|
+
resolve5({
|
|
538
|
+
success: true,
|
|
539
|
+
matches: lines.slice(0, 200),
|
|
540
|
+
totalCount: lines.length,
|
|
541
|
+
truncated: lines.length > 200
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/tool/list.ts
|
|
551
|
+
import * as fs5 from "fs/promises";
|
|
552
|
+
import * as path5 from "path";
|
|
553
|
+
import { z as z7 } from "zod";
|
|
554
|
+
import { tool as tool7 } from "ai";
|
|
555
|
+
function listDirectoryTool(context) {
|
|
556
|
+
return tool7({
|
|
557
|
+
description: "List files and directories in a given path. Use this to explore project structure.",
|
|
558
|
+
inputSchema: z7.object({
|
|
559
|
+
path: z7.string().nullable().describe("Directory path to list (default: current directory)"),
|
|
560
|
+
recursive: z7.boolean().nullable().describe("List recursively (default: false)"),
|
|
561
|
+
maxDepth: z7.number().nullable().describe("Maximum depth for recursive listing (default: 2)")
|
|
562
|
+
}),
|
|
563
|
+
execute: async ({ path: dirPathParam, recursive: recursiveParam, maxDepth: maxDepthParam }) => {
|
|
564
|
+
const dirPath = dirPathParam ?? ".";
|
|
565
|
+
const recursive = recursiveParam ?? false;
|
|
566
|
+
const maxDepth = maxDepthParam ?? 2;
|
|
567
|
+
try {
|
|
568
|
+
const fullPath = path5.resolve(context.cwd, dirPath);
|
|
569
|
+
const results = [];
|
|
570
|
+
const ignoreDirs = /* @__PURE__ */ new Set([
|
|
571
|
+
"node_modules",
|
|
572
|
+
".git",
|
|
573
|
+
"dist",
|
|
574
|
+
"build",
|
|
575
|
+
".next",
|
|
576
|
+
"__pycache__",
|
|
577
|
+
".venv",
|
|
578
|
+
"venv"
|
|
579
|
+
]);
|
|
580
|
+
async function listDir(currentPath, prefix, depth) {
|
|
581
|
+
if (recursive && depth > maxDepth) return;
|
|
582
|
+
const entries = await fs5.readdir(currentPath, { withFileTypes: true });
|
|
583
|
+
entries.sort((a, b) => {
|
|
584
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
585
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
586
|
+
return a.name.localeCompare(b.name);
|
|
587
|
+
});
|
|
588
|
+
for (const entry of entries) {
|
|
589
|
+
if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
|
|
590
|
+
if (ignoreDirs.has(entry.name)) continue;
|
|
591
|
+
const isDir = entry.isDirectory();
|
|
592
|
+
const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
593
|
+
results.push(`${prefix}${icon} ${entry.name}`);
|
|
594
|
+
if (recursive && isDir && depth < maxDepth) {
|
|
595
|
+
const nextPath = path5.join(currentPath, entry.name);
|
|
596
|
+
await listDir(nextPath, prefix + " ", depth + 1);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
await listDir(fullPath, "", 0);
|
|
601
|
+
return {
|
|
602
|
+
success: true,
|
|
603
|
+
path: dirPath,
|
|
604
|
+
entries: results.join("\n"),
|
|
605
|
+
count: results.length
|
|
606
|
+
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
return {
|
|
609
|
+
success: false,
|
|
610
|
+
error: error instanceof Error ? error.message : "\uB514\uB809\uD1A0\uB9AC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/tool/index.ts
|
|
618
|
+
function createTools(context) {
|
|
619
|
+
return {
|
|
620
|
+
read_file: readFileTool(context),
|
|
621
|
+
write_file: writeFileTool(context),
|
|
622
|
+
edit_file: editFileTool(context),
|
|
623
|
+
bash: bashTool(context),
|
|
624
|
+
glob: globTool(context),
|
|
625
|
+
grep: grepTool(context),
|
|
626
|
+
list_directory: listDirectoryTool(context)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/agent/system-prompt.ts
|
|
631
|
+
import * as os2 from "os";
|
|
632
|
+
function getSystemPrompt(cwd) {
|
|
633
|
+
const today = (/* @__PURE__ */ new Date()).toLocaleDateString("ko-KR", {
|
|
634
|
+
year: "numeric",
|
|
635
|
+
month: "long",
|
|
636
|
+
day: "numeric",
|
|
637
|
+
weekday: "long"
|
|
638
|
+
});
|
|
639
|
+
return `You are Hyunjin, an AI coding assistant. You help developers write, debug, and improve code.
|
|
640
|
+
|
|
641
|
+
## Environment
|
|
642
|
+
- Working directory: ${cwd}
|
|
643
|
+
- Platform: ${os2.platform()}
|
|
644
|
+
- Today: ${today}
|
|
645
|
+
|
|
646
|
+
## Your Capabilities
|
|
647
|
+
You have access to the following tools:
|
|
648
|
+
- **read_file**: Read file contents with optional line range
|
|
649
|
+
- **write_file**: Create or overwrite files
|
|
650
|
+
- **edit_file**: Make precise edits to existing files
|
|
651
|
+
- **bash**: Execute shell commands
|
|
652
|
+
- **glob**: Find files matching patterns
|
|
653
|
+
- **grep**: Search for text patterns in files
|
|
654
|
+
- **list_directory**: Explore directory structure
|
|
655
|
+
|
|
656
|
+
## Guidelines
|
|
657
|
+
|
|
658
|
+
### Code Quality
|
|
659
|
+
- Write clean, maintainable code following best practices
|
|
660
|
+
- Use TypeScript/JavaScript modern syntax (ES2022+)
|
|
661
|
+
- Follow the existing code style in the project
|
|
662
|
+
- Add appropriate comments for complex logic
|
|
663
|
+
|
|
664
|
+
### File Operations
|
|
665
|
+
- Always read a file before editing to understand context
|
|
666
|
+
- Use edit_file for small changes, write_file for new files or complete rewrites
|
|
667
|
+
- Create parent directories automatically when writing files
|
|
668
|
+
|
|
669
|
+
### Communication
|
|
670
|
+
- Explain your reasoning briefly before taking action
|
|
671
|
+
- Report results after completing tasks
|
|
672
|
+
- Ask clarifying questions when requirements are ambiguous
|
|
673
|
+
- Use Korean for user-facing messages, English for code
|
|
674
|
+
|
|
675
|
+
### Safety
|
|
676
|
+
- Be careful with destructive operations (rm, overwrite)
|
|
677
|
+
- Confirm before making large-scale changes
|
|
678
|
+
- Don't execute commands that could harm the system
|
|
679
|
+
- Don't expose sensitive information (API keys, passwords)
|
|
680
|
+
|
|
681
|
+
## Response Format
|
|
682
|
+
1. Briefly explain what you're going to do
|
|
683
|
+
2. Use appropriate tools to complete the task
|
|
684
|
+
3. Summarize what was done and any next steps
|
|
685
|
+
|
|
686
|
+
Remember: You are a helpful coding assistant. Focus on solving the user's problem efficiently and safely.`;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/agent/index.ts
|
|
690
|
+
var Agent = class {
|
|
691
|
+
model;
|
|
692
|
+
tools;
|
|
693
|
+
systemPrompt;
|
|
694
|
+
messages = [];
|
|
695
|
+
cwd;
|
|
696
|
+
providerId;
|
|
697
|
+
modelId;
|
|
698
|
+
constructor(options = {}) {
|
|
699
|
+
this.cwd = options.cwd || process.cwd();
|
|
700
|
+
const { providerId, modelId } = parseModelString(options.model);
|
|
701
|
+
this.providerId = providerId;
|
|
702
|
+
this.modelId = modelId;
|
|
703
|
+
this.model = getLanguageModel(providerId, modelId);
|
|
704
|
+
const toolContext = { cwd: this.cwd };
|
|
705
|
+
this.tools = createTools(toolContext);
|
|
706
|
+
this.systemPrompt = getSystemPrompt(this.cwd);
|
|
707
|
+
UI.info(`\uBAA8\uB378: ${providerId}/${modelId}`);
|
|
708
|
+
}
|
|
709
|
+
async run(prompt) {
|
|
710
|
+
const spinner = UI.thinking();
|
|
711
|
+
try {
|
|
712
|
+
let enhancedPrompt = prompt;
|
|
713
|
+
const result = streamText({
|
|
714
|
+
model: this.model,
|
|
715
|
+
system: this.systemPrompt,
|
|
716
|
+
messages: [{ role: "user", content: enhancedPrompt }],
|
|
717
|
+
tools: this.tools,
|
|
718
|
+
stopWhen: stepCountIs(30),
|
|
719
|
+
onStepFinish: (step) => {
|
|
720
|
+
const toolCalls = step.toolCalls;
|
|
721
|
+
if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
722
|
+
spinner.stop();
|
|
723
|
+
for (const call of toolCalls) {
|
|
724
|
+
const args = call.args ? JSON.stringify(call.args) : "{}";
|
|
725
|
+
const truncatedArgs = args.length > 100 ? args.slice(0, 100) + "..." : args;
|
|
726
|
+
UI.tool(call.toolName || "unknown", truncatedArgs);
|
|
727
|
+
}
|
|
728
|
+
spinner.start();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
spinner.stop();
|
|
733
|
+
console.log();
|
|
734
|
+
for await (const textPart of result.textStream) {
|
|
735
|
+
process.stdout.write(textPart);
|
|
736
|
+
}
|
|
737
|
+
console.log();
|
|
738
|
+
} catch (error) {
|
|
739
|
+
spinner.stop();
|
|
740
|
+
if (error instanceof Error) {
|
|
741
|
+
UI.error(error.message);
|
|
742
|
+
}
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async chat(prompt) {
|
|
747
|
+
const spinner = UI.thinking();
|
|
748
|
+
try {
|
|
749
|
+
this.messages.push({ role: "user", content: prompt });
|
|
750
|
+
const result = streamText({
|
|
751
|
+
model: this.model,
|
|
752
|
+
system: this.systemPrompt,
|
|
753
|
+
messages: this.messages,
|
|
754
|
+
tools: this.tools,
|
|
755
|
+
stopWhen: stepCountIs(30),
|
|
756
|
+
onStepFinish: (step) => {
|
|
757
|
+
const toolCalls = step.toolCalls;
|
|
758
|
+
if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
759
|
+
spinner.stop();
|
|
760
|
+
for (const call of toolCalls) {
|
|
761
|
+
const args = call.args ? JSON.stringify(call.args) : "{}";
|
|
762
|
+
const truncatedArgs = args.length > 100 ? args.slice(0, 100) + "..." : args;
|
|
763
|
+
UI.tool(call.toolName || "unknown", truncatedArgs);
|
|
764
|
+
}
|
|
765
|
+
spinner.start();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
spinner.stop();
|
|
770
|
+
console.log();
|
|
771
|
+
let responseText = "";
|
|
772
|
+
for await (const textPart of result.textStream) {
|
|
773
|
+
process.stdout.write(textPart);
|
|
774
|
+
responseText += textPart;
|
|
775
|
+
}
|
|
776
|
+
console.log();
|
|
777
|
+
if (responseText) {
|
|
778
|
+
this.messages.push({ role: "assistant", content: responseText });
|
|
779
|
+
} else {
|
|
780
|
+
UI.warn("\uC751\uB2F5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
spinner.stop();
|
|
784
|
+
if (error instanceof Error) {
|
|
785
|
+
UI.error(error.message);
|
|
786
|
+
}
|
|
787
|
+
throw error;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
getMessages() {
|
|
791
|
+
return [...this.messages];
|
|
792
|
+
}
|
|
793
|
+
clearHistory() {
|
|
794
|
+
this.messages = [];
|
|
795
|
+
UI.info("\uB300\uD654 \uAE30\uB85D\uC774 \uCD08\uAE30\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/cli/run.ts
|
|
800
|
+
async function runCommand(message, options) {
|
|
801
|
+
const prompt = message.join(" ");
|
|
802
|
+
if (!prompt.trim()) {
|
|
803
|
+
UI.error("\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694");
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const agent = new Agent({
|
|
808
|
+
model: options.model,
|
|
809
|
+
files: options.file
|
|
810
|
+
});
|
|
811
|
+
await agent.run(prompt);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
if (error instanceof Error) {
|
|
814
|
+
UI.error(error.message);
|
|
815
|
+
}
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/cli/chat.ts
|
|
821
|
+
import * as readline2 from "readline";
|
|
822
|
+
async function chatCommand(options) {
|
|
823
|
+
UI.info("\uB300\uD654\uD615 \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4. \uC885\uB8CC\uD558\uB824\uBA74 'exit' \uB610\uB294 Ctrl+C\uB97C \uC785\uB825\uD558\uC138\uC694.");
|
|
824
|
+
UI.divider();
|
|
825
|
+
const agent = new Agent({
|
|
826
|
+
model: options.model
|
|
827
|
+
});
|
|
828
|
+
const rl = readline2.createInterface({
|
|
829
|
+
input: process.stdin,
|
|
830
|
+
output: process.stdout
|
|
831
|
+
});
|
|
832
|
+
const askQuestion = () => {
|
|
833
|
+
rl.question(UI.prompt(), async (input) => {
|
|
834
|
+
const trimmed = input.trim();
|
|
835
|
+
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
836
|
+
UI.info("\uC138\uC158\uC744 \uC885\uB8CC\uD569\uB2C8\uB2E4.");
|
|
837
|
+
rl.close();
|
|
838
|
+
process.exit(0);
|
|
839
|
+
}
|
|
840
|
+
if (trimmed === "") {
|
|
841
|
+
askQuestion();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
try {
|
|
845
|
+
await agent.chat(trimmed);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (error instanceof Error) {
|
|
848
|
+
UI.error(error.message);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
console.log();
|
|
852
|
+
askQuestion();
|
|
853
|
+
});
|
|
854
|
+
};
|
|
855
|
+
askQuestion();
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/index.ts
|
|
859
|
+
var program = new Command();
|
|
860
|
+
program.name("hyunjin").description("AI-powered coding agent for terminal").version(VERSION).hook("preAction", () => {
|
|
861
|
+
UI.logo();
|
|
862
|
+
});
|
|
863
|
+
program.command("run").description("Run a single prompt and exit").argument("[message...]", "Message to send to the agent").option("-m, --model <model>", "Model to use (e.g., openai/gpt-4o, anthropic/claude-3-5-sonnet)").option("-f, --file <files...>", "Files to include in context").action(runCommand);
|
|
864
|
+
program.command("chat").description("Start an interactive chat session").option("-m, --model <model>", "Model to use").action(chatCommand);
|
|
865
|
+
program.command("config").description("Configure API keys and settings").option("--set-key <provider>", "Set API key for a provider").action(configCommand);
|
|
866
|
+
program.argument("[message...]", "Message to send").option("-m, --model <model>", "Model to use").action(async (message, options) => {
|
|
867
|
+
if (message.length > 0) {
|
|
868
|
+
await runCommand(message, options);
|
|
869
|
+
} else {
|
|
870
|
+
await chatCommand(options);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
program.parse();
|
|
874
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/agent/index.ts","../src/provider/index.ts","../src/cli/config.ts","../src/ui/index.ts","../src/version.ts","../src/tool/read.ts","../src/tool/write.ts","../src/tool/edit.ts","../src/tool/bash.ts","../src/tool/glob.ts","../src/tool/grep.ts","../src/tool/list.ts","../src/tool/index.ts","../src/agent/system-prompt.ts","../src/cli/run.ts","../src/cli/chat.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport { runCommand } from \"./cli/run\"\nimport { chatCommand } from \"./cli/chat\"\nimport { configCommand } from \"./cli/config\"\nimport { VERSION } from \"./version\"\nimport { UI } from \"./ui\"\n\nconst program = new Command()\n\nprogram\n .name(\"hyunjin\")\n .description(\"AI-powered coding agent for terminal\")\n .version(VERSION)\n .hook(\"preAction\", () => {\n UI.logo()\n })\n\nprogram\n .command(\"run\")\n .description(\"Run a single prompt and exit\")\n .argument(\"[message...]\", \"Message to send to the agent\")\n .option(\"-m, --model <model>\", \"Model to use (e.g., openai/gpt-4o, anthropic/claude-3-5-sonnet)\")\n .option(\"-f, --file <files...>\", \"Files to include in context\")\n .action(runCommand)\n\nprogram\n .command(\"chat\")\n .description(\"Start an interactive chat session\")\n .option(\"-m, --model <model>\", \"Model to use\")\n .action(chatCommand)\n\nprogram\n .command(\"config\")\n .description(\"Configure API keys and settings\")\n .option(\"--set-key <provider>\", \"Set API key for a provider\")\n .action(configCommand)\n\n// Default command (chat)\nprogram\n .argument(\"[message...]\", \"Message to send\")\n .option(\"-m, --model <model>\", \"Model to use\")\n .action(async (message: string[], options) => {\n if (message.length > 0) {\n await runCommand(message, options)\n } else {\n await chatCommand(options)\n }\n })\n\nprogram.parse()\n","import { streamText, stepCountIs, type ModelMessage } from \"ai\"\nimport { parseModelString, getLanguageModel } from \"../provider\"\nimport { createTools, type ToolContext } from \"../tool\"\nimport { getSystemPrompt } from \"./system-prompt\"\nimport { UI } from \"../ui\"\n\nexport interface AgentOptions {\n model?: string\n files?: string[]\n cwd?: string\n}\n\nexport class Agent {\n private model: ReturnType<typeof getLanguageModel>\n private tools: ReturnType<typeof createTools>\n private systemPrompt: string\n private messages: ModelMessage[] = []\n private cwd: string\n private providerId: string\n private modelId: string\n\n constructor(options: AgentOptions = {}) {\n this.cwd = options.cwd || process.cwd()\n\n const { providerId, modelId } = parseModelString(options.model)\n this.providerId = providerId\n this.modelId = modelId\n this.model = getLanguageModel(providerId, modelId)\n\n const toolContext: ToolContext = { cwd: this.cwd }\n this.tools = createTools(toolContext)\n this.systemPrompt = getSystemPrompt(this.cwd)\n\n UI.info(`모델: ${providerId}/${modelId}`)\n }\n\n async run(prompt: string): Promise<void> {\n const spinner = UI.thinking()\n\n try {\n // Add file context if provided\n let enhancedPrompt = prompt\n\n const result = streamText({\n model: this.model,\n system: this.systemPrompt,\n messages: [{ role: \"user\", content: enhancedPrompt }],\n tools: this.tools,\n stopWhen: stepCountIs(30),\n onStepFinish: (step) => {\n const toolCalls = step.toolCalls\n if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {\n spinner.stop()\n for (const call of toolCalls) {\n const args = (call as any).args ? JSON.stringify((call as any).args) : \"{}\"\n const truncatedArgs =\n args.length > 100 ? args.slice(0, 100) + \"...\" : args\n UI.tool(call.toolName || \"unknown\", truncatedArgs)\n }\n spinner.start()\n }\n },\n })\n\n spinner.stop()\n console.log()\n\n // Stream text output\n for await (const textPart of result.textStream) {\n process.stdout.write(textPart)\n }\n console.log()\n } catch (error) {\n spinner.stop()\n if (error instanceof Error) {\n UI.error(error.message)\n }\n throw error\n }\n }\n\n async chat(prompt: string): Promise<void> {\n const spinner = UI.thinking()\n\n try {\n this.messages.push({ role: \"user\", content: prompt })\n\n const result = streamText({\n model: this.model,\n system: this.systemPrompt,\n messages: this.messages,\n tools: this.tools,\n stopWhen: stepCountIs(30),\n onStepFinish: (step) => {\n const toolCalls = step.toolCalls\n if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {\n spinner.stop()\n for (const call of toolCalls) {\n const args = (call as any).args ? JSON.stringify((call as any).args) : \"{}\"\n const truncatedArgs =\n args.length > 100 ? args.slice(0, 100) + \"...\" : args\n UI.tool(call.toolName || \"unknown\", truncatedArgs)\n }\n spinner.start()\n }\n },\n })\n\n spinner.stop()\n console.log()\n\n // Stream text output\n let responseText = \"\"\n for await (const textPart of result.textStream) {\n process.stdout.write(textPart)\n responseText += textPart\n }\n console.log()\n\n // Add assistant response to history\n if (responseText) {\n this.messages.push({ role: \"assistant\", content: responseText })\n } else {\n UI.warn(\"응답이 없습니다\")\n }\n } catch (error) {\n spinner.stop()\n if (error instanceof Error) {\n UI.error(error.message)\n }\n throw error\n }\n }\n\n getMessages(): ModelMessage[] {\n return [...this.messages]\n }\n\n clearHistory(): void {\n this.messages = []\n UI.info(\"대화 기록이 초기화되었습니다\")\n }\n}\n","import { createOpenAI } from '@ai-sdk/openai'\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport type { LanguageModel } from 'ai'\nimport { loadConfig } from '../cli/config'\n\nexport interface ProviderInfo {\n id: string\n name: string\n models: string[]\n defaultModel: string\n}\n\nexport const PROVIDERS: Record<string, ProviderInfo> = {\n openai: {\n id: 'openai',\n name: 'OpenAI',\n models: ['gpt-5', 'gpt-4o', 'gpt-4-turbo', 'gpt-4', 'o1', 'o1-mini', 'o1-preview'],\n defaultModel: 'gpt-5',\n },\n anthropic: {\n id: 'anthropic',\n name: 'Anthropic',\n models: ['claude-4-5-sonnet', 'claude-4-5-haiku', 'claude-4-opus', 'claude-3-5-sonnet-latest'],\n defaultModel: 'claude-4-5-sonnet',\n },\n}\n\nexport function parseModelString(modelString?: string): { providerId: string; modelId: string } {\n if (!modelString) {\n // Default: try to find available provider\n const config = loadConfig()\n const openaiKey = process.env.OPENAI_API_KEY || config.providers.openai?.apiKey\n const anthropicKey = process.env.ANTHROPIC_API_KEY || config.providers.anthropic?.apiKey\n\n if (anthropicKey) {\n return { providerId: 'anthropic', modelId: PROVIDERS.anthropic.defaultModel }\n }\n if (openaiKey) {\n return { providerId: 'openai', modelId: PROVIDERS.openai.defaultModel }\n }\n\n throw new Error(\n 'API 키가 설정되지 않았습니다.\\n' +\n '환경변수(OPENAI_API_KEY 또는 ANTHROPIC_API_KEY)를 설정하거나\\n' +\n \"'hyunjin config --set-key openai' 명령어로 설정해주세요.\",\n )\n }\n\n const parts = modelString.split('/')\n if (parts.length === 2) {\n return { providerId: parts[0], modelId: parts[1] }\n }\n\n // If no provider specified, try to detect from model name\n const modelId = modelString\n if (modelId.startsWith('gpt-') || modelId.startsWith('o1')) {\n return { providerId: 'openai', modelId }\n }\n if (modelId.startsWith('claude-')) {\n return { providerId: 'anthropic', modelId }\n }\n\n throw new Error(`알 수 없는 모델: ${modelString}`)\n}\n\nexport function getLanguageModel(providerId: string, modelId: string): LanguageModel {\n const config = loadConfig()\n\n switch (providerId) {\n case 'openai': {\n const apiKey = process.env.OPENAI_API_KEY || config.providers.openai?.apiKey\n if (!apiKey) {\n throw new Error('OpenAI API 키가 설정되지 않았습니다')\n }\n const openai = createOpenAI({ apiKey })\n return openai(modelId)\n }\n\n case 'anthropic': {\n const apiKey = process.env.ANTHROPIC_API_KEY || config.providers.anthropic?.apiKey\n if (!apiKey) {\n throw new Error('Anthropic API 키가 설정되지 않았습니다')\n }\n const anthropic = createAnthropic({ apiKey })\n return anthropic(modelId)\n }\n\n default:\n throw new Error(`지원하지 않는 provider: ${providerId}`)\n }\n}\n","import * as fs from 'fs'\nimport * as path from 'path'\nimport * as os from 'os'\nimport * as readline from 'readline'\nimport { UI } from '../ui'\n\ninterface ConfigOptions {\n setKey?: string\n}\n\nconst CONFIG_DIR = path.join(os.homedir(), '.hyunjin')\nconst CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')\n\nexport interface HyunjinConfig {\n providers: {\n openai?: { apiKey: string }\n anthropic?: { apiKey: string }\n }\n defaultModel?: string\n}\n\nexport function loadConfig(): HyunjinConfig {\n try {\n if (fs.existsSync(CONFIG_FILE)) {\n const content = fs.readFileSync(CONFIG_FILE, 'utf-8')\n return JSON.parse(content)\n }\n } catch {\n // ignore\n }\n return { providers: {} }\n}\n\nexport function saveConfig(config: HyunjinConfig) {\n if (!fs.existsSync(CONFIG_DIR)) {\n fs.mkdirSync(CONFIG_DIR, { recursive: true })\n }\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))\n}\n\nexport async function configCommand(options: ConfigOptions) {\n if (options.setKey) {\n const provider = options.setKey.toLowerCase()\n\n if (!['openai', 'anthropic'].includes(provider)) {\n UI.error(`지원하지 않는 provider입니다: ${provider}`)\n UI.info('지원하는 provider: openai, anthropic')\n process.exit(1)\n }\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n })\n\n rl.question(`${provider} API 키를 입력하세요: `, (apiKey) => {\n rl.close()\n\n if (!apiKey.trim()) {\n UI.error('API 키가 입력되지 않았습니다')\n process.exit(1)\n }\n\n const config = loadConfig()\n config.providers[provider as 'openai' | 'anthropic'] = { apiKey: apiKey.trim() }\n saveConfig(config)\n\n UI.success(`${provider} API 키가 저장되었습니다`)\n })\n } else {\n const config = loadConfig()\n\n UI.info('현재 설정:')\n UI.divider()\n\n const providers = Object.keys(config.providers)\n if (providers.length === 0) {\n console.log(' 설정된 provider가 없습니다')\n console.log()\n UI.info('API 키 설정: hyunjin config --set-key <provider>')\n UI.info('환경변수 사용: OPENAI_API_KEY, ANTHROPIC_API_KEY')\n } else {\n providers.forEach((p) => {\n console.log(` ${p}: ****설정됨****`)\n })\n }\n\n if (config.defaultModel) {\n console.log(` 기본 모델: ${config.defaultModel}`)\n }\n }\n}\n","import chalk from \"chalk\"\nimport { Marked } from \"marked\"\nimport { markedTerminal } from \"marked-terminal\"\nimport ora, { type Ora } from \"ora\"\nimport { VERSION } from \"../version.js\"\n\nconst marked = new Marked(markedTerminal() as any)\n\nexport namespace UI {\n export function logo() {\n console.log()\n console.log(chalk.bold.cyan(\" ╦ ╦ ┬ ┬ ┬ ┬ ┌┐┌ ┌┐┌ ┌┐┌\"))\n console.log(chalk.bold.cyan(\" ╠═╣ └┬┘ │ │ │ │ │││ │││ │││\"))\n console.log(chalk.bold.cyan(\" ╩ ╩ ┴ └─┘ └─┘ ┘└┘ ┘└┘ ┘└┘\"))\n console.log(chalk.dim(` AI Coding Agent v${VERSION}`))\n console.log()\n }\n\n export function info(message: string) {\n console.log(chalk.blue(\"ℹ\"), message)\n }\n\n export function success(message: string) {\n console.log(chalk.green(\"✓\"), message)\n }\n\n export function warn(message: string) {\n console.log(chalk.yellow(\"⚠\"), message)\n }\n\n export function error(message: string) {\n console.log(chalk.red(\"✗\"), message)\n }\n\n export function markdown(content: string): string {\n return marked.parse(content) as string\n }\n\n export function tool(name: string, description: string) {\n const colors: Record<string, typeof chalk.blue> = {\n read: chalk.cyan,\n write: chalk.green,\n edit: chalk.yellow,\n bash: chalk.red,\n glob: chalk.magenta,\n grep: chalk.blue,\n }\n const color = colors[name] || chalk.gray\n console.log(color(\"│\"), chalk.bold(name.padEnd(8)), chalk.dim(description))\n }\n\n export function thinking(): Ora {\n return ora({\n text: chalk.dim(\"Thinking...\"),\n spinner: \"dots\",\n }).start()\n }\n\n export function divider() {\n console.log(chalk.dim(\"─\".repeat(60)))\n }\n\n export function prompt(agent: string = \"hyunjin\") {\n return chalk.bold.cyan(`${agent} > `)\n }\n}\n","export const VERSION = \"0.1.0\"\n","import * as fs from \"fs/promises\"\nimport * as path from \"path\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function readFileTool(context: ToolContext) {\n return tool({\n description:\n \"Read the contents of a file. Use this to examine code, configuration files, or any text file.\",\n inputSchema: z.object({\n path: z.string().describe(\"The file path to read (relative to current directory)\"),\n offset: z.number().nullable().describe(\"Line number to start reading from (1-indexed)\"),\n limit: z.number().nullable().describe(\"Maximum number of lines to read\"),\n }),\n execute: async ({ path: filePath, offset, limit }) => {\n try {\n const fullPath = path.resolve(context.cwd, filePath)\n const content = await fs.readFile(fullPath, \"utf-8\")\n\n let lines = content.split(\"\\n\")\n\n if (offset !== undefined && offset > 0) {\n lines = lines.slice(offset - 1)\n }\n\n if (limit !== undefined && limit > 0) {\n lines = lines.slice(0, limit)\n }\n\n const startLine = offset || 1\n const numberedLines = lines.map(\n (line, i) => `${String(startLine + i).padStart(6)}|${line}`\n )\n\n return {\n success: true,\n path: filePath,\n content: numberedLines.join(\"\\n\"),\n totalLines: content.split(\"\\n\").length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"파일을 읽을 수 없습니다\",\n }\n }\n },\n })\n}\n","import * as fs from \"fs/promises\"\nimport * as path from \"path\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function writeFileTool(context: ToolContext) {\n return tool({\n description:\n \"Write content to a file. Creates the file if it doesn't exist, or overwrites if it does. Creates parent directories if needed.\",\n inputSchema: z.object({\n path: z.string().describe(\"The file path to write to (relative to current directory)\"),\n content: z.string().describe(\"The content to write to the file\"),\n }),\n execute: async ({ path: filePath, content }) => {\n try {\n const fullPath = path.resolve(context.cwd, filePath)\n const dir = path.dirname(fullPath)\n\n // Create parent directories if they don't exist\n await fs.mkdir(dir, { recursive: true })\n await fs.writeFile(fullPath, content, \"utf-8\")\n\n return {\n success: true,\n path: filePath,\n message: `파일이 성공적으로 작성되었습니다: ${filePath}`,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"파일을 쓸 수 없습니다\",\n }\n }\n },\n })\n}\n","import * as fs from \"fs/promises\"\nimport * as path from \"path\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function editFileTool(context: ToolContext) {\n return tool({\n description:\n \"Edit a file by replacing specific text. The old_string must match exactly (including whitespace and indentation).\",\n inputSchema: z.object({\n path: z.string().describe(\"The file path to edit (relative to current directory)\"),\n old_string: z.string().describe(\"The exact text to replace (must match exactly)\"),\n new_string: z.string().describe(\"The new text to replace with\"),\n }),\n execute: async ({ path: filePath, old_string, new_string }) => {\n try {\n const fullPath = path.resolve(context.cwd, filePath)\n const content = await fs.readFile(fullPath, \"utf-8\")\n\n if (!content.includes(old_string)) {\n return {\n success: false,\n error: \"지정된 텍스트를 파일에서 찾을 수 없습니다. 정확한 텍스트인지 확인해주세요.\",\n }\n }\n\n const occurrences = content.split(old_string).length - 1\n if (occurrences > 1) {\n return {\n success: false,\n error: `${occurrences}개의 일치하는 텍스트가 발견되었습니다. 더 구체적인 컨텍스트를 포함해주세요.`,\n }\n }\n\n const newContent = content.replace(old_string, new_string)\n await fs.writeFile(fullPath, newContent, \"utf-8\")\n\n return {\n success: true,\n path: filePath,\n message: \"파일이 성공적으로 수정되었습니다\",\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"파일을 수정할 수 없습니다\",\n }\n }\n },\n })\n}\n","import { spawn } from \"child_process\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function bashTool(context: ToolContext) {\n return tool({\n description:\n \"Execute a bash command. Use this for running scripts, installing packages, git operations, etc. Be careful with destructive commands.\",\n inputSchema: z.object({\n command: z.string().describe(\"The bash command to execute\"),\n timeout: z\n .number()\n .nullable()\n .describe(\"Timeout in milliseconds (default: 30000)\"),\n }),\n execute: async ({ command, timeout: timeoutParam }) => {\n const timeout = timeoutParam ?? 30000\n return new Promise((resolve) => {\n let stdout = \"\"\n let stderr = \"\"\n\n const proc = spawn(\"bash\", [\"-c\", command], {\n cwd: context.cwd,\n env: process.env,\n })\n\n const timer = setTimeout(() => {\n proc.kill(\"SIGTERM\")\n resolve({\n success: false,\n error: `명령어 실행 시간 초과 (${timeout}ms)`,\n stdout,\n stderr,\n })\n }, timeout)\n\n proc.stdout.on(\"data\", (data) => {\n stdout += data.toString()\n })\n\n proc.stderr.on(\"data\", (data) => {\n stderr += data.toString()\n })\n\n proc.on(\"close\", (code) => {\n clearTimeout(timer)\n\n // Truncate output if too long\n const maxLength = 50000\n if (stdout.length > maxLength) {\n stdout = stdout.slice(0, maxLength) + \"\\n... (출력이 잘렸습니다)\"\n }\n if (stderr.length > maxLength) {\n stderr = stderr.slice(0, maxLength) + \"\\n... (출력이 잘렸습니다)\"\n }\n\n resolve({\n success: code === 0,\n exitCode: code,\n stdout: stdout.trim(),\n stderr: stderr.trim(),\n })\n })\n\n proc.on(\"error\", (error) => {\n clearTimeout(timer)\n resolve({\n success: false,\n error: error.message,\n })\n })\n })\n },\n })\n}\n","import { glob } from \"glob\"\nimport * as path from \"path\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function globTool(context: ToolContext) {\n return tool({\n description:\n \"Find files matching a glob pattern. Use this to discover files in the project. Common patterns: **/*.ts, src/**/*.tsx, *.json\",\n inputSchema: z.object({\n pattern: z.string().describe(\"Glob pattern to match files (e.g., **/*.ts, src/**/*.tsx)\"),\n ignore: z\n .array(z.string())\n .nullable()\n .describe(\"Patterns to ignore (default: node_modules, .git)\"),\n }),\n execute: async ({ pattern, ignore: ignoreParam }) => {\n try {\n const ignorePatterns = ignoreParam ?? [\"**/node_modules/**\", \"**/.git/**\", \"**/dist/**\"]\n\n const files = await glob(pattern, {\n cwd: context.cwd,\n ignore: ignorePatterns,\n nodir: true,\n })\n\n // Sort files for consistent output\n files.sort()\n\n // Limit results\n const maxResults = 500\n const truncated = files.length > maxResults\n\n return {\n success: true,\n pattern,\n files: truncated ? files.slice(0, maxResults) : files,\n totalCount: files.length,\n truncated,\n message: truncated\n ? `${maxResults}개의 결과만 표시됩니다 (전체: ${files.length}개)`\n : undefined,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"파일을 찾을 수 없습니다\",\n }\n }\n },\n })\n}\n","import { spawn } from \"child_process\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function grepTool(context: ToolContext) {\n return tool({\n description:\n \"Search for text patterns in files using ripgrep (rg) or grep. Use this to find code, function definitions, imports, etc.\",\n inputSchema: z.object({\n pattern: z.string().describe(\"The regex pattern to search for\"),\n path: z\n .string()\n .nullable()\n .describe(\"Directory or file to search in (default: current directory)\"),\n include: z\n .string()\n .nullable()\n .describe(\"File pattern to include (e.g., *.ts, *.tsx)\"),\n caseSensitive: z\n .boolean()\n .nullable()\n .describe(\"Case sensitive search (default: false)\"),\n }),\n execute: async ({ pattern, path: searchPath, include, caseSensitive: caseSensitiveParam }) => {\n const caseSensitive = caseSensitiveParam ?? false\n return new Promise((resolve) => {\n const targetPath = searchPath ?? \".\"\n\n // Try ripgrep first, fall back to grep\n const args: string[] = []\n\n // Use ripgrep if available, otherwise grep\n const useRipgrep = true // We'll try rg first\n\n if (useRipgrep) {\n args.push(\"--color=never\", \"--line-number\", \"--no-heading\")\n if (!caseSensitive) args.push(\"-i\")\n if (include) args.push(\"-g\", include)\n args.push(\"--\", pattern, targetPath)\n } else {\n args.push(\"-rn\")\n if (!caseSensitive) args.push(\"-i\")\n if (include) args.push(\"--include\", include)\n args.push(pattern, targetPath)\n }\n\n let stdout = \"\"\n let stderr = \"\"\n\n const proc = spawn(\"rg\", args, {\n cwd: context.cwd,\n env: process.env,\n })\n\n proc.stdout.on(\"data\", (data) => {\n stdout += data.toString()\n })\n\n proc.stderr.on(\"data\", (data) => {\n stderr += data.toString()\n })\n\n proc.on(\"close\", (code) => {\n // ripgrep returns 1 when no matches found\n if (code === 1 && !stderr) {\n resolve({\n success: true,\n matches: [],\n message: \"일치하는 결과가 없습니다\",\n })\n return\n }\n\n if (code !== 0 && code !== 1) {\n resolve({\n success: false,\n error: stderr || \"검색 중 오류가 발생했습니다\",\n })\n return\n }\n\n const lines = stdout.trim().split(\"\\n\").filter(Boolean)\n const maxResults = 200\n\n resolve({\n success: true,\n matches: lines.slice(0, maxResults),\n totalCount: lines.length,\n truncated: lines.length > maxResults,\n })\n })\n\n proc.on(\"error\", () => {\n // ripgrep not found, try grep\n const grepProc = spawn(\"grep\", [\"-rn\", caseSensitive ? \"\" : \"-i\", pattern, targetPath].filter(Boolean), {\n cwd: context.cwd,\n })\n\n let grepStdout = \"\"\n let grepStderr = \"\"\n\n grepProc.stdout.on(\"data\", (data) => {\n grepStdout += data.toString()\n })\n\n grepProc.stderr.on(\"data\", (data) => {\n grepStderr += data.toString()\n })\n\n grepProc.on(\"close\", (code) => {\n if (code === 1 && !grepStderr) {\n resolve({\n success: true,\n matches: [],\n message: \"일치하는 결과가 없습니다\",\n })\n return\n }\n\n const lines = grepStdout.trim().split(\"\\n\").filter(Boolean)\n\n resolve({\n success: true,\n matches: lines.slice(0, 200),\n totalCount: lines.length,\n truncated: lines.length > 200,\n })\n })\n })\n })\n },\n })\n}\n","import * as fs from \"fs/promises\"\nimport * as path from \"path\"\nimport { z } from \"zod\"\nimport { tool } from \"ai\"\nimport type { ToolContext } from \"./index\"\n\nexport function listDirectoryTool(context: ToolContext) {\n return tool({\n description:\n \"List files and directories in a given path. Use this to explore project structure.\",\n inputSchema: z.object({\n path: z\n .string()\n .nullable()\n .describe(\"Directory path to list (default: current directory)\"),\n recursive: z\n .boolean()\n .nullable()\n .describe(\"List recursively (default: false)\"),\n maxDepth: z\n .number()\n .nullable()\n .describe(\"Maximum depth for recursive listing (default: 2)\"),\n }),\n execute: async ({ path: dirPathParam, recursive: recursiveParam, maxDepth: maxDepthParam }) => {\n const dirPath = dirPathParam ?? \".\"\n const recursive = recursiveParam ?? false\n const maxDepth = maxDepthParam ?? 2\n try {\n const fullPath = path.resolve(context.cwd, dirPath)\n const results: string[] = []\n\n const ignoreDirs = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \"__pycache__\",\n \".venv\",\n \"venv\",\n ])\n\n async function listDir(currentPath: string, prefix: string, depth: number) {\n if (recursive && depth > maxDepth) return\n\n const entries = await fs.readdir(currentPath, { withFileTypes: true })\n\n // Sort: directories first, then files\n entries.sort((a, b) => {\n if (a.isDirectory() && !b.isDirectory()) return -1\n if (!a.isDirectory() && b.isDirectory()) return 1\n return a.name.localeCompare(b.name)\n })\n\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") && entry.name !== \".env.example\") continue\n if (ignoreDirs.has(entry.name)) continue\n\n const isDir = entry.isDirectory()\n const icon = isDir ? \"📁\" : \"📄\"\n results.push(`${prefix}${icon} ${entry.name}`)\n\n if (recursive && isDir && depth < maxDepth) {\n const nextPath = path.join(currentPath, entry.name)\n await listDir(nextPath, prefix + \" \", depth + 1)\n }\n }\n }\n\n await listDir(fullPath, \"\", 0)\n\n return {\n success: true,\n path: dirPath,\n entries: results.join(\"\\n\"),\n count: results.length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"디렉토리를 읽을 수 없습니다\",\n }\n }\n },\n })\n}\n","import { readFileTool } from \"./read\"\nimport { writeFileTool } from \"./write\"\nimport { editFileTool } from \"./edit\"\nimport { bashTool } from \"./bash\"\nimport { globTool } from \"./glob\"\nimport { grepTool } from \"./grep\"\nimport { listDirectoryTool } from \"./list\"\n\nexport interface ToolContext {\n cwd: string\n}\n\nexport function createTools(context: ToolContext) {\n return {\n read_file: readFileTool(context),\n write_file: writeFileTool(context),\n edit_file: editFileTool(context),\n bash: bashTool(context),\n glob: globTool(context),\n grep: grepTool(context),\n list_directory: listDirectoryTool(context),\n }\n}\n\nexport type Tools = ReturnType<typeof createTools>\n","import * as os from 'os'\n\nexport function getSystemPrompt(cwd: string): string {\n const today = new Date().toLocaleDateString('ko-KR', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n weekday: 'long',\n })\n\n return `You are Hyunjin, an AI coding assistant. You help developers write, debug, and improve code.\n\n## Environment\n- Working directory: ${cwd}\n- Platform: ${os.platform()}\n- Today: ${today}\n\n## Your Capabilities\nYou have access to the following tools:\n- **read_file**: Read file contents with optional line range\n- **write_file**: Create or overwrite files\n- **edit_file**: Make precise edits to existing files\n- **bash**: Execute shell commands\n- **glob**: Find files matching patterns\n- **grep**: Search for text patterns in files\n- **list_directory**: Explore directory structure\n\n## Guidelines\n\n### Code Quality\n- Write clean, maintainable code following best practices\n- Use TypeScript/JavaScript modern syntax (ES2022+)\n- Follow the existing code style in the project\n- Add appropriate comments for complex logic\n\n### File Operations\n- Always read a file before editing to understand context\n- Use edit_file for small changes, write_file for new files or complete rewrites\n- Create parent directories automatically when writing files\n\n### Communication\n- Explain your reasoning briefly before taking action\n- Report results after completing tasks\n- Ask clarifying questions when requirements are ambiguous\n- Use Korean for user-facing messages, English for code\n\n### Safety\n- Be careful with destructive operations (rm, overwrite)\n- Confirm before making large-scale changes\n- Don't execute commands that could harm the system\n- Don't expose sensitive information (API keys, passwords)\n\n## Response Format\n1. Briefly explain what you're going to do\n2. Use appropriate tools to complete the task\n3. Summarize what was done and any next steps\n\nRemember: You are a helpful coding assistant. Focus on solving the user's problem efficiently and safely.`\n}\n","import { Agent } from '../agent'\nimport { UI } from '../ui'\n\ninterface RunOptions {\n model?: string\n file?: string[]\n}\n\nexport async function runCommand(message: string[], options: RunOptions) {\n const prompt = message.join(' ')\n\n if (!prompt.trim()) {\n UI.error('메시지를 입력해주세요')\n process.exit(1)\n }\n\n try {\n const agent = new Agent({\n model: options.model,\n files: options.file,\n })\n\n await agent.run(prompt)\n } catch (error) {\n if (error instanceof Error) {\n UI.error(error.message)\n }\n process.exit(1)\n }\n}\n","import * as readline from 'readline'\nimport { Agent } from '../agent'\nimport { UI } from '../ui'\n\ninterface ChatOptions {\n model?: string\n}\n\nexport async function chatCommand(options: ChatOptions) {\n UI.info(\"대화형 세션을 시작합니다. 종료하려면 'exit' 또는 Ctrl+C를 입력하세요.\")\n UI.divider()\n\n const agent = new Agent({\n model: options.model,\n })\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n })\n\n const askQuestion = () => {\n rl.question(UI.prompt(), async (input) => {\n const trimmed = input.trim()\n\n if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {\n UI.info('세션을 종료합니다.')\n rl.close()\n process.exit(0)\n }\n\n if (trimmed === '') {\n askQuestion()\n return\n }\n\n try {\n await agent.chat(trimmed)\n } catch (error) {\n if (error instanceof Error) {\n UI.error(error.message)\n }\n }\n\n console.log()\n askQuestion()\n })\n }\n\n askQuestion()\n}\n"],"mappings":"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","names":["UI","tool","fs","path","fs","path","z","tool","fs","path","z","tool","z","tool","resolve","z","tool","spawn","z","tool","resolve","fs","path","z","tool","os","readline"]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
2
3
|
"name": "hyunjin",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"description": "AI-powered coding agent for terminal",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "hyunjin",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/hyunjin/hyunjin"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"hyunjin": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
6
29
|
"scripts": {
|
|
7
|
-
"
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "eslint src",
|
|
34
|
+
"prepublishOnly": "pnpm build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@ai-sdk/anthropic": "^3.0.9",
|
|
38
|
+
"@ai-sdk/openai": "^3.0.7",
|
|
39
|
+
"@clack/prompts": "^0.9.1",
|
|
40
|
+
"ai": "6.0.26",
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"glob": "^11.0.0",
|
|
44
|
+
"ignore": "^6.0.2",
|
|
45
|
+
"marked": "^15.0.0",
|
|
46
|
+
"marked-terminal": "^7.2.1",
|
|
47
|
+
"ora": "^8.1.1",
|
|
48
|
+
"strip-ansi": "^7.1.0",
|
|
49
|
+
"zod": "^3.24.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.10.0",
|
|
53
|
+
"tsup": "^8.3.0",
|
|
54
|
+
"tsx": "^4.19.0",
|
|
55
|
+
"typescript": "^5.7.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20.0.0"
|
|
8
59
|
}
|
|
9
60
|
}
|
package/readme.md
CHANGED
|
@@ -1 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
# Hyunjin AI
|
|
2
|
+
|
|
3
|
+
터미널에서 사용하는 AI 코딩 에이전트입니다.
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g hyunjin-ai
|
|
9
|
+
# or
|
|
10
|
+
pnpm add -g hyunjin-ai
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 설정
|
|
14
|
+
|
|
15
|
+
### API 키 설정
|
|
16
|
+
|
|
17
|
+
환경변수 사용:
|
|
18
|
+
```bash
|
|
19
|
+
export OPENAI_API_KEY="your-key"
|
|
20
|
+
# or
|
|
21
|
+
export ANTHROPIC_API_KEY="your-key"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
또는 CLI로 설정:
|
|
25
|
+
```bash
|
|
26
|
+
hyunjin config --set-key openai
|
|
27
|
+
hyunjin config --set-key anthropic
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 사용법
|
|
31
|
+
|
|
32
|
+
### 단일 프롬프트 실행
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
hyunjin "이 프로젝트의 구조를 설명해줘"
|
|
36
|
+
hyunjin run "src/index.ts 파일을 읽고 분석해줘"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 대화형 모드
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
hyunjin chat
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 모델 선택
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
hyunjin -m openai/gpt-4o "코드 리뷰해줘"
|
|
49
|
+
hyunjin -m anthropic/claude-3-5-sonnet-latest "버그를 찾아줘"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 지원 모델
|
|
53
|
+
|
|
54
|
+
### OpenAI
|
|
55
|
+
- gpt-4o (기본)
|
|
56
|
+
- gpt-4o-mini
|
|
57
|
+
- gpt-4-turbo
|
|
58
|
+
- o1, o1-mini, o1-preview
|
|
59
|
+
|
|
60
|
+
### Anthropic
|
|
61
|
+
- claude-3-5-sonnet-latest (기본)
|
|
62
|
+
- claude-3-5-haiku-latest
|
|
63
|
+
- claude-3-opus-latest
|
|
64
|
+
|
|
65
|
+
## 도구 (Tools)
|
|
66
|
+
|
|
67
|
+
Hyunjin은 다음 도구들을 사용할 수 있습니다:
|
|
68
|
+
|
|
69
|
+
- **read_file**: 파일 읽기
|
|
70
|
+
- **write_file**: 파일 쓰기
|
|
71
|
+
- **edit_file**: 파일 수정
|
|
72
|
+
- **bash**: 쉘 명령 실행
|
|
73
|
+
- **glob**: 파일 패턴 검색
|
|
74
|
+
- **grep**: 텍스트 패턴 검색
|
|
75
|
+
- **list_directory**: 디렉토리 탐색
|
|
76
|
+
|
|
77
|
+
## 개발
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 의존성 설치
|
|
81
|
+
pnpm install
|
|
82
|
+
|
|
83
|
+
# 개발 모드 실행
|
|
84
|
+
pnpm dev
|
|
85
|
+
|
|
86
|
+
# 빌드
|
|
87
|
+
pnpm build
|
|
88
|
+
|
|
89
|
+
# 타입 체크
|
|
90
|
+
pnpm typecheck
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 라이선스
|
|
94
|
+
|
|
95
|
+
MIT
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log('hyunjin');
|