@outlit/cli 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +258 -89
- package/package.json +2 -2
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.6.
|
|
107
|
+
version: "1.6.4",
|
|
108
108
|
description: "CLI for Outlit customer intelligence platform",
|
|
109
109
|
license: "Apache-2.0",
|
|
110
110
|
repository: {
|
|
@@ -135,7 +135,7 @@ var init_package = __esm(() => {
|
|
|
135
135
|
},
|
|
136
136
|
dependencies: {
|
|
137
137
|
"@clack/prompts": "^1.0.1",
|
|
138
|
-
"@outlit/tools": "^0.1.
|
|
138
|
+
"@outlit/tools": "^0.1.3",
|
|
139
139
|
citty: "^0.2.1"
|
|
140
140
|
},
|
|
141
141
|
devDependencies: {
|
|
@@ -1493,6 +1493,13 @@ var init_signup = __esm(() => {
|
|
|
1493
1493
|
function isCustomerToolName(value) {
|
|
1494
1494
|
return customerToolNameSet.has(value);
|
|
1495
1495
|
}
|
|
1496
|
+
function normalizeCustomerSourceType(value) {
|
|
1497
|
+
const normalizedValue = value.trim().toUpperCase();
|
|
1498
|
+
if (customerSourceTypeSet.has(normalizedValue)) {
|
|
1499
|
+
return normalizedValue;
|
|
1500
|
+
}
|
|
1501
|
+
return customerSourceTypeAliasMap.get(normalizedValue) ?? null;
|
|
1502
|
+
}
|
|
1496
1503
|
function resolveCustomerContextSearchInput(value) {
|
|
1497
1504
|
if (!value.query) {
|
|
1498
1505
|
return {
|
|
@@ -1539,6 +1546,14 @@ function resolveCustomerContextSearchInput(value) {
|
|
|
1539
1546
|
message: "--after must be before or equal to --before"
|
|
1540
1547
|
};
|
|
1541
1548
|
}
|
|
1549
|
+
const invalidSourceTypes = value.sourceTypes?.filter((sourceType) => !normalizeCustomerSourceType(sourceType)) ?? [];
|
|
1550
|
+
if (invalidSourceTypes.length > 0) {
|
|
1551
|
+
return {
|
|
1552
|
+
ok: false,
|
|
1553
|
+
message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypeInputs.join(", ")}`
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
const normalizedSourceTypes = value.sourceTypes ? Array.from(new Set(value.sourceTypes.map((sourceType) => normalizeCustomerSourceType(sourceType)))).filter((sourceType) => sourceType !== null) : undefined;
|
|
1542
1557
|
return {
|
|
1543
1558
|
ok: true,
|
|
1544
1559
|
request: {
|
|
@@ -1547,7 +1562,7 @@ function resolveCustomerContextSearchInput(value) {
|
|
|
1547
1562
|
topK: value.topK,
|
|
1548
1563
|
after: value.after,
|
|
1549
1564
|
before: value.before,
|
|
1550
|
-
sourceTypes:
|
|
1565
|
+
sourceTypes: normalizedSourceTypes
|
|
1551
1566
|
}
|
|
1552
1567
|
};
|
|
1553
1568
|
}
|
|
@@ -1587,7 +1602,7 @@ function createOutlitClient(options) {
|
|
|
1587
1602
|
}
|
|
1588
1603
|
};
|
|
1589
1604
|
}
|
|
1590
|
-
var customerToolNames, customerToolContracts, customerBillingStatuses, customerFactStatuses, customerFactTypes, unsupportedCustomerFactTypes, customerFactCategories, customerIncludeSections,
|
|
1605
|
+
var customerToolNames, customerSourceTypes, customerSourceTypeAliases, customerSourceTypeInputs, customerToolContracts, customerBillingStatuses, customerFactStatuses, customerFactTypes, unsupportedCustomerFactTypes, customerFactCategories, customerIncludeSections, customerTimeframes, notificationSeverityValues, timelineChannels, timelineTimeframes, userJourneyStages, customerToolNameSet, customerSourceTypeSet, customerSourceTypeAliasMap, iso8601UtcDateTimeRegex, DEFAULT_OUTLIT_API_URL = "https://app.outlit.ai", actionToolNames, defaultAgentToolNames, sqlToolNames, analyticalAgentToolNames;
|
|
1591
1606
|
var init_dist4 = __esm(() => {
|
|
1592
1607
|
customerToolNames = [
|
|
1593
1608
|
"outlit_list_customers",
|
|
@@ -1602,6 +1617,23 @@ var init_dist4 = __esm(() => {
|
|
|
1602
1617
|
"outlit_schema",
|
|
1603
1618
|
"outlit_send_notification"
|
|
1604
1619
|
];
|
|
1620
|
+
customerSourceTypes = [
|
|
1621
|
+
"EMAIL",
|
|
1622
|
+
"CALL",
|
|
1623
|
+
"CALENDAR_EVENT",
|
|
1624
|
+
"SUPPORT_TICKET",
|
|
1625
|
+
"OPPORTUNITY"
|
|
1626
|
+
];
|
|
1627
|
+
customerSourceTypeAliases = ["CRM", "CRM_OPPORTUNITY"];
|
|
1628
|
+
customerSourceTypeInputs = [
|
|
1629
|
+
"EMAIL",
|
|
1630
|
+
"CALL",
|
|
1631
|
+
"CALENDAR_EVENT",
|
|
1632
|
+
"SUPPORT_TICKET",
|
|
1633
|
+
"OPPORTUNITY",
|
|
1634
|
+
"CRM",
|
|
1635
|
+
"CRM_OPPORTUNITY"
|
|
1636
|
+
];
|
|
1605
1637
|
customerToolContracts = {
|
|
1606
1638
|
outlit_list_customers: {
|
|
1607
1639
|
toolName: "outlit_list_customers",
|
|
@@ -1812,11 +1844,21 @@ var init_dist4 = __esm(() => {
|
|
|
1812
1844
|
description: "Customer ID or domain"
|
|
1813
1845
|
},
|
|
1814
1846
|
channels: {
|
|
1815
|
-
description: "Filter by event channel (e.g., EMAIL, SLACK, CALL)",
|
|
1847
|
+
description: "Filter by event channel (e.g., EMAIL, SLACK, CALL, CALENDAR)",
|
|
1816
1848
|
type: "array",
|
|
1817
1849
|
items: {
|
|
1818
1850
|
type: "string",
|
|
1819
|
-
enum: [
|
|
1851
|
+
enum: [
|
|
1852
|
+
"SDK",
|
|
1853
|
+
"EMAIL",
|
|
1854
|
+
"SLACK",
|
|
1855
|
+
"CALL",
|
|
1856
|
+
"CALENDAR",
|
|
1857
|
+
"CRM",
|
|
1858
|
+
"BILLING",
|
|
1859
|
+
"SUPPORT",
|
|
1860
|
+
"INTERNAL"
|
|
1861
|
+
]
|
|
1820
1862
|
}
|
|
1821
1863
|
},
|
|
1822
1864
|
eventTypes: {
|
|
@@ -1883,7 +1925,7 @@ var init_dist4 = __esm(() => {
|
|
|
1883
1925
|
type: "array",
|
|
1884
1926
|
items: {
|
|
1885
1927
|
type: "string",
|
|
1886
|
-
enum:
|
|
1928
|
+
enum: customerSourceTypeInputs
|
|
1887
1929
|
}
|
|
1888
1930
|
},
|
|
1889
1931
|
factTypes: {
|
|
@@ -1984,7 +2026,7 @@ var init_dist4 = __esm(() => {
|
|
|
1984
2026
|
properties: {
|
|
1985
2027
|
sourceType: {
|
|
1986
2028
|
type: "string",
|
|
1987
|
-
enum:
|
|
2029
|
+
enum: customerSourceTypeInputs
|
|
1988
2030
|
},
|
|
1989
2031
|
sourceId: {
|
|
1990
2032
|
type: "string",
|
|
@@ -2045,7 +2087,7 @@ var init_dist4 = __esm(() => {
|
|
|
2045
2087
|
type: "array",
|
|
2046
2088
|
items: {
|
|
2047
2089
|
type: "string",
|
|
2048
|
-
enum:
|
|
2090
|
+
enum: customerSourceTypeInputs
|
|
2049
2091
|
}
|
|
2050
2092
|
}
|
|
2051
2093
|
},
|
|
@@ -2055,21 +2097,22 @@ var init_dist4 = __esm(() => {
|
|
|
2055
2097
|
},
|
|
2056
2098
|
outlit_query: {
|
|
2057
2099
|
toolName: "outlit_query",
|
|
2058
|
-
description: `Execute
|
|
2100
|
+
description: `Execute read-only SQL queries against your analytics views.
|
|
2059
2101
|
|
|
2060
|
-
Available
|
|
2061
|
-
-
|
|
2062
|
-
-
|
|
2063
|
-
-
|
|
2064
|
-
-
|
|
2102
|
+
Available views:
|
|
2103
|
+
- activity: Customer activity events (event_name, event_type, event_channel, customer_id, occurred_at, properties, ...)
|
|
2104
|
+
- customers: Customer attributes (customer_id, domain, name, billing_status, plan, mrr_cents, traits, ...)
|
|
2105
|
+
- users: User attributes (user_id, email, name, customer_id, traits, ...)
|
|
2106
|
+
- revenue: Revenue snapshots over time (customer_id, snapshot_date, mrr_cents, ...)
|
|
2065
2107
|
|
|
2066
2108
|
All queries are automatically filtered to your organization's data.
|
|
2067
2109
|
Only SELECT queries are allowed.
|
|
2110
|
+
Properties and traits are JSON strings; inspect schemas and examples before filtering nested values.
|
|
2068
2111
|
|
|
2069
2112
|
Example queries:
|
|
2070
|
-
- SELECT event_name, count(*) FROM
|
|
2071
|
-
- SELECT billing_status, sum(mrr_cents)/100 as mrr FROM
|
|
2072
|
-
- SELECT * FROM
|
|
2113
|
+
- SELECT event_name, count(*) FROM activity GROUP BY 1 ORDER BY 2 DESC LIMIT 10
|
|
2114
|
+
- SELECT billing_status, sum(mrr_cents)/100 as mrr FROM customers GROUP BY 1
|
|
2115
|
+
- SELECT * FROM activity WHERE customer_id = 'cust_123' ORDER BY occurred_at DESC LIMIT 50`,
|
|
2073
2116
|
inputSchema: {
|
|
2074
2117
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2075
2118
|
type: "object",
|
|
@@ -2081,7 +2124,8 @@ Example queries:
|
|
|
2081
2124
|
limit: {
|
|
2082
2125
|
description: "Max rows to return (default 1000, max 10000)",
|
|
2083
2126
|
default: 1000,
|
|
2084
|
-
type: "
|
|
2127
|
+
type: "integer",
|
|
2128
|
+
minimum: 1,
|
|
2085
2129
|
maximum: 1e4
|
|
2086
2130
|
}
|
|
2087
2131
|
},
|
|
@@ -2091,18 +2135,18 @@ Example queries:
|
|
|
2091
2135
|
},
|
|
2092
2136
|
outlit_schema: {
|
|
2093
2137
|
toolName: "outlit_schema",
|
|
2094
|
-
description: `Get
|
|
2138
|
+
description: `Get schemas for available analytics views.
|
|
2095
2139
|
|
|
2096
2140
|
Use this to discover column names, types, and descriptions before writing SQL queries.
|
|
2097
|
-
Returns column definitions and example queries for each
|
|
2141
|
+
Returns column definitions and example queries for each view.`,
|
|
2098
2142
|
inputSchema: {
|
|
2099
2143
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2100
2144
|
type: "object",
|
|
2101
2145
|
properties: {
|
|
2102
2146
|
table: {
|
|
2103
|
-
description: "Specific
|
|
2147
|
+
description: "Specific analytics view to describe. Omit to list all views.",
|
|
2104
2148
|
type: "string",
|
|
2105
|
-
enum: ["
|
|
2149
|
+
enum: ["activity", "customers", "users", "revenue"]
|
|
2106
2150
|
}
|
|
2107
2151
|
},
|
|
2108
2152
|
additionalProperties: false
|
|
@@ -2268,7 +2312,6 @@ Returns column definitions and example queries for each table.`,
|
|
|
2268
2312
|
"recentTimeline",
|
|
2269
2313
|
"behaviorMetrics"
|
|
2270
2314
|
];
|
|
2271
|
-
customerSourceTypes = ["EMAIL", "CALL", "CALENDAR_EVENT", "SUPPORT_TICKET"];
|
|
2272
2315
|
customerTimeframes = ["7d", "14d", "30d", "90d"];
|
|
2273
2316
|
notificationSeverityValues = ["low", "medium", "high"];
|
|
2274
2317
|
timelineChannels = [
|
|
@@ -2276,6 +2319,7 @@ Returns column definitions and example queries for each table.`,
|
|
|
2276
2319
|
"EMAIL",
|
|
2277
2320
|
"SLACK",
|
|
2278
2321
|
"CALL",
|
|
2322
|
+
"CALENDAR",
|
|
2279
2323
|
"CRM",
|
|
2280
2324
|
"BILLING",
|
|
2281
2325
|
"SUPPORT",
|
|
@@ -2289,13 +2333,12 @@ Returns column definitions and example queries for each table.`,
|
|
|
2289
2333
|
"ENGAGED",
|
|
2290
2334
|
"INACTIVE"
|
|
2291
2335
|
];
|
|
2292
|
-
schemaTables = [
|
|
2293
|
-
"events",
|
|
2294
|
-
"customer_dimensions",
|
|
2295
|
-
"user_dimensions",
|
|
2296
|
-
"mrr_snapshots"
|
|
2297
|
-
];
|
|
2298
2336
|
customerToolNameSet = new Set(customerToolNames);
|
|
2337
|
+
customerSourceTypeSet = new Set(customerSourceTypes);
|
|
2338
|
+
customerSourceTypeAliasMap = /* @__PURE__ */ new Map([
|
|
2339
|
+
["CRM", "OPPORTUNITY"],
|
|
2340
|
+
["CRM_OPPORTUNITY", "OPPORTUNITY"]
|
|
2341
|
+
]);
|
|
2299
2342
|
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))$/;
|
|
2300
2343
|
actionToolNames = [
|
|
2301
2344
|
"outlit_send_notification"
|
|
@@ -2587,7 +2630,8 @@ ${hint}`);
|
|
|
2587
2630
|
async function runTool(client, toolName, params, json, opts) {
|
|
2588
2631
|
const spinner = opts?.spinnerMessage ? createSpinner(opts.spinnerMessage) : null;
|
|
2589
2632
|
try {
|
|
2590
|
-
const
|
|
2633
|
+
const rawData = await client.callTool(toolName, params);
|
|
2634
|
+
const data = opts?.transform ? opts.transform(rawData) : rawData;
|
|
2591
2635
|
spinner?.stop("Done");
|
|
2592
2636
|
const table = opts?.table;
|
|
2593
2637
|
if (isJsonMode(json) || !table) {
|
|
@@ -2923,6 +2967,35 @@ var init_auth2 = __esm(() => {
|
|
|
2923
2967
|
});
|
|
2924
2968
|
|
|
2925
2969
|
// src/args/filters.ts
|
|
2970
|
+
function findRawStringFlagValue(rawArgs, flagName) {
|
|
2971
|
+
if (!rawArgs)
|
|
2972
|
+
return;
|
|
2973
|
+
const flag = `--${flagName}`;
|
|
2974
|
+
let found;
|
|
2975
|
+
for (let i = 0;i < rawArgs.length; i++) {
|
|
2976
|
+
const arg = rawArgs[i];
|
|
2977
|
+
if (!arg)
|
|
2978
|
+
continue;
|
|
2979
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
2980
|
+
const value = arg.slice(flag.length + 1);
|
|
2981
|
+
if (value.length > 0)
|
|
2982
|
+
found = value;
|
|
2983
|
+
continue;
|
|
2984
|
+
}
|
|
2985
|
+
if (arg === flag) {
|
|
2986
|
+
const value = rawArgs[i + 1];
|
|
2987
|
+
if (value && !value.startsWith("-"))
|
|
2988
|
+
found = value;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return found;
|
|
2992
|
+
}
|
|
2993
|
+
function readStringArg(args, flagName, rawArgs) {
|
|
2994
|
+
const value = args[flagName];
|
|
2995
|
+
if (typeof value === "string" && value.length > 0)
|
|
2996
|
+
return value;
|
|
2997
|
+
return findRawStringFlagValue(rawArgs, flagName);
|
|
2998
|
+
}
|
|
2926
2999
|
function parseTraitFilterValue(value) {
|
|
2927
3000
|
if (value === "true")
|
|
2928
3001
|
return true;
|
|
@@ -2955,11 +3028,14 @@ function parseTraitFilters(input) {
|
|
|
2955
3028
|
}
|
|
2956
3029
|
return filters;
|
|
2957
3030
|
}
|
|
2958
|
-
function applyListFilters(params, args) {
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3031
|
+
function applyListFilters(params, args, rawArgs) {
|
|
3032
|
+
const argsRecord = args;
|
|
3033
|
+
const noActivityIn = readStringArg(argsRecord, "no-activity-in", rawArgs);
|
|
3034
|
+
const hasActivityIn = readStringArg(argsRecord, "has-activity-in", rawArgs);
|
|
3035
|
+
if (noActivityIn)
|
|
3036
|
+
params.noActivityInLast = noActivityIn;
|
|
3037
|
+
if (hasActivityIn)
|
|
3038
|
+
params.hasActivityInLast = hasActivityIn;
|
|
2963
3039
|
if (args.search)
|
|
2964
3040
|
params.search = args.search;
|
|
2965
3041
|
if (args["order-by"])
|
|
@@ -3088,6 +3164,33 @@ var exports_list = {};
|
|
|
3088
3164
|
__export(exports_list, {
|
|
3089
3165
|
default: () => list_default
|
|
3090
3166
|
});
|
|
3167
|
+
function normalizeCustomerListResult(data) {
|
|
3168
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
3169
|
+
return data;
|
|
3170
|
+
}
|
|
3171
|
+
const record = data;
|
|
3172
|
+
if (!Array.isArray(record.items)) {
|
|
3173
|
+
return data;
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
...record,
|
|
3177
|
+
items: record.items.map((item) => {
|
|
3178
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
3179
|
+
return item;
|
|
3180
|
+
}
|
|
3181
|
+
const customer = item;
|
|
3182
|
+
const daysSinceActivity = customer.daysSinceActivity;
|
|
3183
|
+
if (typeof daysSinceActivity !== "number" || daysSinceActivity >= 0) {
|
|
3184
|
+
return item;
|
|
3185
|
+
}
|
|
3186
|
+
return {
|
|
3187
|
+
...customer,
|
|
3188
|
+
lastActivityAt: null,
|
|
3189
|
+
daysSinceActivity: null
|
|
3190
|
+
};
|
|
3191
|
+
})
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3091
3194
|
var list_default;
|
|
3092
3195
|
var init_list = __esm(() => {
|
|
3093
3196
|
init_dist4();
|
|
@@ -3145,7 +3248,7 @@ var init_list = __esm(() => {
|
|
|
3145
3248
|
description: "Search by customer name or domain"
|
|
3146
3249
|
}
|
|
3147
3250
|
},
|
|
3148
|
-
async run({ args }) {
|
|
3251
|
+
async run({ args, rawArgs }) {
|
|
3149
3252
|
const json = !!args.json;
|
|
3150
3253
|
const client = await getClientOrExit(args["api-key"], json);
|
|
3151
3254
|
const params = {};
|
|
@@ -3178,10 +3281,11 @@ var init_list = __esm(() => {
|
|
|
3178
3281
|
}, json);
|
|
3179
3282
|
}
|
|
3180
3283
|
}
|
|
3181
|
-
applyListFilters(params, args);
|
|
3284
|
+
applyListFilters(params, args, rawArgs);
|
|
3182
3285
|
applyPagination(params, args, json);
|
|
3183
3286
|
return runTool(client, customerToolContracts.outlit_list_customers.toolName, params, json, {
|
|
3184
3287
|
spinnerMessage: "Fetching customers...",
|
|
3288
|
+
transform: normalizeCustomerListResult,
|
|
3185
3289
|
table: {
|
|
3186
3290
|
columns: [
|
|
3187
3291
|
{ header: "Name", key: "name", format: (v) => truncate(v, 24) },
|
|
@@ -3457,7 +3561,7 @@ var init_list2 = __esm(() => {
|
|
|
3457
3561
|
description: "Search by user name or email"
|
|
3458
3562
|
}
|
|
3459
3563
|
},
|
|
3460
|
-
async run({ args }) {
|
|
3564
|
+
async run({ args, rawArgs }) {
|
|
3461
3565
|
const json = !!args.json;
|
|
3462
3566
|
const client = await getClientOrExit(args["api-key"], json);
|
|
3463
3567
|
const params = {};
|
|
@@ -3478,7 +3582,7 @@ var init_list2 = __esm(() => {
|
|
|
3478
3582
|
}, json);
|
|
3479
3583
|
}
|
|
3480
3584
|
}
|
|
3481
|
-
applyListFilters(params, args);
|
|
3585
|
+
applyListFilters(params, args, rawArgs);
|
|
3482
3586
|
applyPagination(params, args, json);
|
|
3483
3587
|
return runTool(client, customerToolContracts.outlit_list_users.toolName, params, json, {
|
|
3484
3588
|
spinnerMessage: "Fetching users...",
|
|
@@ -4484,7 +4588,40 @@ function invalidValues(values, allowed) {
|
|
|
4484
4588
|
return [];
|
|
4485
4589
|
return values.filter((value) => !allowed.includes(value));
|
|
4486
4590
|
}
|
|
4487
|
-
|
|
4591
|
+
function normalizeSourceTypes(values) {
|
|
4592
|
+
if (!values)
|
|
4593
|
+
return;
|
|
4594
|
+
return Array.from(new Set(values.map((value) => normalizeCustomerSourceType(value)))).filter((sourceType) => sourceType !== null);
|
|
4595
|
+
}
|
|
4596
|
+
function isOneOf(values, value) {
|
|
4597
|
+
return typeof value === "string" && values.includes(value);
|
|
4598
|
+
}
|
|
4599
|
+
function annotateFactFilterability(data) {
|
|
4600
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
4601
|
+
return data;
|
|
4602
|
+
}
|
|
4603
|
+
const record = data;
|
|
4604
|
+
if (!Array.isArray(record.facts)) {
|
|
4605
|
+
return data;
|
|
4606
|
+
}
|
|
4607
|
+
return {
|
|
4608
|
+
...record,
|
|
4609
|
+
facts: record.facts.map((fact) => {
|
|
4610
|
+
if (typeof fact !== "object" || fact === null || Array.isArray(fact)) {
|
|
4611
|
+
return fact;
|
|
4612
|
+
}
|
|
4613
|
+
const factRecord = fact;
|
|
4614
|
+
return {
|
|
4615
|
+
...factRecord,
|
|
4616
|
+
filterability: {
|
|
4617
|
+
factType: isOneOf(customerFactTypes, factRecord.factType) ? "public" : "internal",
|
|
4618
|
+
factCategory: isOneOf(customerFactCategories, factRecord.factCategory) ? "public" : "internal"
|
|
4619
|
+
}
|
|
4620
|
+
};
|
|
4621
|
+
})
|
|
4622
|
+
};
|
|
4623
|
+
}
|
|
4624
|
+
var sourceTypeDescription, list_default3;
|
|
4488
4625
|
var init_list3 = __esm(() => {
|
|
4489
4626
|
init_dist4();
|
|
4490
4627
|
init_dist();
|
|
@@ -4494,6 +4631,7 @@ var init_list3 = __esm(() => {
|
|
|
4494
4631
|
init_api();
|
|
4495
4632
|
init_config();
|
|
4496
4633
|
init_output();
|
|
4634
|
+
sourceTypeDescription = `${customerSourceTypes.join(", ")} (aliases: ${customerSourceTypeAliases.join(", ")})`;
|
|
4497
4635
|
list_default3 = defineCommand2({
|
|
4498
4636
|
meta: {
|
|
4499
4637
|
name: "list",
|
|
@@ -4506,11 +4644,11 @@ var init_list3 = __esm(() => {
|
|
|
4506
4644
|
" outlit facts list acme.com",
|
|
4507
4645
|
" outlit facts list acme.com --status ACTIVE",
|
|
4508
4646
|
" outlit facts list acme.com --fact-types CHURN_RISK,EXPANSION",
|
|
4509
|
-
" outlit facts list acme.com --source-types CALL,
|
|
4647
|
+
" outlit facts list acme.com --source-types CALL,OPPORTUNITY --after 2025-01-01T00:00:00Z",
|
|
4510
4648
|
" outlit facts list acme.com --limit 50 --json",
|
|
4511
4649
|
"",
|
|
4512
4650
|
`Statuses: ${customerFactStatuses.join(", ")}`,
|
|
4513
|
-
`Source types: ${
|
|
4651
|
+
`Source types: ${sourceTypeDescription}`,
|
|
4514
4652
|
`Fact categories: ${customerFactCategories.join(", ")}`,
|
|
4515
4653
|
"",
|
|
4516
4654
|
AGENT_JSON_HINT
|
|
@@ -4532,7 +4670,7 @@ var init_list3 = __esm(() => {
|
|
|
4532
4670
|
},
|
|
4533
4671
|
"source-types": {
|
|
4534
4672
|
type: "string",
|
|
4535
|
-
description: `Comma-separated generic source type filter (${
|
|
4673
|
+
description: `Comma-separated generic source type filter (${sourceTypeDescription})`
|
|
4536
4674
|
},
|
|
4537
4675
|
"fact-types": {
|
|
4538
4676
|
type: "string",
|
|
@@ -4564,13 +4702,14 @@ var init_list3 = __esm(() => {
|
|
|
4564
4702
|
code: "invalid_input"
|
|
4565
4703
|
}, json);
|
|
4566
4704
|
}
|
|
4567
|
-
const invalidSourceTypes =
|
|
4705
|
+
const invalidSourceTypes = sourceTypes?.filter((value) => !normalizeCustomerSourceType(value)) ?? [];
|
|
4568
4706
|
if (invalidSourceTypes.length > 0) {
|
|
4569
4707
|
return outputError({
|
|
4570
|
-
message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${
|
|
4708
|
+
message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypeInputs.join(", ")}`,
|
|
4571
4709
|
code: "invalid_input"
|
|
4572
4710
|
}, json);
|
|
4573
4711
|
}
|
|
4712
|
+
const normalizedSourceTypes = normalizeSourceTypes(sourceTypes);
|
|
4574
4713
|
const invalidFactTypes = invalidValues(factTypes, customerFactTypes);
|
|
4575
4714
|
if (invalidFactTypes.length > 0) {
|
|
4576
4715
|
const anomalyFactTypes = invalidFactTypes.filter((value) => unsupportedCustomerFactTypes.includes(value));
|
|
@@ -4620,8 +4759,8 @@ var init_list3 = __esm(() => {
|
|
|
4620
4759
|
};
|
|
4621
4760
|
if (statuses)
|
|
4622
4761
|
params.status = statuses;
|
|
4623
|
-
if (
|
|
4624
|
-
params.sourceTypes =
|
|
4762
|
+
if (normalizedSourceTypes)
|
|
4763
|
+
params.sourceTypes = normalizedSourceTypes;
|
|
4625
4764
|
if (factTypes)
|
|
4626
4765
|
params.factTypes = factTypes;
|
|
4627
4766
|
if (factCategories)
|
|
@@ -4631,7 +4770,9 @@ var init_list3 = __esm(() => {
|
|
|
4631
4770
|
if (args.before)
|
|
4632
4771
|
params.before = args.before;
|
|
4633
4772
|
applyPagination(params, args, json);
|
|
4634
|
-
return runTool(client, customerToolContracts.outlit_list_facts.toolName, params, json
|
|
4773
|
+
return runTool(client, customerToolContracts.outlit_list_facts.toolName, params, json, {
|
|
4774
|
+
transform: annotateFactFilterability
|
|
4775
|
+
});
|
|
4635
4776
|
}
|
|
4636
4777
|
});
|
|
4637
4778
|
});
|
|
@@ -4733,7 +4874,7 @@ var exports_get3 = {};
|
|
|
4733
4874
|
__export(exports_get3, {
|
|
4734
4875
|
default: () => get_default3
|
|
4735
4876
|
});
|
|
4736
|
-
var get_default3;
|
|
4877
|
+
var sourceTypeDescription2, get_default3;
|
|
4737
4878
|
var init_get3 = __esm(() => {
|
|
4738
4879
|
init_dist4();
|
|
4739
4880
|
init_dist();
|
|
@@ -4741,6 +4882,7 @@ var init_get3 = __esm(() => {
|
|
|
4741
4882
|
init_output2();
|
|
4742
4883
|
init_api();
|
|
4743
4884
|
init_output();
|
|
4885
|
+
sourceTypeDescription2 = `${customerSourceTypes.join(", ")} (aliases: ${customerSourceTypeAliases.join(", ")})`;
|
|
4744
4886
|
get_default3 = defineCommand2({
|
|
4745
4887
|
meta: {
|
|
4746
4888
|
name: "get",
|
|
@@ -4749,9 +4891,10 @@ var init_get3 = __esm(() => {
|
|
|
4749
4891
|
"",
|
|
4750
4892
|
"Examples:",
|
|
4751
4893
|
" outlit sources get --source-type CALL --source-id call_123",
|
|
4894
|
+
" outlit sources get --source-type OPPORTUNITY --source-id opp_123",
|
|
4752
4895
|
" outlit sources get --source-type SUPPORT_TICKET --source-id ticket_456 --json",
|
|
4753
4896
|
"",
|
|
4754
|
-
`Source types: ${
|
|
4897
|
+
`Source types: ${sourceTypeDescription2}`,
|
|
4755
4898
|
"",
|
|
4756
4899
|
AGENT_JSON_HINT
|
|
4757
4900
|
].join(`
|
|
@@ -4762,7 +4905,7 @@ var init_get3 = __esm(() => {
|
|
|
4762
4905
|
...outputArgs,
|
|
4763
4906
|
"source-type": {
|
|
4764
4907
|
type: "string",
|
|
4765
|
-
description:
|
|
4908
|
+
description: `Source type (${sourceTypeDescription2})`,
|
|
4766
4909
|
required: true
|
|
4767
4910
|
},
|
|
4768
4911
|
"source-id": {
|
|
@@ -4773,15 +4916,16 @@ var init_get3 = __esm(() => {
|
|
|
4773
4916
|
},
|
|
4774
4917
|
async run({ args }) {
|
|
4775
4918
|
const json = !!args.json;
|
|
4776
|
-
|
|
4919
|
+
const sourceType = normalizeCustomerSourceType(args["source-type"]);
|
|
4920
|
+
if (!sourceType) {
|
|
4777
4921
|
return outputError({
|
|
4778
|
-
message: `--source-type must be one of ${
|
|
4922
|
+
message: `--source-type must be one of ${customerSourceTypeInputs.join(", ")}`,
|
|
4779
4923
|
code: "invalid_input"
|
|
4780
4924
|
}, json);
|
|
4781
4925
|
}
|
|
4782
4926
|
const client = await getClientOrExit(args["api-key"], json);
|
|
4783
4927
|
return runTool(client, customerToolContracts.outlit_get_source.toolName, {
|
|
4784
|
-
sourceType
|
|
4928
|
+
sourceType,
|
|
4785
4929
|
sourceId: args["source-id"]
|
|
4786
4930
|
}, json);
|
|
4787
4931
|
}
|
|
@@ -4821,7 +4965,7 @@ var exports_search = {};
|
|
|
4821
4965
|
__export(exports_search, {
|
|
4822
4966
|
default: () => search_default
|
|
4823
4967
|
});
|
|
4824
|
-
var search_default;
|
|
4968
|
+
var sourceTypeDescription3, search_default;
|
|
4825
4969
|
var init_search = __esm(() => {
|
|
4826
4970
|
init_dist4();
|
|
4827
4971
|
init_dist();
|
|
@@ -4830,6 +4974,7 @@ var init_search = __esm(() => {
|
|
|
4830
4974
|
init_api();
|
|
4831
4975
|
init_config();
|
|
4832
4976
|
init_output();
|
|
4977
|
+
sourceTypeDescription3 = `${customerSourceTypes.join(", ")} (aliases: ${customerSourceTypeAliases.join(", ")})`;
|
|
4833
4978
|
search_default = defineCommand2({
|
|
4834
4979
|
meta: {
|
|
4835
4980
|
name: "search",
|
|
@@ -4844,7 +4989,7 @@ var init_search = __esm(() => {
|
|
|
4844
4989
|
" outlit search 'churn risk signals' --customer acme.com",
|
|
4845
4990
|
" outlit search 'expansion opportunities' --top-k 50 --json",
|
|
4846
4991
|
" outlit search 'support escalations' --after 2025-01-01T00:00:00Z --before 2025-03-31T23:59:59Z",
|
|
4847
|
-
" outlit search '
|
|
4992
|
+
" outlit search 'renewal opportunities' --source-types CALL,OPPORTUNITY",
|
|
4848
4993
|
"",
|
|
4849
4994
|
AGENT_JSON_HINT
|
|
4850
4995
|
].join(`
|
|
@@ -4876,7 +5021,7 @@ var init_search = __esm(() => {
|
|
|
4876
5021
|
},
|
|
4877
5022
|
"source-types": {
|
|
4878
5023
|
type: "string",
|
|
4879
|
-
description: `Comma-separated generic source type filter (${
|
|
5024
|
+
description: `Comma-separated generic source type filter (${sourceTypeDescription3})`
|
|
4880
5025
|
}
|
|
4881
5026
|
},
|
|
4882
5027
|
async run({ args }) {
|
|
@@ -4886,13 +5031,6 @@ var init_search = __esm(() => {
|
|
|
4886
5031
|
return outputError({ message: "--top-k must be an integer between 1 and 50", code: "invalid_input" }, json);
|
|
4887
5032
|
}
|
|
4888
5033
|
const sourceTypes = args["source-types"] ? splitCsv(args["source-types"]).map((item) => item.trim()).filter(Boolean) : undefined;
|
|
4889
|
-
const invalidSourceTypes = sourceTypes?.filter((value) => !customerSourceTypes.includes(value));
|
|
4890
|
-
if (invalidSourceTypes && invalidSourceTypes.length > 0) {
|
|
4891
|
-
return outputError({
|
|
4892
|
-
message: `Unknown source types: ${invalidSourceTypes.join(", ")}. Allowed: ${customerSourceTypes.join(", ")}`,
|
|
4893
|
-
code: "invalid_input"
|
|
4894
|
-
}, json);
|
|
4895
|
-
}
|
|
4896
5034
|
const resolved = resolveCustomerContextSearchInput({
|
|
4897
5035
|
query: args.query,
|
|
4898
5036
|
customer: args.customer,
|
|
@@ -4919,7 +5057,7 @@ __export(exports_sql, {
|
|
|
4919
5057
|
default: () => sql_default
|
|
4920
5058
|
});
|
|
4921
5059
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4922
|
-
var sql_default;
|
|
5060
|
+
var publicSqlViews, sql_default;
|
|
4923
5061
|
var init_sql = __esm(() => {
|
|
4924
5062
|
init_dist4();
|
|
4925
5063
|
init_dist();
|
|
@@ -4927,21 +5065,22 @@ var init_sql = __esm(() => {
|
|
|
4927
5065
|
init_output2();
|
|
4928
5066
|
init_api();
|
|
4929
5067
|
init_output();
|
|
5068
|
+
publicSqlViews = ["activity", "customers", "users", "revenue"];
|
|
4930
5069
|
sql_default = defineCommand2({
|
|
4931
5070
|
meta: {
|
|
4932
5071
|
name: "sql",
|
|
4933
5072
|
description: [
|
|
4934
|
-
"Run a SQL query against Outlit
|
|
5073
|
+
"Run a SQL query against Outlit analytics views.",
|
|
4935
5074
|
"",
|
|
4936
5075
|
"Provide the query as a positional argument or via --query-file.",
|
|
4937
5076
|
"When both are provided, --query-file takes precedence.",
|
|
4938
5077
|
"",
|
|
4939
|
-
`Available
|
|
5078
|
+
`Available views: ${publicSqlViews.join(", ")}`,
|
|
4940
5079
|
"",
|
|
4941
5080
|
"Examples:",
|
|
4942
|
-
" outlit sql 'SELECT * FROM
|
|
5081
|
+
" outlit sql 'SELECT * FROM activity LIMIT 10'",
|
|
4943
5082
|
" outlit sql --query-file ./my-query.sql",
|
|
4944
|
-
" outlit sql 'SELECT count(*) FROM
|
|
5083
|
+
" outlit sql 'SELECT count(*) FROM activity' --limit 1 --json",
|
|
4945
5084
|
"",
|
|
4946
5085
|
AGENT_JSON_HINT
|
|
4947
5086
|
].join(`
|
|
@@ -4984,8 +5123,8 @@ var init_sql = __esm(() => {
|
|
|
4984
5123
|
return outputError({ message: "Provide a SQL query or --query-file", code: "missing_input" }, json);
|
|
4985
5124
|
}
|
|
4986
5125
|
const limit = Number(args.limit);
|
|
4987
|
-
if (!Number.
|
|
4988
|
-
return outputError({ message: "--limit must be
|
|
5126
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e4) {
|
|
5127
|
+
return outputError({ message: "--limit must be an integer between 1 and 10000", code: "invalid_input" }, json);
|
|
4989
5128
|
}
|
|
4990
5129
|
return runTool(client, customerToolContracts.outlit_query.toolName, { sql, limit }, json);
|
|
4991
5130
|
}
|
|
@@ -5176,28 +5315,29 @@ var exports_schema = {};
|
|
|
5176
5315
|
__export(exports_schema, {
|
|
5177
5316
|
default: () => schema_default
|
|
5178
5317
|
});
|
|
5179
|
-
var schema_default;
|
|
5318
|
+
var publicSqlViews2, schema_default;
|
|
5180
5319
|
var init_schema = __esm(() => {
|
|
5181
5320
|
init_dist4();
|
|
5182
5321
|
init_dist();
|
|
5183
5322
|
init_auth();
|
|
5184
5323
|
init_output2();
|
|
5185
5324
|
init_api();
|
|
5325
|
+
publicSqlViews2 = ["activity", "customers", "users", "revenue"];
|
|
5186
5326
|
schema_default = defineCommand2({
|
|
5187
5327
|
meta: {
|
|
5188
5328
|
name: "schema",
|
|
5189
5329
|
description: [
|
|
5190
|
-
"Describe the analytics
|
|
5330
|
+
"Describe the available analytics views.",
|
|
5191
5331
|
"",
|
|
5192
|
-
"Without a
|
|
5193
|
-
"With a
|
|
5332
|
+
"Without a view name, returns the full schema for all views.",
|
|
5333
|
+
"With a view name, returns detailed column info for that view.",
|
|
5194
5334
|
"",
|
|
5195
|
-
`Available
|
|
5335
|
+
`Available views: ${publicSqlViews2.join(", ")}`,
|
|
5196
5336
|
"",
|
|
5197
5337
|
"Examples:",
|
|
5198
5338
|
" outlit schema",
|
|
5199
|
-
" outlit schema
|
|
5200
|
-
" outlit schema
|
|
5339
|
+
" outlit schema activity",
|
|
5340
|
+
" outlit schema customers --json",
|
|
5201
5341
|
"",
|
|
5202
5342
|
AGENT_JSON_HINT
|
|
5203
5343
|
].join(`
|
|
@@ -5206,9 +5346,9 @@ var init_schema = __esm(() => {
|
|
|
5206
5346
|
args: {
|
|
5207
5347
|
...authArgs,
|
|
5208
5348
|
...outputArgs,
|
|
5209
|
-
|
|
5349
|
+
view: {
|
|
5210
5350
|
type: "positional",
|
|
5211
|
-
description: `
|
|
5351
|
+
description: `View to describe (${publicSqlViews2.join(", ")}). Optional.`,
|
|
5212
5352
|
required: false
|
|
5213
5353
|
}
|
|
5214
5354
|
},
|
|
@@ -5216,8 +5356,9 @@ var init_schema = __esm(() => {
|
|
|
5216
5356
|
const json = !!args.json;
|
|
5217
5357
|
const client = await getClientOrExit(args["api-key"], json);
|
|
5218
5358
|
const params = {};
|
|
5219
|
-
|
|
5220
|
-
|
|
5359
|
+
const view = args.view ?? args.table;
|
|
5360
|
+
if (view)
|
|
5361
|
+
params.table = view;
|
|
5221
5362
|
return runTool(client, customerToolContracts.outlit_schema.toolName, params, json);
|
|
5222
5363
|
}
|
|
5223
5364
|
});
|
|
@@ -5252,14 +5393,22 @@ var INTEGRATION_PROVIDERS, PROVIDER_NAMES;
|
|
|
5252
5393
|
var init_providers = __esm(() => {
|
|
5253
5394
|
init_output();
|
|
5254
5395
|
INTEGRATION_PROVIDERS = {
|
|
5396
|
+
hubspot: { id: "hubspot", name: "HubSpot", category: "crm", authType: "oauth" },
|
|
5255
5397
|
slack: { id: "slack", name: "Slack", category: "communication", authType: "oauth" },
|
|
5256
5398
|
gmail: { id: "google-mail", name: "Gmail", category: "communication", authType: "oauth" },
|
|
5399
|
+
"google-mail": {
|
|
5400
|
+
id: "google-mail",
|
|
5401
|
+
name: "Gmail",
|
|
5402
|
+
category: "communication",
|
|
5403
|
+
authType: "oauth"
|
|
5404
|
+
},
|
|
5257
5405
|
"google-calendar": {
|
|
5258
5406
|
id: "google-calendar",
|
|
5259
5407
|
name: "Google Calendar",
|
|
5260
5408
|
category: "calendar",
|
|
5261
5409
|
authType: "oauth"
|
|
5262
5410
|
},
|
|
5411
|
+
granola: { id: "granola", name: "Granola", category: "calls", authType: "oauth" },
|
|
5263
5412
|
pylon: {
|
|
5264
5413
|
id: "pylon",
|
|
5265
5414
|
name: "Pylon",
|
|
@@ -5683,6 +5832,25 @@ var exports_status2 = {};
|
|
|
5683
5832
|
__export(exports_status2, {
|
|
5684
5833
|
default: () => status_default2
|
|
5685
5834
|
});
|
|
5835
|
+
function hideBlankModelSyncs(data) {
|
|
5836
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
5837
|
+
return data;
|
|
5838
|
+
}
|
|
5839
|
+
const record = data;
|
|
5840
|
+
if (!Array.isArray(record.syncs)) {
|
|
5841
|
+
return data;
|
|
5842
|
+
}
|
|
5843
|
+
return {
|
|
5844
|
+
...record,
|
|
5845
|
+
syncs: record.syncs.filter((sync) => {
|
|
5846
|
+
if (typeof sync !== "object" || sync === null || Array.isArray(sync)) {
|
|
5847
|
+
return false;
|
|
5848
|
+
}
|
|
5849
|
+
const model = sync.model;
|
|
5850
|
+
return typeof model === "string" && model.trim().length > 0;
|
|
5851
|
+
})
|
|
5852
|
+
};
|
|
5853
|
+
}
|
|
5686
5854
|
var status_default2;
|
|
5687
5855
|
var init_status2 = __esm(() => {
|
|
5688
5856
|
init_dist();
|
|
@@ -5723,6 +5891,7 @@ var init_status2 = __esm(() => {
|
|
|
5723
5891
|
const { provider } = resolveProviderOrExit(args.provider, json);
|
|
5724
5892
|
return runTool(client, "outlit_integration_sync_status", { provider: provider.id }, json, {
|
|
5725
5893
|
spinnerMessage: "Fetching sync status...",
|
|
5894
|
+
transform: hideBlankModelSyncs,
|
|
5726
5895
|
table: {
|
|
5727
5896
|
columns: [
|
|
5728
5897
|
{ header: "Model", key: "model" },
|
|
@@ -6114,7 +6283,7 @@ var init_completions = __esm(() => {
|
|
|
6114
6283
|
desc: "Get one exact source record",
|
|
6115
6284
|
flags: [
|
|
6116
6285
|
...COMMON,
|
|
6117
|
-
{ name: "--source-type", desc: "
|
|
6286
|
+
{ name: "--source-type", desc: "Source type" },
|
|
6118
6287
|
{ name: "--source-id", desc: "Exact source ID" }
|
|
6119
6288
|
]
|
|
6120
6289
|
}
|
|
@@ -6141,7 +6310,7 @@ var init_completions = __esm(() => {
|
|
|
6141
6310
|
{ name: "--limit", desc: "Max rows to return" }
|
|
6142
6311
|
]
|
|
6143
6312
|
},
|
|
6144
|
-
{ name: "schema", desc: "Discover
|
|
6313
|
+
{ name: "schema", desc: "Discover analytics view schemas", flags: [...COMMON] },
|
|
6145
6314
|
{
|
|
6146
6315
|
name: "integrations",
|
|
6147
6316
|
desc: "Manage platform integrations",
|
|
@@ -6965,11 +7134,11 @@ Usage examples:
|
|
|
6965
7134
|
outlit users list --journey-stage CHAMPION
|
|
6966
7135
|
outlit facts list acme.com --fact-types CHURN_RISK,EXPANSION
|
|
6967
7136
|
outlit facts get --fact-id fact_123 --include evidence
|
|
6968
|
-
outlit sources get --source-type
|
|
6969
|
-
outlit search 'pricing objections last quarter' --source-types CALL,
|
|
6970
|
-
outlit sql 'SELECT * FROM
|
|
7137
|
+
outlit sources get --source-type OPPORTUNITY --source-id opp_123
|
|
7138
|
+
outlit search 'pricing objections last quarter' --source-types CALL,OPPORTUNITY
|
|
7139
|
+
outlit sql 'SELECT * FROM activity LIMIT 10'
|
|
6971
7140
|
outlit notify --title 'Risk found' '{"customer":"acme.com"}'
|
|
6972
|
-
outlit schema
|
|
7141
|
+
outlit schema activity
|
|
6973
7142
|
outlit doctor --json
|
|
6974
7143
|
|
|
6975
7144
|
For AI agents: commands auto-output JSON when stdout is piped. No --json flag needed.`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outlit/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"description": "CLI for Outlit customer intelligence platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@clack/prompts": "^1.0.1",
|
|
34
|
-
"@outlit/tools": "^0.1.
|
|
34
|
+
"@outlit/tools": "^0.1.3",
|
|
35
35
|
"citty": "^0.2.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|