thatgfsj-code 0.3.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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/ROADMAP.md +61 -0
  4. package/dist/app/agent.d.ts +31 -0
  5. package/dist/app/agent.d.ts.map +1 -0
  6. package/dist/app/agent.js +106 -0
  7. package/dist/app/agent.js.map +1 -0
  8. package/dist/app/index.d.ts +39 -0
  9. package/dist/app/index.d.ts.map +1 -0
  10. package/dist/app/index.js +80 -0
  11. package/dist/app/index.js.map +1 -0
  12. package/dist/cmd/index.d.ts +12 -0
  13. package/dist/cmd/index.d.ts.map +1 -0
  14. package/dist/cmd/index.js +85 -0
  15. package/dist/cmd/index.js.map +1 -0
  16. package/dist/config/index.d.ts +39 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +110 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/providers.d.ts +23 -0
  21. package/dist/config/providers.d.ts.map +1 -0
  22. package/dist/config/providers.js +152 -0
  23. package/dist/config/providers.js.map +1 -0
  24. package/dist/config/types.d.ts +33 -0
  25. package/dist/config/types.d.ts.map +1 -0
  26. package/dist/config/types.js +5 -0
  27. package/dist/config/types.js.map +1 -0
  28. package/dist/hooks/index.d.ts +31 -0
  29. package/dist/hooks/index.d.ts.map +1 -0
  30. package/dist/hooks/index.js +71 -0
  31. package/dist/hooks/index.js.map +1 -0
  32. package/dist/llm/anthropic.d.ts +17 -0
  33. package/dist/llm/anthropic.d.ts.map +1 -0
  34. package/dist/llm/anthropic.js +126 -0
  35. package/dist/llm/anthropic.js.map +1 -0
  36. package/dist/llm/gemini.d.ts +16 -0
  37. package/dist/llm/gemini.d.ts.map +1 -0
  38. package/dist/llm/gemini.js +107 -0
  39. package/dist/llm/gemini.js.map +1 -0
  40. package/dist/llm/index.d.ts +48 -0
  41. package/dist/llm/index.d.ts.map +1 -0
  42. package/dist/llm/index.js +174 -0
  43. package/dist/llm/index.js.map +1 -0
  44. package/dist/llm/openai.d.ts +17 -0
  45. package/dist/llm/openai.d.ts.map +1 -0
  46. package/dist/llm/openai.js +113 -0
  47. package/dist/llm/openai.js.map +1 -0
  48. package/dist/llm/provider.d.ts +33 -0
  49. package/dist/llm/provider.d.ts.map +1 -0
  50. package/dist/llm/provider.js +6 -0
  51. package/dist/llm/provider.js.map +1 -0
  52. package/dist/mcp/client.d.ts +83 -0
  53. package/dist/mcp/client.d.ts.map +1 -0
  54. package/dist/mcp/client.js +267 -0
  55. package/dist/mcp/client.js.map +1 -0
  56. package/dist/prompts/index.d.ts +27 -0
  57. package/dist/prompts/index.d.ts.map +1 -0
  58. package/dist/prompts/index.js +102 -0
  59. package/dist/prompts/index.js.map +1 -0
  60. package/dist/session/compactor.d.ts +48 -0
  61. package/dist/session/compactor.d.ts.map +1 -0
  62. package/dist/session/compactor.js +77 -0
  63. package/dist/session/compactor.js.map +1 -0
  64. package/dist/session/index.d.ts +48 -0
  65. package/dist/session/index.d.ts.map +1 -0
  66. package/dist/session/index.js +69 -0
  67. package/dist/session/index.js.map +1 -0
  68. package/dist/session/message.d.ts +26 -0
  69. package/dist/session/message.d.ts.map +1 -0
  70. package/dist/session/message.js +34 -0
  71. package/dist/session/message.js.map +1 -0
  72. package/dist/tools/file.d.ts +46 -0
  73. package/dist/tools/file.d.ts.map +1 -0
  74. package/dist/tools/file.js +100 -0
  75. package/dist/tools/file.js.map +1 -0
  76. package/dist/tools/git.d.ts +52 -0
  77. package/dist/tools/git.d.ts.map +1 -0
  78. package/dist/tools/git.js +112 -0
  79. package/dist/tools/git.js.map +1 -0
  80. package/dist/tools/index.d.ts +42 -0
  81. package/dist/tools/index.d.ts.map +1 -0
  82. package/dist/tools/index.js +92 -0
  83. package/dist/tools/index.js.map +1 -0
  84. package/dist/tools/search.d.ts +36 -0
  85. package/dist/tools/search.d.ts.map +1 -0
  86. package/dist/tools/search.js +223 -0
  87. package/dist/tools/search.js.map +1 -0
  88. package/dist/tools/shell.d.ts +50 -0
  89. package/dist/tools/shell.d.ts.map +1 -0
  90. package/dist/tools/shell.js +117 -0
  91. package/dist/tools/shell.js.map +1 -0
  92. package/dist/tools/types.d.ts +68 -0
  93. package/dist/tools/types.d.ts.map +1 -0
  94. package/dist/tools/types.js +57 -0
  95. package/dist/tools/types.js.map +1 -0
  96. package/dist/tui/index.d.ts +8 -0
  97. package/dist/tui/index.d.ts.map +1 -0
  98. package/dist/tui/index.js +8 -0
  99. package/dist/tui/index.js.map +1 -0
  100. package/dist/tui/input.d.ts +14 -0
  101. package/dist/tui/input.d.ts.map +1 -0
  102. package/dist/tui/input.js +50 -0
  103. package/dist/tui/input.js.map +1 -0
  104. package/dist/tui/output.d.ts +20 -0
  105. package/dist/tui/output.d.ts.map +1 -0
  106. package/dist/tui/output.js +70 -0
  107. package/dist/tui/output.js.map +1 -0
  108. package/dist/tui/repl.d.ts +25 -0
  109. package/dist/tui/repl.d.ts.map +1 -0
  110. package/dist/tui/repl.js +114 -0
  111. package/dist/tui/repl.js.map +1 -0
  112. package/dist/tui/welcome.d.ts +15 -0
  113. package/dist/tui/welcome.d.ts.map +1 -0
  114. package/dist/tui/welcome.js +86 -0
  115. package/dist/tui/welcome.js.map +1 -0
  116. package/dist/types.d.ts +38 -0
  117. package/dist/types.d.ts.map +1 -0
  118. package/dist/types.js +6 -0
  119. package/dist/types.js.map +1 -0
  120. package/dist/utils/diff.d.ts +22 -0
  121. package/dist/utils/diff.d.ts.map +1 -0
  122. package/dist/utils/diff.js +60 -0
  123. package/dist/utils/diff.js.map +1 -0
  124. package/dist/utils/project.d.ts +32 -0
  125. package/dist/utils/project.d.ts.map +1 -0
  126. package/dist/utils/project.js +89 -0
  127. package/dist/utils/project.js.map +1 -0
  128. package/docs/API_KEY_GUIDE.md +236 -0
  129. package/docs/FAQ.md +182 -0
  130. package/install.bat +63 -0
  131. package/install.ps1 +238 -0
  132. package/install.sh +113 -0
  133. package/package.json +36 -0
  134. package/src/app/agent.ts +140 -0
  135. package/src/app/index.ts +101 -0
  136. package/src/cmd/index.ts +96 -0
  137. package/src/config/index.ts +130 -0
  138. package/src/config/providers.ts +160 -0
  139. package/src/config/types.ts +46 -0
  140. package/src/hooks/index.ts +111 -0
  141. package/src/llm/anthropic.ts +146 -0
  142. package/src/llm/gemini.ts +127 -0
  143. package/src/llm/index.ts +209 -0
  144. package/src/llm/openai.ts +132 -0
  145. package/src/llm/provider.ts +38 -0
  146. package/src/mcp/client.ts +330 -0
  147. package/src/prompts/index.ts +123 -0
  148. package/src/session/compactor.ts +103 -0
  149. package/src/session/index.ts +81 -0
  150. package/src/session/message.ts +42 -0
  151. package/src/tools/file.ts +117 -0
  152. package/src/tools/git.ts +132 -0
  153. package/src/tools/index.ts +108 -0
  154. package/src/tools/search.ts +263 -0
  155. package/src/tools/shell.ts +136 -0
  156. package/src/tools/types.ts +122 -0
  157. package/src/tui/index.ts +8 -0
  158. package/src/tui/input.ts +56 -0
  159. package/src/tui/output.ts +83 -0
  160. package/src/tui/repl.ts +131 -0
  161. package/src/tui/welcome.ts +101 -0
  162. package/src/types.ts +42 -0
  163. package/src/utils/diff.ts +71 -0
  164. package/src/utils/project.ts +99 -0
  165. package/tsconfig.json +19 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * REPL Output Handler
