stackai 0.1.1 → 0.1.3

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.js +250 -56
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -462,6 +462,9 @@ async function writeConfig(config) {
462
462
  await fs2.mkdir(CONFIG_DIR, { recursive: true });
463
463
  await fs2.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
464
464
  }
465
+ async function clearConfig() {
466
+ await fs2.rm(CONFIG_PATH, { force: true });
467
+ }
465
468
 
466
469
  // src/api.ts
467
470
  var ApiClient = class {
@@ -498,36 +501,86 @@ import { Box as Box2, Text as Text2, useApp } from "ink";
498
501
  import Spinner from "ink-spinner";
499
502
 
500
503
  // src/ui/format.ts
501
- function describe(step) {
502
- const p = step.args.path ?? step.args.dir ?? "";
504
+ var ACCENT = "#e8ff47";
505
+ var MAX_PREVIEW = 6;
506
+ function splitLines(s) {
507
+ return s.replace(/\n+$/, "").split("\n");
508
+ }
509
+ function clip(s, n = 76) {
510
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
511
+ }
512
+ function preview(lines, prefix, color) {
513
+ const out = lines.slice(0, MAX_PREVIEW).map((l) => ({ kind: "sub", color, text: `${prefix} ${clip(l)}` }));
514
+ if (lines.length > MAX_PREVIEW) {
515
+ out.push({
516
+ kind: "sub",
517
+ color: "gray",
518
+ text: `\u2026 ${lines.length - MAX_PREVIEW} more lines`
519
+ });
520
+ }
521
+ return out;
522
+ }
523
+ function toolEntries(step) {
524
+ const args = step.args;
525
+ const path3 = String(args.path ?? args.dir ?? "");
503
526
  switch (step.name) {
527
+ case "write_file": {
528
+ const lines = splitLines(String(args.content ?? ""));
529
+ return [
530
+ { kind: "tool", color: ACCENT, label: `Write(${path3})` },
531
+ { kind: "sub", color: "green", text: `+${lines.length} lines` },
532
+ ...preview(lines, "+", "green")
533
+ ];
534
+ }
535
+ case "edit_file": {
536
+ const removed = splitLines(String(args.oldStr ?? ""));
537
+ const added = splitLines(String(args.newStr ?? ""));
538
+ return [
539
+ { kind: "tool", color: ACCENT, label: `Edit(${path3})` },
540
+ {
541
+ kind: "sub",
542
+ color: "gray",
543
+ text: `+${added.length} -${removed.length} lines`
544
+ },
545
+ ...preview(removed, "-", "red"),
546
+ ...preview(added, "+", "green")
547
+ ];
548
+ }
504
549
  case "read_file":
505
- return `Reading ${p}`;
506
- case "write_file":
507
- return `Writing ${p}`;
508
- case "edit_file":
509
- return `Editing ${p}`;
550
+ return [{ kind: "tool", color: ACCENT, label: `Read(${path3})` }];
510
551
  case "list_files":
511
- return `Listing ${p || "."}`;
552
+ return [{ kind: "tool", color: ACCENT, label: `List(${path3 || "."})` }];
512
553
  case "create_dir":
513
- return `Creating ${p}/`;
554
+ return [{ kind: "tool", color: ACCENT, label: `Create(${path3}/)` }];
514
555
  default:
515
- return step.name;
556
+ return [{ kind: "tool", color: ACCENT, label: step.name }];
516
557
  }
517
558
  }
518
559
  function applyStep(entries, step) {
519
560
  if (step.type === "token") {
520
561
  const last = entries[entries.length - 1];
521
562
  if (last && last.kind === "text") {
522
- return [...entries.slice(0, -1), { kind: "text", text: last.text + step.text }];
563
+ return [
564
+ ...entries.slice(0, -1),
565
+ { kind: "text", text: last.text + step.text }
566
+ ];
523
567
  }
524
568
  return [...entries, { kind: "text", text: step.text }];
525
569
  }
526
570
  if (step.type === "tool_call") {
527
- return [...entries, { kind: "tool", color: "gray", text: describe(step) }];
528
- }
529
- if (step.type === "tool_result" && !step.ok) {
530
- return [...entries, { kind: "tool", color: "red", text: step.detail }];
571
+ return [...entries, ...toolEntries(step)];
572
+ }
573
+ if (step.type === "tool_result") {
574
+ if (!step.ok) {
575
+ return [
576
+ ...entries,
577
+ { kind: "sub", color: "red", text: `\u2717 ${step.detail}` }
578
+ ];
579
+ }
580
+ if (step.name === "read_file") {
581
+ const n = step.detail ? splitLines(step.detail).length : 0;
582
+ return [...entries, { kind: "sub", color: "gray", text: `read ${n} lines` }];
583
+ }
531
584
  }
532
585
  return entries;
533
586
  }
@@ -536,12 +589,18 @@ function applyStep(entries, step) {
536
589
  import { Box, Text } from "ink";
537
590
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
538
591
  function EntryLines({ entries }) {
539
- return /* @__PURE__ */ jsx(Fragment, { children: entries.map(
540
- (e, i) => e.kind === "tool" ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: e.color, children: [
541
- "\u2192 ",
542
- e.text
543
- ] }) }, i) : /* @__PURE__ */ jsx(Box, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { children: e.text }) }, i)
544
- ) });
592
+ return /* @__PURE__ */ jsx(Fragment, { children: entries.map((e, i) => {
593
+ if (e.kind === "tool") {
594
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: e.color, bold: true, children: [
595
+ "\u25CF ",
596
+ e.label
597
+ ] }) }, i);
598
+ }
599
+ if (e.kind === "sub") {
600
+ return /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: e.color, children: e.text }) }, i);
601
+ }
602
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { children: e.text }) }, i);
603
+ }) });
545
604
  }
