@hasna/connectors 1.1.17 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +1263 -294
- package/bin/mcp.js +702 -72
- package/bin/serve.js +768 -141
- package/dist/db/jobs.d.ts +53 -0
- package/dist/db/jobs.test.d.ts +1 -0
- package/dist/db/workflows.d.ts +26 -0
- package/dist/db/workflows.test.d.ts +1 -0
- package/dist/lib/llm.d.ts +38 -0
- package/dist/lib/llm.test.d.ts +1 -0
- package/dist/lib/scheduler.d.ts +20 -0
- package/dist/lib/scheduler.test.d.ts +1 -0
- package/dist/lib/strip.d.ts +20 -0
- package/dist/lib/strip.test.d.ts +1 -0
- package/dist/lib/workflow-runner.d.ts +22 -0
- package/package.json +1 -1
package/bin/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
|
-
|
|
1892
|
-
|
|
2447
|
+
join3(thisDir, "..", "connectors"),
|
|
2448
|
+
join3(thisDir, "..", "..", "connectors")
|
|
1893
2449
|
];
|
|
1894
|
-
const connectorsDir = candidates.find((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 =
|
|
1900
|
-
if (
|
|
1901
|
-
const pkg = JSON.parse(
|
|
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
|
|
9468
|
-
import { homedir } from "os";
|
|
9469
|
-
import { join as
|
|
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 =
|
|
9473
|
-
if (
|
|
10028
|
+
const fromBin = join4(__dirname2, "..", "connectors");
|
|
10029
|
+
if (existsSync3(fromBin))
|
|
9474
10030
|
return fromBin;
|
|
9475
|
-
const fromSrc =
|
|
9476
|
-
if (
|
|
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
|
|
10038
|
+
return join4(CONNECTORS_DIR, connectorName);
|
|
9483
10039
|
}
|
|
9484
10040
|
function connectorExists(name) {
|
|
9485
|
-
return
|
|
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 =
|
|
9499
|
-
const destPath =
|
|
9500
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
9517
|
-
|
|
10072
|
+
if (!existsSync3(destDir)) {
|
|
10073
|
+
mkdirSync3(destDir, { recursive: true });
|
|
9518
10074
|
}
|
|
9519
10075
|
cpSync(sourcePath, destPath, { recursive: true });
|
|
9520
|
-
const homeCredDir =
|
|
9521
|
-
if (
|
|
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 =
|
|
9525
|
-
if (
|
|
9526
|
-
cpSync(src,
|
|
10080
|
+
const src = join4(homeCredDir, file);
|
|
10081
|
+
if (existsSync3(src)) {
|
|
10082
|
+
cpSync(src, join4(destPath, file));
|
|
9527
10083
|
}
|
|
9528
10084
|
}
|
|
9529
|
-
const profilesDir =
|
|
9530
|
-
if (
|
|
9531
|
-
cpSync(profilesDir,
|
|
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 =
|
|
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
|
-
|
|
10119
|
+
writeFileSync2(indexPath, content);
|
|
9564
10120
|
}
|
|
9565
10121
|
function getInstalledConnectors(targetDir = process.cwd()) {
|
|
9566
|
-
const connectorsDir =
|
|
9567
|
-
if (!
|
|
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 =
|
|
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 =
|
|
9578
|
-
if (!
|
|
10133
|
+
const claudeMdPath = join4(connectorPath, "CLAUDE.md");
|
|
10134
|
+
if (!existsSync3(claudeMdPath))
|
|
9579
10135
|
return null;
|
|
9580
|
-
const raw =
|
|
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 =
|
|
9620
|
-
const connectorPath =
|
|
9621
|
-
if (!
|
|
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
|
|
9636
|
-
import { join as
|
|
9637
|
-
import { homedir as
|
|
9638
|
-
import { mkdirSync as
|
|
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 =
|
|
9641
|
-
|
|
9642
|
-
return
|
|
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 (
|
|
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
|
|
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
|
|
9704
|
-
import { join as
|
|
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
|
|
10274
|
+
return join6(homedir5(), ".connectors", connectorName);
|
|
9719
10275
|
}
|
|
9720
10276
|
function getCurrentProfile(name) {
|
|
9721
10277
|
const configDir = getConnectorConfigDir(name);
|
|
9722
|
-
const currentProfileFile =
|
|
9723
|
-
if (
|
|
10278
|
+
const currentProfileFile = join6(configDir, "current_profile");
|
|
10279
|
+
if (existsSync5(currentProfileFile)) {
|
|
9724
10280
|
try {
|
|
9725
|
-
return
|
|
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 =
|
|
9738
|
-
if (
|
|
10293
|
+
const profileFile = join6(configDir, "profiles", `${profile}.json`);
|
|
10294
|
+
if (existsSync5(profileFile)) {
|
|
9739
10295
|
try {
|
|
9740
|
-
flatConfig = JSON.parse(
|
|
10296
|
+
flatConfig = JSON.parse(readFileSync4(profileFile, "utf-8"));
|
|
9741
10297
|
} catch {}
|
|
9742
10298
|
}
|
|
9743
|
-
const profileDirConfig =
|
|
9744
|
-
if (
|
|
10299
|
+
const profileDirConfig = join6(configDir, "profiles", profile, "config.json");
|
|
10300
|
+
if (existsSync5(profileDirConfig)) {
|
|
9745
10301
|
try {
|
|
9746
|
-
dirConfig = JSON.parse(
|
|
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 =
|
|
9758
|
-
if (
|
|
10313
|
+
const tokensFile = join6(configDir, "profiles", profile, "tokens.json");
|
|
10314
|
+
if (existsSync5(tokensFile)) {
|
|
9759
10315
|
try {
|
|
9760
|
-
return JSON.parse(
|
|
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 =
|
|
9818
|
-
|
|
10373
|
+
const credentialsFile = join6(configDir, "credentials.json");
|
|
10374
|
+
mkdirSync5(configDir, { recursive: true });
|
|
9819
10375
|
let creds = {};
|
|
9820
|
-
if (
|
|
10376
|
+
if (existsSync5(credentialsFile)) {
|
|
9821
10377
|
try {
|
|
9822
|
-
creds = JSON.parse(
|
|
10378
|
+
creds = JSON.parse(readFileSync4(credentialsFile, "utf-8"));
|
|
9823
10379
|
} catch {}
|
|
9824
10380
|
}
|
|
9825
10381
|
creds[keyField] = key;
|
|
9826
|
-
|
|
10382
|
+
writeFileSync3(credentialsFile, JSON.stringify(creds, null, 2));
|
|
9827
10383
|
return;
|
|
9828
10384
|
}
|
|
9829
|
-
const profileFile =
|
|
9830
|
-
const profileDir =
|
|
9831
|
-
if (
|
|
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(
|
|
10390
|
+
config = JSON.parse(readFileSync4(profileFile, "utf-8"));
|
|
9835
10391
|
} catch {}
|
|
9836
10392
|
config[keyField] = key;
|
|
9837
|
-
|
|
10393
|
+
writeFileSync3(profileFile, JSON.stringify(config, null, 2));
|
|
9838
10394
|
return;
|
|
9839
10395
|
}
|
|
9840
|
-
if (
|
|
9841
|
-
const configFile =
|
|
10396
|
+
if (existsSync5(profileDir)) {
|
|
10397
|
+
const configFile = join6(profileDir, "config.json");
|
|
9842
10398
|
let config = {};
|
|
9843
|
-
if (
|
|
10399
|
+
if (existsSync5(configFile)) {
|
|
9844
10400
|
try {
|
|
9845
|
-
config = JSON.parse(
|
|
10401
|
+
config = JSON.parse(readFileSync4(configFile, "utf-8"));
|
|
9846
10402
|
} catch {}
|
|
9847
10403
|
}
|
|
9848
10404
|
config[keyField] = key;
|
|
9849
|
-
|
|
10405
|
+
writeFileSync3(configFile, JSON.stringify(config, null, 2));
|
|
9850
10406
|
return;
|
|
9851
10407
|
}
|
|
9852
|
-
|
|
9853
|
-
|
|
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 =
|
|
9872
|
-
if (
|
|
10427
|
+
const credentialsFile = join6(configDir, "credentials.json");
|
|
10428
|
+
if (existsSync5(credentialsFile)) {
|
|
9873
10429
|
try {
|
|
9874
|
-
const creds = JSON.parse(
|
|
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 =
|
|
9954
|
-
|
|
9955
|
-
const tokensFile =
|
|
9956
|
-
|
|
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 =
|
|
9999
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
10020
|
-
|
|
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 =
|
|
10027
|
-
const profileFile =
|
|
10028
|
-
if (
|
|
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 =
|
|
10036
|
-
if (
|
|
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
|
|
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 =
|
|
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
|
|
10288
|
-
import { join as
|
|
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
|
|
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(
|
|
10302
|
-
candidates.push(
|
|
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(
|
|
10307
|
-
candidates.push(
|
|
10808
|
+
candidates.push(join8(mainDir, "..", "dashboard", "dist"));
|
|
10809
|
+
candidates.push(join8(mainDir, "..", "..", "dashboard", "dist"));
|
|
10308
10810
|
}
|
|
10309
|
-
candidates.push(
|
|
10811
|
+
candidates.push(join8(process.cwd(), "dashboard", "dist"));
|
|
10310
10812
|
for (const candidate of candidates) {
|
|
10311
|
-
if (
|
|
10813
|
+
if (existsSync7(candidate))
|
|
10312
10814
|
return candidate;
|
|
10313
10815
|
}
|
|
10314
|
-
return
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
10601
|
-
const currentProfileFile =
|
|
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 (
|
|
11221
|
+
if (existsSync7(currentProfileFile)) {
|
|
10604
11222
|
try {
|
|
10605
|
-
current =
|
|
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 =
|
|
11270
|
+
const connectDir = join8(homedir6(), ".connectors");
|
|
10653
11271
|
const result = {};
|
|
10654
|
-
if (
|
|
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 =
|
|
10661
|
-
if (!
|
|
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(
|
|
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 =
|
|
10675
|
-
if (
|
|
11292
|
+
const configPath = join8(profilesDir, pEntry.name, "config.json");
|
|
11293
|
+
if (existsSync7(configPath)) {
|
|
10676
11294
|
try {
|
|
10677
|
-
const config = JSON.parse(
|
|
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 =
|
|
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 =
|
|
10719
|
-
const profilesDir =
|
|
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
|
-
|
|
10724
|
-
const profileFile =
|
|
10725
|
-
|
|
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 =
|
|
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 =
|
|
11403
|
+
const indexPath = join8(dashboardDir, "index.html");
|
|
10786
11404
|
const res = serveStaticFile(indexPath);
|
|
10787
11405
|
if (res)
|
|
10788
11406
|
return res;
|
|
@@ -10796,6 +11414,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
10796
11414
|
};
|
|
10797
11415
|
process.on("SIGINT", shutdown);
|
|
10798
11416
|
process.on("SIGTERM", shutdown);
|
|
11417
|
+
const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), exports_scheduler));
|
|
11418
|
+
const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
11419
|
+
startScheduler2(getDatabase2());
|
|
10799
11420
|
const url = `http://localhost:${port}`;
|
|
10800
11421
|
console.log(`Connectors Dashboard running at ${url}`);
|
|
10801
11422
|
if (shouldOpen) {
|
|
@@ -10810,6 +11431,12 @@ var activityLog, MAX_ACTIVITY_LOG = 100, MIME_TYPES, SECURITY_HEADERS, MAX_BODY_
|
|
|
10810
11431
|
var init_serve = __esm(() => {
|
|
10811
11432
|
init_agents();
|
|
10812
11433
|
init_rate();
|
|
11434
|
+
init_strip();
|
|
11435
|
+
init_llm();
|
|
11436
|
+
init_jobs();
|
|
11437
|
+
init_workflows();
|
|
11438
|
+
init_scheduler();
|
|
11439
|
+
init_workflow_runner();
|
|
10813
11440
|
init_registry();
|
|
10814
11441
|
init_installer();
|
|
10815
11442
|
init_auth();
|
|
@@ -10854,7 +11481,101 @@ var {
|
|
|
10854
11481
|
|
|
10855
11482
|
// src/cli/index.tsx
|
|
10856
11483
|
import chalk2 from "chalk";
|
|
10857
|
-
|
|
11484
|
+
// package.json
|
|
11485
|
+
var package_default = {
|
|
11486
|
+
name: "@hasna/connectors",
|
|
11487
|
+
version: "1.1.18",
|
|
11488
|
+
description: "Open source connector library - Install API connectors with a single command",
|
|
11489
|
+
type: "module",
|
|
11490
|
+
bin: {
|
|
11491
|
+
connectors: "./bin/index.js",
|
|
11492
|
+
"connectors-mcp": "./bin/mcp.js",
|
|
11493
|
+
"connectors-serve": "./bin/serve.js"
|
|
11494
|
+
},
|
|
11495
|
+
files: [
|
|
11496
|
+
"bin/",
|
|
11497
|
+
"dist/",
|
|
11498
|
+
"dashboard/dist/",
|
|
11499
|
+
"connectors/",
|
|
11500
|
+
"README.md"
|
|
11501
|
+
],
|
|
11502
|
+
exports: {
|
|
11503
|
+
".": {
|
|
11504
|
+
import: "./dist/index.js",
|
|
11505
|
+
types: "./dist/index.d.ts"
|
|
11506
|
+
}
|
|
11507
|
+
},
|
|
11508
|
+
main: "./dist/index.js",
|
|
11509
|
+
types: "./dist/index.d.ts",
|
|
11510
|
+
scripts: {
|
|
11511
|
+
build: "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --outDir ./dist",
|
|
11512
|
+
"build:dashboard": "cd dashboard && bun run build",
|
|
11513
|
+
postinstall: '[ "$SKIP_DASHBOARD" = "1" ] || [ ! -f dashboard/package.json ] || [ -d dashboard/node_modules ] || (cd dashboard && bun install)',
|
|
11514
|
+
dev: "bun run ./src/cli/index.tsx",
|
|
11515
|
+
typecheck: "tsc --noEmit",
|
|
11516
|
+
test: "bun test",
|
|
11517
|
+
"sdk:build": "cd sdk && bun run build",
|
|
11518
|
+
prepublishOnly: "bun test && bun run build"
|
|
11519
|
+
},
|
|
11520
|
+
keywords: [
|
|
11521
|
+
"connectors",
|
|
11522
|
+
"api",
|
|
11523
|
+
"cli",
|
|
11524
|
+
"typescript",
|
|
11525
|
+
"bun",
|
|
11526
|
+
"figma",
|
|
11527
|
+
"stripe",
|
|
11528
|
+
"github",
|
|
11529
|
+
"openai",
|
|
11530
|
+
"mcp",
|
|
11531
|
+
"model-context-protocol"
|
|
11532
|
+
],
|
|
11533
|
+
author: "Hasna",
|
|
11534
|
+
license: "Apache-2.0",
|
|
11535
|
+
devDependencies: {
|
|
11536
|
+
"@types/bun": "latest",
|
|
11537
|
+
"@types/react": "^18.2.0",
|
|
11538
|
+
"ink-testing-library": "^4.0.0",
|
|
11539
|
+
typescript: "^5"
|
|
11540
|
+
},
|
|
11541
|
+
dependencies: {
|
|
11542
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11543
|
+
chalk: "^5.3.0",
|
|
11544
|
+
commander: "^12.1.0",
|
|
11545
|
+
conf: "^13.0.1",
|
|
11546
|
+
"fast-xml-parser": "^5.5.3",
|
|
11547
|
+
ink: "^5.0.1",
|
|
11548
|
+
"ink-select-input": "^6.0.0",
|
|
11549
|
+
"ink-spinner": "^5.0.0",
|
|
11550
|
+
"ink-text-input": "^6.0.0",
|
|
11551
|
+
open: "^11.0.0",
|
|
11552
|
+
react: "^18.2.0",
|
|
11553
|
+
zod: "3"
|
|
11554
|
+
},
|
|
11555
|
+
engines: {
|
|
11556
|
+
bun: ">=1.0.0"
|
|
11557
|
+
},
|
|
11558
|
+
publishConfig: {
|
|
11559
|
+
registry: "https://registry.npmjs.org",
|
|
11560
|
+
access: "public"
|
|
11561
|
+
},
|
|
11562
|
+
repository: {
|
|
11563
|
+
type: "git",
|
|
11564
|
+
url: "git+https://github.com/hasna/connectors.git"
|
|
11565
|
+
},
|
|
11566
|
+
homepage: "https://github.com/hasna/connectors#readme",
|
|
11567
|
+
bugs: {
|
|
11568
|
+
url: "https://github.com/hasna/connectors/issues"
|
|
11569
|
+
}
|
|
11570
|
+
};
|
|
11571
|
+
|
|
11572
|
+
// src/cli/index.tsx
|
|
11573
|
+
init_llm();
|
|
11574
|
+
init_jobs();
|
|
11575
|
+
init_workflows();
|
|
11576
|
+
init_scheduler();
|
|
11577
|
+
init_workflow_runner();
|
|
11578
|
+
init_database();
|
|
10858
11579
|
|
|
10859
11580
|
// src/cli/components/App.tsx
|
|
10860
11581
|
import { useState as useState7 } from "react";
|
|
@@ -12328,9 +13049,9 @@ function App({ initialConnectors, overwrite = false }) {
|
|
|
12328
13049
|
init_registry();
|
|
12329
13050
|
init_installer();
|
|
12330
13051
|
init_auth();
|
|
12331
|
-
import { readdirSync as readdirSync4, existsSync as
|
|
12332
|
-
import { homedir as
|
|
12333
|
-
import { join as
|
|
13052
|
+
import { readdirSync as readdirSync4, existsSync as existsSync8, statSync as statSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
13053
|
+
import { homedir as homedir7 } from "os";
|
|
13054
|
+
import { join as join9, relative } from "path";
|
|
12334
13055
|
|
|
12335
13056
|
// src/lib/test-endpoints.ts
|
|
12336
13057
|
var TEST_ENDPOINTS = {
|
|
@@ -12531,17 +13252,17 @@ var TEST_ENDPOINTS = {
|
|
|
12531
13252
|
import { createInterface } from "readline";
|
|
12532
13253
|
|
|
12533
13254
|
// src/lib/runner.ts
|
|
12534
|
-
import { existsSync as
|
|
12535
|
-
import { join as
|
|
13255
|
+
import { existsSync as existsSync6 } from "fs";
|
|
13256
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
12536
13257
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12537
|
-
import { spawn } from "child_process";
|
|
13258
|
+
import { spawn as spawn3 } from "child_process";
|
|
12538
13259
|
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
12539
13260
|
function resolveConnectorsDir2() {
|
|
12540
|
-
const fromBin =
|
|
12541
|
-
if (
|
|
13261
|
+
const fromBin = join7(__dirname3, "..", "connectors");
|
|
13262
|
+
if (existsSync6(fromBin))
|
|
12542
13263
|
return fromBin;
|
|
12543
|
-
const fromSrc =
|
|
12544
|
-
if (
|
|
13264
|
+
const fromSrc = join7(__dirname3, "..", "..", "connectors");
|
|
13265
|
+
if (existsSync6(fromSrc))
|
|
12545
13266
|
return fromSrc;
|
|
12546
13267
|
return fromBin;
|
|
12547
13268
|
}
|
|
@@ -12581,13 +13302,13 @@ function buildEnvWithCredentials(connectorName, baseEnv) {
|
|
|
12581
13302
|
}
|
|
12582
13303
|
function getConnectorCliPath(name) {
|
|
12583
13304
|
const safeName = name.replace(/[^a-z0-9-]/g, "");
|
|
12584
|
-
const connectorDir =
|
|
12585
|
-
const cliPath =
|
|
12586
|
-
if (
|
|
13305
|
+
const connectorDir = join7(CONNECTORS_DIR2, `connect-${safeName}`);
|
|
13306
|
+
const cliPath = join7(connectorDir, "src", "cli", "index.ts");
|
|
13307
|
+
if (existsSync6(cliPath))
|
|
12587
13308
|
return cliPath;
|
|
12588
13309
|
return null;
|
|
12589
13310
|
}
|
|
12590
|
-
function
|
|
13311
|
+
function runConnectorCommand2(name, args, timeoutMs = 30000) {
|
|
12591
13312
|
const cliPath = getConnectorCliPath(name);
|
|
12592
13313
|
if (!cliPath) {
|
|
12593
13314
|
return Promise.resolve({
|
|
@@ -12598,7 +13319,7 @@ function runConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
12598
13319
|
});
|
|
12599
13320
|
}
|
|
12600
13321
|
return new Promise((resolve) => {
|
|
12601
|
-
const proc =
|
|
13322
|
+
const proc = spawn3("bun", ["run", cliPath, ...args], {
|
|
12602
13323
|
timeout: timeoutMs,
|
|
12603
13324
|
env: buildEnvWithCredentials(name, process.env),
|
|
12604
13325
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -12634,7 +13355,7 @@ async function getConnectorOperations(name) {
|
|
|
12634
13355
|
if (!cliPath) {
|
|
12635
13356
|
return { commands: [], helpText: "", hasCli: false };
|
|
12636
13357
|
}
|
|
12637
|
-
const result = await
|
|
13358
|
+
const result = await runConnectorCommand2(name, ["--help"]);
|
|
12638
13359
|
const helpText = result.stdout || result.stderr;
|
|
12639
13360
|
const commands = [];
|
|
12640
13361
|
const lines = helpText.split(`
|
|
@@ -12658,14 +13379,12 @@ async function getConnectorOperations(name) {
|
|
|
12658
13379
|
return { commands, helpText, hasCli: true };
|
|
12659
13380
|
}
|
|
12660
13381
|
async function getConnectorCommandHelp(name, command) {
|
|
12661
|
-
const result = await
|
|
13382
|
+
const result = await runConnectorCommand2(name, [command, "--help"]);
|
|
12662
13383
|
return result.stdout || result.stderr;
|
|
12663
13384
|
}
|
|
12664
13385
|
|
|
12665
13386
|
// src/cli/index.tsx
|
|
12666
13387
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
12667
|
-
var _require = createRequire(import.meta.url);
|
|
12668
|
-
var _pkg = _require("../package.json");
|
|
12669
13388
|
loadConnectorVersions();
|
|
12670
13389
|
var isTTY = process.stdout.isTTY ?? false;
|
|
12671
13390
|
var PRESETS = {
|
|
@@ -12677,7 +13396,7 @@ var PRESETS = {
|
|
|
12677
13396
|
commerce: { description: "Commerce and finance", connectors: ["stripe", "shopify", "revolut", "mercury", "pandadoc"] }
|
|
12678
13397
|
};
|
|
12679
13398
|
var program2 = new Command;
|
|
12680
|
-
program2.name("connectors").description("Install API connectors for your project").version(
|
|
13399
|
+
program2.name("connectors").description("Install API connectors for your project").version(package_default.version).enablePositionalOptions();
|
|
12681
13400
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
|
|
12682
13401
|
if (!isTTY) {
|
|
12683
13402
|
console.log(`Non-interactive environment detected. Use a subcommand:
|
|
@@ -12698,7 +13417,7 @@ Run 'connectors --help' for full usage.`);
|
|
|
12698
13417
|
function listFilesRecursive(dir, base = dir) {
|
|
12699
13418
|
const files = [];
|
|
12700
13419
|
for (const entry of readdirSync4(dir)) {
|
|
12701
|
-
const fullPath =
|
|
13420
|
+
const fullPath = join9(dir, entry);
|
|
12702
13421
|
if (statSync4(fullPath).isDirectory()) {
|
|
12703
13422
|
files.push(...listFilesRecursive(fullPath, base));
|
|
12704
13423
|
} else {
|
|
@@ -12747,7 +13466,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
|
|
|
12747
13466
|
}
|
|
12748
13467
|
if (options.dryRun) {
|
|
12749
13468
|
const installed = getInstalledConnectors();
|
|
12750
|
-
const destDir =
|
|
13469
|
+
const destDir = join9(process.cwd(), ".connectors");
|
|
12751
13470
|
const actions = [];
|
|
12752
13471
|
for (const name of connectors) {
|
|
12753
13472
|
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
@@ -12765,7 +13484,7 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
|
|
|
12765
13484
|
}
|
|
12766
13485
|
const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
12767
13486
|
const sourcePath = getConnectorPath(name);
|
|
12768
|
-
const destPath =
|
|
13487
|
+
const destPath = join9(destDir, connectorDirName);
|
|
12769
13488
|
const alreadyInstalled = installed.includes(name);
|
|
12770
13489
|
const files = listFilesRecursive(sourcePath);
|
|
12771
13490
|
const importLine = `export * as ${name} from './${connectorDirName}/src/index.js';`;
|
|
@@ -13269,17 +13988,17 @@ Updating ${toUpdate.length} connector(s)...
|
|
|
13269
13988
|
});
|
|
13270
13989
|
program2.command("status").option("--json", "Output as JSON", false).description("Show auth status of all configured connectors (project + global)").action((options) => {
|
|
13271
13990
|
const installed = getInstalledConnectors();
|
|
13272
|
-
const configDir =
|
|
13991
|
+
const configDir = join9(homedir7(), ".connectors");
|
|
13273
13992
|
const seen = new Set;
|
|
13274
13993
|
const allStatuses = [];
|
|
13275
13994
|
function buildStatusEntry(name, source) {
|
|
13276
13995
|
const auth = getAuthStatus(name);
|
|
13277
13996
|
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
13278
|
-
const currentProfileFile =
|
|
13997
|
+
const currentProfileFile = join9(configDir, connectorName, "current_profile");
|
|
13279
13998
|
let profile = "default";
|
|
13280
|
-
if (
|
|
13999
|
+
if (existsSync8(currentProfileFile)) {
|
|
13281
14000
|
try {
|
|
13282
|
-
profile =
|
|
14001
|
+
profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
|
|
13283
14002
|
} catch {}
|
|
13284
14003
|
}
|
|
13285
14004
|
let expiryLabel = null;
|
|
@@ -13315,13 +14034,13 @@ program2.command("status").option("--json", "Output as JSON", false).description
|
|
|
13315
14034
|
seen.add(name);
|
|
13316
14035
|
allStatuses.push(buildStatusEntry(name, "project"));
|
|
13317
14036
|
}
|
|
13318
|
-
if (
|
|
14037
|
+
if (existsSync8(configDir)) {
|
|
13319
14038
|
try {
|
|
13320
14039
|
const globalDirs = readdirSync4(configDir).filter((f) => {
|
|
13321
14040
|
if (!f.startsWith("connect-"))
|
|
13322
14041
|
return false;
|
|
13323
14042
|
try {
|
|
13324
|
-
return statSync4(
|
|
14043
|
+
return statSync4(join9(configDir, f)).isDirectory();
|
|
13325
14044
|
} catch {
|
|
13326
14045
|
return false;
|
|
13327
14046
|
}
|
|
@@ -13686,15 +14405,15 @@ program2.command("init").option("--json", "Output presets and suggestions as JSO
|
|
|
13686
14405
|
{ key: "commerce", emoji: "\uD83D\uDCB3", label: "Commerce", connectors: ["stripe", "shopify", "paypal", "revolut", "mercury"], description: "Commerce and finance" },
|
|
13687
14406
|
{ key: "google", emoji: "\uD83D\uDCC1", label: "Google Workspace", connectors: ["gmail", "googledrive", "googlecalendar", "googledocs", "googlesheets"], description: "Google Workspace suite" }
|
|
13688
14407
|
];
|
|
13689
|
-
const connectorsHome =
|
|
14408
|
+
const connectorsHome = join9(homedir7(), ".connectors");
|
|
13690
14409
|
let configuredCount = 0;
|
|
13691
14410
|
const configuredNames = [];
|
|
13692
14411
|
try {
|
|
13693
|
-
if (
|
|
13694
|
-
const entries = readdirSync4(connectorsHome).filter((e) => e.startsWith("connect-") && statSync4(
|
|
14412
|
+
if (existsSync8(connectorsHome)) {
|
|
14413
|
+
const entries = readdirSync4(connectorsHome).filter((e) => e.startsWith("connect-") && statSync4(join9(connectorsHome, e)).isDirectory());
|
|
13695
14414
|
for (const entry of entries) {
|
|
13696
|
-
const profilesDir =
|
|
13697
|
-
if (
|
|
14415
|
+
const profilesDir = join9(connectorsHome, entry, "profiles");
|
|
14416
|
+
if (existsSync8(profilesDir)) {
|
|
13698
14417
|
configuredCount++;
|
|
13699
14418
|
configuredNames.push(entry.replace(/^connect-/, ""));
|
|
13700
14419
|
}
|
|
@@ -13792,44 +14511,44 @@ function redactSecrets(obj) {
|
|
|
13792
14511
|
return obj;
|
|
13793
14512
|
}
|
|
13794
14513
|
program2.command("export").option("-o, --output <file>", "Write to file instead of stdout").option("--include-secrets", "Include secrets in plaintext (dangerous \u2014 use only for backup/restore)").description("Export all connector credentials as JSON backup").action((options) => {
|
|
13795
|
-
const connectDir =
|
|
14514
|
+
const connectDir = join9(homedir7(), ".connectors");
|
|
13796
14515
|
const result = {};
|
|
13797
|
-
if (
|
|
14516
|
+
if (existsSync8(connectDir)) {
|
|
13798
14517
|
for (const entry of readdirSync4(connectDir)) {
|
|
13799
|
-
const entryPath =
|
|
14518
|
+
const entryPath = join9(connectDir, entry);
|
|
13800
14519
|
if (!statSync4(entryPath).isDirectory() || !entry.startsWith("connect-"))
|
|
13801
14520
|
continue;
|
|
13802
14521
|
const connectorName = entry.replace(/^connect-/, "");
|
|
13803
14522
|
let credentials = undefined;
|
|
13804
|
-
const credentialsPath =
|
|
13805
|
-
if (
|
|
14523
|
+
const credentialsPath = join9(entryPath, "credentials.json");
|
|
14524
|
+
if (existsSync8(credentialsPath)) {
|
|
13806
14525
|
try {
|
|
13807
|
-
credentials = JSON.parse(
|
|
14526
|
+
credentials = JSON.parse(readFileSync6(credentialsPath, "utf-8"));
|
|
13808
14527
|
} catch {}
|
|
13809
14528
|
}
|
|
13810
|
-
const profilesDir =
|
|
13811
|
-
if (!
|
|
14529
|
+
const profilesDir = join9(entryPath, "profiles");
|
|
14530
|
+
if (!existsSync8(profilesDir) && !credentials)
|
|
13812
14531
|
continue;
|
|
13813
14532
|
const profiles = {};
|
|
13814
|
-
if (
|
|
14533
|
+
if (existsSync8(profilesDir)) {
|
|
13815
14534
|
for (const pEntry of readdirSync4(profilesDir)) {
|
|
13816
|
-
const pPath =
|
|
14535
|
+
const pPath = join9(profilesDir, pEntry);
|
|
13817
14536
|
if (statSync4(pPath).isFile() && pEntry.endsWith(".json")) {
|
|
13818
14537
|
try {
|
|
13819
|
-
profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(
|
|
14538
|
+
profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync6(pPath, "utf-8"));
|
|
13820
14539
|
} catch {}
|
|
13821
14540
|
} else if (statSync4(pPath).isDirectory()) {
|
|
13822
|
-
const configPath =
|
|
13823
|
-
const tokensPath =
|
|
14541
|
+
const configPath = join9(pPath, "config.json");
|
|
14542
|
+
const tokensPath = join9(pPath, "tokens.json");
|
|
13824
14543
|
let merged = {};
|
|
13825
|
-
if (
|
|
14544
|
+
if (existsSync8(configPath)) {
|
|
13826
14545
|
try {
|
|
13827
|
-
merged = { ...merged, ...JSON.parse(
|
|
14546
|
+
merged = { ...merged, ...JSON.parse(readFileSync6(configPath, "utf-8")) };
|
|
13828
14547
|
} catch {}
|
|
13829
14548
|
}
|
|
13830
|
-
if (
|
|
14549
|
+
if (existsSync8(tokensPath)) {
|
|
13831
14550
|
try {
|
|
13832
|
-
merged = { ...merged, ...JSON.parse(
|
|
14551
|
+
merged = { ...merged, ...JSON.parse(readFileSync6(tokensPath, "utf-8")) };
|
|
13833
14552
|
} catch {}
|
|
13834
14553
|
}
|
|
13835
14554
|
if (Object.keys(merged).length > 0)
|
|
@@ -13850,7 +14569,7 @@ program2.command("export").option("-o, --output <file>", "Write to file instead
|
|
|
13850
14569
|
}
|
|
13851
14570
|
const exportData = JSON.stringify(exportPayload, null, 2);
|
|
13852
14571
|
if (options.output) {
|
|
13853
|
-
|
|
14572
|
+
writeFileSync5(options.output, exportData);
|
|
13854
14573
|
console.log(chalk2.green(`\u2713 Exported to ${options.output}`));
|
|
13855
14574
|
} else {
|
|
13856
14575
|
console.log(exportData);
|
|
@@ -13864,7 +14583,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
13864
14583
|
chunks.push(chunk.toString());
|
|
13865
14584
|
raw = chunks.join("");
|
|
13866
14585
|
} else {
|
|
13867
|
-
if (!
|
|
14586
|
+
if (!existsSync8(file)) {
|
|
13868
14587
|
if (options.json) {
|
|
13869
14588
|
console.log(JSON.stringify({ error: `File not found: ${file}` }));
|
|
13870
14589
|
} else {
|
|
@@ -13873,7 +14592,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
13873
14592
|
process.exit(1);
|
|
13874
14593
|
return;
|
|
13875
14594
|
}
|
|
13876
|
-
raw =
|
|
14595
|
+
raw = readFileSync6(file, "utf-8");
|
|
13877
14596
|
}
|
|
13878
14597
|
let data;
|
|
13879
14598
|
try {
|
|
@@ -13896,25 +14615,25 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
13896
14615
|
process.exit(1);
|
|
13897
14616
|
return;
|
|
13898
14617
|
}
|
|
13899
|
-
const connectDir =
|
|
14618
|
+
const connectDir = join9(homedir7(), ".connectors");
|
|
13900
14619
|
let imported = 0;
|
|
13901
14620
|
for (const [connectorName, connData] of Object.entries(data.connectors)) {
|
|
13902
14621
|
if (!/^[a-z0-9-]+$/.test(connectorName))
|
|
13903
14622
|
continue;
|
|
13904
|
-
const connectorDir =
|
|
14623
|
+
const connectorDir = join9(connectDir, `connect-${connectorName}`);
|
|
13905
14624
|
if (connData.credentials && typeof connData.credentials === "object") {
|
|
13906
|
-
|
|
13907
|
-
|
|
14625
|
+
mkdirSync7(connectorDir, { recursive: true });
|
|
14626
|
+
writeFileSync5(join9(connectorDir, "credentials.json"), JSON.stringify(connData.credentials, null, 2));
|
|
13908
14627
|
imported++;
|
|
13909
14628
|
}
|
|
13910
14629
|
if (!connData.profiles || typeof connData.profiles !== "object")
|
|
13911
14630
|
continue;
|
|
13912
|
-
const profilesDir =
|
|
14631
|
+
const profilesDir = join9(connectorDir, "profiles");
|
|
13913
14632
|
for (const [profileName, config] of Object.entries(connData.profiles)) {
|
|
13914
14633
|
if (!config || typeof config !== "object")
|
|
13915
14634
|
continue;
|
|
13916
|
-
|
|
13917
|
-
|
|
14635
|
+
mkdirSync7(profilesDir, { recursive: true });
|
|
14636
|
+
writeFileSync5(join9(profilesDir, `${profileName}.json`), JSON.stringify(config, null, 2));
|
|
13918
14637
|
imported++;
|
|
13919
14638
|
}
|
|
13920
14639
|
}
|
|
@@ -13925,9 +14644,9 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
|
|
|
13925
14644
|
}
|
|
13926
14645
|
});
|
|
13927
14646
|
program2.command("auth-import").option("--json", "Output as JSON", false).option("-d, --dry-run", "Preview what would be imported without copying", false).option("--force", "Overwrite existing files in ~/.connectors/", false).description("Migrate auth tokens from ~/.connect/ to ~/.connectors/").action((options) => {
|
|
13928
|
-
const oldBase =
|
|
13929
|
-
const newBase =
|
|
13930
|
-
if (!
|
|
14647
|
+
const oldBase = join9(homedir7(), ".connect");
|
|
14648
|
+
const newBase = join9(homedir7(), ".connectors");
|
|
14649
|
+
if (!existsSync8(oldBase)) {
|
|
13931
14650
|
if (options.json) {
|
|
13932
14651
|
console.log(JSON.stringify({ imported: [], skipped: [], error: null, message: "No ~/.connect/ directory found" }));
|
|
13933
14652
|
} else {
|
|
@@ -13939,7 +14658,7 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
|
|
|
13939
14658
|
if (!name.startsWith("connect-"))
|
|
13940
14659
|
return false;
|
|
13941
14660
|
try {
|
|
13942
|
-
return statSync4(
|
|
14661
|
+
return statSync4(join9(oldBase, name)).isDirectory();
|
|
13943
14662
|
} catch {
|
|
13944
14663
|
return false;
|
|
13945
14664
|
}
|
|
@@ -13955,8 +14674,8 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
|
|
|
13955
14674
|
const imported = [];
|
|
13956
14675
|
const skipped = [];
|
|
13957
14676
|
for (const dirName of entries) {
|
|
13958
|
-
const oldDir =
|
|
13959
|
-
const newDir =
|
|
14677
|
+
const oldDir = join9(oldBase, dirName);
|
|
14678
|
+
const newDir = join9(newBase, dirName);
|
|
13960
14679
|
const connectorName = dirName.replace(/^connect-/, "");
|
|
13961
14680
|
const allFiles = listFilesRecursive(oldDir);
|
|
13962
14681
|
const authFiles = allFiles.filter((f) => {
|
|
@@ -13967,17 +14686,17 @@ program2.command("auth-import").option("--json", "Output as JSON", false).option
|
|
|
13967
14686
|
const copiedFiles = [];
|
|
13968
14687
|
const skippedFiles = [];
|
|
13969
14688
|
for (const relFile of authFiles) {
|
|
13970
|
-
const srcPath =
|
|
13971
|
-
const destPath =
|
|
13972
|
-
if (
|
|
14689
|
+
const srcPath = join9(oldDir, relFile);
|
|
14690
|
+
const destPath = join9(newDir, relFile);
|
|
14691
|
+
if (existsSync8(destPath) && !options.force) {
|
|
13973
14692
|
skippedFiles.push(relFile);
|
|
13974
14693
|
continue;
|
|
13975
14694
|
}
|
|
13976
14695
|
if (!options.dryRun) {
|
|
13977
|
-
const parentDir =
|
|
13978
|
-
|
|
13979
|
-
const content =
|
|
13980
|
-
|
|
14696
|
+
const parentDir = join9(destPath, "..");
|
|
14697
|
+
mkdirSync7(parentDir, { recursive: true });
|
|
14698
|
+
const content = readFileSync6(srcPath);
|
|
14699
|
+
writeFileSync5(destPath, content);
|
|
13981
14700
|
}
|
|
13982
14701
|
copiedFiles.push(relFile);
|
|
13983
14702
|
}
|
|
@@ -14222,7 +14941,7 @@ program2.command("env").option("-o, --output <file>", "Write to file instead of
|
|
|
14222
14941
|
`) + `
|
|
14223
14942
|
`;
|
|
14224
14943
|
if (options.output) {
|
|
14225
|
-
|
|
14944
|
+
writeFileSync5(options.output, output);
|
|
14226
14945
|
console.log(chalk2.green(`\u2713 Written to ${options.output} (${vars.length} variables)`));
|
|
14227
14946
|
} else {
|
|
14228
14947
|
console.log(output);
|
|
@@ -14250,7 +14969,7 @@ Available presets:
|
|
|
14250
14969
|
`));
|
|
14251
14970
|
});
|
|
14252
14971
|
program2.command("whoami").option("--json", "Output as JSON", false).description("Show current setup: config dir, installed connectors, auth status").action((options) => {
|
|
14253
|
-
const configDir =
|
|
14972
|
+
const configDir = join9(homedir7(), ".connectors");
|
|
14254
14973
|
const installed = getInstalledConnectors();
|
|
14255
14974
|
const version = "0.3.1";
|
|
14256
14975
|
let configured = 0;
|
|
@@ -14264,23 +14983,23 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
14264
14983
|
configured++;
|
|
14265
14984
|
else
|
|
14266
14985
|
unconfigured++;
|
|
14267
|
-
const connectorConfigDir =
|
|
14268
|
-
const currentProfileFile =
|
|
14986
|
+
const connectorConfigDir = join9(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
|
|
14987
|
+
const currentProfileFile = join9(connectorConfigDir, "current_profile");
|
|
14269
14988
|
let profile = "default";
|
|
14270
|
-
if (
|
|
14989
|
+
if (existsSync8(currentProfileFile)) {
|
|
14271
14990
|
try {
|
|
14272
|
-
profile =
|
|
14991
|
+
profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
|
|
14273
14992
|
} catch {}
|
|
14274
14993
|
}
|
|
14275
14994
|
connectorDetails.push({ name, configured: auth.configured, authType: auth.type, profile, source: "project" });
|
|
14276
14995
|
}
|
|
14277
|
-
if (
|
|
14996
|
+
if (existsSync8(configDir)) {
|
|
14278
14997
|
try {
|
|
14279
14998
|
const globalDirs = readdirSync4(configDir).filter((f) => {
|
|
14280
14999
|
if (!f.startsWith("connect-"))
|
|
14281
15000
|
return false;
|
|
14282
15001
|
try {
|
|
14283
|
-
return statSync4(
|
|
15002
|
+
return statSync4(join9(configDir, f)).isDirectory();
|
|
14284
15003
|
} catch {
|
|
14285
15004
|
return false;
|
|
14286
15005
|
}
|
|
@@ -14294,11 +15013,11 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
14294
15013
|
continue;
|
|
14295
15014
|
seen.add(name);
|
|
14296
15015
|
configured++;
|
|
14297
|
-
const currentProfileFile =
|
|
15016
|
+
const currentProfileFile = join9(configDir, dir, "current_profile");
|
|
14298
15017
|
let profile = "default";
|
|
14299
|
-
if (
|
|
15018
|
+
if (existsSync8(currentProfileFile)) {
|
|
14300
15019
|
try {
|
|
14301
|
-
profile =
|
|
15020
|
+
profile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
|
|
14302
15021
|
} catch {}
|
|
14303
15022
|
}
|
|
14304
15023
|
connectorDetails.push({ name, configured: true, authType: auth.type, profile, source: "global" });
|
|
@@ -14309,7 +15028,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
14309
15028
|
console.log(JSON.stringify({
|
|
14310
15029
|
version,
|
|
14311
15030
|
configDir,
|
|
14312
|
-
configDirExists:
|
|
15031
|
+
configDirExists: existsSync8(configDir),
|
|
14313
15032
|
installed: installed.length,
|
|
14314
15033
|
configured,
|
|
14315
15034
|
unconfigured,
|
|
@@ -14321,7 +15040,7 @@ program2.command("whoami").option("--json", "Output as JSON", false).description
|
|
|
14321
15040
|
Connectors Setup
|
|
14322
15041
|
`));
|
|
14323
15042
|
console.log(` Version: ${chalk2.cyan(version)}`);
|
|
14324
|
-
console.log(` Config: ${configDir}${
|
|
15043
|
+
console.log(` Config: ${configDir}${existsSync8(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
|
|
14325
15044
|
console.log(` Installed: ${installed.length} connector${installed.length !== 1 ? "s" : ""}`);
|
|
14326
15045
|
console.log(` Configured: ${chalk2.green(String(configured))} ready, ${unconfigured > 0 ? chalk2.red(String(unconfigured)) : chalk2.dim("0")} need auth`);
|
|
14327
15046
|
const projectConnectors = connectorDetails.filter((c) => c.source === "project");
|
|
@@ -14411,18 +15130,18 @@ Testing connector credentials...
|
|
|
14411
15130
|
}
|
|
14412
15131
|
}
|
|
14413
15132
|
if (!apiKey) {
|
|
14414
|
-
const connectorConfigDir =
|
|
15133
|
+
const connectorConfigDir = join9(homedir7(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
|
|
14415
15134
|
let currentProfile = "default";
|
|
14416
|
-
const currentProfileFile =
|
|
14417
|
-
if (
|
|
15135
|
+
const currentProfileFile = join9(connectorConfigDir, "current_profile");
|
|
15136
|
+
if (existsSync8(currentProfileFile)) {
|
|
14418
15137
|
try {
|
|
14419
|
-
currentProfile =
|
|
15138
|
+
currentProfile = readFileSync6(currentProfileFile, "utf-8").trim() || "default";
|
|
14420
15139
|
} catch {}
|
|
14421
15140
|
}
|
|
14422
|
-
const tokensFile =
|
|
14423
|
-
if (
|
|
15141
|
+
const tokensFile = join9(connectorConfigDir, "profiles", currentProfile, "tokens.json");
|
|
15142
|
+
if (existsSync8(tokensFile)) {
|
|
14424
15143
|
try {
|
|
14425
|
-
const tokens = JSON.parse(
|
|
15144
|
+
const tokens = JSON.parse(readFileSync6(tokensFile, "utf-8"));
|
|
14426
15145
|
const isExpired = tokens.expiresAt && Date.now() >= tokens.expiresAt - 60000;
|
|
14427
15146
|
if (isExpired && tokens.refreshToken) {
|
|
14428
15147
|
try {
|
|
@@ -14440,19 +15159,19 @@ Testing connector credentials...
|
|
|
14440
15159
|
} catch {}
|
|
14441
15160
|
}
|
|
14442
15161
|
if (!apiKey) {
|
|
14443
|
-
const profileFile =
|
|
14444
|
-
if (
|
|
15162
|
+
const profileFile = join9(connectorConfigDir, "profiles", `${currentProfile}.json`);
|
|
15163
|
+
if (existsSync8(profileFile)) {
|
|
14445
15164
|
try {
|
|
14446
|
-
const config = JSON.parse(
|
|
15165
|
+
const config = JSON.parse(readFileSync6(profileFile, "utf-8"));
|
|
14447
15166
|
apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
|
|
14448
15167
|
} catch {}
|
|
14449
15168
|
}
|
|
14450
15169
|
}
|
|
14451
15170
|
if (!apiKey) {
|
|
14452
|
-
const profileDirConfig =
|
|
14453
|
-
if (
|
|
15171
|
+
const profileDirConfig = join9(connectorConfigDir, "profiles", currentProfile, "config.json");
|
|
15172
|
+
if (existsSync8(profileDirConfig)) {
|
|
14454
15173
|
try {
|
|
14455
|
-
const config = JSON.parse(
|
|
15174
|
+
const config = JSON.parse(readFileSync6(profileDirConfig, "utf-8"));
|
|
14456
15175
|
apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
|
|
14457
15176
|
} catch {}
|
|
14458
15177
|
}
|
|
@@ -14589,7 +15308,7 @@ program2.command("run").description("Execute an API operation on a connector").a
|
|
|
14589
15308
|
console.error(chalk2.yellow(`No command specified. Run ${chalk2.white(`connectors ops ${name}`)} to see available operations.`));
|
|
14590
15309
|
process.exit(1);
|
|
14591
15310
|
}
|
|
14592
|
-
const result = await
|
|
15311
|
+
const result = await runConnectorCommand2(name, args, parseInt(options.timeout));
|
|
14593
15312
|
if (result.stdout) {
|
|
14594
15313
|
console.log(result.stdout);
|
|
14595
15314
|
}
|
|
@@ -14619,7 +15338,7 @@ Setting up ${meta.displayName}...
|
|
|
14619
15338
|
const alreadyInstalled = installed.includes(meta.name);
|
|
14620
15339
|
let installResult;
|
|
14621
15340
|
if (alreadyInstalled && !options.overwrite) {
|
|
14622
|
-
installResult = { success: true, path:
|
|
15341
|
+
installResult = { success: true, path: join9(process.cwd(), ".connectors", `connect-${meta.name}`) };
|
|
14623
15342
|
if (!options.json) {
|
|
14624
15343
|
console.log(` ${chalk2.green("\u2713")} Already installed`);
|
|
14625
15344
|
}
|
|
@@ -14745,4 +15464,254 @@ Setting up ${meta.displayName}...
|
|
|
14745
15464
|
}
|
|
14746
15465
|
process.exit(0);
|
|
14747
15466
|
});
|
|
15467
|
+
var jobsCmd = program2.command("jobs").description("Manage scheduled connector jobs");
|
|
15468
|
+
jobsCmd.command("add").description("Add a scheduled job").requiredOption("--name <name>", "Job name").requiredOption("--connector <connector>", "Connector name").requiredOption("--command <command>", "Command to run").requiredOption("--cron <cron>", "Cron expression (5-field)").option("--args <args>", "Command args (space-separated)").option("--strip", "Apply LLM stripping to output").option("--json", "Output as JSON").action((options) => {
|
|
15469
|
+
const db = getDatabase();
|
|
15470
|
+
const args = options.args ? options.args.split(" ") : [];
|
|
15471
|
+
const job = createJob({ name: options.name, connector: options.connector, command: options.command, args, cron: options.cron, strip: !!options.strip }, db);
|
|
15472
|
+
if (options.json) {
|
|
15473
|
+
console.log(JSON.stringify(job));
|
|
15474
|
+
return;
|
|
15475
|
+
}
|
|
15476
|
+
console.log(chalk2.green("\u2713") + ` Job created: ${job.name} (${job.cron})`);
|
|
15477
|
+
});
|
|
15478
|
+
jobsCmd.command("list").description("List all jobs").option("--json", "Output as JSON").action((options) => {
|
|
15479
|
+
const jobs = listJobs(getDatabase());
|
|
15480
|
+
if (options.json) {
|
|
15481
|
+
console.log(JSON.stringify(jobs));
|
|
15482
|
+
return;
|
|
15483
|
+
}
|
|
15484
|
+
if (jobs.length === 0) {
|
|
15485
|
+
console.log(chalk2.dim("No jobs configured."));
|
|
15486
|
+
return;
|
|
15487
|
+
}
|
|
15488
|
+
for (const j of jobs) {
|
|
15489
|
+
const status = j.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
|
|
15490
|
+
const strip = j.strip ? chalk2.cyan(" [strip]") : "";
|
|
15491
|
+
console.log(` ${j.name.padEnd(20)} ${j.connector}.${j.command.padEnd(16)} ${j.cron.padEnd(15)} ${status}${strip}`);
|
|
15492
|
+
}
|
|
15493
|
+
});
|
|
15494
|
+
jobsCmd.command("run").description("Manually trigger a job").argument("<name>", "Job name").option("--json", "Output as JSON").action(async (name, options) => {
|
|
15495
|
+
const db = getDatabase();
|
|
15496
|
+
const job = getJobByName(name, db);
|
|
15497
|
+
if (!job) {
|
|
15498
|
+
console.error(chalk2.red(`Job "${name}" not found`));
|
|
15499
|
+
process.exit(1);
|
|
15500
|
+
}
|
|
15501
|
+
if (!options.json)
|
|
15502
|
+
console.log(chalk2.dim(`Running ${job.connector} ${job.command}...`));
|
|
15503
|
+
const result = await triggerJob(job, db);
|
|
15504
|
+
if (options.json) {
|
|
15505
|
+
console.log(JSON.stringify(result));
|
|
15506
|
+
return;
|
|
15507
|
+
}
|
|
15508
|
+
const icon = result.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
15509
|
+
console.log(`${icon} Run ${result.run_id} \u2014 exit ${result.exit_code}`);
|
|
15510
|
+
if (result.output)
|
|
15511
|
+
console.log(result.output.slice(0, 2000));
|
|
15512
|
+
});
|
|
15513
|
+
jobsCmd.command("logs").description("Show recent runs for a job").argument("<name>", "Job name").option("--limit <n>", "Max results", "10").option("--json", "Output as JSON").action((name, options) => {
|
|
15514
|
+
const db = getDatabase();
|
|
15515
|
+
const job = getJobByName(name, db);
|
|
15516
|
+
if (!job) {
|
|
15517
|
+
console.error(chalk2.red(`Job "${name}" not found`));
|
|
15518
|
+
process.exit(1);
|
|
15519
|
+
}
|
|
15520
|
+
const runs = listJobRuns(job.id, parseInt(options.limit), db);
|
|
15521
|
+
if (options.json) {
|
|
15522
|
+
console.log(JSON.stringify(runs));
|
|
15523
|
+
return;
|
|
15524
|
+
}
|
|
15525
|
+
if (runs.length === 0) {
|
|
15526
|
+
console.log(chalk2.dim("No runs yet."));
|
|
15527
|
+
return;
|
|
15528
|
+
}
|
|
15529
|
+
for (const r of runs) {
|
|
15530
|
+
const icon = r.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
15531
|
+
console.log(` ${icon} ${r.started_at.slice(0, 19)} \u2014 exit ${r.exit_code ?? "?"}`);
|
|
15532
|
+
}
|
|
15533
|
+
});
|
|
15534
|
+
jobsCmd.command("enable").description("Enable a job").argument("<name>").action((name) => {
|
|
15535
|
+
const db = getDatabase();
|
|
15536
|
+
const job = getJobByName(name, db);
|
|
15537
|
+
if (!job) {
|
|
15538
|
+
console.error(chalk2.red(`Job "${name}" not found`));
|
|
15539
|
+
process.exit(1);
|
|
15540
|
+
}
|
|
15541
|
+
updateJob(job.id, { enabled: true }, db);
|
|
15542
|
+
console.log(chalk2.green("\u2713") + ` Job "${name}" enabled`);
|
|
15543
|
+
});
|
|
15544
|
+
jobsCmd.command("disable").description("Disable a job").argument("<name>").action((name) => {
|
|
15545
|
+
const db = getDatabase();
|
|
15546
|
+
const job = getJobByName(name, db);
|
|
15547
|
+
if (!job) {
|
|
15548
|
+
console.error(chalk2.red(`Job "${name}" not found`));
|
|
15549
|
+
process.exit(1);
|
|
15550
|
+
}
|
|
15551
|
+
updateJob(job.id, { enabled: false }, db);
|
|
15552
|
+
console.log(chalk2.green("\u2713") + ` Job "${name}" disabled`);
|
|
15553
|
+
});
|
|
15554
|
+
jobsCmd.command("delete").description("Delete a job").argument("<name>").action((name) => {
|
|
15555
|
+
const db = getDatabase();
|
|
15556
|
+
const job = getJobByName(name, db);
|
|
15557
|
+
if (!job) {
|
|
15558
|
+
console.error(chalk2.red(`Job "${name}" not found`));
|
|
15559
|
+
process.exit(1);
|
|
15560
|
+
}
|
|
15561
|
+
deleteJob(job.id, db);
|
|
15562
|
+
console.log(chalk2.green("\u2713") + ` Job "${name}" deleted`);
|
|
15563
|
+
});
|
|
15564
|
+
var workflowsCmd = program2.command("workflows").description("Manage connector workflows (sequential pipelines)");
|
|
15565
|
+
workflowsCmd.command("add").description("Create a workflow from a JSON steps array").requiredOption("--name <name>", "Workflow name").requiredOption("--steps <json>", `Steps JSON array, e.g. '[{"connector":"stripe","command":"products list"}]'`).option("--json", "Output as JSON").action((options) => {
|
|
15566
|
+
let steps;
|
|
15567
|
+
try {
|
|
15568
|
+
steps = JSON.parse(options.steps);
|
|
15569
|
+
} catch {
|
|
15570
|
+
console.error(chalk2.red("Invalid JSON for --steps"));
|
|
15571
|
+
process.exit(1);
|
|
15572
|
+
}
|
|
15573
|
+
const wf = createWorkflow({ name: options.name, steps }, getDatabase());
|
|
15574
|
+
if (options.json) {
|
|
15575
|
+
console.log(JSON.stringify(wf));
|
|
15576
|
+
return;
|
|
15577
|
+
}
|
|
15578
|
+
console.log(chalk2.green("\u2713") + ` Workflow "${wf.name}" created (${wf.steps.length} steps)`);
|
|
15579
|
+
});
|
|
15580
|
+
workflowsCmd.command("list").description("List all workflows").option("--json", "Output as JSON").action((options) => {
|
|
15581
|
+
const wfs = listWorkflows(getDatabase());
|
|
15582
|
+
if (options.json) {
|
|
15583
|
+
console.log(JSON.stringify(wfs));
|
|
15584
|
+
return;
|
|
15585
|
+
}
|
|
15586
|
+
if (wfs.length === 0) {
|
|
15587
|
+
console.log(chalk2.dim("No workflows configured."));
|
|
15588
|
+
return;
|
|
15589
|
+
}
|
|
15590
|
+
for (const wf of wfs) {
|
|
15591
|
+
const status = wf.enabled ? chalk2.green("enabled") : chalk2.dim("disabled");
|
|
15592
|
+
console.log(` ${wf.name.padEnd(20)} ${String(wf.steps.length).padStart(2)} steps ${status}`);
|
|
15593
|
+
}
|
|
15594
|
+
});
|
|
15595
|
+
workflowsCmd.command("run").description("Run a workflow").argument("<name>", "Workflow name").option("--json", "Output as JSON").action(async (name, options) => {
|
|
15596
|
+
const wf = getWorkflowByName(name, getDatabase());
|
|
15597
|
+
if (!wf) {
|
|
15598
|
+
console.error(chalk2.red(`Workflow "${name}" not found`));
|
|
15599
|
+
process.exit(1);
|
|
15600
|
+
}
|
|
15601
|
+
if (!options.json)
|
|
15602
|
+
console.log(chalk2.dim(`Running workflow "${wf.name}" (${wf.steps.length} steps)...`));
|
|
15603
|
+
const result = await runWorkflow(wf);
|
|
15604
|
+
if (options.json) {
|
|
15605
|
+
console.log(JSON.stringify(result));
|
|
15606
|
+
return;
|
|
15607
|
+
}
|
|
15608
|
+
for (const step of result.steps) {
|
|
15609
|
+
const icon = step.exit_code === 0 ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
15610
|
+
console.log(` ${icon} Step ${step.step}: ${step.connector} ${step.command}`);
|
|
15611
|
+
}
|
|
15612
|
+
console.log(result.success ? chalk2.green(`
|
|
15613
|
+
Workflow completed`) : chalk2.red(`
|
|
15614
|
+
Workflow failed`));
|
|
15615
|
+
});
|
|
15616
|
+
workflowsCmd.command("delete").description("Delete a workflow").argument("<name>").action((name) => {
|
|
15617
|
+
const wf = getWorkflowByName(name, getDatabase());
|
|
15618
|
+
if (!wf) {
|
|
15619
|
+
console.error(chalk2.red(`Workflow "${name}" not found`));
|
|
15620
|
+
process.exit(1);
|
|
15621
|
+
}
|
|
15622
|
+
deleteWorkflow(wf.id, getDatabase());
|
|
15623
|
+
console.log(chalk2.green("\u2713") + ` Workflow "${name}" deleted`);
|
|
15624
|
+
});
|
|
15625
|
+
var llmCmd = program2.command("llm").description("Manage LLM provider for output stripping");
|
|
15626
|
+
llmCmd.command("set").description("Configure LLM provider and API key").requiredOption("--provider <provider>", "Provider: cerebras, groq, openai, anthropic").requiredOption("--key <key>", "API key").option("--model <model>", "Model name (defaults to provider default)").option("--json", "Output as JSON").action((options) => {
|
|
15627
|
+
const provider = options.provider;
|
|
15628
|
+
const validProviders = ["cerebras", "groq", "openai", "anthropic"];
|
|
15629
|
+
if (!validProviders.includes(provider)) {
|
|
15630
|
+
console.error(chalk2.red(`Unknown provider "${provider}". Valid: ${validProviders.join(", ")}`));
|
|
15631
|
+
process.exit(1);
|
|
15632
|
+
}
|
|
15633
|
+
const model = options.model || PROVIDER_DEFAULTS[provider].model;
|
|
15634
|
+
const existing = getLlmConfig();
|
|
15635
|
+
saveLlmConfig({ provider, model, api_key: options.key, strip: existing?.strip ?? false });
|
|
15636
|
+
if (options.json) {
|
|
15637
|
+
console.log(JSON.stringify({ provider, model, key: maskKey(options.key), strip: existing?.strip ?? false }));
|
|
15638
|
+
} else {
|
|
15639
|
+
console.log(chalk2.green("\u2713") + ` LLM configured: ${provider} / ${model}`);
|
|
15640
|
+
console.log(` Key: ${maskKey(options.key)}`);
|
|
15641
|
+
console.log(` Strip: ${existing?.strip ? chalk2.green("enabled") : chalk2.dim("disabled")} (run 'connectors llm strip enable' to turn on)`);
|
|
15642
|
+
}
|
|
15643
|
+
});
|
|
15644
|
+
llmCmd.command("status").description("Show current LLM configuration").option("--json", "Output as JSON").action((options) => {
|
|
15645
|
+
const config = getLlmConfig();
|
|
15646
|
+
if (!config) {
|
|
15647
|
+
if (options.json)
|
|
15648
|
+
console.log(JSON.stringify({ configured: false }));
|
|
15649
|
+
else
|
|
15650
|
+
console.log(chalk2.dim("No LLM configured. Run: connectors llm set --provider <provider> --key <key>"));
|
|
15651
|
+
return;
|
|
15652
|
+
}
|
|
15653
|
+
if (options.json) {
|
|
15654
|
+
console.log(JSON.stringify({ configured: true, provider: config.provider, model: config.model, key: maskKey(config.api_key), strip: config.strip }));
|
|
15655
|
+
} else {
|
|
15656
|
+
console.log(chalk2.bold("LLM Configuration"));
|
|
15657
|
+
console.log(` Provider : ${config.provider}`);
|
|
15658
|
+
console.log(` Model : ${config.model}`);
|
|
15659
|
+
console.log(` Key : ${maskKey(config.api_key)}`);
|
|
15660
|
+
console.log(` Strip : ${config.strip ? chalk2.green("enabled") : chalk2.red("disabled")}`);
|
|
15661
|
+
}
|
|
15662
|
+
});
|
|
15663
|
+
llmCmd.command("strip").description("Enable or disable global output stripping").argument("<action>", "enable or disable").action((action) => {
|
|
15664
|
+
if (action !== "enable" && action !== "disable") {
|
|
15665
|
+
console.error(chalk2.red('Action must be "enable" or "disable"'));
|
|
15666
|
+
process.exit(1);
|
|
15667
|
+
}
|
|
15668
|
+
try {
|
|
15669
|
+
setLlmStrip(action === "enable");
|
|
15670
|
+
console.log(chalk2.green("\u2713") + ` Output stripping ${action}d`);
|
|
15671
|
+
} catch (e) {
|
|
15672
|
+
console.error(chalk2.red(String(e instanceof Error ? e.message : e)));
|
|
15673
|
+
process.exit(1);
|
|
15674
|
+
}
|
|
15675
|
+
});
|
|
15676
|
+
llmCmd.command("test").description("Test current LLM configuration with a sample prompt").option("--json", "Output as JSON").action(async (options) => {
|
|
15677
|
+
const config = getLlmConfig();
|
|
15678
|
+
if (!config) {
|
|
15679
|
+
console.error(chalk2.red("No LLM configured. Run: connectors llm set --provider <provider> --key <key>"));
|
|
15680
|
+
process.exit(1);
|
|
15681
|
+
}
|
|
15682
|
+
if (!options.json)
|
|
15683
|
+
console.log(chalk2.dim(`Testing ${config.provider} / ${config.model}...`));
|
|
15684
|
+
try {
|
|
15685
|
+
const client = new LLMClient(config);
|
|
15686
|
+
const result = await client.complete('You are a helpful assistant. Respond with exactly: {"status":"ok"}', "ping");
|
|
15687
|
+
if (options.json) {
|
|
15688
|
+
console.log(JSON.stringify({ success: true, provider: result.provider, model: result.model, latency_ms: result.latency_ms, response: result.content }));
|
|
15689
|
+
} else {
|
|
15690
|
+
console.log(chalk2.green("\u2713") + ` Response received in ${result.latency_ms}ms`);
|
|
15691
|
+
console.log(` ${result.content.trim()}`);
|
|
15692
|
+
}
|
|
15693
|
+
} catch (e) {
|
|
15694
|
+
if (options.json)
|
|
15695
|
+
console.log(JSON.stringify({ success: false, error: String(e instanceof Error ? e.message : e) }));
|
|
15696
|
+
else
|
|
15697
|
+
console.error(chalk2.red("\u2717 " + String(e instanceof Error ? e.message : e)));
|
|
15698
|
+
process.exit(1);
|
|
15699
|
+
}
|
|
15700
|
+
});
|
|
15701
|
+
llmCmd.command("providers").description("List supported LLM providers").option("--json", "Output as JSON").action((options) => {
|
|
15702
|
+
const providers = [
|
|
15703
|
+
{ name: "cerebras", baseUrl: "https://api.cerebras.ai/v1", defaultModel: PROVIDER_DEFAULTS.cerebras.model, compatible: "OpenAI" },
|
|
15704
|
+
{ name: "groq", baseUrl: "https://api.groq.com/openai/v1", defaultModel: PROVIDER_DEFAULTS.groq.model, compatible: "OpenAI" },
|
|
15705
|
+
{ name: "openai", baseUrl: "https://api.openai.com/v1", defaultModel: PROVIDER_DEFAULTS.openai.model, compatible: "OpenAI" },
|
|
15706
|
+
{ name: "anthropic", baseUrl: "https://api.anthropic.com/v1", defaultModel: PROVIDER_DEFAULTS.anthropic.model, compatible: "Anthropic" }
|
|
15707
|
+
];
|
|
15708
|
+
if (options.json) {
|
|
15709
|
+
console.log(JSON.stringify(providers));
|
|
15710
|
+
return;
|
|
15711
|
+
}
|
|
15712
|
+
console.log(chalk2.bold("Supported LLM Providers"));
|
|
15713
|
+
for (const p of providers) {
|
|
15714
|
+
console.log(` ${chalk2.cyan(p.name.padEnd(12))} ${p.defaultModel.padEnd(30)} ${chalk2.dim(p.baseUrl)}`);
|
|
15715
|
+
}
|
|
15716
|
+
});
|
|
14748
15717
|
program2.parse();
|