@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/mcp.js CHANGED
@@ -26,6 +26,7 @@ var __export = (target, all) => {
26
26
  set: (newValue) => all[name] = () => newValue
27
27
  });
28
28
  };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
30
 
30
31
  // node_modules/ajv/dist/compile/codegen/code.js
31
32
  var require_code = __commonJS((exports) => {
@@ -6499,6 +6500,629 @@ var require_dist = __commonJS((exports, module) => {
6499
6500
  exports.default = formatsPlugin;
6500
6501
  });
6501
6502
 
6503
+ // src/db/database.ts
6504
+ var exports_database = {};
6505
+ __export(exports_database, {
6506
+ shortUuid: () => shortUuid,
6507
+ now: () => now,
6508
+ getDatabase: () => getDatabase,
6509
+ closeDatabase: () => closeDatabase
6510
+ });
6511
+ import { Database } from "bun:sqlite";
6512
+ import { join as join6 } from "path";
6513
+ import { homedir as homedir4 } from "os";
6514
+ import { mkdirSync as mkdirSync4 } from "fs";
6515
+ function getDatabase(path) {
6516
+ if (_db)
6517
+ return _db;
6518
+ const dbPath = path ?? DB_PATH;
6519
+ mkdirSync4(join6(dbPath, ".."), { recursive: true });
6520
+ _db = new Database(dbPath);
6521
+ _db.run("PRAGMA journal_mode = WAL");
6522
+ migrate(_db);
6523
+ return _db;
6524
+ }
6525
+ function closeDatabase() {
6526
+ _db?.close();
6527
+ _db = null;
6528
+ }
6529
+ function now() {
6530
+ return new Date().toISOString();
6531
+ }
6532
+ function shortUuid() {
6533
+ return crypto.randomUUID().slice(0, 8);
6534
+ }
6535
+ function migrate(db) {
6536
+ db.run(`
6537
+ CREATE TABLE IF NOT EXISTS agents (
6538
+ id TEXT PRIMARY KEY,
6539
+ name TEXT UNIQUE NOT NULL,
6540
+ session_id TEXT,
6541
+ role TEXT NOT NULL DEFAULT 'agent',
6542
+ last_seen_at TEXT NOT NULL,
6543
+ created_at TEXT NOT NULL
6544
+ )
6545
+ `);
6546
+ db.run(`
6547
+ CREATE TABLE IF NOT EXISTS resource_locks (
6548
+ id TEXT PRIMARY KEY,
6549
+ resource_type TEXT NOT NULL CHECK(resource_type IN ('connector', 'agent', 'profile', 'token')),
6550
+ resource_id TEXT NOT NULL,
6551
+ agent_id TEXT NOT NULL,
6552
+ lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
6553
+ locked_at TEXT NOT NULL DEFAULT (datetime('now')),
6554
+ expires_at TEXT NOT NULL
6555
+ )
6556
+ `);
6557
+ db.run(`
6558
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
6559
+ ON resource_locks(resource_type, resource_id)
6560
+ WHERE lock_type = 'exclusive'
6561
+ `);
6562
+ db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id)`);
6563
+ db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at)`);
6564
+ db.run(`
6565
+ CREATE TABLE IF NOT EXISTS connector_rate_usage (
6566
+ agent_id TEXT NOT NULL,
6567
+ connector TEXT NOT NULL,
6568
+ window_start TEXT NOT NULL,
6569
+ call_count INTEGER NOT NULL DEFAULT 0,
6570
+ PRIMARY KEY (agent_id, connector, window_start)
6571
+ )
6572
+ `);
6573
+ db.run(`CREATE INDEX IF NOT EXISTS idx_rate_usage_window ON connector_rate_usage(connector, window_start)`);
6574
+ db.run(`
6575
+ CREATE TABLE IF NOT EXISTS connector_jobs (
6576
+ id TEXT PRIMARY KEY,
6577
+ name TEXT UNIQUE NOT NULL,
6578
+ connector TEXT NOT NULL,
6579
+ command TEXT NOT NULL,
6580
+ args TEXT NOT NULL DEFAULT '[]',
6581
+ cron TEXT NOT NULL,
6582
+ enabled INTEGER NOT NULL DEFAULT 1,
6583
+ strip INTEGER NOT NULL DEFAULT 0,
6584
+ created_at TEXT NOT NULL,
6585
+ last_run_at TEXT
6586
+ )
6587
+ `);
6588
+ db.run(`CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON connector_jobs(enabled)`);
6589
+ db.run(`
6590
+ CREATE TABLE IF NOT EXISTS connector_job_runs (
6591
+ id TEXT PRIMARY KEY,
6592
+ job_id TEXT NOT NULL REFERENCES connector_jobs(id) ON DELETE CASCADE,
6593
+ started_at TEXT NOT NULL,
6594
+ finished_at TEXT,
6595
+ exit_code INTEGER,
6596
+ raw_output TEXT,
6597
+ stripped_output TEXT
6598
+ )
6599
+ `);
6600
+ db.run(`CREATE INDEX IF NOT EXISTS idx_job_runs_job ON connector_job_runs(job_id, started_at DESC)`);
6601
+ db.run(`
6602
+ CREATE TABLE IF NOT EXISTS connector_workflows (
6603
+ id TEXT PRIMARY KEY,
6604
+ name TEXT UNIQUE NOT NULL,
6605
+ steps TEXT NOT NULL DEFAULT '[]',
6606
+ enabled INTEGER NOT NULL DEFAULT 1,
6607
+ created_at TEXT NOT NULL
6608
+ )
6609
+ `);
6610
+ }
6611
+ var DB_DIR, DB_PATH, _db = null;
6612
+ var init_database = __esm(() => {
6613
+ DB_DIR = join6(homedir4(), ".connectors");
6614
+ DB_PATH = join6(DB_DIR, "connectors.db");
6615
+ });
6616
+
6617
+ // src/lib/llm.ts
6618
+ var exports_llm = {};
6619
+ __export(exports_llm, {
6620
+ setLlmStrip: () => setLlmStrip,
6621
+ saveLlmConfig: () => saveLlmConfig,
6622
+ maskKey: () => maskKey,
6623
+ isStripEnabled: () => isStripEnabled,
6624
+ getLlmConfig: () => getLlmConfig,
6625
+ PROVIDER_DEFAULTS: () => PROVIDER_DEFAULTS,
6626
+ LLMClient: () => LLMClient
6627
+ });
6628
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
6629
+ import { join as join7 } from "path";
6630
+ import { homedir as homedir5 } from "os";
6631
+ function getLlmConfigPath() {
6632
+ return join7(homedir5(), ".connectors", "llm.json");
6633
+ }
6634
+ function getLlmConfig() {
6635
+ const path = getLlmConfigPath();
6636
+ if (!existsSync6(path))
6637
+ return null;
6638
+ try {
6639
+ return JSON.parse(readFileSync4(path, "utf-8"));
6640
+ } catch {
6641
+ return null;
6642
+ }
6643
+ }
6644
+ function saveLlmConfig(config2) {
6645
+ const dir = join7(homedir5(), ".connectors");
6646
+ mkdirSync5(dir, { recursive: true });
6647
+ writeFileSync3(getLlmConfigPath(), JSON.stringify(config2, null, 2));
6648
+ }
6649
+ function setLlmStrip(enabled) {
6650
+ const config2 = getLlmConfig();
6651
+ if (!config2)
6652
+ throw new Error("No LLM config found. Run: connectors llm set --provider <provider> --key <key>");
6653
+ saveLlmConfig({ ...config2, strip: enabled });
6654
+ }
6655
+ function isStripEnabled() {
6656
+ return getLlmConfig()?.strip === true;
6657
+ }
6658
+ function maskKey(key) {
6659
+ if (key.length <= 8)
6660
+ return "***";
6661
+ return key.slice(0, 8) + "***";
6662
+ }
6663
+
6664
+ class LLMClient {
6665
+ config;
6666
+ constructor(config2) {
6667
+ this.config = config2;
6668
+ }
6669
+ static fromConfig() {
6670
+ const config2 = getLlmConfig();
6671
+ if (!config2)
6672
+ return null;
6673
+ return new LLMClient(config2);
6674
+ }
6675
+ async complete(prompt, content) {
6676
+ const start = Date.now();
6677
+ const { provider, model, api_key } = this.config;
6678
+ if (provider === "anthropic") {
6679
+ return this._anthropicComplete(prompt, content, start);
6680
+ }
6681
+ const baseUrl = PROVIDER_BASE_URLS[provider];
6682
+ const response = await fetch(`${baseUrl}/chat/completions`, {
6683
+ method: "POST",
6684
+ headers: {
6685
+ "Content-Type": "application/json",
6686
+ Authorization: `Bearer ${api_key}`
6687
+ },
6688
+ body: JSON.stringify({
6689
+ model,
6690
+ messages: [
6691
+ { role: "system", content: prompt },
6692
+ { role: "user", content }
6693
+ ],
6694
+ temperature: 0,
6695
+ max_tokens: 4096
6696
+ })
6697
+ });
6698
+ if (!response.ok) {
6699
+ const error2 = await response.text();
6700
+ throw new Error(`LLM request failed (${provider} ${response.status}): ${error2}`);
6701
+ }
6702
+ const data = await response.json();
6703
+ return {
6704
+ content: data.choices[0].message.content,
6705
+ provider,
6706
+ model,
6707
+ latency_ms: Date.now() - start
6708
+ };
6709
+ }
6710
+ async _anthropicComplete(prompt, content, start) {
6711
+ const { model, api_key } = this.config;
6712
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
6713
+ method: "POST",
6714
+ headers: {
6715
+ "Content-Type": "application/json",
6716
+ "x-api-key": api_key,
6717
+ "anthropic-version": "2023-06-01"
6718
+ },
6719
+ body: JSON.stringify({
6720
+ model,
6721
+ system: prompt,
6722
+ messages: [{ role: "user", content }],
6723
+ max_tokens: 4096
6724
+ })
6725
+ });
6726
+ if (!response.ok) {
6727
+ const error2 = await response.text();
6728
+ throw new Error(`LLM request failed (anthropic ${response.status}): ${error2}`);
6729
+ }
6730
+ const data = await response.json();
6731
+ return {
6732
+ content: data.content[0].text,
6733
+ provider: "anthropic",
6734
+ model,
6735
+ latency_ms: Date.now() - start
6736
+ };
6737
+ }
6738
+ }
6739
+ var PROVIDER_BASE_URLS, PROVIDER_DEFAULTS;
6740
+ var init_llm = __esm(() => {
6741
+ PROVIDER_BASE_URLS = {
6742
+ cerebras: "https://api.cerebras.ai/v1",
6743
+ groq: "https://api.groq.com/openai/v1",
6744
+ openai: "https://api.openai.com/v1"
6745
+ };
6746
+ PROVIDER_DEFAULTS = {
6747
+ cerebras: { model: "qwen-3-32b" },
6748
+ groq: { model: "llama-3.3-70b-versatile" },
6749
+ openai: { model: "gpt-4o-mini" },
6750
+ anthropic: { model: "claude-haiku-4-5-20251001" }
6751
+ };
6752
+ });
6753
+
6754
+ // src/lib/strip.ts
6755
+ async function maybeStrip(output, _type = "json") {
6756
+ const config2 = getLlmConfig();
6757
+ if (!config2?.strip)
6758
+ return output;
6759
+ if (!output || output.trim().length === 0)
6760
+ return output;
6761
+ const client = LLMClient.fromConfig();
6762
+ if (!client)
6763
+ return output;
6764
+ try {
6765
+ const result = await client.complete(STRIP_PROMPT, output);
6766
+ return result.content.trim();
6767
+ } catch {
6768
+ return output;
6769
+ }
6770
+ }
6771
+ var STRIP_PROMPT = `You are a data extraction assistant. Your job is to take raw API output and return ONLY the essential, structured data.
6772
+
6773
+ Rules:
6774
+ - Return valid JSON only (no markdown, no explanation)
6775
+ - Remove pagination metadata, rate limit headers, empty fields, null values
6776
+ - Keep all meaningful data fields
6777
+ - If the input is already minimal, return it unchanged
6778
+ - If input is not JSON, extract key facts as a JSON object
6779
+ - Never truncate actual data values`;
6780
+ var init_strip = __esm(() => {
6781
+ init_llm();
6782
+ });
6783
+
6784
+ // src/db/jobs.ts
6785
+ var exports_jobs = {};
6786
+ __export(exports_jobs, {
6787
+ updateJob: () => updateJob,
6788
+ touchJobLastRun: () => touchJobLastRun,
6789
+ listJobs: () => listJobs,
6790
+ listJobRuns: () => listJobRuns,
6791
+ listEnabledJobs: () => listEnabledJobs,
6792
+ getLatestRun: () => getLatestRun,
6793
+ getJobByName: () => getJobByName,
6794
+ getJob: () => getJob,
6795
+ finishJobRun: () => finishJobRun,
6796
+ deleteJob: () => deleteJob,
6797
+ createJobRun: () => createJobRun,
6798
+ createJob: () => createJob
6799
+ });
6800
+ function rowToJob(row) {
6801
+ return {
6802
+ ...row,
6803
+ args: JSON.parse(row.args || "[]"),
6804
+ enabled: row.enabled === 1,
6805
+ strip: row.strip === 1
6806
+ };
6807
+ }
6808
+ function createJob(input, db) {
6809
+ const d = db ?? getDatabase();
6810
+ const id = shortUuid();
6811
+ const ts = now();
6812
+ 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]);
6813
+ return getJob(id, d);
6814
+ }
6815
+ function getJob(id, db) {
6816
+ const d = db ?? getDatabase();
6817
+ const row = d.query("SELECT * FROM connector_jobs WHERE id = ?").get(id);
6818
+ return row ? rowToJob(row) : null;
6819
+ }
6820
+ function getJobByName(name, db) {
6821
+ const d = db ?? getDatabase();
6822
+ const row = d.query("SELECT * FROM connector_jobs WHERE name = ?").get(name);
6823
+ return row ? rowToJob(row) : null;
6824
+ }
6825
+ function listJobs(db) {
6826
+ const d = db ?? getDatabase();
6827
+ return d.query("SELECT * FROM connector_jobs ORDER BY name").all().map(rowToJob);
6828
+ }
6829
+ function listEnabledJobs(db) {
6830
+ const d = db ?? getDatabase();
6831
+ return d.query("SELECT * FROM connector_jobs WHERE enabled = 1").all().map(rowToJob);
6832
+ }
6833
+ function updateJob(id, input, db) {
6834
+ const d = db ?? getDatabase();
6835
+ const sets = [];
6836
+ const params = [];
6837
+ if (input.name !== undefined) {
6838
+ sets.push("name = ?");
6839
+ params.push(input.name);
6840
+ }
6841
+ if (input.connector !== undefined) {
6842
+ sets.push("connector = ?");
6843
+ params.push(input.connector);
6844
+ }
6845
+ if (input.command !== undefined) {
6846
+ sets.push("command = ?");
6847
+ params.push(input.command);
6848
+ }
6849
+ if (input.args !== undefined) {
6850
+ sets.push("args = ?");
6851
+ params.push(JSON.stringify(input.args));
6852
+ }
6853
+ if (input.cron !== undefined) {
6854
+ sets.push("cron = ?");
6855
+ params.push(input.cron);
6856
+ }
6857
+ if (input.enabled !== undefined) {
6858
+ sets.push("enabled = ?");
6859
+ params.push(input.enabled ? 1 : 0);
6860
+ }
6861
+ if (input.strip !== undefined) {
6862
+ sets.push("strip = ?");
6863
+ params.push(input.strip ? 1 : 0);
6864
+ }
6865
+ if (sets.length === 0)
6866
+ return getJob(id, d);
6867
+ params.push(id);
6868
+ d.run(`UPDATE connector_jobs SET ${sets.join(", ")} WHERE id = ?`, params);
6869
+ return getJob(id, d);
6870
+ }
6871
+ function deleteJob(id, db) {
6872
+ const d = db ?? getDatabase();
6873
+ return d.run("DELETE FROM connector_jobs WHERE id = ?", [id]).changes > 0;
6874
+ }
6875
+ function touchJobLastRun(id, db) {
6876
+ const d = db ?? getDatabase();
6877
+ d.run("UPDATE connector_jobs SET last_run_at = ? WHERE id = ?", [now(), id]);
6878
+ }
6879
+ function createJobRun(jobId, db) {
6880
+ const d = db ?? getDatabase();
6881
+ const id = shortUuid();
6882
+ const ts = now();
6883
+ d.run("INSERT INTO connector_job_runs (id, job_id, started_at) VALUES (?, ?, ?)", [id, jobId, ts]);
6884
+ return { id, job_id: jobId, started_at: ts, finished_at: null, exit_code: null, raw_output: null, stripped_output: null };
6885
+ }
6886
+ function finishJobRun(id, result, db) {
6887
+ const d = db ?? getDatabase();
6888
+ 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]);
6889
+ }
6890
+ function getLatestRun(jobId, db) {
6891
+ const d = db ?? getDatabase();
6892
+ return d.query("SELECT * FROM connector_job_runs WHERE job_id = ? ORDER BY started_at DESC LIMIT 1").get(jobId);
6893
+ }
6894
+ function listJobRuns(jobId, limit = 20, db) {
6895
+ const d = db ?? getDatabase();
6896
+ return d.query("SELECT * FROM connector_job_runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?").all(jobId, limit);
6897
+ }
6898
+ var init_jobs = __esm(() => {
6899
+ init_database();
6900
+ });
6901
+
6902
+ // src/lib/scheduler.ts
6903
+ var exports_scheduler = {};
6904
+ __export(exports_scheduler, {
6905
+ triggerJob: () => triggerJob,
6906
+ stopScheduler: () => stopScheduler,
6907
+ startScheduler: () => startScheduler
6908
+ });
6909
+ import { spawn as spawn2 } from "child_process";
6910
+ function cronMatches(cron, d) {
6911
+ const parts = cron.trim().split(/\s+/);
6912
+ if (parts.length !== 5)
6913
+ return false;
6914
+ const [min, hour, dom, mon, dow] = parts;
6915
+ function matches(field, value, min_v, max_v) {
6916
+ if (field === "*")
6917
+ return true;
6918
+ if (field.startsWith("*/")) {
6919
+ const step = parseInt(field.slice(2));
6920
+ return value % step === 0;
6921
+ }
6922
+ if (field.includes("-")) {
6923
+ const [a, b] = field.split("-").map(Number);
6924
+ return value >= a && value <= b;
6925
+ }
6926
+ if (field.includes(",")) {
6927
+ return field.split(",").map(Number).includes(value);
6928
+ }
6929
+ return parseInt(field) === value;
6930
+ }
6931
+ 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);
6932
+ }
6933
+ async function runConnectorCommand2(connector, command, args) {
6934
+ return new Promise((resolve) => {
6935
+ const cmdArgs = [connector, command, ...args, "--format", "json"];
6936
+ const proc = spawn2("connectors", ["run", ...cmdArgs], { shell: false });
6937
+ let output = "";
6938
+ proc.stdout.on("data", (d) => {
6939
+ output += d.toString();
6940
+ });
6941
+ proc.stderr.on("data", (d) => {
6942
+ output += d.toString();
6943
+ });
6944
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
6945
+ proc.on("error", () => resolve({ exitCode: 1, output: `Failed to spawn connectors run` }));
6946
+ setTimeout(() => {
6947
+ proc.kill();
6948
+ resolve({ exitCode: 124, output: output + `
6949
+ [timeout]` });
6950
+ }, 60000);
6951
+ });
6952
+ }
6953
+ async function executeJob(job, db) {
6954
+ const run = createJobRun(job.id, db);
6955
+ try {
6956
+ const { exitCode, output } = await runConnectorCommand2(job.connector, job.command, job.args);
6957
+ const stripped = job.strip ? await maybeStrip(output) : undefined;
6958
+ finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
6959
+ touchJobLastRun(job.id, db);
6960
+ } catch (e) {
6961
+ finishJobRun(run.id, { exit_code: 1, raw_output: String(e) }, db);
6962
+ }
6963
+ }
6964
+ function startScheduler(db) {
6965
+ if (_interval)
6966
+ return;
6967
+ _interval = setInterval(async () => {
6968
+ const now3 = new Date;
6969
+ const currentMinute = now3.getMinutes() + now3.getHours() * 60;
6970
+ if (currentMinute === _lastCheckedMinute)
6971
+ return;
6972
+ _lastCheckedMinute = currentMinute;
6973
+ const jobs = listEnabledJobs(db);
6974
+ for (const job of jobs) {
6975
+ if (cronMatches(job.cron, now3)) {
6976
+ executeJob(job, db).catch(() => {});
6977
+ }
6978
+ }
6979
+ }, 30000);
6980
+ }
6981
+ function stopScheduler() {
6982
+ if (_interval) {
6983
+ clearInterval(_interval);
6984
+ _interval = null;
6985
+ _lastCheckedMinute = -1;
6986
+ }
6987
+ }
6988
+ async function triggerJob(job, db) {
6989
+ const run = createJobRun(job.id, db);
6990
+ const { exitCode, output } = await runConnectorCommand2(job.connector, job.command, job.args);
6991
+ const stripped = job.strip ? await maybeStrip(output) : undefined;
6992
+ finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
6993
+ touchJobLastRun(job.id, db);
6994
+ return { run_id: run.id, exit_code: exitCode, output: stripped ?? output };
6995
+ }
6996
+ var _interval = null, _lastCheckedMinute = -1;
6997
+ var init_scheduler = __esm(() => {
6998
+ init_jobs();
6999
+ init_strip();
7000
+ });
7001
+
7002
+ // src/db/workflows.ts
7003
+ var exports_workflows = {};
7004
+ __export(exports_workflows, {
7005
+ updateWorkflow: () => updateWorkflow,
7006
+ listWorkflows: () => listWorkflows,
7007
+ getWorkflowByName: () => getWorkflowByName,
7008
+ getWorkflow: () => getWorkflow,
7009
+ deleteWorkflow: () => deleteWorkflow,
7010
+ createWorkflow: () => createWorkflow
7011
+ });
7012
+ function rowToWorkflow(row) {
7013
+ return {
7014
+ ...row,
7015
+ steps: JSON.parse(row.steps || "[]"),
7016
+ enabled: row.enabled === 1
7017
+ };
7018
+ }
7019
+ function createWorkflow(input, db) {
7020
+ const d = db ?? getDatabase();
7021
+ const id = shortUuid();
7022
+ d.run("INSERT INTO connector_workflows (id, name, steps, enabled, created_at) VALUES (?, ?, ?, 1, ?)", [id, input.name, JSON.stringify(input.steps), now()]);
7023
+ return getWorkflow(id, d);
7024
+ }
7025
+ function getWorkflow(id, db) {
7026
+ const d = db ?? getDatabase();
7027
+ const row = d.query("SELECT * FROM connector_workflows WHERE id = ?").get(id);
7028
+ return row ? rowToWorkflow(row) : null;
7029
+ }
7030
+ function getWorkflowByName(name, db) {
7031
+ const d = db ?? getDatabase();
7032
+ const row = d.query("SELECT * FROM connector_workflows WHERE name = ?").get(name);
7033
+ return row ? rowToWorkflow(row) : null;
7034
+ }
7035
+ function listWorkflows(db) {
7036
+ const d = db ?? getDatabase();
7037
+ return d.query("SELECT * FROM connector_workflows ORDER BY name").all().map(rowToWorkflow);
7038
+ }
7039
+ function updateWorkflow(id, input, db) {
7040
+ const d = db ?? getDatabase();
7041
+ const sets = [];
7042
+ const params = [];
7043
+ if (input.name !== undefined) {
7044
+ sets.push("name = ?");
7045
+ params.push(input.name);
7046
+ }
7047
+ if (input.steps !== undefined) {
7048
+ sets.push("steps = ?");
7049
+ params.push(JSON.stringify(input.steps));
7050
+ }
7051
+ if (input.enabled !== undefined) {
7052
+ sets.push("enabled = ?");
7053
+ params.push(input.enabled ? 1 : 0);
7054
+ }
7055
+ if (sets.length === 0)
7056
+ return getWorkflow(id, d);
7057
+ params.push(id);
7058
+ d.run(`UPDATE connector_workflows SET ${sets.join(", ")} WHERE id = ?`, params);
7059
+ return getWorkflow(id, d);
7060
+ }
7061
+ function deleteWorkflow(id, db) {
7062
+ const d = db ?? getDatabase();
7063
+ return d.run("DELETE FROM connector_workflows WHERE id = ?", [id]).changes > 0;
7064
+ }
7065
+ var init_workflows = __esm(() => {
7066
+ init_database();
7067
+ });
7068
+
7069
+ // src/lib/workflow-runner.ts
7070
+ var exports_workflow_runner = {};
7071
+ __export(exports_workflow_runner, {
7072
+ runWorkflow: () => runWorkflow
7073
+ });
7074
+ import { spawn as spawn3 } from "child_process";
7075
+ async function runStep(step, previousOutput) {
7076
+ return new Promise((resolve) => {
7077
+ const args = [...step.args ?? []];
7078
+ if (previousOutput && previousOutput.trim()) {
7079
+ args.push("--input", previousOutput.trim().slice(0, 4096));
7080
+ }
7081
+ const cmdArgs = ["run", step.connector, step.command, ...args, "--format", "json"];
7082
+ const proc = spawn3("connectors", cmdArgs, { shell: false });
7083
+ let output = "";
7084
+ proc.stdout.on("data", (d) => {
7085
+ output += d.toString();
7086
+ });
7087
+ proc.stderr.on("data", (d) => {
7088
+ output += d.toString();
7089
+ });
7090
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
7091
+ proc.on("error", () => resolve({ exitCode: 1, output: "Failed to spawn connectors" }));
7092
+ setTimeout(() => {
7093
+ proc.kill();
7094
+ resolve({ exitCode: 124, output: output + `
7095
+ [timeout]` });
7096
+ }, 60000);
7097
+ });
7098
+ }
7099
+ async function runWorkflow(workflow) {
7100
+ const results = [];
7101
+ let previousOutput;
7102
+ let success = true;
7103
+ for (let i = 0;i < workflow.steps.length; i++) {
7104
+ const step = workflow.steps[i];
7105
+ const { exitCode, output } = await runStep(step, previousOutput);
7106
+ const stripped = await maybeStrip(output);
7107
+ results.push({ step: i + 1, connector: step.connector, command: step.command, exit_code: exitCode, output: stripped });
7108
+ if (exitCode !== 0) {
7109
+ success = false;
7110
+ break;
7111
+ }
7112
+ previousOutput = stripped;
7113
+ }
7114
+ return {
7115
+ workflow_id: workflow.id,
7116
+ workflow_name: workflow.name,
7117
+ steps: results,
7118
+ success,
7119
+ final_output: results[results.length - 1]?.output ?? ""
7120
+ };
7121
+ }
7122
+ var init_workflow_runner = __esm(() => {
7123
+ init_strip();
7124
+ });
7125
+
6502
7126
  // node_modules/zod/v3/external.js
