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 CHANGED
@@ -10,3 +10,5 @@
10
10
  # Optional: use OpenAI (ChatGPT) instead of the backend. Used when ANTHROPIC_API_KEY is not set.
11
11
  # OPENAI_API_KEY=sk-your_openai_api_key
12
12
  # OPENAI_MODEL=gpt-4o-mini
13
+
14
+ # OLLAMA_API_KEY=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markov-cli",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Markov CLI",
5
5
  "type": "module",
6
6
  "bin": {
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('/help');
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
- const inputLine = chalk.cyan(PREFIX) + (buffer || HINT);
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 onSigint = () => {
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 → exit
140
- if (key === '\x03') { onSigint(); return; }
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') { onSigint(); return; }
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
  }