@vierratale/ai 0.1.0-beta.11 → 0.1.0-beta.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vierratale/ai",
3
- "version": "0.1.0-beta.11",
3
+ "version": "0.1.0-beta.12",
4
4
  "description": "VierrataleAI - Intelligent terminal assistant",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.js CHANGED
@@ -18,6 +18,7 @@ import { Session } from './session.js';
18
18
  import { ChatUI, FrameThrottle } from './ui/chatbox.js';
19
19
  import { logger } from './utils/logger.js';
20
20
  import { runWithTools, TOOL_RESULTS_PROMPT, looksLikeOperationRequest } from './cmd/agent.js';
21
+ import { loadTodos, addTodo, updateTodo, clearTodos, saveTodos, formatTodos, todosFile } from './cmd/todos.js';
21
22
 
22
23
  function parseArgs(args) {
23
24
  const parsed = { provider: null, model: null, clear: false, version: false, help: false, install: false };
@@ -54,6 +55,7 @@ Commands (in chat):
54
55
  /search <query> Search the web and summarize with AI
55
56
  /fetch <url> Open a link and summarize its content
56
57
  /download <url> Download a file, folder listing or archive
58
+ /todos List the todo list (add <text> | done/undone <n> | clear)
57
59
  /log [n|path] Show the last n log entries (default 30), or the log file path
58
60
  /clear Clear screen
59
61
  /new Start a new empty session (keeps old ones)
@@ -79,6 +81,7 @@ export const SLASH_COMMANDS = [
79
81
  { name: 'fetch', args: '<url>', desc: 'Open a link and summarize' },
80
82
  { name: 'download', args: '<url>', desc: 'Download a file / folder / archive' },
81
83
  { name: 'system', desc: 'Show or set the system prompt' },
84
+ { name: 'todos', args: '[add <text> | done <n> | undone <n> | clear]', desc: 'Manage the todo list' },
82
85
  { name: 'sessions', desc: 'List saved sessions' },
83
86
  { name: 'session', args: 'new | open | delete | backup | restore', desc: 'Manage sessions' },
84
87
  { name: 'log', args: '[n | path]', desc: 'Show recent log entries' },
@@ -656,7 +659,7 @@ async function chat(provider, systemPrompt) {
656
659
  if (cmd === '/help') {
657
660
  chatUI.clearNotices();
658
661
  chatUI.notify(
659
- `Commands:\n /search <query> · /fetch <url> · /model [name] · /provider [name]\n /models · /log [n|path] · /new · /sessions · /session new|open|delete|backup|restore\n /clear · /help · /quit`
662
+ `Commands:\n /search <query> · /fetch <url> · /model [name] · /provider [name]\n /models · /todos · /log [n|path] · /new · /sessions · /session new|open|delete|backup|restore\n /clear · /help · /quit`
660
663
  );
661
664
  chatUI.render(messages);
662
665
  continue;
@@ -977,6 +980,11 @@ async function chat(provider, systemPrompt) {
977
980
  continue;
978
981
  }
979
982
 
983
+ if (cmd === '/todos' || cmd.startsWith('/todos ')) {
984
+ handleTodosCommand(trimmed, chatUI, messages, process.cwd());
985
+ continue;
986
+ }
987
+
980
988
  const intent = detectIntent(trimmed);
981
989
  if (intent.type === 'self') {
982
990
  const model = Config.getEffectiveModel(provider.name);
@@ -1125,6 +1133,49 @@ async function ensureRawLocalModel(realModel, notify) {
1125
1133
  export { looksLikeFileRequest, requestedFileName, extractFallbackFile, storyFileName, responseWantsFile, responseIsSoloCode, languageFileName, fallbackHasUsefulCode, validateWrittenCode, looksLikeJunkCode, warnIfJunkCode, writeFilesFromResponse };
1126
1134
  // Command/tool layer: asks the model to plan filesystem/shell operations,
1127
1135
  // executes them with the sandboxed executor, then streams the final answer.
1136
+ function handleTodosCommand(trimmed, chatUI, messages, cwd) {
1137
+ chatUI.clearNotices();
1138
+ const rest = trimmed.slice('/todos'.length).trim();
1139
+ let out;
1140
+ if (!rest) {
1141
+ out = `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1142
+ } else if (/^add\s+/i.test(rest)) {
1143
+ const err = addTodo(cwd, rest.replace(/^add\s+/i, '').trim());
1144
+ out = err && err.error ? err.error : `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1145
+ } else if (/^done\s+\d+$/i.test(rest)) {
1146
+ const err = updateTodo(cwd, parseInt(rest.replace(/^done\s+/i, ''), 10), { done: true });
1147
+ out = err && err.error ? err.error : `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1148
+ } else if (/^undone\s+\d+$/i.test(rest)) {
1149
+ const err = updateTodo(cwd, parseInt(rest.replace(/^undone\s+/i, ''), 10), { done: false });
1150
+ out = err && err.error ? err.error : `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1151
+ } else if (/^remove\s+\d+$/i.test(rest)) {
1152
+ const n = parseInt(rest.replace(/^remove\s+/i, ''), 10);
1153
+ const todos = loadTodos(cwd);
1154
+ if (!Number.isInteger(n - 1) || n - 1 < 0 || n - 1 >= todos.length) {
1155
+ out = `No todo #${n}.`;
1156
+ } else {
1157
+ const next = todos.filter((_, i) => i !== n - 1);
1158
+ saveTodos(cwd, next);
1159
+ out = `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1160
+ }
1161
+ } else if (/^clear$/i.test(rest)) {
1162
+ clearTodos(cwd);
1163
+ out = `Todos cleared (${todosFile(cwd)}). (no todos yet)`;
1164
+ } else if (/^\d+$/.test(rest)) {
1165
+ const n = parseInt(rest, 10);
1166
+ const todos = loadTodos(cwd);
1167
+ const err = (Number.isInteger(n - 1) && n - 1 >= 0 && n - 1 < todos.length)
1168
+ ? updateTodo(cwd, n, { done: !todos[n - 1].done })
1169
+ : { error: `No todo #${n}.` };
1170
+ out = err && err.error ? err.error : `Todos (${todosFile(cwd)}):\n${formatTodos(loadTodos(cwd))}`;
1171
+ } else {
1172
+ out = 'Usage: /todos [add <text> | done <n> | undone <n> | remove <n> | <n> (toggle) | clear]';
1173
+ }
1174
+ chatUI.notify(out);
1175
+ logger.log('CMD', `todos: ${rest || '(list)'}`);
1176
+ chatUI.render(messages);
1177
+ }
1178
+
1128
1179
  async function answerWithTools(messages, provider, requestText, systemPrompt, confirm, chatUI, cwd, timeoutMs) {
1129
1180
  let results = [];
1130
1181
  try {
package/src/cmd/agent.js CHANGED
@@ -19,6 +19,10 @@ Reply with EXACTLY ONE JSON array of the tool calls needed to do the job, in ord
19
19
  {"name":"list_directory","path":"."}
20
20
  {"name":"delete_file","path":"old.txt"}
21
21
  {"name":"download_url","url":"https://example.com/file.zip","dir":"downloads"}
22
+ {"name":"todo_add","text":"install dependencies"}
23
+ {"name":"todo_list"}
24
+ {"name":"todo_update","index":1,"done":true}
25
+ {"name":"todo_clear"}
22
26
 
23
27
  Rules:
24
28
  - run_command runs WITHOUT a shell. Allowed commands: mkdir touch ls pwd cat cp mv rm npm npx node python python3 git echo printf.
@@ -27,6 +31,7 @@ Rules:
27
31
  - Prefer create_file over redirecting output.
28
32
  - If you need to create a folder first, plan create_directory before create_file inside it.
29
33
  - To download a file, web asset, archive or folder listing from the web, use download_url with the full URL. dir is optional (defaults to ~/Downloads).
34
+ - For multi-step work, track your progress with todo_add/todo_list/todo_update (e.g. todo_add for each remaining subtask, todo_update to mark steps done).
30
35
  - Code you create must be runnable. For Python servers bind to 127.0.0.1, read PORT from the environment (default 8000), and fall back to a free port (port 0) when the default is taken. HTML/CSS/JS must be self-contained and reference each other by relative filename.
31
36
  - If NO tools are needed, reply exactly: NONE
32
37
  - Output the JSON array only — no prose, no markdown fences unless the array is inside them.`;
@@ -37,6 +42,20 @@ ${resultsJson}
37
42
 
38
43
  Give the user a short, natural reply (in the user's own language) telling them what happened. Keep the whole reply under 40 words. Do NOT tell them to run the commands yourself. If they asked for a file containing code and files were created with content, you may also emit FILE: blocks if more files are still required.`;
39
44
 
45
+ // Auto error fixing: shown to the model when a tool call fails so it can emit
46
+ // a corrected plan instead of giving up. Bounded by MAX_AUTO_FIXES per run.
47
+ const MAX_AUTO_FIXES = 3;
48
+
49
+ const TOOL_FIX_PROMPT = (call, result) => `A tool call made while working in the workspace FAILED. Fix the problem so the original request can complete.
50
+
51
+ Failed tool call:
52
+ ${JSON.stringify(call, null, 2)}
53
+
54
+ Failure:
55
+ ${JSON.stringify({ success: false, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, null, 2)}
56
+
57
+ Emit a NEW tool plan (a JSON array of tool calls) that corrects the failure — for example re-create the file with the error fixed, run a command to repair it, or download/read what is missing. If your previous plan skipped a required step (like creating a parent folder), include it. Use todo tools to track remaining subtasks if useful. Reply with exactly one JSON array, or the single word NONE if no tool call can fix this.`;
58
+
40
59
  // ---------------------------------------------------------------------------
41
60
  // Request detection
42
61
  // ---------------------------------------------------------------------------
@@ -84,6 +103,18 @@ export function normalizeToolCall(raw) {
84
103
  } else if (name === 'download_url') {
85
104
  if (typeof raw.url !== 'string' || raw.url.trim() === '') return null;
86
105
  params = { url: raw.url, dir: typeof raw.dir === 'string' && raw.dir.trim() ? raw.dir.trim() : undefined };
106
+ } else if (name === 'todo_add') {
107
+ if (typeof raw.text !== 'string' || raw.text.trim() === '') return null;
108
+ params = { text: raw.text };
109
+ } else if (name === 'todo_update') {
110
+ if (!Number.isInteger(raw.index) && !/^\d+$/.test(String(raw.index ?? ''))) return null;
111
+ params = {
112
+ index: Number(raw.index),
113
+ done: typeof raw.done === 'boolean' ? raw.done : undefined,
114
+ text: typeof raw.text === 'string' && raw.text.trim() ? raw.text : undefined,
115
+ };
116
+ } else if (name === 'todo_list' || name === 'todo_clear') {
117
+ params = {};
87
118
  } else {
88
119
  return null;
89
120
  }
@@ -1631,6 +1662,75 @@ export async function planOnce(provider, messages, requestText, options = {}) {
1631
1662
  return heuristic;
1632
1663
  }
1633
1664
 
1665
+ async function executeToolCall({ executor, call, chatUI, messages, cwd }) {
1666
+ const label = call.name === 'run_command' ? call.params.command
1667
+ : call.name === 'download_url' ? `download_url(${call.params.url})`
1668
+ : (call.name === 'todo_add' || call.name === 'todo_update')
1669
+ ? `${call.name}(${JSON.stringify(call.params ?? {})})`
1670
+ : `${call.name}(${call.params.path})`;
1671
+ if (chatUI) {
1672
+ chatUI.setStatus(`Running: ${label}`);
1673
+ chatUI.setThinking(true);
1674
+ chatUI.render(messages);
1675
+ }
1676
+ const result = await dispatchTool(executor, call);
1677
+ logger.log('EXEC', `${label} -> ${result.exitCode}${result.timedOut ? ' [timeout]' : ''}`);
1678
+ if (result.success) {
1679
+ if (chatUI) chatUI.notify(`\u2713 ${label}`);
1680
+ if (result.stdout.trim()) logger.log('EXEC', `${label} stdout: ${result.stdout.trim().slice(0, 500)}`);
1681
+ if (result.stderr.trim()) logger.log('WARN', `${label} stderr: ${result.stderr.trim().slice(0, 500)}`);
1682
+ if (call.name === 'create_file' && /\.py$/i.test(call.params.path)) {
1683
+ const check = spawnSync('python3', [
1684
+ '-c', 'import ast,sys; ast.parse(open(sys.argv[1], encoding="utf-8").read())',
1685
+ join(cwd || '.', call.params.path),
1686
+ ], { encoding: 'utf-8', timeout: 30000 });
1687
+ if (check.status === 1) {
1688
+ const detail = (check.stderr || '').trim().split('\n').slice(-2).join(' ');
1689
+ if (chatUI) chatUI.notify(`\u2717 python check failed: ${call.params.path} (${detail})`);
1690
+ logger.log('WARN', `${label} failed python syntax check: ${detail}`);
1691
+ }
1692
+ }
1693
+ } else {
1694
+ if (chatUI) chatUI.notify(`\u2717 ${label}: ${(result.stderr || 'failed').trim().slice(0, 300)}`);
1695
+ logger.log('ERROR', `${label} failed: ${(result.stderr || result.stdout || 'exit ' + result.exitCode).trim().slice(0, 500)}`);
1696
+ }
1697
+ return { label, result };
1698
+ }
1699
+
1700
+ // Ask the model for a corrected tool plan after a failed call. Returns []
1701
+ // when the model emits nothing usable. Does NOT mutate the session messages.
1702
+ async function planAutoFix(provider, messages, requestText, call, result) {
1703
+ const history = (messages && messages.length ? messages.slice(-6) : [{ role: 'user', content: String(requestText || '') }]);
1704
+ const deadline = Config.get('planTimeoutMs');
1705
+ const controller = new AbortController();
1706
+ let text = '';
1707
+ let timer;
1708
+ try {
1709
+ text = await Promise.race([
1710
+ provider.complete([...history, { role: 'user', content: TOOL_FIX_PROMPT(call, result) }], {
1711
+ model: Config.getEffectiveModel(provider.name),
1712
+ system_prompt: TOOL_PLAN_PROMPT,
1713
+ maxTokens: 400,
1714
+ signal: controller.signal,
1715
+ }),
1716
+ new Promise((_resolve, reject) => {
1717
+ timer = setTimeout(() => {
1718
+ if (!controller.signal.aborted) controller.abort();
1719
+ reject(Object.assign(new Error(`auto-fix exceeded ${deadline}ms`), { name: 'DeadlineError' }));
1720
+ }, deadline);
1721
+ }),
1722
+ ]);
1723
+ } catch (err) {
1724
+ logger.log('FIX', `auto-fix plan failed: ${err.message}`);
1725
+ return [];
1726
+ } finally {
1727
+ clearTimeout(timer);
1728
+ }
1729
+ const plan = parseToolPlan(text);
1730
+ if (plan.length) logger.log('FIX', `auto-fix plan: ${plan.map((p) => p.name).join(', ')}`);
1731
+ return plan;
1732
+ }
1733
+
1634
1734
  export async function runWithTools({
1635
1735
  provider,
1636
1736
  messages,
@@ -1648,6 +1748,7 @@ export async function runWithTools({
1648
1748
  const executor = makeExecutor({ cwd, timeoutMs });
1649
1749
  const results = [];
1650
1750
  const plannedByModel = [];
1751
+ let autoFixes = 0;
1651
1752
 
1652
1753
  for (let round = 0; round < MAX_PLANNING_ROUNDS; round++) {
1653
1754
  let plan;
@@ -1662,38 +1763,25 @@ export async function runWithTools({
1662
1763
  const steps = Math.min(plan.length, MAX_STEPS - results.length);
1663
1764
  let allOk = true;
1664
1765
  for (const call of plan.slice(0, steps)) {
1665
- const label = call.name === 'run_command' ? call.params.command
1666
- : call.name === 'download_url' ? `download_url(${call.params.url})`
1667
- : `${call.name}(${call.params.path})`;
1668
- if (chatUI) {
1669
- chatUI.setStatus(`Running: ${label}`);
1670
- chatUI.setThinking(true);
1671
- chatUI.render(messages);
1672
- }
1673
- const result = await dispatchTool(executor, call);
1766
+ const { result } = await executeToolCall({ executor, call, chatUI, messages, cwd });
1674
1767
  results.push({ name: call.name, params: call.params, result });
1675
- logger.log('EXEC', `${label} -> ${result.exitCode}${result.timedOut ? ' [timeout]' : ''}`);
1676
- if (result.success) {
1677
- if (chatUI) chatUI.notify(`\u2713 ${label}`);
1678
- if (result.stdout.trim()) logger.log('EXEC', `${label} stdout: ${result.stdout.trim().slice(0, 500)}`);
1679
- if (result.stderr.trim()) logger.log('WARN', `${label} stderr: ${result.stderr.trim().slice(0, 500)}`);
1680
- if (call.name === 'create_file' && /\.py$/i.test(call.params.path)) {
1681
- const check = spawnSync('python3', [
1682
- '-c', 'import ast,sys; ast.parse(open(sys.argv[1], encoding="utf-8").read())',
1683
- join(cwd || '.', call.params.path),
1684
- ], { encoding: 'utf-8', timeout: 30000 });
1685
- if (check.status === 1) {
1686
- const detail = (check.stderr || '').trim().split('\n').slice(-2).join(' ');
1687
- if (chatUI) chatUI.notify(`\u2717 python check failed: ${call.params.path} (${detail})`);
1688
- logger.log('WARN', `${label} failed python syntax check: ${detail}`);
1689
- }
1768
+ if (!result.success) allOk = false;
1769
+ if (result.timedOut && !result.success) break;
1770
+
1771
+ if (!result.success && autoFixes < MAX_AUTO_FIXES && results.length < MAX_STEPS) {
1772
+ autoFixes++;
1773
+ if (chatUI) chatUI.setStatus('Auto-fixing failed step…');
1774
+ const fixPlan = await planAutoFix(provider, messages, requestText, call, result);
1775
+ const fixSteps = Math.min(fixPlan.length, MAX_STEPS - results.length);
1776
+ let fixedOk = fixPlan.length > 0;
1777
+ for (const fcall of fixPlan.slice(0, fixSteps)) {
1778
+ const f = await executeToolCall({ executor, call: fcall, chatUI, messages, cwd });
1779
+ results.push({ name: fcall.name, params: fcall.params, result: f.result });
1780
+ if (!f.result.success) { fixedOk = false; break; }
1690
1781
  }
1691
- } else {
1692
- allOk = false;
1693
- if (chatUI) chatUI.notify(`\u2717 ${label}: ${(result.stderr || 'failed').trim().slice(0, 300)}`);
1694
- logger.log('ERROR', `${label} failed: ${(result.stderr || result.stdout || 'exit ' + result.exitCode).trim().slice(0, 500)}`);
1782
+ if (fixedOk && chatUI) chatUI.notify('Auto-fixed after failure.');
1783
+ if (fixedOk) allOk = true;
1695
1784
  }
1696
- if (result.timedOut && !result.success) break;
1697
1785
  }
1698
1786
  if (allOk || !results.length || round >= MAX_PLANNING_ROUNDS - 1) break;
1699
1787
  }
@@ -0,0 +1,64 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ const FILE_NAME = '.todos.json';
5
+
6
+ export function todosFile(cwd) {
7
+ return join(cwd || '.', FILE_NAME);
8
+ }
9
+
10
+ export function loadTodos(cwd) {
11
+ try {
12
+ const data = JSON.parse(readFileSync(todosFile(cwd), 'utf-8'));
13
+ if (!Array.isArray(data)) return [];
14
+ return data.map((t, i) => {
15
+ if (t && typeof t === 'object') {
16
+ return {
17
+ text: String(t.text ?? '').trim(),
18
+ done: Boolean(t.done),
19
+ createdAt: t.createdAt ?? null,
20
+ updatedAt: t.updatedAt ?? null,
21
+ };
22
+ }
23
+ return { text: String(t ?? '').trim(), done: false, createdAt: null, updatedAt: null };
24
+ }).filter((t) => t.text.length > 0);
25
+ } catch {
26
+ return [];
27
+ }
28
+ }
29
+
30
+ export function saveTodos(cwd, todos) {
31
+ writeFileSync(todosFile(cwd), JSON.stringify(todos, null, 2) + '\n', 'utf-8');
32
+ }
33
+
34
+ export function formatTodos(todos) {
35
+ if (!todos || !todos.length) return '(no todos yet)';
36
+ return todos.map((t, i) => `${i + 1}. ${t.done ? '[x]' : '[ ]'} ${t.text}`).join('\n');
37
+ }
38
+
39
+ export function addTodo(cwd, text) {
40
+ const clean = String(text ?? '').trim();
41
+ if (!clean) return { error: 'Missing todo text.' };
42
+ const todos = loadTodos(cwd);
43
+ todos.push({ text: clean, done: false, createdAt: new Date().toISOString(), updatedAt: null });
44
+ saveTodos(cwd, todos);
45
+ return { okay: true };
46
+ }
47
+
48
+ export function updateTodo(cwd, index, { done, text } = {}) {
49
+ const todos = loadTodos(cwd);
50
+ const i = Number(index) - 1;
51
+ if (!Number.isInteger(i) || i < 0 || i >= todos.length) {
52
+ return { error: `No todo #${index}.` };
53
+ }
54
+ if (typeof done === 'boolean') todos[i].done = done;
55
+ if (typeof text === 'string' && text.trim()) todos[i].text = text.trim();
56
+ todos[i].updatedAt = new Date().toISOString();
57
+ saveTodos(cwd, todos);
58
+ return { okay: true };
59
+ }
60
+
61
+ export function clearTodos(cwd) {
62
+ saveTodos(cwd, []);
63
+ return { okay: true };
64
+ }
package/src/cmd/tools.js CHANGED
@@ -3,6 +3,7 @@ import { resolve, sep } from 'path';
3
3
  import { toolResult, SafeCommandExecutor } from './executor.js';
4
4
  import { Downloader } from '../utils/downloader.js';
5
5
  import { Config } from '../config.js';
6
+ import { loadTodos, addTodo, updateTodo, clearTodos, formatTodos } from './todos.js';
6
7
 
7
8
  // Resolve a path relative to the workspace and require it to stay inside.
8
9
  // Throws if the path escapes the workspace (or hits the workspace root for
@@ -124,6 +125,35 @@ export async function downloadUrl(cwd, url, dir) {
124
125
  }
125
126
  }
126
127
 
128
+ export function todoAdd(cwd, text) {
129
+ const err = addTodo(cwd, text);
130
+ if (err && err.error) return fail({ command: `todo_add("${text}")`, stderr: err.error, cwd });
131
+ return ok({ command: `todo_add("${text}")`, stdout: formatTodos(loadTodos(cwd)), cwd });
132
+ }
133
+
134
+ export function todoList(cwd) {
135
+ try {
136
+ return ok({ command: 'todo_list', stdout: formatTodos(loadTodos(cwd)), cwd });
137
+ } catch (err) {
138
+ return fail({ command: 'todo_list', stderr: err.message, cwd });
139
+ }
140
+ }
141
+
142
+ export function todoUpdate(cwd, index, done, text) {
143
+ const err = updateTodo(cwd, index, { done, text });
144
+ if (err && err.error) return fail({ command: `todo_update(${index})`, stderr: err.error, cwd });
145
+ return ok({ command: `todo_update(${index})`, stdout: formatTodos(loadTodos(cwd)), cwd });
146
+ }
147
+
148
+ export function todoClear(cwd) {
149
+ try {
150
+ clearTodos(cwd);
151
+ return ok({ command: 'todo_clear', stdout: '(no todos yet)', cwd });
152
+ } catch (err) {
153
+ return fail({ command: 'todo_clear', stderr: err.message, cwd });
154
+ }
155
+ }
156
+
127
157
  // Structured tool registry shared with the agent loop.
128
158
  export const TOOL_EXECUTORS = {
129
159
  run_command: (executor, params) => executor.runCommand(params.command),
@@ -133,6 +163,10 @@ export const TOOL_EXECUTORS = {
133
163
  list_directory: (executor, params) => listDirectory(executor.cwd, params.path || '.'),
134
164
  delete_file: (executor, params) => deleteFile(executor.cwd, params.path),
135
165
  download_url: (executor, params) => downloadUrl(executor.cwd, params.url, params.dir),
166
+ todo_add: (executor, params) => todoAdd(executor.cwd, params ? params.text : undefined),
167
+ todo_list: (executor, params) => todoList(executor.cwd),
168
+ todo_update: (executor, params) => todoUpdate(executor.cwd, params ? params.index : undefined, params ? params.done : undefined, params ? params.text : undefined),
169
+ todo_clear: (executor, params) => todoClear(executor.cwd),
136
170
  };
137
171
 
138
172
  export function makeExecutor({ cwd, timeoutMs } = {}) {
@@ -1,6 +1,6 @@
1
1
  export const Branding = {
2
2
  APP_NAME: 'VierrataleAI',
3
- VERSION: '0.1.0-beta.11',
3
+ VERSION: '0.1.0-beta.12',
4
4
 
5
5
  colors: {
6
6
  primary: '\x1b[38;2;124;58;237m',