@intercom/cli 0.14.1 → 0.15.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/CHANGELOG.md +14 -0
- package/dist/index.js +135 -49
- 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.15.0] - 2026-11-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `intercom fin workflows list`, `get` and `upload` now return a `web_url` that opens the workflow in the visual builder. The builder URL uses a different id from the workflow's `id`, so use this field instead of building the URL yourself.
|
|
15
|
+
- `intercom articles list`, `search`, `get`, `create` and `update` add a `web_url` that opens each article in the Intercom editor, in the workspace's own region.
|
|
16
|
+
- `intercom fin workflows upload --note <text>` labels the saved version, as `fin procedures upload --note` already does. The note shows in `intercom fin workflows versions <id>`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `intercom fin workflows upload --verify` no longer reports drift on `web_url` or `workflow_instance_id`. The server assigns those itself, so a file copied from another workflow no longer fails verification.
|
|
21
|
+
- `intercom fin workflows get <id> --version <version_id>` and `intercom fin procedures get <id> --version <version_id>` now download that version. Before, a space between `--version` and the id printed the CLI's own version instead; only `--version=<version_id>` worked.
|
|
22
|
+
- `intercom fin workflows upload --verify` no longer reports drift when an uploaded audio file, such as a phone greeting or hold music, comes back with a fresh download signature. The link still points at the same file.
|
|
23
|
+
|
|
10
24
|
## [0.14.1] - 2026-10-27
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,51 @@ async function fetchWithTimeout(url, init, options) {
|
|
|
59
59
|
return response;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// src/api/regions.ts
|
|
63
|
+
var REGION_BASE_URLS = {
|
|
64
|
+
us: "https://api.intercom.io",
|
|
65
|
+
eu: "https://api.eu.intercom.io",
|
|
66
|
+
au: "https://api.au.intercom.io"
|
|
67
|
+
};
|
|
68
|
+
var APP_BASE_URLS = {
|
|
69
|
+
[REGION_BASE_URLS.us]: "https://app.intercom.com",
|
|
70
|
+
[REGION_BASE_URLS.eu]: "https://app.eu.intercom.com",
|
|
71
|
+
[REGION_BASE_URLS.au]: "https://app.au.intercom.com"
|
|
72
|
+
};
|
|
73
|
+
function appBaseUrlFor(apiBaseUrl) {
|
|
74
|
+
return process.env.INTERCOM_APP_BASE_URL || APP_BASE_URLS[apiBaseUrl.replace(/\/+$/, "")];
|
|
75
|
+
}
|
|
76
|
+
function regionBaseUrl(region) {
|
|
77
|
+
if (!region) return void 0;
|
|
78
|
+
return REGION_BASE_URLS[region];
|
|
79
|
+
}
|
|
80
|
+
function normalizeRegion(value) {
|
|
81
|
+
if (typeof value !== "string") return void 0;
|
|
82
|
+
switch (value.trim().toLowerCase()) {
|
|
83
|
+
case "us":
|
|
84
|
+
case "usa":
|
|
85
|
+
case "united states":
|
|
86
|
+
return "us";
|
|
87
|
+
case "eu":
|
|
88
|
+
case "europe":
|
|
89
|
+
return "eu";
|
|
90
|
+
case "au":
|
|
91
|
+
case "australia":
|
|
92
|
+
return "au";
|
|
93
|
+
default:
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function detectRegion(me) {
|
|
98
|
+
if (!me || typeof me !== "object") return void 0;
|
|
99
|
+
const m = me;
|
|
100
|
+
const app = m.app;
|
|
101
|
+
return normalizeRegion(app?.region ?? m.region);
|
|
102
|
+
}
|
|
103
|
+
function resolveBaseUrl(region) {
|
|
104
|
+
return process.env.INTERCOM_API_BASE_URL || regionBaseUrl(region) || void 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
62
107
|
// src/api/client.ts
|
|
63
108
|
var DEFAULT_BASE_URL = "https://api.intercom.io";
|
|
64
109
|
var MAX_RAW_ERROR_LEN = 500;
|
|
@@ -104,6 +149,9 @@ var IntercomClient = class _IntercomClient {
|
|
|
104
149
|
this.verbose = options.verbose ?? false;
|
|
105
150
|
this.timeoutMs = options.timeoutMs ?? API_TIMEOUT_MS;
|
|
106
151
|
}
|
|
152
|
+
get appBaseUrl() {
|
|
153
|
+
return appBaseUrlFor(this.baseUrl);
|
|
154
|
+
}
|
|
107
155
|
authHeader() {
|
|
108
156
|
if (this.authType === "api_key") {
|
|
109
157
|
return `Basic ${Buffer.from(`${this.appId}:${this.token}`).toString("base64")}`;
|
|
@@ -363,43 +411,6 @@ ${await value.text()}`);
|
|
|
363
411
|
}
|
|
364
412
|
};
|
|
365
413
|
|
|
366
|
-
// src/api/regions.ts
|
|
367
|
-
var REGION_BASE_URLS = {
|
|
368
|
-
us: "https://api.intercom.io",
|
|
369
|
-
eu: "https://api.eu.intercom.io",
|
|
370
|
-
au: "https://api.au.intercom.io"
|
|
371
|
-
};
|
|
372
|
-
function regionBaseUrl(region) {
|
|
373
|
-
if (!region) return void 0;
|
|
374
|
-
return REGION_BASE_URLS[region];
|
|
375
|
-
}
|
|
376
|
-
function normalizeRegion(value) {
|
|
377
|
-
if (typeof value !== "string") return void 0;
|
|
378
|
-
switch (value.trim().toLowerCase()) {
|
|
379
|
-
case "us":
|
|
380
|
-
case "usa":
|
|
381
|
-
case "united states":
|
|
382
|
-
return "us";
|
|
383
|
-
case "eu":
|
|
384
|
-
case "europe":
|
|
385
|
-
return "eu";
|
|
386
|
-
case "au":
|
|
387
|
-
case "australia":
|
|
388
|
-
return "au";
|
|
389
|
-
default:
|
|
390
|
-
return void 0;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
function detectRegion(me) {
|
|
394
|
-
if (!me || typeof me !== "object") return void 0;
|
|
395
|
-
const m = me;
|
|
396
|
-
const app = m.app;
|
|
397
|
-
return normalizeRegion(app?.region ?? m.region);
|
|
398
|
-
}
|
|
399
|
-
function resolveBaseUrl(region) {
|
|
400
|
-
return process.env.INTERCOM_API_BASE_URL || regionBaseUrl(region) || void 0;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
414
|
// src/auth/store.ts
|
|
404
415
|
import { readFileSync, mkdirSync } from "fs";
|
|
405
416
|
|
|
@@ -1604,6 +1615,13 @@ function htmlToText(html) {
|
|
|
1604
1615
|
function stripBody(items) {
|
|
1605
1616
|
return items.map(({ body: _body, ...rest }) => rest);
|
|
1606
1617
|
}
|
|
1618
|
+
function withWebUrl(article, appBaseUrl) {
|
|
1619
|
+
if (!appBaseUrl || article.id == null || article.workspace_id == null) return article;
|
|
1620
|
+
return {
|
|
1621
|
+
...article,
|
|
1622
|
+
web_url: `${appBaseUrl}/a/apps/${String(article.workspace_id)}/knowledge-hub/article/${String(article.id)}`
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1607
1625
|
function registerArticlesCommand(program) {
|
|
1608
1626
|
const articles = program.command("articles").description("Manage help center articles (list, get, create, update, delete, search)").addHelpText(
|
|
1609
1627
|
"after",
|
|
@@ -1645,6 +1663,7 @@ Examples:
|
|
|
1645
1663
|
}
|
|
1646
1664
|
result = raw;
|
|
1647
1665
|
}
|
|
1666
|
+
result.data = result.data?.map((a) => withWebUrl(a, client.appBaseUrl));
|
|
1648
1667
|
const items = result.data ?? [];
|
|
1649
1668
|
render(
|
|
1650
1669
|
result,
|
|
@@ -1688,6 +1707,11 @@ Examples:
|
|
|
1688
1707
|
data.articles = stripBody(data.articles);
|
|
1689
1708
|
}
|
|
1690
1709
|
}
|
|
1710
|
+
if (Array.isArray(result.data)) {
|
|
1711
|
+
result.data = result.data.map((a) => withWebUrl(a, client.appBaseUrl));
|
|
1712
|
+
} else if (result.data && Array.isArray(result.data.articles)) {
|
|
1713
|
+
result.data.articles = result.data.articles.map((a) => withWebUrl(a, client.appBaseUrl));
|
|
1714
|
+
}
|
|
1691
1715
|
const dataAfter = result.data;
|
|
1692
1716
|
const items = Array.isArray(dataAfter) ? dataAfter : dataAfter?.articles ?? [];
|
|
1693
1717
|
render(
|
|
@@ -1709,12 +1733,13 @@ Examples:
|
|
|
1709
1733
|
articles.command("get <id>").description("Get a help center article 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) {
|
|
1710
1734
|
const opts = this.optsWithGlobals();
|
|
1711
1735
|
const client = await resolveClient(opts);
|
|
1712
|
-
const result = await client.get(`/articles/${id}`);
|
|
1736
|
+
const result = withWebUrl(await client.get(`/articles/${id}`), client.appBaseUrl);
|
|
1713
1737
|
render(result, opts, () => {
|
|
1714
1738
|
const meta = detail({
|
|
1715
1739
|
ID: String(result.id ?? ""),
|
|
1716
1740
|
Title: String(result.title ?? ""),
|
|
1717
1741
|
State: String(result.state ?? ""),
|
|
1742
|
+
"Web URL": result.web_url != null ? String(result.web_url) : void 0,
|
|
1718
1743
|
"Author ID": result.author_id != null ? String(result.author_id) : void 0,
|
|
1719
1744
|
Updated: formatTimestamp(result.updated_at),
|
|
1720
1745
|
Created: formatTimestamp(result.created_at)
|
|
@@ -1755,14 +1780,15 @@ Use -F for typed values (numbers, booleans) and @file reads.`
|
|
|
1755
1780
|
const parsed = parseInt(body.author_id, 10);
|
|
1756
1781
|
if (!isNaN(parsed)) body.author_id = parsed;
|
|
1757
1782
|
}
|
|
1758
|
-
const result = await client.post("/articles", body);
|
|
1783
|
+
const result = withWebUrl(await client.post("/articles", body), client.appBaseUrl);
|
|
1759
1784
|
render(
|
|
1760
1785
|
result,
|
|
1761
1786
|
opts,
|
|
1762
1787
|
() => detail({
|
|
1763
1788
|
ID: String(result.id ?? ""),
|
|
1764
1789
|
Title: String(result.title ?? ""),
|
|
1765
|
-
State: String(result.state ?? "")
|
|
1790
|
+
State: String(result.state ?? ""),
|
|
1791
|
+
"Web URL": result.web_url != null ? String(result.web_url) : void 0
|
|
1766
1792
|
})
|
|
1767
1793
|
);
|
|
1768
1794
|
});
|
|
@@ -1787,14 +1813,15 @@ Use -F for typed values (numbers, booleans) and @file reads.`
|
|
|
1787
1813
|
const parsed = parseInt(body.author_id, 10);
|
|
1788
1814
|
if (!isNaN(parsed)) body.author_id = parsed;
|
|
1789
1815
|
}
|
|
1790
|
-
const result = await client.put(`/articles/${id}`, body);
|
|
1816
|
+
const result = withWebUrl(await client.put(`/articles/${id}`, body), client.appBaseUrl);
|
|
1791
1817
|
render(
|
|
1792
1818
|
result,
|
|
1793
1819
|
opts,
|
|
1794
1820
|
() => detail({
|
|
1795
1821
|
ID: String(result.id ?? ""),
|
|
1796
1822
|
Title: String(result.title ?? ""),
|
|
1797
|
-
State: String(result.state ?? "")
|
|
1823
|
+
State: String(result.state ?? ""),
|
|
1824
|
+
"Web URL": result.web_url != null ? String(result.web_url) : void 0
|
|
1798
1825
|
})
|
|
1799
1826
|
);
|
|
1800
1827
|
});
|
|
@@ -2966,7 +2993,36 @@ function scalarsMatch(sent, saved) {
|
|
|
2966
2993
|
if (typeof saved === "object") {
|
|
2967
2994
|
return false;
|
|
2968
2995
|
}
|
|
2969
|
-
|
|
2996
|
+
if (String(sent) === String(saved)) {
|
|
2997
|
+
return true;
|
|
2998
|
+
}
|
|
2999
|
+
const unsignedSent = withoutSignature(String(sent));
|
|
3000
|
+
return unsignedSent !== null && unsignedSent === withoutSignature(String(saved));
|
|
3001
|
+
}
|
|
3002
|
+
var SIGNATURE_PARAMS = ["expires", "signature", "req"];
|
|
3003
|
+
var UPLOAD_HOSTS = [
|
|
3004
|
+
/^downloads\.intercomcdn\.eu$/,
|
|
3005
|
+
/^downloads(\.[a-z]+)?\.intercomcdn\.com$/,
|
|
3006
|
+
/\.intercom-attachments(-[0-9]+)?\.(com|eu)$/,
|
|
3007
|
+
/^video-messages\.intercomcdn\.com$/,
|
|
3008
|
+
/\.intercom-mail\.(com|eu)$/
|
|
3009
|
+
];
|
|
3010
|
+
function withoutSignature(value) {
|
|
3011
|
+
if (!/^https?:\/\//i.test(value)) {
|
|
3012
|
+
return null;
|
|
3013
|
+
}
|
|
3014
|
+
try {
|
|
3015
|
+
const url = new URL(value);
|
|
3016
|
+
if (!UPLOAD_HOSTS.some((host) => host.test(url.hostname))) {
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
for (const param of SIGNATURE_PARAMS) {
|
|
3020
|
+
url.searchParams.delete(param);
|
|
3021
|
+
}
|
|
3022
|
+
return url.toString();
|
|
3023
|
+
} catch {
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
2970
3026
|
}
|
|
2971
3027
|
function findDrift(sent, saved, path = "") {
|
|
2972
3028
|
const here = path || ".";
|
|
@@ -3532,7 +3588,16 @@ function ensureWorkflowType(raw) {
|
|
|
3532
3588
|
}
|
|
3533
3589
|
function registerWorkflowsCommand(fin) {
|
|
3534
3590
|
const workflows = fin.command("workflows").description("Manage Fin SDK workflows");
|
|
3535
|
-
workflows.command("list").description("List Fin SDK workflows").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").
|
|
3591
|
+
workflows.command("list").description("List Fin SDK workflows").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
3592
|
+
"after",
|
|
3593
|
+
`
|
|
3594
|
+
Examples:
|
|
3595
|
+
$ intercom fin workflows list
|
|
3596
|
+
$ intercom fin workflows list --jq '.web_url'
|
|
3597
|
+
|
|
3598
|
+
Each workflow's web_url opens it in the visual builder. Use it rather than
|
|
3599
|
+
building a URL from id: the builder uses a different id.`
|
|
3600
|
+
).action(async function() {
|
|
3536
3601
|
const opts = this.optsWithGlobals();
|
|
3537
3602
|
const client = await resolveClient(opts);
|
|
3538
3603
|
const result = await client.get("/fin-sdk/workflows");
|
|
@@ -3561,7 +3626,8 @@ Examples:
|
|
|
3561
3626
|
$ intercom fin workflows get 42 --version 4821 # print that version's YAML
|
|
3562
3627
|
|
|
3563
3628
|
The YAML it prints is ready to edit and re-upload with
|
|
3564
|
-
\`intercom fin workflows upload\`.
|
|
3629
|
+
\`intercom fin workflows upload\`. Its web_url field links to the workflow in
|
|
3630
|
+
the visual builder, whose URL uses a different id from this one.
|
|
3565
3631
|
|
|
3566
3632
|
--version downloads the re-uploadable YAML of a historical version, so you can
|
|
3567
3633
|
diff a local file against what is live (\`intercom fin workflows versions <id>\`
|
|
@@ -3582,19 +3648,22 @@ shows which version that is).`
|
|
|
3582
3648
|
console.log(text.replace(/\n+$/, ""));
|
|
3583
3649
|
}
|
|
3584
3650
|
});
|
|
3585
|
-
workflows.command("upload <file>").description('Upload a workflow YAML file (creates if id is "new", updates if numeric)').option("--verify", "Fail if the saved workflow differs from the file uploaded").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
3651
|
+
workflows.command("upload <file>").description('Upload a workflow YAML file (creates if id is "new", updates if numeric)').option("--note <text>", "Version note to record against this save").option("--verify", "Fail if the saved workflow differs from the file uploaded").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
3586
3652
|
"after",
|
|
3587
3653
|
`
|
|
3588
3654
|
Examples:
|
|
3589
3655
|
$ intercom fin workflows upload ./welcome.yaml
|
|
3590
3656
|
$ intercom fin workflows get wf-42 > local.yaml; vim local.yaml; intercom fin workflows upload local.yaml
|
|
3591
|
-
$ intercom fin workflows upload ./new.yaml --json | jq .
|
|
3657
|
+
$ intercom fin workflows upload ./new.yaml --json | jq .web_url
|
|
3592
3658
|
$ intercom fin workflows upload ./welcome.yaml --verify
|
|
3659
|
+
$ intercom fin workflows upload ./welcome.yaml --note "added VIP routing"
|
|
3593
3660
|
|
|
3594
3661
|
The YAML file's id field determines whether this creates or updates:
|
|
3595
3662
|
id: "new" \u2192 creates a new workflow
|
|
3596
3663
|
id: <int> \u2192 updates the existing workflow with that id
|
|
3597
3664
|
|
|
3665
|
+
--note labels this save in \`intercom fin workflows versions <id>\`.
|
|
3666
|
+
|
|
3598
3667
|
--verify compares the workflow the server saved against the file just uploaded
|
|
3599
3668
|
and exits non-zero listing any field that did not survive the round-trip. Fields
|
|
3600
3669
|
the server adds (ids, defaults) are ignored; only content that was sent and came
|
|
@@ -3607,6 +3676,9 @@ back missing or changed is reported.`
|
|
|
3607
3676
|
const formData = new FormData();
|
|
3608
3677
|
const blob = new Blob([yamlBody], { type: "application/x-yaml" });
|
|
3609
3678
|
formData.append("file_data", blob, basename3(filePath));
|
|
3679
|
+
if (opts.note) {
|
|
3680
|
+
formData.append("version_note", opts.note);
|
|
3681
|
+
}
|
|
3610
3682
|
const result = await client.postFormData("/fin-sdk/upload", formData);
|
|
3611
3683
|
render(
|
|
3612
3684
|
result,
|
|
@@ -3615,7 +3687,7 @@ back missing or changed is reported.`
|
|
|
3615
3687
|
"ID": String(result.id ?? ""),
|
|
3616
3688
|
"Type": result.type != null ? String(result.type) : void 0,
|
|
3617
3689
|
"Message": result.message != null ? String(result.message) : void 0,
|
|
3618
|
-
"
|
|
3690
|
+
"Web URL": result.web_url != null || result.visual_builder_url != null ? String(result.web_url ?? result.visual_builder_url) : void 0
|
|
3619
3691
|
})
|
|
3620
3692
|
);
|
|
3621
3693
|
if (opts.verify && !isDryRun()) {
|
|
@@ -3743,6 +3815,8 @@ function withoutExpectedChanges(sent) {
|
|
|
3743
3815
|
const rest = { ...sent };
|
|
3744
3816
|
delete rest.type;
|
|
3745
3817
|
delete rest.version;
|
|
3818
|
+
delete rest.web_url;
|
|
3819
|
+
delete rest.workflow_instance_id;
|
|
3746
3820
|
if (String(rest.id ?? "").toLowerCase() === "new") {
|
|
3747
3821
|
delete rest.id;
|
|
3748
3822
|
}
|
|
@@ -5193,6 +5267,18 @@ Examples:
|
|
|
5193
5267
|
});
|
|
5194
5268
|
}
|
|
5195
5269
|
|
|
5270
|
+
// src/argv.ts
|
|
5271
|
+
function attachVersionValues(argv) {
|
|
5272
|
+
const out = [...argv];
|
|
5273
|
+
for (let i = 3; i < out.length - 1; i++) {
|
|
5274
|
+
if (out[i] === "--") break;
|
|
5275
|
+
if (out[i] === "--version" && !out[i + 1].startsWith("-")) {
|
|
5276
|
+
out.splice(i, 2, `--version=${out[i + 1]}`);
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
return out;
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5196
5282
|
// src/index.ts
|
|
5197
5283
|
function detectBinaryName() {
|
|
5198
5284
|
const argv1 = process.argv[1] ?? "";
|
|
@@ -5274,7 +5360,7 @@ var isMainModule = process.argv[1]?.endsWith("index.js") || process.argv[1]?.end
|
|
|
5274
5360
|
if (isMainModule) {
|
|
5275
5361
|
const program = createProgram();
|
|
5276
5362
|
try {
|
|
5277
|
-
await program.parseAsync();
|
|
5363
|
+
await program.parseAsync(attachVersionValues(process.argv));
|
|
5278
5364
|
} catch (err) {
|
|
5279
5365
|
handleError(err);
|
|
5280
5366
|
}
|