@outlit/cli 1.4.1 → 1.6.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 +2188 -922
  2. package/package.json +4 -3
package/dist/cli.js CHANGED
@@ -104,7 +104,7 @@ var package_default;
104
104
  var init_package = __esm(() => {
105
105
  package_default = {
106
106
  name: "@outlit/cli",
107
- version: "1.4.1",
107
+ version: "1.6.0",
108
108
  description: "CLI for Outlit customer intelligence platform",
109
109
  license: "Apache-2.0",
110
110
  repository: {
@@ -134,8 +134,9 @@ var init_package = __esm(() => {
134
134
  typecheck: "tsc --noEmit"
135
135
  },
136
136
  dependencies: {
137
- citty: "^0.2.1",
138
- "@clack/prompts": "^1.0.1"
137
+ "@clack/prompts": "^1.0.1",
138
+ "@outlit/tools": "^0.1.0",
139
+ citty: "^0.2.1"
139
140
  },
140
141
  devDependencies: {
141
142
  typescript: "^5.9.3",
@@ -222,6 +223,70 @@ var init_output = __esm(() => {
222
223
  init_tty();
223
224
  });
224
225
 
226
+ // src/lib/config.ts
227
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
228
+ import { homedir } from "node:os";
229
+ import { dirname, join } from "node:path";
230
+ function isEnoentError(err) {
231
+ return err instanceof Error && err.code === "ENOENT";
232
+ }
233
+ function splitCsv(value) {
234
+ return value.split(",").map((s) => s.trim());
235
+ }
236
+ function getConfigDir() {
237
+ if (process.platform === "win32") {
238
+ return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "outlit");
239
+ }
240
+ return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "outlit");
241
+ }
242
+ function resolveApiKey(flagValue) {
243
+ if (flagValue)
244
+ return { key: flagValue, source: "flag" };
245
+ const envKey = process.env.OUTLIT_API_KEY;
246
+ if (envKey)
247
+ return { key: envKey, source: "env" };
248
+ const credPath = join(getConfigDir(), "credentials.json");
249
+ if (existsSync(credPath)) {
250
+ try {
251
+ const raw = readFileSync(credPath, "utf-8");
252
+ const config = JSON.parse(raw);
253
+ if (config.apiKey)
254
+ return { key: config.apiKey, source: "config" };
255
+ } catch {}
256
+ }
257
+ return null;
258
+ }
259
+ function maskKey(key) {
260
+ if (key.length <= 9)
261
+ return key;
262
+ return `${key.slice(0, 5)}...${key.slice(-4)}`;
263
+ }
264
+ function requireCredential(flagApiKey, json) {
265
+ const credential = resolveApiKey(flagApiKey);
266
+ if (!credential) {
267
+ return outputError({
268
+ message: "Not authenticated. Run `outlit auth login` or pass --api-key.",
269
+ code: "not_authenticated"
270
+ }, json);
271
+ }
272
+ return credential;
273
+ }
274
+ function storeApiKey(apiKey) {
275
+ const configDir = getConfigDir();
276
+ mkdirSync(configDir, { recursive: true, mode: 448 });
277
+ const credPath = join(configDir, "credentials.json");
278
+ writeFileSync(credPath, JSON.stringify({ apiKey }, null, 2), { mode: 384 });
279
+ return credPath;
280
+ }
281
+ 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;
282
+ var init_config = __esm(() => {
283
+ init_package();
284
+ init_output();
285
+ init_tty();
286
+ CLI_VERSION2 = package_default.version;
287
+ TICK2 = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
288
+ });
289
+
225
290
  // ../../node_modules/.bun/citty@0.2.1/node_modules/citty/dist/index.mjs
226
291
  function defineCommand2(def) {
227
292
  return def;
@@ -1375,109 +1440,6 @@ Note: JSON is also auto-enabled when stdout is piped (e.g. in CI, scripts, or AI
1375
1440
  };
1376
1441
  });
1377
1442
 
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
1443
  // src/commands/auth/signup.ts
1482
1444
  var exports_signup = {};
1483
1445
  __export(exports_signup, {
@@ -1527,6 +1489,725 @@ var init_signup = __esm(() => {
1527
1489
  });
1528
1490
  });
1529
1491
 
1492
+ // ../tools/dist/index.js
1493
+ function isCustomerToolName(value) {
1494
+ return customerToolNameSet.has(value);
1495
+ }
1496
+ function resolveCustomerContextSearchInput(value) {
1497
+ if (!value.query) {
1498
+ return {
1499
+ ok: false,
1500
+ message: "A query argument is required"
1501
+ };
1502
+ }
1503
+ const normalizedQuery = value.query.trim();
1504
+ if (normalizedQuery.length < 2) {
1505
+ return {
1506
+ ok: false,
1507
+ message: "Query must be at least 2 non-whitespace characters"
1508
+ };
1509
+ }
1510
+ if (value.after !== undefined && !iso8601UtcDateTimeRegex.test(value.after)) {
1511
+ return {
1512
+ ok: false,
1513
+ message: "--after must be a valid ISO 8601 datetime"
1514
+ };
1515
+ }
1516
+ const afterTime = value.after === undefined ? undefined : new Date(value.after).getTime();
1517
+ if (afterTime !== undefined && Number.isNaN(afterTime)) {
1518
+ return {
1519
+ ok: false,
1520
+ message: "--after must be a valid ISO 8601 datetime"
1521
+ };
1522
+ }
1523
+ if (value.before !== undefined && !iso8601UtcDateTimeRegex.test(value.before)) {
1524
+ return {
1525
+ ok: false,
1526
+ message: "--before must be a valid ISO 8601 datetime"
1527
+ };
1528
+ }
1529
+ const beforeTime = value.before === undefined ? undefined : new Date(value.before).getTime();
1530
+ if (beforeTime !== undefined && Number.isNaN(beforeTime)) {
1531
+ return {
1532
+ ok: false,
1533
+ message: "--before must be a valid ISO 8601 datetime"
1534
+ };
1535
+ }
1536
+ if (afterTime !== undefined && beforeTime !== undefined && afterTime > beforeTime) {
1537
+ return {
1538
+ ok: false,
1539
+ message: "--after must be before or equal to --before"
1540
+ };
1541
+ }
1542
+ return {
1543
+ ok: true,
1544
+ request: {
1545
+ query: normalizedQuery,
1546
+ customer: value.customer,
1547
+ topK: value.topK,
1548
+ after: value.after,
1549
+ before: value.before,
1550
+ sourceTypes: value.sourceTypes
1551
+ }
1552
+ };
1553
+ }
1554
+ function createOutlitClient(options) {
1555
+ const key = options.apiKey.trim();
1556
+ const baseUrl = options.baseUrl ?? DEFAULT_OUTLIT_API_URL;
1557
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1558
+ if (!key) {
1559
+ throw new Error("apiKey is required");
1560
+ }
1561
+ if (!fetchImpl) {
1562
+ throw new Error("fetch is not available");
1563
+ }
1564
+ return {
1565
+ key,
1566
+ baseUrl,
1567
+ async callTool(toolName, input = {}) {
1568
+ if (!isCustomerToolName(toolName)) {
1569
+ throw new Error(`Unknown customer tool: ${toolName}`);
1570
+ }
1571
+ const response = await fetchImpl(new URL("/api/tools/call", baseUrl).toString(), {
1572
+ method: "POST",
1573
+ headers: {
1574
+ Authorization: `Bearer ${key}`,
1575
+ "Content-Type": "application/json"
1576
+ },
1577
+ body: JSON.stringify({
1578
+ tool: toolName,
1579
+ input
1580
+ })
1581
+ });
1582
+ if (!response.ok) {
1583
+ const text = await response.text();
1584
+ throw new Error(`API error (${response.status}): ${text}`);
1585
+ }
1586
+ return response.json();
1587
+ }
1588
+ };
1589
+ }
1590
+ var customerToolNames, customerToolContracts, customerBillingStatuses, customerFactStatuses, customerFactTypes, unsupportedCustomerFactTypes, customerFactCategories, customerIncludeSections, customerSourceTypes, customerTimeframes, timelineChannels, timelineTimeframes, userJourneyStages, schemaTables, customerToolNameSet, iso8601UtcDateTimeRegex, DEFAULT_OUTLIT_API_URL = "https://app.outlit.ai", defaultAgentToolNames, sqlToolNames, analyticalAgentToolNames;
1591
+ var init_dist4 = __esm(() => {
1592
+ customerToolNames = [
1593
+ "outlit_list_customers",
1594
+ "outlit_list_users",
1595
+ "outlit_get_customer",
1596
+ "outlit_get_timeline",
1597
+ "outlit_list_facts",
1598
+ "outlit_get_fact",
1599
+ "outlit_get_source",
1600
+ "outlit_search_customer_context",
1601
+ "outlit_query",
1602
+ "outlit_schema"
1603
+ ];
1604
+ customerToolContracts = {
1605
+ outlit_list_customers: {
1606
+ toolName: "outlit_list_customers",
1607
+ 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).",
1608
+ inputSchema: {
1609
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1610
+ type: "object",
1611
+ properties: {
1612
+ billingStatus: {
1613
+ description: "Filter by billing status",
1614
+ type: "string",
1615
+ enum: ["NONE", "TRIALING", "PAYING", "PAST_DUE", "CHURNED"]
1616
+ },
1617
+ hasActivityInLast: {
1618
+ description: "Filter customers with activity in the last N days",
1619
+ type: "string",
1620
+ enum: ["7d", "14d", "30d", "90d"]
1621
+ },
1622
+ noActivityInLast: {
1623
+ description: "Filter customers with NO activity in the last N days",
1624
+ type: "string",
1625
+ enum: ["7d", "14d", "30d", "90d"]
1626
+ },
1627
+ mrrAbove: {
1628
+ description: "Minimum MRR in cents (e.g., 10000 = $100)",
1629
+ type: "number",
1630
+ minimum: 0
1631
+ },
1632
+ mrrBelow: {
1633
+ description: "Maximum MRR in cents",
1634
+ type: "number",
1635
+ minimum: 0
1636
+ },
1637
+ traitFilters: {
1638
+ description: "Filter by exact trait values using key/value pairs",
1639
+ type: "object",
1640
+ propertyNames: {
1641
+ type: "string",
1642
+ pattern: "^[A-Za-z0-9_-]{1,100}$"
1643
+ },
1644
+ additionalProperties: {
1645
+ anyOf: [
1646
+ {
1647
+ type: "string",
1648
+ maxLength: 500
1649
+ },
1650
+ {
1651
+ type: "number"
1652
+ },
1653
+ {
1654
+ type: "boolean"
1655
+ }
1656
+ ]
1657
+ }
1658
+ },
1659
+ search: {
1660
+ description: "Search by customer name or domain (case-insensitive)",
1661
+ type: "string",
1662
+ maxLength: 500
1663
+ },
1664
+ limit: {
1665
+ description: "Results per page (max 1000)",
1666
+ default: 50,
1667
+ type: "number",
1668
+ minimum: 1,
1669
+ maximum: 1000
1670
+ },
1671
+ cursor: {
1672
+ description: "Pagination cursor from previous response",
1673
+ type: "string"
1674
+ },
1675
+ orderBy: {
1676
+ description: "Field to order results by",
1677
+ default: "last_activity_at",
1678
+ type: "string",
1679
+ enum: ["last_activity_at", "first_seen_at", "name", "mrr_cents"]
1680
+ },
1681
+ orderDirection: {
1682
+ description: "Sort direction",
1683
+ default: "desc",
1684
+ type: "string",
1685
+ enum: ["asc", "desc"]
1686
+ }
1687
+ },
1688
+ additionalProperties: false
1689
+ }
1690
+ },
1691
+ outlit_list_users: {
1692
+ toolName: "outlit_list_users",
1693
+ 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.",
1694
+ inputSchema: {
1695
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1696
+ type: "object",
1697
+ properties: {
1698
+ journeyStage: {
1699
+ description: "Filter by user journey stage",
1700
+ type: "string",
1701
+ enum: ["DISCOVERED", "SIGNED_UP", "ACTIVATED", "ENGAGED", "INACTIVE"]
1702
+ },
1703
+ customerId: {
1704
+ description: "Filter users by customer ID",
1705
+ type: "string",
1706
+ maxLength: 500
1707
+ },
1708
+ traitFilters: {
1709
+ description: "Filter by exact trait values using key/value pairs",
1710
+ type: "object",
1711
+ propertyNames: {
1712
+ type: "string",
1713
+ pattern: "^[A-Za-z0-9_-]{1,100}$"
1714
+ },
1715
+ additionalProperties: {
1716
+ anyOf: [
1717
+ {
1718
+ type: "string",
1719
+ maxLength: 500
1720
+ },
1721
+ {
1722
+ type: "number"
1723
+ },
1724
+ {
1725
+ type: "boolean"
1726
+ }
1727
+ ]
1728
+ }
1729
+ },
1730
+ hasActivityInLast: {
1731
+ description: "Filter users active within this window. Format: Nd, Nh, or Nm (e.g., '7d', '24h', '90m')",
1732
+ type: "string",
1733
+ pattern: "^\\d+(d|h|m)$"
1734
+ },
1735
+ noActivityInLast: {
1736
+ description: "Filter users NOT active within this window. Format: Nd, Nh, or Nm (e.g., '30d', '2h')",
1737
+ type: "string",
1738
+ pattern: "^\\d+(d|h|m)$"
1739
+ },
1740
+ search: {
1741
+ description: "Search by user email or name (case-insensitive)",
1742
+ type: "string",
1743
+ maxLength: 500
1744
+ },
1745
+ limit: {
1746
+ description: "Results per page (max 1000)",
1747
+ default: 50,
1748
+ type: "number",
1749
+ minimum: 1,
1750
+ maximum: 1000
1751
+ },
1752
+ cursor: {
1753
+ description: "Pagination cursor from previous response",
1754
+ type: "string"
1755
+ },
1756
+ orderBy: {
1757
+ description: "Field to order by",
1758
+ default: "last_activity_at",
1759
+ type: "string",
1760
+ enum: ["last_activity_at", "first_seen_at", "email"]
1761
+ },
1762
+ orderDirection: {
1763
+ description: "Sort direction",
1764
+ default: "desc",
1765
+ type: "string",
1766
+ enum: ["asc", "desc"]
1767
+ }
1768
+ },
1769
+ additionalProperties: false
1770
+ }
1771
+ },
1772
+ outlit_get_customer: {
1773
+ toolName: "outlit_get_customer",
1774
+ 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).",
1775
+ inputSchema: {
1776
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1777
+ type: "object",
1778
+ properties: {
1779
+ customer: {
1780
+ type: "string",
1781
+ description: "Customer ID, domain, or name to look up"
1782
+ },
1783
+ include: {
1784
+ description: "Additional data sections to include in the response",
1785
+ type: "array",
1786
+ items: {
1787
+ type: "string",
1788
+ enum: ["users", "revenue", "recentTimeline", "behaviorMetrics"]
1789
+ }
1790
+ },
1791
+ timeframe: {
1792
+ description: "Timeframe for timeline and behavior metrics (default: 30d)",
1793
+ default: "30d",
1794
+ type: "string",
1795
+ enum: ["7d", "14d", "30d", "90d"]
1796
+ }
1797
+ },
1798
+ required: ["customer"],
1799
+ additionalProperties: false
1800
+ }
1801
+ },
1802
+ outlit_get_timeline: {
1803
+ toolName: "outlit_get_timeline",
1804
+ 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.",
1805
+ inputSchema: {
1806
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1807
+ type: "object",
1808
+ properties: {
1809
+ customer: {
1810
+ type: "string",
1811
+ description: "Customer ID or domain"
1812
+ },
1813
+ channels: {
1814
+ description: "Filter by event channel (e.g., EMAIL, SLACK, CALL)",
1815
+ type: "array",
1816
+ items: {
1817
+ type: "string",
1818
+ enum: ["SDK", "EMAIL", "SLACK", "CALL", "CRM", "BILLING", "SUPPORT", "INTERNAL"]
1819
+ }
1820
+ },
1821
+ eventTypes: {
1822
+ description: "Filter by event type",
1823
+ type: "array",
1824
+ items: {
1825
+ type: "string"
1826
+ }
1827
+ },
1828
+ timeframe: {
1829
+ description: "Relative time window (default: 30d). Cannot be used with startDate/endDate.",
1830
+ type: "string",
1831
+ enum: ["7d", "14d", "30d", "90d", "all"]
1832
+ },
1833
+ startDate: {
1834
+ description: "Start of time window (ISO 8601, e.g. '2025-01-01T00:00:00Z'). Cannot be used with timeframe.",
1835
+ type: "string",
1836
+ format: "date-time",
1837
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
1838
+ },
1839
+ endDate: {
1840
+ description: "End of time window (ISO 8601, e.g. '2025-01-31T23:59:59Z'). Cannot be used with timeframe.",
1841
+ type: "string",
1842
+ format: "date-time",
1843
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
1844
+ },
1845
+ limit: {
1846
+ description: "Results per page (max 1000)",
1847
+ default: 50,
1848
+ type: "number",
1849
+ minimum: 1,
1850
+ maximum: 1000
1851
+ },
1852
+ cursor: {
1853
+ description: "Pagination cursor from previous response",
1854
+ type: "string"
1855
+ }
1856
+ },
1857
+ required: ["customer"],
1858
+ additionalProperties: false
1859
+ }
1860
+ },
1861
+ outlit_list_facts: {
1862
+ toolName: "outlit_list_facts",
1863
+ description: "List structured facts known about a customer. Use filters like status, sourceTypes, factTypes, factCategories, and date bounds to narrow the result set. For topic-specific retrieval, use outlit_search_customer_context instead.",
1864
+ inputSchema: {
1865
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1866
+ type: "object",
1867
+ properties: {
1868
+ customer: {
1869
+ type: "string",
1870
+ description: "Customer ID or domain"
1871
+ },
1872
+ status: {
1873
+ description: "Optional fact status filter",
1874
+ type: "array",
1875
+ items: {
1876
+ type: "string",
1877
+ enum: ["ACTIVE", "ACKNOWLEDGED", "RESOLVED", "SNOOZED", "CANDIDATE"]
1878
+ }
1879
+ },
1880
+ sourceTypes: {
1881
+ description: "Optional generic source types to restrict fact results to.",
1882
+ type: "array",
1883
+ items: {
1884
+ type: "string",
1885
+ enum: ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"]
1886
+ }
1887
+ },
1888
+ factTypes: {
1889
+ description: "Optional customer-memory fact type filters, such as CHURN_RISK, EXPANSION, SENTIMENT, or BUDGET. Anomaly detector fact types are not supported.",
1890
+ type: "array",
1891
+ items: {
1892
+ type: "string",
1893
+ enum: [
1894
+ "CUSTOM",
1895
+ "COMPANY_CHANGE",
1896
+ "FUNDING_REVENUE",
1897
+ "TECHNOLOGY",
1898
+ "STRATEGY",
1899
+ "COMPETITIVE",
1900
+ "SENTIMENT",
1901
+ "CHAMPION_RISK",
1902
+ "EXPANSION",
1903
+ "CHURN_RISK",
1904
+ "TIMELINE",
1905
+ "BUDGET",
1906
+ "DECISION_MAKER",
1907
+ "REQUIREMENTS",
1908
+ "PRODUCT_USAGE",
1909
+ "CONTACT_INFO",
1910
+ "CONTACT_PREFERENCE"
1911
+ ]
1912
+ }
1913
+ },
1914
+ factCategories: {
1915
+ description: "Optional public fact category filters. Supported values: MEMORY, CUSTOM.",
1916
+ type: "array",
1917
+ items: {
1918
+ type: "string",
1919
+ enum: ["MEMORY", "CUSTOM"]
1920
+ }
1921
+ },
1922
+ after: {
1923
+ description: "ISO 8601 datetime lower bound",
1924
+ type: "string",
1925
+ format: "date-time",
1926
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
1927
+ },
1928
+ before: {
1929
+ description: "ISO 8601 datetime upper bound",
1930
+ type: "string",
1931
+ format: "date-time",
1932
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
1933
+ },
1934
+ limit: {
1935
+ description: "Results per page (max 100)",
1936
+ default: 50,
1937
+ type: "number",
1938
+ minimum: 1,
1939
+ maximum: 100
1940
+ },
1941
+ cursor: {
1942
+ description: "Pagination cursor from previous response",
1943
+ type: "string"
1944
+ }
1945
+ },
1946
+ required: ["customer"],
1947
+ additionalProperties: false
1948
+ }
1949
+ },
1950
+ outlit_get_fact: {
1951
+ toolName: "outlit_get_fact",
1952
+ description: "Get one exact fact by ID. Returns the canonical fact shape and optionally expands requested related data such as evidence.",
1953
+ inputSchema: {
1954
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1955
+ type: "object",
1956
+ properties: {
1957
+ factId: {
1958
+ type: "string",
1959
+ minLength: 1,
1960
+ maxLength: 500,
1961
+ description: "Exact fact ID to retrieve"
1962
+ },
1963
+ include: {
1964
+ description: "Optional best-effort expansions. Use include=['evidence'] to request evidence when available; unsupported include values are ignored.",
1965
+ type: "array",
1966
+ items: {
1967
+ type: "string",
1968
+ minLength: 1,
1969
+ maxLength: 100
1970
+ }
1971
+ }
1972
+ },
1973
+ required: ["factId"],
1974
+ additionalProperties: false
1975
+ }
1976
+ },
1977
+ outlit_get_source: {
1978
+ toolName: "outlit_get_source",
1979
+ 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.",
1980
+ inputSchema: {
1981
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1982
+ type: "object",
1983
+ properties: {
1984
+ sourceType: {
1985
+ type: "string",
1986
+ enum: ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"]
1987
+ },
1988
+ sourceId: {
1989
+ type: "string",
1990
+ minLength: 1,
1991
+ maxLength: 500
1992
+ }
1993
+ },
1994
+ required: ["sourceType", "sourceId"],
1995
+ additionalProperties: false
1996
+ }
1997
+ },
1998
+ outlit_search_customer_context: {
1999
+ toolName: "outlit_search_customer_context",
2000
+ 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.",
2001
+ inputSchema: {
2002
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2003
+ type: "object",
2004
+ properties: {
2005
+ customer: {
2006
+ description: "Customer ID, domain, or name. Omit to search across all customers.",
2007
+ anyOf: [
2008
+ {
2009
+ type: "string",
2010
+ minLength: 1,
2011
+ maxLength: 500
2012
+ },
2013
+ {
2014
+ type: "null"
2015
+ }
2016
+ ]
2017
+ },
2018
+ query: {
2019
+ type: "string",
2020
+ minLength: 2,
2021
+ maxLength: 2000,
2022
+ description: "Natural language query or topic to search for."
2023
+ },
2024
+ topK: {
2025
+ description: "Maximum number of artifact results to return (default 20).",
2026
+ type: "integer",
2027
+ minimum: 1,
2028
+ maximum: 50
2029
+ },
2030
+ after: {
2031
+ description: "ISO 8601 datetime lower bound",
2032
+ type: "string",
2033
+ format: "date-time",
2034
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
2035
+ },
2036
+ before: {
2037
+ description: "ISO 8601 datetime upper bound",
2038
+ type: "string",
2039
+ format: "date-time",
2040
+ pattern: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
2041
+ },
2042
+ sourceTypes: {
2043
+ description: "Optional generic source types to restrict the search to.",
2044
+ type: "array",
2045
+ items: {
2046
+ type: "string",
2047
+ enum: ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"]
2048
+ }
2049
+ }
2050
+ },
2051
+ required: ["query"],
2052
+ additionalProperties: false
2053
+ }
2054
+ },
2055
+ outlit_query: {
2056
+ toolName: "outlit_query",
2057
+ description: `Execute raw SQL queries against your analytics data.
2058
+
2059
+ Available tables:
2060
+ - events: Customer activity events (event_type, event_channel, customer_id, occurred_at, properties, ...)
2061
+ - customer_dimensions: Customer attributes (customer_id, domain, name, billing_status, plan, mrr_cents, ...)
2062
+ - user_dimensions: User attributes (user_id, email, name, customer_id, ...)
2063
+ - mrr_snapshots: Revenue snapshots over time (customer_id, snapshot_date, mrr_cents, ...)
2064
+
2065
+ All queries are automatically filtered to your organization's data.
2066
+ Only SELECT queries are allowed.
2067
+
2068
+ Example queries:
2069
+ - SELECT event_type, count(*) FROM events GROUP BY 1 ORDER BY 2 DESC LIMIT 10
2070
+ - SELECT billing_status, sum(mrr_cents)/100 as mrr FROM customer_dimensions GROUP BY 1
2071
+ - SELECT * FROM events WHERE customer_id = 'cust_123' ORDER BY occurred_at DESC LIMIT 50`,
2072
+ inputSchema: {
2073
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2074
+ type: "object",
2075
+ properties: {
2076
+ sql: {
2077
+ type: "string",
2078
+ description: "SQL SELECT query to execute"
2079
+ },
2080
+ limit: {
2081
+ description: "Max rows to return (default 1000, max 10000)",
2082
+ default: 1000,
2083
+ type: "number",
2084
+ maximum: 1e4
2085
+ }
2086
+ },
2087
+ required: ["sql"],
2088
+ additionalProperties: false
2089
+ }
2090
+ },
2091
+ outlit_schema: {
2092
+ toolName: "outlit_schema",
2093
+ description: `Get table schemas for available analytics tables.
2094
+
2095
+ Use this to discover column names, types, and descriptions before writing SQL queries.
2096
+ Returns column definitions and example queries for each table.`,
2097
+ inputSchema: {
2098
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2099
+ type: "object",
2100
+ properties: {
2101
+ table: {
2102
+ description: "Specific table to describe, or omit for all tables",
2103
+ type: "string",
2104
+ enum: ["events", "customer_dimensions", "user_dimensions", "mrr_snapshots"]
2105
+ }
2106
+ },
2107
+ additionalProperties: false
2108
+ }
2109
+ }
2110
+ };
2111
+ customerBillingStatuses = [
2112
+ "NONE",
2113
+ "TRIALING",
2114
+ "PAYING",
2115
+ "PAST_DUE",
2116
+ "CHURNED"
2117
+ ];
2118
+ customerFactStatuses = [
2119
+ "ACTIVE",
2120
+ "ACKNOWLEDGED",
2121
+ "RESOLVED",
2122
+ "SNOOZED",
2123
+ "CANDIDATE"
2124
+ ];
2125
+ customerFactTypes = [
2126
+ "CUSTOM",
2127
+ "COMPANY_CHANGE",
2128
+ "FUNDING_REVENUE",
2129
+ "TECHNOLOGY",
2130
+ "STRATEGY",
2131
+ "COMPETITIVE",
2132
+ "SENTIMENT",
2133
+ "CHAMPION_RISK",
2134
+ "EXPANSION",
2135
+ "CHURN_RISK",
2136
+ "TIMELINE",
2137
+ "BUDGET",
2138
+ "DECISION_MAKER",
2139
+ "REQUIREMENTS",
2140
+ "PRODUCT_USAGE",
2141
+ "CONTACT_INFO",
2142
+ "CONTACT_PREFERENCE"
2143
+ ];
2144
+ unsupportedCustomerFactTypes = [
2145
+ "TRACKING_GAP",
2146
+ "SCHEMA_DRIFT",
2147
+ "INGESTION_LAG",
2148
+ "ACTIVATION_RATE_DROP",
2149
+ "FUNNEL_DROPOFF",
2150
+ "CORE_ACTION_DECAY",
2151
+ "CADENCE_BREAK",
2152
+ "QUIET_ACCOUNT",
2153
+ "CHAMPION_AT_RISK",
2154
+ "SEGMENT_DIVERGENCE"
2155
+ ];
2156
+ customerFactCategories = ["MEMORY", "CUSTOM"];
2157
+ customerIncludeSections = [
2158
+ "users",
2159
+ "revenue",
2160
+ "recentTimeline",
2161
+ "behaviorMetrics"
2162
+ ];
2163
+ customerSourceTypes = ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"];
2164
+ customerTimeframes = ["7d", "14d", "30d", "90d"];
2165
+ timelineChannels = [
2166
+ "SDK",
2167
+ "EMAIL",
2168
+ "SLACK",
2169
+ "CALL",
2170
+ "CRM",
2171
+ "BILLING",
2172
+ "SUPPORT",
2173
+ "INTERNAL"
2174
+ ];
2175
+ timelineTimeframes = ["7d", "14d", "30d", "90d", "all"];
2176
+ userJourneyStages = [
2177
+ "DISCOVERED",
2178
+ "SIGNED_UP",
2179
+ "ACTIVATED",
2180
+ "ENGAGED",
2181
+ "INACTIVE"
2182
+ ];
2183
+ schemaTables = [
2184
+ "events",
2185
+ "customer_dimensions",
2186
+ "user_dimensions",
2187
+ "mrr_snapshots"
2188
+ ];
2189
+ customerToolNameSet = new Set(customerToolNames);
2190
+ iso8601UtcDateTimeRegex = /^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$/;
2191
+ defaultAgentToolNames = [
2192
+ "outlit_list_customers",
2193
+ "outlit_list_users",
2194
+ "outlit_get_customer",
2195
+ "outlit_get_timeline",
2196
+ "outlit_list_facts",
2197
+ "outlit_get_fact",
2198
+ "outlit_get_source",
2199
+ "outlit_search_customer_context"
2200
+ ];
2201
+ sqlToolNames = [
2202
+ "outlit_schema",
2203
+ "outlit_query"
2204
+ ];
2205
+ analyticalAgentToolNames = [
2206
+ ...defaultAgentToolNames,
2207
+ ...sqlToolNames
2208
+ ];
2209
+ });
2210
+
1530
2211
  // src/lib/client.ts
1531
2212
  function buildUrl(base, path, params) {
1532
2213
  const url = new URL(path, base);
@@ -1552,11 +2233,18 @@ async function createClient(flagApiKey) {
1552
2233
  throw new Error(`Invalid API key format. Keys must start with "ok_" followed by at least 32 alphanumeric characters. Get one at ${OUTLIT_DASHBOARD_URL}`);
1553
2234
  }
1554
2235
  const baseUrl = process.env.OUTLIT_API_URL ?? DEFAULT_API_URL;
2236
+ const toolsClient = createOutlitClient({
2237
+ apiKey: credential.key,
2238
+ baseUrl
2239
+ });
1555
2240
  return {
1556
2241
  key: credential.key,
1557
2242
  baseUrl,
1558
2243
  async callTool(toolName, params) {
1559
- const endpoint = TOOL_ENDPOINTS[toolName];
2244
+ if (isCustomerToolName(toolName)) {
2245
+ return toolsClient.callTool(toolName, params);
2246
+ }
2247
+ const endpoint = CLI_TOOL_ENDPOINTS[toolName];
1560
2248
  if (!endpoint) {
1561
2249
  throw new Error(`Unknown tool: ${toolName}`);
1562
2250
  }
@@ -1585,29 +2273,22 @@ async function createClient(flagApiKey) {
1585
2273
  }
1586
2274
  };
1587
2275
  }
1588
- var API_KEY_REGEX, TOOL_ENDPOINTS;
2276
+ var API_KEY_REGEX, CLI_TOOL_ENDPOINTS;
1589
2277
  var init_client = __esm(() => {
2278
+ init_dist4();
1590
2279
  init_config();
1591
2280
  API_KEY_REGEX = /^ok_[A-Za-z0-9_-]{32,}$/;
1592
- TOOL_ENDPOINTS = {
1593
- outlit_list_customers: { method: "GET", path: "/api/internal/mcp/customers" },
1594
- outlit_get_customer: { method: "POST", path: "/api/internal/mcp/customers" },
1595
- outlit_list_users: { method: "GET", path: "/api/internal/mcp/users" },
1596
- outlit_get_timeline: { method: "POST", path: "/api/internal/mcp/timeline" },
1597
- outlit_get_facts: { method: "POST", path: "/api/internal/mcp/facts" },
1598
- outlit_schema: { method: "GET", path: "/api/internal/mcp/sql-schema" },
1599
- outlit_query: { method: "POST", path: "/api/internal/mcp/sql" },
1600
- outlit_search_customer_context: { method: "POST", path: "/api/internal/mcp/context-search" },
1601
- outlit_list_integrations: { method: "GET", path: "/api/internal/mcp/integrations" },
1602
- outlit_connect_integration: { method: "POST", path: "/api/internal/mcp/integrations/connect" },
1603
- outlit_connect_status: { method: "GET", path: "/api/internal/mcp/integrations/connect/status" },
2281
+ CLI_TOOL_ENDPOINTS = {
2282
+ outlit_list_integrations: { method: "GET", path: "/api/integrations" },
2283
+ outlit_connect_integration: { method: "POST", path: "/api/integrations/connect" },
2284
+ outlit_connect_status: { method: "GET", path: "/api/integrations/connect/status" },
1604
2285
  outlit_disconnect_integration: {
1605
2286
  method: "POST",
1606
- path: "/api/internal/mcp/integrations/disconnect"
2287
+ path: "/api/integrations/disconnect"
1607
2288
  },
1608
2289
  outlit_integration_sync_status: {
1609
2290
  method: "GET",
1610
- path: "/api/internal/mcp/integrations/sync-status"
2291
+ path: "/api/integrations/sync-status"
1611
2292
  }
1612
2293
  };
1613
2294
  });
@@ -1736,10 +2417,28 @@ async function getClientOrExit(flagApiKey, json) {
1736
2417
  return createClient(flagApiKey).catch((err) => outputError({ message: errorMessage(err, "Authentication failed"), code: "auth_required" }, json));
1737
2418
  }
1738
2419
  async function pingApiKey(apiKey) {
1739
- const client = await createClient(apiKey);
1740
- await client.callTool("outlit_list_customers", { limit: 1 });
1741
- }
1742
- async function validateKeyOrExit(apiKey, json) {
2420
+ const baseUrl = process.env.OUTLIT_API_URL ?? DEFAULT_API_URL;
2421
+ const url = new URL("/api/validate-api-key", baseUrl).toString();
2422
+ const response = await globalThis.fetch(url, {
2423
+ method: "POST",
2424
+ headers: {
2425
+ Authorization: `Bearer ${apiKey}`
2426
+ }
2427
+ });
2428
+ const text = await response.text();
2429
+ const payload = text.length > 0 ? (() => {
2430
+ try {
2431
+ return JSON.parse(text);
2432
+ } catch {
2433
+ return null;
2434
+ }
2435
+ })() : null;
2436
+ if (!response.ok || !payload?.valid) {
2437
+ const message = payload?.error ?? (text.length > 0 ? text : `API error (${response.status})`);
2438
+ throw new Error(message);
2439
+ }
2440
+ }
2441
+ async function validateKeyOrExit(apiKey, json) {
1743
2442
  try {
1744
2443
  await pingApiKey(apiKey);
1745
2444
  } catch (err) {
@@ -1789,6 +2488,7 @@ async function runTool(client, toolName, params, json, opts) {
1789
2488
  }
1790
2489
  var init_api = __esm(() => {
1791
2490
  init_client();
2491
+ init_config();
1792
2492
  init_output();
1793
2493
  init_spinner();
1794
2494
  init_table();
@@ -1922,8 +2622,8 @@ var exports_logout = {};
1922
2622
  __export(exports_logout, {
1923
2623
  default: () => logout_default
1924
2624
  });
1925
- import { rmSync } from "node:fs";
1926
- import { join as join2 } from "node:path";
2625
+ import { rmSync as rmSync2 } from "node:fs";
2626
+ import { join as join3 } from "node:path";
1927
2627
  var logout_default;
1928
2628
  var init_logout = __esm(() => {
1929
2629
  init_dist();
@@ -1949,13 +2649,13 @@ var init_logout = __esm(() => {
1949
2649
  async run({ args }) {
1950
2650
  const json = !!args.json;
1951
2651
  const configDir = getConfigDir();
1952
- const credPath = join2(configDir, "credentials.json");
2652
+ const credPath = join3(configDir, "credentials.json");
1953
2653
  if (process.env.OUTLIT_API_KEY) {
1954
2654
  process.stderr.write(`Warning: OUTLIT_API_KEY env var is still set and will continue to work after logout.
1955
2655
  `);
1956
2656
  }
1957
2657
  try {
1958
- rmSync(credPath, { force: true });
2658
+ rmSync2(credPath, { force: true });
1959
2659
  } catch (err) {
1960
2660
  if (!isEnoentError(err)) {
1961
2661
  return outputError({ message: errorMessage(err, "Failed to remove credentials file"), code: "unlink_error" }, json);
@@ -2277,6 +2977,7 @@ __export(exports_list, {
2277
2977
  });
2278
2978
  var list_default;
2279
2979
  var init_list = __esm(() => {
2980
+ init_dist4();
2280
2981
  init_dist();
2281
2982
  init_auth();
2282
2983
  init_filters();
@@ -2300,7 +3001,7 @@ var init_list = __esm(() => {
2300
3001
  " outlit customers list --mrr-above 10000 --limit 50 # high-value at-risk",
2301
3002
  " outlit customers list --json | jq '.items[].domain' # pipe-friendly",
2302
3003
  "",
2303
- "Billing statuses: NONE, TRIALING, PAYING, PAST_DUE, CHURNED",
3004
+ `Billing statuses: ${customerBillingStatuses.join(", ")}`,
2304
3005
  "Activity periods: 7d, 14d, 30d, 90d",
2305
3006
  "",
2306
3007
  AGENT_JSON_HINT
@@ -2316,7 +3017,7 @@ var init_list = __esm(() => {
2316
3017
  ...orderArgs,
2317
3018
  "billing-status": {
2318
3019
  type: "string",
2319
- description: "Filter by billing status (NONE, TRIALING, PAYING, PAST_DUE, CHURNED)"
3020
+ description: `Filter by billing status (${customerBillingStatuses.join(", ")})`
2320
3021
  },
2321
3022
  "mrr-above": {
2322
3023
  type: "string",
@@ -2329,14 +3030,6 @@ var init_list = __esm(() => {
2329
3030
  search: {
2330
3031
  type: "string",
2331
3032
  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
3033
  }
2341
3034
  },
2342
3035
  async run({ args }) {
@@ -2374,11 +3067,7 @@ var init_list = __esm(() => {
2374
3067
  }
2375
3068
  applyListFilters(params, args);
2376
3069
  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, {
3070
+ return runTool(client, customerToolContracts.outlit_list_customers.toolName, params, json, {
2382
3071
  spinnerMessage: "Fetching customers...",
2383
3072
  table: {
2384
3073
  columns: [
@@ -2401,6 +3090,7 @@ __export(exports_get, {
2401
3090
  });
2402
3091
  var get_default;
2403
3092
  var init_get = __esm(() => {
3093
+ init_dist4();
2404
3094
  init_dist();
2405
3095
  init_auth();
2406
3096
  init_output2();
@@ -2420,6 +3110,9 @@ var init_get = __esm(() => {
2420
3110
  'Naming note: --include users returns data under the "contacts" key in',
2421
3111
  "the response. This is a server-side naming inconsistency, not a CLI bug.",
2422
3112
  "",
3113
+ `Available include sections: ${customerIncludeSections.join(", ")}`,
3114
+ `Timeframes: ${customerTimeframes.join(", ")}`,
3115
+ "",
2423
3116
  "Examples:",
2424
3117
  " outlit customers get acme.com",
2425
3118
  " outlit customers get acme.com --include users,revenue",
@@ -2441,7 +3134,7 @@ var init_get = __esm(() => {
2441
3134
  type: "string",
2442
3135
  description: [
2443
3136
  "Comma-separated sections to include in response.",
2444
- "Available: users, revenue, recentTimeline, behaviorMetrics",
3137
+ `Available: ${customerIncludeSections.join(", ")}`,
2445
3138
  'Note: "users" maps to "contacts" in the response (server naming).'
2446
3139
  ].join(`
2447
3140
  `)
@@ -2462,7 +3155,7 @@ var init_get = __esm(() => {
2462
3155
  if (args.include) {
2463
3156
  params.include = splitCsv(args.include);
2464
3157
  }
2465
- return runTool(client, "outlit_get_customer", params, json);
3158
+ return runTool(client, customerToolContracts.outlit_get_customer.toolName, params, json);
2466
3159
  }
2467
3160
  });
2468
3161
  });
@@ -2474,6 +3167,7 @@ __export(exports_timeline, {
2474
3167
  });
2475
3168
  var timeline_default;
2476
3169
  var init_timeline = __esm(() => {
3170
+ init_dist4();
2477
3171
  init_dist();
2478
3172
  init_auth();
2479
3173
  init_output2();
@@ -2492,12 +3186,14 @@ var init_timeline = __esm(() => {
2492
3186
  "",
2493
3187
  "Timeframe is used when no explicit date range is set.",
2494
3188
  "When --start-date or --end-date is provided, --timeframe is ignored.",
3189
+ `Channels: ${timelineChannels.join(", ")}`,
3190
+ `Timeframes: ${timelineTimeframes.join(", ")}`,
2495
3191
  "",
2496
3192
  "Examples:",
2497
3193
  " outlit customers timeline acme.com",
2498
3194
  " outlit customers timeline acme.com --timeframe 90d",
2499
3195
  " outlit customers timeline acme.com --channels EMAIL,SLACK",
2500
- " outlit customers timeline acme.com --start-date 2025-01-01 --end-date 2025-03-01",
3196
+ " outlit customers timeline acme.com --start-date 2025-01-01T00:00:00Z --end-date 2025-03-01T23:59:59Z",
2501
3197
  " outlit customers timeline acme.com --event-types PAGE_VIEW,MEETING --limit 50",
2502
3198
  "",
2503
3199
  AGENT_JSON_HINT
@@ -2515,7 +3211,7 @@ var init_timeline = __esm(() => {
2515
3211
  },
2516
3212
  channels: {
2517
3213
  type: "string",
2518
- description: "Comma-separated list of channels to filter (e.g. EMAIL,SLACK)"
3214
+ description: `Comma-separated list of channels to filter (${timelineChannels.join(", ")})`
2519
3215
  },
2520
3216
  "event-types": {
2521
3217
  type: "string",
@@ -2523,16 +3219,16 @@ var init_timeline = __esm(() => {
2523
3219
  },
2524
3220
  timeframe: {
2525
3221
  type: "string",
2526
- description: "Timeframe for events (e.g. 7d, 14d, 30d, 90d). Ignored when --start-date or --end-date is set.",
3222
+ description: `Timeframe for events (${timelineTimeframes.join(", ")}). Ignored when --start-date or --end-date is set.`,
2527
3223
  default: "30d"
2528
3224
  },
2529
3225
  "start-date": {
2530
3226
  type: "string",
2531
- description: "Start date for the event range (ISO 8601, e.g. 2025-01-01). When set, --timeframe is ignored."
3227
+ description: "Start datetime for the event range (ISO 8601, e.g. 2025-01-01T00:00:00Z). When set, --timeframe is ignored."
2532
3228
  },
2533
3229
  "end-date": {
2534
3230
  type: "string",
2535
- description: "End date for the event range (ISO 8601, e.g. 2025-03-01). When set, --timeframe is ignored."
3231
+ description: "End datetime for the event range (ISO 8601, e.g. 2025-03-01T23:59:59Z). When set, --timeframe is ignored."
2536
3232
  }
2537
3233
  },
2538
3234
  async run({ args }) {
@@ -2555,7 +3251,7 @@ var init_timeline = __esm(() => {
2555
3251
  params.eventTypes = splitCsv(args["event-types"]);
2556
3252
  }
2557
3253
  applyPagination(params, args, json);
2558
- return runTool(client, "outlit_get_timeline", params, json);
3254
+ return runTool(client, customerToolContracts.outlit_get_timeline.toolName, params, json);
2559
3255
  }
2560
3256
  });
2561
3257
  });
@@ -2599,6 +3295,7 @@ __export(exports_list2, {
2599
3295
  });
2600
3296
  var list_default2;
2601
3297
  var init_list2 = __esm(() => {
3298
+ init_dist4();
2602
3299
  init_dist();
2603
3300
  init_auth();
2604
3301
  init_filters();
@@ -2617,7 +3314,7 @@ var init_list2 = __esm(() => {
2617
3314
  "",
2618
3315
  "Examples:",
2619
3316
  " outlit users list # all users",
2620
- " outlit users list --journey-stage CHAMPION # champions only",
3317
+ " outlit users list --journey-stage ENGAGED # engaged users only",
2621
3318
  " outlit users list --customer-id <uuid> # users for a customer",
2622
3319
  " outlit users list --no-activity-in 30d # inactive users",
2623
3320
  " outlit users list --search alice --order-by last_activity_at",
@@ -2634,7 +3331,7 @@ var init_list2 = __esm(() => {
2634
3331
  ...traitFilterArgs,
2635
3332
  "journey-stage": {
2636
3333
  type: "string",
2637
- description: "Filter by journey stage (e.g. CHAMPION, AT_RISK, CHURNED)"
3334
+ description: `Filter by journey stage (${userJourneyStages.join(", ")})`
2638
3335
  },
2639
3336
  "customer-id": {
2640
3337
  type: "string",
@@ -2670,7 +3367,7 @@ var init_list2 = __esm(() => {
2670
3367
  }
2671
3368
  applyListFilters(params, args);
2672
3369
  applyPagination(params, args, json);
2673
- return runTool(client, "outlit_list_users", params, json, {
3370
+ return runTool(client, customerToolContracts.outlit_list_users.toolName, params, json, {
2674
3371
  spinnerMessage: "Fetching users...",
2675
3372
  table: {
2676
3373
  columns: [
@@ -2714,376 +3411,213 @@ var init_users = __esm(() => {
2714
3411
  });
2715
3412
  });
2716
3413
 
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);
2738
- }
2739
- return { success: false, error: errorMessage(err, `${opts.cliName} mcp add failed`) };
2740
- }
2741
- if (exitOnError) {
2742
- if (isJsonMode(json)) {
2743
- outputResult({ success: true, agent: opts.agentId });
2744
- return { success: true };
2745
- }
2746
- console.log(`${TICK2} ${opts.successMessage}`);
3414
+ // src/lib/update.ts
3415
+ import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
3416
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, realpathSync as realpathSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
3417
+ import { homedir as homedir3 } from "node:os";
3418
+ import { dirname as dirname3, join as join4 } from "node:path";
3419
+ function compareVersions2(a, b) {
3420
+ const aParts = a.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
3421
+ const bParts = b.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
3422
+ const maxLength = Math.max(aParts.length, bParts.length);
3423
+ for (let index = 0;index < maxLength; index++) {
3424
+ const left = aParts[index] ?? 0;
3425
+ const right = bParts[index] ?? 0;
3426
+ if (left > right)
3427
+ return 1;
3428
+ if (left < right)
3429
+ return -1;
2747
3430
  }
2748
- return { success: true };
3431
+ return 0;
2749
3432
  }
2750
- function runMcpFileSetup(key, json, opts, exitOnError = true) {
2751
- const mcpConfig = opts.mcpConfig ?? {
2752
- url: DEFAULT_MCP_URL,
2753
- headers: { Authorization: `Bearer ${key}` }
2754
- };
2755
- 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}`) };
3433
+ function inferInstallerFromUserAgent2(agent) {
3434
+ if (agent.startsWith("bun/"))
3435
+ return "bun";
3436
+ if (agent.startsWith("npm/"))
3437
+ return "npm";
3438
+ if (agent.startsWith("pnpm/"))
3439
+ return "pnpm";
3440
+ if (agent.startsWith("yarn/"))
3441
+ return "yarn";
3442
+ return null;
3443
+ }
3444
+ function isUnderPath2(path, parent) {
3445
+ const normalizedPath = normalizeInstallerPath2(path);
3446
+ const normalizedParent = normalizeInstallerPath2(parent);
3447
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`);
3448
+ }
3449
+ function normalizeInstallerPath2(path) {
3450
+ return path.replace(/^\/private\/tmp\//, "/tmp/");
3451
+ }
3452
+ function inferInstallerFromInstallation2(opts) {
3453
+ const candidatePaths = [opts.argv1, opts.realExecPath].filter((value) => !!value);
3454
+ if (opts.npmGlobalPrefix) {
3455
+ const npmPackageRoots = [
3456
+ join4(opts.npmGlobalPrefix, "node_modules", PACKAGE_NAME2),
3457
+ join4(opts.npmGlobalPrefix, "lib", "node_modules", PACKAGE_NAME2)
3458
+ ];
3459
+ if (candidatePaths.some((path) => npmPackageRoots.some((root) => isUnderPath2(path, root)))) {
3460
+ return "npm";
2760
3461
  }
2761
- throw err;
2762
3462
  }
2763
- if (exitOnError) {
2764
- if (isJsonMode(json)) {
2765
- outputResult({ success: true, path: opts.configPath, agent: opts.agentId });
2766
- return { success: true };
3463
+ if (opts.bunGlobalBin) {
3464
+ const bunGlobalBin = opts.bunGlobalBin;
3465
+ if (candidatePaths.some((path) => isUnderPath2(path, bunGlobalBin))) {
3466
+ return "bun";
2767
3467
  }
2768
- console.log(`${TICK2} ${opts.successMessage}`);
2769
3468
  }
2770
- return { success: true };
3469
+ return null;
2771
3470
  }
2772
- var init_setup = __esm(() => {
2773
- init_config();
2774
- init_output();
2775
- });
2776
-
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
2782
- });
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;
3471
+ function readCommandOutput2(command, args) {
3472
+ try {
3473
+ return execFileSync3(command, args, {
3474
+ encoding: "utf8",
3475
+ stdio: ["ignore", "pipe", "ignore"]
3476
+ }).trim();
3477
+ } catch {
3478
+ return null;
3479
+ }
2821
3480
  }
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
- }
3481
+ function inferInstaller2() {
3482
+ const fromAgent = inferInstallerFromUserAgent2(process.env.npm_config_user_agent ?? "");
3483
+ if (fromAgent)
3484
+ return fromAgent;
3485
+ const argv1 = process.argv[1];
3486
+ const realExecPath = argv1 ? readCommandOutput2("realpath", [argv1]) ?? safeRealPath2(argv1) : null;
3487
+ const npmGlobalPrefix = process.env.npm_config_prefix ?? readCommandOutput2("npm", ["prefix", "-g"]);
3488
+ const bunGlobalBin = process.env.BUN_INSTALL ? join4(process.env.BUN_INSTALL, "bin") : readCommandOutput2("bun", ["pm", "bin", "-g"]) ?? join4(homedir3(), ".bun", "bin");
3489
+ return inferInstallerFromInstallation2({
3490
+ argv1,
3491
+ realExecPath,
3492
+ npmGlobalPrefix,
3493
+ bunGlobalBin
2938
3494
  });
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
3495
  }
3023
- function configureSafe5(key, json) {
3024
- const skillPath = join4(getSkillDir(), "SKILL.md");
3496
+ function safeRealPath2(filePath) {
3025
3497
  try {
3026
- writeConfigFile(skillPath, buildSkillContent(maskKey(key)), { json, label: "SKILL.md" });
3027
- return true;
3498
+ return realpathSync2(filePath);
3028
3499
  } catch {
3029
- return false;
3500
+ return null;
3030
3501
  }
3031
3502
  }
3032
- var openclaw_default;
3033
- var init_openclaw = __esm(() => {
3034
- init_dist();
3035
- init_auth();
3036
- init_output2();
3503
+ function formatUpdateCommand2(installer = inferInstaller2()) {
3504
+ switch (installer) {
3505
+ case "bun":
3506
+ return "bun add -g @outlit/cli";
3507
+ case "npm":
3508
+ return "npm install -g @outlit/cli";
3509
+ case "pnpm":
3510
+ return "pnpm add -g @outlit/cli";
3511
+ case "yarn":
3512
+ return "yarn global add @outlit/cli";
3513
+ default:
3514
+ return `update ${PACKAGE_NAME2} with your package manager`;
3515
+ }
3516
+ }
3517
+ function getUpgradeCommand(installer = inferInstaller2()) {
3518
+ switch (installer) {
3519
+ case "bun":
3520
+ return {
3521
+ command: "bun",
3522
+ args: ["add", "-g", "@outlit/cli"],
3523
+ displayCommand: "bun add -g @outlit/cli"
3524
+ };
3525
+ case "npm":
3526
+ return {
3527
+ command: "npm",
3528
+ args: ["install", "-g", "@outlit/cli"],
3529
+ displayCommand: "npm install -g @outlit/cli"
3530
+ };
3531
+ case "pnpm":
3532
+ return {
3533
+ command: "pnpm",
3534
+ args: ["add", "-g", "@outlit/cli"],
3535
+ displayCommand: "pnpm add -g @outlit/cli"
3536
+ };
3537
+ case "yarn":
3538
+ return {
3539
+ command: "yarn",
3540
+ args: ["global", "add", "@outlit/cli"],
3541
+ displayCommand: "yarn global add @outlit/cli"
3542
+ };
3543
+ default:
3544
+ return null;
3545
+ }
3546
+ }
3547
+ async function fetchLatestCliVersion2() {
3548
+ const response = await fetch(LATEST_VERSION_URL2, { signal: AbortSignal.timeout(5000) });
3549
+ if (!response.ok)
3550
+ throw new Error("registry unavailable");
3551
+ const data = await response.json();
3552
+ if (!data.version)
3553
+ throw new Error("registry returned no version");
3554
+ return data.version;
3555
+ }
3556
+ function runUpgradeCommand(command) {
3557
+ const result = spawnSync2(command.command, command.args, { stdio: "inherit" });
3558
+ if (result.error)
3559
+ throw result.error;
3560
+ if (result.status !== 0 || result.signal) {
3561
+ throw new Error(`Upgrade command failed: ${command.displayCommand}`);
3562
+ }
3563
+ }
3564
+ var UPDATE_CHECK_INTERVAL_MS2, PACKAGE_NAME2 = "@outlit/cli", LATEST_VERSION_URL2 = "https://registry.npmjs.org/@outlit%2Fcli/latest";
3565
+ var init_update = __esm(() => {
3037
3566
  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
- });
3567
+ init_tty();
3568
+ UPDATE_CHECK_INTERVAL_MS2 = 12 * 60 * 60 * 1000;
3057
3569
  });
3058
3570
 
3059
3571
  // src/commands/setup/skills.ts
3060
3572
  var exports_skills = {};
3061
3573
  __export(exports_skills, {
3062
3574
  runSkillsInstall: () => runSkillsInstall,
3575
+ runAgentSkillsInstall: () => runAgentSkillsInstall,
3576
+ getSkillAgentId: () => getSkillAgentId,
3063
3577
  detectPackageRunner: () => detectPackageRunner,
3064
- default: () => skills_default
3578
+ default: () => skills_default,
3579
+ SKILLS_REPO_URL: () => SKILLS_REPO_URL
3065
3580
  });
3066
- import { execFileSync as execFileSync3 } from "node:child_process";
3581
+ import { execFileSync as execFileSync4 } from "node:child_process";
3582
+ function getSkillAgentId(agent) {
3583
+ return skillAgentMap[agent];
3584
+ }
3067
3585
  function detectPackageRunner() {
3068
3586
  const whichCmd = process.platform === "win32" ? "where" : "which";
3069
3587
  for (const runner of ["npx", "bunx", "pnpx"]) {
3070
3588
  try {
3071
- execFileSync3(whichCmd, [runner], { stdio: "ignore" });
3589
+ execFileSync4(whichCmd, [runner], { stdio: "ignore" });
3072
3590
  return runner;
3073
3591
  } catch {}
3074
3592
  }
3075
3593
  return null;
3076
3594
  }
3077
- function buildRunnerArgs(runner) {
3595
+ function buildRunnerArgs(runner, opts) {
3078
3596
  const args = runner === "npx" ? ["-y"] : [];
3079
3597
  args.push("skills", "add", SKILLS_REPO_URL);
3080
- for (const name of SKILL_NAMES) {
3081
- args.push("--skill", name);
3598
+ for (const skillName of opts.skillNames ?? []) {
3599
+ args.push("--skill", skillName);
3600
+ }
3601
+ for (const agent of opts.agents ?? []) {
3602
+ args.push("--agent", agent);
3603
+ }
3604
+ if (opts.autoConfirm) {
3605
+ args.push("-y");
3606
+ }
3607
+ if (opts.global !== false) {
3608
+ args.push("-g");
3082
3609
  }
3083
- args.push("-y", "-g");
3084
3610
  return args;
3085
3611
  }
3086
- function runSkillsInstall(json, exitOnError = true) {
3612
+ function runSkillsInstall(opts) {
3613
+ const {
3614
+ json,
3615
+ exitOnError = true,
3616
+ agents,
3617
+ skillNames,
3618
+ reportedAgent = "skills",
3619
+ autoConfirm = false
3620
+ } = opts;
3087
3621
  const runner = detectPackageRunner();
3088
3622
  if (!runner) {
3089
3623
  if (exitOnError) {
@@ -3094,8 +3628,11 @@ function runSkillsInstall(json, exitOnError = true) {
3094
3628
  }
3095
3629
  return { success: false, error: "No package runner found" };
3096
3630
  }
3631
+ const isInteractiveInstall = (agents?.length ?? 0) === 0 && (skillNames?.length ?? 0) === 0 && !autoConfirm && !isJsonMode(json);
3097
3632
  try {
3098
- execFileSync3(runner, buildRunnerArgs(runner), { stdio: "pipe" });
3633
+ execFileSync4(runner, buildRunnerArgs(runner, { agents, skillNames, autoConfirm }), {
3634
+ stdio: isInteractiveInstall ? "inherit" : "pipe"
3635
+ });
3099
3636
  } catch (err) {
3100
3637
  if (exitOnError) {
3101
3638
  if (isEnoentError(err)) {
@@ -3114,210 +3651,355 @@ function runSkillsInstall(json, exitOnError = true) {
3114
3651
  }
3115
3652
  if (exitOnError) {
3116
3653
  if (isJsonMode(json)) {
3117
- outputResult({ success: true, agent: "skills", runner });
3654
+ outputResult({ success: true, agent: reportedAgent, runner });
3118
3655
  return { success: true, runner };
3119
3656
  }
3120
- console.log(`${TICK2} Agent skills installed (${SKILL_NAMES.join(", ")})`);
3657
+ if (reportedAgent === "skills") {
3658
+ console.log(`${TICK2} Outlit skills installer completed (${runner})`);
3659
+ } else {
3660
+ console.log(`${TICK2} Outlit skill installed for ${reportedAgent}`);
3661
+ }
3121
3662
  }
3122
3663
  return { success: true, runner };
3123
3664
  }
3124
- var SKILLS_REPO_URL = "https://github.com/OutlitAI/outlit-agent-skills", SKILL_NAMES, skills_default;
3665
+ function runAgentSkillsInstall(agent, json, exitOnError = true) {
3666
+ return runSkillsInstall({
3667
+ json,
3668
+ exitOnError,
3669
+ agents: [getSkillAgentId(agent)],
3670
+ skillNames: [DEFAULT_SKILL_NAME],
3671
+ reportedAgent: agent,
3672
+ autoConfirm: true
3673
+ });
3674
+ }
3675
+ var SKILLS_REPO_URL = "https://github.com/OutlitAI/outlit-agent-skills", DEFAULT_SKILL_NAME = "outlit", skillAgentMap, skills_default;
3125
3676
  var init_skills = __esm(() => {
3126
3677
  init_dist();
3127
3678
  init_output2();
3128
3679
  init_config();
3129
3680
  init_output();
3130
- SKILL_NAMES = ["outlit-cli", "outlit-sdk"];
3681
+ skillAgentMap = {
3682
+ "claude-code": "claude-code",
3683
+ codex: "codex",
3684
+ gemini: "gemini-cli",
3685
+ droid: "droid",
3686
+ opencode: "opencode",
3687
+ pi: "pi",
3688
+ openclaw: "openclaw"
3689
+ };
3131
3690
  skills_default = defineCommand2({
3132
3691
  meta: {
3133
3692
  name: "skills",
3134
3693
  description: [
3135
- "Install Outlit agent skills for Claude Code and other AI agents.",
3694
+ "Launch the interactive Skills installer for Outlit.",
3136
3695
  "",
3137
- "Downloads outlit-cli and outlit-sdk skills from github.com/OutlitAI/outlit-agent-skills.",
3696
+ "Uses the Outlit skills repo and lets you choose `outlit` and optional extras like `outlit-sdk`.",
3138
3697
  "No API key required."
3139
3698
  ].join(`
3140
3699
  `)
3141
3700
  },
3142
3701
  args: { ...outputArgs },
3143
3702
  run({ args }) {
3144
- const json = !!args.json;
3145
- runSkillsInstall(json);
3703
+ runSkillsInstall({ json: !!args.json });
3146
3704
  }
3147
3705
  });
3148
3706
  });
3149
3707
 
3150
- // src/commands/setup/vscode.ts
3151
- var exports_vscode = {};
3152
- __export(exports_vscode, {
3153
- default: () => vscode_default,
3154
- configureSafe: () => configureSafe6
3708
+ // src/commands/setup/claude-code.ts
3709
+ var exports_claude_code = {};
3710
+ __export(exports_claude_code, {
3711
+ default: () => claude_code_default
3155
3712
  });
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(() => {
3713
+ var claude_code_default;
3714
+ var init_claude_code = __esm(() => {
3168
3715
  init_dist();
3169
- init_auth();
3170
3716
  init_output2();
3171
- init_config();
3172
- init_setup();
3173
- vscode_default = defineCommand2({
3717
+ init_skills();
3718
+ claude_code_default = defineCommand2({
3174
3719
  meta: {
3175
- name: "vscode",
3176
- description: "Write Outlit MCP config to .vscode/mcp.json in the current directory."
3720
+ name: "claude-code",
3721
+ description: "Install the Outlit skill for Claude Code."
3177
3722
  },
3178
- args: { ...authArgs, ...outputArgs },
3723
+ args: { ...outputArgs },
3179
3724
  run({ args }) {
3180
- const json = !!args.json;
3181
- const { key } = requireCredential(args["api-key"], json);
3182
- runMcpFileSetup(key, json, getConfig5());
3725
+ runAgentSkillsInstall("claude-code", !!args.json);
3183
3726
  }
3184
3727
  });
3185
3728
  });
3186
3729
 
3187
- // src/commands/setup/index.ts
3188
- import { execFileSync as execFileSync4 } from "node:child_process";
3189
- import { existsSync as existsSync2 } from "node:fs";
3190
- import { homedir as homedir4 } from "node:os";
3191
- import { join as join6 } from "node:path";
3192
- function isCommandAvailable(cmd) {
3193
- try {
3194
- const whichCmd = process.platform === "win32" ? "where" : "which";
3195
- execFileSync4(whichCmd, [cmd], { stdio: "ignore" });
3196
- return true;
3197
- } catch {
3198
- return false;
3199
- }
3200
- }
3201
- function detectAgents() {
3202
- const home = homedir4();
3203
- const detected = [];
3204
- if (existsSync2(join6(home, ".cursor")))
3205
- detected.push("cursor");
3206
- if (isCommandAvailable("claude"))
3207
- 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");
3212
- if (isCommandAvailable("gemini"))
3213
- detected.push("gemini");
3214
- if (existsSync2(join6(home, ".openclaw", "skills")))
3215
- detected.push("openclaw");
3216
- return detected;
3217
- }
3218
- var agentLabels, configurators, setup_default;
3219
- var init_setup2 = __esm(() => {
3730
+ // src/commands/setup/codex.ts
3731
+ var exports_codex = {};
3732
+ __export(exports_codex, {
3733
+ default: () => codex_default
3734
+ });
3735
+ var codex_default;
3736
+ var init_codex = __esm(() => {
3220
3737
  init_dist();
3221
- init_auth();
3222
3738
  init_output2();
3223
- init_config();
3224
- init_output();
3225
- init_claude_code();
3226
- init_claude_desktop();
3227
- init_cursor();
3228
- init_gemini();
3229
- init_openclaw();
3230
3739
  init_skills();
3231
- init_vscode();
3232
- agentLabels = {
3233
- cursor: { label: "Cursor", hint: "~/.cursor/" },
3234
- "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" },
3237
- 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
3247
- };
3248
- setup_default = defineCommand2({
3740
+ codex_default = defineCommand2({
3249
3741
  meta: {
3250
- name: "setup",
3251
- description: [
3252
- "Configure Outlit for AI agent tools.",
3253
- "",
3254
- "Without a subcommand, auto-detects installed agents and configures them all.",
3255
- "Subcommands: cursor, claude-code, claude-desktop, vscode, gemini, openclaw, skills"
3256
- ].join(`
3257
- `)
3742
+ name: "codex",
3743
+ description: "Install the Outlit skill for Codex."
3744
+ },
3745
+ args: { ...outputArgs },
3746
+ run({ args }) {
3747
+ runAgentSkillsInstall("codex", !!args.json);
3748
+ }
3749
+ });
3750
+ });
3751
+
3752
+ // src/commands/setup/gemini.ts
3753
+ var exports_gemini = {};
3754
+ __export(exports_gemini, {
3755
+ default: () => gemini_default
3756
+ });
3757
+ var gemini_default;
3758
+ var init_gemini = __esm(() => {
3759
+ init_dist();
3760
+ init_output2();
3761
+ init_skills();
3762
+ gemini_default = defineCommand2({
3763
+ meta: {
3764
+ name: "gemini",
3765
+ description: "Install the Outlit skill for Gemini CLI."
3766
+ },
3767
+ args: { ...outputArgs },
3768
+ run({ args }) {
3769
+ runAgentSkillsInstall("gemini", !!args.json);
3770
+ }
3771
+ });
3772
+ });
3773
+
3774
+ // src/commands/setup/droid.ts
3775
+ var exports_droid = {};
3776
+ __export(exports_droid, {
3777
+ default: () => droid_default
3778
+ });
3779
+ var droid_default;
3780
+ var init_droid = __esm(() => {
3781
+ init_dist();
3782
+ init_output2();
3783
+ init_skills();
3784
+ droid_default = defineCommand2({
3785
+ meta: {
3786
+ name: "droid",
3787
+ description: "Install the Outlit skill for Droid."
3788
+ },
3789
+ args: { ...outputArgs },
3790
+ run({ args }) {
3791
+ runAgentSkillsInstall("droid", !!args.json);
3792
+ }
3793
+ });
3794
+ });
3795
+
3796
+ // src/commands/setup/opencode.ts
3797
+ var exports_opencode = {};
3798
+ __export(exports_opencode, {
3799
+ default: () => opencode_default
3800
+ });
3801
+ var opencode_default;
3802
+ var init_opencode = __esm(() => {
3803
+ init_dist();
3804
+ init_output2();
3805
+ init_skills();
3806
+ opencode_default = defineCommand2({
3807
+ meta: {
3808
+ name: "opencode",
3809
+ description: "Install the Outlit skill for OpenCode."
3810
+ },
3811
+ args: { ...outputArgs },
3812
+ run({ args }) {
3813
+ runAgentSkillsInstall("opencode", !!args.json);
3814
+ }
3815
+ });
3816
+ });
3817
+
3818
+ // src/commands/setup/pi.ts
3819
+ var exports_pi = {};
3820
+ __export(exports_pi, {
3821
+ default: () => pi_default
3822
+ });
3823
+ var pi_default;
3824
+ var init_pi = __esm(() => {
3825
+ init_dist();
3826
+ init_output2();
3827
+ init_skills();
3828
+ pi_default = defineCommand2({
3829
+ meta: {
3830
+ name: "pi",
3831
+ description: "Install the Outlit skill for Pi."
3832
+ },
3833
+ args: { ...outputArgs },
3834
+ run({ args }) {
3835
+ runAgentSkillsInstall("pi", !!args.json);
3836
+ }
3837
+ });
3838
+ });
3839
+
3840
+ // src/commands/setup/openclaw.ts
3841
+ var exports_openclaw = {};
3842
+ __export(exports_openclaw, {
3843
+ default: () => openclaw_default
3844
+ });
3845
+ var openclaw_default;
3846
+ var init_openclaw = __esm(() => {
3847
+ init_dist();
3848
+ init_output2();
3849
+ init_skills();
3850
+ openclaw_default = defineCommand2({
3851
+ meta: {
3852
+ name: "openclaw",
3853
+ description: "Install the Outlit skill for OpenClaw."
3854
+ },
3855
+ args: { ...outputArgs },
3856
+ run({ args }) {
3857
+ runAgentSkillsInstall("openclaw", !!args.json);
3858
+ }
3859
+ });
3860
+ });
3861
+
3862
+ // src/commands/setup/index.ts
3863
+ import { execFileSync as execFileSync5 } from "node:child_process";
3864
+ import { existsSync as existsSync4 } from "node:fs";
3865
+ import { homedir as homedir4 } from "node:os";
3866
+ import { join as join5 } from "node:path";
3867
+ function isCommandAvailable(cmd) {
3868
+ try {
3869
+ const whichCmd = process.platform === "win32" ? "where" : "which";
3870
+ execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
3871
+ return true;
3872
+ } catch {
3873
+ return false;
3874
+ }
3875
+ }
3876
+ function getHomeDir() {
3877
+ return process.env.HOME?.trim() || homedir4();
3878
+ }
3879
+ function detectAgents() {
3880
+ const home = getHomeDir();
3881
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join5(home, ".config");
3882
+ const detected = [];
3883
+ if (isCommandAvailable("claude"))
3884
+ detected.push("claude-code");
3885
+ if (isCommandAvailable("codex"))
3886
+ detected.push("codex");
3887
+ if (isCommandAvailable("gemini"))
3888
+ detected.push("gemini");
3889
+ if (existsSync4(join5(home, ".factory")))
3890
+ detected.push("droid");
3891
+ if (existsSync4(join5(configHome, "opencode")))
3892
+ detected.push("opencode");
3893
+ if (existsSync4(join5(home, ".pi", "agent")))
3894
+ detected.push("pi");
3895
+ if (existsSync4(join5(home, ".openclaw")) || existsSync4(join5(home, ".clawdbot")) || existsSync4(join5(home, ".moltbot"))) {
3896
+ detected.push("openclaw");
3897
+ }
3898
+ return detected;
3899
+ }
3900
+ var agentLabels, setupSubcommandNames, setup_default;
3901
+ var init_setup = __esm(() => {
3902
+ init_dist();
3903
+ init_output2();
3904
+ init_config();
3905
+ init_output();
3906
+ init_skills();
3907
+ agentLabels = {
3908
+ "claude-code": { label: "Claude Code", hint: "claude CLI found" },
3909
+ codex: { label: "Codex", hint: "codex CLI found" },
3910
+ gemini: { label: "Gemini CLI", hint: "gemini CLI found" },
3911
+ droid: { label: "Droid", hint: ".factory config found" },
3912
+ opencode: { label: "OpenCode", hint: "opencode config found" },
3913
+ pi: { label: "Pi", hint: ".pi/agent config found" },
3914
+ openclaw: { label: "OpenClaw", hint: "OpenClaw config found" }
3915
+ };
3916
+ setupSubcommandNames = new Set([
3917
+ "claude-code",
3918
+ "codex",
3919
+ "gemini",
3920
+ "droid",
3921
+ "opencode",
3922
+ "pi",
3923
+ "openclaw",
3924
+ "skills"
3925
+ ]);
3926
+ setup_default = defineCommand2({
3927
+ meta: {
3928
+ name: "setup",
3929
+ description: [
3930
+ "Install the Outlit skill for coding agents.",
3931
+ "",
3932
+ "Without a subcommand, auto-detects supported coding agents and installs `outlit` for all of them.",
3933
+ "Subcommands: claude-code, codex, gemini, droid, opencode, pi, openclaw, skills"
3934
+ ].join(`
3935
+ `)
3258
3936
  },
3259
3937
  args: {
3260
- ...authArgs,
3261
3938
  ...outputArgs,
3262
3939
  yes: {
3263
3940
  type: "boolean",
3264
- description: "Configure all detected agents without prompting."
3941
+ description: "Install for all detected coding agents without prompting."
3265
3942
  }
3266
3943
  },
3267
3944
  subCommands: {
3268
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
3269
3945
  "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),
3946
+ codex: () => Promise.resolve().then(() => (init_codex(), exports_codex)).then((m) => m.default),
3272
3947
  gemini: () => Promise.resolve().then(() => (init_gemini(), exports_gemini)).then((m) => m.default),
3948
+ droid: () => Promise.resolve().then(() => (init_droid(), exports_droid)).then((m) => m.default),
3949
+ opencode: () => Promise.resolve().then(() => (init_opencode(), exports_opencode)).then((m) => m.default),
3950
+ pi: () => Promise.resolve().then(() => (init_pi(), exports_pi)).then((m) => m.default),
3273
3951
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
3274
3952
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
3275
3953
  },
3276
- async run({ args }) {
3954
+ async run({ args, rawArgs }) {
3955
+ const setupRawArgs = rawArgs ?? [];
3956
+ const subcommandName = setupRawArgs.find((arg) => !arg.startsWith("-"));
3957
+ if (subcommandName && setupSubcommandNames.has(subcommandName)) {
3958
+ return;
3959
+ }
3277
3960
  const json = !!args.json;
3278
- const credential = requireCredential(args["api-key"], json);
3279
3961
  const detected = detectAgents();
3280
3962
  if (detected.length === 0) {
3281
3963
  if (isJsonMode(json)) {
3282
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
3964
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
3283
3965
  }
3284
- console.log("No supported agent tools detected.");
3966
+ console.log("No supported coding agents detected.");
3285
3967
  return;
3286
3968
  }
3287
3969
  if (!isJsonMode(json) && !args.yes) {
3288
- console.log("Detected agents:");
3970
+ console.log("Detected coding agents:");
3289
3971
  for (const agentId of detected) {
3290
3972
  const { label, hint } = agentLabels[agentId];
3291
3973
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
3292
3974
  }
3293
3975
  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.");
3976
+ Installing Outlit skill...`);
3311
3977
  }
3978
+ const install = runSkillsInstall({
3979
+ json,
3980
+ exitOnError: false,
3981
+ agents: detected.map(getSkillAgentId),
3982
+ skillNames: ["outlit"],
3983
+ autoConfirm: true
3984
+ });
3985
+ const configured = install.success ? detected : [];
3986
+ const failed = install.success ? [] : detected;
3312
3987
  if (isJsonMode(json)) {
3313
- return outputResult({ detected, configured, failed, skills });
3988
+ return outputResult({
3989
+ detected,
3990
+ configured,
3991
+ failed,
3992
+ runner: install.runner ?? null
3993
+ });
3314
3994
  }
3315
- if (failed.length > 0) {
3995
+ if (!install.success) {
3316
3996
  console.log(`
3317
- ${failed.length} agent(s) failed: ${failed.join(", ")}`);
3997
+ ! Outlit skill install failed: ${install.error ?? "unknown error"}`);
3998
+ console.log(" Run `outlit setup skills` to retry manually.");
3999
+ return;
3318
4000
  }
3319
4001
  console.log(`
3320
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
4002
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
3321
4003
  }
3322
4004
  });
3323
4005
  });
@@ -3325,21 +4007,16 @@ Done. ${configured.length}/${detected.length} agent(s) configured successfully.`
3325
4007
  // src/commands/doctor.ts
3326
4008
  var exports_doctor = {};
3327
4009
  __export(exports_doctor, {
3328
- default: () => doctor_default
4010
+ default: () => doctor_default,
4011
+ buildAgentChecks: () => buildAgentChecks
3329
4012
  });
3330
- import { existsSync as existsSync3 } from "node:fs";
4013
+ import { existsSync as existsSync5 } from "node:fs";
3331
4014
  import { homedir as homedir5 } from "node:os";
3332
- import { join as join7 } from "node:path";
4015
+ import { join as join6 } from "node:path";
3333
4016
  async function checkCliVersion() {
3334
4017
  const current = CLI_VERSION2;
3335
4018
  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";
4019
+ const latest = await fetchLatestCliVersion2();
3343
4020
  if (latest === current) {
3344
4021
  return { name: "CLI version", status: "pass", message: `v${current} (latest)` };
3345
4022
  }
@@ -3347,7 +4024,7 @@ async function checkCliVersion() {
3347
4024
  name: "CLI version",
3348
4025
  status: "warn",
3349
4026
  message: `v${current} installed, v${latest} available`,
3350
- detail: "Run `npm update -g @outlit/cli` to update"
4027
+ detail: `Run \`${formatUpdateCommand2()}\` to update`
3351
4028
  };
3352
4029
  } catch {
3353
4030
  return {
@@ -3434,8 +4111,42 @@ async function checkIntegrations(apiKey) {
3434
4111
  };
3435
4112
  }
3436
4113
  }
3437
- function detectAgents2() {
3438
- const detected = detectAgents();
4114
+ function getHomeDir2(options) {
4115
+ return options?.homeDir?.trim() || process.env.HOME?.trim() || homedir5();
4116
+ }
4117
+ function getSharedSkillsDir(options) {
4118
+ return join6(getHomeDir2(options), ".agents", "skills");
4119
+ }
4120
+ function getOpenClawHome(options) {
4121
+ const home = getHomeDir2(options);
4122
+ if (existsSync5(join6(home, ".openclaw")))
4123
+ return join6(home, ".openclaw");
4124
+ if (existsSync5(join6(home, ".clawdbot")))
4125
+ return join6(home, ".clawdbot");
4126
+ if (existsSync5(join6(home, ".moltbot")))
4127
+ return join6(home, ".moltbot");
4128
+ return join6(home, ".openclaw");
4129
+ }
4130
+ function getAgentSkillDir(agentId, options) {
4131
+ const home = getHomeDir2(options);
4132
+ switch (agentId) {
4133
+ case "claude-code":
4134
+ return join6(options?.claudeConfigDir?.trim() || process.env.CLAUDE_CONFIG_DIR?.trim() || join6(home, ".claude"), "skills");
4135
+ case "codex":
4136
+ return getSharedSkillsDir(options);
4137
+ case "gemini":
4138
+ return getSharedSkillsDir(options);
4139
+ case "droid":
4140
+ return join6(home, ".factory", "skills");
4141
+ case "opencode":
4142
+ return getSharedSkillsDir(options);
4143
+ case "pi":
4144
+ return join6(home, ".pi", "agent", "skills");
4145
+ case "openclaw":
4146
+ return join6(getOpenClawHome(options), "skills");
4147
+ }
4148
+ }
4149
+ function buildAgentChecks(detected = detectAgents(), options) {
3439
4150
  if (detected.length === 0) {
3440
4151
  return [
3441
4152
  {
@@ -3446,66 +4157,15 @@ function detectAgents2() {
3446
4157
  ];
3447
4158
  }
3448
4159
  const results = [];
3449
- const home = homedir5();
3450
4160
  for (const agentId of detected) {
3451
4161
  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
- }
4162
+ const hasSkill = existsSync5(join6(getAgentSkillDir(agentId, options), "outlit", "SKILL.md"));
4163
+ results.push({
4164
+ name: meta.name,
4165
+ status: hasSkill ? "pass" : "warn",
4166
+ message: hasSkill ? "Outlit skill installed" : "Installed, but Outlit skill not found",
4167
+ detail: hasSkill ? undefined : meta.missingDetail
4168
+ });
3509
4169
  }
3510
4170
  return results;
3511
4171
  }
@@ -3540,7 +4200,8 @@ var init_doctor = __esm(() => {
3540
4200
  init_config();
3541
4201
  init_output();
3542
4202
  init_tty();
3543
- init_setup2();
4203
+ init_update();
4204
+ init_setup();
3544
4205
  FAIL_SYMBOL2 = isUnicodeSupported ? String.fromCodePoint(10007) : "x";
3545
4206
  STATUS_ICONS = {
3546
4207
  pass: TICK2,
@@ -3557,73 +4218,374 @@ var init_doctor = __esm(() => {
3557
4218
  " 1. CLI version -- compares against npm registry",
3558
4219
  " 2. API key -- checks presence and format (ok_ prefix)",
3559
4220
  " 3. API validation -- makes a live test call to verify the key works",
3560
- " 4. Agent detection -- detects OpenClaw, Cursor, Claude Desktop, VS Code",
4221
+ " 4. Agent detection -- detects supported coding agents and whether the Outlit skill is installed",
3561
4222
  "",
3562
4223
  "Exit code: 0 if all checks pass or warn, 1 if any check fails.",
3563
4224
  "",
3564
- "JSON output format:",
3565
- ' { "ok": boolean, "checks": [{ "name", "status", "message", "detail?" }] }',
4225
+ "JSON output format:",
4226
+ ' { "ok": boolean, "checks": [{ "name", "status", "message", "detail?" }] }',
4227
+ "",
4228
+ "Examples:",
4229
+ " outlit doctor",
4230
+ " outlit doctor --json",
4231
+ ` outlit doctor --json | jq '.checks[] | select(.status == "fail")'`,
4232
+ "",
4233
+ "For AI agents: use outlit doctor --json to get structured diagnostics."
4234
+ ].join(`
4235
+ `)
4236
+ },
4237
+ args: { ...authArgs, ...outputArgs },
4238
+ async run({ args }) {
4239
+ const json = !!args.json;
4240
+ const checks = [];
4241
+ checks.push(await checkCliVersion());
4242
+ const credential = resolveApiKey(args["api-key"]);
4243
+ checks.push(checkApiKeyPresence(credential));
4244
+ if (credential) {
4245
+ const apiCheck = await validateApiKey(credential.key);
4246
+ checks.push(apiCheck);
4247
+ if (apiCheck.status !== "fail") {
4248
+ checks.push(await checkIntegrations(credential.key));
4249
+ }
4250
+ } else {
4251
+ checks.push({
4252
+ name: "API validation",
4253
+ status: "fail",
4254
+ message: "Skipped -- no API key found"
4255
+ });
4256
+ }
4257
+ checks.push(...buildAgentChecks());
4258
+ const hasFail = checks.some((c) => c.status === "fail");
4259
+ if (isJsonMode(json)) {
4260
+ outputResult({ ok: !hasFail, checks });
4261
+ } else {
4262
+ printChecks(checks);
4263
+ }
4264
+ if (hasFail)
4265
+ process.exit(1);
4266
+ }
4267
+ });
4268
+ agentChecks = {
4269
+ "claude-code": {
4270
+ name: "Claude Code",
4271
+ missingDetail: "Run `outlit setup claude-code` to install the Outlit skill"
4272
+ },
4273
+ codex: {
4274
+ name: "Codex",
4275
+ missingDetail: "Run `outlit setup codex` to install the Outlit skill"
4276
+ },
4277
+ gemini: {
4278
+ name: "Gemini CLI",
4279
+ missingDetail: "Run `outlit setup gemini` to install the Outlit skill"
4280
+ },
4281
+ droid: {
4282
+ name: "Droid",
4283
+ missingDetail: "Run `outlit setup droid` to install the Outlit skill"
4284
+ },
4285
+ opencode: {
4286
+ name: "OpenCode",
4287
+ missingDetail: "Run `outlit setup opencode` to install the Outlit skill"
4288
+ },
4289
+ pi: {
4290
+ name: "Pi",
4291
+ missingDetail: "Run `outlit setup pi` to install the Outlit skill"
4292
+ },
4293
+ openclaw: {
4294
+ name: "OpenClaw",
4295
+ missingDetail: "Run `outlit setup openclaw` to install the Outlit skill"
4296
+ }
4297
+ };
4298
+ });
4299
+
4300
+ // src/commands/upgrade.ts
4301
+ var exports_upgrade = {};
4302
+ __export(exports_upgrade, {
4303
+ default: () => upgrade_default
4304
+ });
4305
+ var upgrade_default;
4306
+ var init_upgrade = __esm(() => {
4307
+ init_dist();
4308
+ init_config();
4309
+ init_output();
4310
+ init_update();
4311
+ upgrade_default = defineCommand2({
4312
+ meta: {
4313
+ name: "upgrade",
4314
+ description: [
4315
+ "Upgrade the Outlit CLI using the same package manager it was installed with.",
4316
+ "",
4317
+ "Checks npm for the latest published version first.",
4318
+ "If the current version is already latest, no install command is run.",
4319
+ "",
4320
+ "Examples:",
4321
+ " outlit upgrade"
4322
+ ].join(`
4323
+ `)
4324
+ },
4325
+ async run() {
4326
+ const upgradeCommand = getUpgradeCommand();
4327
+ if (!upgradeCommand) {
4328
+ return outputError({
4329
+ 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`.",
4330
+ code: "unknown_installer"
4331
+ }, false);
4332
+ }
4333
+ let latestVersion;
4334
+ try {
4335
+ latestVersion = await fetchLatestCliVersion2();
4336
+ } catch {
4337
+ return outputError({
4338
+ message: "Could not check for CLI updates. Try again later or update manually.",
4339
+ code: "update_check_failed"
4340
+ }, false);
4341
+ }
4342
+ if (compareVersions2(CLI_VERSION2, latestVersion) >= 0) {
4343
+ console.log(`Outlit CLI is already up to date (v${CLI_VERSION2})`);
4344
+ return;
4345
+ }
4346
+ try {
4347
+ runUpgradeCommand(upgradeCommand);
4348
+ } catch (err) {
4349
+ return outputError({
4350
+ message: errorMessage(err, `Failed to run ${upgradeCommand.displayCommand}`),
4351
+ code: "upgrade_failed"
4352
+ }, false);
4353
+ }
4354
+ }
4355
+ });
4356
+ });
4357
+
4358
+ // src/commands/facts/list.ts
4359
+ var exports_list3 = {};
4360
+ __export(exports_list3, {
4361
+ default: () => list_default3
4362
+ });
4363
+ function parseCsvArg(value) {
4364
+ if (!value)
4365
+ return;
4366
+ const items = splitCsv(value).map((item) => item.trim()).filter(Boolean);
4367
+ return items.length > 0 ? items : undefined;
4368
+ }
4369
+ function invalidValues(values, allowed) {
4370
+ if (!values)
4371
+ return [];
4372
+ return values.filter((value) => !allowed.includes(value));
4373
+ }
4374
+ var list_default3;
4375
+ var init_list3 = __esm(() => {
4376
+ init_dist4();
4377
+ init_dist();
4378
+ init_auth();
4379
+ init_output2();
4380
+ init_pagination();
4381
+ init_api();
4382
+ init_config();
4383
+ init_output();
4384
+ list_default3 = defineCommand2({
4385
+ meta: {
4386
+ name: "list",
4387
+ description: [
4388
+ "List structured facts for a customer.",
4389
+ "",
4390
+ "Filter by fact status, source type, or occurrence date range.",
4391
+ "",
4392
+ "Examples:",
4393
+ " outlit facts list acme.com",
4394
+ " outlit facts list acme.com --status ACTIVE",
4395
+ " outlit facts list acme.com --fact-types CHURN_RISK,EXPANSION",
4396
+ " outlit facts list acme.com --source-types CALL,EMAIL --after 2025-01-01T00:00:00Z",
4397
+ " outlit facts list acme.com --limit 50 --json",
4398
+ "",
4399
+ `Statuses: ${customerFactStatuses.join(", ")}`,
4400
+ `Source types: ${customerSourceTypes.join(", ")}`,
4401
+ `Fact categories: ${customerFactCategories.join(", ")}`,
4402
+ "",
4403
+ AGENT_JSON_HINT
4404
+ ].join(`
4405
+ `)
4406
+ },
4407
+ args: {
4408
+ ...authArgs,
4409
+ ...outputArgs,
4410
+ ...paginationArgs,
4411
+ customer: {
4412
+ type: "positional",
4413
+ description: "Customer UUID or domain to retrieve facts for",
4414
+ required: true
4415
+ },
4416
+ status: {
4417
+ type: "string",
4418
+ description: `Comma-separated fact statuses (${customerFactStatuses.join(", ")})`
4419
+ },
4420
+ "source-types": {
4421
+ type: "string",
4422
+ description: `Comma-separated generic source type filter (${customerSourceTypes.join(", ")})`
4423
+ },
4424
+ "fact-types": {
4425
+ type: "string",
4426
+ description: "Comma-separated customer-memory fact type filter, such as CHURN_RISK, EXPANSION, or SENTIMENT"
4427
+ },
4428
+ "fact-categories": {
4429
+ type: "string",
4430
+ description: `Comma-separated fact category filter (${customerFactCategories.join(", ")})`
4431
+ },
4432
+ after: {
4433
+ type: "string",
4434
+ description: "Filter to facts occurring after this ISO 8601 datetime"
4435
+ },
4436
+ before: {
4437
+ type: "string",
4438
+ description: "Filter to facts occurring before this ISO 8601 datetime"
4439
+ }
4440
+ },
4441
+ async run({ args }) {
4442
+ const json = !!args.json;
4443
+ const statuses = parseCsvArg(args.status);
4444
+ const sourceTypes = parseCsvArg(args["source-types"]);
4445
+ const factTypes = parseCsvArg(args["fact-types"]);
4446
+ const factCategories = parseCsvArg(args["fact-categories"]);
4447
+ const invalidStatuses = invalidValues(statuses, customerFactStatuses);
4448
+ if (invalidStatuses.length > 0) {
4449
+ return outputError({
4450
+ message: `Unknown fact statuses: ${invalidStatuses.join(", ")}. Allowed: ${customerFactStatuses.join(", ")}`,
4451
+ code: "invalid_input"
4452
+ }, json);
4453
+ }
4454
+ const invalidSourceTypes = invalidValues(sourceTypes, customerSourceTypes);
4455
+ if (invalidSourceTypes.length > 0) {
4456
+ return outputError({
4457
+ message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
4458
+ code: "invalid_input"
4459
+ }, json);
4460
+ }
4461
+ const invalidFactTypes = invalidValues(factTypes, customerFactTypes);
4462
+ if (invalidFactTypes.length > 0) {
4463
+ const anomalyFactTypes = invalidFactTypes.filter((value) => unsupportedCustomerFactTypes.includes(value));
4464
+ const unknownFactTypes = invalidFactTypes.filter((value) => !unsupportedCustomerFactTypes.includes(value));
4465
+ const messageParts = [];
4466
+ if (anomalyFactTypes.length > 0) {
4467
+ messageParts.push(`Anomaly detector fact types are not supported as public filters: ${anomalyFactTypes.join(", ")}. Use customer-memory fact types such as CHURN_RISK, EXPANSION, SENTIMENT, or PRODUCT_USAGE.`);
4468
+ }
4469
+ if (unknownFactTypes.length > 0) {
4470
+ messageParts.push(`Unknown fact types: ${unknownFactTypes.join(", ")}. Allowed: ${customerFactTypes.join(", ")}`);
4471
+ }
4472
+ return outputError({
4473
+ message: messageParts.join(" "),
4474
+ code: "invalid_input"
4475
+ }, json);
4476
+ }
4477
+ const invalidFactCategories = invalidValues(factCategories, customerFactCategories);
4478
+ if (invalidFactCategories.length > 0) {
4479
+ return outputError({
4480
+ message: `Unsupported fact categories: ${invalidFactCategories.join(", ")}. Allowed: ${customerFactCategories.join(", ")}`,
4481
+ code: "invalid_input"
4482
+ }, json);
4483
+ }
4484
+ const afterDate = args.after ? new Date(args.after) : null;
4485
+ const beforeDate = args.before ? new Date(args.before) : null;
4486
+ if (afterDate && Number.isNaN(afterDate.getTime())) {
4487
+ return outputError({
4488
+ message: "--after must be a valid ISO 8601 datetime",
4489
+ code: "invalid_input"
4490
+ }, json);
4491
+ }
4492
+ if (beforeDate && Number.isNaN(beforeDate.getTime())) {
4493
+ return outputError({
4494
+ message: "--before must be a valid ISO 8601 datetime",
4495
+ code: "invalid_input"
4496
+ }, json);
4497
+ }
4498
+ if (afterDate && beforeDate && afterDate.getTime() > beforeDate.getTime()) {
4499
+ return outputError({
4500
+ message: "--after must be before or equal to --before",
4501
+ code: "invalid_input"
4502
+ }, json);
4503
+ }
4504
+ const client = await getClientOrExit(args["api-key"], json);
4505
+ const params = {
4506
+ customer: args.customer
4507
+ };
4508
+ if (statuses)
4509
+ params.status = statuses;
4510
+ if (sourceTypes)
4511
+ params.sourceTypes = sourceTypes;
4512
+ if (factTypes)
4513
+ params.factTypes = factTypes;
4514
+ if (factCategories)
4515
+ params.factCategories = factCategories;
4516
+ if (args.after)
4517
+ params.after = args.after;
4518
+ if (args.before)
4519
+ params.before = args.before;
4520
+ applyPagination(params, args, json);
4521
+ return runTool(client, customerToolContracts.outlit_list_facts.toolName, params, json);
4522
+ }
4523
+ });
4524
+ });
4525
+
4526
+ // src/commands/facts/get.ts
4527
+ var exports_get2 = {};
4528
+ __export(exports_get2, {
4529
+ default: () => get_default2
4530
+ });
4531
+ function parseCsvArg2(value) {
4532
+ if (!value)
4533
+ return;
4534
+ const items = splitCsv(value).map((item) => item.trim()).filter(Boolean);
4535
+ return items.length > 0 ? items : undefined;
4536
+ }
4537
+ var get_default2;
4538
+ var init_get2 = __esm(() => {
4539
+ init_dist4();
4540
+ init_dist();
4541
+ init_auth();
4542
+ init_output2();
4543
+ init_api();
4544
+ init_config();
4545
+ get_default2 = defineCommand2({
4546
+ meta: {
4547
+ name: "get",
4548
+ description: [
4549
+ "Get one exact fact by ID.",
4550
+ "",
4551
+ "Use --include evidence to request best-effort evidence expansion.",
3566
4552
  "",
3567
4553
  "Examples:",
3568
- " outlit doctor",
3569
- " outlit doctor --json",
3570
- ` outlit doctor --json | jq '.checks[] | select(.status == "fail")'`,
4554
+ " outlit facts get --fact-id fact_123",
4555
+ " outlit facts get --fact-id fact_123 --include evidence",
3571
4556
  "",
3572
- "For AI agents: use outlit doctor --json to get structured diagnostics."
4557
+ AGENT_JSON_HINT
3573
4558
  ].join(`
3574
4559
  `)
3575
4560
  },
3576
- args: { ...authArgs, ...outputArgs },
4561
+ args: {
4562
+ ...authArgs,
4563
+ ...outputArgs,
4564
+ "fact-id": {
4565
+ type: "string",
4566
+ description: "Fact ID to fetch",
4567
+ required: true
4568
+ },
4569
+ include: {
4570
+ type: "string",
4571
+ description: "Comma-separated best-effort expansions (for example: evidence)"
4572
+ }
4573
+ },
3577
4574
  async run({ args }) {
3578
4575
  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);
4576
+ const client = await getClientOrExit(args["api-key"], json);
4577
+ const params = {
4578
+ factId: args["fact-id"]
4579
+ };
4580
+ const include = parseCsvArg2(args.include);
4581
+ if (include)
4582
+ params.include = include;
4583
+ return runTool(client, customerToolContracts.outlit_get_fact.toolName, params, json);
3605
4584
  }
3606
4585
  });
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
4586
  });
3625
4587
 
3626
- // src/commands/facts.ts
4588
+ // src/commands/facts/index.ts
3627
4589
  var exports_facts = {};
3628
4590
  __export(exports_facts, {
3629
4591
  default: () => facts_default
@@ -3631,23 +4593,52 @@ __export(exports_facts, {
3631
4593
  var facts_default;
3632
4594
  var init_facts = __esm(() => {
3633
4595
  init_dist();
3634
- init_auth();
3635
4596
  init_output2();
3636
- init_pagination();
3637
- init_api();
3638
4597
  facts_default = defineCommand2({
3639
4598
  meta: {
3640
4599
  name: "facts",
3641
4600
  description: [
3642
- "Retrieve facts for a customer.",
4601
+ "Query structured customer facts.",
3643
4602
  "",
3644
- "Returns a list of facts (signals, events, and derived insights) for the",
3645
- "specified customer within the given timeframe.",
4603
+ "Subcommands:",
4604
+ " list -- list facts for a customer with filters",
4605
+ " get -- fetch one exact fact by id",
4606
+ "",
4607
+ AGENT_JSON_HINT
4608
+ ].join(`
4609
+ `)
4610
+ },
4611
+ subCommands: {
4612
+ list: () => Promise.resolve().then(() => (init_list3(), exports_list3)).then((m) => m.default),
4613
+ get: () => Promise.resolve().then(() => (init_get2(), exports_get2)).then((m) => m.default)
4614
+ }
4615
+ });
4616
+ });
4617
+
4618
+ // src/commands/sources/get.ts
4619
+ var exports_get3 = {};
4620
+ __export(exports_get3, {
4621
+ default: () => get_default3
4622
+ });
4623
+ var get_default3;
4624
+ var init_get3 = __esm(() => {
4625
+ init_dist4();
4626
+ init_dist();
4627
+ init_auth();
4628
+ init_output2();
4629
+ init_api();
4630
+ init_output();
4631
+ get_default3 = defineCommand2({
4632
+ meta: {
4633
+ name: "get",
4634
+ description: [
4635
+ "Get one exact source by source type and source id.",
3646
4636
  "",
3647
4637
  "Examples:",
3648
- " outlit facts acme.com",
3649
- " outlit facts acme.com --timeframe 90d",
3650
- " outlit facts acme.com --limit 50 --json",
4638
+ " outlit sources get --source-type CALL --source-id call_123",
4639
+ " outlit sources get --source-type SUPPORT_TICKET --source-id ticket_456 --json",
4640
+ "",
4641
+ `Source types: ${customerSourceTypes.join(", ")}`,
3651
4642
  "",
3652
4643
  AGENT_JSON_HINT
3653
4644
  ].join(`
@@ -3656,27 +4647,58 @@ var init_facts = __esm(() => {
3656
4647
  args: {
3657
4648
  ...authArgs,
3658
4649
  ...outputArgs,
3659
- ...paginationArgs,
3660
- customer: {
3661
- type: "positional",
3662
- description: "Customer UUID or domain to retrieve facts for",
4650
+ "source-type": {
4651
+ type: "string",
4652
+ description: "Canonical source type",
3663
4653
  required: true
3664
4654
  },
3665
- timeframe: {
4655
+ "source-id": {
3666
4656
  type: "string",
3667
- description: "Lookback window (e.g. 7d, 30d, 90d). Default: 30d.",
3668
- default: "30d"
4657
+ description: "Exact source id",
4658
+ required: true
3669
4659
  }
3670
4660
  },
3671
4661
  async run({ args }) {
3672
4662
  const json = !!args.json;
4663
+ if (!customerSourceTypes.includes(args["source-type"])) {
4664
+ return outputError({
4665
+ message: `--source-type must be one of ${customerSourceTypes.join(", ")}`,
4666
+ code: "invalid_input"
4667
+ }, json);
4668
+ }
3673
4669
  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);
4670
+ return runTool(client, customerToolContracts.outlit_get_source.toolName, {
4671
+ sourceType: args["source-type"],
4672
+ sourceId: args["source-id"]
4673
+ }, json);
4674
+ }
4675
+ });
4676
+ });
4677
+
4678
+ // src/commands/sources/index.ts
4679
+ var exports_sources = {};
4680
+ __export(exports_sources, {
4681
+ default: () => sources_default
4682
+ });
4683
+ var sources_default;
4684
+ var init_sources = __esm(() => {
4685
+ init_dist();
4686
+ init_output2();
4687
+ sources_default = defineCommand2({
4688
+ meta: {
4689
+ name: "sources",
4690
+ description: [
4691
+ "Fetch concrete customer sources by type and id.",
4692
+ "",
4693
+ "Subcommands:",
4694
+ " get -- fetch one exact source by sourceType and sourceId",
4695
+ "",
4696
+ AGENT_JSON_HINT
4697
+ ].join(`
4698
+ `)
4699
+ },
4700
+ subCommands: {
4701
+ get: () => Promise.resolve().then(() => (init_get3(), exports_get3)).then((m) => m.default)
3680
4702
  }
3681
4703
  });
3682
4704
  });
@@ -3688,28 +4710,28 @@ __export(exports_search, {
3688
4710
  });
3689
4711
  var search_default;
3690
4712
  var init_search = __esm(() => {
4713
+ init_dist4();
3691
4714
  init_dist();
3692
4715
  init_auth();
3693
4716
  init_output2();
3694
4717
  init_api();
4718
+ init_config();
3695
4719
  init_output();
3696
4720
  search_default = defineCommand2({
3697
4721
  meta: {
3698
4722
  name: "search",
3699
4723
  description: [
3700
- "Search customer context using natural language or direct source lookup.",
4724
+ "Search customer context using natural language.",
3701
4725
  "",
3702
- "Performs a semantic search over customer facts, emails, and transcripts.",
4726
+ "Performs a semantic search over grouped source and fact results.",
3703
4727
  "Optionally scope to a specific customer with --customer.",
3704
- "Use --source-type and --source-id for direct source lookup (query becomes optional).",
3705
4728
  "",
3706
4729
  "Examples:",
3707
4730
  " outlit search 'pricing objections last quarter'",
3708
4731
  " outlit search 'churn risk signals' --customer acme.com",
3709
4732
  " 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",
4733
+ " outlit search 'support escalations' --after 2025-01-01T00:00:00Z --before 2025-03-31T23:59:59Z",
4734
+ " outlit search 'onboarding issues' --source-types CALL,EMAIL",
3713
4735
  "",
3714
4736
  AGENT_JSON_HINT
3715
4737
  ].join(`
@@ -3720,8 +4742,8 @@ var init_search = __esm(() => {
3720
4742
  ...outputArgs,
3721
4743
  query: {
3722
4744
  type: "positional",
3723
- description: "Natural language search query. Optional when --source-type and --source-id are provided.",
3724
- required: false
4745
+ description: "Natural language search query",
4746
+ required: true
3725
4747
  },
3726
4748
  customer: {
3727
4749
  type: "string",
@@ -3729,75 +4751,51 @@ var init_search = __esm(() => {
3729
4751
  },
3730
4752
  "top-k": {
3731
4753
  type: "string",
3732
- description: "Maximum number of results to return (1–50). Default: 20.",
3733
- default: "20"
4754
+ description: "Maximum number of results to return (1–50). Default: 20."
3734
4755
  },
3735
4756
  after: {
3736
4757
  type: "string",
3737
- description: "Filter to events occurring after this date (ISO 8601, e.g. 2025-01-01)"
4758
+ description: "Filter to events occurring after this datetime (ISO 8601, e.g. 2025-01-01T00:00:00Z)"
3738
4759
  },
3739
4760
  before: {
3740
4761
  type: "string",
3741
- description: "Filter to events occurring before this date (ISO 8601, e.g. 2025-03-31)"
4762
+ description: "Filter to events occurring before this datetime (ISO 8601, e.g. 2025-03-31T23:59:59Z)"
3742
4763
  },
3743
4764
  "source-types": {
3744
4765
  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)"
4766
+ description: `Comma-separated generic source type filter (${customerSourceTypes.join(", ")})`
3754
4767
  }
3755
4768
  },
3756
4769
  async run({ args }) {
3757
4770
  const json = !!args.json;
3758
- const topK = Number(args["top-k"]);
3759
- if (!Number.isFinite(topK) || topK < 1 || topK > 50) {
4771
+ const topK = args["top-k"] ? Number(args["top-k"]) : undefined;
4772
+ if (topK !== undefined && (!Number.isFinite(topK) || !Number.isInteger(topK) || topK < 1 || topK > 50)) {
3760
4773
  return outputError({ message: "--top-k must be an integer between 1 and 50", code: "invalid_input" }, json);
3761
4774
  }
3762
- const sourceType = args["source-type"];
3763
- const sourceId = args["source-id"];
3764
- const sourceTypes = args["source-types"];
3765
- if (sourceType && !sourceId || !sourceType && sourceId) {
4775
+ const sourceTypes = args["source-types"] ? splitCsv(args["source-types"]).map((item) => item.trim()).filter(Boolean) : undefined;
4776
+ const invalidSourceTypes = sourceTypes?.filter((value) => !customerSourceTypes.includes(value));
4777
+ if (invalidSourceTypes && invalidSourceTypes.length > 0) {
3766
4778
  return outputError({
3767
- message: "--source-type and --source-id must be provided together",
4779
+ message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
3768
4780
  code: "invalid_input"
3769
4781
  }, json);
3770
4782
  }
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) {
4783
+ const resolved = resolveCustomerContextSearchInput({
4784
+ query: args.query,
4785
+ customer: args.customer,
4786
+ topK,
4787
+ after: args.after,
4788
+ before: args.before,
4789
+ sourceTypes
4790
+ });
4791
+ if (!resolved.ok) {
3778
4792
  return outputError({
3779
- message: "--source-types cannot be combined with --source-type/--source-id",
4793
+ message: resolved.message,
3780
4794
  code: "invalid_input"
3781
4795
  }, json);
3782
4796
  }
3783
4797
  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);
4798
+ return runTool(client, customerToolContracts.outlit_search_customer_context.toolName, resolved.request, json);
3801
4799
  }
3802
4800
  });
3803
4801
  });
@@ -3807,9 +4805,10 @@ var exports_sql = {};
3807
4805
  __export(exports_sql, {
3808
4806
  default: () => sql_default
3809
4807
  });
3810
- import { readFileSync as readFileSync2 } from "node:fs";
4808
+ import { readFileSync as readFileSync4 } from "node:fs";
3811
4809
  var sql_default;
3812
4810
  var init_sql = __esm(() => {
4811
+ init_dist4();
3813
4812
  init_dist();
3814
4813
  init_auth();
3815
4814
  init_output2();
@@ -3824,7 +4823,7 @@ var init_sql = __esm(() => {
3824
4823
  "Provide the query as a positional argument or via --query-file.",
3825
4824
  "When both are provided, --query-file takes precedence.",
3826
4825
  "",
3827
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4826
+ `Available tables: ${schemaTables.join(", ")}`,
3828
4827
  "",
3829
4828
  "Examples:",
3830
4829
  " outlit sql 'SELECT * FROM events LIMIT 10'",
@@ -3859,7 +4858,7 @@ var init_sql = __esm(() => {
3859
4858
  let sql;
3860
4859
  if (args["query-file"]) {
3861
4860
  try {
3862
- sql = readFileSync2(args["query-file"], "utf-8");
4861
+ sql = readFileSync4(args["query-file"], "utf-8");
3863
4862
  } catch (err) {
3864
4863
  return outputError({
3865
4864
  message: `Cannot read file: ${errorMessage(err, "unknown error")}`,
@@ -3875,7 +4874,7 @@ var init_sql = __esm(() => {
3875
4874
  if (!Number.isFinite(limit) || limit <= 0) {
3876
4875
  return outputError({ message: "--limit must be a positive number", code: "invalid_input" }, json);
3877
4876
  }
3878
- return runTool(client, "outlit_query", { sql, limit }, json);
4877
+ return runTool(client, customerToolContracts.outlit_query.toolName, { sql, limit }, json);
3879
4878
  }
3880
4879
  });
3881
4880
  });
@@ -3887,6 +4886,7 @@ __export(exports_schema, {
3887
4886
  });
3888
4887
  var schema_default;
3889
4888
  var init_schema = __esm(() => {
4889
+ init_dist4();
3890
4890
  init_dist();
3891
4891
  init_auth();
3892
4892
  init_output2();
@@ -3900,7 +4900,7 @@ var init_schema = __esm(() => {
3900
4900
  "Without a table name, returns the full schema for all tables.",
3901
4901
  "With a table name, returns detailed column info for that table.",
3902
4902
  "",
3903
- "Available tables: events, customer_dimensions, user_dimensions, mrr_snapshots",
4903
+ `Available tables: ${schemaTables.join(", ")}`,
3904
4904
  "",
3905
4905
  "Examples:",
3906
4906
  " outlit schema",
@@ -3916,7 +4916,7 @@ var init_schema = __esm(() => {
3916
4916
  ...outputArgs,
3917
4917
  table: {
3918
4918
  type: "positional",
3919
- description: "Table to describe (events, customer_dimensions, user_dimensions, mrr_snapshots). Optional.",
4919
+ description: `Table to describe (${schemaTables.join(", ")}). Optional.`,
3920
4920
  required: false
3921
4921
  }
3922
4922
  },
@@ -3926,7 +4926,7 @@ var init_schema = __esm(() => {
3926
4926
  const params = {};
3927
4927
  if (args.table)
3928
4928
  params.table = args.table;
3929
- return runTool(client, "outlit_schema", params, json);
4929
+ return runTool(client, customerToolContracts.outlit_schema.toolName, params, json);
3930
4930
  }
3931
4931
  });
3932
4932
  });
@@ -4022,17 +5022,17 @@ var init_providers = __esm(() => {
4022
5022
  });
4023
5023
 
4024
5024
  // src/commands/integrations/list.ts
4025
- var exports_list3 = {};
4026
- __export(exports_list3, {
4027
- default: () => list_default3
5025
+ var exports_list4 = {};
5026
+ __export(exports_list4, {
5027
+ default: () => list_default4
4028
5028
  });
4029
- var list_default3;
4030
- var init_list3 = __esm(() => {
5029
+ var list_default4;
5030
+ var init_list4 = __esm(() => {
4031
5031
  init_dist();
4032
5032
  init_auth();
4033
5033
  init_output2();
4034
5034
  init_api();
4035
- list_default3 = defineCommand2({
5035
+ list_default4 = defineCommand2({
4036
5036
  meta: {
4037
5037
  name: "list",
4038
5038
  description: [
@@ -4486,7 +5486,7 @@ var init_integrations = __esm(() => {
4486
5486
  `)
4487
5487
  },
4488
5488
  subCommands: {
4489
- list: () => Promise.resolve().then(() => (init_list3(), exports_list3)).then((m) => m.default),
5489
+ list: () => Promise.resolve().then(() => (init_list4(), exports_list4)).then((m) => m.default),
4490
5490
  add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((m) => m.default),
4491
5491
  remove: () => Promise.resolve().then(() => (init_remove(), exports_remove)).then((m) => m.default),
4492
5492
  status: () => Promise.resolve().then(() => (init_status2(), exports_status2)).then((m) => m.default)
@@ -4741,9 +5741,7 @@ var init_completions = __esm(() => {
4741
5741
  { name: "--billing-status", desc: "Filter by billing status" },
4742
5742
  { name: "--mrr-above", desc: "MRR above threshold (cents)" },
4743
5743
  { 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" }
5744
+ { name: "--search", desc: "Search name or domain" }
4747
5745
  ]
4748
5746
  },
4749
5747
  {
@@ -4790,7 +5788,45 @@ var init_completions = __esm(() => {
4790
5788
  {
4791
5789
  name: "facts",
4792
5790
  desc: "Get customer facts",
4793
- flags: [...PAGINATED, { name: "--timeframe", desc: "Lookback window (7d, 30d, 90d)" }]
5791
+ subs: [
5792
+ {
5793
+ name: "list",
5794
+ desc: "List customer facts",
5795
+ flags: [
5796
+ ...PAGINATED,
5797
+ { name: "--status", desc: "Filter by fact status" },
5798
+ { name: "--source-types", desc: "Filter by source types" },
5799
+ { name: "--fact-types", desc: "Filter by fact types" },
5800
+ { name: "--fact-categories", desc: "Filter by fact categories" },
5801
+ { name: "--after", desc: "Facts after date (ISO 8601)" },
5802
+ { name: "--before", desc: "Facts before date (ISO 8601)" }
5803
+ ]
5804
+ },
5805
+ {
5806
+ name: "get",
5807
+ desc: "Get a single fact by ID",
5808
+ flags: [
5809
+ ...COMMON,
5810
+ { name: "--fact-id", desc: "Fact ID to fetch" },
5811
+ { name: "--include", desc: "Best-effort expansions" }
5812
+ ]
5813
+ }
5814
+ ]
5815
+ },
5816
+ {
5817
+ name: "sources",
5818
+ desc: "Get a concrete source by type and id",
5819
+ subs: [
5820
+ {
5821
+ name: "get",
5822
+ desc: "Get one exact source record",
5823
+ flags: [
5824
+ ...COMMON,
5825
+ { name: "--source-type", desc: "Canonical source type" },
5826
+ { name: "--source-id", desc: "Exact source ID" }
5827
+ ]
5828
+ }
5829
+ ]
4794
5830
  },
4795
5831
  {
4796
5832
  name: "search",
@@ -4800,7 +5836,8 @@ var init_completions = __esm(() => {
4800
5836
  { name: "--customer", desc: "Scope to customer (UUID or domain)" },
4801
5837
  { name: "--top-k", desc: "Max results" },
4802
5838
  { name: "--after", desc: "Events after date (ISO 8601)" },
4803
- { name: "--before", desc: "Events before date (ISO 8601)" }
5839
+ { name: "--before", desc: "Events before date (ISO 8601)" },
5840
+ { name: "--source-types", desc: "Broad source type filter" }
4804
5841
  ]
4805
5842
  },
4806
5843
  {
@@ -4837,17 +5874,20 @@ var init_completions = __esm(() => {
4837
5874
  },
4838
5875
  {
4839
5876
  name: "setup",
4840
- desc: "Configure AI agent tools",
4841
- flags: [...COMMON, { name: "--yes", desc: "Skip prompts" }],
5877
+ desc: "Install Outlit skills for coding agents",
5878
+ flags: [JSON_F, { name: "--yes", desc: "Skip prompts" }],
4842
5879
  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] }
5880
+ { name: "claude-code", desc: "Install the Outlit skill for Claude Code", flags: [JSON_F] },
5881
+ { name: "codex", desc: "Install the Outlit skill for Codex", flags: [JSON_F] },
5882
+ { name: "gemini", desc: "Install the Outlit skill for Gemini CLI", flags: [JSON_F] },
5883
+ { name: "droid", desc: "Install the Outlit skill for Droid", flags: [JSON_F] },
5884
+ { name: "opencode", desc: "Install the Outlit skill for OpenCode", flags: [JSON_F] },
5885
+ { name: "pi", desc: "Install the Outlit skill for Pi", flags: [JSON_F] },
5886
+ { name: "openclaw", desc: "Install the Outlit skill for OpenClaw", flags: [JSON_F] },
5887
+ { name: "skills", desc: "Launch the interactive Outlit skills installer", flags: [JSON_F] }
4849
5888
  ]
4850
5889
  },
5890
+ { name: "upgrade", desc: "Upgrade the CLI", flags: [] },
4851
5891
  { name: "doctor", desc: "Diagnose environment", flags: [...COMMON] },
4852
5892
  { name: "completions", desc: "Generate shell completions", flags: [] }
4853
5893
  ];
@@ -4900,142 +5940,149 @@ var init_completions = __esm(() => {
4900
5940
  var exports_setup = {};
4901
5941
  __export(exports_setup, {
4902
5942
  isCommandAvailable: () => isCommandAvailable2,
4903
- detectAgents: () => detectAgents3,
5943
+ detectAgents: () => detectAgents2,
4904
5944
  default: () => setup_default2
4905
5945
  });
4906
- import { execFileSync as execFileSync5 } from "node:child_process";
4907
- import { existsSync as existsSync4 } from "node:fs";
5946
+ import { execFileSync as execFileSync6 } from "node:child_process";
5947
+ import { existsSync as existsSync6 } from "node:fs";
4908
5948
  import { homedir as homedir6 } from "node:os";
4909
- import { join as join8 } from "node:path";
5949
+ import { join as join7 } from "node:path";
4910
5950
  function isCommandAvailable2(cmd) {
4911
5951
  try {
4912
5952
  const whichCmd = process.platform === "win32" ? "where" : "which";
4913
- execFileSync5(whichCmd, [cmd], { stdio: "ignore" });
5953
+ execFileSync6(whichCmd, [cmd], { stdio: "ignore" });
4914
5954
  return true;
4915
5955
  } catch {
4916
5956
  return false;
4917
5957
  }
4918
5958
  }
4919
- function detectAgents3() {
4920
- const home = homedir6();
5959
+ function getHomeDir3() {
5960
+ return process.env.HOME?.trim() || homedir6();
5961
+ }
5962
+ function detectAgents2() {
5963
+ const home = getHomeDir3();
5964
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join7(home, ".config");
4921
5965
  const detected = [];
4922
- if (existsSync4(join8(home, ".cursor")))
4923
- detected.push("cursor");
4924
5966
  if (isCommandAvailable2("claude"))
4925
5967
  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");
5968
+ if (isCommandAvailable2("codex"))
5969
+ detected.push("codex");
4930
5970
  if (isCommandAvailable2("gemini"))
4931
5971
  detected.push("gemini");
4932
- if (existsSync4(join8(home, ".openclaw", "skills")))
5972
+ if (existsSync6(join7(home, ".factory")))
5973
+ detected.push("droid");
5974
+ if (existsSync6(join7(configHome, "opencode")))
5975
+ detected.push("opencode");
5976
+ if (existsSync6(join7(home, ".pi", "agent")))
5977
+ detected.push("pi");
5978
+ if (existsSync6(join7(home, ".openclaw")) || existsSync6(join7(home, ".clawdbot")) || existsSync6(join7(home, ".moltbot"))) {
4933
5979
  detected.push("openclaw");
5980
+ }
4934
5981
  return detected;
4935
5982
  }
4936
- var agentLabels2, configurators2, setup_default2;
4937
- var init_setup3 = __esm(() => {
5983
+ var agentLabels2, setupSubcommandNames2, setup_default2;
5984
+ var init_setup2 = __esm(() => {
4938
5985
  init_dist();
4939
- init_auth();
4940
5986
  init_output2();
4941
5987
  init_config();
4942
5988
  init_output();
4943
- init_claude_code();
4944
- init_claude_desktop();
4945
- init_cursor();
4946
- init_gemini();
4947
- init_openclaw();
4948
5989
  init_skills();
4949
- init_vscode();
4950
5990
  agentLabels2 = {
4951
- cursor: { label: "Cursor", hint: "~/.cursor/" },
4952
5991
  "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" },
5992
+ codex: { label: "Codex", hint: "codex CLI found" },
4955
5993
  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
5994
+ droid: { label: "Droid", hint: ".factory config found" },
5995
+ opencode: { label: "OpenCode", hint: "opencode config found" },
5996
+ pi: { label: "Pi", hint: ".pi/agent config found" },
5997
+ openclaw: { label: "OpenClaw", hint: "OpenClaw config found" }
4965
5998
  };
5999
+ setupSubcommandNames2 = new Set([
6000
+ "claude-code",
6001
+ "codex",
6002
+ "gemini",
6003
+ "droid",
6004
+ "opencode",
6005
+ "pi",
6006
+ "openclaw",
6007
+ "skills"
6008
+ ]);
4966
6009
  setup_default2 = defineCommand2({
4967
6010
  meta: {
4968
6011
  name: "setup",
4969
6012
  description: [
4970
- "Configure Outlit for AI agent tools.",
6013
+ "Install the Outlit skill for coding agents.",
4971
6014
  "",
4972
- "Without a subcommand, auto-detects installed agents and configures them all.",
4973
- "Subcommands: cursor, claude-code, claude-desktop, vscode, gemini, openclaw, skills"
6015
+ "Without a subcommand, auto-detects supported coding agents and installs `outlit` for all of them.",
6016
+ "Subcommands: claude-code, codex, gemini, droid, opencode, pi, openclaw, skills"
4974
6017
  ].join(`
4975
6018
  `)
4976
6019
  },
4977
6020
  args: {
4978
- ...authArgs,
4979
6021
  ...outputArgs,
4980
6022
  yes: {
4981
6023
  type: "boolean",
4982
- description: "Configure all detected agents without prompting."
6024
+ description: "Install for all detected coding agents without prompting."
4983
6025
  }
4984
6026
  },
4985
6027
  subCommands: {
4986
- cursor: () => Promise.resolve().then(() => (init_cursor(), exports_cursor)).then((m) => m.default),
4987
6028
  "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),
6029
+ codex: () => Promise.resolve().then(() => (init_codex(), exports_codex)).then((m) => m.default),
4990
6030
  gemini: () => Promise.resolve().then(() => (init_gemini(), exports_gemini)).then((m) => m.default),
6031
+ droid: () => Promise.resolve().then(() => (init_droid(), exports_droid)).then((m) => m.default),
6032
+ opencode: () => Promise.resolve().then(() => (init_opencode(), exports_opencode)).then((m) => m.default),
6033
+ pi: () => Promise.resolve().then(() => (init_pi(), exports_pi)).then((m) => m.default),
4991
6034
  openclaw: () => Promise.resolve().then(() => (init_openclaw(), exports_openclaw)).then((m) => m.default),
4992
6035
  skills: () => Promise.resolve().then(() => (init_skills(), exports_skills)).then((m) => m.default)
4993
6036
  },
4994
- async run({ args }) {
6037
+ async run({ args, rawArgs }) {
6038
+ const setupRawArgs = rawArgs ?? [];
6039
+ const subcommandName = setupRawArgs.find((arg) => !arg.startsWith("-"));
6040
+ if (subcommandName && setupSubcommandNames2.has(subcommandName)) {
6041
+ return;
6042
+ }
4995
6043
  const json = !!args.json;
4996
- const credential = requireCredential(args["api-key"], json);
4997
- const detected = detectAgents3();
6044
+ const detected = detectAgents2();
4998
6045
  if (detected.length === 0) {
4999
6046
  if (isJsonMode(json)) {
5000
- return outputResult({ detected: [], configured: [], failed: [], skills: null });
6047
+ return outputResult({ detected: [], configured: [], failed: [], runner: null });
5001
6048
  }
5002
- console.log("No supported agent tools detected.");
6049
+ console.log("No supported coding agents detected.");
5003
6050
  return;
5004
6051
  }
5005
6052
  if (!isJsonMode(json) && !args.yes) {
5006
- console.log("Detected agents:");
6053
+ console.log("Detected coding agents:");
5007
6054
  for (const agentId of detected) {
5008
6055
  const { label, hint } = agentLabels2[agentId];
5009
6056
  console.log(` ${TICK2} ${label.padEnd(14)} -- ${hint}`);
5010
6057
  }
5011
6058
  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.");
6059
+ Installing Outlit skill...`);
5029
6060
  }
6061
+ const install = runSkillsInstall({
6062
+ json,
6063
+ exitOnError: false,
6064
+ agents: detected.map(getSkillAgentId),
6065
+ skillNames: ["outlit"],
6066
+ autoConfirm: true
6067
+ });
6068
+ const configured = install.success ? detected : [];
6069
+ const failed = install.success ? [] : detected;
5030
6070
  if (isJsonMode(json)) {
5031
- return outputResult({ detected, configured, failed, skills });
6071
+ return outputResult({
6072
+ detected,
6073
+ configured,
6074
+ failed,
6075
+ runner: install.runner ?? null
6076
+ });
5032
6077
  }
5033
- if (failed.length > 0) {
6078
+ if (!install.success) {
5034
6079
  console.log(`
5035
- ${failed.length} agent(s) failed: ${failed.join(", ")}`);
6080
+ ! Outlit skill install failed: ${install.error ?? "unknown error"}`);
6081
+ console.log(" Run `outlit setup skills` to retry manually.");
6082
+ return;
5036
6083
  }
5037
6084
  console.log(`
5038
- Done. ${configured.length}/${detected.length} agent(s) configured successfully.`);
6085
+ Done. Installed Outlit for ${configured.length} coding agent(s).`);
5039
6086
  }
5040
6087
  });
5041
6088
  });
@@ -5392,11 +6439,226 @@ init_tty();
5392
6439
  var CLI_VERSION = package_default.version;
5393
6440
  var TICK = `\x1B[32m${isUnicodeSupported ? String.fromCodePoint(10003) : String.fromCodePoint(8730)}\x1B[0m`;
5394
6441
 
6442
+ // src/lib/update.ts
6443
+ init_config();
6444
+ init_tty();
6445
+ import { execFileSync as execFileSync2, spawn, spawnSync } from "node:child_process";
6446
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
6447
+ import { homedir as homedir2 } from "node:os";
6448
+ import { dirname as dirname2, join as join2 } from "node:path";
6449
+ var UPDATE_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
6450
+ var PACKAGE_NAME = "@outlit/cli";
6451
+ var LATEST_VERSION_URL = "https://registry.npmjs.org/@outlit%2Fcli/latest";
6452
+ var INTERNAL_UPDATE_FLAG = "--internal-update-check";
6453
+ function getUpdateCachePath() {
6454
+ return join2(getConfigDir(), "update-check.json");
6455
+ }
6456
+ function readCachedUpdateState() {
6457
+ const cachePath = getUpdateCachePath();
6458
+ if (!existsSync2(cachePath))
6459
+ return null;
6460
+ try {
6461
+ return JSON.parse(readFileSync2(cachePath, "utf8"));
6462
+ } catch {
6463
+ return null;
6464
+ }
6465
+ }
6466
+ function writeCachedUpdateState(state) {
6467
+ const cachePath = getUpdateCachePath();
6468
+ mkdirSync2(dirname2(cachePath), { recursive: true });
6469
+ writeFileSync2(cachePath, `${JSON.stringify(state, null, 2)}
6470
+ `);
6471
+ }
6472
+ function isUpdateCheckDue(state) {
6473
+ if (!state?.lastCheckedAt)
6474
+ return true;
6475
+ return Date.now() - state.lastCheckedAt >= UPDATE_CHECK_INTERVAL_MS;
6476
+ }
6477
+ function compareVersions(a, b) {
6478
+ const aParts = a.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
6479
+ const bParts = b.split("-")[0]?.split(".").map((part) => Number.parseInt(part, 10) || 0) ?? [];
6480
+ const maxLength = Math.max(aParts.length, bParts.length);
6481
+ for (let index = 0;index < maxLength; index++) {
6482
+ const left = aParts[index] ?? 0;
6483
+ const right = bParts[index] ?? 0;
6484
+ if (left > right)
6485
+ return 1;
6486
+ if (left < right)
6487
+ return -1;
6488
+ }
6489
+ return 0;
6490
+ }
6491
+ function inferInstallerFromUserAgent(agent) {
6492
+ if (agent.startsWith("bun/"))
6493
+ return "bun";
6494
+ if (agent.startsWith("npm/"))
6495
+ return "npm";
6496
+ if (agent.startsWith("pnpm/"))
6497
+ return "pnpm";
6498
+ if (agent.startsWith("yarn/"))
6499
+ return "yarn";
6500
+ return null;
6501
+ }
6502
+ function isUnderPath(path, parent) {
6503
+ const normalizedPath = normalizeInstallerPath(path);
6504
+ const normalizedParent = normalizeInstallerPath(parent);
6505
+ return normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`);
6506
+ }
6507
+ function normalizeInstallerPath(path) {
6508
+ return path.replace(/^\/private\/tmp\//, "/tmp/");
6509
+ }
6510
+ function inferInstallerFromInstallation(opts) {
6511
+ const candidatePaths = [opts.argv1, opts.realExecPath].filter((value) => !!value);
6512
+ if (opts.npmGlobalPrefix) {
6513
+ const npmPackageRoots = [
6514
+ join2(opts.npmGlobalPrefix, "node_modules", PACKAGE_NAME),
6515
+ join2(opts.npmGlobalPrefix, "lib", "node_modules", PACKAGE_NAME)
6516
+ ];
6517
+ if (candidatePaths.some((path) => npmPackageRoots.some((root) => isUnderPath(path, root)))) {
6518
+ return "npm";
6519
+ }
6520
+ }
6521
+ if (opts.bunGlobalBin) {
6522
+ const bunGlobalBin = opts.bunGlobalBin;
6523
+ if (candidatePaths.some((path) => isUnderPath(path, bunGlobalBin))) {
6524
+ return "bun";
6525
+ }
6526
+ }
6527
+ return null;
6528
+ }
6529
+ function readCommandOutput(command, args) {
6530
+ try {
6531
+ return execFileSync2(command, args, {
6532
+ encoding: "utf8",
6533
+ stdio: ["ignore", "pipe", "ignore"]
6534
+ }).trim();
6535
+ } catch {
6536
+ return null;
6537
+ }
6538
+ }
6539
+ function inferInstaller() {
6540
+ const fromAgent = inferInstallerFromUserAgent(process.env.npm_config_user_agent ?? "");
6541
+ if (fromAgent)
6542
+ return fromAgent;
6543
+ const argv1 = process.argv[1];
6544
+ const realExecPath = argv1 ? readCommandOutput("realpath", [argv1]) ?? safeRealPath(argv1) : null;
6545
+ const npmGlobalPrefix = process.env.npm_config_prefix ?? readCommandOutput("npm", ["prefix", "-g"]);
6546
+ const bunGlobalBin = process.env.BUN_INSTALL ? join2(process.env.BUN_INSTALL, "bin") : readCommandOutput("bun", ["pm", "bin", "-g"]) ?? join2(homedir2(), ".bun", "bin");
6547
+ return inferInstallerFromInstallation({
6548
+ argv1,
6549
+ realExecPath,
6550
+ npmGlobalPrefix,
6551
+ bunGlobalBin
6552
+ });
6553
+ }
6554
+ function safeRealPath(filePath) {
6555
+ try {
6556
+ return realpathSync(filePath);
6557
+ } catch {
6558
+ return null;
6559
+ }
6560
+ }
6561
+ function formatUpdateCommand(installer = inferInstaller()) {
6562
+ switch (installer) {
6563
+ case "bun":
6564
+ return "bun add -g @outlit/cli";
6565
+ case "npm":
6566
+ return "npm install -g @outlit/cli";
6567
+ case "pnpm":
6568
+ return "pnpm add -g @outlit/cli";
6569
+ case "yarn":
6570
+ return "yarn global add @outlit/cli";
6571
+ default:
6572
+ return `update ${PACKAGE_NAME} with your package manager`;
6573
+ }
6574
+ }
6575
+ function getCachedUpdateNotice(state = readCachedUpdateState()) {
6576
+ if (!state?.latestVersion)
6577
+ return null;
6578
+ if (compareVersions(CLI_VERSION2, state.latestVersion) >= 0)
6579
+ return null;
6580
+ return {
6581
+ currentVersion: CLI_VERSION2,
6582
+ latestVersion: state.latestVersion,
6583
+ command: formatUpdateCommand(state.installer)
6584
+ };
6585
+ }
6586
+ function shouldCheckForUpdates() {
6587
+ if (process.env.OUTLIT_NO_UPDATE_NOTIFIER)
6588
+ return false;
6589
+ return isInteractive();
6590
+ }
6591
+ function shouldShowUpdateNotice(argv = process.argv) {
6592
+ return shouldCheckForUpdates() && !argv.includes("--json") && !argv.includes(INTERNAL_UPDATE_FLAG);
6593
+ }
6594
+ function printCachedUpdateNotice(argv = process.argv, notify = console.error) {
6595
+ if (!shouldShowUpdateNotice(argv))
6596
+ return false;
6597
+ const notice = getCachedUpdateNotice();
6598
+ if (!notice)
6599
+ return false;
6600
+ notify(`Outlit CLI update available: ${notice.currentVersion} -> ${notice.latestVersion}
6601
+ Update with: ${notice.command}`);
6602
+ return true;
6603
+ }
6604
+ function scheduleBackgroundUpdateCheck(argv = process.argv, spawnProcess = spawn) {
6605
+ if (!shouldShowUpdateNotice(argv))
6606
+ return false;
6607
+ if (!isUpdateCheckDue(readCachedUpdateState()))
6608
+ return false;
6609
+ const runtimePath = argv[0];
6610
+ const scriptPath = argv[1];
6611
+ if (!runtimePath || !scriptPath)
6612
+ return false;
6613
+ const child = spawnProcess(runtimePath, [scriptPath, INTERNAL_UPDATE_FLAG], {
6614
+ detached: true,
6615
+ stdio: "ignore"
6616
+ });
6617
+ child.unref?.();
6618
+ return true;
6619
+ }
6620
+ function initializeUpdateNotifier(opts) {
6621
+ const argv = opts?.argv ?? process.argv;
6622
+ printCachedUpdateNotice(argv, opts?.notify);
6623
+ scheduleBackgroundUpdateCheck(argv, opts?.spawn);
6624
+ }
6625
+ async function fetchLatestCliVersion() {
6626
+ const response = await fetch(LATEST_VERSION_URL, { signal: AbortSignal.timeout(5000) });
6627
+ if (!response.ok)
6628
+ throw new Error("registry unavailable");
6629
+ const data = await response.json();
6630
+ if (!data.version)
6631
+ throw new Error("registry returned no version");
6632
+ return data.version;
6633
+ }
6634
+ async function runInternalUpdateCheck(opts) {
6635
+ const fetchLatestVersion = opts?.fetchLatestVersion ?? fetchLatestCliVersion;
6636
+ const installer = opts?.installer ?? inferInstaller();
6637
+ try {
6638
+ const latestVersion = await fetchLatestVersion();
6639
+ writeCachedUpdateState({
6640
+ lastCheckedAt: Date.now(),
6641
+ latestVersion,
6642
+ ...installer ? { installer } : {}
6643
+ });
6644
+ } catch {
6645
+ writeCachedUpdateState({
6646
+ lastCheckedAt: Date.now(),
6647
+ ...installer ? { installer } : {}
6648
+ });
6649
+ }
6650
+ }
6651
+
5395
6652
  // src/cli.ts
5396
6653
  if (process.argv.includes("-v")) {
5397
6654
  console.log(CLI_VERSION);
5398
6655
  process.exit(0);
5399
6656
  }
6657
+ if (process.argv.includes(INTERNAL_UPDATE_FLAG)) {
6658
+ await runInternalUpdateCheck();
6659
+ process.exit(0);
6660
+ }
6661
+ initializeUpdateNotifier();
5400
6662
  var main = defineCommand({
5401
6663
  meta: {
5402
6664
  name: "outlit",
@@ -5408,8 +6670,10 @@ Usage examples:
5408
6670
  outlit customers get acme.com --include users,revenue
5409
6671
  outlit customers timeline acme.com --timeframe 90d
5410
6672
  outlit users list --journey-stage CHAMPION
5411
- outlit facts acme.com --timeframe 90d
5412
- outlit search 'pricing objections last quarter'
6673
+ outlit facts list acme.com --fact-types CHURN_RISK,EXPANSION
6674
+ outlit facts get --fact-id fact_123 --include evidence
6675
+ outlit sources get --source-type CALL --source-id call_123
6676
+ outlit search 'pricing objections last quarter' --source-types CALL,EMAIL
5413
6677
  outlit sql 'SELECT * FROM events LIMIT 10'
5414
6678
  outlit schema events
5415
6679
  outlit doctor --json
@@ -5421,13 +6685,15 @@ For AI agents: commands auto-output JSON when stdout is piped. No --json flag ne
5421
6685
  customers: () => Promise.resolve().then(() => (init_customers(), exports_customers)).then((m) => m.default),
5422
6686
  users: () => Promise.resolve().then(() => (init_users(), exports_users)).then((m) => m.default),
5423
6687
  doctor: () => Promise.resolve().then(() => (init_doctor(), exports_doctor)).then((m) => m.default),
6688
+ upgrade: () => Promise.resolve().then(() => (init_upgrade(), exports_upgrade)).then((m) => m.default),
5424
6689
  facts: () => Promise.resolve().then(() => (init_facts(), exports_facts)).then((m) => m.default),
6690
+ sources: () => Promise.resolve().then(() => (init_sources(), exports_sources)).then((m) => m.default),
5425
6691
  search: () => Promise.resolve().then(() => (init_search(), exports_search)).then((m) => m.default),
5426
6692
  sql: () => Promise.resolve().then(() => (init_sql(), exports_sql)).then((m) => m.default),
5427
6693
  schema: () => Promise.resolve().then(() => (init_schema(), exports_schema)).then((m) => m.default),
5428
6694
  integrations: () => Promise.resolve().then(() => (init_integrations(), exports_integrations)).then((m) => m.default),
5429
6695
  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)
6696
+ setup: () => Promise.resolve().then(() => (init_setup2(), exports_setup)).then((m) => m.default)
5431
6697
  }
5432
6698
  });
5433
6699
  runMain(main);