sharkcode 0.2.0 → 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.
- package/README.md +59 -39
- package/dist/cli.mjs +298 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,49 +1,46 @@
|
|
|
1
1
|
# Shark Code
|
|
2
2
|
|
|
3
|
-
> Local First, open-source AI coding agent.
|
|
3
|
+
> Local First, open-source AI coding agent.
|
|
4
4
|
> A small CLI inspired by Claude Code / OpenCode, with DeepSeek as the default provider.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
|
-
### Global install from npm
|
|
9
|
-
|
|
10
8
|
```bash
|
|
11
9
|
npm install -g sharkcode
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Launch interactive mode:
|
|
15
13
|
|
|
16
14
|
```bash
|
|
17
|
-
sharkcode
|
|
18
|
-
sharkcode "fix the null pointer bug in auth.ts"
|
|
19
|
-
sharkcode "add error handling to the API routes"
|
|
15
|
+
sharkcode
|
|
20
16
|
```
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
Or single-shot mode:
|
|
23
19
|
|
|
24
20
|
```bash
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
bun install
|
|
28
|
-
bun run start "explain this codebase"
|
|
21
|
+
sharkcode "explain this codebase"
|
|
22
|
+
sharkcode "fix the null pointer bug in auth.ts"
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
##
|
|
25
|
+
## Providers
|
|
32
26
|
|
|
33
|
-
Shark Code
|
|
27
|
+
Shark Code supports multiple providers. Switch between them anytime with `/provider`.
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
| Provider | Description | Docs |
|
|
30
|
+
|----------|-------------|------|
|
|
31
|
+
| `deepseek` | DeepSeek 官网 (default) | [platform.deepseek.com](https://platform.deepseek.com) |
|
|
32
|
+
| `ark` | 火山引擎 方舟 Coding Plan | [volcengine.com/activity/codingplan](https://www.volcengine.com/activity/codingplan) |
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
## Configure
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
export DEEPSEEK_API_KEY=sk-xxxxxx
|
|
41
|
-
```
|
|
36
|
+
### Option 1: Slash commands (recommended)
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
Start `sharkcode`, then:
|
|
44
39
|
|
|
45
|
-
```
|
|
46
|
-
|
|
40
|
+
```
|
|
41
|
+
◆ /provider ark # switch to 方舟 Coding Plan
|
|
42
|
+
◆ /key sk-xxxxxxxxxx # set API key (saved automatically)
|
|
43
|
+
◆ /model ark-code-latest # optionally change model
|
|
47
44
|
```
|
|
48
45
|
|
|
49
46
|
### Option 2: Config file
|
|
@@ -51,37 +48,60 @@ $env:DEEPSEEK_API_KEY="sk-xxxxxx"
|
|
|
51
48
|
`~/.sharkcode/config.toml`
|
|
52
49
|
|
|
53
50
|
```toml
|
|
54
|
-
[
|
|
51
|
+
[default]
|
|
52
|
+
provider = "deepseek" # or "ark"
|
|
53
|
+
|
|
54
|
+
[providers.deepseek]
|
|
55
|
+
# API key from https://platform.deepseek.com
|
|
55
56
|
key = "sk-xxxxxx"
|
|
56
57
|
model = "deepseek-chat"
|
|
57
|
-
base_url = "https://api.deepseek.com/v1"
|
|
58
|
-
```
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
[providers.ark]
|
|
60
|
+
# API key from https://ark.cn-beijing.volces.com (方舟 Coding Plan)
|
|
61
|
+
key = "sk-xxxxxx"
|
|
62
|
+
model = "ark-code-latest"
|
|
63
|
+
```
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
### Option 3: Environment variables
|
|
63
66
|
|
|
64
|
-
```
|
|
65
|
-
|
|
67
|
+
```powershell
|
|
68
|
+
$env:DEEPSEEK_API_KEY="sk-xxxxxx" # deepseek provider
|
|
69
|
+
$env:ARK_API_KEY="sk-xxxxxx" # ark provider
|
|
66
70
|
```
|
|
67
71
|
|
|
68
|
-
##
|
|
72
|
+
## Slash Commands
|
|
73
|
+
|
|
74
|
+
Type `/` commands anytime inside the interactive REPL:
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `/provider` | show current provider & key status |
|
|
79
|
+
| `/provider <name>` | switch provider (`deepseek` \| `ark`) |
|
|
80
|
+
| `/key <api-key>` | set API key for current provider |
|
|
81
|
+
| `/model <model-id>` | set model for current provider |
|
|
82
|
+
| `/clear` | clear conversation history |
|
|
83
|
+
| `/help` | show command list |
|
|
84
|
+
| `/exit` | quit |
|
|
85
|
+
|
|
86
|
+
## Run from source
|
|
69
87
|
|
|
70
88
|
```bash
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
git clone https://github.com/syy-shark/sharkcode.git
|
|
90
|
+
cd sharkcode
|
|
91
|
+
bun install
|
|
92
|
+
bun run start
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
## Upgrade
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
```bash
|
|
98
|
+
npm update -g sharkcode
|
|
99
|
+
```
|
|
80
100
|
|
|
81
101
|
## How It Works
|
|
82
102
|
|
|
83
|
-
```
|
|
84
|
-
User input
|
|
103
|
+
```
|
|
104
|
+
User input → Prompt + Tools → LLM → Tool execution → Result → Repeat
|
|
85
105
|
```
|
|
86
106
|
|
|
87
107
|
Built-in tools:
|
|
@@ -97,7 +117,7 @@ Built-in tools:
|
|
|
97
117
|
|
|
98
118
|
- Bun + TypeScript
|
|
99
119
|
- Vercel AI SDK
|
|
100
|
-
- DeepSeek API
|
|
120
|
+
- DeepSeek API / 火山引擎 方舟 API
|
|
101
121
|
|
|
102
122
|
## License
|
|
103
123
|
|
package/dist/cli.mjs
CHANGED
|
@@ -9,44 +9,100 @@ import { parse } from "smol-toml";
|
|
|
9
9
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
|
+
var PROVIDERS = {
|
|
13
|
+
deepseek: {
|
|
14
|
+
baseURL: "https://api.deepseek.com/v1",
|
|
15
|
+
defaultModel: "deepseek-chat",
|
|
16
|
+
label: "DeepSeek \u5B98\u7F51"
|
|
17
|
+
},
|
|
18
|
+
ark: {
|
|
19
|
+
// Coding Plan uses a dedicated endpoint — do NOT use /api/v3 (that's the pay-per-use endpoint)
|
|
20
|
+
baseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
|
21
|
+
defaultModel: "ark-code-latest",
|
|
22
|
+
label: "\u65B9\u821F Coding Plan"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
12
25
|
var CONFIG_DIR = join(homedir(), ".sharkcode");
|
|
13
26
|
var CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
function serializeConfig(mc) {
|
|
28
|
+
const dp = mc.providers.deepseek;
|
|
29
|
+
const ap = mc.providers.ark;
|
|
30
|
+
return [
|
|
31
|
+
"# Shark Code Configuration",
|
|
32
|
+
"# https://github.com/syy-shark/sharkcode",
|
|
33
|
+
"",
|
|
34
|
+
"[default]",
|
|
35
|
+
`provider = "${mc.activeProvider}"`,
|
|
36
|
+
"",
|
|
37
|
+
"[providers.deepseek]",
|
|
38
|
+
"# API key from https://platform.deepseek.com",
|
|
39
|
+
`key = "${dp?.key ?? ""}"`,
|
|
40
|
+
`model = "${dp?.model ?? PROVIDERS.deepseek.defaultModel}"`,
|
|
41
|
+
"",
|
|
42
|
+
"[providers.ark]",
|
|
43
|
+
"# API key from https://ark.cn-beijing.volces.com (\u65B9\u821F Coding Plan)",
|
|
44
|
+
`key = "${ap?.key ?? ""}"`,
|
|
45
|
+
`model = "${ap?.model ?? PROVIDERS.ark.defaultModel}"`,
|
|
46
|
+
""
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
function ensureConfig() {
|
|
25
50
|
if (!existsSync(CONFIG_DIR)) {
|
|
26
51
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
52
|
}
|
|
28
53
|
if (!existsSync(CONFIG_FILE)) {
|
|
29
|
-
|
|
54
|
+
const initial = {
|
|
55
|
+
activeProvider: "deepseek",
|
|
56
|
+
providers: {
|
|
57
|
+
deepseek: { key: "", model: PROVIDERS.deepseek.defaultModel },
|
|
58
|
+
ark: { key: "", model: PROVIDERS.ark.defaultModel }
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
writeFileSync(CONFIG_FILE, serializeConfig(initial), "utf-8");
|
|
30
62
|
}
|
|
31
63
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
64
|
+
function readMultiConfig() {
|
|
65
|
+
ensureConfig();
|
|
34
66
|
let toml = {};
|
|
35
67
|
try {
|
|
36
68
|
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
37
69
|
toml = parse(raw);
|
|
38
70
|
} catch {
|
|
39
71
|
}
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
const legacy = toml.api;
|
|
73
|
+
const providersRaw = toml.providers ?? {};
|
|
74
|
+
const defaultSection = toml.default ?? {};
|
|
75
|
+
const deepseekKey = process.env.DEEPSEEK_API_KEY || providersRaw.deepseek?.key || legacy?.key || "";
|
|
76
|
+
const arkKey = process.env.ARK_API_KEY || providersRaw.ark?.key || "";
|
|
77
|
+
const activeProvider = defaultSection.provider || (legacy ? "deepseek" : "deepseek");
|
|
78
|
+
return {
|
|
79
|
+
activeProvider,
|
|
80
|
+
providers: {
|
|
81
|
+
deepseek: {
|
|
82
|
+
key: deepseekKey,
|
|
83
|
+
model: providersRaw.deepseek?.model || legacy?.model || PROVIDERS.deepseek.defaultModel
|
|
84
|
+
},
|
|
85
|
+
ark: {
|
|
86
|
+
key: arkKey,
|
|
87
|
+
model: providersRaw.ark?.model || PROVIDERS.ark.defaultModel
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function saveMultiConfig(mc) {
|
|
93
|
+
ensureConfig();
|
|
94
|
+
writeFileSync(CONFIG_FILE, serializeConfig(mc), "utf-8");
|
|
95
|
+
}
|
|
96
|
+
function resolveConfig(mc) {
|
|
97
|
+
const name = mc.activeProvider;
|
|
98
|
+
const entry = mc.providers[name];
|
|
99
|
+
const meta = PROVIDERS[name];
|
|
100
|
+
return {
|
|
101
|
+
providerName: name,
|
|
102
|
+
apiKey: entry?.key || "",
|
|
103
|
+
model: entry?.model || meta?.defaultModel || "deepseek-chat",
|
|
104
|
+
baseURL: meta?.baseURL || PROVIDERS.deepseek.baseURL
|
|
105
|
+
};
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
// src/agent.ts
|
|
@@ -210,12 +266,36 @@ function createProvider(config) {
|
|
|
210
266
|
const provider = createOpenAI({
|
|
211
267
|
baseURL: config.baseURL,
|
|
212
268
|
apiKey: config.apiKey,
|
|
213
|
-
name
|
|
269
|
+
// ARK requires a non-default name to avoid SDK header overrides
|
|
270
|
+
name: config.providerName === "ark" ? "ark" : "deepseek"
|
|
214
271
|
});
|
|
215
272
|
return provider.chat(config.model);
|
|
216
273
|
}
|
|
217
274
|
|
|
218
275
|
// src/agent.ts
|
|
276
|
+
var PURPLE = chalk2.hex("#a855f7");
|
|
277
|
+
function createSpinner() {
|
|
278
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
279
|
+
let i = 0;
|
|
280
|
+
let timer = null;
|
|
281
|
+
return {
|
|
282
|
+
start() {
|
|
283
|
+
process.stdout.write("\n");
|
|
284
|
+
timer = setInterval(() => {
|
|
285
|
+
process.stdout.write(
|
|
286
|
+
`\r${PURPLE(frames[i++ % frames.length])} ${chalk2.gray("thinking...")}`
|
|
287
|
+
);
|
|
288
|
+
}, 80);
|
|
289
|
+
},
|
|
290
|
+
stop() {
|
|
291
|
+
if (timer) {
|
|
292
|
+
clearInterval(timer);
|
|
293
|
+
timer = null;
|
|
294
|
+
process.stdout.write("\r\x1B[K");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
219
299
|
function buildSystemPrompt() {
|
|
220
300
|
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.
|
|
221
301
|
|
|
@@ -246,7 +326,14 @@ async function runAgent(messages, config) {
|
|
|
246
326
|
stopWhen: stepCountIs(30)
|
|
247
327
|
});
|
|
248
328
|
let currentStep = 0;
|
|
329
|
+
const spinner = createSpinner();
|
|
330
|
+
let spinnerStopped = false;
|
|
331
|
+
spinner.start();
|
|
249
332
|
for await (const event of result.fullStream) {
|
|
333
|
+
if (!spinnerStopped && (event.type === "text-delta" || event.type === "tool-call" || event.type === "error")) {
|
|
334
|
+
spinner.stop();
|
|
335
|
+
spinnerStopped = true;
|
|
336
|
+
}
|
|
250
337
|
switch (event.type) {
|
|
251
338
|
case "text-delta":
|
|
252
339
|
process.stdout.write(event.text);
|
|
@@ -282,6 +369,9 @@ async function runAgent(messages, config) {
|
|
|
282
369
|
break;
|
|
283
370
|
}
|
|
284
371
|
}
|
|
372
|
+
if (!spinnerStopped) {
|
|
373
|
+
spinner.stop();
|
|
374
|
+
}
|
|
285
375
|
process.stdout.write("\n");
|
|
286
376
|
const usage = await result.totalUsage;
|
|
287
377
|
if (usage) {
|
|
@@ -314,7 +404,12 @@ function truncate(str, max) {
|
|
|
314
404
|
}
|
|
315
405
|
|
|
316
406
|
// src/cli.ts
|
|
317
|
-
var
|
|
407
|
+
var PURPLE2 = chalk3.hex("#a855f7");
|
|
408
|
+
var GRAY = chalk3.gray;
|
|
409
|
+
var YELLOW = chalk3.yellow;
|
|
410
|
+
var GREEN = chalk3.green;
|
|
411
|
+
var RED = chalk3.red;
|
|
412
|
+
var CYAN = chalk3.cyan;
|
|
318
413
|
var GLYPHS = {
|
|
319
414
|
S: [[0, 1, 1, 1, 1], [1, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 1], [1, 1, 1, 1, 0]],
|
|
320
415
|
H: [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]],
|
|
@@ -338,11 +433,137 @@ function renderWord(word, padLeft = 2) {
|
|
|
338
433
|
}
|
|
339
434
|
if (i < letters.length - 1) line += " ";
|
|
340
435
|
}
|
|
341
|
-
rows.push(
|
|
436
|
+
rows.push(PURPLE2(line));
|
|
342
437
|
}
|
|
343
438
|
return rows;
|
|
344
439
|
}
|
|
345
440
|
var BANNER = ["", ...renderWord("shark", 2), "", ...renderWord("code", 8), ""].join("\n");
|
|
441
|
+
function printSlashHelp() {
|
|
442
|
+
const pad = (s, n) => s + " ".repeat(Math.max(0, n - s.length));
|
|
443
|
+
console.log(PURPLE2("\n \u25C6 Slash Commands\n"));
|
|
444
|
+
const cmds = [
|
|
445
|
+
["/provider", "show current provider & list options"],
|
|
446
|
+
["/provider <name>", "switch provider (deepseek | ark)"],
|
|
447
|
+
["/key <api-key>", "set API key for current provider"],
|
|
448
|
+
["/model <model-id>", "set model for current provider"],
|
|
449
|
+
["/clear", "clear conversation history"],
|
|
450
|
+
["/help", "show this menu"],
|
|
451
|
+
["/exit", "quit"]
|
|
452
|
+
];
|
|
453
|
+
for (const [cmd, desc] of cmds) {
|
|
454
|
+
console.log(" " + PURPLE2(pad(cmd, 26)) + GRAY(desc));
|
|
455
|
+
}
|
|
456
|
+
console.log();
|
|
457
|
+
}
|
|
458
|
+
function handleSlash(input, multiConfig) {
|
|
459
|
+
const parts = input.trim().split(/\s+/);
|
|
460
|
+
const cmd = parts[0].toLowerCase();
|
|
461
|
+
const arg = parts.slice(1).join(" ");
|
|
462
|
+
if (cmd === "/" || cmd === "/help") {
|
|
463
|
+
printSlashHelp();
|
|
464
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
465
|
+
}
|
|
466
|
+
if (cmd === "/exit" || cmd === "/quit") {
|
|
467
|
+
console.log(GRAY("Bye! \u{1F988}"));
|
|
468
|
+
return { multiConfig, config: resolveConfig(multiConfig), exit: true };
|
|
469
|
+
}
|
|
470
|
+
if (cmd === "/clear") {
|
|
471
|
+
console.log(GRAY(" \u2713 Conversation cleared."));
|
|
472
|
+
return { multiConfig, config: resolveConfig(multiConfig), clearHistory: true };
|
|
473
|
+
}
|
|
474
|
+
if (cmd === "/provider") {
|
|
475
|
+
if (!arg) {
|
|
476
|
+
const current = multiConfig.activeProvider;
|
|
477
|
+
console.log(PURPLE2("\n \u25C6 Providers\n"));
|
|
478
|
+
for (const [name2, meta] of Object.entries(PROVIDERS)) {
|
|
479
|
+
const entry = multiConfig.providers[name2];
|
|
480
|
+
const active = name2 === current;
|
|
481
|
+
const hasKey = !!entry?.key;
|
|
482
|
+
const marker = active ? PURPLE2("\u25B6") : " ";
|
|
483
|
+
const keyStatus = hasKey ? GREEN("\u2713 key set") : YELLOW("\u2717 no key");
|
|
484
|
+
console.log(
|
|
485
|
+
` ${marker} ${active ? PURPLE2(name2) : GRAY(name2)} ${GRAY(meta.label)} ${keyStatus}` + (active ? ` ${GRAY("model: " + (entry?.model ?? meta.defaultModel))}` : "")
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
console.log(
|
|
489
|
+
`
|
|
490
|
+
${GRAY("Usage:")} ${PURPLE2("/provider deepseek")} ${GRAY("or")} ${PURPLE2("/provider ark")}
|
|
491
|
+
`
|
|
492
|
+
);
|
|
493
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
494
|
+
}
|
|
495
|
+
const name = arg.toLowerCase();
|
|
496
|
+
if (!PROVIDERS[name]) {
|
|
497
|
+
console.log(RED(` \u2717 Unknown provider: "${name}". Available: ${Object.keys(PROVIDERS).join(", ")}`));
|
|
498
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
499
|
+
}
|
|
500
|
+
const updated = { ...multiConfig, activeProvider: name };
|
|
501
|
+
saveMultiConfig(updated);
|
|
502
|
+
const newConfig = resolveConfig(updated);
|
|
503
|
+
console.log(
|
|
504
|
+
GREEN(`
|
|
505
|
+
\u2713 Switched to ${PROVIDERS[name].label}`) + GRAY(` (${name}) \u2014 model: ${newConfig.model}`)
|
|
506
|
+
);
|
|
507
|
+
if (!newConfig.apiKey) {
|
|
508
|
+
console.log(YELLOW(` \u26A0 No API key set. Use /key <your-key> to configure it.
|
|
509
|
+
`));
|
|
510
|
+
} else {
|
|
511
|
+
console.log();
|
|
512
|
+
}
|
|
513
|
+
return { multiConfig: updated, config: newConfig };
|
|
514
|
+
}
|
|
515
|
+
if (cmd === "/key") {
|
|
516
|
+
if (!arg) {
|
|
517
|
+
console.log(YELLOW(` Usage: /key <your-api-key>`));
|
|
518
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
519
|
+
}
|
|
520
|
+
const name = multiConfig.activeProvider;
|
|
521
|
+
const updated = {
|
|
522
|
+
...multiConfig,
|
|
523
|
+
providers: {
|
|
524
|
+
...multiConfig.providers,
|
|
525
|
+
[name]: {
|
|
526
|
+
...multiConfig.providers[name] ?? { model: PROVIDERS[name]?.defaultModel ?? "" },
|
|
527
|
+
key: arg
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
saveMultiConfig(updated);
|
|
532
|
+
const masked = arg.slice(0, 6) + "\u2022".repeat(Math.max(0, arg.length - 10)) + arg.slice(-4);
|
|
533
|
+
console.log(GREEN(` \u2713 API key saved for ${name}: ${GRAY(masked)}
|
|
534
|
+
`));
|
|
535
|
+
return { multiConfig: updated, config: resolveConfig(updated) };
|
|
536
|
+
}
|
|
537
|
+
if (cmd === "/model") {
|
|
538
|
+
if (!arg) {
|
|
539
|
+
const name2 = multiConfig.activeProvider;
|
|
540
|
+
const current = multiConfig.providers[name2]?.model ?? PROVIDERS[name2]?.defaultModel;
|
|
541
|
+
console.log(GRAY(` Current model: `) + PURPLE2(current ?? "unknown"));
|
|
542
|
+
console.log(GRAY(` Usage: /model <model-id>
|
|
543
|
+
`));
|
|
544
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
545
|
+
}
|
|
546
|
+
const name = multiConfig.activeProvider;
|
|
547
|
+
const updated = {
|
|
548
|
+
...multiConfig,
|
|
549
|
+
providers: {
|
|
550
|
+
...multiConfig.providers,
|
|
551
|
+
[name]: {
|
|
552
|
+
...multiConfig.providers[name] ?? { key: "" },
|
|
553
|
+
model: arg
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
saveMultiConfig(updated);
|
|
558
|
+
console.log(GREEN(` \u2713 Model set to ${PURPLE2(arg)} for ${name}
|
|
559
|
+
`));
|
|
560
|
+
return { multiConfig: updated, config: resolveConfig(updated) };
|
|
561
|
+
}
|
|
562
|
+
console.log(RED(` \u2717 Unknown command: "${cmd}"`));
|
|
563
|
+
console.log(GRAY(` Type /help to see available commands.
|
|
564
|
+
`));
|
|
565
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
566
|
+
}
|
|
346
567
|
async function readLine(prompt) {
|
|
347
568
|
return new Promise((resolve4) => {
|
|
348
569
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -357,60 +578,93 @@ async function readLine(prompt) {
|
|
|
357
578
|
});
|
|
358
579
|
});
|
|
359
580
|
}
|
|
581
|
+
function statusLine(config) {
|
|
582
|
+
const providerLabel = PROVIDERS[config.providerName]?.label ?? config.providerName;
|
|
583
|
+
return PURPLE2(" \u25C6") + GRAY(` ${providerLabel}`) + CYAN(` [${config.model}]`) + GRAY(" type /help for commands\n");
|
|
584
|
+
}
|
|
360
585
|
async function main() {
|
|
361
586
|
const args = process.argv.slice(2);
|
|
362
587
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
363
588
|
console.log(BANNER);
|
|
364
|
-
console.log(
|
|
365
|
-
console.log(" " +
|
|
366
|
-
console.log(" " +
|
|
589
|
+
console.log(PURPLE2(" Usage:"));
|
|
590
|
+
console.log(" " + PURPLE2("sharkcode") + GRAY(" \u2014 interactive mode"));
|
|
591
|
+
console.log(" " + PURPLE2("sharkcode") + YELLOW(' "prompt"') + GRAY(" \u2014 single-shot mode"));
|
|
592
|
+
console.log("\n " + PURPLE2("Slash commands (in interactive mode):"));
|
|
593
|
+
printSlashHelp();
|
|
367
594
|
return;
|
|
368
595
|
}
|
|
369
596
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
370
|
-
console.log("sharkcode v0.
|
|
597
|
+
console.log("sharkcode v0.3.0");
|
|
371
598
|
return;
|
|
372
599
|
}
|
|
373
|
-
const config = loadConfig();
|
|
374
600
|
if (args.length > 0) {
|
|
375
|
-
|
|
601
|
+
const mc = readMultiConfig();
|
|
602
|
+
const config2 = resolveConfig(mc);
|
|
603
|
+
if (!config2.apiKey) {
|
|
604
|
+
console.error(RED("\u274C No API key found for provider: " + config2.providerName));
|
|
605
|
+
console.error(GRAY(" Run sharkcode in interactive mode and use /key <your-key>"));
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
console.log(PURPLE2("\n\u{1F988} SharkCode") + GRAY(` | ${PROVIDERS[config2.providerName]?.label ?? config2.providerName} | model: ${config2.model}
|
|
376
609
|
`));
|
|
377
610
|
const messages2 = [{ role: "user", content: args.join(" ") }];
|
|
378
|
-
await runAgent(messages2,
|
|
611
|
+
await runAgent(messages2, config2);
|
|
379
612
|
return;
|
|
380
613
|
}
|
|
381
614
|
console.log(BANNER);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
);
|
|
615
|
+
let multiConfig = readMultiConfig();
|
|
616
|
+
let config = resolveConfig(multiConfig);
|
|
617
|
+
console.log(statusLine(config));
|
|
618
|
+
if (!config.apiKey) {
|
|
619
|
+
console.log(
|
|
620
|
+
YELLOW(" \u26A0 No API key configured for ") + PURPLE2(PROVIDERS[config.providerName]?.label ?? config.providerName) + YELLOW(".")
|
|
621
|
+
);
|
|
622
|
+
console.log(GRAY(" Use /key <your-api-key> to set it, or /provider to switch.\n"));
|
|
623
|
+
}
|
|
385
624
|
let messages = [];
|
|
386
625
|
while (true) {
|
|
387
|
-
const input = await readLine(
|
|
626
|
+
const input = await readLine(PURPLE2("\n\u25C6 "));
|
|
388
627
|
if (input === null) {
|
|
389
|
-
console.log(
|
|
628
|
+
console.log(GRAY("\nBye! \u{1F988}"));
|
|
390
629
|
break;
|
|
391
630
|
}
|
|
392
631
|
const trimmed = input.trim();
|
|
393
632
|
if (!trimmed) continue;
|
|
394
|
-
if (trimmed === "exit" || trimmed === "quit"
|
|
395
|
-
console.log(
|
|
633
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
634
|
+
console.log(GRAY("Bye! \u{1F988}"));
|
|
396
635
|
break;
|
|
397
636
|
}
|
|
637
|
+
if (trimmed.startsWith("/")) {
|
|
638
|
+
const result = handleSlash(trimmed, multiConfig);
|
|
639
|
+
if (!result) continue;
|
|
640
|
+
multiConfig = result.multiConfig;
|
|
641
|
+
config = result.config;
|
|
642
|
+
if (result.clearHistory) messages = [];
|
|
643
|
+
if (result.exit) break;
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (!config.apiKey) {
|
|
647
|
+
console.log(
|
|
648
|
+
YELLOW("\n \u26A0 No API key for ") + PURPLE2(PROVIDERS[config.providerName]?.label ?? config.providerName) + YELLOW(". Set it with /key <your-api-key>\n")
|
|
649
|
+
);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
398
652
|
messages.push({ role: "user", content: trimmed });
|
|
399
653
|
try {
|
|
400
654
|
messages = await runAgent(messages, config);
|
|
401
655
|
} catch (err) {
|
|
402
656
|
const error = err;
|
|
403
|
-
console.error(
|
|
657
|
+
console.error(RED(`
|
|
404
658
|
\u274C Error: ${error.message}`));
|
|
405
659
|
if (error.message?.includes("401") || error.message?.includes("Unauthorized")) {
|
|
406
|
-
console.error(
|
|
660
|
+
console.error(YELLOW(" Check your API key with /key <your-api-key>"));
|
|
407
661
|
}
|
|
408
662
|
messages = messages.slice(0, -1);
|
|
409
663
|
}
|
|
410
664
|
}
|
|
411
665
|
}
|
|
412
666
|
main().catch((err) => {
|
|
413
|
-
console.error(
|
|
667
|
+
console.error(RED(`
|
|
414
668
|
\u274C Fatal: ${err.message}`));
|
|
415
669
|
process.exit(1);
|
|
416
670
|
});
|