markov-cli 1.0.15 → 1.0.17
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/.env.example +2 -0
- package/package.json +1 -1
- package/src/claude.js +27 -0
- package/src/editor/codeBlockEdits.js +1 -1
- package/src/input.js +39 -13
- package/src/interactive.js +242 -190
- package/src/ollama.js +42 -6
- package/src/tools.js +30 -1
- package/src/ui/logo.js +9 -17
- package/src/ui/prompts.js +57 -9
- package/src/ui/spinner.js +91 -5
package/.env.example
CHANGED
package/package.json
CHANGED
package/src/claude.js
CHANGED
|
@@ -24,6 +24,33 @@ function getHeaders() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* List available Claude models from Anthropic's Models API.
|
|
29
|
+
* Preserves API order so newer models appear first.
|
|
30
|
+
* Returns [{ label, model }].
|
|
31
|
+
*/
|
|
32
|
+
export async function listAvailableModels(signal = null) {
|
|
33
|
+
const res = await fetchWithRetry(
|
|
34
|
+
`${ANTHROPIC_API}/models`,
|
|
35
|
+
{ method: 'GET', headers: getHeaders() },
|
|
36
|
+
signal
|
|
37
|
+
);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const errBody = await res.text().catch(() => '');
|
|
40
|
+
throw new Error(`Anthropic API error ${res.status} ${res.statusText}${errBody ? ': ' + errBody : ''}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
const models = Array.isArray(data?.data) ? data.data : [];
|
|
45
|
+
|
|
46
|
+
return models
|
|
47
|
+
.filter((model) => model?.type === 'model' && typeof model?.id === 'string' && model.id.startsWith('claude-'))
|
|
48
|
+
.map((model) => ({
|
|
49
|
+
label: model.display_name || `Claude ${model.id}`,
|
|
50
|
+
model: model.id,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
/**
|
|
28
55
|
* Fetch with retry on 429 (rate limit). Waits Retry-After seconds or default 60s, then retries up to RATE_LIMIT_MAX_RETRIES.
|
|
29
56
|
*/
|
|
@@ -17,7 +17,7 @@ export async function applyCodeBlockEdits(responseText, loadedFiles = []) {
|
|
|
17
17
|
for (const edit of edits) {
|
|
18
18
|
renderDiff(edit.filepath, edit.content);
|
|
19
19
|
const ok = await confirm(chalk.bold(`Apply changes to ${chalk.cyan(edit.filepath)}? [y/N] `));
|
|
20
|
-
if (ok) {
|
|
20
|
+
if (ok === true) {
|
|
21
21
|
applyEdit(edit.filepath, edit.content);
|
|
22
22
|
console.log(chalk.green(` ✓ ${edit.filepath} updated\n`));
|
|
23
23
|
} else {
|
package/src/input.js
CHANGED
|
@@ -7,7 +7,7 @@ const visibleLen = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
|
7
7
|
|
|
8
8
|
const PREFIX = '❯ ';
|
|
9
9
|
const HINT = chalk.dim(' Ask Markov anything...');
|
|
10
|
-
const STATUS_LEFT = chalk.dim('
|
|
10
|
+
const STATUS_LEFT = chalk.dim('ctrl + tab to switch mode');
|
|
11
11
|
const PICKER_MAX = 6;
|
|
12
12
|
|
|
13
13
|
function border() {
|
|
@@ -25,12 +25,13 @@ const INPUT_HISTORY_MAX = 100;
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Show an interactive raw-mode prompt that supports @file autocomplete and Up/Down input history.
|
|
28
|
-
* Returns a Promise<string|null> — null means the prompt was cancelled (Ctrl+Q)
|
|
28
|
+
* Returns a Promise<string|object|null> — null means the prompt was cancelled (Ctrl+Q),
|
|
29
|
+
* and { type: 'interrupt' } means the prompt was interrupted with Ctrl+C.
|
|
29
30
|
* @param {string} _promptStr
|
|
30
31
|
* @param {string[]} allFiles
|
|
31
32
|
* @param {string[]} inputHistory - Mutable array of previous inputs; newest last. Up/Down navigate this; Enter pushes current input.
|
|
32
33
|
*/
|
|
33
|
-
export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
34
|
+
export function chatPrompt(_promptStr, allFiles, inputHistory = [], modes = [], initialMode = null) {
|
|
34
35
|
return new Promise((resolve) => {
|
|
35
36
|
const stdin = process.stdin;
|
|
36
37
|
const stdout = process.stdout;
|
|
@@ -41,6 +42,7 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
41
42
|
let pickerIndex = 0;
|
|
42
43
|
let historyIndex = -1; // -1 = not navigating history
|
|
43
44
|
let cursorLineOffset = 0; // lines from top of drawn block to where cursor sits
|
|
45
|
+
let currentMode = initialMode;
|
|
44
46
|
|
|
45
47
|
const getAtPos = () => {
|
|
46
48
|
for (let i = buffer.length - 1; i >= 0; i--) {
|
|
@@ -58,7 +60,18 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
const redraw = () => {
|
|
61
|
-
|
|
63
|
+
let modePrefix = '';
|
|
64
|
+
if (currentMode) {
|
|
65
|
+
switch (currentMode) {
|
|
66
|
+
case '/cmd': modePrefix = chalk.yellow(`[${currentMode}] `); break;
|
|
67
|
+
case '/agent': modePrefix = chalk.green(`[${currentMode}] `); break;
|
|
68
|
+
case '/plan': modePrefix = chalk.blue(`[${currentMode}] `); break;
|
|
69
|
+
case '/build': modePrefix = chalk.cyan(`[${currentMode}] `); break;
|
|
70
|
+
case '/yolo': modePrefix = chalk.red(`[${currentMode}] `); break;
|
|
71
|
+
default: modePrefix = chalk.magenta(`[${currentMode}] `); break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const inputLine = modePrefix + chalk.cyan(PREFIX) + (buffer || HINT);
|
|
62
75
|
|
|
63
76
|
// Build scrollable picker window of PICKER_MAX items.
|
|
64
77
|
const total = pickerFiles.length;
|
|
@@ -99,12 +112,12 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
99
112
|
// it takes multiple visual lines and we must move up by that many to reach
|
|
100
113
|
// the top border on the next redraw.
|
|
101
114
|
const w = process.stdout.columns || 80;
|
|
102
|
-
const inputVisualLen = visibleLen(chalk.cyan(PREFIX)) + visibleLen(buffer || HINT);
|
|
115
|
+
const inputVisualLen = visibleLen(modePrefix) + visibleLen(chalk.cyan(PREFIX)) + visibleLen(buffer || HINT);
|
|
103
116
|
const inputVisualLines = Math.max(1, Math.ceil(inputVisualLen / w));
|
|
104
117
|
cursorLineOffset = inputVisualLines;
|
|
105
118
|
|
|
106
119
|
// Position cursor: beforeCursorLen is the character offset where the cursor sits.
|
|
107
|
-
const prefixLen = visibleLen(chalk.cyan(PREFIX));
|
|
120
|
+
const prefixLen = visibleLen(modePrefix) + visibleLen(chalk.cyan(PREFIX));
|
|
108
121
|
const beforeCursorLen = prefixLen + visibleLen(buffer.slice(0, cursorPos));
|
|
109
122
|
const lineIdx = Math.floor(beforeCursorLen / w);
|
|
110
123
|
const col = (beforeCursorLen % w) + 1;
|
|
@@ -119,16 +132,22 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
119
132
|
cursorLineOffset = 0;
|
|
120
133
|
};
|
|
121
134
|
|
|
122
|
-
const
|
|
135
|
+
const onExit = () => {
|
|
123
136
|
clearPanel();
|
|
124
137
|
cleanup();
|
|
125
138
|
stdout.write('\n');
|
|
126
139
|
process.exit(0);
|
|
127
140
|
};
|
|
128
141
|
|
|
142
|
+
const onInterrupt = () => {
|
|
143
|
+
clearPanel();
|
|
144
|
+
cleanup();
|
|
145
|
+
stdout.write(chalk.dim('(interrupted)\n'));
|
|
146
|
+
resolve({ type: 'interrupt' });
|
|
147
|
+
};
|
|
148
|
+
|
|
129
149
|
const cleanup = () => {
|
|
130
150
|
stdin.removeListener('data', onData);
|
|
131
|
-
process.removeListener('SIGINT', onSigint);
|
|
132
151
|
stdin.setRawMode(false);
|
|
133
152
|
stdin.pause();
|
|
134
153
|
};
|
|
@@ -136,8 +155,8 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
136
155
|
const onData = (data) => {
|
|
137
156
|
const key = data.toString();
|
|
138
157
|
|
|
139
|
-
// Ctrl+C →
|
|
140
|
-
if (key === '\x03') {
|
|
158
|
+
// Ctrl+C → interrupt prompt
|
|
159
|
+
if (key === '\x03') { onInterrupt(); return; }
|
|
141
160
|
|
|
142
161
|
// Ctrl+Q → cancel prompt
|
|
143
162
|
if (key === '\x11') {
|
|
@@ -149,7 +168,7 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
// Ctrl+D → exit
|
|
152
|
-
if (key === '\x04') {
|
|
171
|
+
if (key === '\x04') { onExit(); return; }
|
|
153
172
|
|
|
154
173
|
// Enter
|
|
155
174
|
if (key === '\r' || key === '\n') {
|
|
@@ -171,7 +190,15 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
171
190
|
clearPanel();
|
|
172
191
|
cleanup();
|
|
173
192
|
stdout.write('\n');
|
|
174
|
-
resolve(buffer);
|
|
193
|
+
resolve(modes.length > 0 ? { text: buffer, mode: currentMode } : buffer);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Shift+Tab → toggle mode
|
|
198
|
+
if (key === '\x1b[Z' && modes.length > 0) {
|
|
199
|
+
const idx = modes.indexOf(currentMode);
|
|
200
|
+
currentMode = modes[(idx + 1) % modes.length];
|
|
201
|
+
redraw();
|
|
175
202
|
return;
|
|
176
203
|
}
|
|
177
204
|
|
|
@@ -251,7 +278,6 @@ export function chatPrompt(_promptStr, allFiles, inputHistory = []) {
|
|
|
251
278
|
stdin.resume();
|
|
252
279
|
stdin.setEncoding('utf8');
|
|
253
280
|
stdin.on('data', onData);
|
|
254
|
-
process.on('SIGINT', onSigint);
|
|
255
281
|
redraw();
|
|
256
282
|
});
|
|
257
283
|
}
|