@outlit/cli 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +959 -122
  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.4.1",
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)
@@ -679,7 +712,7 @@ var import_picocolors, import_sisteransi, at = (t) => t === 161 || t === 164 ||
679
712
  ` && (r && p && (i += st(r)), n && (i += it(n))), V += h.length, m = A, A = g.next();
680
713
  }
681
714
  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;
715
+ }, 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
716
  var init_dist2 = __esm(() => {
684
717
  import_picocolors = __toESM(require_picocolors(), 1);
685
718
  import_sisteransi = __toESM(require_src(), 1);
@@ -758,6 +791,23 @@ var init_dist2 = __esm(() => {
758
791
  }
759
792
  }
760
793
  };
794
+ kt = class kt extends x {
795
+ get cursor() {
796
+ return this.value ? 0 : 1;
797
+ }
798
+ get _value() {
799
+ return this.cursor === 0;
800
+ }
801
+ constructor(e) {
802
+ super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
803
+ this.value = this._value;
804
+ }), this.on("confirm", (s) => {
805
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
806
+ }), this.on("cursor", () => {
807
+ this.value = !this.value;
808
+ });
809
+ }
810
+ };
761
811
  yt = class yt extends x {
762
812
  options;
763
813
  cursor = 0;
@@ -1073,6 +1123,32 @@ var import_picocolors2, import_sisteransi2, et2, ct2 = () => process.env.CI ===
1073
1123
  for (const w of A)
1074
1124
  B2.push(w);
1075
1125
  return h && B2.push(g), B2;
1126
+ }, Re = (t) => {
1127
+ const r = t.active ?? "Yes", s = t.inactive ?? "No";
1128
+ return new kt({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
1129
+ const i = t.withGuide ?? _.withGuide, a = `${i ? `${import_picocolors2.default.gray(d)}
1130
+ ` : ""}${W2(this.state)} ${t.message}
1131
+ `, o = this.value ? r : s;
1132
+ switch (this.state) {
1133
+ case "submit": {
1134
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1135
+ return `${a}${u}${import_picocolors2.default.dim(o)}`;
1136
+ }
1137
+ case "cancel": {
1138
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1139
+ return `${a}${u}${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}${i ? `
1140
+ ${import_picocolors2.default.gray(d)}` : ""}`;
1141
+ }
1142
+ default: {
1143
+ const u = i ? `${import_picocolors2.default.cyan(d)} ` : "", l = i ? import_picocolors2.default.cyan(x2) : "";
1144
+ return `${a}${u}${this.value ? `${import_picocolors2.default.green(Q2)} ${r}` : `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(r)}`}${t.vertical ? i ? `
1145
+ ${import_picocolors2.default.cyan(d)} ` : `
1146
+ ` : ` ${import_picocolors2.default.dim("/")} `}${this.value ? `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.green(Q2)} ${s}`}
1147
+ ${l}
1148
+ `;
1149
+ }
1150
+ }
1151
+ } }).prompt();
1076
1152
  }, R2, Ne = (t = "", r) => {
1077
1153
  (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(x2)} ${import_picocolors2.default.red(t)}
1078
1154
 
@@ -1299,57 +1375,6 @@ Note: JSON is also auto-enabled when stdout is piped (e.g. in CI, scripts, or AI
1299
1375
  };
1300
1376
  });
1301
1377
 
1302
- // src/commands/auth/signup.ts
1303
- var exports_signup = {};
1304
- __export(exports_signup, {
1305
- default: () => signup_default
1306
- });
1307
- import { execFileSync } from "node:child_process";
1308
- var OUTLIT_SIGNUP_URL = "https://app.outlit.ai/sign-up", signup_default;
1309
- var init_signup = __esm(() => {
1310
- init_dist3();
1311
- init_dist();
1312
- init_output2();
1313
- init_output();
1314
- init_tty();
1315
- signup_default = defineCommand2({
1316
- meta: {
1317
- name: "signup",
1318
- description: [
1319
- "Create an Outlit account.",
1320
- "",
1321
- "Opens the Outlit sign-up page in your default browser.",
1322
- "",
1323
- "Examples:",
1324
- " outlit auth signup # opens browser",
1325
- " outlit auth signup --json # outputs URL as JSON"
1326
- ].join(`
1327
- `)
1328
- },
1329
- args: { ...outputArgs },
1330
- async run({ args }) {
1331
- const json = !!args.json;
1332
- if (isJsonMode(json)) {
1333
- return outputResult({ url: OUTLIT_SIGNUP_URL });
1334
- }
1335
- if (isInteractive()) {
1336
- We("Outlit CLI -- Sign Up");
1337
- }
1338
- try {
1339
- if (process.platform === "win32") {
1340
- execFileSync("cmd", ["/c", "start", "", OUTLIT_SIGNUP_URL], { stdio: "ignore" });
1341
- } else {
1342
- execFileSync(openBrowserCmd(), [OUTLIT_SIGNUP_URL], { stdio: "ignore" });
1343
- }
1344
- } catch {}
1345
- if (isInteractive()) {
1346
- R2.info(`Opening ${OUTLIT_SIGNUP_URL}`);
1347
- Le("Once you've signed up, run `outlit auth login` to store your API key.");
1348
- }
1349
- }
1350
- });
1351
- });
1352
-
1353
1378
  // src/lib/config.ts
1354
1379
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1355
1380
  import { homedir } from "node:os";
@@ -1444,7 +1469,7 @@ function storeApiKey(apiKey) {
1444
1469
  writeFileSync(credPath, JSON.stringify({ apiKey }, null, 2), { mode: 384 });
1445
1470
  return credPath;
1446
1471
  }
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;
1472
+ 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", OUTLIT_SIGNUP_URL = "https://app.outlit.ai/sign-up", TICK2;
1448
1473
  var init_config = __esm(() => {
1449
1474
  init_package();
1450
1475
  init_output();
@@ -1453,6 +1478,55 @@ var init_config = __esm(() => {
1453
1478
  TICK2 = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
1454
1479
  });
1455
1480
 
1481
+ // src/commands/auth/signup.ts
1482
+ var exports_signup = {};
1483
+ __export(exports_signup, {
1484
+ default: () => signup_default
1485
+ });
1486
+ var signup_default;
1487
+ var init_signup = __esm(() => {
1488
+ init_dist3();
1489
+ init_dist();
1490
+ init_output2();
1491
+ init_config();
1492
+ init_output();
1493
+ init_tty();
1494
+ signup_default = defineCommand2({
1495
+ meta: {
1496
+ name: "signup",
1497
+ description: [
1498
+ "Create an Outlit account.",
1499
+ "",
1500
+ "Opens the Outlit sign-up page in your default browser.",
1501
+ "",
1502
+ "Examples:",
1503
+ " outlit auth signup # opens browser",
1504
+ " outlit auth signup --json # outputs URL as JSON"
1505
+ ].join(`
1506
+ `)
1507
+ },
1508
+ args: { ...outputArgs },
1509
+ async run({ args }) {
1510
+ const json = !!args.json;
1511
+ if (isJsonMode(json)) {
1512
+ return outputResult({ url: OUTLIT_SIGNUP_URL });
1513
+ }
1514
+ if (isInteractive()) {
1515
+ We("Outlit CLI -- Sign Up");
1516
+ }
1517
+ const opened = openBrowser(OUTLIT_SIGNUP_URL);
1518
+ if (isInteractive()) {
1519
+ if (opened) {
1520
+ R2.info(`Opening ${OUTLIT_SIGNUP_URL}`);
1521
+ } else {
1522
+ R2.info(`Could not open browser. Visit ${OUTLIT_SIGNUP_URL}`);
1523
+ }
1524
+ Le("Once you've signed up, run `outlit auth login` to store your API key.");
1525
+ }
1526
+ }
1527
+ });
1528
+ });
1529
+
1456
1530
  // src/lib/client.ts
1457
1531
  function buildUrl(base, path, params) {
1458
1532
  const url = new URL(path, base);
@@ -1461,6 +1535,8 @@ function buildUrl(base, path, params) {
1461
1535
  continue;
1462
1536
  if (Array.isArray(value)) {
1463
1537
  url.searchParams.set(key, value.join(","));
1538
+ } else if (typeof value === "object") {
1539
+ url.searchParams.set(key, JSON.stringify(value));
1464
1540
  } else {
1465
1541
  url.searchParams.set(key, String(value));
1466
1542
  }
@@ -1521,7 +1597,18 @@ var init_client = __esm(() => {
1521
1597
  outlit_get_facts: { method: "POST", path: "/api/internal/mcp/facts" },
1522
1598
  outlit_schema: { method: "GET", path: "/api/internal/mcp/sql-schema" },
1523
1599
  outlit_query: { method: "POST", path: "/api/internal/mcp/sql" },
1524
- outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" }
1600
+ outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" },
1601
+ outlit_list_integrations: { method: "GET", path: "/api/internal/mcp/integrations" },
1602
+ outlit_connect_integration: { method: "POST", path: "/api/internal/mcp/integrations/connect" },
1603
+ outlit_connect_status: { method: "GET", path: "/api/internal/mcp/integrations/connect/status" },
1604
+ outlit_disconnect_integration: {
1605
+ method: "POST",
1606
+ path: "/api/internal/mcp/integrations/disconnect"
1607
+ },
1608
+ outlit_integration_sync_status: {
1609
+ method: "GET",
1610
+ path: "/api/internal/mcp/integrations/sync-status"
1611
+ }
1525
1612
  };
1526
1613
  });
1527
1614
 
@@ -1712,7 +1799,6 @@ var exports_login = {};
1712
1799
  __export(exports_login, {
1713
1800
  default: () => login_default
1714
1801
  });
1715
- import { execFileSync as execFileSync2 } from "node:child_process";
1716
1802
  var login_default;
1717
1803
  var init_login = __esm(() => {
1718
1804
  init_dist3();
@@ -1773,14 +1859,12 @@ Format: ok_ followed by 32+ alphanumeric characters.`
1773
1859
  if (Ct(method))
