sharkcode 0.3.0 → 0.3.2
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/dist/cli.mjs +167 -159
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import chalk3 from "chalk";
|
|
5
5
|
import * as readline2 from "readline";
|
|
6
|
+
import { select, input } from "@inquirer/prompts";
|
|
6
7
|
|
|
7
8
|
// src/config.ts
|
|
8
9
|
import { parse } from "smol-toml";
|
|
@@ -438,177 +439,179 @@ function renderWord(word, padLeft = 2) {
|
|
|
438
439
|
return rows;
|
|
439
440
|
}
|
|
440
441
|
var BANNER = ["", ...renderWord("shark", 2), "", ...renderWord("code", 8), ""].join("\n");
|
|
441
|
-
function
|
|
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
|
-
}
|
|
442
|
+
async function showCommandMenu(multiConfig) {
|
|
456
443
|
console.log();
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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) };
|
|
444
|
+
try {
|
|
445
|
+
const action = await select({
|
|
446
|
+
message: PURPLE2("\u25C6 \u9009\u62E9\u64CD\u4F5C"),
|
|
447
|
+
choices: [
|
|
448
|
+
{ name: "\u{1F50C} \u5207\u6362 / \u914D\u7F6E Provider", value: "provider" },
|
|
449
|
+
{ name: "\u{1F5D1}\uFE0F \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", value: "clear" },
|
|
450
|
+
{ name: "\u{1F6AA} \u9000\u51FA", value: "exit" }
|
|
451
|
+
]
|
|
452
|
+
});
|
|
453
|
+
switch (action) {
|
|
454
|
+
case "provider":
|
|
455
|
+
return showSetupFlow(multiConfig);
|
|
456
|
+
case "clear":
|
|
457
|
+
console.log(GRAY("\n \u2713 \u5BF9\u8BDD\u5DF2\u6E05\u7A7A\n"));
|
|
458
|
+
return { multiConfig, config: resolveConfig(multiConfig), clearHistory: true };
|
|
459
|
+
case "exit":
|
|
460
|
+
console.log(GRAY("\nBye! \u{1F988}"));
|
|
461
|
+
return { multiConfig, config: resolveConfig(multiConfig), exit: true };
|
|
494
462
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
463
|
+
} catch {
|
|
464
|
+
console.log(GRAY("\n \u53D6\u6D88\n"));
|
|
465
|
+
}
|
|
466
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
467
|
+
}
|
|
468
|
+
async function showSetupFlow(multiConfig) {
|
|
469
|
+
console.log();
|
|
470
|
+
try {
|
|
471
|
+
const providerChoices = Object.entries(PROVIDERS).map(([id, meta]) => {
|
|
472
|
+
const hasKey = !!multiConfig.providers[id]?.key;
|
|
473
|
+
const badge = hasKey ? GREEN("\u2713 \u5DF2\u914D\u7F6E") : YELLOW("\u2717 \u672A\u914D\u7F6E");
|
|
474
|
+
return { name: `${meta.label} ${badge}`, value: id };
|
|
475
|
+
});
|
|
476
|
+
const selectedProvider = await select({
|
|
477
|
+
message: PURPLE2("\u25C6 \u9009\u62E9 Provider"),
|
|
478
|
+
choices: providerChoices,
|
|
479
|
+
default: multiConfig.activeProvider
|
|
480
|
+
});
|
|
481
|
+
const currentKey = multiConfig.providers[selectedProvider]?.key ?? "";
|
|
482
|
+
const hint = currentKey ? GRAY("(\u56DE\u8F66\u4FDD\u7559 " + currentKey.slice(0, 6) + "\u2022\u2022\u2022)") : GRAY("(\u5FC5\u586B)");
|
|
483
|
+
const rawKey = await input({
|
|
484
|
+
message: PURPLE2("\u25C6 API Key ") + hint,
|
|
485
|
+
default: currentKey || void 0
|
|
486
|
+
});
|
|
487
|
+
const newKey = rawKey.trim() || currentKey;
|
|
488
|
+
let updated = { ...multiConfig, activeProvider: selectedProvider };
|
|
489
|
+
if (newKey) {
|
|
490
|
+
updated = {
|
|
491
|
+
...updated,
|
|
492
|
+
providers: {
|
|
493
|
+
...updated.providers,
|
|
494
|
+
[selectedProvider]: {
|
|
495
|
+
model: PROVIDERS[selectedProvider]?.defaultModel ?? "",
|
|
496
|
+
...updated.providers[selectedProvider] ?? {},
|
|
497
|
+
key: newKey
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
499
501
|
}
|
|
500
|
-
const updated = { ...multiConfig, activeProvider: name };
|
|
501
502
|
saveMultiConfig(updated);
|
|
502
503
|
const newConfig = resolveConfig(updated);
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
\u2713
|
|
506
|
-
);
|
|
504
|
+
const keyMsg = newKey && newKey !== currentKey ? "\uFF0CAPI Key \u5DF2\u4FDD\u5B58" : "";
|
|
505
|
+
console.log(GREEN(`
|
|
506
|
+
\u2713 \u5DF2\u5207\u6362\u5230 ${PROVIDERS[selectedProvider].label}${keyMsg}`) + "\n");
|
|
507
507
|
if (!newConfig.apiKey) {
|
|
508
|
-
console.log(YELLOW(
|
|
509
|
-
`));
|
|
510
|
-
} else {
|
|
511
|
-
console.log();
|
|
508
|
+
console.log(YELLOW(" \u26A0 \u8FD8\u672A\u586B\u5199 API Key\uFF0C\u65E0\u6CD5\u53D1\u9001\u6D88\u606F\n"));
|
|
512
509
|
}
|
|
513
510
|
return { multiConfig: updated, config: newConfig };
|
|
511
|
+
} catch {
|
|
512
|
+
console.log(GRAY("\n \u53D6\u6D88\n"));
|
|
513
|
+
return { multiConfig, config: resolveConfig(multiConfig) };
|
|
514
514
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
515
|
+
}
|
|
516
|
+
async function readLineRaw(promptStr) {
|
|
517
|
+
process.stdout.write(promptStr);
|
|
518
|
+
return new Promise((resolve4) => {
|
|
519
|
+
let buffer = "";
|
|
520
|
+
let finished = false;
|
|
521
|
+
const finish = (value) => {
|
|
522
|
+
if (finished) return;
|
|
523
|
+
finished = true;
|
|
524
|
+
process.stdin.removeListener("data", onData);
|
|
525
|
+
try {
|
|
526
|
+
process.stdin.setRawMode(false);
|
|
527
|
+
} catch {
|
|
529
528
|
}
|
|
529
|
+
process.stdin.pause();
|
|
530
|
+
resolve4(value);
|
|
530
531
|
};
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
532
|
+
const onData = (chunk) => {
|
|
533
|
+
const str = chunk.toString("utf8");
|
|
534
|
+
if (str.startsWith("\x1B")) return;
|
|
535
|
+
for (const ch of str) {
|
|
536
|
+
const code = ch.codePointAt(0);
|
|
537
|
+
if (code === 3 || code === 4) {
|
|
538
|
+
process.stdout.write("\n");
|
|
539
|
+
finish(null);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (code === 13 || code === 10) {
|
|
543
|
+
process.stdout.write("\n");
|
|
544
|
+
finish(buffer);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (code === 127 || code === 8) {
|
|
548
|
+
if (buffer.length > 0) {
|
|
549
|
+
const chars = [...buffer];
|
|
550
|
+
chars.pop();
|
|
551
|
+
buffer = chars.join("");
|
|
552
|
+
process.stdout.write("\b \b");
|
|
553
|
+
}
|
|
554
|
+
continue;
|
|
554
555
|
}
|
|
556
|
+
if (code < 32) continue;
|
|
557
|
+
if (ch === "/" && buffer === "") {
|
|
558
|
+
process.stdout.write("\n");
|
|
559
|
+
finish("/");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
buffer += ch;
|
|
563
|
+
process.stdout.write(ch);
|
|
555
564
|
}
|
|
556
565
|
};
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
resolve4(answer);
|
|
575
|
-
});
|
|
576
|
-
rl.on("close", () => {
|
|
577
|
-
if (!answered) resolve4(null);
|
|
578
|
-
});
|
|
566
|
+
try {
|
|
567
|
+
process.stdin.setRawMode(true);
|
|
568
|
+
process.stdin.resume();
|
|
569
|
+
process.stdin.on("data", onData);
|
|
570
|
+
} catch {
|
|
571
|
+
finished = true;
|
|
572
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
573
|
+
let answered = false;
|
|
574
|
+
rl.question(promptStr, (answer) => {
|
|
575
|
+
answered = true;
|
|
576
|
+
rl.close();
|
|
577
|
+
resolve4(answer);
|
|
578
|
+
});
|
|
579
|
+
rl.on("close", () => {
|
|
580
|
+
if (!answered) resolve4(null);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
579
583
|
});
|
|
580
584
|
}
|
|
581
585
|
function statusLine(config) {
|
|
582
|
-
const
|
|
583
|
-
return PURPLE2(" \u25C6") + GRAY(` ${
|
|
586
|
+
const label = PROVIDERS[config.providerName]?.label ?? config.providerName;
|
|
587
|
+
return PURPLE2(" \u25C6") + GRAY(` ${label}`) + CYAN(` [${config.model}]`) + GRAY(" \u8F93\u5165 / \u8C03\u51FA\u6307\u4EE4\u83DC\u5355\n");
|
|
584
588
|
}
|
|
585
589
|
async function main() {
|
|
586
590
|
const args = process.argv.slice(2);
|
|
587
591
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
588
592
|
console.log(BANNER);
|
|
589
593
|
console.log(PURPLE2(" Usage:"));
|
|
590
|
-
console.log(" " + PURPLE2("sharkcode") + GRAY("
|
|
591
|
-
console.log(" " + PURPLE2("sharkcode") + YELLOW(' "prompt"') + GRAY("
|
|
592
|
-
console.log("\n
|
|
593
|
-
printSlashHelp();
|
|
594
|
+
console.log(" " + PURPLE2("sharkcode") + GRAY(" \u2014 \u4EA4\u4E92\u6A21\u5F0F\uFF08\u76F4\u63A5\u542F\u52A8\uFF09"));
|
|
595
|
+
console.log(" " + PURPLE2("sharkcode") + YELLOW(' "prompt"') + GRAY(" \u2014 \u5355\u6B21\u6267\u884C"));
|
|
596
|
+
console.log(GRAY("\n \u4EA4\u4E92\u6A21\u5F0F\u5185\u8F93\u5165 / \u8C03\u51FA\u6307\u4EE4\u83DC\u5355\n"));
|
|
594
597
|
return;
|
|
595
598
|
}
|
|
596
599
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
597
|
-
console.log("sharkcode v0.3.
|
|
600
|
+
console.log("sharkcode v0.3.2");
|
|
598
601
|
return;
|
|
599
602
|
}
|
|
600
603
|
if (args.length > 0) {
|
|
601
604
|
const mc = readMultiConfig();
|
|
602
605
|
const config2 = resolveConfig(mc);
|
|
603
606
|
if (!config2.apiKey) {
|
|
604
|
-
console.error(RED("\u274C
|
|
605
|
-
console.error(GRAY(" Run sharkcode in interactive mode and use /key <your-key>"));
|
|
607
|
+
console.error(RED("\u274C \u672A\u914D\u7F6E API Key\u3002\u8BF7\u5148\u8FD0\u884C sharkcode \u5E76\u8F93\u5165 / \u914D\u7F6E Provider\u3002"));
|
|
606
608
|
process.exit(1);
|
|
607
609
|
}
|
|
608
|
-
console.log(
|
|
609
|
-
`
|
|
610
|
-
|
|
611
|
-
|
|
610
|
+
console.log(
|
|
611
|
+
PURPLE2("\n\u{1F988} SharkCode") + GRAY(` | ${PROVIDERS[config2.providerName]?.label ?? config2.providerName} | ${config2.model}
|
|
612
|
+
`)
|
|
613
|
+
);
|
|
614
|
+
await runAgent([{ role: "user", content: args.join(" ") }], config2);
|
|
612
615
|
return;
|
|
613
616
|
}
|
|
614
617
|
console.log(BANNER);
|
|
@@ -617,54 +620,59 @@ async function main() {
|
|
|
617
620
|
console.log(statusLine(config));
|
|
618
621
|
if (!config.apiKey) {
|
|
619
622
|
console.log(
|
|
620
|
-
YELLOW(" \u26A0
|
|
623
|
+
YELLOW(" \u26A0 \u5C1A\u672A\u914D\u7F6E API Key\u3002") + GRAY("\u8F93\u5165 / \u7136\u540E\u9009\u62E9\u300C\u5207\u6362 / \u914D\u7F6E Provider\u300D\n")
|
|
621
624
|
);
|
|
622
|
-
console.log(GRAY(" Use /key <your-api-key> to set it, or /provider to switch.\n"));
|
|
623
625
|
}
|
|
624
626
|
let messages = [];
|
|
625
627
|
while (true) {
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
+
const raw = await readLineRaw(PURPLE2("\n\u25C6 "));
|
|
629
|
+
if (raw === null) {
|
|
628
630
|
console.log(GRAY("\nBye! \u{1F988}"));
|
|
629
631
|
break;
|
|
630
632
|
}
|
|
631
|
-
const trimmed =
|
|
633
|
+
const trimmed = raw.trim();
|
|
632
634
|
if (!trimmed) continue;
|
|
633
635
|
if (trimmed === "exit" || trimmed === "quit") {
|
|
634
636
|
console.log(GRAY("Bye! \u{1F988}"));
|
|
635
637
|
break;
|
|
636
638
|
}
|
|
639
|
+
if (trimmed === "/") {
|
|
640
|
+
const r = await showCommandMenu(multiConfig);
|
|
641
|
+
multiConfig = r.multiConfig;
|
|
642
|
+
config = r.config;
|
|
643
|
+
if (r.clearHistory) messages = [];
|
|
644
|
+
if (r.exit) break;
|
|
645
|
+
console.log(statusLine(config));
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (trimmed === "/provider" || trimmed.startsWith("/provider ") || trimmed === "/model" || trimmed.startsWith("/model ") || trimmed === "/key" || trimmed.startsWith("/key ")) {
|
|
649
|
+
const r = await showSetupFlow(multiConfig);
|
|
650
|
+
multiConfig = r.multiConfig;
|
|
651
|
+
config = r.config;
|
|
652
|
+
if (r.exit) break;
|
|
653
|
+
console.log(statusLine(config));
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
637
656
|
if (trimmed.startsWith("/")) {
|
|
638
|
-
|
|
639
|
-
if (!result) continue;
|
|
640
|
-
multiConfig = result.multiConfig;
|
|
641
|
-
config = result.config;
|
|
642
|
-
if (result.clearHistory) messages = [];
|
|
643
|
-
if (result.exit) break;
|
|
657
|
+
console.log(GRAY(" \u672A\u77E5\u547D\u4EE4\u3002\u8F93\u5165 / \u8C03\u51FA\u6307\u4EE4\u83DC\u5355\n"));
|
|
644
658
|
continue;
|
|
645
659
|
}
|
|
646
660
|
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
|
-
);
|
|
661
|
+
console.log(YELLOW(" \u26A0 \u8FD8\u672A\u586B\u5199 API Key\u3002\u8F93\u5165 / \u2192 \u5207\u6362 / \u914D\u7F6E Provider\n"));
|
|
650
662
|
continue;
|
|
651
663
|
}
|
|
652
664
|
messages.push({ role: "user", content: trimmed });
|
|
653
665
|
try {
|
|
654
666
|
messages = await runAgent(messages, config);
|
|
655
667
|
} catch (err) {
|
|
656
|
-
const error = err;
|
|
657
668
|
console.error(RED(`
|
|
658
|
-
\u274C
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
messages = messages.slice(0, -1);
|
|
669
|
+
\u274C ${String(err)}
|
|
670
|
+
`));
|
|
671
|
+
messages.pop();
|
|
663
672
|
}
|
|
664
673
|
}
|
|
665
674
|
}
|
|
666
675
|
main().catch((err) => {
|
|
667
|
-
console.error(
|
|
668
|
-
\u274C Fatal: ${err.message}`));
|
|
676
|
+
console.error(chalk3.red(`Fatal: ${String(err)}`));
|
|
669
677
|
process.exit(1);
|
|
670
678
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharkcode",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Local First, open-source AI Coding Agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@ai-sdk/openai": "^3.0.48",
|
|
29
|
+
"@inquirer/prompts": "^8.3.2",
|
|
29
30
|
"ai": "^6.0.141",
|
|
30
31
|
"chalk": "^5.6.2",
|
|
31
32
|
"smol-toml": "^1.6.1",
|