@intercom/cli 0.1.0 → 0.2.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,116 +1,43 @@
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-QEXGKXTU.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
- }
100
-
101
- // src/commands/api.ts
102
- import { readFileSync as readFileSync2 } from "fs";
103
-
104
17
  // src/api/timeouts.ts
105
18
  var API_TIMEOUT_MS = 3e4;
19
+ var SIMULATION_POLL_TIMEOUT_MS = 6e5;
20
+
21
+ // src/version.ts
22
+ import { createRequire } from "module";
23
+ var require2 = createRequire(import.meta.url);
24
+ var pkg = require2("../package.json");
25
+ var VERSION = pkg.version;
106
26
 
107
27
  // src/api/fetch.ts
108
- var USER_AGENT = "IntercomCLI/0.1.0";
28
+ var USER_AGENT = `IntercomCLI/${VERSION}`;
29
+ var _cliCommand = "";
30
+ function setCliCommand(name) {
31
+ _cliCommand = name;
32
+ }
109
33
  async function fetchWithTimeout(url, init, options) {
110
34
  const headers = new Headers(init.headers);
111
35
  if (!headers.has("User-Agent")) {
112
36
  headers.set("User-Agent", USER_AGENT);
113
37
  }
38
+ if (_cliCommand) {
39
+ headers.set("X-CLI-Command", _cliCommand);
40
+ }
114
41
  if (options.verbose) {
115
42
  console.error(`> ${init.method ?? "GET"} ${url}`);
116
43
  }
@@ -560,11 +487,16 @@ async function resolveClient(options = {}) {
560
487
  'Not authenticated. Run "intercom auth login" to get started.'
561
488
  );
562
489
  }
490
+ if (!options.workspace && config.default && !config.workspaces[appId]) {
491
+ throw new AuthError(
492
+ `Default workspace "${appId}" is not in configured workspaces. Run "intercom auth bootstrap" or "intercom auth login --token <token>" to re-authenticate.`
493
+ );
494
+ }
563
495
  const store = new CredentialStore(createDefaultBackend(configDir));
564
496
  const cred = await store.get(appId);
565
497
  if (!cred) {
566
498
  throw new AuthError(
567
- `No credentials found for workspace ${appId}. Run "intercom auth login".`
499
+ `No credentials found for workspace "${appId}". Run "intercom auth bootstrap" or "intercom auth login --token <token>" to authenticate.`
568
500
  );
569
501
  }
570
502
  return new IntercomClient({
@@ -614,6 +546,20 @@ function render(data, opts, humanRenderer, listItems) {
614
546
  console.log(formatJson(data));
615
547
  return;
616
548
  }
549
+ if (opts.agent) {
550
+ if (listItems) {
551
+ for (const item of listItems) {
552
+ console.log(JSON.stringify(item));
553
+ }
554
+ } else {
555
+ console.log(JSON.stringify(data));
556
+ }
557
+ return;
558
+ }
559
+ if (opts.human) {
560
+ console.log(humanRenderer());
561
+ return;
562
+ }
617
563
  if (!process.stdout.isTTY) {
618
564
  if (listItems) {
619
565
  for (const item of listItems) {
@@ -627,8 +573,9 @@ function render(data, opts, humanRenderer, listItems) {
627
573
  console.log(humanRenderer());
628
574
  }
629
575
 
630
- // src/commands/api.ts
631
- function parseFields(fields, typed) {
576
+ // src/utils/fields.ts
577
+ import { readFileSync as readFileSync2 } from "fs";
578
+ function parseTypedFields(fields, typed) {
632
579
  const result = {};
633
580
  for (const field of fields) {
634
581
  const eqIndex = field.indexOf("=");
@@ -654,8 +601,18 @@ function parseFields(fields, typed) {
654
601
  }
655
602
  return result;
656
603
  }
604
+
605
+ // src/commands/api.ts
657
606
  function registerApiCommand(program) {
658
- 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(
607
+ program.command("api").description("Make an authenticated API request (like gh api)").argument("<endpoint>", "API endpoint path (e.g., /articles)").addHelpText(
608
+ "after",
609
+ `
610
+ Examples:
611
+ intercom api /me
612
+ intercom api /articles --paginate
613
+ intercom api /contacts -X POST -f email=test@example.com -f role=user
614
+ intercom api /articles/123 --jq '.title'`
615
+ ).option("-X, --method <method>", "HTTP method").option("-f, --field <key=value...>", "String field").option(
659
616
  "-F, --typed-field <key=value...>",
660
617
  "Typed field (bools, numbers, file reads)"
661
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) {
@@ -664,8 +621,8 @@ function registerApiCommand(program) {
664
621
  const stringFields = options.field ?? [];
665
622
  const typedFields = options.typedField ?? [];
666
623
  const body = {
667
- ...parseFields(stringFields, false),
668
- ...parseFields(typedFields, true)
624
+ ...parseTypedFields(stringFields, false),
625
+ ...parseTypedFields(typedFields, true)
669
626
  };
670
627
  const hasBody = stringFields.length > 0 || typedFields.length > 0;
671
628
  const method = options.method ?? (hasBody ? "POST" : "GET");
@@ -810,7 +767,6 @@ async function signup(options) {
810
767
  email_submission: {},
811
768
  sign_up_locale: "en",
812
769
  new_app_feature_flags: [
813
- "synchronously-create-subscription-on-signup",
814
770
  "fin-sdk",
815
771
  "messenger-public-api"
816
772
  ]
@@ -911,6 +867,13 @@ async function findOrCreateAppPackage(baseUrl, cookies, appId, appPackageId, tim
911
867
  { timeoutMs }
912
868
  );
913
869
  if (!listResponse.ok) {
870
+ if (listResponse.status === 403) {
871
+ throw new Error(
872
+ `Developer Hub access denied (403). This usually means the admin email has not been verified.
873
+ Verify your email first: intercom auth verify-email --url <url-from-verification-email>
874
+ Then retry this command.`
875
+ );
876
+ }
914
877
  throw new Error(`Failed to list app packages: ${listResponse.status}`);
915
878
  }
916
879
  const data = await listResponse.json();
@@ -952,13 +915,20 @@ async function findOrCreateAppPackage(baseUrl, cookies, appId, appPackageId, tim
952
915
  { timeoutMs }
953
916
  );
954
917
  if (!createResponse.ok) {
918
+ if (createResponse.status === 403) {
919
+ throw new Error(
920
+ `Developer Hub access denied (403). This usually means the admin email has not been verified.
921
+ Verify your email first: intercom auth verify-email --url <url-from-verification-email>
922
+ Then retry this command.`
923
+ );
924
+ }
955
925
  throw new Error(`Failed to create app package: ${createResponse.status}`);
956
926
  }
957
927
  const created = await createResponse.json();
958
- const pkg = created.app_package ?? created;
928
+ const pkg2 = created.app_package ?? created;
959
929
  return {
960
- id: String(pkg.id),
961
- name: pkg.name ?? "CLI Access Token",
930
+ id: String(pkg2.id),
931
+ name: pkg2.name ?? "CLI Access Token",
962
932
  created: true
963
933
  };
964
934
  }
@@ -1168,6 +1138,25 @@ function registerSignupCommand(auth) {
1168
1138
  URL: `${baseUrl}/a/apps/${workspace.id}`
1169
1139
  })
1170
1140
  );
1141
+ try {
1142
+ const { token, appId } = await getToken({
1143
+ baseUrl,
1144
+ email: options.email,
1145
+ password,
1146
+ appId: workspace.id
1147
+ });
1148
+ const configDir = process.env.INTERCOM_CONFIG_DIR;
1149
+ const store = new CredentialStore(createDefaultBackend(configDir));
1150
+ await store.set(appId, { token, auth_type: "oauth", app_id: appId });
1151
+ await addWorkspace(configDir, { app_id: appId, name: workspace.name });
1152
+ console.log(`
1153
+ Authenticated to workspace ${appId}`);
1154
+ } catch {
1155
+ console.error(
1156
+ `
1157
+ Warning: Workspace created but auto-login failed. Run "intercom auth bootstrap --email ${options.email} --app-id ${workspace.id}" to authenticate.`
1158
+ );
1159
+ }
1171
1160
  } catch (err) {
1172
1161
  if (err instanceof CLIError) throw err;
1173
1162
  const message = err instanceof Error ? err.message : String(err);
@@ -1207,7 +1196,14 @@ function registerVerifyEmailCommand(auth) {
1207
1196
 
1208
1197
  // src/commands/auth/index.ts
1209
1198
  function registerAuthCommand(program) {
1210
- const auth = program.command("auth").description("Manage authentication credentials");
1199
+ const auth = program.command("auth").description("Manage authentication credentials").addHelpText(
1200
+ "after",
1201
+ `
1202
+ Examples:
1203
+ intercom auth login --token <api-token>
1204
+ intercom auth status
1205
+ intercom auth logout`
1206
+ );
1211
1207
  registerLoginCommand(auth);
1212
1208
  registerBootstrapCommand(auth);
1213
1209
  registerStatusCommand(auth);
@@ -1249,7 +1245,16 @@ function htmlToText(html) {
1249
1245
 
1250
1246
  // src/commands/articles.ts
1251
1247
  function registerArticlesCommand(program) {
1252
- const articles = program.command("articles").description("Manage help center articles");
1248
+ const articles = program.command("articles").description("Manage help center articles").addHelpText(
1249
+ "after",
1250
+ `
1251
+ Examples:
1252
+ intercom articles list
1253
+ intercom articles get 123
1254
+ intercom articles create -f title="Getting Started" -F body=@article.md
1255
+ intercom articles update 123 -f state=published
1256
+ intercom articles delete 123`
1257
+ );
1253
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() {
1254
1259
  const opts = this.optsWithGlobals();
1255
1260
  const client = await resolveClient(opts);
@@ -1302,10 +1307,18 @@ ${body}` : meta;
1302
1307
  "Field to set (repeatable)",
1303
1308
  (v, acc) => [...acc, v],
1304
1309
  []
1310
+ ).option(
1311
+ "-F, --typed-field <key=value>",
1312
+ "Typed field: bools, numbers, @file reads (repeatable)",
1313
+ (v, acc) => [...acc, v],
1314
+ []
1305
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() {
1306
1316
  const opts = this.optsWithGlobals();
1307
1317
  const client = await resolveClient(opts);
1308
- const body = parseFieldArgs(opts.field ?? []);
1318
+ const body = {
1319
+ ...parseFieldArgs(opts.field ?? []),
1320
+ ...parseTypedFields(opts.typedField ?? [], true)
1321
+ };
1309
1322
  if (body.author_id && typeof body.author_id === "string") {
1310
1323
  const parsed = parseInt(body.author_id, 10);
1311
1324
  if (!isNaN(parsed)) body.author_id = parsed;
@@ -1326,10 +1339,18 @@ ${body}` : meta;
1326
1339
  "Field to set (repeatable)",
1327
1340
  (v, acc) => [...acc, v],
1328
1341
  []
1342
+ ).option(
1343
+ "-F, --typed-field <key=value>",
1344
+ "Typed field: bools, numbers, @file reads (repeatable)",
1345
+ (v, acc) => [...acc, v],
1346
+ []
1329
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(id) {
1330
1348
  const opts = this.optsWithGlobals();
1331
1349
  const client = await resolveClient(opts);
1332
- const body = parseFieldArgs(opts.field ?? []);
1350
+ const body = {
1351
+ ...parseFieldArgs(opts.field ?? []),
1352
+ ...parseTypedFields(opts.typedField ?? [], true)
1353
+ };
1333
1354
  if (body.author_id && typeof body.author_id === "string") {
1334
1355
  const parsed = parseInt(body.author_id, 10);
1335
1356
  if (!isNaN(parsed)) body.author_id = parsed;
@@ -1356,7 +1377,15 @@ ${body}` : meta;
1356
1377
 
1357
1378
  // src/commands/contacts.ts
1358
1379
  function registerContactsCommand(program) {
1359
- const contacts = program.command("contacts").description("Manage contacts");
1380
+ const contacts = program.command("contacts").description("Manage contacts").addHelpText(
1381
+ "after",
1382
+ `
1383
+ Examples:
1384
+ intercom contacts list --limit 10
1385
+ intercom contacts get abc123
1386
+ intercom contacts create -f email=user@example.com -f role=user
1387
+ intercom contacts delete abc123`
1388
+ );
1360
1389
  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() {
1361
1390
  const opts = this.optsWithGlobals();
1362
1391
  const client = await resolveClient(opts);
@@ -1533,7 +1562,14 @@ function renderConversationStream(result) {
1533
1562
  return parts.join("\n");
1534
1563
  }
1535
1564
  function registerConversationsCommand(program) {
1536
- const conversations = program.command("conversations").description("Manage conversations");
1565
+ const conversations = program.command("conversations").description("Manage conversations").addHelpText(
1566
+ "after",
1567
+ `
1568
+ Examples:
1569
+ intercom conversations list
1570
+ intercom conversations get 123
1571
+ intercom conversations reply 123 -f body="Thanks!" -f message_type=comment -f type=admin -f admin_id=456`
1572
+ );
1537
1573
  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() {
1538
1574
  const opts = this.optsWithGlobals();
1539
1575
  const client = await resolveClient(opts);
@@ -1604,7 +1640,14 @@ function registerConversationsCommand(program) {
1604
1640
 
1605
1641
  // src/commands/help-center.ts
1606
1642
  function registerHelpCenterCommand(program) {
1607
- const helpCenter = program.command("help-center").description("Manage help center collections");
1643
+ const helpCenter = program.command("help-center").description("Manage help center collections").addHelpText(
1644
+ "after",
1645
+ `
1646
+ Examples:
1647
+ intercom help-center list
1648
+ intercom help-center get 123
1649
+ intercom help-center create -f name="FAQ"`
1650
+ );
1608
1651
  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() {
1609
1652
  const opts = this.optsWithGlobals();
1610
1653
  const client = await resolveClient(opts);
@@ -1699,7 +1742,14 @@ export function IntercomWidget() {
1699
1742
  }`;
1700
1743
  }
1701
1744
  function registerMessengerCommand(program) {
1702
- const messenger = program.command("messenger").description("Manage Intercom Messenger settings");
1745
+ const messenger = program.command("messenger").description("Manage Intercom Messenger settings").addHelpText(
1746
+ "after",
1747
+ `
1748
+ Examples:
1749
+ intercom messenger get
1750
+ intercom messenger update -f alignment=right -f theme_mode=dark
1751
+ intercom messenger snippet --format react`
1752
+ );
1703
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() {
1704
1754
  const opts = this.optsWithGlobals();
1705
1755
  const client = await resolveClient(opts);
@@ -1766,6 +1816,111 @@ function registerMessengerCommand(program) {
1766
1816
  });
1767
1817
  }
1768
1818
 
1819
+ // src/commands/fin.ts
1820
+ import { writeFile as writeFile2 } from "fs/promises";
1821
+
1822
+ // src/commands/fin/procedures.ts
1823
+ import { readFile as readFile2 } from "fs/promises";
1824
+ import { basename } from "path";
1825
+ import AdmZip from "adm-zip";
1826
+ function registerProceduresCommand(fin) {
1827
+ const procedures = fin.command("procedures").description("Manage Fin SDK procedures");
1828
+ 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() {
1829
+ const opts = this.optsWithGlobals();
1830
+ const client = await resolveClient(opts);
1831
+ const result = await client.get("/fin-sdk/manifest");
1832
+ const manifest = result;
1833
+ const items = manifest.procedures ?? [];
1834
+ render(items, opts, () => {
1835
+ if (items.length === 0) {
1836
+ return "No procedures found.";
1837
+ }
1838
+ const rows = items.map((item) => ({
1839
+ ID: String(item.id ?? ""),
1840
+ NAME: String(item.name ?? "")
1841
+ }));
1842
+ return table({ columns: ["ID", "NAME"], rows });
1843
+ }, items);
1844
+ });
1845
+ 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) {
1846
+ const opts = this.optsWithGlobals();
1847
+ const client = await resolveClient(opts);
1848
+ const buffer = await client.getBuffer(`/fin-sdk/procedures/${id}/download`);
1849
+ const zip = new AdmZip(buffer);
1850
+ const entry = zip.getEntries().find((e) => e.entryName.endsWith("procedure.yaml"));
1851
+ if (!entry) {
1852
+ throw new Error("procedure.yaml not found in downloaded ZIP");
1853
+ }
1854
+ console.log(zip.readAsText(entry));
1855
+ });
1856
+ 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) {
1857
+ const opts = this.optsWithGlobals();
1858
+ const client = await resolveClient(opts);
1859
+ const fileContent = await readFile2(filePath);
1860
+ const formData = new FormData();
1861
+ const blob = new Blob([fileContent], { type: "application/x-yaml" });
1862
+ formData.append("file_data", blob, basename(filePath));
1863
+ const result = await client.postFormData("/fin-sdk/upload", formData);
1864
+ render(result, opts, () => formatJson(result));
1865
+ });
1866
+ }
1867
+
1868
+ // src/commands/fin/data-connectors.ts
1869
+ function registerDataConnectorsCommand(fin) {
1870
+ const dataConnectors = fin.command("data-connectors").description("Manage Fin SDK data connectors");
1871
+ 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() {
1872
+ const opts = this.optsWithGlobals();
1873
+ const client = await resolveClient(opts);
1874
+ const result = await client.get("/fin-sdk/manifest");
1875
+ const manifest = result;
1876
+ const items = manifest.data_connectors ?? [];
1877
+ render(items, opts, () => {
1878
+ if (items.length === 0) {
1879
+ return "No data connectors found.";
1880
+ }
1881
+ const rows = items.map((item) => ({
1882
+ ID: String(item.id ?? ""),
1883
+ NAME: String(item.name ?? "")
1884
+ }));
1885
+ return table({ columns: ["ID", "NAME"], rows });
1886
+ }, items);
1887
+ });
1888
+ 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() {
1889
+ const opts = this.optsWithGlobals();
1890
+ const client = await resolveClient(opts);
1891
+ const fields = opts.field ?? [];
1892
+ const body = {};
1893
+ for (const f of fields) {
1894
+ const eq = f.indexOf("=");
1895
+ if (eq === -1) throw new Error(`Invalid field format: ${f}. Expected key=value`);
1896
+ body[f.slice(0, eq)] = f.slice(eq + 1);
1897
+ }
1898
+ const result = await client.post("/fin-sdk/data-connectors", body);
1899
+ render(
1900
+ result,
1901
+ opts,
1902
+ () => detail({
1903
+ "ID": String(result.id ?? ""),
1904
+ "Name": String(result.name ?? ""),
1905
+ "Status": result.status != null ? String(result.status) : void 0
1906
+ })
1907
+ );
1908
+ });
1909
+ 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) {
1910
+ const opts = this.optsWithGlobals();
1911
+ const client = await resolveClient(opts);
1912
+ const result = await client.get(`/fin-sdk/data-connectors/${id}/download`);
1913
+ render(
1914
+ result,
1915
+ opts,
1916
+ () => detail({
1917
+ "ID": String(result.id ?? ""),
1918
+ "Name": String(result.name ?? "")
1919
+ })
1920
+ );
1921
+ });
1922
+ }
1923
+
1769
1924
  // src/commands/fin/guidance.ts
1770
1925
  function registerGuidanceCommand(fin) {
1771
1926
  const guidance = fin.command("guidance").description("Manage Fin SDK guidance");
@@ -1794,6 +1949,169 @@ function registerGuidanceCommand(fin) {
1794
1949
  });
1795
1950
  }
1796
1951
 
1952
+ // src/commands/fin/attributes.ts
1953
+ function registerAttributesCommand(fin) {
1954
+ const attributes = fin.command("attributes").description("Manage Fin SDK attributes");
1955
+ attributes.command("list").description("List Fin SDK attributes").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
1956
+ const opts = this.optsWithGlobals();
1957
+ const client = await resolveClient(opts);
1958
+ const result = await client.get("/fin-sdk/attributes");
1959
+ const items = Array.isArray(result) ? result : result.attributes ?? [];
1960
+ render(items, opts, () => {
1961
+ if (items.length === 0) {
1962
+ return "No attributes found.";
1963
+ }
1964
+ const rows = items.map((item) => ({
1965
+ NAME: String(item.name ?? ""),
1966
+ TYPE: String(item.type ?? ""),
1967
+ DESCRIPTION: String(item.description ?? "")
1968
+ }));
1969
+ return table({ columns: ["NAME", "TYPE", "DESCRIPTION"], rows });
1970
+ }, items);
1971
+ });
1972
+ }
1973
+
1974
+ // src/commands/fin/audiences.ts
1975
+ function registerAudiencesCommand(fin) {
1976
+ const audiences = fin.command("audiences").description("Manage Fin SDK audiences");
1977
+ audiences.command("list").description("List Fin SDK audiences").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
1978
+ const opts = this.optsWithGlobals();
1979
+ const client = await resolveClient(opts);
1980
+ const result = await client.get("/fin-sdk/audiences");
1981
+ const items = Array.isArray(result) ? result : result.audiences ?? [];
1982
+ render(items, opts, () => {
1983
+ if (items.length === 0) {
1984
+ return "No audiences found.";
1985
+ }
1986
+ const rows = items.map((item) => ({
1987
+ ID: String(item.id ?? ""),
1988
+ NAME: String(item.name ?? "")
1989
+ }));
1990
+ return table({ columns: ["ID", "NAME"], rows });
1991
+ }, items);
1992
+ });
1993
+ }
1994
+
1995
+ // src/commands/fin/workflows.ts
1996
+ function registerWorkflowsCommand(fin) {
1997
+ const workflows = fin.command("workflows").description("Manage Fin SDK workflows");
1998
+ workflows.command("list").description("List Fin SDK workflows").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
1999
+ const opts = this.optsWithGlobals();
2000
+ const client = await resolveClient(opts);
2001
+ const result = await client.get("/fin-sdk/workflows");
2002
+ const items = Array.isArray(result) ? result : result.workflows ?? [];
2003
+ render(items, opts, () => {
2004
+ if (items.length === 0) {
2005
+ return "No workflows found.";
2006
+ }
2007
+ const rows = items.map((item) => ({
2008
+ ID: String(item.id ?? ""),
2009
+ TITLE: String(item.title ?? ""),
2010
+ STATE: String(item.state ?? ""),
2011
+ TRIGGER: String(item.trigger_type ?? "")
2012
+ }));
2013
+ return table({ columns: ["ID", "TITLE", "STATE", "TRIGGER"], rows });
2014
+ }, items);
2015
+ });
2016
+ 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) {
2017
+ const opts = this.optsWithGlobals();
2018
+ const client = await resolveClient(opts);
2019
+ const result = await client.get(`/fin-sdk/workflows/${id}`);
2020
+ const r = result;
2021
+ render(
2022
+ result,
2023
+ opts,
2024
+ () => detail({
2025
+ "ID": String(r.id ?? ""),
2026
+ "Title": String(r.title ?? ""),
2027
+ "State": String(r.state ?? ""),
2028
+ "Trigger": r.trigger_type != null ? String(r.trigger_type) : void 0
2029
+ })
2030
+ );
2031
+ });
2032
+ }
2033
+
2034
+ // src/commands/fin/simulations.ts
2035
+ function registerSimulationsCommand(fin) {
2036
+ const simulations = fin.command("simulations").description("Manage Fin SDK simulations");
2037
+ simulations.command("list <procedure_id>").description("List simulations for a procedure (displays YAML)").option("--verbose", "Show HTTP request/response details").action(async function(procedureId) {
2038
+ const opts = this.optsWithGlobals();
2039
+ const client = await resolveClient(opts);
2040
+ const text = await client.getText(`/fin-sdk/procedures/${procedureId}/simulations`);
2041
+ console.log(text);
2042
+ });
2043
+ 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) {
2044
+ const opts = this.optsWithGlobals();
2045
+ const client = await resolveClient(opts);
2046
+ let result;
2047
+ if (testId) {
2048
+ result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/${testId}/run`, {});
2049
+ } else {
2050
+ result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/run`, {});
2051
+ }
2052
+ const r = result;
2053
+ render(result, opts, () => {
2054
+ if (r.enqueued && Array.isArray(r.enqueued) && r.enqueued.length > 0) {
2055
+ const rows = r.enqueued.map((e) => ({
2056
+ RUN_ID: String(e.run_id ?? ""),
2057
+ TEST_ID: String(e.test_id ?? ""),
2058
+ NAME: String(e.simulation_name ?? ""),
2059
+ STATUS: String(e.status ?? "")
2060
+ }));
2061
+ return table({ columns: ["RUN_ID", "TEST_ID", "NAME", "STATUS"], rows });
2062
+ } else {
2063
+ const runId = r.run_id;
2064
+ if (runId !== void 0) {
2065
+ return `run_id: ${runId}`;
2066
+ } else {
2067
+ return detail({
2068
+ "Procedure ID": String(r.procedure_id ?? "")
2069
+ });
2070
+ }
2071
+ }
2072
+ });
2073
+ });
2074
+ 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) {
2075
+ const opts = this.optsWithGlobals();
2076
+ const client = await resolveClient(opts);
2077
+ let result = await client.get(`/fin-sdk/runs/${runId}`);
2078
+ if (opts.wait) {
2079
+ const deadline = Date.now() + SIMULATION_POLL_TIMEOUT_MS;
2080
+ while (result.status === "pending" || result.status === "running") {
2081
+ if (Date.now() > deadline) {
2082
+ throw new TimeoutError(
2083
+ `Simulation polling timed out after ${SIMULATION_POLL_TIMEOUT_MS / 1e3}s. Last status: ${result.status}. Run "intercom fin simulations result ${runId}" to check later.`
2084
+ );
2085
+ }
2086
+ await new Promise((r) => setTimeout(r, 2e3));
2087
+ result = await client.get(`/fin-sdk/runs/${runId}`);
2088
+ }
2089
+ }
2090
+ render(
2091
+ result,
2092
+ opts,
2093
+ () => detail({
2094
+ "Run ID": String(result.run_id ?? ""),
2095
+ "Status": String(result.status ?? "")
2096
+ })
2097
+ );
2098
+ });
2099
+ 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) {
2100
+ const opts = this.optsWithGlobals();
2101
+ const client = await resolveClient(opts);
2102
+ const result = await client.post(`/fin-sdk/procedures/${procedureId}/simulations/generate`, {});
2103
+ const r = result;
2104
+ render(
2105
+ result,
2106
+ opts,
2107
+ () => detail({
2108
+ "Status": String(r.status ?? ""),
2109
+ "Procedure ID": String(r.procedure_id ?? "")
2110
+ })
2111
+ );
2112
+ });
2113
+ }
2114
+
1797
2115
  // src/auth/ember-session.ts
1798
2116
  async function getEmberSession(options) {
1799
2117
  const { email, password, appId, verifyEmailUrl, timeoutMs = API_TIMEOUT_MS } = options;
@@ -1964,7 +2282,14 @@ function renderResourceTable(label, items) {
1964
2282
  ${table({ columns: ["ID", "NAME"], rows })}`;
1965
2283
  }
1966
2284
  function registerFinCommand(program) {
1967
- const fin = program.command("fin").description("Manage Fin SDK resources");
2285
+ const fin = program.command("fin").description("Manage Fin AI Agent and SDK resources").addHelpText(
2286
+ "after",
2287
+ `
2288
+ Examples:
2289
+ intercom fin manifest
2290
+ intercom fin enable --email admin@company.com
2291
+ intercom fin guidance list`
2292
+ );
1968
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() {
1969
2294
  const opts = this.optsWithGlobals();
1970
2295
  const client = await resolveClient(opts);
@@ -1990,12 +2315,26 @@ function registerFinCommand(program) {
1990
2315
  });
1991
2316
  });
1992
2317
  registerFinEnableCommand(fin);
2318
+ registerProceduresCommand(fin);
2319
+ registerDataConnectorsCommand(fin);
1993
2320
  registerGuidanceCommand(fin);
2321
+ registerAttributesCommand(fin);
2322
+ registerAudiencesCommand(fin);
2323
+ registerWorkflowsCommand(fin);
2324
+ registerSimulationsCommand(fin);
2325
+ 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() {
2326
+ const opts = this.optsWithGlobals();
2327
+ const outputPath = opts.output ?? "./fin-resources.zip";
2328
+ const client = await resolveClient(opts);
2329
+ const buffer = await client.getBuffer("/fin-sdk/download");
2330
+ await writeFile2(outputPath, buffer);
2331
+ console.log(`Downloaded ${buffer.length} bytes to ${outputPath}`);
2332
+ });
1994
2333
  }
1995
2334
 
1996
2335
  // src/commands/setup.ts
1997
2336
  import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
1998
- import { join as join3, basename } from "path";
2337
+ import { join as join3, basename as basename2 } from "path";
1999
2338
  function isGreenfield(opts) {
2000
2339
  return !!(opts.email && opts.name && (process.env.INTERCOM_PASSWORD || opts.passwordStdin));
2001
2340
  }
@@ -2003,6 +2342,11 @@ function hasPartialGreenfieldFlags(opts) {
2003
2342
  const hasPassword = !!(process.env.INTERCOM_PASSWORD || opts.passwordStdin);
2004
2343
  return !!(opts.email && hasPassword && !opts.name);
2005
2344
  }
2345
+ async function hasExistingAuth() {
2346
+ const configDir = process.env.INTERCOM_CONFIG_DIR;
2347
+ const config = await readConfig(configDir);
2348
+ return !!config.default;
2349
+ }
2006
2350
  function buildPlan(opts) {
2007
2351
  const steps = [];
2008
2352
  let stepNum = 1;
@@ -2024,23 +2368,6 @@ function buildPlan(opts) {
2024
2368
  ]
2025
2369
  });
2026
2370
  }
2027
- if (opts.companyName) {
2028
- steps.push({
2029
- step: stepNum++,
2030
- description: "Configure Messenger branding",
2031
- commands: [
2032
- `intercom messenger update -f name="${opts.companyName}"`
2033
- ]
2034
- });
2035
- } else {
2036
- steps.push({
2037
- step: stepNum++,
2038
- description: "Configure Messenger branding",
2039
- commands: [
2040
- `intercom messenger update -f name="<your-company-name>"`
2041
- ]
2042
- });
2043
- }
2044
2371
  steps.push({
2045
2372
  step: stepNum++,
2046
2373
  description: "Get Messenger install snippet",
@@ -2065,8 +2392,8 @@ function buildPlan(opts) {
2065
2392
  }
2066
2393
  if (mdFiles.length > 0) {
2067
2394
  const articleCommands = mdFiles.map((file) => {
2068
- const title = basename(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2069
- return `intercom articles create -f title="${title}" -F body=@${join3(opts.articlesFrom, file)}`;
2395
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2396
+ return `intercom articles create -f title="${title}" -F body=@${join3(opts.articlesFrom, file)} -f author_id=$(intercom me --json | jq -r .id)`;
2070
2397
  });
2071
2398
  steps.push({
2072
2399
  step: stepNum++,
@@ -2078,7 +2405,7 @@ function buildPlan(opts) {
2078
2405
  step: stepNum++,
2079
2406
  description: `Create articles from ${opts.articlesFrom}`,
2080
2407
  commands: [
2081
- `intercom articles create -f title="<article-title>" -F body=@${opts.articlesFrom}/<file>.md`
2408
+ `intercom articles create -f title="<article-title>" -F body=@${opts.articlesFrom}/<file>.md -f author_id=$(intercom me --json | jq -r .id)`
2082
2409
  ]
2083
2410
  });
2084
2411
  }
@@ -2103,19 +2430,21 @@ function formatPlanText(steps) {
2103
2430
  return lines.join("\n");
2104
2431
  }
2105
2432
  async function runPostAuthSteps(client, opts, results, appId) {
2106
- const messengerBody = {};
2107
- if (opts.companyName) messengerBody.name = opts.companyName;
2108
- await client.put("/messenger", messengerBody, { "Intercom-Version": "Unstable" });
2109
- results.push(`\u2713 Messenger configured${opts.companyName ? ` (${opts.companyName})` : ""}`);
2433
+ results.push(`\u2713 Messenger configured`);
2434
+ const me = await client.get("/me");
2435
+ const authorId = me.id != null ? Number(me.id) : void 0;
2110
2436
  const helpCenterName = opts.companyName ? `${opts.companyName} Help` : "Help Center";
2111
2437
  const collections = await client.paginate("/help_center/collections");
2112
2438
  const existingCollection = collections.find(
2113
2439
  (c) => String(c.name ?? "") === helpCenterName
2114
2440
  );
2441
+ let collectionId;
2115
2442
  if (existingCollection) {
2443
+ collectionId = String(existingCollection.id);
2116
2444
  results.push(`\u2713 Help center already exists (${helpCenterName})`);
2117
2445
  } else {
2118
- await client.post("/help_center/collections", { name: helpCenterName });
2446
+ const created = await client.post("/help_center/collections", { name: helpCenterName });
2447
+ collectionId = created.id != null ? String(created.id) : void 0;
2119
2448
  results.push(`\u2713 Help center created (${helpCenterName})`);
2120
2449
  }
2121
2450
  if (opts.articlesFrom) {
@@ -2130,12 +2459,18 @@ async function runPostAuthSteps(client, opts, results, appId) {
2130
2459
  let created = 0;
2131
2460
  let skipped = 0;
2132
2461
  for (const file of mdFiles) {
2133
- const title = basename(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2462
+ const title = basename2(file, ".md").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2134
2463
  const body = readFileSync3(join3(opts.articlesFrom, file), "utf-8");
2135
2464
  if (existingTitles.has(title)) {
2136
2465
  skipped++;
2137
2466
  } else {
2138
- await client.post("/articles", { title, body, state: "published" });
2467
+ const articleBody = { title, body, state: "published" };
2468
+ if (authorId != null) articleBody.author_id = authorId;
2469
+ if (collectionId != null) {
2470
+ articleBody.parent_id = collectionId;
2471
+ articleBody.parent_type = "collection";
2472
+ }
2473
+ await client.post("/articles", articleBody);
2139
2474
  created++;
2140
2475
  }
2141
2476
  }
@@ -2237,7 +2572,19 @@ async function runPostAuthSteps(client, opts, results, appId) {
2237
2572
  console.log("Setup complete!");
2238
2573
  }
2239
2574
  function registerSetupCommand(program) {
2240
- 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() {
2575
+ program.command("setup").description("Set up a complete Intercom installation").addHelpText(
2576
+ "after",
2577
+ `
2578
+ Examples:
2579
+ # Existing workspace (already authenticated)
2580
+ intercom setup --company-name "Acme Inc" --articles-from ./docs
2581
+
2582
+ # New workspace (greenfield)
2583
+ INTERCOM_PASSWORD=*** intercom setup --email admin@acme.com --name "Admin" --company-name "Acme Inc"
2584
+
2585
+ # Preview steps without executing
2586
+ intercom setup --plan`
2587
+ ).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() {
2241
2588
  const opts = this.optsWithGlobals();
2242
2589
  if (hasPartialGreenfieldFlags(opts)) {
2243
2590
  throw new CLIError(
@@ -2288,7 +2635,8 @@ function registerSetupCommand(program) {
2288
2635
  return;
2289
2636
  }
2290
2637
  const results = [];
2291
- if (isGreenfield(opts)) {
2638
+ const alreadyAuthed = await hasExistingAuth();
2639
+ if (isGreenfield(opts) && !alreadyAuthed) {
2292
2640
  const password = await resolvePassword({ passwordStdin: opts.passwordStdin });
2293
2641
  const baseUrl = getAppBaseUrl();
2294
2642
  const planTier = opts.planTier ?? "advanced";
@@ -2302,6 +2650,13 @@ function registerSetupCommand(program) {
2302
2650
  plan: planTier
2303
2651
  });
2304
2652
  results.push(`\u2713 Workspace created: ${workspace.name} (${workspace.id})`);
2653
+ if (opts.verifyEmailUrl) {
2654
+ try {
2655
+ await fetchWithTimeout(opts.verifyEmailUrl, { redirect: "manual" }, { timeoutMs: API_TIMEOUT_MS });
2656
+ results.push(`\u2713 Email verified`);
2657
+ } catch {
2658
+ }
2659
+ }
2305
2660
  let token;
2306
2661
  try {
2307
2662
  const result = await getToken({
@@ -2316,9 +2671,17 @@ function registerSetupCommand(program) {
2316
2671
  console.error(`\u2713 Workspace created: ${workspace.name} (${workspace.id})`);
2317
2672
  console.error(`
2318
2673
  Authentication failed after workspace creation: ${message}`);
2319
- console.error(`
2674
+ if (message.includes("403")) {
2675
+ console.error(`
2676
+ This is likely because the email has not been verified.`);
2677
+ console.error(`Verify your email first, then bootstrap:`);
2678
+ console.error(` intercom auth verify-email --url <url-from-verification-email>`);
2679
+ } else {
2680
+ console.error(`
2320
2681
  Your workspace exists but credentials were not saved.`);
2321
- console.error(`To complete setup, run:
2682
+ }
2683
+ console.error(`
2684
+ To complete setup, run:
2322
2685
  `);
2323
2686
  console.error(` INTERCOM_PASSWORD=*** intercom auth bootstrap --email ${opts.email} --app-id ${workspace.id}`);
2324
2687
  console.error(`
@@ -2352,10 +2715,246 @@ Then re-run setup without signup flags:
2352
2715
  });
2353
2716
  }
2354
2717
 
2718
+ // src/commands/completion.ts
2719
+ function binName() {
2720
+ const argv1 = process.argv[1] ?? "";
2721
+ if (argv1.endsWith("/fin") || argv1.endsWith("\\fin")) return "fin";
2722
+ return "intercom";
2723
+ }
2724
+ var COMMANDS = {
2725
+ api: [],
2726
+ auth: ["login", "logout", "status", "bootstrap", "signup", "verify-email"],
2727
+ me: [],
2728
+ articles: ["list", "get", "create", "update", "delete"],
2729
+ contacts: ["list", "get", "create", "update", "delete"],
2730
+ conversations: ["list", "get", "reply"],
2731
+ "help-center": ["list", "get", "create"],
2732
+ messenger: ["get", "update", "snippet"],
2733
+ fin: ["manifest", "enable", "guidance"],
2734
+ setup: [],
2735
+ pricing: [],
2736
+ completion: ["bash", "zsh", "fish"]
2737
+ };
2738
+ var GLOBAL_FLAGS = [
2739
+ "--json",
2740
+ "--jq",
2741
+ "--workspace",
2742
+ "--verbose",
2743
+ "--no-retry",
2744
+ "--timeout",
2745
+ "--agent",
2746
+ "--human",
2747
+ "--help",
2748
+ "--version"
2749
+ ];
2750
+ function bashScript(name = binName()) {
2751
+ const commandNames = Object.keys(COMMANDS).join(" ");
2752
+ const fnName = `_${name}`;
2753
+ const subcommandCases = Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd, subs]) => ` ${cmd}) COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- "$cur") ) ;;`).join("\n");
2754
+ return `# bash completion for ${name} CLI
2755
+ # Add to ~/.bashrc: eval "$(${name} completion bash)"
2756
+
2757
+ ${fnName}() {
2758
+ local cur prev commands
2759
+ COMPREPLY=()
2760
+ cur="\${COMP_WORDS[COMP_CWORD]}"
2761
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
2762
+ commands="${commandNames}"
2763
+
2764
+ if [[ "$cur" == -* ]]; then
2765
+ COMPREPLY=( $(compgen -W "${GLOBAL_FLAGS.join(" ")}" -- "$cur") )
2766
+ return 0
2767
+ fi
2768
+
2769
+ case "\${COMP_WORDS[1]}" in
2770
+ ${subcommandCases}
2771
+ *)
2772
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
2773
+ COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
2774
+ fi
2775
+ ;;
2776
+ esac
2777
+ return 0
2778
+ }
2779
+
2780
+ complete -F ${fnName} ${name}
2781
+ `;
2782
+ }
2783
+ function zshScript(name = binName()) {
2784
+ const fnName = `_${name}`;
2785
+ const commandDescs = [
2786
+ "api:'Make an authenticated API request'",
2787
+ "auth:'Manage authentication credentials'",
2788
+ "me:'Show current admin and workspace info'",
2789
+ "articles:'Manage help center articles'",
2790
+ "contacts:'Manage contacts'",
2791
+ "conversations:'Manage conversations'",
2792
+ "help-center:'Manage help center collections'",
2793
+ "messenger:'Manage Messenger settings'",
2794
+ "fin:'Manage Fin AI Agent'",
2795
+ "setup:'Set up a complete Intercom installation'",
2796
+ "pricing:'Show current Intercom pricing information'",
2797
+ "completion:'Generate shell completion scripts'"
2798
+ ];
2799
+ const subcommandFunctions = Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd, subs]) => {
2800
+ const subFnName = cmd.replace(/-/g, "_");
2801
+ const subLines = subs.map((s) => `'${s}'`).join(" ");
2802
+ return `${fnName}_${subFnName}() {
2803
+ local -a subcommands
2804
+ subcommands=(${subLines})
2805
+ _describe 'subcommand' subcommands
2806
+ }`;
2807
+ }).join("\n\n");
2808
+ return `#compdef ${name}
2809
+ # zsh completion for ${name} CLI
2810
+ # Add to ~/.zshrc: eval "$(${name} completion zsh)"
2811
+
2812
+ ${fnName}() {
2813
+ local -a commands global_flags
2814
+ commands=(
2815
+ ${commandDescs.join("\n ")}
2816
+ )
2817
+ global_flags=(
2818
+ ${GLOBAL_FLAGS.map((f) => `'${f}'`).join("\n ")}
2819
+ )
2820
+
2821
+ _arguments -C \\
2822
+ '1:command:->command' \\
2823
+ '*::arg:->args'
2824
+
2825
+ case $state in
2826
+ command)
2827
+ _describe 'command' commands
2828
+ ;;
2829
+ args)
2830
+ case $words[1] in
2831
+ ${Object.entries(COMMANDS).filter(([, subs]) => subs.length > 0).map(([cmd]) => ` ${cmd}) ${fnName}_${cmd.replace(/-/g, "_")} ;;`).join("\n")}
2832
+ esac
2833
+ ;;
2834
+ esac
2835
+ }
2836
+
2837
+ ${subcommandFunctions}
2838
+
2839
+ ${fnName} "$@"
2840
+ `;
2841
+ }
2842
+ function fishScript(name = binName()) {
2843
+ const lines = [
2844
+ `# fish completion for ${name} CLI`,
2845
+ `# Add to fish config: ${name} completion fish | source`,
2846
+ "",
2847
+ "# Disable file completions",
2848
+ `complete -c ${name} -f`,
2849
+ "",
2850
+ "# Top-level commands"
2851
+ ];
2852
+ const commandDescs = {
2853
+ api: "Make an authenticated API request",
2854
+ auth: "Manage authentication credentials",
2855
+ me: "Show current admin and workspace info",
2856
+ articles: "Manage help center articles",
2857
+ contacts: "Manage contacts",
2858
+ conversations: "Manage conversations",
2859
+ "help-center": "Manage help center collections",
2860
+ messenger: "Manage Messenger settings",
2861
+ fin: "Manage Fin AI Agent",
2862
+ setup: "Set up a complete Intercom installation",
2863
+ pricing: "Show current Intercom pricing information",
2864
+ completion: "Generate shell completion scripts"
2865
+ };
2866
+ for (const [cmd, desc] of Object.entries(commandDescs)) {
2867
+ lines.push(
2868
+ `complete -c ${name} -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'`
2869
+ );
2870
+ }
2871
+ lines.push("");
2872
+ lines.push("# Subcommands");
2873
+ for (const [cmd, subs] of Object.entries(COMMANDS)) {
2874
+ for (const sub of subs) {
2875
+ lines.push(
2876
+ `complete -c ${name} -n '__fish_seen_subcommand_from ${cmd}' -a '${sub}'`
2877
+ );
2878
+ }
2879
+ }
2880
+ lines.push("");
2881
+ lines.push("# Global flags");
2882
+ for (const flag of GLOBAL_FLAGS) {
2883
+ lines.push(`complete -c ${name} -l '${flag.replace(/^--/, "")}'`);
2884
+ }
2885
+ lines.push("");
2886
+ return lines.join("\n");
2887
+ }
2888
+ function registerCompletionCommand(program) {
2889
+ const name = binName();
2890
+ const completion = program.command("completion").description("Generate shell completion scripts").addHelpText(
2891
+ "after",
2892
+ `
2893
+ Examples:
2894
+ # Bash (add to ~/.bashrc)
2895
+ eval "$(${name} completion bash)"
2896
+
2897
+ # Zsh (add to ~/.zshrc)
2898
+ eval "$(${name} completion zsh)"
2899
+
2900
+ # Fish (add to fish config)
2901
+ ${name} completion fish | source`
2902
+ );
2903
+ completion.command("bash").description("Generate bash completion script").action(() => {
2904
+ console.log(bashScript());
2905
+ });
2906
+ completion.command("zsh").description("Generate zsh completion script").action(() => {
2907
+ console.log(zshScript());
2908
+ });
2909
+ completion.command("fish").description("Generate fish completion script").action(() => {
2910
+ console.log(fishScript());
2911
+ });
2912
+ }
2913
+
2914
+ // src/commands/pricing.ts
2915
+ var PRICING_URL = "https://www.intercom.com/pricing.md";
2916
+ function registerPricingCommand(program) {
2917
+ 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() {
2918
+ const opts = this.optsWithGlobals();
2919
+ const response = await fetchWithTimeout(
2920
+ PRICING_URL,
2921
+ { headers: { Accept: "text/markdown, text/plain, */*" } },
2922
+ { timeoutMs: API_TIMEOUT_MS, verbose: opts.verbose }
2923
+ );
2924
+ if (!response.ok) {
2925
+ throw new CLIError(`Failed to fetch pricing: ${response.status} ${response.statusText}`);
2926
+ }
2927
+ const markdown = await response.text();
2928
+ if (opts.json) {
2929
+ const { formatJson: formatJson2 } = await import("./json-TDEGGEUF.js");
2930
+ console.log(formatJson2({ url: PRICING_URL, content: markdown }));
2931
+ } else {
2932
+ console.log(markdown);
2933
+ }
2934
+ });
2935
+ }
2936
+
2355
2937
  // src/index.ts
2938
+ function detectBinaryName() {
2939
+ const argv1 = process.argv[1] ?? "";
2940
+ if (argv1.endsWith("/fin") || argv1.endsWith("\\fin")) return "fin";
2941
+ return "intercom";
2942
+ }
2943
+ function fullCommandName(cmd) {
2944
+ const parts = [];
2945
+ const rootNames = /* @__PURE__ */ new Set(["intercom", "fin"]);
2946
+ let current = cmd;
2947
+ while (current) {
2948
+ const name = current.name();
2949
+ if (name && !rootNames.has(name)) parts.unshift(name);
2950
+ current = current.parent;
2951
+ }
2952
+ return parts.join(" ");
2953
+ }
2356
2954
  function createProgram() {
2357
2955
  const program = new Command();
2358
- program.name("intercom").description("CLI tool for managing Intercom workspaces").version("0.1.0").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");
2956
+ 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");
2359
2958
  registerApiCommand(program);
2360
2959
  registerAuthCommand(program);
2361
2960
  registerMeCommand(program);
@@ -2366,10 +2965,19 @@ function createProgram() {
2366
2965
  registerMessengerCommand(program);
2367
2966
  registerFinCommand(program);
2368
2967
  registerSetupCommand(program);
2968
+ registerPricingCommand(program);
2969
+ registerCompletionCommand(program);
2970
+ program.hook("preAction", (thisCommand, actionCommand) => {
2971
+ const rootOpts = thisCommand.opts();
2972
+ if (rootOpts.agent && rootOpts.human) {
2973
+ throw new CLIError("--agent and --human are mutually exclusive");
2974
+ }
2975
+ setCliCommand(fullCommandName(actionCommand));
2976
+ });
2369
2977
  return program;
2370
2978
  }
2371
2979
  function handleError(err) {
2372
- const isJson = process.argv.includes("--json");
2980
+ const isJson = process.argv.includes("--json") || process.argv.includes("--agent");
2373
2981
  if (err instanceof CLIError) {
2374
2982
  if (isJson) {
2375
2983
  console.error(formatJson({
@@ -2387,7 +2995,7 @@ function handleError(err) {
2387
2995
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
2388
2996
  process.exit(1);
2389
2997
  }
2390
- var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("intercom");
2998
+ var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.endsWith("intercom") || process.argv[1]?.endsWith("fin");
2391
2999
  if (isMainModule) {
2392
3000
  const program = createProgram();
2393
3001
  try {