@intercom/cli 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.js +367 -11
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  View it from the CLI with `intercom changelog`.
9
9
 
10
+ ## [0.13.0] - 2026-09-01
11
+
12
+ ### Changed
13
+
14
+ - Workspaces created with `intercom auth signup` are now opted into the narrower `public-fin-cli` capability instead of the broad internal Fin SDK one. New workspaces get exactly the endpoints the CLI uses; existing workspaces are unaffected.
15
+
16
+ ## [0.12.0] - 2026-08-26
17
+
18
+ ### Added
19
+
20
+ - `intercom internal-articles` manages the private knowledge Fin reads: `list`, `get`, `create`, `update` and `delete`. Previously internal articles could only be reached through raw `intercom api` calls. Pass `--unstable` to include each article's `folder_id` in the output.
21
+ - `intercom internal-articles folders list` and `folders get <id>` read the internal article folder tree. Walk `parent_folder_id` to rebuild the hierarchy and match each article to a folder by its `folder_id`. These always send `Intercom-Version: Unstable`, because the folders API is not generally available yet.
22
+ - `intercom internal-articles folders articles <folder-id>` lists the articles in a folder and every folder below it.
23
+
10
24
  ## [0.11.0] - 2026-08-17
11
25
 
12
26
  ### Added
package/dist/index.js CHANGED
@@ -327,9 +327,9 @@ ${await value.text()}`);
327
327
  async postFormData(path, formData) {
328
328
  return this.request("POST", path, formData);
329
329
  }
330
- async paginate(path, params, dataKey = "data") {
330
+ async paginate(path, params, dataKey = "data", headers) {
331
331
  const allData = [];
332
- const firstResponse = await this.get(path, { ...params });
332
+ const firstResponse = await this.get(path, { ...params }, headers);
333
333
  const firstData = firstResponse[dataKey];
334
334
  if (firstData) allData.push(...firstData);
335
335
  const pages = firstResponse.pages;
@@ -337,10 +337,11 @@ ${await value.text()}`);
337
337
  if (startingAfter) {
338
338
  let cursor = startingAfter;
339
339
  while (cursor) {
340
- const response = await this.get(path, {
341
- ...params,
342
- starting_after: cursor
343
- });
340
+ const response = await this.get(
341
+ path,
342
+ { ...params, starting_after: cursor },
343
+ headers
344
+ );
344
345
  const data = response[dataKey];
345
346
  if (data) allData.push(...data);
346
347
  const p = response.pages;
@@ -350,7 +351,11 @@ ${await value.text()}`);
350
351
  }
351
352
  const totalPages = pages?.total_pages ?? 1;
352
353
  for (let page = 2; page <= totalPages; page++) {
353
- const response = await this.get(path, { ...params, page: String(page) });
354
+ const response = await this.get(
355
+ path,
356
+ { ...params, page: String(page) },
357
+ headers
358
+ );
354
359
  const data = response[dataKey];
355
360
  if (data) allData.push(...data);
356
361
  }
@@ -959,7 +964,7 @@ async function signup(options) {
959
964
  email_submission: {},
960
965
  sign_up_locale: "en",
961
966
  new_app_feature_flags: [
962
- "fin-sdk",
967
+ "public-fin-cli",
963
968
  "messenger-public-api"
964
969
  ]
965
970
  };
@@ -1790,6 +1795,356 @@ Use -F for typed values (numbers, booleans) and @file reads.`
1790
1795
  });
1791
1796
  }
1792
1797
 
