@outlit/cli 1.3.1 → 1.5.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.
Files changed (2) hide show
  1. package/dist/cli.js +2434 -948
  2. package/package.json +5 -3
package/dist/cli.js CHANGED
@@ -104,7 +104,7 @@ var package_default;
104
104
  var init_package = __esm(() => {
105
105
  package_default = {
106
106
  name: "@outlit/cli",
107
- version: "1.3.1",
107
+ version: "1.5.0",
108
108
  description: "CLI for Outlit customer intelligence platform",
109
109
  license: "Apache-2.0",
110
110
  repository: {
@@ -120,7 +120,9 @@ var init_package = __esm(() => {
120
120
  bin: {
121
121
  outlit: "./dist/cli.js"
122
122
  },
123
- files: ["dist"],
123
+ files: [
124
+ "dist"
125
+ ],
124
126
  scripts: {
125
127
  dev: "bun run src/cli.ts",
126
128
  "dev:watch": "bun --watch src/cli.ts",
@@ -136,15 +138,46 @@ var init_package = __esm(() => {
136
138
  "@clack/prompts": "^1.0.1"
137
139
  },
138
140
  devDependencies: {
139
- typescript: "^5.5.0",
141
+ typescript: "^5.9.3",
140
142
  "@types/bun": "latest"
141
143
  }
142
144
  };
143
145
  });
144
146
 
145
147
  // src/lib/tty.ts
148
+ import { execFileSync } from "node:child_process";
149
+ import { createInterface } from "node:readline";
146
150
  function openBrowserCmd() {
147
- return process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
151
+ return process.platform === "darwin" ? "open" : "xdg-open";
152
+ }
153
+ function openBrowser(url) {
154
+ try {
155
+ if (process.platform === "win32") {
156
+ execFileSync("cmd", ["/c", "start", "", url.replace(/&/g, "^&")], { stdio: "ignore" });
157
+ } else {
158
+ execFileSync(openBrowserCmd(), [url], { stdio: "ignore" });
159
+ }
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+ function promptInput(label, opts) {
166
+ return new Promise((resolve) => {
167
+ const rl = createInterface({
168
+ input: process.stdin,
169
+ output: opts?.secret ? undefined : process.stderr,
170
+ terminal: !opts?.secret
171
+ });
172
+ process.stderr.write(`${label}: `);
173
+ rl.question("", (answer) => {
174
+ rl.close();
175
+ if (opts?.secret)
176
+ process.stderr.write(`
177
+ `);
178
+ resolve(answer.trim());
179
+ });
180
+ });
148
181
  }
149
182
  function isInteractive() {
150
183
  if (!process.stdin.isTTY || !process.stdout.isTTY)
@@ -189,6 +222,70 @@ var init_output = __esm(() => {
189
222
  init_tty();
190
223
  });
191
224
 
225
+ // src/lib/config.ts
226
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
227
+ import { homedir } from "node:os";
228
+ import { dirname, join } from "node:path";
229
+ function isEnoentError(err) {
230
+ return err instanceof Error && err.code === "ENOENT";
231
+ }
232
+ function splitCsv(value) {
233
+ return value.split(",").map((s) => s.trim());
234
+ }
235
+ function getConfigDir() {
236
+ if (process.platform === "win32") {
237
+ return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "outlit");
238
+ }
239
+ return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "outlit");
240
+ }
241
+ function resolveApiKey(flagValue) {
242
+ if (flagValue)
243
+ return { key: flagValue, source: "flag" };
244
+ const envKey = process.env.OUTLIT_API_KEY;
245
+ if (envKey)
246
+ return { key: envKey, source: "env" };
247
+ const credPath = join(getConfigDir(), "credentials.json");
248
+ if (existsSync(credPath)) {
249
+ try {
250
+ const raw = readFileSync(credPath, "utf-8");
251
+ const config = JSON.parse(raw);
252
+ if (config.apiKey)
253
+ return { key: config.apiKey, source: "config" };
254
+ } catch {}
255
+ }
256
+ return null;
257
+ }
258
+ function maskKey(key) {
259
+ if (key.length <= 9)
260
+ return key;
261
+ return `${key.slice(0, 5)}...${key.slice(-4)}`;
262
+ }
263
+ function requireCredential(flagApiKey, json) {
264
+ const credential = resolveApiKey(flagApiKey);
265
+ if (!credential) {
266
+ return outputError({
267
+ message: "Not authenticated. Run `outlit auth login` or pass --api-key.",
268
+ code: "not_authenticated"
269
+ }, json);
270
+ }
271
+ return credential;
272
+ }
273
+ function storeApiKey(apiKey) {
274
+ const configDir = getConfigDir();
275
+ mkdirSync(configDir, { recursive: true, mode: 448 });
276
+ const credPath = join(configDir, "credentials.json");
277
+ writeFileSync(credPath, JSON.stringify({ apiKey }, null, 2), { mode: 384 });
278
+ return credPath;
279
+ }
280
+ var CLI_VERSION2, DEFAULT_API_URL = "https://app.outlit.ai", OUTLIT_DASHBOARD_URL = "https://app.outlit.ai/workspace-profile", OUTLIT_SIGNUP_URL = "https://app.outlit.ai/sign-up", TICK2;
281
+ var init_config = __esm(() => {
282
+ init_package();
283
+ init_output();
284
+ init_tty();
285
+ CLI_VERSION2 = package_default.version;
286
+ TICK2 = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
287
+ });
288
+
192
289
  // ../../node_modules/.bun/citty@0.2.1/node_modules/citty/dist/index.mjs
193
290
  function defineCommand2(def) {
194
291
  return def;
@@ -679,7 +776,7 @@ var import_picocolors, import_sisteransi, at = (t) => t === 161 || t === 164 ||
679
776
  ` && (r && p && (i += st(r)), n && (i += it(n))), V += h.length, m = A, A = g.next();
680
777
  }
681
778
  return i;
682
- }, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, yt, Mt, Wt;
779
+ }, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, kt, yt, Mt, Wt;
683
780
  var init_dist2 = __esm(() => {
684
781
  import_picocolors = __toESM(require_picocolors(), 1);
685
782
  import_sisteransi = __toESM(require_src(), 1);
@@ -758,6 +855,23 @@ var init_dist2 = __esm(() => {
758
855
  }
759
856
  }
760
857
  };
858
+ kt = class kt extends x {
859
+ get cursor() {
860
+ return this.value ? 0 : 1;
861
+ }
862
+ get _value() {
863
+ return this.cursor === 0;
864
+ }
865
+ constructor(e) {
866
+ super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
867
+ this.value = this._value;
868
+ }), this.on("confirm", (s) => {
869
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
870
+ }), this.on("cursor", () => {
871
+ this.value = !this.value;
872
+ });
873
+ }
874
+ };
761
875
  yt = class yt extends x {
762
876
  options;
763
877
  cursor = 0;
@@ -1073,6 +1187,32 @@ var import_picocolors2, import_sisteransi2, et2, ct2 = () => process.env.CI ===
1073
1187
  for (const w of A)
1074
1188
  B2.push(w);
1075
1189
  return h && B2.push(g), B2;
1190
+ }, Re = (t) => {
1191
+ const r = t.active ?? "Yes", s = t.inactive ?? "No";
1192
+ return new kt({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
1193
+ const i = t.withGuide ?? _.withGuide, a = `${i ? `${import_picocolors2.default.gray(d)}
1194
+ ` : ""}${W2(this.state)} ${t.message}
1195
+ `, o = this.value ? r : s;
1196
+ switch (this.state) {
1197
+ case "submit": {
1198
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1199
+ return `${a}${u}${import_picocolors2.default.dim(o)}`;
1200
+ }
1201
+ case "cancel": {
1202
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1203
+ return `${a}${u}${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}${i ? `
1204
+ ${import_picocolors2.default.gray(d)}` : ""}`;
1205
+ }
1206
+ default: {
1207
+ const u = i ? `${import_picocolors2.default.cyan(d)} ` : "", l = i ? import_picocolors2.default.cyan(x2) : "";
1208
+ return `${a}${u}${this.value ? `${import_picocolors2.default.green(Q2)} ${r}` : `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(r)}`}${t.vertical ? i ? `
1209
+ ${import_picocolors2.default.cyan(d)} ` : `
1210
+ ` : ` ${import_picocolors2.default.dim("/")} `}${this.value ? `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.green(Q2)} ${s}`}
1211
+ ${l}
1212
+ `;
1213
+ }
1214
+ }
1215
+ } }).prompt();
1076
1216
  }, R2, Ne = (t = "", r) => {
1077
1217
  (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(x2)} ${import_picocolors2.default.red(t)}
1078
1218
 
@@ -1304,12 +1444,12 @@ var exports_signup = {};
1304
1444
  __export(exports_signup, {
1305
1445
  default: () => signup_default
1306
1446
  });
1307
- import { execFileSync } from "node:child_process";
1308
- var OUTLIT_SIGNUP_URL = "https://app.outlit.ai/sign-up", signup_default;
1447
+ var signup_default;
1309
1448
  var init_signup = __esm(() => {
1310
1449
  init_dist3();
1311
1450
  init_dist();
1312
1451
  init_output2();
1452
+ init_config();
1313
1453
  init_output();
1314
1454
  init_tty();
1315
1455
  signup_default = defineCommand2({
@@ -1335,124 +1475,19 @@ var init_signup = __esm(() => {
1335
1475
  if (isInteractive()) {
1336
1476
  We("Outlit CLI -- Sign Up");
1337
1477
  }
1338
- try {
1339
- if (process.platform === "win32") {
1340
- execFileSync("cmd", ["/c", "start", "", OUTLIT_SIGNUP_URL], { stdio: "ignore" });
1478
+ const opened = openBrowser(OUTLIT_SIGNUP_URL);
1479
+ if (isInteractive()) {
1480
+ if (opened) {
1481
+ R2.info(`Opening ${OUTLIT_SIGNUP_URL}`);
1341
1482
  } else {
1342
- execFileSync(openBrowserCmd(), [OUTLIT_SIGNUP_URL], { stdio: "ignore" });
1483
+ R2.info(`Could not open browser. Visit ${OUTLIT_SIGNUP_URL}`);
1343
1484
  }
1344
- } catch {}
1345
- if (isInteractive()) {
1346
- R2.info(`Opening ${OUTLIT_SIGNUP_URL}`);
1347
1485
  Le("Once you've signed up, run `outlit auth login` to store your API key.");
1348
1486
  }
1349
1487
  }
1350
1488
  });
1351
1489
  });
1352
1490
 
1353
- // src/lib/config.ts
1354
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1355
- import { homedir } from "node:os";
1356
- import { dirname, join } from "node:path";
1357
- function isEnoentError(err) {
1358
- return err instanceof Error && err.code === "ENOENT";
1359
- }
1360
- function splitCsv(value) {
1361
- return value.split(",").map((s) => s.trim());
1362
- }
1363
- function getClaudeDesktopConfigPath() {
1364
- const home = homedir();
1365
- switch (process.platform) {
1366
- case "win32":
1367
- return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
1368
- case "darwin":
1369
- return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1370
- default:
1371
- return join(home, ".config", "Claude", "claude_desktop_config.json");
1372
- }
1373
- }
1374
- function getConfigDir() {
1375
- if (process.platform === "win32") {
1376
- return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "outlit");
1377
- }
1378
- return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "outlit");
1379
- }
1380
- function resolveApiKey(flagValue) {
1381
- if (flagValue)
1382
- return { key: flagValue, source: "flag" };
1383
- const envKey = process.env.OUTLIT_API_KEY;
1384
- if (envKey)
1385
- return { key: envKey, source: "env" };
1386
- const credPath = join(getConfigDir(), "credentials.json");
1387
- if (existsSync(credPath)) {
1388
- try {
1389
- const raw = readFileSync(credPath, "utf-8");
1390
- const config = JSON.parse(raw);
1391
- if (config.apiKey)
1392
- return { key: config.apiKey, source: "config" };
1393
- } catch {}
1394
- }
1395
- return null;
1396
- }
1397
- function maskKey(key) {
1398
- if (key.length <= 9)
1399
- return key;
1400
- return `${key.slice(0, 5)}...${key.slice(-4)}`;
1401
- }
1402
- function readJsonConfig(filePath) {
1403
- if (!existsSync(filePath))
1404
- return {};
1405
- try {
1406
- return JSON.parse(readFileSync(filePath, "utf-8"));
1407
- } catch {
1408
- return {};
1409
- }
1410
- }
1411
- function requireCredential(flagApiKey, json) {
1412
- const credential = resolveApiKey(flagApiKey);
1413
- if (!credential) {
1414
- return outputError({
1415
- message: "Not authenticated. Run `outlit auth login` or pass --api-key.",
1416
- code: "not_authenticated"
1417
- }, json);
1418
- }
1419
- return credential;
1420
- }
1421
- function writeConfigFile(filePath, content, opts) {
1422
- try {
1423
- mkdirSync(dirname(filePath), { recursive: true });
1424
- writeFileSync(filePath, content);
1425
- return true;
1426
- } catch (err) {
1427
- return outputError({ message: errorMessage(err, `Failed to write ${opts.label}`), code: "write_error" }, opts.json);
1428
- }
1429
- }
1430
- function mergeOutlitMcpConfig(configPath, serversKey, outlitConfig, opts) {
1431
- const existing = readJsonConfig(configPath);
1432
- const existingServers = existing[serversKey] ?? {};
1433
- const merged = {
1434
- ...existing,
1435
- [serversKey]: { ...existingServers, outlit: outlitConfig }
1436
- };
1437
- writeConfigFile(configPath, `${JSON.stringify(merged, null, 2)}
1438
- `, opts);
1439
- }
1440
- function storeApiKey(apiKey) {
1441
- const configDir = getConfigDir();
1442
- mkdirSync(configDir, { recursive: true, mode: 448 });
1443
- const credPath = join(configDir, "credentials.json");
1444
- writeFileSync(credPath, JSON.stringify({ apiKey }, null, 2), { mode: 384 });
1445
- return credPath;
1446
- }
1447
- var CLI_VERSION2, DEFAULT_MCP_URL = "https://mcp.outlit.ai/mcp", DEFAULT_API_URL = "https://app.outlit.ai", OUTLIT_DASHBOARD_URL = "https://app.outlit.ai/workspace-profile", TICK2;
1448
- var init_config = __esm(() => {
1449
- init_package();
1450
- init_output();
1451
- init_tty();
1452
- CLI_VERSION2 = package_default.version;
1453
- TICK2 = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
1454
- });
1455
-
1456
1491
  // src/lib/client.ts
1457
1492
  function buildUrl(base, path, params) {
1458
1493
  const url = new URL(path, base);
@@ -1461,6 +1496,8 @@ function buildUrl(base, path, params) {
1461
1496
  continue;
1462
1497
  if (Array.isArray(value)) {
1463
1498
  url.searchParams.set(key, value.join(","));
1499
+ } else if (typeof value === "object") {
1500
+ url.searchParams.set(key, JSON.stringify(value));
1464
1501
  } else {
1465
1502
  url.searchParams.set(key, String(value));
1466
1503
  }
@@ -1518,10 +1555,23 @@ var init_client = __esm(() => {
1518
1555
  outlit_get_customer: { method: "POST", path: "/api/internal/mcp/customers" },
1519
1556
  outlit_list_users: { method: "GET", path: "/api/internal/mcp/users" },
1520
1557
  outlit_get_timeline: { method: "POST", path: "/api/internal/mcp/timeline" },
1521
- outlit_get_facts: { method: "POST", path: "/api/internal/mcp/facts" },
1558
+ outlit_list_facts: { method: "POST", path: "/api/internal/mcp/facts" },
1559
+ outlit_get_fact: { method: "POST", path: "/api/internal/mcp/facts/get" },
1560
+ outlit_get_source: { method: "POST", path: "/api/internal/mcp/context-source" },
1522
1561
  outlit_schema: { method: "GET", path: "/api/internal/mcp/sql-schema" },
1523
1562
  outlit_query: { method: "POST", path: "/api/internal/mcp/sql" },
1524
- outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" }
1563
+ outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" },
1564
+ outlit_list_integrations: { method: "GET", path: "/api/internal/mcp/integrations" },
1565
+ outlit_connect_integration: { method: "POST", path: "/api/internal/mcp/integrations/connect" },
1566
+ outlit_connect_status: { method: "GET", path: "/api/internal/mcp/integrations/connect/status" },
1567
+ outlit_disconnect_integration: {
1568
+ method: "POST",
1569
+ path: "/api/internal/mcp/integrations/disconnect"
1570
+ },
1571
+ outlit_integration_sync_status: {
1572
+ method: "GET",
1573
+ path: "/api/internal/mcp/integrations/sync-status"
1574
+ }
1525
1575
  };
1526
1576
  });
1527
1577
 
@@ -1649,8 +1699,26 @@ async function getClientOrExit(flagApiKey, json) {
1649
1699
  return createClient(flagApiKey).catch((err) => outputError({ message: errorMessage(err, "Authentication failed"), code: "auth_required" }, json));
1650
1700
  }
1651
1701
  async function pingApiKey(apiKey) {
1652
- const client = await createClient(apiKey);
1653
- await client.callTool("outlit_list_customers", { limit: 1 });
1702
+ const baseUrl = process.env.OUTLIT_API_URL ?? DEFAULT_API_URL;
1703
+ const url = new URL("/api/internal/mcp/validate-api-key", baseUrl).toString();
1704
+ const response = await globalThis.fetch(url, {
1705
+ method: "POST",
1706
+ headers: {
1707
+ Authorization: `Bearer ${apiKey}`
1708
+ }
1709
+ });
1710
+ const text = await response.text();
1711
+ const payload = text.length > 0 ? (() => {
1712
+ try {
1713
+ return JSON.parse(text);
1714
+ } catch {
1715
+ return null;
1716
+ }
1717
+ })() : null;
1718
+ if (!response.ok || !payload?.valid) {
1719
+ const message = payload?.error ?? (text.length > 0 ? text : `API error (${response.status})`);
1720
+ throw new Error(message);
1721
+ }
1654
1722
  }
1655
1723
  async function validateKeyOrExit(apiKey, json) {
1656
1724
  try {
@@ -1702,6 +1770,7 @@ async function runTool(client, toolName, params, json, opts) {
1702
1770
  }
1703
1771
  var init_api = __esm(() => {
1704
1772
  init_client();
1773
+ init_config();
1705
1774
  init_output();
1706
1775
  init_spinner();
1707
1776
  init_table();
@@ -1712,7 +1781,6 @@ var exports_login = {};
1712
1781
  __export(exports_login, {
1713
1782
  default: () => login_default
1714
1783
  });
1715
- import { execFileSync as execFileSync2 } from "node:child_process";
1716
1784
  var login_default;
1717
1785
  var init_login = __esm(() => {
1718
1786
  init_dist3();
@@ -1773,14 +1841,12 @@ Format: ok_ followed by 32+ alphanumeric characters.`
1773
1841
  if (Ct(method))
1774
1842
  cancelLogin();
1775
1843
  if (method === "browser") {
1776
- try {
1777
- if (process.platform === "win32") {
1778
- execFileSync2("cmd", ["/c", "start", "", OUTLIT_DASHBOARD_URL], { stdio: "ignore" });
1779
- } else {
1780
- execFileSync2(openBrowserCmd(), [OUTLIT_DASHBOARD_URL], { stdio: "ignore" });
1781
- }
1782
- } catch {}
1783
- R2.info("Opening browser... paste your key once you have it.");
1844
+ const opened = openBrowser(OUTLIT_DASHBOARD_URL);
1845
+ if (opened) {
1846
+ R2.info("Opening browser... paste your key once you have it.");
1847
+ } else {
1848
+ R2.info(`Could not open browser. Visit ${OUTLIT_DASHBOARD_URL} manually.`);
1849
+ }
1784
1850
  }
1785
1851
  const result = await He({
1786
1852
  message: "Paste your Outlit API key:",
@@ -1838,8 +1904,8 @@ var exports_logout = {};
1838
1904
  __export(exports_logout, {
1839
1905
  default: () => logout_default
1840
1906
  });
1841
- import { rmSync } from "node:fs";
1842
- import { join as join2 } from "node:path";
1907
+ import { rmSync as rmSync2 } from "node:fs";
1908
+ import { join as join3 } from "node:path";
1843
1909
  var logout_default;
1844
1910
  var init_logout = __esm(() => {
1845
1911
  init_dist();
@@ -1865,13 +1931,13 @@ var init_logout = __esm(() => {
1865
1931
  async run({ args }) {
1866
1932
  const json = !!args.json;
1867
1933
  const configDir = getConfigDir();
1868
- const credPath = join2(configDir, "credentials.json");
1934
+ const credPath = join3(configDir, "credentials.json");
1869
1935
  if (process.env.OUTLIT_API_KEY) {
1870
1936
  process.stderr.write(`Warning: OUTLIT_API_KEY env var is still set and will continue to work after logout.
1871
1937
  `);
1872
1938
  }
1873
1939
  try {
1874
- rmSync(credPath, { force: true });
1940
+ rmSync2(credPath, { force: true });
1875
1941
  } catch (err) {
1876
1942
  if (!isEnoentError(err)) {
1877
1943
  return outputError({ message: errorMessage(err, "Failed to remove credentials file"), code: "unlink_error" }, json);
@@ -2026,6 +2092,38 @@ var init_auth2 = __esm(() => {
2026
2092
  });
2027
2093
 
2028
2094
  // src/args/filters.ts
2095
+ function parseTraitFilterValue(value) {
2096
+ if (value === "true")
2097
+ return true;
2098
+ if (value === "false")
2099
+ return false;
2100
+ const parsed = Number(value);
2101
+ if (value.trim() !== "" && Number.isFinite(parsed)) {
2102
+ return parsed;
2103
+ }
2104
+ return value;
2105
+ }
2106
+ function parseTraitFilters(input) {
2107
+ if (!input)
2108
+ return;
2109
+ const entries = input.split(",").map((entry) => entry.trim()).filter(Boolean);
2110
+ if (entries.length === 0)
2111
+ return;
2112
+ const filters = {};
2113
+ for (const entry of entries) {
2114
+ const separatorIndex = entry.indexOf("=");
2115
+ if (separatorIndex <= 0 || separatorIndex === entry.length - 1) {
2116
+ throw new Error(`Invalid trait filter "${entry}". Expected key=value.`);
2117
+ }
2118
+ const key = entry.slice(0, separatorIndex).trim();
2119
+ const rawValue = entry.slice(separatorIndex + 1).trim();
2120
+ if (!/^[A-Za-z0-9_-]{1,100}$/.test(key)) {
2121
+ throw new Error(`Invalid trait filter key "${key}". Keys may only contain letters, numbers, underscores, and dashes.`);
2122
+ }
2123
+ filters[key] = parseTraitFilterValue(rawValue);
2124
+ }
2125
+ return filters;
2126
+ }
2029
2127
  function applyListFilters(params, args) {
2030
2128
  if (args["no-activity-in"])
2031
2129
  params.noActivityInLast = args["no-activity-in"];
@@ -2038,7 +2136,7 @@ function applyListFilters(params, args) {
2038
2136
  if (args["order-direction"])
2039
2137
  params.orderDirection = args["order-direction"];
2040
2138
  }
2041
- var activityFilterArgs, orderArgs;
2139
+ var activityFilterArgs, orderArgs, traitFilterArgs;
2042
2140
  var init_filters = __esm(() => {
2043
2141
  activityFilterArgs = {
2044
2142
  "no-activity-in": {
@@ -2062,6 +2160,12 @@ var init_filters = __esm(() => {
2062
2160
  default: "desc"
2063
2161
  }
2064
2162
  };
2163
+ traitFilterArgs = {
2164
+ trait: {
2165
+ type: "string",
2166
+ description: "Filter by traits using key=value pairs. Separate multiple filters with commas (e.g. role=admin,active=true)"
2167
+ }
2168
+ };
2065
2169
  });
2066
2170
 
2067
2171
  // src/args/pagination.ts
@@ -2097,6 +2201,148 @@ Use this to fetch the next page of results.`
2097
2201
  };
2098
2202
  });
2099
2203
 
2204
+ // src/generated/tool-contracts.ts
2205
+ function resolveCustomerContextSearchInput(value) {
2206
+ if (!value.query) {
2207
+ return {
2208
+ ok: false,
2209
+ message: "A query argument is required"
2210
+ };
2211
+ }
2212
+ const normalizedQuery = value.query.trim();
2213
+ if (normalizedQuery.length < 2) {
2214
+ return {
2215
+ ok: false,
2216
+ message: "Query must be at least 2 non-whitespace characters"
2217
+ };
2218
+ }
2219
+ if (value.after !== undefined && value.before !== undefined && new Date(value.after).getTime() > new Date(value.before).getTime()) {
2220
+ return {
2221
+ ok: false,
2222
+ message: "--after must be before or equal to --before"
2223
+ };
2224
+ }
2225
+ return {
2226
+ ok: true,
2227
+ request: {
2228
+ query: normalizedQuery,
2229
+ customer: value.customer,
2230
+ topK: value.topK,
2231
+ after: value.after,
2232
+ before: value.before,
2233
+ sourceTypes: value.sourceTypes
2234
+ }
2235
+ };
2236
+ }
2237
+ var customerToolContracts, customerBillingStatuses, customerFactStatuses, customerIncludeSections, customerSourceTypes, customerTimeframes, timelineChannels, timelineTimeframes, userJourneyStages, schemaTables;
2238
+ var init_tool_contracts = __esm(() => {
2239
+ customerToolContracts = {
2240
+ outlit_list_customers: {
2241
+ toolName: "outlit_list_customers",
2242
+ description: "Browse and filter customers. Use this to find customers by billing status, activity recency, revenue, or name. Returns a paginated list with summary info (MRR, last activity, status)."
2243
+ },
2244
+ outlit_list_users: {
2245
+ toolName: "outlit_list_users",
2246
+ description: "Browse and filter users. Use this to find users by journey stage, activity recency, customer, or email/name. Returns a paginated list with activity info."
2247
+ },
2248
+ outlit_get_customer: {
2249
+ toolName: "outlit_get_customer",
2250
+ description: "Get full details for a single customer. Use this when you already know which customer you want to inspect. Optionally include related data (users, revenue, recent activity, engagement metrics)."
2251
+ },
2252
+ outlit_get_timeline: {
2253
+ toolName: "outlit_get_timeline",
2254
+ description: "Get the chronological activity timeline for a customer. Use this to see what happened and when — emails, calls, Slack messages, billing events, etc. Supports channel and date filtering."
2255
+ },
2256
+ outlit_list_facts: {
2257
+ toolName: "outlit_list_facts",
2258
+ description: "List structured facts known about a customer. Use filters like status, sourceTypes, and date bounds to narrow the result set. For topic-specific retrieval, use outlit_search_customer_context instead."
2259
+ },
2260
+ outlit_get_fact: {
2261
+ toolName: "outlit_get_fact",
2262
+ description: "Get one exact fact by ID. Returns the canonical fact shape and optionally expands requested related data such as evidence."
2263
+ },
2264
+ outlit_get_source: {
2265
+ toolName: "outlit_get_source",
2266
+ description: "Get one exact source record by generic sourceType and sourceId. Use this when you already know the concrete underlying source you want to inspect."
2267
+ },
2268
+ outlit_search_customer_context: {
2269
+ toolName: "outlit_search_customer_context",
2270
+ description: "Search across all known customer context using a natural-language query. Returns grouped artifact-level results for matching sources and facts. Omit customer to search across all customers in the organization."
2271
+ },
2272
+ outlit_query: {
2273
+ toolName: "outlit_query",
2274
+ description: `Execute raw SQL queries against your analytics data.
2275
+
2276
+ Available tables:
2277
+ - events: Customer activity events (event_type, event_channel, customer_id, occurred_at, properties, ...)
2278
+ - customer_dimensions: Customer attributes (customer_id, domain, name, billing_status, plan, mrr_cents, ...)
2279
+ - user_dimensions: User attributes (user_id, email, name, customer_id, ...)
2280
+ - mrr_snapshots: Revenue snapshots over time (customer_id, snapshot_date, mrr_cents, ...)
2281
+
2282
+ All queries are automatically filtered to your organization's data.
2283
+ Only SELECT queries are allowed.
2284
+
2285
+ Example queries:
2286
+ - SELECT event_type, count(*) FROM events GROUP BY 1 ORDER BY 2 DESC LIMIT 10
2287
+ - SELECT billing_status, sum(mrr_cents)/100 as mrr FROM customer_dimensions GROUP BY 1
2288
+ - SELECT * FROM events WHERE customer_id = 'cust_123' ORDER BY occurred_at DESC LIMIT 50`
2289
+ },
2290
+ outlit_schema: {
2291
+ toolName: "outlit_schema",
2292
+ description: `Get table schemas for available analytics tables.
2293
+
2294
+ Use this to discover column names, types, and descriptions before writing SQL queries.
2295
+ Returns column definitions and example queries for each table.`
2296
+ }
2297
+ };
2298
+ customerBillingStatuses = [
2299
+ "NONE",
2300
+ "TRIALING",
2301
+ "PAYING",
2302
+ "PAST_DUE",
2303
+ "CHURNED"
2304
+ ];
2305
+ customerFactStatuses = [
2306
+ "ACTIVE",
2307
+ "ACKNOWLEDGED",
2308
+ "RESOLVED",
2309
+ "SNOOZED",
2310
+ "CANDIDATE"
2311
+ ];
2312
+ customerIncludeSections = [
2313
+ "users",
2314
+ "revenue",
2315
+ "recentTimeline",
2316
+ "behaviorMetrics"
2317
+ ];
2318
+ customerSourceTypes = ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"];
2319
+ customerTimeframes = ["7d", "14d", "30d", "90d"];
2320
+ timelineChannels = [
2321
+ "SDK",
2322
+ "EMAIL",
2323
+ "SLACK",
2324
+ "CALL",
2325
+ "CRM",
2326
+ "BILLING",
2327
+ "SUPPORT",
2328
+ "INTERNAL"
2329
+ ];
2330
+ timelineTimeframes = ["7d", "14d", "30d", "90d", "all"];
2331
+ userJourneyStages = [
2332
+ "DISCOVERED",
2333
+ "SIGNED_UP",
2334
+ "ACTIVATED",
2335
+ "ENGAGED",
2336
+ "INACTIVE"
2337
+ ];
2338
+ schemaTables = [
2339
+ "events",
2340
+ "customer_dimensions",
2341
+ "user_dimensions",
2342
+ "mrr_snapshots"
2343
+ ];
2344
+ });
2345
+
2100
2346
  // src/lib/format.ts
2101
2347
  function formatCents(value) {
2102
2348
  if (value == null || typeof value !== "number" || Number.isNaN(value))
@@ -2137,6 +2383,16 @@ function truncate(value, maxLen) {
2137
2383
  return "...".slice(0, maxLen);
2138
2384
  return `${str.slice(0, maxLen - 3)}...`;
2139
2385
  }
2386
+ function capitalize(value) {
2387
+ if (value == null || typeof value !== "string")
2388
+ return "--";
2389
+ return value.charAt(0).toUpperCase() + value.slice(1);
2390
+ }
2391
+ function formatNumber(value) {
2392
+ if (value == null || typeof value !== "number" || Number.isNaN(value))
2393
+ return "--";
2394
+ return value.toLocaleString("en-US");
2395
+ }
2140
2396
 
2141
2397
  // src/commands/customers/list.ts
2142
2398
  var exports_list = {};
@@ -2150,6 +2406,7 @@ var init_list = __esm(() => {
2150
2406
  init_filters();
2151
2407
  init_output2();
2152
2408
  init_pagination();
2409
+ init_tool_contracts();
2153
2410
  init_api();
2154
2411
  init_output();
2155
2412
  list_default = defineCommand2({
@@ -2168,7 +2425,7 @@ var init_list = __esm(() => {
2168
2425
  " outlit customers list --mrr-above 10000 --limit 50 # high-value at-risk",
2169
2426
  " outlit customers list --json | jq '.items[].domain' # pipe-friendly",
2170
2427
  "",
2171
- "Billing statuses: NONE, TRIALING, PAYING, PAST_DUE, CHURNED",
2428
+ `Billing statuses: ${customerBillingStatuses.join(", ")}`,
2172
2429
  "Activity periods: 7d, 14d, 30d, 90d",
2173
2430
  "",
2174
2431
  AGENT_JSON_HINT
@@ -2180,10 +2437,11 @@ var init_list = __esm(() => {
2180
2437
  ...outputArgs,
2181
2438
  ...paginationArgs,
2182
2439
  ...activityFilterArgs,
2440
+ ...traitFilterArgs,
2183
2441
  ...orderArgs,
2184
2442
  "billing-status": {
2185
2443
  type: "string",
2186
- description: "Filter by billing status (NONE, TRIALING, PAYING, PAST_DUE, CHURNED)"
2444
+ description: `Filter by billing status (${customerBillingStatuses.join(", ")})`
2187
2445
  },
2188
2446
  "mrr-above": {
2189
2447
  type: "string",
@@ -2196,14 +2454,6 @@ var init_list = __esm(() => {
2196
2454
  search: {
2197
2455
  type: "string",
2198
2456
  description: "Search by customer name or domain"
2199
- },
2200
- status: {
2201
- type: "string",
2202
- description: "Customer status filter (PROVISIONAL, ACTIVE, CHURNED, MERGED)"
2203
- },
2204
- type: {
2205
- type: "string",
2206
- description: "Customer type filter (COMPANY, INDIVIDUAL)"
2207
2457
  }
2208
2458
  },
2209
2459
  async run({ args }) {
@@ -2226,16 +2476,25 @@ var init_list = __esm(() => {
2226
2476
  }
2227
2477
  params.mrrBelow = value;
2228
2478
  }
2229
- applyListFilters(params, args);
2230
- applyPagination(params, args, json);
2231
- if (args.status)
2232
- params.status = args.status;
2233
- if (args.type)
2234
- params.type = args.type;
2235
- return runTool(client, "outlit_list_customers", params, json, {
2236
- spinnerMessage: "Fetching customers...",
2237
- table: {
2238
- columns: [
2479
+ if (args.trait) {
2480
+ try {
2481
+ const traitFilters = parseTraitFilters(args.trait);
2482
+ if (traitFilters) {
2483
+ params.traitFilters = traitFilters;
2484
+ }
2485
+ } catch (error) {
2486
+ return outputError({
2487
+ message: error instanceof Error ? error.message : "Invalid --trait filter",
2488
+ code: "invalid_input"
2489
+ }, json);
2490
+ }
2491
+ }
2492
+ applyListFilters(params, args);
2493
+ applyPagination(params, args, json);
2494
+ return runTool(client, customerToolContracts.outlit_list_customers.toolName, params, json, {
2495
+ spinnerMessage: "Fetching customers...",
2496
+ table: {
2497
+ columns: [
2239
2498
  { header: "Name", key: "name", format: (v) => truncate(v, 24) },
2240
2499
  { header: "Domain", key: "domain" },
2241
2500
  { header: "Billing", key: "billingStatus" },
@@ -2258,6 +2517,7 @@ var init_get = __esm(() => {
2258
2517
  init_dist();
2259
2518
  init_auth();
2260
2519
  init_output2();
2520
+ init_tool_contracts();
2261
2521
  init_api();
2262
2522
  init_config();
2263
2523
  get_default = defineCommand2({
@@ -2274,6 +2534,9 @@ var init_get = __esm(() => {
2274
2534
  'Naming note: --include users returns data under the "contacts" key in',
2275
2535
  "the response. This is a server-side naming inconsistency, not a CLI bug.",
2276
2536
  "",
2537
+ `Available include sections: ${customerIncludeSections.join(", ")}`,
2538
+ `Timeframes: ${customerTimeframes.join(", ")}`,
2539
+ "",
2277
2540
  "Examples:",
2278
2541
  " outlit customers get acme.com",
2279
2542
  " outlit customers get acme.com --include users,revenue",
@@ -2295,7 +2558,7 @@ var init_get = __esm(() => {
2295
2558
  type: "string",
2296
2559
  description: [
2297
2560
  "Comma-separated sections to include in response.",
2298
- "Available: users, revenue, recentTimeline, behaviorMetrics",
2561
+ `Available: ${customerIncludeSections.join(", ")}`,
2299
2562
  'Note: "users" maps to "contacts" in the response (server naming).'
2300
2563
  ].join(`
2301
2564
  `)
@@ -2316,7 +2579,7 @@ var init_get = __esm(() => {
2316
2579
  if (args.include) {
2317
2580
  params.include = splitCsv(args.include);
2318
2581
  }
2319
- return runTool(client, "outlit_get_customer", params, json);
2582
+ return runTool(client, customerToolContracts.outlit_get_customer.toolName, params, json);
2320
2583
  }
2321
2584
  });
2322
2585
  });
@@ -2332,6 +2595,7 @@ var init_timeline = __esm(() => {
2332
2595
  init_auth();
2333
2596
  init_output2();
2334
2597
  init_pagination();
2598
+ init_tool_contracts();
2335
2599
  init_api();
2336
2600
  init_config();
2337
2601
  timeline_default = defineCommand2({
@@ -2346,6 +2610,8 @@ var init_timeline = __esm(() => {
2346
2610
  "",
2347
2611
  "Timeframe is used when no explicit date range is set.",
2348
2612
  "When --start-date or --end-date is provided, --timeframe is ignored.",
2613
+ `Channels: ${timelineChannels.join(", ")}`,
2614
+ `Timeframes: ${timelineTimeframes.join(", ")}`,
2349
2615
  "",
2350
2616
  "Examples:",
2351
2617
  " outlit customers timeline acme.com",
@@ -2369,7 +2635,7 @@ var init_timeline = __esm(() => {
2369
2635
  },
2370
2636
  channels: {
2371
2637
  type: "string",
2372
- description: "Comma-separated list of channels to filter (e.g. EMAIL,SLACK)"
2638
+ description: `Comma-separated list of channels to filter (${timelineChannels.join(", ")})`
2373
2639
  },
2374
2640
  "event-types": {
2375
2641
  type: "string",
@@ -2377,7 +2643,7 @@ var init_timeline = __esm(() => {
2377
2643
  },
2378
2644
  timeframe: {
2379
2645
  type: "string",
2380
- description: "Timeframe for events (e.g. 7d, 14d, 30d, 90d). Ignored when --start-date or --end-date is set.",
2646
+ description: `Timeframe for events (${timelineTimeframes.join(", ")}). Ignored when --start-date or --end-date is set.`,
2381
2647
  default: "30d"
2382
2648
  },
2383
2649
  "start-date": {
@@ -2409,7 +2675,7 @@ var init_timeline = __esm(() => {
2409
2675
  params.eventTypes = splitCsv(args["event-types"]);
2410
2676
  }
2411
2677
  applyPagination(params, args, json);
2412
- return runTool(client, "outlit_get_timeline", params, json);
2678
+ return runTool(client, customerToolContracts.outlit_get_timeline.toolName, params, json);
2413
2679
  }
2414
2680
  });
2415
2681
  });
@@ -2458,7 +2724,9 @@ var init_list2 = __esm(() => {
2458
2724
  init_filters();
2459
2725
  init_output2();
2460
2726
  init_pagination();
2727
+ init_tool_contracts();
2461
2728
  init_api();
2729
+ init_output();
2462
2730
  list_default2 = defineCommand2({
2463
2731
  meta: {
2464
2732
  name: "list",
@@ -2470,7 +2738,7 @@ var init_list2 = __esm(() => {
2470
2738
  "",
2471
2739
  "Examples:",
2472
2740
  " outlit users list # all users",
2473
- " outlit users list --journey-stage CHAMPION # champions only",
2741
+ " outlit users list --journey-stage ENGAGED # engaged users only",
2474
2742
  " outlit users list --customer-id <uuid> # users for a customer",
2475
2743
  " outlit users list --no-activity-in 30d # inactive users",
2476
2744
  " outlit users list --search alice --order-by last_activity_at",
@@ -2484,9 +2752,10 @@ var init_list2 = __esm(() => {
2484
2752
  ...authArgs,
2485
2753
  ...outputArgs,
2486
2754
  ...paginationArgs,
2755
+ ...traitFilterArgs,
2487
2756
  "journey-stage": {
2488
2757
  type: "string",
2489
- description: "Filter by journey stage (e.g. CHAMPION, AT_RISK, CHURNED)"
2758
+ description: `Filter by journey stage (${userJourneyStages.join(", ")})`
2490
2759
  },
2491
2760
  "customer-id": {
2492
2761
  type: "string",
@@ -2507,9 +2776,22 @@ var init_list2 = __esm(() => {
2507
2776
  params.journeyStage = args["journey-stage"];
2508
2777
  if (args["customer-id"])
2509
2778
  params.customerId = args["customer-id"];
2779
+ if (args.trait) {
2780
+ try {
2781
+ const traitFilters = parseTraitFilters(args.trait);
2782
+ if (traitFilters) {
2783
+ params.traitFilters = traitFilters;
2784
+ }
2785
+ } catch (error) {
2786
+ return outputError({
2787
+ message: error instanceof Error ? error.message : "Invalid --trait filter",
2788
+ code: "invalid_input"
2789
+ }, json);
2790
+ }
2791
+ }
2510
2792
  applyListFilters(params, args);
2511
2793
  applyPagination(params, args, json);
2512
- return runTool(client, "outlit_list_users", params, json, {
2794
+ return runTool(client, customerToolContracts.outlit_list_users.toolName, params, json, {
2513
2795
  spinnerMessage: "Fetching users...",
2514
2796
  table: {
2515
2797
  columns: [
@@ -2553,361 +2835,177 @@ var init_users = __esm(() => {
2553
2835
  });
2554
2836
  });
2555
2837
 
2556
- // src/lib/setup.ts
2557
- import { execFileSync as execFileSync3 } from "node:child_process";
2558
- function runMcpCliSetup(key, json, opts, exitOnError = true) {
2559
- try {
2560
- execFileSync3(opts.cliName, [
2561
- "mcp",
2562
- "add",
2563
- "--transport",
2564
- "http",
2565
- "outlit",
2566
- DEFAULT_MCP_URL,
2567
- "--header",
2568
- `Authorization: Bearer ${key}`
2569
- ], { stdio: "pipe" });
2570
- } catch (err) {
2571
- if (exitOnError) {
2572
- if (isEnoentError(err)) {
2573
- return outputError({ message: opts.notFoundMessage, code: opts.notFoundCode }, json);
2574
- }
2575
- opts.extraErrorHandler?.(err, json);
2576
- return outputError({ message: errorMessage(err, `${opts.cliName} mcp add failed`), code: "exec_error" }, json);
2577
- }
2578
- return { success: false, error: errorMessage(err, `${opts.cliName} mcp add failed`) };
2579
- }
2580
- if (exitOnError) {
2581
- if (isJsonMode(json)) {
2582
- outputResult({ success: true, agent: opts.agentId });
2583
- return { success: true };
2584
- }
2585
- console.log(`${TICK2} ${opts.successMessage}`);
2838
+ // src/lib/update.ts
2839
+ import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
2840
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, realpathSync as realpathSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
2841
+ import { homedir as homedir3 } from "node:os";
2842
+ import { dirname as dirname3, join as join4 } from "node:path";
2843
+ function compareVersions2(a, b) {
2844
+ const aParts = a.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
2845
+ const bParts = b.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
2846
+ const maxLength = Math.max(aParts.length, bParts.length);
2847
+ for (let index = 0;index < maxLength; index++) {
2848
+ const left = aParts[index] ?? 0;
2849
+ const right = bParts[index] ?? 0;
2850
+ if (left > right)
2851
+ return 1;
2852
+ if (left < right)
2853
+ return -1;
2586
2854
  }
2587
- return { success: true };
2855
+ return 0;
2588
2856
  }
2589
- function runMcpFileSetup(key, json, opts, exitOnError = true) {
2590
- const mcpConfig = opts.mcpConfig ?? {
2591
- url: DEFAULT_MCP_URL,
2592
- headers: { Authorization: `Bearer ${key}` }
2593
- };
2594
- try {
2595
- mergeOutlitMcpConfig(opts.configPath, opts.serversKey, mcpConfig, { json, label: opts.label });
2596
- } catch (err) {
2597
- if (!exitOnError) {
2598
- return { success: false, error: errorMessage(err, `Failed to write ${opts.label}`) };
2857
+ function inferInstallerFromUserAgent2(agent) {
2858
+ if (agent.startsWith("bun/"))
2859
+ return "bun";
2860
+ if (agent.startsWith("npm/"))
2861
+ return "npm";
2862
+ if (agent.startsWith("pnpm/"))
2863
+ return "pnpm";
2864
+ if (agent.startsWith("yarn/"))
2865
+ return "yarn";
2866
+ return null;
2867
+ }
2868
+ function isUnderPath2(path, parent) {
2869
+ const normalizedPath = normalizeInstallerPath2(path);
2870
+ const normalizedParent = normalizeInstallerPath2(parent);
2871
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`);
2872
+ }
2873
+ function normalizeInstallerPath2(path) {
2874
+ return path.replace(/^\/private\/tmp\//, "/tmp/");
2875
+ }
2876
+ function inferInstallerFromInstallation2(opts) {
2877
+ const candidatePaths = [opts.argv1, opts.realExecPath].filter((value) => !!value);
2878
+ if (opts.npmGlobalPrefix) {
2879
+ const npmPackageRoots = [
2880
+ join4(opts.npmGlobalPrefix, "node_modules", PACKAGE_NAME2),
2881
+ join4(opts.npmGlobalPrefix, "lib", "node_modules", PACKAGE_NAME2)
2882
+ ];
2883
+ if (candidatePaths.some((path) => npmPackageRoots.some((root) => isUnderPath2(path, root)))) {
2884
+ return "npm";
2599
2885
  }
2600
- throw err;
2601
2886
  }
2602
- if (exitOnError) {
2603
- if (isJsonMode(json)) {
2604
- outputResult({ success: true, path: opts.configPath, agent: opts.agentId });
2605
- return { success: true };
2887
+ if (opts.bunGlobalBin) {
2888
+ const bunGlobalBin = opts.bunGlobalBin;
2889
+ if (candidatePaths.some((path) => isUnderPath2(path, bunGlobalBin))) {
2890
+ return "bun";
2606
2891
  }
2607
- console.log(`${TICK2} ${opts.successMessage}`);
2608
2892
  }
2609
- return { success: true };
2893
+ return null;
2610
2894
  }
2611
- var init_setup = __esm(() => {
2612
- init_config();
2613
- init_output();
2614
- });
2615
-
2616
- // src/commands/setup/claude-code.ts
2617
- var exports_claude_code = {};
2618
- __export(exports_claude_code, {
2619
- default: () => claude_code_default,
2620
- configureSafe: () => configureSafe
2621
- });
2622
- function configureSafe(key, json) {
2623
- return runMcpCliSetup(key, json, getConfig(), false).success;
2624
- }
2625
- var getConfig = () => ({
2626
- cliName: "claude",
2627
- agentId: "claude-code",
2628
- notFoundMessage: "claude CLI not found. Install from https://claude.ai/code",
2629
- notFoundCode: "claude_not_found",
2630
- successMessage: "Outlit added to Claude Code. Restart Claude Code to apply."
2631
- }), claude_code_default;
2632
- var init_claude_code = __esm(() => {
2633
- init_dist();
2634
- init_auth();
2635
- init_output2();
2636
- init_config();
2637
- init_setup();
2638
- claude_code_default = defineCommand2({
2639
- meta: {
2640
- name: "claude-code",
2641
- description: "Register Outlit MCP server with Claude Code via `claude mcp add`."
2642
- },
2643
- args: { ...authArgs, ...outputArgs },
2644
- run({ args }) {
2645
- const json = !!args.json;
2646
- const { key } = requireCredential(args["api-key"], json);
2647
- runMcpCliSetup(key, json, getConfig());
2648
- }
2649
- });
2650
- });
2651
-
2652
- // src/commands/setup/claude-desktop.ts
2653
- var exports_claude_desktop = {};
2654
- __export(exports_claude_desktop, {
2655
- default: () => claude_desktop_default,
2656
- configureSafe: () => configureSafe2
2657
- });
2658
- function configureSafe2(key, json) {
2659
- return runMcpFileSetup(key, json, getConfig2(key), false).success;
2895
+ function readCommandOutput2(command, args) {
2896
+ try {
2897
+ return execFileSync3(command, args, {
2898
+ encoding: "utf8",
2899
+ stdio: ["ignore", "pipe", "ignore"]
2900
+ }).trim();
2901
+ } catch {
2902
+ return null;
2903
+ }
2660
2904
  }
2661
- var getConfig2 = (key) => {
2662
- const configPath = getClaudeDesktopConfigPath();
2663
- return {
2664
- agentId: "claude-desktop",
2665
- configPath,
2666
- serversKey: "mcpServers",
2667
- label: "Claude Desktop config",
2668
- successMessage: `Outlit added to Claude Desktop. Restart Claude Desktop to apply.
2669
- Config: ${configPath}`,
2670
- mcpConfig: { command: "outlit", args: ["mcp", "serve"], env: { OUTLIT_API_KEY: key } }
2671
- };
2672
- }, claude_desktop_default;
2673
- var init_claude_desktop = __esm(() => {
2674
- init_dist();
2675
- init_auth();
2676
- init_output2();
2677
- init_config();
2678
- init_setup();
2679
- claude_desktop_default = defineCommand2({
2680
- meta: {
2681
- name: "claude-desktop",
2682
- description: "Add Outlit MCP server to Claude Desktop config."
2683
- },
2684
- args: { ...authArgs, ...outputArgs },
2685
- run({ args }) {
2686
- const json = !!args.json;
2687
- const { key } = requireCredential(args["api-key"], json);
2688
- runMcpFileSetup(key, json, getConfig2(key));
2689
- }
2690
- });
2691
- });
2692
-
2693
- // src/commands/setup/cursor.ts
2694
- var exports_cursor = {};
2695
- __export(exports_cursor, {
2696
- default: () => cursor_default,
2697
- configureSafe: () => configureSafe3
2698
- });
2699
- import { homedir as homedir2 } from "node:os";
2700
- import { join as join3 } from "node:path";
2701
- function configureSafe3(key, json) {
2702
- return runMcpFileSetup(key, json, getConfig3(), false).success;
2703
- }
2704
- var getConfig3 = () => ({
2705
- agentId: "cursor",
2706
- configPath: join3(homedir2(), ".cursor", "mcp.json"),
2707
- serversKey: "mcpServers",
2708
- label: "cursor mcp.json",
2709
- successMessage: `Outlit added to Cursor MCP config. Restart Cursor to apply.
2710
- Config: ~/.cursor/mcp.json`
2711
- }), cursor_default;
2712
- var init_cursor = __esm(() => {
2713
- init_dist();
2714
- init_auth();
2715
- init_output2();
2716
- init_config();
2717
- init_setup();
2718
- cursor_default = defineCommand2({
2719
- meta: {
2720
- name: "cursor",
2721
- description: "Add Outlit MCP server to ~/.cursor/mcp.json."
2722
- },
2723
- args: { ...authArgs, ...outputArgs },
2724
- run({ args }) {
2725
- const json = !!args.json;
2726
- const { key } = requireCredential(args["api-key"], json);
2727
- runMcpFileSetup(key, json, getConfig3());
2728
- }
2729
- });
2730
- });
2731
-
2732
- // src/commands/setup/gemini.ts
2733
- var exports_gemini = {};
2734
- __export(exports_gemini, {
2735
- default: () => gemini_default,
2736
- configureSafe: () => configureSafe4
2737
- });
2738
- function configureSafe4(key, json) {
2739
- return runMcpCliSetup(key, json, getConfig4(), false).success;
2740
- }
2741
- var getConfig4 = () => ({
2742
- cliName: "gemini",
2743
- agentId: "gemini",
2744
- notFoundMessage: "gemini CLI not found. Install from https://github.com/google-gemini/gemini-cli",
2745
- notFoundCode: "gemini_not_found",
2746
- successMessage: "Outlit added to Gemini CLI. Restart Gemini CLI to apply."
2747
- }), gemini_default;
2748
- var init_gemini = __esm(() => {
2749
- init_dist();
2750
- init_auth();
2751
- init_output2();
2752
- init_config();
2753
- init_output();
2754
- init_setup();
2755
- gemini_default = defineCommand2({
2756
- meta: {
2757
- name: "gemini",
2758
- description: "Register Outlit MCP server with Gemini CLI via `gemini mcp add`."
2759
- },
2760
- args: { ...authArgs, ...outputArgs },
2761
- run({ args }) {
2762
- const json = !!args.json;
2763
- const { key } = requireCredential(args["api-key"], json);
2764
- runMcpCliSetup(key, json, {
2765
- ...getConfig4(),
2766
- extraErrorHandler(err, j2) {
2767
- const msg = err instanceof Error ? err.message.toLowerCase() : "";
2768
- if (msg.includes("mcp") && msg.includes("not")) {
2769
- outputError({
2770
- message: "Your Gemini CLI version does not support 'mcp add'. Run 'gemini update' or reinstall.",
2771
- code: "gemini_mcp_unsupported"
2772
- }, j2);
2773
- }
2774
- }
2775
- });
2776
- }
2905
+ function inferInstaller2() {
2906
+ const fromAgent = inferInstallerFromUserAgent2(process.env.npm_config_user_agent ?? "");
2907
+ if (fromAgent)
2908
+ return fromAgent;
2909
+ const argv1 = process.argv[1];
2910
+ const realExecPath = argv1 ? readCommandOutput2("realpath", [argv1]) ?? safeRealPath2(argv1) : null;
2911
+ const npmGlobalPrefix = process.env.npm_config_prefix ?? readCommandOutput2("npm", ["prefix", "-g"]);
2912
+ const bunGlobalBin = process.env.BUN_INSTALL ? join4(process.env.BUN_INSTALL, "bin") : readCommandOutput2("bun", ["pm", "bin", "-g"]) ?? join4(homedir3(), ".bun", "bin");
2913
+ return inferInstallerFromInstallation2({
2914
+ argv1,
2915
+ realExecPath,
2916
+ npmGlobalPrefix,
2917
+ bunGlobalBin
2777
2918
  });
2778
- });
2779
-
2780
- // src/commands/setup/openclaw.ts
2781
- var exports_openclaw = {};
2782
- __export(exports_openclaw, {
2783
- getSkillDir: () => getSkillDir,
2784
- default: () => openclaw_default,
2785
- configureSafe: () => configureSafe5,
2786
- buildSkillContent: () => buildSkillContent
2787
- });
2788
- import { existsSync as existsSync2 } from "node:fs";
2789
- import { homedir as homedir3 } from "node:os";
2790
- import { join as join4 } from "node:path";
2791
- function getSkillDir() {
2792
- const home = homedir3();
2793
- const clawdDir = join4(home, "clawd");
2794
- if (existsSync2(clawdDir)) {
2795
- return join4(clawdDir, "skills", "outlit-intelligence");
2796
- }
2797
- return join4(home, ".openclaw", "skills", "outlit-intelligence");
2798
- }
2799
- function buildSkillContent(maskedKey) {
2800
- return `---
2801
- name: outlit-intelligence
2802
- description: Query customer data, revenue metrics, and analytics via the Outlit CLI.
2803
- metadata:
2804
- openclaw:
2805
- requires:
2806
- bins: ["outlit"]
2807
- env: ["OUTLIT_API_KEY"]
2808
- ---
2809
-
2810
- # Outlit Customer Intelligence
2811
-
2812
- You have access to the \`outlit\` CLI for querying customer data and analytics.
2813
- The CLI outputs structured JSON automatically in non-TTY contexts.
2814
-
2815
- ## Authentication
2816
-
2817
- Set the env var before running any command:
2818
-
2819
- export OUTLIT_API_KEY=${maskedKey}
2820
-
2821
- ## List customers
2822
-
2823
- outlit customers list --billing-status PAYING --no-activity-in 30d --order-by mrr_cents
2824
-
2825
- ## Get customer details
2826
-
2827
- outlit customers get acme.com --include users,revenue,recentTimeline
2828
-
2829
- ## Get customer activity timeline
2830
-
2831
- outlit customers timeline acme.com --channels EMAIL,SLACK --limit 50
2832
-
2833
- ## List users for a customer
2834
-
2835
- outlit users list --customer-id <uuid> --journey-stage ACTIVATED
2836
-
2837
- ## Search across customer context
2838
-
2839
- outlit search "budget concerns" --customer acme.com
2840
-
2841
- ## Get facts about a customer
2842
-
2843
- outlit facts acme.com --timeframe 90d
2844
-
2845
- ## Run custom SQL
2846
-
2847
- outlit sql "SELECT event_type, count(*) FROM events GROUP BY 1 ORDER BY 2 DESC LIMIT 10"
2848
-
2849
- ## Query with a file (for complex SQL)
2850
-
2851
- outlit sql --query-file /tmp/query.sql
2852
-
2853
- ## Discover table schemas
2854
-
2855
- outlit schema
2856
- outlit schema events
2857
-
2858
- ## Rules
2859
- - Always parse the JSON response before reporting to the user
2860
- - Convert monetary values from cents to dollars (divide by 100)
2861
- - IDs are UUIDs (e.g., "a1b2c3d4-e5f6-...")
2862
- - Never show the raw API key to the user
2863
- - Use --query-file for complex SQL to avoid shell escaping issues
2864
- - All list responses include pagination.hasMore and pagination.nextCursor
2865
- `;
2866
2919
  }
2867
- function configureSafe5(key, json) {
2868
- const skillPath = join4(getSkillDir(), "SKILL.md");
2920
+ function safeRealPath2(filePath) {
2869
2921
  try {
2870
- writeConfigFile(skillPath, buildSkillContent(maskKey(key)), { json, label: "SKILL.md" });
2871
- return true;
2922
+ return realpathSync2(filePath);
2872
2923
  } catch {
2873
- return false;
2924
+ return null;
2874
2925
  }
2875
2926
  }
2876
- var openclaw_default;
2877
- var init_openclaw = __esm(() => {
2878
- init_dist();
2879
- init_auth();
2880
- init_output2();
2927
+ function formatUpdateCommand2(installer = inferInstaller2()) {
2928
+ switch (installer) {
2929
+ case "bun":
2930
+ return "bun add -g @outlit/cli";
2931
+ case "npm":
2932
+ return "npm install -g @outlit/cli";
2933
+ case "pnpm":
2934
+ return "pnpm add -g @outlit/cli";
2935
+ case "yarn":
2936
+ return "yarn global add @outlit/cli";
2937
+ default:
2938
+ return `update ${PACKAGE_NAME2} with your package manager`;
2939
+ }
2940
+ }
2941
+ function getUpgradeCommand(installer = inferInstaller2()) {
2942
+ switch (installer) {
2943
+ case "bun":
2944
+ return {
2945
+ command: "bun",
2946
+ args: ["add", "-g", "@outlit/cli"],
2947
+ displayCommand: "bun add -g @outlit/cli"
2948
+ };
2949
+ case "npm":
2950
+ return {
2951
+ command: "npm",
2952
+ args: ["install", "-g", "@outlit/cli"],
2953
+ displayCommand: "npm install -g @outlit/cli"
2954
+ };
2955
+ case "pnpm":
2956
+ return {
2957
+ command: "pnpm",
2958
+ args: ["add", "-g", "@outlit/cli"],
2959
+ displayCommand: "pnpm add -g @outlit/cli"
2960
+ };
2961
+ case "yarn":
2962
+ return {
2963
+ command: "yarn",
2964
+ args: ["global", "add", "@outlit/cli"],
2965
+ displayCommand: "yarn global add @outlit/cli"
2966
+ };
2967
+ default:
2968
+ return null;
2969
+ }
2970
+ }
2971
+ async function fetchLatestCliVersion2() {
2972
+ const response = await fetch(LATEST_VERSION_URL2, { signal: AbortSignal.timeout(5000) });
2973
+ if (!response.ok)
2974
+ throw new Error("registry unavailable");
2975
+ const data = await response.json();
2976
+ if (!data.version)
2977
+ throw new Error("registry returned no version");
2978
+ return data.version;
2979
+ }
2980
+ function runUpgradeCommand(command) {
2981
+ const result = spawnSync2(command.command, command.args, { stdio: "inherit" });
2982
+ if (result.error)
2983
+ throw result.error;
2984
+ if (result.status !== 0 || result.signal) {
2985
+ throw new Error(`Upgrade command failed: ${command.displayCommand}`);
2986
+ }
2987
+ }
2988
+ var UPDATE_CHECK_INTERVAL_MS2, PACKAGE_NAME2 = "@outlit/cli", LATEST_VERSION_URL2 = "https://registry.npmjs.org/@outlit%2Fcli/latest";
2989
+ var init_update = __esm(() => {
2881
2990
  init_config();
2882
- init_output();
2883
- openclaw_default = defineCommand2({
2884
- meta: {
2885
- name: "openclaw",
2886
- description: "Write Outlit intelligence skill to ~/clawd/skills/ for OpenClaw."
2887
- },
2888
- args: { ...authArgs, ...outputArgs },
2889
- run({ args }) {
2890
- const json = !!args.json;
2891
- const { key } = requireCredential(args["api-key"], json);
2892
- const skillPath = join4(getSkillDir(), "SKILL.md");
2893
- const content = buildSkillContent(maskKey(key));
2894
- writeConfigFile(skillPath, content, { json, label: "SKILL.md" });
2895
- if (isJsonMode(json)) {
2896
- return outputResult({ success: true, path: skillPath, agent: "openclaw" });
2897
- }
2898
- console.log(`${TICK2} Outlit skill written to ${skillPath}. OpenClaw will load it automatically.`);
2899
- }
2900
- });
2991
+ init_tty();
2992
+ UPDATE_CHECK_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
2901
2993
  });
2902
2994
 
2903
2995
  // src/commands/setup/skills.ts
2904
2996
  var exports_skills = {};
2905
2997
  __export(exports_skills, {
2906
2998
  runSkillsInstall: () => runSkillsInstall,
2999
+ runAgentSkillsInstall: () => runAgentSkillsInstall,
3000
+ getSkillAgentId: () => getSkillAgentId,
2907
3001
  detectPackageRunner: () => detectPackageRunner,
2908
- default: () => skills_default
3002
+ default: () => skills_default,
3003
+ SKILLS_REPO_URL: () => SKILLS_REPO_URL
2909
3004
  });
2910
3005
  import { execFileSync as execFileSync4 } from "node:child_process";
3006
+ function getSkillAgentId(agent) {
3007
+ return skillAgentMap[agent];
3008
+ }
2911
3009
  function detectPackageRunner() {
2912
3010
  const whichCmd = process.platform === "win32" ? "where" : "which";
2913
3011
  for (const runner of ["npx", "bunx", "pnpx"]) {
@@ -2918,16 +3016,32 @@ function detectPackageRunner() {
2918
3016
  }
2919
3017
  return null;
2920
3018
  }
2921
- function buildRunnerArgs(runner) {
3019
+ function buildRunnerArgs(runner, opts) {
2922
3020
  const args = runner === "npx" ? ["-y"] : [];
2923
3021
  args.push("skills", "add", SKILLS_REPO_URL);
2924
- for (const name of SKILL_NAMES) {
2925
- args.push("--skill", name);
3022
+ for (const skillName of opts.skillNames ?? []) {
3023
+ args.push("--skill", skillName);
3024
+ }
3025
+ for (const agent of opts.agents ?? []) {
3026
+ args.push("--agent", agent);
3027
+ }
3028
+ if (opts.autoConfirm) {
3029
+ args.push("-y");
3030
+ }
3031
+ if (opts.global !== false) {
3032
+ args.push("-g");
2926
3033
  }
2927
- args.push("-y", "-g");
2928
3034
  return args;
2929
3035
  }
2930
- function runSkillsInstall(json, exitOnError = true) {
3036
+ function runSkillsInstall(opts) {
3037
+ const {
3038
+ json,
3039
+ exitOnError = true,
3040
+ agents,
3041
+ skillNames,
3042
+ reportedAgent = "skills",
3043
+ autoConfirm = false
3044
+ } = opts;
2931
3045
  const runner = detectPackageRunner();
2932
3046
  if (!runner) {
2933
3047
  if (exitOnError) {
@@ -2938,8 +3052,11 @@ function runSkillsInstall(json, exitOnError = true) {
2938
3052
  }
2939
3053
  return { success: false, error: "No package runner found" };
2940
3054
  }
3055
+ const isInteractiveInstall = (agents?.length ?? 0) === 0 && (skillNames?.length ?? 0) === 0 && !autoConfirm && !isJsonMode(json);
2941
3056
  try {
2942
- execFileSync4(runner, buildRunnerArgs(runner), { stdio: "pipe" });
3057
+ execFileSync4(runner, buildRunnerArgs(runner, { agents, skillNames, autoConfirm }), {
3058
+ stdio: isInteractiveInstall ? "inherit" : "pipe"
3059
+ });
2943
3060
  } catch (err) {
2944
3061
  if (exitOnError) {
2945
3062
  if (isEnoentError(err)) {
@@ -2958,210 +3075,355 @@ function runSkillsInstall(json, exitOnError = true) {
2958
3075
  }
2959
3076
  if (exitOnError) {
2960
3077
  if (isJsonMode(json)) {
2961
- outputResult({ success: true, agent: "skills", runner });
3078
+ outputResult({ success: true, agent: reportedAgent, runner });
2962
3079
  return { success: true, runner };
2963
3080
  }
2964
- console.log(`${TICK2} Agent skills installed (${SKILL_NAMES.join(", ")})`);
3081
+ if (reportedAgent === "skills") {
3082
+ console.log(`${TICK2} Outlit skills installer completed (${runner})`);
3083
+ } else {
3084
+ console.log(`${TICK2} Outlit skill installed for ${reportedAgent}`);
3085
+ }
2965
3086
  }
2966
3087
  return { success: true, runner };
2967
3088
  }
2968
- var SKILLS_REPO_URL = "https://github.com/OutlitAI/outlit-agent-skills", SKILL_NAMES, skills_default;
3089
+ function runAgentSkillsInstall(agent, json, exitOnError = true) {
3090
+ return runSkillsInstall({
3091
+ json,
3092
+ exitOnError,
3093
+ agents: [getSkillAgentId(agent)],
3094
+ skillNames: [DEFAULT_SKILL_NAME],
3095
+ reportedAgent: agent,
3096
+ autoConfirm: true
3097
+ });
3098
+ }
3099
+ var SKILLS_REPO_URL = "https://github.com/OutlitAI/outlit-agent-skills", DEFAULT_SKILL_NAME = "outlit", skillAgentMap, skills_default;
2969
3100
  var init_skills = __esm(() => {
2970
3101
  init_dist();
2971
3102
  init_output2();
2972
3103
  init_config();
2973
3104
  init_output();
2974
- SKILL_NAMES = ["outlit-cli", "outlit-sdk"];
3105
+ skillAgentMap = {
3106
+ "claude-code": "claude-code",
3107
+ codex: "codex",
3108
+ gemini: "gemini-cli",
3109
+ droid: "droid",
3110
+ opencode: "opencode",
3111
+ pi: "pi",
3112
+ openclaw: "openclaw"
3113
+ };
2975
3114
  skills_default = defineCommand2({
2976
3115
  meta: {
2977
3116
  name: "skills",
2978
3117
  description: [
2979
- "Install Outlit agent skills for Claude Code and other AI agents.",
3118
+ "Launch the interactive Skills installer for Outlit.",
2980
3119
  "",
2981
- "Downloads outlit-cli and outlit-sdk skills from github.com/OutlitAI/outlit-agent-skills.",
3120
+ "Uses the Outlit skills repo and lets you choose `outlit` and optional extras like `outlit-sdk`.",
2982
3121
  "No API key required."
2983
3122
  ].join(`
2984
3123
  `)
2985
3124
  },
2986
3125
  args: { ...outputArgs },
2987
3126
  run({ args }) {
2988
- const json = !!args.json;
2989
- runSkillsInstall(json);
3127
+ runSkillsInstall({ json: !!args.json });
2990
3128
  }
2991
3129
  });
2992
3130
  });
2993
3131
 
2994
- // src/commands/setup/vscode.ts
2995
- var exports_vscode = {};
2996
- __export(exports_vscode, {
2997
- default: () => vscode_default,
2998
- configureSafe: () => configureSafe6
3132
+ // src/commands/setup/claude-code.ts
3133
+ var exports_claude_code = {};
3134
+ __export(exports_claude_code, {
3135
+ default: () => claude_code_default
2999
3136
  });
3000
- import { join as join5 } from "node:path";
3001
- function configureSafe6(key, json) {
3002
- return runMcpFileSetup(key, json, getConfig5(), false).success;
3003
- }
3004
- var getConfig5 = () => ({
3005
- agentId: "vscode",
3006
- configPath: join5(process.cwd(), ".vscode", "mcp.json"),
3007
- serversKey: "servers",
3008
- label: ".vscode/mcp.json",
3009
- successMessage: "Outlit MCP config written to .vscode/mcp.json. Restart VS Code to apply."
3010
- }), vscode_default;
3011
- var init_vscode = __esm(() => {
3137
+ var claude_code_default;
3138
+ var init_claude_code = __esm(() => {
3012
3139
  init_dist();
3013
- init_auth();
3014
3140
  init_output2();
3015
- init_config();
3016
- init_setup();
3017
- vscode_default = defineCommand2({
3141
+ init_skills();
3142
+ claude_code_default = defineCommand2({
3018
3143
  meta: {
3019
- name: "vscode",
3020
- description: "Write Outlit MCP config to .vscode/mcp.json in the current directory."
3144
+ name: "claude-code",
3145
+ description: "Install the Outlit skill for Claude Code."
3021
3146
  },
3022
- args: { ...authArgs, ...outputArgs },
3147
+ args: { ...outputArgs },
3023
3148
  run({ args }) {
3024
- const json = !!args.json;
3025
- const { key } = requireCredential(args["api-key"], json);
3026
- runMcpFileSetup(key, json, getConfig5());
3149
+ runAgentSkillsInstall("claude-code", !!args.json);
3027
3150
  }
3028
3151
  });
3029
3152
  });
3030
3153
 
3031
- // src/commands/setup/index.ts
3032
- import { execFileSync as execFileSync5 } from "node:child_process";
3033
- import { existsSync as existsSync3 } from "node:fs";
3034
- import { homedir as homedir4 } from "node:os";
3035
- import { join as join6 } from "node:path";
3036
- function isCommandAvailable(cmd) {
3037
- try {
3038
- const whichCmd = process.platform === "win32" ? "where" : "which";
3039
- execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
3040
- return true;
3041
- } catch {
3042
- return false;
3043
- }
3044
- }
3045
- function detectAgents() {
3046
- const home = homedir4();
3047
- const detected = [];
3048
- if (existsSync3(join6(home, ".cursor")))
3049
- detected.push("cursor");
3050
- if (isCommandAvailable("claude"))
3051
- detected.push("claude-code");
3052
- if (existsSync3(getClaudeDesktopConfigPath()))
3053
- detected.push("claude-desktop");
3054
- if (isCommandAvailable("code") || existsSync3(join6(process.cwd(), ".vscode")))
3055
- detected.push("vscode");
3056
- if (isCommandAvailable("gemini"))
3057
- detected.push("gemini");
3058
- if (existsSync3(join6(home, "clawd", "skills")) || existsSync3(join6(home, ".openclaw", "skills")))
3059
- detected.push("openclaw");
3060
- return detected;
3061
- }
3062
- var agentLabels, configurators, setup_default;
3063
- var init_setup2 = __esm(() => {
3154
+ // src/commands/setup/codex.ts
3155
+ var exports_codex = {};
3156
+ __export(exports_codex, {
3157
+ default: () => codex_default
3158
+ });
3159
+ var codex_default;
3160
+ var init_codex = __esm(() => {
3064
3161
  init_dist();
3065
- init_auth();
3066
3162
  init_output2();
3067
- init_config();
3068
- init_output();
3069
- init_claude_code();
3070
- init_claude_desktop();
3071
- init_cursor();
3072
- init_gemini();
3073
- init_openclaw();
3074
3163
  init_skills();
3075
- init_vscode();
3076
- agentLabels = {
3077
- cursor: { label: "Cursor", hint: "~/.cursor/" },
3078
- "claude-code": { label: "Claude Code", hint: "claude CLI found" },
3079
- "claude-desktop": { label: "Claude Desktop", hint: "config file found" },
3080
- vscode: { label: "VS Code", hint: "code CLI or .vscode/ found" },
3081
- gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
3082
- openclaw: { label: "OpenClaw", hint: "skills directory found" }
3083
- };
3084
- configurators = {
3085
- cursor: configureSafe3,
3086
- "claude-code": configureSafe,
3087
- "claude-desktop": configureSafe2,
3088
- vscode: configureSafe6,
3089
- gemini: configureSafe4,
3090
- openclaw: configureSafe5
3091
- };
3092
- setup_default = defineCommand2({
3164
+ codex_default = defineCommand2({
3093
3165
  meta: {
3094
- name: "setup",
3095
- description: [
3096
- "Configure Outlit for AI agent tools.",
3097
- "",
3098
- "Without a subcommand, auto-detects installed agents and configures them all.",
3099
- "Subcommands: cursor, claude-code, claude-desktop, vscode, gemini, openclaw, skills"
3100
- ].join(`
3101
- `)
3166
+ name: "codex",
3167
+ description: "Install the Outlit skill for Codex."
3102
3168
  },
3103
- args: {
3104
- ...authArgs,
3169
+ args: { ...outputArgs },
3170
+ run({ args }) {
3171
+ runAgentSkillsInstall("codex", !!args.json);
3172
+ }
3173
+ });
3174
+ });
3175
+
3176
+ // src/commands/setup/gemini.ts
3177
+ var exports_gemini = {};
3178
+ __export(exports_gemini, {
3179
+ default: () => gemini_default
3180
+ });
3181
+ var gemini_default;
3182
+ var init_gemini = __esm(() => {
3183
+ init_dist();
3184
+ init_output2();
3185
+ init_skills();
3186
+ gemini_default = defineCommand2({
3187
+ meta: {
3188
+ name: "gemini",
3189
+ description: "Install the Outlit skill for Gemini CLI."
3190
+ },
3191
+ args: { ...outputArgs },
3192
+ run({ args }) {
3193
+ runAgentSkillsInstall("gemini", !!args.json);
3194
+ }
3195
+ });
3196
+ });
3197
+
3198
+ // src/commands/setup/droid.ts
3199
+ var exports_droid = {};
3200
+ __export(exports_droid, {
3201
+ default: () => droid_default
3202
+ });
3203
+ var droid_default;
3204
+ var init_droid = __esm(() => {
3205
+ init_dist();
3206
+ init_output2();
3207
+ init_skills();
3208
+ droid_default = defineCommand2({
3209
+ meta: {
3210
+ name: "droid",
3211
+ description: "Install the Outlit skill for Droid."
3212
+ },
3213
+ args: { ...outputArgs },
3214
+ run({ args }) {
3215
+ runAgentSkillsInstall("droid", !!args.json);
3216
+ }
3217
+ });
3218
+ });
3219
+
3220
+ // src/commands/setup/opencode.ts
3221
+ var exports_opencode = {};
3222
+ __export(exports_opencode, {
3223
+ default: () => opencode_default
3224
+ });
3225
+ var opencode_default;
3226
+ var init_opencode = __esm(() => {
3227
+ init_dist();
3228
+ init_output2();
3229
+ init_skills();
3230
+ opencode_default = defineCommand2({
3231
+ meta: {
3232
+ name: "opencode",
3233
+ description: "Install the Outlit skill for OpenCode."
3234
+ },
3235
+ args: { ...outputArgs },
3236
+ run({ args }) {
3237
+ runAgentSkillsInstall("opencode", !!args.json);
3238
+ }
3239
+ });
3240
+ });
3241
+
3242
+ // src/commands/setup/pi.ts
3243
+ var exports_pi = {};
3244
+ __export(exports_pi, {
3245
+ default: () => pi_default
3246
+ });
3247
+ var pi_default;
3248
+ var init_pi = __esm(() => {
3249
+ init_dist();
3250
+ init_output2();
3251
+ init_skills();
3252
+ pi_default = defineCommand2({
3253
+ meta: {
3254
+ name: "pi",
3255
+ description: "Install the Outlit skill for Pi."
3256
+ },
3257
+ args: { ...outputArgs },
3258
+ run({ args }) {
3259
+ runAgentSkillsInstall("pi", !!args.json);
3260
+ }
3261
+ });
3262
+ });
3263
+
3264
+ // src/commands/setup/openclaw.ts
3265
+ var exports_openclaw = {};
3266
+ __export(exports_openclaw, {
3267
+ default: () => openclaw_default
3268
+ });
3269
+ var openclaw_default;
3270
+ var init_openclaw = __esm(() => {
3271
+ init_dist();
3272
+ init_output2();
3273
+ init_skills();
3274
+ openclaw_default = defineCommand2({
3275
+ meta: {
3276
+ name: "openclaw",
3277
+ description: "Install the Outlit skill for OpenClaw."
3278
+ },
3279
+ args: { ...outputArgs },
3280
+ run({ args }) {
3281
+ runAgentSkillsInstall("openclaw", !!args.json);
3282
+ }
3283
+ });
3284
+ });
3285
+
3286
+ // src/commands/setup/index.ts
3287
+ import { execFileSync as execFileSync5 } from "node:child_process";
3288
+ import { existsSync as existsSync4 } from "node:fs";
3289
+ import { homedir as homedir4 } from "node:os";
3290
+ import { join as join5 } from "node:path";
3291
+ function isCommandAvailable(cmd) {
3292
+ try {
3293
+ const whichCmd = process.platform === "win32" ? "where" : "which";
3294
+ execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
3295
+ return true;
3296
+ } catch {
3297
+ return false;
3298
+ }
3299
+ }
3300
+ function getHomeDir() {
3301
+ return process.env.HOME?.trim() || homedir4();
3302
+ }
3303
+ function detectAgents() {
3304
+ const home = getHomeDir();
3305
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join5(home, ".config");
3306
+ const detected = [];
3307
+ if (isCommandAvailable("claude"))
3308
+ detected.push("claude-code");
3309
+ if (isCommandAvailable("codex"))
3310
+ detected.push("codex");
3311
+ if (isCommandAvailable("gemini"))
3312
+ detected.push("gemini");
3313
+ if (existsSync4(join5(home, ".factory")))
3314
+ detected.push("droid");
3315
+ if (existsSync4(join5(configHome, "opencode")))
3316
+ detected.push("opencode");
3317
+ if (existsSync4(join5(home, ".pi", "agent")))
3318
+ detected.push("pi");
3319
+ if (existsSync4(join5(home, ".openclaw")) || existsSync4(join5(home, ".clawdbot")) || existsSync4(join5(home, ".moltbot"))) {
3320
+ detected.push("openclaw");
3321
+ }
3322
+ return detected;
3323
+ }
3324
+ var agentLabels, setupSubcommandNames, setup_default;
3325
+ var init_setup = __esm(() => {
3326
+ init_dist();
3327
+ init_output2();
3328
+ init_config();
3329
+ init_output();
3330
+ init_skills();
3331
+ agentLabels = {
3332
+ "claude-code": { label: "Claude Code", hint: "claude CLI found" },
3333
+ codex: { label: "Codex", hint: "codex CLI found" },
3334
+ gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
3335
+ droid: { label: "Droid", hint: ".factory config found" },
3336
+ opencode: { label: "OpenCode", hint: "opencode config found" },
3337
+ pi: { label: "Pi", hint: ".pi/agent config found" },
3338
+ openclaw: { label: "OpenClaw", hint: "OpenClaw config found" }
3339
+ };
3340
+ setupSubcommandNames = new Set([
3341
+ "claude-code",
3342
+ "codex",
3343
+ "gemini",
3344
+ "droid",
3345
+ "opencode",
3346
+ "pi",
3347
+ "openclaw",
3348
+ "skills"
3349
+ ]);
3350
+ setup_default = defineCommand2({
3351
+ meta: {
3352
+ name: "setup",
3353
+ description: [
3354
+ "Install the Outlit skill for coding agents.",
3355
+ "",
3356
+ "Without a subcommand, auto-detects supported coding agents and installs `outlit` for all of them.",
3357
+ "Subcommands: claude-code, codex, gemini, droid, opencode, pi, openclaw, skills"
3358
+ ].join(`
3359
+ `)
3360
+ },
3361
+ args: {
3105
3362
  ...outputArgs,
3106
3363
  yes: {
3107
3364
  type: "boolean",
3108
- description: "Configure all detected agents without prompting."
3365
+ description: "Install for all detected coding agents without prompting."
3109
3366
  }
3110
3367
  },
3111
3368
  subCommands: {
3112
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
3113
3369
  "claude-code": () => Promise.resolve().then(() => (init_claude_code(), exports_claude_code)).then((m) => m.default),
3114
- "claude-desktop": () => Promise.resolve().then(() => (init_claude_desktop(), exports_claude_desktop)).then((m) => m.default),
3115
- vscode: () => Promise.resolve().then(() => (init_vscode(), exports_vscode)).then((m) => m.default),
3370
+ codex: () => Promise.resolve().then(() => (init_codex(), exports_codex)).then((m) => m.default),
3116
3371
  gemini: () => Promise.resolve().then(() => (init_gemini(), exports_gemini)).then((m) => m.default),
3372
+ droid: () => Promise.resolve().then(() => (init_droid(), exports_droid)).then((m) => m.default),
3373
+ opencode: () => Promise.resolve().then(() => (init_opencode(), exports_opencode)).then((m) => m.default),
3374
+ pi: () => Promise.resolve().then(() => (init_pi(), exports_pi)).then((m) => m.default),
3117
3375
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
3118
3376
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
3119
3377
  },
3120
- async run({ args }) {
3378
+ async run({ args, rawArgs }) {
3379
+ const setupRawArgs = rawArgs ?? [];
3380
+ const subcommandName = setupRawArgs.find((arg) => !arg.startsWith("-"));
3381
+ if (subcommandName && setupSubcommandNames.has(subcommandName)) {
3382
+ return;
3383
+ }
3121
3384
  const json = !!args.json;
3122
- const credential = requireCredential(args["api-key"], json);
3123
3385
  const detected = detectAgents();
3124
3386
  if (detected.length === 0) {
3125
3387
  if (isJsonMode(json)) {
3126
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
3388
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
3127
3389
  }
3128
- console.log("No supported agent tools detected.");
3390
+ console.log("No supported coding agents detected.");
3129
3391
  return;
3130
3392
  }
3131
3393
  if (!isJsonMode(json) && !args.yes) {
3132
- console.log("Detected agents:");
3394
+ console.log("Detected coding agents:");
3133
3395
  for (const agentId of detected) {
3134
3396
  const { label, hint } = agentLabels[agentId];
3135
3397
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
3136
3398
  }
3137
3399
  console.log(`
3138
- Configuring...`);
3139
- }
3140
- const configured = [];
3141
- const failed = [];
3142
- for (const agentId of detected) {
3143
- const ok = configurators[agentId](credential.key, json);
3144
- if (ok) {
3145
- configured.push(agentId);
3146
- } else {
3147
- failed.push(agentId);
3148
- }
3149
- }
3150
- const skills = runSkillsInstall(json, false);
3151
- if (!isJsonMode(json) && !skills.success) {
3152
- console.log(`
3153
- ! Agent skills installation failed: ${skills.error ?? "unknown error"}`);
3154
- console.log(" Run `outlit setup skills` to retry.");
3400
+ Installing Outlit skill...`);
3155
3401
  }
3402
+ const install = runSkillsInstall({
3403
+ json,
3404
+ exitOnError: false,
3405
+ agents: detected.map(getSkillAgentId),
3406
+ skillNames: ["outlit"],
3407
+ autoConfirm: true
3408
+ });
3409
+ const configured = install.success ? detected : [];
3410
+ const failed = install.success ? [] : detected;
3156
3411
  if (isJsonMode(json)) {
3157
- return outputResult({ detected, configured, failed, skills });
3412
+ return outputResult({
3413
+ detected,
3414
+ configured,
3415
+ failed,
3416
+ runner: install.runner ?? null
3417
+ });
3158
3418
  }
3159
- if (failed.length > 0) {
3419
+ if (!install.success) {
3160
3420
  console.log(`
3161
- ${failed.length} agent(s) failed: ${failed.join(", ")}`);
3421
+ ! Outlit skill install failed: ${install.error ?? "unknown error"}`);
3422
+ console.log(" Run `outlit setup skills` to retry manually.");
3423
+ return;
3162
3424
  }
3163
3425
  console.log(`
3164
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
3426
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
3165
3427
  }
3166
3428
  });
3167
3429
  });
@@ -3169,21 +3431,16 @@ Done. ${configured.length}/${detected.length} agent(s) configured successfully.`
3169
3431
  // src/commands/doctor.ts
3170
3432
  var exports_doctor = {};
3171
3433
  __export(exports_doctor, {
3172
- default: () => doctor_default
3434
+ default: () => doctor_default,
3435
+ buildAgentChecks: () => buildAgentChecks
3173
3436
  });
3174
- import { existsSync as existsSync4 } from "node:fs";
3437
+ import { existsSync as existsSync5 } from "node:fs";
3175
3438
  import { homedir as homedir5 } from "node:os";
3176
- import { join as join7 } from "node:path";
3439
+ import { join as join6 } from "node:path";
3177
3440
  async function checkCliVersion() {
3178
3441
  const current = CLI_VERSION2;
3179
3442
  try {
3180
- const res = await fetch("https://registry.npmjs.org/@outlit%2Fcli/latest", {
3181
- signal: AbortSignal.timeout(5000)
3182
- });
3183
- if (!res.ok)
3184
- throw new Error("registry unavailable");
3185
- const data = await res.json();
3186
- const latest = data.version ?? "unknown";
3443
+ const latest = await fetchLatestCliVersion2();
3187
3444
  if (latest === current) {
3188
3445
  return { name: "CLI version", status: "pass", message: `v${current} (latest)` };
3189
3446
  }
@@ -3191,7 +3448,7 @@ async function checkCliVersion() {
3191
3448
  name: "CLI version",
3192
3449
  status: "warn",
3193
3450
  message: `v${current} installed, v${latest} available`,
3194
- detail: "Run `npm update -g @outlit/cli` to update"
3451
+ detail: `Run \`${formatUpdateCommand2()}\` to update`
3195
3452
  };
3196
3453
  } catch {
3197
3454
  return {
@@ -3237,8 +3494,83 @@ async function validateApiKey(apiKey) {
3237
3494
  };
3238
3495
  }
3239
3496
  }
3240
- function detectAgents2() {
3241
- const detected = detectAgents();
3497
+ async function checkIntegrations(apiKey) {
3498
+ try {
3499
+ const client = await createClient(apiKey);
3500
+ const timeout = new Promise((_2, reject) => setTimeout(() => reject(new Error("Integrations check timed out")), 1e4));
3501
+ const data = await Promise.race([
3502
+ client.callTool("outlit_list_integrations", {}),
3503
+ timeout
3504
+ ]);
3505
+ const items = data.items ?? [];
3506
+ const connected = items.filter((i) => i.status === "connected").length;
3507
+ const errors = items.filter((i) => i.status === "error").length;
3508
+ if (errors > 0) {
3509
+ return {
3510
+ name: "Integrations",
3511
+ status: "warn",
3512
+ message: `${connected} connected, ${errors} with errors`,
3513
+ detail: "Run `outlit integrations status` for details"
3514
+ };
3515
+ }
3516
+ if (connected === 0) {
3517
+ return {
3518
+ name: "Integrations",
3519
+ status: "pass",
3520
+ message: "No integrations connected",
3521
+ detail: "Run `outlit integrations list` to see available integrations"
3522
+ };
3523
+ }
3524
+ return {
3525
+ name: "Integrations",
3526
+ status: "pass",
3527
+ message: `${connected} integration(s) connected`
3528
+ };
3529
+ } catch {
3530
+ return {
3531
+ name: "Integrations",
3532
+ status: "warn",
3533
+ message: "Could not check integrations",
3534
+ detail: "Integration status endpoint may not be available yet"
3535
+ };
3536
+ }
3537
+ }
3538
+ function getHomeDir2(options) {
3539
+ return options?.homeDir?.trim() || process.env.HOME?.trim() || homedir5();
3540
+ }
3541
+ function getSharedSkillsDir(options) {
3542
+ return join6(getHomeDir2(options), ".agents", "skills");
3543
+ }
3544
+ function getOpenClawHome(options) {
3545
+ const home = getHomeDir2(options);
3546
+ if (existsSync5(join6(home, ".openclaw")))
3547
+ return join6(home, ".openclaw");
3548
+ if (existsSync5(join6(home, ".clawdbot")))
3549
+ return join6(home, ".clawdbot");
3550
+ if (existsSync5(join6(home, ".moltbot")))
3551
+ return join6(home, ".moltbot");
3552
+ return join6(home, ".openclaw");
3553
+ }
3554
+ function getAgentSkillDir(agentId, options) {
3555
+ const home = getHomeDir2(options);
3556
+ switch (agentId) {
3557
+ case "claude-code":
3558
+ return join6(options?.claudeConfigDir?.trim() || process.env.CLAUDE_CONFIG_DIR?.trim() || join6(home, ".claude"), "skills");
3559
+ case "codex":
3560
+ return getSharedSkillsDir(options);
3561
+ case "gemini":
3562
+ return getSharedSkillsDir(options);
3563
+ case "droid":
3564
+ return join6(home, ".factory", "skills");
3565
+ case "opencode":
3566
+ return getSharedSkillsDir(options);
3567
+ case "pi":
3568
+ return join6(home, ".pi", "agent", "skills");
3569
+ case "openclaw":
3570
+ return join6(getOpenClawHome(options), "skills");
3571
+ }
3572
+ }
3573
+ function buildAgentChecks(detected = detectAgents(), options) {
3242
3574
  if (detected.length === 0) {
3243
3575
  return [
3244
3576
  {
@@ -3249,66 +3581,15 @@ function detectAgents2() {
3249
3581
  ];
3250
3582
  }
3251
3583
  const results = [];
3252
- const home = homedir5();
3253
3584
  for (const agentId of detected) {
3254
3585
  const meta = agentChecks[agentId];
3255
- if (agentId === "openclaw") {
3256
- const openclawBase = existsSync4(join7(home, "clawd", "skills")) ? join7(home, "clawd", "skills") : join7(home, ".openclaw", "skills");
3257
- const hasSkill = existsSync4(join7(openclawBase, "outlit-intelligence", "SKILL.md"));
3258
- results.push({
3259
- name: meta.name,
3260
- status: hasSkill ? "pass" : "warn",
3261
- message: hasSkill ? "Installed, Outlit skill configured" : "Installed, but Outlit skill not found",
3262
- detail: hasSkill ? undefined : "Run `outlit setup openclaw` to configure"
3263
- });
3264
- continue;
3265
- }
3266
- if (!meta.configCheck) {
3267
- results.push({
3268
- name: meta.name,
3269
- status: "warn",
3270
- message: "Installed, but Outlit MCP not verified",
3271
- detail: `Run \`outlit setup ${agentId}\` to configure`
3272
- });
3273
- if (agentId === "claude-code") {
3274
- const skillsDir = join7(home, ".claude", "skills");
3275
- const hasSkills = existsSync4(join7(skillsDir, "outlit-cli")) || existsSync4(join7(skillsDir, "outlit-sdk")) || existsSync4(join7(skillsDir, "outlit-mcp"));
3276
- results.push({
3277
- name: "Agent Skills",
3278
- status: hasSkills ? "pass" : "warn",
3279
- message: hasSkills ? "Outlit agent skills installed" : "Outlit agent skills not found",
3280
- detail: hasSkills ? undefined : "Run `outlit setup skills` to install deeper AI agent context"
3281
- });
3282
- }
3283
- continue;
3284
- }
3285
- const { path, key } = meta.configCheck;
3286
- if (!existsSync4(path)) {
3287
- results.push({
3288
- name: meta.name,
3289
- status: "warn",
3290
- message: "Installed, but config file not found",
3291
- detail: `Run \`outlit setup ${agentId}\` to configure`
3292
- });
3293
- continue;
3294
- }
3295
- try {
3296
- const config = readJsonConfig(path);
3297
- const configured = !!config[key]?.outlit;
3298
- results.push({
3299
- name: meta.name,
3300
- status: configured ? "pass" : "warn",
3301
- message: configured ? "Installed, Outlit MCP configured" : "Installed, but Outlit MCP not configured",
3302
- detail: configured ? undefined : `Run \`outlit setup ${agentId}\` to configure`
3303
- });
3304
- } catch {
3305
- results.push({
3306
- name: meta.name,
3307
- status: "warn",
3308
- message: "Installed, but config file is malformed",
3309
- detail: `Check ${path} for JSON syntax errors`
3310
- });
3311
- }
3586
+ const hasSkill = existsSync5(join6(getAgentSkillDir(agentId, options), "outlit", "SKILL.md"));
3587
+ results.push({
3588
+ name: meta.name,
3589
+ status: hasSkill ? "pass" : "warn",
3590
+ message: hasSkill ? "Outlit skill installed" : "Installed, but Outlit skill not found",
3591
+ detail: hasSkill ? undefined : meta.missingDetail
3592
+ });
3312
3593
  }
3313
3594
  return results;
3314
3595
  }
@@ -3339,10 +3620,12 @@ var init_doctor = __esm(() => {
3339
3620
  init_auth();
3340
3621
  init_output2();
3341
3622
  init_api();
3623
+ init_client();
3342
3624
  init_config();
3343
3625
  init_output();
3344
3626
  init_tty();
3345
- init_setup2();
3627
+ init_update();
3628
+ init_setup();
3346
3629
  FAIL_SYMBOL2 = isUnicodeSupported ? String.fromCodePoint(10007) : "x";
3347
3630
  STATUS_ICONS = {
3348
3631
  pass: TICK2,
@@ -3359,7 +3642,7 @@ var init_doctor = __esm(() => {
3359
3642
  " 1. CLI version -- compares against npm registry",
3360
3643
  " 2. API key -- checks presence and format (ok_ prefix)",
3361
3644
  " 3. API validation -- makes a live test call to verify the key works",
3362
- " 4. Agent detection -- detects OpenClaw, Cursor, Claude Desktop, VS Code",
3645
+ " 4. Agent detection -- detects supported coding agents and whether the Outlit skill is installed",
3363
3646
  "",
3364
3647
  "Exit code: 0 if all checks pass or warn, 1 if any check fails.",
3365
3648
  "",
@@ -3383,7 +3666,11 @@ var init_doctor = __esm(() => {
3383
3666
  const credential = resolveApiKey(args["api-key"]);
3384
3667
  checks.push(checkApiKeyPresence(credential));
3385
3668
  if (credential) {
3386
- checks.push(await validateApiKey(credential.key));
3669
+ const apiCheck = await validateApiKey(credential.key);
3670
+ checks.push(apiCheck);
3671
+ if (apiCheck.status !== "fail") {
3672
+ checks.push(await checkIntegrations(credential.key));
3673
+ }
3387
3674
  } else {
3388
3675
  checks.push({
3389
3676
  name: "API validation",
@@ -3391,60 +3678,149 @@ var init_doctor = __esm(() => {
3391
3678
  message: "Skipped -- no API key found"
3392
3679
  });
3393
3680
  }
3394
- checks.push(...detectAgents2());
3681
+ checks.push(...buildAgentChecks());
3395
3682
  const hasFail = checks.some((c) => c.status === "fail");
3396
3683
  if (isJsonMode(json)) {
3397
3684
  outputResult({ ok: !hasFail, checks });
3398
3685
  } else {
3399
3686
  printChecks(checks);
3400
3687
  }
3401
- process.exit(hasFail ? 1 : 0);
3688
+ if (hasFail)
3689
+ process.exit(1);
3402
3690
  }
3403
3691
  });
3404
3692
  agentChecks = {
3405
- cursor: {
3406
- name: "Cursor",
3407
- configCheck: { path: join7(homedir5(), ".cursor", "mcp.json"), key: "mcpServers" }
3693
+ "claude-code": {
3694
+ name: "Claude Code",
3695
+ missingDetail: "Run `outlit setup claude-code` to install the Outlit skill"
3696
+ },
3697
+ codex: {
3698
+ name: "Codex",
3699
+ missingDetail: "Run `outlit setup codex` to install the Outlit skill"
3408
3700
  },
3409
- "claude-code": { name: "Claude Code" },
3410
- "claude-desktop": {
3411
- name: "Claude Desktop",
3412
- configCheck: { path: getClaudeDesktopConfigPath(), key: "mcpServers" }
3701
+ gemini: {
3702
+ name: "Gemini CLI",
3703
+ missingDetail: "Run `outlit setup gemini` to install the Outlit skill"
3413
3704
  },
3414
- vscode: {
3415
- name: "VS Code",
3416
- configCheck: { path: join7(process.cwd(), ".vscode", "mcp.json"), key: "servers" }
3705
+ droid: {
3706
+ name: "Droid",
3707
+ missingDetail: "Run `outlit setup droid` to install the Outlit skill"
3417
3708
  },
3418
- gemini: { name: "Gemini CLI" },
3419
- openclaw: { name: "OpenClaw" }
3709
+ opencode: {
3710
+ name: "OpenCode",
3711
+ missingDetail: "Run `outlit setup opencode` to install the Outlit skill"
3712
+ },
3713
+ pi: {
3714
+ name: "Pi",
3715
+ missingDetail: "Run `outlit setup pi` to install the Outlit skill"
3716
+ },
3717
+ openclaw: {
3718
+ name: "OpenClaw",
3719
+ missingDetail: "Run `outlit setup openclaw` to install the Outlit skill"
3720
+ }
3420
3721
  };
3421
3722
  });
3422
3723
 
3423
- // src/commands/facts.ts
3424
- var exports_facts = {};
3425
- __export(exports_facts, {
3426
- default: () => facts_default
3724
+ // src/commands/upgrade.ts
3725
+ var exports_upgrade = {};
3726
+ __export(exports_upgrade, {
3727
+ default: () => upgrade_default
3427
3728
  });
3428
- var facts_default;
3429
- var init_facts = __esm(() => {
3729
+ var upgrade_default;
3730
+ var init_upgrade = __esm(() => {
3731
+ init_dist();
3732
+ init_config();
3733
+ init_output();
3734
+ init_update();
3735
+ upgrade_default = defineCommand2({
3736
+ meta: {
3737
+ name: "upgrade",
3738
+ description: [
3739
+ "Upgrade the Outlit CLI using the same package manager it was installed with.",
3740
+ "",
3741
+ "Checks npm for the latest published version first.",
3742
+ "If the current version is already latest, no install command is run.",
3743
+ "",
3744
+ "Examples:",
3745
+ " outlit upgrade"
3746
+ ].join(`
3747
+ `)
3748
+ },
3749
+ async run() {
3750
+ const upgradeCommand = getUpgradeCommand();
3751
+ if (!upgradeCommand) {
3752
+ return outputError({
3753
+ message: "Could not determine how Outlit CLI was installed. Update it manually with your package manager, for example `bun add -g @outlit/cli` or `npm install -g @outlit/cli`.",
3754
+ code: "unknown_installer"
3755
+ }, false);
3756
+ }
3757
+ let latestVersion;
3758
+ try {
3759
+ latestVersion = await fetchLatestCliVersion2();
3760
+ } catch {
3761
+ return outputError({
3762
+ message: "Could not check for CLI updates. Try again later or update manually.",
3763
+ code: "update_check_failed"
3764
+ }, false);
3765
+ }
3766
+ if (compareVersions2(CLI_VERSION2, latestVersion) >= 0) {
3767
+ console.log(`Outlit CLI is already up to date (v${CLI_VERSION2})`);
3768
+ return;
3769
+ }
3770
+ try {
3771
+ runUpgradeCommand(upgradeCommand);
3772
+ } catch (err) {
3773
+ return outputError({
3774
+ message: errorMessage(err, `Failed to run ${upgradeCommand.displayCommand}`),
3775
+ code: "upgrade_failed"
3776
+ }, false);
3777
+ }
3778
+ }
3779
+ });
3780
+ });
3781
+
3782
+ // src/commands/facts/list.ts
3783
+ var exports_list3 = {};
3784
+ __export(exports_list3, {
3785
+ default: () => list_default3
3786
+ });
3787
+ function parseCsvArg(value) {
3788
+ if (!value)
3789
+ return;
3790
+ const items = splitCsv(value).map((item) => item.trim()).filter(Boolean);
3791
+ return items.length > 0 ? items : undefined;
3792
+ }
3793
+ function invalidValues(values, allowed) {
3794
+ if (!values)
3795
+ return [];
3796
+ return values.filter((value) => !allowed.includes(value));
3797
+ }
3798
+ var list_default3;
3799
+ var init_list3 = __esm(() => {
3430
3800
  init_dist();
3431
3801
  init_auth();
3432
3802
  init_output2();
3433
3803
  init_pagination();
3804
+ init_tool_contracts();
3434
3805
  init_api();
3435
- facts_default = defineCommand2({
3806
+ init_config();
3807
+ init_output();
3808
+ list_default3 = defineCommand2({
3436
3809
  meta: {
3437
- name: "facts",
3810
+ name: "list",
3438
3811
  description: [
3439
- "Retrieve facts for a customer.",
3812
+ "List structured facts for a customer.",
3440
3813
  "",
3441
- "Returns a list of facts (signals, events, and derived insights) for the",
3442
- "specified customer within the given timeframe.",
3814
+ "Filter by fact status, source type, or occurrence date range.",
3443
3815
  "",
3444
3816
  "Examples:",
3445
- " outlit facts acme.com",
3446
- " outlit facts acme.com --timeframe 90d",
3447
- " outlit facts acme.com --limit 50 --json",
3817
+ " outlit facts list acme.com",
3818
+ " outlit facts list acme.com --status ACTIVE",
3819
+ " outlit facts list acme.com --source-types CALL,EMAIL --after 2025-01-01T00:00:00Z",
3820
+ " outlit facts list acme.com --limit 50 --json",
3821
+ "",
3822
+ `Statuses: ${customerFactStatuses.join(", ")}`,
3823
+ `Source types: ${customerSourceTypes.join(", ")}`,
3448
3824
  "",
3449
3825
  AGENT_JSON_HINT
3450
3826
  ].join(`
@@ -3459,51 +3835,817 @@ var init_facts = __esm(() => {
3459
3835
  description: "Customer UUID or domain to retrieve facts for",
3460
3836
  required: true
3461
3837
  },
3462
- timeframe: {
3838
+ status: {
3463
3839
  type: "string",
3464
- description: "Lookback window (e.g. 7d, 30d, 90d). Default: 30d.",
3465
- default: "30d"
3840
+ description: `Comma-separated fact statuses (${customerFactStatuses.join(", ")})`
3841
+ },
3842
+ "source-types": {
3843
+ type: "string",
3844
+ description: `Comma-separated generic source type filter (${customerSourceTypes.join(", ")})`
3845
+ },
3846
+ after: {
3847
+ type: "string",
3848
+ description: "Filter to facts occurring after this ISO 8601 datetime"
3849
+ },
3850
+ before: {
3851
+ type: "string",
3852
+ description: "Filter to facts occurring before this ISO 8601 datetime"
3466
3853
  }
3467
3854
  },
3468
3855
  async run({ args }) {
3469
3856
  const json = !!args.json;
3470
- const client = await getClientOrExit(args["api-key"], json);
3471
- const params = {
3472
- customer: args.customer,
3473
- timeframe: args.timeframe
3474
- };
3475
- applyPagination(params, args, json);
3476
- return runTool(client, "outlit_get_facts", params, json);
3477
- }
3478
- });
3479
- });
3480
-
3481
- // src/commands/search.ts
3482
- var exports_search = {};
3483
- __export(exports_search, {
3857
+ const statuses = parseCsvArg(args.status);
3858
+ const sourceTypes = parseCsvArg(args["source-types"]);
3859
+ const invalidStatuses = invalidValues(statuses, customerFactStatuses);
3860
+ if (invalidStatuses.length > 0) {
3861
+ return outputError({
3862
+ message: `Unknown fact statuses: ${invalidStatuses.join(", ")}. Allowed: ${customerFactStatuses.join(", ")}`,
3863
+ code: "invalid_input"
3864
+ }, json);
3865
+ }
3866
+ const invalidSourceTypes = invalidValues(sourceTypes, customerSourceTypes);
3867
+ if (invalidSourceTypes.length > 0) {
3868
+ return outputError({
3869
+ message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
3870
+ code: "invalid_input"
3871
+ }, json);
3872
+ }
3873
+ const afterDate = args.after ? new Date(args.after) : null;
3874
+ const beforeDate = args.before ? new Date(args.before) : null;
3875
+ if (afterDate && Number.isNaN(afterDate.getTime())) {
3876
+ return outputError({
3877
+ message: "--after must be a valid ISO 8601 datetime",
3878
+ code: "invalid_input"
3879
+ }, json);
3880
+ }
3881
+ if (beforeDate && Number.isNaN(beforeDate.getTime())) {
3882
+ return outputError({
3883
+ message: "--before must be a valid ISO 8601 datetime",
3884
+ code: "invalid_input"
3885
+ }, json);
3886
+ }
3887
+ if (afterDate && beforeDate && afterDate.getTime() > beforeDate.getTime()) {
3888
+ return outputError({
3889
+ message: "--after must be before or equal to --before",
3890
+ code: "invalid_input"
3891
+ }, json);
3892
+ }
3893
+ const client = await getClientOrExit(args["api-key"], json);
3894
+ const params = {
3895
+ customer: args.customer
3896
+ };
3897
+ if (statuses)
3898
+ params.status = statuses;
3899
+ if (sourceTypes)
3900
+ params.sourceTypes = sourceTypes;
3901
+ if (args.after)
3902
+ params.after = args.after;
3903
+ if (args.before)
3904
+ params.before = args.before;
3905
+ applyPagination(params, args, json);
3906
+ return runTool(client, customerToolContracts.outlit_list_facts.toolName, params, json);
3907
+ }
3908
+ });
3909
+ });
3910
+
3911
+ // src/commands/facts/get.ts
3912
+ var exports_get2 = {};
3913
+ __export(exports_get2, {
3914
+ default: () => get_default2
3915
+ });
3916
+ function parseCsvArg2(value) {
3917
+ if (!value)
3918
+ return;
3919
+ const items = splitCsv(value).map((item) => item.trim()).filter(Boolean);
3920
+ return items.length > 0 ? items : undefined;
3921
+ }
3922
+ var get_default2;
3923
+ var init_get2 = __esm(() => {
3924
+ init_dist();
3925
+ init_auth();
3926
+ init_output2();
3927
+ init_tool_contracts();
3928
+ init_api();
3929
+ init_config();
3930
+ get_default2 = defineCommand2({
3931
+ meta: {
3932
+ name: "get",
3933
+ description: [
3934
+ "Get one exact fact by ID.",
3935
+ "",
3936
+ "Use --include evidence to request best-effort evidence expansion.",
3937
+ "",
3938
+ "Examples:",
3939
+ " outlit facts get --fact-id fact_123",
3940
+ " outlit facts get --fact-id fact_123 --include evidence",
3941
+ "",
3942
+ AGENT_JSON_HINT
3943
+ ].join(`
3944
+ `)
3945
+ },
3946
+ args: {
3947
+ ...authArgs,
3948
+ ...outputArgs,
3949
+ "fact-id": {
3950
+ type: "string",
3951
+ description: "Fact ID to fetch",
3952
+ required: true
3953
+ },
3954
+ include: {
3955
+ type: "string",
3956
+ description: "Comma-separated best-effort expansions (for example: evidence)"
3957
+ }
3958
+ },
3959
+ async run({ args }) {
3960
+ const json = !!args.json;
3961
+ const client = await getClientOrExit(args["api-key"], json);
3962
+ const params = {
3963
+ factId: args["fact-id"]
3964
+ };
3965
+ const include = parseCsvArg2(args.include);
3966
+ if (include)
3967
+ params.include = include;
3968
+ return runTool(client, customerToolContracts.outlit_get_fact.toolName, params, json);
3969
+ }
3970
+ });
3971
+ });
3972
+
3973
+ // src/commands/facts/index.ts
3974
+ var exports_facts = {};
3975
+ __export(exports_facts, {
3976
+ default: () => facts_default
3977
+ });
3978
+ var facts_default;
3979
+ var init_facts = __esm(() => {
3980
+ init_dist();
3981
+ init_output2();
3982
+ facts_default = defineCommand2({
3983
+ meta: {
3984
+ name: "facts",
3985
+ description: [
3986
+ "Query structured customer facts.",
3987
+ "",
3988
+ "Subcommands:",
3989
+ " list -- list facts for a customer with filters",
3990
+ " get -- fetch one exact fact by id",
3991
+ "",
3992
+ AGENT_JSON_HINT
3993
+ ].join(`
3994
+ `)
3995
+ },
3996
+ subCommands: {
3997
+ list: () => Promise.resolve().then(() => (init_list3(), exports_list3)).then((m) => m.default),
3998
+ get: () => Promise.resolve().then(() => (init_get2(), exports_get2)).then((m) => m.default)
3999
+ }
4000
+ });
4001
+ });
4002
+
4003
+ // src/commands/sources/get.ts
4004
+ var exports_get3 = {};
4005
+ __export(exports_get3, {
4006
+ default: () => get_default3
4007
+ });
4008
+ var get_default3;
4009
+ var init_get3 = __esm(() => {
4010
+ init_dist();
4011
+ init_auth();
4012
+ init_output2();
4013
+ init_tool_contracts();
4014
+ init_api();
4015
+ init_output();
4016
+ get_default3 = defineCommand2({
4017
+ meta: {
4018
+ name: "get",
4019
+ description: [
4020
+ "Get one exact source by source type and source id.",
4021
+ "",
4022
+ "Examples:",
4023
+ " outlit sources get --source-type CALL --source-id call_123",
4024
+ " outlit sources get --source-type SUPPORT_TICKET --source-id ticket_456 --json",
4025
+ "",
4026
+ `Source types: ${customerSourceTypes.join(", ")}`,
4027
+ "",
4028
+ AGENT_JSON_HINT
4029
+ ].join(`
4030
+ `)
4031
+ },
4032
+ args: {
4033
+ ...authArgs,
4034
+ ...outputArgs,
4035
+ "source-type": {
4036
+ type: "string",
4037
+ description: "Canonical source type",
4038
+ required: true
4039
+ },
4040
+ "source-id": {
4041
+ type: "string",
4042
+ description: "Exact source id",
4043
+ required: true
4044
+ }
4045
+ },
4046
+ async run({ args }) {
4047
+ const json = !!args.json;
4048
+ if (!customerSourceTypes.includes(args["source-type"])) {
4049
+ return outputError({
4050
+ message: `--source-type must be one of ${customerSourceTypes.join(", ")}`,
4051
+ code: "invalid_input"
4052
+ }, json);
4053
+ }
4054
+ const client = await getClientOrExit(args["api-key"], json);
4055
+ return runTool(client, customerToolContracts.outlit_get_source.toolName, {
4056
+ sourceType: args["source-type"],
4057
+ sourceId: args["source-id"]
4058
+ }, json);
4059
+ }
4060
+ });
4061
+ });
4062
+
4063
+ // src/commands/sources/index.ts
4064
+ var exports_sources = {};
4065
+ __export(exports_sources, {
4066
+ default: () => sources_default
4067
+ });
4068
+ var sources_default;
4069
+ var init_sources = __esm(() => {
4070
+ init_dist();
4071
+ init_output2();
4072
+ sources_default = defineCommand2({
4073
+ meta: {
4074
+ name: "sources",
4075
+ description: [
4076
+ "Fetch concrete customer sources by type and id.",
4077
+ "",
4078
+ "Subcommands:",
4079
+ " get -- fetch one exact source by sourceType and sourceId",
4080
+ "",
4081
+ AGENT_JSON_HINT
4082
+ ].join(`
4083
+ `)
4084
+ },
4085
+ subCommands: {
4086
+ get: () => Promise.resolve().then(() => (init_get3(), exports_get3)).then((m) => m.default)
4087
+ }
4088
+ });
4089
+ });
4090
+
4091
+ // src/commands/search.ts
4092
+ var exports_search = {};
4093
+ __export(exports_search, {
3484
4094
  default: () => search_default
3485
4095
  });
3486
4096
  var search_default;
3487
4097
  var init_search = __esm(() => {
4098
+ init_dist();
4099
+ init_auth();
4100
+ init_output2();
4101
+ init_tool_contracts();
4102
+ init_api();
4103
+ init_config();
4104
+ init_output();
4105
+ search_default = defineCommand2({
4106
+ meta: {
4107
+ name: "search",
4108
+ description: [
4109
+ "Search customer context using natural language.",
4110
+ "",
4111
+ "Performs a semantic search over grouped source and fact results.",
4112
+ "Optionally scope to a specific customer with --customer.",
4113
+ "",
4114
+ "Examples:",
4115
+ " outlit search 'pricing objections last quarter'",
4116
+ " outlit search 'churn risk signals' --customer acme.com",
4117
+ " outlit search 'expansion opportunities' --top-k 50 --json",
4118
+ " outlit search 'support escalations' --after 2025-01-01T00:00:00Z --before 2025-03-31T23:59:59Z",
4119
+ " outlit search 'onboarding issues' --source-types CALL,EMAIL",
4120
+ "",
4121
+ AGENT_JSON_HINT
4122
+ ].join(`
4123
+ `)
4124
+ },
4125
+ args: {
4126
+ ...authArgs,
4127
+ ...outputArgs,
4128
+ query: {
4129
+ type: "positional",
4130
+ description: "Natural language search query",
4131
+ required: true
4132
+ },
4133
+ customer: {
4134
+ type: "string",
4135
+ description: "Scope search to a specific customer (UUID or domain)"
4136
+ },
4137
+ "top-k": {
4138
+ type: "string",
4139
+ description: "Maximum number of results to return (1–50). Default: 20."
4140
+ },
4141
+ after: {
4142
+ type: "string",
4143
+ description: "Filter to events occurring after this datetime (ISO 8601, e.g. 2025-01-01T00:00:00Z)"
4144
+ },
4145
+ before: {
4146
+ type: "string",
4147
+ description: "Filter to events occurring before this datetime (ISO 8601, e.g. 2025-03-31T23:59:59Z)"
4148
+ },
4149
+ "source-types": {
4150
+ type: "string",
4151
+ description: `Comma-separated generic source type filter (${customerSourceTypes.join(", ")})`
4152
+ }
4153
+ },
4154
+ async run({ args }) {
4155
+ const json = !!args.json;
4156
+ const topK = args["top-k"] ? Number(args["top-k"]) : undefined;
4157
+ if (topK !== undefined && (!Number.isFinite(topK) || !Number.isInteger(topK) || topK < 1 || topK > 50)) {
4158
+ return outputError({ message: "--top-k must be an integer between 1 and 50", code: "invalid_input" }, json);
4159
+ }
4160
+ const sourceTypes = args["source-types"] ? splitCsv(args["source-types"]).map((item) => item.trim()).filter(Boolean) : undefined;
4161
+ const invalidSourceTypes = sourceTypes?.filter((value) => !customerSourceTypes.includes(value));
4162
+ if (invalidSourceTypes && invalidSourceTypes.length > 0) {
4163
+ return outputError({
4164
+ message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
4165
+ code: "invalid_input"
4166
+ }, json);
4167
+ }
4168
+ const resolved = resolveCustomerContextSearchInput({
4169
+ query: args.query,
4170
+ customer: args.customer,
4171
+ topK,
4172
+ after: args.after,
4173
+ before: args.before,
4174
+ sourceTypes
4175
+ });
4176
+ if (!resolved.ok) {
4177
+ return outputError({
4178
+ message: resolved.message,
4179
+ code: "invalid_input"
4180
+ }, json);
4181
+ }
4182
+ const client = await getClientOrExit(args["api-key"], json);
4183
+ return runTool(client, customerToolContracts.outlit_search_customer_context.toolName, resolved.request, json);
4184
+ }
4185
+ });
4186
+ });
4187
+
4188
+ // src/commands/sql.ts
4189
+ var exports_sql = {};
4190
+ __export(exports_sql, {
4191
+ default: () => sql_default
4192
+ });
4193
+ import { readFileSync as readFileSync4 } from "node:fs";
4194
+ var sql_default;
4195
+ var init_sql = __esm(() => {
4196
+ init_dist();
4197
+ init_auth();
4198
+ init_output2();
4199
+ init_tool_contracts();
4200
+ init_api();
4201
+ init_output();
4202
+ sql_default = defineCommand2({
4203
+ meta: {
4204
+ name: "sql",
4205
+ description: [
4206
+ "Run a SQL query against Outlit's analytics database.",
4207
+ "",
4208
+ "Provide the query as a positional argument or via --query-file.",
4209
+ "When both are provided, --query-file takes precedence.",
4210
+ "",
4211
+ `Available tables: ${schemaTables.join(", ")}`,
4212
+ "",
4213
+ "Examples:",
4214
+ " outlit sql 'SELECT * FROM events LIMIT 10'",
4215
+ " outlit sql --query-file ./my-query.sql",
4216
+ " outlit sql 'SELECT count(*) FROM events' --limit 1 --json",
4217
+ "",
4218
+ AGENT_JSON_HINT
4219
+ ].join(`
4220
+ `)
4221
+ },
4222
+ args: {
4223
+ ...authArgs,
4224
+ ...outputArgs,
4225
+ query: {
4226
+ type: "positional",
4227
+ description: "SQL query string to execute",
4228
+ required: false
4229
+ },
4230
+ "query-file": {
4231
+ type: "string",
4232
+ description: "Path to a .sql file to read the query from (takes precedence over positional)"
4233
+ },
4234
+ limit: {
4235
+ type: "string",
4236
+ description: "Maximum number of rows to return. Default: 1000.",
4237
+ default: "1000"
4238
+ }
4239
+ },
4240
+ async run({ args }) {
4241
+ const json = !!args.json;
4242
+ const client = await getClientOrExit(args["api-key"], json);
4243
+ let sql;
4244
+ if (args["query-file"]) {
4245
+ try {
4246
+ sql = readFileSync4(args["query-file"], "utf-8");
4247
+ } catch (err) {
4248
+ return outputError({
4249
+ message: `Cannot read file: ${errorMessage(err, "unknown error")}`,
4250
+ code: "file_error"
4251
+ }, json);
4252
+ }
4253
+ } else if (args.query) {
4254
+ sql = args.query;
4255
+ } else {
4256
+ return outputError({ message: "Provide a SQL query or --query-file", code: "missing_input" }, json);
4257
+ }
4258
+ const limit = Number(args.limit);
4259
+ if (!Number.isFinite(limit) || limit <= 0) {
4260
+ return outputError({ message: "--limit must be a positive number", code: "invalid_input" }, json);
4261
+ }
4262
+ return runTool(client, customerToolContracts.outlit_query.toolName, { sql, limit }, json);
4263
+ }
4264
+ });
4265
+ });
4266
+
4267
+ // src/commands/schema.ts
4268
+ var exports_schema = {};
4269
+ __export(exports_schema, {
4270
+ default: () => schema_default
4271
+ });
4272
+ var schema_default;
4273
+ var init_schema = __esm(() => {
4274
+ init_dist();
4275
+ init_auth();
4276
+ init_output2();
4277
+ init_tool_contracts();
4278
+ init_api();
4279
+ schema_default = defineCommand2({
4280
+ meta: {
4281
+ name: "schema",
4282
+ description: [
4283
+ "Describe the analytics database schema.",
4284
+ "",
4285
+ "Without a table name, returns the full schema for all tables.",
4286
+ "With a table name, returns detailed column info for that table.",
4287
+ "",
4288
+ `Available tables: ${schemaTables.join(", ")}`,
4289
+ "",
4290
+ "Examples:",
4291
+ " outlit schema",
4292
+ " outlit schema events",
4293
+ " outlit schema customer_dimensions --json",
4294
+ "",
4295
+ AGENT_JSON_HINT
4296
+ ].join(`
4297
+ `)
4298
+ },
4299
+ args: {
4300
+ ...authArgs,
4301
+ ...outputArgs,
4302
+ table: {
4303
+ type: "positional",
4304
+ description: `Table to describe (${schemaTables.join(", ")}). Optional.`,
4305
+ required: false
4306
+ }
4307
+ },
4308
+ async run({ args }) {
4309
+ const json = !!args.json;
4310
+ const client = await getClientOrExit(args["api-key"], json);
4311
+ const params = {};
4312
+ if (args.table)
4313
+ params.table = args.table;
4314
+ return runTool(client, customerToolContracts.outlit_schema.toolName, params, json);
4315
+ }
4316
+ });
4317
+ });
4318
+
4319
+ // src/lib/providers.ts
4320
+ function resolveProvider(input) {
4321
+ const normalized = input.toLowerCase().trim();
4322
+ const provider = INTEGRATION_PROVIDERS[normalized];
4323
+ if (provider)
4324
+ return { provider, cliName: normalized };
4325
+ const suggestion = findClosestMatch(normalized);
4326
+ const base = `Unknown integration: "${input}". Available: ${PROVIDER_NAMES.join(", ")}`;
4327
+ if (suggestion) {
4328
+ return { error: `${base}
4329
+
4330
+ Did you mean "${suggestion}"?`, suggestion };
4331
+ }
4332
+ return { error: base };
4333
+ }
4334
+ function resolveProviderOrExit(input, json) {
4335
+ const result = resolveProvider(input);
4336
+ if ("error" in result) {
4337
+ return outputError({ message: result.error, code: "unknown_provider" }, json);
4338
+ }
4339
+ return result;
4340
+ }
4341
+ function findClosestMatch(input) {
4342
+ return PROVIDER_NAMES.find((name) => name.startsWith(input) || input.startsWith(name)) ?? null;
4343
+ }
4344
+ var INTEGRATION_PROVIDERS, PROVIDER_NAMES;
4345
+ var init_providers = __esm(() => {
4346
+ init_output();
4347
+ INTEGRATION_PROVIDERS = {
4348
+ slack: { id: "slack", name: "Slack", category: "communication", authType: "oauth" },
4349
+ gmail: { id: "google-mail", name: "Gmail", category: "communication", authType: "oauth" },
4350
+ "google-calendar": {
4351
+ id: "google-calendar",
4352
+ name: "Google Calendar",
4353
+ category: "calendar",
4354
+ authType: "oauth"
4355
+ },
4356
+ pylon: {
4357
+ id: "pylon",
4358
+ name: "Pylon",
4359
+ category: "support",
4360
+ authType: "api_key",
4361
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
4362
+ },
4363
+ stripe: {
4364
+ id: "brex-api-key",
4365
+ name: "Stripe",
4366
+ category: "billing",
4367
+ authType: "api_key",
4368
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
4369
+ },
4370
+ fireflies: {
4371
+ id: "fireflies",
4372
+ name: "Fireflies",
4373
+ category: "calls",
4374
+ authType: "api_key",
4375
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
4376
+ },
4377
+ posthog: {
4378
+ id: "posthog",
4379
+ name: "PostHog",
4380
+ category: "analytics",
4381
+ authType: "api_key",
4382
+ configFields: [
4383
+ { key: "apiKey", label: "API Key", secret: true },
4384
+ { key: "region", label: "Region (us or eu)" },
4385
+ { key: "projectId", label: "Project ID" }
4386
+ ]
4387
+ },
4388
+ supabase: {
4389
+ id: "supabase",
4390
+ name: "Supabase",
4391
+ category: "data",
4392
+ authType: "api_key",
4393
+ configFields: [
4394
+ { key: "projectUrl", label: "Project URL" },
4395
+ { key: "serviceRoleKey", label: "Service Role Key", secret: true }
4396
+ ]
4397
+ },
4398
+ clerk: {
4399
+ id: "clerk",
4400
+ name: "Clerk",
4401
+ category: "auth",
4402
+ authType: "api_key",
4403
+ configFields: [{ key: "secretKey", label: "Secret Key", secret: true }]
4404
+ }
4405
+ };
4406
+ PROVIDER_NAMES = Object.keys(INTEGRATION_PROVIDERS).sort();
4407
+ });
4408
+
4409
+ // src/commands/integrations/list.ts
4410
+ var exports_list4 = {};
4411
+ __export(exports_list4, {
4412
+ default: () => list_default4
4413
+ });
4414
+ var list_default4;
4415
+ var init_list4 = __esm(() => {
4416
+ init_dist();
4417
+ init_auth();
4418
+ init_output2();
4419
+ init_api();
4420
+ list_default4 = defineCommand2({
4421
+ meta: {
4422
+ name: "list",
4423
+ description: [
4424
+ "List available integrations and their connection status.",
4425
+ "",
4426
+ "Shows all supported third-party integrations with whether they",
4427
+ "are connected, available, or in an error state.",
4428
+ "",
4429
+ "Examples:",
4430
+ " outlit integrations list",
4431
+ " outlit integrations list --json",
4432
+ "",
4433
+ AGENT_JSON_HINT
4434
+ ].join(`
4435
+ `)
4436
+ },
4437
+ args: {
4438
+ ...authArgs,
4439
+ ...outputArgs
4440
+ },
4441
+ async run({ args }) {
4442
+ const json = !!args.json;
4443
+ const client = await getClientOrExit(args["api-key"], json);
4444
+ return runTool(client, "outlit_list_integrations", {}, json, {
4445
+ spinnerMessage: "Fetching integrations...",
4446
+ table: {
4447
+ columns: [
4448
+ { header: "Name", key: "name", format: (v) => truncate(v, 24) },
4449
+ { header: "Category", key: "category", format: capitalize },
4450
+ { header: "Status", key: "status" },
4451
+ { header: "Last Synced", key: "lastDataReceivedAt", format: relativeDate }
4452
+ ]
4453
+ }
4454
+ });
4455
+ }
4456
+ });
4457
+ });
4458
+
4459
+ // src/lib/poll.ts
4460
+ async function pollUntil(fn, predicate, opts = {}) {
4461
+ const intervalMs = opts.intervalMs ?? 2000;
4462
+ const timeoutMs = opts.timeoutMs ?? 300000;
4463
+ const start = Date.now();
4464
+ while (Date.now() - start < timeoutMs) {
4465
+ try {
4466
+ const result = await fn();
4467
+ if (predicate(result))
4468
+ return result;
4469
+ } catch {}
4470
+ if (opts.spinner && opts.spinnerMessage) {
4471
+ const elapsed = Math.floor((Date.now() - start) / 1000);
4472
+ opts.spinner.update(`${opts.spinnerMessage} (${elapsed}s)`);
4473
+ }
4474
+ await sleep(intervalMs);
4475
+ }
4476
+ return null;
4477
+ }
4478
+ function sleep(ms) {
4479
+ return new Promise((resolve) => setTimeout(resolve, ms));
4480
+ }
4481
+
4482
+ // src/commands/integrations/add.ts
4483
+ var exports_add = {};
4484
+ __export(exports_add, {
4485
+ default: () => add_default
4486
+ });
4487
+ async function addApiKeyProvider(client, provider, cliName, json, rawConfig) {
4488
+ const fields = provider.configFields ?? [];
4489
+ let config;
4490
+ if (rawConfig) {
4491
+ try {
4492
+ const parsed = JSON.parse(rawConfig);
4493
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4494
+ return outputError({ message: "Invalid JSON in --config. Expected a JSON object.", code: "invalid_config" }, json);
4495
+ }
4496
+ config = parsed;
4497
+ } catch {
4498
+ return outputError({ message: "Invalid JSON in --config. Expected a JSON object.", code: "invalid_config" }, json);
4499
+ }
4500
+ const missing = fields.filter((f) => !config[f.key]);
4501
+ if (missing.length > 0) {
4502
+ return outputError({
4503
+ message: `Missing required fields: ${missing.map((f) => f.key).join(", ")}`,
4504
+ code: "invalid_config"
4505
+ }, json);
4506
+ }
4507
+ } else if (isInteractive() && !isJsonMode(json)) {
4508
+ console.log(`
4509
+ Configure ${provider.name}:
4510
+ `);
4511
+ config = {};
4512
+ for (const field of fields) {
4513
+ config[field.key] = await promptInput(` ${field.label}`, { secret: field.secret });
4514
+ }
4515
+ console.log();
4516
+ } else {
4517
+ return outputResult({
4518
+ status: "config_required",
4519
+ provider: cliName,
4520
+ message: `${provider.name} requires API key configuration. Pass --config with the required fields.`,
4521
+ requiredFields: fields.map((f) => ({ key: f.key, label: f.label }))
4522
+ });
4523
+ }
4524
+ const spinner = createSpinner(`Connecting ${provider.name}...`);
4525
+ try {
4526
+ await client.callTool("outlit_connect_integration", {
4527
+ provider: provider.id,
4528
+ config
4529
+ });
4530
+ spinner.stop(`${provider.name} connected successfully!`);
4531
+ if (isJsonMode(json)) {
4532
+ return outputResult({
4533
+ status: "connected",
4534
+ provider: cliName
4535
+ });
4536
+ }
4537
+ console.log(` Use \`outlit integrations status ${cliName}\` to check sync progress.`);
4538
+ } catch (err) {
4539
+ spinner.fail(`Failed to connect ${provider.name}`);
4540
+ return outputError({ message: errorMessage(err, "Failed to connect integration"), code: "api_error" }, json);
4541
+ }
4542
+ }
4543
+ async function addOAuthProvider(client, provider, cliName, json, force) {
4544
+ const spinner = createSpinner(`Connecting ${provider.name}...`);
4545
+ let connectData;
4546
+ try {
4547
+ connectData = await client.callTool("outlit_connect_integration", {
4548
+ provider: provider.id
4549
+ });
4550
+ } catch (err) {
4551
+ spinner.fail(`Failed to initiate ${provider.name} connection`);
4552
+ return outputError({ message: errorMessage(err, "Failed to start connection flow"), code: "api_error" }, json);
4553
+ }
4554
+ if (connectData.alreadyConnected && !force) {
4555
+ spinner.stop(`${provider.name} is already connected`);
4556
+ if (isJsonMode(json)) {
4557
+ return outputResult({
4558
+ status: "already_connected",
4559
+ provider: cliName,
4560
+ message: `${provider.name} is already connected. Use --force to reconnect.`
4561
+ });
4562
+ }
4563
+ console.log(`
4564
+ ${provider.name} is already connected.`);
4565
+ console.log(` To reconnect, run: outlit integrations add ${cliName} --force`);
4566
+ return;
4567
+ }
4568
+ const integrationsUrl = `${client.baseUrl}/integrations`;
4569
+ spinner.update(`Opening browser for ${provider.name} authentication...`);
4570
+ const opened = openBrowser(integrationsUrl);
4571
+ if (!opened) {
4572
+ spinner.stop("Could not open browser automatically");
4573
+ if (isJsonMode(json)) {
4574
+ return outputResult({
4575
+ status: "browser_failed",
4576
+ provider: cliName,
4577
+ url: integrationsUrl,
4578
+ sessionId: connectData.sessionId
4579
+ });
4580
+ }
4581
+ console.log(`
4582
+ Open this URL in your browser to connect ${provider.name}:`);
4583
+ console.log(` ${integrationsUrl}
4584
+ `);
4585
+ } else {
4586
+ spinner.stop(`Browser opened for ${provider.name} authentication`);
4587
+ }
4588
+ if (isJsonMode(json)) {
4589
+ return outputResult({
4590
+ status: "awaiting_auth",
4591
+ provider: cliName,
4592
+ sessionId: connectData.sessionId
4593
+ });
4594
+ }
4595
+ await waitForConnection(client, connectData.sessionId, provider, cliName);
4596
+ }
4597
+ async function waitForConnection(client, sessionId, provider, cliName) {
4598
+ const spinner = createSpinner(`Waiting for ${provider.name} authentication...`);
4599
+ const result = await pollUntil(() => client.callTool("outlit_connect_status", { sessionId }).then((r) => r), (r) => r.status !== "pending", {
4600
+ intervalMs: 2000,
4601
+ timeoutMs: 300000,
4602
+ spinner,
4603
+ spinnerMessage: `Waiting for ${provider.name} authentication...`
4604
+ });
4605
+ if (!result || result.status === "expired") {
4606
+ spinner.fail("Connection timed out");
4607
+ console.log(`
4608
+ The authentication session expired.`);
4609
+ console.log(` Run \`outlit integrations add ${cliName}\` to try again.`);
4610
+ process.exit(1);
4611
+ }
4612
+ if (result.status === "failed") {
4613
+ spinner.fail(`${provider.name} connection failed`);
4614
+ if (result.error)
4615
+ console.log(`
4616
+ ${result.error}`);
4617
+ process.exit(1);
4618
+ }
4619
+ spinner.stop(`${provider.name} connected successfully!`);
4620
+ console.log(` Sync will begin automatically.`);
4621
+ console.log(` Use \`outlit integrations status ${cliName}\` to check progress.`);
4622
+ }
4623
+ var add_default;
4624
+ var init_add = __esm(() => {
3488
4625
  init_dist();
3489
4626
  init_auth();
3490
4627
  init_output2();
3491
4628
  init_api();
3492
4629
  init_output();
3493
- search_default = defineCommand2({
4630
+ init_providers();
4631
+ init_spinner();
4632
+ init_tty();
4633
+ add_default = defineCommand2({
3494
4634
  meta: {
3495
- name: "search",
4635
+ name: "add",
3496
4636
  description: [
3497
- "Search customer context using natural language.",
4637
+ "Connect a new integration.",
3498
4638
  "",
3499
- "Performs a semantic search over customer events and signals.",
3500
- "Optionally scope to a specific customer with --customer.",
4639
+ "For OAuth providers (Slack, Gmail, etc.), opens the browser for authentication.",
4640
+ "For API-key providers (Stripe, PostHog, etc.), accepts credentials via --config or interactive prompts.",
3501
4641
  "",
3502
4642
  "Examples:",
3503
- " outlit search 'pricing objections last quarter'",
3504
- " outlit search 'churn risk signals' --customer acme.com",
3505
- " outlit search 'expansion opportunities' --top-k 50 --json",
3506
- " outlit search 'support escalations' --after 2025-01-01 --before 2025-03-31",
4643
+ " outlit integrations add slack",
4644
+ ` outlit integrations add stripe --config '{"apiKey": "rk_xxx"}'`,
4645
+ " outlit integrations add posthog --json # outputs required fields as JSON",
4646
+ " outlit integrations add slack --force # reconnect if already connected",
4647
+ "",
4648
+ `Providers: ${PROVIDER_NAMES.join(", ")}`,
3507
4649
  "",
3508
4650
  AGENT_JSON_HINT
3509
4651
  ].join(`
@@ -3512,79 +4654,61 @@ var init_search = __esm(() => {
3512
4654
  args: {
3513
4655
  ...authArgs,
3514
4656
  ...outputArgs,
3515
- query: {
4657
+ provider: {
3516
4658
  type: "positional",
3517
- description: "Natural language search query",
4659
+ description: "Integration provider to connect",
3518
4660
  required: true
3519
4661
  },
3520
- customer: {
3521
- type: "string",
3522
- description: "Scope search to a specific customer (UUID or domain)"
3523
- },
3524
- "top-k": {
3525
- type: "string",
3526
- description: "Maximum number of results to return. Default: 20.",
3527
- default: "20"
3528
- },
3529
- after: {
3530
- type: "string",
3531
- description: "Filter to events occurring after this date (ISO 8601, e.g. 2025-01-01)"
4662
+ force: {
4663
+ type: "boolean",
4664
+ description: "Reconnect even if the integration is already connected."
3532
4665
  },
3533
- before: {
4666
+ config: {
3534
4667
  type: "string",
3535
- description: "Filter to events occurring before this date (ISO 8601, e.g. 2025-03-31)"
4668
+ description: `JSON configuration for API-key integrations (e.g. '{"apiKey": "sk_xxx"}')`
3536
4669
  }
3537
4670
  },
3538
4671
  async run({ args }) {
3539
4672
  const json = !!args.json;
3540
4673
  const client = await getClientOrExit(args["api-key"], json);
3541
- const topK = Number(args["top-k"]);
3542
- if (!Number.isFinite(topK) || topK <= 0) {
3543
- return outputError({ message: "--top-k must be a positive number", code: "invalid_input" }, json);
4674
+ const { provider, cliName } = resolveProviderOrExit(args.provider, json);
4675
+ if (provider.authType === "api_key") {
4676
+ await addApiKeyProvider(client, provider, cliName, json, args.config);
4677
+ } else {
4678
+ await addOAuthProvider(client, provider, cliName, json, !!args.force);
3544
4679
  }
3545
- const params = {
3546
- query: args.query,
3547
- topK
3548
- };
3549
- if (args.customer)
3550
- params.customer = args.customer;
3551
- if (args.after)
3552
- params.occurredAfter = args.after;
3553
- if (args.before)
3554
- params.occurredBefore = args.before;
3555
- return runTool(client, "outlit_search_customer_context", params, json);
3556
4680
  }
3557
4681
  });
3558
4682
  });
3559
4683
 
3560
- // src/commands/sql.ts
3561
- var exports_sql = {};
3562
- __export(exports_sql, {
3563
- default: () => sql_default
4684
+ // src/commands/integrations/remove.ts
4685
+ var exports_remove = {};
4686
+ __export(exports_remove, {
4687
+ default: () => remove_default
3564
4688
  });
3565
- import { readFileSync as readFileSync2 } from "node:fs";
3566
- var sql_default;
3567
- var init_sql = __esm(() => {
4689
+ var remove_default;
4690
+ var init_remove = __esm(() => {
4691
+ init_dist3();
3568
4692
  init_dist();
3569
4693
  init_auth();
3570
4694
  init_output2();
3571
4695
  init_api();
3572
4696
  init_output();
3573
- sql_default = defineCommand2({
4697
+ init_providers();
4698
+ init_spinner();
4699
+ init_tty();
4700
+ remove_default = defineCommand2({
3574
4701
  meta: {
3575
- name: "sql",
4702
+ name: "remove",
3576
4703
  description: [
3577
- "Run a SQL query against Outlit's analytics database.",
3578
- "",
3579
- "Provide the query as a positional argument or via --query-file.",
3580
- "When both are provided, --query-file takes precedence.",
4704
+ "Disconnect an integration and remove all synced data.",
3581
4705
  "",
3582
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4706
+ "This permanently deletes all data that was synced from the integration",
4707
+ "(e.g., opportunities, contacts, messages). The action cannot be undone.",
3583
4708
  "",
3584
4709
  "Examples:",
3585
- " outlit sql 'SELECT * FROM events LIMIT 10'",
3586
- " outlit sql --query-file ./my-query.sql",
3587
- " outlit sql 'SELECT count(*) FROM events' --limit 1 --json",
4710
+ " outlit integrations remove posthog",
4711
+ " outlit integrations remove slack --yes # skip confirmation prompt",
3588
4712
  "",
3589
4713
  AGENT_JSON_HINT
3590
4714
  ].join(`
@@ -3593,74 +4717,84 @@ var init_sql = __esm(() => {
3593
4717
  args: {
3594
4718
  ...authArgs,
3595
4719
  ...outputArgs,
3596
- query: {
4720
+ provider: {
3597
4721
  type: "positional",
3598
- description: "SQL query string to execute",
3599
- required: false
3600
- },
3601
- "query-file": {
3602
- type: "string",
3603
- description: "Path to a .sql file to read the query from (takes precedence over positional)"
4722
+ description: "Integration provider to disconnect",
4723
+ required: true
3604
4724
  },
3605
- limit: {
3606
- type: "string",
3607
- description: "Maximum number of rows to return. Default: 1000.",
3608
- default: "1000"
4725
+ yes: {
4726
+ type: "boolean",
4727
+ description: "Skip confirmation prompt (required in non-interactive mode)."
3609
4728
  }
3610
4729
  },
3611
4730
  async run({ args }) {
3612
4731
  const json = !!args.json;
4732
+ const { provider, cliName } = resolveProviderOrExit(args.provider, json);
3613
4733
  const client = await getClientOrExit(args["api-key"], json);
3614
- let sql;
3615
- if (args["query-file"]) {
3616
- try {
3617
- sql = readFileSync2(args["query-file"], "utf-8");
3618
- } catch (err) {
4734
+ if (!args.yes) {
4735
+ if (!isInteractive() || json) {
3619
4736
  return outputError({
3620
- message: `Cannot read file: ${errorMessage(err, "unknown error")}`,
3621
- code: "file_error"
4737
+ message: `Disconnecting ${provider.name} requires confirmation. Use --yes to confirm in non-interactive or JSON mode.`,
4738
+ code: "confirmation_required"
3622
4739
  }, json);
3623
4740
  }
3624
- } else if (args.query) {
3625
- sql = args.query;
3626
- } else {
3627
- return outputError({ message: "Provide a SQL query or --query-file", code: "missing_input" }, json);
4741
+ R2.warn(`This will disconnect ${provider.name} and delete all synced data.
4742
+ This action cannot be undone.`);
4743
+ const confirmed = await Re({ message: `Disconnect ${provider.name}?` });
4744
+ if (Ct(confirmed) || !confirmed) {
4745
+ Ne("Cancelled.");
4746
+ return;
4747
+ }
3628
4748
  }
3629
- const limit = Number(args.limit);
3630
- if (!Number.isFinite(limit) || limit <= 0) {
3631
- return outputError({ message: "--limit must be a positive number", code: "invalid_input" }, json);
4749
+ const spinner = createSpinner(`Disconnecting ${provider.name}...`);
4750
+ try {
4751
+ const result = await client.callTool("outlit_disconnect_integration", {
4752
+ provider: provider.id
4753
+ });
4754
+ if (result.success) {
4755
+ spinner.stop(`${provider.name} disconnected. All synced data has been removed.`);
4756
+ if (isJsonMode(json)) {
4757
+ return outputResult({ success: true, provider: cliName });
4758
+ }
4759
+ } else {
4760
+ spinner.fail(`Failed to disconnect ${provider.name}`);
4761
+ return outputError({
4762
+ message: result.message ?? `Failed to disconnect ${provider.name}`,
4763
+ code: "disconnect_failed"
4764
+ }, json);
4765
+ }
4766
+ } catch (err) {
4767
+ spinner.fail(`Failed to disconnect ${provider.name}`);
4768
+ return outputError({ message: errorMessage(err, "Disconnect request failed"), code: "api_error" }, json);
3632
4769
  }
3633
- return runTool(client, "outlit_query", { sql, limit }, json);
3634
4770
  }
3635
4771
  });
3636
4772
  });
3637
4773
 
3638
- // src/commands/schema.ts
3639
- var exports_schema = {};
3640
- __export(exports_schema, {
3641
- default: () => schema_default
4774
+ // src/commands/integrations/status.ts
4775
+ var exports_status2 = {};
4776
+ __export(exports_status2, {
4777
+ default: () => status_default2
3642
4778
  });
3643
- var schema_default;
3644
- var init_schema = __esm(() => {
4779
+ var status_default2;
4780
+ var init_status2 = __esm(() => {
3645
4781
  init_dist();
3646
4782
  init_auth();
3647
4783
  init_output2();
3648
4784
  init_api();
3649
- schema_default = defineCommand2({
4785
+ init_providers();
4786
+ status_default2 = defineCommand2({
3650
4787
  meta: {
3651
- name: "schema",
4788
+ name: "status",
3652
4789
  description: [
3653
- "Describe the analytics database schema.",
3654
- "",
3655
- "Without a table name, returns the full schema for all tables.",
3656
- "With a table name, returns detailed column info for that table.",
4790
+ "Show sync status for connected integrations.",
3657
4791
  "",
3658
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4792
+ "Without a provider name, shows a summary of all connected integrations.",
4793
+ "With a provider name, shows detailed per-model sync status.",
3659
4794
  "",
3660
4795
  "Examples:",
3661
- " outlit schema",
3662
- " outlit schema events",
3663
- " outlit schema customer_dimensions --json",
4796
+ " outlit integrations status # summary of all",
4797
+ " outlit integrations status stripe # detailed Stripe sync status",
3664
4798
  "",
3665
4799
  AGENT_JSON_HINT
3666
4800
  ].join(`
@@ -3669,19 +4803,78 @@ var init_schema = __esm(() => {
3669
4803
  args: {
3670
4804
  ...authArgs,
3671
4805
  ...outputArgs,
3672
- table: {
4806
+ provider: {
3673
4807
  type: "positional",
3674
- description: "Table to describe (events, customer_dimensions, user_dimensions, mrr_snapshots). Optional.",
4808
+ description: "Provider name to show detailed status for (optional)",
3675
4809
  required: false
3676
4810
  }
3677
4811
  },
3678
4812
  async run({ args }) {
3679
4813
  const json = !!args.json;
3680
4814
  const client = await getClientOrExit(args["api-key"], json);
3681
- const params = {};
3682
- if (args.table)
3683
- params.table = args.table;
3684
- return runTool(client, "outlit_schema", params, json);
4815
+ if (args.provider) {
4816
+ const { provider } = resolveProviderOrExit(args.provider, json);
4817
+ return runTool(client, "outlit_integration_sync_status", { provider: provider.id }, json, {
4818
+ spinnerMessage: "Fetching sync status...",
4819
+ table: {
4820
+ columns: [
4821
+ { header: "Model", key: "model" },
4822
+ { header: "Status", key: "status" },
4823
+ { header: "Records", key: "recordCount", format: formatNumber },
4824
+ { header: "Last Synced", key: "lastSyncedAt", format: relativeDate }
4825
+ ],
4826
+ itemsKey: "syncs"
4827
+ }
4828
+ });
4829
+ }
4830
+ return runTool(client, "outlit_list_integrations", { connectedOnly: true }, json, {
4831
+ spinnerMessage: "Fetching integration status...",
4832
+ table: {
4833
+ columns: [
4834
+ { header: "Name", key: "name", format: (v) => truncate(v, 24) },
4835
+ { header: "Category", key: "category", format: capitalize },
4836
+ { header: "Sync Status", key: "syncStatus" },
4837
+ { header: "Last Synced", key: "lastDataReceivedAt", format: relativeDate }
4838
+ ]
4839
+ }
4840
+ });
4841
+ }
4842
+ });
4843
+ });
4844
+
4845
+ // src/commands/integrations/index.ts
4846
+ var exports_integrations = {};
4847
+ __export(exports_integrations, {
4848
+ default: () => integrations_default
4849
+ });
4850
+ var integrations_default;
4851
+ var init_integrations = __esm(() => {
4852
+ init_dist();
4853
+ init_providers();
4854
+ integrations_default = defineCommand2({
4855
+ meta: {
4856
+ name: "integrations",
4857
+ description: [
4858
+ "Manage platform integrations (communication, analytics, billing, etc.).",
4859
+ "",
4860
+ "Connect third-party services like Slack, Stripe, and PostHog",
4861
+ "to sync data into your Outlit workspace.",
4862
+ "",
4863
+ "Commands:",
4864
+ " list List available integrations and connection status",
4865
+ " add <provider> Connect a new integration (opens browser for OAuth)",
4866
+ " remove <provider> Disconnect an integration and remove synced data",
4867
+ " status [provider] Show sync status for connected integrations",
4868
+ "",
4869
+ `Providers: ${PROVIDER_NAMES.join(", ")}`
4870
+ ].join(`
4871
+ `)
4872
+ },
4873
+ subCommands: {
4874
+ list: () => Promise.resolve().then(() => (init_list4(), exports_list4)).then((m) => m.default),
4875
+ add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((m) => m.default),
4876
+ remove: () => Promise.resolve().then(() => (init_remove(), exports_remove)).then((m) => m.default),
4877
+ status: () => Promise.resolve().then(() => (init_status2(), exports_status2)).then((m) => m.default)
3685
4878
  }
3686
4879
  });
3687
4880
  });
@@ -3756,7 +4949,10 @@ function zshDescribe(items) {
3756
4949
  function generateZsh() {
3757
4950
  const topLevel = zshDescribe(COMMANDS);
3758
4951
  const subCases = cmdsWithSubs.map((c) => {
3759
- const items = [...c.subs.map((s) => ({ name: s.name, desc: s.desc })), ...(c.flags ?? []).map((f) => ({ name: f.name, desc: f.desc }))];
4952
+ const items = [
4953
+ ...c.subs.map((s) => ({ name: s.name, desc: s.desc })),
4954
+ ...(c.flags ?? []).map((f) => ({ name: f.name, desc: f.desc }))
4955
+ ];
3760
4956
  return ` ${c.name})
3761
4957
  completions=(${zshDescribe(items)})
3762
4958
  _describe 'subcommand' completions
@@ -3887,7 +5083,6 @@ function generateFish() {
3887
5083
  var JSON_F, API_KEY_F, LIMIT_F, CURSOR_F, COMMON, PAGINATED, ACTIVITY_ORDER, COMMANDS, cmdsWithSubs, leafCmds, SCRIPTS, completions_default;
3888
5084
  var init_completions = __esm(() => {
3889
5085
  init_dist();
3890
- init_output2();
3891
5086
  init_output();
3892
5087
  JSON_F = { name: "--json", desc: "Force JSON output" };
3893
5088
  API_KEY_F = { name: "--api-key", desc: "Outlit API key" };
@@ -3907,7 +5102,11 @@ var init_completions = __esm(() => {
3907
5102
  desc: "Manage authentication",
3908
5103
  subs: [
3909
5104
  { name: "signup", desc: "Create an Outlit account", flags: [JSON_F] },
3910
- { name: "login", desc: "Store API key", flags: [JSON_F, { name: "--key", desc: "API key to store" }] },
5105
+ {
5106
+ name: "login",
5107
+ desc: "Store API key",
5108
+ flags: [JSON_F, { name: "--key", desc: "API key to store" }]
5109
+ },
3911
5110
  { name: "logout", desc: "Remove stored key", flags: [JSON_F] },
3912
5111
  { name: "status", desc: "Check auth state", flags: [...COMMON] },
3913
5112
  { name: "whoami", desc: "Print masked key", flags: [...COMMON] }
@@ -3923,12 +5122,11 @@ var init_completions = __esm(() => {
3923
5122
  flags: [
3924
5123
  ...PAGINATED,
3925
5124
  ...ACTIVITY_ORDER,
5125
+ { name: "--trait", desc: "Filter by trait key=value pairs" },
3926
5126
  { name: "--billing-status", desc: "Filter by billing status" },
3927
5127
  { name: "--mrr-above", desc: "MRR above threshold (cents)" },
3928
5128
  { name: "--mrr-below", desc: "MRR below threshold (cents)" },
3929
- { name: "--search", desc: "Search name or domain" },
3930
- { name: "--status", desc: "Customer status filter" },
3931
- { name: "--type", desc: "Customer type filter" }
5129
+ { name: "--search", desc: "Search name or domain" }
3932
5130
  ]
3933
5131
  },
3934
5132
  {
@@ -3964,6 +5162,7 @@ var init_completions = __esm(() => {
3964
5162
  flags: [
3965
5163
  ...PAGINATED,
3966
5164
  ...ACTIVITY_ORDER,
5165
+ { name: "--trait", desc: "Filter by trait key=value pairs" },
3967
5166
  { name: "--journey-stage", desc: "Filter by journey stage" },
3968
5167
  { name: "--customer-id", desc: "Filter by customer UUID" },
3969
5168
  { name: "--search", desc: "Search name or email" }
@@ -3974,7 +5173,43 @@ var init_completions = __esm(() => {
3974
5173
  {
3975
5174
  name: "facts",
3976
5175
  desc: "Get customer facts",
3977
- flags: [...PAGINATED, { name: "--timeframe", desc: "Lookback window (7d, 30d, 90d)" }]
5176
+ subs: [
5177
+ {
5178
+ name: "list",
5179
+ desc: "List customer facts",
5180
+ flags: [
5181
+ ...PAGINATED,
5182
+ { name: "--status", desc: "Filter by fact status" },
5183
+ { name: "--source-types", desc: "Filter by source types" },
5184
+ { name: "--after", desc: "Facts after date (ISO 8601)" },
5185
+ { name: "--before", desc: "Facts before date (ISO 8601)" }
5186
+ ]
5187
+ },
5188
+ {
5189
+ name: "get",
5190
+ desc: "Get a single fact by ID",
5191
+ flags: [
5192
+ ...COMMON,
5193
+ { name: "--fact-id", desc: "Fact ID to fetch" },
5194
+ { name: "--include", desc: "Best-effort expansions" }
5195
+ ]
5196
+ }
5197
+ ]
5198
+ },
5199
+ {
5200
+ name: "sources",
5201
+ desc: "Get a concrete source by type and id",
5202
+ subs: [
5203
+ {
5204
+ name: "get",
5205
+ desc: "Get one exact source record",
5206
+ flags: [
5207
+ ...COMMON,
5208
+ { name: "--source-type", desc: "Canonical source type" },
5209
+ { name: "--source-id", desc: "Exact source ID" }
5210
+ ]
5211
+ }
5212
+ ]
3978
5213
  },
3979
5214
  {
3980
5215
  name: "search",
@@ -3984,7 +5219,8 @@ var init_completions = __esm(() => {
3984
5219
  { name: "--customer", desc: "Scope to customer (UUID or domain)" },
3985
5220
  { name: "--top-k", desc: "Max results" },
3986
5221
  { name: "--after", desc: "Events after date (ISO 8601)" },
3987
- { name: "--before", desc: "Events before date (ISO 8601)" }
5222
+ { name: "--before", desc: "Events before date (ISO 8601)" },
5223
+ { name: "--source-types", desc: "Broad source type filter" }
3988
5224
  ]
3989
5225
  },
3990
5226
  {
@@ -3997,21 +5233,46 @@ var init_completions = __esm(() => {
3997
5233
  ]
3998
5234
  },
3999
5235
  { name: "schema", desc: "Discover table schemas", flags: [...COMMON] },
5236
+ {
5237
+ name: "integrations",
5238
+ desc: "Manage platform integrations",
5239
+ subs: [
5240
+ { name: "list", desc: "List integrations and status", flags: [...COMMON] },
5241
+ {
5242
+ name: "add",
5243
+ desc: "Connect an integration",
5244
+ flags: [
5245
+ ...COMMON,
5246
+ { name: "--config", desc: "JSON config for API-key providers" },
5247
+ { name: "--force", desc: "Reconnect if already connected" }
5248
+ ]
5249
+ },
5250
+ {
5251
+ name: "remove",
5252
+ desc: "Disconnect an integration",
5253
+ flags: [...COMMON, { name: "--yes", desc: "Skip confirmation" }]
5254
+ },
5255
+ { name: "status", desc: "Show sync status", flags: [...COMMON] }
5256
+ ]
5257
+ },
4000
5258
  {
4001
5259
  name: "setup",
4002
- desc: "Configure AI agent tools",
4003
- flags: [...COMMON, { name: "--yes", desc: "Skip prompts" }],
5260
+ desc: "Install Outlit skills for coding agents",
5261
+ flags: [JSON_F, { name: "--yes", desc: "Skip prompts" }],
4004
5262
  subs: [
4005
- { name: "cursor", desc: "Configure Cursor", flags: [...COMMON] },
4006
- { name: "claude-code", desc: "Configure Claude Code", flags: [...COMMON] },
4007
- { name: "claude-desktop", desc: "Configure Claude Desktop", flags: [...COMMON] },
4008
- { name: "vscode", desc: "Configure VS Code", flags: [...COMMON] },
4009
- { name: "gemini", desc: "Configure Gemini CLI", flags: [...COMMON] },
4010
- { name: "openclaw", desc: "Configure OpenClaw", flags: [...COMMON] }
5263
+ { name: "claude-code", desc: "Install the Outlit skill for Claude Code", flags: [JSON_F] },
5264
+ { name: "codex", desc: "Install the Outlit skill for Codex", flags: [JSON_F] },
5265
+ { name: "gemini", desc: "Install the Outlit skill for Gemini CLI", flags: [JSON_F] },
5266
+ { name: "droid", desc: "Install the Outlit skill for Droid", flags: [JSON_F] },
5267
+ { name: "opencode", desc: "Install the Outlit skill for OpenCode", flags: [JSON_F] },
5268
+ { name: "pi", desc: "Install the Outlit skill for Pi", flags: [JSON_F] },
5269
+ { name: "openclaw", desc: "Install the Outlit skill for OpenClaw", flags: [JSON_F] },
5270
+ { name: "skills", desc: "Launch the interactive Outlit skills installer", flags: [JSON_F] }
4011
5271
  ]
4012
5272
  },
5273
+ { name: "upgrade", desc: "Upgrade the CLI", flags: [] },
4013
5274
  { name: "doctor", desc: "Diagnose environment", flags: [...COMMON] },
4014
- { name: "completions", desc: "Generate shell completions", flags: [JSON_F] }
5275
+ { name: "completions", desc: "Generate shell completions", flags: [] }
4015
5276
  ];
4016
5277
  cmdsWithSubs = COMMANDS.filter((c) => c.subs?.length);
4017
5278
  leafCmds = COMMANDS.filter((c) => !c.subs?.length && c.flags?.length);
@@ -4038,7 +5299,6 @@ var init_completions = __esm(() => {
4038
5299
  `)
4039
5300
  },
4040
5301
  args: {
4041
- ...outputArgs,
4042
5302
  shell: {
4043
5303
  type: "positional",
4044
5304
  description: "Shell to generate completions for (bash, zsh, fish)",
@@ -4046,14 +5306,13 @@ var init_completions = __esm(() => {
4046
5306
  }
4047
5307
  },
4048
5308
  run({ args }) {
4049
- const json = !!args.json;
4050
5309
  const shell = args.shell;
4051
5310
  const generate = SCRIPTS[shell];
4052
5311
  if (!generate) {
4053
5312
  return outputError({
4054
5313
  message: `Unknown shell: ${shell}. Supported: bash, zsh, fish`,
4055
5314
  code: "unknown_shell"
4056
- }, json);
5315
+ }, false);
4057
5316
  }
4058
5317
  process.stdout.write(generate());
4059
5318
  }
@@ -4064,13 +5323,13 @@ var init_completions = __esm(() => {
4064
5323
  var exports_setup = {};
4065
5324
  __export(exports_setup, {
4066
5325
  isCommandAvailable: () => isCommandAvailable2,
4067
- detectAgents: () => detectAgents3,
5326
+ detectAgents: () => detectAgents2,
4068
5327
  default: () => setup_default2
4069
5328
  });
4070
5329
  import { execFileSync as execFileSync6 } from "node:child_process";
4071
- import { existsSync as existsSync5 } from "node:fs";
5330
+ import { existsSync as existsSync6 } from "node:fs";
4072
5331
  import { homedir as homedir6 } from "node:os";
4073
- import { join as join8 } from "node:path";
5332
+ import { join as join7 } from "node:path";
4074
5333
  function isCommandAvailable2(cmd) {
4075
5334
  try {
4076
5335
  const whichCmd = process.platform === "win32" ? "where" : "which";
@@ -4080,126 +5339,133 @@ function isCommandAvailable2(cmd) {
4080
5339
  return false;
4081
5340
  }
4082
5341
  }
4083
- function detectAgents3() {
4084
- const home = homedir6();
5342
+ function getHomeDir3() {
5343
+ return process.env.HOME?.trim() || homedir6();
5344
+ }
5345
+ function detectAgents2() {
5346
+ const home = getHomeDir3();
5347
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join7(home, ".config");
4085
5348
  const detected = [];
4086
- if (existsSync5(join8(home, ".cursor")))
4087
- detected.push("cursor");
4088
5349
  if (isCommandAvailable2("claude"))
4089
5350
  detected.push("claude-code");
4090
- if (existsSync5(getClaudeDesktopConfigPath()))
4091
- detected.push("claude-desktop");
4092
- if (isCommandAvailable2("code") || existsSync5(join8(process.cwd(), ".vscode")))
4093
- detected.push("vscode");
5351
+ if (isCommandAvailable2("codex"))
5352
+ detected.push("codex");
4094
5353
  if (isCommandAvailable2("gemini"))
4095
5354
  detected.push("gemini");
4096
- if (existsSync5(join8(home, "clawd", "skills")) || existsSync5(join8(home, ".openclaw", "skills")))
5355
+ if (existsSync6(join7(home, ".factory")))
5356
+ detected.push("droid");
5357
+ if (existsSync6(join7(configHome, "opencode")))
5358
+ detected.push("opencode");
5359
+ if (existsSync6(join7(home, ".pi", "agent")))
5360
+ detected.push("pi");
5361
+ if (existsSync6(join7(home, ".openclaw")) || existsSync6(join7(home, ".clawdbot")) || existsSync6(join7(home, ".moltbot"))) {
4097
5362
  detected.push("openclaw");
5363
+ }
4098
5364
  return detected;
4099
5365
  }
4100
- var agentLabels2, configurators2, setup_default2;
4101
- var init_setup3 = __esm(() => {
5366
+ var agentLabels2, setupSubcommandNames2, setup_default2;
5367
+ var init_setup2 = __esm(() => {
4102
5368
  init_dist();
4103
- init_auth();
4104
5369
  init_output2();
4105
5370
  init_config();
4106
5371
  init_output();
4107
- init_claude_code();
4108
- init_claude_desktop();
4109
- init_cursor();
4110
- init_gemini();
4111
- init_openclaw();
4112
5372
  init_skills();
4113
- init_vscode();
4114
5373
  agentLabels2 = {
4115
- cursor: { label: "Cursor", hint: "~/.cursor/" },
4116
5374
  "claude-code": { label: "Claude Code", hint: "claude CLI found" },
4117
- "claude-desktop": { label: "Claude Desktop", hint: "config file found" },
4118
- vscode: { label: "VS Code", hint: "code CLI or .vscode/ found" },
5375
+ codex: { label: "Codex", hint: "codex CLI found" },
4119
5376
  gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
4120
- openclaw: { label: "OpenClaw", hint: "skills directory found" }
4121
- };
4122
- configurators2 = {
4123
- cursor: configureSafe3,
4124
- "claude-code": configureSafe,
4125
- "claude-desktop": configureSafe2,
4126
- vscode: configureSafe6,
4127
- gemini: configureSafe4,
4128
- openclaw: configureSafe5
5377
+ droid: { label: "Droid", hint: ".factory config found" },
5378
+ opencode: { label: "OpenCode", hint: "opencode config found" },
5379
+ pi: { label: "Pi", hint: ".pi/agent config found" },
5380
+ openclaw: { label: "OpenClaw", hint: "OpenClaw config found" }
4129
5381
  };
5382
+ setupSubcommandNames2 = new Set([
5383
+ "claude-code",
5384
+ "codex",
5385
+ "gemini",
5386
+ "droid",
5387
+ "opencode",
5388
+ "pi",
5389
+ "openclaw",
5390
+ "skills"
5391
+ ]);
4130
5392
  setup_default2 = defineCommand2({
4131
5393
  meta: {
4132
5394
  name: "setup",
4133
5395
  description: [
4134
- "Configure Outlit for AI agent tools.",
5396
+ "Install the Outlit skill for coding agents.",
4135
5397
  "",
4136
- "Without a subcommand, auto-detects installed agents and configures them all.",
4137
- "Subcommands: cursor, claude-code, claude-desktop, vscode, gemini, openclaw, skills"
5398
+ "Without a subcommand, auto-detects supported coding agents and installs `outlit` for all of them.",
5399
+ "Subcommands: claude-code, codex, gemini, droid, opencode, pi, openclaw, skills"
4138
5400
  ].join(`
4139
5401
  `)
4140
5402
  },
4141
5403
  args: {
4142
- ...authArgs,
4143
5404
  ...outputArgs,
4144
5405
  yes: {
4145
5406
  type: "boolean",
4146
- description: "Configure all detected agents without prompting."
5407
+ description: "Install for all detected coding agents without prompting."
4147
5408
  }
4148
5409
  },
4149
5410
  subCommands: {
4150
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
4151
5411
  "claude-code": () => Promise.resolve().then(() => (init_claude_code(), exports_claude_code)).then((m) => m.default),
4152
- "claude-desktop": () => Promise.resolve().then(() => (init_claude_desktop(), exports_claude_desktop)).then((m) => m.default),
4153
- vscode: () => Promise.resolve().then(() => (init_vscode(), exports_vscode)).then((m) => m.default),
5412
+ codex: () => Promise.resolve().then(() => (init_codex(), exports_codex)).then((m) => m.default),
4154
5413
  gemini: () => Promise.resolve().then(() => (init_gemini(), exports_gemini)).then((m) => m.default),
5414
+ droid: () => Promise.resolve().then(() => (init_droid(), exports_droid)).then((m) => m.default),
5415
+ opencode: () => Promise.resolve().then(() => (init_opencode(), exports_opencode)).then((m) => m.default),
5416
+ pi: () => Promise.resolve().then(() => (init_pi(), exports_pi)).then((m) => m.default),
4155
5417
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
4156
5418
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
4157
5419
  },
4158
- async run({ args }) {
5420
+ async run({ args, rawArgs }) {
5421
+ const setupRawArgs = rawArgs ?? [];
5422
+ const subcommandName = setupRawArgs.find((arg) => !arg.startsWith("-"));
5423
+ if (subcommandName && setupSubcommandNames2.has(subcommandName)) {
5424
+ return;
5425
+ }
4159
5426
  const json = !!args.json;
4160
- const credential = requireCredential(args["api-key"], json);
4161
- const detected = detectAgents3();
5427
+ const detected = detectAgents2();
4162
5428
  if (detected.length === 0) {
4163
5429
  if (isJsonMode(json)) {
4164
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
5430
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
4165
5431
  }
4166
- console.log("No supported agent tools detected.");
5432
+ console.log("No supported coding agents detected.");
4167
5433
  return;
4168
5434
  }
4169
5435
  if (!isJsonMode(json) && !args.yes) {
4170
- console.log("Detected agents:");
5436
+ console.log("Detected coding agents:");
4171
5437
  for (const agentId of detected) {
4172
5438
  const { label, hint } = agentLabels2[agentId];
4173
5439
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
4174
5440
  }
4175
5441
  console.log(`
4176
- Configuring...`);
4177
- }
4178
- const configured = [];
4179
- const failed = [];
4180
- for (const agentId of detected) {
4181
- const ok = configurators2[agentId](credential.key, json);
4182
- if (ok) {
4183
- configured.push(agentId);
4184
- } else {
4185
- failed.push(agentId);
4186
- }
4187
- }
4188
- const skills = runSkillsInstall(json, false);
4189
- if (!isJsonMode(json) && !skills.success) {
4190
- console.log(`
4191
- ! Agent skills installation failed: ${skills.error ?? "unknown error"}`);
4192
- console.log(" Run `outlit setup skills` to retry.");
5442
+ Installing Outlit skill...`);
4193
5443
  }
5444
+ const install = runSkillsInstall({
5445
+ json,
5446
+ exitOnError: false,
5447
+ agents: detected.map(getSkillAgentId),
5448
+ skillNames: ["outlit"],
5449
+ autoConfirm: true
5450
+ });
5451
+ const configured = install.success ? detected : [];
5452
+ const failed = install.success ? [] : detected;
4194
5453
  if (isJsonMode(json)) {
4195
- return outputResult({ detected, configured, failed, skills });
5454
+ return outputResult({
5455
+ detected,
5456
+ configured,
5457
+ failed,
5458
+ runner: install.runner ?? null
5459
+ });
4196
5460
  }
4197
- if (failed.length > 0) {
5461
+ if (!install.success) {
4198
5462
  console.log(`
4199
- ${failed.length} agent(s) failed: ${failed.join(", ")}`);
5463
+ ! Outlit skill install failed: ${install.error ?? "unknown error"}`);
5464
+ console.log(" Run `outlit setup skills` to retry manually.");
5465
+ return;
4200
5466
  }
4201
5467
  console.log(`
4202
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
5468
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
4203
5469
  }
4204
5470
  });
4205
5471
  });
@@ -4556,11 +5822,226 @@ init_tty();
4556
5822
  var CLI_VERSION = package_default.version;
4557
5823
  var TICK = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
4558
5824
 
5825
+ // src/lib/update.ts
5826
+ init_config();
5827
+ init_tty();
5828
+ import { execFileSync as execFileSync2, spawn, spawnSync } from "node:child_process";
5829
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
5830
+ import { homedir as homedir2 } from "node:os";
5831
+ import { dirname as dirname2, join as join2 } from "node:path";
5832
+ var UPDATE_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
5833
+ var PACKAGE_NAME = "@outlit/cli";
5834
+ var LATEST_VERSION_URL = "https://registry.npmjs.org/@outlit%2Fcli/latest";
5835
+ var INTERNAL_UPDATE_FLAG = "--internal-update-check";
5836
+ function getUpdateCachePath() {
5837
+ return join2(getConfigDir(), "update-check.json");
5838
+ }
5839
+ function readCachedUpdateState() {
5840
+ const cachePath = getUpdateCachePath();
5841
+ if (!existsSync2(cachePath))
5842
+ return null;
5843
+ try {
5844
+ return JSON.parse(readFileSync2(cachePath, "utf8"));
5845
+ } catch {
5846
+ return null;
5847
+ }
5848
+ }
5849
+ function writeCachedUpdateState(state) {
5850
+ const cachePath = getUpdateCachePath();
5851
+ mkdirSync2(dirname2(cachePath), { recursive: true });
5852
+ writeFileSync2(cachePath, `${JSON.stringify(state, null, 2)}
5853
+ `);
5854
+ }
5855
+ function isUpdateCheckDue(state) {
5856
+ if (!state?.lastCheckedAt)
5857
+ return true;
5858
+ return Date.now() - state.lastCheckedAt >= UPDATE_CHECK_INTERVAL_MS;
5859
+ }
5860
+ function compareVersions(a, b) {
5861
+ const aParts = a.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
5862
+ const bParts = b.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
5863
+ const maxLength = Math.max(aParts.length, bParts.length);
5864
+ for (let index = 0;index < maxLength; index++) {
5865
+ const left = aParts[index] ?? 0;
5866
+ const right = bParts[index] ?? 0;
5867
+ if (left > right)
5868
+ return 1;
5869
+ if (left < right)
5870
+ return -1;
5871
+ }
5872
+ return 0;
5873
+ }
5874
+ function inferInstallerFromUserAgent(agent) {
5875
+ if (agent.startsWith("bun/"))
5876
+ return "bun";
5877
+ if (agent.startsWith("npm/"))
5878
+ return "npm";
5879
+ if (agent.startsWith("pnpm/"))
5880
+ return "pnpm";
5881
+ if (agent.startsWith("yarn/"))
5882
+ return "yarn";
5883
+ return null;
5884
+ }
5885
+ function isUnderPath(path, parent) {
5886
+ const normalizedPath = normalizeInstallerPath(path);
5887
+ const normalizedParent = normalizeInstallerPath(parent);
5888
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`);
5889
+ }
5890
+ function normalizeInstallerPath(path) {
5891
+ return path.replace(/^\/private\/tmp\//, "/tmp/");
5892
+ }
5893
+ function inferInstallerFromInstallation(opts) {
5894
+ const candidatePaths = [opts.argv1, opts.realExecPath].filter((value) => !!value);
5895
+ if (opts.npmGlobalPrefix) {
5896
+ const npmPackageRoots = [
5897
+ join2(opts.npmGlobalPrefix, "node_modules", PACKAGE_NAME),
5898
+ join2(opts.npmGlobalPrefix, "lib", "node_modules", PACKAGE_NAME)
5899
+ ];
5900
+ if (candidatePaths.some((path) => npmPackageRoots.some((root) => isUnderPath(path, root)))) {
5901
+ return "npm";
5902
+ }
5903
+ }
5904
+ if (opts.bunGlobalBin) {
5905
+ const bunGlobalBin = opts.bunGlobalBin;
5906
+ if (candidatePaths.some((path) => isUnderPath(path, bunGlobalBin))) {
5907
+ return "bun";
5908
+ }
5909
+ }
5910
+ return null;
5911
+ }
5912
+ function readCommandOutput(command, args) {
5913
+ try {
5914
+ return execFileSync2(command, args, {
5915
+ encoding: "utf8",
5916
+ stdio: ["ignore", "pipe", "ignore"]
5917
+ }).trim();
5918
+ } catch {
5919
+ return null;
5920
+ }
5921
+ }
5922
+ function inferInstaller() {
5923
+ const fromAgent = inferInstallerFromUserAgent(process.env.npm_config_user_agent ?? "");
5924
+ if (fromAgent)
5925
+ return fromAgent;
5926
+ const argv1 = process.argv[1];
5927
+ const realExecPath = argv1 ? readCommandOutput("realpath", [argv1]) ?? safeRealPath(argv1) : null;
5928
+ const npmGlobalPrefix = process.env.npm_config_prefix ?? readCommandOutput("npm", ["prefix", "-g"]);
5929
+ const bunGlobalBin = process.env.BUN_INSTALL ? join2(process.env.BUN_INSTALL, "bin") : readCommandOutput("bun", ["pm", "bin", "-g"]) ?? join2(homedir2(), ".bun", "bin");
5930
+ return inferInstallerFromInstallation({
5931
+ argv1,
5932
+ realExecPath,
5933
+ npmGlobalPrefix,
5934
+ bunGlobalBin
5935
+ });
5936
+ }
5937
+ function safeRealPath(filePath) {
5938
+ try {
5939
+ return realpathSync(filePath);
5940
+ } catch {
5941
+ return null;
5942
+ }
5943
+ }
5944
+ function formatUpdateCommand(installer = inferInstaller()) {
5945
+ switch (installer) {
5946
+ case "bun":
5947
+ return "bun add -g @outlit/cli";
5948
+ case "npm":
5949
+ return "npm install -g @outlit/cli";
5950
+ case "pnpm":
5951
+ return "pnpm add -g @outlit/cli";
5952
+ case "yarn":
5953
+ return "yarn global add @outlit/cli";
5954
+ default:
5955
+ return `update ${PACKAGE_NAME} with your package manager`;
5956
+ }
5957
+ }
5958
+ function getCachedUpdateNotice(state = readCachedUpdateState()) {
5959
+ if (!state?.latestVersion)
5960
+ return null;
5961
+ if (compareVersions(CLI_VERSION2, state.latestVersion) >= 0)
5962
+ return null;
5963
+ return {
5964
+ currentVersion: CLI_VERSION2,
5965
+ latestVersion: state.latestVersion,
5966
+ command: formatUpdateCommand(state.installer)
5967
+ };
5968
+ }
5969
+ function shouldCheckForUpdates() {
5970
+ if (process.env.OUTLIT_NO_UPDATE_NOTIFIER)
5971
+ return false;
5972
+ return isInteractive();
5973
+ }
5974
+ function shouldShowUpdateNotice(argv = process.argv) {
5975
+ return shouldCheckForUpdates() && !argv.includes("--json") && !argv.includes(INTERNAL_UPDATE_FLAG);
5976
+ }
5977
+ function printCachedUpdateNotice(argv = process.argv, notify = console.error) {
5978
+ if (!shouldShowUpdateNotice(argv))
5979
+ return false;
5980
+ const notice = getCachedUpdateNotice();
5981
+ if (!notice)
5982
+ return false;
5983
+ notify(`Outlit CLI update available: ${notice.currentVersion} -> ${notice.latestVersion}
5984
+ Update with: ${notice.command}`);
5985
+ return true;
5986
+ }
5987
+ function scheduleBackgroundUpdateCheck(argv = process.argv, spawnProcess = spawn) {
5988
+ if (!shouldShowUpdateNotice(argv))
5989
+ return false;
5990
+ if (!isUpdateCheckDue(readCachedUpdateState()))
5991
+ return false;
5992
+ const runtimePath = argv[0];
5993
+ const scriptPath = argv[1];
5994
+ if (!runtimePath || !scriptPath)
5995
+ return false;
5996
+ const child = spawnProcess(runtimePath, [scriptPath, INTERNAL_UPDATE_FLAG], {
5997
+ detached: true,
5998
+ stdio: "ignore"
5999
+ });
6000
+ child.unref?.();
6001
+ return true;
6002
+ }
6003
+ function initializeUpdateNotifier(opts) {
6004
+ const argv = opts?.argv ?? process.argv;
6005
+ printCachedUpdateNotice(argv, opts?.notify);
6006
+ scheduleBackgroundUpdateCheck(argv, opts?.spawn);
6007
+ }
6008
+ async function fetchLatestCliVersion() {
6009
+ const response = await fetch(LATEST_VERSION_URL, { signal: AbortSignal.timeout(5000) });
6010
+ if (!response.ok)
6011
+ throw new Error("registry unavailable");
6012
+ const data = await response.json();
6013
+ if (!data.version)
6014
+ throw new Error("registry returned no version");
6015
+ return data.version;
6016
+ }
6017
+ async function runInternalUpdateCheck(opts) {
6018
+ const fetchLatestVersion = opts?.fetchLatestVersion ?? fetchLatestCliVersion;
6019
+ const installer = opts?.installer ?? inferInstaller();
6020
+ try {
6021
+ const latestVersion = await fetchLatestVersion();
6022
+ writeCachedUpdateState({
6023
+ lastCheckedAt: Date.now(),
6024
+ latestVersion,
6025
+ ...installer ? { installer } : {}
6026
+ });
6027
+ } catch {
6028
+ writeCachedUpdateState({
6029
+ lastCheckedAt: Date.now(),
6030
+ ...installer ? { installer } : {}
6031
+ });
6032
+ }
6033
+ }
6034
+
4559
6035
  // src/cli.ts
4560
6036
  if (process.argv.includes("-v")) {
4561
6037
  console.log(CLI_VERSION);
4562
6038
  process.exit(0);
4563
6039
  }
6040
+ if (process.argv.includes(INTERNAL_UPDATE_FLAG)) {
6041
+ await runInternalUpdateCheck();
6042
+ process.exit(0);
6043
+ }
6044
+ initializeUpdateNotifier();
4564
6045
  var main = defineCommand({
4565
6046
  meta: {
4566
6047
  name: "outlit",
@@ -4572,8 +6053,10 @@ Usage examples:
4572
6053
  outlit customers get acme.com --include users,revenue
4573
6054
  outlit customers timeline acme.com --timeframe 90d
4574
6055
  outlit users list --journey-stage CHAMPION
4575
- outlit facts acme.com --timeframe 90d
4576
- outlit search 'pricing objections last quarter'
6056
+ outlit facts list acme.com --source-types CALL --after 2025-01-01T00:00:00Z
6057
+ outlit facts get --fact-id fact_123 --include evidence
6058
+ outlit sources get --source-type CALL --source-id call_123
6059
+ outlit search 'pricing objections last quarter' --source-types CALL,EMAIL
4577
6060
  outlit sql 'SELECT * FROM events LIMIT 10'
4578
6061
  outlit schema events
4579
6062
  outlit doctor --json
@@ -4585,12 +6068,15 @@ For AI agents: commands auto-output JSON when stdout is piped. No --json flag ne
4585
6068
  customers: () => Promise.resolve().then(() => (init_customers(), exports_customers)).then((m) => m.default),
4586
6069
  users: () => Promise.resolve().then(() => (init_users(), exports_users)).then((m) => m.default),
4587
6070
  doctor: () => Promise.resolve().then(() => (init_doctor(), exports_doctor)).then((m) => m.default),
6071
+ upgrade: () => Promise.resolve().then(() => (init_upgrade(), exports_upgrade)).then((m) => m.default),
4588
6072
  facts: () => Promise.resolve().then(() => (init_facts(), exports_facts)).then((m) => m.default),
6073
+ sources: () => Promise.resolve().then(() => (init_sources(), exports_sources)).then((m) => m.default),
4589
6074
  search: () => Promise.resolve().then(() => (init_search(), exports_search)).then((m) => m.default),
4590
6075
  sql: () => Promise.resolve().then(() => (init_sql(), exports_sql)).then((m) => m.default),
4591
6076
  schema: () => Promise.resolve().then(() => (init_schema(), exports_schema)).then((m) => m.default),
6077
+ integrations: () => Promise.resolve().then(() => (init_integrations(), exports_integrations)).then((m) => m.default),
4592
6078
  completions: () => Promise.resolve().then(() => (init_completions(), exports_completions)).then((m) => m.default),
4593
- setup: () => Promise.resolve().then(() => (init_setup3(), exports_setup)).then((m) => m.default)
6079
+ setup: () => Promise.resolve().then(() => (init_setup2(), exports_setup)).then((m) => m.default)
4594
6080
  }
4595
6081
  });
4596
6082
  runMain(main);