6503
7127
  var exports_external = {};
6504
7128
  __export(exports_external, {
@@ -25975,61 +26599,10 @@ async function getConnectorCommandHelp(name, command) {
25975
26599
  return result.stdout || result.stderr;
25976
26600
  }
25977
26601
 
25978
- // src/db/database.ts
25979
- import { Database } from "bun:sqlite";
25980
- import { join as join6 } from "path";
25981
- import { homedir as homedir4 } from "os";
25982
- import { mkdirSync as mkdirSync4 } from "fs";
25983
- var DB_DIR = join6(homedir4(), ".connectors");
25984
- var DB_PATH = join6(DB_DIR, "connectors.db");
25985
- var _db = null;
25986
- function getDatabase(path) {
25987
- if (_db)
25988
- return _db;
25989
- const dbPath = path ?? DB_PATH;
25990
- mkdirSync4(join6(dbPath, ".."), { recursive: true });
25991
- _db = new Database(dbPath);
25992
- _db.run("PRAGMA journal_mode = WAL");
25993
- migrate(_db);
25994
- return _db;
25995
- }
25996
- function now() {
25997
- return new Date().toISOString();
25998
- }
25999
- function migrate(db) {
26000
- db.run(`
26001
- CREATE TABLE IF NOT EXISTS agents (
26002
- id TEXT PRIMARY KEY,
26003
- name TEXT UNIQUE NOT NULL,
26004
- session_id TEXT,
26005
- role TEXT NOT NULL DEFAULT 'agent',
26006
- last_seen_at TEXT NOT NULL,
26007
- created_at TEXT NOT NULL
26008
- )
26009
- `);
26010
- db.run(`
26011
- CREATE TABLE IF NOT EXISTS resource_locks (
26012
- id TEXT PRIMARY KEY,
26013
- resource_type TEXT NOT NULL CHECK(resource_type IN ('connector', 'agent', 'profile', 'token')),
26014
- resource_id TEXT NOT NULL,
26015
- agent_id TEXT NOT NULL,
26016
- lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
26017
- locked_at TEXT NOT NULL DEFAULT (datetime('now')),
26018
- expires_at TEXT NOT NULL
26019
- )
26020
- `);
26021
- db.run(`
26022
- CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
26023
- ON resource_locks(resource_type, resource_id)
26024
- WHERE lock_type = 'exclusive'
26025
- `);
26026
- db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id)`);
26027
- db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at)`);
26028
- }
26029
-
26030
26602
  // src/db/agents.ts