1798
+ // src/commands/internal-articles.ts
1799
+ var UNSTABLE_HEADERS = { "Intercom-Version": "Unstable" };
1800
+ var BODY_KEY = "internal_article";
1801
+ var INTEGER_FIELDS = ["owner_id", "author_id", "folder_id"];
1802
+ function stripBody2(items) {
1803
+ return items.map(({ body: _body, json_blocks: _blocks, ...rest }) => rest);
1804
+ }
1805
+ function headersFor(opts) {
1806
+ return opts.unstable ? UNSTABLE_HEADERS : void 0;
1807
+ }
1808
+ function buildBody(opts) {
1809
+ const attrs = {
1810
+ ...parseFieldArgs(opts.field ?? []),
1811
+ ...parseTypedFields(opts.typedField ?? [], true)
1812
+ };
1813
+ for (const key of INTEGER_FIELDS) {
1814
+ if (typeof attrs[key] === "string") {
1815
+ const parsed = parseInt(attrs[key], 10);
1816
+ if (!isNaN(parsed)) attrs[key] = parsed;
1817
+ }
1818
+ }
1819
+ return { [BODY_KEY]: attrs };
1820
+ }
1821
+ function articleRows(items) {
1822
+ return items.map((a) => ({
1823
+ ID: String(a.id ?? ""),
1824
+ TITLE: String(a.title ?? ""),
1825
+ FOLDER: a.folder_id != null ? String(a.folder_id) : "",
1826
+ LOCALE: String(a.locale ?? ""),
1827
+ UPDATED: formatTimestamp(a.updated_at)
1828
+ }));
1829
+ }
1830
+ var ARTICLE_COLUMNS = ["ID", "TITLE", "FOLDER", "LOCALE", "UPDATED"];
1831
+ function articleDetail(result) {
1832
+ const meta = detail({
1833
+ ID: String(result.id ?? ""),
1834
+ Title: String(result.title ?? ""),
1835
+ Locale: String(result.locale ?? ""),
1836
+ "Folder ID": result.folder_id != null ? String(result.folder_id) : void 0,
1837
+ "Owner ID": result.owner_id != null ? String(result.owner_id) : void 0,
1838
+ "Author ID": result.author_id != null ? String(result.author_id) : void 0,
1839
+ Updated: formatTimestamp(result.updated_at),
1840
+ Created: formatTimestamp(result.created_at)
1841
+ });
1842
+ const body = typeof result.body === "string" ? htmlToText(result.body) : "";
1843
+ return body ? `${meta}
1844
+
1845
+ ${body}` : meta;
1846
+ }
1847
+ function addCommonOptions(cmd) {
1848
+ return cmd.option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details");
1849
+ }
1850
+ function addFieldOptions(cmd) {
1851
+ return cmd.option(
1852
+ "-f, --field <key=value>",
1853
+ "Field to set (repeatable)",
1854
+ (v, acc) => [...acc, v],
1855
+ []
1856
+ ).option(
1857
+ "-F, --typed-field <key=value>",
1858
+ "Typed field: bools, numbers, @file reads (repeatable)",
1859
+ (v, acc) => [...acc, v],
1860
+ []
1861
+ );
1862
+ }
1863
+ function registerInternalArticlesCommand(program) {
1864
+ const internalArticles = program.command("internal-articles").description(
1865
+ "Manage internal articles (list, get, create, update, delete, folders)"
1866
+ ).addHelpText(
1867
+ "after",
1868
+ `
1869
+ Internal articles are the private knowledge Fin reads but end users never see.
1870
+ They are separate from "intercom articles", which manages public help center content.
1871
+
1872
+ Examples:
1873
+ intercom internal-articles list
1874
+ intercom internal-articles list --unstable # include folder_id
1875
+ intercom internal-articles get 123
1876
+ intercom internal-articles create -f title='Refunds' -f owner_id=1 -f author_id=1 -F body_markdown=@refunds.md
1877
+ intercom internal-articles update 123 -f title='Refunds (2026)'
1878
+ intercom internal-articles delete 123
1879
+ intercom internal-articles folders list # folder hierarchy`
1880
+ );
1881
+ addCommonOptions(
1882
+ internalArticles.command("list").description("List internal articles").option("--limit <n>", "Max results to return", "25").option("--all", "Auto-paginate all pages").option(
1883
+ "--body",
1884
+ "Include the body and json_blocks fields (omitted by default to keep output small)"
1885
+ ).option(
1886
+ "--unstable",
1887
+ "Send Intercom-Version: Unstable, which adds folder_id to each article"
1888
+ )
1889
+ ).addHelpText(
1890
+ "after",
1891
+ `
1892
+ Examples:
1893
+ $ intercom internal-articles list --json
1894
+ $ intercom internal-articles list --all --jq '.[].title'
1895
+ $ intercom internal-articles list --unstable --json # include folder_id
1896
+ $ intercom internal-articles list --body --json # include full body`
1897
+ ).action(async function() {
1898
+ const opts = this.optsWithGlobals();
1899
+ const client = await resolveClient(opts);
1900
+ const headers = headersFor(opts);
1901
+ let result;
1902
+ if (opts.all) {
1903
+ const allItems = await client.paginate(
1904
+ "/internal_articles",
1905
+ { per_page: "50" },
1906
+ "data",
1907
+ headers
1908
+ );
1909
+ result = {
1910
+ type: "list",
1911
+ data: opts.body ? allItems : stripBody2(allItems)
1912
+ };
1913
+ } else {
1914
+ const raw = await client.get(
1915
+ "/internal_articles",
1916
+ { per_page: opts.limit ?? "25" },
1917
+ headers
1918
+ );
1919
+ if (!opts.body && Array.isArray(raw.data)) {
1920
+ raw.data = stripBody2(raw.data);
1921
+ }
1922
+ result = raw;
1923
+ }
1924
+ const items = result.data ?? [];
1925
+ render(
1926
+ result,
1927
+ opts,
1928
+ () => table({ columns: ARTICLE_COLUMNS, rows: articleRows(items) }),
1929
+ items
1930
+ );
1931
+ });
1932
+ addCommonOptions(
1933
+ internalArticles.command("get <id>").description("Get an internal article by ID").option(
1934
+ "--unstable",
1935
+ "Send Intercom-Version: Unstable, which adds folder_id to the article"
1936
+ )
1937
+ ).action(async function(id) {
1938
+ const opts = this.optsWithGlobals();
1939
+ const client = await resolveClient(opts);
1940
+ const result = await client.get(
1941
+ `/internal_articles/${id}`,
1942
+ void 0,
1943
+ headersFor(opts)
1944
+ );
1945
+ render(result, opts, () => articleDetail(result));
1946
+ });
1947
+ addCommonOptions(
1948
+ addFieldOptions(
1949
+ internalArticles.command("create").description("Create an internal article").option(
1950
+ "--unstable",
1951
+ "Send Intercom-Version: Unstable, needed to set folder_id"
1952
+ )
1953
+ )
1954
+ ).addHelpText(
1955
+ "after",
1956
+ `
1957
+ Examples:
1958
+ $ intercom internal-articles create -f title='Refund policy' -f owner_id=1 -f author_id=1 -F body_markdown=@refunds.md
1959
+ $ intercom internal-articles create -f title='Escalations' -f owner_id=1 -f author_id=1 -f folder_id=7 --unstable
1960
+
1961
+ Required fields: title, owner_id, author_id
1962
+ Content: pass body (HTML) or body_markdown (Markdown), not both.
1963
+ Use -F for typed values (numbers, booleans) and @file reads.`
1964
+ ).action(async function() {
1965
+ const opts = this.optsWithGlobals();
1966
+ const client = await resolveClient(opts);
1967
+ const result = await client.post(
1968
+ "/internal_articles",
1969
+ buildBody(opts),
1970
+ headersFor(opts)
1971
+ );
1972
+ render(
1973
+ result,
1974
+ opts,
1975
+ () => articleDetail(result)
1976
+ );
1977
+ });
1978
+ addCommonOptions(
1979
+ addFieldOptions(
1980
+ internalArticles.command("update <id>").description("Update an internal article").option(
1981
+ "--unstable",
1982
+ "Send Intercom-Version: Unstable, needed to set folder_id"
1983
+ )
1984
+ )
1985
+ ).addHelpText(
1986
+ "after",
1987
+ `
1988
+ Examples:
1989
+ $ intercom internal-articles update 123 -f title='Refund policy (2026)'
1990
+ $ intercom internal-articles update 123 -F body_markdown=@refunds.md
1991
+ $ intercom internal-articles update 123 -f folder_id=7 --unstable`
1992
+ ).action(async function(id) {
1993
+ const opts = this.optsWithGlobals();
1994
+ const client = await resolveClient(opts);
1995
+ const result = await client.put(
1996
+ `/internal_articles/${id}`,
1997
+ buildBody(opts),
1998
+ headersFor(opts)
1999
+ );
2000
+ render(
2001
+ result,
2002
+ opts,
2003
+ () => articleDetail(result)
2004
+ );
2005
+ });
2006
+ addCommonOptions(
2007
+ internalArticles.command("delete <id>").description("Delete an internal article")
2008
+ ).action(async function(id) {
2009
+ const opts = this.optsWithGlobals();
2010
+ const client = await resolveClient(opts);
2011
+ const result = await client.delete(`/internal_articles/${id}`);
2012
+ const deleted = result;
2013
+ render(result, opts, () => `Deleted internal article ${deleted.id ?? id}`);
2014
+ });
2015
+ const folders = internalArticles.command("folders").description("List and inspect internal article folders").addHelpText(
2016
+ "after",
2017
+ `
2018
+ Folders are served only on the Unstable API version until the folders API is
2019
+ released, so these subcommands always send Intercom-Version: Unstable.
2020
+
2021
+ Reconstruct the hierarchy by walking parent_folder_id, then match articles to
2022
+ folders with the folder_id from "internal-articles list --unstable".
2023
+
2024
+ Examples:
2025
+ intercom internal-articles folders list
2026
+ intercom internal-articles folders list --parent-folder-id 7
2027
+ intercom internal-articles folders get 7
2028
+ intercom internal-articles folders articles 7`
2029
+ );
2030
+ addCommonOptions(
2031
+ folders.command("list").description("List internal article folders").option("--limit <n>", "Max results to return", "25").option("--all", "Auto-paginate all pages").option(
2032
+ "--parent-folder-id <id>",
2033
+ "Only return folders directly inside this folder"
2034
+ )
2035
+ ).action(async function() {
2036
+ const opts = this.optsWithGlobals();
2037
+ const client = await resolveClient(opts);
2038
+ const params = {};
2039
+ if (opts.parentFolderId) params.parent_folder_id = opts.parentFolderId;
2040
+ let result;
2041
+ if (opts.all) {
2042
+ const allItems = await client.paginate(
2043
+ "/folders",
2044
+ { ...params, per_page: "50" },
2045
+ "data",
2046
+ UNSTABLE_HEADERS
2047
+ );
2048
+ result = { type: "list", data: allItems };
2049
+ } else {
2050
+ result = await client.get(
2051
+ "/folders",
2052
+ { ...params, per_page: opts.limit ?? "25" },
2053
+ UNSTABLE_HEADERS
2054
+ );
2055
+ }
2056
+ const items = result.data ?? [];
2057
+ render(
2058
+ result,
2059
+ opts,
2060
+ () => {
2061
+ if (items.length === 0) return "No folders found.";
2062
+ const rows = items.map((f) => ({
2063
+ ID: String(f.id ?? ""),
2064
+ NAME: String(f.name ?? ""),
2065
+ PARENT: f.parent_folder_id != null ? String(f.parent_folder_id) : "",
2066
+ ITEMS: f.content_count != null ? String(f.content_count) : ""
2067
+ }));
2068
+ return table({ columns: ["ID", "NAME", "PARENT", "ITEMS"], rows });
2069
+ },
2070
+ items
2071
+ );
2072
+ });
2073
+ addCommonOptions(
2074
+ folders.command("get <id>").description("Get an internal article folder by ID")
2075
+ ).action(async function(id) {
2076
+ const opts = this.optsWithGlobals();
2077
+ const client = await resolveClient(opts);
2078
+ const result = await client.get(`/folders/${id}`, void 0, UNSTABLE_HEADERS);
2079
+ render(
2080
+ result,
2081
+ opts,
2082
+ () => detail({
2083
+ ID: String(result.id ?? ""),
2084
+ Name: String(result.name ?? ""),
2085
+ Description: result.description != null ? String(result.description) : void 0,
2086
+ Emoji: result.emoji != null ? String(result.emoji) : void 0,
2087
+ "Parent folder ID": result.parent_folder_id != null ? String(result.parent_folder_id) : void 0,
2088
+ Items: result.content_count != null ? String(result.content_count) : void 0,
2089
+ Updated: formatTimestamp(result.updated_at),
2090
+ Created: formatTimestamp(result.created_at)
2091
+ })
2092
+ );
2093
+ });
2094
+ addCommonOptions(
2095
+ folders.command("articles <folder-id>").description("List the articles in a folder and everything below it").option("--limit <n>", "Max results to return", "25").option("--all", "Auto-paginate all pages").option(
2096
+ "--body",
2097
+ "Include the body and json_blocks fields (omitted by default to keep output small)"
2098
+ )
2099
+ ).addHelpText(
2100
+ "after",
2101
+ `
2102
+ This walks the folder tree, so it returns the articles in every descendant
2103
+ folder too, not just the ones directly inside.
2104
+
2105
+ Examples:
2106
+ $ intercom internal-articles folders articles 7
2107
+ $ intercom internal-articles folders articles 7 --all --jq '.[].title'`
2108
+ ).action(async function(folderId) {
2109
+ const opts = this.optsWithGlobals();
2110
+ const client = await resolveClient(opts);
2111
+ const params = { folder_id: folderId };
2112
+ let result;
2113
+ if (opts.all) {
2114
+ const allItems = await client.paginate(
2115
+ "/internal_articles/search",
2116
+ { ...params, per_page: "50" },
2117
+ "internal_articles",
2118
+ UNSTABLE_HEADERS
2119
+ );
2120
+ result = {
2121
+ type: "list",
2122
+ internal_articles: opts.body ? allItems : stripBody2(allItems)
2123
+ };
2124
+ } else {
2125
+ const raw = await client.get(
2126
+ "/internal_articles/search",
2127
+ { ...params, per_page: opts.limit ?? "25" },
2128
+ UNSTABLE_HEADERS
2129
+ );
2130
+ if (!opts.body && Array.isArray(raw.internal_articles)) {
2131
+ raw.internal_articles = stripBody2(raw.internal_articles);
2132
+ }
2133
+ result = raw;
2134
+ }
2135
+ const items = result.internal_articles ?? [];
2136
+ render(
2137
+ result,
2138
+ opts,
2139
+ () => {
2140
+ if (items.length === 0) return "No internal articles found.";
2141
+ return table({ columns: ARTICLE_COLUMNS, rows: articleRows(items) });
2142
+ },
2143
+ items
2144
+ );
2145
+ });
2146
+ }
2147
+
1793
2148
  // src/commands/contacts.ts