1774
1860
  cancelLogin();
1775
1861
  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.");
1862
+ const opened = openBrowser(OUTLIT_DASHBOARD_URL);
1863
+ if (opened) {
1864
+ R2.info("Opening browser... paste your key once you have it.");
1865
+ } else {
1866
+ R2.info(`Could not open browser. Visit ${OUTLIT_DASHBOARD_URL} manually.`);
1867
+ }
1784
1868
  }
1785
1869
  const result = await He({
1786
1870
  message: "Paste your Outlit API key:",
@@ -2026,6 +2110,38 @@ var init_auth2 = __esm(() => {
2026
2110
  });
2027
2111
 
2028
2112
  // src/args/filters.ts
2113
+ function parseTraitFilterValue(value) {
2114
+ if (value === "true")
2115
+ return true;
2116
+ if (value === "false")
2117
+ return false;
2118
+ const parsed = Number(value);
2119
+ if (value.trim() !== "" && Number.isFinite(parsed)) {
2120
+ return parsed;
2121
+ }
2122
+ return value;
2123
+ }
2124
+ function parseTraitFilters(input) {
2125
+ if (!input)
2126
+ return;
2127
+ const entries = input.split(",").map((entry) => entry.trim()).filter(Boolean);
2128
+ if (entries.length === 0)
2129
+ return;
2130
+ const filters = {};
2131
+ for (const entry of entries) {
2132
+ const separatorIndex = entry.indexOf("=");
2133
+ if (separatorIndex <= 0 || separatorIndex === entry.length - 1) {
2134
+ throw new Error(`Invalid trait filter "${entry}". Expected key=value.`);
2135
+ }
2136
+ const key = entry.slice(0, separatorIndex).trim();
2137
+ const rawValue = entry.slice(separatorIndex + 1).trim();
2138
+ if (!/^[A-Za-z0-9_-]{1,100}$/.test(key)) {
2139
+ throw new Error(`Invalid trait filter key "${key}". Keys may only contain letters, numbers, underscores, and dashes.`);
2140
+ }
2141
+ filters[key] = parseTraitFilterValue(rawValue);
2142
+ }
2143
+ return filters;
2144
+ }
2029
2145
  function applyListFilters(params, args) {
2030
2146
  if (args["no-activity-in"])
2031
2147
  params.noActivityInLast = args["no-activity-in"];
@@ -2038,7 +2154,7 @@ function applyListFilters(params, args) {
2038
2154
  if (args["order-direction"])
2039
2155
  params.orderDirection = args["order-direction"];
2040
2156
  }
2041
- var activityFilterArgs, orderArgs;
2157
+ var activityFilterArgs, orderArgs, traitFilterArgs;
2042
2158
  var init_filters = __esm(() => {
2043
2159
  activityFilterArgs = {
2044
2160
  "no-activity-in": {
@@ -2062,6 +2178,12 @@ var init_filters = __esm(() => {
2062
2178
  default: "desc"
2063
2179
  }
2064
2180
  };
2181
+ traitFilterArgs = {
2182
+ trait: {
2183
+ type: "string",
2184
+ description: "Filter by traits using key=value pairs. Separate multiple filters with commas (e.g. role=admin,active=true)"
2185
+ }
2186
+ };
2065
2187
  });
2066
2188
 
2067
2189
  // src/args/pagination.ts
@@ -2137,6 +2259,16 @@ function truncate(value, maxLen) {
2137
2259
  return "...".slice(0, maxLen);
2138
2260
  return `${str.slice(0, maxLen - 3)}...`;
2139
2261
  }
2262
+ function capitalize(value) {
2263
+ if (value == null || typeof value !== "string")
2264
+ return "--";
2265
+ return value.charAt(0).toUpperCase() + value.slice(1);
2266
+ }
2267
+ function formatNumber(value) {
2268
+ if (value == null || typeof value !== "number" || Number.isNaN(value))
2269
+ return "--";
2270
+ return value.toLocaleString("en-US");
2271
+ }
2140
2272
 
2141
2273
  // src/commands/customers/list.ts
2142
2274
  var exports_list = {};
@@ -2180,6 +2312,7 @@ var init_list = __esm(() => {
2180
2312
  ...outputArgs,
2181
2313
  ...paginationArgs,
2182
2314
  ...activityFilterArgs,
2315
+ ...traitFilterArgs,
2183
2316
  ...orderArgs,
2184
2317
  "billing-status": {
2185
2318
  type: "string",
@@ -2226,6 +2359,19 @@ var init_list = __esm(() => {
2226
2359
  }
2227
2360
  params.mrrBelow = value;
2228
2361
  }
2362
+ if (args.trait) {
2363
+ try {
2364
+ const traitFilters = parseTraitFilters(args.trait);
2365
+ if (traitFilters) {
2366
+ params.traitFilters = traitFilters;
2367
+ }
2368
+ } catch (error) {
2369
+ return outputError({
2370
+ message: error instanceof Error ? error.message : "Invalid --trait filter",
2371
+ code: "invalid_input"
2372
+ }, json);
2373
+ }
2374
+ }
2229
2375
  applyListFilters(params, args);
2230
2376
  applyPagination(params, args, json);
2231
2377
  if (args.status)
@@ -2459,6 +2605,7 @@ var init_list2 = __esm(() => {
2459
2605
  init_output2();
2460
2606
  init_pagination();
2461
2607
  init_api();
2608
+ init_output();
2462
2609
  list_default2 = defineCommand2({
2463
2610
  meta: {
2464
2611
  name: "list",
@@ -2484,6 +2631,7 @@ var init_list2 = __esm(() => {
2484
2631
  ...authArgs,
2485
2632
  ...outputArgs,
2486
2633
  ...paginationArgs,
2634
+ ...traitFilterArgs,
2487
2635
  "journey-stage": {
2488
2636
  type: "string",
2489
2637
  description: "Filter by journey stage (e.g. CHAMPION, AT_RISK, CHURNED)"
@@ -2507,6 +2655,19 @@ var init_list2 = __esm(() => {
2507
2655
  params.journeyStage = args["journey-stage"];
2508
2656
  if (args["customer-id"])
2509
2657
  params.customerId = args["customer-id"];
2658
+ if (args.trait) {
2659
+ try {
2660
+ const traitFilters = parseTraitFilters(args.trait);
2661
+ if (traitFilters) {
2662
+ params.traitFilters = traitFilters;
2663
+ }
2664
+ } catch (error) {
2665
+ return outputError({
2666
+ message: error instanceof Error ? error.message : "Invalid --trait filter",
2667
+ code: "invalid_input"
2668
+ }, json);
2669
+ }
2670
+ }
2510
2671
  applyListFilters(params, args);
2511
2672
  applyPagination(params, args, json);
2512
2673
  return runTool(client, "outlit_list_users", params, json, {
@@ -2554,10 +2715,10 @@ var init_users = __esm(() => {
2554
2715
  });
2555
2716
 
2556
2717
  // src/lib/setup.ts
2557
- import { execFileSync as execFileSync3 } from "node:child_process";
2718
+ import { execFileSync as execFileSync2 } from "node:child_process";
2558
2719
  function runMcpCliSetup(key, json, opts, exitOnError = true) {
2559
2720
  try {
2560
- execFileSync3(opts.cliName, [
2721
+ execFileSync2(opts.cliName, [
2561
2722
  "mcp",
2562
2723
  "add",
2563
2724
  "--transport",
@@ -2785,20 +2946,15 @@ __export(exports_openclaw, {
2785
2946
  configureSafe: () => configureSafe5,
2786
2947
  buildSkillContent: () => buildSkillContent
2787
2948
  });
2788
- import { existsSync as existsSync2 } from "node:fs";
2789
2949
  import { homedir as homedir3 } from "node:os";
2790
2950
  import { join as join4 } from "node:path";
2791
2951
  function getSkillDir() {
2792
2952
  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");
2953
+ return join4(home, ".openclaw", "skills", "outlit");
2798
2954
  }
2799
2955
  function buildSkillContent(maskedKey) {
2800
2956
  return `---
2801
- name: outlit-intelligence
2957
+ name: outlit
2802
2958
  description: Query customer data, revenue metrics, and analytics via the Outlit CLI.
2803
2959
  metadata:
2804
2960
  openclaw:
@@ -2883,7 +3039,7 @@ var init_openclaw = __esm(() => {
2883
3039
  openclaw_default = defineCommand2({
2884
3040
  meta: {
2885
3041
  name: "openclaw",
2886
- description: "Write Outlit intelligence skill to ~/clawd/skills/ for OpenClaw."
3042
+ description: "Write Outlit skill to ~/.openclaw/skills/ for OpenClaw."
2887
3043
  },
2888
3044
  args: { ...authArgs, ...outputArgs },
2889
3045
  run({ args }) {
@@ -2907,12 +3063,12 @@ __export(exports_skills, {
2907
3063
  detectPackageRunner: () => detectPackageRunner,
2908
3064
  default: () => skills_default
2909
3065
  });
2910
- import { execFileSync as execFileSync4 } from "node:child_process";
3066
+ import { execFileSync as execFileSync3 } from "node:child_process";
2911
3067
  function detectPackageRunner() {
2912
3068
  const whichCmd = process.platform === "win32" ? "where" : "which";
2913
3069
  for (const runner of ["npx", "bunx", "pnpx"]) {
2914
3070
  try {
2915
- execFileSync4(whichCmd, [runner], { stdio: "ignore" });
3071
+ execFileSync3(whichCmd, [runner], { stdio: "ignore" });
2916
3072
  return runner;
2917
3073
  } catch {}
2918
3074
  }
@@ -2939,7 +3095,7 @@ function runSkillsInstall(json, exitOnError = true) {
2939
3095
  return { success: false, error: "No package runner found" };
2940
3096
  }
2941
3097
  try {
2942
- execFileSync4(runner, buildRunnerArgs(runner), { stdio: "pipe" });
3098
+ execFileSync3(runner, buildRunnerArgs(runner), { stdio: "pipe" });
2943
3099
  } catch (err) {
2944
3100
  if (exitOnError) {
2945
3101
  if (isEnoentError(err)) {
@@ -3029,14 +3185,14 @@ var init_vscode = __esm(() => {
3029
3185
  });
3030
3186
 
3031
3187
  // src/commands/setup/index.ts
3032
- import { execFileSync as execFileSync5 } from "node:child_process";
3033
- import { existsSync as existsSync3 } from "node:fs";
3188
+ import { execFileSync as execFileSync4 } from "node:child_process";
3189
+ import { existsSync as existsSync2 } from "node:fs";
3034
3190
  import { homedir as homedir4 } from "node:os";
3035
3191
  import { join as join6 } from "node:path";
3036
3192
  function isCommandAvailable(cmd) {
3037
3193
  try {
3038
3194
  const whichCmd = process.platform === "win32" ? "where" : "which";
3039
- execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
3195
+ execFileSync4(whichCmd, [cmd], { stdio: "ignore" });
3040
3196
  return true;
3041
3197
  } catch {
3042
3198
  return false;
@@ -3045,17 +3201,17 @@ function isCommandAvailable(cmd) {
3045
3201
  function detectAgents() {
3046
3202
  const home = homedir4();
3047
3203
  const detected = [];
3048
- if (existsSync3(join6(home, ".cursor")))
3204
+ if (existsSync2(join6(home, ".cursor")))
3049
3205
  detected.push("cursor");
3050
3206
  if (isCommandAvailable("claude"))
3051
3207
  detected.push("claude-code");
3052
- if (existsSync3(getClaudeDesktopConfigPath()))
3208
+ if (existsSync2(getClaudeDesktopConfigPath()))
3053
3209
  detected.push("claude-desktop");
3054
- if (isCommandAvailable("code") || existsSync3(join6(process.cwd(), ".vscode")))
3210
+ if (isCommandAvailable("code") || existsSync2(join6(process.cwd(), ".vscode")))
3055
3211
  detected.push("vscode");
3056
3212
  if (isCommandAvailable("gemini"))
3057
3213
  detected.push("gemini");
3058
- if (existsSync3(join6(home, "clawd", "skills")) || existsSync3(join6(home, ".openclaw", "skills")))
3214
+ if (existsSync2(join6(home, ".openclaw", "skills")))
3059
3215
  detected.push("openclaw");
3060
3216
  return detected;
3061
3217
  }
@@ -3171,7 +3327,7 @@ var exports_doctor = {};
3171
3327
  __export(exports_doctor, {
3172
3328
  default: () => doctor_default
3173
3329
  });
3174
- import { existsSync as existsSync4 } from "node:fs";
3330
+ import { existsSync as existsSync3 } from "node:fs";
3175
3331
  import { homedir as homedir5 } from "node:os";
3176
3332
  import { join as join7 } from "node:path";
3177
3333
  async function checkCliVersion() {
@@ -3237,6 +3393,47 @@ async function validateApiKey(apiKey) {
3237
3393
  };
3238
3394
  }
3239
3395
  }
3396
+ async function checkIntegrations(apiKey) {
3397
+ try {
3398
+ const client = await createClient(apiKey);
3399
+ const timeout = new Promise((_2, reject) => setTimeout(() => reject(new Error("Integrations check timed out")), 1e4));
3400
+ const data = await Promise.race([
3401
+ client.callTool("outlit_list_integrations", {}),
3402
+ timeout
3403
+ ]);
3404
+ const items = data.items ?? [];
3405
+ const connected = items.filter((i) => i.status === "connected").length;
3406
+ const errors = items.filter((i) => i.status === "error").length;
3407
+ if (errors > 0) {
3408
+ return {
3409
+ name: "Integrations",
3410
+ status: "warn",
3411
+ message: `${connected} connected, ${errors} with errors`,
3412
+ detail: "Run `outlit integrations status` for details"
3413
+ };
3414
+ }
3415
+ if (connected === 0) {
3416
+ return {
3417
+ name: "Integrations",
3418
+ status: "pass",
3419
+ message: "No integrations connected",
3420
+ detail: "Run `outlit integrations list` to see available integrations"
3421
+ };
3422
+ }
3423
+ return {
3424
+ name: "Integrations",
3425
+ status: "pass",
3426
+ message: `${connected} integration(s) connected`
3427
+ };
3428
+ } catch {
3429
+ return {
3430
+ name: "Integrations",
3431
+ status: "warn",
3432
+ message: "Could not check integrations",
3433
+ detail: "Integration status endpoint may not be available yet"
3434
+ };
3435
+ }
3436
+ }
3240
3437
  function detectAgents2() {
3241
3438
  const detected = detectAgents();
3242
3439
  if (detected.length === 0) {
@@ -3253,13 +3450,13 @@ function detectAgents2() {
3253
3450
  for (const agentId of detected) {
3254
3451
  const meta = agentChecks[agentId];
3255
3452
  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"));
3453
+ const openclawBase = join7(home, ".openclaw", "skills");
3454
+ const hasSkill = existsSync3(join7(openclawBase, "outlit", "SKILL.md"));
3258
3455
  results.push({
3259
3456
  name: meta.name,
3260
3457
  status: hasSkill ? "pass" : "warn",
3261
3458
  message: hasSkill ? "Installed, Outlit skill configured" : "Installed, but Outlit skill not found",
3262
- detail: hasSkill ? undefined : "Run `outlit setup openclaw` to configure"
3459
+ detail: hasSkill ? undefined : "Run `clawhub install outlit` to configure"
3263
3460
  });
3264
3461
  continue;
3265
3462
  }
@@ -3272,7 +3469,7 @@ function detectAgents2() {
3272
3469
  });
3273
3470
  if (agentId === "claude-code") {
3274
3471
  const skillsDir = join7(home, ".claude", "skills");
3275
- const hasSkills = existsSync4(join7(skillsDir, "outlit-cli")) || existsSync4(join7(skillsDir, "outlit-sdk")) || existsSync4(join7(skillsDir, "outlit-mcp"));
3472
+ const hasSkills = existsSync3(join7(skillsDir, "outlit-cli")) || existsSync3(join7(skillsDir, "outlit-sdk")) || existsSync3(join7(skillsDir, "outlit-mcp"));
3276
3473
  results.push({
3277
3474
  name: "Agent Skills",
3278
3475
  status: hasSkills ? "pass" : "warn",
@@ -3283,7 +3480,7 @@ function detectAgents2() {
3283
3480
  continue;
3284
3481
  }
3285
3482
  const { path, key } = meta.configCheck;
3286
- if (!existsSync4(path)) {
3483
+ if (!existsSync3(path)) {
3287
3484
  results.push({
3288
3485
  name: meta.name,
3289
3486
  status: "warn",
@@ -3339,6 +3536,7 @@ var init_doctor = __esm(() => {
3339
3536
  init_auth();
3340
3537
  init_output2();
3341
3538
  init_api();
3539
+ init_client();
3342
3540
  init_config();
3343
3541
  init_output();
3344
3542
  init_tty();
@@ -3383,7 +3581,11 @@ var init_doctor = __esm(() => {
3383
3581
  const credential = resolveApiKey(args["api-key"]);
3384
3582
  checks.push(checkApiKeyPresence(credential));
3385
3583
  if (credential) {
3386
- checks.push(await validateApiKey(credential.key));
3584
+ const apiCheck = await validateApiKey(credential.key);
3585
+ checks.push(apiCheck);
3586
+ if (apiCheck.status !== "fail") {
3587
+ checks.push(await checkIntegrations(credential.key));
3588
+ }
3387
3589
  } else {
3388
3590
  checks.push({
3389
3591
  name: "API validation",
@@ -3398,7 +3600,8 @@ var init_doctor = __esm(() => {
3398
3600
  } else {
3399
3601
  printChecks(checks);
3400
3602
  }
3401
- process.exit(hasFail ? 1 : 0);
3603
+ if (hasFail)
3604
+ process.exit(1);
3402
3605
  }
3403
3606
  });
3404
3607
  agentChecks = {
@@ -3494,16 +3697,19 @@ var init_search = __esm(() => {
3494
3697
  meta: {
3495
3698
  name: "search",
3496
3699
  description: [
3497
- "Search customer context using natural language.",
3700
+ "Search customer context using natural language or direct source lookup.",
3498
3701
  "",
3499
- "Performs a semantic search over customer events and signals.",
3702
+ "Performs a semantic search over customer facts, emails, and transcripts.",
3500
3703
  "Optionally scope to a specific customer with --customer.",
3704
+ "Use --source-type and --source-id for direct source lookup (query becomes optional).",
3501
3705
  "",
3502
3706
  "Examples:",
3503
3707
  " outlit search 'pricing objections last quarter'",
3504
3708
  " outlit search 'churn risk signals' --customer acme.com",
3505
3709
  " outlit search 'expansion opportunities' --top-k 50 --json",
3506
3710
  " outlit search 'support escalations' --after 2025-01-01 --before 2025-03-31",
3711
+ " outlit search 'onboarding issues' --source-types call_transcript,email",
3712
+ " outlit search --source-type call_transcript --source-id call_123",
3507
3713
  "",
3508
3714
  AGENT_JSON_HINT
3509
3715
  ].join(`
@@ -3514,8 +3720,8 @@ var init_search = __esm(() => {
3514
3720
  ...outputArgs,
3515
3721
  query: {
3516
3722
  type: "positional",
3517
- description: "Natural language search query",
3518
- required: true
3723
+ description: "Natural language search query. Optional when --source-type and --source-id are provided.",
3724
+ required: false
3519
3725
  },
3520
3726
  customer: {
3521
3727
  type: "string",
@@ -3523,7 +3729,7 @@ var init_search = __esm(() => {
3523
3729
  },
3524
3730
  "top-k": {
3525
3731
  type: "string",
3526
- description: "Maximum number of results to return. Default: 20.",
3732
+ description: "Maximum number of results to return (1–50). Default: 20.",
3527
3733
  default: "20"
3528
3734
  },
3529
3735
  after: {
@@ -3533,25 +3739,64 @@ var init_search = __esm(() => {
3533
3739
  before: {
3534
3740
  type: "string",
3535
3741
  description: "Filter to events occurring before this date (ISO 8601, e.g. 2025-03-31)"
3742
+ },
3743
+ "source-types": {
3744
+ type: "string",
3745
+ description: "Comma-separated broad source type filter"
3746
+ },
3747
+ "source-type": {
3748
+ type: "string",
3749
+ description: "Exact source type for direct lookup (must pair with --source-id)"
3750
+ },
3751
+ "source-id": {
3752
+ type: "string",
3753
+ description: "Exact source ID for direct lookup (must pair with --source-type)"
3536
3754
  }
3537
3755
  },
3538
3756
  async run({ args }) {
3539
3757
  const json = !!args.json;
3540
- const client = await getClientOrExit(args["api-key"], json);
3541
3758
  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);
3759
+ if (!Number.isFinite(topK) || topK < 1 || topK > 50) {
3760
+ return outputError({ message: "--top-k must be an integer between 1 and 50", code: "invalid_input" }, json);
3544
3761
  }
3545
- const params = {
3546
- query: args.query,
3547
- topK
3548
- };
3762
+ const sourceType = args["source-type"];
3763
+ const sourceId = args["source-id"];
3764
+ const sourceTypes = args["source-types"];
3765
+ if (sourceType && !sourceId || !sourceType && sourceId) {
3766
+ return outputError({
3767
+ message: "--source-type and --source-id must be provided together",
3768
+ code: "invalid_input"
3769
+ }, json);
3770
+ }
3771
+ if (!args.query && !sourceType) {
3772
+ return outputError({
3773
+ message: "A query argument or --source-type and --source-id are required",
3774
+ code: "invalid_input"
3775
+ }, json);
3776
+ }
3777
+ if (sourceTypes && sourceType) {
3778
+ return outputError({
3779
+ message: "--source-types cannot be combined with --source-type/--source-id",
3780
+ code: "invalid_input"
3781
+ }, json);
3782
+ }
3783
+ const client = await getClientOrExit(args["api-key"], json);
3784
+ const parseCsv = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
3785
+ const params = { topK };
3786
+ if (args.query)
3787
+ params.query = args.query;
3549
3788
  if (args.customer)
3550
3789
  params.customer = args.customer;
3551
3790
  if (args.after)
3552
3791
  params.occurredAfter = args.after;
3553
3792
  if (args.before)
3554
3793
  params.occurredBefore = args.before;
3794
+ if (sourceTypes)
3795
+ params.sourceTypes = parseCsv(sourceTypes);
3796
+ if (sourceType)
3797
+ params.sourceType = sourceType;
3798
+ if (sourceId)
3799
+ params.sourceId = sourceId;
3555
3800
  return runTool(client, "outlit_search_customer_context", params, json);
3556
3801
  }
3557
3802
  });
@@ -3686,6 +3931,569 @@ var init_schema = __esm(() => {
3686
3931
  });
3687
3932
  });
3688
3933
 
3934
+ // src/lib/providers.ts
3935
+ function resolveProvider(input) {
3936
+ const normalized = input.toLowerCase().trim();
3937
+ const provider = INTEGRATION_PROVIDERS[normalized];
3938
+ if (provider)
3939
+ return { provider, cliName: normalized };
3940
+ const suggestion = findClosestMatch(normalized);
3941
+ const base = `Unknown integration: "${input}". Available: ${PROVIDER_NAMES.join(", ")}`;
3942
+ if (suggestion) {
3943
+ return { error: `${base}
3944
+
3945
+ Did you mean "${suggestion}"?`, suggestion };
3946
+ }
3947
+ return { error: base };
3948
+ }
3949
+ function resolveProviderOrExit(input, json) {
3950
+ const result = resolveProvider(input);
3951
+ if ("error" in result) {
3952
+ return outputError({ message: result.error, code: "unknown_provider" }, json);
3953
+ }
3954
+ return result;
3955
+ }
3956
+ function findClosestMatch(input) {
3957
+ return PROVIDER_NAMES.find((name) => name.startsWith(input) || input.startsWith(name)) ?? null;
3958
+ }
3959
+ var INTEGRATION_PROVIDERS, PROVIDER_NAMES;
3960
+ var init_providers = __esm(() => {
3961
+ init_output();
3962
+ INTEGRATION_PROVIDERS = {
3963
+ slack: { id: "slack", name: "Slack", category: "communication", authType: "oauth" },
3964
+ gmail: { id: "google-mail", name: "Gmail", category: "communication", authType: "oauth" },
3965
+ "google-calendar": {
3966
+ id: "google-calendar",
3967
+ name: "Google Calendar",
3968
+ category: "calendar",
3969
+ authType: "oauth"
3970
+ },
3971
+ pylon: {
3972
+ id: "pylon",
3973
+ name: "Pylon",
3974
+ category: "support",
3975
+ authType: "api_key",
3976
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
3977
+ },
3978
+ stripe: {
3979
+ id: "brex-api-key",
3980
+ name: "Stripe",
3981
+ category: "billing",
3982
+ authType: "api_key",
3983
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
3984
+ },
3985
+ fireflies: {
3986
+ id: "fireflies",
3987
+ name: "Fireflies",
3988
+ category: "calls",
3989
+ authType: "api_key",
3990
+ configFields: [{ key: "apiKey", label: "API Key", secret: true }]
3991
+ },
3992
+ posthog: {
3993
+ id: "posthog",
3994
+ name: "PostHog",
3995
+ category: "analytics",
3996
+ authType: "api_key",
3997
+ configFields: [
3998
+ { key: "apiKey", label: "API Key", secret: true },
3999
+ { key: "region", label: "Region (us or eu)" },
4000
+ { key: "projectId", label: "Project ID" }
4001
+ ]
4002
+ },
4003
+ supabase: {
4004
+ id: "supabase",
4005
+ name: "Supabase",
4006
+ category: "data",
4007
+ authType: "api_key",
4008
+ configFields: [
4009
+ { key: "projectUrl", label: "Project URL" },
4010
+ { key: "serviceRoleKey", label: "Service Role Key", secret: true }
4011
+ ]
4012
+ },
4013
+ clerk: {
4014
+ id: "clerk",
4015
+ name: "Clerk",
4016
+ category: "auth",
4017
+ authType: "api_key",
4018
+ configFields: [{ key: "secretKey", label: "Secret Key", secret: true }]
4019
+ }
4020
+ };
4021
+ PROVIDER_NAMES = Object.keys(INTEGRATION_PROVIDERS).sort();
4022
+ });
4023
+
4024
+ // src/commands/integrations/list.ts
4025
+ var exports_list3 = {};
4026
+ __export(exports_list3, {
4027
+ default: () => list_default3
4028
+ });
4029
+ var list_default3;
4030
+ var init_list3 = __esm(() => {
4031
+ init_dist();
4032
+ init_auth();
4033
+ init_output2();
4034
+ init_api();
4035
+ list_default3 = defineCommand2({
4036
+ meta: {
4037
+ name: "list",
4038
+ description: [
4039
+ "List available integrations and their connection status.",
4040
+ "",
4041
+ "Shows all supported third-party integrations with whether they",
4042
+ "are connected, available, or in an error state.",
4043
+ "",
4044
+ "Examples:",
4045
+ " outlit integrations list",
4046
+ " outlit integrations list --json",
4047
+ "",
4048
+ AGENT_JSON_HINT
4049
+ ].join(`
4050
+ `)
4051
+ },
4052
+ args: {
4053
+ ...authArgs,
4054
+ ...outputArgs
4055
+ },
4056
+ async run({ args }) {
4057
+ const json = !!args.json;
4058
+ const client = await getClientOrExit(args["api-key"], json);
4059
+ return runTool(client, "outlit_list_integrations", {}, json, {
4060
+ spinnerMessage: "Fetching integrations...",
4061
+ table: {
4062
+ columns: [
4063
+ { header: "Name", key: "name", format: (v) => truncate(v, 24) },
4064
+ { header: "Category", key: "category", format: capitalize },
4065
+ { header: "Status", key: "status" },
4066
+ { header: "Last Synced", key: "lastDataReceivedAt", format: relativeDate }
4067
+ ]
4068
+ }
4069
+ });
4070
+ }
4071
+ });
4072
+ });
4073
+
4074
+ // src/lib/poll.ts
4075
+ async function pollUntil(fn, predicate, opts = {}) {
4076
+ const intervalMs = opts.intervalMs ?? 2000;
4077
+ const timeoutMs = opts.timeoutMs ?? 300000;
4078
+ const start = Date.now();
4079
+ while (Date.now() - start < timeoutMs) {
4080
+ try {
4081
+ const result = await fn();
4082
+ if (predicate(result))
4083
+ return result;
4084
+ } catch {}
4085
+ if (opts.spinner && opts.spinnerMessage) {
4086
+ const elapsed = Math.floor((Date.now() - start) / 1000);
4087
+ opts.spinner.update(`${opts.spinnerMessage} (${elapsed}s)`);
4088
+ }
4089
+ await sleep(intervalMs);
4090
+ }
4091
+ return null;
4092
+ }
4093
+ function sleep(ms) {
4094
+ return new Promise((resolve) => setTimeout(resolve, ms));
4095
+ }
4096
+
4097
+ // src/commands/integrations/add.ts
4098
+ var exports_add = {};
4099
+ __export(exports_add, {
4100
+ default: () => add_default
4101
+ });
4102
+ async function addApiKeyProvider(client, provider, cliName, json, rawConfig) {
4103
+ const fields = provider.configFields ?? [];
4104
+ let config;
4105
+ if (rawConfig) {
4106
+ try {
4107
+ const parsed = JSON.parse(rawConfig);
4108
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4109
+ return outputError({ message: "Invalid JSON in --config. Expected a JSON object.", code: "invalid_config" }, json);
4110
+ }
4111
+ config = parsed;
4112
+ } catch {
4113
+ return outputError({ message: "Invalid JSON in --config. Expected a JSON object.", code: "invalid_config" }, json);
4114
+ }
4115
+ const missing = fields.filter((f) => !config[f.key]);
4116
+ if (missing.length > 0) {
4117
+ return outputError({
4118
+ message: `Missing required fields: ${missing.map((f) => f.key).join(", ")}`,
4119
+ code: "invalid_config"
4120
+ }, json);
4121
+ }
4122
+ } else if (isInteractive() && !isJsonMode(json)) {
4123
+ console.log(`
4124
+ Configure ${provider.name}:
4125
+ `);
4126
+ config = {};
4127
+ for (const field of fields) {
4128
+ config[field.key] = await promptInput(` ${field.label}`, { secret: field.secret });
4129
+ }
4130
+ console.log();
4131
+ } else {
4132
+ return outputResult({
4133
+ status: "config_required",
4134
+ provider: cliName,
4135
+ message: `${provider.name} requires API key configuration. Pass --config with the required fields.`,
4136
+ requiredFields: fields.map((f) => ({ key: f.key, label: f.label }))
4137
+ });
4138
+ }
4139
+ const spinner = createSpinner(`Connecting ${provider.name}...`);
4140
+ try {
4141
+ await client.callTool("outlit_connect_integration", {
4142
+ provider: provider.id,
4143
+ config
4144
+ });
4145
+ spinner.stop(`${provider.name} connected successfully!`);
4146
+ if (isJsonMode(json)) {
4147
+ return outputResult({
4148
+ status: "connected",
4149
+ provider: cliName
4150
+ });
4151
+ }
4152
+ console.log(` Use \`outlit integrations status ${cliName}\` to check sync progress.`);
4153
+ } catch (err) {
4154
+ spinner.fail(`Failed to connect ${provider.name}`);
4155
+ return outputError({ message: errorMessage(err, "Failed to connect integration"), code: "api_error" }, json);
4156
+ }
4157
+ }
4158
+ async function addOAuthProvider(client, provider, cliName, json, force) {
4159
+ const spinner = createSpinner(`Connecting ${provider.name}...`);
4160
+ let connectData;
4161
+ try {
4162
+ connectData = await client.callTool("outlit_connect_integration", {
4163
+ provider: provider.id
4164
+ });
4165
+ } catch (err) {
4166
+ spinner.fail(`Failed to initiate ${provider.name} connection`);
4167
+ return outputError({ message: errorMessage(err, "Failed to start connection flow"), code: "api_error" }, json);
4168
+ }
4169
+ if (connectData.alreadyConnected && !force) {
4170
+ spinner.stop(`${provider.name} is already connected`);
4171
+ if (isJsonMode(json)) {
4172
+ return outputResult({
4173
+ status: "already_connected",
4174
+ provider: cliName,
4175
+ message: `${provider.name} is already connected. Use --force to reconnect.`
4176
+ });
4177
+ }
4178
+ console.log(`
4179
+ ${provider.name} is already connected.`);
4180
+ console.log(` To reconnect, run: outlit integrations add ${cliName} --force`);
4181
+ return;
4182
+ }
4183
+ const integrationsUrl = `${client.baseUrl}/integrations`;
4184
+ spinner.update(`Opening browser for ${provider.name} authentication...`);
4185
+ const opened = openBrowser(integrationsUrl);
4186
+ if (!opened) {
4187
+ spinner.stop("Could not open browser automatically");
4188
+ if (isJsonMode(json)) {
4189
+ return outputResult({
4190
+ status: "browser_failed",
4191
+ provider: cliName,
4192
+ url: integrationsUrl,
4193
+ sessionId: connectData.sessionId
4194
+ });
4195
+ }
4196
+ console.log(`
4197
+ Open this URL in your browser to connect ${provider.name}:`);
4198
+ console.log(` ${integrationsUrl}
4199
+ `);
4200
+ } else {
4201
+ spinner.stop(`Browser opened for ${provider.name} authentication`);
4202
+ }
4203
+ if (isJsonMode(json)) {
4204
+ return outputResult({
4205
+ status: "awaiting_auth",
4206
+ provider: cliName,
4207
+ sessionId: connectData.sessionId
4208
+ });
4209
+ }
4210
+ await waitForConnection(client, connectData.sessionId, provider, cliName);
4211
+ }
4212
+ async function waitForConnection(client, sessionId, provider, cliName) {
4213
+ const spinner = createSpinner(`Waiting for ${provider.name} authentication...`);
4214
+ const result = await pollUntil(() => client.callTool("outlit_connect_status", { sessionId }).then((r) => r), (r) => r.status !== "pending", {
4215
+ intervalMs: 2000,
4216
+ timeoutMs: 300000,
4217
+ spinner,
4218
+ spinnerMessage: `Waiting for ${provider.name} authentication...`
4219
+ });
4220
+ if (!result || result.status === "expired") {
4221
+ spinner.fail("Connection timed out");
4222
+ console.log(`
4223
+ The authentication session expired.`);
4224
+ console.log(` Run \`outlit integrations add ${cliName}\` to try again.`);
4225
+ process.exit(1);
4226
+ }
4227
+ if (result.status === "failed") {
4228
+ spinner.fail(`${provider.name} connection failed`);
4229
+ if (result.error)
4230
+ console.log(`
4231
+ ${result.error}`);
4232
+ process.exit(1);
4233
+ }
4234
+ spinner.stop(`${provider.name} connected successfully!`);
4235
+ console.log(` Sync will begin automatically.`);
4236
+ console.log(` Use \`outlit integrations status ${cliName}\` to check progress.`);
4237
+ }
4238
+ var add_default;
4239
+ var init_add = __esm(() => {
4240
+ init_dist();
4241
+ init_auth();
4242
+ init_output2();
4243
+ init_api();
4244
+ init_output();
4245
+ init_providers();
4246
+ init_spinner();
4247
+ init_tty();
4248
+ add_default = defineCommand2({
4249
+ meta: {
4250
+ name: "add",
4251
+ description: [
4252
+ "Connect a new integration.",
4253
+ "",
4254
+ "For OAuth providers (Slack, Gmail, etc.), opens the browser for authentication.",
4255
+ "For API-key providers (Stripe, PostHog, etc.), accepts credentials via --config or interactive prompts.",
4256
+ "",
4257
+ "Examples:",
4258
+ " outlit integrations add slack",
4259
+ ` outlit integrations add stripe --config '{"apiKey": "rk_xxx"}'`,
4260
+ " outlit integrations add posthog --json # outputs required fields as JSON",
4261
+ " outlit integrations add slack --force # reconnect if already connected",
4262
+ "",
4263
+ `Providers: ${PROVIDER_NAMES.join(", ")}`,
4264
+ "",
4265
+ AGENT_JSON_HINT
4266
+ ].join(`
4267
+ `)
4268
+ },
4269
+ args: {
4270
+ ...authArgs,
4271
+ ...outputArgs,
4272
+ provider: {
4273
+ type: "positional",
4274
+ description: "Integration provider to connect",
4275
+ required: true
4276
+ },
4277
+ force: {
4278
+ type: "boolean",
4279
+ description: "Reconnect even if the integration is already connected."
4280
+ },
4281
+ config: {
4282
+ type: "string",
4283
+ description: `JSON configuration for API-key integrations (e.g. '{"apiKey": "sk_xxx"}')`
4284
+ }
4285
+ },
4286
+ async run({ args }) {
4287
+ const json = !!args.json;
4288
+ const client = await getClientOrExit(args["api-key"], json);
4289
+ const { provider, cliName } = resolveProviderOrExit(args.provider, json);
4290
+ if (provider.authType === "api_key") {
4291
+ await addApiKeyProvider(client, provider, cliName, json, args.config);
4292
+ } else {
4293
+ await addOAuthProvider(client, provider, cliName, json, !!args.force);
4294
+ }
4295
+ }
4296
+ });
4297
+ });
4298
+
4299
+ // src/commands/integrations/remove.ts
4300
+ var exports_remove = {};
4301
+ __export(exports_remove, {
4302
+ default: () => remove_default
4303
+ });
4304
+ var remove_default;
4305
+ var init_remove = __esm(() => {
4306
+ init_dist3();
4307
+ init_dist();
4308
+ init_auth();
4309
+ init_output2();
4310
+ init_api();
4311
+ init_output();
4312
+ init_providers();
4313
+ init_spinner();
4314
+ init_tty();
4315
+ remove_default = defineCommand2({
4316
+ meta: {
4317
+ name: "remove",
4318
+ description: [
4319
+ "Disconnect an integration and remove all synced data.",
4320
+ "",
4321
+ "This permanently deletes all data that was synced from the integration",
4322
+ "(e.g., opportunities, contacts, messages). The action cannot be undone.",
4323
+ "",
4324
+ "Examples:",
4325
+ " outlit integrations remove posthog",
4326
+ " outlit integrations remove slack --yes # skip confirmation prompt",
4327
+ "",
4328
+ AGENT_JSON_HINT
4329
+ ].join(`
4330
+ `)
4331
+ },
4332
+ args: {
4333
+ ...authArgs,
4334
+ ...outputArgs,
4335
+ provider: {
4336
+ type: "positional",
4337
+ description: "Integration provider to disconnect",
4338
+ required: true
4339
+ },
4340
+ yes: {
4341
+ type: "boolean",
4342
+ description: "Skip confirmation prompt (required in non-interactive mode)."
4343
+ }
4344
+ },
4345
+ async run({ args }) {
4346
+ const json = !!args.json;
4347
+ const { provider, cliName } = resolveProviderOrExit(args.provider, json);
4348
+ const client = await getClientOrExit(args["api-key"], json);
4349
+ if (!args.yes) {
4350
+ if (!isInteractive() || json) {
4351
+ return outputError({
4352
+ message: `Disconnecting ${provider.name} requires confirmation. Use --yes to confirm in non-interactive or JSON mode.`,
4353
+ code: "confirmation_required"
4354
+ }, json);
4355
+ }
4356
+ R2.warn(`This will disconnect ${provider.name} and delete all synced data.
4357
+ This action cannot be undone.`);
4358
+ const confirmed = await Re({ message: `Disconnect ${provider.name}?` });
4359
+ if (Ct(confirmed) || !confirmed) {
4360
+ Ne("Cancelled.");
4361
+ return;
4362
+ }
4363
+ }
4364
+ const spinner = createSpinner(`Disconnecting ${provider.name}...`);
4365
+ try {
4366
+ const result = await client.callTool("outlit_disconnect_integration", {
4367
+ provider: provider.id
4368
+ });
4369
+ if (result.success) {
4370
+ spinner.stop(`${provider.name} disconnected. All synced data has been removed.`);
4371
+ if (isJsonMode(json)) {
4372
+ return outputResult({ success: true, provider: cliName });
4373
+ }
4374
+ } else {
4375
+ spinner.fail(`Failed to disconnect ${provider.name}`);
4376
+ return outputError({
4377
+ message: result.message ?? `Failed to disconnect ${provider.name}`,
4378
+ code: "disconnect_failed"
4379
+ }, json);
4380
+ }
4381
+ } catch (err) {
4382
+ spinner.fail(`Failed to disconnect ${provider.name}`);
4383
+ return outputError({ message: errorMessage(err, "Disconnect request failed"), code: "api_error" }, json);
4384
+ }
4385
+ }
4386
+ });
4387
+ });
4388
+
4389
+ // src/commands/integrations/status.ts
4390
+ var exports_status2 = {};
4391
+ __export(exports_status2, {
4392
+ default: () => status_default2
4393
+ });
4394
+ var status_default2;
4395
+ var init_status2 = __esm(() => {
4396
+ init_dist();
4397
+ init_auth();
4398
+ init_output2();
4399
+ init_api();
4400
+ init_providers();
4401
+ status_default2 = defineCommand2({
4402
+ meta: {
4403
+ name: "status",
4404
+ description: [
4405
+ "Show sync status for connected integrations.",
4406
+ "",
4407
+ "Without a provider name, shows a summary of all connected integrations.",
4408
+ "With a provider name, shows detailed per-model sync status.",
4409
+ "",
4410
+ "Examples:",
4411
+ " outlit integrations status # summary of all",
4412
+ " outlit integrations status stripe # detailed Stripe sync status",
4413
+ "",
4414
+ AGENT_JSON_HINT
4415
+ ].join(`
4416
+ `)
4417
+ },
4418
+ args: {
4419
+ ...authArgs,
4420
+ ...outputArgs,
4421
+ provider: {
4422
+ type: "positional",
4423
+ description: "Provider name to show detailed status for (optional)",
4424
+ required: false
4425
+ }
4426
+ },
4427
+ async run({ args }) {
4428
+ const json = !!args.json;
4429
+ const client = await getClientOrExit(args["api-key"], json);
4430
+ if (args.provider) {
4431
+ const { provider } = resolveProviderOrExit(args.provider, json);
4432
+ return runTool(client, "outlit_integration_sync_status", { provider: provider.id }, json, {
4433
+ spinnerMessage: "Fetching sync status...",
4434
+ table: {
4435
+ columns: [
4436
+ { header: "Model", key: "model" },
4437
+ { header: "Status", key: "status" },
4438
+ { header: "Records", key: "recordCount", format: formatNumber },
4439
+ { header: "Last Synced", key: "lastSyncedAt", format: relativeDate }
4440
+ ],
4441
+ itemsKey: "syncs"
4442
+ }
4443
+ });
4444
+ }
4445
+ return runTool(client, "outlit_list_integrations", { connectedOnly: true }, json, {
4446
+ spinnerMessage: "Fetching integration status...",
4447
+ table: {
4448
+ columns: [
4449
+ { header: "Name", key: "name", format: (v) => truncate(v, 24) },
4450
+ { header: "Category", key: "category", format: capitalize },
4451
+ { header: "Sync Status", key: "syncStatus" },
4452
+ { header: "Last Synced", key: "lastDataReceivedAt", format: relativeDate }
4453
+ ]
4454
+ }
4455
+ });
4456
+ }
4457
+ });
4458
+ });
4459
+
4460
+ // src/commands/integrations/index.ts
4461
+ var exports_integrations = {};
4462
+ __export(exports_integrations, {
4463
+ default: () => integrations_default
4464
+ });
4465
+ var integrations_default;
4466
+ var init_integrations = __esm(() => {
4467
+ init_dist();
4468
+ init_providers();
4469
+ integrations_default = defineCommand2({
4470
+ meta: {
4471
+ name: "integrations",
4472
+ description: [
4473
+ "Manage platform integrations (communication, analytics, billing, etc.).",
4474
+ "",
4475
+ "Connect third-party services like Slack, Stripe, and PostHog",
4476
+ "to sync data into your Outlit workspace.",
4477
+ "",
4478
+ "Commands:",
4479
+ " list List available integrations and connection status",
4480
+ " add <provider> Connect a new integration (opens browser for OAuth)",
4481
+ " remove <provider> Disconnect an integration and remove synced data",
4482
+ " status [provider] Show sync status for connected integrations",
4483
+ "",
4484
+ `Providers: ${PROVIDER_NAMES.join(", ")}`
4485
+ ].join(`
4486
+ `)
4487
+ },
4488
+ subCommands: {
4489
+ list: () => Promise.resolve().then(() => (init_list3(), exports_list3)).then((m) => m.default),
4490
+ add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((m) => m.default),
4491
+ remove: () => Promise.resolve().then(() => (init_remove(), exports_remove)).then((m) => m.default),
4492
+ status: () => Promise.resolve().then(() => (init_status2(), exports_status2)).then((m) => m.default)
4493
+ }
4494
+ });
4495
+ });
4496
+
3689
4497
  // src/commands/completions.ts
3690
4498
  var exports_completions = {};
3691
4499
  __export(exports_completions, {
@@ -3756,7 +4564,10 @@ function zshDescribe(items) {
3756
4564
  function generateZsh() {
3757
4565
  const topLevel = zshDescribe(COMMANDS);
3758
4566
  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 }))];
4567
+ const items = [
4568
+ ...c.subs.map((s) => ({ name: s.name, desc: s.desc })),
4569
+ ...(c.flags ?? []).map((f) => ({ name: f.name, desc: f.desc }))
4570
+ ];
3760
4571
  return ` ${c.name})
3761
4572
  completions=(${zshDescribe(items)})
3762
4573
  _describe 'subcommand' completions
@@ -3887,7 +4698,6 @@ function generateFish() {
3887
4698
  var JSON_F, API_KEY_F, LIMIT_F, CURSOR_F, COMMON, PAGINATED, ACTIVITY_ORDER, COMMANDS, cmdsWithSubs, leafCmds, SCRIPTS, completions_default;
3888
4699
  var init_completions = __esm(() => {
3889
4700
  init_dist();
3890
- init_output2();
3891
4701
  init_output();
3892
4702
  JSON_F = { name: "--json", desc: "Force JSON output" };
3893
4703
  API_KEY_F = { name: "--api-key", desc: "Outlit API key" };
@@ -3907,7 +4717,11 @@ var init_completions = __esm(() => {
3907
4717
  desc: "Manage authentication",
3908
4718
  subs: [
3909
4719
  { 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" }] },
4720
+ {
4721
+ name: "login",
4722
+ desc: "Store API key",
4723
+ flags: [JSON_F, { name: "--key", desc: "API key to store" }]
4724
+ },
3911
4725
  { name: "logout", desc: "Remove stored key", flags: [JSON_F] },
3912
4726
  { name: "status", desc: "Check auth state", flags: [...COMMON] },
3913
4727
  { name: "whoami", desc: "Print masked key", flags: [...COMMON] }
@@ -3923,6 +4737,7 @@ var init_completions = __esm(() => {
3923
4737
  flags: [
3924
4738
  ...PAGINATED,
3925
4739
  ...ACTIVITY_ORDER,
4740
+ { name: "--trait", desc: "Filter by trait key=value pairs" },
3926
4741
  { name: "--billing-status", desc: "Filter by billing status" },
3927
4742
  { name: "--mrr-above", desc: "MRR above threshold (cents)" },
3928
4743
  { name: "--mrr-below", desc: "MRR below threshold (cents)" },
@@ -3964,6 +4779,7 @@ var init_completions = __esm(() => {
3964
4779
  flags: [
3965
4780
  ...PAGINATED,
3966
4781
  ...ACTIVITY_ORDER,
4782
+ { name: "--trait", desc: "Filter by trait key=value pairs" },
3967
4783
  { name: "--journey-stage", desc: "Filter by journey stage" },
3968
4784
  { name: "--customer-id", desc: "Filter by customer UUID" },
3969
4785
  { name: "--search", desc: "Search name or email" }
@@ -3997,6 +4813,28 @@ var init_completions = __esm(() => {
3997
4813
  ]
3998
4814
  },
3999
4815
  { name: "schema", desc: "Discover table schemas", flags: [...COMMON] },
4816
+ {
4817
+ name: "integrations",
4818
+ desc: "Manage platform integrations",
4819
+ subs: [
4820
+ { name: "list", desc: "List integrations and status", flags: [...COMMON] },
4821
+ {
4822
+ name: "add",
4823
+ desc: "Connect an integration",
4824
+ flags: [
4825
+ ...COMMON,
4826
+ { name: "--config", desc: "JSON config for API-key providers" },
4827
+ { name: "--force", desc: "Reconnect if already connected" }
4828
+ ]
4829
+ },
4830
+ {
4831
+ name: "remove",
4832
+ desc: "Disconnect an integration",
4833
+ flags: [...COMMON, { name: "--yes", desc: "Skip confirmation" }]
4834
+ },
4835
+ { name: "status", desc: "Show sync status", flags: [...COMMON] }
4836
+ ]
4837
+ },
4000
4838
  {
4001
4839
  name: "setup",
4002
4840
  desc: "Configure AI agent tools",
@@ -4011,7 +4849,7 @@ var init_completions = __esm(() => {
4011
4849
  ]
4012
4850
  },
4013
4851
  { name: "doctor", desc: "Diagnose environment", flags: [...COMMON] },
4014
- { name: "completions", desc: "Generate shell completions", flags: [JSON_F] }
4852
+ { name: "completions", desc: "Generate shell completions", flags: [] }
4015
4853
  ];
4016
4854
  cmdsWithSubs = COMMANDS.filter((c) => c.subs?.length);
4017
4855
  leafCmds = COMMANDS.filter((c) => !c.subs?.length && c.flags?.length);
@@ -4038,7 +4876,6 @@ var init_completions = __esm(() => {
4038
4876
  `)
4039
4877
  },
4040
4878
  args: {
4041
- ...outputArgs,
4042
4879
  shell: {
4043
4880
  type: "positional",
4044
4881
  description: "Shell to generate completions for (bash, zsh, fish)",
@@ -4046,14 +4883,13 @@ var init_completions = __esm(() => {
4046
4883
  }
4047
4884
  },
4048
4885
  run({ args }) {
4049
- const json = !!args.json;
4050
4886
  const shell = args.shell;
4051
4887
  const generate = SCRIPTS[shell];
4052
4888
  if (!generate) {
4053
4889
  return outputError({
4054
4890
  message: `Unknown shell: ${shell}. Supported: bash, zsh, fish`,
4055
4891
  code: "unknown_shell"
4056
- }, json);
4892
+ }, false);
4057
4893
  }
4058
4894
  process.stdout.write(generate());
4059
4895
  }
@@ -4067,14 +4903,14 @@ __export(exports_setup, {
4067
4903
  detectAgents: () => detectAgents3,
4068
4904
  default: () => setup_default2
4069
4905
  });
4070
- import { execFileSync as execFileSync6 } from "node:child_process";
4071
- import { existsSync as existsSync5 } from "node:fs";
4906
+ import { execFileSync as execFileSync5 } from "node:child_process";
4907
+ import { existsSync as existsSync4 } from "node:fs";
4072
4908
  import { homedir as homedir6 } from "node:os";
4073
4909
  import { join as join8 } from "node:path";
4074
4910
  function isCommandAvailable2(cmd) {
4075
4911
  try {
4076
4912
  const whichCmd = process.platform === "win32" ? "where" : "which";
4077
- execFileSync6(whichCmd, [cmd], { stdio: "ignore" });
4913
+ execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
4078
4914
  return true;
4079
4915
  } catch {
4080
4916
  return false;
@@ -4083,17 +4919,17 @@ function isCommandAvailable2(cmd) {
4083
4919
  function detectAgents3() {
4084
4920
  const home = homedir6();
4085
4921
  const detected = [];
4086
- if (existsSync5(join8(home, ".cursor")))
4922
+ if (existsSync4(join8(home, ".cursor")))
4087
4923
  detected.push("cursor");
4088
4924
  if (isCommandAvailable2("claude"))
4089
4925
  detected.push("claude-code");
4090
- if (existsSync5(getClaudeDesktopConfigPath()))
4926
+ if (existsSync4(getClaudeDesktopConfigPath()))
4091
4927
  detected.push("claude-desktop");
4092
- if (isCommandAvailable2("code") || existsSync5(join8(process.cwd(), ".vscode")))
4928
+ if (isCommandAvailable2("code") || existsSync4(join8(process.cwd(), ".vscode")))
4093
4929
  detected.push("vscode");
4094
4930
  if (isCommandAvailable2("gemini"))
4095
4931
  detected.push("gemini");
4096
- if (existsSync5(join8(home, "clawd", "skills")) || existsSync5(join8(home, ".openclaw", "skills")))
4932
+ if (existsSync4(join8(home, ".openclaw", "skills")))
4097
4933
  detected.push("openclaw");
4098
4934
  return detected;
4099
4935
  }
@@ -4589,6 +5425,7 @@ For AI agents: commands auto-output JSON when stdout is piped. No --json flag ne
4589
5425
  search: () => Promise.resolve().then(() => (init_search(), exports_search)).then((m) => m.default),
4590
5426
  sql: () => Promise.resolve().then(() => (init_sql(), exports_sql)).then((m) => m.default),
4591
5427
  schema: () => Promise.resolve().then(() => (init_schema(), exports_schema)).then((m) => m.default),
5428
+ integrations: () => Promise.resolve().then(() => (init_integrations(), exports_integrations)).then((m) => m.default),
4592
5429
  completions: () => Promise.resolve().then(() => (init_completions(), exports_completions)).then((m) => m.default),
4593
5430
  setup: () => Promise.resolve().then(() => (init_setup3(), exports_setup)).then((m) => m.default)
4594
5431
  }