@intercom/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,11 +9,14 @@ import {
9
9
  applyJq,
10
10
  detectAuthType,
11
11
  formatJson
12
- } from "./chunk-QEXGKXTU.js";
12
+ } from "./chunk-Y4JK5AHH.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { Command } from "commander";
16
16
 
17
+ // src/commands/api.ts
18
+ import { readFileSync as readFileSync3 } from "fs";
19
+
17
20
  // src/api/timeouts.ts
18
21
  var API_TIMEOUT_MS = 3e4;
19
22
  var SIMULATION_POLL_TIMEOUT_MS = 6e5;
@@ -163,6 +166,22 @@ var IntercomClient = class {
163
166
  retryAfter
164
167
  );
165
168
  }
169
+ const RETRYABLE_5XX = [502, 503, 504];
170
+ if (RETRYABLE_5XX.includes(response.status)) {
171
+ if (attempt < this.maxRetries) {
172
+ const backoffMs = Math.pow(2, attempt) * 1e3;
173
+ if (this.verbose) {
174
+ console.error(
175
+ `Server error ${response.status}. Retrying in ${backoffMs / 1e3}s (attempt ${attempt + 1}/${this.maxRetries})...`
176
+ );
177
+ }
178
+ await new Promise((r) => setTimeout(r, backoffMs));
179
+ continue;
180
+ }
181
+ throw new CLIError(
182
+ `Server error ${response.status} after ${this.maxRetries} retries: ${errorMsg}`
183
+ );
184
+ }
166
185
  throw new CLIError(errorMsg);
167
186
  }
168
187
  throw new CLIError("Request failed after retries");
@@ -392,9 +411,9 @@ var FallbackBackend = class {
392
411
  }
393
412
  }
394
413
  };