1794
2149
  function registerContactsCommand(program) {
1795
2150
  const contacts = program.command("contacts").description("Manage contacts (list, get, create, update, delete, search)").addHelpText(
@@ -2258,7 +2613,7 @@ Examples:
2258
2613
  }
2259
2614
 
2260
2615
  // src/commands/messenger.ts
2261
- var UNSTABLE_HEADERS = { "Intercom-Version": "Unstable" };
2616
+ var UNSTABLE_HEADERS2 = { "Intercom-Version": "Unstable" };
2262
2617
  var WIDGET_LOADER = (appId) => `(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/${appId}';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();`;
2263
2618
  function generateHtmlJwtSnippet(appId) {
2264
2619
  return `<!--
@@ -2491,7 +2846,7 @@ Examples:
2491
2846
  ).action(async function() {
2492
2847
  const opts = this.optsWithGlobals();
2493
2848
  const client = await resolveClient(opts);
2494
- const result = await client.get("/messenger", void 0, UNSTABLE_HEADERS);
2849
+ const result = await client.get("/messenger", void 0, UNSTABLE_HEADERS2);
2495
2850
  const r = result;
2496
2851
  render(
2497
2852
  result,
@@ -2518,7 +2873,7 @@ Examples:
2518
2873
  const opts = this.optsWithGlobals();
2519
2874
  const client = await resolveClient(opts);
2520
2875
  const body = parseFieldArgs(opts.field ?? []);
2521
- const result = await client.put("/messenger", body, UNSTABLE_HEADERS);
2876
+ const result = await client.put("/messenger", body, UNSTABLE_HEADERS2);
2522
2877
  const r = result;
2523
2878
  render(
2524
2879
  result,
@@ -4821,6 +5176,7 @@ Environment: Set INTERCOM_TOKEN to skip login entirely.`;
4821
5176
  registerAuthCommand(program);
4822
5177
  registerMeCommand(program);
4823
5178
  registerArticlesCommand(program);
5179
+ registerInternalArticlesCommand(program);
4824
5180
  registerContactsCommand(program);
4825
5181
  registerConversationsCommand(program);
4826
5182
  registerHelpCenterCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intercom/cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI tool for managing Intercom workspaces",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",