@outlit/cli 1.4.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 +1527 -878
  2. package/package.json +1 -1
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.4.1",
107
+ version: "1.5.0",
108
108
  description: "CLI for Outlit customer intelligence platform",
109
109
  license: "Apache-2.0",
110
110
  repository: {
@@ -222,6 +222,70 @@ var init_output = __esm(() => {
222
222
  init_tty();
223
223
  });
224
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
+
225
289
  // ../../node_modules/.bun/citty@0.2.1/node_modules/citty/dist/index.mjs
226
290
  function defineCommand2(def) {
227
291
  return def;
@@ -1375,109 +1439,6 @@ Note: JSON is also auto-enabled when stdout is piped (e.g. in CI, scripts, or AI
1375
1439
  };
1376
1440
  });
1377
1441
 
1378
- // src/lib/config.ts
1379
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1380
- import { homedir } from "node:os";
1381
- import { dirname, join } from "node:path";
1382
- function isEnoentError(err) {
1383
- return err instanceof Error && err.code === "ENOENT";
1384
- }
1385
- function splitCsv(value) {
1386
- return value.split(",").map((s) => s.trim());
1387
- }
1388
- function getClaudeDesktopConfigPath() {
1389
- const home = homedir();
1390
- switch (process.platform) {
1391
- case "win32":
1392
- return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
1393
- case "darwin":
1394
- return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1395
- default:
1396
- return join(home, ".config", "Claude", "claude_desktop_config.json");
1397
- }
1398
- }
1399
- function getConfigDir() {
1400
- if (process.platform === "win32") {
1401
- return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "outlit");
1402
- }
1403
- return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "outlit");
1404
- }
1405
- function resolveApiKey(flagValue) {
1406
- if (flagValue)
1407
- return { key: flagValue, source: "flag" };
1408
- const envKey = process.env.OUTLIT_API_KEY;
1409
- if (envKey)
1410
- return { key: envKey, source: "env" };
1411
- const credPath = join(getConfigDir(), "credentials.json");
1412
- if (existsSync(credPath)) {
1413
- try {
1414
- const raw = readFileSync(credPath, "utf-8");
1415
- const config = JSON.parse(raw);
1416
- if (config.apiKey)
1417
- return { key: config.apiKey, source: "config" };
1418
- } catch {}
1419
- }
1420
- return null;
1421
- }
1422
- function maskKey(key) {
1423
- if (key.length <= 9)
1424
- return key;
1425
- return `${key.slice(0, 5)}...${key.slice(-4)}`;
1426
- }
1427
- function readJsonConfig(filePath) {
1428
- if (!existsSync(filePath))
1429
- return {};
1430
- try {
1431
- return JSON.parse(readFileSync(filePath, "utf-8"));
1432
- } catch {
1433
- return {};
1434
- }
1435
- }
1436
- function requireCredential(flagApiKey, json) {
1437
- const credential = resolveApiKey(flagApiKey);
1438
- if (!credential) {
1439
- return outputError({
1440
- message: "Not authenticated. Run `outlit auth login` or pass --api-key.",
1441
- code: "not_authenticated"
1442
- }, json);
1443
- }
1444
- return credential;
1445
- }
1446
- function writeConfigFile(filePath, content, opts) {
1447
- try {
1448
- mkdirSync(dirname(filePath), { recursive: true });
1449
- writeFileSync(filePath, content);
1450
- return true;
1451
- } catch (err) {
1452
- return outputError({ message: errorMessage(err, `Failed to write ${opts.label}`), code: "write_error" }, opts.json);
1453
- }
1454
- }
1455
- function mergeOutlitMcpConfig(configPath, serversKey, outlitConfig, opts) {
1456
- const existing = readJsonConfig(configPath);
1457
- const existingServers = existing[serversKey] ?? {};
1458
- const merged = {
1459
- ...existing,
1460
- [serversKey]: { ...existingServers, outlit: outlitConfig }
1461
- };
1462
- writeConfigFile(configPath, `${JSON.stringify(merged, null, 2)}
1463
- `, opts);
1464
- }
1465
- function storeApiKey(apiKey) {
1466
- const configDir = getConfigDir();
1467
- mkdirSync(configDir, { recursive: true, mode: 448 });
1468
- const credPath = join(configDir, "credentials.json");
1469
- writeFileSync(credPath, JSON.stringify({ apiKey }, null, 2), { mode: 384 });
1470
- return credPath;
1471
- }
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;
1473
- var init_config = __esm(() => {
1474
- init_package();
1475
- init_output();
1476
- init_tty();
1477
- CLI_VERSION2 = package_default.version;
1478
- TICK2 = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
1479
- });
1480
-
1481
1442
  // src/commands/auth/signup.ts
1482
1443
  var exports_signup = {};