546
605
 
547
606
  // src/ui/run-view.tsx
@@ -594,7 +653,12 @@ import { Box as Box3, Text as Text3, useApp as useApp2 } from "ink";
594
653
  import Spinner2 from "ink-spinner";
595
654
  import TextInput from "ink-text-input";
596
655
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
597
- function Interactive({ session, cwd }) {
656
+ var ACCENT2 = "#e8ff47";
657
+ var LOGO = [
658
+ "\u2588\u2580\u2580 \u2580\u2588\u2580 \u2588\u2580\u2588 \u2588\u2580\u2580 \u2588\u2584\u2580 \u2588\u2580\u2588 \u2588",
659
+ "\u2584\u2584\u2588 \u2588 \u2588\u2580\u2588 \u2588\u2584\u2584 \u2588\u2580\u2584 \u2588\u2580\u2588 \u2588"
660
+ ];
661
+ function Interactive({ session, cwd, version }) {
598
662
  const { exit } = useApp2();
599
663
  const [history, setHistory] = useState2([]);
600
664
  const [input, setInput] = useState2("");
@@ -619,11 +683,10 @@ function Interactive({ session, cwd }) {
619
683
  setHistory((prev) => {
620
684
  const last = prev[prev.length - 1];
621
685
  if (!last || last.kind !== "agent") return prev;
622
- const updated = {
623
- kind: "agent",
624
- entries: applyStep(last.entries, step)
625
- };
626
- return [...prev.slice(0, -1), updated];
686
+ return [
687
+ ...prev.slice(0, -1),
688
+ { kind: "agent", entries: applyStep(last.entries, step) }
689
+ ];
627
690
  });
628
691
  };
629
692
  try {
@@ -635,19 +698,37 @@ function Interactive({ session, cwd }) {
635
698
  }
636
699
  }
637
700
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingY: 1, children: [
638
- /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
639
- /* @__PURE__ */ jsxs3(Text3, { color: "#e8ff47", bold: true, children: [
640
- "StackAI",
641
- " "
642
- ] }),
643
- /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
644
- "\u2014 ",
645
- cwd
646
- ] })
647
- ] }),
701
+ /* @__PURE__ */ jsxs3(
702
+ Box3,
703
+ {
704
+ flexDirection: "column",
705
+ borderStyle: "round",
706
+ borderColor: ACCENT2,
707
+ paddingX: 2,
708
+ paddingY: 1,
709
+ children: [
710
+ LOGO.map((line, i) => /* @__PURE__ */ jsx3(Text3, { color: ACCENT2, bold: true, children: line }, i)),
711
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
712
+ "v",
713
+ version,
714
+ " \xB7 MiMo v2.5 Pro \xB7 1M context"
715
+ ] }) }),
716
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: cwd })
717
+ ]
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
721
+ "Describe what you want to build \xB7 type",
722
+ " ",
723
+ /* @__PURE__ */ jsx3(Text3, { color: ACCENT2, children: "/exit" }),
724
+ " to quit"
725
+ ] }) }),
648
726
  history.map(
649
727
  (block, i) => block.kind === "user" ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
650
- /* @__PURE__ */ jsx3(Text3, { color: "#e8ff47", children: "\u203A " }),
728
+ /* @__PURE__ */ jsxs3(Text3, { color: ACCENT2, bold: true, children: [
729
+ "\u203A",
730
+ " "
731
+ ] }),
651
732
  /* @__PURE__ */ jsx3(Text3, { children: block.text })
652
733
  ] }, i) : /* @__PURE__ */ jsx3(EntryLines, { entries: block.entries }, i)
