goto-assistant 0.1.2 → 0.1.4

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 CHANGED
@@ -26,37 +26,46 @@ PORT=3001 npx goto-assistant
26
26
  ## Architecture
27
27
 
28
28
  ```
29
- ┌─────────────────────────────────────────────────────────┐
30
- │ Browser
31
- │ ┌──────────────┐ ┌──────────────┐
32
- │ │ index.html │ │ setup.html │
33
- │ │ (Chat UI) │ │ (Config) │
34
- │ └──────┬───────┘ └──────┬───────┘
35
- │ │ WebSocket │ HTTP POST
36
- └─────────┼──────────────────┼────────────────────────────┘
29
+ ┌──────────────────────────────────────────────────────────────────┐
30
+ │ Browser
31
+ │ ┌──────────────┐ ┌──────────────┐
32
+ │ │ index.html │ │ setup.html │
33
+ │ │ (Chat UI) │ │ (Config) │
34
+ │ └──────┬───────┘ └──────┬───────┘
35
+ │ │ WebSocket │ HTTP POST
36
+ └─────────┼──────────────────┼──────────────────────────────────────┘
37
37
  │ │
38
- ┌─────────┼──────────────────┼────────────────────────────┐
39
- │ server.ts │
40
- │ ┌──────┴───────┐ ┌──────┴───────┐
41
- │ │ WebSocket │ │ REST API │
42
- │ │ Handler │ │ /api/* │
43
- │ └──────┬───────┘ └──────────────┘
44
- │ │
45
- │ ┌──────┴───────┐
46
- │ │ router.ts │──── routes by provider
47
- │ └──┬────────┬──┘
48
- │ │ │
49
- │ ┌──┴──┐ ┌──┴───┐ ┌────────────┐
50
- │ │Claude│ │OpenAI│────▶│ MCP Servers
51
- │ │Agent │ │Agent │ │ (memory,
52
- │ │ SDK │ │ SDK │ fs, cron)
53
- │ └──┬──┘ └──┬───┘ └────────────┘
54
- │ │ │
55
- ┌──┴────────┴──┐ ┌────────────┐
56
- sessions.ts │────▶│ SQLite DB
57
- (persistence) data/
58
- └──────────────┘ └────────────┘
59
- └─────────────────────────────────────────────────────────┘
38
+ ┌─────────┼──────────────────┼──────────────────────────────────────┐
39
+ │ server.ts │
40
+ │ ┌──────┴───────┐ ┌──────┴───────┐
41
+ │ │ WebSocket │ │ REST API │
42
+ │ │ Handler │ │ /api/* │
43
+ │ └──────┬───────┘ └──────────────┘
44
+ │ │
45
+ │ ┌──────┴───────┐
46
+ │ │ router.ts │──── routes by provider
47
+ │ └──┬────────┬──┘
48
+ │ │ │
49
+ │ ┌──┴──┐ ┌──┴───┐ ┌───────────────────────────────────┐
50
+ │ │Claude│ │OpenAI│───▶│ MCP Servers
51
+ │ │Agent │ │Agent │ │
52
+ │ │ SDK │ │ SDK │ ┌────────┐ ┌────────────┐ ···
53
+ │ └──┬──┘ └──┬───┘ │ memory │ │ filesystem │ │ │
54
+ │ │ │ └────────┘ └────────────┘ │ │
55
+ │ │ │ │ ▲ ▲ │ │
56
+ │ │ │ │
57
+ ┌────┴────────────┴──────────┐
58
+ │ │ │ │ │ mcp-cron │ │ │
59
+ │ │ │ │ │ AI tasks (w/ MCP access) │ │ │
60
+ │ │ │ │ │ + shell commands │ │ │
61
+ │ │ │ │ └────────────────────────────┘ │ │
62
+ │ │ │ └───────────────────────────────────┘ │
63
+ │ │ │ │
64
+ │ ┌──┴────────┴──┐ ┌────────────┐ │
65
+ │ │ sessions.ts │───▶│ SQLite DB │ │
66
+ │ │ (persistence)│ │ data/ │ │
67
+ │ └──────────────┘ └────────────┘ │
68
+ └──────────────────────────────────────────────────────────────────┘
60
69
  ```
61
70
 
62
71
  ## Development Setup
