@hasna/connectors 1.1.18 → 1.2.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/bin/index.js +1175 -290
- package/bin/mcp.js +701 -71
- package/bin/serve.js +768 -141
- package/dist/db/jobs.d.ts +53 -0
- package/dist/db/jobs.test.d.ts +1 -0
- package/dist/db/workflows.d.ts +26 -0
- package/dist/db/workflows.test.d.ts +1 -0
- package/dist/lib/llm.d.ts +38 -0
- package/dist/lib/llm.test.d.ts +1 -0
- package/dist/lib/scheduler.d.ts +20 -0
- package/dist/lib/scheduler.test.d.ts +1 -0
- package/dist/lib/strip.d.ts +20 -0
- package/dist/lib/strip.test.d.ts +1 -0
- package/dist/lib/workflow-runner.d.ts +22 -0
- package/package.json +1 -1
package/bin/serve.js
CHANGED
|
@@ -16,19 +16,30 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
16
16
|
});
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
29
|
var __require = import.meta.require;
|
|
20
30
|
|
|
21
|
-
// src/server/serve.ts
|
|
22
|
-
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
23
|
-
|
|
24
31
|
// src/db/database.ts
|
|
32
|
+
var exports_database = {};
|
|
33
|
+
__export(exports_database, {
|
|
34
|
+
shortUuid: () => shortUuid,
|
|
35
|
+
now: () => now,
|
|
36
|
+
getDatabase: () => getDatabase,
|
|
37
|
+
closeDatabase: () => closeDatabase
|
|
38
|
+
});
|
|
25
39
|
import { Database } from "bun:sqlite";
|
|
26
40
|
import { join } from "path";
|
|
27
41
|
import { homedir } from "os";
|
|
28
42
|
import { mkdirSync } from "fs";
|
|
29
|
-
var DB_DIR = join(homedir(), ".connectors");
|
|
30
|
-
var DB_PATH = join(DB_DIR, "connectors.db");
|
|
31
|
-
var _db = null;
|
|
32
43
|
function getDatabase(path) {
|
|
33
44
|
if (_db)
|
|
34
45
|
return _db;
|
|
@@ -39,9 +50,16 @@ function getDatabase(path) {
|
|
|
39
50
|
migrate(_db);
|
|
40
51
|
return _db;
|
|
41
52
|
}
|
|
53
|
+
function closeDatabase() {
|
|
54
|
+
_db?.close();
|
|
55
|
+
_db = null;
|
|
56
|
+
}
|
|
42
57
|
function now() {
|
|
43
58
|
return new Date().toISOString();
|
|
44
59
|
}
|
|
60
|
+
function shortUuid() {
|
|
61
|
+
return crypto.randomUUID().slice(0, 8);
|
|
62
|
+
}
|
|
45
63
|
function migrate(db) {
|
|
46
64
|
db.run(`
|
|
47
65
|
CREATE TABLE IF NOT EXISTS agents (
|
|
@@ -71,11 +89,407 @@ function migrate(db) {
|
|
|
71
89
|
`);
|
|
72
90
|
db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id)`);
|
|
73
91
|
db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at)`);
|
|
92
|
+
db.run(`
|
|
93
|
+
CREATE TABLE IF NOT EXISTS connector_rate_usage (
|
|
94
|
+
agent_id TEXT NOT NULL,
|
|
95
|
+
connector TEXT NOT NULL,
|
|
96
|
+
window_start TEXT NOT NULL,
|
|
97
|
+
call_count INTEGER NOT NULL DEFAULT 0,
|
|
98
|
+
PRIMARY KEY (agent_id, connector, window_start)
|
|
99
|
+
)
|
|
100
|
+
`);
|
|
101
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_rate_usage_window ON connector_rate_usage(connector, window_start)`);
|
|
102
|
+
db.run(`
|
|
103
|
+
CREATE TABLE IF NOT EXISTS connector_jobs (
|
|
104
|
+
id TEXT PRIMARY KEY,
|
|
105
|
+
name TEXT UNIQUE NOT NULL,
|
|
106
|
+
connector TEXT NOT NULL,
|
|
107
|
+
command TEXT NOT NULL,
|
|
108
|
+
args TEXT NOT NULL DEFAULT '[]',
|
|
109
|
+
cron TEXT NOT NULL,
|
|
110
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
111
|
+
strip INTEGER NOT NULL DEFAULT 0,
|
|
112
|
+
created_at TEXT NOT NULL,
|
|
113
|
+
last_run_at TEXT
|
|
114
|
+
)
|
|
115
|
+
`);
|
|
116
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON connector_jobs(enabled)`);
|
|
117
|
+
db.run(`
|
|
118
|
+
CREATE TABLE IF NOT EXISTS connector_job_runs (
|
|
119
|
+
id TEXT PRIMARY KEY,
|
|
120
|
+
job_id TEXT NOT NULL REFERENCES connector_jobs(id) ON DELETE CASCADE,
|
|
121
|
+
started_at TEXT NOT NULL,
|
|
122
|
+
finished_at TEXT,
|
|
123
|
+
exit_code INTEGER,
|
|
124
|
+
raw_output TEXT,
|
|
125
|
+
stripped_output TEXT
|
|
126
|
+
)
|
|
127
|
+
`);
|
|
128
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_job_runs_job ON connector_job_runs(job_id, started_at DESC)`);
|
|
129
|
+
db.run(`
|
|
130
|
+
CREATE TABLE IF NOT EXISTS connector_workflows (
|
|
131
|
+
id TEXT PRIMARY KEY,
|
|
132
|
+
name TEXT UNIQUE NOT NULL,
|
|
133
|
+
steps TEXT NOT NULL DEFAULT '[]',
|
|
134
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
135
|
+
created_at TEXT NOT NULL
|
|
136
|
+
)
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
var DB_DIR, DB_PATH, _db = null;
|
|
140
|
+
var init_database = __esm(() => {
|
|
141
|
+
DB_DIR = join(homedir(), ".connectors");
|
|
142
|
+
DB_PATH = join(DB_DIR, "connectors.db");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// src/lib/llm.ts
|
|
146
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
147
|
+
import { join as join2 } from "path";
|
|
148
|
+
import { homedir as homedir2 } from "os";
|
|
149
|
+
function getLlmConfigPath() {
|
|
150
|
+
return join2(homedir2(), ".connectors", "llm.json");
|
|
151
|
+
}
|
|
152
|
+
function getLlmConfig() {
|
|
153
|
+
const path = getLlmConfigPath();
|
|
154
|
+
if (!existsSync(path))
|
|
155
|
+
return null;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function saveLlmConfig(config) {
|
|
163
|
+
const dir = join2(homedir2(), ".connectors");
|
|
164
|
+
mkdirSync2(dir, { recursive: true });
|
|
165
|
+
writeFileSync(getLlmConfigPath(), JSON.stringify(config, null, 2));
|
|
166
|
+
}
|
|
167
|
+
function maskKey(key) {
|
|
168
|
+
if (key.length <= 8)
|
|
169
|
+
return "***";
|
|
170
|
+
return key.slice(0, 8) + "***";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
class LLMClient {
|
|
174
|
+
config;
|
|
175
|
+
constructor(config) {
|
|
176
|
+
this.config = config;
|
|
177
|
+
}
|
|
178
|
+
static fromConfig() {
|
|
179
|
+
const config = getLlmConfig();
|
|
180
|
+
if (!config)
|
|
181
|
+
return null;
|
|
182
|
+
return new LLMClient(config);
|
|
183
|
+
}
|
|
184
|
+
async complete(prompt, content) {
|
|
185
|
+
const start = Date.now();
|
|
186
|
+
const { provider, model, api_key } = this.config;
|
|
187
|
+
if (provider === "anthropic") {
|
|
188
|
+
return this._anthropicComplete(prompt, content, start);
|
|
189
|
+
}
|
|
190
|
+
const baseUrl = PROVIDER_BASE_URLS[provider];
|
|
191
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
Authorization: `Bearer ${api_key}`
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
model,
|
|
199
|
+
messages: [
|
|
200
|
+
{ role: "system", content: prompt },
|
|
201
|
+
{ role: "user", content }
|
|
202
|
+
],
|
|
203
|
+
temperature: 0,
|
|
204
|
+
max_tokens: 4096
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
const error = await response.text();
|
|
209
|
+
throw new Error(`LLM request failed (${provider} ${response.status}): ${error}`);
|
|
210
|
+
}
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
return {
|
|
213
|
+
content: data.choices[0].message.content,
|
|
214
|
+
provider,
|
|
215
|
+
model,
|
|
216
|
+
latency_ms: Date.now() - start
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async _anthropicComplete(prompt, content, start) {
|
|
220
|
+
const { model, api_key } = this.config;
|
|
221
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
"x-api-key": api_key,
|
|
226
|
+
"anthropic-version": "2023-06-01"
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
model,
|
|
230
|
+
system: prompt,
|
|
231
|
+
messages: [{ role: "user", content }],
|
|
232
|
+
max_tokens: 4096
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const error = await response.text();
|
|
237
|
+
throw new Error(`LLM request failed (anthropic ${response.status}): ${error}`);
|
|
238
|
+
}
|
|
239
|
+
const data = await response.json();
|
|
240
|
+
return {
|
|
241
|
+
content: data.content[0].text,
|
|
242
|
+
provider: "anthropic",
|
|
243
|
+
model,
|
|
244
|
+
latency_ms: Date.now() - start
|
|
245
|
+
};
|
|
246
|
+
}
|
|
74
247
|
}
|
|
248
|
+
var PROVIDER_BASE_URLS;
|
|
249
|
+
var init_llm = __esm(() => {
|
|
250
|
+
PROVIDER_BASE_URLS = {
|
|
251
|
+
cerebras: "https://api.cerebras.ai/v1",
|
|
252
|
+
groq: "https://api.groq.com/openai/v1",
|
|
253
|
+
openai: "https://api.openai.com/v1"
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// src/lib/strip.ts
|
|
258
|
+
async function maybeStrip(output, _type = "json") {
|
|
259
|
+
const config = getLlmConfig();
|
|
260
|
+
if (!config?.strip)
|
|
261
|
+
return output;
|
|
262
|
+
if (!output || output.trim().length === 0)
|
|
263
|
+
return output;
|
|
264
|
+
const client = LLMClient.fromConfig();
|
|
265
|
+
if (!client)
|
|
266
|
+
return output;
|
|
267
|
+
try {
|
|
268
|
+
const result = await client.complete(STRIP_PROMPT, output);
|
|
269
|
+
return result.content.trim();
|
|
270
|
+
} catch {
|
|
271
|
+
return output;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
var STRIP_PROMPT = `You are a data extraction assistant. Your job is to take raw API output and return ONLY the essential, structured data.
|
|
275
|
+
|
|
276
|
+
Rules:
|
|
277
|
+
- Return valid JSON only (no markdown, no explanation)
|
|
278
|
+
- Remove pagination metadata, rate limit headers, empty fields, null values
|
|
279
|
+
- Keep all meaningful data fields
|
|
280
|
+
- If the input is already minimal, return it unchanged
|
|
281
|
+
- If input is not JSON, extract key facts as a JSON object
|
|
282
|
+
- Never truncate actual data values`;
|
|
283
|
+
var init_strip = __esm(() => {
|
|
284
|
+
init_llm();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// src/db/jobs.ts
|
|
288
|
+
function rowToJob(row) {
|
|
289
|
+
return {
|
|
290
|
+
...row,
|
|
291
|
+
args: JSON.parse(row.args || "[]"),
|
|
292
|
+
enabled: row.enabled === 1,
|
|
293
|
+
strip: row.strip === 1
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function createJob(input, db) {
|
|
297
|
+
const d = db ?? getDatabase();
|
|
298
|
+
const id = shortUuid();
|
|
299
|
+
const ts = now();
|
|
300
|
+
d.run("INSERT INTO connector_jobs (id, name, connector, command, args, cron, enabled, strip, created_at) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)", [id, input.name, input.connector, input.command, JSON.stringify(input.args ?? []), input.cron, input.strip ? 1 : 0, ts]);
|
|
301
|
+
return getJob(id, d);
|
|
302
|
+
}
|
|
303
|
+
function getJob(id, db) {
|
|
304
|
+
const d = db ?? getDatabase();
|
|
305
|
+
const row = d.query("SELECT * FROM connector_jobs WHERE id = ?").get(id);
|
|
306
|
+
return row ? rowToJob(row) : null;
|
|
307
|
+
}
|
|
308
|
+
function getJobByName(name, db) {
|
|
309
|
+
const d = db ?? getDatabase();
|
|
310
|
+
const row = d.query("SELECT * FROM connector_jobs WHERE name = ?").get(name);
|
|
311
|
+
return row ? rowToJob(row) : null;
|
|
312
|
+
}
|
|
313
|
+
function listJobs(db) {
|
|
314
|
+
const d = db ?? getDatabase();
|
|
315
|
+
return d.query("SELECT * FROM connector_jobs ORDER BY name").all().map(rowToJob);
|
|
316
|
+
}
|
|
317
|
+
function listEnabledJobs(db) {
|
|
318
|
+
const d = db ?? getDatabase();
|
|
319
|
+
return d.query("SELECT * FROM connector_jobs WHERE enabled = 1").all().map(rowToJob);
|
|
320
|
+
}
|
|
321
|
+
function updateJob(id, input, db) {
|
|
322
|
+
const d = db ?? getDatabase();
|
|
323
|
+
const sets = [];
|
|
324
|
+
const params = [];
|
|
325
|
+
if (input.name !== undefined) {
|
|
326
|
+
sets.push("name = ?");
|
|
327
|
+
params.push(input.name);
|
|
328
|
+
}
|
|
329
|
+
if (input.connector !== undefined) {
|
|
330
|
+
sets.push("connector = ?");
|
|
331
|
+
params.push(input.connector);
|
|
332
|
+
}
|
|
333
|
+
if (input.command !== undefined) {
|
|
334
|
+
sets.push("command = ?");
|
|
335
|
+
params.push(input.command);
|
|
336
|
+
}
|
|
337
|
+
if (input.args !== undefined) {
|
|
338
|
+
sets.push("args = ?");
|
|
339
|
+
params.push(JSON.stringify(input.args));
|
|
340
|
+
}
|
|
341
|
+
if (input.cron !== undefined) {
|
|
342
|
+
sets.push("cron = ?");
|
|
343
|
+
params.push(input.cron);
|
|
344
|
+
}
|
|
345
|
+
if (input.enabled !== undefined) {
|
|
346
|
+
sets.push("enabled = ?");
|
|
347
|
+
params.push(input.enabled ? 1 : 0);
|
|
348
|
+
}
|
|
349
|
+
if (input.strip !== undefined) {
|
|
350
|
+
sets.push("strip = ?");
|
|
351
|
+
params.push(input.strip ? 1 : 0);
|
|
352
|
+
}
|
|
353
|
+
if (sets.length === 0)
|
|
354
|
+
return getJob(id, d);
|
|
355
|
+
params.push(id);
|
|
356
|
+
d.run(`UPDATE connector_jobs SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
357
|
+
return getJob(id, d);
|
|
358
|
+
}
|
|
359
|
+
function deleteJob(id, db) {
|
|
360
|
+
const d = db ?? getDatabase();
|
|
361
|
+
return d.run("DELETE FROM connector_jobs WHERE id = ?", [id]).changes > 0;
|
|
362
|
+
}
|
|
363
|
+
function touchJobLastRun(id, db) {
|
|
364
|
+
const d = db ?? getDatabase();
|
|
365
|
+
d.run("UPDATE connector_jobs SET last_run_at = ? WHERE id = ?", [now(), id]);
|
|
366
|
+
}
|
|
367
|
+
function createJobRun(jobId, db) {
|
|
368
|
+
const d = db ?? getDatabase();
|
|
369
|
+
const id = shortUuid();
|
|
370
|
+
const ts = now();
|
|
371
|
+
d.run("INSERT INTO connector_job_runs (id, job_id, started_at) VALUES (?, ?, ?)", [id, jobId, ts]);
|
|
372
|
+
return { id, job_id: jobId, started_at: ts, finished_at: null, exit_code: null, raw_output: null, stripped_output: null };
|
|
373
|
+
}
|
|
374
|
+
function finishJobRun(id, result, db) {
|
|
375
|
+
const d = db ?? getDatabase();
|
|
376
|
+
d.run("UPDATE connector_job_runs SET finished_at = ?, exit_code = ?, raw_output = ?, stripped_output = ? WHERE id = ?", [now(), result.exit_code, result.raw_output, result.stripped_output ?? null, id]);
|
|
377
|
+
}
|
|
378
|
+
function listJobRuns(jobId, limit = 20, db) {
|
|
379
|
+
const d = db ?? getDatabase();
|
|
380
|
+
return d.query("SELECT * FROM connector_job_runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?").all(jobId, limit);
|
|
381
|
+
}
|
|
382
|
+
var init_jobs = __esm(() => {
|
|
383
|
+
init_database();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// src/lib/scheduler.ts
|
|
387
|
+
var exports_scheduler = {};
|
|
388
|
+
__export(exports_scheduler, {
|
|
389
|
+
triggerJob: () => triggerJob,
|
|
390
|
+
stopScheduler: () => stopScheduler,
|
|
391
|
+
startScheduler: () => startScheduler
|
|
392
|
+
});
|
|
393
|
+
import { spawn } from "child_process";
|
|
394
|
+
function cronMatches(cron, d) {
|
|
395
|
+
const parts = cron.trim().split(/\s+/);
|
|
396
|
+
if (parts.length !== 5)
|
|
397
|
+
return false;
|
|
398
|
+
const [min, hour, dom, mon, dow] = parts;
|
|
399
|
+
function matches(field, value, min_v, max_v) {
|
|
400
|
+
if (field === "*")
|
|
401
|
+
return true;
|
|
402
|
+
if (field.startsWith("*/")) {
|
|
403
|
+
const step = parseInt(field.slice(2));
|
|
404
|
+
return value % step === 0;
|
|
405
|
+
}
|
|
406
|
+
if (field.includes("-")) {
|
|
407
|
+
const [a, b] = field.split("-").map(Number);
|
|
408
|
+
return value >= a && value <= b;
|
|
409
|
+
}
|
|
410
|
+
if (field.includes(",")) {
|
|
411
|
+
return field.split(",").map(Number).includes(value);
|
|
412
|
+
}
|
|
413
|
+
return parseInt(field) === value;
|
|
414
|
+
}
|
|
415
|
+
return matches(min, d.getMinutes(), 0, 59) && matches(hour, d.getHours(), 0, 23) && matches(dom, d.getDate(), 1, 31) && matches(mon, d.getMonth() + 1, 1, 12) && matches(dow, d.getDay(), 0, 6);
|
|
416
|
+
}
|
|
417
|
+
async function runConnectorCommand(connector, command, args) {
|
|
418
|
+
return new Promise((resolve) => {
|
|
419
|
+
const cmdArgs = [connector, command, ...args, "--format", "json"];
|
|
420
|
+
const proc = spawn("connectors", ["run", ...cmdArgs], { shell: false });
|
|
421
|
+
let output = "";
|
|
422
|
+
proc.stdout.on("data", (d) => {
|
|
423
|
+
output += d.toString();
|
|
424
|
+
});
|
|
425
|
+
proc.stderr.on("data", (d) => {
|
|
426
|
+
output += d.toString();
|
|
427
|
+
});
|
|
428
|
+
proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
|
|
429
|
+
proc.on("error", () => resolve({ exitCode: 1, output: `Failed to spawn connectors run` }));
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
proc.kill();
|
|
432
|
+
resolve({ exitCode: 124, output: output + `
|
|
433
|
+
[timeout]` });
|
|
434
|
+
}, 60000);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
async function executeJob(job, db) {
|
|
438
|
+
const run = createJobRun(job.id, db);
|
|
439
|
+
try {
|
|
440
|
+
const { exitCode, output } = await runConnectorCommand(job.connector, job.command, job.args);
|
|
441
|
+
const stripped = job.strip ? await maybeStrip(output) : undefined;
|
|
442
|
+
finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
|
|
443
|
+
touchJobLastRun(job.id, db);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
finishJobRun(run.id, { exit_code: 1, raw_output: String(e) }, db);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function startScheduler(db) {
|
|
449
|
+
if (_interval)
|
|
450
|
+
return;
|
|
451
|
+
_interval = setInterval(async () => {
|
|
452
|
+
const now3 = new Date;
|
|
453
|
+
const currentMinute = now3.getMinutes() + now3.getHours() * 60;
|
|
454
|
+
if (currentMinute === _lastCheckedMinute)
|
|
455
|
+
return;
|
|
456
|
+
_lastCheckedMinute = currentMinute;
|
|
457
|
+
const jobs = listEnabledJobs(db);
|
|
458
|
+
for (const job of jobs) {
|
|
459
|
+
if (cronMatches(job.cron, now3)) {
|
|
460
|
+
executeJob(job, db).catch(() => {});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}, 30000);
|
|
464
|
+
}
|
|
465
|
+
function stopScheduler() {
|
|
466
|
+
if (_interval) {
|
|
467
|
+
clearInterval(_interval);
|
|
468
|
+
_interval = null;
|
|
469
|
+
_lastCheckedMinute = -1;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function triggerJob(job, db) {
|
|
473
|
+
const run = createJobRun(job.id, db);
|
|
474
|
+
const { exitCode, output } = await runConnectorCommand(job.connector, job.command, job.args);
|
|
475
|
+
const stripped = job.strip ? await maybeStrip(output) : undefined;
|
|
476
|
+
finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
|
|
477
|
+
touchJobLastRun(job.id, db);
|
|
478
|
+
return { run_id: run.id, exit_code: exitCode, output: stripped ?? output };
|
|
479
|
+
}
|
|
480
|
+
var _interval = null, _lastCheckedMinute = -1;
|
|
481
|
+
var init_scheduler = __esm(() => {
|
|
482
|
+
init_jobs();
|
|
483
|
+
init_strip();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// src/server/serve.ts
|
|
487
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
|
|
75
488
|
|
|
76
489
|
// src/db/agents.ts
|
|
490
|
+
init_database();
|
|
77
491
|
var AGENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
|
|
78
|
-
function
|
|
492
|
+
function shortUuid2() {
|
|
79
493
|
return crypto.randomUUID().slice(0, 8);
|
|
80
494
|
}
|
|
81
495
|
function isAgentConflict(result) {
|
|
@@ -112,7 +526,7 @@ function registerAgent(input, db) {
|
|
|
112
526
|
d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
113
527
|
return getAgent(existing.id, d);
|
|
114
528
|
}
|
|
115
|
-
const id =
|
|
529
|
+
const id = shortUuid2();
|
|
116
530
|
const ts = now();
|
|
117
531
|
d.run(`INSERT INTO agents (id, name, session_id, role, last_seen_at, created_at)
|
|
118
532
|
VALUES (?, ?, ?, ?, ?, ?)`, [id, normalizedName, input.session_id ?? null, input.role ?? "agent", ts, ts]);
|
|
@@ -136,6 +550,7 @@ function deleteAgent(id, db) {
|
|
|
136
550
|
}
|
|
137
551
|
|
|
138
552
|
// src/db/rate.ts
|
|
553
|
+
init_database();
|
|
139
554
|
var AGENT_ACTIVE_WINDOW_MS2 = 30 * 60 * 1000;
|
|
140
555
|
var WINDOW_SECONDS = 60;
|
|
141
556
|
function ensureRateTable(db) {
|
|
@@ -205,13 +620,106 @@ function getRateBudget(agentId, connector, connectorLimit, db) {
|
|
|
205
620
|
}
|
|
206
621
|
|
|
207
622
|
// src/server/serve.ts
|
|
208
|
-
|
|
623
|
+
init_strip();
|
|
624
|
+
init_llm();
|
|
625
|
+
init_jobs();
|
|
626
|
+
|
|
627
|
+
// src/db/workflows.ts
|
|
628
|
+
init_database();
|
|
629
|
+
function rowToWorkflow(row) {
|
|
630
|
+
return {
|
|
631
|
+
...row,
|
|
632
|
+
steps: JSON.parse(row.steps || "[]"),
|
|
633
|
+
enabled: row.enabled === 1
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function createWorkflow(input, db) {
|
|
637
|
+
const d = db ?? getDatabase();
|
|
638
|
+
const id = shortUuid();
|
|
639
|
+
d.run("INSERT INTO connector_workflows (id, name, steps, enabled, created_at) VALUES (?, ?, ?, 1, ?)", [id, input.name, JSON.stringify(input.steps), now()]);
|
|
640
|
+
return getWorkflow(id, d);
|
|
641
|
+
}
|
|
642
|
+
function getWorkflow(id, db) {
|
|
643
|
+
const d = db ?? getDatabase();
|
|
644
|
+
const row = d.query("SELECT * FROM connector_workflows WHERE id = ?").get(id);
|
|
645
|
+
return row ? rowToWorkflow(row) : null;
|
|
646
|
+
}
|
|
647
|
+
function getWorkflowByName(name, db) {
|
|
648
|
+
const d = db ?? getDatabase();
|
|
649
|
+
const row = d.query("SELECT * FROM connector_workflows WHERE name = ?").get(name);
|
|
650
|
+
return row ? rowToWorkflow(row) : null;
|
|
651
|
+
}
|
|
652
|
+
function listWorkflows(db) {
|
|
653
|
+
const d = db ?? getDatabase();
|
|
654
|
+
return d.query("SELECT * FROM connector_workflows ORDER BY name").all().map(rowToWorkflow);
|
|
655
|
+
}
|
|
656
|
+
function deleteWorkflow(id, db) {
|
|
657
|
+
const d = db ?? getDatabase();
|
|
658
|
+
return d.run("DELETE FROM connector_workflows WHERE id = ?", [id]).changes > 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/server/serve.ts
|
|
662
|
+
init_scheduler();
|
|
663
|
+
|
|
664
|
+
// src/lib/workflow-runner.ts
|
|
665
|
+
init_strip();
|
|
666
|
+
import { spawn as spawn2 } from "child_process";
|
|
667
|
+
async function runStep(step, previousOutput) {
|
|
668
|
+
return new Promise((resolve) => {
|
|
669
|
+
const args = [...step.args ?? []];
|
|
670
|
+
if (previousOutput && previousOutput.trim()) {
|
|
671
|
+
args.push("--input", previousOutput.trim().slice(0, 4096));
|
|
672
|
+
}
|
|
673
|
+
const cmdArgs = ["run", step.connector, step.command, ...args, "--format", "json"];
|
|
674
|
+
const proc = spawn2("connectors", cmdArgs, { shell: false });
|
|
675
|
+
let output = "";
|
|
676
|
+
proc.stdout.on("data", (d) => {
|
|
677
|
+
output += d.toString();
|
|
678
|
+
});
|
|
679
|
+
proc.stderr.on("data", (d) => {
|
|
680
|
+
output += d.toString();
|
|
681
|
+
});
|
|
682
|
+
proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
|
|
683
|
+
proc.on("error", () => resolve({ exitCode: 1, output: "Failed to spawn connectors" }));
|
|
684
|
+
setTimeout(() => {
|
|
685
|
+
proc.kill();
|
|
686
|
+
resolve({ exitCode: 124, output: output + `
|
|
687
|
+
[timeout]` });
|
|
688
|
+
}, 60000);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
async function runWorkflow(workflow) {
|
|
692
|
+
const results = [];
|
|
693
|
+
let previousOutput;
|
|
694
|
+
let success = true;
|
|
695
|
+
for (let i = 0;i < workflow.steps.length; i++) {
|
|
696
|
+
const step = workflow.steps[i];
|
|
697
|
+
const { exitCode, output } = await runStep(step, previousOutput);
|
|
698
|
+
const stripped = await maybeStrip(output);
|
|
699
|
+
results.push({ step: i + 1, connector: step.connector, command: step.command, exit_code: exitCode, output: stripped });
|
|
700
|
+
if (exitCode !== 0) {
|
|
701
|
+
success = false;
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
previousOutput = stripped;
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
workflow_id: workflow.id,
|
|
708
|
+
workflow_name: workflow.name,
|
|
709
|
+
steps: results,
|
|
710
|
+
success,
|
|
711
|
+
final_output: results[results.length - 1]?.output ?? ""
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/server/serve.ts
|
|
716
|
+
import { join as join7, dirname as dirname3, extname, basename } from "path";
|
|
209
717
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
210
|
-
import { homedir as
|
|
718
|
+
import { homedir as homedir6 } from "os";
|
|
211
719
|
|
|
212
720
|
// src/lib/registry.ts
|
|
213
|
-
import { existsSync, readFileSync } from "fs";
|
|
214
|
-
import { join as
|
|
721
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
722
|
+
import { join as join3, dirname } from "path";
|
|
215
723
|
import { fileURLToPath } from "url";
|
|
216
724
|
var CONNECTORS = [
|
|
217
725
|
{
|
|
@@ -6119,17 +6627,17 @@ function loadConnectorVersions() {
|
|
|
6119
6627
|
versionsLoaded = true;
|
|
6120
6628
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
6121
6629
|
const candidates = [
|
|
6122
|
-
|
|
6123
|
-
|
|
6630
|
+
join3(thisDir, "..", "connectors"),
|
|
6631
|
+
join3(thisDir, "..", "..", "connectors")
|
|
6124
6632
|
];
|
|
6125
|
-
const connectorsDir = candidates.find((d) =>
|
|
6633
|
+
const connectorsDir = candidates.find((d) => existsSync2(d));
|
|
6126
6634
|
if (!connectorsDir)
|
|
6127
6635
|
return;
|
|
6128
6636
|
for (const connector of CONNECTORS) {
|
|
6129
6637
|
try {
|
|
6130
|
-
const pkgPath =
|
|
6131
|
-
if (
|
|
6132
|
-
const pkg = JSON.parse(
|
|
6638
|
+
const pkgPath = join3(connectorsDir, `connect-${connector.name}`, "package.json");
|
|
6639
|
+
if (existsSync2(pkgPath)) {
|
|
6640
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
6133
6641
|
connector.version = pkg.version || "0.0.0";
|
|
6134
6642
|
}
|
|
6135
6643
|
} catch {}
|
|
@@ -6137,24 +6645,24 @@ function loadConnectorVersions() {
|
|
|
6137
6645
|
}
|
|
6138
6646
|
|
|
6139
6647
|
// src/lib/installer.ts
|
|
6140
|
-
import { existsSync as
|
|
6141
|
-
import { homedir as
|
|
6142
|
-
import { join as
|
|
6648
|
+
import { existsSync as existsSync3, cpSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync, statSync, rmSync } from "fs";
|
|
6649
|
+
import { homedir as homedir3 } from "os";
|
|
6650
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
6143
6651
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6144
6652
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
6145
6653
|
function resolveConnectorsDir() {
|
|
6146
|
-
const fromBin =
|
|
6147
|
-
if (
|
|
6654
|
+
const fromBin = join4(__dirname2, "..", "connectors");
|
|
6655
|
+
if (existsSync3(fromBin))
|
|
6148
6656
|
return fromBin;
|
|
6149
|
-
const fromSrc =
|
|
6150
|
-
if (
|
|
6657
|
+
const fromSrc = join4(__dirname2, "..", "..", "connectors");
|
|
6658
|
+
if (existsSync3(fromSrc))
|
|
6151
6659
|
return fromSrc;
|
|
6152
6660
|
return fromBin;
|
|
6153
6661
|
}
|
|
6154
6662
|
var CONNECTORS_DIR = resolveConnectorsDir();
|
|
6155
6663
|
function getConnectorPath(name) {
|
|
6156
6664
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
6157
|
-
return
|
|
6665
|
+
return join4(CONNECTORS_DIR, connectorName);
|
|
6158
6666
|
}
|
|
6159
6667
|
function installConnector(name, options = {}) {
|
|
6160
6668
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
@@ -6167,16 +6675,16 @@ function installConnector(name, options = {}) {
|
|
|
6167
6675
|
}
|
|
6168
6676
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
6169
6677
|
const sourcePath = getConnectorPath(name);
|
|
6170
|
-
const destDir =
|
|
6171
|
-
const destPath =
|
|
6172
|
-
if (!
|
|
6678
|
+
const destDir = join4(targetDir, ".connectors");
|
|
6679
|
+
const destPath = join4(destDir, connectorName);
|
|
6680
|
+
if (!existsSync3(sourcePath)) {
|
|
6173
6681
|
return {
|
|
6174
6682
|
connector: name,
|
|
6175
6683
|
success: false,
|
|
6176
6684
|
error: `Connector '${name}' not found`
|
|
6177
6685
|
};
|
|
6178
6686
|
}
|
|
6179
|
-
if (
|
|
6687
|
+
if (existsSync3(destPath) && !overwrite) {
|
|
6180
6688
|
return {
|
|
6181
6689
|
connector: name,
|
|
6182
6690
|
success: false,
|
|
@@ -6185,22 +6693,22 @@ function installConnector(name, options = {}) {
|
|
|
6185
6693
|
};
|
|
6186
6694
|
}
|
|
6187
6695
|
try {
|
|
6188
|
-
if (!
|
|
6189
|
-
|
|
6696
|
+
if (!existsSync3(destDir)) {
|
|
6697
|
+
mkdirSync3(destDir, { recursive: true });
|
|
6190
6698
|
}
|
|
6191
6699
|
cpSync(sourcePath, destPath, { recursive: true });
|
|
6192
|
-
const homeCredDir =
|
|
6193
|
-
if (
|
|
6700
|
+
const homeCredDir = join4(homedir3(), ".connectors", connectorName);
|
|
6701
|
+
if (existsSync3(homeCredDir)) {
|
|
6194
6702
|
const filesToCopy = ["credentials.json", "current_profile"];
|
|
6195
6703
|
for (const file of filesToCopy) {
|
|
6196
|
-
const src =
|
|
6197
|
-
if (
|
|
6198
|
-
cpSync(src,
|
|
6704
|
+
const src = join4(homeCredDir, file);
|
|
6705
|
+
if (existsSync3(src)) {
|
|
6706
|
+
cpSync(src, join4(destPath, file));
|
|
6199
6707
|
}
|
|
6200
6708
|
}
|
|
6201
|
-
const profilesDir =
|
|
6202
|
-
if (
|
|
6203
|
-
cpSync(profilesDir,
|
|
6709
|
+
const profilesDir = join4(homeCredDir, "profiles");
|
|
6710
|
+
if (existsSync3(profilesDir)) {
|
|
6711
|
+
cpSync(profilesDir, join4(destPath, "profiles"), { recursive: true });
|
|
6204
6712
|
}
|
|
6205
6713
|
}
|
|
6206
6714
|
updateConnectorsIndex(destDir);
|
|
@@ -6218,7 +6726,7 @@ function installConnector(name, options = {}) {
|
|
|
6218
6726
|
}
|
|
6219
6727
|
}
|
|
6220
6728
|
function updateConnectorsIndex(connectorsDir) {
|
|
6221
|
-
const indexPath =
|
|
6729
|
+
const indexPath = join4(connectorsDir, "index.ts");
|
|
6222
6730
|
const connectors = readdirSync(connectorsDir).filter((f) => f.startsWith("connect-") && !f.includes("."));
|
|
6223
6731
|
const exports = connectors.map((c) => {
|
|
6224
6732
|
const name = c.replace("connect-", "");
|
|
@@ -6232,24 +6740,24 @@ function updateConnectorsIndex(connectorsDir) {
|
|
|
6232
6740
|
|
|
6233
6741
|
${exports}
|
|
6234
6742
|
`;
|
|
6235
|
-
|
|
6743
|
+
writeFileSync2(indexPath, content);
|
|
6236
6744
|
}
|
|
6237
6745
|
function getInstalledConnectors(targetDir = process.cwd()) {
|
|
6238
|
-
const connectorsDir =
|
|
6239
|
-
if (!
|
|
6746
|
+
const connectorsDir = join4(targetDir, ".connectors");
|
|
6747
|
+
if (!existsSync3(connectorsDir)) {
|
|
6240
6748
|
return [];
|
|
6241
6749
|
}
|
|
6242
6750
|
return readdirSync(connectorsDir).filter((f) => {
|
|
6243
|
-
const fullPath =
|
|
6751
|
+
const fullPath = join4(connectorsDir, f);
|
|
6244
6752
|
return f.startsWith("connect-") && statSync(fullPath).isDirectory();
|
|
6245
6753
|
}).map((f) => f.replace("connect-", ""));
|
|
6246
6754
|
}
|
|
6247
6755
|
function getConnectorDocs(name) {
|
|
6248
6756
|
const connectorPath = getConnectorPath(name);
|
|
6249
|
-
const claudeMdPath =
|
|
6250
|
-
if (!
|
|
6757
|
+
const claudeMdPath = join4(connectorPath, "CLAUDE.md");
|
|
6758
|
+
if (!existsSync3(claudeMdPath))
|
|
6251
6759
|
return null;
|
|
6252
|
-
const raw =
|
|
6760
|
+
const raw = readFileSync3(claudeMdPath, "utf-8");
|
|
6253
6761
|
return {
|
|
6254
6762
|
overview: extractSection(raw, "Project Overview"),
|
|
6255
6763
|
auth: extractSection(raw, "Authentication"),
|
|
@@ -6288,9 +6796,9 @@ function parseEnvVarsTable(section) {
|
|
|
6288
6796
|
}
|
|
6289
6797
|
function removeConnector(name, targetDir = process.cwd()) {
|
|
6290
6798
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
6291
|
-
const connectorsDir =
|
|
6292
|
-
const connectorPath =
|
|
6293
|
-
if (!
|
|
6799
|
+
const connectorsDir = join4(targetDir, ".connectors");
|
|
6800
|
+
const connectorPath = join4(connectorsDir, connectorName);
|
|
6801
|
+
if (!existsSync3(connectorPath)) {
|
|
6294
6802
|
return false;
|
|
6295
6803
|
}
|
|
6296
6804
|
rmSync(connectorPath, { recursive: true });
|
|
@@ -6299,16 +6807,16 @@ function removeConnector(name, targetDir = process.cwd()) {
|
|
|
6299
6807
|
}
|
|
6300
6808
|
|
|
6301
6809
|
// src/server/auth.ts
|
|
6302
|
-
import { existsSync as
|
|
6810
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync3 } from "fs";
|
|
6303
6811
|
import { randomBytes } from "crypto";
|
|
6304
|
-
import { homedir as
|
|
6305
|
-
import { join as
|
|
6812
|
+
import { homedir as homedir5 } from "os";
|
|
6813
|
+
import { join as join6 } from "path";
|
|
6306
6814
|
|
|
6307
6815
|
// src/lib/lock.ts
|
|
6308
|
-
import { openSync, closeSync, unlinkSync, existsSync as
|
|
6309
|
-
import { join as
|
|
6310
|
-
import { homedir as
|
|
6311
|
-
import { mkdirSync as
|
|
6816
|
+
import { openSync, closeSync, unlinkSync, existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
6817
|
+
import { join as join5 } from "path";
|
|
6818
|
+
import { homedir as homedir4 } from "os";
|
|
6819
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
6312
6820
|
var LOCK_TIMEOUT_MS = 5000;
|
|
6313
6821
|
var LOCK_RETRY_MS = 100;
|
|
6314
6822
|
var STALE_LOCK_MS = 30000;
|
|
@@ -6322,9 +6830,9 @@ class LockTimeoutError extends Error {
|
|
|
6322
6830
|
}
|
|
6323
6831
|
}
|
|
6324
6832
|
function lockPath(connector) {
|
|
6325
|
-
const dir =
|
|
6326
|
-
|
|
6327
|
-
return
|
|
6833
|
+
const dir = join5(homedir4(), ".connectors", `connect-${connector}`);
|
|
6834
|
+
mkdirSync4(dir, { recursive: true });
|
|
6835
|
+
return join5(dir, ".write.lock");
|
|
6328
6836
|
}
|
|
6329
6837
|
function isStale(path) {
|
|
6330
6838
|
try {
|
|
@@ -6335,7 +6843,7 @@ function isStale(path) {
|
|
|
6335
6843
|
}
|
|
6336
6844
|
}
|
|
6337
6845
|
function tryAcquire(path) {
|
|
6338
|
-
if (
|
|
6846
|
+
if (existsSync4(path) && isStale(path)) {
|
|
6339
6847
|
try {
|
|
6340
6848
|
unlinkSync(path);
|
|
6341
6849
|
} catch {}
|
|
@@ -6427,14 +6935,14 @@ function getAuthType(name) {
|
|
|
6427
6935
|
}
|
|
6428
6936
|
function getConnectorConfigDir(name) {
|
|
6429
6937
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
6430
|
-
return
|
|
6938
|
+
return join6(homedir5(), ".connectors", connectorName);
|
|
6431
6939
|
}
|
|
6432
6940
|
function getCurrentProfile(name) {
|
|
6433
6941
|
const configDir = getConnectorConfigDir(name);
|
|
6434
|
-
const currentProfileFile =
|
|
6435
|
-
if (
|
|
6942
|
+
const currentProfileFile = join6(configDir, "current_profile");
|
|
6943
|
+
if (existsSync5(currentProfileFile)) {
|
|
6436
6944
|
try {
|
|
6437
|
-
return
|
|
6945
|
+
return readFileSync4(currentProfileFile, "utf-8").trim() || "default";
|
|
6438
6946
|
} catch {
|
|
6439
6947
|
return "default";
|
|
6440
6948
|
}
|
|
@@ -6446,16 +6954,16 @@ function loadProfileConfig(name) {
|
|
|
6446
6954
|
const profile = getCurrentProfile(name);
|
|
6447
6955
|
let flatConfig = {};
|
|
6448
6956
|
let dirConfig = {};
|
|
6449
|
-
const profileFile =
|
|
6450
|
-
if (
|
|
6957
|
+
const profileFile = join6(configDir, "profiles", `${profile}.json`);
|
|
6958
|
+
if (existsSync5(profileFile)) {
|
|
6451
6959
|
try {
|
|
6452
|
-
flatConfig = JSON.parse(
|
|
6960
|
+
flatConfig = JSON.parse(readFileSync4(profileFile, "utf-8"));
|
|
6453
6961
|
} catch {}
|
|
6454
6962
|
}
|
|
6455
|
-
const profileDirConfig =
|
|
6456
|
-
if (
|
|
6963
|
+
const profileDirConfig = join6(configDir, "profiles", profile, "config.json");
|
|
6964
|
+
if (existsSync5(profileDirConfig)) {
|
|
6457
6965
|
try {
|
|
6458
|
-
dirConfig = JSON.parse(
|
|
6966
|
+
dirConfig = JSON.parse(readFileSync4(profileDirConfig, "utf-8"));
|
|
6459
6967
|
} catch {}
|
|
6460
6968
|
}
|
|
6461
6969
|
if (Object.keys(flatConfig).length === 0 && Object.keys(dirConfig).length === 0) {
|
|
@@ -6466,10 +6974,10 @@ function loadProfileConfig(name) {
|
|
|
6466
6974
|
function loadTokens(name) {
|
|
6467
6975
|
const configDir = getConnectorConfigDir(name);
|
|
6468
6976
|
const profile = getCurrentProfile(name);
|
|
6469
|
-
const tokensFile =
|
|
6470
|
-
if (
|
|
6977
|
+
const tokensFile = join6(configDir, "profiles", profile, "tokens.json");
|
|
6978
|
+
if (existsSync5(tokensFile)) {
|
|
6471
6979
|
try {
|
|
6472
|
-
return JSON.parse(
|
|
6980
|
+
return JSON.parse(readFileSync4(tokensFile, "utf-8"));
|
|
6473
6981
|
} catch {
|
|
6474
6982
|
return null;
|
|
6475
6983
|
}
|
|
@@ -6522,43 +7030,43 @@ function _saveApiKey(name, key, field) {
|
|
|
6522
7030
|
const profile = getCurrentProfile(name);
|
|
6523
7031
|
const keyField = field || guessKeyField(name);
|
|
6524
7032
|
if (keyField === "clientId" || keyField === "clientSecret") {
|
|
6525
|
-
const credentialsFile =
|
|
6526
|
-
|
|
7033
|
+
const credentialsFile = join6(configDir, "credentials.json");
|
|
7034
|
+
mkdirSync5(configDir, { recursive: true });
|
|
6527
7035
|
let creds = {};
|
|
6528
|
-
if (
|
|
7036
|
+
if (existsSync5(credentialsFile)) {
|
|
6529
7037
|
try {
|
|
6530
|
-
creds = JSON.parse(
|
|
7038
|
+
creds = JSON.parse(readFileSync4(credentialsFile, "utf-8"));
|
|
6531
7039
|
} catch {}
|
|
6532
7040
|
}
|
|
6533
7041
|
creds[keyField] = key;
|
|
6534
|
-
|
|
7042
|
+
writeFileSync3(credentialsFile, JSON.stringify(creds, null, 2));
|
|
6535
7043
|
return;
|
|
6536
7044
|
}
|
|
6537
|
-
const profileFile =
|
|
6538
|
-
const profileDir =
|
|
6539
|
-
if (
|
|
7045
|
+
const profileFile = join6(configDir, "profiles", `${profile}.json`);
|
|
7046
|
+
const profileDir = join6(configDir, "profiles", profile);
|
|
7047
|
+
if (existsSync5(profileFile)) {
|
|
6540
7048
|
let config = {};
|
|
6541
7049
|
try {
|
|
6542
|
-
config = JSON.parse(
|
|
7050
|
+
config = JSON.parse(readFileSync4(profileFile, "utf-8"));
|
|
6543
7051
|
} catch {}
|
|
6544
7052
|
config[keyField] = key;
|
|
6545
|
-
|
|
7053
|
+
writeFileSync3(profileFile, JSON.stringify(config, null, 2));
|
|
6546
7054
|
return;
|
|
6547
7055
|
}
|
|
6548
|
-
if (
|
|
6549
|
-
const configFile =
|
|
7056
|
+
if (existsSync5(profileDir)) {
|
|
7057
|
+
const configFile = join6(profileDir, "config.json");
|
|
6550
7058
|
let config = {};
|
|
6551
|
-
if (
|
|
7059
|
+
if (existsSync5(configFile)) {
|
|
6552
7060
|
try {
|
|
6553
|
-
config = JSON.parse(
|
|
7061
|
+
config = JSON.parse(readFileSync4(configFile, "utf-8"));
|
|
6554
7062
|
} catch {}
|
|
6555
7063
|
}
|
|
6556
7064
|
config[keyField] = key;
|
|
6557
|
-
|
|
7065
|
+
writeFileSync3(configFile, JSON.stringify(config, null, 2));
|
|
6558
7066
|
return;
|
|
6559
7067
|
}
|
|
6560
|
-
|
|
6561
|
-
|
|
7068
|
+
mkdirSync5(profileDir, { recursive: true });
|
|
7069
|
+
writeFileSync3(join6(profileDir, "config.json"), JSON.stringify({ [keyField]: key }, null, 2));
|
|
6562
7070
|
}
|
|
6563
7071
|
function guessKeyField(name) {
|
|
6564
7072
|
const docs = getConnectorDocs(name);
|
|
@@ -6576,10 +7084,10 @@ function guessKeyField(name) {
|
|
|
6576
7084
|
}
|
|
6577
7085
|
function getOAuthConfig(name) {
|
|
6578
7086
|
const configDir = getConnectorConfigDir(name);
|
|
6579
|
-
const credentialsFile =
|
|
6580
|
-
if (
|
|
7087
|
+
const credentialsFile = join6(configDir, "credentials.json");
|
|
7088
|
+
if (existsSync5(credentialsFile)) {
|
|
6581
7089
|
try {
|
|
6582
|
-
const creds = JSON.parse(
|
|
7090
|
+
const creds = JSON.parse(readFileSync4(credentialsFile, "utf-8"));
|
|
6583
7091
|
return { clientId: creds.clientId, clientSecret: creds.clientSecret };
|
|
6584
7092
|
} catch {}
|
|
6585
7093
|
}
|
|
@@ -6658,10 +7166,10 @@ async function exchangeOAuthCode(name, code, redirectUri) {
|
|
|
6658
7166
|
function saveOAuthTokens(name, tokens) {
|
|
6659
7167
|
const configDir = getConnectorConfigDir(name);
|
|
6660
7168
|
const profile = getCurrentProfile(name);
|
|
6661
|
-
const profileDir =
|
|
6662
|
-
|
|
6663
|
-
const tokensFile =
|
|
6664
|
-
|
|
7169
|
+
const profileDir = join6(configDir, "profiles", profile);
|
|
7170
|
+
mkdirSync5(profileDir, { recursive: true });
|
|
7171
|
+
const tokensFile = join6(profileDir, "tokens.json");
|
|
7172
|
+
writeFileSync3(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
6665
7173
|
}
|
|
6666
7174
|
async function refreshOAuthToken(name) {
|
|
6667
7175
|
return withWriteLock(name, () => _refreshOAuthToken(name));
|
|
@@ -6703,14 +7211,14 @@ async function _refreshOAuthToken(name) {
|
|
|
6703
7211
|
}
|
|
6704
7212
|
function listProfiles(name) {
|
|
6705
7213
|
const configDir = getConnectorConfigDir(name);
|
|
6706
|
-
const profilesDir =
|
|
6707
|
-
if (!
|
|
7214
|
+
const profilesDir = join6(configDir, "profiles");
|
|
7215
|
+
if (!existsSync5(profilesDir))
|
|
6708
7216
|
return ["default"];
|
|
6709
7217
|
const seen = new Set;
|
|
6710
7218
|
try {
|
|
6711
7219
|
const entries = readdirSync2(profilesDir);
|
|
6712
7220
|
for (const entry of entries) {
|
|
6713
|
-
const fullPath =
|
|
7221
|
+
const fullPath = join6(profilesDir, entry);
|
|
6714
7222
|
const stat = statSync3(fullPath);
|
|
6715
7223
|
if (stat.isDirectory()) {
|
|
6716
7224
|
seen.add(entry);
|
|
@@ -6724,24 +7232,24 @@ function listProfiles(name) {
|
|
|
6724
7232
|
}
|
|
6725
7233
|
function switchProfile(name, profile) {
|
|
6726
7234
|
const configDir = getConnectorConfigDir(name);
|
|
6727
|
-
|
|
6728
|
-
|
|
7235
|
+
mkdirSync5(configDir, { recursive: true });
|
|
7236
|
+
writeFileSync3(join6(configDir, "current_profile"), profile);
|
|
6729
7237
|
}
|
|
6730
7238
|
function deleteProfile(name, profile) {
|
|
6731
7239
|
if (profile === "default")
|
|
6732
7240
|
return false;
|
|
6733
7241
|
const configDir = getConnectorConfigDir(name);
|
|
6734
|
-
const profilesDir =
|
|
6735
|
-
const profileFile =
|
|
6736
|
-
if (
|
|
7242
|
+
const profilesDir = join6(configDir, "profiles");
|
|
7243
|
+
const profileFile = join6(profilesDir, `${profile}.json`);
|
|
7244
|
+
if (existsSync5(profileFile)) {
|
|
6737
7245
|
rmSync2(profileFile);
|
|
6738
7246
|
if (getCurrentProfile(name) === profile) {
|
|
6739
7247
|
switchProfile(name, "default");
|
|
6740
7248
|
}
|
|
6741
7249
|
return true;
|
|
6742
7250
|
}
|
|
6743
|
-
const profileDir =
|
|
6744
|
-
if (
|
|
7251
|
+
const profileDir = join6(profilesDir, profile);
|
|
7252
|
+
if (existsSync5(profileDir)) {
|
|
6745
7253
|
rmSync2(profileDir, { recursive: true });
|
|
6746
7254
|
if (getCurrentProfile(name) === profile) {
|
|
6747
7255
|
switchProfile(name, "default");
|
|
@@ -6764,20 +7272,20 @@ function resolveDashboardDir() {
|
|
|
6764
7272
|
const candidates = [];
|
|
6765
7273
|
try {
|
|
6766
7274
|
const scriptDir = dirname3(fileURLToPath3(import.meta.url));
|
|
6767
|
-
candidates.push(
|
|
6768
|
-
candidates.push(
|
|
7275
|
+
candidates.push(join7(scriptDir, "..", "dashboard", "dist"));
|
|
7276
|
+
candidates.push(join7(scriptDir, "..", "..", "dashboard", "dist"));
|
|
6769
7277
|
} catch {}
|
|
6770
7278
|
if (process.argv[1]) {
|
|
6771
7279
|
const mainDir = dirname3(process.argv[1]);
|
|
6772
|
-
candidates.push(
|
|
6773
|
-
candidates.push(
|
|
7280
|
+
candidates.push(join7(mainDir, "..", "dashboard", "dist"));
|
|
7281
|
+
candidates.push(join7(mainDir, "..", "..", "dashboard", "dist"));
|
|
6774
7282
|
}
|
|
6775
|
-
candidates.push(
|
|
7283
|
+
candidates.push(join7(process.cwd(), "dashboard", "dist"));
|
|
6776
7284
|
for (const candidate of candidates) {
|
|
6777
|
-
if (
|
|
7285
|
+
if (existsSync6(candidate))
|
|
6778
7286
|
return candidate;
|
|
6779
7287
|
}
|
|
6780
|
-
return
|
|
7288
|
+
return join7(process.cwd(), "dashboard", "dist");
|
|
6781
7289
|
}
|
|
6782
7290
|
var MIME_TYPES = {
|
|
6783
7291
|
".html": "text/html; charset=utf-8",
|
|
@@ -6805,6 +7313,18 @@ function json(data, status = 200, port) {
|
|
|
6805
7313
|
}
|
|
6806
7314
|
});
|
|
6807
7315
|
}
|
|
7316
|
+
async function jsonStripped(data, status = 200, port) {
|
|
7317
|
+
const raw = JSON.stringify(data);
|
|
7318
|
+
const body = await maybeStrip(raw);
|
|
7319
|
+
return new Response(body, {
|
|
7320
|
+
status,
|
|
7321
|
+
headers: {
|
|
7322
|
+
"Content-Type": "application/json",
|
|
7323
|
+
"Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
|
|
7324
|
+
...SECURITY_HEADERS
|
|
7325
|
+
}
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
6808
7328
|
function htmlResponse(content, status = 200) {
|
|
6809
7329
|
return new Response(content, {
|
|
6810
7330
|
status,
|
|
@@ -6867,7 +7387,7 @@ function oauthPage(type, title, message, hint, extra) {
|
|
|
6867
7387
|
</body></html>`;
|
|
6868
7388
|
}
|
|
6869
7389
|
function serveStaticFile(filePath) {
|
|
6870
|
-
if (!
|
|
7390
|
+
if (!existsSync6(filePath))
|
|
6871
7391
|
return null;
|
|
6872
7392
|
const ext = extname(filePath);
|
|
6873
7393
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -6891,7 +7411,7 @@ async function startServer(requestedPort, options) {
|
|
|
6891
7411
|
const shouldOpen = options?.open ?? true;
|
|
6892
7412
|
loadConnectorVersions();
|
|
6893
7413
|
const dashboardDir = resolveDashboardDir();
|
|
6894
|
-
const dashboardExists =
|
|
7414
|
+
const dashboardExists = existsSync6(dashboardDir);
|
|
6895
7415
|
if (!dashboardExists) {
|
|
6896
7416
|
console.error(`
|
|
6897
7417
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -6920,10 +7440,10 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6920
7440
|
const fields = fieldsParam ? new Set(fieldsParam.split(",").map((f) => f.trim())) : null;
|
|
6921
7441
|
const data = getAllConnectorsWithAuth();
|
|
6922
7442
|
if (compact) {
|
|
6923
|
-
return
|
|
7443
|
+
return jsonStripped(data.map((c) => ({ name: c.name, category: c.category, installed: c.installed })), 200, port);
|
|
6924
7444
|
}
|
|
6925
7445
|
if (fields) {
|
|
6926
|
-
return
|
|
7446
|
+
return jsonStripped(data.map((c) => {
|
|
6927
7447
|
const out = {};
|
|
6928
7448
|
for (const f of fields) {
|
|
6929
7449
|
if (f in c)
|
|
@@ -6932,7 +7452,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
6932
7452
|
return out;
|
|
6933
7453
|
}), 200, port);
|
|
6934
7454
|
}
|
|
6935
|
-
return
|
|
7455
|
+
return jsonStripped(data, 200, port);
|
|
6936
7456
|
}
|
|
6937
7457
|
const singleMatch = path.match(/^\/api\/connectors\/([^/]+)$/);
|
|
6938
7458
|
if (singleMatch && method === "GET") {
|
|
@@ -7040,6 +7560,110 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7040
7560
|
if (path === "/api/activity" && method === "GET") {
|
|
7041
7561
|
return json(activityLog, 200, port);
|
|
7042
7562
|
}
|
|
7563
|
+
if (path === "/api/llm" && method === "GET") {
|
|
7564
|
+
const config = getLlmConfig();
|
|
7565
|
+
if (!config)
|
|
7566
|
+
return json({ configured: false }, 200, port);
|
|
7567
|
+
return json({ configured: true, provider: config.provider, model: config.model, key: maskKey(config.api_key), strip: config.strip }, 200, port);
|
|
7568
|
+
}
|
|
7569
|
+
if (path === "/api/llm" && method === "POST") {
|
|
7570
|
+
const body = await req.json().catch(() => ({}));
|
|
7571
|
+
const validProviders = ["cerebras", "groq", "openai", "anthropic"];
|
|
7572
|
+
const provider = body.provider;
|
|
7573
|
+
if (!provider || !validProviders.includes(provider))
|
|
7574
|
+
return json({ error: "provider must be one of: " + validProviders.join(", ") }, 400, port);
|
|
7575
|
+
const api_key = body.api_key;
|
|
7576
|
+
if (!api_key)
|
|
7577
|
+
return json({ error: "api_key is required" }, 400, port);
|
|
7578
|
+
const model = body.model || getLlmConfig()?.model || "qwen-3-32b";
|
|
7579
|
+
const strip = typeof body.strip === "boolean" ? body.strip : getLlmConfig()?.strip ?? false;
|
|
7580
|
+
saveLlmConfig({ provider, model, api_key, strip });
|
|
7581
|
+
return json({ success: true, provider, model, strip }, 200, port);
|
|
7582
|
+
}
|
|
7583
|
+
if (path === "/api/llm/test" && method === "POST") {
|
|
7584
|
+
const config = getLlmConfig();
|
|
7585
|
+
if (!config)
|
|
7586
|
+
return json({ error: "No LLM configured" }, 400, port);
|
|
7587
|
+
try {
|
|
7588
|
+
const client = new LLMClient(config);
|
|
7589
|
+
const result = await client.complete('Respond with exactly: {"status":"ok"}', "ping");
|
|
7590
|
+
return json({ success: true, provider: result.provider, model: result.model, latency_ms: result.latency_ms, response: result.content }, 200, port);
|
|
7591
|
+
} catch (e) {
|
|
7592
|
+
return json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500, port);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
if (path === "/api/jobs" && method === "GET") {
|
|
7596
|
+
return json(listJobs(getDatabase2()), 200, port);
|
|
7597
|
+
}
|
|
7598
|
+
if (path === "/api/jobs" && method === "POST") {
|
|
7599
|
+
const body = await req.json().catch(() => ({}));
|
|
7600
|
+
if (!body.name || !body.connector || !body.command || !body.cron)
|
|
7601
|
+
return json({ error: "name, connector, command, cron required" }, 400, port);
|
|
7602
|
+
const job = createJob({ name: body.name, connector: body.connector, command: body.command, args: body.args ?? [], cron: body.cron, strip: !!body.strip }, getDatabase2());
|
|
7603
|
+
return json(job, 201, port);
|
|
7604
|
+
}
|
|
7605
|
+
const jobMatch = path.match(/^\/api\/jobs\/([^/]+)$/);
|
|
7606
|
+
if (jobMatch) {
|
|
7607
|
+
const db = getDatabase2();
|
|
7608
|
+
const job = getJobByName(jobMatch[1]) ?? getDatabase2().query("SELECT * FROM connector_jobs WHERE id = ?").get(jobMatch[1]);
|
|
7609
|
+
if (!job && method !== "DELETE")
|
|
7610
|
+
return json({ error: "Job not found" }, 404, port);
|
|
7611
|
+
if (method === "GET")
|
|
7612
|
+
return json(listJobRuns(job.id, 20, db), 200, port);
|
|
7613
|
+
if (method === "DELETE") {
|
|
7614
|
+
const j = getJobByName(jobMatch[1], db);
|
|
7615
|
+
if (!j)
|
|
7616
|
+
return json({ error: "Job not found" }, 404, port);
|
|
7617
|
+
deleteJob(j.id, db);
|
|
7618
|
+
return json({ success: true }, 200, port);
|
|
7619
|
+
}
|
|
7620
|
+
if (method === "PATCH") {
|
|
7621
|
+
const body = await req.json().catch(() => ({}));
|
|
7622
|
+
const j = getJobByName(jobMatch[1], db);
|
|
7623
|
+
const updated = updateJob(j.id, { enabled: typeof body.enabled === "boolean" ? body.enabled : undefined, strip: typeof body.strip === "boolean" ? body.strip : undefined }, db);
|
|
7624
|
+
return json(updated, 200, port);
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
const jobRunMatch = path.match(/^\/api\/jobs\/([^/]+)\/run$/);
|
|
7628
|
+
if (jobRunMatch && method === "POST") {
|
|
7629
|
+
const db = getDatabase2();
|
|
7630
|
+
const job = getJobByName(jobRunMatch[1], db);
|
|
7631
|
+
if (!job)
|
|
7632
|
+
return json({ error: "Job not found" }, 404, port);
|
|
7633
|
+
const result = await triggerJob(job, db);
|
|
7634
|
+
return json(result, 200, port);
|
|
7635
|
+
}
|
|
7636
|
+
if (path === "/api/workflows" && method === "GET") {
|
|
7637
|
+
return json(listWorkflows(getDatabase2()), 200, port);
|
|
7638
|
+
}
|
|
7639
|
+
if (path === "/api/workflows" && method === "POST") {
|
|
7640
|
+
const body = await req.json().catch(() => ({}));
|
|
7641
|
+
if (!body.name || !body.steps)
|
|
7642
|
+
return json({ error: "name and steps required" }, 400, port);
|
|
7643
|
+
const wf = createWorkflow({ name: body.name, steps: body.steps }, getDatabase2());
|
|
7644
|
+
return json(wf, 201, port);
|
|
7645
|
+
}
|
|
7646
|
+
const wfMatch = path.match(/^\/api\/workflows\/([^/]+)$/);
|
|
7647
|
+
if (wfMatch) {
|
|
7648
|
+
const db = getDatabase2();
|
|
7649
|
+
const wf = getWorkflowByName(wfMatch[1], db);
|
|
7650
|
+
if (!wf)
|
|
7651
|
+
return json({ error: "Workflow not found" }, 404, port);
|
|
7652
|
+
if (method === "GET")
|
|
7653
|
+
return json(wf, 200, port);
|
|
7654
|
+
if (method === "DELETE") {
|
|
7655
|
+
deleteWorkflow(wf.id, db);
|
|
7656
|
+
return json({ success: true }, 200, port);
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
const wfRunMatch = path.match(/^\/api\/workflows\/([^/]+)\/run$/);
|
|
7660
|
+
if (wfRunMatch && method === "POST") {
|
|
7661
|
+
const wf = getWorkflowByName(wfRunMatch[1], getDatabase2());
|
|
7662
|
+
if (!wf)
|
|
7663
|
+
return json({ error: "Workflow not found" }, 404, port);
|
|
7664
|
+
const result = await runWorkflow(wf);
|
|
7665
|
+
return json(result, 200, port);
|
|
7666
|
+
}
|
|
7043
7667
|
if (path === "/api/agents" && method === "GET") {
|
|
7044
7668
|
return json(listAgents(), 200, port);
|
|
7045
7669
|
}
|
|
@@ -7080,12 +7704,12 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7080
7704
|
return json({ error: "Invalid connector name" }, 400, port);
|
|
7081
7705
|
try {
|
|
7082
7706
|
const profiles = listProfiles(name);
|
|
7083
|
-
const configDir =
|
|
7084
|
-
const currentProfileFile =
|
|
7707
|
+
const configDir = join7(homedir6(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
|
|
7708
|
+
const currentProfileFile = join7(configDir, "current_profile");
|
|
7085
7709
|
let current = "default";
|
|
7086
|
-
if (
|
|
7710
|
+
if (existsSync6(currentProfileFile)) {
|
|
7087
7711
|
try {
|
|
7088
|
-
current =
|
|
7712
|
+
current = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
|
|
7089
7713
|
} catch {}
|
|
7090
7714
|
}
|
|
7091
7715
|
return json({ current, profiles }, 200, port);
|
|
@@ -7132,16 +7756,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7132
7756
|
}
|
|
7133
7757
|
if (path === "/api/export" && method === "GET") {
|
|
7134
7758
|
try {
|
|
7135
|
-
const connectDir =
|
|
7759
|
+
const connectDir = join7(homedir6(), ".connectors");
|
|
7136
7760
|
const result = {};
|
|
7137
|
-
if (
|
|
7761
|
+
if (existsSync6(connectDir)) {
|
|
7138
7762
|
const entries = readdirSync3(connectDir, { withFileTypes: true });
|
|
7139
7763
|
for (const entry of entries) {
|
|
7140
7764
|
if (!entry.isDirectory() || !entry.name.startsWith("connect-"))
|
|
7141
7765
|
continue;
|
|
7142
7766
|
const connectorName = entry.name.replace(/^connect-/, "");
|
|
7143
|
-
const profilesDir =
|
|
7144
|
-
if (!
|
|
7767
|
+
const profilesDir = join7(connectDir, entry.name, "profiles");
|
|
7768
|
+
if (!existsSync6(profilesDir))
|
|
7145
7769
|
continue;
|
|
7146
7770
|
const profiles = {};
|
|
7147
7771
|
const profileEntries = readdirSync3(profilesDir, { withFileTypes: true });
|
|
@@ -7149,15 +7773,15 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7149
7773
|
if (pEntry.isFile() && pEntry.name.endsWith(".json")) {
|
|
7150
7774
|
const profileName = basename(pEntry.name, ".json");
|
|
7151
7775
|
try {
|
|
7152
|
-
const config = JSON.parse(
|
|
7776
|
+
const config = JSON.parse(readFileSync5(join7(profilesDir, pEntry.name), "utf-8"));
|
|
7153
7777
|
profiles[profileName] = config;
|
|
7154
7778
|
} catch {}
|
|
7155
7779
|
}
|
|
7156
7780
|
if (pEntry.isDirectory()) {
|
|
7157
|
-
const configPath =
|
|
7158
|
-
if (
|
|
7781
|
+
const configPath = join7(profilesDir, pEntry.name, "config.json");
|
|
7782
|
+
if (existsSync6(configPath)) {
|
|
7159
7783
|
try {
|
|
7160
|
-
const config = JSON.parse(
|
|
7784
|
+
const config = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
7161
7785
|
profiles[pEntry.name] = config;
|
|
7162
7786
|
} catch {}
|
|
7163
7787
|
}
|
|
@@ -7192,20 +7816,20 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7192
7816
|
return json({ error: "Invalid import format: missing 'connectors' object" }, 400, port);
|
|
7193
7817
|
}
|
|
7194
7818
|
let imported = 0;
|
|
7195
|
-
const connectDir =
|
|
7819
|
+
const connectDir = join7(homedir6(), ".connectors");
|
|
7196
7820
|
for (const [connectorName, data] of Object.entries(body.connectors)) {
|
|
7197
7821
|
if (!isValidConnectorName(connectorName))
|
|
7198
7822
|
continue;
|
|
7199
7823
|
if (!data.profiles || typeof data.profiles !== "object")
|
|
7200
7824
|
continue;
|
|
7201
|
-
const connectorDir =
|
|
7202
|
-
const profilesDir =
|
|
7825
|
+
const connectorDir = join7(connectDir, `connect-${connectorName}`);
|
|
7826
|
+
const profilesDir = join7(connectorDir, "profiles");
|
|
7203
7827
|
for (const [profileName, config] of Object.entries(data.profiles)) {
|
|
7204
7828
|
if (!config || typeof config !== "object")
|
|
7205
7829
|
continue;
|
|
7206
|
-
|
|
7207
|
-
const profileFile =
|
|
7208
|
-
|
|
7830
|
+
mkdirSync6(profilesDir, { recursive: true });
|
|
7831
|
+
const profileFile = join7(profilesDir, `${profileName}.json`);
|
|
7832
|
+
writeFileSync4(profileFile, JSON.stringify(config, null, 2));
|
|
7209
7833
|
imported++;
|
|
7210
7834
|
}
|
|
7211
7835
|
}
|
|
@@ -7260,12 +7884,12 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7260
7884
|
}
|
|
7261
7885
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
7262
7886
|
if (path !== "/") {
|
|
7263
|
-
const filePath =
|
|
7887
|
+
const filePath = join7(dashboardDir, path);
|
|
7264
7888
|
const res2 = serveStaticFile(filePath);
|
|
7265
7889
|
if (res2)
|
|
7266
7890
|
return res2;
|
|
7267
7891
|
}
|
|
7268
|
-
const indexPath =
|
|
7892
|
+
const indexPath = join7(dashboardDir, "index.html");
|
|
7269
7893
|
const res = serveStaticFile(indexPath);
|
|
7270
7894
|
if (res)
|
|
7271
7895
|
return res;
|
|
@@ -7279,6 +7903,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
7279
7903
|
};
|
|
7280
7904
|
process.on("SIGINT", shutdown);
|
|
7281
7905
|
process.on("SIGTERM", shutdown);
|
|
7906
|
+
const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), exports_scheduler));
|
|
7907
|
+
const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
7908
|
+
startScheduler2(getDatabase2());
|
|
7282
7909
|
const url = `http://localhost:${port}`;
|
|
7283
7910
|
console.log(`Connectors Dashboard running at ${url}`);
|
|
7284
7911
|
if (shouldOpen) {
|