653
734
  ),
@@ -655,38 +736,118 @@ function Interactive({ session, cwd }) {
655
736
  "Error: ",
656
737
  error
657
738
  ] }) }),
658
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: busy ? /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
659
- /* @__PURE__ */ jsx3(Spinner2, { type: "dots" }),
660
- " working\u2026"
661
- ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
662
- /* @__PURE__ */ jsx3(Text3, { color: "#e8ff47", children: "\u203A " }),
663
- /* @__PURE__ */ jsx3(
664
- TextInput,
739
+ /* @__PURE__ */ jsx3(
740
+ Box3,
741
+ {
742
+ marginTop: 1,
743
+ borderStyle: "round",
744
+ borderColor: busy ? "gray" : ACCENT2,
745
+ paddingX: 1,
746
+ children: busy ? /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
747
+ /* @__PURE__ */ jsx3(Spinner2, { type: "dots" }),
748
+ " working\u2026"
749
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
750
+ /* @__PURE__ */ jsx3(Text3, { color: ACCENT2, children: "\u203A " }),
751
+ /* @__PURE__ */ jsx3(
752
+ TextInput,
753
+ {
754
+ value: input,
755
+ onChange: setInput,
756
+ onSubmit,
757
+ placeholder: "message StackAI\u2026"
758
+ }
759
+ )
760
+ ] })
761
+ }
762
+ )
763
+ ] });
764
+ }
765
+
766
+ // src/ui/login-view.tsx
767
+ import { useState as useState3 } from "react";
768
+ import { Box as Box4, Text as Text4, useApp as useApp3 } from "ink";
769
+ import TextInput2 from "ink-text-input";
770
+ import Spinner3 from "ink-spinner";
771
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
772
+ function LoginView() {
773
+ const { exit } = useApp3();
774
+ const [value, setValue] = useState3("");
775
+ const [phase, setPhase] = useState3("input");
776
+ const [message, setMessage] = useState3("");
777
+ async function submit(raw) {
778
+ const key = raw.trim();
779
+ if (!key) return;
780
+ setPhase("checking");
781
+ try {
782
+ await writeConfig({ apiKey: key, apiUrl: DEFAULT_API_URL });
783
+ const verify = await new ApiClient({
784
+ apiKey: key,
785
+ apiUrl: DEFAULT_API_URL
786
+ }).verify();
787
+ setMessage(`Logged in as @${verify.user.username} \xB7 ${verify.user.tier} tier`);
788
+ setPhase("done");
789
+ } catch (err) {
790
+ setMessage(err instanceof Error ? err.message : String(err));
791
+ setPhase("error");
792
+ } finally {
793
+ setTimeout(() => exit(), 60);
794
+ }
795
+ }
796
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingY: 1, children: [
797
+ /* @__PURE__ */ jsxs4(Box4, { children: [
798
+ /* @__PURE__ */ jsxs4(Text4, { color: "#e8ff47", bold: true, children: [
799
+ "StackAI",
800
+ " "
801
+ ] }),
802
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "login" })
803
+ ] }),
804
+ phase === "input" && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
805
+ /* @__PURE__ */ jsx4(Text4, { children: "API key " }),
806
+ /* @__PURE__ */ jsx4(Text4, { color: "#e8ff47", children: "\u203A " }),
807
+ /* @__PURE__ */ jsx4(
808
+ TextInput2,
665
809
  {
666
- value: input,
667
- onChange: setInput,
668
- onSubmit,
669
- placeholder: "type a message, /exit to quit"
810
+ value,
811
+ onChange: setValue,
812
+ onSubmit: submit,
813
+ mask: "*",
814
+ placeholder: "sk_live_..."
670
815
  }
671
816
  )
