miii-agent 0.1.25 → 0.1.27
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/README.md +22 -1
- package/dist/cli.js +377 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/miii-agent"><img src="https://img.shields.io/npm/v/miii-agent" alt="npm version"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/miii-agent"><img src="https://img.shields.io/npm/dt/miii-agent" alt="npm total downloads"></a>
|
|
11
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license"></a>
|
|
12
13
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="node version"></a>
|
|
13
14
|
<a href="https://ollama.com"><img src="https://img.shields.io/badge/powered%20by-Ollama-black" alt="powered by Ollama"></a>
|
|
@@ -29,10 +30,12 @@ Your code never leaves your disk. There's nothing to log in to. Pull a model, ty
|
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
32
|
ollama pull qwen2.5-coder:14b # any coding model works
|
|
32
|
-
|
|
33
|
+
curl -fsSL https://raw.githubusercontent.com/maruakshay/miii-cli/main/install.sh | sh
|
|
33
34
|
miii
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
Prefer npm? `npm install -g miii-agent`.
|
|
38
|
+
|
|
36
39
|
Then just talk to it:
|
|
37
40
|
|
|
38
41
|
```
|
|
@@ -43,6 +46,19 @@ Then just talk to it:
|
|
|
43
46
|
|
|
44
47
|
> **Needs:** Node ≥ 18 and [Ollama](https://ollama.com/download) running locally.
|
|
45
48
|
|
|
49
|
+
## Staying up to date
|
|
50
|
+
|
|
51
|
+
miii checks npm on launch and, when a newer release exists, pulls it in the
|
|
52
|
+
background — it applies the next time you start. Manual options:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
miii update # update now
|
|
56
|
+
miii --version # what you're running
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Opt out of background updates by adding `"autoUpdate": false` to `~/.miii/config.json`,
|
|
60
|
+
or re-run the install script (`curl … | sh`) any time to update by hand.
|
|
61
|
+
|
|
46
62
|
## Why local-first?
|
|
47
63
|
|
|
48
64
|
Most "AI coding tools" are just wrappers around a cloud API — slow, metered, and they ship your private codebase to someone else's server.
|
|
@@ -68,6 +84,10 @@ It doesn't just chat, either — it decomposes the problem, calls tools, and che
|
|
|
68
84
|
miii doctor # grade every installed model
|
|
69
85
|
miii doctor qwen2.5-coder:7b # grade one
|
|
70
86
|
```
|
|
87
|
+
- **🖼️ Paste images** — copy a screenshot and hit `Ctrl+V` to attach it to your message, or paste an image file path. Great for "why does this UI look broken?" or reading an error screenshot. **Needs a vision-capable model** (`llava`, `llama3.2-vision`, `qwen2-vl`, …) — text-only models silently ignore the image.
|
|
88
|
+
```bash
|
|
89
|
+
ollama pull llava # or llama3.2-vision
|
|
90
|
+
```
|
|
71
91
|
- **💧 Lossless output spill** — that 50K-line test log won't get truncated and leave the model guessing. miii spills the full output to disk and lets the model page through it. Nothing is ever lost.
|
|
72
92
|
- **🔒 Permission-gated tools** — you approve what the agent can touch; "always" approvals persist. File tools are confined to your working directory.
|
|
73
93
|
- **📄 `MIII.md`** — drop one in your repo to teach miii your conventions, build/test commands, and do's & don'ts. Same idea as `CLAUDE.md`, read every turn.
|
|
@@ -96,6 +116,7 @@ File tools (`read_file`, `write_file`, `edit_file`) reject `../` traversal and a
|
|
|
96
116
|
|-----|--------|
|
|
97
117
|
| `Enter` | Send prompt |
|
|
98
118
|
| `@filename` | Attach file to context |
|
|
119
|
+
| `Ctrl+V` | Paste clipboard image (needs a vision model) |
|
|
99
120
|
| `/models` | Switch active model |
|
|
100
121
|
| `/clear` | Reset conversation |
|
|
101
122
|
| `Esc` | Stop generation or tool run |
|
package/dist/cli.js
CHANGED
|
@@ -35,9 +35,13 @@ function migrate(raw) {
|
|
|
35
35
|
provider: raw.provider,
|
|
36
36
|
effort: raw.effort,
|
|
37
37
|
providers,
|
|
38
|
-
modelContexts: raw.modelContexts
|
|
38
|
+
modelContexts: raw.modelContexts,
|
|
39
|
+
autoUpdate: raw.autoUpdate
|
|
39
40
|
};
|
|
40
41
|
}
|
|
42
|
+
function autoUpdateEnabled(cfg = loadConfig()) {
|
|
43
|
+
return cfg.autoUpdate !== false;
|
|
44
|
+
}
|
|
41
45
|
function readRawConfig() {
|
|
42
46
|
if (!existsSync(CONFIG_PATH)) return {};
|
|
43
47
|
try {
|
|
@@ -129,6 +133,15 @@ function isConnectionError(err) {
|
|
|
129
133
|
const msg = err instanceof Error ? err.message : String(err);
|
|
130
134
|
return msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("connect");
|
|
131
135
|
}
|
|
136
|
+
function isNoVisionError(err) {
|
|
137
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
138
|
+
return msg.includes("does not support image") || msg.includes("image input");
|
|
139
|
+
}
|
|
140
|
+
function noVisionError(model) {
|
|
141
|
+
return new Error(
|
|
142
|
+
`"${model}" can't read images \u2014 it has no vision support. Switch to a vision-capable model (e.g. llava, llama3.2-vision, qwen2-vl) with /models, then resend. Pull one with: ollama pull llava`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
132
145
|
async function listModels(entry) {
|
|
133
146
|
try {
|
|
134
147
|
const { models } = await makeClient(entry).list();
|
|
@@ -208,6 +221,7 @@ async function* chat(entry, model, messages, tools, opts) {
|
|
|
208
221
|
}
|
|
209
222
|
} catch (err) {
|
|
210
223
|
if (signal?.aborted) return;
|
|
224
|
+
if (isNoVisionError(err)) throw noVisionError(model);
|
|
211
225
|
if (isConnectionError(err)) {
|
|
212
226
|
throw new Error(NOT_RUNNING);
|
|
213
227
|
}
|
|
@@ -233,6 +247,7 @@ async function* chat(entry, model, messages, tools, opts) {
|
|
|
233
247
|
}
|
|
234
248
|
} catch (err) {
|
|
235
249
|
if (opts?.signal?.aborted) return;
|
|
250
|
+
if (isNoVisionError(err)) throw noVisionError(model);
|
|
236
251
|
if (isConnectionError(err)) {
|
|
237
252
|
throw new Error(NOT_RUNNING);
|
|
238
253
|
}
|
|
@@ -1292,7 +1307,8 @@ Ask in a numbered list. One round of questions per turn. Then wait.
|
|
|
1292
1307
|
|
|
1293
1308
|
# Tool calls
|
|
1294
1309
|
- When you need a tool, emit the tool call directly. No preamble, no narration, no "I will use X".
|
|
1295
|
-
- Never
|
|
1310
|
+
- Use the native function-calling interface as the ONLY channel for tool calls. Never print a tool call as text \u2014 not as JSON, not as a fenced code block, not as \`call:name{...}\`, not in any custom or tagged syntax. Text-form calls do NOT execute; they leak to the user and nothing happens.
|
|
1311
|
+
- If you cannot emit a real function call, do not fake one in prose \u2014 answer in plain text instead.
|
|
1296
1312
|
- After a tool result, move directly to the next tool call or the final answer. Do not restate what the previous tool did.
|
|
1297
1313
|
|
|
1298
1314
|
# Tools
|
|
@@ -1429,7 +1445,9 @@ function toOllamaMessages(history, system) {
|
|
|
1429
1445
|
const out = [{ role: "system", content: system }];
|
|
1430
1446
|
for (const msg of history) {
|
|
1431
1447
|
if (typeof msg.content === "string") {
|
|
1432
|
-
|
|
1448
|
+
const om = { role: msg.role === "system" ? "system" : msg.role, content: msg.content };
|
|
1449
|
+
if (msg.role === "user" && msg.images && msg.images.length > 0) om.images = msg.images;
|
|
1450
|
+
out.push(om);
|
|
1433
1451
|
continue;
|
|
1434
1452
|
}
|
|
1435
1453
|
if (msg.role === "assistant") {
|
|
@@ -1462,6 +1480,11 @@ function parseTextToolCalls(text, knownToolNames) {
|
|
|
1462
1480
|
if (!text) return { calls: [], cleanedText: text };
|
|
1463
1481
|
const calls = [];
|
|
1464
1482
|
let cleaned = text;
|
|
1483
|
+
const callSyntax = parseCallSyntax(cleaned, knownToolNames);
|
|
1484
|
+
if (callSyntax.calls.length > 0) {
|
|
1485
|
+
calls.push(...callSyntax.calls);
|
|
1486
|
+
cleaned = callSyntax.cleanedText;
|
|
1487
|
+
}
|
|
1465
1488
|
const tagRe = /<\|?tool_call\|?>\s*([\s\S]*?)\s*<\|?\/?tool_call\|?>/g;
|
|
1466
1489
|
cleaned = cleaned.replace(tagRe, (_m, body) => {
|
|
1467
1490
|
const c = tryParse(body, knownToolNames);
|
|
@@ -1502,6 +1525,74 @@ function tryParse(raw, knownToolNames) {
|
|
|
1502
1525
|
return null;
|
|
1503
1526
|
}
|
|
1504
1527
|
}
|
|
1528
|
+
function parseCallSyntax(text, knownToolNames) {
|
|
1529
|
+
const calls = [];
|
|
1530
|
+
const headRe = /call:\s*([a-zA-Z_]\w*)\s*\{/g;
|
|
1531
|
+
let m;
|
|
1532
|
+
let cleaned = "";
|
|
1533
|
+
let lastEnd = 0;
|
|
1534
|
+
while ((m = headRe.exec(text)) !== null) {
|
|
1535
|
+
const name = m[1];
|
|
1536
|
+
const body = parseCallBody(text, m.index + m[0].length);
|
|
1537
|
+
if (!body) continue;
|
|
1538
|
+
headRe.lastIndex = body.end;
|
|
1539
|
+
if (!knownToolNames.includes(name)) continue;
|
|
1540
|
+
calls.push({ function: { name, arguments: body.args } });
|
|
1541
|
+
cleaned += text.slice(lastEnd, m.index);
|
|
1542
|
+
lastEnd = body.end;
|
|
1543
|
+
}
|
|
1544
|
+
cleaned += text.slice(lastEnd);
|
|
1545
|
+
return { calls, cleanedText: cleaned.trim() };
|
|
1546
|
+
}
|
|
1547
|
+
function parseCallBody(text, start) {
|
|
1548
|
+
const args2 = {};
|
|
1549
|
+
let i = start;
|
|
1550
|
+
const isWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
1551
|
+
while (i < text.length) {
|
|
1552
|
+
while (i < text.length && (isWs(text[i]) || text[i] === ",")) i++;
|
|
1553
|
+
if (i >= text.length) return null;
|
|
1554
|
+
if (text[i] === "}") return { args: args2, end: i + 1 };
|
|
1555
|
+
const keyStart = i;
|
|
1556
|
+
while (i < text.length && text[i] !== ":" && text[i] !== "}") i++;
|
|
1557
|
+
if (i >= text.length || text[i] !== ":") return null;
|
|
1558
|
+
const key = text.slice(keyStart, i).trim().replace(/^["']|["']$/g, "");
|
|
1559
|
+
i++;
|
|
1560
|
+
while (i < text.length && isWs(text[i])) i++;
|
|
1561
|
+
if (text.startsWith(VAL_DELIM, i)) {
|
|
1562
|
+
const vStart = i + VAL_DELIM.length;
|
|
1563
|
+
const vEnd = text.indexOf(VAL_DELIM, vStart);
|
|
1564
|
+
if (vEnd === -1) return null;
|
|
1565
|
+
args2[key] = text.slice(vStart, vEnd);
|
|
1566
|
+
i = vEnd + VAL_DELIM.length;
|
|
1567
|
+
} else {
|
|
1568
|
+
const vStart = i;
|
|
1569
|
+
let depth = 0;
|
|
1570
|
+
let inStr = null;
|
|
1571
|
+
let esc = false;
|
|
1572
|
+
while (i < text.length) {
|
|
1573
|
+
const ch = text[i];
|
|
1574
|
+
if (inStr) {
|
|
1575
|
+
if (esc) esc = false;
|
|
1576
|
+
else if (ch === "\\") esc = true;
|
|
1577
|
+
else if (ch === inStr) inStr = null;
|
|
1578
|
+
} else if (ch === '"' || ch === "'") inStr = ch;
|
|
1579
|
+
else if (ch === "{" || ch === "[") depth++;
|
|
1580
|
+
else if (ch === "}" || ch === "]") {
|
|
1581
|
+
if (depth === 0) break;
|
|
1582
|
+
depth--;
|
|
1583
|
+
} else if (ch === "," && depth === 0) break;
|
|
1584
|
+
i++;
|
|
1585
|
+
}
|
|
1586
|
+
const rawVal = text.slice(vStart, i).trim();
|
|
1587
|
+
try {
|
|
1588
|
+
args2[key] = JSON.parse(rawVal);
|
|
1589
|
+
} catch {
|
|
1590
|
+
args2[key] = rawVal.replace(/^["']|["']$/g, "");
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1505
1596
|
function extractFirstJsonObject(s) {
|
|
1506
1597
|
const start = s.indexOf("{");
|
|
1507
1598
|
if (start === -1) return null;
|
|
@@ -1528,6 +1619,18 @@ function extractFirstJsonObject(s) {
|
|
|
1528
1619
|
}
|
|
1529
1620
|
return null;
|
|
1530
1621
|
}
|
|
1622
|
+
function looksLikeLeakedToolCall(text, knownToolNames) {
|
|
1623
|
+
if (!text) return false;
|
|
1624
|
+
if (text.includes(VAL_DELIM)) return true;
|
|
1625
|
+
if (/<\|?tool_call/i.test(text)) return true;
|
|
1626
|
+
const callMatch = text.match(/\bcall:\s*(\w+)\s*\{/);
|
|
1627
|
+
if (callMatch && knownToolNames.includes(callMatch[1])) return true;
|
|
1628
|
+
if (/"name"\s*:\s*"([^"]+)"/.test(text) && /"(arguments|parameters|input)"\s*:/.test(text)) {
|
|
1629
|
+
const name = text.match(/"name"\s*:\s*"([^"]+)"/)?.[1];
|
|
1630
|
+
if (name && knownToolNames.includes(name)) return true;
|
|
1631
|
+
}
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1531
1634
|
function blocksFromOllama(text, tool_calls, knownToolNames = []) {
|
|
1532
1635
|
const blocks = [];
|
|
1533
1636
|
let finalText = text;
|
|
@@ -1550,9 +1653,11 @@ function blocksFromOllama(text, tool_calls, knownToolNames = []) {
|
|
|
1550
1653
|
}
|
|
1551
1654
|
return blocks;
|
|
1552
1655
|
}
|
|
1656
|
+
var VAL_DELIM;
|
|
1553
1657
|
var init_adapter = __esm({
|
|
1554
1658
|
"src/agent/adapter.ts"() {
|
|
1555
1659
|
"use strict";
|
|
1660
|
+
VAL_DELIM = '<|"|>';
|
|
1556
1661
|
}
|
|
1557
1662
|
});
|
|
1558
1663
|
|
|
@@ -1615,12 +1720,17 @@ async function* runAgent(opts) {
|
|
|
1615
1720
|
const effort = EFFORT_OPTIONS[loadConfig().effort ?? "medium"];
|
|
1616
1721
|
const history = [
|
|
1617
1722
|
...opts.history,
|
|
1618
|
-
{
|
|
1723
|
+
{
|
|
1724
|
+
role: "user",
|
|
1725
|
+
content: opts.userText,
|
|
1726
|
+
...opts.images && opts.images.length > 0 ? { images: opts.images } : {}
|
|
1727
|
+
}
|
|
1619
1728
|
];
|
|
1620
1729
|
let promptTokens = 0;
|
|
1621
1730
|
let evalTokens = 0;
|
|
1622
1731
|
let lastAssistantSig = "";
|
|
1623
1732
|
let repeatCount = 0;
|
|
1733
|
+
let leakNudges = 0;
|
|
1624
1734
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
1625
1735
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
1626
1736
|
let text = "";
|
|
@@ -1705,6 +1815,16 @@ async function* runAgent(opts) {
|
|
|
1705
1815
|
continue;
|
|
1706
1816
|
}
|
|
1707
1817
|
if (tool_uses.length === 0) {
|
|
1818
|
+
const assistantText = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1819
|
+
if (leakNudges < MAX_LEAK_NUDGES && looksLikeLeakedToolCall(assistantText, toolNames)) {
|
|
1820
|
+
leakNudges++;
|
|
1821
|
+
history.push({
|
|
1822
|
+
role: "user",
|
|
1823
|
+
content: "That tool call was written as plain text, so it did not run and nothing happened. Re-issue it using the function-calling interface only \u2014 do not print the call as text, JSON, or any custom syntax. If you did not mean to call a tool, answer in prose."
|
|
1824
|
+
});
|
|
1825
|
+
yield { type: "turn-end", stop_reason: "tool_use" };
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1708
1828
|
yield { type: "turn-end", stop_reason: "end_turn" };
|
|
1709
1829
|
break;
|
|
1710
1830
|
}
|
|
@@ -1812,7 +1932,7 @@ async function* runAgent(opts) {
|
|
|
1812
1932
|
yield { type: "done", prompt_tokens: promptTokens, eval_tokens: evalTokens };
|
|
1813
1933
|
return history;
|
|
1814
1934
|
}
|
|
1815
|
-
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL, BIG_WRITE_TOOLS;
|
|
1935
|
+
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL, MAX_LEAK_NUDGES, BIG_WRITE_TOOLS;
|
|
1816
1936
|
var init_loop = __esm({
|
|
1817
1937
|
"src/agent/loop.ts"() {
|
|
1818
1938
|
"use strict";
|
|
@@ -1828,21 +1948,22 @@ var init_loop = __esm({
|
|
|
1828
1948
|
MAX_TURNS = 25;
|
|
1829
1949
|
REPEAT_TAIL = 120;
|
|
1830
1950
|
REPEAT_KILL = 4;
|
|
1951
|
+
MAX_LEAK_NUDGES = 2;
|
|
1831
1952
|
BIG_WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
|
|
1832
1953
|
}
|
|
1833
1954
|
});
|
|
1834
1955
|
|
|
1835
1956
|
// eval/runner.ts
|
|
1836
|
-
import { mkdtempSync, writeFileSync as
|
|
1837
|
-
import { dirname as dirname3, join as
|
|
1838
|
-
import { tmpdir } from "os";
|
|
1957
|
+
import { mkdtempSync, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, rmSync as rmSync4 } from "fs";
|
|
1958
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
1959
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1839
1960
|
async function runScenario(model, s) {
|
|
1840
|
-
const dir = mkdtempSync(
|
|
1961
|
+
const dir = mkdtempSync(join11(tmpdir2(), "miii-eval-"));
|
|
1841
1962
|
const prevCwd = process.cwd();
|
|
1842
1963
|
for (const [rel, content] of Object.entries(s.files ?? {})) {
|
|
1843
|
-
const abs =
|
|
1844
|
-
|
|
1845
|
-
|
|
1964
|
+
const abs = join11(dir, rel);
|
|
1965
|
+
mkdirSync7(dirname3(abs), { recursive: true });
|
|
1966
|
+
writeFileSync8(abs, content, "utf-8");
|
|
1846
1967
|
}
|
|
1847
1968
|
const r = {
|
|
1848
1969
|
name: s.name,
|
|
@@ -1880,7 +2001,7 @@ async function runScenario(model, s) {
|
|
|
1880
2001
|
r.durationMs = Date.now() - start;
|
|
1881
2002
|
if (r.error) {
|
|
1882
2003
|
r.reason = `loop error: ${r.error}`;
|
|
1883
|
-
|
|
2004
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
1884
2005
|
return r;
|
|
1885
2006
|
}
|
|
1886
2007
|
try {
|
|
@@ -1890,7 +2011,7 @@ async function runScenario(model, s) {
|
|
|
1890
2011
|
} catch (err) {
|
|
1891
2012
|
r.reason = `check threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
1892
2013
|
}
|
|
1893
|
-
|
|
2014
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
1894
2015
|
return r;
|
|
1895
2016
|
}
|
|
1896
2017
|
var autoYes;
|
|
@@ -1903,13 +2024,13 @@ var init_runner = __esm({
|
|
|
1903
2024
|
});
|
|
1904
2025
|
|
|
1905
2026
|
// eval/scenarios.ts
|
|
1906
|
-
import { readFileSync as
|
|
1907
|
-
import { join as
|
|
2027
|
+
import { readFileSync as readFileSync10, existsSync as existsSync8 } from "fs";
|
|
2028
|
+
import { join as join12 } from "path";
|
|
1908
2029
|
var read, scenarios;
|
|
1909
2030
|
var init_scenarios = __esm({
|
|
1910
2031
|
"eval/scenarios.ts"() {
|
|
1911
2032
|
"use strict";
|
|
1912
|
-
read = (dir, f) =>
|
|
2033
|
+
read = (dir, f) => existsSync8(join12(dir, f)) ? readFileSync10(join12(dir, f), "utf-8") : null;
|
|
1913
2034
|
scenarios = [
|
|
1914
2035
|
{
|
|
1915
2036
|
name: "edit-exact-string",
|
|
@@ -2059,13 +2180,25 @@ init_client();
|
|
|
2059
2180
|
init_config();
|
|
2060
2181
|
import { useState as useState5, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
2061
2182
|
import { Box as Box13, Text as Text13, useApp } from "ink";
|
|
2062
|
-
import { homedir as
|
|
2183
|
+
import { homedir as homedir8 } from "os";
|
|
2063
2184
|
import { sep as sep2 } from "path";
|
|
2064
2185
|
|
|
2065
2186
|
// src/ui/WelcomeBlock.tsx
|
|
2066
2187
|
import { Box, Text } from "ink";
|
|
2067
2188
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2068
|
-
function
|
|
2189
|
+
function updateBannerText(version, status) {
|
|
2190
|
+
switch (status) {
|
|
2191
|
+
case "downloading":
|
|
2192
|
+
return `\u2191 v${version} downloading in the background \u2014 restart miii to apply`;
|
|
2193
|
+
case "installed":
|
|
2194
|
+
return `\u2713 v${version} installed \u2014 restart miii to apply`;
|
|
2195
|
+
case "failed":
|
|
2196
|
+
return `\u2191 v${version} update failed \u2014 run \`miii update\` manually`;
|
|
2197
|
+
default:
|
|
2198
|
+
return `\u2191 v${version} available \u2014 run \`miii update\` to upgrade`;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
function WelcomeBlock({ model, activeCtx, effort, cwd, updateAvailable, updateStatus = "idle" }) {
|
|
2069
2202
|
const ctxLabel = activeCtx != null ? `${Math.round(activeCtx / 1024)}k ctx` : "\u2014 ctx";
|
|
2070
2203
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2071
2204
|
/* @__PURE__ */ jsxs(
|
|
@@ -2092,7 +2225,7 @@ function WelcomeBlock({ model, activeCtx, effort, cwd, updateAvailable }) {
|
|
|
2092
2225
|
]
|
|
2093
2226
|
}
|
|
2094
2227
|
),
|
|
2095
|
-
updateAvailable && /* @__PURE__ */ jsx(Text, { color: "
|
|
2228
|
+
updateAvailable && /* @__PURE__ */ jsx(Text, { color: updateStatus === "failed" ? "red" : updateStatus === "installed" ? "green" : "yellow", children: updateBannerText(updateAvailable, updateStatus) })
|
|
2096
2229
|
] });
|
|
2097
2230
|
}
|
|
2098
2231
|
|
|
@@ -2346,7 +2479,8 @@ function persistSession(id, messages, title) {
|
|
|
2346
2479
|
};
|
|
2347
2480
|
const lines = [JSON.stringify({ type: "meta", ...meta })];
|
|
2348
2481
|
for (const message of messages) {
|
|
2349
|
-
|
|
2482
|
+
const { images: _img, ...rest } = message;
|
|
2483
|
+
lines.push(JSON.stringify({ type: "message", message: rest }));
|
|
2350
2484
|
}
|
|
2351
2485
|
writeFileSync2(sessionPath(id), lines.join("\n") + "\n", "utf-8");
|
|
2352
2486
|
}
|
|
@@ -3240,6 +3374,7 @@ var EMPTY_STATE_HINTS = [
|
|
|
3240
3374
|
"\u2022 /models \u2014 switch model or effort",
|
|
3241
3375
|
"\u2022 /new \u2014 start a new chat",
|
|
3242
3376
|
"\u2022 /sessions \u2014 view saved chats",
|
|
3377
|
+
"\u2022 ctrl+v \u2014 paste an image (needs a vision model)",
|
|
3243
3378
|
"\u2022 ctrl+t \u2014 toggle thinking"
|
|
3244
3379
|
];
|
|
3245
3380
|
var EMPTY_STATE_TITLE = "Ask anything, or try:";
|
|
@@ -3685,7 +3820,7 @@ function useAgentRunner(model, activeCtx) {
|
|
|
3685
3820
|
setPendingPermission(null);
|
|
3686
3821
|
req.resolve(answers[cursor]);
|
|
3687
3822
|
}
|
|
3688
|
-
async function sendMessage(text) {
|
|
3823
|
+
async function sendMessage(text, images) {
|
|
3689
3824
|
if (busyRef.current || !model) return;
|
|
3690
3825
|
busyRef.current = true;
|
|
3691
3826
|
setBusy(true);
|
|
@@ -3743,6 +3878,7 @@ function useAgentRunner(model, activeCtx) {
|
|
|
3743
3878
|
cwd: process.cwd(),
|
|
3744
3879
|
history: agentHistory,
|
|
3745
3880
|
userText: text,
|
|
3881
|
+
images,
|
|
3746
3882
|
permissions: { ask: askPermission },
|
|
3747
3883
|
signal: controller.signal,
|
|
3748
3884
|
num_ctx: activeCtx ?? void 0
|
|
@@ -3872,16 +4008,141 @@ function useAgentRunner(model, activeCtx) {
|
|
|
3872
4008
|
}
|
|
3873
4009
|
|
|
3874
4010
|
// src/ui/hooks/useKeyboard.ts
|
|
3875
|
-
|
|
4011
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
4012
|
+
import { basename, join as join9 } from "path";
|
|
4013
|
+
import { homedir as homedir6 } from "os";
|
|
3876
4014
|
import { useInput, useStdout } from "ink";
|
|
4015
|
+
|
|
4016
|
+
// src/ui/clipboard.ts
|
|
4017
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4018
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
|
|
4019
|
+
import { join as join8 } from "path";
|
|
4020
|
+
import { tmpdir } from "os";
|
|
4021
|
+
function safeRm(p) {
|
|
4022
|
+
try {
|
|
4023
|
+
rmSync3(p, { force: true });
|
|
4024
|
+
} catch {
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
function consume(p) {
|
|
4028
|
+
try {
|
|
4029
|
+
const b64 = readFileSync7(p).toString("base64");
|
|
4030
|
+
return b64.length > 0 ? b64 : null;
|
|
4031
|
+
} catch {
|
|
4032
|
+
return null;
|
|
4033
|
+
} finally {
|
|
4034
|
+
safeRm(p);
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
function readMac(out) {
|
|
4038
|
+
try {
|
|
4039
|
+
execFileSync2("pngpaste", [out], { stdio: "ignore" });
|
|
4040
|
+
if (existsSync6(out)) return consume(out);
|
|
4041
|
+
} catch {
|
|
4042
|
+
}
|
|
4043
|
+
const png = "\xABclass PNGf\xBB";
|
|
4044
|
+
const script = [
|
|
4045
|
+
"try",
|
|
4046
|
+
`set f to open for access (POSIX file "${out}") with write permission`,
|
|
4047
|
+
`set theData to (the clipboard as ${png})`,
|
|
4048
|
+
"write theData to f",
|
|
4049
|
+
"close access f",
|
|
4050
|
+
"on error",
|
|
4051
|
+
"try",
|
|
4052
|
+
"close access f",
|
|
4053
|
+
"end try",
|
|
4054
|
+
'return "NOIMG"',
|
|
4055
|
+
"end try"
|
|
4056
|
+
];
|
|
4057
|
+
try {
|
|
4058
|
+
const res = execFileSync2("osascript", script.flatMap((s) => ["-e", s]), { encoding: "utf8" });
|
|
4059
|
+
if (res.includes("NOIMG")) {
|
|
4060
|
+
safeRm(out);
|
|
4061
|
+
return null;
|
|
4062
|
+
}
|
|
4063
|
+
if (existsSync6(out)) return consume(out);
|
|
4064
|
+
} catch {
|
|
4065
|
+
}
|
|
4066
|
+
safeRm(out);
|
|
4067
|
+
return null;
|
|
4068
|
+
}
|
|
4069
|
+
function readLinux(out) {
|
|
4070
|
+
for (const [cmd2, args2] of [
|
|
4071
|
+
["wl-paste", ["--type", "image/png"]],
|
|
4072
|
+
["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
|
|
4073
|
+
]) {
|
|
4074
|
+
try {
|
|
4075
|
+
const buf = execFileSync2(cmd2, args2, { maxBuffer: 64 * 1024 * 1024 });
|
|
4076
|
+
if (buf.length > 0) return buf.toString("base64");
|
|
4077
|
+
} catch {
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
return null;
|
|
4081
|
+
}
|
|
4082
|
+
function readWindows(out) {
|
|
4083
|
+
const ps = [
|
|
4084
|
+
"Add-Type -AssemblyName System.Windows.Forms,System.Drawing;",
|
|
4085
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage();",
|
|
4086
|
+
// Single-quoted PS string → backslashes are literal, no escaping needed.
|
|
4087
|
+
`if ($img -ne $null) { $img.Save('${out}', [System.Drawing.Imaging.ImageFormat]::Png); 'OK' } else { 'NOIMG' }`
|
|
4088
|
+
].join(" ");
|
|
4089
|
+
try {
|
|
4090
|
+
const res = execFileSync2(
|
|
4091
|
+
"powershell",
|
|
4092
|
+
["-NoProfile", "-NonInteractive", "-STA", "-Command", ps],
|
|
4093
|
+
{ encoding: "utf8" }
|
|
4094
|
+
);
|
|
4095
|
+
if (res.includes("NOIMG")) {
|
|
4096
|
+
safeRm(out);
|
|
4097
|
+
return null;
|
|
4098
|
+
}
|
|
4099
|
+
if (existsSync6(out)) return consume(out);
|
|
4100
|
+
} catch {
|
|
4101
|
+
}
|
|
4102
|
+
safeRm(out);
|
|
4103
|
+
return null;
|
|
4104
|
+
}
|
|
4105
|
+
function readClipboardImage() {
|
|
4106
|
+
const out = join8(tmpdir(), `miii-clip-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
|
|
4107
|
+
if (process.platform === "darwin") return readMac(out);
|
|
4108
|
+
if (process.platform === "linux") return readLinux(out);
|
|
4109
|
+
if (process.platform === "win32") return readWindows(out);
|
|
4110
|
+
return null;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
// src/ui/hooks/useKeyboard.ts
|
|
4114
|
+
init_config();
|
|
3877
4115
|
var EFFORTS = ["low", "medium", "high"];
|
|
3878
4116
|
var PASTE_CHIP_LINES = 4;
|
|
3879
4117
|
var PASTE_CHIP_CHARS = 200;
|
|
3880
4118
|
var pasteStore = /* @__PURE__ */ new Map();
|
|
3881
4119
|
var pasteCounter = 0;
|
|
4120
|
+
var imageStore = /* @__PURE__ */ new Map();
|
|
4121
|
+
var imageCounter = 0;
|
|
4122
|
+
var IMAGE_EXT_RE = /\.(png|jpe?g|webp|gif|bmp)$/i;
|
|
3882
4123
|
function clearPasteStore() {
|
|
3883
4124
|
pasteStore.clear();
|
|
3884
4125
|
pasteCounter = 0;
|
|
4126
|
+
imageStore.clear();
|
|
4127
|
+
imageCounter = 0;
|
|
4128
|
+
}
|
|
4129
|
+
function tryImagePaste(cleaned) {
|
|
4130
|
+
let p = cleaned.trim();
|
|
4131
|
+
if (p.startsWith('"') && p.endsWith('"') || p.startsWith("'") && p.endsWith("'")) {
|
|
4132
|
+
p = p.slice(1, -1);
|
|
4133
|
+
}
|
|
4134
|
+
p = p.replace(/\\ /g, " ");
|
|
4135
|
+
if (p.includes("\n") || !IMAGE_EXT_RE.test(p)) return null;
|
|
4136
|
+
if (p.startsWith("~/")) p = join9(homedir6(), p.slice(2));
|
|
4137
|
+
if (!existsSync7(p)) return null;
|
|
4138
|
+
try {
|
|
4139
|
+
const b64 = readFileSync8(p).toString("base64");
|
|
4140
|
+
const chip = `[Image #${++imageCounter} \xB7 ${basename(p)}]`;
|
|
4141
|
+
imageStore.set(chip, b64);
|
|
4142
|
+
return chip;
|
|
4143
|
+
} catch {
|
|
4144
|
+
return null;
|
|
4145
|
+
}
|
|
3885
4146
|
}
|
|
3886
4147
|
var inputHistory = [];
|
|
3887
4148
|
var historyIndex = -1;
|
|
@@ -3902,6 +4163,8 @@ function stripControls(chunk) {
|
|
|
3902
4163
|
function sanitizePaste(chunk) {
|
|
3903
4164
|
if (chunk.length <= 1) return chunk;
|
|
3904
4165
|
const cleaned = stripControls(chunk).replace(/\r/g, "");
|
|
4166
|
+
const imageChip = tryImagePaste(cleaned);
|
|
4167
|
+
if (imageChip) return imageChip;
|
|
3905
4168
|
const lines = cleaned.split("\n").length;
|
|
3906
4169
|
if (lines > PASTE_CHIP_LINES || cleaned.length > PASTE_CHIP_CHARS) {
|
|
3907
4170
|
const chip = `[Pasted #${++pasteCounter} \xB7 ${lines} line${lines === 1 ? "" : "s"}]`;
|
|
@@ -4136,6 +4399,19 @@ function useKeyboard(opts) {
|
|
|
4136
4399
|
}
|
|
4137
4400
|
if (state === "ready") {
|
|
4138
4401
|
if (busyRef.current) return;
|
|
4402
|
+
if (key.ctrl && char === "v") {
|
|
4403
|
+
const b64 = readClipboardImage();
|
|
4404
|
+
if (!b64) {
|
|
4405
|
+
setNotice("no image in clipboard");
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
const chip = `[Image #${++imageCounter} \xB7 clipboard]`;
|
|
4409
|
+
imageStore.set(chip, b64);
|
|
4410
|
+
historyIndex = -1;
|
|
4411
|
+
setInput((s) => s.slice(0, caret) + chip + s.slice(caret));
|
|
4412
|
+
setCaret((i) => i + chip.length);
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4139
4415
|
const paletteOpen = input.startsWith("/");
|
|
4140
4416
|
const matches2 = paletteOpen ? filteredCommands(input) : [];
|
|
4141
4417
|
const mention = !paletteOpen ? parseMention(input) : null;
|
|
@@ -4248,8 +4524,16 @@ function useKeyboard(opts) {
|
|
|
4248
4524
|
}
|
|
4249
4525
|
} else if (trimmed) {
|
|
4250
4526
|
setNotice(null);
|
|
4251
|
-
const
|
|
4252
|
-
|
|
4527
|
+
const images = [];
|
|
4528
|
+
let textPart = trimmed;
|
|
4529
|
+
for (const [chip, b64] of imageStore) {
|
|
4530
|
+
if (textPart.includes(chip)) {
|
|
4531
|
+
images.push(b64);
|
|
4532
|
+
textPart = textPart.split(chip).join("").trim();
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
const message = expandPastes(textPart) || "Describe the attached image.";
|
|
4536
|
+
sendMessage(message, images.length ? images : void 0);
|
|
4253
4537
|
}
|
|
4254
4538
|
clearPasteStore();
|
|
4255
4539
|
setInput(() => "");
|
|
@@ -4283,8 +4567,14 @@ function useKeyboard(opts) {
|
|
|
4283
4567
|
for (const chip of pasteStore.keys()) {
|
|
4284
4568
|
if (before.endsWith(chip) && chip.length > match.length) match = chip;
|
|
4285
4569
|
}
|
|
4570
|
+
for (const chip of imageStore.keys()) {
|
|
4571
|
+
if (before.endsWith(chip) && chip.length > match.length) match = chip;
|
|
4572
|
+
}
|
|
4286
4573
|
const cut = match ? match.length : 1;
|
|
4287
|
-
if (match)
|
|
4574
|
+
if (match) {
|
|
4575
|
+
pasteStore.delete(match);
|
|
4576
|
+
imageStore.delete(match);
|
|
4577
|
+
}
|
|
4288
4578
|
setInput((s) => s.slice(0, caret - cut) + s.slice(caret));
|
|
4289
4579
|
setCaret((i) => Math.max(0, i - cut));
|
|
4290
4580
|
} else if (char && !key.ctrl && !key.meta && !key.tab) {
|
|
@@ -4303,8 +4593,28 @@ function useKeyboard(opts) {
|
|
|
4303
4593
|
|
|
4304
4594
|
// src/updateCheck.ts
|
|
4305
4595
|
import { createRequire } from "module";
|
|
4596
|
+
import { homedir as homedir7 } from "os";
|
|
4597
|
+
import { join as join10 } from "path";
|
|
4598
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
4306
4599
|
var require2 = createRequire(import.meta.url);
|
|
4307
4600
|
var PKG_NAME = "miii-agent";
|
|
4601
|
+
var UPDATE_ATTEMPT_PATH = join10(homedir7(), ".miii", ".last-update-attempt");
|
|
4602
|
+
var UPDATE_ATTEMPT_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
4603
|
+
function recentlyAttemptedUpdate() {
|
|
4604
|
+
try {
|
|
4605
|
+
const last = Number(readFileSync9(UPDATE_ATTEMPT_PATH, "utf8").trim());
|
|
4606
|
+
return Number.isFinite(last) && Date.now() - last < UPDATE_ATTEMPT_COOLDOWN_MS;
|
|
4607
|
+
} catch {
|
|
4608
|
+
return false;
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
function markUpdateAttempt() {
|
|
4612
|
+
try {
|
|
4613
|
+
mkdirSync6(join10(homedir7(), ".miii"), { recursive: true });
|
|
4614
|
+
writeFileSync7(UPDATE_ATTEMPT_PATH, String(Date.now()));
|
|
4615
|
+
} catch {
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4308
4618
|
function currentVersion() {
|
|
4309
4619
|
try {
|
|
4310
4620
|
return require2("../package.json").version;
|
|
@@ -4320,6 +4630,30 @@ function newerVersion(current, latest) {
|
|
|
4320
4630
|
if (lb !== cb) return lb > cb;
|
|
4321
4631
|
return lc > cc;
|
|
4322
4632
|
}
|
|
4633
|
+
function autoUpdate(onDone) {
|
|
4634
|
+
if (recentlyAttemptedUpdate()) return false;
|
|
4635
|
+
try {
|
|
4636
|
+
markUpdateAttempt();
|
|
4637
|
+
const { spawn } = require2("child_process");
|
|
4638
|
+
const child = spawn("npm", ["i", "-g", `${PKG_NAME}@latest`], {
|
|
4639
|
+
detached: true,
|
|
4640
|
+
stdio: "ignore",
|
|
4641
|
+
shell: process.platform === "win32"
|
|
4642
|
+
});
|
|
4643
|
+
let settled = false;
|
|
4644
|
+
const settle = (ok) => {
|
|
4645
|
+
if (settled) return;
|
|
4646
|
+
settled = true;
|
|
4647
|
+
onDone?.(ok);
|
|
4648
|
+
};
|
|
4649
|
+
child.on("error", () => settle(false));
|
|
4650
|
+
child.on("exit", (code) => settle(code === 0));
|
|
4651
|
+
child.unref();
|
|
4652
|
+
return true;
|
|
4653
|
+
} catch {
|
|
4654
|
+
return false;
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4323
4657
|
async function checkForUpdate() {
|
|
4324
4658
|
try {
|
|
4325
4659
|
const res = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {
|
|
@@ -4340,7 +4674,7 @@ async function checkForUpdate() {
|
|
|
4340
4674
|
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4341
4675
|
function App() {
|
|
4342
4676
|
const { exit } = useApp();
|
|
4343
|
-
const cwd = process.cwd().replace(
|
|
4677
|
+
const cwd = process.cwd().replace(homedir8(), "~").split(sep2).join("/");
|
|
4344
4678
|
const [cfg, setCfg] = useState5(loadConfig());
|
|
4345
4679
|
const [models, setModels] = useState5([]);
|
|
4346
4680
|
const [contexts, setContexts] = useState5(() => cfg.modelContexts ?? {});
|
|
@@ -4351,6 +4685,7 @@ function App() {
|
|
|
4351
4685
|
const [cursor, setCursor] = useState5(0);
|
|
4352
4686
|
const [pickerQuery, setPickerQuery] = useState5("");
|
|
4353
4687
|
const [updateAvailable, setUpdateAvailable] = useState5(null);
|
|
4688
|
+
const [updateStatus, setUpdateStatus] = useState5("idle");
|
|
4354
4689
|
const [providerDown, setProviderDown] = useState5(false);
|
|
4355
4690
|
const [sessionId, setSessionId] = useState5(() => newSessionId());
|
|
4356
4691
|
const [sessions, setSessions] = useState5([]);
|
|
@@ -4362,7 +4697,12 @@ function App() {
|
|
|
4362
4697
|
const agent = useAgentRunner(cfg.model, activeCtx);
|
|
4363
4698
|
useEffect4(() => {
|
|
4364
4699
|
checkForUpdate().then((v) => {
|
|
4365
|
-
if (v)
|
|
4700
|
+
if (!v) return;
|
|
4701
|
+
setUpdateAvailable(v);
|
|
4702
|
+
if (autoUpdateEnabled()) {
|
|
4703
|
+
const started = autoUpdate((ok) => setUpdateStatus(ok ? "installed" : "failed"));
|
|
4704
|
+
if (started) setUpdateStatus("downloading");
|
|
4705
|
+
}
|
|
4366
4706
|
});
|
|
4367
4707
|
}, []);
|
|
4368
4708
|
const titledSessions = useRef2(/* @__PURE__ */ new Set());
|
|
@@ -4479,7 +4819,7 @@ function App() {
|
|
|
4479
4819
|
return Math.round(used / activeCtx * 100);
|
|
4480
4820
|
})();
|
|
4481
4821
|
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, children: [
|
|
4482
|
-
state !== "ready" && /* @__PURE__ */ jsx13(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error, updateAvailable }),
|
|
4822
|
+
state !== "ready" && /* @__PURE__ */ jsx13(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error, updateAvailable, updateStatus }),
|
|
4483
4823
|
state === "loading" && !agent.error && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `connecting to ${provName}\u2026` }) }),
|
|
4484
4824
|
agent.error && state !== "ready" && /* @__PURE__ */ jsx13(
|
|
4485
4825
|
ChatView,
|
|
@@ -4540,7 +4880,8 @@ function App() {
|
|
|
4540
4880
|
return /* @__PURE__ */ jsx13(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
|
|
4541
4881
|
})(),
|
|
4542
4882
|
/* @__PURE__ */ jsx13(InputBar, { input, caret, disabled: agent.busy, processingLabel: agent.processingLabel }),
|
|
4543
|
-
!agent.busy && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: providerDown ? "provider unavailable \u2014 /provider to switch \xB7 /models to pick a model" : "type / to see commands" }) })
|
|
4883
|
+
!agent.busy && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: providerDown ? "provider unavailable \u2014 /provider to switch \xB7 /models to pick a model" : "type / to see commands" }) }),
|
|
4884
|
+
updateAvailable && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: updateStatus === "failed" ? "red" : updateStatus === "installed" ? "green" : "yellow", children: updateBannerText(updateAvailable, updateStatus) }) })
|
|
4544
4885
|
] })
|
|
4545
4886
|
] });
|
|
4546
4887
|
}
|
|
@@ -4559,7 +4900,12 @@ for (let i = 0; i < args.length; i++) {
|
|
|
4559
4900
|
cmd = args[i];
|
|
4560
4901
|
}
|
|
4561
4902
|
}
|
|
4562
|
-
if (cmd === "
|
|
4903
|
+
if (cmd === "version" || cmd === "--version" || cmd === "-v") {
|
|
4904
|
+
const { createRequire: createRequire2 } = await import("module");
|
|
4905
|
+
const pkg = createRequire2(import.meta.url)("../package.json");
|
|
4906
|
+
console.log(pkg.version);
|
|
4907
|
+
process.exit(0);
|
|
4908
|
+
} else if (cmd === "update" || cmd === "--update" || cmd === "-u") {
|
|
4563
4909
|
const { spawnSync } = await import("child_process");
|
|
4564
4910
|
console.log("Updating miii-agent\u2026");
|
|
4565
4911
|
const r = spawnSync("npm", ["i", "-g", "miii-agent@latest"], { stdio: "inherit", shell: process.platform === "win32" });
|
package/package.json
CHANGED