395
- function createDefaultBackend(configDir) {
396
- if (configDir) {
397
- return new EncryptedFileBackend(configDir);
414
+ function createDefaultBackend(configDir2) {
415
+ if (configDir2) {
416
+ return new EncryptedFileBackend(configDir2);
398
417
  }
399
418
  return new FallbackBackend(
400
419
  new NativeKeychainBackend(),
@@ -433,8 +452,8 @@ var CONFIG_FILE = "config.json";
433
452
  function defaultConfigDir() {
434
453
  return join2(homedir2(), ".intercom");
435
454
  }
436
- async function readConfig(configDir) {
437
- const dir = configDir ?? defaultConfigDir();
455
+ async function readConfig(configDir2) {
456
+ const dir = configDir2 ?? defaultConfigDir();
438
457
  try {
439
458
  const raw = await readFile(join2(dir, CONFIG_FILE), "utf-8");
440
459
  return JSON.parse(raw);
@@ -442,27 +461,27 @@ async function readConfig(configDir) {
442
461
  return { workspaces: {} };
443
462
  }
444
463
  }
445
- async function writeConfig(config, configDir) {
446
- const dir = configDir ?? defaultConfigDir();
464
+ async function writeConfig(config, configDir2) {
465
+ const dir = configDir2 ?? defaultConfigDir();
447
466
  await mkdir(dir, { recursive: true });
448
467
  await atomicWriteFile(join2(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
449
468
  }
450
- async function addWorkspace(configDir, workspace) {
451
- const config = await readConfig(configDir);
469
+ async function addWorkspace(configDir2, workspace) {
470
+ const config = await readConfig(configDir2);
452
471
  config.workspaces[workspace.app_id] = workspace;
453
472
  if (!config.default) {
454
473
  config.default = workspace.app_id;
455
474
  }
456
- await writeConfig(config, configDir);
475
+ await writeConfig(config, configDir2);
457
476
  }
458
- async function removeWorkspace(configDir, appId) {
459
- const config = await readConfig(configDir);
477
+ async function removeWorkspace(configDir2, appId) {
478
+ const config = await readConfig(configDir2);
460
479
  delete config.workspaces[appId];
461
480
  if (config.default === appId) {
462
481
  const remaining = Object.keys(config.workspaces);
463
482
  config.default = remaining.length > 0 ? remaining[0] : void 0;
464
483
  }
465
- await writeConfig(config, configDir);
484
+ await writeConfig(config, configDir2);
466
485
  }
467
486
 
468
487
  // src/commands/helpers.ts
@@ -479,8 +498,8 @@ async function resolveClient(options = {}) {
479
498
  timeoutMs: options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0
480
499
  });
481
500
  }
482
- const configDir = process.env.INTERCOM_CONFIG_DIR;
483
- const config = await readConfig(configDir);
501
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
502
+ const config = await readConfig(configDir2);
484
503
  const appId = options.workspace ?? config.default;
485
504
  if (!appId) {
486
505
  throw new AuthError(
@@ -492,7 +511,7 @@ async function resolveClient(options = {}) {
492
511
  `Default workspace "${appId}" is not in configured workspaces. Run "intercom auth bootstrap" or "intercom auth login --token <token>" to re-authenticate.`
493
512
  );
494
513
  }
495
- const store = new CredentialStore(createDefaultBackend(configDir));
514
+ const store = new CredentialStore(createDefaultBackend(configDir2));
496
515
  const cred = await store.get(appId);
497
516
  if (!cred) {
498
517
  throw new AuthError(
@@ -603,6 +622,20 @@ function parseTypedFields(fields, typed) {
603
622
  }
604
623
 
605
624
  // src/commands/api.ts
625
+ function readInput(input) {
626
+ let raw;
627
+ if (input === "-") {
628
+ const fd = readFileSync3(0, "utf-8");
629
+ raw = fd;
630
+ } else {
631
+ raw = readFileSync3(input, "utf-8");
632
+ }
633
+ try {
634
+ return JSON.parse(raw);
635
+ } catch {
636
+ throw new CLIError(`Failed to parse JSON from ${input === "-" ? "stdin" : input}`);
637
+ }
638
+ }
606
639
  function registerApiCommand(program) {
607
640
  program.command("api").description("Make an authenticated API request (like gh api)").argument("<endpoint>", "API endpoint path (e.g., /articles)").addHelpText(
608
641
  "after",
@@ -615,16 +648,18 @@ Examples:
615
648
  ).option("-X, --method <method>", "HTTP method").option("-f, --field <key=value...>", "String field").option(
616
649
  "-F, --typed-field <key=value...>",
617
650
  "Typed field (bools, numbers, file reads)"
618
- ).option("--jq <expr>", "Filter response with jq expression").option("--json", "Output as JSON").option("--paginate", "Auto-paginate through all pages").option("--verbose", "Show full HTTP request/response").action(async function(endpoint) {
651
+ ).option("--jq <expr>", "Filter response with jq expression").option("--json", "Output as JSON").option("--input <file>", "Read JSON body from file (use - for stdin)").option("--paginate", "Auto-paginate through all pages").option("--verbose", "Show full HTTP request/response").action(async function(endpoint) {
619
652
  const options = this.optsWithGlobals();
620
653
  const client = await resolveClient(options);
621
654
  const stringFields = options.field ?? [];
622
655
  const typedFields = options.typedField ?? [];
656
+ const inputBody = options.input ? readInput(options.input) : {};
623
657
  const body = {
658
+ ...inputBody,
624
659
  ...parseTypedFields(stringFields, false),
625
660
  ...parseTypedFields(typedFields, true)
626
661
  };
627
- const hasBody = stringFields.length > 0 || typedFields.length > 0;
662
+ const hasBody = stringFields.length > 0 || typedFields.length > 0 || !!options.input;
628
663
  const method = options.method ?? (hasBody ? "POST" : "GET");
629
664
  let result;
630
665
  if (options.paginate && method.toUpperCase() === "GET") {
@@ -668,7 +703,14 @@ Examples:
668
703
 
669
704
  // src/commands/auth/login.ts
670
705
  function registerLoginCommand(auth) {
671
- auth.command("login").description("Authenticate with Intercom").option("--token <token>", "Authenticate with an API key or token directly").option("--device", "Use device flow (for headless environments)").action(async (options) => {
706
+ auth.command("login").description("Authenticate with Intercom").option("--token <token>", "Authenticate with an API key or token directly").option("--device", "Use device flow (for headless environments)").addHelpText(
707
+ "after",
708
+ `
709
+ Examples:
710
+ $ intercom auth login --token <your-api-token>
711
+
712
+ Environment: Set INTERCOM_TOKEN to skip login entirely.`
713
+ ).action(async (options) => {
672
714
  if (options.token) {
673
715
  await loginWithToken(options.token);
674
716
  } else if (options.device) {
@@ -692,17 +734,17 @@ async function loginWithToken(token) {
692
734
  const app = me.app;
693
735
  const appId = app?.id_code ?? me.id;
694
736
  const name = app?.name ?? "Unknown";
695
- const configDir = process.env.INTERCOM_CONFIG_DIR;
696
- const store = new CredentialStore(createDefaultBackend(configDir));
737
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
738
+ const store = new CredentialStore(createDefaultBackend(configDir2));
697
739
  await store.set(appId, { token, auth_type: authType, app_id: appId });
698
- await addWorkspace(configDir, { app_id: appId, name });
740
+ await addWorkspace(configDir2, { app_id: appId, name });
699
741
  console.log(`Authenticated to ${name} (${appId})`);
700
742
  }
701
743
 
702
744
  // src/auth/cookies.ts
703
745
  function extractCookies(response) {
704
746
  const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : (response.headers.get("set-cookie") ?? "").split(/,(?=[^ ])/).filter(Boolean);
705
- return setCookies.map((c) => c.split(";")[0]).join("; ");
747
+ return setCookies.map((c2) => c2.split(";")[0]).join("; ");
706
748
  }
707
749
  function mergeCookies(existing, newCookies) {
708
750
  const map = /* @__PURE__ */ new Map();
@@ -1026,10 +1068,10 @@ function registerBootstrapCommand(auth) {
1026
1068
  console.log(`Found app package: ${appPackage.name} (id: ${appPackage.id})`);
1027
1069
  }
1028
1070
  console.log("Access token acquired");
1029
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1030
- const store = new CredentialStore(createDefaultBackend(configDir));
1071
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1072
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1031
1073
  await store.set(appId, { token, auth_type: "oauth", app_id: appId });
1032
- await addWorkspace(configDir, { app_id: appId, name: appId });
1074
+ await addWorkspace(configDir2, { app_id: appId, name: appId });
1033
1075
  console.log(`
1034
1076
  Authenticated to workspace ${appId}`);
1035
1077
  } catch (err) {
@@ -1041,15 +1083,85 @@ Authenticated to workspace ${appId}`);
1041
1083
  }
1042
1084
 
1043
1085
  // src/output/formatter.ts
1086
+ import pc from "picocolors";
1087
+ function isColorEnabled() {
1088
+ return !!process.stdout.isTTY && !process.env.NO_COLOR;
1089
+ }
1090
+ function c(fn, s) {
1091
+ return isColorEnabled() ? fn(s) : s;
1092
+ }
1093
+ var STATUS_COLORS = {
1094
+ // green
1095
+ active: pc.green,
1096
+ open: pc.green,
1097
+ published: pc.green,
1098
+ enabled: pc.green,
1099
+ verified: pc.green,
1100
+ true: pc.green,
1101
+ // red
1102
+ error: pc.red,
1103
+ closed: pc.red,
1104
+ deleted: pc.red,
1105
+ disabled: pc.red,
1106
+ false: pc.red,
1107
+ // yellow
1108
+ pending: pc.yellow,
1109
+ draft: pc.yellow,
1110
+ snoozed: pc.yellow
1111
+ };
1112
+ function colorizeStatus(value) {
1113
+ if (!isColorEnabled()) return value;
1114
+ const fn = STATUS_COLORS[value.toLowerCase()];
1115
+ return fn ? fn(value) : value;
1116
+ }
1117
+ function truncate(value, max) {
1118
+ if (value.length <= max) return value;
1119
+ return value.slice(0, max - 1) + "\u2026";
1120
+ }
1121
+ var DEFAULT_MAX_COLUMNS = 8;
1122
+ var DEFAULT_TRUNCATION = 50;
1123
+ var WIDE_TRUNCATION = 200;
1124
+ var STATUS_KEYS = /* @__PURE__ */ new Set([
1125
+ "STATE",
1126
+ "STATUS",
1127
+ "OPEN",
1128
+ "ROLE",
1129
+ "ENABLED"
1130
+ ]);
1131
+ function isWide() {
1132
+ return process.argv.includes("--wide");
1133
+ }
1044
1134
  function table({ columns, rows }) {
1045
1135
  if (rows.length === 0) return "";
1046
- const widths = columns.map(
1047
- (col) => Math.max(col.length, ...rows.map((row) => (row[col] ?? "").length))
1136
+ const wide = isWide();
1137
+ const maxTrunc = wide ? WIDE_TRUNCATION : DEFAULT_TRUNCATION;
1138
+ const displayColumns = wide ? columns : columns.slice(0, DEFAULT_MAX_COLUMNS);
1139
+ const truncatedRows = rows.map((row) => {
1140
+ const out = {};
1141
+ for (const col of displayColumns) {
1142
+ out[col] = truncate(row[col] ?? "", maxTrunc);
1143
+ }
1144
+ return out;
1145
+ });
1146
+ const widths = displayColumns.map(
1147
+ (col) => Math.max(
1148
+ col.length,
1149
+ ...truncatedRows.map((row) => (row[col] ?? "").length)
1150
+ )
1048
1151
  );
1049
- const formatRow = (values) => values.map((val, i) => i < values.length - 1 ? val.padEnd(widths[i]) : val).join(" ");
1050
- const header = formatRow(columns);
1051
- const body = rows.map(
1052
- (row) => formatRow(columns.map((col) => row[col] ?? ""))
1152
+ const formatRow = (values, isHeader) => values.map((val, i) => {
1153
+ const col = displayColumns[i];
1154
+ const padded = i < values.length - 1 ? val.padEnd(widths[i]) : val;
1155
+ if (isHeader) return c(pc.bold, padded);
1156
+ if (STATUS_KEYS.has(col)) return colorizeStatus(padded);
1157
+ return padded;
1158
+ }).join(" ");
1159
+ const header = formatRow(displayColumns, true);
1160
+ const body = truncatedRows.map(
1161
+ (row) => formatRow(
1162
+ displayColumns.map((col) => row[col] ?? ""),
1163
+ false
1164
+ )
1053
1165
  );
1054
1166
  return [header, ...body].join("\n");
1055
1167
  }
@@ -1059,19 +1171,25 @@ function detail(data) {
1059
1171
  );
1060
1172
  if (entries.length === 0) return "";
1061
1173
  const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
1062
- return entries.map(([key, value]) => `${key.padEnd(maxKeyLen)}: ${value}`).join("\n");
1174
+ return entries.map(([key, value]) => {
1175
+ const label = c(pc.bold, key.padEnd(maxKeyLen));
1176
+ const strVal = String(value);
1177
+ const isStatus = STATUS_KEYS.has(key.toUpperCase());
1178
+ const colored = isStatus ? colorizeStatus(strVal) : strVal;
1179
+ return `${label}: ${colored}`;
1180
+ }).join("\n");
1063
1181
  }
1064
1182
 
1065
1183
  // src/commands/auth/status.ts
1066
1184
  function registerStatusCommand(auth) {
1067
1185
  auth.command("status").description("Show current authentication status").option("--json", "Output as JSON").action(async (options) => {
1068
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1069
- const config = await readConfig(configDir);
1186
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1187
+ const config = await readConfig(configDir2);
1070
1188
  const appId = config.default;
1071
1189
  if (!appId) {
1072
1190
  throw new AuthError('Not authenticated. Run "intercom auth login" to get started.');
1073
1191
  }
1074
- const store = new CredentialStore(createDefaultBackend(configDir));
1192
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1075
1193
  const cred = await store.get(appId);
1076
1194
  if (!cred) {
1077
1195
  throw new AuthError('Not authenticated. Run "intercom auth login" to get started.');
@@ -1098,15 +1216,15 @@ function registerStatusCommand(auth) {
1098
1216
  // src/commands/auth/logout.ts
1099
1217
  function registerLogoutCommand(auth) {
1100
1218
  auth.command("logout").description("Remove stored credentials").option("--workspace <id>", "Workspace app_id to log out of").action(async (options) => {
1101
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1102
- const config = await readConfig(configDir);
1219
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1220
+ const config = await readConfig(configDir2);
1103
1221
  const appId = options.workspace ?? config.default;
1104
1222
  if (!appId) {
1105
1223
  throw new AuthError("Not authenticated.");
1106
1224
  }
1107
- const store = new CredentialStore(createDefaultBackend(configDir));
1225
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1108
1226
  await store.delete(appId);
1109
- await removeWorkspace(configDir, appId);
1227
+ await removeWorkspace(configDir2, appId);
1110
1228
  const name = config.workspaces[appId]?.name ?? appId;
1111
1229
  console.log(`Logged out of ${name} (${appId})`);
1112
1230
  });
@@ -1145,10 +1263,10 @@ function registerSignupCommand(auth) {
1145
1263
  password,
1146
1264
  appId: workspace.id
1147
1265
  });
1148
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1149
- const store = new CredentialStore(createDefaultBackend(configDir));
1266
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1267
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1150
1268
  await store.set(appId, { token, auth_type: "oauth", app_id: appId });
1151
- await addWorkspace(configDir, { app_id: appId, name: workspace.name });
1269
+ await addWorkspace(configDir2, { app_id: appId, name: workspace.name });
1152
1270
  console.log(`
1153
1271
  Authenticated to workspace ${appId}`);
1154
1272
  } catch {
@@ -1194,6 +1312,64 @@ function registerVerifyEmailCommand(auth) {
1194
1312
  });
1195
1313
  }
1196
1314
 
1315
+ // src/commands/auth/list.ts
1316
+ function registerListCommand(auth) {
1317
+ auth.command("list").description("List all configured workspaces").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").action(async function() {
1318
+ const opts = this.optsWithGlobals();
1319
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1320
+ const config = await readConfig(configDir2);
1321
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1322
+ const workspaces = await Promise.all(
1323
+ Object.values(config.workspaces).map(async (ws) => {
1324
+ const cred = await store.get(ws.app_id);
1325
+ return {
1326
+ app_id: ws.app_id,
1327
+ name: ws.name,
1328
+ auth_type: cred?.auth_type ?? "unknown",
1329
+ default: ws.app_id === config.default
1330
+ };
1331
+ })
1332
+ );
1333
+ render(
1334
+ workspaces,
1335
+ opts,
1336
+ () => {
1337
+ if (workspaces.length === 0) {
1338
+ return 'No workspaces configured. Run "intercom auth login" to get started.';
1339
+ }
1340
+ const rows = workspaces.map((ws) => ({
1341
+ DEFAULT: ws.default ? "*" : "",
1342
+ APP_ID: ws.app_id,
1343
+ NAME: ws.name,
1344
+ AUTH_TYPE: ws.auth_type
1345
+ }));
1346
+ return table({
1347
+ columns: ["DEFAULT", "APP_ID", "NAME", "AUTH_TYPE"],
1348
+ rows
1349
+ });
1350
+ },
1351
+ workspaces
1352
+ );
1353
+ });
1354
+ }
1355
+
1356
+ // src/commands/auth/switch.ts
1357
+ function registerSwitchCommand(auth) {
1358
+ auth.command("switch <app_id>").description("Switch the default workspace").action(async (appId) => {
1359
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1360
+ const config = await readConfig(configDir2);
1361
+ if (!config.workspaces[appId]) {
1362
+ const available = Object.keys(config.workspaces);
1363
+ const hint = available.length > 0 ? ` Available: ${available.join(", ")}` : ' Run "intercom auth login" to add a workspace.';
1364
+ throw new CLIError(`Workspace "${appId}" not found.${hint}`);
1365
+ }
1366
+ config.default = appId;
1367
+ await writeConfig(config, configDir2);
1368
+ const name = config.workspaces[appId].name;
1369
+ console.log(`Switched to ${name} (${appId})`);
1370
+ });
1371
+ }
1372
+
1197
1373
  // src/commands/auth/index.ts
1198
1374
  function registerAuthCommand(program) {
1199
1375
  const auth = program.command("auth").description("Manage authentication credentials").addHelpText(
@@ -1202,7 +1378,9 @@ function registerAuthCommand(program) {
1202
1378
  Examples:
1203
1379
  intercom auth login --token <api-token>
1204
1380
  intercom auth status
1205
- intercom auth logout`
1381
+ intercom auth logout
1382
+
1383
+ Environment: Set INTERCOM_TOKEN to skip login entirely.`
1206
1384
  );
1207
1385
  registerLoginCommand(auth);
1208
1386
  registerBootstrapCommand(auth);
@@ -1210,6 +1388,8 @@ Examples:
1210
1388
  registerLogoutCommand(auth);
1211
1389
  registerSignupCommand(auth);
1212
1390
  registerVerifyEmailCommand(auth);
1391
+ registerListCommand(auth);
1392
+ registerSwitchCommand(auth);
1213
1393
  }
1214
1394
 
1215
1395
  // src/commands/me.ts
@@ -1245,7 +1425,7 @@ function htmlToText(html) {
1245
1425
 
1246
1426
  // src/commands/articles.ts
1247
1427
  function registerArticlesCommand(program) {
1248
- const articles = program.command("articles").description("Manage help center articles").addHelpText(
1428
+ const articles = program.command("articles").description("Manage help center articles (list, get, create, update, delete)").addHelpText(
1249
1429
  "after",
1250
1430
  `
1251
1431
  Examples:
@@ -1255,7 +1435,13 @@ Examples:
1255
1435
  intercom articles update 123 -f state=published
1256
1436
  intercom articles delete 123`
1257
1437
  );
1258
- articles.command("list").description("List help center articles").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").option("--limit <n>", "Max results to return", "25").option("--all", "Auto-paginate all pages").action(async function() {
1438
+ articles.command("list").description("List help center articles").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").option("--limit <n>", "Max results to return", "25").option("--all", "Auto-paginate all pages").addHelpText(
1439
+ "after",
1440
+ `
1441
+ Examples:
1442
+ $ intercom articles list --json
1443
+ $ intercom articles list --all --jq '.[].title'`
1444
+ ).action(async function() {
1259
1445
  const opts = this.optsWithGlobals();
1260
1446
  const client = await resolveClient(opts);
1261
1447
  let result;
@@ -1312,7 +1498,16 @@ ${body}` : meta;
1312
1498
  "Typed field: bools, numbers, @file reads (repeatable)",
1313
1499
  (v, acc) => [...acc, v],
1314
1500
  []
1315
- ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
1501
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
1502
+ "after",
1503
+ `
1504
+ Examples:
1505
+ $ intercom articles create -f title='Getting Started' -f body='<p>Hello</p>'
1506
+ $ intercom articles create -F body=@article.html -f title='Guide'
1507
+
1508
+ Common fields: title, body, author_id, state, parent_id, parent_type
1509
+ Use -F for typed values (numbers, booleans) and @file reads.`
1510
+ ).action(async function() {
1316
1511
  const opts = this.optsWithGlobals();
1317
1512
  const client = await resolveClient(opts);
1318
1513
  const body = {
@@ -1377,7 +1572,7 @@ ${body}` : meta;
1377
1572
 
1378
1573
  // src/commands/contacts.ts
1379
1574
  function registerContactsCommand(program) {
1380
- const contacts = program.command("contacts").description("Manage contacts").addHelpText(
1575
+ const contacts = program.command("contacts").description("Manage contacts (list, get, create, update, delete, search)").addHelpText(
1381
1576
  "after",
1382
1577
  `
1383
1578
  Examples:
@@ -1403,12 +1598,12 @@ Examples:
1403
1598
  result,
1404
1599
  opts,
1405
1600
  () => {
1406
- const rows = items.map((c) => ({
1407
- ID: String(c.id ?? ""),
1408
- EMAIL: String(c.email ?? ""),
1409
- NAME: String(c.name ?? ""),
1410
- ROLE: String(c.role ?? ""),
1411
- UPDATED: formatTimestamp(c.updated_at)
1601
+ const rows = items.map((c2) => ({
1602
+ ID: String(c2.id ?? ""),
1603
+ EMAIL: String(c2.email ?? ""),
1604
+ NAME: String(c2.name ?? ""),
1605
+ ROLE: String(c2.role ?? ""),
1606
+ UPDATED: formatTimestamp(c2.updated_at)
1412
1607
  }));
1413
1608
  return table({
1414
1609
  columns: ["ID", "EMAIL", "NAME", "ROLE", "UPDATED"],
@@ -1486,6 +1681,45 @@ Examples:
1486
1681
  const deleted = result;
1487
1682
  render(result, opts, () => `Deleted contact ${deleted.id ?? id}`);
1488
1683
  });
1684
+ contacts.command("search").description("Search contacts (POST /contacts/search)").option(
1685
+ "-f, --field <key=value>",
1686
+ "String field (repeatable)",
1687
+ (v, acc) => [...acc, v],
1688
+ []
1689
+ ).option(
1690
+ "-F, --typed-field <key=value>",
1691
+ "Typed field (repeatable)",
1692
+ (v, acc) => [...acc, v],
1693
+ []
1694
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
1695
+ const opts = this.optsWithGlobals();
1696
+ const client = await resolveClient(opts);
1697
+ const body = {
1698
+ ...parseTypedFields(opts.field ?? [], false),
1699
+ ...parseTypedFields(opts.typedField ?? [], true)
1700
+ };
1701
+ const result = await client.post("/contacts/search", body);
1702
+ const response = result;
1703
+ const items = response.data ?? [];
1704
+ render(
1705
+ result,
1706
+ opts,
1707
+ () => {
1708
+ const rows = items.map((c2) => ({
1709
+ ID: String(c2.id ?? ""),
1710
+ EMAIL: String(c2.email ?? ""),
1711
+ NAME: String(c2.name ?? ""),
1712
+ ROLE: String(c2.role ?? ""),
1713
+ UPDATED: formatTimestamp(c2.updated_at)
1714
+ }));
1715
+ return table({
1716
+ columns: ["ID", "EMAIL", "NAME", "ROLE", "UPDATED"],
1717
+ rows
1718
+ });
1719
+ },
1720
+ items
1721
+ );
1722
+ });
1489
1723
  }
1490
1724
 
1491
1725
  // src/commands/conversations.ts
@@ -1562,7 +1796,7 @@ function renderConversationStream(result) {
1562
1796
  return parts.join("\n");
1563
1797
  }
1564
1798
  function registerConversationsCommand(program) {
1565
- const conversations = program.command("conversations").description("Manage conversations").addHelpText(
1799
+ const conversations = program.command("conversations").description("Manage conversations (list, get, reply, search)").addHelpText(
1566
1800
  "after",
1567
1801
  `
1568
1802
  Examples:
@@ -1591,12 +1825,12 @@ Examples:
1591
1825
  result,
1592
1826
  opts,
1593
1827
  () => {
1594
- const rows = items.map((c) => ({
1595
- ID: String(c.id ?? ""),
1596
- STATE: String(c.state ?? ""),
1597
- OPEN: String(c.open ?? ""),
1598
- CREATED: formatTimestamp(c.created_at),
1599
- UPDATED: formatTimestamp(c.updated_at)
1828
+ const rows = items.map((c2) => ({
1829
+ ID: String(c2.id ?? ""),
1830
+ STATE: String(c2.state ?? ""),
1831
+ OPEN: String(c2.open ?? ""),
1832
+ CREATED: formatTimestamp(c2.created_at),
1833
+ UPDATED: formatTimestamp(c2.updated_at)
1600
1834
  }));
1601
1835
  return table({
1602
1836
  columns: ["ID", "STATE", "OPEN", "CREATED", "UPDATED"],
@@ -1620,7 +1854,14 @@ Examples:
1620
1854
  "Field to set (repeatable)",
1621
1855
  (v, acc) => [...acc, v],
1622
1856
  []
1623
- ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function(id) {
1857
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
1858
+ "after",
1859
+ `
1860
+ Examples:
1861
+ $ intercom conversations reply 123 -f type=admin -f body='Thanks!'
1862
+
1863
+ Required fields: body, type (admin or user). Also: admin_id, message_type.`
1864
+ ).action(async function(id) {
1624
1865
  const opts = this.optsWithGlobals();
1625
1866
  const client = await resolveClient(opts);
1626
1867
  const body = parseFieldArgs(opts.field ?? []);
@@ -1636,6 +1877,45 @@ Examples:
1636
1877
  })
1637
1878
  );
1638
1879
  });
1880
+ conversations.command("search").description("Search conversations (POST /conversations/search)").option(
1881
+ "-f, --field <key=value>",
1882
+ "String field (repeatable)",
1883
+ (v, acc) => [...acc, v],
1884
+ []
1885
+ ).option(
1886
+ "-F, --typed-field <key=value>",
1887
+ "Typed field (repeatable)",
1888
+ (v, acc) => [...acc, v],
1889
+ []
1890
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
1891
+ const opts = this.optsWithGlobals();
1892
+ const client = await resolveClient(opts);
1893
+ const body = {
1894
+ ...parseTypedFields(opts.field ?? [], false),
1895
+ ...parseTypedFields(opts.typedField ?? [], true)
1896
+ };
1897
+ const result = await client.post("/conversations/search", body);
1898
+ const response = result;
1899
+ const items = response.conversations ?? [];
1900
+ render(
1901
+ result,
1902
+ opts,
1903
+ () => {
1904
+ const rows = items.map((c2) => ({
1905
+ ID: String(c2.id ?? ""),
1906
+ STATE: String(c2.state ?? ""),
1907
+ OPEN: String(c2.open ?? ""),
1908
+ CREATED: formatTimestamp(c2.created_at),
1909
+ UPDATED: formatTimestamp(c2.updated_at)
1910
+ }));
1911
+ return table({
1912
+ columns: ["ID", "STATE", "OPEN", "CREATED", "UPDATED"],
1913
+ rows
1914
+ });
1915
+ },
1916
+ items
1917
+ );
1918
+ });
1639
1919
  }
1640
1920
 
1641
1921
  // src/commands/help-center.ts
@@ -1669,11 +1949,11 @@ Examples:
1669
1949
  result,
1670
1950
  opts,
1671
1951
  () => {
1672
- const rows = items.map((c) => ({
1673
- ID: String(c.id ?? ""),
1674
- NAME: String(c.name ?? ""),
1675
- DESCRIPTION: String(c.description ?? ""),
1676
- UPDATED: formatTimestamp(c.updated_at)
1952
+ const rows = items.map((c2) => ({
1953
+ ID: String(c2.id ?? ""),
1954
+ NAME: String(c2.name ?? ""),
1955
+ DESCRIPTION: String(c2.description ?? ""),
1956
+ UPDATED: formatTimestamp(c2.updated_at)
1677
1957
  }));
1678
1958
  return table({
1679
1959
  columns: ["ID", "NAME", "DESCRIPTION", "UPDATED"],
@@ -1742,7 +2022,7 @@ export function IntercomWidget() {
1742
2022
  }`;
1743
2023
  }
1744
2024
  function registerMessengerCommand(program) {
1745
- const messenger = program.command("messenger").description("Manage Intercom Messenger settings").addHelpText(
2025
+ const messenger = program.command("messenger").description("Manage Intercom Messenger settings (get, update, snippet)").addHelpText(
1746
2026
  "after",
1747
2027
  `
1748
2028
  Examples:
@@ -1750,7 +2030,13 @@ Examples:
1750
2030
  intercom messenger update -f alignment=right -f theme_mode=dark
1751
2031
  intercom messenger snippet --format react`
1752
2032
  );
1753
- messenger.command("get").description("Get Messenger settings").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
2033
+ messenger.command("get").description("Get Messenger settings").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
2034
+ "after",
2035
+ `
2036
+ Examples:
2037
+ $ intercom messenger get --json
2038
+ $ intercom messenger get --jq '.alignment'`
2039
+ ).action(async function() {
1754
2040
  const opts = this.optsWithGlobals();
1755
2041
  const client = await resolveClient(opts);
1756
2042
  const result = await client.get("/messenger", void 0, UNSTABLE_HEADERS);
@@ -1802,17 +2088,15 @@ Examples:
1802
2088
  "--format <format>",
1803
2089
  "Output format: html (default) or react",
1804
2090
  "html"
1805
- ).option("--verbose", "Show HTTP request/response details").action(async function() {
2091
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
1806
2092
  const opts = this.optsWithGlobals();
1807
2093
  const client = await resolveClient(opts);
1808
2094
  const me = await client.get("/me");
1809
2095
  const app = me.app;
1810
2096
  const appId = String(app?.id_code ?? "");
1811
- if (opts.format === "react") {
1812
- console.log(generateReactSnippet(appId));
1813
- } else {
1814
- console.log(generateHtmlSnippet(appId));
1815
- }
2097
+ const snippet = opts.format === "react" ? generateReactSnippet(appId) : generateHtmlSnippet(appId);
2098
+ const format = opts.format ?? "html";
2099
+ render({ format, snippet }, opts, () => snippet);
1816
2100
  });
1817
2101
  }
1818
2102
 
@@ -1941,11 +2225,44 @@ function registerGuidanceCommand(fin) {
1941
2225
  return table({ columns: ["ID", "NAME"], rows });
1942
2226
  }, items);
1943
2227
  });
1944
- guidance.command("get <id>").description("Download a guidance item by ID and print its YAML content").option("--verbose", "Show HTTP request/response details").action(async function(id) {
2228
+ guidance.command("create").description("Create a guidance item").option(
2229
+ "-f, --field <key=value>",
2230
+ "Field to set (repeatable)",
2231
+ (v, acc) => [...acc, v],
2232
+ []
2233
+ ).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
2234
+ "after",
2235
+ `
2236
+ Examples:
2237
+ $ intercom fin guidance create -f title="Brand Voice" -f prompt="Be friendly and concise." -f category=tone
2238
+ $ intercom fin guidance create -f title="Escalation Rules" -f prompt="Always escalate billing issues." -f category=behavior
2239
+
2240
+ Required fields: title, prompt, category
2241
+ Categories: tone, clarification, content, spam_detection, other, copilot,
2242
+ voice_tone, voice_clarification, voice_routing, voice_content
2243
+ Optional fields: state (live or paused, default: paused), audiences`
2244
+ ).action(async function() {
2245
+ const opts = this.optsWithGlobals();
2246
+ const client = await resolveClient(opts);
2247
+ const body = parseFieldArgs(opts.field ?? []);
2248
+ const result = await client.post("/fin-sdk/guidance", body);
2249
+ const r = result;
2250
+ render(
2251
+ result,
2252
+ opts,
2253
+ () => detail({
2254
+ ID: String(r.id ?? ""),
2255
+ Title: r.title != null ? String(r.title) : void 0,
2256
+ State: r.state != null ? String(r.state) : void 0,
2257
+ Message: r.message != null ? String(r.message) : void 0
2258
+ })
2259
+ );
2260
+ });
2261
+ guidance.command("get <id>").description("Download a guidance item by ID and print its YAML content").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function(id) {
1945
2262
  const opts = this.optsWithGlobals();
1946
2263
  const client = await resolveClient(opts);
1947
2264
  const text = await client.getText(`/fin-sdk/guidance/${id}/download`);
1948
- console.log(text);
2265
+ render({ format: "yaml", content: text }, opts, () => text);
1949
2266
  });
1950
2267
  }
1951
2268
 
@@ -2166,7 +2483,7 @@ async function emberRequest(session, path, options = {}) {
2166
2483
  }
2167
2484
  function extractCookies2(response) {
2168
2485
  const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : (response.headers.get("set-cookie") ?? "").split(/,(?=[^ ])/).filter(Boolean);
2169
- return setCookies.map((c) => c.split(";")[0]).join("; ");
2486
+ return setCookies.map((c2) => c2.split(";")[0]).join("; ");
2170
2487
  }
2171
2488
  function mergeCookies2(existing, newCookies) {
2172
2489
  const map = /* @__PURE__ */ new Map();
@@ -2187,8 +2504,8 @@ function registerFinEnableCommand(fin) {
2187
2504
  ).requiredOption("--email <email>", "Admin email address").option("--password-stdin", "Read password from stdin (or set INTERCOM_PASSWORD)").option("--verify-email-url <url>", "Email verification URL (if email not yet verified)").option("--verbose", "Show detailed output").action(async function() {
2188
2505
  const opts = this.optsWithGlobals();
2189
2506
  const password = await resolvePassword({ passwordStdin: opts.passwordStdin });
2190
- const configDir = process.env.INTERCOM_CONFIG_DIR;
2191
- const config = await readConfig(configDir);
2507
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
2508
+ const config = await readConfig(configDir2);
2192
2509
  const appId = opts.workspace ?? config.default;
2193
2510
  if (!appId) {
2194
2511
  throw new CLIError(
@@ -2290,7 +2607,13 @@ Examples:
2290
2607
  intercom fin enable --email admin@company.com
2291
2608
  intercom fin guidance list`
2292
2609
  );
2293
- fin.command("manifest").description("Show the Fin SDK manifest for this workspace").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
2610
+ fin.command("manifest").description("Show the Fin SDK manifest for this workspace").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
2611
+ "after",
2612
+ `
2613
+ Examples:
2614
+ $ intercom fin manifest
2615
+ $ intercom fin manifest --json`
2616
+ ).action(async function() {
2294
2617
  const opts = this.optsWithGlobals();
2295
2618
  const client = await resolveClient(opts);
2296
2619
  const result = await client.get("/fin-sdk/manifest");
@@ -2333,7 +2656,7 @@ Examples:
2333
2656
  }
2334
2657
 
2335
2658
  // src/commands/setup.ts
2336
- import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
2659
+ import { readdirSync, readFileSync as readFileSync4, statSync } from "fs";
2337
2660
  import { join as join3, basename as basename2 } from "path";
2338
2661
  function isGreenfield(opts) {
2339
2662
  return !!(opts.email && opts.name && (process.env.INTERCOM_PASSWORD || opts.passwordStdin));
@@ -2343,8 +2666,8 @@ function hasPartialGreenfieldFlags(opts) {
2343
2666
  return !!(opts.email && hasPassword && !opts.name);
2344
2667
  }
2345
2668
  async function hasExistingAuth() {
2346
- const configDir = process.env.INTERCOM_CONFIG_DIR;
2347
- const config = await readConfig(configDir);
2669
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
2670
+ const config = await readConfig(configDir2);
2348
2671
  return !!config.default;
2349
2672
  }
2350
2673
  function buildPlan(opts) {
@@ -2392,7 +2715,7 @@ function buildPlan(opts) {
2392
2715
  }
2393
2716
  if (mdFiles.length > 0) {
2394
2717
  const articleCommands = mdFiles.map((file) => {
2395
- const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2718
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
2396
2719
  return `intercom articles create -f title="${title}" -F body=@${join3(opts.articlesFrom, file)} -f author_id=$(intercom me --json | jq -r .id)`;
2397
2720
  });
2398
2721
  steps.push({
@@ -2436,7 +2759,7 @@ async function runPostAuthSteps(client, opts, results, appId) {
2436
2759
  const helpCenterName = opts.companyName ? `${opts.companyName} Help` : "Help Center";
2437
2760
  const collections = await client.paginate("/help_center/collections");
2438
2761
  const existingCollection = collections.find(
2439
- (c) => String(c.name ?? "") === helpCenterName
2762
+ (c2) => String(c2.name ?? "") === helpCenterName
2440
2763
  );
2441
2764
  let collectionId;
2442
2765
  if (existingCollection) {
@@ -2459,8 +2782,8 @@ async function runPostAuthSteps(client, opts, results, appId) {
2459
2782
  let created = 0;
2460
2783
  let skipped = 0;
2461
2784
  for (const file of mdFiles) {
2462
- const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2463
- const body = readFileSync3(join3(opts.articlesFrom, file), "utf-8");
2785
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
2786
+ const body = readFileSync4(join3(opts.articlesFrom, file), "utf-8");
2464
2787
  if (existingTitles.has(title)) {
2465
2788
  skipped++;
2466
2789
  } else {
@@ -2485,19 +2808,19 @@ async function runPostAuthSteps(client, opts, results, appId) {
2485
2808
  const articleCount = (await client.paginate("/articles")).length;
2486
2809
  if (articleCount === 0) {
2487
2810
  results.push(`\u26A0 No help center articles found!`);
2488
- console.log(results.join("\n"));
2489
- console.log("");
2490
- console.log("WARNING: Fin needs help center articles to answer questions.");
2491
- console.log("Without articles, Fin will have nothing to draw from and cannot help visitors.\n");
2492
- console.log("To add articles, either:");
2493
- console.log(" 1. Re-run with: intercom setup --articles-from <directory-of-markdown-files>");
2494
- console.log(' 2. Create articles manually: intercom articles create -f title="..." -f body="..." -F author_id=<id>');
2495
- console.log("");
2496
- console.log("Tip for AI agents: if no articles directory exists, consider creating one by");
2497
- console.log(" analyzing the project's source code, README, documentation, or any available");
2498
- console.log(" context about the product. Write markdown files covering key topics that");
2499
- console.log(" customers would ask about, then re-run setup with --articles-from.\n");
2500
- console.log("Skipping Fin enablement \u2014 enable Fin after adding articles with: intercom fin enable --email <email>");
2811
+ console.error(results.join("\n"));
2812
+ console.error("");
2813
+ console.error("WARNING: Fin needs help center articles to answer questions.");
2814
+ console.error("Without articles, Fin will have nothing to draw from and cannot help visitors.\n");
2815
+ console.error("To add articles, either:");
2816
+ console.error(" 1. Re-run with: intercom setup --articles-from <directory-of-markdown-files>");
2817
+ console.error(' 2. Create articles manually: intercom articles create -f title="..." -f body="..." -F author_id=<id>');
2818
+ console.error("");
2819
+ console.error("Tip for AI agents: if no articles directory exists, consider creating one by");
2820
+ console.error(" analyzing the project's source code, README, documentation, or any available");
2821
+ console.error(" context about the product. Write markdown files covering key topics that");
2822
+ console.error(" customers would ask about, then re-run setup with --articles-from.\n");
2823
+ console.error("Skipping Fin enablement \u2014 enable Fin after adding articles with: intercom fin enable --email <email>");
2501
2824
  return;
2502
2825
  }
2503
2826
  results.push(`\u2713 ${articleCount} article(s) in help center`);
@@ -2521,8 +2844,8 @@ async function runPostAuthSteps(client, opts, results, appId) {
2521
2844
  results.push(`\u2713 Fin already opted in`);
2522
2845
  } else {
2523
2846
  results.push(`\u26A0 Fin opt-in failed (${optIn.status}) \u2014 enable manually with: intercom fin enable --email <email>`);
2524
- console.log(results.join("\n"));
2525
- console.log("Setup complete (Fin requires manual enablement).");
2847
+ console.error(results.join("\n"));
2848
+ console.error("Setup complete (Fin requires manual enablement).");
2526
2849
  return;
2527
2850
  }
2528
2851
  const profile = await emberRequest(
@@ -2553,7 +2876,7 @@ async function runPostAuthSteps(client, opts, results, appId) {
2553
2876
  }
2554
2877
  } else if (opts.enableFin !== false) {
2555
2878
  results.push(`\u26A0 Fin not enabled (--email required for Fin activation)`);
2556
- console.log(" Tip: Re-run with --email to enable Fin, or use: intercom fin enable --email <email>");
2879
+ console.error(" Tip: Re-run with --email to enable Fin, or use: intercom fin enable --email <email>");
2557
2880
  }
2558
2881
  try {
2559
2882
  const manifest = await client.get("/fin-sdk/manifest");
@@ -2568,8 +2891,8 @@ async function runPostAuthSteps(client, opts, results, appId) {
2568
2891
  throw err;
2569
2892
  }
2570
2893
  }
2571
- console.log(results.join("\n"));
2572
- console.log("Setup complete!");
2894
+ console.error(results.join("\n"));
2895
+ console.error("Setup complete!");
2573
2896
  }
2574
2897
  function registerSetupCommand(program) {
2575
2898
  program.command("setup").description("Set up a complete Intercom installation").addHelpText(
@@ -2583,7 +2906,9 @@ Examples:
2583
2906
  INTERCOM_PASSWORD=*** intercom setup --email admin@acme.com --name "Admin" --company-name "Acme Inc"
2584
2907
 
2585
2908
  # Preview steps without executing
2586
- intercom setup --plan`
2909
+ intercom setup --plan
2910
+
2911
+ Flow: signup \u2192 auth \u2192 messenger \u2192 help center \u2192 articles \u2192 fin`
2587
2912
  ).option("--company-name <name>", "Company name for Messenger branding").option("--articles-from <dir>", "Directory of markdown files to create as articles").option("--plan", "Output setup steps as CLI commands without executing").option("--dry-run", "Show what would happen without executing").option("--json", "Output as JSON (for --plan mode)").option("--verbose", "Show HTTP request/response details").option("--email <email>", "Admin email address (for new workspace signup)").option("--password-stdin", "Read password from stdin (or set INTERCOM_PASSWORD env var)").option("--name <name>", "Admin name (for new workspace signup)").option("--plan-tier <tier>", "Trial plan tier: essential, advanced, expert (default: advanced)").option("--enable-fin", "Enable Fin AI Agent (default: true for greenfield setup)", true).option("--no-enable-fin", "Skip Fin enablement").option("--verify-email-url <url>", "Email verification URL (needed for Fin on fresh workspaces)").action(async function() {
2588
2913
  const opts = this.optsWithGlobals();
2589
2914
  if (hasPartialGreenfieldFlags(opts)) {
@@ -2694,10 +3019,10 @@ Then re-run setup without signup flags:
2694
3019
  );
2695
3020
  }
2696
3021
  results.push(`\u2713 Authentication token obtained`);
2697
- const configDir = process.env.INTERCOM_CONFIG_DIR;
2698
- const store = new CredentialStore(createDefaultBackend(configDir));
3022
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
3023
+ const store = new CredentialStore(createDefaultBackend(configDir2));
2699
3024
  await store.set(workspace.id, { token, auth_type: "oauth", app_id: workspace.id });
2700
- await addWorkspace(configDir, { app_id: workspace.id, name: workspace.name });
3025
+ await addWorkspace(configDir2, { app_id: workspace.id, name: workspace.name });
2701
3026
  const client2 = new IntercomClient({
2702
3027
  token,
2703
3028
  authType: "oauth",
@@ -2926,7 +3251,7 @@ function registerPricingCommand(program) {
2926
3251
  }
2927
3252
  const markdown = await response.text();
2928
3253
  if (opts.json) {
2929
- const { formatJson: formatJson2 } = await import("./json-TDEGGEUF.js");
3254
+ const { formatJson: formatJson2 } = await import("./json-MMBOB7YU.js");
2930
3255
  console.log(formatJson2({ url: PRICING_URL, content: markdown }));
2931
3256
  } else {
2932
3257
  console.log(markdown);
@@ -2934,6 +3259,100 @@ function registerPricingCommand(program) {
2934
3259
  });
2935
3260
  }
2936
3261
 
3262
+ // src/commands/config.ts
3263
+ import { join as join4 } from "path";
3264
+ var SUPPORTED_KEYS = ["default_workspace", "timeout"];
3265
+ function configDir() {
3266
+ return process.env.INTERCOM_CONFIG_DIR ?? defaultConfigDir();
3267
+ }
3268
+ function validateKey(key) {
3269
+ if (!SUPPORTED_KEYS.includes(key)) {
3270
+ throw new CLIError(
3271
+ `Unknown config key "${key}". Supported keys: ${SUPPORTED_KEYS.join(", ")}`
3272
+ );
3273
+ }
3274
+ return key;
3275
+ }
3276
+ async function getValue(key) {
3277
+ const config = await readConfig(configDir());
3278
+ switch (key) {
3279
+ case "default_workspace":
3280
+ return config.default;
3281
+ case "timeout":
3282
+ return config.timeout !== void 0 ? String(config.timeout) : void 0;
3283
+ }
3284
+ }
3285
+ function resolveSource(key) {
3286
+ switch (key) {
3287
+ case "default_workspace":
3288
+ return process.env.INTERCOM_WORKSPACE ? "INTERCOM_WORKSPACE" : "config";
3289
+ case "timeout":
3290
+ return "config";
3291
+ }
3292
+ }
3293
+ async function setValue(key, value) {
3294
+ const dir = configDir();
3295
+ const config = await readConfig(dir);
3296
+ switch (key) {
3297
+ case "default_workspace":
3298
+ config.default = value;
3299
+ break;
3300
+ case "timeout": {
3301
+ const n = parseInt(value, 10);
3302
+ if (isNaN(n) || n <= 0) {
3303
+ throw new CLIError("timeout must be a positive integer (seconds)");
3304
+ }
3305
+ config.timeout = n;
3306
+ break;
3307
+ }
3308
+ }
3309
+ await writeConfig(config, dir);
3310
+ }
3311
+ function registerConfigCommand(program) {
3312
+ const cmd = program.command("config").description("Manage CLI configuration");
3313
+ cmd.command("list").description("List all configuration settings").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").action(async function() {
3314
+ const opts = this.optsWithGlobals();
3315
+ const config = await readConfig(configDir());
3316
+ const entries = {};
3317
+ for (const key of SUPPORTED_KEYS) {
3318
+ const val = await getValue(key);
3319
+ entries[key] = {
3320
+ value: val ?? "",
3321
+ source: val !== void 0 ? resolveSource(key) : ""
3322
+ };
3323
+ }
3324
+ render(
3325
+ entries,
3326
+ opts,
3327
+ () => table({
3328
+ columns: ["KEY", "VALUE", "SOURCE"],
3329
+ rows: SUPPORTED_KEYS.map((key) => ({
3330
+ KEY: key,
3331
+ VALUE: entries[key].value,
3332
+ SOURCE: entries[key].source
3333
+ }))
3334
+ })
3335
+ );
3336
+ });
3337
+ cmd.command("get <key>").description("Get a configuration value").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").action(async function(key) {
3338
+ const opts = this.optsWithGlobals();
3339
+ const validKey = validateKey(key);
3340
+ const value = await getValue(validKey);
3341
+ if (value === void 0) {
3342
+ throw new CLIError(`Key "${key}" is not set`);
3343
+ }
3344
+ render({ key, value }, opts, () => value);
3345
+ });
3346
+ cmd.command("set <key> <value>").description("Set a configuration value").action(async function(key, value) {
3347
+ const validKey = validateKey(key);
3348
+ await setValue(validKey, value);
3349
+ console.log(`Set ${key} = ${value}`);
3350
+ });
3351
+ cmd.command("path").description("Show configuration file path").action(async function() {
3352
+ console.log(join4(configDir(), "config.json"));
3353
+ });
3354
+ }
3355
+
2937
3356
  // src/index.ts
2938
3357
  function detectBinaryName() {
2939
3358
  const argv1 = process.argv[1] ?? "";
@@ -2954,7 +3373,17 @@ function fullCommandName(cmd) {
2954
3373
  function createProgram() {
2955
3374
  const program = new Command();
2956
3375
  const binName2 = detectBinaryName();
2957
- program.name(binName2).description("CLI tool for managing Intercom workspaces").version(VERSION).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output").option("--workspace <id>", "Override default workspace").option("--verbose", "Show HTTP request/response details").option("--no-retry", "Disable automatic rate limit retry").option("--timeout <seconds>", "Request timeout in seconds").option("--agent", "Force machine-readable output (JSON/NDJSON)").option("--human", "Force human-readable output even when piped");
3376
+ program.name(binName2).description("CLI tool for managing Intercom workspaces").version(VERSION).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output").option("--workspace <id>", "Override default workspace").option("--verbose", "Show HTTP request/response details").option("--no-retry", "Disable automatic rate limit retry").option("--timeout <seconds>", "Request timeout in seconds").option("--agent", "Force machine-readable output (JSON/NDJSON)").option("--human", "Force human-readable output even when piped").option("--wide", "Show all columns with wider truncation").addHelpText(
3377
+ "after",
3378
+ `
3379
+ Quick start:
3380
+ $ intercom auth login --token <your-api-token>
3381
+ $ intercom me
3382
+ $ intercom articles list --json
3383
+ $ intercom setup --company-name "Acme" --articles-from ./docs
3384
+
3385
+ Environment: Set INTERCOM_TOKEN to skip login entirely.`
3386
+ );
2958
3387
  registerApiCommand(program);
2959
3388
  registerAuthCommand(program);
2960
3389
  registerMeCommand(program);
@@ -2967,6 +3396,7 @@ function createProgram() {
2967
3396
  registerSetupCommand(program);
2968
3397
  registerPricingCommand(program);
2969
3398
  registerCompletionCommand(program);
3399
+ registerConfigCommand(program);
2970
3400
  program.hook("preAction", (thisCommand, actionCommand) => {
2971
3401
  const rootOpts = thisCommand.opts();
2972
3402
  if (rootOpts.agent && rootOpts.human) {