@zhanngning/hecode 0.7.0 → 1.0.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.
@@ -1,670 +0,0 @@
1
- // src/tools/read_file.ts
2
- import { readFile } from "fs/promises";
3
- var readFileTool = {
4
- name: "read_file",
5
- description: "Read file content. Supports offset and limit for large files.",
6
- parameters: {
7
- type: "object",
8
- properties: {
9
- file_path: { type: "string", description: "Absolute path to the file" },
10
- offset: { type: "number", description: "Line number to start from (1-indexed)" },
11
- limit: { type: "number", description: "Max lines to read (default 2000)" }
12
- },
13
- required: ["file_path"]
14
- },
15
- async execute(params) {
16
- try {
17
- const content = await readFile(params.file_path, "utf-8");
18
- const lines = content.split("\n");
19
- const offset = params.offset || 1;
20
- const limit = params.limit || 2e3;
21
- const sliced = lines.slice(offset - 1, offset - 1 + limit);
22
- const numbered = sliced.map((line, i) => `${offset + i}: ${line}`).join("\n");
23
- return { output: numbered };
24
- } catch (e) {
25
- return { output: "", error: e.message };
26
- }
27
- }
28
- };
29
-
30
- // src/tools/write_file.ts
31
- import { writeFile, mkdir } from "fs/promises";
32
- import { dirname } from "path";
33
- var writeFileTool = {
34
- name: "write_file",
35
- description: "Write content to a file. Creates parent directories if needed.",
36
- parameters: {
37
- type: "object",
38
- properties: {
39
- file_path: { type: "string", description: "Absolute path to the file" },
40
- content: { type: "string", description: "Content to write" }
41
- },
42
- required: ["file_path", "content"]
43
- },
44
- async execute(params) {
45
- try {
46
- await mkdir(dirname(params.file_path), { recursive: true });
47
- await writeFile(params.file_path, params.content, "utf-8");
48
- return { output: `File written: ${params.file_path}` };
49
- } catch (e) {
50
- return { output: "", error: e.message };
51
- }
52
- }
53
- };
54
-
55
- // src/tools/edit_file.ts
56
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
57
- var editFileTool = {
58
- name: "edit_file",
59
- description: "Replace exact string match in a file. Fails if old_string not found or matches multiple times.",
60
- parameters: {
61
- type: "object",
62
- properties: {
63
- file_path: { type: "string", description: "Absolute path to the file" },
64
- old_string: { type: "string", description: "Exact string to replace" },
65
- new_string: { type: "string", description: "Replacement string" },
66
- replace_all: { type: "boolean", description: "Replace all occurrences (default false)" }
67
- },
68
- required: ["file_path", "old_string", "new_string"]
69
- },
70
- async execute(params) {
71
- try {
72
- const content = await readFile2(params.file_path, "utf-8");
73
- const oldStr = params.old_string;
74
- const newStr = params.new_string;
75
- const replaceAll = params.replace_all;
76
- if (!content.includes(oldStr)) {
77
- return { output: "", error: `old_string not found in ${params.file_path}` };
78
- }
79
- let result;
80
- if (replaceAll) {
81
- result = content.replaceAll(oldStr, newStr);
82
- } else {
83
- const count = content.split(oldStr).length - 1;
84
- if (count > 1) {
85
- return {
86
- output: "",
87
- error: `Found ${count} matches for old_string. Provide more context or set replace_all=true.`
88
- };
89
- }
90
- result = content.replace(oldStr, newStr);
91
- }
92
- await writeFile2(params.file_path, result, "utf-8");
93
- return { output: `Edited: ${params.file_path}` };
94
- } catch (e) {
95
- return { output: "", error: e.message };
96
- }
97
- }
98
- };
99
-
100
- // src/tools/bash.ts
101
- import { exec } from "child_process";
102
- var bashTool = {
103
- name: "bash",
104
- description: "Execute a shell command. Returns stdout and stderr.",
105
- parameters: {
106
- type: "object",
107
- properties: {
108
- command: { type: "string", description: "The command to execute" },
109
- timeout: { type: "number", description: "Timeout in ms (default 120000)" },
110
- workdir: { type: "string", description: "Working directory" }
111
- },
112
- required: ["command"]
113
- },
114
- async execute(params) {
115
- return new Promise((resolve) => {
116
- const timeout = params.timeout || 12e4;
117
- exec(
118
- params.command,
119
- {
120
- timeout,
121
- cwd: params.workdir || process.cwd(),
122
- encoding: "utf-8"
123
- },
124
- (error, stdout, stderr) => {
125
- if (error && error.killed) {
126
- resolve({
127
- output: stdout ?? "",
128
- error: `Command timed out after ${timeout}ms`
129
- });
130
- } else if (error && !stdout && !stderr) {
131
- resolve({ output: "", error: error.message });
132
- } else {
133
- resolve({ output: [stdout, stderr].filter(Boolean).join("\n").trim() });
134
- }
135
- }
136
- );
137
- });
138
- }
139
- };
140
-
141
- // src/tools/glob.ts
142
- import { glob } from "fs/promises";
143
- var globTool = {
144
- name: "glob",
145
- description: "Find files matching a glob pattern.",
146
- parameters: {
147
- type: "object",
148
- properties: {
149
- pattern: {
150
- type: "string",
151
- description: "Glob pattern (e.g. '**/*.ts', 'src/**/*.test.*')"
152
- },
153
- path: { type: "string", description: "Directory to search in (default: cwd)" }
154
- },
155
- required: ["pattern"]
156
- },
157
- async execute(params) {
158
- try {
159
- const files = [];
160
- for await (const f of glob(params.pattern, {
161
- cwd: params.path || process.cwd()
162
- })) {
163
- files.push(f);
164
- }
165
- files.sort();
166
- return { output: files.join("\n") || "No files found" };
167
- } catch (e) {
168
- return { output: "", error: e.message };
169
- }
170
- }
171
- };
172
-
173
- // src/tools/grep.ts
174
- import { readFile as readFile3, readdir } from "fs/promises";
175
- import { join } from "path";
176
- var grepTool = {
177
- name: "grep",
178
- description: "Search file contents using regex. Returns file paths and matching line numbers.",
179
- parameters: {
180
- type: "object",
181
- properties: {
182
- pattern: { type: "string", description: "Regex pattern to search for" },
183
- path: { type: "string", description: "Directory to search in (default: cwd)" },
184
- include: { type: "string", description: "File pattern to include (e.g. '*.ts')" }
185
- },
186
- required: ["pattern"]
187
- },
188
- async execute(params) {
189
- try {
190
- const regex = new RegExp(params.pattern, "gi");
191
- const rootDir = params.path || process.cwd();
192
- const includePattern = params.include;
193
- const results = [];
194
- async function searchDir(dir, depth) {
195
- if (depth > 10 || results.length >= 200) return;
196
- const entries = await readdir(dir, { withFileTypes: true });
197
- for (const entry of entries) {
198
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
199
- const fullPath = join(dir, entry.name);
200
- if (entry.isDirectory()) {
201
- await searchDir(fullPath, depth + 1);
202
- } else if (entry.isFile()) {
203
- if (includePattern && !matchGlob(entry.name, includePattern)) continue;
204
- try {
205
- const content = await readFile3(fullPath, "utf-8");
206
- const lines = content.split("\n");
207
- for (let i = 0; i < lines.length; i++) {
208
- if (regex.test(lines[i])) {
209
- results.push(`${fullPath}:${i + 1}: ${lines[i].trim()}`);
210
- if (results.length >= 200) return;
211
- }
212
- regex.lastIndex = 0;
213
- }
214
- } catch {
215
- }
216
- }
217
- }
218
- }
219
- await searchDir(rootDir, 0);
220
- return { output: results.join("\n") || "No matches found" };
221
- } catch (e) {
222
- return { output: "", error: e.message };
223
- }
224
- }
225
- };
226
- function matchGlob(filename, pattern) {
227
- const regexStr = "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
228
- return new RegExp(regexStr, "i").test(filename);
229
- }
230
-
231
- // src/datasources/akshare.ts
232
- import { exec as exec2 } from "child_process";
233
- import { promisify } from "util";
234
- import path from "path";
235
- var execAsync = promisify(exec2);
236
- var AkshareDataSource = class {
237
- scriptPath;
238
- constructor() {
239
- this.scriptPath = path.join(import.meta.dirname, "akshare_datasource.py");
240
- }
241
- async getStockInfo(code, market = "A") {
242
- const { stdout } = await execAsync(
243
- `python ${this.scriptPath} info ${code} ${market}`
244
- );
245
- const data = JSON.parse(stdout);
246
- if (data.error) throw new Error(data.error);
247
- return {
248
- code,
249
- name: data[0]?.name || code,
250
- market,
251
- date: data[0]?.date,
252
- open: data[0]?.open,
253
- close: data[0]?.close,
254
- high: data[0]?.high,
255
- low: data[0]?.low,
256
- volume: data[0]?.volume,
257
- amount: data[0]?.amount,
258
- change_pct: data[0]?.change_pct
259
- };
260
- }
261
- async getFinancialData(code, market = "A") {
262
- const { stdout } = await execAsync(
263
- `python ${this.scriptPath} financial ${code} ${market}`
264
- );
265
- const data = JSON.parse(stdout);
266
- if (data.error) throw new Error(data.error);
267
- return {
268
- code,
269
- price: data[0]?.price,
270
- change_pct: data[0]?.change_pct,
271
- volume: data[0]?.volume,
272
- amount: data[0]?.amount,
273
- turnover: data[0]?.turnover
274
- };
275
- }
276
- async getStockHistory(code, market = "A", days = 30) {
277
- const { stdout } = await execAsync(
278
- `python ${this.scriptPath} history ${code} ${market} ${days}`
279
- );
280
- const data = JSON.parse(stdout);
281
- if (data.error) throw new Error(data.error);
282
- return data.map((item) => ({
283
- date: item.date,
284
- open: item.open,
285
- close: item.close,
286
- high: item.high,
287
- low: item.low,
288
- volume: item.volume,
289
- change_pct: item.change_pct
290
- }));
291
- }
292
- };
293
-
294
- // src/workflow/state.ts
295
- function createInitialState(target) {
296
- return {
297
- target,
298
- data: {},
299
- analysis: {},
300
- output: {},
301
- status: "pending"
302
- };
303
- }
304
-
305
- // src/workflow/engine.ts
306
- var WorkflowEngine = class {
307
- nodes;
308
- onStateChange;
309
- constructor(config) {
310
- this.nodes = config.nodes;
311
- this.onStateChange = config.onStateChange;
312
- }
313
- async execute(target) {
314
- let state = createInitialState(target);
315
- for (const node of this.nodes) {
316
- state = await node(state);
317
- this.onStateChange?.(state);
318
- if (state.status === "error") {
319
- throw new Error(state.error);
320
- }
321
- }
322
- return state;
323
- }
324
- };
325
-
326
- // src/workflow/nodes/data-collection.ts
327
- async function dataCollectionNode(state, dataSource) {
328
- const { code, market } = state.target;
329
- try {
330
- const [stockInfo, financial, history] = await Promise.all([
331
- dataSource.getStockInfo(code, market),
332
- dataSource.getFinancialData(code, market),
333
- dataSource.getStockHistory(code, market, 30)
334
- ]);
335
- return {
336
- ...state,
337
- data: {
338
- stockInfo,
339
- financial,
340
- history
341
- },
342
- status: "collecting"
343
- };
344
- } catch (error) {
345
- return {
346
- ...state,
347
- status: "error",
348
- error: `\u6570\u636E\u91C7\u96C6\u5931\u8D25: ${error.message}`
349
- };
350
- }
351
- }
352
-
353
- // src/workflow/nodes/analysis.ts
354
- function calculateMA(data, period) {
355
- if (data.length < period) return void 0;
356
- const slice = data.slice(-period);
357
- return slice.reduce((a, b) => a + b, 0) / period;
358
- }
359
- function calculateRSI(prices, period = 14) {
360
- if (prices.length < period + 1) return void 0;
361
- let gains = 0;
362
- let losses = 0;
363
- for (let i = prices.length - period; i < prices.length; i++) {
364
- const change = prices[i] - prices[i - 1];
365
- if (change > 0) {
366
- gains += change;
367
- } else {
368
- losses -= change;
369
- }
370
- }
371
- const avgGain = gains / period;
372
- const avgLoss = losses / period;
373
- if (avgLoss === 0) return 100;
374
- const rs = avgGain / avgLoss;
375
- return 100 - 100 / (1 + rs);
376
- }
377
- async function analysisNode(state) {
378
- if (state.status === "error") return state;
379
- const { financial, history, stockInfo } = state.data;
380
- try {
381
- const prices = history?.map((h) => h.close) || [];
382
- const ma5 = calculateMA(prices, 5);
383
- const ma10 = calculateMA(prices, 10);
384
- const ma20 = calculateMA(prices, 20);
385
- const rsi = calculateRSI(prices);
386
- const financialAnalysis = {
387
- price: financial?.price,
388
- change_pct: financial?.change_pct,
389
- volume: financial?.volume,
390
- amount: financial?.amount,
391
- turnover: financial?.turnover
392
- };
393
- const trend = {
394
- ma5: ma5 ? Math.round(ma5 * 100) / 100 : void 0,
395
- ma10: ma10 ? Math.round(ma10 * 100) / 100 : void 0,
396
- ma20: ma20 ? Math.round(ma20 * 100) / 100 : void 0,
397
- rsi: rsi ? Math.round(rsi * 100) / 100 : void 0
398
- };
399
- let trendSignal = "\u4E2D\u6027";
400
- if (ma5 && ma10 && ma20) {
401
- if (ma5 > ma10 && ma10 > ma20) {
402
- trendSignal = "\u4E0A\u5347\u8D8B\u52BF";
403
- } else if (ma5 < ma10 && ma10 < ma20) {
404
- trendSignal = "\u4E0B\u964D\u8D8B\u52BF";
405
- }
406
- }
407
- return {
408
- ...state,
409
- analysis: {
410
- financial: financialAnalysis,
411
- trend,
412
- comparison: {
413
- industry: stockInfo?.industry || "\u672A\u77E5",
414
- trendSignal
415
- }
416
- },
417
- status: "analyzing"
418
- };
419
- } catch (error) {
420
- return {
421
- ...state,
422
- status: "error",
423
- error: `\u5206\u6790\u5931\u8D25: ${error.message}`
424
- };
425
- }
426
- }
427
-
428
- // src/workflow/nodes/report.ts
429
- async function reportNode(state) {
430
- if (state.status === "error") return state;
431
- const { target, analysis, data } = state;
432
- try {
433
- const report = `# ${target.company} (${target.code}) \u7814\u7A76\u62A5\u544A
434
-
435
- ## \u516C\u53F8\u6982\u51B5
436
- - **\u80A1\u7968\u4EE3\u7801**: ${target.code}
437
- - **\u5E02\u573A**: ${target.market}
438
- - **\u884C\u4E1A**: ${analysis.comparison?.industry || "\u672A\u77E5"}
439
-
440
- ## \u4EF7\u683C\u4FE1\u606F
441
- - **\u5F53\u524D\u4EF7\u683C**: \xA5${analysis.financial?.price || "N/A"}
442
- - **\u6DA8\u8DCC\u5E45**: ${analysis.financial?.change_pct || "N/A"}%
443
- - **\u6210\u4EA4\u91CF**: ${analysis.financial?.volume ? (analysis.financial.volume / 1e4).toFixed(2) + "\u4E07\u624B" : "N/A"}
444
- - **\u6210\u4EA4\u989D**: ${analysis.financial?.amount ? (analysis.financial.amount / 1e8).toFixed(2) + "\u4EBF" : "N/A"}
445
-
446
- ## \u6280\u672F\u5206\u6790
447
- - **5\u65E5\u5747\u7EBF**: \xA5${analysis.trend?.ma5 || "N/A"}
448
- - **10\u65E5\u5747\u7EBF**: \xA5${analysis.trend?.ma10 || "N/A"}
449
- - **20\u65E5\u5747\u7EBF**: \xA5${analysis.trend?.ma20 || "N/A"}
450
- - **RSI(14)**: ${analysis.trend?.rsi || "N/A"}
451
- - **\u8D8B\u52BF\u4FE1\u53F7**: ${analysis.comparison?.trendSignal || "\u4E2D\u6027"}
452
-
453
- ## \u8FD1\u671F\u8D70\u52BF
454
- ${data.history ? data.history.slice(-5).map(
455
- (h) => `- ${h.date}: \u5F00\u76D8 \xA5${h.open} | \u6536\u76D8 \xA5${h.close} | \u6DA8\u8DCC ${h.change_pct}%`
456
- ).join("\n") : "\u65E0\u6570\u636E"}
457
-
458
- ## \u6295\u8D44\u5EFA\u8BAE
459
- \u57FA\u4E8E\u6280\u672F\u5206\u6790\uFF0C${target.company} \u5F53\u524D\u5904\u4E8E **${analysis.comparison?.trendSignal || "\u4E2D\u6027"}**\u3002
460
-
461
- ${analysis.trend?.rsi && analysis.trend.rsi > 70 ? "\u26A0\uFE0F RSI \u8D85\u4E70\u533A\u57DF\uFF0C\u6CE8\u610F\u56DE\u8C03\u98CE\u9669" : ""}
462
- ${analysis.trend?.rsi && analysis.trend.rsi < 30 ? "\u2705 RSI \u8D85\u5356\u533A\u57DF\uFF0C\u53EF\u80FD\u5B58\u5728\u53CD\u5F39\u673A\u4F1A" : ""}
463
-
464
- ---
465
- *\u62A5\u544A\u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toISOString()}*
466
- *\u6570\u636E\u6765\u6E90: akshare (\u514D\u8D39\u6570\u636E)*
467
- `;
468
- return {
469
- ...state,
470
- output: {
471
- report,
472
- format: "markdown"
473
- },
474
- status: "done"
475
- };
476
- } catch (error) {
477
- return {
478
- ...state,
479
- status: "error",
480
- error: `\u62A5\u544A\u751F\u6210\u5931\u8D25: ${error.message}`
481
- };
482
- }
483
- }
484
-
485
- // src/tools/finance.ts
486
- var financeTool = {
487
- name: "finance_analyze",
488
- description: "\u5206\u6790\u4E0A\u5E02\u516C\u53F8\u8D22\u52A1\u6570\u636E\u5E76\u751F\u6210\u7814\u7A76\u62A5\u544A",
489
- parameters: {
490
- type: "object",
491
- properties: {
492
- company: {
493
- type: "string",
494
- description: "\u516C\u53F8\u540D\u79F0"
495
- },
496
- code: {
497
- type: "string",
498
- description: "\u80A1\u7968\u4EE3\u7801"
499
- },
500
- market: {
501
- type: "string",
502
- enum: ["A", "HK"],
503
- description: "\u5E02\u573A\u7C7B\u578B (A: A\u80A1, HK: \u6E2F\u80A1)"
504
- }
505
- },
506
- required: ["company", "code"]
507
- },
508
- execute: async (params) => {
509
- try {
510
- const dataSource = new AkshareDataSource();
511
- const engine = new WorkflowEngine({
512
- nodes: [
513
- (state) => dataCollectionNode(state, dataSource),
514
- analysisNode,
515
- reportNode
516
- ]
517
- });
518
- const result = await engine.execute({
519
- company: params.company,
520
- code: params.code,
521
- market: params.market || "A"
522
- });
523
- return {
524
- output: result.output.report || "\u5206\u6790\u5B8C\u6210"
525
- };
526
- } catch (error) {
527
- return {
528
- output: "",
529
- error: error.message
530
- };
531
- }
532
- }
533
- };
534
-
535
- // src/tools/index.ts
536
- var builtinTools = [
537
- readFileTool,
538
- writeFileTool,
539
- editFileTool,
540
- bashTool,
541
- globTool,
542
- grepTool,
543
- financeTool
544
- ];
545
-
546
- // src/core/orchestrator.ts
547
- var Orchestrator = class {
548
- messages = [];
549
- tools;
550
- model;
551
- systemPrompt;
552
- maxIterations;
553
- callbacks;
554
- constructor(opts) {
555
- this.model = opts.model;
556
- this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
557
- if (opts.skillInstructions) {
558
- this.systemPrompt += "\n\n" + opts.skillInstructions;
559
- }
560
- this.tools = opts.tools ?? builtinTools;
561
- this.maxIterations = opts.maxIterations ?? 20;
562
- this.callbacks = {
563
- onToken: opts.onToken,
564
- onToolStart: opts.onToolStart,
565
- onToolEnd: opts.onToolEnd
566
- };
567
- if (this.systemPrompt) {
568
- this.messages.push({ role: "system", content: this.systemPrompt });
569
- }
570
- }
571
- async run(userMessage) {
572
- this.messages.push({ role: "user", content: userMessage });
573
- for (let i = 0; i < this.maxIterations; i++) {
574
- const response = await this.model.chat(this.messages, this.tools);
575
- if (response.content) {
576
- this.messages.push({
577
- role: "assistant",
578
- content: response.content,
579
- tool_calls: response.tool_calls
580
- });
581
- }
582
- if (!response.tool_calls?.length) {
583
- return response.content;
584
- }
585
- for (const tc of response.tool_calls) {
586
- this.callbacks.onToolStart?.(
587
- tc.function.name,
588
- JSON.parse(tc.function.arguments)
589
- );
590
- const result = await this.executeTool(tc);
591
- this.callbacks.onToolEnd?.(tc.function.name, result);
592
- this.messages.push({
593
- role: "tool",
594
- content: result.error ? `Error: ${result.error}` : result.output,
595
- tool_call_id: tc.id
596
- });
597
- }
598
- }
599
- return "Max iterations reached.";
600
- }
601
- async *runStream(userMessage) {
602
- this.messages.push({ role: "user", content: userMessage });
603
- for (let i = 0; i < this.maxIterations; i++) {
604
- let fullContent = "";
605
- let toolCalls = [];
606
- for await (const chunk of this.model.stream(this.messages, this.tools)) {
607
- if (chunk.content) {
608
- fullContent += chunk.content;
609
- yield chunk.content;
610
- }
611
- if (chunk.tool_calls) {
612
- toolCalls = chunk.tool_calls;
613
- }
614
- }
615
- if (!toolCalls.length) {
616
- this.messages.push({ role: "assistant", content: fullContent });
617
- return;
618
- }
619
- this.messages.push({
620
- role: "assistant",
621
- content: fullContent,
622
- tool_calls: toolCalls
623
- });
624
- for (const tc of toolCalls) {
625
- this.callbacks.onToolStart?.(
626
- tc.function.name,
627
- JSON.parse(tc.function.arguments)
628
- );
629
- const result = await this.executeTool(tc);
630
- this.callbacks.onToolEnd?.(tc.function.name, result);
631
- this.messages.push({
632
- role: "tool",
633
- content: result.error ? `Error: ${result.error}` : result.output,
634
- tool_call_id: tc.id
635
- });
636
- }
637
- }
638
- }
639
- async executeTool(tc) {
640
- const tool = this.tools.find((t) => t.name === tc.function.name);
641
- if (!tool) return { output: "", error: `Unknown tool: ${tc.function.name}` };
642
- try {
643
- const params = JSON.parse(tc.function.arguments);
644
- return await tool.execute(params);
645
- } catch (e) {
646
- return { output: "", error: e.message };
647
- }
648
- }
649
- getMessages() {
650
- return [...this.messages];
651
- }
652
- clearMessages() {
653
- this.messages = [];
654
- if (this.systemPrompt) {
655
- this.messages.push({ role: "system", content: this.systemPrompt });
656
- }
657
- }
658
- };
659
- var DEFAULT_SYSTEM_PROMPT = `You are Hecode, an AI coding assistant. You help users with software engineering tasks using the tools available to you.
660
-
661
- Key principles:
662
- - Be concise and direct
663
- - Use tools to read/write files, execute commands, and search code
664
- - Verify your work by running tests or checks when possible
665
- - Follow existing code conventions in the project`;
666
-
667
- export {
668
- Orchestrator
669
- };
670
- //# sourceMappingURL=chunk-I4W33CWB.js.map