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.
- package/dist/cli.js +250 -56
- 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
|
-
|
|
502
|
-
|
|
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 `
|
|
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 `
|
|
552
|
+
return [{ kind: "tool", color: ACCENT, label: `List(${path3 || "."})` }];
|
|
512
553
|
case "create_dir":
|
|
513
|
-
return `
|
|
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 [
|
|
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,
|
|
528
|
-
}
|
|
529
|
-
if (step.type === "tool_result"
|
|
530
|
-
|
|
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
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
623
|
-
|
|
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(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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__ */
|
|
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(
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
|
667
|
-
onChange:
|
|
668
|
-
onSubmit,
|
|
669
|
-
|
|
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
|
|
678
|
-
var VERSION = "0.1.
|
|
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
|
|
686
|
-
$ stackai
|
|
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
|
|
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(
|
|
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__ */
|
|
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));
|