miii-cli 1.2.0 → 1.2.1
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 +6 -6
- package/dist/config.js +3 -1
- package/dist/parser/stream-parser.js +4 -4
- package/dist/sessions.js +1 -0
- package/dist/tasks/compactor.js +3 -0
- package/dist/tools/index.js +1 -1
- package/dist/tui/InputBar.js +4 -12
- package/dist/tui/hooks/useRunLoop.js +9 -1
- package/dist/tui/image.js +83 -0
- package/dist/tui/printer.js +8 -0
- package/dist/tui/thinking.js +93 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
**Miii is a fully autonomous coding agent that runs entirely on your machine.** It plans, edits files, runs your tests, searches the web, indexes your codebase semantically, and iterates until the job is done — all without a single byte of your code leaving your network.
|
|
15
15
|
|
|
16
|
-
Zero subscription. Zero cloud dependency. Zero Python overhead. **
|
|
16
|
+
Zero subscription. Zero cloud dependency. Zero Python overhead. **Lightning fast startup.**
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install -g miii-cli && miii
|
|
@@ -52,9 +52,9 @@ Your compute. Your data. Your rules.
|
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## How it Works
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Miii isn't just autocomplete—it's a **full autonomous agent loop** that reasons through complex tasks:
|
|
58
58
|
|
|
59
59
|
1. You describe a goal
|
|
60
60
|
2. Miii reads your codebase, plans the changes, edits the files
|
|
@@ -95,7 +95,7 @@ This isn't autocomplete. Miii is a **full autonomous agent loop:**
|
|
|
95
95
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
|
-
##
|
|
98
|
+
## 🚀 Core Capabilities
|
|
99
99
|
|
|
100
100
|
**🔒 Privacy-First, Local by Default**
|
|
101
101
|
Run on Ollama and your code never leaves your machine. No account. No API key. No monthly bill. Switch to Anthropic or OpenAI when you need it — one command, live, mid-session.
|
|
@@ -132,7 +132,7 @@ Connect any MCP-compatible tool server. Miii discovers tools automatically and m
|
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
135
|
-
##
|
|
135
|
+
## ⚡ Quick Start
|
|
136
136
|
|
|
137
137
|
```bash
|
|
138
138
|
# 1. Start Ollama and pull a model
|
|
@@ -150,7 +150,7 @@ No API keys. No account. No sign-up form. First run walks you through setup inte
|
|
|
150
150
|
|
|
151
151
|
---
|
|
152
152
|
|
|
153
|
-
## Power Commands
|
|
153
|
+
## ⌨️ Power Commands
|
|
154
154
|
|
|
155
155
|
| Command | What it does |
|
|
156
156
|
|---|---|
|
package/dist/config.js
CHANGED
|
@@ -99,12 +99,12 @@ function extractFileToolArgs(text, toolName) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
// For patch_file: extract old/new fields
|
|
102
|
-
const oldM = text.match(/"old"\s*:\s*"([\s\S]
|
|
102
|
+
const oldM = text.match(/"old"\s*:\s*"((?:[^"\\]|\\[\s\S])*)"/);
|
|
103
103
|
if (oldM)
|
|
104
|
-
args.old = oldM[1].replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
|
105
|
-
const newM = text.match(/"new"\s*:\s*"([\s\S]
|
|
104
|
+
args.old = oldM[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
105
|
+
const newM = text.match(/"new"\s*:\s*"((?:[^"\\]|\\[\s\S])*)"/);
|
|
106
106
|
if (newM)
|
|
107
|
-
args.new = newM[1].replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
|
107
|
+
args.new = newM[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
108
108
|
return Object.keys(args).length > 0 ? args : null;
|
|
109
109
|
}
|
|
110
110
|
// Extract a bare tool-call JSON from arbitrary text (LLM skipped <tool_call> wrapper)
|
package/dist/sessions.js
CHANGED
package/dist/tasks/compactor.js
CHANGED
|
@@ -66,9 +66,12 @@ export async function compactContext(messages, cfg, goal) {
|
|
|
66
66
|
role: 'user',
|
|
67
67
|
content: `[Context compacted — ${toSummarize.length} messages summarised]\n\n${summary}`,
|
|
68
68
|
};
|
|
69
|
+
const needsBridge = recent.length > 0 && recent[0].role === 'user';
|
|
70
|
+
const bridgeMsg = { role: 'assistant', content: 'Understood. Continuing from the summarized context.' };
|
|
69
71
|
return [
|
|
70
72
|
...(system ? [system] : []),
|
|
71
73
|
summaryMsg,
|
|
74
|
+
...(needsBridge ? [bridgeMsg] : []),
|
|
72
75
|
...recent,
|
|
73
76
|
];
|
|
74
77
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -193,7 +193,7 @@ export const tools = [
|
|
|
193
193
|
if (!message)
|
|
194
194
|
throw new Error('git_commit: message required');
|
|
195
195
|
const fileStr = String(files);
|
|
196
|
-
if (/\.\./.test(fileStr) || !/^(-A|\.|[\w
|
|
196
|
+
if (/\.\./.test(fileStr) || !/^(-A|\.|[\w./\-]+(?: [\w./\-]+)*)$/.test(fileStr))
|
|
197
197
|
throw new Error('git_commit: invalid files argument — use -A, ., or space-separated paths (no .. allowed)');
|
|
198
198
|
try {
|
|
199
199
|
const fileArgs = fileStr === '-A' ? ['-A'] : fileStr === '.' ? ['.'] : fileStr.split(/\s+/).filter(Boolean);
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ModelPicker } from './components/ModelPicker.js';
|
|
|
6
6
|
import { ConfigPicker } from './components/ConfigPicker.js';
|
|
7
7
|
import { Divider } from './components/StatusBar.js';
|
|
8
8
|
import { tools } from '../tools/index.js';
|
|
9
|
-
import { toolArgSummary } from './printer.js';
|
|
9
|
+
import { toolArgSummary, formatElapsed } from './printer.js';
|
|
10
10
|
import { MacroQueue } from '../tasks/queue.js';
|
|
11
11
|
import { TaskExecutor } from '../tasks/executor.js';
|
|
12
12
|
import { THINKING_PHRASES, SPARKLE } from './thinking.js';
|
|
@@ -23,14 +23,6 @@ import { createSearchCodebaseTool } from '../index/tool.js';
|
|
|
23
23
|
import { saveConfig } from '../config.js';
|
|
24
24
|
import { getTavilyKey, saveTavilyKey } from '../tavily/client.js';
|
|
25
25
|
import { warmup } from '../llm/stream.js';
|
|
26
|
-
function formatElapsed(ms) {
|
|
27
|
-
const s = Math.floor(ms / 1000);
|
|
28
|
-
if (s < 60)
|
|
29
|
-
return `${s}s`;
|
|
30
|
-
const m = Math.floor(s / 60);
|
|
31
|
-
const rem = s % 60;
|
|
32
|
-
return rem === 0 ? `${m}m` : `${m}m ${rem}s`;
|
|
33
|
-
}
|
|
34
26
|
const MAX_DIFF_LINES = 40;
|
|
35
27
|
const DIFF_CTX = 2;
|
|
36
28
|
function lineDiff(oldText, newText) {
|
|
@@ -143,7 +135,7 @@ export function InputBar({ config: initialConfig, skills, cwd, session, version,
|
|
|
143
135
|
setConfig(c => ({ ...c, ...configPatch }));
|
|
144
136
|
saveConfig(configPatch);
|
|
145
137
|
}
|
|
146
|
-
}, onTavilyKey: (key) => { saveTavilyKey(key); setTavilyKey(key); }, onClose: () => { setConfigOpen(false); } }), _jsx(Divider, { cols: cols })] })) : pickerOpen ? (_jsxs(_Fragment, { children: [_jsx(ModelPicker, { models: pickerModels, current: currentModel, loading: pickerLoading, error: pickerError, pull: pullState, onSelect: handleModelSelect, onPull: handleModelPull, onClose: () => { setPickerOpen(false); } }), _jsx(Divider, { cols: cols })] })) : compactRequest ? (_jsxs(Box, { paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0" }), _jsx(Text, { color: "white", bold: true, children: "context is large" }), _jsxs(Text, { color: "gray", children: ["(~", compactRequest.messageCount, "k chars)"] })] }), _jsx(Text, { color: "gray", dimColor: true, children: "compact to keep responses fast, or keep full history" })] })) : permissionRequest ? (_jsxs(Box, { paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0" }), _jsx(Text, { color: "white", bold: true, children: permissionRequest.toolName }), _jsx(Text, { color: "gray", children: toolArgSummary(permissionRequest.args) })] }), _jsx(DiffPreview, { toolName: permissionRequest.toolName, args: permissionRequest.args })] })) : (status === 'thinking' || status === 'tool') ? (
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
}, onTavilyKey: (key) => { saveTavilyKey(key); setTavilyKey(key); }, onClose: () => { setConfigOpen(false); } }), _jsx(Divider, { cols: cols })] })) : pickerOpen ? (_jsxs(_Fragment, { children: [_jsx(ModelPicker, { models: pickerModels, current: currentModel, loading: pickerLoading, error: pickerError, pull: pullState, onSelect: handleModelSelect, onPull: handleModelPull, onClose: () => { setPickerOpen(false); } }), _jsx(Divider, { cols: cols })] })) : compactRequest ? (_jsxs(Box, { paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0" }), _jsx(Text, { color: "white", bold: true, children: "context is large" }), _jsxs(Text, { color: "gray", children: ["(~", compactRequest.messageCount, "k chars)"] })] }), _jsx(Text, { color: "gray", dimColor: true, children: "compact to keep responses fast, or keep full history" })] })) : permissionRequest ? (_jsxs(Box, { paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0" }), _jsx(Text, { color: "white", bold: true, children: permissionRequest.toolName }), _jsx(Text, { color: "gray", children: toolArgSummary(permissionRequest.args) })] }), _jsx(DiffPreview, { toolName: permissionRequest.toolName, args: permissionRequest.args })] })) : (status === 'thinking' || status === 'tool') ? (_jsx(Box, { paddingX: 1, gap: 1, children: status === 'thinking'
|
|
139
|
+
? _jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: SPARKLE[tick % SPARKLE.length] }), _jsx(Text, { color: Math.floor(tick / 4) % 6 >= 2 && Math.floor(tick / 4) % 6 <= 4 ? 'white' : 'gray', italic: true, children: THINKING_PHRASES[phraseSeq[Math.floor(tick / 62) % phraseSeq.length]] }), _jsx(Text, { color: "gray", dimColor: true, children: formatElapsed(Date.now() - thinkingStartRef.current) }), taskLabel && _jsx(Text, { color: "cyan", dimColor: true, children: taskLabel })] })
|
|
140
|
+
: _jsxs(_Fragment, { children: [_jsxs(Text, { color: "yellow", dimColor: true, children: ["\u2699 running ", currentTool ?? 'tool', "\u2026"] }), _jsx(Text, { color: "gray", dimColor: true, children: formatElapsed(Date.now() - thinkingStartRef.current) }), taskLabel && _jsx(Text, { color: "cyan", dimColor: true, children: taskLabel })] }) })) : null, _jsx(InputArea, { status: status, skills: skillList, cwd: cwd, planningMode: planningMode, permissionRequest: permissionRequest, onPermissionResponse: resolvePermission, compactRequest: compactRequest, onCompactResponse: resolveCompact, onSubmit: handleSubmit, onAbort: handleAbort, history: historyRef.current.filter(m => m.role === 'user').map(m => m.content), watchActive: watchActive })] }));
|
|
149
141
|
}
|
|
@@ -120,6 +120,7 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
|
|
|
120
120
|
await runLoop([...msgs, { role: 'assistant', content: fullText }, nudge], depth + 1, goal);
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
|
+
printer.systemMsg(`done in ${printer.formatElapsed(Date.now() - thinkingStartRef.current)}`);
|
|
123
124
|
setStatus('idle');
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
@@ -171,12 +172,19 @@ export function useRunLoop(config, currentModelRef, pushHistory, extraTools = []
|
|
|
171
172
|
const oldText = tc.args.old;
|
|
172
173
|
if (filePath && oldText && existsSync(filePath)) {
|
|
173
174
|
const current = readFileSync(filePath, 'utf-8');
|
|
174
|
-
|
|
175
|
+
const occurrences = current.split(oldText).length - 1;
|
|
176
|
+
if (occurrences === 0) {
|
|
175
177
|
printer.errorMsg(`patch stale: old text not found in ${filePath} — injecting fresh content`);
|
|
176
178
|
next.push({ role: 'user', content: `Tool read_file result:\n${current}` });
|
|
177
179
|
next.push({ role: 'user', content: `patch_file failed: old text not found in ${filePath}. The file content above is the current state. Retry patch_file with the correct exact text.` });
|
|
178
180
|
continue;
|
|
179
181
|
}
|
|
182
|
+
if (occurrences > 1) {
|
|
183
|
+
printer.errorMsg(`patch ambiguous: old text matches ${occurrences} locations in ${filePath} — injecting fresh content`);
|
|
184
|
+
next.push({ role: 'user', content: `Tool read_file result:\n${current}` });
|
|
185
|
+
next.push({ role: 'user', content: `patch_file failed: old text matches ${occurrences} locations in ${filePath}. Use more surrounding context to make old text unique, then retry.` });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
180
188
|
}
|
|
181
189
|
}
|
|
182
190
|
printer.toolCallStart(tc.name, tc.args);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execFileSync, execSync } from 'child_process';
|
|
2
|
+
import { writeFileSync, readFileSync, existsSync, unlinkSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
export function hasClipboardImage() {
|
|
6
|
+
try {
|
|
7
|
+
if (process.platform === 'darwin') {
|
|
8
|
+
const info = execFileSync('osascript', ['-e', 'clipboard info'], { encoding: 'utf8' });
|
|
9
|
+
return info.includes('PNGf') || info.includes('JPEG') || info.includes('TIFF');
|
|
10
|
+
}
|
|
11
|
+
if (process.platform === 'linux') {
|
|
12
|
+
const targets = execSync('xclip -selection clipboard -t TARGETS -o 2>/dev/null', { encoding: 'utf8' });
|
|
13
|
+
return targets.includes('image/');
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function readClipboardText() {
|
|
22
|
+
try {
|
|
23
|
+
if (process.platform === 'darwin') {
|
|
24
|
+
return execFileSync('pbpaste', { encoding: 'utf8' }) || null;
|
|
25
|
+
}
|
|
26
|
+
if (process.platform === 'linux') {
|
|
27
|
+
return execSync('xclip -selection clipboard -o 2>/dev/null', { encoding: 'utf8' }) || null;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function readClipboardImage() {
|
|
36
|
+
const stamp = Date.now();
|
|
37
|
+
const tmpImg = join(tmpdir(), `miii_clip_${stamp}.png`);
|
|
38
|
+
const tmpScript = join(tmpdir(), `miii_clip_${stamp}.scpt`);
|
|
39
|
+
try {
|
|
40
|
+
if (process.platform === 'darwin') {
|
|
41
|
+
writeFileSync(tmpScript, [
|
|
42
|
+
'try',
|
|
43
|
+
` set theData to (the clipboard as «class PNGf»)`,
|
|
44
|
+
` set theFile to (open for access POSIX file "${tmpImg}" with write permission)`,
|
|
45
|
+
' write theData to theFile',
|
|
46
|
+
' close access theFile',
|
|
47
|
+
' return "ok"',
|
|
48
|
+
'on error',
|
|
49
|
+
' return "no"',
|
|
50
|
+
'end try',
|
|
51
|
+
].join('\n'), 'utf8');
|
|
52
|
+
const result = execFileSync('osascript', [tmpScript], { encoding: 'utf8' }).trim();
|
|
53
|
+
if (result !== 'ok' || !existsSync(tmpImg))
|
|
54
|
+
return null;
|
|
55
|
+
const buf = readFileSync(tmpImg);
|
|
56
|
+
if (!buf.length)
|
|
57
|
+
return null;
|
|
58
|
+
return { data: buf.toString('base64'), mediaType: 'image/png' };
|
|
59
|
+
}
|
|
60
|
+
if (process.platform === 'linux') {
|
|
61
|
+
execSync(`xclip -selection clipboard -t image/png -o > "${tmpImg}" 2>/dev/null`);
|
|
62
|
+
if (!existsSync(tmpImg))
|
|
63
|
+
return null;
|
|
64
|
+
const buf = readFileSync(tmpImg);
|
|
65
|
+
if (!buf.length)
|
|
66
|
+
return null;
|
|
67
|
+
return { data: buf.toString('base64'), mediaType: 'image/png' };
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
for (const f of [tmpImg, tmpScript]) {
|
|
76
|
+
try {
|
|
77
|
+
if (existsSync(f))
|
|
78
|
+
unlinkSync(f);
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/tui/printer.js
CHANGED
|
@@ -280,3 +280,11 @@ export function divider() {
|
|
|
280
280
|
const cols = process.stdout.columns ?? 80;
|
|
281
281
|
write(`${gray('─'.repeat(cols))}\n`);
|
|
282
282
|
}
|
|
283
|
+
export function formatElapsed(ms) {
|
|
284
|
+
const s = Math.floor(ms / 1000);
|
|
285
|
+
if (s < 60)
|
|
286
|
+
return `${s}s`;
|
|
287
|
+
const m = Math.floor(s / 60);
|
|
288
|
+
const rem = s % 60;
|
|
289
|
+
return rem === 0 ? `${m}m` : `${m}m ${rem}s`;
|
|
290
|
+
}
|
package/dist/tui/thinking.js
CHANGED
|
@@ -1,53 +1,96 @@
|
|
|
1
1
|
export const THINKING_PHRASES = [
|
|
2
|
-
'
|
|
3
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
2
|
+
'thinking…',
|
|
3
|
+
'reasoning…',
|
|
4
|
+
'processing…',
|
|
5
|
+
'analyzing…',
|
|
6
|
+
'working…',
|
|
7
|
+
'reading…',
|
|
8
|
+
'planning…',
|
|
9
|
+
'figuring out…',
|
|
10
|
+
'computing…',
|
|
11
|
+
'calculating…',
|
|
12
|
+
'evaluating…',
|
|
13
|
+
'examining…',
|
|
14
|
+
'reviewing…',
|
|
15
|
+
'considering…',
|
|
16
|
+
'reflecting…',
|
|
17
|
+
'determining…',
|
|
18
|
+
'exploring…',
|
|
19
|
+
'searching…',
|
|
20
|
+
'checking…',
|
|
21
|
+
'verifying…',
|
|
22
|
+
'scanning…',
|
|
23
|
+
'parsing…',
|
|
24
|
+
'loading…',
|
|
25
|
+
'mapping…',
|
|
26
|
+
'tracing…',
|
|
27
|
+
'resolving…',
|
|
28
|
+
'generating…',
|
|
29
|
+
'building…',
|
|
30
|
+
'compiling…',
|
|
31
|
+
'drafting…',
|
|
32
|
+
'forming…',
|
|
33
|
+
'shaping…',
|
|
34
|
+
'crafting…',
|
|
35
|
+
'assembling…',
|
|
36
|
+
'organizing…',
|
|
37
|
+
'sorting…',
|
|
38
|
+
'filtering…',
|
|
39
|
+
'matching…',
|
|
40
|
+
'comparing…',
|
|
41
|
+
'correlating…',
|
|
42
|
+
'inferring…',
|
|
43
|
+
'deducing…',
|
|
44
|
+
'estimating…',
|
|
45
|
+
'predicting…',
|
|
46
|
+
'modeling…',
|
|
47
|
+
'simulating…',
|
|
48
|
+
'optimizing…',
|
|
49
|
+
'refining…',
|
|
50
|
+
'debugging…',
|
|
51
|
+
'testing…',
|
|
52
|
+
'validating…',
|
|
53
|
+
'calibrating…',
|
|
54
|
+
'measuring…',
|
|
55
|
+
'indexing…',
|
|
56
|
+
'formatting…',
|
|
57
|
+
'encoding…',
|
|
58
|
+
'extracting…',
|
|
59
|
+
'transforming…',
|
|
60
|
+
'converting…',
|
|
61
|
+
'merging…',
|
|
62
|
+
'clustering…',
|
|
63
|
+
'ranking…',
|
|
64
|
+
'scoring…',
|
|
65
|
+
'sampling…',
|
|
66
|
+
'iterating…',
|
|
67
|
+
'traversing…',
|
|
68
|
+
'navigating…',
|
|
69
|
+
'querying…',
|
|
70
|
+
'updating…',
|
|
71
|
+
'patching…',
|
|
72
|
+
'adapting…',
|
|
73
|
+
'learning…',
|
|
74
|
+
'recalling…',
|
|
75
|
+
'synthesizing…',
|
|
76
|
+
'concluding…',
|
|
77
|
+
'deciding…',
|
|
78
|
+
'selecting…',
|
|
79
|
+
'choosing…',
|
|
80
|
+
'prioritizing…',
|
|
81
|
+
'sequencing…',
|
|
82
|
+
'aligning…',
|
|
83
|
+
'balancing…',
|
|
84
|
+
'adjusting…',
|
|
85
|
+
'reconciling…',
|
|
86
|
+
'confirming…',
|
|
87
|
+
'finalizing…',
|
|
88
|
+
'structuring…',
|
|
89
|
+
'decomposing…',
|
|
90
|
+
'abstracting…',
|
|
91
|
+
'simplifying…',
|
|
92
|
+
'expanding…',
|
|
93
|
+
'summarizing…',
|
|
94
|
+
'distilling…',
|
|
52
95
|
];
|
|
53
96
|
export const SPARKLE = ['✦', '✧', '✶', '✷', '✸', '✹'];
|