sharkcode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/cli.mjs +351 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 syy-shark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Shark Code
|
|
2
|
+
|
|
3
|
+
> Local First, open-source AI coding agent.
|
|
4
|
+
> A small CLI inspired by Claude Code / OpenCode, with DeepSeek as the default provider.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
### Global install from npm
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g sharkcode
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
sharkcode "explain this codebase"
|
|
18
|
+
sharkcode "fix the null pointer bug in auth.ts"
|
|
19
|
+
sharkcode "add error handling to the API routes"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Run from source
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/syy-shark/sharkcode.git
|
|
26
|
+
cd sharkcode
|
|
27
|
+
bun install
|
|
28
|
+
bun run start "explain this codebase"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configure API Key
|
|
32
|
+
|
|
33
|
+
Shark Code reads `DEEPSEEK_API_KEY` from either an environment variable or `~/.sharkcode/config.toml`.
|
|
34
|
+
|
|
35
|
+
### Option 1: Environment variable
|
|
36
|
+
|
|
37
|
+
macOS / Linux:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export DEEPSEEK_API_KEY=sk-xxxxxx
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Windows PowerShell:
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
$env:DEEPSEEK_API_KEY="sk-xxxxxx"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Option 2: Config file
|
|
50
|
+
|
|
51
|
+
`~/.sharkcode/config.toml`
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
[api]
|
|
55
|
+
key = "sk-xxxxxx"
|
|
56
|
+
model = "deepseek-chat"
|
|
57
|
+
base_url = "https://api.deepseek.com/v1"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Upgrade
|
|
61
|
+
|
|
62
|
+
When you publish a new version to npm, users can upgrade with:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm update -g sharkcode
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Publish
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm login
|
|
72
|
+
npm publish
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Every code update is published as a new npm version. Typical flow:
|
|
76
|
+
|
|
77
|
+
1. Update code.
|
|
78
|
+
2. Bump `version` in `package.json`.
|
|
79
|
+
3. Run `npm publish`.
|
|
80
|
+
|
|
81
|
+
## How It Works
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
User input -> Prompt + Tools -> LLM -> Tool execution -> Result -> Repeat
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Built-in tools:
|
|
88
|
+
|
|
89
|
+
| Tool | Description |
|
|
90
|
+
|------|-------------|
|
|
91
|
+
| `read_file` | Read a file |
|
|
92
|
+
| `write_file` | Create or overwrite a file |
|
|
93
|
+
| `edit_file` | Replace an exact string in a file |
|
|
94
|
+
| `bash` | Execute a shell command with approval |
|
|
95
|
+
|
|
96
|
+
## Tech Stack
|
|
97
|
+
|
|
98
|
+
- Bun + TypeScript
|
|
99
|
+
- Vercel AI SDK
|
|
100
|
+
- DeepSeek API
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import chalk3 from "chalk";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
import { parse } from "smol-toml";
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
var CONFIG_DIR = join(homedir(), ".sharkcode");
|
|
12
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
13
|
+
var DEFAULT_CONFIG = `# Shark Code Configuration
|
|
14
|
+
# https://github.com/syy-ex/sharkcode
|
|
15
|
+
|
|
16
|
+
[api]
|
|
17
|
+
# Get your API key from https://platform.deepseek.com
|
|
18
|
+
# Or set DEEPSEEK_API_KEY environment variable
|
|
19
|
+
key = ""
|
|
20
|
+
model = "deepseek-chat"
|
|
21
|
+
base_url = "https://api.deepseek.com/v1"
|
|
22
|
+
`;
|
|
23
|
+
function ensureConfigDir() {
|
|
24
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
25
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
28
|
+
writeFileSync(CONFIG_FILE, DEFAULT_CONFIG, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function loadConfig() {
|
|
32
|
+
ensureConfigDir();
|
|
33
|
+
let toml = {};
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
36
|
+
toml = parse(raw);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
const api = toml.api ?? {};
|
|
40
|
+
const apiKey = process.env.DEEPSEEK_API_KEY || api.key || "";
|
|
41
|
+
const model = api.model || "deepseek-chat";
|
|
42
|
+
const baseURL = api.base_url || "https://api.deepseek.com/v1";
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
console.error("\u274C No API key found.");
|
|
45
|
+
console.error(` Set DEEPSEEK_API_KEY env var or edit ${CONFIG_FILE}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
return { apiKey, model, baseURL };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/agent.ts
|
|
52
|
+
import { stepCountIs, streamText } from "ai";
|
|
53
|
+
import chalk2 from "chalk";
|
|
54
|
+
|
|
55
|
+
// src/tools/read-file.ts
|
|
56
|
+
import { tool } from "ai";
|
|
57
|
+
import { z } from "zod";
|
|
58
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
59
|
+
import { resolve } from "path";
|
|
60
|
+
var readFileTool = tool({
|
|
61
|
+
description: "Read the contents of a file at the given path. Returns the file content as a string.",
|
|
62
|
+
inputSchema: z.object({
|
|
63
|
+
path: z.string().describe("File path to read (relative to current working directory)")
|
|
64
|
+
}),
|
|
65
|
+
execute: async ({ path: filePath }) => {
|
|
66
|
+
const fullPath = resolve(process.cwd(), filePath);
|
|
67
|
+
if (!existsSync2(fullPath)) {
|
|
68
|
+
return `Error: File not found: ${filePath}`;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return readFileSync2(fullPath, "utf-8");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
return `Error reading file: ${msg}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// src/tools/write-file.ts
|
|
80
|
+
import { tool as tool2 } from "ai";
|
|
81
|
+
import { z as z2 } from "zod";
|
|
82
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
83
|
+
import { resolve as resolve2, dirname } from "path";
|
|
84
|
+
var writeFileTool = tool2({
|
|
85
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Creates parent directories as needed.",
|
|
86
|
+
inputSchema: z2.object({
|
|
87
|
+
path: z2.string().describe("File path to write to (relative to current working directory)"),
|
|
88
|
+
content: z2.string().describe("The content to write to the file")
|
|
89
|
+
}),
|
|
90
|
+
execute: async ({ path: filePath, content }) => {
|
|
91
|
+
const fullPath = resolve2(process.cwd(), filePath);
|
|
92
|
+
try {
|
|
93
|
+
mkdirSync2(dirname(fullPath), { recursive: true });
|
|
94
|
+
writeFileSync2(fullPath, content, "utf-8");
|
|
95
|
+
return `Successfully wrote to ${filePath}`;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
return `Error writing file: ${msg}`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// src/tools/edit-file.ts
|
|
104
|
+
import { tool as tool3 } from "ai";
|
|
105
|
+
import { z as z3 } from "zod";
|
|
106
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
|
|
107
|
+
import { resolve as resolve3 } from "path";
|
|
108
|
+
var editFileTool = tool3({
|
|
109
|
+
description: "Edit a file by replacing an exact string match with new content. The old_str must match exactly one occurrence in the file.",
|
|
110
|
+
inputSchema: z3.object({
|
|
111
|
+
path: z3.string().describe("File path to edit (relative to current working directory)"),
|
|
112
|
+
old_str: z3.string().describe("The exact string to find and replace"),
|
|
113
|
+
new_str: z3.string().describe("The replacement string")
|
|
114
|
+
}),
|
|
115
|
+
execute: async ({ path: filePath, old_str, new_str }) => {
|
|
116
|
+
const fullPath = resolve3(process.cwd(), filePath);
|
|
117
|
+
if (!existsSync3(fullPath)) {
|
|
118
|
+
return `Error: File not found: ${filePath}`;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const content = readFileSync3(fullPath, "utf-8");
|
|
122
|
+
const count = content.split(old_str).length - 1;
|
|
123
|
+
if (count === 0) {
|
|
124
|
+
return `Error: old_str not found in ${filePath}`;
|
|
125
|
+
}
|
|
126
|
+
if (count > 1) {
|
|
127
|
+
return `Error: old_str found ${count} times in ${filePath}, expected exactly 1. Include more context to make it unique.`;
|
|
128
|
+
}
|
|
129
|
+
const newContent = content.replace(old_str, new_str);
|
|
130
|
+
writeFileSync3(fullPath, newContent, "utf-8");
|
|
131
|
+
return `Successfully edited ${filePath}`;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
134
|
+
return `Error editing file: ${msg}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// src/tools/bash.ts
|
|
140
|
+
import { tool as tool4 } from "ai";
|
|
141
|
+
import { z as z4 } from "zod";
|
|
142
|
+
import { exec } from "child_process";
|
|
143
|
+
|
|
144
|
+
// src/permission.ts
|
|
145
|
+
import * as readline from "readline";
|
|
146
|
+
import chalk from "chalk";
|
|
147
|
+
async function askPermission(command) {
|
|
148
|
+
process.stderr.write(
|
|
149
|
+
chalk.yellow(`
|
|
150
|
+
\u26A0\uFE0F Will execute: `) + chalk.white(command) + "\n"
|
|
151
|
+
);
|
|
152
|
+
const rl = readline.createInterface({
|
|
153
|
+
input: process.stdin,
|
|
154
|
+
output: process.stderr
|
|
155
|
+
});
|
|
156
|
+
return new Promise((resolve4) => {
|
|
157
|
+
rl.question(chalk.yellow(" Allow? [y/N] "), (answer) => {
|
|
158
|
+
rl.close();
|
|
159
|
+
const yes = answer.trim().toLowerCase();
|
|
160
|
+
resolve4(yes === "y" || yes === "yes");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/tools/bash.ts
|
|
166
|
+
var bashTool = tool4({
|
|
167
|
+
description: "Execute a shell command. The command will be shown to the user for approval before execution. Use this for running tests, installing packages, checking git status, etc.",
|
|
168
|
+
inputSchema: z4.object({
|
|
169
|
+
command: z4.string().describe("The shell command to execute")
|
|
170
|
+
}),
|
|
171
|
+
execute: async ({ command }) => {
|
|
172
|
+
const allowed = await askPermission(command);
|
|
173
|
+
if (!allowed) {
|
|
174
|
+
return "Command execution denied by user.";
|
|
175
|
+
}
|
|
176
|
+
return new Promise((resolve4) => {
|
|
177
|
+
exec(
|
|
178
|
+
command,
|
|
179
|
+
{
|
|
180
|
+
cwd: process.cwd(),
|
|
181
|
+
timeout: 12e4,
|
|
182
|
+
maxBuffer: 1024 * 1024 * 5
|
|
183
|
+
},
|
|
184
|
+
(error, stdout, stderr) => {
|
|
185
|
+
let result = "";
|
|
186
|
+
if (stdout) result += stdout;
|
|
187
|
+
if (stderr) result += (result ? "\n" : "") + `[stderr]: ${stderr}`;
|
|
188
|
+
if (error && error.code !== null) {
|
|
189
|
+
result += (result ? "\n" : "") + `[exit code]: ${error.code}`;
|
|
190
|
+
}
|
|
191
|
+
resolve4(result || "(no output)");
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// src/tools/index.ts
|
|
199
|
+
var tools = {
|
|
200
|
+
read_file: readFileTool,
|
|
201
|
+
write_file: writeFileTool,
|
|
202
|
+
edit_file: editFileTool,
|
|
203
|
+
bash: bashTool
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/provider.ts
|
|
207
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
208
|
+
function createProvider(config) {
|
|
209
|
+
const provider = createOpenAI({
|
|
210
|
+
baseURL: config.baseURL,
|
|
211
|
+
apiKey: config.apiKey,
|
|
212
|
+
name: "deepseek"
|
|
213
|
+
});
|
|
214
|
+
return provider.chat(config.model);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/agent.ts
|
|
218
|
+
function buildSystemPrompt() {
|
|
219
|
+
return `You are Shark Code, an AI coding assistant. You help users with coding tasks by reading, writing, and editing files, and running shell commands.
|
|
220
|
+
|
|
221
|
+
Current working directory: ${process.cwd()}
|
|
222
|
+
Environment: Windows with PowerShell. Use PowerShell-compatible commands, not bash syntax.
|
|
223
|
+
|
|
224
|
+
Available tools:
|
|
225
|
+
- read_file: Read the contents of a file
|
|
226
|
+
- write_file: Create or overwrite a file (creates parent dirs)
|
|
227
|
+
- edit_file: Find and replace an exact string in a file
|
|
228
|
+
- bash: Execute a shell command (requires user approval)
|
|
229
|
+
|
|
230
|
+
Guidelines:
|
|
231
|
+
- Always read a file before editing it
|
|
232
|
+
- Explain what you plan to do before making changes
|
|
233
|
+
- Use edit_file for targeted changes, write_file for creating new files
|
|
234
|
+
- When using shell commands, prefer PowerShell built-ins or commands that work on Windows
|
|
235
|
+
- Keep changes minimal and focused on the user's request
|
|
236
|
+
- When done, summarize what you changed`;
|
|
237
|
+
}
|
|
238
|
+
async function runAgent(prompt, config) {
|
|
239
|
+
const model = createProvider(config);
|
|
240
|
+
const result = streamText({
|
|
241
|
+
model,
|
|
242
|
+
system: buildSystemPrompt(),
|
|
243
|
+
prompt,
|
|
244
|
+
tools,
|
|
245
|
+
stopWhen: stepCountIs(30)
|
|
246
|
+
});
|
|
247
|
+
let currentStep = 0;
|
|
248
|
+
for await (const event of result.fullStream) {
|
|
249
|
+
switch (event.type) {
|
|
250
|
+
case "text-delta":
|
|
251
|
+
process.stdout.write(event.text);
|
|
252
|
+
break;
|
|
253
|
+
case "tool-input-available":
|
|
254
|
+
process.stdout.write(
|
|
255
|
+
chalk2.cyan(`
|
|
256
|
+
\u{1F527} ${event.toolName}`) + chalk2.gray(`(${formatArgs(event.input)})
|
|
257
|
+
`)
|
|
258
|
+
);
|
|
259
|
+
break;
|
|
260
|
+
case "tool-output-available":
|
|
261
|
+
process.stdout.write(
|
|
262
|
+
chalk2.green(`\u2705 tool`) + chalk2.gray(` \u2192 ${truncate(String(event.output), 120)}
|
|
263
|
+
|
|
264
|
+
`)
|
|
265
|
+
);
|
|
266
|
+
break;
|
|
267
|
+
case "tool-output-error":
|
|
268
|
+
process.stderr.write(chalk2.red(`
|
|
269
|
+
\u274C Tool error: ${event.errorText}
|
|
270
|
+
`));
|
|
271
|
+
break;
|
|
272
|
+
case "finish-step":
|
|
273
|
+
currentStep++;
|
|
274
|
+
break;
|
|
275
|
+
case "error":
|
|
276
|
+
process.stderr.write(chalk2.red(`
|
|
277
|
+
\u274C Error: ${event.errorText}
|
|
278
|
+
`));
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
process.stdout.write("\n");
|
|
283
|
+
const usage = await result.totalUsage;
|
|
284
|
+
if (usage) {
|
|
285
|
+
process.stderr.write(
|
|
286
|
+
chalk2.gray(
|
|
287
|
+
`
|
|
288
|
+
\u{1F4CA} Tokens: ${usage.inputTokens} in / ${usage.outputTokens} out | Steps: ${currentStep}
|
|
289
|
+
`
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function formatArgs(args) {
|
|
295
|
+
if (typeof args !== "object" || args === null) return String(args);
|
|
296
|
+
const obj = args;
|
|
297
|
+
return Object.entries(obj).map(([k, v]) => {
|
|
298
|
+
const val = typeof v === "string" ? truncate(v, 60) : String(v);
|
|
299
|
+
return `${k}: ${val}`;
|
|
300
|
+
}).join(", ");
|
|
301
|
+
}
|
|
302
|
+
function truncate(str, max) {
|
|
303
|
+
const oneLine = str.replace(/\n/g, "\\n");
|
|
304
|
+
return oneLine.length > max ? oneLine.slice(0, max) + "\u2026" : oneLine;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/cli.ts
|
|
308
|
+
var BANNER = chalk3.bold.cyan(`
|
|
309
|
+
\u{1F988} Shark Code v0.1
|
|
310
|
+
AI Coding Agent \u2014 Local First, Open Source
|
|
311
|
+
`);
|
|
312
|
+
var HELP = `${BANNER}
|
|
313
|
+
${chalk3.white("Usage:")}
|
|
314
|
+
${chalk3.green("sharkcode")} ${chalk3.yellow('"your prompt here"')}
|
|
315
|
+
|
|
316
|
+
${chalk3.white("Examples:")}
|
|
317
|
+
sharkcode "explain this codebase"
|
|
318
|
+
sharkcode "fix the null pointer bug in auth.ts"
|
|
319
|
+
sharkcode "add error handling to the API routes"
|
|
320
|
+
|
|
321
|
+
${chalk3.white("Config:")}
|
|
322
|
+
Set ${chalk3.yellow("DEEPSEEK_API_KEY")} env var, or edit ${chalk3.gray("~/.sharkcode/config.toml")}
|
|
323
|
+
Get your key at ${chalk3.underline("https://platform.deepseek.com")}
|
|
324
|
+
`;
|
|
325
|
+
async function main() {
|
|
326
|
+
const args = process.argv.slice(2);
|
|
327
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
328
|
+
console.log(HELP);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
332
|
+
console.log("sharkcode v0.1.0");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const prompt = args.join(" ");
|
|
336
|
+
const config = loadConfig();
|
|
337
|
+
process.stdout.write(
|
|
338
|
+
chalk3.cyan("\u{1F988} Shark Code") + chalk3.gray(` | model: ${config.model}
|
|
339
|
+
|
|
340
|
+
`)
|
|
341
|
+
);
|
|
342
|
+
await runAgent(prompt, config);
|
|
343
|
+
}
|
|
344
|
+
main().catch((err) => {
|
|
345
|
+
console.error(chalk3.red(`
|
|
346
|
+
\u274C Fatal: ${err.message}`));
|
|
347
|
+
if (err.message?.includes("401") || err.message?.includes("Unauthorized")) {
|
|
348
|
+
console.error(chalk3.yellow(" Check your API key in ~/.sharkcode/config.toml"));
|
|
349
|
+
}
|
|
350
|
+
process.exit(1);
|
|
351
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sharkcode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local First, open-source AI Coding Agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sharkcode": "dist/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "bun src/cli.ts",
|
|
16
|
+
"dev": "bun --watch src/cli.ts",
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"prepublishOnly": "npm run build && bun test"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"@types/node": "^24.6.0",
|
|
24
|
+
"tsup": "^8.5.0",
|
|
25
|
+
"typescript": "^5.9.3"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@ai-sdk/openai": "^3.0.48",
|
|
29
|
+
"ai": "^6.0.141",
|
|
30
|
+
"chalk": "^5.6.2",
|
|
31
|
+
"smol-toml": "^1.6.1",
|
|
32
|
+
"zod": "^4.3.6"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/syy-shark/sharkcode.git"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/syy-shark/sharkcode",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/syy-shark/sharkcode/issues"
|
|
47
|
+
}
|
|
48
|
+
}
|