@intercom/cli 0.9.0 → 0.11.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 +19 -0
- package/dist/index.js +313 -38
- package/package.json +1 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ 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.11.0] - 2026-08-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `fin workflows versions <id>` lists a workflow's server-side version history, showing which version is live, which is draft, and who saved it. `fin workflows get <id> --version <version_id>` downloads any of those versions as re-uploadable YAML, so a local file can be diffed against what Fin is actually running.
|
|
15
|
+
|
|
16
|
+
## [0.10.0] - 2026-08-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `fin procedures publish <id>` sets a procedure's state to live, and `fin procedures pause <id>` sets it to paused — a convenience for download, edit the state, and re-upload. Both accept `--note <text>` to label the save in `fin procedures versions <id>`. There is no `draft` verb: the upload endpoint rejects that state and directs you to `pause`.
|
|
21
|
+
- `fin procedures versions <id>` lists a procedure's server-side version history, showing which version is live, which is draft, and who saved it. `fin procedures get <id> --version <version_id>` downloads any of those versions as re-uploadable YAML, so a local file can be diffed against what Fin is actually running.
|
|
22
|
+
- `fin procedures upload <file> --note <text>` records a version note against the save, so the entry is identifiable in `fin procedures versions`. `--verify` compares what the server saved against the file you uploaded and exits non-zero listing any field that did not survive the round-trip (fields the server adds, like ids and defaults, are ignored) — useful when a setting silently resets on push.
|
|
23
|
+
- `fin attributes get <name>` shows a single Fin custom attribute by name. `fin attributes download <file>` exports all attributes to a YAML file in the shape `fin attributes upload` accepts, completing the export/edit/re-import round-trip (attributes previously supported only `list` and `upload`).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `--dry-run` (`-n`) now prints the actual file payload for upload commands (`fin attributes upload`, `fin procedures upload`, `fin simulations upload`, `fin workflows upload`) instead of an opaque `[FormData]`. You can now preview the exact YAML that would be sent — including the routing `type:` the CLI adds for you — before uploading.
|
|
28
|
+
|
|
10
29
|
## [0.9.0] - 2026-07-03
|
|
11
30
|
|
|
12
31
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,9 @@ var _dryRun = false;
|
|
|
84
84
|
function setDryRun(enabled) {
|
|
85
85
|
_dryRun = enabled;
|
|
86
86
|
}
|
|
87
|
+
function isDryRun() {
|
|
88
|
+
return _dryRun;
|
|
89
|
+
}
|
|
87
90
|
var IntercomClient = class _IntercomClient {
|
|
88
91
|
token;
|
|
89
92
|
authType;
|
|
@@ -153,10 +156,18 @@ var IntercomClient = class _IntercomClient {
|
|
|
153
156
|
const url = `${this.baseUrl}${path}`;
|
|
154
157
|
if (_dryRun) {
|
|
155
158
|
console.error(`> ${method} ${url}`);
|
|
156
|
-
if (body) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
if (body instanceof FormData) {
|
|
160
|
+
for (const [name, value] of body.entries()) {
|
|
161
|
+
if (typeof value === "string") {
|
|
162
|
+
console.error(`> ${name}: ${value}`);
|
|
163
|
+
} else {
|
|
164
|
+
const fileName = "name" in value ? value.name : "file";
|
|
165
|
+
console.error(`> ${name} (${fileName}):
|
|
166
|
+
${await value.text()}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else if (body) {
|
|
170
|
+
console.error(`> Body: ${JSON.stringify(body)}`);
|
|
160
171
|
}
|
|
161
172
|
return {};
|
|
162
173
|
}
|
|
@@ -2572,6 +2583,46 @@ import { writeFile as writeFile3 } from "fs/promises";
|
|
|
2572
2583
|
import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
|
|
2573
2584
|
import { basename, join as join3 } from "path";
|
|
2574
2585
|
import AdmZip from "adm-zip";
|
|
2586
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2587
|
+
|
|
2588
|
+
// src/utils/yaml-drift.ts
|
|
2589
|
+
function isRecord(value) {
|
|
2590
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2591
|
+
}
|
|
2592
|
+
function scalarsMatch(sent, saved) {
|
|
2593
|
+
if (sent == null && saved == null) {
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
if (sent == null || saved == null) {
|
|
2597
|
+
return false;
|
|
2598
|
+
}
|
|
2599
|
+
if (typeof saved === "object") {
|
|
2600
|
+
return false;
|
|
2601
|
+
}
|
|
2602
|
+
return String(sent) === String(saved);
|
|
2603
|
+
}
|
|
2604
|
+
function findDrift(sent, saved, path = "") {
|
|
2605
|
+
const here = path || ".";
|
|
2606
|
+
if (Array.isArray(sent)) {
|
|
2607
|
+
if (!Array.isArray(saved) || saved.length !== sent.length) {
|
|
2608
|
+
return [here];
|
|
2609
|
+
}
|
|
2610
|
+
return sent.flatMap((item, index) => findDrift(item, saved[index], `${path}[${index}]`));
|
|
2611
|
+
}
|
|
2612
|
+
if (isRecord(sent)) {
|
|
2613
|
+
if (!isRecord(saved)) {
|
|
2614
|
+
return [here];
|
|
2615
|
+
}
|
|
2616
|
+
return Object.entries(sent).flatMap(
|
|
2617
|
+
([key, value]) => findDrift(value, saved[key], path ? `${path}.${key}` : key)
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
return scalarsMatch(sent, saved) ? [] : [here];
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/commands/fin/procedures.ts
|
|
2624
|
+
var STATE_LIVE = "live";
|
|
2625
|
+
var STATE_PAUSED = "paused";
|
|
2575
2626
|
function registerProceduresCommand(fin) {
|
|
2576
2627
|
const procedures = fin.command("procedures").description("Manage Fin SDK procedures");
|
|
2577
2628
|
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() {
|
|
@@ -2591,7 +2642,7 @@ function registerProceduresCommand(fin) {
|
|
|
2591
2642
|
return table({ columns: ["ID", "NAME"], rows });
|
|
2592
2643
|
}, items);
|
|
2593
2644
|
});
|
|
2594
|
-
procedures.command("get <id> [output]").description("Download a procedure by ID as YAML (writes to [output], or stdout)").option("--with-simulations", "Also download the procedure's simulations").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2645
|
+
procedures.command("get <id> [output]").description("Download a procedure by ID as YAML (writes to [output], or stdout)").option("--with-simulations", "Also download the procedure's simulations").option("--version <version_id>", "Download a historical version instead of the current one").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2595
2646
|
"after",
|
|
2596
2647
|
`
|
|
2597
2648
|
Examples:
|
|
@@ -2599,22 +2650,26 @@ Examples:
|
|
|
2599
2650
|
$ intercom fin procedures get proc-1 procedure.yaml # write YAML to a file
|
|
2600
2651
|
$ intercom fin procedures get proc-1 ./out --with-simulations
|
|
2601
2652
|
# writes ./out/procedure.yaml and ./out/simulations.yaml
|
|
2653
|
+
$ intercom fin procedures versions proc-1 # find a version ID
|
|
2654
|
+
$ intercom fin procedures get proc-1 --version 4821 # print that version's YAML
|
|
2602
2655
|
|
|
2603
2656
|
With --with-simulations, [output] is treated as a directory (default: current
|
|
2604
2657
|
directory). Without it, [output] is a single file for the procedure YAML.
|
|
2605
2658
|
|
|
2659
|
+
--version downloads the re-uploadable YAML of a historical version, so you can
|
|
2660
|
+
diff a local file against what is live (\`intercom fin procedures versions <id>\`
|
|
2661
|
+
shows which version that is). Simulations are not versioned, so --version and
|
|
2662
|
+
--with-simulations cannot be combined.
|
|
2663
|
+
|
|
2606
2664
|
Re-import the pieces with \`intercom fin procedures upload <procedure.yaml>\`
|
|
2607
2665
|
and \`intercom fin simulations upload <simulations.yaml>\`.`
|
|
2608
2666
|
).action(async function(id, output) {
|
|
2609
2667
|
const opts = this.optsWithGlobals();
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
const zip = new AdmZip(buffer);
|
|
2613
|
-
const entry = zip.getEntries().find((e) => e.entryName.endsWith("procedure.yaml"));
|
|
2614
|
-
if (!entry) {
|
|
2615
|
-
throw new Error("procedure.yaml not found in downloaded ZIP");
|
|
2668
|
+
if (opts.version && opts.withSimulations) {
|
|
2669
|
+
throw new CLIError("--version cannot be combined with --with-simulations (simulations are not versioned)");
|
|
2616
2670
|
}
|
|
2617
|
-
const
|
|
2671
|
+
const client = await resolveClient(opts);
|
|
2672
|
+
const procedureYaml = opts.version ? await getVersionYaml(client, id, opts.version) : await getCurrentYaml(client, id);
|
|
2618
2673
|
let simulationsYaml;
|
|
2619
2674
|
if (opts.withSimulations) {
|
|
2620
2675
|
simulationsYaml = await client.getText(`/fin-sdk/procedures/${id}/simulations`);
|
|
@@ -2637,17 +2692,163 @@ and \`intercom fin simulations upload <simulations.yaml>\`.`
|
|
|
2637
2692
|
console.log(procedureYaml);
|
|
2638
2693
|
}
|
|
2639
2694
|
});
|
|
2640
|
-
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").
|
|
2695
|
+
procedures.command("upload <file>").description("Upload a procedure file to Fin SDK").option("--note <text>", "Version note to record against this save").option("--verify", "Fail if the saved procedure 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(
|
|
2696
|
+
"after",
|
|
2697
|
+
`
|
|
2698
|
+
Examples:
|
|
2699
|
+
$ intercom fin procedures upload procedure.yaml
|
|
2700
|
+
$ intercom fin procedures upload procedure.yaml --note "tightened refund wording"
|
|
2701
|
+
$ intercom fin procedures upload procedure.yaml --verify
|
|
2702
|
+
|
|
2703
|
+
--note labels this save in the procedure's version history, so it is
|
|
2704
|
+
identifiable in \`intercom fin procedures versions <id>\` instead of appearing
|
|
2705
|
+
as an unlabelled entry.
|
|
2706
|
+
|
|
2707
|
+
--verify compares the procedure the server saved against the file just uploaded
|
|
2708
|
+
and exits non-zero listing any field that did not survive the round-trip. Fields
|
|
2709
|
+
the server adds (ids, defaults) are ignored; only content that was sent and came
|
|
2710
|
+
back missing or changed is reported.`
|
|
2711
|
+
).action(async function(filePath) {
|
|
2641
2712
|
const opts = this.optsWithGlobals();
|
|
2642
2713
|
const client = await resolveClient(opts);
|
|
2643
2714
|
const fileContent = await readFile2(filePath);
|
|
2644
2715
|
const formData = new FormData();
|
|
2645
2716
|
const blob = new Blob([fileContent], { type: "application/x-yaml" });
|
|
2646
2717
|
formData.append("file_data", blob, basename(filePath));
|
|
2718
|
+
if (opts.note) {
|
|
2719
|
+
formData.append("version_note", opts.note);
|
|
2720
|
+
}
|
|
2647
2721
|
const result = await client.postFormData("/fin-sdk/upload", formData);
|
|
2648
2722
|
render(result, opts, () => formatJson(result));
|
|
2723
|
+
if (opts.verify) {
|
|
2724
|
+
verifySaved(filePath, fileContent.toString("utf8"), result);
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
procedures.command("publish <id>").description("Set a procedure's state to live").option("--note <text>", "Version note to record against this save").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2728
|
+
"after",
|
|
2729
|
+
`
|
|
2730
|
+
Examples:
|
|
2731
|
+
$ intercom fin procedures publish proc-1
|
|
2732
|
+
$ intercom fin procedures publish proc-1 --note "tightened refund wording"
|
|
2733
|
+
|
|
2734
|
+
This is a convenience for download \u2192 set state \u2192 upload. If another operator
|
|
2735
|
+
edits the procedure between the download and upload, those edits will be
|
|
2736
|
+
overwritten (the server does not expose a concurrency token).
|
|
2737
|
+
|
|
2738
|
+
--note labels this save in \`intercom fin procedures versions <id>\`.`
|
|
2739
|
+
).action(async function(id) {
|
|
2740
|
+
const opts = this.optsWithGlobals();
|
|
2741
|
+
await setProcedureState(id, STATE_LIVE, opts);
|
|
2742
|
+
});
|
|
2743
|
+
procedures.command("pause <id>").description("Set a procedure's state to paused").option("--note <text>", "Version note to record against this save").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2744
|
+
"after",
|
|
2745
|
+
`
|
|
2746
|
+
Examples:
|
|
2747
|
+
$ intercom fin procedures pause proc-1
|
|
2748
|
+
$ intercom fin procedures pause proc-1 --note "pausing during refund policy review"
|
|
2749
|
+
|
|
2750
|
+
This is a convenience for download \u2192 set state \u2192 upload. If another operator
|
|
2751
|
+
edits the procedure between the download and upload, those edits will be
|
|
2752
|
+
overwritten (the server does not expose a concurrency token).
|
|
2753
|
+
|
|
2754
|
+
pause is the way to deactivate a procedure: the server rejects an uploaded
|
|
2755
|
+
\`state: draft\` outright, so there is no separate "unpublish to draft" command.
|
|
2756
|
+
|
|
2757
|
+
--note labels this save in \`intercom fin procedures versions <id>\`.`
|
|
2758
|
+
).action(async function(id) {
|
|
2759
|
+
const opts = this.optsWithGlobals();
|
|
2760
|
+
await setProcedureState(id, STATE_PAUSED, opts);
|
|
2761
|
+
});
|
|
2762
|
+
procedures.command("versions <id>").description("List a procedure's server-side version history (which one is live)").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2763
|
+
"after",
|
|
2764
|
+
`
|
|
2765
|
+
Examples:
|
|
2766
|
+
$ intercom fin procedures versions proc-1
|
|
2767
|
+
$ intercom fin procedures versions proc-1 --jq '.entity_version_id'
|
|
2768
|
+
|
|
2769
|
+
STATUS is the server's own vocabulary: live is what Fin is running now, draft is
|
|
2770
|
+
unpublished, previously_live is superseded. Download any of them with
|
|
2771
|
+
\`intercom fin procedures get <id> --version <VERSION>\`.`
|
|
2772
|
+
).action(async function(id) {
|
|
2773
|
+
const opts = this.optsWithGlobals();
|
|
2774
|
+
const client = await resolveClient(opts);
|
|
2775
|
+
const result = await client.get(`/fin-sdk/procedures/${id}/versions`);
|
|
2776
|
+
const items = result.versions ?? [];
|
|
2777
|
+
render(items, opts, () => {
|
|
2778
|
+
if (items.length === 0) {
|
|
2779
|
+
return "No versions found.";
|
|
2780
|
+
}
|
|
2781
|
+
const rows = items.map((v) => ({
|
|
2782
|
+
VERSION: String(v.entity_version_id ?? ""),
|
|
2783
|
+
STATUS: String(v.status ?? ""),
|
|
2784
|
+
CREATED: String(v.created_at ?? ""),
|
|
2785
|
+
AUTHOR: String(v.author?.name ?? ""),
|
|
2786
|
+
NOTE: String(v.note ?? "")
|
|
2787
|
+
}));
|
|
2788
|
+
return table({ columns: ["VERSION", "STATUS", "CREATED", "AUTHOR", "NOTE"], rows });
|
|
2789
|
+
}, items);
|
|
2649
2790
|
});
|
|
2650
2791
|
}
|
|
2792
|
+
async function setProcedureState(id, state, opts) {
|
|
2793
|
+
const client = await resolveClient(opts);
|
|
2794
|
+
if (isDryRun()) {
|
|
2795
|
+
console.error(`> would set procedure ${id} state to ${state}`);
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
const procedureYaml = await getCurrentYaml(client, id);
|
|
2799
|
+
let parsed;
|
|
2800
|
+
try {
|
|
2801
|
+
parsed = parseYaml(procedureYaml);
|
|
2802
|
+
} catch (err) {
|
|
2803
|
+
throw new CLIError(
|
|
2804
|
+
`server returned unparseable YAML for procedure ${id}: ${err.message}`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
parsed.state = state;
|
|
2808
|
+
const formData = new FormData();
|
|
2809
|
+
const blob = new Blob([stringifyYaml(parsed)], { type: "application/x-yaml" });
|
|
2810
|
+
formData.append("file_data", blob, `procedure-${id}.yaml`);
|
|
2811
|
+
if (opts.note) {
|
|
2812
|
+
formData.append("version_note", opts.note);
|
|
2813
|
+
}
|
|
2814
|
+
const result = await client.postFormData("/fin-sdk/upload", formData);
|
|
2815
|
+
render(result, opts, () => formatJson(result));
|
|
2816
|
+
}
|
|
2817
|
+
async function getCurrentYaml(client, id) {
|
|
2818
|
+
const buffer = await client.getBuffer(`/fin-sdk/procedures/${id}/download`);
|
|
2819
|
+
const zip = new AdmZip(buffer);
|
|
2820
|
+
const entry = zip.getEntries().find((e) => e.entryName.endsWith("procedure.yaml"));
|
|
2821
|
+
if (!entry) {
|
|
2822
|
+
throw new CLIError("procedure.yaml not found in downloaded ZIP");
|
|
2823
|
+
}
|
|
2824
|
+
return zip.readAsText(entry);
|
|
2825
|
+
}
|
|
2826
|
+
async function getVersionYaml(client, id, versionId) {
|
|
2827
|
+
const result = await client.get(
|
|
2828
|
+
`/fin-sdk/procedures/${id}/versions/${versionId}`
|
|
2829
|
+
);
|
|
2830
|
+
const content = result.content;
|
|
2831
|
+
if (content == null) {
|
|
2832
|
+
throw new CLIError(`Procedure version ${versionId} returned no content`);
|
|
2833
|
+
}
|
|
2834
|
+
return typeof content === "string" ? content : stringifyYaml(content);
|
|
2835
|
+
}
|
|
2836
|
+
function verifySaved(filePath, sentYaml, result) {
|
|
2837
|
+
if (typeof result.new_file_content !== "string") {
|
|
2838
|
+
throw new CLIError("Cannot verify: the server did not return the saved procedure");
|
|
2839
|
+
}
|
|
2840
|
+
const drift = findDrift(parseYaml(sentYaml), parseYaml(result.new_file_content));
|
|
2841
|
+
if (drift.length === 0) {
|
|
2842
|
+
console.error(`Verified: the saved procedure matches ${filePath}`);
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
for (const path of drift) {
|
|
2846
|
+
console.error(` ${path}`);
|
|
2847
|
+
}
|
|
2848
|
+
throw new CLIError(
|
|
2849
|
+
`${drift.length} field(s) did not survive the upload (listed above). Compare with \`intercom fin procedures get ${result.id ?? "<id>"}\`.`
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2651
2852
|
|
|
2652
2853
|
// src/commands/fin/data-connectors.ts
|
|
2653
2854
|
function summarizeKeys(obj) {
|
|
@@ -2803,34 +3004,37 @@ Optional fields: state (live or paused, default: paused), audiences`
|
|
|
2803
3004
|
// src/commands/fin/attributes.ts
|
|
2804
3005
|
import { readFile as readFile3 } from "fs/promises";
|
|
2805
3006
|
import { basename as basename2 } from "path";
|
|
2806
|
-
import { parse as
|
|
3007
|
+
import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
|
|
2807
3008
|
var ATTRIBUTE_TYPE = "attribute";
|
|
3009
|
+
async function fetchAttributes(client) {
|
|
3010
|
+
const result = await client.get("/fin-sdk/attributes");
|
|
3011
|
+
return Array.isArray(result) ? result : result.attributes ?? [];
|
|
3012
|
+
}
|
|
2808
3013
|
function buildAttributesUploadBody(raw) {
|
|
2809
3014
|
let parsed;
|
|
2810
3015
|
try {
|
|
2811
|
-
parsed =
|
|
3016
|
+
parsed = parseYaml2(raw);
|
|
2812
3017
|
} catch {
|
|
2813
3018
|
return raw;
|
|
2814
3019
|
}
|
|
2815
3020
|
if (Array.isArray(parsed)) {
|
|
2816
|
-
return
|
|
3021
|
+
return stringifyYaml2({ type: ATTRIBUTE_TYPE, attributes: parsed });
|
|
2817
3022
|
}
|
|
2818
3023
|
if (parsed == null || typeof parsed !== "object") {
|
|
2819
3024
|
return raw;
|
|
2820
3025
|
}
|
|
2821
3026
|
const obj = parsed;
|
|
2822
3027
|
if (Array.isArray(obj.attributes)) {
|
|
2823
|
-
return
|
|
3028
|
+
return stringifyYaml2({ ...obj, type: ATTRIBUTE_TYPE });
|
|
2824
3029
|
}
|
|
2825
|
-
return
|
|
3030
|
+
return stringifyYaml2({ type: ATTRIBUTE_TYPE, attributes: [obj] });
|
|
2826
3031
|
}
|
|
2827
3032
|
function registerAttributesCommand(fin) {
|
|
2828
3033
|
const attributes = fin.command("attributes").description("Manage Fin SDK attributes");
|
|
2829
3034
|
attributes.command("list").description("List Fin SDK attributes").option("--json", "Output as JSON").option("--verbose", "Show HTTP request/response details").action(async function() {
|
|
2830
3035
|
const opts = this.optsWithGlobals();
|
|
2831
3036
|
const client = await resolveClient(opts);
|
|
2832
|
-
const
|
|
2833
|
-
const items = Array.isArray(result) ? result : result.attributes ?? [];
|
|
3037
|
+
const items = await fetchAttributes(client);
|
|
2834
3038
|
render(items, opts, () => {
|
|
2835
3039
|
if (items.length === 0) {
|
|
2836
3040
|
return "No attributes found.";
|
|
@@ -2843,6 +3047,32 @@ function registerAttributesCommand(fin) {
|
|
|
2843
3047
|
return table({ columns: ["NAME", "TYPE", "DESCRIPTION"], rows });
|
|
2844
3048
|
}, items);
|
|
2845
3049
|
});
|
|
3050
|
+
attributes.command("get <name>").description("Show a single Fin SDK attribute by name").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").action(async function(name) {
|
|
3051
|
+
const opts = this.optsWithGlobals();
|
|
3052
|
+
const client = await resolveClient(opts);
|
|
3053
|
+
const items = await fetchAttributes(client);
|
|
3054
|
+
const match = items.find((item) => item.name === name);
|
|
3055
|
+
if (!match) {
|
|
3056
|
+
throw new NotFoundError(`No Fin attribute named "${name}".`);
|
|
3057
|
+
}
|
|
3058
|
+
render(
|
|
3059
|
+
match,
|
|
3060
|
+
opts,
|
|
3061
|
+
() => detail({
|
|
3062
|
+
"Name": match.name != null ? String(match.name) : void 0,
|
|
3063
|
+
"Type": match.type != null ? String(match.type) : void 0,
|
|
3064
|
+
"Description": match.description != null ? String(match.description) : void 0
|
|
3065
|
+
})
|
|
3066
|
+
);
|
|
3067
|
+
});
|
|
3068
|
+
attributes.command("download <file>").description("Download all Fin SDK attributes to a YAML file").option("--verbose", "Show HTTP request/response details").action(async function(filePath) {
|
|
3069
|
+
const opts = this.optsWithGlobals();
|
|
3070
|
+
const client = await resolveClient(opts);
|
|
3071
|
+
const items = await fetchAttributes(client);
|
|
3072
|
+
const yaml = stringifyYaml2({ type: ATTRIBUTE_TYPE, attributes: items });
|
|
3073
|
+
await atomicWriteFile(filePath, yaml, 420);
|
|
3074
|
+
console.error(`Wrote ${items.length} attribute${items.length === 1 ? "" : "s"} to ${filePath}`);
|
|
3075
|
+
});
|
|
2846
3076
|
attributes.command("upload <file>").description("Upload attributes from a YAML file (creates or updates)").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2847
3077
|
"after",
|
|
2848
3078
|
`
|
|
@@ -2912,14 +3142,14 @@ function registerAudiencesCommand(fin) {
|
|
|
2912
3142
|
// src/commands/fin/workflows.ts
|
|
2913
3143
|
import { readFile as readFile4 } from "fs/promises";
|
|
2914
3144
|
import { basename as basename3 } from "path";
|
|
2915
|
-
import { parse as
|
|
2916
|
-
var
|
|
2917
|
-
var
|
|
3145
|
+
import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
|
|
3146
|
+
var STATE_LIVE2 = "live";
|
|
3147
|
+
var STATE_PAUSED2 = "paused";
|
|
2918
3148
|
var WORKFLOW_TYPE = "workflow";
|
|
2919
3149
|
function ensureWorkflowType(raw) {
|
|
2920
3150
|
let parsed;
|
|
2921
3151
|
try {
|
|
2922
|
-
parsed =
|
|
3152
|
+
parsed = parseYaml3(raw);
|
|
2923
3153
|
} catch {
|
|
2924
3154
|
return raw;
|
|
2925
3155
|
}
|
|
@@ -2931,7 +3161,7 @@ function ensureWorkflowType(raw) {
|
|
|
2931
3161
|
return raw;
|
|
2932
3162
|
}
|
|
2933
3163
|
obj.type = WORKFLOW_TYPE;
|
|
2934
|
-
return
|
|
3164
|
+
return stringifyYaml3(obj);
|
|
2935
3165
|
}
|
|
2936
3166
|
function registerWorkflowsCommand(fin) {
|
|
2937
3167
|
const workflows = fin.command("workflows").description("Manage Fin SDK workflows");
|
|
@@ -2953,22 +3183,28 @@ function registerWorkflowsCommand(fin) {
|
|
|
2953
3183
|
return table({ columns: ["ID", "TITLE", "STATE", "TRIGGER"], rows });
|
|
2954
3184
|
}, items);
|
|
2955
3185
|
});
|
|
2956
|
-
workflows.command("get <id> [output]").description("Get a workflow by ID as YAML (writes to [output] file, or stdout)").option("--json", "Output as parsed JSON instead of YAML").option("--jq <expr>", "Filter parsed-JSON output with a jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
3186
|
+
workflows.command("get <id> [output]").description("Get a workflow by ID as YAML (writes to [output] file, or stdout)").option("--json", "Output as parsed JSON instead of YAML").option("--jq <expr>", "Filter parsed-JSON output with a jq expression").option("--version <version_id>", "Download a historical version instead of the current one").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
2957
3187
|
"after",
|
|
2958
3188
|
`
|
|
2959
3189
|
Examples:
|
|
2960
3190
|
$ intercom fin workflows get 42 workflow.yaml # write YAML to a file
|
|
2961
3191
|
$ intercom fin workflows get 42 > workflow.yaml # or redirect stdout
|
|
2962
3192
|
$ intercom fin workflows get 42 --json | jq .state
|
|
3193
|
+
$ intercom fin workflows versions 42 # find a version ID
|
|
3194
|
+
$ intercom fin workflows get 42 --version 4821 # print that version's YAML
|
|
2963
3195
|
|
|
2964
3196
|
The YAML it prints is ready to edit and re-upload with
|
|
2965
|
-
\`intercom fin workflows upload
|
|
3197
|
+
\`intercom fin workflows upload\`.
|
|
3198
|
+
|
|
3199
|
+
--version downloads the re-uploadable YAML of a historical version, so you can
|
|
3200
|
+
diff a local file against what is live (\`intercom fin workflows versions <id>\`
|
|
3201
|
+
shows which version that is).`
|
|
2966
3202
|
).action(async function(id, output) {
|
|
2967
3203
|
const opts = this.optsWithGlobals();
|
|
2968
3204
|
const client = await resolveClient(opts);
|
|
2969
|
-
const text = await client.getText(`/fin-sdk/workflows/${id}`);
|
|
3205
|
+
const text = opts.version ? await getVersionYaml2(client, id, opts.version) : await client.getText(`/fin-sdk/workflows/${id}`);
|
|
2970
3206
|
if (opts.json || opts.jq || opts.agent) {
|
|
2971
|
-
const parsed =
|
|
3207
|
+
const parsed = parseYaml3(text);
|
|
2972
3208
|
render(parsed, opts, () => formatJson(parsed));
|
|
2973
3209
|
return;
|
|
2974
3210
|
}
|
|
@@ -3026,13 +3262,13 @@ overwritten (the server does not expose a concurrency token).`
|
|
|
3026
3262
|
const text = await client.getText(`/fin-sdk/workflows/${id}`);
|
|
3027
3263
|
let parsed;
|
|
3028
3264
|
try {
|
|
3029
|
-
parsed =
|
|
3265
|
+
parsed = parseYaml3(text);
|
|
3030
3266
|
} catch (err) {
|
|
3031
3267
|
throw new Error(`server returned unparseable YAML for workflow ${id}: ${err.message}`);
|
|
3032
3268
|
}
|
|
3033
3269
|
parsed.type = WORKFLOW_TYPE;
|
|
3034
|
-
parsed.state =
|
|
3035
|
-
const yamlBody =
|
|
3270
|
+
parsed.state = STATE_LIVE2;
|
|
3271
|
+
const yamlBody = stringifyYaml3(parsed);
|
|
3036
3272
|
const formData = new FormData();
|
|
3037
3273
|
const blob = new Blob([yamlBody], { type: "application/x-yaml" });
|
|
3038
3274
|
formData.append("file_data", blob, `workflow-${id}.yaml`);
|
|
@@ -3063,13 +3299,13 @@ overwritten (the server does not expose a concurrency token).`
|
|
|
3063
3299
|
const text = await client.getText(`/fin-sdk/workflows/${id}`);
|
|
3064
3300
|
let parsed;
|
|
3065
3301
|
try {
|
|
3066
|
-
parsed =
|
|
3302
|
+
parsed = parseYaml3(text);
|
|
3067
3303
|
} catch (err) {
|
|
3068
3304
|
throw new Error(`server returned unparseable YAML for workflow ${id}: ${err.message}`);
|
|
3069
3305
|
}
|
|
3070
3306
|
parsed.type = WORKFLOW_TYPE;
|
|
3071
|
-
parsed.state =
|
|
3072
|
-
const yamlBody =
|
|
3307
|
+
parsed.state = STATE_PAUSED2;
|
|
3308
|
+
const yamlBody = stringifyYaml3(parsed);
|
|
3073
3309
|
const formData = new FormData();
|
|
3074
3310
|
const blob = new Blob([yamlBody], { type: "application/x-yaml" });
|
|
3075
3311
|
formData.append("file_data", blob, `workflow-${id}.yaml`);
|
|
@@ -3084,17 +3320,56 @@ overwritten (the server does not expose a concurrency token).`
|
|
|
3084
3320
|
})
|
|
3085
3321
|
);
|
|
3086
3322
|
});
|
|
3323
|
+
workflows.command("versions <id>").description("List a workflow's server-side version history (which one is live)").option("--json", "Output as JSON").option("--jq <expr>", "Filter JSON output with jq expression").option("--verbose", "Show HTTP request/response details").addHelpText(
|
|
3324
|
+
"after",
|
|
3325
|
+
`
|
|
3326
|
+
Examples:
|
|
3327
|
+
$ intercom fin workflows versions 42
|
|
3328
|
+
$ intercom fin workflows versions 42 --jq '.entity_version_id'
|
|
3329
|
+
|
|
3330
|
+
STATUS is the server's own vocabulary: live is what Fin is running now, draft is
|
|
3331
|
+
unpublished, previously_live is superseded. Download any of them with
|
|
3332
|
+
\`intercom fin workflows get <id> --version <VERSION>\`.`
|
|
3333
|
+
).action(async function(id) {
|
|
3334
|
+
const opts = this.optsWithGlobals();
|
|
3335
|
+
const client = await resolveClient(opts);
|
|
3336
|
+
const result = await client.get(`/fin-sdk/workflows/${id}/versions`);
|
|
3337
|
+
const items = result.versions ?? [];
|
|
3338
|
+
render(items, opts, () => {
|
|
3339
|
+
if (items.length === 0) {
|
|
3340
|
+
return "No versions found.";
|
|
3341
|
+
}
|
|
3342
|
+
const rows = items.map((v) => ({
|
|
3343
|
+
VERSION: String(v.entity_version_id ?? ""),
|
|
3344
|
+
STATUS: String(v.status ?? ""),
|
|
3345
|
+
CREATED: String(v.created_at ?? ""),
|
|
3346
|
+
AUTHOR: String(v.author?.name ?? ""),
|
|
3347
|
+
NOTE: String(v.note ?? "")
|
|
3348
|
+
}));
|
|
3349
|
+
return table({ columns: ["VERSION", "STATUS", "CREATED", "AUTHOR", "NOTE"], rows });
|
|
3350
|
+
}, items);
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
async function getVersionYaml2(client, id, versionId) {
|
|
3354
|
+
const result = await client.get(
|
|
3355
|
+
`/fin-sdk/workflows/${id}/versions/${versionId}`
|
|
3356
|
+
);
|
|
3357
|
+
const content = result.content;
|
|
3358
|
+
if (content == null) {
|
|
3359
|
+
throw new CLIError(`Workflow version ${versionId} returned no content`);
|
|
3360
|
+
}
|
|
3361
|
+
return typeof content === "string" ? content : stringifyYaml3(content);
|
|
3087
3362
|
}
|
|
3088
3363
|
|
|
3089
3364
|
// src/commands/fin/simulations.ts
|
|
3090
3365
|
import { readFile as readFile5 } from "fs/promises";
|
|
3091
3366
|
import { basename as basename4 } from "path";
|
|
3092
|
-
import { parse as
|
|
3367
|
+
import { parse as parseYaml4, stringify as stringifyYaml4 } from "yaml";
|
|
3093
3368
|
var SIMULATION_SET_TYPE = "simulation_set";
|
|
3094
3369
|
function ensureSimulationSetType(raw) {
|
|
3095
3370
|
let parsed;
|
|
3096
3371
|
try {
|
|
3097
|
-
parsed =
|
|
3372
|
+
parsed = parseYaml4(raw);
|
|
3098
3373
|
} catch {
|
|
3099
3374
|
return raw;
|
|
3100
3375
|
}
|
|
@@ -3106,7 +3381,7 @@ function ensureSimulationSetType(raw) {
|
|
|
3106
3381
|
return raw;
|
|
3107
3382
|
}
|
|
3108
3383
|
obj.type = SIMULATION_SET_TYPE;
|
|
3109
|
-
return
|
|
3384
|
+
return stringifyYaml4(obj);
|
|
3110
3385
|
}
|
|
3111
3386
|
function registerSimulationsCommand(fin) {
|
|
3112
3387
|
const simulations = fin.command("simulations").description("Manage Fin SDK simulations");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intercom/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "CLI tool for managing Intercom workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.23.0",
|
|
@@ -41,9 +41,5 @@
|
|
|
41
41
|
"tsup": "^8.5.1",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vitest": "^4.1.0"
|
|
44
|
-
},
|
|
45
|
-
"pnpm": {
|
|
46
|
-
"overrides": {},
|
|
47
|
-
"onlyBuiltDependencies": []
|
|
48
44
|
}
|
|
49
45
|
}
|