@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.
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ import fg from "fast-glob";
5
+ const IGNORE = ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"];
6
+ const globTool = {
7
+ name: "glob_files",
8
+ safe: true,
9
+ description: 'Find files by glob pattern, e.g. "src/**/*.js" or "**/*.test.*".',
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ pattern: { type: "string" },
14
+ path: { type: "string", description: "Base directory (defaults to cwd)" }
15
+ },
16
+ required: ["pattern"]
17
+ },
18
+ describe: (a) => `Glob(${a.pattern})`,
19
+ async run(a, ctx) {
20
+ const files = await fg(a.pattern, {
21
+ cwd: path.resolve(ctx.cwd, a.path || "."),
22
+ ignore: IGNORE,
23
+ onlyFiles: true,
24
+ dot: false,
25
+ suppressErrors: true
26
+ });
27
+ if (!files.length) return "No files matched.";
28
+ const shown = files.slice(0, 200);
29
+ return shown.join("\n") + (files.length > 200 ? `
30
+ \u2026 +${files.length - 200} more` : "");
31
+ }
32
+ };
33
+ const grepTool = {
34
+ name: "grep_search",
35
+ safe: true,
36
+ description: "Search file contents with a regex. Returns file:line:text matches.",
37
+ parameters: {
38
+ type: "object",
39
+ properties: {
40
+ pattern: { type: "string", description: "Regular expression" },
41
+ path: { type: "string", description: "Directory or file to search (defaults to cwd)" },
42
+ include: { type: "string", description: 'Only search files matching this glob, e.g. "*.js"' }
43
+ },
44
+ required: ["pattern"]
45
+ },
46
+ describe: (a) => `Grep(${a.pattern})`,
47
+ async run(a, ctx) {
48
+ const base = path.resolve(ctx.cwd, a.path || ".");
49
+ try {
50
+ return await rgSearch(a, base);
51
+ } catch (err) {
52
+ if (err?.code !== "ENOENT") throw err;
53
+ return jsSearch(a, base);
54
+ }
55
+ }
56
+ };
57
+ function rgSearch(a, base) {
58
+ return new Promise((resolve, reject) => {
59
+ const args = ["-n", "--no-heading", "-S", "-m", "50", "--max-columns", "300"];
60
+ if (a.include) args.push("-g", a.include);
61
+ args.push("--", a.pattern, base);
62
+ execFile("rg", args, { maxBuffer: 4 * 1024 * 1024 }, (err, stdout) => {
63
+ if (err && err.code === 1) return resolve("No matches.");
64
+ if (err && typeof err.code !== "number") return reject(err);
65
+ if (err && err.code > 1) return reject(new Error(err.message));
66
+ const lines = stdout.trim().split("\n").slice(0, 200);
67
+ resolve(lines.map((l) => l.replace(base + path.sep, "")).join("\n"));
68
+ });
69
+ });
70
+ }
71
+ async function jsSearch(a, base) {
72
+ const re = new RegExp(a.pattern);
73
+ const files = await fg(a.include || "**/*", {
74
+ cwd: base,
75
+ ignore: IGNORE,
76
+ onlyFiles: true,
77
+ dot: false,
78
+ suppressErrors: true
79
+ });
80
+ const out = [];
81
+ for (const f of files.slice(0, 3e3)) {
82
+ const p = path.join(base, f);
83
+ let src;
84
+ try {
85
+ if (fs.statSync(p).size > 512 * 1024) continue;
86
+ src = fs.readFileSync(p, "utf8");
87
+ } catch {
88
+ continue;
89
+ }
90
+ if (src.includes("\0")) continue;
91
+ const lines = src.split("\n");
92
+ for (let i = 0; i < lines.length; i++) {
93
+ if (re.test(lines[i])) {
94
+ out.push(`${f}:${i + 1}:${lines[i].trim().slice(0, 300)}`);
95
+ if (out.length >= 200) return out.join("\n");
96
+ }
97
+ }
98
+ }
99
+ return out.length ? out.join("\n") : "No matches.";
100
+ }
101
+ const searchTools = [globTool, grepTool];
102
+ export {
103
+ globTool,
104
+ grepTool,
105
+ searchTools
106
+ };
package/dist/ui/App.js ADDED
@@ -0,0 +1,345 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
+ import { Box, Static, Text, useApp, useInput } from "ink";
4
+ import { Agent } from "../agent.js";
5
+ import { Permissions } from "../permissions.js";
6
+ import { fetchModels, getCredits } from "../openrouter.js";
7
+ import { saveConfig } from "../config.js";
8
+ import { listSessions, loadSession } from "../sessions.js";
9
+ import { slashCommands, helpText } from "../slash.js";
10
+ import { INIT_PROMPT } from "../prompts.js";
11
+ import { color, GLYPH, randomTip, shortenPath } from "../theme.js";
12
+ import { renderMarkdown } from "../markdown.js";
13
+ import LogItem from "./LogItem.js";
14
+ import InputBox from "./InputBox.js";
15
+ import PermissionPrompt from "./PermissionPrompt.js";
16
+ import Picker from "./Picker.js";
17
+ import SpinnerLine from "./SpinnerLine.js";
18
+ import Footer from "./Footer.js";
19
+ import Onboarding from "./Onboarding.js";
20
+ function App({
21
+ config,
22
+ cwd,
23
+ version,
24
+ mcp,
25
+ skills,
26
+ contextFiles,
27
+ initialPrompt,
28
+ resumeTarget,
29
+ yolo
30
+ }) {
31
+ const { exit } = useApp();
32
+ const [ready, setReady] = useState(!!config.apiKey);
33
+ const [items, setItems] = useState([]);
34
+ const [busy, setBusy] = useState(false);
35
+ const [streamText, setStreamText] = useState("");
36
+ const [activity, setActivity] = useState(null);
37
+ const [permission, setPermission] = useState(null);
38
+ const [overlay, setOverlay] = useState(null);
39
+ const [stats, setStats] = useState({
40
+ tokens: 0,
41
+ contextLength: 128e3,
42
+ cost: 0,
43
+ prompt: 0,
44
+ completion: 0,
45
+ requests: 0
46
+ });
47
+ const [model, setModelState] = useState(config.model);
48
+ const [yoloOn, setYoloOn] = useState(!!yolo);
49
+ const [exitHint, setExitHint] = useState(false);
50
+ const keyRef = useRef(0);
51
+ const agentRef = useRef(null);
52
+ const permsRef = useRef(null);
53
+ const streamRef = useRef("");
54
+ const flushTimer = useRef(null);
55
+ const historyRef = useRef([]);
56
+ const push = useCallback((item) => {
57
+ setItems((prev) => [...prev, { ...item, key: keyRef.current++ }]);
58
+ }, []);
59
+ const commands = slashCommands(skills);
60
+ function doResume(fileOrId) {
61
+ const loaded = loadSession(fileOrId);
62
+ if (!loaded) return push({ kind: "error", text: "Could not load session." });
63
+ agentRef.current.resumeFrom(loaded);
64
+ push({
65
+ kind: "info",
66
+ text: `Resumed session ${loaded.meta.id} (${loaded.messages.length} messages).`
67
+ });
68
+ const lastAssistant = [...loaded.messages].reverse().find((m) => m.role === "assistant" && m.content);
69
+ if (lastAssistant) push({ kind: "assistant", text: lastAssistant.content });
70
+ setStats(agentRef.current.stats());
71
+ }
72
+ function runPrompt(text, displayText = text) {
73
+ push({ kind: "user", text: displayText });
74
+ setBusy(true);
75
+ agentRef.current.send(text).finally(() => {
76
+ setBusy(false);
77
+ setActivity(null);
78
+ });
79
+ }
80
+ function applyModel(id) {
81
+ setModelState(id);
82
+ agentRef.current.setModel(id);
83
+ saveConfig({ model: id });
84
+ push({ kind: "info", text: `Model \u2192 ${id}` });
85
+ }
86
+ async function cleanup() {
87
+ try {
88
+ await mcp.closeAll();
89
+ } catch {
90
+ }
91
+ }
92
+ async function handleSlash(raw) {
93
+ const [cmdName, ...rest] = raw.slice(1).split(" ");
94
+ const args = rest.join(" ").trim();
95
+ const cmd = commands.find((c) => c.name === cmdName.toLowerCase());
96
+ if (!cmd) return push({ kind: "error", text: `Unknown command /${cmdName} \u2014 try /help` });
97
+ if (cmd.skill) {
98
+ return runPrompt(
99
+ `Use the "${cmd.skill.name}" skill below to handle this request.
100
+
101
+ ${cmd.skill.body}
102
+
103
+ ---
104
+ User input: ${args || "(none)"}`,
105
+ `/${cmd.name} ${args}`.trim()
106
+ );
107
+ }
108
+ switch (cmd.name) {
109
+ case "help":
110
+ return push({ kind: "info", text: helpText(commands) });
111
+ case "clear":
112
+ agentRef.current.reset();
113
+ setStats(agentRef.current.stats());
114
+ return push({ kind: "info", text: "Started a fresh conversation (history cleared)." });
115
+ case "exit":
116
+ await cleanup();
117
+ return exit();
118
+ case "yolo": {
119
+ const p = permsRef.current;
120
+ p.yolo = !p.yolo;
121
+ setYoloOn(p.yolo);
122
+ return push({
123
+ kind: "info",
124
+ text: p.yolo ? "YOLO on \u2014 all tools auto-approved. Careful." : "YOLO off \u2014 permissions restored."
125
+ });
126
+ }
127
+ case "cost": {
128
+ const s = agentRef.current.stats();
129
+ const credits = await getCredits(config.apiKey);
130
+ const creditLine = credits ? `
131
+ OpenRouter credits: $${(credits.total_credits - credits.total_usage).toFixed(2)} remaining` : "";
132
+ return push({
133
+ kind: "info",
134
+ text: `Session: ${s.requests} requests \xB7 ${s.prompt + s.completion} tokens (${s.prompt} in / ${s.completion} out) \xB7 $${s.cost.toFixed(4)}${creditLine}`
135
+ });
136
+ }
137
+ case "mcp": {
138
+ const st = mcp.status();
139
+ return push({
140
+ kind: "info",
141
+ text: st.length ? st.map(
142
+ (x) => `${x.name}: ${x.status}${x.status === "ready" ? ` (${x.tools} tools)` : ""}${x.error ? " \u2014 " + x.error : ""}`
143
+ ).join("\n") : "No MCP servers configured. Add them to ~/.manix/mcp.json or .manix/mcp.json."
144
+ });
145
+ }
146
+ case "skills":
147
+ return push({
148
+ kind: "info",
149
+ text: skills.length ? skills.map((s) => `${s.name}: ${s.description}`).join("\n") : "No skills found. Add SKILL.md folders under ~/.manix/skills/ or .manix/skills/."
150
+ });
151
+ case "init":
152
+ return runPrompt(INIT_PROMPT, "/init");
153
+ case "compact": {
154
+ setBusy(true);
155
+ push({ kind: "info", text: "Compacting conversation\u2026" });
156
+ try {
157
+ await agentRef.current.compact();
158
+ push({ kind: "info", text: "Compacted \u2014 context freed." });
159
+ } catch (e) {
160
+ push({ kind: "error", text: "Compact failed: " + e.message });
161
+ } finally {
162
+ setBusy(false);
163
+ }
164
+ return;
165
+ }
166
+ case "model":
167
+ if (args) return applyModel(args);
168
+ return setOverlay({ type: "models" });
169
+ case "resume":
170
+ return setOverlay({ type: "sessions" });
171
+ default:
172
+ return;
173
+ }
174
+ }
175
+ function handleSubmit(raw) {
176
+ historyRef.current.push(raw);
177
+ if (raw.startsWith("/")) return handleSlash(raw);
178
+ runPrompt(raw);
179
+ }
180
+ useEffect(() => {
181
+ if (!ready || agentRef.current) return;
182
+ const permissions = new Permissions({ yolo: yoloOn });
183
+ permsRef.current = permissions;
184
+ agentRef.current = new Agent({
185
+ config: { ...config, model },
186
+ cwd,
187
+ permissions,
188
+ mcp,
189
+ skills,
190
+ contextFiles,
191
+ handlers: {
192
+ onTextDelta: (d) => {
193
+ streamRef.current += d;
194
+ if (!flushTimer.current) {
195
+ flushTimer.current = setTimeout(() => {
196
+ flushTimer.current = null;
197
+ setStreamText(streamRef.current);
198
+ }, 60);
199
+ }
200
+ },
201
+ onAssistantDone: (text) => {
202
+ if (flushTimer.current) {
203
+ clearTimeout(flushTimer.current);
204
+ flushTimer.current = null;
205
+ }
206
+ streamRef.current = "";
207
+ setStreamText("");
208
+ if (text?.trim()) push({ kind: "assistant", text });
209
+ },
210
+ onToolStart: ({ display }) => setActivity(display),
211
+ onToolEnd: ({ display, ok, summary }) => {
212
+ setActivity(null);
213
+ push({ kind: "tool", display, ok, summary });
214
+ },
215
+ onInfo: (text) => push({ kind: "info", text }),
216
+ onError: (text) => push({ kind: "error", text }),
217
+ onStats: (s) => setStats(s),
218
+ requestPermission: (req) => new Promise((resolve) => setPermission({ ...req, resolve }))
219
+ }
220
+ });
221
+ push({
222
+ kind: "banner",
223
+ version,
224
+ model,
225
+ cwd: shortenPath(cwd),
226
+ hasManixmd: contextFiles.some((f) => f.path.startsWith(cwd)),
227
+ tip: randomTip()
228
+ });
229
+ if (resumeTarget === "last" || resumeTarget === "pick") {
230
+ const sessions = listSessions(cwd);
231
+ if (!sessions.length) push({ kind: "info", text: "No previous sessions found." });
232
+ else if (resumeTarget === "last") doResume(sessions[0].file);
233
+ else setOverlay({ type: "sessions" });
234
+ } else if (resumeTarget) {
235
+ doResume(resumeTarget);
236
+ }
237
+ if (initialPrompt) runPrompt(initialPrompt);
238
+ }, [ready]);
239
+ useInput((input, key) => {
240
+ if (key.ctrl && input === "c") {
241
+ if (permission) {
242
+ permission.resolve("no");
243
+ setPermission(null);
244
+ }
245
+ if (busy) return agentRef.current?.abort();
246
+ if (exitHint) return void cleanup().finally(() => exit());
247
+ setExitHint(true);
248
+ setTimeout(() => setExitHint(false), 1500);
249
+ return;
250
+ }
251
+ if (key.escape && busy && !permission) agentRef.current?.abort();
252
+ });
253
+ if (!ready) {
254
+ return /* @__PURE__ */ jsx(
255
+ Onboarding,
256
+ {
257
+ onDone: (key) => {
258
+ config.apiKey = key;
259
+ setReady(true);
260
+ }
261
+ }
262
+ );
263
+ }
264
+ const overlayEl = overlay?.type === "models" ? /* @__PURE__ */ jsx(
265
+ Picker,
266
+ {
267
+ title: "Select model",
268
+ load: async () => {
269
+ const models = await fetchModels(config.apiKey);
270
+ return models.slice().sort((a, b) => a.id.localeCompare(b.id)).map((m) => ({
271
+ id: m.id,
272
+ label: m.id,
273
+ extra: `${Math.round((m.context_length || 0) / 1e3)}k ctx \xB7 $${perM(m.pricing?.prompt)}/${perM(m.pricing?.completion)} per M`
274
+ }));
275
+ },
276
+ onSelect: (it) => {
277
+ setOverlay(null);
278
+ applyModel(it.id);
279
+ },
280
+ onCancel: () => setOverlay(null)
281
+ }
282
+ ) : overlay?.type === "sessions" ? /* @__PURE__ */ jsx(
283
+ Picker,
284
+ {
285
+ title: "Resume session",
286
+ load: () => listSessions(cwd).map((s) => ({
287
+ id: s.file,
288
+ label: s.title,
289
+ extra: `${timeAgo(s.mtime)} \xB7 ${s.count} msgs`
290
+ })),
291
+ onSelect: (it) => {
292
+ setOverlay(null);
293
+ doResume(it.id);
294
+ },
295
+ onCancel: () => setOverlay(null)
296
+ }
297
+ ) : null;
298
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
299
+ /* @__PURE__ */ jsx(Static, { items, children: (item) => /* @__PURE__ */ jsx(LogItem, { item }, item.key) }),
300
+ streamText ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
301
+ /* @__PURE__ */ jsxs(Text, { color: color.accent, children: [
302
+ GLYPH.assistant,
303
+ " "
304
+ ] }),
305
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: renderMarkdown(streamText) }) })
306
+ ] }) : null,
307
+ busy && !permission ? /* @__PURE__ */ jsx(SpinnerLine, { label: activity, cost: stats.cost }) : null,
308
+ permission ? /* @__PURE__ */ jsx(
309
+ PermissionPrompt,
310
+ {
311
+ request: permission,
312
+ onAnswer: (v) => {
313
+ const { resolve } = permission;
314
+ setPermission(null);
315
+ resolve(v);
316
+ }
317
+ }
318
+ ) : overlayEl || /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
319
+ /* @__PURE__ */ jsx(
320
+ InputBox,
321
+ {
322
+ onSubmit: handleSubmit,
323
+ commands,
324
+ active: !busy,
325
+ history: historyRef.current
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx(Footer, { model, stats, yolo: yoloOn, cwd, exitHint })
329
+ ] })
330
+ ] });
331
+ }
332
+ function perM(p) {
333
+ const n = parseFloat(p || 0) * 1e6;
334
+ return n ? n < 10 ? n.toFixed(2) : Math.round(n) : "0";
335
+ }
336
+ function timeAgo(ms) {
337
+ const s = Math.floor((Date.now() - ms) / 1e3);
338
+ if (s < 60) return `${s}s ago`;
339
+ if (s < 3600) return `${Math.floor(s / 60)}m ago`;
340
+ if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
341
+ return `${Math.floor(s / 86400)}d ago`;
342
+ }
343
+ export {
344
+ App as default
345
+ };
@@ -0,0 +1,38 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import path from "node:path";
4
+ import { Box, Text } from "ink";
5
+ import { color } from "../theme.js";
6
+ const SEP = " \xB7 ";
7
+ function Footer({ model, stats, yolo, cwd, exitHint }) {
8
+ if (exitHint) {
9
+ return /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: color.warn, children: "press ctrl+c again to exit" }) });
10
+ }
11
+ const pct = stats.contextLength ? Math.min(99, Math.round(stats.tokens / stats.contextLength * 100)) : 0;
12
+ const pctColor = pct < 50 ? color.dim : pct < 80 ? color.warn : color.err;
13
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
14
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: model }),
15
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: SEP }),
16
+ /* @__PURE__ */ jsxs(Text, { color: pctColor, children: [
17
+ "ctx ",
18
+ pct,
19
+ "%"
20
+ ] }),
21
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: SEP }),
22
+ /* @__PURE__ */ jsxs(Text, { color: color.accent, children: [
23
+ "$",
24
+ stats.cost.toFixed(4)
25
+ ] }),
26
+ yolo ? /* @__PURE__ */ jsxs(Text, { color: color.warn, children: [
27
+ SEP,
28
+ "YOLO"
29
+ ] }) : null,
30
+ /* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
31
+ SEP,
32
+ path.basename(cwd)
33
+ ] })
34
+ ] });
35
+ }
36
+ export {
37
+ Footer as default
38
+ };
@@ -0,0 +1,123 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import chalk from "chalk";
5
+ import { color, hr } from "../theme.js";
6
+ function InputBox({ onSubmit, commands, active, history }) {
7
+ const [value, setValue] = useState("");
8
+ const [pos, setPos] = useState(0);
9
+ const [histIdx, setHistIdx] = useState(-1);
10
+ const [menuIdx, setMenuIdx] = useState(0);
11
+ const menuOpen = active && value.startsWith("/") && !value.includes(" ");
12
+ const matches = menuOpen ? commands.filter((c) => ("/" + c.name).startsWith(value.toLowerCase())) : [];
13
+ const sel = matches.length ? Math.min(menuIdx, matches.length - 1) : 0;
14
+ useInput(
15
+ (input, key) => {
16
+ if (key.return) {
17
+ if (menuOpen && matches.length) {
18
+ setValue("");
19
+ setPos(0);
20
+ setMenuIdx(0);
21
+ setHistIdx(-1);
22
+ onSubmit("/" + matches[sel].name);
23
+ return;
24
+ }
25
+ const text = value.trim();
26
+ if (!text) return;
27
+ setValue("");
28
+ setPos(0);
29
+ setHistIdx(-1);
30
+ onSubmit(text);
31
+ return;
32
+ }
33
+ if (key.tab) {
34
+ if (menuOpen && matches.length) {
35
+ const cmd = "/" + matches[sel].name + " ";
36
+ setValue(cmd);
37
+ setPos(cmd.length);
38
+ }
39
+ return;
40
+ }
41
+ if (key.upArrow) {
42
+ if (menuOpen && matches.length) return setMenuIdx((i) => (i + matches.length - 1) % matches.length);
43
+ if (history.length) {
44
+ const next = histIdx === -1 ? history.length - 1 : Math.max(0, histIdx - 1);
45
+ setHistIdx(next);
46
+ setValue(history[next]);
47
+ setPos(history[next].length);
48
+ }
49
+ return;
50
+ }
51
+ if (key.downArrow) {
52
+ if (menuOpen && matches.length) return setMenuIdx((i) => (i + 1) % matches.length);
53
+ if (histIdx !== -1) {
54
+ const next = histIdx + 1;
55
+ if (next >= history.length) {
56
+ setHistIdx(-1);
57
+ setValue("");
58
+ setPos(0);
59
+ } else {
60
+ setHistIdx(next);
61
+ setValue(history[next]);
62
+ setPos(history[next].length);
63
+ }
64
+ }
65
+ return;
66
+ }
67
+ if (key.leftArrow) return setPos((p) => Math.max(0, p - 1));
68
+ if (key.rightArrow) return setPos((p) => Math.min(value.length, p + 1));
69
+ if (key.escape) {
70
+ setValue("");
71
+ setPos(0);
72
+ setHistIdx(-1);
73
+ return;
74
+ }
75
+ if (key.backspace || key.delete) {
76
+ if (pos > 0) {
77
+ setValue(value.slice(0, pos - 1) + value.slice(pos));
78
+ setPos(pos - 1);
79
+ }
80
+ return;
81
+ }
82
+ if (key.ctrl && input === "u") {
83
+ setValue("");
84
+ setPos(0);
85
+ return;
86
+ }
87
+ if (key.ctrl && input === "a") return setPos(0);
88
+ if (key.ctrl && input === "e") return setPos(value.length);
89
+ if (key.ctrl || key.meta) return;
90
+ if (input) {
91
+ const clean = input.replace(/\r/g, "\n");
92
+ setValue(value.slice(0, pos) + clean + value.slice(pos));
93
+ setPos(pos + clean.length);
94
+ setMenuIdx(0);
95
+ }
96
+ },
97
+ { isActive: active }
98
+ );
99
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
100
+ menuOpen && matches.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: matches.slice(0, 6).map((c, i) => /* @__PURE__ */ jsxs(Text, { children: [
101
+ /* @__PURE__ */ jsxs(Text, { color: i === sel ? color.accent : color.dim, children: [
102
+ i === sel ? "\u276F " : " ",
103
+ "/",
104
+ c.name.padEnd(14)
105
+ ] }),
106
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: c.desc })
107
+ ] }, c.name)) }),
108
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: hr(2) }),
109
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
110
+ /* @__PURE__ */ jsx(Text, { color: color.accent, children: "\u276F " }),
111
+ /* @__PURE__ */ jsx(Text, { children: renderValue(value, pos, active) })
112
+ ] }),
113
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: hr(2) })
114
+ ] });
115
+ }
116
+ function renderValue(value, pos, active) {
117
+ if (!active) return chalk.hex(color.faint)(value || "working\u2026 (esc to interrupt)");
118
+ if (!value) return chalk.inverse(" ") + chalk.hex(color.faint)('ask anything \xB7 "/" for commands');
119
+ return value.slice(0, pos) + chalk.inverse(value[pos] || " ") + value.slice(pos + 1);
120
+ }
121
+ export {
122
+ InputBox as default
123
+ };
@@ -0,0 +1,82 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Box, Text } from "ink";
4
+ import { color, GLYPH } from "../theme.js";
5
+ import { renderMarkdown } from "../markdown.js";
6
+ import Mascot from "./Mascot.js";
7
+ function LogItem({ item }) {
8
+ switch (item.kind) {
9
+ case "banner":
10
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
11
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
12
+ /* @__PURE__ */ jsx(Mascot, {}),
13
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
14
+ /* @__PURE__ */ jsxs(Text, { children: [
15
+ /* @__PURE__ */ jsx(Text, { bold: true, color: color.user, children: "Manix" }),
16
+ /* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
17
+ " v",
18
+ item.version
19
+ ] })
20
+ ] }),
21
+ /* @__PURE__ */ jsx(Text, { color: color.dim, children: item.model }),
22
+ /* @__PURE__ */ jsx(Text, { color: color.faint, children: item.cwd })
23
+ ] })
24
+ ] }),
25
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
26
+ !item.hasManixmd && /* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
27
+ GLYPH.info,
28
+ " MANIX.md not found \u2014 /init to create"
29
+ ] }),
30
+ /* @__PURE__ */ jsxs(Text, { color: color.faint, children: [
31
+ GLYPH.info,
32
+ " ",
33
+ item.tip
34
+ ] })
35
+ ] })
36
+ ] });
37
+ case "user":
38
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
39
+ /* @__PURE__ */ jsxs(Text, { color: color.accent, children: [
40
+ GLYPH.user,
41
+ " "
42
+ ] }),
43
+ /* @__PURE__ */ jsx(Text, { color: color.user, children: item.text })
44
+ ] });
45
+ case "assistant":
46
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
47
+ /* @__PURE__ */ jsxs(Text, { color: color.accent, children: [
48
+ GLYPH.assistant,
49
+ " "
50
+ ] }),
51
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: renderMarkdown(item.text) }) })
52
+ ] });
53
+ case "tool":
54
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
55
+ /* @__PURE__ */ jsxs(Text, { children: [
56
+ /* @__PURE__ */ jsxs(Text, { color: item.ok ? color.accent : color.err, children: [
57
+ GLYPH.tool,
58
+ " "
59
+ ] }),
60
+ /* @__PURE__ */ jsx(Text, { bold: true, children: item.display })
61
+ ] }),
62
+ item.summary ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: color.dim, children: item.summary }) }) : null
63
+ ] });
64
+ case "info":
65
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: color.dim, children: [
66
+ GLYPH.info,
67
+ " ",
68
+ item.text
69
+ ] }) });
70
+ case "error":
71
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: color.err, children: [
72
+ GLYPH.error,
73
+ " ",
74
+ item.text
75
+ ] }) });
76
+ default:
77
+ return null;
78
+ }
79
+ }
80
+ export {
81
+ LogItem as default
82
+ };