1483
1444
  __export(exports_signup, {
@@ -1594,7 +1555,9 @@ var init_client = __esm(() => {
1594
1555
  outlit_get_customer: { method: "POST", path: "/api/internal/mcp/customers" },
1595
1556
  outlit_list_users: { method: "GET", path: "/api/internal/mcp/users" },
1596
1557
  outlit_get_timeline: { method: "POST", path: "/api/internal/mcp/timeline" },
1597
- 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" },
1598
1561
  outlit_schema: { method: "GET", path: "/api/internal/mcp/sql-schema" },
1599
1562
  outlit_query: { method: "POST", path: "/api/internal/mcp/sql" },
1600
1563
  outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" },
@@ -1736,8 +1699,26 @@ async function getClientOrExit(flagApiKey, json) {
1736
1699
  return createClient(flagApiKey).catch((err) => outputError({ message: errorMessage(err, "Authentication failed"), code: "auth_required" }, json));
1737
1700
  }
1738
1701
  async function pingApiKey(apiKey) {
1739
- const client = await createClient(apiKey);
1740
- 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
+ }
1741
1722
  }
1742
1723
  async function validateKeyOrExit(apiKey, json) {
1743
1724
  try {
@@ -1789,6 +1770,7 @@ async function runTool(client, toolName, params, json, opts) {
1789
1770
  }
1790
1771
  var init_api = __esm(() => {
1791
1772
  init_client();
1773
+ init_config();
1792
1774
  init_output();
1793
1775
  init_spinner();
1794
1776
  init_table();
@@ -1922,8 +1904,8 @@ var exports_logout = {};
1922
1904
  __export(exports_logout, {
1923
1905
  default: () => logout_default
1924
1906
  });
1925
- import { rmSync } from "node:fs";
1926
- import { join as join2 } from "node:path";
1907
+ import { rmSync as rmSync2 } from "node:fs";
1908
+ import { join as join3 } from "node:path";
1927
1909
  var logout_default;
1928
1910
  var init_logout = __esm(() => {
1929
1911
  init_dist();
@@ -1949,13 +1931,13 @@ var init_logout = __esm(() => {
1949
1931
  async run({ args }) {
1950
1932
  const json = !!args.json;
1951
1933
  const configDir = getConfigDir();
1952
- const credPath = join2(configDir, "credentials.json");
1934
+ const credPath = join3(configDir, "credentials.json");
1953
1935
  if (process.env.OUTLIT_API_KEY) {
1954
1936
  process.stderr.write(`Warning: OUTLIT_API_KEY env var is still set and will continue to work after logout.
1955
1937
  `);
1956
1938
  }
1957
1939
  try {
1958
- rmSync(credPath, { force: true });
1940
+ rmSync2(credPath, { force: true });
1959
1941
  } catch (err) {
1960
1942
  if (!isEnoentError(err)) {
1961
1943
  return outputError({ message: errorMessage(err, "Failed to remove credentials file"), code: "unlink_error" }, json);
@@ -2219,6 +2201,148 @@ Use this to fetch the next page of results.`
2219
2201
  };
2220
2202
  });
2221
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
+
2222
2346
  // src/lib/format.ts
2223
2347
  function formatCents(value) {
2224
2348
  if (value == null || typeof value !== "number" || Number.isNaN(value))
@@ -2282,6 +2406,7 @@ var init_list = __esm(() => {
2282
2406
  init_filters();
2283
2407
  init_output2();
2284
2408
  init_pagination();
2409
+ init_tool_contracts();
2285
2410
  init_api();
2286
2411
  init_output();
2287
2412
  list_default = defineCommand2({
@@ -2300,7 +2425,7 @@ var init_list = __esm(() => {
2300
2425
  " outlit customers list --mrr-above 10000 --limit 50 # high-value at-risk",
2301
2426
  " outlit customers list --json | jq '.items[].domain' # pipe-friendly",
2302
2427
  "",
2303
- "Billing statuses: NONE, TRIALING, PAYING, PAST_DUE, CHURNED",
2428
+ `Billing statuses: ${customerBillingStatuses.join(", ")}`,
2304
2429
  "Activity periods: 7d, 14d, 30d, 90d",
2305
2430
  "",
2306
2431
  AGENT_JSON_HINT
@@ -2316,7 +2441,7 @@ var init_list = __esm(() => {
2316
2441
  ...orderArgs,
2317
2442
  "billing-status": {
2318
2443
  type: "string",
2319
- description: "Filter by billing status (NONE, TRIALING, PAYING, PAST_DUE, CHURNED)"
2444
+ description: `Filter by billing status (${customerBillingStatuses.join(", ")})`
2320
2445
  },
2321
2446
  "mrr-above": {
2322
2447
  type: "string",
@@ -2329,14 +2454,6 @@ var init_list = __esm(() => {
2329
2454
  search: {
2330
2455
  type: "string",
2331
2456
  description: "Search by customer name or domain"
2332
- },
2333
- status: {
2334
- type: "string",
2335
- description: "Customer status filter (PROVISIONAL, ACTIVE, CHURNED, MERGED)"
2336
- },
2337
- type: {
2338
- type: "string",
2339
- description: "Customer type filter (COMPANY, INDIVIDUAL)"
2340
2457
  }
2341
2458
  },
2342
2459
  async run({ args }) {
@@ -2374,11 +2491,7 @@ var init_list = __esm(() => {
2374
2491
  }
2375
2492
  applyListFilters(params, args);
2376
2493
  applyPagination(params, args, json);
2377
- if (args.status)
2378
- params.status = args.status;
2379
- if (args.type)
2380
- params.type = args.type;
2381
- return runTool(client, "outlit_list_customers", params, json, {
2494
+ return runTool(client, customerToolContracts.outlit_list_customers.toolName, params, json, {
2382
2495
  spinnerMessage: "Fetching customers...",
2383
2496
  table: {
2384
2497
  columns: [
@@ -2404,6 +2517,7 @@ var init_get = __esm(() => {
2404
2517
  init_dist();
2405
2518
  init_auth();
2406
2519
  init_output2();
2520
+ init_tool_contracts();
2407
2521
  init_api();
2408
2522
  init_config();
2409
2523
  get_default = defineCommand2({
@@ -2420,6 +2534,9 @@ var init_get = __esm(() => {
2420
2534
  'Naming note: --include users returns data under the "contacts" key in',
2421
2535
  "the response. This is a server-side naming inconsistency, not a CLI bug.",
2422
2536
  "",
2537
+ `Available include sections: ${customerIncludeSections.join(", ")}`,
2538
+ `Timeframes: ${customerTimeframes.join(", ")}`,
2539
+ "",
2423
2540
  "Examples:",
2424
2541
  " outlit customers get acme.com",
2425
2542
  " outlit customers get acme.com --include users,revenue",
@@ -2441,7 +2558,7 @@ var init_get = __esm(() => {
2441
2558
  type: "string",
2442
2559
  description: [
2443
2560
  "Comma-separated sections to include in response.",
2444
- "Available: users, revenue, recentTimeline, behaviorMetrics",
2561
+ `Available: ${customerIncludeSections.join(", ")}`,
2445
2562
  'Note: "users" maps to "contacts" in the response (server naming).'
2446
2563
  ].join(`
2447
2564
  `)
@@ -2462,7 +2579,7 @@ var init_get = __esm(() => {
2462
2579
  if (args.include) {
2463
2580
  params.include = splitCsv(args.include);
2464
2581
  }
2465
- return runTool(client, "outlit_get_customer", params, json);
2582
+ return runTool(client, customerToolContracts.outlit_get_customer.toolName, params, json);
2466
2583
  }
2467
2584
  });
2468
2585
  });
@@ -2478,6 +2595,7 @@ var init_timeline = __esm(() => {
2478
2595
  init_auth();
2479
2596
  init_output2();
2480
2597
  init_pagination();
2598
+ init_tool_contracts();
2481
2599
  init_api();
2482
2600
  init_config();
2483
2601
  timeline_default = defineCommand2({
@@ -2492,6 +2610,8 @@ var init_timeline = __esm(() => {
2492
2610
  "",
2493
2611
  "Timeframe is used when no explicit date range is set.",
2494
2612
  "When --start-date or --end-date is provided, --timeframe is ignored.",
2613
+ `Channels: ${timelineChannels.join(", ")}`,
2614
+ `Timeframes: ${timelineTimeframes.join(", ")}`,
2495
2615
  "",
2496
2616
  "Examples:",
2497
2617
  " outlit customers timeline acme.com",
@@ -2515,7 +2635,7 @@ var init_timeline = __esm(() => {
2515
2635
  },
2516
2636
  channels: {
2517
2637
  type: "string",
2518
- description: "Comma-separated list of channels to filter (e.g. EMAIL,SLACK)"
2638
+ description: `Comma-separated list of channels to filter (${timelineChannels.join(", ")})`
2519
2639
  },
2520
2640
  "event-types": {
2521
2641
  type: "string",
@@ -2523,7 +2643,7 @@ var init_timeline = __esm(() => {
2523
2643
  },
2524
2644
  timeframe: {
2525
2645
  type: "string",
2526
- 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.`,
2527
2647
  default: "30d"
2528
2648
  },
2529
2649
  "start-date": {
@@ -2555,7 +2675,7 @@ var init_timeline = __esm(() => {
2555
2675
  params.eventTypes = splitCsv(args["event-types"]);
2556
2676
  }
2557
2677
  applyPagination(params, args, json);
2558
- return runTool(client, "outlit_get_timeline", params, json);
2678
+ return runTool(client, customerToolContracts.outlit_get_timeline.toolName, params, json);
2559
2679
  }
2560
2680
  });
2561
2681
  });
@@ -2604,6 +2724,7 @@ var init_list2 = __esm(() => {
2604
2724
  init_filters();
2605
2725
  init_output2();
2606
2726
  init_pagination();
2727
+ init_tool_contracts();
2607
2728
  init_api();
2608
2729
  init_output();
2609
2730
  list_default2 = defineCommand2({
@@ -2617,7 +2738,7 @@ var init_list2 = __esm(() => {
2617
2738
  "",
2618
2739
  "Examples:",
2619
2740
  " outlit users list # all users",
2620
- " outlit users list --journey-stage CHAMPION # champions only",
2741
+ " outlit users list --journey-stage ENGAGED # engaged users only",
2621
2742
  " outlit users list --customer-id <uuid> # users for a customer",
2622
2743
  " outlit users list --no-activity-in 30d # inactive users",
2623
2744
  " outlit users list --search alice --order-by last_activity_at",
@@ -2634,7 +2755,7 @@ var init_list2 = __esm(() => {
2634
2755
  ...traitFilterArgs,
2635
2756
  "journey-stage": {
2636
2757
  type: "string",
2637
- description: "Filter by journey stage (e.g. CHAMPION, AT_RISK, CHURNED)"
2758
+ description: `Filter by journey stage (${userJourneyStages.join(", ")})`
2638
2759
  },
2639
2760
  "customer-id": {
2640
2761
  type: "string",
@@ -2670,7 +2791,7 @@ var init_list2 = __esm(() => {
2670
2791
  }
2671
2792
  applyListFilters(params, args);
2672
2793
  applyPagination(params, args, json);
2673
- return runTool(client, "outlit_list_users", params, json, {
2794
+ return runTool(client, customerToolContracts.outlit_list_users.toolName, params, json, {
2674
2795
  spinnerMessage: "Fetching users...",
2675
2796
  table: {
2676
2797
  columns: [
@@ -2714,376 +2835,213 @@ var init_users = __esm(() => {
2714
2835
  });
2715
2836
  });
2716
2837
 
2717
- // src/lib/setup.ts
2718
- import { execFileSync as execFileSync2 } from "node:child_process";
2719
- function runMcpCliSetup(key, json, opts, exitOnError = true) {
2720
- try {
2721
- execFileSync2(opts.cliName, [
2722
- "mcp",
2723
- "add",
2724
- "--transport",
2725
- "http",
2726
- "outlit",
2727
- DEFAULT_MCP_URL,
2728
- "--header",
2729
- `Authorization: Bearer ${key}`
2730
- ], { stdio: "pipe" });
2731
- } catch (err) {
2732
- if (exitOnError) {
2733
- if (isEnoentError(err)) {
2734
- return outputError({ message: opts.notFoundMessage, code: opts.notFoundCode }, json);
2735
- }
2736
- opts.extraErrorHandler?.(err, json);
2737
- return outputError({ message: errorMessage(err, `${opts.cliName} mcp add failed`), code: "exec_error" }, json);
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;
2854
+ }
2855
+ return 0;
2856
+ }
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";
2738
2885
  }
2739
- return { success: false, error: errorMessage(err, `${opts.cliName} mcp add failed`) };
2740
2886
  }
2741
- if (exitOnError) {
2742
- if (isJsonMode(json)) {
2743
- outputResult({ success: true, agent: opts.agentId });
2744
- return { success: true };
2887
+ if (opts.bunGlobalBin) {
2888
+ const bunGlobalBin = opts.bunGlobalBin;
2889
+ if (candidatePaths.some((path) => isUnderPath2(path, bunGlobalBin))) {
2890
+ return "bun";
2745
2891
  }
2746
- console.log(`${TICK2} ${opts.successMessage}`);
2747
2892
  }
2748
- return { success: true };
2893
+ return null;
2749
2894
  }
2750
- function runMcpFileSetup(key, json, opts, exitOnError = true) {
2751
- const mcpConfig = opts.mcpConfig ?? {
2752
- url: DEFAULT_MCP_URL,
2753
- headers: { Authorization: `Bearer ${key}` }
2754
- };
2895
+ function readCommandOutput2(command, args) {
2755
2896
  try {
2756
- mergeOutlitMcpConfig(opts.configPath, opts.serversKey, mcpConfig, { json, label: opts.label });
2757
- } catch (err) {
2758
- if (!exitOnError) {
2759
- return { success: false, error: errorMessage(err, `Failed to write ${opts.label}`) };
2760
- }
2761
- throw err;
2897
+ return execFileSync3(command, args, {
2898
+ encoding: "utf8",
2899
+ stdio: ["ignore", "pipe", "ignore"]
2900
+ }).trim();
2901
+ } catch {
2902
+ return null;
2762
2903
  }
2763
- if (exitOnError) {
2764
- if (isJsonMode(json)) {
2765
- outputResult({ success: true, path: opts.configPath, agent: opts.agentId });
2766
- return { success: true };
2767
- }
2768
- console.log(`${TICK2} ${opts.successMessage}`);
2904
+ }
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
2918
+ });
2919
+ }
2920
+ function safeRealPath2(filePath) {
2921
+ try {
2922
+ return realpathSync2(filePath);
2923
+ } catch {
2924
+ return null;
2769
2925
  }
2770
- return { success: true };
2771
2926
  }
2772
- var init_setup = __esm(() => {
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(() => {
2773
2990
  init_config();
2774
- init_output();
2991
+ init_tty();
2992
+ UPDATE_CHECK_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
2775
2993
  });
2776
2994
 
2777
- // src/commands/setup/claude-code.ts
2778
- var exports_claude_code = {};
2779
- __export(exports_claude_code, {
2780
- default: () => claude_code_default,
2781
- configureSafe: () => configureSafe
2995
+ // src/commands/setup/skills.ts
2996
+ var exports_skills = {};
2997
+ __export(exports_skills, {
2998
+ runSkillsInstall: () => runSkillsInstall,
2999
+ runAgentSkillsInstall: () => runAgentSkillsInstall,
3000
+ getSkillAgentId: () => getSkillAgentId,
3001
+ detectPackageRunner: () => detectPackageRunner,
3002
+ default: () => skills_default,
3003
+ SKILLS_REPO_URL: () => SKILLS_REPO_URL
2782
3004
  });
2783
- function configureSafe(key, json) {
2784
- return runMcpCliSetup(key, json, getConfig(), false).success;
2785
- }
2786
- var getConfig = () => ({
2787
- cliName: "claude",
2788
- agentId: "claude-code",
2789
- notFoundMessage: "claude CLI not found. Install from https://claude.ai/code",
2790
- notFoundCode: "claude_not_found",
2791
- successMessage: "Outlit added to Claude Code. Restart Claude Code to apply."
2792
- }), claude_code_default;
2793
- var init_claude_code = __esm(() => {
2794
- init_dist();
2795
- init_auth();
2796
- init_output2();
2797
- init_config();
2798
- init_setup();
2799
- claude_code_default = defineCommand2({
2800
- meta: {
2801
- name: "claude-code",
2802
- description: "Register Outlit MCP server with Claude Code via `claude mcp add`."
2803
- },
2804
- args: { ...authArgs, ...outputArgs },
2805
- run({ args }) {
2806
- const json = !!args.json;
2807
- const { key } = requireCredential(args["api-key"], json);
2808
- runMcpCliSetup(key, json, getConfig());
2809
- }
2810
- });
2811
- });
2812
-
2813
- // src/commands/setup/claude-desktop.ts
2814
- var exports_claude_desktop = {};
2815
- __export(exports_claude_desktop, {
2816
- default: () => claude_desktop_default,
2817
- configureSafe: () => configureSafe2
2818
- });
2819
- function configureSafe2(key, json) {
2820
- return runMcpFileSetup(key, json, getConfig2(key), false).success;
2821
- }
2822
- var getConfig2 = (key) => {
2823
- const configPath = getClaudeDesktopConfigPath();
2824
- return {
2825
- agentId: "claude-desktop",
2826
- configPath,
2827
- serversKey: "mcpServers",
2828
- label: "Claude Desktop config",
2829
- successMessage: `Outlit added to Claude Desktop. Restart Claude Desktop to apply.
2830
- Config: ${configPath}`,
2831
- mcpConfig: { command: "outlit", args: ["mcp", "serve"], env: { OUTLIT_API_KEY: key } }
2832
- };
2833
- }, claude_desktop_default;
2834
- var init_claude_desktop = __esm(() => {
2835
- init_dist();
2836
- init_auth();
2837
- init_output2();
2838
- init_config();
2839
- init_setup();
2840
- claude_desktop_default = defineCommand2({
2841
- meta: {
2842
- name: "claude-desktop",
2843
- description: "Add Outlit MCP server to Claude Desktop config."
2844
- },
2845
- args: { ...authArgs, ...outputArgs },
2846
- run({ args }) {
2847
- const json = !!args.json;
2848
- const { key } = requireCredential(args["api-key"], json);
2849
- runMcpFileSetup(key, json, getConfig2(key));
2850
- }
2851
- });
2852
- });
2853
-
2854
- // src/commands/setup/cursor.ts
2855
- var exports_cursor = {};
2856
- __export(exports_cursor, {
2857
- default: () => cursor_default,
2858
- configureSafe: () => configureSafe3
2859
- });
2860
- import { homedir as homedir2 } from "node:os";
2861
- import { join as join3 } from "node:path";
2862
- function configureSafe3(key, json) {
2863
- return runMcpFileSetup(key, json, getConfig3(), false).success;
2864
- }
2865
- var getConfig3 = () => ({
2866
- agentId: "cursor",
2867
- configPath: join3(homedir2(), ".cursor", "mcp.json"),
2868
- serversKey: "mcpServers",
2869
- label: "cursor mcp.json",
2870
- successMessage: `Outlit added to Cursor MCP config. Restart Cursor to apply.
2871
- Config: ~/.cursor/mcp.json`
2872
- }), cursor_default;
2873
- var init_cursor = __esm(() => {
2874
- init_dist();
2875
- init_auth();
2876
- init_output2();
2877
- init_config();
2878
- init_setup();
2879
- cursor_default = defineCommand2({
2880
- meta: {
2881
- name: "cursor",
2882
- description: "Add Outlit MCP server to ~/.cursor/mcp.json."
2883
- },
2884
- args: { ...authArgs, ...outputArgs },
2885
- run({ args }) {
2886
- const json = !!args.json;
2887
- const { key } = requireCredential(args["api-key"], json);
2888
- runMcpFileSetup(key, json, getConfig3());
2889
- }
2890
- });
2891
- });
2892
-
2893
- // src/commands/setup/gemini.ts
2894
- var exports_gemini = {};
2895
- __export(exports_gemini, {
2896
- default: () => gemini_default,
2897
- configureSafe: () => configureSafe4
2898
- });
2899
- function configureSafe4(key, json) {
2900
- return runMcpCliSetup(key, json, getConfig4(), false).success;
2901
- }
2902
- var getConfig4 = () => ({
2903
- cliName: "gemini",
2904
- agentId: "gemini",
2905
- notFoundMessage: "gemini CLI not found. Install from https://github.com/google-gemini/gemini-cli",
2906
- notFoundCode: "gemini_not_found",
2907
- successMessage: "Outlit added to Gemini CLI. Restart Gemini CLI to apply."
2908
- }), gemini_default;
2909
- var init_gemini = __esm(() => {
2910
- init_dist();
2911
- init_auth();
2912
- init_output2();
2913
- init_config();
2914
- init_output();
2915
- init_setup();
2916
- gemini_default = defineCommand2({
2917
- meta: {
2918
- name: "gemini",
2919
- description: "Register Outlit MCP server with Gemini CLI via `gemini mcp add`."
2920
- },
2921
- args: { ...authArgs, ...outputArgs },
2922
- run({ args }) {
2923
- const json = !!args.json;
2924
- const { key } = requireCredential(args["api-key"], json);
2925
- runMcpCliSetup(key, json, {
2926
- ...getConfig4(),
2927
- extraErrorHandler(err, j2) {
2928
- const msg = err instanceof Error ? err.message.toLowerCase() : "";
2929
- if (msg.includes("mcp") && msg.includes("not")) {
2930
- outputError({
2931
- message: "Your Gemini CLI version does not support 'mcp add'. Run 'gemini update' or reinstall.",
2932
- code: "gemini_mcp_unsupported"
2933
- }, j2);
2934
- }
2935
- }
2936
- });
2937
- }
2938
- });
2939
- });
2940
-
2941
- // src/commands/setup/openclaw.ts
2942
- var exports_openclaw = {};
2943
- __export(exports_openclaw, {
2944
- getSkillDir: () => getSkillDir,
2945
- default: () => openclaw_default,
2946
- configureSafe: () => configureSafe5,
2947
- buildSkillContent: () => buildSkillContent
2948
- });
2949
- import { homedir as homedir3 } from "node:os";
2950
- import { join as join4 } from "node:path";
2951
- function getSkillDir() {
2952
- const home = homedir3();
2953
- return join4(home, ".openclaw", "skills", "outlit");
2954
- }
2955
- function buildSkillContent(maskedKey) {
2956
- return `---
2957
- name: outlit
2958
- description: Query customer data, revenue metrics, and analytics via the Outlit CLI.
2959
- metadata:
2960
- openclaw:
2961
- requires:
2962
- bins: ["outlit"]
2963
- env: ["OUTLIT_API_KEY"]
2964
- ---
2965
-
2966
- # Outlit Customer Intelligence
2967
-
2968
- You have access to the \`outlit\` CLI for querying customer data and analytics.
2969
- The CLI outputs structured JSON automatically in non-TTY contexts.
2970
-
2971
- ## Authentication
2972
-
2973
- Set the env var before running any command:
2974
-
2975
- export OUTLIT_API_KEY=${maskedKey}
2976
-
2977
- ## List customers
2978
-
2979
- outlit customers list --billing-status PAYING --no-activity-in 30d --order-by mrr_cents
2980
-
2981
- ## Get customer details
2982
-
2983
- outlit customers get acme.com --include users,revenue,recentTimeline
2984
-
2985
- ## Get customer activity timeline
2986
-
2987
- outlit customers timeline acme.com --channels EMAIL,SLACK --limit 50
2988
-
2989
- ## List users for a customer
2990
-
2991
- outlit users list --customer-id <uuid> --journey-stage ACTIVATED
2992
-
2993
- ## Search across customer context
2994
-
2995
- outlit search "budget concerns" --customer acme.com
2996
-
2997
- ## Get facts about a customer
2998
-
2999
- outlit facts acme.com --timeframe 90d
3000
-
3001
- ## Run custom SQL
3002
-
3003
- outlit sql "SELECT event_type, count(*) FROM events GROUP BY 1 ORDER BY 2 DESC LIMIT 10"
3004
-
3005
- ## Query with a file (for complex SQL)
3006
-
3007
- outlit sql --query-file /tmp/query.sql
3008
-
3009
- ## Discover table schemas
3010
-
3011
- outlit schema
3012
- outlit schema events
3013
-
3014
- ## Rules
3015
- - Always parse the JSON response before reporting to the user
3016
- - Convert monetary values from cents to dollars (divide by 100)
3017
- - IDs are UUIDs (e.g., "a1b2c3d4-e5f6-...")
3018
- - Never show the raw API key to the user
3019
- - Use --query-file for complex SQL to avoid shell escaping issues
3020
- - All list responses include pagination.hasMore and pagination.nextCursor
3021
- `;
3022
- }
3023
- function configureSafe5(key, json) {
3024
- const skillPath = join4(getSkillDir(), "SKILL.md");
3025
- try {
3026
- writeConfigFile(skillPath, buildSkillContent(maskKey(key)), { json, label: "SKILL.md" });
3027
- return true;
3028
- } catch {
3029
- return false;
3030
- }
3005
+ import { execFileSync as execFileSync4 } from "node:child_process";
3006
+ function getSkillAgentId(agent) {
3007
+ return skillAgentMap[agent];
3031
3008
  }
3032
- var openclaw_default;
3033
- var init_openclaw = __esm(() => {
3034
- init_dist();
3035
- init_auth();
3036
- init_output2();
3037
- init_config();
3038
- init_output();
3039
- openclaw_default = defineCommand2({
3040
- meta: {
3041
- name: "openclaw",
3042
- description: "Write Outlit skill to ~/.openclaw/skills/ for OpenClaw."
3043
- },
3044
- args: { ...authArgs, ...outputArgs },
3045
- run({ args }) {
3046
- const json = !!args.json;
3047
- const { key } = requireCredential(args["api-key"], json);
3048
- const skillPath = join4(getSkillDir(), "SKILL.md");
3049
- const content = buildSkillContent(maskKey(key));
3050
- writeConfigFile(skillPath, content, { json, label: "SKILL.md" });
3051
- if (isJsonMode(json)) {
3052
- return outputResult({ success: true, path: skillPath, agent: "openclaw" });
3053
- }
3054
- console.log(`${TICK2} Outlit skill written to ${skillPath}. OpenClaw will load it automatically.`);
3055
- }
3056
- });
3057
- });
3058
-
3059
- // src/commands/setup/skills.ts
3060
- var exports_skills = {};
3061
- __export(exports_skills, {
3062
- runSkillsInstall: () => runSkillsInstall,
3063
- detectPackageRunner: () => detectPackageRunner,
3064
- default: () => skills_default
3065
- });
3066
- import { execFileSync as execFileSync3 } from "node:child_process";
3067
3009
  function detectPackageRunner() {
3068
3010
  const whichCmd = process.platform === "win32" ? "where" : "which";
3069
3011
  for (const runner of ["npx", "bunx", "pnpx"]) {
3070
3012
  try {
3071
- execFileSync3(whichCmd, [runner], { stdio: "ignore" });
3013
+ execFileSync4(whichCmd, [runner], { stdio: "ignore" });
3072
3014
  return runner;
3073
3015
  } catch {}
3074
3016
  }
3075
3017
  return null;
3076
3018
  }
3077
- function buildRunnerArgs(runner) {
3019
+ function buildRunnerArgs(runner, opts) {
3078
3020
  const args = runner === "npx" ? ["-y"] : [];
3079
3021
  args.push("skills", "add", SKILLS_REPO_URL);
3080
- for (const name of SKILL_NAMES) {
3081
- 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");
3082
3033
  }
3083
- args.push("-y", "-g");
3084
3034
  return args;
3085
3035
  }
3086
- 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;
3087
3045
  const runner = detectPackageRunner();
3088
3046
  if (!runner) {
3089
3047
  if (exitOnError) {
@@ -3094,8 +3052,11 @@ function runSkillsInstall(json, exitOnError = true) {
3094
3052
  }
3095
3053
  return { success: false, error: "No package runner found" };
3096
3054
  }
3055
+ const isInteractiveInstall = (agents?.length ?? 0) === 0 && (skillNames?.length ?? 0) === 0 && !autoConfirm && !isJsonMode(json);
3097
3056
  try {
3098
- execFileSync3(runner, buildRunnerArgs(runner), { stdio: "pipe" });
3057
+ execFileSync4(runner, buildRunnerArgs(runner, { agents, skillNames, autoConfirm }), {
3058
+ stdio: isInteractiveInstall ? "inherit" : "pipe"
3059
+ });
3099
3060
  } catch (err) {
3100
3061
  if (exitOnError) {
3101
3062
  if (isEnoentError(err)) {
@@ -3114,210 +3075,355 @@ function runSkillsInstall(json, exitOnError = true) {
3114
3075
  }
3115
3076
  if (exitOnError) {
3116
3077
  if (isJsonMode(json)) {
3117
- outputResult({ success: true, agent: "skills", runner });
3078
+ outputResult({ success: true, agent: reportedAgent, runner });
3118
3079
  return { success: true, runner };
3119
3080
  }
3120
- 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
+ }
3121
3086
  }
3122
3087
  return { success: true, runner };
3123
3088
  }
3124
- 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;
3125
3100
  var init_skills = __esm(() => {
3126
3101
  init_dist();
3127
3102
  init_output2();
3128
3103
  init_config();
3129
3104
  init_output();
3130
- 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
+ };
3131
3114
  skills_default = defineCommand2({
3132
3115
  meta: {
3133
3116
  name: "skills",
3134
3117
  description: [
3135
- "Install Outlit agent skills for Claude Code and other AI agents.",
3118
+ "Launch the interactive Skills installer for Outlit.",
3136
3119
  "",
3137
- "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`.",
3138
3121
  "No API key required."
3139
3122
  ].join(`
3140
3123
  `)
3141
3124
  },
3142
3125
  args: { ...outputArgs },
3143
3126
  run({ args }) {
3144
- const json = !!args.json;
3145
- runSkillsInstall(json);
3127
+ runSkillsInstall({ json: !!args.json });
3146
3128
  }
3147
3129
  });
3148
3130
  });
3149
3131
 
3150
- // src/commands/setup/vscode.ts
3151
- var exports_vscode = {};
3152
- __export(exports_vscode, {
3153
- default: () => vscode_default,
3154
- configureSafe: () => configureSafe6
3132
+ // src/commands/setup/claude-code.ts
3133
+ var exports_claude_code = {};
3134
+ __export(exports_claude_code, {
3135
+ default: () => claude_code_default
3155
3136
  });
3156
- import { join as join5 } from "node:path";
3157
- function configureSafe6(key, json) {
3158
- return runMcpFileSetup(key, json, getConfig5(), false).success;
3159
- }
3160
- var getConfig5 = () => ({
3161
- agentId: "vscode",
3162
- configPath: join5(process.cwd(), ".vscode", "mcp.json"),
3163
- serversKey: "servers",
3164
- label: ".vscode/mcp.json",
3165
- successMessage: "Outlit MCP config written to .vscode/mcp.json. Restart VS Code to apply."
3166
- }), vscode_default;
3167
- var init_vscode = __esm(() => {
3137
+ var claude_code_default;
3138
+ var init_claude_code = __esm(() => {
3168
3139
  init_dist();
3169
- init_auth();
3170
3140
  init_output2();
3171
- init_config();
3172
- init_setup();
3173
- vscode_default = defineCommand2({
3141
+ init_skills();
3142
+ claude_code_default = defineCommand2({
3174
3143
  meta: {
3175
- name: "vscode",
3176
- 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."
3177
3146
  },
3178
- args: { ...authArgs, ...outputArgs },
3147
+ args: { ...outputArgs },
3179
3148
  run({ args }) {
3180
- const json = !!args.json;
3181
- const { key } = requireCredential(args["api-key"], json);
3182
- runMcpFileSetup(key, json, getConfig5());
3149
+ runAgentSkillsInstall("claude-code", !!args.json);
3150
+ }
3151
+ });
3152
+ });
3153
+
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(() => {
3161
+ init_dist();
3162
+ init_output2();
3163
+ init_skills();
3164
+ codex_default = defineCommand2({
3165
+ meta: {
3166
+ name: "codex",
3167
+ description: "Install the Outlit skill for Codex."
3168
+ },
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);
3183
3282
  }
3184
3283
  });
3185
3284
  });
3186
3285
 
3187
3286
  // src/commands/setup/index.ts
3188
- import { execFileSync as execFileSync4 } from "node:child_process";
3189
- import { existsSync as existsSync2 } from "node:fs";
3287
+ import { execFileSync as execFileSync5 } from "node:child_process";
3288
+ import { existsSync as existsSync4 } from "node:fs";
3190
3289
  import { homedir as homedir4 } from "node:os";
3191
- import { join as join6 } from "node:path";
3290
+ import { join as join5 } from "node:path";
3192
3291
  function isCommandAvailable(cmd) {
3193
3292
  try {
3194
3293
  const whichCmd = process.platform === "win32" ? "where" : "which";
3195
- execFileSync4(whichCmd, [cmd], { stdio: "ignore" });
3294
+ execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
3196
3295
  return true;
3197
3296
  } catch {
3198
3297
  return false;
3199
3298
  }
3200
3299
  }
3300
+ function getHomeDir() {
3301
+ return process.env.HOME?.trim() || homedir4();
3302
+ }
3201
3303
  function detectAgents() {
3202
- const home = homedir4();
3304
+ const home = getHomeDir();
3305
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join5(home, ".config");
3203
3306
  const detected = [];
3204
- if (existsSync2(join6(home, ".cursor")))
3205
- detected.push("cursor");
3206
3307
  if (isCommandAvailable("claude"))
3207
3308
  detected.push("claude-code");
3208
- if (existsSync2(getClaudeDesktopConfigPath()))
3209
- detected.push("claude-desktop");
3210
- if (isCommandAvailable("code") || existsSync2(join6(process.cwd(), ".vscode")))
3211
- detected.push("vscode");
3309
+ if (isCommandAvailable("codex"))
3310
+ detected.push("codex");
3212
3311
  if (isCommandAvailable("gemini"))
3213
3312
  detected.push("gemini");
3214
- if (existsSync2(join6(home, ".openclaw", "skills")))
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"))) {
3215
3320
  detected.push("openclaw");
3321
+ }
3216
3322
  return detected;
3217
3323
  }
3218
- var agentLabels, configurators, setup_default;
3219
- var init_setup2 = __esm(() => {
3324
+ var agentLabels, setupSubcommandNames, setup_default;
3325
+ var init_setup = __esm(() => {
3220
3326
  init_dist();
3221
- init_auth();
3222
3327
  init_output2();
3223
3328
  init_config();
3224
3329
  init_output();
3225
- init_claude_code();
3226
- init_claude_desktop();
3227
- init_cursor();
3228
- init_gemini();
3229
- init_openclaw();
3230
3330
  init_skills();
3231
- init_vscode();
3232
3331
  agentLabels = {
3233
- cursor: { label: "Cursor", hint: "~/.cursor/" },
3234
3332
  "claude-code": { label: "Claude Code", hint: "claude CLI found" },
3235
- "claude-desktop": { label: "Claude Desktop", hint: "config file found" },
3236
- vscode: { label: "VS Code", hint: "code CLI or .vscode/ found" },
3333
+ codex: { label: "Codex", hint: "codex CLI found" },
3237
3334
  gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
3238
- openclaw: { label: "OpenClaw", hint: "skills directory found" }
3239
- };
3240
- configurators = {
3241
- cursor: configureSafe3,
3242
- "claude-code": configureSafe,
3243
- "claude-desktop": configureSafe2,
3244
- vscode: configureSafe6,
3245
- gemini: configureSafe4,
3246
- openclaw: configureSafe5
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" }
3247
3339
  };
3340
+ setupSubcommandNames = new Set([
3341
+ "claude-code",
3342
+ "codex",
3343
+ "gemini",
3344
+ "droid",
3345
+ "opencode",
3346
+ "pi",
3347
+ "openclaw",
3348
+ "skills"
3349
+ ]);
3248
3350
  setup_default = defineCommand2({
3249
3351
  meta: {
3250
3352
  name: "setup",
3251
3353
  description: [
3252
- "Configure Outlit for AI agent tools.",
3354
+ "Install the Outlit skill for coding agents.",
3253
3355
  "",
3254
- "Without a subcommand, auto-detects installed agents and configures them all.",
3255
- "Subcommands: cursor, claude-code, claude-desktop, vscode, gemini, openclaw, skills"
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"
3256
3358
  ].join(`
3257
3359
  `)
3258
3360
  },
3259
3361
  args: {
3260
- ...authArgs,
3261
3362
  ...outputArgs,
3262
3363
  yes: {
3263
3364
  type: "boolean",
3264
- description: "Configure all detected agents without prompting."
3365
+ description: "Install for all detected coding agents without prompting."
3265
3366
  }
3266
3367
  },
3267
3368
  subCommands: {
3268
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
3269
3369
  "claude-code": () => Promise.resolve().then(() => (init_claude_code(), exports_claude_code)).then((m) => m.default),
3270
- "claude-desktop": () => Promise.resolve().then(() => (init_claude_desktop(), exports_claude_desktop)).then((m) => m.default),
3271
- 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),
3272
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),
3273
3375
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
3274
3376
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
3275
3377
  },
3276
- 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
+ }
3277
3384
  const json = !!args.json;
3278
- const credential = requireCredential(args["api-key"], json);
3279
3385
  const detected = detectAgents();
3280
3386
  if (detected.length === 0) {
3281
3387
  if (isJsonMode(json)) {
3282
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
3388
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
3283
3389
  }
3284
- console.log("No supported agent tools detected.");
3390
+ console.log("No supported coding agents detected.");
3285
3391
  return;
3286
3392
  }
3287
3393
  if (!isJsonMode(json) && !args.yes) {
3288
- console.log("Detected agents:");
3394
+ console.log("Detected coding agents:");
3289
3395
  for (const agentId of detected) {
3290
3396
  const { label, hint } = agentLabels[agentId];
3291
3397
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
3292
3398
  }
3293
3399
  console.log(`
3294
- Configuring...`);
3295
- }
3296
- const configured = [];
3297
- const failed = [];
3298
- for (const agentId of detected) {
3299
- const ok = configurators[agentId](credential.key, json);
3300
- if (ok) {
3301
- configured.push(agentId);
3302
- } else {
3303
- failed.push(agentId);
3304
- }
3305
- }
3306
- const skills = runSkillsInstall(json, false);
3307
- if (!isJsonMode(json) && !skills.success) {
3308
- console.log(`
3309
- ! Agent skills installation failed: ${skills.error ?? "unknown error"}`);
3310
- console.log(" Run `outlit setup skills` to retry.");
3400
+ Installing Outlit skill...`);
3311
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;
3312
3411
  if (isJsonMode(json)) {
3313
- return outputResult({ detected, configured, failed, skills });
3412
+ return outputResult({
3413
+ detected,
3414
+ configured,
3415
+ failed,
3416
+ runner: install.runner ?? null
3417
+ });
3314
3418
  }
3315
- if (failed.length > 0) {
3419
+ if (!install.success) {
3316
3420
  console.log(`
3317
- ${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;
3318
3424
  }
3319
3425
  console.log(`
3320
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
3426
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
3321
3427
  }
3322
3428
  });
3323
3429
  });
@@ -3325,21 +3431,16 @@ Done. ${configured.length}/${detected.length} agent(s) configured successfully.`
3325
3431
  // src/commands/doctor.ts
3326
3432
  var exports_doctor = {};
3327
3433
  __export(exports_doctor, {
3328
- default: () => doctor_default
3434
+ default: () => doctor_default,
3435
+ buildAgentChecks: () => buildAgentChecks
3329
3436
  });
3330
- import { existsSync as existsSync3 } from "node:fs";
3437
+ import { existsSync as existsSync5 } from "node:fs";
3331
3438
  import { homedir as homedir5 } from "node:os";
3332
- import { join as join7 } from "node:path";
3439
+ import { join as join6 } from "node:path";
3333
3440
  async function checkCliVersion() {
3334
3441
  const current = CLI_VERSION2;
3335
3442
  try {
3336
- const res = await fetch("https://registry.npmjs.org/@outlit%2Fcli/latest", {
3337
- signal: AbortSignal.timeout(5000)
3338
- });
3339
- if (!res.ok)
3340
- throw new Error("registry unavailable");
3341
- const data = await res.json();
3342
- const latest = data.version ?? "unknown";
3443
+ const latest = await fetchLatestCliVersion2();
3343
3444
  if (latest === current) {
3344
3445
  return { name: "CLI version", status: "pass", message: `v${current} (latest)` };
3345
3446
  }
@@ -3347,7 +3448,7 @@ async function checkCliVersion() {
3347
3448
  name: "CLI version",
3348
3449
  status: "warn",
3349
3450
  message: `v${current} installed, v${latest} available`,
3350
- detail: "Run `npm update -g @outlit/cli` to update"
3451
+ detail: `Run \`${formatUpdateCommand2()}\` to update`
3351
3452
  };
3352
3453
  } catch {
3353
3454
  return {
@@ -3434,8 +3535,42 @@ async function checkIntegrations(apiKey) {
3434
3535
  };
3435
3536
  }
3436
3537
  }
3437
- function detectAgents2() {
3438
- const detected = detectAgents();
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) {
3439
3574
  if (detected.length === 0) {
3440
3575
  return [
3441
3576
  {
@@ -3446,66 +3581,15 @@ function detectAgents2() {
3446
3581
  ];
3447
3582
  }
3448
3583
  const results = [];
3449
- const home = homedir5();
3450
3584
  for (const agentId of detected) {
3451
3585
  const meta = agentChecks[agentId];
3452
- if (agentId === "openclaw") {
3453
- const openclawBase = join7(home, ".openclaw", "skills");
3454
- const hasSkill = existsSync3(join7(openclawBase, "outlit", "SKILL.md"));
3455
- results.push({
3456
- name: meta.name,
3457
- status: hasSkill ? "pass" : "warn",
3458
- message: hasSkill ? "Installed, Outlit skill configured" : "Installed, but Outlit skill not found",
3459
- detail: hasSkill ? undefined : "Run `clawhub install outlit` to configure"
3460
- });
3461
- continue;
3462
- }
3463
- if (!meta.configCheck) {
3464
- results.push({
3465
- name: meta.name,
3466
- status: "warn",
3467
- message: "Installed, but Outlit MCP not verified",
3468
- detail: `Run \`outlit setup ${agentId}\` to configure`
3469
- });
3470
- if (agentId === "claude-code") {
3471
- const skillsDir = join7(home, ".claude", "skills");
3472
- const hasSkills = existsSync3(join7(skillsDir, "outlit-cli")) || existsSync3(join7(skillsDir, "outlit-sdk")) || existsSync3(join7(skillsDir, "outlit-mcp"));
3473
- results.push({
3474
- name: "Agent Skills",
3475
- status: hasSkills ? "pass" : "warn",
3476
- message: hasSkills ? "Outlit agent skills installed" : "Outlit agent skills not found",
3477
- detail: hasSkills ? undefined : "Run `outlit setup skills` to install deeper AI agent context"
3478
- });
3479
- }
3480
- continue;
3481
- }
3482
- const { path, key } = meta.configCheck;
3483
- if (!existsSync3(path)) {
3484
- results.push({
3485
- name: meta.name,
3486
- status: "warn",
3487
- message: "Installed, but config file not found",
3488
- detail: `Run \`outlit setup ${agentId}\` to configure`
3489
- });
3490
- continue;
3491
- }
3492
- try {
3493
- const config = readJsonConfig(path);
3494
- const configured = !!config[key]?.outlit;
3495
- results.push({
3496
- name: meta.name,
3497
- status: configured ? "pass" : "warn",
3498
- message: configured ? "Installed, Outlit MCP configured" : "Installed, but Outlit MCP not configured",
3499
- detail: configured ? undefined : `Run \`outlit setup ${agentId}\` to configure`
3500
- });
3501
- } catch {
3502
- results.push({
3503
- name: meta.name,
3504
- status: "warn",
3505
- message: "Installed, but config file is malformed",
3506
- detail: `Check ${path} for JSON syntax errors`
3507
- });
3508
- }
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
+ });
3509
3593
  }
3510
3594
  return results;
3511
3595
  }
@@ -3540,7 +3624,8 @@ var init_doctor = __esm(() => {
3540
3624
  init_config();
3541
3625
  init_output();
3542
3626
  init_tty();
3543
- init_setup2();
3627
+ init_update();
3628
+ init_setup();
3544
3629
  FAIL_SYMBOL2 = isUnicodeSupported ? String.fromCodePoint(10007) : "x";
3545
3630
  STATUS_ICONS = {
3546
3631
  pass: TICK2,
@@ -3557,73 +3642,335 @@ var init_doctor = __esm(() => {
3557
3642
  " 1. CLI version -- compares against npm registry",
3558
3643
  " 2. API key -- checks presence and format (ok_ prefix)",
3559
3644
  " 3. API validation -- makes a live test call to verify the key works",
3560
- " 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",
3561
3646
  "",
3562
3647
  "Exit code: 0 if all checks pass or warn, 1 if any check fails.",
3563
3648
  "",
3564
- "JSON output format:",
3565
- ' { "ok": boolean, "checks": [{ "name", "status", "message", "detail?" }] }',
3649
+ "JSON output format:",
3650
+ ' { "ok": boolean, "checks": [{ "name", "status", "message", "detail?" }] }',
3651
+ "",
3652
+ "Examples:",
3653
+ " outlit doctor",
3654
+ " outlit doctor --json",
3655
+ ` outlit doctor --json | jq '.checks[] | select(.status == "fail")'`,
3656
+ "",
3657
+ "For AI agents: use outlit doctor --json to get structured diagnostics."
3658
+ ].join(`
3659
+ `)
3660
+ },
3661
+ args: { ...authArgs, ...outputArgs },
3662
+ async run({ args }) {
3663
+ const json = !!args.json;
3664
+ const checks = [];
3665
+ checks.push(await checkCliVersion());
3666
+ const credential = resolveApiKey(args["api-key"]);
3667
+ checks.push(checkApiKeyPresence(credential));
3668
+ if (credential) {
3669
+ const apiCheck = await validateApiKey(credential.key);
3670
+ checks.push(apiCheck);
3671
+ if (apiCheck.status !== "fail") {
3672
+ checks.push(await checkIntegrations(credential.key));
3673
+ }
3674
+ } else {
3675
+ checks.push({
3676
+ name: "API validation",
3677
+ status: "fail",
3678
+ message: "Skipped -- no API key found"
3679
+ });
3680
+ }
3681
+ checks.push(...buildAgentChecks());
3682
+ const hasFail = checks.some((c) => c.status === "fail");
3683
+ if (isJsonMode(json)) {
3684
+ outputResult({ ok: !hasFail, checks });
3685
+ } else {
3686
+ printChecks(checks);
3687
+ }
3688
+ if (hasFail)
3689
+ process.exit(1);
3690
+ }
3691
+ });
3692
+ agentChecks = {
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"
3700
+ },
3701
+ gemini: {
3702
+ name: "Gemini CLI",
3703
+ missingDetail: "Run `outlit setup gemini` to install the Outlit skill"
3704
+ },
3705
+ droid: {
3706
+ name: "Droid",
3707
+ missingDetail: "Run `outlit setup droid` to install the Outlit skill"
3708
+ },
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
+ }
3721
+ };
3722
+ });
3723
+
3724
+ // src/commands/upgrade.ts
3725
+ var exports_upgrade = {};
3726
+ __export(exports_upgrade, {
3727
+ default: () => upgrade_default
3728
+ });
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(() => {
3800
+ init_dist();
3801
+ init_auth();
3802
+ init_output2();
3803
+ init_pagination();
3804
+ init_tool_contracts();
3805
+ init_api();
3806
+ init_config();
3807
+ init_output();
3808
+ list_default3 = defineCommand2({
3809
+ meta: {
3810
+ name: "list",
3811
+ description: [
3812
+ "List structured facts for a customer.",
3813
+ "",
3814
+ "Filter by fact status, source type, or occurrence date range.",
3815
+ "",
3816
+ "Examples:",
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(", ")}`,
3824
+ "",
3825
+ AGENT_JSON_HINT
3826
+ ].join(`
3827
+ `)
3828
+ },
3829
+ args: {
3830
+ ...authArgs,
3831
+ ...outputArgs,
3832
+ ...paginationArgs,
3833
+ customer: {
3834
+ type: "positional",
3835
+ description: "Customer UUID or domain to retrieve facts for",
3836
+ required: true
3837
+ },
3838
+ status: {
3839
+ type: "string",
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"
3853
+ }
3854
+ },
3855
+ async run({ args }) {
3856
+ const json = !!args.json;
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.",
3566
3937
  "",
3567
3938
  "Examples:",
3568
- " outlit doctor",
3569
- " outlit doctor --json",
3570
- ` outlit doctor --json | jq '.checks[] | select(.status == "fail")'`,
3939
+ " outlit facts get --fact-id fact_123",
3940
+ " outlit facts get --fact-id fact_123 --include evidence",
3571
3941
  "",
3572
- "For AI agents: use outlit doctor --json to get structured diagnostics."
3942
+ AGENT_JSON_HINT
3573
3943
  ].join(`
3574
3944
  `)
3575
3945
  },
3576
- args: { ...authArgs, ...outputArgs },
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
+ },
3577
3959
  async run({ args }) {
3578
3960
  const json = !!args.json;
3579
- const checks = [];
3580
- checks.push(await checkCliVersion());
3581
- const credential = resolveApiKey(args["api-key"]);
3582
- checks.push(checkApiKeyPresence(credential));
3583
- if (credential) {
3584
- const apiCheck = await validateApiKey(credential.key);
3585
- checks.push(apiCheck);
3586
- if (apiCheck.status !== "fail") {
3587
- checks.push(await checkIntegrations(credential.key));
3588
- }
3589
- } else {
3590
- checks.push({
3591
- name: "API validation",
3592
- status: "fail",
3593
- message: "Skipped -- no API key found"
3594
- });
3595
- }
3596
- checks.push(...detectAgents2());
3597
- const hasFail = checks.some((c) => c.status === "fail");
3598
- if (isJsonMode(json)) {
3599
- outputResult({ ok: !hasFail, checks });
3600
- } else {
3601
- printChecks(checks);
3602
- }
3603
- if (hasFail)
3604
- process.exit(1);
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);
3605
3969
  }
3606
3970
  });
3607
- agentChecks = {
3608
- cursor: {
3609
- name: "Cursor",
3610
- configCheck: { path: join7(homedir5(), ".cursor", "mcp.json"), key: "mcpServers" }
3611
- },
3612
- "claude-code": { name: "Claude Code" },
3613
- "claude-desktop": {
3614
- name: "Claude Desktop",
3615
- configCheck: { path: getClaudeDesktopConfigPath(), key: "mcpServers" }
3616
- },
3617
- vscode: {
3618
- name: "VS Code",
3619
- configCheck: { path: join7(process.cwd(), ".vscode", "mcp.json"), key: "servers" }
3620
- },
3621
- gemini: { name: "Gemini CLI" },
3622
- openclaw: { name: "OpenClaw" }
3623
- };
3624
3971
  });
3625
3972
 
3626
- // src/commands/facts.ts
3973
+ // src/commands/facts/index.ts
3627
3974
  var exports_facts = {};
3628
3975
  __export(exports_facts, {
3629
3976
  default: () => facts_default
@@ -3631,23 +3978,52 @@ __export(exports_facts, {
3631
3978
  var facts_default;
3632
3979
  var init_facts = __esm(() => {
3633
3980
  init_dist();
3634
- init_auth();
3635
3981
  init_output2();
3636
- init_pagination();
3637
- init_api();
3638
3982
  facts_default = defineCommand2({
3639
3983
  meta: {
3640
3984
  name: "facts",
3641
3985
  description: [
3642
- "Retrieve facts for a customer.",
3986
+ "Query structured customer facts.",
3643
3987
  "",
3644
- "Returns a list of facts (signals, events, and derived insights) for the",
3645
- "specified customer within the given timeframe.",
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.",
3646
4021
  "",
3647
4022
  "Examples:",
3648
- " outlit facts acme.com",
3649
- " outlit facts acme.com --timeframe 90d",
3650
- " outlit facts acme.com --limit 50 --json",
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(", ")}`,
3651
4027
  "",
3652
4028
  AGENT_JSON_HINT
3653
4029
  ].join(`
@@ -3656,27 +4032,58 @@ var init_facts = __esm(() => {
3656
4032
  args: {
3657
4033
  ...authArgs,
3658
4034
  ...outputArgs,
3659
- ...paginationArgs,
3660
- customer: {
3661
- type: "positional",
3662
- description: "Customer UUID or domain to retrieve facts for",
4035
+ "source-type": {
4036
+ type: "string",
4037
+ description: "Canonical source type",
3663
4038
  required: true
3664
4039
  },
3665
- timeframe: {
4040
+ "source-id": {
3666
4041
  type: "string",
3667
- description: "Lookback window (e.g. 7d, 30d, 90d). Default: 30d.",
3668
- default: "30d"
4042
+ description: "Exact source id",
4043
+ required: true
3669
4044
  }
3670
4045
  },
3671
4046
  async run({ args }) {
3672
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
+ }
3673
4054
  const client = await getClientOrExit(args["api-key"], json);
3674
- const params = {
3675
- customer: args.customer,
3676
- timeframe: args.timeframe
3677
- };
3678
- applyPagination(params, args, json);
3679
- return runTool(client, "outlit_get_facts", params, 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)
3680
4087
  }
3681
4088
  });
3682
4089
  });
@@ -3691,25 +4098,25 @@ var init_search = __esm(() => {
3691
4098
  init_dist();
3692
4099
  init_auth();
3693
4100
  init_output2();
4101
+ init_tool_contracts();
3694
4102
  init_api();
4103
+ init_config();
3695
4104
  init_output();
3696
4105
  search_default = defineCommand2({
3697
4106
  meta: {
3698
4107
  name: "search",
3699
4108
  description: [
3700
- "Search customer context using natural language or direct source lookup.",
4109
+ "Search customer context using natural language.",
3701
4110
  "",
3702
- "Performs a semantic search over customer facts, emails, and transcripts.",
4111
+ "Performs a semantic search over grouped source and fact results.",
3703
4112
  "Optionally scope to a specific customer with --customer.",
3704
- "Use --source-type and --source-id for direct source lookup (query becomes optional).",
3705
4113
  "",
3706
4114
  "Examples:",
3707
4115
  " outlit search 'pricing objections last quarter'",
3708
4116
  " outlit search 'churn risk signals' --customer acme.com",
3709
4117
  " outlit search 'expansion opportunities' --top-k 50 --json",
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",
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",
3713
4120
  "",
3714
4121
  AGENT_JSON_HINT
3715
4122
  ].join(`
@@ -3720,8 +4127,8 @@ var init_search = __esm(() => {
3720
4127
  ...outputArgs,
3721
4128
  query: {
3722
4129
  type: "positional",
3723
- description: "Natural language search query. Optional when --source-type and --source-id are provided.",
3724
- required: false
4130
+ description: "Natural language search query",
4131
+ required: true
3725
4132
  },
3726
4133
  customer: {
3727
4134
  type: "string",
@@ -3729,75 +4136,51 @@ var init_search = __esm(() => {
3729
4136
  },
3730
4137
  "top-k": {
3731
4138
  type: "string",
3732
- description: "Maximum number of results to return (1–50). Default: 20.",
3733
- default: "20"
4139
+ description: "Maximum number of results to return (1–50). Default: 20."
3734
4140
  },
3735
4141
  after: {
3736
4142
  type: "string",
3737
- description: "Filter to events occurring after this date (ISO 8601, e.g. 2025-01-01)"
4143
+ description: "Filter to events occurring after this datetime (ISO 8601, e.g. 2025-01-01T00:00:00Z)"
3738
4144
  },
3739
4145
  before: {
3740
4146
  type: "string",
3741
- description: "Filter to events occurring before this date (ISO 8601, e.g. 2025-03-31)"
4147
+ description: "Filter to events occurring before this datetime (ISO 8601, e.g. 2025-03-31T23:59:59Z)"
3742
4148
  },
3743
4149
  "source-types": {
3744
4150
  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)"
4151
+ description: `Comma-separated generic source type filter (${customerSourceTypes.join(", ")})`
3754
4152
  }
3755
4153
  },
3756
4154
  async run({ args }) {
3757
4155
  const json = !!args.json;
3758
- const topK = Number(args["top-k"]);
3759
- if (!Number.isFinite(topK) || topK < 1 || topK > 50) {
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)) {
3760
4158
  return outputError({ message: "--top-k must be an integer between 1 and 50", code: "invalid_input" }, json);
3761
4159
  }
3762
- const sourceType = args["source-type"];
3763
- const sourceId = args["source-id"];
3764
- const sourceTypes = args["source-types"];
3765
- if (sourceType && !sourceId || !sourceType && sourceId) {
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) {
3766
4163
  return outputError({
3767
- message: "--source-type and --source-id must be provided together",
4164
+ message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
3768
4165
  code: "invalid_input"
3769
4166
  }, json);
3770
4167
  }
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) {
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) {
3778
4177
  return outputError({
3779
- message: "--source-types cannot be combined with --source-type/--source-id",
4178
+ message: resolved.message,
3780
4179
  code: "invalid_input"
3781
4180
  }, json);
3782
4181
  }
3783
4182
  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;
3788
- if (args.customer)
3789
- params.customer = args.customer;
3790
- if (args.after)
3791
- params.occurredAfter = args.after;
3792
- if (args.before)
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;
3800
- return runTool(client, "outlit_search_customer_context", params, json);
4183
+ return runTool(client, customerToolContracts.outlit_search_customer_context.toolName, resolved.request, json);
3801
4184
  }
3802
4185
  });
3803
4186
  });
@@ -3807,12 +4190,13 @@ var exports_sql = {};
3807
4190
  __export(exports_sql, {
3808
4191
  default: () => sql_default
3809
4192
  });
3810
- import { readFileSync as readFileSync2 } from "node:fs";
4193
+ import { readFileSync as readFileSync4 } from "node:fs";
3811
4194
  var sql_default;
3812
4195
  var init_sql = __esm(() => {
3813
4196
  init_dist();
3814
4197
  init_auth();
3815
4198
  init_output2();
4199
+ init_tool_contracts();
3816
4200
  init_api();
3817
4201
  init_output();
3818
4202
  sql_default = defineCommand2({
@@ -3824,7 +4208,7 @@ var init_sql = __esm(() => {
3824
4208
  "Provide the query as a positional argument or via --query-file.",
3825
4209
  "When both are provided, --query-file takes precedence.",
3826
4210
  "",
3827
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4211
+ `Available tables: ${schemaTables.join(", ")}`,
3828
4212
  "",
3829
4213
  "Examples:",
3830
4214
  " outlit sql 'SELECT * FROM events LIMIT 10'",
@@ -3859,7 +4243,7 @@ var init_sql = __esm(() => {
3859
4243
  let sql;
3860
4244
  if (args["query-file"]) {
3861
4245
  try {
3862
- sql = readFileSync2(args["query-file"], "utf-8");
4246
+ sql = readFileSync4(args["query-file"], "utf-8");
3863
4247
  } catch (err) {
3864
4248
  return outputError({
3865
4249
  message: `Cannot read file: ${errorMessage(err, "unknown error")}`,
@@ -3875,7 +4259,7 @@ var init_sql = __esm(() => {
3875
4259
  if (!Number.isFinite(limit) || limit <= 0) {
3876
4260
  return outputError({ message: "--limit must be a positive number", code: "invalid_input" }, json);
3877
4261
  }
3878
- return runTool(client, "outlit_query", { sql, limit }, json);
4262
+ return runTool(client, customerToolContracts.outlit_query.toolName, { sql, limit }, json);
3879
4263
  }
3880
4264
  });
3881
4265
  });
@@ -3890,6 +4274,7 @@ var init_schema = __esm(() => {
3890
4274
  init_dist();
3891
4275
  init_auth();
3892
4276
  init_output2();
4277
+ init_tool_contracts();
3893
4278
  init_api();
3894
4279
  schema_default = defineCommand2({
3895
4280
  meta: {
@@ -3900,7 +4285,7 @@ var init_schema = __esm(() => {
3900
4285
  "Without a table name, returns the full schema for all tables.",
3901
4286
  "With a table name, returns detailed column info for that table.",
3902
4287
  "",
3903
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4288
+ `Available tables: ${schemaTables.join(", ")}`,
3904
4289
  "",
3905
4290
  "Examples:",
3906
4291
  " outlit schema",
@@ -3916,7 +4301,7 @@ var init_schema = __esm(() => {
3916
4301
  ...outputArgs,
3917
4302
  table: {
3918
4303
  type: "positional",
3919
- description: "Table to describe (events, customer_dimensions, user_dimensions, mrr_snapshots). Optional.",
4304
+ description: `Table to describe (${schemaTables.join(", ")}). Optional.`,
3920
4305
  required: false
3921
4306
  }
3922
4307
  },
@@ -3926,7 +4311,7 @@ var init_schema = __esm(() => {
3926
4311
  const params = {};
3927
4312
  if (args.table)
3928
4313
  params.table = args.table;
3929
- return runTool(client, "outlit_schema", params, json);
4314
+ return runTool(client, customerToolContracts.outlit_schema.toolName, params, json);
3930
4315
  }
3931
4316
  });
3932
4317
  });
@@ -4022,17 +4407,17 @@ var init_providers = __esm(() => {
4022
4407
  });
4023
4408
 
4024
4409
  // src/commands/integrations/list.ts
4025
- var exports_list3 = {};
4026
- __export(exports_list3, {
4027
- default: () => list_default3
4410
+ var exports_list4 = {};
4411
+ __export(exports_list4, {
4412
+ default: () => list_default4
4028
4413
  });
4029
- var list_default3;
4030
- var init_list3 = __esm(() => {
4414
+ var list_default4;
4415
+ var init_list4 = __esm(() => {
4031
4416
  init_dist();
4032
4417
  init_auth();
4033
4418
  init_output2();
4034
4419
  init_api();
4035
- list_default3 = defineCommand2({
4420
+ list_default4 = defineCommand2({
4036
4421
  meta: {
4037
4422
  name: "list",
4038
4423
  description: [
@@ -4486,7 +4871,7 @@ var init_integrations = __esm(() => {
4486
4871
  `)
4487
4872
  },
4488
4873
  subCommands: {
4489
- list: () => Promise.resolve().then(() => (init_list3(), exports_list3)).then((m) => m.default),
4874
+ list: () => Promise.resolve().then(() => (init_list4(), exports_list4)).then((m) => m.default),
4490
4875
  add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((m) => m.default),
4491
4876
  remove: () => Promise.resolve().then(() => (init_remove(), exports_remove)).then((m) => m.default),
4492
4877
  status: () => Promise.resolve().then(() => (init_status2(), exports_status2)).then((m) => m.default)
@@ -4741,9 +5126,7 @@ var init_completions = __esm(() => {
4741
5126
  { name: "--billing-status", desc: "Filter by billing status" },
4742
5127
  { name: "--mrr-above", desc: "MRR above threshold (cents)" },
4743
5128
  { name: "--mrr-below", desc: "MRR below threshold (cents)" },
4744
- { name: "--search", desc: "Search name or domain" },
4745
- { name: "--status", desc: "Customer status filter" },
4746
- { name: "--type", desc: "Customer type filter" }
5129
+ { name: "--search", desc: "Search name or domain" }
4747
5130
  ]
4748
5131
  },
4749
5132
  {
@@ -4790,7 +5173,43 @@ var init_completions = __esm(() => {
4790
5173
  {
4791
5174
  name: "facts",
4792
5175
  desc: "Get customer facts",
4793
- 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
+ ]
4794
5213
  },
4795
5214
  {
4796
5215
  name: "search",
@@ -4800,7 +5219,8 @@ var init_completions = __esm(() => {
4800
5219
  { name: "--customer", desc: "Scope to customer (UUID or domain)" },
4801
5220
  { name: "--top-k", desc: "Max results" },
4802
5221
  { name: "--after", desc: "Events after date (ISO 8601)" },
4803
- { 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" }
4804
5224
  ]
4805
5225
  },
4806
5226
  {
@@ -4837,17 +5257,20 @@ var init_completions = __esm(() => {
4837
5257
  },
4838
5258
  {
4839
5259
  name: "setup",
4840
- desc: "Configure AI agent tools",
4841
- flags: [...COMMON, { name: "--yes", desc: "Skip prompts" }],
5260
+ desc: "Install Outlit skills for coding agents",
5261
+ flags: [JSON_F, { name: "--yes", desc: "Skip prompts" }],
4842
5262
  subs: [
4843
- { name: "cursor", desc: "Configure Cursor", flags: [...COMMON] },
4844
- { name: "claude-code", desc: "Configure Claude Code", flags: [...COMMON] },
4845
- { name: "claude-desktop", desc: "Configure Claude Desktop", flags: [...COMMON] },
4846
- { name: "vscode", desc: "Configure VS Code", flags: [...COMMON] },
4847
- { name: "gemini", desc: "Configure Gemini CLI", flags: [...COMMON] },
4848
- { 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] }
4849
5271
  ]
4850
5272
  },
5273
+ { name: "upgrade", desc: "Upgrade the CLI", flags: [] },
4851
5274
  { name: "doctor", desc: "Diagnose environment", flags: [...COMMON] },
4852
5275
  { name: "completions", desc: "Generate shell completions", flags: [] }
4853
5276
  ];
@@ -4900,142 +5323,149 @@ var init_completions = __esm(() => {
4900
5323
  var exports_setup = {};
4901
5324
  __export(exports_setup, {
4902
5325
  isCommandAvailable: () => isCommandAvailable2,
4903
- detectAgents: () => detectAgents3,
5326
+ detectAgents: () => detectAgents2,
4904
5327
  default: () => setup_default2
4905
5328
  });
4906
- import { execFileSync as execFileSync5 } from "node:child_process";
4907
- import { existsSync as existsSync4 } from "node:fs";
5329
+ import { execFileSync as execFileSync6 } from "node:child_process";
5330
+ import { existsSync as existsSync6 } from "node:fs";
4908
5331
  import { homedir as homedir6 } from "node:os";
4909
- import { join as join8 } from "node:path";
5332
+ import { join as join7 } from "node:path";
4910
5333
  function isCommandAvailable2(cmd) {
4911
5334
  try {
4912
5335
  const whichCmd = process.platform === "win32" ? "where" : "which";
4913
- execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
5336
+ execFileSync6(whichCmd, [cmd], { stdio: "ignore" });
4914
5337
  return true;
4915
5338
  } catch {
4916
5339
  return false;
4917
5340
  }
4918
5341
  }
4919
- function detectAgents3() {
4920
- 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");
4921
5348
  const detected = [];
4922
- if (existsSync4(join8(home, ".cursor")))
4923
- detected.push("cursor");
4924
5349
  if (isCommandAvailable2("claude"))
4925
5350
  detected.push("claude-code");
4926
- if (existsSync4(getClaudeDesktopConfigPath()))
4927
- detected.push("claude-desktop");
4928
- if (isCommandAvailable2("code") || existsSync4(join8(process.cwd(), ".vscode")))
4929
- detected.push("vscode");
5351
+ if (isCommandAvailable2("codex"))
5352
+ detected.push("codex");
4930
5353
  if (isCommandAvailable2("gemini"))
4931
5354
  detected.push("gemini");
4932
- if (existsSync4(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"))) {
4933
5362
  detected.push("openclaw");
5363
+ }
4934
5364
  return detected;
4935
5365
  }
4936
- var agentLabels2, configurators2, setup_default2;
4937
- var init_setup3 = __esm(() => {
5366
+ var agentLabels2, setupSubcommandNames2, setup_default2;
5367
+ var init_setup2 = __esm(() => {
4938
5368
  init_dist();
4939
- init_auth();
4940
5369
  init_output2();
4941
5370
  init_config();
4942
5371
  init_output();
4943
- init_claude_code();
4944
- init_claude_desktop();
4945
- init_cursor();
4946
- init_gemini();
4947
- init_openclaw();
4948
5372
  init_skills();
4949
- init_vscode();
4950
5373
  agentLabels2 = {
4951
- cursor: { label: "Cursor", hint: "~/.cursor/" },
4952
5374
  "claude-code": { label: "Claude Code", hint: "claude CLI found" },
4953
- "claude-desktop": { label: "Claude Desktop", hint: "config file found" },
4954
- vscode: { label: "VS Code", hint: "code CLI or .vscode/ found" },
5375
+ codex: { label: "Codex", hint: "codex CLI found" },
4955
5376
  gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
4956
- openclaw: { label: "OpenClaw", hint: "skills directory found" }
4957
- };
4958
- configurators2 = {
4959
- cursor: configureSafe3,
4960
- "claude-code": configureSafe,
4961
- "claude-desktop": configureSafe2,
4962
- vscode: configureSafe6,
4963
- gemini: configureSafe4,
4964
- 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" }
4965
5381
  };
5382
+ setupSubcommandNames2 = new Set([
5383
+ "claude-code",
5384
+ "codex",
5385
+ "gemini",
5386
+ "droid",
5387
+ "opencode",
5388
+ "pi",
5389
+ "openclaw",
5390
+ "skills"
5391
+ ]);
4966
5392
  setup_default2 = defineCommand2({
4967
5393
  meta: {
4968
5394
  name: "setup",
4969
5395
  description: [
4970
- "Configure Outlit for AI agent tools.",
5396
+ "Install the Outlit skill for coding agents.",
4971
5397
  "",
4972
- "Without a subcommand, auto-detects installed agents and configures them all.",
4973
- "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"
4974
5400
  ].join(`
4975
5401
  `)
4976
5402
  },
4977
5403
  args: {
4978
- ...authArgs,
4979
5404
  ...outputArgs,
4980
5405
  yes: {
4981
5406
  type: "boolean",
4982
- description: "Configure all detected agents without prompting."
5407
+ description: "Install for all detected coding agents without prompting."
4983
5408
  }
4984
5409
  },
4985
5410
  subCommands: {
4986
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
4987
5411
  "claude-code": () => Promise.resolve().then(() => (init_claude_code(), exports_claude_code)).then((m) => m.default),
4988
- "claude-desktop": () => Promise.resolve().then(() => (init_claude_desktop(), exports_claude_desktop)).then((m) => m.default),
4989
- 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),
4990
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),
4991
5417
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
4992
5418
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
4993
5419
  },
4994
- 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
+ }
4995
5426
  const json = !!args.json;
4996
- const credential = requireCredential(args["api-key"], json);
4997
- const detected = detectAgents3();
5427
+ const detected = detectAgents2();
4998
5428
  if (detected.length === 0) {
4999
5429
  if (isJsonMode(json)) {
5000
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
5430
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
5001
5431
  }
5002
- console.log("No supported agent tools detected.");
5432
+ console.log("No supported coding agents detected.");
5003
5433
  return;
5004
5434
  }
5005
5435
  if (!isJsonMode(json) && !args.yes) {
5006
- console.log("Detected agents:");
5436
+ console.log("Detected coding agents:");
5007
5437
  for (const agentId of detected) {
5008
5438
  const { label, hint } = agentLabels2[agentId];
5009
5439
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
5010
5440
  }
5011
5441
  console.log(`
5012
- Configuring...`);
5013
- }
5014
- const configured = [];
5015
- const failed = [];
5016
- for (const agentId of detected) {
5017
- const ok = configurators2[agentId](credential.key, json);
5018
- if (ok) {
5019
- configured.push(agentId);
5020
- } else {
5021
- failed.push(agentId);
5022
- }
5023
- }
5024
- const skills = runSkillsInstall(json, false);
5025
- if (!isJsonMode(json) && !skills.success) {
5026
- console.log(`
5027
- ! Agent skills installation failed: ${skills.error ?? "unknown error"}`);
5028
- console.log(" Run `outlit setup skills` to retry.");
5442
+ Installing Outlit skill...`);
5029
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;
5030
5453
  if (isJsonMode(json)) {
5031
- return outputResult({ detected, configured, failed, skills });
5454
+ return outputResult({
5455
+ detected,
5456
+ configured,
5457
+ failed,
5458
+ runner: install.runner ?? null
5459
+ });
5032
5460
  }
5033
- if (failed.length > 0) {
5461
+ if (!install.success) {
5034
5462
  console.log(`
5035
- ${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;
5036
5466
  }
5037
5467
  console.log(`
5038
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
5468
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
5039
5469
  }
5040
5470
  });
5041
5471
  });
@@ -5392,11 +5822,226 @@ init_tty();
5392
5822
  var CLI_VERSION = package_default.version;
5393
5823
  var TICK = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
5394
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
+
5395
6035
  // src/cli.ts
5396
6036
  if (process.argv.includes("-v")) {
5397
6037
  console.log(CLI_VERSION);
5398
6038
  process.exit(0);
5399
6039
  }
6040
+ if (process.argv.includes(INTERNAL_UPDATE_FLAG)) {
6041
+ await runInternalUpdateCheck();
6042
+ process.exit(0);
6043
+ }
6044
+ initializeUpdateNotifier();
5400
6045
  var main = defineCommand({
5401
6046
  meta: {
5402
6047
  name: "outlit",
@@ -5408,8 +6053,10 @@ Usage examples:
5408
6053
  outlit customers get acme.com --include users,revenue
5409
6054
  outlit customers timeline acme.com --timeframe 90d
5410
6055
  outlit users list --journey-stage CHAMPION
5411
- outlit facts acme.com --timeframe 90d
5412
- 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
5413
6060
  outlit sql 'SELECT * FROM events LIMIT 10'
5414
6061
  outlit schema events
5415
6062
  outlit doctor --json
@@ -5421,13 +6068,15 @@ For AI agents: commands auto-output JSON when stdout is piped. No --json flag ne
5421
6068
  customers: () => Promise.resolve().then(() => (init_customers(), exports_customers)).then((m) => m.default),
5422
6069
  users: () => Promise.resolve().then(() => (init_users(), exports_users)).then((m) => m.default),
5423
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),
5424
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),
5425
6074
  search: () => Promise.resolve().then(() => (init_search(), exports_search)).then((m) => m.default),
5426
6075
  sql: () => Promise.resolve().then(() => (init_sql(), exports_sql)).then((m) => m.default),
5427
6076
  schema: () => Promise.resolve().then(() => (init_schema(), exports_schema)).then((m) => m.default),
5428
6077
  integrations: () => Promise.resolve().then(() => (init_integrations(), exports_integrations)).then((m) => m.default),
5429
6078
  completions: () => Promise.resolve().then(() => (init_completions(), exports_completions)).then((m) => m.default),
5430
- 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)
5431
6080
  }
5432
6081
  });
5433
6082
  runMain(main);