sharkcode 0.2.1 → 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 +261 -40
- 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,7 +266,8 @@ 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
|
}
|
|
@@ -348,6 +405,11 @@ function truncate(str, max) {
|
|
|
348
405
|
|
|
349
406
|
// src/cli.ts
|
|
350
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;
|
|
351
413
|
var GLYPHS = {
|
|
352
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]],
|
|
353
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]],
|
|
@@ -376,6 +438,132 @@ function renderWord(word, padLeft = 2) {
|
|
|
376
438
|
return rows;
|
|
377
439
|
}
|
|
378
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
|
+
}
|
|
379
567
|
async function readLine(prompt) {
|
|
380
568
|
return new Promise((resolve4) => {
|
|
381
569
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -390,60 +578,93 @@ async function readLine(prompt) {
|
|
|
390
578
|
});
|
|
391
579
|
});
|
|
392
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
|
+
}
|
|
393
585
|
async function main() {
|
|
394
586
|
const args = process.argv.slice(2);
|
|
395
587
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
396
588
|
console.log(BANNER);
|
|
397
589
|
console.log(PURPLE2(" Usage:"));
|
|
398
|
-
console.log(" " + PURPLE2("sharkcode") +
|
|
399
|
-
console.log(" " + PURPLE2("sharkcode") +
|
|
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();
|
|
400
594
|
return;
|
|
401
595
|
}
|
|
402
596
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
403
|
-
console.log("sharkcode v0.
|
|
597
|
+
console.log("sharkcode v0.3.0");
|
|
404
598
|
return;
|
|
405
599
|
}
|
|
406
|
-
const config = loadConfig();
|
|
407
600
|
if (args.length > 0) {
|
|
408
|
-
|
|
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}
|
|
409
609
|
`));
|
|
410
610
|
const messages2 = [{ role: "user", content: args.join(" ") }];
|
|
411
|
-
await runAgent(messages2,
|
|
611
|
+
await runAgent(messages2, config2);
|
|
412
612
|
return;
|
|
413
613
|
}
|
|
414
614
|
console.log(BANNER);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
);
|
|
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
|
+
}
|
|
418
624
|
let messages = [];
|
|
419
625
|
while (true) {
|
|
420
626
|
const input = await readLine(PURPLE2("\n\u25C6 "));
|
|
421
627
|
if (input === null) {
|
|
422
|
-
console.log(
|
|
628
|
+
console.log(GRAY("\nBye! \u{1F988}"));
|
|
423
629
|
break;
|
|
424
630
|
}
|
|
425
631
|
const trimmed = input.trim();
|
|
426
632
|
if (!trimmed) continue;
|
|
427
|
-
if (trimmed === "exit" || trimmed === "quit"
|
|
428
|
-
console.log(
|
|
633
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
634
|
+
console.log(GRAY("Bye! \u{1F988}"));
|
|
429
635
|
break;
|
|
430
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
|
+
}
|
|
431
652
|
messages.push({ role: "user", content: trimmed });
|
|
432
653
|
try {
|
|
433
654
|
messages = await runAgent(messages, config);
|
|
434
655
|
} catch (err) {
|
|
435
656
|
const error = err;
|
|
436
|
-
console.error(
|
|
657
|
+
console.error(RED(`
|
|
437
658
|
\u274C Error: ${error.message}`));
|
|
438
659
|
if (error.message?.includes("401") || error.message?.includes("Unauthorized")) {
|
|
439
|
-
console.error(
|
|
660
|
+
console.error(YELLOW(" Check your API key with /key <your-api-key>"));
|
|
440
661
|
}
|
|
441
662
|
messages = messages.slice(0, -1);
|
|
442
663
|
}
|
|
443
664
|
}
|
|
444
665
|
}
|
|
445
666
|
main().catch((err) => {
|
|
446
|
-
console.error(
|
|
667
|
+
console.error(RED(`
|
|
447
668
|
\u274C Fatal: ${err.message}`));
|
|
448
669
|
process.exit(1);
|
|
449
670
|
});
|