@hasna/connectors 1.1.18 → 1.2.1

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();
@@ -10857,7 +11484,7 @@ import chalk2 from "chalk";
10857
11484
  // package.json
10858
11485
  var package_default = {
10859
11486
  name: "@hasna/connectors",
10860
- version: "1.1.18",
11487
+ version: "1.2.0",
10861
11488
  description: "Open source connector library - Install API connectors with a single command",
10862
11489
  type: "module",
10863
11490
  bin: {
@@ -10942,6 +11569,14 @@ var package_default = {
10942
11569
  }
10943
11570
  };
10944
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();
11579
+
10945
11580
  // src/cli/components/App.tsx
10946
11581
  import { useState as useState7 } from "react";
10947
11582
  import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
@@ -12414,9 +13049,9 @@ function App({ initialConnectors, overwrite = false }) {
12414
13049
  init_registry();
12415
13050
  init_installer();
12416
13051
  init_auth();
12417
- import { readdirSync as readdirSync4, existsSync as existsSync7, statSync as statSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
12418
- import { homedir as homedir6 } from "os";
12419
- 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";
12420
13055
 
12421
13056
  // src/lib/test-endpoints.ts
12422
13057
  var TEST_ENDPOINTS = {
@@ -12617,17 +13252,17 @@ var TEST_ENDPOINTS = {
12617
13252
  import { createInterface } from "readline";
12618
13253
 
12619
13254
  // src/lib/runner.ts
12620
- import { existsSync as existsSync5 } from "fs";
12621
- 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";
12622
13257
  import { fileURLToPath as fileURLToPath3 } from "url";
12623
- import { spawn } from "child_process";
13258
+ import { spawn as spawn3 } from "child_process";
12624
13259
  var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
12625
13260
  function resolveConnectorsDir2() {
12626
- const fromBin = join5(__dirname3, "..", "connectors");
12627
- if (existsSync5(fromBin))
13261
+ const fromBin = join7(__dirname3, "..", "connectors");
13262
+ if (existsSync6(fromBin))
12628
13263
  return fromBin;
12629
- const fromSrc = join5(__dirname3, "..", "..", "connectors");
12630
- if (existsSync5(fromSrc))
13264
+ const fromSrc = join7(__dirname3, "..", "..", "connectors");
13265
+ if (existsSync6(fromSrc))
12631
13266
  return fromSrc;
12632
13267
  return fromBin;
12633
13268
  }
@@ -12667,13 +13302,13 @@ function buildEnvWithCredentials(connectorName, baseEnv) {
12667
13302
  }
12668
13303
  function getConnectorCliPath(name) {
12669
13304
  const safeName = name.replace(/[^a-z0-9-]/g, "");
12670
- const connectorDir = join5(CONNECTORS_DIR2, `connect-${safeName}`);
12671
- const cliPath = join5(connectorDir, "src", "cli", "index.ts");
12672
- if (existsSync5(cliPath))
13305
+ const connectorDir = join7(CONNECTORS_DIR2, `connect-${safeName}`);
13306
+ const cliPath = join7(connectorDir, "src", "cli", "index.ts");
13307
+ if (existsSync6(cliPath))
12673
13308
  return cliPath;
12674
13309
  return null;
12675
13310
  }
12676
- function runConnectorCommand(name, args, timeoutMs = 30000) {
13311
+ function runConnectorCommand2(name, args, timeoutMs = 30000) {
12677
13312
  const cliPath = getConnectorCliPath(name);
12678
13313
  if (!cliPath) {
12679
13314
  return Promise.resolve({
@@ -12684,7 +13319,7 @@ function runConnectorCommand(name, args, timeoutMs = 30000) {
12684
13319
  });
12685
13320
  }
12686
13321
  return new Promise((resolve) => {
12687
- const proc = spawn("bun", ["run", cliPath, ...args], {
13322
+ const proc = spawn3("bun", ["run", cliPath, ...args], {
12688
13323
  timeout: timeoutMs,
12689
13324
  env: buildEnvWithCredentials(name, process.env),
12690
13325
  stdio: ["pipe", "pipe", "pipe"]
@@ -12720,7 +13355,7 @@ async function getConnectorOperations(name) {
12720
13355
  if (!cliPath) {
12721
13356
  return { commands: [], helpText: "", hasCli: false };
12722
13357
  }
12723
- const result = await runConnectorCommand(name, ["--help"]);
13358
+ const result = await runConnectorCommand2(name, ["--help"]);
12724
13359
  const helpText = result.stdout || result.stderr;
12725
13360
  const commands = [];
12726
13361
  const lines = helpText.split(`
@@ -12744,7 +13379,7 @@ async function getConnectorOperations(name) {
12744
13379
  return { commands, helpText, hasCli: true };
12745
13380
  }
12746
13381
  async function getConnectorCommandHelp(name, command) {
12747
- const result = await runConnectorCommand(name, [command, "--help"]);
13382
+ const result = await runConnectorCommand2(name, [command, "--help"]);
12748
13383
  return result.stdout || result.stderr;
12749
13384
  }
12750
13385
 
@@ -12782,7 +13417,7 @@ Run 'connectors --help' for full usage.`);
12782
13417
  function listFilesRecursive(dir, base = dir) {
12783
13418
  const files = [];
12784
13419
  for (const entry of readdirSync4(dir)) {
12785
- const fullPath = join8(dir, entry);
13420
+ const fullPath = join9(dir, entry);
12786
13421
  if (statSync4(fullPath).isDirectory()) {
12787
13422
  files.push(...listFilesRecursive(fullPath, base));
12788
13423
  } else {
@@ -12831,7 +13466,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
12831
13466
  }
12832
13467
  if (options.dryRun) {
12833
13468
  const installed = getInstalledConnectors();
12834
- const destDir = join8(process.cwd(), ".connectors");
13469
+ const destDir = join9(process.cwd(), ".connectors");
12835
13470
  const actions = [];
12836
13471
  for (const name of connectors) {
12837
13472
  if (!/^[a-z0-9-]+$/.test(name)) {
@@ -12849,7 +13484,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
12849
13484
  }
12850
13485
  const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
12851
13486
  const sourcePath = getConnectorPath(name);
12852
- const destPath = join8(destDir, connectorDirName);
13487
+ const destPath = join9(destDir, connectorDirName);
12853
13488
  const alreadyInstalled = installed.includes(name);
12854
13489
  const files = listFilesRecursive(sourcePath);
12855
13490
  const importLine = `export * as ${name} from './${connectorDirName}/src/index.js';`;
@@ -13353,17 +13988,17 @@ Updating ${toUpdate.length} connector(s)...
13353
13988
  });
13354
13989
  program2.command("status").option("--json", "Output as JSON", false).description("Show auth status of all configured connectors (project + global)").action((options) => {
13355
13990
  const installed = getInstalledConnectors();
13356
- const configDir = join8(homedir6(), ".connectors");
13991
+ const configDir = join9(homedir7(), ".connectors");
13357
13992
  const seen = new Set;
13358
13993
  const allStatuses = [];
13359
13994
  function buildStatusEntry(name, source) {
13360
13995
  const auth = getAuthStatus(name);
13361
13996
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
13362
- const currentProfileFile = join8(configDir, connectorName, "current_profile");
13997
+ const currentProfileFile = join9(configDir, connectorName, "current_profile");
13363
13998
  let profile = "default";
13364
- if (existsSync7(currentProfileFile)) {
13999
+ if (existsSync8(currentProfileFile)) {
13365
14000
  try {
13366
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
14001
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
13367
14002
  } catch {}
13368
14003
  }
13369
14004
  let expiryLabel = null;
@@ -13399,13 +14034,13 @@ program2.command("status").option("--json", "Output as JSON", false).description
13399
14034
  seen.add(name);
13400
14035
  allStatuses.push(buildStatusEntry(name, "project"));
13401
14036
  }
13402
- if (existsSync7(configDir)) {
14037
+ if (existsSync8(configDir)) {
13403
14038
  try {
13404
14039
  const globalDirs = readdirSync4(configDir).filter((f) => {
13405
14040
  if (!f.startsWith("connect-"))
13406
14041
  return false;
13407
14042
  try {
13408
- return statSync4(join8(configDir, f)).isDirectory();
14043
+ return statSync4(join9(configDir, f)).isDirectory();
13409
14044
  } catch {
13410
14045
  return false;
13411
14046
  }
@@ -13770,15 +14405,15 @@ program2.command("init").option("--json", "Output presets and suggestions as JSO
13770
14405
  { key: "commerce", emoji: "\uD83D\uDCB3", label: "Commerce", connectors: ["stripe", "shopify", "paypal", "revolut", "mercury"], description: "Commerce and finance" },
13771
14406
  { key: "google", emoji: "\uD83D\uDCC1", label: "Google Workspace", connectors: ["gmail", "googledrive", "googlecalendar", "googledocs", "googlesheets"], description: "Google Workspace suite" }
13772
14407
  ];
13773
- const connectorsHome = join8(homedir6(), ".connectors");
14408
+ const connectorsHome = join9(homedir7(), ".connectors");
13774
14409
  let configuredCount = 0;
13775
14410
  const configuredNames = [];
13776
14411
  try {
13777
- if (existsSync7(connectorsHome)) {
13778
- 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());
13779
14414
  for (const entry of entries) {
13780
- const profilesDir = join8(connectorsHome, entry, "profiles");
13781
- if (existsSync7(profilesDir)) {
14415
+ const profilesDir = join9(connectorsHome, entry, "profiles");
14416
+ if (existsSync8(profilesDir)) {
13782
14417
  configuredCount++;
13783
14418
  configuredNames.push(entry.replace(/^connect-/, ""));
13784
14419
  }
@@ -13876,44 +14511,44 @@ function redactSecrets(obj) {
13876
14511
  return obj;
13877
14512
  }
13878
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) => {
13879
- const connectDir = join8(homedir6(), ".connectors");
14514
+ const connectDir = join9(homedir7(), ".connectors");
13880
14515
  const result = {};
13881
- if (existsSync7(connectDir)) {
14516
+ if (existsSync8(connectDir)) {
13882
14517
  for (const entry of readdirSync4(connectDir)) {
13883
- const entryPath = join8(connectDir, entry);
14518
+ const entryPath = join9(connectDir, entry);
13884
14519
  if (!statSync4(entryPath).isDirectory() || !entry.startsWith("connect-"))
13885
14520
  continue;
13886
14521
  const connectorName = entry.replace(/^connect-/, "");
13887
14522
  let credentials = undefined;
13888
- const credentialsPath = join8(entryPath, "credentials.json");
13889
- if (existsSync7(credentialsPath)) {
14523
+ const credentialsPath = join9(entryPath, "credentials.json");
14524
+ if (existsSync8(credentialsPath)) {
13890
14525
  try {
13891
- credentials = JSON.parse(readFileSync5(credentialsPath, "utf-8"));
14526
+ credentials = JSON.parse(readFileSync6(credentialsPath, "utf-8"));
13892
14527
  } catch {}
13893
14528
  }
13894
- const profilesDir = join8(entryPath, "profiles");
13895
- if (!existsSync7(profilesDir) && !credentials)
14529
+ const profilesDir = join9(entryPath, "profiles");
14530
+ if (!existsSync8(profilesDir) && !credentials)
13896
14531
  continue;
13897
14532
  const profiles = {};
13898
- if (existsSync7(profilesDir)) {
14533
+ if (existsSync8(profilesDir)) {
13899
14534
  for (const pEntry of readdirSync4(profilesDir)) {
13900
- const pPath = join8(profilesDir, pEntry);
14535
+ const pPath = join9(profilesDir, pEntry);
13901
14536
  if (statSync4(pPath).isFile() && pEntry.endsWith(".json")) {
13902
14537
  try {
13903
- profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
14538
+ profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync6(pPath, "utf-8"));
13904
14539
  } catch {}
13905
14540
  } else if (statSync4(pPath).isDirectory()) {
13906
- const configPath = join8(pPath, "config.json");
13907
- const tokensPath = join8(pPath, "tokens.json");
14541
+ const configPath = join9(pPath, "config.json");
14542
+ const tokensPath = join9(pPath, "tokens.json");
13908
14543
  let merged = {};
13909
- if (existsSync7(configPath)) {
14544
+ if (existsSync8(configPath)) {
13910
14545
  try {
13911
- merged = { ...merged, ...JSON.parse(readFileSync5(configPath, "utf-8")) };
14546
+ merged = { ...merged, ...JSON.parse(readFileSync6(configPath, "utf-8")) };
13912
14547
  } catch {}
13913
14548
  }
13914
- if (existsSync7(tokensPath)) {
14549
+ if (existsSync8(tokensPath)) {
13915
14550
  try {
13916
- merged = { ...merged, ...JSON.parse(readFileSync5(tokensPath, "utf-8")) };
14551
+ merged = { ...merged, ...JSON.parse(readFileSync6(tokensPath, "utf-8")) };
13917
14552
  } catch {}
13918
14553
  }
13919
14554
  if (Object.keys(merged).length > 0)
@@ -13934,7 +14569,7 @@ program2.command("export").option("-o, --output <file>", "Write to file instead
13934
14569
  }
13935
14570
  const exportData = JSON.stringify(exportPayload, null, 2);
13936
14571
  if (options.output) {
13937
- writeFileSync4(options.output, exportData);
14572
+ writeFileSync5(options.output, exportData);
13938
14573
  console.log(chalk2.green(`\u2713 Exported to ${options.output}`));
13939
14574
  } else {
13940
14575
  console.log(exportData);
@@ -13948,7 +14583,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13948
14583
  chunks.push(chunk.toString());
13949
14584
  raw = chunks.join("");
13950
14585
  } else {
13951
- if (!existsSync7(file)) {
14586
+ if (!existsSync8(file)) {
13952
14587
  if (options.json) {
13953
14588
  console.log(JSON.stringify({ error: `File not found: ${file}` }));
13954
14589
  } else {
@@ -13957,7 +14592,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13957
14592
  process.exit(1);
13958
14593
  return;
13959
14594
  }
13960
- raw = readFileSync5(file, "utf-8");
14595
+ raw = readFileSync6(file, "utf-8");
13961
14596
  }
13962
14597
  let data;
13963
14598
  try {
@@ -13980,25 +14615,25 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
13980
14615
  process.exit(1);
13981
14616
  return;
13982
14617
  }
13983
- const connectDir = join8(homedir6(), ".connectors");
14618
+ const connectDir = join9(homedir7(), ".connectors");
13984
14619
  let imported = 0;
13985
14620
  for (const [connectorName, connData] of Object.entries(data.connectors)) {
13986
14621
  if (!/^[a-z0-9-]+$/.test(connectorName))
13987
14622
  continue;
13988
- const connectorDir = join8(connectDir, `connect-${connectorName}`);
14623
+ const connectorDir = join9(connectDir, `connect-${connectorName}`);
13989
14624
  if (connData.credentials && typeof connData.credentials === "object") {
13990
- mkdirSync6(connectorDir, { recursive: true });
13991
- 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));
13992
14627
  imported++;
13993
14628
  }
13994
14629
  if (!connData.profiles || typeof connData.profiles !== "object")
13995
14630
  continue;
13996
- const profilesDir = join8(connectorDir, "profiles");
14631
+ const profilesDir = join9(connectorDir, "profiles");
13997
14632
  for (const [profileName, config] of Object.entries(connData.profiles)) {
13998
14633
  if (!config || typeof config !== "object")
13999
14634
  continue;
14000
- mkdirSync6(profilesDir, { recursive: true });
14001
- 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));
14002
14637
  imported++;
14003
14638
  }
14004
14639
  }
@@ -14009,9 +14644,9 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
14009
14644
  }
14010
14645
  });
14011
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) => {
14012
- const oldBase = join8(homedir6(), ".connect");
14013
- const newBase = join8(homedir6(), ".connectors");
14014
- if (!existsSync7(oldBase)) {
14647
+ const oldBase = join9(homedir7(), ".connect");
14648
+ const newBase = join9(homedir7(), ".connectors");
14649
+ if (!existsSync8(oldBase)) {
14015
14650
  if (options.json) {
14016
14651
  console.log(JSON.stringify({ imported: [], skipped: [], error: null, message: "No ~/.connect/ directory found" }));
14017
14652
  } else {
@@ -14023,7 +14658,7 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
14023
14658
  if (!name.startsWith("connect-"))
14024
14659
  return false;
14025
14660
  try {
14026
- return statSync4(join8(oldBase, name)).isDirectory();
14661
+ return statSync4(join9(oldBase, name)).isDirectory();
14027
14662
  } catch {
14028
14663
  return false;
14029
14664
  }
@@ -14039,8 +14674,8 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
14039
14674
  const imported = [];
14040
14675
  const skipped = [];
14041
14676
  for (const dirName of entries) {
14042
- const oldDir = join8(oldBase, dirName);
14043
- const newDir = join8(newBase, dirName);
14677
+ const oldDir = join9(oldBase, dirName);
14678
+ const newDir = join9(newBase, dirName);
14044
14679
  const connectorName = dirName.replace(/^connect-/, "");
14045
14680
  const allFiles = listFilesRecursive(oldDir);
14046
14681
  const authFiles = allFiles.filter((f) => {
@@ -14051,17 +14686,17 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
14051
14686
  const copiedFiles = [];
14052
14687
  const skippedFiles = [];
14053
14688
  for (const relFile of authFiles) {
14054
- const srcPath = join8(oldDir, relFile);
14055
- const destPath = join8(newDir, relFile);
14056
- if (existsSync7(destPath) && !options.force) {
14689
+ const srcPath = join9(oldDir, relFile);
14690
+ const destPath = join9(newDir, relFile);
14691
+ if (existsSync8(destPath) && !options.force) {
14057
14692
  skippedFiles.push(relFile);
14058
14693
  continue;
14059
14694
  }
14060
14695
  if (!options.dryRun) {
14061
- const parentDir = join8(destPath, "..");
14062
- mkdirSync6(parentDir, { recursive: true });
14063
- const content = readFileSync5(srcPath);
14064
- writeFileSync4(destPath, content);
14696
+ const parentDir = join9(destPath, "..");
14697
+ mkdirSync7(parentDir, { recursive: true });
14698
+ const content = readFileSync6(srcPath);
14699
+ writeFileSync5(destPath, content);
14065
14700
  }
14066
14701
  copiedFiles.push(relFile);
14067
14702
  }
@@ -14306,7 +14941,7 @@ program2.command("env").option("-o, --output <file>", "Write to file instead of
14306
14941
  `) + `
14307
14942
  `;
14308
14943
  if (options.output) {
14309
- writeFileSync4(options.output, output);
14944
+ writeFileSync5(options.output, output);
14310
14945
  console.log(chalk2.green(`\u2713 Written to ${options.output} (${vars.length} variables)`));
14311
14946
  } else {
14312
14947
  console.log(output);
@@ -14334,7 +14969,7 @@ Available presets:
14334
14969
  `));
14335
14970
  });
14336
14971
  program2.command("whoami").option("--json", "Output as JSON", false).description("Show current setup: config dir, installed connectors, auth status").action((options) => {
14337
- const configDir = join8(homedir6(), ".connectors");
14972
+ const configDir = join9(homedir7(), ".connectors");
14338
14973
  const installed = getInstalledConnectors();
14339
14974
  const version = "0.3.1";
14340
14975
  let configured = 0;
@@ -14348,23 +14983,23 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14348
14983
  configured++;
14349
14984
  else
14350
14985
  unconfigured++;
14351
- const connectorConfigDir = join8(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
14352
- const currentProfileFile = join8(connectorConfigDir, "current_profile");
14986
+ const connectorConfigDir = join9(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
14987
+ const currentProfileFile = join9(connectorConfigDir, "current_profile");
14353
14988
  let profile = "default";
14354
- if (existsSync7(currentProfileFile)) {
14989
+ if (existsSync8(currentProfileFile)) {
14355
14990
  try {
14356
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
14991
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14357
14992
  } catch {}
14358
14993
  }
14359
14994
  connectorDetails.push({ name, configured: auth.configured, authType: auth.type, profile, source: "project" });
14360
14995
  }
14361
- if (existsSync7(configDir)) {
14996
+ if (existsSync8(configDir)) {
14362
14997
  try {
14363
14998
  const globalDirs = readdirSync4(configDir).filter((f) => {
14364
14999
  if (!f.startsWith("connect-"))
14365
15000
  return false;
14366
15001
  try {
14367
- return statSync4(join8(configDir, f)).isDirectory();
15002
+ return statSync4(join9(configDir, f)).isDirectory();
14368
15003
  } catch {
14369
15004
  return false;
14370
15005
  }
@@ -14378,11 +15013,11 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14378
15013
  continue;
14379
15014
  seen.add(name);
14380
15015
  configured++;
14381
- const currentProfileFile = join8(configDir, dir, "current_profile");
15016
+ const currentProfileFile = join9(configDir, dir, "current_profile");
14382
15017
  let profile = "default";
14383
- if (existsSync7(currentProfileFile)) {
15018
+ if (existsSync8(currentProfileFile)) {
14384
15019
  try {
14385
- profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
15020
+ profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14386
15021
  } catch {}
14387
15022
  }
14388
15023
  connectorDetails.push({ name, configured: true, authType: auth.type, profile, source: "global" });
@@ -14393,7 +15028,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14393
15028
  console.log(JSON.stringify({
14394
15029
  version,
14395
15030
  configDir,
14396
- configDirExists: existsSync7(configDir),
15031
+ configDirExists: existsSync8(configDir),
14397
15032
  installed: installed.length,
14398
15033
  configured,
14399
15034
  unconfigured,
@@ -14405,7 +15040,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
14405
15040
  Connectors Setup
14406
15041
  `));
14407
15042
  console.log(` Version: ${chalk2.cyan(version)}`);
14408
- console.log(` Config: ${configDir}${existsSync7(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
15043
+ console.log(` Config: ${configDir}${existsSync8(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
14409
15044
  console.log(` Installed: ${installed.length} connector${installed.length !== 1 ? "s" : ""}`);
14410
15045
  console.log(` Configured: ${chalk2.green(String(configured))} ready, ${unconfigured > 0 ? chalk2.red(String(unconfigured)) : chalk2.dim("0")} need auth`);
14411
15046
  const projectConnectors = connectorDetails.filter((c) => c.source === "project");
@@ -14495,18 +15130,18 @@ Testing connector credentials...
14495
15130
  }
14496
15131
  }
14497
15132
  if (!apiKey) {
14498
- const connectorConfigDir = join8(homedir6(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
15133
+ const connectorConfigDir = join9(homedir7(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
14499
15134
  let currentProfile = "default";
14500
- const currentProfileFile = join8(connectorConfigDir, "current_profile");
14501
- if (existsSync7(currentProfileFile)) {
15135
+ const currentProfileFile = join9(connectorConfigDir, "current_profile");
15136
+ if (existsSync8(currentProfileFile)) {
14502
15137
  try {
14503
- currentProfile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
15138
+ currentProfile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
14504
15139
  } catch {}
14505
15140
  }
14506
- const tokensFile = join8(connectorConfigDir, "profiles", currentProfile, "tokens.json");
14507
- if (existsSync7(tokensFile)) {
15141
+ const tokensFile = join9(connectorConfigDir, "profiles", currentProfile, "tokens.json");
15142
+ if (existsSync8(tokensFile)) {
14508
15143
  try {
14509
- const tokens = JSON.parse(readFileSync5(tokensFile, "utf-8"));
15144
+ const tokens = JSON.parse(readFileSync6(tokensFile, "utf-8"));
14510
15145
  const isExpired = tokens.expiresAt && Date.now() >= tokens.expiresAt - 60000;
14511
15146
  if (isExpired && tokens.refreshToken) {
14512
15147
  try {
@@ -14524,19 +15159,19 @@ Testing connector credentials...
14524
15159
  } catch {}
14525
15160
  }
14526
15161
  if (!apiKey) {
14527
- const profileFile = join8(connectorConfigDir, "profiles", `${currentProfile}.json`);
14528
- if (existsSync7(profileFile)) {
15162
+ const profileFile = join9(connectorConfigDir, "profiles", `${currentProfile}.json`);
15163
+ if (existsSync8(profileFile)) {
14529
15164
  try {
14530
- const config = JSON.parse(readFileSync5(profileFile, "utf-8"));
15165
+ const config = JSON.parse(readFileSync6(profileFile, "utf-8"));
14531
15166
  apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
14532
15167
  } catch {}
14533
15168
  }
14534
15169
  }
14535
15170
  if (!apiKey) {
14536
- const profileDirConfig = join8(connectorConfigDir, "profiles", currentProfile, "config.json");
14537
- if (existsSync7(profileDirConfig)) {
15171
+ const profileDirConfig = join9(connectorConfigDir, "profiles", currentProfile, "config.json");
15172
+ if (existsSync8(profileDirConfig)) {
14538
15173
  try {
14539
- const config = JSON.parse(readFileSync5(profileDirConfig, "utf-8"));
15174
+ const config = JSON.parse(readFileSync6(profileDirConfig, "utf-8"));
14540
15175
  apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
14541
15176
  } catch {}
14542
15177
  }
@@ -14673,7 +15308,7 @@ program2.command("run").description("Execute an API operation on a connector").a
14673
15308
  console.error(chalk2.yellow(`No command specified. Run ${chalk2.white(`connectors ops ${name}`)} to see available operations.`));
14674
15309
  process.exit(1);
14675
15310
  }
14676
- const result = await runConnectorCommand(name, args, parseInt(options.timeout));
15311
+ const result = await runConnectorCommand2(name, args, parseInt(options.timeout));
14677
15312
  if (result.stdout) {
14678
15313
  console.log(result.stdout);
14679
15314
  }
@@ -14703,7 +15338,7 @@ Setting up ${meta.displayName}...
14703
15338
  const alreadyInstalled = installed.includes(meta.name);
14704
15339
  let installResult;
14705
15340
  if (alreadyInstalled && !options.overwrite) {
14706
- installResult = { success: true, path: join8(process.cwd(), ".connectors", `connect-${meta.name}`) };
15341
+ installResult = { success: true, path: join9(process.cwd(), ".connectors", `connect-${meta.name}`) };
14707
15342
  if (!options.json) {
14708
15343
  console.log(` ${chalk2.green("\u2713")} Already installed`);
14709
15344
  }
@@ -14829,4 +15464,254 @@ Setting up ${meta.displayName}...
14829
15464
  }
14830
15465
  process.exit(0);
14831
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
+ });
14832
15717
  program2.parse();