agent-termo 0.0.0-stage → 1.0.1

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 (56) hide show
  1. package/.claude/agents/backend-architecture-designer.md +1250 -0
  2. package/.claude/agents/backend-expert.md +1731 -0
  3. package/.claude/agents/deployment-expert.md +1126 -0
  4. package/.claude/agents/dev-environment-initiator.md +788 -0
  5. package/.claude/agents/frontend-expert.md +1343 -0
  6. package/.claude/agents/frontend-planner.md +1044 -0
  7. package/.claude/agents/git-manager.md +811 -0
  8. package/.claude/agents/project-manager.md +843 -0
  9. package/.claude/agents/security-expert.md +835 -0
  10. package/.claude/agents/tech-stack-expert.md +1357 -0
  11. package/.claude/agents/test-agent.md +1372 -0
  12. package/.claude/agents/uiux-designer.md +955 -0
  13. package/.claude/commands/build.md +5 -0
  14. package/.claude/commands/deploy.md +3 -0
  15. package/.claude/commands/feature.md +3 -0
  16. package/.claude/commands/fix.md +3 -0
  17. package/.claude/skills/pdf-reader/.skillfish.json +10 -0
  18. package/.claude/skills/pdf-reader/SKILL.md +94 -0
  19. package/.claude/skills/pdf-reader/pdf-reader +203 -0
  20. package/README.md +163 -3
  21. package/dist/.claude/agents/backend-architecture-designer.md +1250 -0
  22. package/dist/.claude/agents/backend-expert.md +1731 -0
  23. package/dist/.claude/agents/deployment-expert.md +1126 -0
  24. package/dist/.claude/agents/dev-environment-initiator.md +788 -0
  25. package/dist/.claude/agents/frontend-expert.md +1343 -0
  26. package/dist/.claude/agents/frontend-planner.md +1044 -0
  27. package/dist/.claude/agents/git-manager.md +811 -0
  28. package/dist/.claude/agents/project-manager.md +843 -0
  29. package/dist/.claude/agents/security-expert.md +835 -0
  30. package/dist/.claude/agents/tech-stack-expert.md +1357 -0
  31. package/dist/.claude/agents/test-agent.md +1372 -0
  32. package/dist/.claude/agents/uiux-designer.md +955 -0
  33. package/dist/.claude/commands/build.md +5 -0
  34. package/dist/.claude/commands/deploy.md +3 -0
  35. package/dist/.claude/commands/feature.md +3 -0
  36. package/dist/.claude/commands/fix.md +3 -0
  37. package/dist/.claude/skills/pdf-reader/.skillfish.json +10 -0
  38. package/dist/.claude/skills/pdf-reader/SKILL.md +94 -0
  39. package/dist/.claude/skills/pdf-reader/pdf-reader +203 -0
  40. package/dist/cli.js +210 -0
  41. package/dist/cli.js.map +7 -0
  42. package/dist/main.js +173 -0
  43. package/dist/main.js.map +7 -0
  44. package/package.json +90 -6
  45. package/src/agents/index.ts +94 -0
  46. package/src/cli.ts +155 -0
  47. package/src/diff/index.ts +255 -0
  48. package/src/llm/client.ts +192 -0
  49. package/src/llm/tools.ts +206 -0
  50. package/src/main.ts +427 -0
  51. package/src/memory/index.ts +130 -0
  52. package/src/tools/index.ts +371 -0
  53. package/src/types/index.ts +151 -0
  54. package/src/ui/index.ts +519 -0
  55. package/src/utils/ansi.ts +134 -0
  56. package/src/utils/config.ts +118 -0