3
+ * Migrated from old src/repl/output.ts
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import ora, { Ora } from 'ora';
8
+
9
+ export class REPLOutput {
10
+ private spinner: Ora | null = null;
11
+
12
+ printError(error: string): void {
13
+ console.error(chalk.red(`\n❌ Error: ${error}`));
14
+ }
15
+
16
+ printWarning(warning: string): void {
17
+ console.warn(chalk.yellow(`\n⚠️ ${warning}`));
18
+ }
19
+
20
+ printSuccess(message: string): void {
21
+ console.log(chalk.green(`\n✅ ${message}`));
22
+ }
23
+
24
+ printInfo(message: string): void {
25
+ console.log(chalk.gray(message));
26
+ }
27
+
28
+ printDivider(char = '─', length = 40): void {
29
+ console.log(chalk.gray(char.repeat(length)));
30
+ }
31
+
32
+ printHeader(text: string): void {
33
+ this.printDivider();
34
+ console.log(chalk.cyan(text));
35
+ this.printDivider();
36
+ }
37
+
38
+ startSpinner(text: string): void {
39
+ this.spinner = ora({ text, color: 'cyan', spinner: 'dots' }).start();
40
+ }
41
+
42
+ stopSpinner(): void {
43
+ if (this.spinner) {
44
+ this.spinner.stop();
45
+ this.spinner = null;
46
+ }
47
+ }
48
+
49
+ stopSpinnerFail(text?: string): void {
50
+ if (this.spinner) {
51
+ this.spinner.fail(text || this.spinner.text);
52
+ this.spinner = null;
53
+ }
54
+ }
55
+
56
+ clear(): void {
57
+ console.clear();
58
+ }
59
+
60
+ printBanner(): void {
61
+ console.log(chalk.cyan(`
62
+ ╔═══════════════════════════════════════╗
63
+ ║ 🤖 Thatgfsj Code v0.3.0 ║
64
+ ║ AI Coding Assistant ║
65
+ ╚═══════════════════════════════════════╝
66
+ `));
67
+ }
68
+
69
+ printHelp(): void {
70
+ console.log(`
71
+ ${chalk.cyan('Commands:')}
72
+ ${chalk.gray('exit, Ctrl+C')} - Exit
73
+ ${chalk.gray('clear')} - Clear screen
74
+ ${chalk.gray('help')} - Show this help
75
+ ${chalk.gray('tools')} - List available tools
76
+ ${chalk.gray('model')} - Show current model
77
+
78
+ ${chalk.cyan('Tips:')}
79
+ • Use ${chalk.gray('↑/↓')} for command history
80
+ • Use ${chalk.gray('Tab')} for auto-complete
81
+ `);
82
+ }
83
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * REPL - Main interactive loop
3
+ * Migrated from old src/repl/loop.ts + src/index.ts interactive mode
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import { REPLInput } from './input.js';
8
+ import { REPLOutput } from './output.js';
9
+ import { App } from '../app/index.js';
10
+ import { ProjectContext } from '../utils/project.js';
11
+
12
+ export class REPL {
13
+ private input: REPLInput;
14
+ private output: REPLOutput;
15
+ private app: App;
16
+ private running = false;
17
+
18
+ constructor(app: App) {
19
+ this.input = new REPLInput();
20
+ this.output = new REPLOutput();
21
+ this.app = app;
22
+ }
23
+
24
+ /**
25
+ * Start the REPL
26
+ */
27
+ async start(): Promise<void> {
28
+ this.running = true;
29
+
30
+ // Show banner
31
+ this.output.clear();
32
+ this.output.printBanner();
33
+ this.output.printInfo('Type "help" for available commands\n');
34
+ this.output.printDivider();
35
+
36
+ // Show project context
37
+ console.log(chalk.gray(ProjectContext.getSummary()));
38
+ this.output.printDivider();
39
+ console.log();
40
+
41
+ // Main loop
42
+ while (this.running) {
43
+ try {
44
+ const userInput = await this.input.prompt();
45
+
46
+ if (!userInput.trim()) continue;
47
+
48
+ // Handle commands
49
+ const handled = await this.handleCommand(userInput.trim());
50
+ if (handled) continue;
51
+
52
+ // Process with AI
53
+ await this.processInput(userInput.trim());
54
+ } catch (error: any) {
55
+ if (error.message === 'SIGINT') continue;
56
+ this.output.printError(error.message);
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Handle built-in commands
63
+ */
64
+ private async handleCommand(input: string): Promise<boolean> {
65
+ const cmd = input.toLowerCase();
66
+
67
+ switch (cmd) {
68
+ case 'exit':
69
+ case 'quit':
70
+ this.output.printInfo('\n👋 Goodbye!');
71
+ this.running = false;
72
+ this.input.close();
73
+ return true;
74
+
75
+ case 'clear':
76
+ this.output.clear();
77
+ this.output.printBanner();
78
+ return true;
79
+
80
+ case 'help':
81
+ this.output.printHelp();
82
+ return true;
83
+
84
+ case 'tools':
85
+ this.output.printHeader('🔧 Available Tools');
86
+ for (const tool of this.app.tools.list()) {
87
+ this.output.printInfo(` ${tool.name} - ${tool.description}`);
88
+ }
89
+ console.log();
90
+ return true;
91
+
92
+ case 'model':
93
+ this.output.printHeader('🤖 Current Model');
94
+ const cfg = this.app.config.get();
95
+ this.output.printInfo(` Provider: ${cfg.provider}`);
96
+ this.output.printInfo(` Model: ${cfg.model}`);
97
+ this.output.printInfo(` Base URL: ${cfg.baseUrl || 'default'}`);
98
+ console.log();
99
+ return true;
100
+
101
+ default:
102
+ return false;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Process user input with AI
108
+ */
109
+ private async processInput(input: string): Promise<void> {
110
+ this.app.session.addMessage('user', input);
111
+
112
+ this.output.startSpinner('Thinking...');
113
+ let fullResponse = '';
114
+
115
+ try {
116
+ const stream = this.app.getAgent().run(this.app.session.getMessages());
117
+ for await (const chunk of stream) {
118
+ this.output.stopSpinner();
119
+ process.stdout.write(chunk);
120
+ fullResponse += chunk;
121
+ }
122
+
123
+ console.log(); // newline
124
+
125
+ this.app.session.addMessage('assistant', fullResponse);
126
+ this.app.session.truncate();
127
+ } catch (error: any) {
128
+ this.output.stopSpinnerFail(chalk.red(`Error: ${error.message}`));
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Welcome / Setup Wizard
3
+ * Migrated and simplified from old src/repl/welcome.ts
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import readline from 'readline';
8
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir } from 'os';
11
+ import { PROVIDERS, getModelsForProvider, listProviders } from '../config/providers.js';
12
+ import type { ProviderName } from '../config/types.js';
13
+
14
+ export class WelcomeScreen {
15
+ /**
16
+ * Show welcome if no API key is configured
17
+ */
18
+ static show(hasApiKey: boolean): void {
19
+ if (hasApiKey) return;
20
+
21
+ const w = 62;
22
+ console.log(chalk.cyan('+') + chalk.white.bold(' Thatgfsj Code ') + chalk.cyan('-'.repeat(w - 16)) + '+');
23
+ console.log(chalk.cyan('|') + chalk.yellow(' 快速开始指南').padEnd(w) + chalk.cyan('|'));
24
+ console.log(chalk.cyan('|') + ' '.repeat(w) + chalk.cyan('|'));
25
+ console.log(chalk.cyan('|') + chalk.gray(' 运行 gfcode init 配置你的 API Key').padEnd(w) + chalk.cyan('|'));
26
+ console.log(chalk.cyan('|') + chalk.gray(' 或设置环境变量: OPENAI_API_KEY, SILICONFLOW_API_KEY 等').padEnd(w) + chalk.cyan('|'));
27
+ console.log(chalk.cyan('+') + '-'.repeat(w) + '+');
28
+ console.log();
29
+ }
30
+
31
+ /**
32
+ * Interactive setup wizard
33
+ */
34
+ static async interactiveSetup(): Promise<void> {
35
+ console.clear();
36
+ console.log(chalk.cyan('\n 🤖 Thatgfsj Code 配置向导\n'));
37
+
38
+ const rl = readline.createInterface({
39
+ input: process.stdin,
40
+ output: process.stdout,
41
+ });
42
+
43
+ const question = (prompt: string): Promise<string> =>
44
+ new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
45
+
46
+ try {
47
+ // Step 1: Choose provider
48
+ console.log(chalk.white(' 步骤 1/3: 选择 AI 提供商\n'));
49
+ const providers = listProviders();
50
+ providers.forEach((p, i) => {
51
+ console.log(chalk.green(` ${i + 1}. ${p.name}`));
52
+ });
53
+ console.log();
54
+
55
+ const choice = await question(chalk.green(' 请选择 (1-' + providers.length + '): '));
56
+ const selected = providers[parseInt(choice) - 1] || providers[0];
57
+ const providerName = selected.key;
58
+
59
+ // Step 2: Enter API Key
60
+ console.log(chalk.white('\n 步骤 2/3: 输入 API Key\n'));
61
+ console.log(chalk.gray(` 获取地址: ${PROVIDERS[providerName].baseUrl.replace('/v1', '').replace('/v1beta', '')}`));
62
+ const apiKey = await question(chalk.green(' 请输入 API Key: '));
63
+
64
+ // Step 3: Choose model
65
+ console.log(chalk.white('\n 步骤 3/3: 选择模型\n'));
66
+ const models = getModelsForProvider(providerName);
67
+ models.forEach((m, i) => {
68
+ console.log(chalk.green(` ${i + 1}. ${m.name}`) + chalk.gray(` - ${m.desc}`));
69
+ });
70
+ console.log();
71
+
72
+ const modelChoice = await question(chalk.green(' 请选择模型 (1-' + models.length + '): '));
73
+ const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
74
+
75
+ // Save config
76
+ const configDir = join(homedir(), '.thatgfsj');
77
+ const configPath = join(configDir, 'config.json');
78
+
79
+ if (!existsSync(configDir)) {
80
+ mkdirSync(configDir, { recursive: true });
81
+ }
82
+
83
+ writeFileSync(configPath, JSON.stringify({
84
+ provider: providerName,
85
+ model: selectedModel.id,
86
+ apiKey,
87
+ temperature: 0.7,
88
+ maxTokens: 4096,
89
+ }, null, 2));
90
+
91
+ console.log(chalk.green('\n ✅ 配置已保存!'));
92
+ console.log(chalk.gray(` 提供商: ${providerName}`));
93
+ console.log(chalk.gray(` 模型: ${selectedModel.name}`));
94
+ console.log(chalk.gray(` 配置文件: ${configPath}`));
95
+ console.log(chalk.cyan('\n 运行 gfcode 开始使用\n'));
96
+
97
+ } finally {
98
+ rl.close();
99
+ }
100
+ }
101
+ }
package/src/types.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Global shared types for Thatgfsj Code
3
+ * Only the most fundamental types used across modules
4
+ */
5
+
6
+ export type Role = 'system' | 'user' | 'assistant' | 'tool';
7
+
8
+ export interface ChatMessage {
9
+ role: Role;
10
+ content: string;
11
+ name?: string;
12
+ tool_calls?: ToolCall[];
13
+ tool_call_id?: string;
14
+ reasoning_content?: string;
15
+ }
16
+
17
+ export interface ToolCall {
18
+ id: string;
19
+ type: 'function';
20
+ function: {
21
+ name: string;
22
+ arguments: string;
23
+ };
24
+ }
25
+
26
+ export interface ChatResponse {
27
+ content: string;
28
+ role: 'assistant';
29
+ usage?: {
30
+ prompt_tokens: number;
31
+ completion_tokens: number;
32
+ total_tokens: number;
33
+ };
34
+ tool_calls?: ToolCall[];
35
+ reasoning_content?: string;
36
+ }
37
+
38
+ export interface ChatOptions {
39
+ temperature?: number;
40
+ maxTokens?: number;
41
+ stream?: boolean;
42
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Diff Preview - Show file changes in a readable format
3
+ * Migrated from old src/utils/diff-preview.ts
4
+ */
5
+
6
+ export interface DiffResult {
7
+ original: string;
8
+ modified: string;
9
+ diff: string;
10
+ added: number;
11
+ removed: number;
12
+ }
13
+
14
+ export class DiffPreview {
15
+ /**
16
+ * Generate a simple line-by-line diff
17
+ */
18
+ static diff(original: string, modified: string): DiffResult {
19
+ const originalLines = original.split('\n');
20
+ const modifiedLines = modified.split('\n');
21
+ const diffLines: string[] = [];
22
+ let added = 0;
23
+ let removed = 0;
24
+
25
+ // Simple diff: show removed (-) and added (+) lines
26
+ const maxLen = Math.max(originalLines.length, modifiedLines.length);
27
+
28
+ for (let i = 0; i < maxLen; i++) {
29
+ const orig = originalLines[i];
30
+ const mod = modifiedLines[i];
31
+
32
+ if (orig === mod) {
33
+ diffLines.push(` ${orig}`);
34
+ } else {
35
+ if (orig !== undefined) {
36
+ diffLines.push(`- ${orig}`);
37
+ removed++;
38
+ }
39
+ if (mod !== undefined) {
40
+ diffLines.push(`+ ${mod}`);
41
+ added++;
42
+ }
43
+ }
44
+ }
45
+
46
+ return {
47
+ original,
48
+ modified,
49
+ diff: diffLines.join('\n'),
50
+ added,
51
+ removed,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Format diff for display
57
+ */
58
+ static format(result: DiffResult): string {
59
+ const lines = result.diff.split('\n').map(line => {
60
+ if (line.startsWith('+')) return `\x1b[32m${line}\x1b[0m`;
61
+ if (line.startsWith('-')) return `\x1b[31m${line}\x1b[0m`;
62
+ return line;
63
+ });
64
+
65
+ return [
66
+ `\x1b[36m--- Changes: -${result.removed} / +${result.added} ---\x1b[0m`,
67
+ ...lines,
68
+ `\x1b[36m--- End ---\x1b[0m`,
69
+ ].join('\n');
70
+ }
71
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Project Context - Detect project type and gather info
3
+ * Migrated from old src/utils/project-context.ts + getProjectContext() from index.ts
4
+ */
5
+
6
+ import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ export interface ProjectInfo {
10
+ type: string;
11
+ name: string;
12
+ path: string;
13
+ fileCount: number;
14
+ dependencies: number;
15
+ }
16
+
17
+ export interface PackageJson {
18
+ name?: string;
19
+ version?: string;
20
+ dependencies?: Record<string, string>;
21
+ devDependencies?: Record<string, string>;
22
+ }
23
+
24
+ export class ProjectContext {
25
+ /**
26
+ * Get project context string for display
27
+ */
28
+ static getSummary(cwd: string = process.cwd()): string {
29
+ const info: string[] = [];
30
+
31
+ try {
32
+ const pkgPath = join(cwd, 'package.json');
33
+ if (existsSync(pkgPath)) {
34
+ const pkg: PackageJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
35
+ info.push(`📦 Project: ${cwd}`);
36
+ info.push(` Name: ${pkg.name || 'unknown'}`);
37
+ const depCount = Object.keys(pkg.dependencies || {}).length;
38
+ if (depCount > 0) info.push(` Deps: ${depCount} packages`);
39
+ } else {
40
+ info.push(`📁 Working dir: ${cwd}`);
41
+ }
42
+
43
+ const fileCount = ProjectContext.countCodeFiles(cwd);
44
+ if (fileCount > 0) {
45
+ info.push(` Files: ${fileCount} code files`);
46
+ }
47
+ } catch {
48
+ info.push(`📁 Working dir: ${cwd}`);
49
+ }
50
+
51
+ return info.join('\n');
52
+ }
53
+
54
+ /**
55
+ * Count code files in directory (skipping node_modules, .git, etc.)
56
+ */
57
+ static countCodeFiles(dir: string, maxDepth = 3): number {
58
+ let count = 0;
59
+ const codeExts = /\.(ts|js|py|go|rs|java|cpp|c|h|tsx|jsx|vue|svelte)$/;
60
+
61
+ const walk = (d: string, depth: number) => {
62
+ if (depth > maxDepth) return;
63
+ try {
64
+ const items = readdirSync(d);
65
+ for (const item of items) {
66
+ if (['node_modules', '.git', 'dist', 'build', '__pycache__'].includes(item)) continue;
67
+ const fullPath = join(d, item);
68
+ try {
69
+ const stat = statSync(fullPath);
70
+ if (stat.isDirectory()) {
71
+ walk(fullPath, depth + 1);
72
+ } else if (codeExts.test(item)) {
73
+ count++;
74
+ }
75
+ } catch {
76
+ // Skip inaccessible files
77
+ }
78
+ }
79
+ } catch {
80
+ // Skip inaccessible directories
81
+ }
82
+ };
83
+
84
+ walk(dir, 0);
85
+ return count;
86
+ }
87
+
88
+ /**
89
+ * Detect project type from files
90
+ */
91
+ static detectType(cwd: string = process.cwd()): string {
92
+ if (existsSync(join(cwd, 'package.json'))) return 'node';
93
+ if (existsSync(join(cwd, 'Cargo.toml'))) return 'rust';
94
+ if (existsSync(join(cwd, 'go.mod'))) return 'go';
95
+ if (existsSync(join(cwd, 'pyproject.toml')) || existsSync(join(cwd, 'requirements.txt'))) return 'python';
96
+ if (existsSync(join(cwd, 'build.gradle'))) return 'java';
97
+ return 'unknown';
98
+ }
99
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }