@intercom/cli 0.1.1 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,105 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ AuthError,
4
+ CLIError,
5
+ NotFoundError,
6
+ PermissionError,
7
+ RateLimitError,
8
+ TimeoutError,
9
+ applyJq,
10
+ detectAuthType,
11
+ formatJson
12
+ } from "./chunk-Y4JK5AHH.js";
2
13
 
3
14
  // src/index.ts
4
15
  import { Command } from "commander";
5
16
 
6
- // src/types.ts
7
- var CLIError = class extends Error {
8
- constructor(message, exitCode = 1, error) {
9
- super(message);
10
- this.exitCode = exitCode;
11
- this.error = error;
12
- this.name = "CLIError";
13
- }
14
- };
15
- var AuthError = class extends CLIError {
16
- constructor(message) {
17
- super(message, 2);
18
- }
19
- };
20
- var RateLimitError = class extends CLIError {
21
- constructor(message, retryAfter) {
22
- super(message, 3);
23
- this.retryAfter = retryAfter;
24
- }
25
- };
26
- var NotFoundError = class extends CLIError {
27
- constructor(message) {
28
- super(message, 4);
29
- }
30
- };
31
- var PermissionError = class extends CLIError {
32
- constructor(message) {
33
- super(message, 5);
34
- }
35
- };
36
- var TimeoutError = class extends CLIError {
37
- constructor(message) {
38
- super(message, 6);
39
- }
40
- };
41
- function detectAuthType(token) {
42
- try {
43
- const decoded = Buffer.from(token, "base64").toString("utf-8");
44
- if (decoded.startsWith("tok:")) {
45
- return "oauth";
46
- }
47
- } catch {
48
- }
49
- return "api_key";
50
- }
51
-
52
- // src/output/json.ts
53
- function formatJson(data) {
54
- return JSON.stringify(data, null, 2);
55
- }
56
- function applyJq(data, expr) {
57
- if (expr === ".") return data;
58
- if (!expr.startsWith(".")) {
59
- throw new CLIError(
60
- `Unsupported jq expression: "${expr}". Supported: ".", ".field", ".field.nested", ".field[].nested"`
61
- );
62
- }
63
- if (/[|(){},;]/.test(expr)) {
64
- throw new CLIError(
65
- `Unsupported jq expression: "${expr}". Supported: ".", ".field", ".field.nested", ".field[].nested"`
66
- );
67
- }
68
- const parts = expr.replace(/^\.\s*/, "").split(/\./).filter(Boolean);
69
- for (const part of parts) {
70
- if (!/^\w+(\[\])?$/.test(part)) {
71
- throw new CLIError(
72
- `Unsupported jq expression: "${expr}". Supported: ".", ".field", ".field.nested", ".field[].nested"`
73
- );
74
- }
75
- }
76
- let current = data;
77
- for (const part of parts) {
78
- const arrayMatch = part.match(/^(\w+)\[\]$/);
79
- if (arrayMatch) {
80
- const key = arrayMatch[1];
81
- if (current && typeof current === "object" && key in current) {
82
- const arr = current[key];
83
- if (!Array.isArray(arr)) return void 0;
84
- current = arr;
85
- } else {
86
- return void 0;
87
- }
88
- } else if (Array.isArray(current)) {
89
- current = current.map(
90
- (item) => item && typeof item === "object" ? item[part] : void 0
91
- );
92
- } else if (current && typeof current === "object") {
93
- current = current[part];
94
- } else {
95
- return void 0;
96
- }
97
- }
98
- return current;
99
- }
17
+ // src/commands/api.ts
18
+ import { readFileSync as readFileSync3 } from "fs";
100
19
 
101
20
  // src/api/timeouts.ts
102
21
  var API_TIMEOUT_MS = 3e4;
22
+ var SIMULATION_POLL_TIMEOUT_MS = 6e5;
103
23
 
104
24
  // src/version.ts
105
25
  import { createRequire } from "module";
@@ -246,6 +166,22 @@ var IntercomClient = class {
246
166
  retryAfter
247
167
  );
248
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
+ }
249
185
  throw new CLIError(errorMsg);
250
186
  }
251
187
  throw new CLIError("Request failed after retries");
@@ -475,9 +411,9 @@ var FallbackBackend = class {
475
411
  }
476
412
  }
477
413
  };
478
- function createDefaultBackend(configDir) {
479
- if (configDir) {
480
- return new EncryptedFileBackend(configDir);
414
+ function createDefaultBackend(configDir2) {
415
+ if (configDir2) {
416
+ return new EncryptedFileBackend(configDir2);
481
417
  }
482
418
  return new FallbackBackend(
483
419
  new NativeKeychainBackend(),
@@ -516,8 +452,8 @@ var CONFIG_FILE = "config.json";
516
452
  function defaultConfigDir() {
517
453
  return join2(homedir2(), ".intercom");
518
454
  }
519
- async function readConfig(configDir) {
520
- const dir = configDir ?? defaultConfigDir();
455
+ async function readConfig(configDir2) {
456
+ const dir = configDir2 ?? defaultConfigDir();
521
457
  try {
522
458
  const raw = await readFile(join2(dir, CONFIG_FILE), "utf-8");
523
459
  return JSON.parse(raw);
@@ -525,27 +461,27 @@ async function readConfig(configDir) {
525
461
  return { workspaces: {} };
526
462
  }
527
463
  }
528
- async function writeConfig(config, configDir) {
529
- const dir = configDir ?? defaultConfigDir();
464
+ async function writeConfig(config, configDir2) {
465
+ const dir = configDir2 ?? defaultConfigDir();
530
466
  await mkdir(dir, { recursive: true });
531
467
  await atomicWriteFile(join2(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
532
468
  }
533
- async function addWorkspace(configDir, workspace) {
534
- const config = await readConfig(configDir);
469
+ async function addWorkspace(configDir2, workspace) {
470
+ const config = await readConfig(configDir2);
535
471
  config.workspaces[workspace.app_id] = workspace;
536
472
  if (!config.default) {
537
473
  config.default = workspace.app_id;
538
474
  }
539
- await writeConfig(config, configDir);
475
+ await writeConfig(config, configDir2);
540
476
  }
541
- async function removeWorkspace(configDir, appId) {
542
- const config = await readConfig(configDir);
477
+ async function removeWorkspace(configDir2, appId) {
478
+ const config = await readConfig(configDir2);
543
479
  delete config.workspaces[appId];
544
480
  if (config.default === appId) {
545
481
  const remaining = Object.keys(config.workspaces);
546
482
  config.default = remaining.length > 0 ? remaining[0] : void 0;
547
483
  }
548
- await writeConfig(config, configDir);
484
+ await writeConfig(config, configDir2);
549
485
  }
550
486
 
551
487
  // src/commands/helpers.ts
@@ -562,8 +498,8 @@ async function resolveClient(options = {}) {
562
498
  timeoutMs: options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0
563
499
  });
564
500
  }
565
- const configDir = process.env.INTERCOM_CONFIG_DIR;
566
- const config = await readConfig(configDir);
501
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
502
+ const config = await readConfig(configDir2);
567
503
  const appId = options.workspace ?? config.default;
568
504
  if (!appId) {
569
505
  throw new AuthError(
@@ -575,7 +511,7 @@ async function resolveClient(options = {}) {
575
511
  `Default workspace "${appId}" is not in configured workspaces. Run "intercom auth bootstrap" or "intercom auth login --token <token>" to re-authenticate.`
576
512
  );
577
513
  }
578
- const store = new CredentialStore(createDefaultBackend(configDir));
514
+ const store = new CredentialStore(createDefaultBackend(configDir2));
579
515
  const cred = await store.get(appId);
580
516
  if (!cred) {
581
517
  throw new AuthError(
@@ -629,6 +565,20 @@ function render(data, opts, humanRenderer, listItems) {
629
565
  console.log(formatJson(data));
630
566
  return;
631
567
  }
568
+ if (opts.agent) {
569
+ if (listItems) {
570
+ for (const item of listItems) {
571
+ console.log(JSON.stringify(item));
572
+ }
573
+ } else {
574
+ console.log(JSON.stringify(data));
575
+ }
576
+ return;
577
+ }
578
+ if (opts.human) {
579
+ console.log(humanRenderer());
580
+ return;
581
+ }
632
582
  if (!process.stdout.isTTY) {
633
583
  if (listItems) {
634
584
  for (const item of listItems) {
@@ -672,20 +622,44 @@ function parseTypedFields(fields, typed) {
672
622
  }
673
623
 
674
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
+ }
675
639
  function registerApiCommand(program) {
676
- program.command("api").description("Make an authenticated API request (like gh api)").argument("<endpoint>", "API endpoint path (e.g., /articles)").option("-X, --method <method>", "HTTP method").option("-f, --field <key=value...>", "String field").option(
640
+ program.command("api").description("Make an authenticated API request (like gh api)").argument("<endpoint>", "API endpoint path (e.g., /articles)").addHelpText(
641
+ "after",
642
+ `
643
+ Examples:
644
+ intercom api /me
645
+ intercom api /articles --paginate
646
+ intercom api /contacts -X POST -f email=test@example.com -f role=user
647
+ intercom api /articles/123 --jq '.title'`
648
+ ).option("-X, --method <method>", "HTTP method").option("-f, --field <key=value...>", "String field").option(
677
649
  "-F, --typed-field <key=value...>",
678
650
  "Typed field (bools, numbers, file reads)"
679
- ).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) {
680
652
  const options = this.optsWithGlobals();
681
653
  const client = await resolveClient(options);
682
654
  const stringFields = options.field ?? [];
683
655
  const typedFields = options.typedField ?? [];
656
+ const inputBody = options.input ? readInput(options.input) : {};
684
657
  const body = {
658
+ ...inputBody,
685
659
  ...parseTypedFields(stringFields, false),
686
660
  ...parseTypedFields(typedFields, true)
687
661
  };
688
- const hasBody = stringFields.length > 0 || typedFields.length > 0;
662
+ const hasBody = stringFields.length > 0 || typedFields.length > 0 || !!options.input;
689
663
  const method = options.method ?? (hasBody ? "POST" : "GET");
690
664
  let result;
691
665
  if (options.paginate && method.toUpperCase() === "GET") {
@@ -729,7 +703,14 @@ function registerApiCommand(program) {
729
703
 
730
704
  // src/commands/auth/login.ts
731
705
  function registerLoginCommand(auth) {
732
- 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) => {
733
714
  if (options.token) {
734
715
  await loginWithToken(options.token);
735
716
  } else if (options.device) {
@@ -753,17 +734,17 @@ async function loginWithToken(token) {
753
734
  const app = me.app;
754
735
  const appId = app?.id_code ?? me.id;
755
736
  const name = app?.name ?? "Unknown";
756
- const configDir = process.env.INTERCOM_CONFIG_DIR;
757
- const store = new CredentialStore(createDefaultBackend(configDir));
737
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
738
+ const store = new CredentialStore(createDefaultBackend(configDir2));
758
739
  await store.set(appId, { token, auth_type: authType, app_id: appId });
759
- await addWorkspace(configDir, { app_id: appId, name });
740
+ await addWorkspace(configDir2, { app_id: appId, name });
760
741
  console.log(`Authenticated to ${name} (${appId})`);
761
742
  }
762
743
 
763
744
  // src/auth/cookies.ts
764
745
  function extractCookies(response) {
765
746
  const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : (response.headers.get("set-cookie") ?? "").split(/,(?=[^ ])/).filter(Boolean);
766
- return setCookies.map((c) => c.split(";")[0]).join("; ");
747
+ return setCookies.map((c2) => c2.split(";")[0]).join("; ");
767
748
  }
768
749
  function mergeCookies(existing, newCookies) {
769
750
  const map = /* @__PURE__ */ new Map();
@@ -828,7 +809,6 @@ async function signup(options) {
828
809
  email_submission: {},
829
810
  sign_up_locale: "en",
830
811
  new_app_feature_flags: [
831
- "synchronously-create-subscription-on-signup",
832
812
  "fin-sdk",
833
813
  "messenger-public-api"
834
814
  ]
@@ -929,6 +909,13 @@ async function findOrCreateAppPackage(baseUrl, cookies, appId, appPackageId, tim
929
909
  { timeoutMs }
930
910
  );
931
911
  if (!listResponse.ok) {
912
+ if (listResponse.status === 403) {
913
+ throw new Error(
914
+ `Developer Hub access denied (403). This usually means the admin email has not been verified.
915
+ Verify your email first: intercom auth verify-email --url <url-from-verification-email>
916
+ Then retry this command.`
917
+ );
918
+ }
932
919
  throw new Error(`Failed to list app packages: ${listResponse.status}`);
933
920
  }
934
921
  const data = await listResponse.json();
@@ -970,6 +957,13 @@ async function findOrCreateAppPackage(baseUrl, cookies, appId, appPackageId, tim
970
957
  { timeoutMs }
971
958
  );
972
959
  if (!createResponse.ok) {
960
+ if (createResponse.status === 403) {
961
+ throw new Error(
962
+ `Developer Hub access denied (403). This usually means the admin email has not been verified.
963
+ Verify your email first: intercom auth verify-email --url <url-from-verification-email>
964
+ Then retry this command.`
965
+ );
966
+ }
973
967
  throw new Error(`Failed to create app package: ${createResponse.status}`);
974
968
  }
975
969
  const created = await createResponse.json();
@@ -1074,10 +1068,10 @@ function registerBootstrapCommand(auth) {
1074
1068
  console.log(`Found app package: ${appPackage.name} (id: ${appPackage.id})`);
1075
1069
  }
1076
1070
  console.log("Access token acquired");
1077
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1078
- const store = new CredentialStore(createDefaultBackend(configDir));
1071
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1072
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1079
1073
  await store.set(appId, { token, auth_type: "oauth", app_id: appId });
1080
- await addWorkspace(configDir, { app_id: appId, name: appId });
1074
+ await addWorkspace(configDir2, { app_id: appId, name: appId });
1081
1075
  console.log(`
1082
1076
  Authenticated to workspace ${appId}`);
1083
1077
  } catch (err) {
@@ -1089,15 +1083,85 @@ Authenticated to workspace ${appId}`);
1089
1083
  }
1090
1084
 
1091
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
+ }
1092
1134
  function table({ columns, rows }) {
1093
1135
  if (rows.length === 0) return "";
1094
- const widths = columns.map(
1095
- (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
+ )
1096
1151
  );
1097
- const formatRow = (values) => values.map((val, i) => i < values.length - 1 ? val.padEnd(widths[i]) : val).join(" ");
1098
- const header = formatRow(columns);
1099
- const body = rows.map(
1100
- (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
+ )
1101
1165
  );
1102
1166
  return [header, ...body].join("\n");
1103
1167
  }
@@ -1107,19 +1171,25 @@ function detail(data) {
1107
1171
  );
1108
1172
  if (entries.length === 0) return "";
1109
1173
  const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
1110
- 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");
1111
1181
  }
1112
1182
 
1113
1183
  // src/commands/auth/status.ts
1114
1184
  function registerStatusCommand(auth) {
1115
1185
  auth.command("status").description("Show current authentication status").option("--json", "Output as JSON").action(async (options) => {
1116
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1117
- const config = await readConfig(configDir);
1186
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1187
+ const config = await readConfig(configDir2);
1118
1188
  const appId = config.default;
1119
1189
  if (!appId) {
1120
1190
  throw new AuthError('Not authenticated. Run "intercom auth login" to get started.');
1121
1191
  }
1122
- const store = new CredentialStore(createDefaultBackend(configDir));
1192
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1123
1193
  const cred = await store.get(appId);
1124
1194
  if (!cred) {
1125
1195
  throw new AuthError('Not authenticated. Run "intercom auth login" to get started.');
@@ -1146,15 +1216,15 @@ function registerStatusCommand(auth) {
1146
1216
  // src/commands/auth/logout.ts
1147
1217
  function registerLogoutCommand(auth) {
1148
1218
  auth.command("logout").description("Remove stored credentials").option("--workspace <id>", "Workspace app_id to log out of").action(async (options) => {
1149
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1150
- const config = await readConfig(configDir);
1219
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1220
+ const config = await readConfig(configDir2);
1151
1221
  const appId = options.workspace ?? config.default;
1152
1222
  if (!appId) {
1153
1223
  throw new AuthError("Not authenticated.");
1154
1224
  }
1155
- const store = new CredentialStore(createDefaultBackend(configDir));
1225
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1156
1226
  await store.delete(appId);
1157
- await removeWorkspace(configDir, appId);
1227
+ await removeWorkspace(configDir2, appId);
1158
1228
  const name = config.workspaces[appId]?.name ?? appId;
1159
1229
  console.log(`Logged out of ${name} (${appId})`);
1160
1230
  });
@@ -1193,10 +1263,10 @@ function registerSignupCommand(auth) {
1193
1263
  password,
1194
1264
  appId: workspace.id
1195
1265
  });
1196
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1197
- const store = new CredentialStore(createDefaultBackend(configDir));
1266
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
1267
+ const store = new CredentialStore(createDefaultBackend(configDir2));
1198
1268
  await store.set(appId, { token, auth_type: "oauth", app_id: appId });
1199
- await addWorkspace(configDir, { app_id: appId, name: workspace.name });
1269
+ await addWorkspace(configDir2, { app_id: appId, name: workspace.name });
1200
1270
  console.log(`
1201
1271
  Authenticated to workspace ${appId}`);
1202
1272
  } catch {
@@ -1242,15 +1312,84 @@ function registerVerifyEmailCommand(auth) {
1242
1312
  });
1243
1313
  }
1244
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
+
1245
1373
  // src/commands/auth/index.ts
1246
1374
  function registerAuthCommand(program) {
1247
- const auth = program.command("auth").description("Manage authentication credentials");
1375
+ const auth = program.command("auth").description("Manage authentication credentials").addHelpText(
1376
+ "after",
1377
+ `
1378
+ Examples:
1379
+ intercom auth login --token <api-token>
1380
+ intercom auth status
1381
+ intercom auth logout
1382
+
1383
+ Environment: Set INTERCOM_TOKEN to skip login entirely.`
1384
+ );
1248
1385
  registerLoginCommand(auth);
1249
1386
  registerBootstrapCommand(auth);
1250
1387
  registerStatusCommand(auth);
1251
1388
  registerLogoutCommand(auth);
1252
1389
  registerSignupCommand(auth);
1253
1390
  registerVerifyEmailCommand(auth);
1391
+ registerListCommand(auth);
1392
+ registerSwitchCommand(auth);
1254
1393
  }
1255
1394
 
1256
1395
  // src/commands/me.ts
@@ -1286,8 +1425,23 @@ function htmlToText(html) {
1286
1425
 
1287
1426
  // src/commands/articles.ts
1288
1427
  function registerArticlesCommand(program) {
1289
- const articles = program.command("articles").description("Manage help center articles");
1290
- 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() {
1428
+ const articles = program.command("articles").description("Manage help center articles (list, get, create, update, delete)").addHelpText(
1429
+ "after",
1430
+ `
1431
+ Examples:
1432
+ intercom articles list
1433
+ intercom articles get 123
1434
+ intercom articles create -f title="Getting Started" -F body=@article.md
1435
+ intercom articles update 123 -f state=published
1436
+ intercom articles delete 123`
1437
+ );
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() {
1291
1445
  const opts = this.optsWithGlobals();
1292
1446
  const client = await resolveClient(opts);
1293
1447
  let result;
@@ -1344,7 +1498,16 @@ ${body}` : meta;
1344
1498
  "Typed field: bools, numbers, @file reads (repeatable)",
1345
1499
  (v, acc) => [...acc, v],
1346
1500
  []
1347
- ).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() {
1348
1511
  const opts = this.optsWithGlobals();
1349
1512
  const client = await resolveClient(opts);
1350
1513
  const body = {
@@ -1409,7 +1572,15 @@ ${body}` : meta;
1409
1572
 
1410
1573
  // src/commands/contacts.ts
1411
1574
  function registerContactsCommand(program) {
1412
- const contacts = program.command("contacts").description("Manage contacts");
1575
+ const contacts = program.command("contacts").description("Manage contacts (list, get, create, update, delete, search)").addHelpText(
1576
+ "after",
1577
+ `
1578
+ Examples:
1579
+ intercom contacts list --limit 10
1580
+ intercom contacts get abc123
1581
+ intercom contacts create -f email=user@example.com -f role=user
1582
+ intercom contacts delete abc123`
1583
+ );
1413
1584
  contacts.command("list").description("List contacts").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() {
1414
1585
  const opts = this.optsWithGlobals();
1415
1586
  const client = await resolveClient(opts);
@@ -1427,12 +1598,12 @@ function registerContactsCommand(program) {
1427
1598
  result,
1428
1599
  opts,
1429
1600
  () => {
1430
- const rows = items.map((c) => ({
1431
- ID: String(c.id ?? ""),
1432
- EMAIL: String(c.email ?? ""),
1433
- NAME: String(c.name ?? ""),
1434
- ROLE: String(c.role ?? ""),
1435
- 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)
1436
1607
  }));
1437
1608
  return table({
1438
1609
  columns: ["ID", "EMAIL", "NAME", "ROLE", "UPDATED"],
@@ -1510,6 +1681,45 @@ function registerContactsCommand(program) {
1510
1681
  const deleted = result;
1511
1682
  render(result, opts, () => `Deleted contact ${deleted.id ?? id}`);
1512
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
+ });
1513
1723
  }
1514
1724
 
1515
1725
  // src/commands/conversations.ts
@@ -1586,7 +1796,14 @@ function renderConversationStream(result) {
1586
1796
  return parts.join("\n");
1587
1797
  }
1588
1798
  function registerConversationsCommand(program) {
1589
- const conversations = program.command("conversations").description("Manage conversations");
1799
+ const conversations = program.command("conversations").description("Manage conversations (list, get, reply, search)").addHelpText(
1800
+ "after",
1801
+ `
1802
+ Examples:
1803
+ intercom conversations list
1804
+ intercom conversations get 123
1805
+ intercom conversations reply 123 -f body="Thanks!" -f message_type=comment -f type=admin -f admin_id=456`
1806
+ );
1590
1807
  conversations.command("list").description("List conversations").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() {
1591
1808
  const opts = this.optsWithGlobals();
1592
1809
  const client = await resolveClient(opts);
@@ -1608,12 +1825,12 @@ function registerConversationsCommand(program) {
1608
1825
  result,
1609
1826
  opts,
1610
1827
  () => {
1611
- const rows = items.map((c) => ({
1612
- ID: String(c.id ?? ""),
1613
- STATE: String(c.state ?? ""),
1614
- OPEN: String(c.open ?? ""),
1615
- CREATED: formatTimestamp(c.created_at),
1616
- 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)
1617
1834
  }));
1618
1835
  return table({
1619
1836
  columns: ["ID", "STATE", "OPEN", "CREATED", "UPDATED"],
@@ -1637,7 +1854,14 @@ function registerConversationsCommand(program) {
1637
1854
  "Field to set (repeatable)",
1638
1855
  (v, acc) => [...acc, v],
1639
1856
  []
1640
- ).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) {
1641
1865
  const opts = this.optsWithGlobals();
1642
1866
  const client = await resolveClient(opts);
1643
1867
  const body = parseFieldArgs(opts.field ?? []);
@@ -1653,11 +1877,57 @@ function registerConversationsCommand(program) {
1653
1877
  })
1654
1878
  );
1655
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
+ });
1656
1919
  }
1657
1920
 
1658
1921
  // src/commands/help-center.ts
1659
1922
  function registerHelpCenterCommand(program) {
1660
- const helpCenter = program.command("help-center").description("Manage help center collections");
1923
+ const helpCenter = program.command("help-center").description("Manage help center collections").addHelpText(
1924
+ "after",
1925
+ `
1926
+ Examples:
1927
+ intercom help-center list
1928
+ intercom help-center get 123
1929
+ intercom help-center create -f name="FAQ"`
1930
+ );
1661
1931
  helpCenter.command("list").description("List help center collections").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() {
1662
1932
  const opts = this.optsWithGlobals();
1663
1933
  const client = await resolveClient(opts);
@@ -1679,11 +1949,11 @@ function registerHelpCenterCommand(program) {
1679
1949
  result,
1680
1950
  opts,
1681
1951
  () => {
1682
- const rows = items.map((c) => ({
1683
- ID: String(c.id ?? ""),
1684
- NAME: String(c.name ?? ""),
1685
- DESCRIPTION: String(c.description ?? ""),
1686
- 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)
1687
1957
  }));
1688
1958
  return table({
1689
1959
  columns: ["ID", "NAME", "DESCRIPTION", "UPDATED"],
@@ -1752,8 +2022,21 @@ export function IntercomWidget() {
1752
2022
  }`;
1753
2023
  }
1754
2024
  function registerMessengerCommand(program) {
1755
- const messenger = program.command("messenger").description("Manage Intercom Messenger settings");
1756
- 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() {
2025
+ const messenger = program.command("messenger").description("Manage Intercom Messenger settings (get, update, snippet)").addHelpText(
2026
+ "after",
2027
+ `
2028
+ Examples:
2029
+ intercom messenger get
2030
+ intercom messenger update -f alignment=right -f theme_mode=dark
2031
+ intercom messenger snippet --format react`
2032
+ );
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() {
1757
2040
  const opts = this.optsWithGlobals();
1758
2041
  const client = await resolveClient(opts);
1759
2042
  const result = await client.get("/messenger", void 0, UNSTABLE_HEADERS);
@@ -1805,17 +2088,120 @@ function registerMessengerCommand(program) {
1805
2088
  "--format <format>",
1806
2089
  "Output format: html (default) or react",
1807
2090
  "html"
1808
- ).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() {
1809
2092
  const opts = this.optsWithGlobals();
1810
2093
  const client = await resolveClient(opts);
1811
2094
  const me = await client.get("/me");
1812
2095
  const app = me.app;
1813
2096
  const appId = String(app?.id_code ?? "");
1814
- if (opts.format === "react") {
1815
- console.log(generateReactSnippet(appId));
1816
- } else {
1817
- console.log(generateHtmlSnippet(appId));
2097
+ const snippet = opts.format === "react" ? generateReactSnippet(appId) : generateHtmlSnippet(appId);
2098
+ const format = opts.format ?? "html";
2099
+ render({ format, snippet }, opts, () => snippet);
2100
+ });
2101
+ }
2102
+
2103
+ // src/commands/fin.ts
2104
+ import { writeFile as writeFile2 } from "fs/promises";
2105
+
2106
+ // src/commands/fin/procedures.ts
2107
+ import { readFile as readFile2 } from "fs/promises";
2108
+ import { basename } from "path";
2109
+ import AdmZip from "adm-zip";
2110
+ function registerProceduresCommand(fin) {
2111
+ const procedures = fin.command("procedures").description("Manage Fin SDK procedures");
2112
+ procedures.command("list").description("List procedures from the Fin SDK manifest").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
2113
+ const opts = this.optsWithGlobals();
2114
+ const client = await resolveClient(opts);
2115
+ const result = await client.get("/fin-sdk/manifest");
2116
+ const manifest = result;
2117
+ const items = manifest.procedures ?? [];
2118
+ render(items, opts, () => {
2119
+ if (items.length === 0) {
2120
+ return "No procedures found.";
2121
+ }
2122
+ const rows = items.map((item) => ({
2123
+ ID: String(item.id ?? ""),
2124
+ NAME: String(item.name ?? "")
2125
+ }));
2126
+ return table({ columns: ["ID", "NAME"], rows });
2127
+ }, items);
2128
+ });
2129
+ procedures.command("get <id>").description("Download a procedure by ID and print its YAML content").option("--verbose", "Show HTTP request/response details").action(async function(id) {
2130
+ const opts = this.optsWithGlobals();
2131
+ const client = await resolveClient(opts);
2132
+ const buffer = await client.getBuffer(`/fin-sdk/procedures/${id}/download`);
2133
+ const zip = new AdmZip(buffer);
2134
+ const entry = zip.getEntries().find((e) => e.entryName.endsWith("procedure.yaml"));
2135
+ if (!entry) {
2136
+ throw new Error("procedure.yaml not found in downloaded ZIP");
2137
+ }
2138
+ console.log(zip.readAsText(entry));
2139
+ });
2140
+ procedures.command("upload <file>").description("Upload a procedure file to Fin SDK").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function(filePath) {
2141
+ const opts = this.optsWithGlobals();
2142
+ const client = await resolveClient(opts);
2143
+ const fileContent = await readFile2(filePath);
2144
+ const formData = new FormData();
2145
+ const blob = new Blob([fileContent], { type: "application/x-yaml" });
2146
+ formData.append("file_data", blob, basename(filePath));
2147
+ const result = await client.postFormData("/fin-sdk/upload", formData);
2148
+ render(result, opts, () => formatJson(result));
2149
+ });
2150
+ }
2151
+
2152
+ // src/commands/fin/data-connectors.ts
2153
+ function registerDataConnectorsCommand(fin) {
2154
+ const dataConnectors = fin.command("data-connectors").description("Manage Fin SDK data connectors");
2155
+ dataConnectors.command("list").description("List data connectors from the Fin SDK manifest").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
2156
+ const opts = this.optsWithGlobals();
2157
+ const client = await resolveClient(opts);
2158
+ const result = await client.get("/fin-sdk/manifest");
2159
+ const manifest = result;
2160
+ const items = manifest.data_connectors ?? [];
2161
+ render(items, opts, () => {
2162
+ if (items.length === 0) {
2163
+ return "No data connectors found.";
2164
+ }
2165
+ const rows = items.map((item) => ({
2166
+ ID: String(item.id ?? ""),
2167
+ NAME: String(item.name ?? "")
2168
+ }));
2169
+ return table({ columns: ["ID", "NAME"], rows });
2170
+ }, items);
2171
+ });
2172
+ dataConnectors.command("create").description("Create a data connector").requiredOption("-f, --field <key=value>", "Field to set (e.g. name=<name>)", (v, acc) => [...acc, v], []).option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
2173
+ const opts = this.optsWithGlobals();
2174
+ const client = await resolveClient(opts);
2175
+ const fields = opts.field ?? [];
2176
+ const body = {};
2177
+ for (const f of fields) {
2178
+ const eq = f.indexOf("=");
2179
+ if (eq === -1) throw new Error(`Invalid field format: ${f}. Expected key=value`);
2180
+ body[f.slice(0, eq)] = f.slice(eq + 1);
1818
2181
  }
2182
+ const result = await client.post("/fin-sdk/data-connectors", body);
2183
+ render(
2184
+ result,
2185
+ opts,
2186
+ () => detail({
2187
+ "ID": String(result.id ?? ""),
2188
+ "Name": String(result.name ?? ""),
2189
+ "Status": result.status != null ? String(result.status) : void 0
2190
+ })
2191
+ );
2192
+ });
2193
+ dataConnectors.command("get <id>").description("Download a data connector by ID").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) {
2194
+ const opts = this.optsWithGlobals();
2195
+ const client = await resolveClient(opts);
2196
+ const result = await client.get(`/fin-sdk/data-connectors/${id}/download`);
2197
+ render(
2198
+ result,
2199
+ opts,
2200
+ () => detail({
2201
+ "ID": String(result.id ?? ""),
2202
+ "Name": String(result.name ?? "")
2203
+ })
2204
+ );
1819
2205
  });
1820
2206
  }
1821
2207
 
@@ -1839,12 +2225,175 @@ function registerGuidanceCommand(fin) {
1839
2225
  return table({ columns: ["ID", "NAME"], rows });
1840
2226
  }, items);
1841
2227
  });
1842
- 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("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) {
1843
2229
  const opts = this.optsWithGlobals();
1844
2230
  const client = await resolveClient(opts);
1845
2231
  const text = await client.getText(`/fin-sdk/guidance/${id}/download`);
2232
+ render({ format: "yaml", content: text }, opts, () => text);
2233
+ });
2234
+ }
2235
+
2236
+ // src/commands/fin/attributes.ts
2237
+ function registerAttributesCommand(fin) {
2238
+ const attributes = fin.command("attributes").description("Manage Fin SDK attributes");
2239
+ attributes.command("list").description("List Fin SDK attributes").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
2240
+ const opts = this.optsWithGlobals();
2241
+ const client = await resolveClient(opts);
2242
+ const result = await client.get("/fin-sdk/attributes");
2243
+ const items = Array.isArray(result) ? result : result.attributes ?? [];
2244
+ render(items, opts, () => {
2245
+ if (items.length === 0) {
2246
+ return "No attributes found.";
2247
+ }
2248
+ const rows = items.map((item) => ({
2249
+ NAME: String(item.name ?? ""),
2250
+ TYPE: String(item.type ?? ""),
2251
+ DESCRIPTION: String(item.description ?? "")
2252
+ }));
2253
+ return table({ columns: ["NAME", "TYPE", "DESCRIPTION"], rows });
2254
+ }, items);
2255
+ });
2256
+ }
2257
+
2258
+ // src/commands/fin/audiences.ts
2259
+ function registerAudiencesCommand(fin) {
2260
+ const audiences = fin.command("audiences").description("Manage Fin SDK audiences");
2261
+ audiences.command("list").description("List Fin SDK audiences").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
2262
+ const opts = this.optsWithGlobals();
2263
+ const client = await resolveClient(opts);
2264
+ const result = await client.get("/fin-sdk/audiences");
2265
+ const items = Array.isArray(result) ? result : result.audiences ?? [];
2266
+ render(items, opts, () => {
2267
+ if (items.length === 0) {
2268
+ return "No audiences found.";
2269
+ }
2270
+ const rows = items.map((item) => ({
2271
+ ID: String(item.id ?? ""),
2272
+ NAME: String(item.name ?? "")
2273
+ }));
2274
+ return table({ columns: ["ID", "NAME"], rows });
2275
+ }, items);
2276
+ });
2277
+ }
2278
+
2279
+ // src/commands/fin/workflows.ts
2280
+ function registerWorkflowsCommand(fin) {
2281
+ const workflows = fin.command("workflows").description("Manage Fin SDK workflows");
2282
+ workflows.command("list").description("List Fin SDK workflows").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
2283
+ const opts = this.optsWithGlobals();
2284
+ const client = await resolveClient(opts);
2285
+ const result = await client.get("/fin-sdk/workflows");
2286
+ const items = Array.isArray(result) ? result : result.workflows ?? [];
2287
+ render(items, opts, () => {
2288
+ if (items.length === 0) {
2289
+ return "No workflows found.";
2290
+ }
2291
+ const rows = items.map((item) => ({
2292
+ ID: String(item.id ?? ""),
2293
+ TITLE: String(item.title ?? ""),
2294
+ STATE: String(item.state ?? ""),
2295
+ TRIGGER: String(item.trigger_type ?? "")
2296
+ }));
2297
+ return table({ columns: ["ID", "TITLE", "STATE", "TRIGGER"], rows });
2298
+ }, items);
2299
+ });
2300
+ workflows.command("get <id>").description("Get a workflow by ID").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function(id) {
2301
+ const opts = this.optsWithGlobals();
2302
+ const client = await resolveClient(opts);
2303
+ const result = await client.get(`/fin-sdk/workflows/${id}`);
2304
+ const r = result;
2305
+ render(
2306
+ result,
2307
+ opts,
2308
+ () => detail({
2309
+ "ID": String(r.id ?? ""),
2310
+ "Title": String(r.title ?? ""),
2311
+ "State": String(r.state ?? ""),
2312
+ "Trigger": r.trigger_type != null ? String(r.trigger_type) : void 0
2313
+ })
2314
+ );
2315
+ });
2316
+ }
2317
+
2318
+ // src/commands/fin/simulations.ts
2319
+ function registerSimulationsCommand(fin) {
2320
+ const simulations = fin.command("simulations").description("Manage Fin SDK simulations");
2321
+ simulations.command("list <procedure_id>").description("List simulations for a procedure (displays YAML)").option("--verbose", "Show HTTP request/response details").action(async function(procedureId) {
2322
+ const opts = this.optsWithGlobals();
2323
+ const client = await resolveClient(opts);
2324
+ const text = await client.getText(`/fin-sdk/procedures/${procedureId}/simulations`);
1846
2325
  console.log(text);
1847
2326
  });
2327
+ simulations.command("run <procedure_id> [test_id]").description("Run one or all simulations for a procedure").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function(procedureId, testId) {
2328
+ const opts = this.optsWithGlobals();
2329
+ const client = await resolveClient(opts);
2330
+ let result;
2331
+ if (testId) {
2332
+ result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/${testId}/run`, {});
2333
+ } else {
2334
+ result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/run`, {});
2335
+ }
2336
+ const r = result;
2337
+ render(result, opts, () => {
2338
+ if (r.enqueued && Array.isArray(r.enqueued) && r.enqueued.length > 0) {
2339
+ const rows = r.enqueued.map((e) => ({
2340
+ RUN_ID: String(e.run_id ?? ""),
2341
+ TEST_ID: String(e.test_id ?? ""),
2342
+ NAME: String(e.simulation_name ?? ""),
2343
+ STATUS: String(e.status ?? "")
2344
+ }));
2345
+ return table({ columns: ["RUN_ID", "TEST_ID", "NAME", "STATUS"], rows });
2346
+ } else {
2347
+ const runId = r.run_id;
2348
+ if (runId !== void 0) {
2349
+ return `run_id: ${runId}`;
2350
+ } else {
2351
+ return detail({
2352
+ "Procedure ID": String(r.procedure_id ?? "")
2353
+ });
2354
+ }
2355
+ }
2356
+ });
2357
+ });
2358
+ simulations.command("result <run_id>").description("Get the result of a simulation run").option("--json", "Output as JSON").option("--wait", "Poll until run completes").option("--verbose", "Show HTTP request/response details").action(async function(runId) {
2359
+ const opts = this.optsWithGlobals();
2360
+ const client = await resolveClient(opts);
2361
+ let result = await client.get(`/fin-sdk/runs/${runId}`);
2362
+ if (opts.wait) {
2363
+ const deadline = Date.now() + SIMULATION_POLL_TIMEOUT_MS;
2364
+ while (result.status === "pending" || result.status === "running") {
2365
+ if (Date.now() > deadline) {
2366
+ throw new TimeoutError(
2367
+ `Simulation polling timed out after ${SIMULATION_POLL_TIMEOUT_MS / 1e3}s. Last status: ${result.status}. Run "intercom fin simulations result ${runId}" to check later.`
2368
+ );
2369
+ }
2370
+ await new Promise((r) => setTimeout(r, 2e3));
2371
+ result = await client.get(`/fin-sdk/runs/${runId}`);
2372
+ }
2373
+ }
2374
+ render(
2375
+ result,
2376
+ opts,
2377
+ () => detail({
2378
+ "Run ID": String(result.run_id ?? ""),
2379
+ "Status": String(result.status ?? "")
2380
+ })
2381
+ );
2382
+ });
2383
+ simulations.command("generate <procedure_id>").description("Generate simulations for a procedure").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function(procedureId) {
2384
+ const opts = this.optsWithGlobals();
2385
+ const client = await resolveClient(opts);
2386
+ const result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/generate`, {});
2387
+ const r = result;
2388
+ render(
2389
+ result,
2390
+ opts,
2391
+ () => detail({
2392
+ "Status": String(r.status ?? ""),
2393
+ "Procedure ID": String(r.procedure_id ?? "")
2394
+ })
2395
+ );
2396
+ });
1848
2397
  }
1849
2398
 
1850
2399
  // src/auth/ember-session.ts
@@ -1901,7 +2450,7 @@ async function emberRequest(session, path, options = {}) {
1901
2450
  }
1902
2451
  function extractCookies2(response) {
1903
2452
  const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : (response.headers.get("set-cookie") ?? "").split(/,(?=[^ ])/).filter(Boolean);
1904
- return setCookies.map((c) => c.split(";")[0]).join("; ");
2453
+ return setCookies.map((c2) => c2.split(";")[0]).join("; ");
1905
2454
  }
1906
2455
  function mergeCookies2(existing, newCookies) {
1907
2456
  const map = /* @__PURE__ */ new Map();
@@ -1922,8 +2471,8 @@ function registerFinEnableCommand(fin) {
1922
2471
  ).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() {
1923
2472
  const opts = this.optsWithGlobals();
1924
2473
  const password = await resolvePassword({ passwordStdin: opts.passwordStdin });
1925
- const configDir = process.env.INTERCOM_CONFIG_DIR;
1926
- const config = await readConfig(configDir);
2474
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
2475
+ const config = await readConfig(configDir2);
1927
2476
  const appId = opts.workspace ?? config.default;
1928
2477
  if (!appId) {
1929
2478
  throw new CLIError(
@@ -2017,8 +2566,21 @@ function renderResourceTable(label, items) {
2017
2566
  ${table({ columns: ["ID", "NAME"], rows })}`;
2018
2567
  }
2019
2568
  function registerFinCommand(program) {
2020
- const fin = program.command("fin").description("Manage Fin SDK resources");
2021
- 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() {
2569
+ const fin = program.command("fin").description("Manage Fin AI Agent and SDK resources").addHelpText(
2570
+ "after",
2571
+ `
2572
+ Examples:
2573
+ intercom fin manifest
2574
+ intercom fin enable --email admin@company.com
2575
+ intercom fin guidance list`
2576
+ );
2577
+ 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(
2578
+ "after",
2579
+ `
2580
+ Examples:
2581
+ $ intercom fin manifest
2582
+ $ intercom fin manifest --json`
2583
+ ).action(async function() {
2022
2584
  const opts = this.optsWithGlobals();
2023
2585
  const client = await resolveClient(opts);
2024
2586
  const result = await client.get("/fin-sdk/manifest");
@@ -2043,12 +2605,26 @@ function registerFinCommand(program) {
2043
2605
  });
2044
2606
  });
2045
2607
  registerFinEnableCommand(fin);
2608
+ registerProceduresCommand(fin);
2609
+ registerDataConnectorsCommand(fin);
2046
2610
  registerGuidanceCommand(fin);
2611
+ registerAttributesCommand(fin);
2612
+ registerAudiencesCommand(fin);
2613
+ registerWorkflowsCommand(fin);
2614
+ registerSimulationsCommand(fin);
2615
+ fin.command("download").description("Download Fin SDK resources as a ZIP file").option("-o, --output <path>", "Output file path", "./fin-resources.zip").option("--verbose", "Show HTTP request/response details").action(async function() {
2616
+ const opts = this.optsWithGlobals();
2617
+ const outputPath = opts.output ?? "./fin-resources.zip";
2618
+ const client = await resolveClient(opts);
2619
+ const buffer = await client.getBuffer("/fin-sdk/download");
2620
+ await writeFile2(outputPath, buffer);
2621
+ console.log(`Downloaded ${buffer.length} bytes to ${outputPath}`);
2622
+ });
2047
2623
  }
2048
2624
 
2049
2625
  // src/commands/setup.ts
2050
- import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
2051
- import { join as join3, basename } from "path";
2626
+ import { readdirSync, readFileSync as readFileSync4, statSync } from "fs";
2627
+ import { join as join3, basename as basename2 } from "path";
2052
2628
  function isGreenfield(opts) {
2053
2629
  return !!(opts.email && opts.name && (process.env.INTERCOM_PASSWORD || opts.passwordStdin));
2054
2630
  }
@@ -2056,6 +2632,11 @@ function hasPartialGreenfieldFlags(opts) {
2056
2632
  const hasPassword = !!(process.env.INTERCOM_PASSWORD || opts.passwordStdin);
2057
2633
  return !!(opts.email && hasPassword && !opts.name);
2058
2634
  }
2635
+ async function hasExistingAuth() {
2636
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
2637
+ const config = await readConfig(configDir2);
2638
+ return !!config.default;
2639
+ }
2059
2640
  function buildPlan(opts) {
2060
2641
  const steps = [];
2061
2642
  let stepNum = 1;
@@ -2101,8 +2682,8 @@ function buildPlan(opts) {
2101
2682
  }
2102
2683
  if (mdFiles.length > 0) {
2103
2684
  const articleCommands = mdFiles.map((file) => {
2104
- const title = basename(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2105
- return `intercom articles create -f title="${title}" -F body=@${join3(opts.articlesFrom, file)}`;
2685
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
2686
+ return `intercom articles create -f title="${title}" -F body=@${join3(opts.articlesFrom, file)} -f author_id=$(intercom me --json | jq -r .id)`;
2106
2687
  });
2107
2688
  steps.push({
2108
2689
  step: stepNum++,
@@ -2114,7 +2695,7 @@ function buildPlan(opts) {
2114
2695
  step: stepNum++,
2115
2696
  description: `Create articles from ${opts.articlesFrom}`,
2116
2697
  commands: [
2117
- `intercom articles create -f title="<article-title>" -F body=@${opts.articlesFrom}/<file>.md`
2698
+ `intercom articles create -f title="<article-title>" -F body=@${opts.articlesFrom}/<file>.md -f author_id=$(intercom me --json | jq -r .id)`
2118
2699
  ]
2119
2700
  });
2120
2701
  }
@@ -2140,15 +2721,20 @@ function formatPlanText(steps) {
2140
2721
  }
2141
2722
  async function runPostAuthSteps(client, opts, results, appId) {
2142
2723
  results.push(`\u2713 Messenger configured`);
2724
+ const me = await client.get("/me");
2725
+ const authorId = me.id != null ? Number(me.id) : void 0;
2143
2726
  const helpCenterName = opts.companyName ? `${opts.companyName} Help` : "Help Center";
2144
2727
  const collections = await client.paginate("/help_center/collections");
2145
2728
  const existingCollection = collections.find(
2146
- (c) => String(c.name ?? "") === helpCenterName
2729
+ (c2) => String(c2.name ?? "") === helpCenterName
2147
2730
  );
2731
+ let collectionId;
2148
2732
  if (existingCollection) {
2733
+ collectionId = String(existingCollection.id);
2149
2734
  results.push(`\u2713 Help center already exists (${helpCenterName})`);
2150
2735
  } else {
2151
- await client.post("/help_center/collections", { name: helpCenterName });
2736
+ const created = await client.post("/help_center/collections", { name: helpCenterName });
2737
+ collectionId = created.id != null ? String(created.id) : void 0;
2152
2738
  results.push(`\u2713 Help center created (${helpCenterName})`);
2153
2739
  }
2154
2740
  if (opts.articlesFrom) {
@@ -2163,12 +2749,18 @@ async function runPostAuthSteps(client, opts, results, appId) {
2163
2749
  let created = 0;
2164
2750
  let skipped = 0;
2165
2751
  for (const file of mdFiles) {
2166
- const title = basename(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2167
- const body = readFileSync3(join3(opts.articlesFrom, file), "utf-8");
2752
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
2753
+ const body = readFileSync4(join3(opts.articlesFrom, file), "utf-8");
2168
2754
  if (existingTitles.has(title)) {
2169
2755
  skipped++;
2170
2756
  } else {
2171
- await client.post("/articles", { title, body, state: "published" });
2757
+ const articleBody = { title, body, state: "published" };
2758
+ if (authorId != null) articleBody.author_id = authorId;
2759
+ if (collectionId != null) {
2760
+ articleBody.parent_id = collectionId;
2761
+ articleBody.parent_type = "collection";
2762
+ }
2763
+ await client.post("/articles", articleBody);
2172
2764
  created++;
2173
2765
  }
2174
2766
  }
@@ -2183,19 +2775,19 @@ async function runPostAuthSteps(client, opts, results, appId) {
2183
2775
  const articleCount = (await client.paginate("/articles")).length;
2184
2776
  if (articleCount === 0) {
2185
2777
  results.push(`\u26A0 No help center articles found!`);
2186
- console.log(results.join("\n"));
2187
- console.log("");
2188
- console.log("WARNING: Fin needs help center articles to answer questions.");
2189
- console.log("Without articles, Fin will have nothing to draw from and cannot help visitors.\n");
2190
- console.log("To add articles, either:");
2191
- console.log(" 1. Re-run with: intercom setup --articles-from <directory-of-markdown-files>");
2192
- console.log(' 2. Create articles manually: intercom articles create -f title="..." -f body="..." -F author_id=<id>');
2193
- console.log("");
2194
- console.log("Tip for AI agents: if no articles directory exists, consider creating one by");
2195
- console.log(" analyzing the project's source code, README, documentation, or any available");
2196
- console.log(" context about the product. Write markdown files covering key topics that");
2197
- console.log(" customers would ask about, then re-run setup with --articles-from.\n");
2198
- console.log("Skipping Fin enablement \u2014 enable Fin after adding articles with: intercom fin enable --email <email>");
2778
+ console.error(results.join("\n"));
2779
+ console.error("");
2780
+ console.error("WARNING: Fin needs help center articles to answer questions.");
2781
+ console.error("Without articles, Fin will have nothing to draw from and cannot help visitors.\n");
2782
+ console.error("To add articles, either:");
2783
+ console.error(" 1. Re-run with: intercom setup --articles-from <directory-of-markdown-files>");
2784
+ console.error(' 2. Create articles manually: intercom articles create -f title="..." -f body="..." -F author_id=<id>');
2785
+ console.error("");
2786
+ console.error("Tip for AI agents: if no articles directory exists, consider creating one by");
2787
+ console.error(" analyzing the project's source code, README, documentation, or any available");
2788
+ console.error(" context about the product. Write markdown files covering key topics that");
2789
+ console.error(" customers would ask about, then re-run setup with --articles-from.\n");
2790
+ console.error("Skipping Fin enablement \u2014 enable Fin after adding articles with: intercom fin enable --email <email>");
2199
2791
  return;
2200
2792
  }
2201
2793
  results.push(`\u2713 ${articleCount} article(s) in help center`);
@@ -2219,8 +2811,8 @@ async function runPostAuthSteps(client, opts, results, appId) {
2219
2811
  results.push(`\u2713 Fin already opted in`);
2220
2812
  } else {
2221
2813
  results.push(`\u26A0 Fin opt-in failed (${optIn.status}) \u2014 enable manually with: intercom fin enable --email <email>`);
2222
- console.log(results.join("\n"));
2223
- console.log("Setup complete (Fin requires manual enablement).");
2814
+ console.error(results.join("\n"));
2815
+ console.error("Setup complete (Fin requires manual enablement).");
2224
2816
  return;
2225
2817
  }
2226
2818
  const profile = await emberRequest(
@@ -2251,7 +2843,7 @@ async function runPostAuthSteps(client, opts, results, appId) {
2251
2843
  }
2252
2844
  } else if (opts.enableFin !== false) {
2253
2845
  results.push(`\u26A0 Fin not enabled (--email required for Fin activation)`);
2254
- console.log(" Tip: Re-run with --email to enable Fin, or use: intercom fin enable --email <email>");
2846
+ console.error(" Tip: Re-run with --email to enable Fin, or use: intercom fin enable --email <email>");
2255
2847
  }
2256
2848
  try {
2257
2849
  const manifest = await client.get("/fin-sdk/manifest");
@@ -2266,11 +2858,25 @@ async function runPostAuthSteps(client, opts, results, appId) {
2266
2858
  throw err;
2267
2859
  }
2268
2860
  }
2269
- console.log(results.join("\n"));
2270
- console.log("Setup complete!");
2861
+ console.error(results.join("\n"));
2862
+ console.error("Setup complete!");
2271
2863
  }
2272
2864
  function registerSetupCommand(program) {
2273
- program.command("setup").description("Set up a complete Intercom installation").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() {
2865
+ program.command("setup").description("Set up a complete Intercom installation").addHelpText(
2866
+ "after",
2867
+ `
2868
+ Examples:
2869
+ # Existing workspace (already authenticated)
2870
+ intercom setup --company-name "Acme Inc" --articles-from ./docs
2871
+
2872
+ # New workspace (greenfield)
2873
+ INTERCOM_PASSWORD=*** intercom setup --email admin@acme.com --name "Admin" --company-name "Acme Inc"
2874
+
2875
+ # Preview steps without executing
2876
+ intercom setup --plan
2877
+
2878
+ Flow: signup \u2192 auth \u2192 messenger \u2192 help center \u2192 articles \u2192 fin`
2879
+ ).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() {
2274
2880
  const opts = this.optsWithGlobals();
2275
2881
  if (hasPartialGreenfieldFlags(opts)) {
2276
2882
  throw new CLIError(
@@ -2321,7 +2927,8 @@ function registerSetupCommand(program) {
2321
2927
  return;
2322
2928
  }
2323
2929
  const results = [];
2324
- if (isGreenfield(opts)) {
2930
+ const alreadyAuthed = await hasExistingAuth();
2931
+ if (isGreenfield(opts) && !alreadyAuthed) {
2325
2932
  const password = await resolvePassword({ passwordStdin: opts.passwordStdin });
2326
2933
  const baseUrl = getAppBaseUrl();
2327
2934
  const planTier = opts.planTier ?? "advanced";
@@ -2335,6 +2942,13 @@ function registerSetupCommand(program) {
2335
2942
  plan: planTier
2336
2943
  });
2337
2944
  results.push(`\u2713 Workspace created: ${workspace.name} (${workspace.id})`);
2945
+ if (opts.verifyEmailUrl) {
2946
+ try {
2947
+ await fetchWithTimeout(opts.verifyEmailUrl, { redirect: "manual" }, { timeoutMs: API_TIMEOUT_MS });
2948
+ results.push(`\u2713 Email verified`);
2949
+ } catch {
2950
+ }
2951
+ }
2338
2952
  let token;
2339
2953
  try {
2340
2954
  const result = await getToken({
@@ -2349,9 +2963,17 @@ function registerSetupCommand(program) {
2349
2963
  console.error(`\u2713 Workspace created: ${workspace.name} (${workspace.id})`);
2350
2964
  console.error(`
2351
2965
  Authentication failed after workspace creation: ${message}`);
2352
- console.error(`
2966
+ if (message.includes("403")) {
2967
+ console.error(`
2968
+ This is likely because the email has not been verified.`);
2969
+ console.error(`Verify your email first, then bootstrap:`);
2970
+ console.error(` intercom auth verify-email --url <url-from-verification-email>`);
2971
+ } else {
2972
+ console.error(`
2353
2973
  Your workspace exists but credentials were not saved.`);
2354
- console.error(`To complete setup, run:
2974
+ }
2975
+ console.error(`
2976
+ To complete setup, run:
2355
2977
  `);
2356
2978
  console.error(` INTERCOM_PASSWORD=*** intercom auth bootstrap --email ${opts.email} --app-id ${workspace.id}`);
2357
2979
  console.error(`
@@ -2364,10 +2986,10 @@ Then re-run setup without signup flags:
2364
2986
  );
2365
2987
  }
2366
2988
  results.push(`\u2713 Authentication token obtained`);
2367
- const configDir = process.env.INTERCOM_CONFIG_DIR;
2368
- const store = new CredentialStore(createDefaultBackend(configDir));
2989
+ const configDir2 = process.env.INTERCOM_CONFIG_DIR;
2990
+ const store = new CredentialStore(createDefaultBackend(configDir2));
2369
2991
  await store.set(workspace.id, { token, auth_type: "oauth", app_id: workspace.id });
2370
- await addWorkspace(configDir, { app_id: workspace.id, name: workspace.name });
2992
+ await addWorkspace(configDir2, { app_id: workspace.id, name: workspace.name });
2371
2993
  const client2 = new IntercomClient({
2372
2994
  token,
2373
2995
  authType: "oauth",
@@ -2385,20 +3007,350 @@ Then re-run setup without signup flags:
2385
3007
  });
2386
3008
  }
2387
3009
 
3010
+ // src/commands/completion.ts
3011
+ function binName() {
3012
+ const argv1 = process.argv[1] ?? "";
3013
+ if (argv1.endsWith("/fin") || argv1.endsWith("\\fin")) return "fin";
3014
+ return "intercom";
3015
+ }
3016
+ var COMMANDS = {
3017
+ api: [],
3018
+ auth: ["login", "logout", "status", "bootstrap", "signup", "verify-email"],
3019
+ me: [],
3020
+ articles: ["list", "get", "create", "update", "delete"],
3021
+ contacts: ["list", "get", "create", "update", "delete"],
3022
+ conversations: ["list", "get", "reply"],
3023
+ "help-center": ["list", "get", "create"],
3024
+ messenger: ["get", "update", "snippet"],
3025
+ fin: ["manifest", "enable", "guidance"],
3026
+ setup: [],
3027
+ pricing: [],
3028
+ completion: ["bash", "zsh", "fish"]
3029
+ };
3030
+ var GLOBAL_FLAGS = [
3031
+ "--json",
3032
+ "--jq",
3033
+ "--workspace",
3034
+ "--verbose",
3035
+ "--no-retry",
3036
+ "--timeout",
3037
+ "--agent",
3038
+ "--human",
3039
+ "--help",
3040
+ "--version"
3041
+ ];
3042
+ function bashScript(name = binName()) {
3043
+ const commandNames = Object.keys(COMMANDS).join(" ");
3044
+ const fnName = `_${name}`;
3045
+ const subcommandCases = Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd, subs]) => ` ${cmd}) COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- "$cur") ) ;;`).join("\n");
3046
+ return `# bash completion for ${name} CLI
3047
+ # Add to ~/.bashrc: eval "$(${name} completion bash)"
3048
+
3049
+ ${fnName}() {
3050
+ local cur prev commands
3051
+ COMPREPLY=()
3052
+ cur="\${COMP_WORDS[COMP_CWORD]}"
3053
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
3054
+ commands="${commandNames}"
3055
+
3056
+ if [[ "$cur" == -* ]]; then
3057
+ COMPREPLY=( $(compgen -W "${GLOBAL_FLAGS.join(" ")}" -- "$cur") )
3058
+ return 0
3059
+ fi
3060
+
3061
+ case "\${COMP_WORDS[1]}" in
3062
+ ${subcommandCases}
3063
+ *)
3064
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
3065
+ COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
3066
+ fi
3067
+ ;;
3068
+ esac
3069
+ return 0
3070
+ }
3071
+
3072
+ complete -F ${fnName} ${name}
3073
+ `;
3074
+ }
3075
+ function zshScript(name = binName()) {
3076
+ const fnName = `_${name}`;
3077
+ const commandDescs = [
3078
+ "api:'Make an authenticated API request'",
3079
+ "auth:'Manage authentication credentials'",
3080
+ "me:'Show current admin and workspace info'",
3081
+ "articles:'Manage help center articles'",
3082
+ "contacts:'Manage contacts'",
3083
+ "conversations:'Manage conversations'",
3084
+ "help-center:'Manage help center collections'",
3085
+ "messenger:'Manage Messenger settings'",
3086
+ "fin:'Manage Fin AI Agent'",
3087
+ "setup:'Set up a complete Intercom installation'",
3088
+ "pricing:'Show current Intercom pricing information'",
3089
+ "completion:'Generate shell completion scripts'"
3090
+ ];
3091
+ const subcommandFunctions = Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd, subs]) => {
3092
+ const subFnName = cmd.replace(/-/g, "_");
3093
+ const subLines = subs.map((s) => `'${s}'`).join(" ");
3094
+ return `${fnName}_${subFnName}() {
3095
+ local -a subcommands
3096
+ subcommands=(${subLines})
3097
+ _describe 'subcommand' subcommands
3098
+ }`;
3099
+ }).join("\n\n");
3100
+ return `#compdef ${name}
3101
+ # zsh completion for ${name} CLI
3102
+ # Add to ~/.zshrc: eval "$(${name} completion zsh)"
3103
+
3104
+ ${fnName}() {
3105
+ local -a commands global_flags
3106
+ commands=(
3107
+ ${commandDescs.join("\n ")}
3108
+ )
3109
+ global_flags=(
3110
+ ${GLOBAL_FLAGS.map((f) => `'${f}'`).join("\n ")}
3111
+ )
3112
+
3113
+ _arguments -C \\
3114
+ '1:command:->command' \\
3115
+ '*::arg:->args'
3116
+
3117
+ case $state in
3118
+ command)
3119
+ _describe 'command' commands
3120
+ ;;
3121
+ args)
3122
+ case $words[1] in
3123
+ ${Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd]) => ` ${cmd}) ${fnName}_${cmd.replace(/-/g, "_")} ;;`).join("\n")}
3124
+ esac
3125
+ ;;
3126
+ esac
3127
+ }
3128
+
3129
+ ${subcommandFunctions}
3130
+
3131
+ ${fnName} "$@"
3132
+ `;
3133
+ }
3134
+ function fishScript(name = binName()) {
3135
+ const lines = [
3136
+ `# fish completion for ${name} CLI`,
3137
+ `# Add to fish config: ${name} completion fish | source`,
3138
+ "",
3139
+ "# Disable file completions",
3140
+ `complete -c ${name} -f`,
3141
+ "",
3142
+ "# Top-level commands"
3143
+ ];
3144
+ const commandDescs = {
3145
+ api: "Make an authenticated API request",
3146
+ auth: "Manage authentication credentials",
3147
+ me: "Show current admin and workspace info",
3148
+ articles: "Manage help center articles",
3149
+ contacts: "Manage contacts",
3150
+ conversations: "Manage conversations",
3151
+ "help-center": "Manage help center collections",
3152
+ messenger: "Manage Messenger settings",
3153
+ fin: "Manage Fin AI Agent",
3154
+ setup: "Set up a complete Intercom installation",
3155
+ pricing: "Show current Intercom pricing information",
3156
+ completion: "Generate shell completion scripts"
3157
+ };
3158
+ for (const [cmd, desc] of Object.entries(commandDescs)) {
3159
+ lines.push(
3160
+ `complete -c ${name} -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'`
3161
+ );
3162
+ }
3163
+ lines.push("");
3164
+ lines.push("# Subcommands");
3165
+ for (const [cmd, subs] of Object.entries(COMMANDS)) {
3166
+ for (const sub of subs) {
3167
+ lines.push(
3168
+ `complete -c ${name} -n '__fish_seen_subcommand_from ${cmd}' -a '${sub}'`
3169
+ );
3170
+ }
3171
+ }
3172
+ lines.push("");
3173
+ lines.push("# Global flags");
3174
+ for (const flag of GLOBAL_FLAGS) {
3175
+ lines.push(`complete -c ${name} -l '${flag.replace(/^--/, "")}'`);
3176
+ }
3177
+ lines.push("");
3178
+ return lines.join("\n");
3179
+ }
3180
+ function registerCompletionCommand(program) {
3181
+ const name = binName();
3182
+ const completion = program.command("completion").description("Generate shell completion scripts").addHelpText(
3183
+ "after",
3184
+ `
3185
+ Examples:
3186
+ # Bash (add to ~/.bashrc)
3187
+ eval "$(${name} completion bash)"
3188
+
3189
+ # Zsh (add to ~/.zshrc)
3190
+ eval "$(${name} completion zsh)"
3191
+
3192
+ # Fish (add to fish config)
3193
+ ${name} completion fish | source`
3194
+ );
3195
+ completion.command("bash").description("Generate bash completion script").action(() => {
3196
+ console.log(bashScript());
3197
+ });
3198
+ completion.command("zsh").description("Generate zsh completion script").action(() => {
3199
+ console.log(zshScript());
3200
+ });
3201
+ completion.command("fish").description("Generate fish completion script").action(() => {
3202
+ console.log(fishScript());
3203
+ });
3204
+ }
3205
+
3206
+ // src/commands/pricing.ts
3207
+ var PRICING_URL = "https://www.intercom.com/pricing.md";
3208
+ function registerPricingCommand(program) {
3209
+ program.command("pricing").description("Show current Intercom pricing information").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function() {
3210
+ const opts = this.optsWithGlobals();
3211
+ const response = await fetchWithTimeout(
3212
+ PRICING_URL,
3213
+ { headers: { Accept: "text/markdown, text/plain, */*" } },
3214
+ { timeoutMs: API_TIMEOUT_MS, verbose: opts.verbose }
3215
+ );
3216
+ if (!response.ok) {
3217
+ throw new CLIError(`Failed to fetch pricing: ${response.status} ${response.statusText}`);
3218
+ }
3219
+ const markdown = await response.text();
3220
+ if (opts.json) {
3221
+ const { formatJson: formatJson2 } = await import("./json-MMBOB7YU.js");
3222
+ console.log(formatJson2({ url: PRICING_URL, content: markdown }));
3223
+ } else {
3224
+ console.log(markdown);
3225
+ }
3226
+ });
3227
+ }
3228
+
3229
+ // src/commands/config.ts
3230
+ import { join as join4 } from "path";
3231
+ var SUPPORTED_KEYS = ["default_workspace", "timeout"];
3232
+ function configDir() {
3233
+ return process.env.INTERCOM_CONFIG_DIR ?? defaultConfigDir();
3234
+ }
3235
+ function validateKey(key) {
3236
+ if (!SUPPORTED_KEYS.includes(key)) {
3237
+ throw new CLIError(
3238
+ `Unknown config key "${key}". Supported keys: ${SUPPORTED_KEYS.join(", ")}`
3239
+ );
3240
+ }
3241
+ return key;
3242
+ }
3243
+ async function getValue(key) {
3244
+ const config = await readConfig(configDir());
3245
+ switch (key) {
3246
+ case "default_workspace":
3247
+ return config.default;
3248
+ case "timeout":
3249
+ return config.timeout !== void 0 ? String(config.timeout) : void 0;
3250
+ }
3251
+ }
3252
+ function resolveSource(key) {
3253
+ switch (key) {
3254
+ case "default_workspace":
3255
+ return process.env.INTERCOM_WORKSPACE ? "INTERCOM_WORKSPACE" : "config";
3256
+ case "timeout":
3257
+ return "config";
3258
+ }
3259
+ }
3260
+ async function setValue(key, value) {
3261
+ const dir = configDir();
3262
+ const config = await readConfig(dir);
3263
+ switch (key) {
3264
+ case "default_workspace":
3265
+ config.default = value;
3266
+ break;
3267
+ case "timeout": {
3268
+ const n = parseInt(value, 10);
3269
+ if (isNaN(n) || n <= 0) {
3270
+ throw new CLIError("timeout must be a positive integer (seconds)");
3271
+ }
3272
+ config.timeout = n;
3273
+ break;
3274
+ }
3275
+ }
3276
+ await writeConfig(config, dir);
3277
+ }
3278
+ function registerConfigCommand(program) {
3279
+ const cmd = program.command("config").description("Manage CLI configuration");
3280
+ 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() {
3281
+ const opts = this.optsWithGlobals();
3282
+ const config = await readConfig(configDir());
3283
+ const entries = {};
3284
+ for (const key of SUPPORTED_KEYS) {
3285
+ const val = await getValue(key);
3286
+ entries[key] = {
3287
+ value: val ?? "",
3288
+ source: val !== void 0 ? resolveSource(key) : ""
3289
+ };
3290
+ }
3291
+ render(
3292
+ entries,
3293
+ opts,
3294
+ () => table({
3295
+ columns: ["KEY", "VALUE", "SOURCE"],
3296
+ rows: SUPPORTED_KEYS.map((key) => ({
3297
+ KEY: key,
3298
+ VALUE: entries[key].value,
3299
+ SOURCE: entries[key].source
3300
+ }))
3301
+ })
3302
+ );
3303
+ });
3304
+ 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) {
3305
+ const opts = this.optsWithGlobals();
3306
+ const validKey = validateKey(key);
3307
+ const value = await getValue(validKey);
3308
+ if (value === void 0) {
3309
+ throw new CLIError(`Key "${key}" is not set`);
3310
+ }
3311
+ render({ key, value }, opts, () => value);
3312
+ });
3313
+ cmd.command("set <key> <value>").description("Set a configuration value").action(async function(key, value) {
3314
+ const validKey = validateKey(key);
3315
+ await setValue(validKey, value);
3316
+ console.log(`Set ${key} = ${value}`);
3317
+ });
3318
+ cmd.command("path").description("Show configuration file path").action(async function() {
3319
+ console.log(join4(configDir(), "config.json"));
3320
+ });
3321
+ }
3322
+
2388
3323
  // src/index.ts
3324
+ function detectBinaryName() {
3325
+ const argv1 = process.argv[1] ?? "";
3326
+ if (argv1.endsWith("/fin") || argv1.endsWith("\\fin")) return "fin";
3327
+ return "intercom";
3328
+ }
2389
3329
  function fullCommandName(cmd) {
2390
3330
  const parts = [];
3331
+ const rootNames = /* @__PURE__ */ new Set(["intercom", "fin"]);
2391
3332
  let current = cmd;
2392
3333
  while (current) {
2393
3334
  const name = current.name();
2394
- if (name && name !== "intercom") parts.unshift(name);
3335
+ if (name && !rootNames.has(name)) parts.unshift(name);
2395
3336
  current = current.parent;
2396
3337
  }
2397
3338
  return parts.join(" ");
2398
3339
  }
2399
3340
  function createProgram() {
2400
3341
  const program = new Command();
2401
- program.name("intercom").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");
3342
+ const binName2 = detectBinaryName();
3343
+ 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(
3344
+ "after",
3345
+ `
3346
+ Quick start:
3347
+ $ intercom auth login --token <your-api-token>
3348
+ $ intercom me
3349
+ $ intercom articles list --json
3350
+ $ intercom setup --company-name "Acme" --articles-from ./docs
3351
+
3352
+ Environment: Set INTERCOM_TOKEN to skip login entirely.`
3353
+ );
2402
3354
  registerApiCommand(program);
2403
3355
  registerAuthCommand(program);
2404
3356
  registerMeCommand(program);
@@ -2409,13 +3361,20 @@ function createProgram() {
2409
3361
  registerMessengerCommand(program);
2410
3362
  registerFinCommand(program);
2411
3363
  registerSetupCommand(program);
2412
- program.hook("preAction", (_, actionCommand) => {
3364
+ registerPricingCommand(program);
3365
+ registerCompletionCommand(program);
3366
+ registerConfigCommand(program);
3367
+ program.hook("preAction", (thisCommand, actionCommand) => {
3368
+ const rootOpts = thisCommand.opts();
3369
+ if (rootOpts.agent && rootOpts.human) {
3370
+ throw new CLIError("--agent and --human are mutually exclusive");
3371
+ }
2413
3372
  setCliCommand(fullCommandName(actionCommand));
2414
3373
  });
2415
3374
  return program;
2416
3375
  }
2417
3376
  function handleError(err) {
2418
- const isJson = process.argv.includes("--json");
3377
+ const isJson = process.argv.includes("--json") || process.argv.includes("--agent");
2419
3378
  if (err instanceof CLIError) {
2420
3379
  if (isJson) {
2421
3380
  console.error(formatJson({
@@ -2433,7 +3392,7 @@ function handleError(err) {
2433
3392
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
2434
3393
  process.exit(1);
2435
3394
  }
2436
- var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("intercom");
3395
+ var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("intercom") || process.argv[1]?.endsWith("fin");
2437
3396
  if (isMainModule) {
2438
3397
  const program = createProgram();
2439
3398
  try {