26603
+ init_database();
26031
26604
  var AGENT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
26032
- function shortUuid() {
26605
+ function shortUuid2() {
26033
26606
  return crypto.randomUUID().slice(0, 8);
26034
26607
  }
26035
26608
  function isAgentConflict(result) {
@@ -26066,7 +26639,7 @@ function registerAgent(input, db) {
26066
26639
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
26067
26640
  return getAgent(existing.id, d);
26068
26641
  }
26069
- const id = shortUuid();
26642
+ const id = shortUuid2();
26070
26643
  const ts = now();
26071
26644
  d.run(`INSERT INTO agents (id, name, session_id, role, last_seen_at, created_at)
26072
26645
  VALUES (?, ?, ?, ?, ?, ?)`, [id, normalizedName, input.session_id ?? null, input.role ?? "agent", ts, ts]);
@@ -26086,6 +26659,7 @@ function listAgents(db) {
26086
26659
  }
26087
26660
 
26088
26661
  // src/db/rate.ts
26662
+ init_database();
26089
26663
  var AGENT_ACTIVE_WINDOW_MS2 = 30 * 60 * 1000;
26090
26664
  var WINDOW_SECONDS = 60;
26091
26665
  function ensureRateTable(db) {
@@ -26153,6 +26727,9 @@ function checkRateBudget(agentId, connector, connectorLimit, consume = true, db)
26153
26727
  function getRateBudget(agentId, connector, connectorLimit, db) {
26154
26728
  return checkRateBudget(agentId, connector, connectorLimit, false, db);
26155
26729
  }
26730
+
26731
+ // src/mcp/index.ts
26732
+ init_strip();
26156
26733
  // package.json
26157
26734
  var package_default = {
26158
26735
  name: "@hasna/connectors",
@@ -26243,6 +26820,9 @@ var package_default = {
26243
26820
 
26244
26821
  // src/mcp/index.ts
26245
26822
  loadConnectorVersions();
26823
+ async function stripped(text) {
26824
+ return { content: [{ type: "text", text: await maybeStrip(text) }] };
26825
+ }
26246
26826
  var server = new McpServer({
26247
26827
  name: "connectors",
26248
26828
  version: package_default.version
@@ -26253,20 +26833,7 @@ server.registerTool("search_connectors", {
26253
26833
  inputSchema: { query: exports_external.string() }
26254
26834
  }, async ({ query }) => {
26255
26835
  const results = searchConnectors(query);
26256
- return {
26257
- content: [
26258
- {
26259
- type: "text",
26260
- text: JSON.stringify(results.map((c) => ({
26261
- name: c.name,
26262
- displayName: c.displayName,
26263
- version: c.version,
26264
- category: c.category,
26265
- description: c.description
26266
- })), null, 2)
26267
- }
26268
- ]
26269
- };
26836
+ return stripped(JSON.stringify(results.map((c) => ({ name: c.name, displayName: c.displayName, version: c.version, category: c.category, description: c.description })), null, 2));
26270
26837
  });
26271
26838
  server.registerTool("list_connectors", {
26272
26839
  title: "List Connectors",
@@ -26299,9 +26866,7 @@ server.registerTool("list_connectors", {
26299
26866
  category: c.category,
26300
26867
  description: c.description
26301
26868
  }));
26302
- return {
26303
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
26304
- };
26869
+ return stripped(JSON.stringify(data, null, 2));
26305
26870
  });
26306
26871
  server.registerTool("connector_docs", {
26307
26872
  title: "Connector Documentation",
@@ -26730,6 +27295,71 @@ server.registerTool("describe_tools", {
26730
27295
  `);
26731
27296
  return { content: [{ type: "text", text: result }] };
26732
27297
  });
27298
+ server.registerTool("list_jobs", { title: "List Jobs", description: "List scheduled connector jobs.", inputSchema: {} }, async () => {
27299
+ const { listJobs: listJobs2 } = await Promise.resolve().then(() => (init_jobs(), exports_jobs));
27300
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
27301
+ return stripped(JSON.stringify(listJobs2(getDatabase2()), null, 2));
27302
+ });
27303
+ server.registerTool("get_latest_job_run", { title: "Get Latest Job Run", description: "Get the most recent run output for a job.", inputSchema: { name: exports_external.string() } }, async ({ name }) => {
27304
+ const { getJobByName: getJobByName2, getLatestRun: getLatestRun2 } = await Promise.resolve().then(() => (init_jobs(), exports_jobs));
27305
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
27306
+ const db = getDatabase2();
27307
+ const job = getJobByName2(name, db);
27308
+ if (!job)
27309
+ return { content: [{ type: "text", text: JSON.stringify({ error: `Job "${name}" not found` }) }], isError: true };
27310
+ const run = getLatestRun2(job.id, db);
27311
+ return stripped(JSON.stringify(run ?? { message: "No runs yet" }, null, 2));
27312
+ });
27313
+ server.registerTool("run_job", { title: "Run Job", description: "Manually trigger a scheduled job.", inputSchema: { name: exports_external.string() } }, async ({ name }) => {
27314
+ const { getJobByName: getJobByName2 } = await Promise.resolve().then(() => (init_jobs(), exports_jobs));
27315
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
27316
+ const { triggerJob: triggerJob2 } = await Promise.resolve().then(() => (init_scheduler(), exports_scheduler));
27317
+ const db = getDatabase2();
27318
+ const job = getJobByName2(name, db);
27319
+ if (!job)
27320
+ return { content: [{ type: "text", text: JSON.stringify({ error: `Job "${name}" not found` }) }], isError: true };
27321
+ const result = await triggerJob2(job, db);
27322
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27323
+ });
27324
+ server.registerTool("list_workflows", { title: "List Workflows", description: "List connector workflows.", inputSchema: {} }, async () => {
27325
+ const { listWorkflows: listWorkflows2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
27326
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
27327
+ return stripped(JSON.stringify(listWorkflows2(getDatabase2()), null, 2));
27328
+ });
27329
+ server.registerTool("run_workflow", { title: "Run Workflow", description: "Execute a connector workflow pipeline.", inputSchema: { name: exports_external.string() } }, async ({ name }) => {
27330
+ const { getWorkflowByName: getWorkflowByName2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
27331
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
27332
+ const { runWorkflow: runWorkflow2 } = await Promise.resolve().then(() => (init_workflow_runner(), exports_workflow_runner));
27333
+ const wf = getWorkflowByName2(name, getDatabase2());
27334
+ if (!wf)
27335
+ return { content: [{ type: "text", text: JSON.stringify({ error: `Workflow "${name}" not found` }) }], isError: true };
27336
+ const result = await runWorkflow2(wf);
27337
+ return stripped(JSON.stringify(result, null, 2));
27338
+ });
27339
+ server.registerTool("get_llm_config", {
27340
+ title: "Get LLM Config",
27341
+ description: "Get current LLM provider config and strip status.",
27342
+ inputSchema: {}
27343
+ }, async () => {
27344
+ const { getLlmConfig: getConfig, maskKey: mask } = await Promise.resolve().then(() => (init_llm(), exports_llm));
27345
+ const config2 = getConfig();
27346
+ if (!config2)
27347
+ return { content: [{ type: "text", text: JSON.stringify({ configured: false }) }] };
27348
+ return { content: [{ type: "text", text: JSON.stringify({ configured: true, provider: config2.provider, model: config2.model, key: mask(config2.api_key), strip: config2.strip }) }] };
27349
+ });
27350
+ server.registerTool("set_llm_strip", {
27351
+ title: "Set LLM Strip",
27352
+ description: "Enable or disable global output stripping.",
27353
+ inputSchema: { enabled: exports_external.boolean() }
27354
+ }, async ({ enabled }) => {
27355
+ const { setLlmStrip: setLlmStrip2 } = await Promise.resolve().then(() => (init_llm(), exports_llm));
27356
+ try {
27357
+ setLlmStrip2(enabled);
27358
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, strip: enabled }) }] };
27359
+ } catch (e) {
27360
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: e instanceof Error ? e.message : String(e) }) }], isError: true };
27361
+ }
27362
+ });
26733
27363
  server.registerTool("register_agent", {
26734
27364
  title: "Register Agent",
26735
27365
  description: "Register or heartbeat an agent. Returns agent or conflict error.",