@@ -0,0 +1,192 @@
1
+ // =============================================================================
2
+ // Nvidia NIM LLM Client — Stepfun + OpenAI-compatible models
3
+ // =============================================================================
4
+
5
+ import OpenAI from 'openai';
6
+ import type { ToolCall, ConversationMessage, ToolDef } from '../types/index.js';
7
+
8
+ type NIMTool = OpenAI.Chat.ChatCompletionTool;
9
+
10
+ export class NimClient {
11
+ private client: OpenAI;
12
+ private model: string;
13
+
14
+ constructor(_projectRoot?: string) {
15
+ const apiKey = process.env.NIM_API_KEY ?? '';
16
+ const baseUrl = process.env.NIM_BASE_URL ?? 'https://integrate.api.nvidia.com/v1';
17
+ this.model = process.env.NIM_MODEL ?? 'stepfun-ai/step-3.7-flash';
18
+ const timeout = parseInt(process.env.LLM_TIMEOUT ?? '120000', 10);
19
+
20
+ this.client = new OpenAI({
21
+ apiKey,
22
+ baseURL: baseUrl,
23
+ timeout,
24
+ defaultHeaders: { 'User-Agent': 'agent-termo/1.0' },
25
+ });
26
+ }
27
+
28
+ buildToolDefs(toolSchemas: ToolDef[]): NIMTool[] {
29
+ return toolSchemas.map((t) => ({
30
+ type: 'function' as const,
31
+ function: {
32
+ name: t.name,
33
+ description: t.description,
34
+ parameters: t.parameters as unknown as NIMTool['function']['parameters'],
35
+ },
36
+ })) as NIMTool[];
37
+ }
38
+
39
+ getModel(): string {
40
+ return this.model;
41
+ }
42
+
43
+ /**
44
+ * Streaming chat completion.
45
+ * Handles Stepfun's quirks: filters "null" reasoning chunks, strips reasoning field.
46
+ * Yields { content, toolCalls } at the end with clean text.
47
+ */
48
+ async *streamComplete(
49
+ messages: ConversationMessage[],
50
+ tools?: NIMTool[],
51
+ onChunk?: (text: string) => void,
52
+ ): AsyncGenerator<{ content: string; toolCalls: ToolCall[] }, void, unknown> {
53
+ const rawChunks: string[] = [];
54
+ const toolCallMap: Map<number, { id: string; name: string; arguments: string }> = new Map();
55
+
56
+ const stream = await this.client.chat.completions.create({
57
+ model: this.model,
58
+ messages: messages.map((m) => {
59
+ if (m.role === 'tool') {
60
+ return {
61
+ role: 'tool' as const,
62
+ content: m.content,
63
+ tool_call_id: m.id,
64
+ };
65
+ }
66
+ return {
67
+ role: m.role as 'system' | 'user' | 'assistant',
68
+ content: m.content,
69
+ };
70
+ }),
71
+ tools: tools,
72
+ temperature: 0.3,
73
+ max_tokens: 8192,
74
+ stream: true,
75
+ });
76
+
77
+ for await (const chunk of stream) {
78
+ const delta = chunk.choices[0]?.delta;
79
+
80
+ if (!delta) continue;
81
+
82
+ // Text delta — filter out "null" chunks (Stepfun's reasoning output)
83
+ const rawContent = delta.content ?? '';
84
+ // Check: if it's the literal string "null", it's reasoning noise — skip it
85
+ const isNoise = rawContent === 'null' || rawContent === 'undefined';
86
+ if (!isNoise && rawContent) {
87
+ rawChunks.push(rawContent);
88
+ onChunk?.(rawContent);
89
+ }
90
+
91
+ // Tool call deltas (accumulate across chunks via index)
92
+ if (delta.tool_calls) {
93
+ for (const tc of delta.tool_calls) {
94
+ const idx = tc.index ?? 0;
95
+ const prev = toolCallMap.get(idx);
96
+ if (!prev) {
97
+ toolCallMap.set(idx, {
98
+ id: tc.id ?? crypto.randomUUID(),
99
+ name: tc.function?.name ?? '',
100
+ arguments: tc.function?.arguments ?? '',
101
+ });
102
+ } else {
103
+ prev.arguments += tc.function?.arguments ?? '';
104
+ if (tc.id && !prev.id) prev.id = tc.id;
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ // For streaming: content is accumulated in rawChunks (null chunks already filtered)
111
+ // For non-streaming (complete() method): reasoning stripping happens in that method
112
+ const finalContent = rawChunks.join('');
113
+
114
+ // Extract tool calls
115
+ const toolCalls: ToolCall[] = Array.from(toolCallMap.values()).map((tc) => {
116
+ let args: Record<string, unknown> = {};
117
+ try {
118
+ args = JSON.parse(tc.arguments);
119
+ } catch {
120
+ args = { _raw: tc.arguments };
121
+ }
122
+ return { id: tc.id, name: tc.name, arguments: args };
123
+ });
124
+
125
+ yield { content: finalContent, toolCalls };
126
+ }
127
+
128
+ /**
129
+ * Non-streaming completion — for when streaming fails or for retries.
130
+ */
131
+ async complete(
132
+ messages: ConversationMessage[],
133
+ tools?: NIMTool[],
134
+ ): Promise<{ content: string; toolCalls: ToolCall[] }> {
135
+ const response = await this.client.chat.completions.create({
136
+ model: this.model,
137
+ messages: messages.map((m) => {
138
+ if (m.role === 'tool') {
139
+ return { role: 'tool' as const, content: m.content, tool_call_id: m.id };
140
+ }
141
+ return { role: m.role as 'system' | 'user' | 'assistant', content: m.content };
142
+ }),
143
+ tools: tools,
144
+ temperature: 0.3,
145
+ max_tokens: 8192,
146
+ stream: false,
147
+ });
148
+
149
+ const msg = response.choices[0]?.message;
150
+ // Strip reasoning noise
151
+ let content =
152
+ msg?.content?.replace(
153
+ /\[ Reasoning[^\]]*\][^\[]*\[\/ Reasoning\][^\n]*/gi,
154
+ '',
155
+ ).trim() ?? '';
156
+
157
+ const toolCalls: ToolCall[] = (msg?.tool_calls ?? []).map((tc) => {
158
+ let args: Record<string, unknown> = {};
159
+ try {
160
+ args = JSON.parse(tc.function.arguments ?? '{}');
161
+ } catch {
162
+ args = { _raw: tc.function.arguments };
163
+ }
164
+ return { id: tc.id, name: tc.function.name, arguments: args };
165
+ });
166
+
167
+ // If no tool calls but content looks like a tool call request, parse it
168
+ if (toolCalls.length === 0 && content.includes('"tool_calls"')) {
169
+ try {
170
+ const parsed = JSON.parse(content);
171
+ if (parsed.tool_calls?.length) {
172
+ toolCalls.push(...parsed.tool_calls.map((tc: { id?: string; name?: string; function?: { name?: string; arguments?: unknown } }) => ({
173
+ id: tc.id ?? crypto.randomUUID(),
174
+ name: tc.function?.name ?? tc.name ?? '',
175
+ arguments: typeof tc.function?.arguments === 'string'
176
+ ? JSON.parse(tc.function.arguments)
177
+ : tc.function?.arguments ?? {},
178
+ })));
179
+ // Remove JSON from content
180
+ const jsonStart = content.indexOf('{"tool_calls"');
181
+ if (jsonStart !== -1) content = content.slice(0, jsonStart).trim();
182
+ }
183
+ } catch {
184
+ // Not valid JSON — leave as-is
185
+ }
186
+ }
187
+
188
+ return { content, toolCalls };
189
+ }
190
+ }
191
+
192
+ export { type ToolDef } from '../types/index.js';
@@ -0,0 +1,206 @@
1
+ // =============================================================================
2
+ // Tool Definitions (OpenAI function calling schema)
3
+ // These are matched to the actual tools implemented in src/tools/
4
+ // =============================================================================
5
+
6
+ import type { ToolDef } from '../types/index.js';
7
+
8
+ export const TOOL_DEFINITIONS: ToolDef[] = [
9
+ // ── File Operations ──────────────────────────────────────────────────────────
10
+ {
11
+ name: 'file:read',
12
+ description: 'Read the complete contents of a file. Use for examining existing code, config, or documentation.',
13
+ parameters: {
14
+ type: 'object',
15
+ properties: {
16
+ path: {
17
+ type: 'string',
18
+ description: 'File path (absolute or relative to project root)',
19
+ },
20
+ limit: {
21
+ type: 'number',
22
+ description: 'Maximum number of lines to read (0 = all)',
23
+ default: 0,
24
+ },
25
+ offset: {
26
+ type: 'number',
27
+ description: 'Line offset to start reading from (0 = start)',
28
+ default: 0,
29
+ },
30
+ },
31
+ required: ['path'],
32
+ },
33
+ },
34
+ {
35
+ name: 'file:write',
36
+ description: 'Write or overwrite a complete file. Use this to create new files or replace existing ones entirely.',
37
+ parameters: {
38
+ type: 'object',
39
+ properties: {
40
+ path: {
41
+ type: 'string',
42
+ description: 'File path to write (relative to project root)',
43
+ },
44
+ content: {
45
+ type: 'string',
46
+ description: 'Complete file content to write',
47
+ },
48
+ },
49
+ required: ['path', 'content'],
50
+ },
51
+ },
52
+ {
53
+ name: 'file:edit',
54
+ description: 'Edit an existing file by replacing an exact section. The old_string must appear verbatim in the file.',
55
+ parameters: {
56
+ type: 'object',
57
+ properties: {
58
+ path: {
59
+ type: 'string',
60
+ description: 'File to edit',
61
+ },
62
+ old_string: {
63
+ type: 'string',
64
+ description: 'Exact text to find and replace (must be unique in file)',
65
+ },
66
+ new_string: {
67
+ type: 'string',
68
+ description: 'Replacement text',
69
+ },
70
+ },
71
+ required: ['path', 'old_string', 'new_string'],
72
+ },
73
+ },
74
+ {
75
+ name: 'file:glob',
76
+ description: 'Find all files matching a glob pattern. Use to discover project structure.',
77
+ parameters: {
78
+ type: 'object',
79
+ properties: {
80
+ pattern: {
81
+ type: 'string',
82
+ description: 'Glob pattern (e.g. "src/**/*.ts", "**/*.json")',
83
+ },
84
+ cwd: {
85
+ type: 'string',
86
+ description: 'Root directory to search in (defaults to project root)',
87
+ },
88
+ },
89
+ required: ['pattern'],
90
+ },
91
+ },
92
+ {
93
+ name: 'grep',
94
+ description: 'Search for text or regex patterns inside files. Use to find function definitions, imports, etc.',
95
+ parameters: {
96
+ type: 'object',
97
+ properties: {
98
+ pattern: {
99
+ type: 'string',
100
+ description: 'Text or regex pattern to search for',
101
+ },
102
+ path: {
103
+ type: 'string',
104
+ description: 'File or directory to search in',
105
+ },
106
+ glob: {
107
+ type: 'string',
108
+ description: 'Only search files matching this glob pattern',
109
+ },
110
+ case_sensitive: {
111
+ type: 'boolean',
112
+ description: 'Case sensitive search (default true)',
113
+ default: true,
114
+ },
115
+ },
116
+ required: ['pattern', 'path'],
117
+ },
118
+ },
119
+ // ── Bash ───────────────────────────────────────────────────────────────────
120
+ {
121
+ name: 'bash:execute',
122
+ description: 'Execute a shell command and return its stdout/stderr output. Destructive commands (rm, dd) require user confirmation.',
123
+ parameters: {
124
+ type: 'object',
125
+ properties: {
126
+ command: {
127
+ type: 'string',
128
+ description: 'Shell command to execute',
129
+ },
130
+ cwd: {
131
+ type: 'string',
132
+ description: 'Working directory (defaults to project root)',
133
+ },
134
+ timeout: {
135
+ type: 'number',
136
+ description: 'Timeout in milliseconds (default 300000)',
137
+ default: 300000,
138
+ },
139
+ },
140
+ required: ['command'],
141
+ },
142
+ },
143
+ // ── Git ────────────────────────────────────────────────────────────────────
144
+ {
145
+ name: 'git:status',
146
+ description: 'Show the git working tree status (modified, staged, untracked files).',
147
+ parameters: {
148
+ type: 'object',
149
+ properties: {
150
+ cwd: {
151
+ type: 'string',
152
+ description: 'Repository root (default: project root)',
153
+ },
154
+ },
155
+ },
156
+ },
157
+ {
158
+ name: 'git:commit',
159
+ description: 'Stage and commit all changes with a conventional commit message.',
160
+ parameters: {
161
+ type: 'object',
162
+ properties: {
163
+ message: {
164
+ type: 'string',
165
+ description: 'Conventional commit message (e.g. "feat: add login page")',
166
+ },
167
+ cwd: {
168
+ type: 'string',
169
+ description: 'Repository root (default: project root)',
170
+ },
171
+ },
172
+ required: ['message'],
173
+ },
174
+ },
175
+ {
176
+ name: 'git:push',
177
+ description: 'Push the current branch to the remote repository.',
178
+ parameters: {
179
+ type: 'object',
180
+ properties: {
181
+ cwd: {
182
+ type: 'string',
183
+ description: 'Repository root (default: project root)',
184
+ },
185
+ },
186
+ },
187
+ },
188
+ {
189
+ name: 'git:log',
190
+ description: 'Show recent git commit history.',
191
+ parameters: {
192
+ type: 'object',
193
+ properties: {
194
+ cwd: {
195
+ type: 'string',
196
+ description: 'Repository root (default: project root)',
197
+ },
198
+ limit: {
199
+ type: 'number',
200
+ description: 'Number of commits to show (default: 20)',
201
+ default: 20,
202
+ },
203
+ },
204
+ },
205
+ },
206
+ ];