817
+ ] }),
818
+ phase === "checking" && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
819
+ /* @__PURE__ */ jsx4(Spinner3, { type: "dots" }),
820
+ " verifying\u2026"
821
+ ] }) }),
822
+ phase === "done" && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
823
+ "\u2713 ",
824
+ message
825
+ ] }) }),
826
+ phase === "error" && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
827
+ "\u2717 ",
828
+ message
672
829
  ] }) })
673
830
  ] });
674
831
  }
675
832
 
676
833
  // src/cli.tsx
677
- import { jsx as jsx4 } from "react/jsx-runtime";
678
- var VERSION = "0.1.0";
834
+ import { jsx as jsx5 } from "react/jsx-runtime";
835
+ var VERSION = "0.1.3";
679
836
  var HELP = `
680
837
  StackAI \u2014 AI coding agent in your terminal
681
838
 
682
839
  Usage
683
840
  $ stackai Start an interactive chat session
684
841
  $ stackai <prompt> Run the agent once, then exit
685
- $ stackai auth <api_key> Save your API key
686
- $ stackai auth <key> <url> ...with a custom API URL
842
+ $ stackai login Log in (prompts for your API key)
843
+ $ stackai login <api_key> Log in directly
687
844
  $ stackai whoami Show current user + usage
845
+ $ stackai logout Remove your saved API key
688
846
  $ stackai --help
689
847
  $ stackai --version
848
+
849
+ Advanced
850
+ $ stackai auth <key> [url] Save key with a custom API URL
690
851
  `;
691
852
  async function main() {
692
853
  const args = process.argv.slice(2);
@@ -699,6 +860,37 @@ async function main() {
699
860
  console.log(VERSION);
700
861
  return;
701
862
  }
863
+ if (first === "login") {
864
+ const key = args[1];
865
+ if (key) {
866
+ await writeConfig({ apiKey: key, apiUrl: DEFAULT_API_URL });
867
+ try {
868
+ const verify = await new ApiClient({
869
+ apiKey: key,
870
+ apiUrl: DEFAULT_API_URL
871
+ }).verify();
872
+ console.log(
873
+ `\u2713 Logged in as @${verify.user.username} \xB7 ${verify.user.tier} tier`
874
+ );
875
+ } catch (err) {
876
+ console.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`);
877
+ process.exitCode = 1;
878
+ }
879
+ return;
880
+ }
881
+ if (!process.stdin.isTTY) {
882
+ console.error("Run: stackai login <api_key>");
883
+ process.exitCode = 1;
884
+ return;
885
+ }
886
+ render(/* @__PURE__ */ jsx5(LoginView, {}));
887
+ return;
888
+ }
889
+ if (first === "logout") {
890
+ await clearConfig();
891
+ console.log("\u2713 Logged out");
892
+ return;
893
+ }
702
894
  if (first === "auth") {
703
895
  const key = args[1];
704
896
  if (!key) {
@@ -713,7 +905,7 @@ async function main() {
713
905
  }
714
906
  const config = await readConfig();
715
907
  if (!config) {
716
- console.error("Not authenticated. Run: stackai auth <api_key>");
908
+ console.error("Not logged in. Run: stackai login");
717
909
  process.exitCode = 1;
718
910
  return;
719
911
  }
@@ -744,11 +936,13 @@ async function main() {
744
936
  process.exitCode = 1;
745
937
  return;
746
938
  }
747
- render(/* @__PURE__ */ jsx4(Interactive, { session: runner.session(cwd), cwd }));
939
+ render(
940
+ /* @__PURE__ */ jsx5(Interactive, { session: runner.session(cwd), cwd, version: VERSION })
941
+ );
748
942
  return;
749
943
  }
750
944
  const prompt = args.join(" ");
751
- render(/* @__PURE__ */ jsx4(RunView, { runner, prompt, cwd }));
945
+ render(/* @__PURE__ */ jsx5(RunView, { runner, prompt, cwd }));
752
946
  }
753
947
  main().catch((err) => {
754
948
  console.error(err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackai",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "StackAI — AI coding agent in your terminal. Read, write, and edit code with AI.",
5
5
  "type": "module",
6
6
  "bin": {