@hasna/connectors 1.1.17 → 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 CHANGED
@@ -1868,9 +1868,565 @@ var require_commander = __commonJS((exports) => {
1868
1868
  exports.InvalidOptionArgumentError = InvalidArgumentError;
1869
1869
  });
1870
1870
 
1871
+ // src/lib/llm.ts
1872
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
1873
+ import { join } from "path";
1874
+ import { homedir } from "os";
1875
+ function getLlmConfigPath() {
1876
+ return join(homedir(), ".connectors", "llm.json");
1877
+ }
1878
+ function getLlmConfig() {
1879
+ const path = getLlmConfigPath();
1880
+ if (!existsSync(path))
1881
+ return null;
1882
+ try {
1883
+ return JSON.parse(readFileSync(path, "utf-8"));
1884
+ } catch {
1885
+ return null;
1886
+ }
1887
+ }
1888
+ function saveLlmConfig(config) {
1889
+ const dir = join(homedir(), ".connectors");
1890
+ mkdirSync(dir, { recursive: true });
1891
+ writeFileSync(getLlmConfigPath(), JSON.stringify(config, null, 2));
1892
+ }
1893
+ function setLlmStrip(enabled) {
1894
+ const config = getLlmConfig();
1895
+ if (!config)
1896
+ throw new Error("No LLM config found. Run: connectors llm set --provider <provider> --key <key>");
1897
+ saveLlmConfig({ ...config, strip: enabled });
1898
+ }
1899
+ function maskKey(key) {
1900
+ if (key.length <= 8)
1901
+ return "***";
1902
+ return key.slice(0, 8) + "***";
1903
+ }
1904
+
1905
+ class LLMClient {
1906
+ config;
1907
+ constructor(config) {
1908
+ this.config = config;
1909
+ }
1910
+ static fromConfig() {
1911
+ const config = getLlmConfig();
1912
+ if (!config)
1913
+ return null;
1914
+ return new LLMClient(config);
1915
+ }
1916
+ async complete(prompt, content) {
1917
+ const start = Date.now();
1918
+ const { provider, model, api_key } = this.config;
1919
+ if (provider === "anthropic") {
1920
+ return this._anthropicComplete(prompt, content, start);
1921
+ }
1922
+ const baseUrl = PROVIDER_BASE_URLS[provider];
1923
+ const response = await fetch(`${baseUrl}/chat/completions`, {
1924
+ method: "POST",
1925
+ headers: {
1926
+ "Content-Type": "application/json",
1927
+ Authorization: `Bearer ${api_key}`
1928
+ },
1929
+ body: JSON.stringify({
1930
+ model,
1931
+ messages: [
1932
+ { role: "system", content: prompt },
1933
+ { role: "user", content }
1934
+ ],
1935
+ temperature: 0,
1936
+ max_tokens: 4096
1937
+ })
1938
+ });
1939
+ if (!response.ok) {
1940
+ const error = await response.text();
1941
+ throw new Error(`LLM request failed (${provider} ${response.status}): ${error}`);
1942
+ }
1943
+ const data = await response.json();
1944
+ return {
1945
+ content: data.choices[0].message.content,
1946
+ provider,
1947
+ model,
1948
+ latency_ms: Date.now() - start
1949
+ };
1950
+ }
1951
+ async _anthropicComplete(prompt, content, start) {
1952
+ const { model, api_key } = this.config;
1953
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
1954
+ method: "POST",
1955
+ headers: {
1956
+ "Content-Type": "application/json",
1957
+ "x-api-key": api_key,
1958
+ "anthropic-version": "2023-06-01"
1959
+ },
1960
+ body: JSON.stringify({
1961
+ model,
1962
+ system: prompt,
1963
+ messages: [{ role: "user", content }],
1964
+ max_tokens: 4096
1965
+ })
1966
+ });
1967
+ if (!response.ok) {
1968
+ const error = await response.text();
1969
+ throw new Error(`LLM request failed (anthropic ${response.status}): ${error}`);
1970
+ }
1971
+ const data = await response.json();
1972
+ return {
1973
+ content: data.content[0].text,
1974
+ provider: "anthropic",
1975
+ model,
1976
+ latency_ms: Date.now() - start
1977
+ };
1978
+ }
1979
+ }
1980
+ var PROVIDER_BASE_URLS, PROVIDER_DEFAULTS;
1981
+ var init_llm = __esm(() => {
1982
+ PROVIDER_BASE_URLS = {
1983
+ cerebras: "https://api.cerebras.ai/v1",
1984
+ groq: "https://api.groq.com/openai/v1",
1985
+ openai: "https://api.openai.com/v1"
1986
+ };
1987
+ PROVIDER_DEFAULTS = {
1988
+ cerebras: { model: "qwen-3-32b" },
1989
+ groq: { model: "llama-3.3-70b-versatile" },
1990
+ openai: { model: "gpt-4o-mini" },
1991
+ anthropic: { model: "claude-haiku-4-5-20251001" }
1992
+ };
1993
+ });
1994
+
1995
+ // src/db/database.ts
1996
+ var exports_database = {};
1997
+ __export(exports_database, {
1998
+ shortUuid: () => shortUuid,
1999
+ now: () => now,
2000
+ getDatabase: () => getDatabase,
2001
+ closeDatabase: () => closeDatabase
2002
+ });
2003
+ import { Database } from "bun:sqlite";
2004
+ import { join as join2 } from "path";
2005
+ import { homedir as homedir2 } from "os";
2006
+ import { mkdirSync as mkdirSync2 } from "fs";
2007
+ function getDatabase(path) {
2008
+ if (_db)
2009
+ return _db;
2010
+ const dbPath = path ?? DB_PATH;
2011
+ mkdirSync2(join2(dbPath, ".."), { recursive: true });
2012
+ _db = new Database(dbPath);
2013
+ _db.run("PRAGMA journal_mode = WAL");
2014
+ migrate(_db);
2015
+ return _db;
2016
+ }
2017
+ function closeDatabase() {
2018
+ _db?.close();
2019
+ _db = null;
2020
+ }
2021
+ function now() {
2022
+ return new Date().toISOString();
2023
+ }
2024
+ function shortUuid() {
2025
+ return crypto.randomUUID().slice(0, 8);
2026
+ }
2027
+ function migrate(db) {
2028
+ db.run(`
2029
+ CREATE TABLE IF NOT EXISTS agents (
2030
+ id TEXT PRIMARY KEY,
2031
+ name TEXT UNIQUE NOT NULL,
2032
+ session_id TEXT,
2033
+ role TEXT NOT NULL DEFAULT 'agent',
2034
+ last_seen_at TEXT NOT NULL,
2035
+ created_at TEXT NOT NULL
2036
+ )
2037
+ `);
2038
+ db.run(`
2039
+ CREATE TABLE IF NOT EXISTS resource_locks (
2040
+ id TEXT PRIMARY KEY,
2041
+ resource_type TEXT NOT NULL CHECK(resource_type IN ('connector', 'agent', 'profile', 'token')),
2042
+ resource_id TEXT NOT NULL,
2043
+ agent_id TEXT NOT NULL,
2044
+ lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
2045
+ locked_at TEXT NOT NULL DEFAULT (datetime('now')),
2046
+ expires_at TEXT NOT NULL
2047
+ )
2048
+ `);
2049
+ db.run(`
2050
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
2051
+ ON resource_locks(resource_type, resource_id)
2052
+ WHERE lock_type = 'exclusive'
2053
+ `);
2054
+ db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id)`);
2055
+ db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at)`);
2056
+ db.run(`
2057
+ CREATE TABLE IF NOT EXISTS connector_rate_usage (
2058
+ agent_id TEXT NOT NULL,
2059
+ connector TEXT NOT NULL,
2060
+ window_start TEXT NOT NULL,
2061
+ call_count INTEGER NOT NULL DEFAULT 0,
2062
+ PRIMARY KEY (agent_id, connector, window_start)
2063
+ )
2064
+ `);
2065
+ db.run(`CREATE INDEX IF NOT EXISTS idx_rate_usage_window ON connector_rate_usage(connector, window_start)`);
2066
+ db.run(`
2067
+ CREATE TABLE IF NOT EXISTS connector_jobs (
2068
+ id TEXT PRIMARY KEY,
2069
+ name TEXT UNIQUE NOT NULL,
2070
+ connector TEXT NOT NULL,
2071
+ command TEXT NOT NULL,
2072
+ args TEXT NOT NULL DEFAULT '[]',
2073
+ cron TEXT NOT NULL,
2074
+ enabled INTEGER NOT NULL DEFAULT 1,
2075
+ strip INTEGER NOT NULL DEFAULT 0,
2076
+ created_at TEXT NOT NULL,
2077
+ last_run_at TEXT
2078
+ )
2079
+ `);
2080
+ db.run(`CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON connector_jobs(enabled)`);
2081
+ db.run(`
2082
+ CREATE TABLE IF NOT EXISTS connector_job_runs (
2083
+ id TEXT PRIMARY KEY,
2084
+ job_id TEXT NOT NULL REFERENCES connector_jobs(id) ON DELETE CASCADE,
2085
+ started_at TEXT NOT NULL,
2086
+ finished_at TEXT,
2087
+ exit_code INTEGER,
2088
+ raw_output TEXT,
2089
+ stripped_output TEXT
2090
+ )
2091
+ `);
2092
+ db.run(`CREATE INDEX IF NOT EXISTS idx_job_runs_job ON connector_job_runs(job_id, started_at DESC)`);
2093
+ db.run(`
2094
+ CREATE TABLE IF NOT EXISTS connector_workflows (
2095
+ id TEXT PRIMARY KEY,
2096
+ name TEXT UNIQUE NOT NULL,
2097
+ steps TEXT NOT NULL DEFAULT '[]',
2098
+ enabled INTEGER NOT NULL DEFAULT 1,
2099
+ created_at TEXT NOT NULL
2100
+ )
2101
+ `);
2102
+ }
2103
+ var DB_DIR, DB_PATH, _db = null;
2104
+ var init_database = __esm(() => {
2105
+ DB_DIR = join2(homedir2(), ".connectors");
2106
+ DB_PATH = join2(DB_DIR, "connectors.db");
2107
+ });
2108
+
2109
+ // src/db/jobs.ts
2110
+ function rowToJob(row) {
2111
+ return {
2112
+ ...row,
2113
+ args: JSON.parse(row.args || "[]"),
2114
+ enabled: row.enabled === 1,
2115
+ strip: row.strip === 1
2116
+ };
2117
+ }
2118
+ function createJob(input, db) {
2119
+ const d = db ?? getDatabase();
2120
+ const id = shortUuid();
2121
+ const ts = now();
2122
+ 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]);
2123
+ return getJob(id, d);
2124
+ }
2125
+ function getJob(id, db) {
2126
+ const d = db ?? getDatabase();
2127
+ const row = d.query("SELECT * FROM connector_jobs WHERE id = ?").get(id);
2128
+ return row ? rowToJob(row) : null;
2129
+ }
2130
+ function getJobByName(name, db) {
2131
+ const d = db ?? getDatabase();
2132
+ const row = d.query("SELECT * FROM connector_jobs WHERE name = ?").get(name);
2133
+ return row ? rowToJob(row) : null;
2134
+ }
2135
+ function listJobs(db) {
2136
+ const d = db ?? getDatabase();
2137
+ return d.query("SELECT * FROM connector_jobs ORDER BY name").all().map(rowToJob);
2138
+ }
2139
+ function listEnabledJobs(db) {
2140
+ const d = db ?? getDatabase();
2141
+ return d.query("SELECT * FROM connector_jobs WHERE enabled = 1").all().map(rowToJob);
2142
+ }
2143
+ function updateJob(id, input, db) {
2144
+ const d = db ?? getDatabase();
2145
+ const sets = [];
2146
+ const params = [];
2147
+ if (input.name !== undefined) {
2148
+ sets.push("name = ?");
2149
+ params.push(input.name);
2150
+ }
2151
+ if (input.connector !== undefined) {
2152
+ sets.push("connector = ?");
2153
+ params.push(input.connector);
2154
+ }
2155
+ if (input.command !== undefined) {
2156
+ sets.push("command = ?");
2157
+ params.push(input.command);
2158
+ }
2159
+ if (input.args !== undefined) {
2160
+ sets.push("args = ?");
2161
+ params.push(JSON.stringify(input.args));
2162
+ }
2163
+ if (input.cron !== undefined) {
2164
+ sets.push("cron = ?");
2165
+ params.push(input.cron);
2166
+ }
2167
+ if (input.enabled !== undefined) {
2168
+ sets.push("enabled = ?");
2169
+ params.push(input.enabled ? 1 : 0);
2170
+ }
2171
+ if (input.strip !== undefined) {
2172
+ sets.push("strip = ?");
2173
+ params.push(input.strip ? 1 : 0);
2174
+ }
2175
+ if (sets.length === 0)
2176
+ return getJob(id, d);
2177
+ params.push(id);
2178
+ d.run(`UPDATE connector_jobs SET ${sets.join(", ")} WHERE id = ?`, params);
2179
+ return getJob(id, d);
2180
+ }
2181
+ function deleteJob(id, db) {
2182
+ const d = db ?? getDatabase();
2183
+ return d.run("DELETE FROM connector_jobs WHERE id = ?", [id]).changes > 0;
2184
+ }
2185
+ function touchJobLastRun(id, db) {
2186
+ const d = db ?? getDatabase();
2187
+ d.run("UPDATE connector_jobs SET last_run_at = ? WHERE id = ?", [now(), id]);
2188
+ }
2189
+ function createJobRun(jobId, db) {
2190
+ const d = db ?? getDatabase();
2191
+ const id = shortUuid();
2192
+ const ts = now();
2193
+ d.run("INSERT INTO connector_job_runs (id, job_id, started_at) VALUES (?, ?, ?)", [id, jobId, ts]);
2194
+ return { id, job_id: jobId, started_at: ts, finished_at: null, exit_code: null, raw_output: null, stripped_output: null };
2195
+ }
2196
+ function finishJobRun(id, result, db) {
2197
+ const d = db ?? getDatabase();
2198
+ 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]);
2199
+ }
2200
+ function listJobRuns(jobId, limit = 20, db) {
2201
+ const d = db ?? getDatabase();
2202
+ return d.query("SELECT * FROM connector_job_runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?").all(jobId, limit);
2203
+ }
2204
+ var init_jobs = __esm(() => {
2205
+ init_database();
2206
+ });
2207
+
2208
+ // src/db/workflows.ts
2209
+ function rowToWorkflow(row) {
2210
+ return {
2211
+ ...row,
2212
+ steps: JSON.parse(row.steps || "[]"),
2213
+ enabled: row.enabled === 1
2214
+ };
2215
+ }
2216
+ function createWorkflow(input, db) {
2217
+ const d = db ?? getDatabase();
2218
+ const id = shortUuid();
2219
+ d.run("INSERT INTO connector_workflows (id, name, steps, enabled, created_at) VALUES (?, ?, ?, 1, ?)", [id, input.name, JSON.stringify(input.steps), now()]);
2220
+ return getWorkflow(id, d);
2221
+ }
2222
+ function getWorkflow(id, db) {
2223
+ const d = db ?? getDatabase();
2224
+ const row = d.query("SELECT * FROM connector_workflows WHERE id = ?").get(id);
2225
+ return row ? rowToWorkflow(row) : null;
2226
+ }
2227
+ function getWorkflowByName(name, db) {
2228
+ const d = db ?? getDatabase();
2229
+ const row = d.query("SELECT * FROM connector_workflows WHERE name = ?").get(name);
2230
+ return row ? rowToWorkflow(row) : null;
2231
+ }
2232
+ function listWorkflows(db) {
2233
+ const d = db ?? getDatabase();
2234
+ return d.query("SELECT * FROM connector_workflows ORDER BY name").all().map(rowToWorkflow);
2235
+ }
2236
+ function deleteWorkflow(id, db) {
2237
+ const d = db ?? getDatabase();
2238
+ return d.run("DELETE FROM connector_workflows WHERE id = ?", [id]).changes > 0;
2239
+ }
2240
+ var init_workflows = __esm(() => {
2241
+ init_database();
2242
+ });
2243
+
2244
+ // src/lib/strip.ts
2245
+ async function maybeStrip(output, _type = "json") {
2246
+ const config = getLlmConfig();
2247
+ if (!config?.strip)
2248
+ return output;
2249
+ if (!output || output.trim().length === 0)
2250
+ return output;
2251
+ const client = LLMClient.fromConfig();
2252
+ if (!client)
2253
+ return output;
2254
+ try {
2255
+ const result = await client.complete(STRIP_PROMPT, output);
2256
+ return result.content.trim();
2257
+ } catch {
2258
+ return output;
2259
+ }
2260
+ }
2261
+ var STRIP_PROMPT = `You are a data extraction assistant. Your job is to take raw API output and return ONLY the essential, structured data.
2262
+
2263
+ Rules:
2264
+ - Return valid JSON only (no markdown, no explanation)
2265
+ - Remove pagination metadata, rate limit headers, empty fields, null values
2266
+ - Keep all meaningful data fields
2267
+ - If the input is already minimal, return it unchanged
2268
+ - If input is not JSON, extract key facts as a JSON object
2269
+ - Never truncate actual data values`;
2270
+ var init_strip = __esm(() => {
2271
+ init_llm();
2272
+ });
2273
+
2274
+ // src/lib/scheduler.ts
2275
+ var exports_scheduler = {};
2276
+ __export(exports_scheduler, {
2277
+ triggerJob: () => triggerJob,
2278
+ stopScheduler: () => stopScheduler,
2279
+ startScheduler: () => startScheduler
2280
+ });
2281
+ import { spawn } from "child_process";
2282
+ function cronMatches(cron, d) {
2283
+ const parts = cron.trim().split(/\s+/);
2284
+ if (parts.length !== 5)
2285
+ return false;
2286
+ const [min, hour, dom, mon, dow] = parts;
2287
+ function matches(field, value, min_v, max_v) {
2288
+ if (field === "*")
2289
+ return true;
2290
+ if (field.startsWith("*/")) {
2291
+ const step = parseInt(field.slice(2));
2292
+ return value % step === 0;
2293
+ }
2294
+ if (field.includes("-")) {
2295
+ const [a, b] = field.split("-").map(Number);
2296
+ return value >= a && value <= b;
2297
+ }
2298
+ if (field.includes(",")) {
2299
+ return field.split(",").map(Number).includes(value);
2300
+ }
2301
+ return parseInt(field) === value;
2302
+ }
2303
+ 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);
2304
+ }
2305
+ async function runConnectorCommand(connector, command, args) {
2306
+ return new Promise((resolve) => {
2307
+ const cmdArgs = [connector, command, ...args, "--format", "json"];
2308
+ const proc = spawn("connectors", ["run", ...cmdArgs], { shell: false });
2309
+ let output = "";
2310
+ proc.stdout.on("data", (d) => {
2311
+ output += d.toString();
2312
+ });
2313
+ proc.stderr.on("data", (d) => {
2314
+ output += d.toString();
2315
+ });
2316
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
2317
+ proc.on("error", () => resolve({ exitCode: 1, output: `Failed to spawn connectors run` }));
2318
+ setTimeout(() => {
2319
+ proc.kill();
2320
+ resolve({ exitCode: 124, output: output + `
2321
+ [timeout]` });
2322
+ }, 60000);
2323
+ });
2324
+ }
2325
+ async function executeJob(job, db) {
2326
+ const run = createJobRun(job.id, db);
2327
+ try {
2328
+ const { exitCode, output } = await runConnectorCommand(job.connector, job.command, job.args);
2329
+ const stripped = job.strip ? await maybeStrip(output) : undefined;
2330
+ finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
2331
+ touchJobLastRun(job.id, db);
2332
+ } catch (e) {
2333
+ finishJobRun(run.id, { exit_code: 1, raw_output: String(e) }, db);
2334
+ }
2335
+ }
2336
+ function startScheduler(db) {
2337
+ if (_interval)
2338
+ return;
2339
+ _interval = setInterval(async () => {
2340
+ const now2 = new Date;
2341
+ const currentMinute = now2.getMinutes() + now2.getHours() * 60;
2342
+ if (currentMinute === _lastCheckedMinute)
2343
+ return;
2344
+ _lastCheckedMinute = currentMinute;
2345
+ const jobs = listEnabledJobs(db);
2346
+ for (const job of jobs) {
2347
+ if (cronMatches(job.cron, now2)) {
2348
+ executeJob(job, db).catch(() => {});
2349
+ }
2350
+ }
2351
+ }, 30000);
2352
+ }
2353
+ function stopScheduler() {
2354
+ if (_interval) {
2355
+ clearInterval(_interval);
2356
+ _interval = null;
2357
+ _lastCheckedMinute = -1;
2358
+ }
2359
+ }
2360
+ async function triggerJob(job, db) {
2361
+ const run = createJobRun(job.id, db);
2362
+ const { exitCode, output } = await runConnectorCommand(job.connector, job.command, job.args);
2363
+ const stripped = job.strip ? await maybeStrip(output) : undefined;
2364
+ finishJobRun(run.id, { exit_code: exitCode, raw_output: output, stripped_output: stripped }, db);
2365
+ touchJobLastRun(job.id, db);
2366
+ return { run_id: run.id, exit_code: exitCode, output: stripped ?? output };
2367
+ }
2368
+ var _interval = null, _lastCheckedMinute = -1;
2369
+ var init_scheduler = __esm(() => {
2370
+ init_jobs();
2371
+ init_strip();
2372
+ });
2373
+
2374
+ // src/lib/workflow-runner.ts
2375
+ import { spawn as spawn2 } from "child_process";
2376
+ async function runStep(step, previousOutput) {
2377
+ return new Promise((resolve) => {
2378
+ const args = [...step.args ?? []];
2379
+ if (previousOutput && previousOutput.trim()) {
2380
+ args.push("--input", previousOutput.trim().slice(0, 4096));
2381
+ }
2382
+ const cmdArgs = ["run", step.connector, step.command, ...args, "--format", "json"];
2383
+ const proc = spawn2("connectors", cmdArgs, { shell: false });
2384
+ let output = "";
2385
+ proc.stdout.on("data", (d) => {
2386
+ output += d.toString();
2387
+ });
2388
+ proc.stderr.on("data", (d) => {
2389
+ output += d.toString();
2390
+ });
2391
+ proc.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
2392
+ proc.on("error", () => resolve({ exitCode: 1, output: "Failed to spawn connectors" }));
2393
+ setTimeout(() => {
2394
+ proc.kill();
2395
+ resolve({ exitCode: 124, output: output + `
2396
+ [timeout]` });
2397
+ }, 60000);
2398
+ });
2399
+ }
2400
+ async function runWorkflow(workflow) {
2401
+ const results = [];
2402
+ let previousOutput;
2403
+ let success = true;
2404
+ for (let i = 0;i < workflow.steps.length; i++) {
2405
+ const step = workflow.steps[i];
2406
+ const { exitCode, output } = await runStep(step, previousOutput);
2407
+ const stripped = await maybeStrip(output);
2408
+ results.push({ step: i + 1, connector: step.connector, command: step.command, exit_code: exitCode, output: stripped });
2409
+ if (exitCode !== 0) {
2410
+ success = false;
2411
+ break;
2412
+ }
2413
+ previousOutput = stripped;
2414
+ }
2415
+ return {
2416
+ workflow_id: workflow.id,
2417
+ workflow_name: workflow.name,
2418
+ steps: results,
2419
+ success,
2420
+ final_output: results[results.length - 1]?.output ?? ""
2421
+ };
2422
+ }
2423
+ var init_workflow_runner = __esm(() => {
2424
+ init_strip();
2425
+ });
2426
+
1871
2427
  // src/lib/registry.ts