@@ -1,7 +1,40 @@
1
- import { Agent, run, MCPServerStdio } from "@openai/agents";
1
+ import { Agent, run, MCPServerStdio, shellTool } from "@openai/agents";
2
+ import { exec } from "node:child_process";
3
+ import { promisify } from "node:util";
2
4
  import { MEMORY_FILE_PATH, MEMORY_SERVER_NAME } from "../config.js";
3
5
  import { parseMessageContent } from "../sessions.js";
4
6
  import { getUpload } from "../uploads.js";
7
+ const execAsync = promisify(exec);
8
+ class LocalShell {
9
+ async run(action) {
10
+ const output = [];
11
+ for (const command of action.commands) {
12
+ let stdout = "";
13
+ let stderr = "";
14
+ let outcome = { type: "exit", exitCode: 0 };
15
+ try {
16
+ const result = await execAsync(command, {
17
+ timeout: action.timeoutMs ?? 30_000,
18
+ maxBuffer: action.maxOutputLength ?? 1024 * 1024,
19
+ });
20
+ stdout = result.stdout;
21
+ stderr = result.stderr;
22
+ }
23
+ catch (error) {
24
+ const err = error;
25
+ stdout = err.stdout ?? "";
26
+ stderr = err.stderr ?? err.message ?? "";
27
+ outcome = err.killed || err.signal === "SIGTERM"
28
+ ? { type: "timeout" }
29
+ : { type: "exit", exitCode: typeof err.code === "number" ? err.code : 1 };
30
+ }
31
+ output.push({ stdout, stderr, outcome });
32
+ if (outcome.type === "timeout")
33
+ break;
34
+ }
35
+ return { output };
36
+ }
37
+ }
5
38
  export async function runOpenAI(prompt, config, mcpServersConfig, onChunk, attachments, history) {
6
39
  const env = {
7
40
  ...process.env,
@@ -30,9 +63,12 @@ export async function runOpenAI(prompt, config, mcpServersConfig, onChunk, attac
30
63
  }
31
64
  const agent = new Agent({
32
65
  name: "goto-assistant",
33
- instructions: "You are a helpful personal AI assistant. You have access to MCP tools for memory, filesystem, browser automation, and scheduled tasks. Use them when appropriate. IMPORTANT: At the start of each conversation, you MUST call the memory read_graph tool to retrieve all known context about the user before responding to their first message.",
66
+ instructions: "You are a helpful personal AI assistant. You have access to MCP tools for memory, filesystem, browser automation, and scheduled tasks. You also have a shell tool to execute commands on the host machine. Use them when appropriate. IMPORTANT: At the start of each conversation, you MUST call the memory read_graph tool to retrieve all known context about the user before responding to their first message.",
34
67
  model: config.openai.model,
35
68
  mcpServers,
69
+ tools: [
70
+ shellTool({ shell: new LocalShell() }),
71
+ ],
36
72
  });
37
73
  // Build conversation input with history
38
74
  const inputMessages = [];
@@ -1 +1 @@
1
- {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/agents/openai.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/agents/openai.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- export const DATA_DIR = path.join(process.cwd(), process.env.GOTO_DATA_DIR || "data");
3
+ export const DATA_DIR = process.env.GOTO_DATA_DIR || path.join(process.cwd(), "data");
4
4
  const CONFIG_PATH = path.join(DATA_DIR, "config.json");
5
5
  export const MCP_CONFIG_PATH = path.join(DATA_DIR, "mcp.json");
6
6
  export const MEMORY_FILE_PATH = path.join(DATA_DIR, "memory.json");
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goto-assistant",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Lightweight, self-hosted personal AI assistant",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@10.29.3",
@@ -55,6 +55,7 @@
55
55
  "@types/multer": "^2.0.0",
56
56
  "@types/ws": "^8.0.0",
57
57
  "eslint": "^9.39.2",
58
+ "jsdom": "^28.0.0",
58
59
  "tsx": "^4.0.0",
59
60
  "typescript": "^5.0.0",
60
61
  "typescript-eslint": "^8.55.0",
package/public/setup.html CHANGED
@@ -6,6 +6,7 @@
6
6
  <title>goto-assistant — Setup</title>
7
7
  <link rel="stylesheet" href="/style.css">
8
8
  <script src="/cron-sync.js"></script>
9
+ <script src="/setup.js"></script>
9
10
  </head>
10
11
  <body>
11
12
  <div class="setup-container">
@@ -54,74 +55,10 @@
54
55
  </div>
55
56
 
56
57
  <script>
57
- const defaultServers = [
58
- { name: 'cron', command: 'npx', args: '-y mcp-cron --transport stdio --prevent-sleep --mcp-config-path ./data/mcp.json --ai-provider anthropic --ai-model claude-sonnet-4-5-20250929', env: {} },
59
- { name: 'memory', command: 'npx', args: '-y @modelcontextprotocol/server-memory', env: {} },
60
- { name: 'filesystem', command: 'npx', args: '-y @modelcontextprotocol/server-filesystem .', env: {} },
61
- ];
62
-
63
- function getProvider() {
64
- return document.querySelector('input[name="provider"]:checked').value;
65
- }
66
-
67
- // Render MCP servers
68
- function renderServers(servers) {
69
- const container = document.getElementById('mcpServers');
70
- container.innerHTML = '';
71
- servers.forEach((s, i) => {
72
- const div = document.createElement('div');
73
- div.className = 'mcp-server';
74
- const envRows = Object.entries(s.env || {}).map(([k, v], ei) =>
75
- `<div class="env-row">
76
- <input type="text" placeholder="Key" value="${k}" data-server="${i}" data-env-key="${ei}">
77
- <input type="text" placeholder="Value" value="${v}" data-server="${i}" data-env-val="${ei}">
78
- <button class="btn-icon" onclick="removeEnv(${i},${ei})">×</button>
79
- </div>`
80
- ).join('');
81
- div.innerHTML = `
82
- <div class="mcp-server-header">
83
- <input type="text" value="${s.name}" data-server="${i}" data-field="name" placeholder="Server name">
84
- <button class="btn-icon" onclick="removeServer(${i})">×</button>
85
- </div>
86
- <div class="form-group">
87
- <label>Command</label>
88
- <input type="text" value="${s.command}" data-server="${i}" data-field="command">
89
- </div>
90
- <div class="form-group">
91
- <label>Args</label>
92
- <input type="text" value="${s.args}" data-server="${i}" data-field="args">
93
- </div>
94
- <div class="form-group">
95
- <label>Environment Variables</label>
96
- ${envRows}
97
- <button class="btn btn-secondary" style="margin-top:4px;font-size:12px;padding:4px 10px" onclick="addEnv(${i})">+ Add Env</button>
98
- </div>`;
99
- container.appendChild(div);
100
- });
101
- }
102
-
103
58
  let servers = [...defaultServers.map(s => ({ ...s, env: { ...s.env } }))];
104
59
  let isEditing = false; // true when revisiting setup with existing config
105
60
  renderServers(servers);
106
61
 
107
- function readServers() {
108
- const container = document.getElementById('mcpServers');
109
- const items = container.querySelectorAll('.mcp-server');
110
- return Array.from(items).map((item, i) => {
111
- const name = item.querySelector('[data-field="name"]').value;
112
- const command = item.querySelector('[data-field="command"]').value;
113
- const args = item.querySelector('[data-field="args"]').value;
114
- const envRows = item.querySelectorAll('.env-row');
115
- const env = {};
116
- envRows.forEach(row => {
117
- const k = row.querySelector('[placeholder="Key"]').value.trim();
118
- const v = row.querySelector('[placeholder="Value"]').value.trim();
119
- if (k) env[k] = v;
120
- });
121
- return { name, command, args, env };
122
- });
123
- }
124
-
125
62
  window.removeServer = (i) => { servers.splice(i, 1); renderServers(servers); };
126
63
  window.addEnv = (i) => {
127
64
  servers = readServers();
@@ -141,39 +78,23 @@
141
78
  renderServers(servers);
142
79
  });
143
80
 
144
- // Sync cron server config with current provider/key/model/baseUrl selections
145
- function syncCronConfig() {
81
+ // Thin wrapper that passes mutable state to the extracted syncCronConfig
82
+ function doSyncCronConfig() {
146
83
  servers = readServers();
147
- const cron = servers.find(s => s.name === 'cron');
148
- if (!cron) return;
149
-
150
- const provider = getProvider();
151
- const apiKey = document.getElementById('apiKey').value.trim();
152
- const model = document.getElementById('model').value;
153
- const baseUrl = document.getElementById('baseUrl').value.trim();
154
-
155
- const result = buildCronConfig({
156
- provider, apiKey, model, baseUrl,
157
- currentArgs: cron.args,
158
- });
159
-
160
- cron.args = result.args;
161
-
162
- // Update env: remove old API_KEY entries, set the correct one
163
- const oldKeys = Object.keys(cron.env).filter(k => k.includes('API_KEY'));
164
- oldKeys.forEach(k => delete cron.env[k]);
165
- cron.env[result.envKey] = result.envValue;
166
-
84
+ servers = syncCronConfig(servers, isEditing, buildCronConfig);
167
85
  renderServers(servers);
168
86
  }
169
87
 
170
88
  // Sync cron when provider, API key, base URL, or model changes
171
89
  document.querySelectorAll('input[name="provider"]').forEach(radio => {
172
- radio.addEventListener('change', syncCronConfig);
90
+ radio.addEventListener('change', () => {
91
+ handleProviderSwitch(isEditing, window._savedConfig);
92
+ doSyncCronConfig();
93
+ });
173
94
  });
174
- document.getElementById('apiKey').addEventListener('input', syncCronConfig);
175
- document.getElementById('baseUrl').addEventListener('input', syncCronConfig);
176
- document.getElementById('model').addEventListener('change', syncCronConfig);
95
+ document.getElementById('apiKey').addEventListener('input', doSyncCronConfig);
96
+ document.getElementById('baseUrl').addEventListener('input', doSyncCronConfig);
97
+ document.getElementById('model').addEventListener('change', doSyncCronConfig);
177
98
 
178
99
  // Load models
179
100
  document.getElementById('loadModelsBtn').addEventListener('click', async () => {
@@ -214,7 +135,7 @@
214
135
  if (!model && !isEditing) { showStatus('Select a model first', 'error'); return; }
215
136
 
216
137
  // Sync cron one final time before saving
217
- syncCronConfig();
138
+ doSyncCronConfig();
218
139
  servers = readServers();
219
140
 
220
141
  const mcpServers = {};
@@ -276,6 +197,7 @@
276
197
  const mcpData = await mcpRes.json();
277
198
  if (data.configured && data.config) {
278
199
  isEditing = true;
200
+ window._savedConfig = data.config;
279
201
  document.getElementById('backBtn').style.display = '';
280
202
  document.getElementById('apiKey').placeholder = 'Leave empty to keep existing key';
281
203
  const c = data.config;
@@ -0,0 +1,127 @@
1
+ // setup.js — extracted functions from setup.html for testability.
2
+ // Loaded as a plain <script> in the browser; importable via require() in tests.
3
+
4
+ // eslint-disable-next-line no-unused-vars
5
+ var defaultServers = [
6
+ { name: 'cron', command: 'npx', args: '-y mcp-cron --transport stdio --prevent-sleep --mcp-config-path ./data/mcp.json --ai-provider anthropic --ai-model claude-sonnet-4-5-20250929', env: {} },
7
+ { name: 'memory', command: 'npx', args: '-y @modelcontextprotocol/server-memory', env: {} },
8
+ { name: 'filesystem', command: 'npx', args: '-y @modelcontextprotocol/server-filesystem .', env: {} },
9
+ ];
10
+
11
+ // eslint-disable-next-line no-unused-vars
12
+ function getProvider() {
13
+ return document.querySelector('input[name="provider"]:checked').value;
14
+ }
15
+
16
+ // eslint-disable-next-line no-unused-vars
17
+ function renderServers(servers) {
18
+ var container = document.getElementById('mcpServers');
19
+ container.innerHTML = '';
20
+ servers.forEach(function (s, i) {
21
+ var div = document.createElement('div');
22
+ div.className = 'mcp-server';
23
+ var envRows = Object.entries(s.env || {}).map(function (_ref, ei) {
24
+ var k = _ref[0], v = _ref[1];
25
+ return '<div class="env-row">' +
26
+ '<input type="text" placeholder="Key" value="' + k + '" data-server="' + i + '" data-env-key="' + ei + '">' +
27
+ '<input type="text" placeholder="Value" value="' + v + '" data-server="' + i + '" data-env-val="' + ei + '">' +
28
+ '<button class="btn-icon" onclick="removeEnv(' + i + ',' + ei + ')">×</button>' +
29
+ '</div>';
30
+ }).join('');
31
+ div.innerHTML =
32
+ '<div class="mcp-server-header">' +
33
+ '<input type="text" value="' + s.name + '" data-server="' + i + '" data-field="name" placeholder="Server name">' +
34
+ '<button class="btn-icon" onclick="removeServer(' + i + ')">×</button>' +
35
+ '</div>' +
36
+ '<div class="form-group">' +
37
+ '<label>Command</label>' +
38
+ '<input type="text" value="' + s.command + '" data-server="' + i + '" data-field="command">' +
39
+ '</div>' +
40
+ '<div class="form-group">' +
41
+ '<label>Args</label>' +
42
+ '<input type="text" value="' + s.args + '" data-server="' + i + '" data-field="args">' +
43
+ '</div>' +
44
+ '<div class="form-group">' +
45
+ '<label>Environment Variables</label>' +
46
+ envRows +
47
+ '<button class="btn btn-secondary" style="margin-top:4px;font-size:12px;padding:4px 10px" onclick="addEnv(' + i + ')">+ Add Env</button>' +
48
+ '</div>';
49
+ container.appendChild(div);
50
+ });
51
+ }
52
+
53
+ // eslint-disable-next-line no-unused-vars
54
+ function readServers() {
55
+ var container = document.getElementById('mcpServers');
56
+ var items = container.querySelectorAll('.mcp-server');
57
+ return Array.from(items).map(function (item) {
58
+ var name = item.querySelector('[data-field="name"]').value;
59
+ var command = item.querySelector('[data-field="command"]').value;
60
+ var args = item.querySelector('[data-field="args"]').value;
61
+ var envRows = item.querySelectorAll('.env-row');
62
+ var env = {};
63
+ envRows.forEach(function (row) {
64
+ var k = row.querySelector('[placeholder="Key"]').value.trim();
65
+ var v = row.querySelector('[placeholder="Value"]').value.trim();
66
+ if (k) env[k] = v;
67
+ });
68
+ return { name: name, command: command, args: args, env: env };
69
+ });
70
+ }
71
+
72
+ // Sync cron server config. Takes servers array, isEditing flag, and buildCronConfigFn.
73
+ // Returns updated servers array (mutates in place for convenience, also returns).
74
+ // eslint-disable-next-line no-unused-vars
75
+ function syncCronConfig(servers, isEditing, buildCronConfigFn) {
76
+ var cron = servers.find(function (s) { return s.name === 'cron'; });
77
+ if (!cron) return servers;
78
+
79
+ var provider = getProvider();
80
+ var apiKey = document.getElementById('apiKey').value.trim();
81
+ var model = document.getElementById('model').value;
82
+ var baseUrl = document.getElementById('baseUrl').value.trim();
83
+
84
+ var result = buildCronConfigFn({
85
+ provider: provider,
86
+ apiKey: apiKey,
87
+ model: model,
88
+ baseUrl: baseUrl,
89
+ currentArgs: cron.args,
90
+ });
91
+
92
+ cron.args = result.args;
93
+
94
+ // Always rename the env key to match the provider.
95
+ // In edit mode with no new key entered, preserve the existing value.
96
+ var oldKeys = Object.keys(cron.env).filter(function (k) { return k.includes('API_KEY'); });
97
+ if (apiKey || !isEditing) {
98
+ oldKeys.forEach(function (k) { delete cron.env[k]; });
99
+ cron.env[result.envKey] = result.envValue;
100
+ } else if (oldKeys.length > 0 && oldKeys[0] !== result.envKey) {
101
+ var existingValue = cron.env[oldKeys[0]];
102
+ oldKeys.forEach(function (k) { delete cron.env[k]; });
103
+ cron.env[result.envKey] = existingValue;
104
+ }
105
+
106
+ return servers;
107
+ }
108
+
109
+ // Handle provider switch: pre-fill baseUrl and model from saved config.
110
+ // eslint-disable-next-line no-unused-vars
111
+ function handleProviderSwitch(isEditing, savedConfig) {
112
+ if (!isEditing || !savedConfig) return;
113
+
114
+ var p = getProvider();
115
+ var pc = savedConfig[p] || {};
116
+ document.getElementById('baseUrl').value = pc.baseUrl || '';
117
+ var select = document.getElementById('model');
118
+ if (pc.model) {
119
+ select.innerHTML = '<option value="' + pc.model + '">' + pc.model + '</option>';
120
+ } else {
121
+ select.innerHTML = '<option value="">— Load models —</option>';
122
+ }
123
+ }
124
+
125
+ if (typeof module !== 'undefined' && module.exports) {
126
+ module.exports = { defaultServers: defaultServers, getProvider: getProvider, renderServers: renderServers, readServers: readServers, syncCronConfig: syncCronConfig, handleProviderSwitch: handleProviderSwitch };
127
+ }