@manix-cli/manix 0.1.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/LICENSE +21 -0
- package/README.md +74 -0
- package/bin/manix.js +9 -0
- package/dist/agent.js +194 -0
- package/dist/cli.js +123 -0
- package/dist/config.js +38 -0
- package/dist/manixmd.js +18 -0
- package/dist/markdown.js +35 -0
- package/dist/mcp.js +100 -0
- package/dist/openrouter.js +108 -0
- package/dist/permissions.js +19 -0
- package/dist/print.js +51 -0
- package/dist/prompts.js +47 -0
- package/dist/sessions.js +80 -0
- package/dist/skills.js +70 -0
- package/dist/slash.js +31 -0
- package/dist/theme.js +84 -0
- package/dist/tools/bash.js +44 -0
- package/dist/tools/fs-tools.js +119 -0
- package/dist/tools/index.js +20 -0
- package/dist/tools/search.js +106 -0
- package/dist/ui/App.js +345 -0
- package/dist/ui/Footer.js +38 -0
- package/dist/ui/InputBox.js +123 -0
- package/dist/ui/LogItem.js +82 -0
- package/dist/ui/Mascot.js +13 -0
- package/dist/ui/Onboarding.js +60 -0
- package/dist/ui/PermissionPrompt.js +32 -0
- package/dist/ui/Picker.js +69 -0
- package/dist/ui/SpinnerLine.js +65 -0
- package/package.json +49 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { MASCOT, color, lerpColor } from "../theme.js";
|
|
5
|
+
function Mascot() {
|
|
6
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginRight: 2, children: MASCOT.map((row, i) => {
|
|
7
|
+
const c = lerpColor(color.accent, color.amber, MASCOT.length > 1 ? i / (MASCOT.length - 1) : 0);
|
|
8
|
+
return /* @__PURE__ */ jsx(Text, { children: [...row].map((ch, j) => ch === "#" ? c("\u2588\u2588") : " ").join("") }, i);
|
|
9
|
+
}) });
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
Mascot as default
|
|
13
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { color, hr } from "../theme.js";
|
|
5
|
+
import { validateKey } from "../openrouter.js";
|
|
6
|
+
import { saveConfig } from "../config.js";
|
|
7
|
+
import Mascot from "./Mascot.js";
|
|
8
|
+
function Onboarding({ onDone }) {
|
|
9
|
+
const [key, setKey] = useState("");
|
|
10
|
+
const [state, setState] = useState("input");
|
|
11
|
+
const [error, setError] = useState("");
|
|
12
|
+
useInput((input, k) => {
|
|
13
|
+
if (state === "checking") return;
|
|
14
|
+
if (k.return) {
|
|
15
|
+
const trimmed = key.trim();
|
|
16
|
+
if (!trimmed) return;
|
|
17
|
+
setState("checking");
|
|
18
|
+
validateKey(trimmed).then((ok) => {
|
|
19
|
+
if (ok) {
|
|
20
|
+
saveConfig({ apiKey: trimmed });
|
|
21
|
+
onDone(trimmed);
|
|
22
|
+
} else {
|
|
23
|
+
setState("error");
|
|
24
|
+
setError("Key rejected by OpenRouter \u2014 check it and try again.");
|
|
25
|
+
setKey("");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (k.backspace || k.delete) return setKey((s) => s.slice(0, -1));
|
|
31
|
+
if (k.ctrl || k.meta || k.escape || k.tab) return;
|
|
32
|
+
if (input) setKey((s) => s + input.replace(/\s/g, ""));
|
|
33
|
+
});
|
|
34
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(Mascot, {}),
|
|
37
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
38
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: color.user, children: "Manix" }),
|
|
39
|
+
/* @__PURE__ */ jsx(Text, { color: color.dim, children: "Terminal coding agent" }),
|
|
40
|
+
/* @__PURE__ */ jsx(Text, { color: color.faint, children: "powered by OpenRouter" })
|
|
41
|
+
] })
|
|
42
|
+
] }),
|
|
43
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
44
|
+
/* @__PURE__ */ jsx(Text, { color: color.faint, children: hr(2) }),
|
|
45
|
+
/* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
|
|
46
|
+
/* @__PURE__ */ jsx(Text, { color: color.accent, children: "\u276F " }),
|
|
47
|
+
state === "checking" ? /* @__PURE__ */ jsx(Text, { color: color.dim, children: "validating key\u2026" }) : key ? /* @__PURE__ */ jsx(Text, { children: "\u2022".repeat(Math.min(key.length, 48)) }) : /* @__PURE__ */ jsx(Text, { color: color.faint, children: "paste your OpenRouter API key, then enter" })
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsx(Text, { color: color.faint, children: hr(2) })
|
|
50
|
+
] }),
|
|
51
|
+
state === "error" && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: color.err, children: error }) }),
|
|
52
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Text, { color: color.faint, children: "Get a key \u2192 https://openrouter.ai/keys" }),
|
|
54
|
+
/* @__PURE__ */ jsx(Text, { color: color.faint, children: "Saved to ~/.manix/config.json (0600) \xB7 or export OPENROUTER_API_KEY" })
|
|
55
|
+
] })
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
Onboarding as default
|
|
60
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { color } from "../theme.js";
|
|
5
|
+
const OPTIONS = [
|
|
6
|
+
{ key: "y", value: "once", label: "Yes, once (y)" },
|
|
7
|
+
{ key: "a", value: "always", label: "Yes, always this session (a)" },
|
|
8
|
+
{ key: "n", value: "no", label: "No (n / esc)" }
|
|
9
|
+
];
|
|
10
|
+
function PermissionPrompt({ request, onAnswer }) {
|
|
11
|
+
const [idx, setIdx] = useState(0);
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (key.upArrow) return setIdx((i) => (i + OPTIONS.length - 1) % OPTIONS.length);
|
|
14
|
+
if (key.downArrow) return setIdx((i) => (i + 1) % OPTIONS.length);
|
|
15
|
+
if (key.return) return onAnswer(OPTIONS[idx].value);
|
|
16
|
+
if (key.escape) return onAnswer("no");
|
|
17
|
+
const hot = OPTIONS.find((o) => o.key === (input || "").toLowerCase());
|
|
18
|
+
if (hot) onAnswer(hot.value);
|
|
19
|
+
});
|
|
20
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: color.warn, paddingX: 1, marginTop: 1, children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Text, { color: color.warn, bold: true, children: "Permission needed" }),
|
|
22
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: request.display }),
|
|
23
|
+
(request.preview || []).slice(0, 16).map((line, i) => /* @__PURE__ */ jsx(Text, { children: line }, i)),
|
|
24
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: OPTIONS.map((o, i) => /* @__PURE__ */ jsxs(Text, { color: i === idx ? color.accent : color.dim, children: [
|
|
25
|
+
i === idx ? "\u276F " : " ",
|
|
26
|
+
o.label
|
|
27
|
+
] }, o.key)) })
|
|
28
|
+
] });
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
PermissionPrompt as default
|
|
32
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { color } from "../theme.js";
|
|
5
|
+
function Picker({ title, load, onSelect, onCancel }) {
|
|
6
|
+
const [items, setItems] = useState(null);
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const [q, setQ] = useState("");
|
|
9
|
+
const [idx, setIdx] = useState(0);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let alive = true;
|
|
12
|
+
Promise.resolve().then(load).then((it) => alive && setItems(it)).catch((e) => alive && setError(e.message));
|
|
13
|
+
return () => {
|
|
14
|
+
alive = false;
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
const filtered = (items || []).filter(
|
|
18
|
+
(it) => (it.label + " " + (it.extra || "")).toLowerCase().includes(q.toLowerCase())
|
|
19
|
+
);
|
|
20
|
+
const visible = filtered.slice(0, 10);
|
|
21
|
+
const sel = visible.length ? Math.min(idx, visible.length - 1) : 0;
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (key.escape) return onCancel();
|
|
24
|
+
if (key.return) {
|
|
25
|
+
if (visible.length) onSelect(visible[sel]);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (key.upArrow) return setIdx(() => Math.max(0, sel - 1));
|
|
29
|
+
if (key.downArrow) return setIdx(() => Math.min(visible.length - 1, sel + 1));
|
|
30
|
+
if (key.backspace || key.delete) {
|
|
31
|
+
setQ((s) => s.slice(0, -1));
|
|
32
|
+
setIdx(0);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (key.ctrl || key.meta || key.tab) return;
|
|
36
|
+
if (input) {
|
|
37
|
+
setQ((s) => s + input);
|
|
38
|
+
setIdx(0);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: color.border, paddingX: 1, marginTop: 1, children: [
|
|
42
|
+
/* @__PURE__ */ jsx(Text, { color: color.accent, bold: true, children: title }),
|
|
43
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
44
|
+
/* @__PURE__ */ jsx(Text, { color: color.accent, children: "\u276F " }),
|
|
45
|
+
q || /* @__PURE__ */ jsx(Text, { color: color.faint, children: "type to filter\u2026" })
|
|
46
|
+
] }),
|
|
47
|
+
error && /* @__PURE__ */ jsx(Text, { color: color.err, children: error }),
|
|
48
|
+
!items && !error && /* @__PURE__ */ jsx(Text, { color: color.dim, children: "loading\u2026" }),
|
|
49
|
+
visible.map((it, i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
50
|
+
/* @__PURE__ */ jsxs(Text, { color: i === sel ? color.accent : color.user, children: [
|
|
51
|
+
i === sel ? "\u276F " : " ",
|
|
52
|
+
it.label
|
|
53
|
+
] }),
|
|
54
|
+
it.extra ? /* @__PURE__ */ jsxs(Text, { color: color.dim, children: [
|
|
55
|
+
" ",
|
|
56
|
+
it.extra
|
|
57
|
+
] }) : null
|
|
58
|
+
] }, it.id ?? it.label)),
|
|
59
|
+
items && !visible.length && /* @__PURE__ */ jsx(Text, { color: color.dim, children: "no matches" }),
|
|
60
|
+
/* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
|
|
61
|
+
"\u2191\u2193 select \xB7 enter confirm \xB7 esc cancel \xB7 ",
|
|
62
|
+
filtered.length,
|
|
63
|
+
" items"
|
|
64
|
+
] })
|
|
65
|
+
] });
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
Picker as default
|
|
69
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { color, lerpColor, LOGO } from "../theme.js";
|
|
5
|
+
const VERBS = [
|
|
6
|
+
"Thinking",
|
|
7
|
+
"Pondering",
|
|
8
|
+
"Cooking",
|
|
9
|
+
"Scheming",
|
|
10
|
+
"Noodling",
|
|
11
|
+
"Percolating",
|
|
12
|
+
"Ruminating",
|
|
13
|
+
"Excavating",
|
|
14
|
+
"Synthesizing",
|
|
15
|
+
"Wrangling",
|
|
16
|
+
"Sketching",
|
|
17
|
+
"Simmering",
|
|
18
|
+
"Puzzling",
|
|
19
|
+
"Tinkering",
|
|
20
|
+
"Marinating",
|
|
21
|
+
"Conjuring"
|
|
22
|
+
];
|
|
23
|
+
const TRIANGLE_FRAMES = ["\u25B2 ", "\u25B6 ", "\u25BC ", "\u25C0 "];
|
|
24
|
+
const SHINE = "#ffe0b0";
|
|
25
|
+
function shineText(text, pos) {
|
|
26
|
+
const WIDTH = 3;
|
|
27
|
+
return [...text].map((ch, i) => {
|
|
28
|
+
const dist = Math.abs(i - pos);
|
|
29
|
+
if (dist < WIDTH) {
|
|
30
|
+
const t = 1 - dist / WIDTH;
|
|
31
|
+
return lerpColor(color.accent, SHINE, t)(ch);
|
|
32
|
+
}
|
|
33
|
+
return lerpColor(color.accent, color.accent, 0)(ch);
|
|
34
|
+
}).join("");
|
|
35
|
+
}
|
|
36
|
+
function SpinnerLine({ label, cost }) {
|
|
37
|
+
const verb = useMemo(() => VERBS[Math.floor(Math.random() * VERBS.length)], []);
|
|
38
|
+
const [frame, setFrame] = useState(0);
|
|
39
|
+
const [start] = useState(() => Date.now());
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const id = setInterval(() => setFrame((f) => f + 1), 80);
|
|
42
|
+
return () => clearInterval(id);
|
|
43
|
+
}, []);
|
|
44
|
+
const secs = Math.floor((Date.now() - start) / 1e3);
|
|
45
|
+
const triFrame = TRIANGLE_FRAMES[Math.floor(frame / 3) % TRIANGLE_FRAMES.length];
|
|
46
|
+
const triColor = lerpColor(color.accent, color.amber, 0.5);
|
|
47
|
+
const displayText = label || verb + "\u2026";
|
|
48
|
+
const TOTAL = displayText.length + 6;
|
|
49
|
+
const shinePos = frame % TOTAL - 2;
|
|
50
|
+
return /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Text, { children: triColor(triFrame) }),
|
|
52
|
+
/* @__PURE__ */ jsx(Text, { children: shineText(displayText, shinePos) }),
|
|
53
|
+
/* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
|
|
54
|
+
" ",
|
|
55
|
+
"(",
|
|
56
|
+
secs,
|
|
57
|
+
"s \xB7 esc to interrupt",
|
|
58
|
+
cost ? ` \xB7 $${cost.toFixed(4)}` : "",
|
|
59
|
+
")"
|
|
60
|
+
] })
|
|
61
|
+
] });
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
SpinnerLine as default
|
|
65
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@manix-cli/manix",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Manix — a fast, beautiful terminal coding agent powered by OpenRouter. Any model, one key.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"manix": "bin/manix.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "node scripts/build.mjs",
|
|
19
|
+
"dev": "node scripts/build.mjs && node bin/manix.js",
|
|
20
|
+
"test": "node scripts/build.mjs && node scripts/smoke.mjs",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
25
|
+
"chalk": "^5.4.1",
|
|
26
|
+
"fast-glob": "^3.3.3",
|
|
27
|
+
"ink": "^5.2.1",
|
|
28
|
+
"react": "^18.3.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"esbuild": "^0.25.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"cli",
|
|
35
|
+
"ai",
|
|
36
|
+
"coding-agent",
|
|
37
|
+
"terminal",
|
|
38
|
+
"openrouter",
|
|
39
|
+
"agent",
|
|
40
|
+
"llm",
|
|
41
|
+
"tui"
|
|
42
|
+
],
|
|
43
|
+
"author": "Manindhra",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/manindhra1412/manix.git"
|
|
48
|
+
}
|
|
49
|
+
}
|