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