1872
- import { existsSync, readFileSync } from "fs";
1873
- import { join, dirname } from "path";
2428
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2429
+ import { join as join3, dirname } from "path";
1874
2430
  import { fileURLToPath } from "url";
1875
2431
  function getConnectorsByCategory(category) {
1876
2432
  return CONNECTORS.filter((c) => c.category === category);
@@ -1888,17 +2444,17 @@ function loadConnectorVersions() {
1888
2444
  versionsLoaded = true;
1889
2445
  const thisDir = dirname(fileURLToPath(import.meta.url));
1890
2446
  const candidates = [
1891
- join(thisDir, "..", "connectors"),
1892
- join(thisDir, "..", "..", "connectors")
2447
+ join3(thisDir, "..", "connectors"),
2448
+ join3(thisDir, "..", "..", "connectors")
1893
2449
  ];
1894
- const connectorsDir = candidates.find((d) => existsSync(d));
2450
+ const connectorsDir = candidates.find((d) => existsSync2(d));
1895
2451
  if (!connectorsDir)
1896
2452
  return;
1897
2453
  for (const connector of CONNECTORS) {
1898
2454
  try {
1899
- const pkgPath = join(connectorsDir, `connect-${connector.name}`, "package.json");
1900
- if (existsSync(pkgPath)) {
1901
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2455
+ const pkgPath = join3(connectorsDir, `connect-${connector.name}`, "package.json");
2456
+ if (existsSync2(pkgPath)) {
2457
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
1902
2458
  connector.version = pkg.version || "0.0.0";
1903
2459
  }
1904
2460
  } catch {}
@@ -9464,25 +10020,25 @@ var require_cli_spinners = __commonJS((exports, module) => {
9464
10020
  });
9465
10021
 
9466
10022
  // src/lib/installer.ts
9467
- import { existsSync as existsSync2, cpSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, readdirSync, statSync, rmSync } from "fs";
9468
- import { homedir } from "os";
9469
- import { join as join2, dirname as dirname2 } from "path";
10023
+ import { existsSync as existsSync3, cpSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync, statSync, rmSync } from "fs";
10024
+ import { homedir as homedir3 } from "os";
10025
+ import { join as join4, dirname as dirname2 } from "path";
9470
10026
  import { fileURLToPath as fileURLToPath2 } from "url";
9471
10027
  function resolveConnectorsDir() {
9472
- const fromBin = join2(__dirname2, "..", "connectors");
9473
- if (existsSync2(fromBin))
10028
+ const fromBin = join4(__dirname2, "..", "connectors");
10029
+ if (existsSync3(fromBin))
9474
10030
  return fromBin;
9475
- const fromSrc = join2(__dirname2, "..", "..", "connectors");
9476
- if (existsSync2(fromSrc))
10031
+ const fromSrc = join4(__dirname2, "..", "..", "connectors");
10032
+ if (existsSync3(fromSrc))
9477
10033
  return fromSrc;
9478
10034
  return fromBin;
9479
10035
  }
9480
10036
  function getConnectorPath(name) {
9481
10037
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
9482
- return join2(CONNECTORS_DIR, connectorName);
10038
+ return join4(CONNECTORS_DIR, connectorName);
9483
10039
  }
9484
10040
  function connectorExists(name) {
9485
- return existsSync2(getConnectorPath(name));
10041
+ return existsSync3(getConnectorPath(name));
9486
10042
  }
9487
10043
  function installConnector(name, options = {}) {
9488
10044
  const { targetDir = process.cwd(), overwrite = false } = options;
@@ -9495,16 +10051,16 @@ function installConnector(name, options = {}) {
9495
10051
  }
9496
10052
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
9497
10053
  const sourcePath = getConnectorPath(name);
9498
- const destDir = join2(targetDir, ".connectors");
9499
- const destPath = join2(destDir, connectorName);
9500
- if (!existsSync2(sourcePath)) {
10054
+ const destDir = join4(targetDir, ".connectors");
10055
+ const destPath = join4(destDir, connectorName);
10056
+ if (!existsSync3(sourcePath)) {
9501
10057
  return {
9502
10058
  connector: name,
9503
10059
  success: false,
9504
10060
  error: `Connector '${name}' not found`
9505
10061
  };
9506
10062
  }
9507
- if (existsSync2(destPath) && !overwrite) {
10063
+ if (existsSync3(destPath) && !overwrite) {
9508
10064
  return {
9509
10065
  connector: name,
9510
10066
  success: false,
@@ -9513,22 +10069,22 @@ function installConnector(name, options = {}) {
9513
10069
  };
9514
10070
  }
9515
10071
  try {
9516
- if (!existsSync2(destDir)) {
9517
- mkdirSync(destDir, { recursive: true });
10072
+ if (!existsSync3(destDir)) {
10073
+ mkdirSync3(destDir, { recursive: true });
9518
10074
  }
9519
10075
  cpSync(sourcePath, destPath, { recursive: true });
9520
- const homeCredDir = join2(homedir(), ".connectors", connectorName);
9521
- if (existsSync2(homeCredDir)) {
10076
+ const homeCredDir = join4(homedir3(), ".connectors", connectorName);
10077
+ if (existsSync3(homeCredDir)) {
9522
10078
  const filesToCopy = ["credentials.json", "current_profile"];
9523
10079
  for (const file of filesToCopy) {
9524
- const src = join2(homeCredDir, file);
9525
- if (existsSync2(src)) {
9526
- cpSync(src, join2(destPath, file));
10080
+ const src = join4(homeCredDir, file);
10081
+ if (existsSync3(src)) {
10082
+ cpSync(src, join4(destPath, file));
9527
10083
  }
9528
10084
  }
9529
- const profilesDir = join2(homeCredDir, "profiles");
9530
- if (existsSync2(profilesDir)) {
9531
- cpSync(profilesDir, join2(destPath, "profiles"), { recursive: true });
10085
+ const profilesDir = join4(homeCredDir, "profiles");
10086
+ if (existsSync3(profilesDir)) {
10087
+ cpSync(profilesDir, join4(destPath, "profiles"), { recursive: true });
9532
10088
  }
9533
10089
  }
9534
10090
  updateConnectorsIndex(destDir);
@@ -9546,7 +10102,7 @@ function installConnector(name, options = {}) {
9546
10102
  }
9547
10103
  }
9548
10104
  function updateConnectorsIndex(connectorsDir) {
9549
- const indexPath = join2(connectorsDir, "index.ts");
10105
+ const indexPath = join4(connectorsDir, "index.ts");
9550
10106
  const connectors = readdirSync(connectorsDir).filter((f) => f.startsWith("connect-") && !f.includes("."));
9551
10107
  const exports = connectors.map((c) => {
9552
10108
  const name = c.replace("connect-", "");
@@ -9560,24 +10116,24 @@ function updateConnectorsIndex(connectorsDir) {
9560
10116
 
9561
10117
  ${exports}
9562
10118
  `;
9563
- writeFileSync(indexPath, content);
10119
+ writeFileSync2(indexPath, content);
9564
10120
  }
9565
10121
  function getInstalledConnectors(targetDir = process.cwd()) {
9566
- const connectorsDir = join2(targetDir, ".connectors");
9567
- if (!existsSync2(connectorsDir)) {
10122
+ const connectorsDir = join4(targetDir, ".connectors");
10123
+ if (!existsSync3(connectorsDir)) {
9568
10124
  return [];
9569
10125
  }
9570
10126
  return readdirSync(connectorsDir).filter((f) => {
9571
- const fullPath = join2(connectorsDir, f);
10127
+ const fullPath = join4(connectorsDir, f);
9572
10128
  return f.startsWith("connect-") && statSync(fullPath).isDirectory();
9573
10129
  }).map((f) => f.replace("connect-", ""));
9574
10130
  }
9575
10131
  function getConnectorDocs(name) {
9576
10132
  const connectorPath = getConnectorPath(name);
9577
- const claudeMdPath = join2(connectorPath, "CLAUDE.md");
9578
- if (!existsSync2(claudeMdPath))
10133
+ const claudeMdPath = join4(connectorPath, "CLAUDE.md");
10134
+ if (!existsSync3(claudeMdPath))
9579
10135
  return null;
9580
- const raw = readFileSync2(claudeMdPath, "utf-8");
10136
+ const raw = readFileSync3(claudeMdPath, "utf-8");
9581
10137
  return {
9582
10138
  overview: extractSection(raw, "Project Overview"),
9583
10139
  auth: extractSection(raw, "Authentication"),
@@ -9616,9 +10172,9 @@ function parseEnvVarsTable(section) {
9616
10172
  }
9617
10173
  function removeConnector(name, targetDir = process.cwd()) {
9618
10174
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
9619
- const connectorsDir = join2(targetDir, ".connectors");
9620
- const connectorPath = join2(connectorsDir, connectorName);
9621
- if (!existsSync2(connectorPath)) {
10175
+ const connectorsDir = join4(targetDir, ".connectors");
10176
+ const connectorPath = join4(connectorsDir, connectorName);
10177
+ if (!existsSync3(connectorPath)) {
9622
10178
  return false;
9623
10179
  }
9624
10180
  rmSync(connectorPath, { recursive: true });
@@ -9632,14 +10188,14 @@ var init_installer = __esm(() => {
9632
10188
  });
9633
10189
 
9634
10190
  // src/lib/lock.ts
9635
- import { openSync, closeSync, unlinkSync, existsSync as existsSync3, statSync as statSync2 } from "fs";
9636
- import { join as join3 } from "path";
9637
- import { homedir as homedir2 } from "os";
9638
- import { mkdirSync as mkdirSync2 } from "fs";
10191
+ import { openSync, closeSync, unlinkSync, existsSync as existsSync4, statSync as statSync2 } from "fs";
10192
+ import { join as join5 } from "path";
10193
+ import { homedir as homedir4 } from "os";
10194
+ import { mkdirSync as mkdirSync4 } from "fs";
9639
10195
  function lockPath(connector) {
9640
- const dir = join3(homedir2(), ".connectors", `connect-${connector}`);
9641
- mkdirSync2(dir, { recursive: true });
9642
- return join3(dir, ".write.lock");
10196
+ const dir = join5(homedir4(), ".connectors", `connect-${connector}`);
10197
+ mkdirSync4(dir, { recursive: true });
10198
+ return join5(dir, ".write.lock");
9643
10199
  }
9644
10200
  function isStale(path) {
9645
10201
  try {
@@ -9650,7 +10206,7 @@ function isStale(path) {
9650
10206
  }
9651
10207
  }
9652
10208
  function tryAcquire(path) {
9653
- if (existsSync3(path) && isStale(path)) {
10209
+ if (existsSync4(path) && isStale(path)) {
9654
10210
  try {
9655
10211
  unlinkSync(path);
9656
10212
  } catch {}
@@ -9698,10 +10254,10 @@ var init_lock = __esm(() => {
9698
10254
  });
9699
10255
 
9700
10256
  // src/server/auth.ts
9701
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync3 } from "fs";
10257
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync3 } from "fs";
9702
10258
  import { randomBytes } from "crypto";
9703
- import { homedir as homedir3 } from "os";
9704
- import { join as join4 } from "path";
10259
+ import { homedir as homedir5 } from "os";
10260
+ import { join as join6 } from "path";
9705
10261
  function getAuthType(name) {
9706
10262
  const docs = getConnectorDocs(name);
9707
10263
  if (!docs?.auth)
@@ -9715,14 +10271,14 @@ function getAuthType(name) {
9715
10271
  }
9716
10272
  function getConnectorConfigDir(name) {
9717
10273
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
9718
- return join4(homedir3(), ".connectors", connectorName);
10274
+ return join6(homedir5(), ".connectors", connectorName);
9719
10275
  }
9720
10276
  function getCurrentProfile(name) {
9721
10277
  const configDir = getConnectorConfigDir(name);
9722
- const currentProfileFile = join4(configDir, "current_profile");
9723
- if (existsSync4(currentProfileFile)) {
10278
+ const currentProfileFile = join6(configDir, "current_profile");
10279
+ if (existsSync5(currentProfileFile)) {
9724
10280
  try {
9725
- return readFileSync3(currentProfileFile, "utf-8").trim() || "default";
10281
+ return readFileSync4(currentProfileFile, "utf-8").trim() || "default";
9726
10282
  } catch {
9727
10283
  return "default";
9728
10284
  }
@@ -9734,16 +10290,16 @@ function loadProfileConfig(name) {
9734
10290
  const profile = getCurrentProfile(name);
9735
10291
  let flatConfig = {};
9736
10292
  let dirConfig = {};
9737
- const profileFile = join4(configDir, "profiles", `${profile}.json`);
9738
- if (existsSync4(profileFile)) {
10293
+ const profileFile = join6(configDir, "profiles", `${profile}.json`);
10294
+ if (existsSync5(profileFile)) {
9739
10295
  try {
9740
- flatConfig = JSON.parse(readFileSync3(profileFile, "utf-8"));
10296
+ flatConfig = JSON.parse(readFileSync4(profileFile, "utf-8"));
9741
10297
  } catch {}
9742
10298
  }
9743
- const profileDirConfig = join4(configDir, "profiles", profile, "config.json");
9744
- if (existsSync4(profileDirConfig)) {
10299
+ const profileDirConfig = join6(configDir, "profiles", profile, "config.json");
10300
+ if (existsSync5(profileDirConfig)) {
9745
10301
  try {
9746
- dirConfig = JSON.parse(readFileSync3(profileDirConfig, "utf-8"));
10302
+ dirConfig = JSON.parse(readFileSync4(profileDirConfig, "utf-8"));
9747
10303
  } catch {}
9748
10304
  }
9749
10305
  if (Object.keys(flatConfig).length === 0 && Object.keys(dirConfig).length === 0) {
@@ -9754,10 +10310,10 @@ function loadProfileConfig(name) {
9754
10310
  function loadTokens(name) {
9755
10311
  const configDir = getConnectorConfigDir(name);
9756
10312
  const profile = getCurrentProfile(name);
9757
- const tokensFile = join4(configDir, "profiles", profile, "tokens.json");
9758
- if (existsSync4(tokensFile)) {
10313
+ const tokensFile = join6(configDir, "profiles", profile, "tokens.json");
10314
+ if (existsSync5(tokensFile)) {
9759
10315
  try {
9760
- return JSON.parse(readFileSync3(tokensFile, "utf-8"));
10316
+ return JSON.parse(readFileSync4(tokensFile, "utf-8"));
9761
10317
  } catch {
9762
10318
  return null;
9763
10319
  }
@@ -9814,43 +10370,43 @@ function _saveApiKey(name, key, field) {
9814
10370
  const profile = getCurrentProfile(name);
9815
10371
  const keyField = field || guessKeyField(name);
9816
10372
  if (keyField === "clientId" || keyField === "clientSecret") {
9817
- const credentialsFile = join4(configDir, "credentials.json");
9818
- mkdirSync3(configDir, { recursive: true });
10373
+ const credentialsFile = join6(configDir, "credentials.json");
10374
+ mkdirSync5(configDir, { recursive: true });
9819
10375
  let creds = {};
9820
- if (existsSync4(credentialsFile)) {
10376
+ if (existsSync5(credentialsFile)) {
9821
10377
  try {
9822
- creds = JSON.parse(readFileSync3(credentialsFile, "utf-8"));
10378
+ creds = JSON.parse(readFileSync4(credentialsFile, "utf-8"));
9823
10379
  } catch {}
9824
10380
  }
9825
10381
  creds[keyField] = key;
9826
- writeFileSync2(credentialsFile, JSON.stringify(creds, null, 2));
10382
+ writeFileSync3(credentialsFile, JSON.stringify(creds, null, 2));
9827
10383
  return;
9828
10384
  }
9829
- const profileFile = join4(configDir, "profiles", `${profile}.json`);
9830
- const profileDir = join4(configDir, "profiles", profile);
9831
- if (existsSync4(profileFile)) {
10385
+ const profileFile = join6(configDir, "profiles", `${profile}.json`);
10386
+ const profileDir = join6(configDir, "profiles", profile);
10387
+ if (existsSync5(profileFile)) {
9832
10388
  let config = {};
9833
10389
  try {
9834
- config = JSON.parse(readFileSync3(profileFile, "utf-8"));
10390
+ config = JSON.parse(readFileSync4(profileFile, "utf-8"));
9835
10391
  } catch {}
9836
10392
  config[keyField] = key;
9837
- writeFileSync2(profileFile, JSON.stringify(config, null, 2));
10393
+ writeFileSync3(profileFile, JSON.stringify(config, null, 2));
9838
10394
  return;
9839
10395
  }
9840
- if (existsSync4(profileDir)) {
9841
- const configFile = join4(profileDir, "config.json");
10396
+ if (existsSync5(profileDir)) {
10397
+ const configFile = join6(profileDir, "config.json");
9842
10398
  let config = {};
9843
- if (existsSync4(configFile)) {
10399
+ if (existsSync5(configFile)) {
9844
10400
  try {
9845
- config = JSON.parse(readFileSync3(configFile, "utf-8"));
10401
+ config = JSON.parse(readFileSync4(configFile, "utf-8"));
9846
10402
  } catch {}
9847
10403
  }
9848
10404
  config[keyField] = key;
9849
- writeFileSync2(configFile, JSON.stringify(config, null, 2));
10405
+ writeFileSync3(configFile, JSON.stringify(config, null, 2));
9850
10406
  return;
9851
10407
  }
9852
- mkdirSync3(profileDir, { recursive: true });
9853
- writeFileSync2(join4(profileDir, "config.json"), JSON.stringify({ [keyField]: key }, null, 2));
10408
+ mkdirSync5(profileDir, { recursive: true });
10409
+ writeFileSync3(join6(profileDir, "config.json"), JSON.stringify({ [keyField]: key }, null, 2));
9854
10410
  }
9855
10411
  function guessKeyField(name) {
9856
10412
  const docs = getConnectorDocs(name);
@@ -9868,10 +10424,10 @@ function guessKeyField(name) {
9868
10424
  }
9869
10425
  function getOAuthConfig(name) {
9870
10426
  const configDir = getConnectorConfigDir(name);
9871
- const credentialsFile = join4(configDir, "credentials.json");
9872
- if (existsSync4(credentialsFile)) {
10427
+ const credentialsFile = join6(configDir, "credentials.json");
10428
+ if (existsSync5(credentialsFile)) {
9873
10429
  try {
9874
- const creds = JSON.parse(readFileSync3(credentialsFile, "utf-8"));
10430
+ const creds = JSON.parse(readFileSync4(credentialsFile, "utf-8"));
9875
10431
  return { clientId: creds.clientId, clientSecret: creds.clientSecret };
9876
10432
  } catch {}
9877
10433
  }
@@ -9950,10 +10506,10 @@ async function exchangeOAuthCode(name, code, redirectUri) {
9950
10506
  function saveOAuthTokens(name, tokens) {
9951
10507
  const configDir = getConnectorConfigDir(name);
9952
10508
  const profile = getCurrentProfile(name);
9953
- const profileDir = join4(configDir, "profiles", profile);
9954
- mkdirSync3(profileDir, { recursive: true });
9955
- const tokensFile = join4(profileDir, "tokens.json");
9956
- writeFileSync2(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
10509
+ const profileDir = join6(configDir, "profiles", profile);
10510
+ mkdirSync5(profileDir, { recursive: true });
10511
+ const tokensFile = join6(profileDir, "tokens.json");
10512
+ writeFileSync3(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
9957
10513
  }
9958
10514
  async function refreshOAuthToken(name) {
9959
10515
  return withWriteLock(name, () => _refreshOAuthToken(name));
@@ -9995,14 +10551,14 @@ async function _refreshOAuthToken(name) {
9995
10551
  }
9996
10552
  function listProfiles(name) {
9997
10553
  const configDir = getConnectorConfigDir(name);
9998
- const profilesDir = join4(configDir, "profiles");
9999
- if (!existsSync4(profilesDir))
10554
+ const profilesDir = join6(configDir, "profiles");
10555
+ if (!existsSync5(profilesDir))
10000
10556
  return ["default"];
10001
10557
  const seen = new Set;
10002
10558
  try {
10003
10559
  const entries = readdirSync2(profilesDir);
10004
10560
  for (const entry of entries) {
10005
- const fullPath = join4(profilesDir, entry);
10561
+ const fullPath = join6(profilesDir, entry);
10006
10562
  const stat = statSync3(fullPath);
10007
10563
  if (stat.isDirectory()) {
10008
10564
  seen.add(entry);
@@ -10016,24 +10572,24 @@ function listProfiles(name) {
10016
10572
  }
10017
10573
  function switchProfile(name, profile) {
10018
10574
  const configDir = getConnectorConfigDir(name);
10019
- mkdirSync3(configDir, { recursive: true });
10020
- writeFileSync2(join4(configDir, "current_profile"), profile);
10575
+ mkdirSync5(configDir, { recursive: true });
10576
+ writeFileSync3(join6(configDir, "current_profile"), profile);
10021
10577
  }
10022
10578
  function deleteProfile(name, profile) {
10023
10579
  if (profile === "default")
10024
10580
  return false;
10025
10581
  const configDir = getConnectorConfigDir(name);
10026
- const profilesDir = join4(configDir, "profiles");
10027
- const profileFile = join4(profilesDir, `${profile}.json`);
10028
- if (existsSync4(profileFile)) {
10582
+ const profilesDir = join6(configDir, "profiles");
10583
+ const profileFile = join6(profilesDir, `${profile}.json`);
10584
+ if (existsSync5(profileFile)) {
10029
10585
  rmSync2(profileFile);
10030
10586
  if (getCurrentProfile(name) === profile) {
10031
10587
  switchProfile(name, "default");
10032
10588
  }
10033
10589
  return true;
10034
10590
  }
10035
- const profileDir = join4(profilesDir, profile);
10036
- if (existsSync4(profileDir)) {
10591
+ const profileDir = join6(profilesDir, profile);
10592
+ if (existsSync5(profileDir)) {
10037
10593
  rmSync2(profileDir, { recursive: true });
10038
10594
  if (getCurrentProfile(name) === profile) {
10039
10595
  switchProfile(name, "default");
@@ -10087,62 +10643,8 @@ var init_auth = __esm(() => {
10087
10643
  };
10088
10644
  });
10089
10645
 
10090
- // src/db/database.ts
10091
- import { Database } from "bun:sqlite";
10092
- import { join as join6 } from "path";
10093
- import { homedir as homedir4 } from "os";
10094
- import { mkdirSync as mkdirSync4 } from "fs";
10095
- function getDatabase(path) {
10096
- if (_db)
10097
- return _db;
10098
- const dbPath = path ?? DB_PATH;
10099
- mkdirSync4(join6(dbPath, ".."), { recursive: true });
10100
- _db = new Database(dbPath);
10101
- _db.run("PRAGMA journal_mode = WAL");
10102
- migrate(_db);
10103
- return _db;
10104
- }
10105
- function now() {
10106
- return new Date().toISOString();
10107
- }
10108
- function migrate(db) {
10109
- db.run(`
10110
- CREATE TABLE IF NOT EXISTS agents (
10111
- id TEXT PRIMARY KEY,
10112
- name TEXT UNIQUE NOT NULL,
10113
- session_id TEXT,
10114
- role TEXT NOT NULL DEFAULT 'agent',
10115
- last_seen_at TEXT NOT NULL,
10116
- created_at TEXT NOT NULL
10117
- )
10118
- `);
10119
- db.run(`
10120
- CREATE TABLE IF NOT EXISTS resource_locks (
10121
- id TEXT PRIMARY KEY,
10122
- resource_type TEXT NOT NULL CHECK(resource_type IN ('connector', 'agent', 'profile', 'token')),
10123
- resource_id TEXT NOT NULL,
10124
- agent_id TEXT NOT NULL,
10125
- lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
10126
- locked_at TEXT NOT NULL DEFAULT (datetime('now')),
10127
- expires_at TEXT NOT NULL
10128
- )
10129
- `);
10130
- db.run(`
10131
- CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
10132
- ON resource_locks(resource_type, resource_id)
10133
- WHERE lock_type = 'exclusive'
10134
- `);
10135
- db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id)`);
10136
- db.run(`CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at)`);
10137
- }
10138
- var DB_DIR, DB_PATH, _db = null;
10139
- var init_database = __esm(() => {
10140
- DB_DIR = join6(homedir4(), ".connectors");
10141
- DB_PATH = join6(DB_DIR, "connectors.db");
10142
- });
10143
-
10144
10646
  // src/db/agents.ts
10145
- function shortUuid() {
10647
+ function shortUuid2() {
10146
10648
  return crypto.randomUUID().slice(0, 8);
10147
10649
  }
10148
10650
  function isAgentConflict(result) {
@@ -10179,7 +10681,7 @@ function registerAgent(input, db) {
10179
10681
  d.run(`UPDATE agents SET ${updates.join(", ")} WHERE id = ?`, params);
10180
10682
  return getAgent(existing.id, d);
10181
10683
  }
10182
- const id = shortUuid();
10684
+ const id = shortUuid2();
10183
10685
  const ts = now();
10184
10686
  d.run(`INSERT INTO agents (id, name, session_id, role, last_seen_at, created_at)
10185
10687
  VALUES (?, ?, ?, ?, ?, ?)`, [id, normalizedName, input.session_id ?? null, input.role ?? "agent", ts, ts]);
@@ -10284,10 +10786,10 @@ var exports_serve = {};
10284
10786
  __export(exports_serve, {
10285
10787
  startServer: () => startServer
10286
10788
  });
10287
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
10288
- import { join as join7, dirname as dirname4, extname, basename } from "path";
10789
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
10790
+ import { join as join8, dirname as dirname4, extname, basename } from "path";
10289
10791
  import { fileURLToPath as fileURLToPath4 } from "url";
10290
- import { homedir as homedir5 } from "os";
10792
+ import { homedir as homedir6 } from "os";
10291
10793
  function logActivity(action, connector, detail) {
10292
10794
  activityLog.unshift({ action, connector, timestamp: Date.now(), detail });
10293
10795
  if (activityLog.length > MAX_ACTIVITY_LOG) {
@@ -10298,20 +10800,20 @@ function resolveDashboardDir() {
10298
10800
  const candidates = [];
10299
10801
  try {
10300
10802
  const scriptDir = dirname4(fileURLToPath4(import.meta.url));
10301
- candidates.push(join7(scriptDir, "..", "dashboard", "dist"));
10302
- candidates.push(join7(scriptDir, "..", "..", "dashboard", "dist"));
10803
+ candidates.push(join8(scriptDir, "..", "dashboard", "dist"));
10804
+ candidates.push(join8(scriptDir, "..", "..", "dashboard", "dist"));
10303
10805
  } catch {}
10304
10806
  if (process.argv[1]) {
10305
10807
  const mainDir = dirname4(process.argv[1]);
10306
- candidates.push(join7(mainDir, "..", "dashboard", "dist"));
10307
- candidates.push(join7(mainDir, "..", "..", "dashboard", "dist"));
10808
+ candidates.push(join8(mainDir, "..", "dashboard", "dist"));
10809
+ candidates.push(join8(mainDir, "..", "..", "dashboard", "dist"));
10308
10810
  }
10309
- candidates.push(join7(process.cwd(), "dashboard", "dist"));
10811
+ candidates.push(join8(process.cwd(), "dashboard", "dist"));
10310
10812
  for (const candidate of candidates) {
10311
- if (existsSync6(candidate))
10813
+ if (existsSync7(candidate))
10312
10814
  return candidate;
10313
10815
  }
10314
- return join7(process.cwd(), "dashboard", "dist");
10816
+ return join8(process.cwd(), "dashboard", "dist");
10315
10817
  }
10316
10818
  function json(data, status = 200, port) {
10317
10819
  return new Response(JSON.stringify(data), {
@@ -10323,6 +10825,18 @@ function json(data, status = 200, port) {
10323
10825
  }
10324
10826
  });
10325
10827
  }
10828
+ async function jsonStripped(data, status = 200, port) {
10829
+ const raw = JSON.stringify(data);
10830
+ const body = await maybeStrip(raw);
10831
+ return new Response(body, {
10832
+ status,
10833
+ headers: {
10834
+ "Content-Type": "application/json",
10835
+ "Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
10836
+ ...SECURITY_HEADERS
10837
+ }
10838
+ });
10839
+ }
10326
10840
  function htmlResponse(content, status = 200) {
10327
10841
  return new Response(content, {
10328
10842
  status,
@@ -10384,7 +10898,7 @@ function oauthPage(type, title, message, hint, extra) {
10384
10898
  </body></html>`;
10385
10899
  }
10386
10900
  function serveStaticFile(filePath) {
10387
- if (!existsSync6(filePath))
10901
+ if (!existsSync7(filePath))
10388
10902
  return null;
10389
10903
  const ext = extname(filePath);
10390
10904
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -10408,7 +10922,7 @@ async function startServer(requestedPort, options) {
10408
10922
  const shouldOpen = options?.open ?? true;
10409
10923
  loadConnectorVersions();
10410
10924
  const dashboardDir = resolveDashboardDir();
10411
- const dashboardExists = existsSync6(dashboardDir);
10925
+ const dashboardExists = existsSync7(dashboardDir);
10412
10926
  if (!dashboardExists) {
10413
10927
  console.error(`
10414
10928
  Dashboard not found at: ${dashboardDir}`);
@@ -10437,10 +10951,10 @@ Dashboard not found at: ${dashboardDir}`);
10437
10951
  const fields = fieldsParam ? new Set(fieldsParam.split(",").map((f) => f.trim())) : null;
10438
10952
  const data = getAllConnectorsWithAuth();
10439
10953
  if (compact) {
10440
- return json(data.map((c) => ({ name: c.name, category: c.category, installed: c.installed })), 200, port);
10954
+ return jsonStripped(data.map((c) => ({ name: c.name, category: c.category, installed: c.installed })), 200, port);
10441
10955
  }
10442
10956
  if (fields) {
10443
- return json(data.map((c) => {
10957
+ return jsonStripped(data.map((c) => {
10444
10958
  const out = {};
10445
10959
  for (const f of fields) {
10446
10960
  if (f in c)
@@ -10449,7 +10963,7 @@ Dashboard not found at: ${dashboardDir}`);
10449
10963
  return out;
10450
10964
  }), 200, port);
10451
10965
  }
10452
- return json(data, 200, port);
10966
+ return jsonStripped(data, 200, port);
10453
10967
  }
10454
10968
  const singleMatch = path.match(/^\/api\/connectors\/([^/]+)$/);
10455
10969
  if (singleMatch && method === "GET") {
@@ -10557,6 +11071,110 @@ Dashboard not found at: ${dashboardDir}`);
10557
11071
  if (path === "/api/activity" && method === "GET") {
10558
11072
  return json(activityLog, 200, port);
10559
11073
  }
11074
+ if (path === "/api/llm" && method === "GET") {
11075
+ const config = getLlmConfig();
11076
+ if (!config)
11077
+ return json({ configured: false }, 200, port);
11078
+ return json({ configured: true, provider: config.provider, model: config.model, key: maskKey(config.api_key), strip: config.strip }, 200, port);
11079
+ }
11080
+ if (path === "/api/llm" && method === "POST") {
11081
+ const body = await req.json().catch(() => ({}));
11082
+ const validProviders = ["cerebras", "groq", "openai", "anthropic"];
11083
+ const provider = body.provider;
11084
+ if (!provider || !validProviders.includes(provider))
11085
+ return json({ error: "provider must be one of: " + validProviders.join(", ") }, 400, port);
11086
+ const api_key = body.api_key;
11087
+ if (!api_key)
11088
+ return json({ error: "api_key is required" }, 400, port);
11089
+ const model = body.model || getLlmConfig()?.model || "qwen-3-32b";
11090
+ const strip = typeof body.strip === "boolean" ? body.strip : getLlmConfig()?.strip ?? false;
11091
+ saveLlmConfig({ provider, model, api_key, strip });
11092
+ return json({ success: true, provider, model, strip }, 200, port);
11093
+ }
11094
+ if (path === "/api/llm/test" && method === "POST") {
11095
+ const config = getLlmConfig();
11096
+ if (!config)
11097
+ return json({ error: "No LLM configured" }, 400, port);
11098
+ try {
11099
+ const client = new LLMClient(config);
11100
+ const result = await client.complete('Respond with exactly: {"status":"ok"}', "ping");
11101
+ return json({ success: true, provider: result.provider, model: result.model, latency_ms: result.latency_ms, response: result.content }, 200, port);
11102
+ } catch (e) {
11103
+ return json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500, port);
11104
+ }
11105
+ }
11106
+ if (path === "/api/jobs" && method === "GET") {
11107
+ return json(listJobs(getDatabase2()), 200, port);
11108
+ }
11109
+ if (path === "/api/jobs" && method === "POST") {
11110
+ const body = await req.json().catch(() => ({}));
11111
+ if (!body.name || !body.connector || !body.command || !body.cron)
11112
+ return json({ error: "name, connector, command, cron required" }, 400, port);
11113
+ const job = createJob({ name: body.name, connector: body.connector, command: body.command, args: body.args ?? [], cron: body.cron, strip: !!body.strip }, getDatabase2());
11114
+ return json(job, 201, port);
11115
+ }
11116
+ const jobMatch = path.match(/^\/api\/jobs\/([^/]+)$/);
11117
+ if (jobMatch) {
11118
+ const db = getDatabase2();
11119
+ const job = getJobByName(jobMatch[1]) ?? getDatabase2().query("SELECT * FROM connector_jobs WHERE id = ?").get(jobMatch[1]);
11120
+ if (!job && method !== "DELETE")
11121
+ return json({ error: "Job not found" }, 404, port);
11122
+ if (method === "GET")
11123
+ return json(listJobRuns(job.id, 20, db), 200, port);
11124
+ if (method === "DELETE") {
11125
+ const j = getJobByName(jobMatch[1], db);
11126
+ if (!j)
11127
+ return json({ error: "Job not found" }, 404, port);
11128
+ deleteJob(j.id, db);
11129
+ return json({ success: true }, 200, port);
11130
+ }
11131
+ if (method === "PATCH") {
11132
+ const body = await req.json().catch(() => ({}));
11133
+ const j = getJobByName(jobMatch[1], db);
11134
+ const updated = updateJob(j.id, { enabled: typeof body.enabled === "boolean" ? body.enabled : undefined, strip: typeof body.strip === "boolean" ? body.strip : undefined }, db);
11135
+ return json(updated, 200, port);
11136
+ }
11137
+ }
11138
+ const jobRunMatch = path.match(/^\/api\/jobs\/([^/]+)\/run$/);
11139
+ if (jobRunMatch && method === "POST") {
11140
+ const db = getDatabase2();
11141
+ const job = getJobByName(jobRunMatch[1], db);
11142
+ if (!job)
11143
+ return json({ error: "Job not found" }, 404, port);
11144
+ const result = await triggerJob(job, db);
11145
+ return json(result, 200, port);
11146
+ }
11147
+ if (path === "/api/workflows" && method === "GET") {
11148
+ return json(listWorkflows(getDatabase2()), 200, port);
11149
+ }
11150
+ if (path === "/api/workflows" && method === "POST") {
11151
+ const body = await req.json().catch(() => ({}));
11152
+ if (!body.name || !body.steps)
11153
+ return json({ error: "name and steps required" }, 400, port);
11154
+ const wf = createWorkflow({ name: body.name, steps: body.steps }, getDatabase2());
11155
+ return json(wf, 201, port);
11156
+ }
11157
+ const wfMatch = path.match(/^\/api\/workflows\/([^/]+)$/);
11158
+ if (wfMatch) {
11159
+ const db = getDatabase2();
11160
+ const wf = getWorkflowByName(wfMatch[1], db);
11161
+ if (!wf)
11162
+ return json({ error: "Workflow not found" }, 404, port);
11163
+ if (method === "GET")
11164
+ return json(wf, 200, port);
11165
+ if (method === "DELETE") {
11166
+ deleteWorkflow(wf.id, db);
11167
+ return json({ success: true }, 200, port);
11168
+ }
11169
+ }
11170
+ const wfRunMatch = path.match(/^\/api\/workflows\/([^/]+)\/run$/);
11171
+ if (wfRunMatch && method === "POST") {
11172
+ const wf = getWorkflowByName(wfRunMatch[1], getDatabase2());
11173
+ if (!wf)
11174
+ return json({ error: "Workflow not found" }, 404, port);
11175
+ const result = await runWorkflow(wf);
11176
+ return json(result, 200, port);
11177
+ }
10560
11178
  if (path === "/api/agents" && method === "GET") {
10561
11179
  return json(listAgents(), 200, port);
10562
11180
  }
@@ -10597,12 +11215,12 @@ Dashboard not found at: ${dashboardDir}`);
10597
11215
  return json({ error: "Invalid connector name" }, 400, port);
10598
11216
  try {
10599
11217
  const profiles = listProfiles(name);
10600
- const configDir = join7(homedir5(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
10601
- const currentProfileFile = join7(configDir, "current_profile");
11218
+ const configDir = join8(homedir6(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
11219
+ const currentProfileFile = join8(configDir, "current_profile");
10602
11220
  let current = "default";
10603
- if (existsSync6(currentProfileFile)) {
11221
+ if (existsSync7(currentProfileFile)) {
10604
11222
  try {
10605
- current = readFileSync4(currentProfileFile, "utf-8").trim() || "default";
11223
+ current = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
10606
11224
  } catch {}
10607
11225
  }
10608
11226
  return json({ current, profiles }, 200, port);
@@ -10649,16 +11267,16 @@ Dashboard not found at: ${dashboardDir}`);
10649
11267
  }
10650
11268
  if (path === "/api/export" && method === "GET") {
10651
11269
  try {
10652
- const connectDir = join7(homedir5(), ".connectors");
11270
+ const connectDir = join8(homedir6(), ".connectors");
10653
11271
  const result = {};
10654
- if (existsSync6(connectDir)) {
11272
+ if (existsSync7(connectDir)) {
10655
11273
  const entries = readdirSync3(connectDir, { withFileTypes: true });
10656
11274
  for (const entry of entries) {
10657
11275
  if (!entry.isDirectory() || !entry.name.startsWith("connect-"))
10658
11276
  continue;
10659
11277
  const connectorName = entry.name.replace(/^connect-/, "");
10660
- const profilesDir = join7(connectDir, entry.name, "profiles");
10661
- if (!existsSync6(profilesDir))
11278
+ const profilesDir = join8(connectDir, entry.name, "profiles");
11279
+ if (!existsSync7(profilesDir))
10662
11280
  continue;
10663
11281
  const profiles = {};
10664
11282
  const profileEntries = readdirSync3(profilesDir, { withFileTypes: true });
@@ -10666,15 +11284,15 @@ Dashboard not found at: ${dashboardDir}`);
10666
11284
  if (pEntry.isFile() && pEntry.name.endsWith(".json")) {
10667
11285
  const profileName = basename(pEntry.name, ".json");
10668
11286
  try {
10669
- const config = JSON.parse(readFileSync4(join7(profilesDir, pEntry.name), "utf-8"));
11287
+ const config = JSON.parse(readFileSync5(join8(profilesDir, pEntry.name), "utf-8"));
10670
11288
  profiles[profileName] = config;
10671
11289
  } catch {}
10672
11290
  }
10673
11291
  if (pEntry.isDirectory()) {
10674
- const configPath = join7(profilesDir, pEntry.name, "config.json");
10675
- if (existsSync6(configPath)) {
11292
+ const configPath = join8(profilesDir, pEntry.name, "config.json");
11293
+ if (existsSync7(configPath)) {
10676
11294
  try {
10677
- const config = JSON.parse(readFileSync4(configPath, "utf-8"));
11295
+ const config = JSON.parse(readFileSync5(configPath, "utf-8"));
10678
11296
  profiles[pEntry.name] = config;
10679
11297
  } catch {}
10680
11298
  }
@@ -10709,20 +11327,20 @@ Dashboard not found at: ${dashboardDir}`);
10709
11327
  return json({ error: "Invalid import format: missing 'connectors' object" }, 400, port);
10710
11328
  }
10711
11329
  let imported = 0;
10712
- const connectDir = join7(homedir5(), ".connectors");
11330
+ const connectDir = join8(homedir6(), ".connectors");
10713
11331
  for (const [connectorName, data] of Object.entries(body.connectors)) {
10714
11332
  if (!isValidConnectorName(connectorName))
10715
11333
  continue;
10716
11334
  if (!data.profiles || typeof data.profiles !== "object")
10717
11335
  continue;
10718
- const connectorDir = join7(connectDir, `connect-${connectorName}`);
10719
- const profilesDir = join7(connectorDir, "profiles");
11336
+ const connectorDir = join8(connectDir, `connect-${connectorName}`);
11337
+ const profilesDir = join8(connectorDir, "profiles");
10720
11338
  for (const [profileName, config] of Object.entries(data.profiles)) {
10721
11339
  if (!config || typeof config !== "object")
10722
11340
  continue;
10723
- mkdirSync5(profilesDir, { recursive: true });
10724
- const profileFile = join7(profilesDir, `${profileName}.json`);
10725
- writeFileSync3(profileFile, JSON.stringify(config, null, 2));
11341
+ mkdirSync6(profilesDir, { recursive: true });
11342
+ const profileFile = join8(profilesDir, `${profileName}.json`);
11343
+ writeFileSync4(profileFile, JSON.stringify(config, null, 2));
10726
11344
  imported++;
10727
11345
  }
10728
11346
  }
@@ -10777,12 +11395,12 @@ Dashboard not found at: ${dashboardDir}`);
10777
11395
  }
10778
11396
  if (dashboardExists && (method === "GET" || method === "HEAD")) {
10779
11397
  if (path !== "/") {
10780
- const filePath = join7(dashboardDir, path);
11398
+ const filePath = join8(dashboardDir, path);
10781
11399
  const res2 = serveStaticFile(filePath);
10782
11400
  if (res2)
10783
11401
  return res2;
10784
11402
  }
10785
- const indexPath = join7(dashboardDir, "index.html");
11403
+ const indexPath = join8(dashboardDir, "index.html");
10786
11404
  const res = serveStaticFile(indexPath);
10787
11405
  if (res)
10788
11406
  return res;
@@ -10796,6 +11414,9 @@ Dashboard not found at: ${dashboardDir}`);
10796
11414
  };
10797
11415
  process.on("SIGINT", shutdown);
10798
11416
  process.on("SIGTERM", shutdown);
11417
+ const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), exports_scheduler));
11418
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
11419
+ startScheduler2(getDatabase2());
10799
11420
  const url = `http://localhost:${port}`;
10800
11421
  console.log(`Connectors Dashboard running at ${url}`);
10801
11422
  if (shouldOpen) {
@@ -10810,6 +11431,12 @@ var activityLog, MAX_ACTIVITY_LOG = 100, MIME_TYPES, SECURITY_HEADERS, MAX_BODY_
10810
11431
  var init_serve = __esm(() => {
10811
11432
  init_agents();
10812
11433
  init_rate();
11434
+ init_strip();
11435
+ init_llm();
11436
+ init_jobs();
11437
+ init_workflows();
11438
+ init_scheduler();
11439
+ init_workflow_runner();
10813
11440
  init_registry();
10814
11441
  init_installer();
10815
11442
  init_auth();
@@ -10854,7 +11481,101 @@ var {
10854
11481
 
10855
11482
  // src/cli/index.tsx
10856
11483
  import chalk2 from "chalk";
10857
- import { createRequire } from "module";
11484
+ // package.json
11485
+ var package_default = {
11486
+ name: "@hasna/connectors",
11487
+ version: "1.1.18",
11488
+ description: "Open source connector library - Install API connectors with a single command",
11489
+ type: "module",
11490
+ bin: {
11491
+ connectors: "./bin/index.js",
11492
+ "connectors-mcp": "./bin/mcp.js",
11493
+ "connectors-serve": "./bin/serve.js"
11494
+ },
11495
+ files: [
11496
+ "bin/",
11497
+ "dist/",
11498
+ "dashboard/dist/",
11499
+ "connectors/",
11500
+ "README.md"
11501
+ ],
11502
+ exports: {
11503
+ ".": {
11504
+ import: "./dist/index.js",
11505
+ types: "./dist/index.d.ts"
11506
+ }
11507
+ },
11508
+ main: "./dist/index.js",
11509
+ types: "./dist/index.d.ts",
11510
+ scripts: {
11511
+ build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --outDir ./dist",
11512
+ "build:dashboard": "cd dashboard && bun run build",
11513
+ postinstall: '[ "$SKIP_DASHBOARD" = "1" ] || [ ! -f dashboard/package.json ] || [ -d dashboard/node_modules ] || (cd dashboard && bun install)',
11514
+ dev: "bun run ./src/cli/index.tsx",
11515
+ typecheck: "tsc --noEmit",
11516
+ test: "bun test",
11517
+ "sdk:build": "cd sdk && bun run build",
11518
+ prepublishOnly: "bun test && bun run build"
11519
+ },
11520
+ keywords: [
11521
+ "connectors",
11522
+ "api",
11523
+ "cli",
11524
+ "typescript",
11525
+ "bun",
11526
+ "figma",
11527
+ "stripe",
11528
+ "github",
11529
+ "openai",
11530
+ "mcp",
11531
+ "model-context-protocol"
11532
+ ],
11533
+ author: "Hasna",
11534
+ license: "Apache-2.0",
11535
+ devDependencies: {
11536
+ "@types/bun": "latest",
11537
+ "@types/react": "^18.2.0",
11538
+ "ink-testing-library": "^4.0.0",
11539
+ typescript: "^5"
11540
+ },
11541
+ dependencies: {
11542
+ "@modelcontextprotocol/sdk": "^1.26.0",
11543
+ chalk: "^5.3.0",
11544
+ commander: "^12.1.0",
11545
+ conf: "^13.0.1",
11546
+ "fast-xml-parser": "^5.5.3",
11547
+ ink: "^5.0.1",
11548
+ "ink-select-input": "^6.0.0",
11549
+ "ink-spinner": "^5.0.0",
11550
+ "ink-text-input": "^6.0.0",
11551
+ open: "^11.0.0",
11552
+ react: "^18.2.0",
11553
+ zod: "3"
11554
+ },
11555
+ engines: {
11556
+ bun: ">=1.0.0"
11557
+ },
11558
+ publishConfig: {
11559
+ registry: "https://registry.npmjs.org",
11560
+ access: "public"
11561
+ },
11562
+ repository: {
11563
+ type: "git",
11564
+ url: "git+https://github.com/hasna/connectors.git"
11565
+ },
11566
+ homepage: "https://github.com/hasna/connectors#readme",
11567
+ bugs: {
11568
+ url: "https://github.com/hasna/connectors/issues"
11569
+ }
11570
+ };
11571
+
11572
+ // src/cli/index.tsx
11573
+ init_llm();
11574
+ init_jobs();
11575
+ init_workflows();
11576
+ init_scheduler();
11577
+ init_workflow_runner();
11578
+ init_database();
10858
11579
 
10859
11580
  // src/cli/components/App.tsx
10860
11581
  import { useState as useState7 } from "react";
@@ -12328,9 +13049,9 @@ function App({ initialConnectors, overwrite = false }) {
12328
13049
  init_registry();
12329
13050
  init_installer();
12330
13051
  init_auth();
12331
- import { readdirSync as readdirSync4, existsSync as existsSync7, statSync as statSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
12332
- import { homedir as homedir6 } from "os";
12333
- import { join as join8, relative } from "path";
13052
+ import { readdirSync as readdirSync4, existsSync as existsSync8, statSync as statSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
13053
+ import { homedir as homedir7 } from "os";
13054
+ import { join as join9, relative } from "path";
12334
13055
 
12335
13056
  // src/lib/test-endpoints.ts
12336
13057
  var TEST_ENDPOINTS = {
@@ -12531,17 +13252,17 @@ var TEST_ENDPOINTS = {
12531
13252
  import { createInterface } from "readline";
12532
13253
 
12533
13254
  // src/lib/runner.ts
12534
- import { existsSync as existsSync5 } from "fs";
12535
- import { join as join5, dirname as dirname3 } from "path";
13255
+ import { existsSync as existsSync6 } from "fs";
13256
+ import { join as join7, dirname as dirname3 } from "path";
12536
13257
  import { fileURLToPath as fileURLToPath3 } from "url";
12537
- import { spawn } from "child_process";
13258
+ import { spawn as spawn3 } from "child_process";
12538
13259
  var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
12539
13260
  function resolveConnectorsDir2() {
12540
- const fromBin = join5(__dirname3, "..", "connectors");
12541
- if (existsSync5(fromBin))
13261
+ const fromBin = join7(__dirname3, "..", "connectors");
13262
+ if (existsSync6(fromBin))
12542
13263
  return fromBin;
12543
- const fromSrc = join5(__dirname3, "..", "..", "connectors");
12544
- if (existsSync5(fromSrc))
13264
+ const fromSrc = join7(__dirname3, "..", "..", "connectors");
13265
+ if (existsSync6(fromSrc))
12545
13266
  return fromSrc;
12546
13267
  return fromBin;
12547
13268
  }
@@ -12581,13 +13302,13 @@ function buildEnvWithCredentials(connectorName, baseEnv) {
12581
13302
  }
12582
13303
  function getConnectorCliPath(name) {
12583
13304
  const safeName = name.replace(/[^a-z0-9-]/g, "");
12584
- const connectorDir = join5(CONNECTORS_DIR2, `connect-${safeName}`);
12585
- const cliPath = join5(connectorDir, "src", "cli", "index.ts");
12586
- if (existsSync5(cliPath))
13305
+ const connectorDir = join7(CONNECTORS_DIR2, `connect-${safeName}`);
13306
+ const cliPath = join7(connectorDir, "src", "cli", "index.ts");
13307
+ if (existsSync6(cliPath))
12587
13308
  return cliPath;
12588
13309
  return null;
12589
13310
  }
12590
- function runConnectorCommand(name, args, timeoutMs = 30000) {
13311
+ function runConnectorCommand2(name, args, timeoutMs = 30000) {
12591
13312
  const cliPath = getConnectorCliPath(name);
12592
13313
  if (!cliPath) {
12593
13314
  return Promise.resolve({
@@ -12598,7 +13319,7 @@ function runConnectorCommand(name, args, timeoutMs = 30000) {
12598
13319
  });
12599
13320
  }
12600
13321
  return new Promise((resolve) => {
12601
- const proc = spawn("bun", ["run", cliPath, ...args], {
13322
+ const proc = spawn3("bun", ["run", cliPath, ...args], {
12602
13323
  timeout: timeoutMs,
12603
13324
  env: buildEnvWithCredentials(name, process.env),
12604
13325
  stdio: ["pipe", "pipe", "pipe"]
@@ -12634,7 +13355,7 @@ async function getConnectorOperations(name) {
12634
13355
  if (!cliPath) {
12635
13356
  return { commands: [], helpText: "", hasCli: false };
12636
13357
  }
12637
- const result = await runConnectorCommand(name, ["--help"]);
13358
+ const result = await runConnectorCommand2(name, ["--help"]);
12638
13359
  const helpText = result.stdout || result.stderr;
12639
13360
  const commands = [];
12640
13361
  const lines = helpText.split(`
@@ -12658,14 +13379,12 @@ async function getConnectorOperations(name) {
12658
13379
  return { commands, helpText, hasCli: true };
12659
13380
  }
12660
13381
  async function getConnectorCommandHelp(name, command) {
12661
- const result = await runConnectorCommand(name, [command, "--help"]);
13382
+ const result = await runConnectorCommand2(name, [command, "--help"]);
12662
13383
  return result.stdout || result.stderr;
12663
13384
  }
12664
13385
 
12665
13386
  // src/cli/index.tsx
12666
13387
  import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
12667
- var _require = createRequire(import.meta.url);
12668
- var _pkg = _require("../package.json");
12669
13388
  loadConnectorVersions();
12670
13389
  var isTTY = process.stdout.isTTY ?? false;
12671
13390
  var PRESETS = {
@@ -12677,7 +13396,7 @@ var PRESETS = {
12677
13396
  commerce: { description: "Commerce and finance", connectors: ["stripe", "shopify", "revolut", "mercury", "pandadoc"] }
12678
13397
  };
12679
13398
  var program2 = new Command;
12680
- program2.name("connectors").description("Install API connectors for your project").version(_pkg.version).enablePositionalOptions();
13399
+ program2.name("connectors").description("Install API connectors for your project").version(package_default.version).enablePositionalOptions();
12681
13400
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
12682
13401
  if (!isTTY) {
12683
13402
  console.log(`Non-interactive environment detected. Use a subcommand:
@@ -12698,7 +13417,7 @@ Run 'connectors --help' for full usage.`);
12698
13417
  function listFilesRecursive(dir, base = dir) {
12699
13418
  const files = [];
12700
13419
  for (const entry of readdirSync4(dir)) {
12701
- const fullPath = join8(dir, entry);
13420
+ const fullPath = join9(dir, entry);
12702
13421
  if (statSync4(fullPath).isDirectory()) {
12703
13422
  files.push(...listFilesRecursive(fullPath, base));
12704
13423
  } else {
@@ -12747,7 +13466,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
12747
13466
  }
12748
13467
  if (options.dryRun) {
12749
13468
  const installed = getInstalledConnectors();
12750
- const destDir = join8(process.cwd(), ".connectors");
13469
+ const destDir = join9(process.cwd(), ".connectors");
12751
13470
  const actions = [];
12752
13471
  for (const name of connectors) {
12753
13472
  if (!/^[a-z0-9-]+$/.test(name)) {
@@ -12765,7 +13484,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
12765
13484
  }
12766
13485
  const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
12767
13486
  const sourcePath = getConnectorPath(name);
12768
- const destPath = join8(destDir, connectorDirName);
13487
+ const destPath = join9(destDir, connectorDirName);
12769
13488
  const alreadyInstalled = installed.includes(name);
12770
13489
  const files = listFilesRecursive(sourcePath);
12771
13490
  const importLine = `export * as ${name} from './${connectorDirName}/src/index.js';`;
@@ -13269,17 +13988,17 @@ Updating ${toUpdate.length} connector(s)...
13269
13988
  });
13270
13989
  program2.command("status").option("--json", "Output as JSON", false).description("Show auth status of all configured connectors (project + global)").action((options) => {
13271
13990
  const installed = getInstalledConnectors();
13272
- const configDir = join8(homedir6(), ".connectors");
13991
+ const configDir = join9(homedir7(), ".connectors");
13273
13992
  const seen = new Set;
13274
13993
  const allStatuses = [];
13275
13994
  function buildStatusEntry(name, source) {
13276
13995
  const auth = getAuthStatus(name);
13277
13996
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
13278
- const currentProfileFile = join8(configDir, connectorName, "current_profile");
13997
+ const currentProfileFile = join9(configDir, connectorName, "current_profile");
13279
13998
  let profile = "default";
13280
- if (existsSync7(currentProfileFile)) {
13999
+ if (existsSync8(currentProfileFile)) {
13281
14000
  try {
13282
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
14001
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
13283
14002
  } catch {}
13284
14003
  }
13285
14004
  let expiryLabel = null;
@@ -13315,13 +14034,13 @@ program2.command("status").option("--json", "Output as JSON", false).description
13315
14034
  seen.add(name);
13316
14035
  allStatuses.push(buildStatusEntry(name, "project"));
13317
14036
  }
13318
- if (existsSync7(configDir)) {
14037
+ if (existsSync8(configDir)) {
13319
14038
  try {
13320
14039
  const globalDirs = readdirSync4(configDir).filter((f) => {
13321
14040
  if (!f.startsWith("connect-"))
13322
14041
  return false;
13323
14042
  try {
13324
- return statSync4(join8(configDir, f)).isDirectory();
14043
+ return statSync4(join9(configDir, f)).isDirectory();
13325
14044
  } catch {
13326
14045
  return false;
13327
14046
  }
@@ -13686,15 +14405,15 @@ program2.command("init").option("--json", "Output presets and suggestions as JSO
13686
14405
  { key: "commerce", emoji: "\uD83D\uDCB3", label: "Commerce", connectors: ["stripe", "shopify", "paypal", "revolut", "mercury"], description: "Commerce and finance" },
13687
14406
  { key: "google", emoji: "\uD83D\uDCC1", label: "Google Workspace", connectors: ["gmail", "googledrive", "googlecalendar", "googledocs", "googlesheets"], description: "Google Workspace suite" }
13688
14407
  ];
13689
- const connectorsHome = join8(homedir6(), ".connectors");
14408
+ const connectorsHome = join9(homedir7(), ".connectors");
13690
14409
  let configuredCount = 0;
13691
14410
  const configuredNames = [];
13692
14411
  try {
13693
- if (existsSync7(connectorsHome)) {
13694
- const entries = readdirSync4(connectorsHome).filter((e) => e.startsWith("connect-") && statSync4(join8(connectorsHome, e)).isDirectory());
14412
+ if (existsSync8(connectorsHome)) {
14413
+ const entries = readdirSync4(connectorsHome).filter((e) => e.startsWith("connect-") && statSync4(join9(connectorsHome, e)).isDirectory());
13695
14414
  for (const entry of entries) {
13696
- const profilesDir = join8(connectorsHome, entry, "profiles");
13697
- if (existsSync7(profilesDir)) {
14415
+ const profilesDir = join9(connectorsHome, entry, "profiles");
14416
+ if (existsSync8(profilesDir)) {
13698
14417
  configuredCount++;
13699
14418
  configuredNames.push(entry.replace(/^connect-/, ""));
13700
14419
  }
@@ -13792,44 +14511,44 @@ function redactSecrets(obj) {
13792
14511
  return obj;
13793
14512
  }
13794
14513
  program2.command("export").option("-o, --output <file>", "Write to file instead of stdout").option("--include-secrets", "Include secrets in plaintext (dangerous \u2014 use only for backup/restore)").description("Export all connector credentials as JSON backup").action((options) => {
13795
- const connectDir = join8(homedir6(), ".connectors");
14514
+ const connectDir = join9(homedir7(), ".connectors");
13796
14515
  const result = {};
13797
- if (existsSync7(connectDir)) {
14516
+ if (existsSync8(connectDir)) {
13798
14517
  for (const entry of readdirSync4(connectDir)) {
13799
- const entryPath = join8(connectDir, entry);
14518
+ const entryPath = join9(connectDir, entry);
13800
14519
  if (!statSync4(entryPath).isDirectory() || !entry.startsWith("connect-"))
13801
14520
  continue;
13802
14521
  const connectorName = entry.replace(/^connect-/, "");
13803
14522
  let credentials = undefined;
13804
- const credentialsPath = join8(entryPath, "credentials.json");
13805
- if (existsSync7(credentialsPath)) {
14523
+ const credentialsPath = join9(entryPath, "credentials.json");
14524
+ if (existsSync8(credentialsPath)) {
13806
14525
  try {
13807
- credentials = JSON.parse(readFileSync5(credentialsPath, "utf-8"));
14526
+ credentials = JSON.parse(readFileSync6(credentialsPath, "utf-8"));
13808
14527
  } catch {}
13809
14528
  }
13810
- const profilesDir = join8(entryPath, "profiles");
13811
- if (!existsSync7(profilesDir) && !credentials)
14529
+ const profilesDir = join9(entryPath, "profiles");
14530
+ if (!existsSync8(profilesDir) && !credentials)
13812
14531
  continue;
13813
14532
  const profiles = {};
13814
- if (existsSync7(profilesDir)) {
14533
+ if (existsSync8(profilesDir)) {
13815
14534
  for (const pEntry of readdirSync4(profilesDir)) {
13816
- const pPath = join8(profilesDir, pEntry);
14535
+ const pPath = join9(profilesDir, pEntry);
13817
14536
  if (statSync4(pPath).isFile() && pEntry.endsWith(".json")) {
13818
14537
  try {
13819
- profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
14538
+ profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync6(pPath, "utf-8"));
13820
14539
  } catch {}
13821
14540
  } else if (statSync4(pPath).isDirectory()) {
13822
- const configPath = join8(pPath, "config.json");
13823
- const tokensPath = join8(pPath, "tokens.json");
14541
+ const configPath = join9(pPath, "config.json");
14542
+ const tokensPath = join9(pPath, "tokens.json");
13824
14543
  let merged = {};
13825
- if (existsSync7(configPath)) {
14544
+ if (existsSync8(configPath)) {
13826
14545
  try {
13827
- merged = { ...merged, ...JSON.parse(readFileSync5(configPath, "utf-8")) };
14546
+ merged = { ...merged, ...JSON.parse(readFileSync6(configPath, "utf-8")) };
13828
14547
  } catch {}
13829
14548
  }
13830
- if (existsSync7(tokensPath)) {
14549
+ if (existsSync8(tokensPath)) {
13831
14550
  try {
13832
- merged = { ...merged, ...JSON.parse(readFileSync5(tokensPath, "utf-8")) };
14551
+ merged = { ...merged, ...JSON.parse(readFileSync6(tokensPath, "utf-8")) };
13833
14552
  } catch {}
13834
14553
  }
13835
14554
  if (Object.keys(merged).length > 0)
@@ -13850,7 +14569,7 @@ program2.command("export").option("-o, --output <file>", "Write to file instead
13850
14569
  }
13851
14570
  const exportData = JSON.stringify(exportPayload, null, 2);
13852
14571
  if (options.output) {
13853
- writeFileSync4(options.output, exportData);
14572
+ writeFileSync5(options.output, exportData);
13854
14573
  console.log(chalk2.green(`\u2713 Exported to ${options.output}`));
13855
14574
  } else {
13856
14575
  console.log(exportData);
@@ -13864,7 +14583,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13864
14583
  chunks.push(chunk.toString());
13865
14584
  raw = chunks.join("");
13866
14585
  } else {
13867
- if (!existsSync7(file)) {
14586
+ if (!existsSync8(file)) {
13868
14587
  if (options.json) {
13869
14588
  console.log(JSON.stringify({ error: `File not found: ${file}` }));
13870
14589
  } else {
@@ -13873,7 +14592,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13873
14592
  process.exit(1);
13874
14593
  return;
13875
14594
  }
13876
- raw = readFileSync5(file, "utf-8");
14595
+ raw = readFileSync6(file, "utf-8");
13877
14596
  }
13878
14597
  let data;
13879
14598
  try {
@@ -13896,25 +14615,25 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13896
14615
  process.exit(1);
13897
14616
  return;
13898
14617
  }
13899
- const connectDir = join8(homedir6(), ".connectors");
14618
+ const connectDir = join9(homedir7(), ".connectors");
13900
14619
  let imported = 0;
13901
14620
  for (const [connectorName, connData] of Object.entries(data.connectors)) {
13902
14621
  if (!/^[a-z0-9-]+$/.test(connectorName))
13903
14622
  continue;
13904
- const connectorDir = join8(connectDir, `connect-${connectorName}`);
14623
+ const connectorDir = join9(connectDir, `connect-${connectorName}`);
13905
14624
  if (connData.credentials && typeof connData.credentials === "object") {
13906
- mkdirSync6(connectorDir, { recursive: true });
13907
- writeFileSync4(join8(connectorDir, "credentials.json"), JSON.stringify(connData.credentials, null, 2));
14625
+ mkdirSync7(connectorDir, { recursive: true });
14626
+ writeFileSync5(join9(connectorDir, "credentials.json"), JSON.stringify(connData.credentials, null, 2));
13908
14627
  imported++;
13909
14628
  }
13910
14629
  if (!connData.profiles || typeof connData.profiles !== "object")
13911
14630
  continue;
13912
- const profilesDir = join8(connectorDir, "profiles");
14631
+ const profilesDir = join9(connectorDir, "profiles");
13913
14632
  for (const [profileName, config] of Object.entries(connData.profiles)) {
13914
14633
  if (!config || typeof config !== "object")
13915
14634
  continue;
13916
- mkdirSync6(profilesDir, { recursive: true });
13917
- writeFileSync4(join8(profilesDir, `${profileName}.json`), JSON.stringify(config, null, 2));
14635
+ mkdirSync7(profilesDir, { recursive: true });
14636
+ writeFileSync5(join9(profilesDir, `${profileName}.json`), JSON.stringify(config, null, 2));
13918
14637
  imported++;
13919
14638
  }
13920
14639
  }
@@ -13925,9 +14644,9 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13925
14644
  }
13926
14645
  });
13927
14646
  program2.command("auth-import").option("--json", "Output as JSON", false).option("-d, --dry-run", "Preview what would be imported without copying", false).option("--force", "Overwrite existing files in ~/.connectors/", false).description("Migrate auth tokens from ~/.connect/ to ~/.connectors/").action((options) => {
13928
- const oldBase = join8(homedir6(), ".connect");
13929
- const newBase = join8(homedir6(), ".connectors");
13930
- if (!existsSync7(oldBase)) {
14647
+ const oldBase = join9(homedir7(), ".connect");
14648
+ const newBase = join9(homedir7(), ".connectors");
14649
+ if (!existsSync8(oldBase)) {
13931
14650
  if (options.json) {
13932
14651
  console.log(JSON.stringify({ imported: [], skipped: [], error: null, message: "No ~/.connect/ directory found" }));
13933
14652
  } else {
@@ -13939,7 +14658,7 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
13939
14658
  if (!name.startsWith("connect-"))
13940
14659
  return false;
13941
14660
  try {
13942
- return statSync4(join8(oldBase, name)).isDirectory();
14661
+ return statSync4(join9(oldBase, name)).isDirectory();
13943
14662
  } catch {
13944
14663
  return false;
13945
14664
  }
@@ -13955,8 +14674,8 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
13955
14674
  const imported = [];
13956
14675
  const skipped = [];
13957
14676
  for (const dirName of entries) {
13958
- const oldDir = join8(oldBase, dirName);
13959
- const newDir = join8(newBase, dirName);
14677
+ const oldDir = join9(oldBase, dirName);
14678
+ const newDir = join9(newBase, dirName);
13960
14679
  const connectorName = dirName.replace(/^connect-/, "");
13961
14680
  const allFiles = listFilesRecursive(oldDir);
13962
14681
  const authFiles = allFiles.filter((f) => {
@@ -13967,17 +14686,17 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
13967
14686
  const copiedFiles = [];
13968
14687
  const skippedFiles = [];
13969
14688
  for (const relFile of authFiles) {
13970
- const srcPath = join8(oldDir, relFile);
13971
- const destPath = join8(newDir, relFile);
13972
- if (existsSync7(destPath) && !options.force) {
14689
+ const srcPath = join9(oldDir, relFile);
14690
+ const destPath = join9(newDir, relFile);
14691
+ if (existsSync8(destPath) && !options.force) {
13973
14692
  skippedFiles.push(relFile);
13974
14693
  continue;
13975
14694
  }
13976
14695
  if (!options.dryRun) {
13977
- const parentDir = join8(destPath, "..");
13978
- mkdirSync6(parentDir, { recursive: true });
13979
- const content = readFileSync5(srcPath);
13980
- writeFileSync4(destPath, content);
14696
+ const parentDir = join9(destPath, "..");
14697
+ mkdirSync7(parentDir, { recursive: true });
14698
+ const content = readFileSync6(srcPath);
14699
+ writeFileSync5(destPath, content);
13981
14700
  }
13982
14701
  copiedFiles.push(relFile);
13983
14702
  }
@@ -14222,7 +14941,7 @@ program2.command("env").option("-o, --output <file>", "Write to file instead of
14222
14941
  `) + `
14223
14942
  `;
14224
14943
  if (options.output) {
14225
- writeFileSync4(options.output, output);
14944
+ writeFileSync5(options.output, output);
14226
14945
  console.log(chalk2.green(`\u2713 Written to ${options.output} (${vars.length} variables)`));
14227
14946
  } else {
14228
14947
  console.log(output);
@@ -14250,7 +14969,7 @@ Available presets:
14250
14969
  `));
14251
14970
  });
14252
14971
  program2.command("whoami").option("--json", "Output as JSON", false).description("Show current setup: config dir, installed connectors, auth status").action((options) => {
14253
- const configDir = join8(homedir6(), ".connectors");
14972
+ const configDir = join9(homedir7(), ".connectors");
14254
14973
  const installed = getInstalledConnectors();
14255
14974
  const version = "0.3.1";
14256
14975
  let configured = 0;
@@ -14264,23 +14983,23 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14264
14983
  configured++;
14265
14984
  else
14266
14985
  unconfigured++;
14267
- const connectorConfigDir = join8(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
14268
- const currentProfileFile = join8(connectorConfigDir, "current_profile");
14986
+ const connectorConfigDir = join9(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
14987
+ const currentProfileFile = join9(connectorConfigDir, "current_profile");
14269
14988
  let profile = "default";
14270
- if (existsSync7(currentProfileFile)) {
14989
+ if (existsSync8(currentProfileFile)) {
14271
14990
  try {
14272
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
14991
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14273
14992
  } catch {}
14274
14993
  }
14275
14994
  connectorDetails.push({ name, configured: auth.configured, authType: auth.type, profile, source: "project" });
14276
14995
  }
14277
- if (existsSync7(configDir)) {
14996
+ if (existsSync8(configDir)) {
14278
14997
  try {
14279
14998
  const globalDirs = readdirSync4(configDir).filter((f) => {
14280
14999
  if (!f.startsWith("connect-"))
14281
15000
  return false;
14282
15001
  try {
14283
- return statSync4(join8(configDir, f)).isDirectory();
15002
+ return statSync4(join9(configDir, f)).isDirectory();
14284
15003
  } catch {
14285
15004
  return false;
14286
15005
  }
@@ -14294,11 +15013,11 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14294
15013
  continue;
14295
15014
  seen.add(name);
14296
15015
  configured++;
14297
- const currentProfileFile = join8(configDir, dir, "current_profile");
15016
+ const currentProfileFile = join9(configDir, dir, "current_profile");
14298
15017
  let profile = "default";
14299
- if (existsSync7(currentProfileFile)) {
15018
+ if (existsSync8(currentProfileFile)) {
14300
15019
  try {
14301
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
15020
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14302
15021
  } catch {}
14303
15022
  }
14304
15023
  connectorDetails.push({ name, configured: true, authType: auth.type, profile, source: "global" });
@@ -14309,7 +15028,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14309
15028
  console.log(JSON.stringify({
14310
15029
  version,
14311
15030
  configDir,
14312
- configDirExists: existsSync7(configDir),
15031
+ configDirExists: existsSync8(configDir),
14313
15032
  installed: installed.length,
14314
15033
  configured,
14315
15034
  unconfigured,
@@ -14321,7 +15040,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14321
15040
  Connectors Setup
14322
15041
  `));
14323
15042
  console.log(` Version: ${chalk2.cyan(version)}`);
14324
- console.log(` Config: ${configDir}${existsSync7(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
15043
+ console.log(` Config: ${configDir}${existsSync8(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
14325
15044
  console.log(` Installed: ${installed.length} connector${installed.length !== 1 ? "s" : ""}`);
14326
15045
  console.log(` Configured: ${chalk2.green(String(configured))} ready, ${unconfigured > 0 ? chalk2.red(String(unconfigured)) : chalk2.dim("0")} need auth`);
14327
15046
  const projectConnectors = connectorDetails.filter((c) => c.source === "project");
@@ -14411,18 +15130,18 @@ Testing connector credentials...
14411
15130
  }
14412
15131
  }
14413
15132
  if (!apiKey) {
14414
- const connectorConfigDir = join8(homedir6(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
15133
+ const connectorConfigDir = join9(homedir7(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
14415
15134
  let currentProfile = "default";
14416
- const currentProfileFile = join8(connectorConfigDir, "current_profile");
14417
- if (existsSync7(currentProfileFile)) {
15135
+ const currentProfileFile = join9(connectorConfigDir, "current_profile");
15136
+ if (existsSync8(currentProfileFile)) {
14418
15137
  try {
14419
- currentProfile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
15138
+ currentProfile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14420
15139
  } catch {}
14421
15140
  }
14422
- const tokensFile = join8(connectorConfigDir, "profiles", currentProfile, "tokens.json");
14423
- if (existsSync7(tokensFile)) {
15141
+ const tokensFile = join9(connectorConfigDir, "profiles", currentProfile, "tokens.json");
15142
+ if (existsSync8(tokensFile)) {
14424
15143
  try {
14425
- const tokens = JSON.parse(readFileSync5(tokensFile, "utf-8"));
15144
+ const tokens = JSON.parse(readFileSync6(tokensFile, "utf-8"));
14426
15145
  const isExpired = tokens.expiresAt && Date.now() >= tokens.expiresAt - 60000;
14427
15146
  if (isExpired && tokens.refreshToken) {
14428
15147
  try {
@@ -14440,19 +15159,19 @@ Testing connector credentials...
14440
15159
  } catch {}
14441
15160
  }
14442
15161
  if (!apiKey) {
14443
- const profileFile = join8(connectorConfigDir, "profiles", `${currentProfile}.json`);
14444
- if (existsSync7(profileFile)) {
15162
+ const profileFile = join9(connectorConfigDir, "profiles", `${currentProfile}.json`);
15163
+ if (existsSync8(profileFile)) {
14445
15164
  try {
14446
- const config = JSON.parse(readFileSync5(profileFile, "utf-8"));
15165
+ const config = JSON.parse(readFileSync6(profileFile, "utf-8"));
14447
15166
  apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
14448
15167
  } catch {}
14449
15168
  }
14450
15169
  }
14451
15170
  if (!apiKey) {
14452
- const profileDirConfig = join8(connectorConfigDir, "profiles", currentProfile, "config.json");
14453
- if (existsSync7(profileDirConfig)) {
15171
+ const profileDirConfig = join9(connectorConfigDir, "profiles", currentProfile, "config.json");
15172
+ if (existsSync8(profileDirConfig)) {
14454
15173
  try {
14455
- const config = JSON.parse(readFileSync5(profileDirConfig, "utf-8"));
15174
+ const config = JSON.parse(readFileSync6(profileDirConfig, "utf-8"));
14456
15175
  apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
14457
15176
  } catch {}
14458
15177
  }
@@ -14589,7 +15308,7 @@ program2.command("run").description("Execute an API operation on a connector").a
14589
15308
  console.error(chalk2.yellow(`No command specified. Run ${chalk2.white(`connectors ops ${name}`)} to see available operations.`));
14590
15309
  process.exit(1);
14591
15310
  }
14592
- const result = await runConnectorCommand(name, args, parseInt(options.timeout));
15311
+ const result = await runConnectorCommand2(name, args, parseInt(options.timeout));
14593
15312
  if (result.stdout) {
14594
15313
  console.log(result.stdout);
14595
15314
  }
@@ -14619,7 +15338,7 @@ Setting up ${meta.displayName}...
14619
15338
  const alreadyInstalled = installed.includes(meta.name);
14620
15339
  let installResult;
14621
15340
  if (alreadyInstalled && !options.overwrite) {
14622
- installResult = { success: true, path: join8(process.cwd(), ".connectors", `connect-${meta.name}`) };
15341
+ installResult = { success: true, path: join9(process.cwd(), ".connectors", `connect-${meta.name}`) };
14623
15342
  if (!options.json) {
14624
15343
  console.log(` ${chalk2.green("\u2713")} Already installed`);
14625
15344
  }
@@ -14745,4 +15464,254 @@ Setting up ${meta.displayName}...
14745
15464
  }
14746
15465
  process.exit(0);
14747
15466
  });
15467
+ var jobsCmd = program2.command("jobs").description("Manage scheduled connector jobs");
15468
+ jobsCmd.command("add").description("Add a scheduled job").requiredOption("--name <name>", "Job name").requiredOption("--connector <connector>", "Connector name").requiredOption("--command <command>", "Command to run").requiredOption("--cron <cron>", "Cron expression (5-field)").option("--args <args>", "Command args (space-separated)").option("--strip", "Apply LLM stripping to output").option("--json", "Output as JSON").action((options) => {
15469
+ const db = getDatabase();
15470
+ const args = options.args ? options.args.split(" ") : [];
15471
+ const job = createJob({ name: options.name, connector: options.connector, command: options.command, args, cron: options.cron, strip: !!options.strip }, db);
15472
+ if (options.json) {
15473
+ console.log(JSON.stringify(job));
15474
+ return;
15475
+ }
15476
+ console.log(chalk2.green("\u2713") + ` Job created: ${job.name} (${job.cron})`);
15477
+ });
15478
+ jobsCmd.command("list").description("List all jobs").option("--json", "Output as JSON").action((options) => {
15479
+ const jobs = listJobs(getDatabase());
15480
+ if (options.json) {
15481
+ console.log(JSON.stringify(jobs));
15482
+ return;
15483
+ }
15484
+ if (jobs.length === 0) {
15485
+ console.log(chalk2.dim("No jobs configured."));
15486
+ return;
15487
+ }
15488
+ for (const j of jobs) {
15489
+ const status = j.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
15490
+ const strip = j.strip ? chalk2.cyan(" [strip]") : "";
15491
+ console.log(` ${j.name.padEnd(20)} ${j.connector}.${j.command.padEnd(16)} ${j.cron.padEnd(15)} ${status}${strip}`);
15492
+ }
15493
+ });
15494
+ jobsCmd.command("run").description("Manually trigger a job").argument("<name>", "Job name").option("--json", "Output as JSON").action(async (name, options) => {
15495
+ const db = getDatabase();
15496
+ const job = getJobByName(name, db);
15497
+ if (!job) {
15498
+ console.error(chalk2.red(`Job "${name}" not found`));
15499
+ process.exit(1);
15500
+ }
15501
+ if (!options.json)
15502
+ console.log(chalk2.dim(`Running ${job.connector} ${job.command}...`));
15503
+ const result = await triggerJob(job, db);
15504
+ if (options.json) {
15505
+ console.log(JSON.stringify(result));
15506
+ return;
15507
+ }
15508
+ const icon = result.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
15509
+ console.log(`${icon} Run ${result.run_id} \u2014 exit ${result.exit_code}`);
15510
+ if (result.output)
15511
+ console.log(result.output.slice(0, 2000));
15512
+ });
15513
+ jobsCmd.command("logs").description("Show recent runs for a job").argument("<name>", "Job name").option("--limit <n>", "Max results", "10").option("--json", "Output as JSON").action((name, options) => {
15514
+ const db = getDatabase();
15515
+ const job = getJobByName(name, db);
15516
+ if (!job) {
15517
+ console.error(chalk2.red(`Job "${name}" not found`));
15518
+ process.exit(1);
15519
+ }
15520
+ const runs = listJobRuns(job.id, parseInt(options.limit), db);
15521
+ if (options.json) {
15522
+ console.log(JSON.stringify(runs));
15523
+ return;
15524
+ }
15525
+ if (runs.length === 0) {
15526
+ console.log(chalk2.dim("No runs yet."));
15527
+ return;
15528
+ }
15529
+ for (const r of runs) {
15530
+ const icon = r.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
15531
+ console.log(` ${icon} ${r.started_at.slice(0, 19)} \u2014 exit ${r.exit_code ?? "?"}`);
15532
+ }
15533
+ });
15534
+ jobsCmd.command("enable").description("Enable a job").argument("<name>").action((name) => {
15535
+ const db = getDatabase();
15536
+ const job = getJobByName(name, db);
15537
+ if (!job) {
15538
+ console.error(chalk2.red(`Job "${name}" not found`));
15539
+ process.exit(1);
15540
+ }
15541
+ updateJob(job.id, { enabled: true }, db);
15542
+ console.log(chalk2.green("\u2713") + ` Job "${name}" enabled`);
15543
+ });
15544
+ jobsCmd.command("disable").description("Disable a job").argument("<name>").action((name) => {
15545
+ const db = getDatabase();
15546
+ const job = getJobByName(name, db);
15547
+ if (!job) {
15548
+ console.error(chalk2.red(`Job "${name}" not found`));
15549
+ process.exit(1);
15550
+ }
15551
+ updateJob(job.id, { enabled: false }, db);
15552
+ console.log(chalk2.green("\u2713") + ` Job "${name}" disabled`);
15553
+ });
15554
+ jobsCmd.command("delete").description("Delete a job").argument("<name>").action((name) => {
15555
+ const db = getDatabase();
15556
+ const job = getJobByName(name, db);
15557
+ if (!job) {
15558
+ console.error(chalk2.red(`Job "${name}" not found`));
15559
+ process.exit(1);
15560
+ }
15561
+ deleteJob(job.id, db);
15562
+ console.log(chalk2.green("\u2713") + ` Job "${name}" deleted`);
15563
+ });
15564
+ var workflowsCmd = program2.command("workflows").description("Manage connector workflows (sequential pipelines)");
15565
+ workflowsCmd.command("add").description("Create a workflow from a JSON steps array").requiredOption("--name <name>", "Workflow name").requiredOption("--steps <json>", `Steps JSON array, e.g. '[{"connector":"stripe","command":"products list"}]'`).option("--json", "Output as JSON").action((options) => {
15566
+ let steps;
15567
+ try {
15568
+ steps = JSON.parse(options.steps);
15569
+ } catch {
15570
+ console.error(chalk2.red("Invalid JSON for --steps"));
15571
+ process.exit(1);
15572
+ }
15573
+ const wf = createWorkflow({ name: options.name, steps }, getDatabase());
15574
+ if (options.json) {
15575
+ console.log(JSON.stringify(wf));
15576
+ return;
15577
+ }
15578
+ console.log(chalk2.green("\u2713") + ` Workflow "${wf.name}" created (${wf.steps.length} steps)`);
15579
+ });
15580
+ workflowsCmd.command("list").description("List all workflows").option("--json", "Output as JSON").action((options) => {
15581
+ const wfs = listWorkflows(getDatabase());
15582
+ if (options.json) {
15583
+ console.log(JSON.stringify(wfs));
15584
+ return;
15585
+ }
15586
+ if (wfs.length === 0) {
15587
+ console.log(chalk2.dim("No workflows configured."));
15588
+ return;
15589
+ }
15590
+ for (const wf of wfs) {
15591
+ const status = wf.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
15592
+ console.log(` ${wf.name.padEnd(20)} ${String(wf.steps.length).padStart(2)} steps ${status}`);
15593
+ }
15594
+ });
15595
+ workflowsCmd.command("run").description("Run a workflow").argument("<name>", "Workflow name").option("--json", "Output as JSON").action(async (name, options) => {
15596
+ const wf = getWorkflowByName(name, getDatabase());
15597
+ if (!wf) {
15598
+ console.error(chalk2.red(`Workflow "${name}" not found`));
15599
+ process.exit(1);
15600
+ }
15601
+ if (!options.json)
15602
+ console.log(chalk2.dim(`Running workflow "${wf.name}" (${wf.steps.length} steps)...`));
15603
+ const result = await runWorkflow(wf);
15604
+ if (options.json) {
15605
+ console.log(JSON.stringify(result));
15606
+ return;
15607
+ }
15608
+ for (const step of result.steps) {
15609
+ const icon = step.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
15610
+ console.log(` ${icon} Step ${step.step}: ${step.connector} ${step.command}`);
15611
+ }
15612
+ console.log(result.success ? chalk2.green(`
15613
+ Workflow completed`) : chalk2.red(`
15614
+ Workflow failed`));
15615
+ });
15616
+ workflowsCmd.command("delete").description("Delete a workflow").argument("<name>").action((name) => {
15617
+ const wf = getWorkflowByName(name, getDatabase());
15618
+ if (!wf) {
15619
+ console.error(chalk2.red(`Workflow "${name}" not found`));
15620
+ process.exit(1);
15621
+ }
15622
+ deleteWorkflow(wf.id, getDatabase());
15623
+ console.log(chalk2.green("\u2713") + ` Workflow "${name}" deleted`);
15624
+ });
15625
+ var llmCmd = program2.command("llm").description("Manage LLM provider for output stripping");
15626
+ llmCmd.command("set").description("Configure LLM provider and API key").requiredOption("--provider <provider>", "Provider: cerebras, groq, openai, anthropic").requiredOption("--key <key>", "API key").option("--model <model>", "Model name (defaults to provider default)").option("--json", "Output as JSON").action((options) => {
15627
+ const provider = options.provider;
15628
+ const validProviders = ["cerebras", "groq", "openai", "anthropic"];
15629
+ if (!validProviders.includes(provider)) {
15630
+ console.error(chalk2.red(`Unknown provider "${provider}". Valid: ${validProviders.join(", ")}`));
15631
+ process.exit(1);
15632
+ }
15633
+ const model = options.model || PROVIDER_DEFAULTS[provider].model;
15634
+ const existing = getLlmConfig();
15635
+ saveLlmConfig({ provider, model, api_key: options.key, strip: existing?.strip ?? false });
15636
+ if (options.json) {
15637
+ console.log(JSON.stringify({ provider, model, key: maskKey(options.key), strip: existing?.strip ?? false }));
15638
+ } else {
15639
+ console.log(chalk2.green("\u2713") + ` LLM configured: ${provider} / ${model}`);
15640
+ console.log(` Key: ${maskKey(options.key)}`);
15641
+ console.log(` Strip: ${existing?.strip ? chalk2.green("enabled") : chalk2.dim("disabled")} (run 'connectors llm strip enable' to turn on)`);
15642
+ }
15643
+ });
15644
+ llmCmd.command("status").description("Show current LLM configuration").option("--json", "Output as JSON").action((options) => {
15645
+ const config = getLlmConfig();
15646
+ if (!config) {
15647
+ if (options.json)
15648
+ console.log(JSON.stringify({ configured: false }));
15649
+ else
15650
+ console.log(chalk2.dim("No LLM configured. Run: connectors llm set --provider <provider> --key <key>"));
15651
+ return;
15652
+ }
15653
+ if (options.json) {
15654
+ console.log(JSON.stringify({ configured: true, provider: config.provider, model: config.model, key: maskKey(config.api_key), strip: config.strip }));
15655
+ } else {
15656
+ console.log(chalk2.bold("LLM Configuration"));
15657
+ console.log(` Provider : ${config.provider}`);
15658
+ console.log(` Model : ${config.model}`);
15659
+ console.log(` Key : ${maskKey(config.api_key)}`);
15660
+ console.log(` Strip : ${config.strip ? chalk2.green("enabled") : chalk2.red("disabled")}`);
15661
+ }
15662
+ });
15663
+ llmCmd.command("strip").description("Enable or disable global output stripping").argument("<action>", "enable or disable").action((action) => {
15664
+ if (action !== "enable" && action !== "disable") {
15665
+ console.error(chalk2.red('Action must be "enable" or "disable"'));
15666
+ process.exit(1);
15667
+ }
15668
+ try {
15669
+ setLlmStrip(action === "enable");
15670
+ console.log(chalk2.green("\u2713") + ` Output stripping ${action}d`);
15671
+ } catch (e) {
15672
+ console.error(chalk2.red(String(e instanceof Error ? e.message : e)));
15673
+ process.exit(1);
15674
+ }
15675
+ });
15676
+ llmCmd.command("test").description("Test current LLM configuration with a sample prompt").option("--json", "Output as JSON").action(async (options) => {
15677
+ const config = getLlmConfig();
15678
+ if (!config) {
15679
+ console.error(chalk2.red("No LLM configured. Run: connectors llm set --provider <provider> --key <key>"));
15680
+ process.exit(1);
15681
+ }
15682
+ if (!options.json)
15683
+ console.log(chalk2.dim(`Testing ${config.provider} / ${config.model}...`));
15684
+ try {
15685
+ const client = new LLMClient(config);
15686
+ const result = await client.complete('You are a helpful assistant. Respond with exactly: {"status":"ok"}', "ping");
15687
+ if (options.json) {
15688
+ console.log(JSON.stringify({ success: true, provider: result.provider, model: result.model, latency_ms: result.latency_ms, response: result.content }));
15689
+ } else {
15690
+ console.log(chalk2.green("\u2713") + ` Response received in ${result.latency_ms}ms`);
15691
+ console.log(` ${result.content.trim()}`);
15692
+ }
15693
+ } catch (e) {
15694
+ if (options.json)
15695
+ console.log(JSON.stringify({ success: false, error: String(e instanceof Error ? e.message : e) }));
15696
+ else
15697
+ console.error(chalk2.red("\u2717 " + String(e instanceof Error ? e.message : e)));
15698
+ process.exit(1);
15699
+ }
15700
+ });
15701
+ llmCmd.command("providers").description("List supported LLM providers").option("--json", "Output as JSON").action((options) => {
15702
+ const providers = [
15703
+ { name: "cerebras", baseUrl: "https://api.cerebras.ai/v1", defaultModel: PROVIDER_DEFAULTS.cerebras.model, compatible: "OpenAI" },
15704
+ { name: "groq", baseUrl: "https://api.groq.com/openai/v1", defaultModel: PROVIDER_DEFAULTS.groq.model, compatible: "OpenAI" },
15705
+ { name: "openai", baseUrl: "https://api.openai.com/v1", defaultModel: PROVIDER_DEFAULTS.openai.model, compatible: "OpenAI" },
15706
+ { name: "anthropic", baseUrl: "https://api.anthropic.com/v1", defaultModel: PROVIDER_DEFAULTS.anthropic.model, compatible: "Anthropic" }
15707
+ ];
15708
+ if (options.json) {
15709
+ console.log(JSON.stringify(providers));
15710
+ return;
15711
+ }
15712
+ console.log(chalk2.bold("Supported LLM Providers"));
15713
+ for (const p of providers) {
15714
+ console.log(` ${chalk2.cyan(p.name.padEnd(12))} ${p.defaultModel.padEnd(30)} ${chalk2.dim(p.baseUrl)}`);
15715
+ }
15716
+ });
14748
15717
  program2.parse();