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.
Files changed (2) hide show
  1. package/dist/cli.mjs +167 -159
  2. 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 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
- }
442
+ async function showCommandMenu(multiConfig) {
456
443
  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) };
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
- 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) };
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
- console.log(
504
- GREEN(`
505
- \u2713 Switched to ${PROVIDERS[name].label}`) + GRAY(` (${name}) \u2014 model: ${newConfig.model}`)
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(` \u26A0 No API key set. Use /key <your-key> to configure it.
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
- 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
- }
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
- 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
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
- 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
- }
567
- async function readLine(prompt) {
568
- return new Promise((resolve4) => {
569
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
570
- let answered = false;
571
- rl.question(prompt, (answer) => {
572
- answered = true;
573
- rl.close();
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 providerLabel = PROVIDERS[config.providerName]?.label ?? config.providerName;
583
- return PURPLE2(" \u25C6") + GRAY(` ${providerLabel}`) + CYAN(` [${config.model}]`) + GRAY(" type /help for commands\n");
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(" \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();
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.0");
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 No API key found for provider: " + config2.providerName));
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(PURPLE2("\n\u{1F988} SharkCode") + GRAY(` | ${PROVIDERS[config2.providerName]?.label ?? config2.providerName} | model: ${config2.model}
609
- `));
610
- const messages2 = [{ role: "user", content: args.join(" ") }];
611
- await runAgent(messages2, config2);
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 No API key configured for ") + PURPLE2(PROVIDERS[config.providerName]?.label ?? config.providerName) + YELLOW(".")
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 input = await readLine(PURPLE2("\n\u25C6 "));
627
- if (input === null) {
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 = input.trim();
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
- 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;
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 Error: ${error.message}`));
659
- if (error.message?.includes("401") || error.message?.includes("Unauthorized")) {
660
- console.error(YELLOW(" Check your API key with /key <your-api-key>"));
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(RED(`
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.0",
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",