goto-assistant 0.1.0

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/dist/server.js ADDED
@@ -0,0 +1,253 @@
1
+ import express from "express";
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ import http from "node:http";
4
+ import path from "node:path";
5
+ import multer from "multer";
6
+ import { isConfigured, loadConfig, saveConfig, getMaskedConfig, loadMcpServers, saveMcpServers, getMaskedMcpServers } from "./config.js";
7
+ import { createConversation, getConversation, updateSessionId, updateTitle, listConversations, saveMessage, getMessages, deleteConversation } from "./sessions.js";
8
+ import { routeMessage } from "./agents/router.js";
9
+ import { saveUpload, getUpload, ALLOWED_IMAGE_TYPES, UPLOADS_DIR } from "./uploads.js";
10
+ export function createApp() {
11
+ const app = express();
12
+ app.use(express.json());
13
+ app.use(express.urlencoded({ extended: true }));
14
+ // Setup redirect middleware: if not configured, redirect to setup page
15
+ app.use((req, res, next) => {
16
+ if (!isConfigured() &&
17
+ !req.path.startsWith("/setup") &&
18
+ !req.path.startsWith("/api/") &&
19
+ !req.path.endsWith(".css") &&
20
+ !req.path.endsWith(".js") &&
21
+ req.path !== "/health") {
22
+ res.redirect("/setup.html");
23
+ return;
24
+ }
25
+ next();
26
+ });
27
+ // Static files
28
+ app.use(express.static(path.join(import.meta.dirname, "..", "public")));
29
+ // Health check
30
+ app.get("/health", (_req, res) => {
31
+ res.json({ status: "ok", configured: isConfigured() });
32
+ });
33
+ // List models for setup page
34
+ app.post("/api/models", async (req, res) => {
35
+ const { provider, apiKey, baseUrl } = req.body;
36
+ if (provider === "claude") {
37
+ // Anthropic doesn't have a list models endpoint; return known models
38
+ res.json({
39
+ models: [
40
+ { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5" },
41
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5" },
42
+ { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
43
+ ],
44
+ });
45
+ return;
46
+ }
47
+ if (provider === "openai") {
48
+ try {
49
+ const url = `${baseUrl || "https://api.openai.com"}/v1/models`;
50
+ const response = await fetch(url, {
51
+ headers: { Authorization: `Bearer ${apiKey}` },
52
+ });
53
+ if (!response.ok) {
54
+ res.status(400).json({ error: "Failed to fetch models. Check your API key." });
55
+ return;
56
+ }
57
+ const data = (await response.json());
58
+ const models = data.data
59
+ .map((m) => ({ id: m.id, name: m.id }))
60
+ .sort((a, b) => a.id.localeCompare(b.id));
61
+ res.json({ models });
62
+ }
63
+ catch {
64
+ res.status(500).json({ error: "Failed to connect to OpenAI API" });
65
+ }
66
+ return;
67
+ }
68
+ res.status(400).json({ error: "Invalid provider" });
69
+ });
70
+ // Save config from setup page
71
+ app.post("/api/setup", (req, res) => {
72
+ const { mcpServers, ...rest } = req.body;
73
+ const config = rest;
74
+ if (!config.provider || !config.server?.port) {
75
+ res.status(400).json({ error: "Invalid config" });
76
+ return;
77
+ }
78
+ saveConfig(config);
79
+ if (mcpServers) {
80
+ saveMcpServers(mcpServers);
81
+ }
82
+ res.json({ ok: true });
83
+ });
84
+ // Get masked config for settings page
85
+ app.get("/api/config", (_req, res) => {
86
+ if (!isConfigured()) {
87
+ res.json({ configured: false });
88
+ return;
89
+ }
90
+ const config = loadConfig();
91
+ res.json({ configured: true, config: getMaskedConfig(config) });
92
+ });
93
+ // MCP servers endpoints
94
+ app.get("/api/mcp-servers", (_req, res) => {
95
+ const servers = loadMcpServers();
96
+ res.json({ mcpServers: getMaskedMcpServers(servers) });
97
+ });
98
+ app.post("/api/mcp-servers", (req, res) => {
99
+ const { mcpServers } = req.body;
100
+ if (!mcpServers || typeof mcpServers !== "object") {
101
+ res.status(400).json({ error: "Invalid mcpServers" });
102
+ return;
103
+ }
104
+ saveMcpServers(mcpServers);
105
+ res.json({ ok: true });
106
+ });
107
+ // List conversations
108
+ app.get("/api/conversations", (_req, res) => {
109
+ const conversations = listConversations();
110
+ res.json({ conversations });
111
+ });
112
+ // Get messages for a conversation
113
+ app.get("/api/conversations/:id/messages", (req, res) => {
114
+ const messages = getMessages(req.params.id);
115
+ res.json({ messages });
116
+ });
117
+ // Delete a conversation
118
+ app.delete("/api/conversations/:id", (req, res) => {
119
+ deleteConversation(req.params.id);
120
+ res.json({ ok: true });
121
+ });
122
+ // File upload
123
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
124
+ app.post("/api/upload", upload.single("file"), (req, res) => {
125
+ const file = req.file;
126
+ if (!file) {
127
+ res.status(400).json({ error: "No file provided" });
128
+ return;
129
+ }
130
+ if (!ALLOWED_IMAGE_TYPES.includes(file.mimetype)) {
131
+ res.status(400).json({ error: `Unsupported file type: ${file.mimetype}. Allowed: ${ALLOWED_IMAGE_TYPES.join(", ")}` });
132
+ return;
133
+ }
134
+ const meta = saveUpload(file.buffer, file.originalname, file.mimetype);
135
+ res.json({ fileId: meta.fileId, filename: meta.filename, mimeType: meta.mimeType, size: meta.size });
136
+ });
137
+ // Serve uploaded files
138
+ app.get("/api/uploads/:fileId", (req, res) => {
139
+ const result = getUpload(req.params.fileId);
140
+ if (!result) {
141
+ res.status(404).json({ error: "File not found" });
142
+ return;
143
+ }
144
+ res.setHeader("Content-Type", result.mimeType);
145
+ res.setHeader("Content-Disposition", `inline; filename="${result.filename}"`);
146
+ res.send(result.data);
147
+ });
148
+ return app;
149
+ }
150
+ export function createServer(app) {
151
+ const server = http.createServer(app);
152
+ const wss = new WebSocketServer({ server });
153
+ wss.on("connection", (ws) => {
154
+ ws.on("message", async (raw) => {
155
+ let msg;
156
+ try {
157
+ msg = JSON.parse(raw.toString());
158
+ }
159
+ catch {
160
+ ws.send(JSON.stringify({ type: "error", text: "Invalid JSON" }));
161
+ return;
162
+ }
163
+ if (msg.type !== "message" || !msg.text) {
164
+ ws.send(JSON.stringify({ type: "error", text: "Invalid message format" }));
165
+ return;
166
+ }
167
+ if (!isConfigured()) {
168
+ ws.send(JSON.stringify({ type: "error", text: "Not configured. Visit /setup.html" }));
169
+ return;
170
+ }
171
+ const config = loadConfig();
172
+ const mcpServers = loadMcpServers();
173
+ // Get or create conversation
174
+ let conversationId = msg.conversationId;
175
+ let resumeSessionId;
176
+ let isNewConversation = false;
177
+ if (conversationId) {
178
+ const existing = getConversation(conversationId);
179
+ if (existing?.sdk_session_id) {
180
+ resumeSessionId = existing.sdk_session_id;
181
+ }
182
+ }
183
+ if (!conversationId) {
184
+ const conv = createConversation(config.provider);
185
+ conversationId = conv.id;
186
+ isNewConversation = true;
187
+ }
188
+ try {
189
+ // Save user message (with attachment metadata if present)
190
+ const msgAttachments = msg.attachments;
191
+ if (msgAttachments && msgAttachments.length > 0) {
192
+ saveMessage(conversationId, "user", JSON.stringify({
193
+ text: msg.text,
194
+ attachments: msgAttachments.map(a => ({ fileId: a.fileId, filename: a.filename, mimeType: a.mimeType })),
195
+ }));
196
+ }
197
+ else {
198
+ saveMessage(conversationId, "user", msg.text);
199
+ }
200
+ // Set title from first message on new conversations
201
+ if (isNewConversation) {
202
+ const title = msg.text.length > 100 ? msg.text.slice(0, 100) + "..." : msg.text;
203
+ updateTitle(conversationId, title);
204
+ }
205
+ // Resolve attachment data from disk
206
+ let attachments;
207
+ if (msgAttachments && msgAttachments.length > 0) {
208
+ attachments = [];
209
+ for (const att of msgAttachments) {
210
+ const upload = getUpload(att.fileId);
211
+ if (upload) {
212
+ attachments.push({
213
+ filename: upload.filename,
214
+ mimeType: upload.mimeType,
215
+ data: upload.data,
216
+ filePath: path.resolve(UPLOADS_DIR, att.fileId, upload.filename),
217
+ });
218
+ }
219
+ }
220
+ }
221
+ // Load conversation history (excluding current message) for providers that need it
222
+ const allMessages = getMessages(conversationId);
223
+ const history = allMessages.slice(0, -1); // exclude the message we just saved
224
+ let responseText = "";
225
+ const result = await routeMessage(msg.text, config, mcpServers, (chunk) => {
226
+ responseText += chunk;
227
+ if (ws.readyState === WebSocket.OPEN) {
228
+ ws.send(JSON.stringify({ type: "chunk", text: chunk }));
229
+ }
230
+ }, resumeSessionId, attachments, history);
231
+ // Save assistant message
232
+ if (responseText) {
233
+ saveMessage(conversationId, "assistant", responseText);
234
+ }
235
+ // Save SDK session ID for resume
236
+ if (result.sessionId) {
237
+ updateSessionId(conversationId, result.sessionId);
238
+ }
239
+ if (ws.readyState === WebSocket.OPEN) {
240
+ ws.send(JSON.stringify({ type: "done", conversationId }));
241
+ }
242
+ }
243
+ catch (err) {
244
+ const errorText = err instanceof Error ? err.message : "Unknown error";
245
+ if (ws.readyState === WebSocket.OPEN) {
246
+ ws.send(JSON.stringify({ type: "error", text: errorText }));
247
+ }
248
+ }
249
+ });
250
+ });
251
+ return server;
252
+ }
253
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAyB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAC5K,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnK,OAAO,EAAE,YAAY,EAAmB,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEvF,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEhD,uEAAuE;IACvE,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,IACE,CAAC,YAAY,EAAE;YACf,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAC9B,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7B,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC1B,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YACzB,GAAG,CAAC,IAAI,KAAK,SAAS,EACtB,CAAC;YACD,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,eAAe;IACf,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAExE,eAAe;IACf,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,6BAA6B;IAC7B,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACzC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QAE/C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,qEAAqE;YACrE,GAAG,CAAC,IAAI,CAAC;gBACP,MAAM,EAAE;oBACN,EAAE,EAAE,EAAE,4BAA4B,EAAE,IAAI,EAAE,mBAAmB,EAAE;oBAC/D,EAAE,EAAE,EAAE,2BAA2B,EAAE,IAAI,EAAE,kBAAkB,EAAE;oBAC7D,EAAE,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE;iBACnD;aACF,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,wBAAwB,YAAY,CAAC;gBAC/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;oBAChC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;iBAC/C,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC,CAAC;oBAC/E,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAoC,CAAC;gBACxE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI;qBACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;qBACtC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO;QACT,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,8BAA8B;IAC9B,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAClC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QACzC,MAAM,MAAM,GAAG,IAAc,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;YAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,UAAU,CAAC,MAAM,CAAC,CAAC;QACnB,IAAI,UAAU,EAAE,CAAC;YACf,cAAc,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,sCAAsC;IACtC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACnC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QAChC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YAClD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QACD,cAAc,CAAC,UAA6C,CAAC,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,qBAAqB;IACrB,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC1C,MAAM,aAAa,GAAG,iBAAiB,EAAE,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,kCAAkC;IAClC,GAAG,CAAC,GAAG,CAAC,iCAAiC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACtD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,wBAAwB;IACxB,GAAG,CAAC,MAAM,CAAC,wBAAwB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAChD,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,cAAc;IACd,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC;IACnG,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1D,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,IAAI,CAAC,QAAQ,cAAc,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACvH,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,uBAAuB;IACvB,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/C,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC9E,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE5C,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAa,EAAE,EAAE;QACrC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,GAAW,EAAE,EAAE;YACrC,IAAI,GAKH,CAAC;YACF,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;gBACjE,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBACxC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC,CAAC,CAAC;gBAC3E,OAAO;YACT,CAAC;YAED,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;gBACpB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,mCAAmC,EAAE,CAAC,CAAC,CAAC;gBACtF,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;YAEpC,6BAA6B;YAC7B,IAAI,cAAc,GAAG,GAAG,CAAC,cAAc,CAAC;YACxC,IAAI,eAAmC,CAAC;YACxC,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAE9B,IAAI,cAAc,EAAE,CAAC;gBACnB,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;gBACjD,IAAI,QAAQ,EAAE,cAAc,EAAE,CAAC;oBAC7B,eAAe,GAAG,QAAQ,CAAC,cAAc,CAAC;gBAC5C,CAAC;YACH,CAAC;YAED,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjD,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;gBACzB,iBAAiB,GAAG,IAAI,CAAC;YAC3B,CAAC;YAED,IAAI,CAAC;gBACH,0DAA0D;gBAC1D,MAAM,cAAc,GAAG,GAAG,CAAC,WAAW,CAAC;gBACvC,IAAI,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChD,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;wBACjD,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,WAAW,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;qBACzG,CAAC,CAAC,CAAC;gBACN,CAAC;qBAAM,CAAC;oBACN,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChD,CAAC;gBAED,oDAAoD;gBACpD,IAAI,iBAAiB,EAAE,CAAC;oBACtB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;oBAChF,WAAW,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;gBACrC,CAAC;gBAED,oCAAoC;gBACpC,IAAI,WAAqC,CAAC;gBAC1C,IAAI,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChD,WAAW,GAAG,EAAE,CAAC;oBACjB,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;wBACjC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;wBACrC,IAAI,MAAM,EAAE,CAAC;4BACX,WAAW,CAAC,IAAI,CAAC;gCACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;gCACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gCACzB,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC;6BACjE,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,mFAAmF;gBACnF,MAAM,WAAW,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;gBAChD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,oCAAoC;gBAE9E,IAAI,YAAY,GAAG,EAAE,CAAC;gBACtB,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,GAAG,CAAC,IAAI,EACR,MAAM,EACN,UAAU,EACV,CAAC,KAAK,EAAE,EAAE;oBACR,YAAY,IAAI,KAAK,CAAC;oBACtB,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;wBACrC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC1D,CAAC;gBACH,CAAC,EACD,eAAe,EACf,WAAW,EACX,OAAO,CACR,CAAC;gBAEF,yBAAyB;gBACzB,IAAI,YAAY,EAAE,CAAC;oBACjB,WAAW,CAAC,cAAc,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;gBACzD,CAAC;gBAED,iCAAiC;gBACjC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;oBACrB,eAAe,CAAC,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;gBACpD,CAAC;gBAED,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;oBACrC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;gBACvE,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;oBACrC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,35 @@
1
+ import Database from "better-sqlite3";
2
+ export interface Conversation {
3
+ id: string;
4
+ provider: string;
5
+ sdk_session_id: string | null;
6
+ title: string | null;
7
+ created_at: string;
8
+ updated_at: string;
9
+ }
10
+ export interface Message {
11
+ id: number;
12
+ conversation_id: string;
13
+ role: string;
14
+ content: string;
15
+ created_at: string;
16
+ }
17
+ export declare function getDb(): Database.Database;
18
+ export declare function createConversation(provider: string): Conversation;
19
+ export declare function getConversation(id: string): Conversation | undefined;
20
+ export declare function updateSessionId(conversationId: string, sdkSessionId: string): void;
21
+ export declare function updateTitle(conversationId: string, title: string): void;
22
+ export declare function listConversations(): Conversation[];
23
+ export declare function saveMessage(conversationId: string, role: string, content: string): void;
24
+ export declare function getMessages(conversationId: string): Message[];
25
+ export declare function deleteConversation(id: string): void;
26
+ export interface ParsedContent {
27
+ text: string;
28
+ attachments?: Array<{
29
+ fileId: string;
30
+ filename: string;
31
+ mimeType: string;
32
+ }>;
33
+ }
34
+ export declare function parseMessageContent(content: string): ParsedContent;
35
+ export declare function closeDb(): void;
@@ -0,0 +1,98 @@
1
+ import Database from "better-sqlite3";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import crypto from "node:crypto";
5
+ import { DATA_DIR } from "./config.js";
6
+ const DB_PATH = path.join(DATA_DIR, "sessions.db");
7
+ let db = null;
8
+ export function getDb() {
9
+ if (db)
10
+ return db;
11
+ if (!fs.existsSync(DATA_DIR)) {
12
+ fs.mkdirSync(DATA_DIR, { recursive: true });
13
+ }
14
+ db = new Database(DB_PATH);
15
+ db.pragma("journal_mode = WAL");
16
+ db.exec(`
17
+ CREATE TABLE IF NOT EXISTS conversations (
18
+ id TEXT PRIMARY KEY,
19
+ provider TEXT NOT NULL,
20
+ sdk_session_id TEXT,
21
+ title TEXT,
22
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
23
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
24
+ )
25
+ `);
26
+ db.exec(`
27
+ CREATE TABLE IF NOT EXISTS messages (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ conversation_id TEXT NOT NULL,
30
+ role TEXT NOT NULL,
31
+ content TEXT NOT NULL,
32
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33
+ FOREIGN KEY (conversation_id) REFERENCES conversations(id)
34
+ )
35
+ `);
36
+ return db;
37
+ }
38
+ export function createConversation(provider) {
39
+ const id = crypto.randomUUID();
40
+ const now = new Date().toISOString();
41
+ getDb()
42
+ .prepare("INSERT INTO conversations (id, provider, created_at, updated_at) VALUES (?, ?, ?, ?)")
43
+ .run(id, provider, now, now);
44
+ return { id, provider, sdk_session_id: null, title: null, created_at: now, updated_at: now };
45
+ }
46
+ export function getConversation(id) {
47
+ return getDb()
48
+ .prepare("SELECT * FROM conversations WHERE id = ?")
49
+ .get(id);
50
+ }
51
+ export function updateSessionId(conversationId, sdkSessionId) {
52
+ getDb()
53
+ .prepare("UPDATE conversations SET sdk_session_id = ?, updated_at = datetime('now') WHERE id = ?")
54
+ .run(sdkSessionId, conversationId);
55
+ }
56
+ export function updateTitle(conversationId, title) {
57
+ getDb()
58
+ .prepare("UPDATE conversations SET title = ?, updated_at = datetime('now') WHERE id = ?")
59
+ .run(title, conversationId);
60
+ }
61
+ export function listConversations() {
62
+ return getDb()
63
+ .prepare("SELECT * FROM conversations ORDER BY updated_at DESC")
64
+ .all();
65
+ }
66
+ export function saveMessage(conversationId, role, content) {
67
+ getDb()
68
+ .prepare("INSERT INTO messages (conversation_id, role, content) VALUES (?, ?, ?)")
69
+ .run(conversationId, role, content);
70
+ }
71
+ export function getMessages(conversationId) {
72
+ return getDb()
73
+ .prepare("SELECT * FROM messages WHERE conversation_id = ? ORDER BY id ASC")
74
+ .all(conversationId);
75
+ }
76
+ export function deleteConversation(id) {
77
+ getDb().prepare("DELETE FROM messages WHERE conversation_id = ?").run(id);
78
+ getDb().prepare("DELETE FROM conversations WHERE id = ?").run(id);
79
+ }
80
+ export function parseMessageContent(content) {
81
+ try {
82
+ const parsed = JSON.parse(content);
83
+ if (parsed && typeof parsed.text === "string") {
84
+ return { text: parsed.text, attachments: parsed.attachments };
85
+ }
86
+ }
87
+ catch {
88
+ // Not JSON — treat as plain text
89
+ }
90
+ return { text: content };
91
+ }
92
+ export function closeDb() {
93
+ if (db) {
94
+ db.close();
95
+ db = null;
96
+ }
97
+ }
98
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export declare const UPLOADS_DIR: string;
2
+ export declare const ALLOWED_IMAGE_TYPES: string[];
3
+ export interface UploadMeta {
4
+ fileId: string;
5
+ filename: string;
6
+ mimeType: string;
7
+ size: number;
8
+ path: string;
9
+ }
10
+ export declare function saveUpload(buffer: Buffer, filename: string, mimeType: string): UploadMeta;
11
+ export declare function getUpload(fileId: string): {
12
+ data: Buffer;
13
+ filename: string;
14
+ mimeType: string;
15
+ } | null;
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { DATA_DIR } from "./config.js";
5
+ export const UPLOADS_DIR = path.join(DATA_DIR, "uploads");
6
+ export const ALLOWED_IMAGE_TYPES = [
7
+ "image/jpeg",
8
+ "image/png",
9
+ "image/gif",
10
+ "image/webp",
11
+ ];
12
+ export function saveUpload(buffer, filename, mimeType) {
13
+ const fileId = crypto.randomUUID();
14
+ const dir = path.join(UPLOADS_DIR, fileId);
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ const filePath = path.join(dir, filename);
17
+ fs.writeFileSync(filePath, buffer);
18
+ return { fileId, filename, mimeType, size: buffer.length, path: filePath };
19
+ }
20
+ export function getUpload(fileId) {
21
+ const dir = path.join(UPLOADS_DIR, fileId);
22
+ if (!fs.existsSync(dir))
23
+ return null;
24
+ const files = fs.readdirSync(dir);
25
+ if (files.length === 0)
26
+ return null;
27
+ const filename = files[0];
28
+ const filePath = path.join(dir, filename);
29
+ const data = fs.readFileSync(filePath);
30
+ const ext = path.extname(filename).toLowerCase();
31
+ const mimeMap = {
32
+ ".jpg": "image/jpeg",
33
+ ".jpeg": "image/jpeg",
34
+ ".png": "image/png",
35
+ ".gif": "image/gif",
36
+ ".webp": "image/webp",
37
+ };
38
+ const mimeType = mimeMap[ext] || "application/octet-stream";
39
+ return { data, filename, mimeType };
40
+ }
41
+ //# sourceMappingURL=uploads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.js","sourceRoot":"","sources":["../src/uploads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAE1D,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,YAAY;IACZ,WAAW;IACX,WAAW;IACX,YAAY;CACb,CAAC;AAUF,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,QAAgB,EAChB,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC3C,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC1C,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,MAAc;IAEd,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,YAAY;QACpB,OAAO,EAAE,YAAY;QACrB,MAAM,EAAE,WAAW;QACnB,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,YAAY;KACtB,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;IAC5D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACtC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "goto-assistant",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight, self-hosted personal AI assistant",
5
+ "license": "MIT",
6
+ "packageManager": "pnpm@10.29.3",
7
+ "type": "module",
8
+ "bin": {
9
+ "goto-assistant": "./bin/goto-assistant.js"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "public/",
14
+ "bin/"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.11.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/jolks/goto-assistant"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "assistant",
26
+ "chat",
27
+ "claude",
28
+ "openai",
29
+ "mcp",
30
+ "self-hosted"
31
+ ],
32
+ "scripts": {
33
+ "dev": "tsx src/index.ts",
34
+ "build": "tsc",
35
+ "start": "node dist/index.js",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@anthropic-ai/claude-agent-sdk": "^0.2.0",
42
+ "@openai/agents": "^0.4.0",
43
+ "@openai/agents-extensions": "^0.4.0",
44
+ "better-sqlite3": "^11.0.0",
45
+ "express": "^5.0.0",
46
+ "multer": "^2.0.2",
47
+ "ws": "^8.0.0",
48
+ "zod": "^4.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/better-sqlite3": "^7.0.0",
52
+ "@types/express": "^5.0.0",
53
+ "@types/multer": "^2.0.0",
54
+ "@types/ws": "^8.0.0",
55
+ "tsx": "^4.0.0",
56
+ "typescript": "^5.0.0",
57
+ "vitest": "^3.0.0"
58
+ },
59
+ "pnpm": {
60
+ "onlyBuiltDependencies": [
61
+ "better-sqlite3",
62
+ "esbuild"
63
+ ]
64
+ }
65
+ }
@@ -0,0 +1,50 @@
1
+ // buildCronConfig — derive mcp-cron args and env from the user's AI settings.
2
+ //
3
+ // Core rule: if baseUrl is set (LiteLLM proxy), cron always uses
4
+ // --ai-provider openai + MCP_CRON_AI_API_KEY, because mcp-cron's Anthropic
5
+ // provider does not support --ai-base-url.
6
+
7
+ // eslint-disable-next-line no-unused-vars
8
+ function buildCronConfig({ provider, apiKey, model, baseUrl, currentArgs }) {
9
+ const useProxy = Boolean(baseUrl);
10
+ const aiProvider = useProxy ? 'openai' : (provider === 'claude' ? 'anthropic' : 'openai');
11
+
12
+ // Update --ai-provider
13
+ let args = currentArgs.replace(/--ai-provider \S+/, `--ai-provider ${aiProvider}`);
14
+
15
+ // Update --ai-model
16
+ if (model) {
17
+ if (/--ai-model \S+/.test(args)) {
18
+ args = args.replace(/--ai-model \S+/, `--ai-model ${model}`);
19
+ } else {
20
+ args += ` --ai-model ${model}`;
21
+ }
22
+ }
23
+
24
+ // Update --ai-base-url
25
+ if (baseUrl) {
26
+ if (/--ai-base-url \S+/.test(args)) {
27
+ args = args.replace(/--ai-base-url \S+/, `--ai-base-url ${baseUrl}`);
28
+ } else {
29
+ args += ` --ai-base-url ${baseUrl}`;
30
+ }
31
+ } else {
32
+ args = args.replace(/\s*--ai-base-url \S+/, '');
33
+ }
34
+
35
+ // Determine env key
36
+ let envKey;
37
+ if (useProxy) {
38
+ envKey = 'MCP_CRON_AI_API_KEY';
39
+ } else if (provider === 'claude') {
40
+ envKey = 'ANTHROPIC_API_KEY';
41
+ } else {
42
+ envKey = 'OPENAI_API_KEY';
43
+ }
44
+
45
+ return { args, envKey, envValue: apiKey };
46
+ }
47
+
48
+ if (typeof module !== 'undefined' && module.exports) {
49
+ module.exports = { buildCronConfig };
50
+ }