@hookstream/cli 0.1.0 → 0.2.1
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 +262 -0
- package/dist/index.js +953 -105
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command77 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/output.ts
|
|
7
7
|
import chalk from "chalk";
|
|
@@ -57,6 +57,9 @@ function formatBytes(bytes) {
|
|
|
57
57
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
58
58
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
59
59
|
}
|
|
60
|
+
function truncate(str, len) {
|
|
61
|
+
return str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
|
|
62
|
+
}
|
|
60
63
|
function statusColor(status) {
|
|
61
64
|
switch (status) {
|
|
62
65
|
case "active":
|
|
@@ -85,7 +88,9 @@ function statusColor(status) {
|
|
|
85
88
|
// src/commands/login.ts
|
|
86
89
|
import { createInterface } from "readline/promises";
|
|
87
90
|
import { stdin, stdout } from "process";
|
|
91
|
+
import { exec } from "child_process";
|
|
88
92
|
import { Command } from "commander";
|
|
93
|
+
import chalk2 from "chalk";
|
|
89
94
|
|
|
90
95
|
// src/config.ts
|
|
91
96
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
@@ -121,8 +126,8 @@ var hookstreamClient = class {
|
|
|
121
126
|
baseUrl;
|
|
122
127
|
constructor(opts = {}) {
|
|
123
128
|
const config = loadConfig();
|
|
124
|
-
this.apiKey = opts.apiKey || process.env.HOOKSTREAM_API_KEY || config.api_key || "";
|
|
125
|
-
this.baseUrl = (opts.baseUrl || process.env.HOOKSTREAM_BASE_URL || config.base_url || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
129
|
+
this.apiKey = opts.apiKey || process.env.__HS_CLI_API_KEY || process.env.HOOKSTREAM_API_KEY || config.api_key || "";
|
|
130
|
+
this.baseUrl = (opts.baseUrl || process.env.__HS_CLI_BASE_URL || process.env.HOOKSTREAM_BASE_URL || config.base_url || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
126
131
|
if (!this.apiKey) {
|
|
127
132
|
throw new Error("Not authenticated. Run `hookstream login` or set HOOKSTREAM_API_KEY.");
|
|
128
133
|
}
|
|
@@ -177,24 +182,110 @@ async function publicRequest(baseUrl, path, opts = {}) {
|
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
// src/commands/login.ts
|
|
180
|
-
var
|
|
185
|
+
var DEFAULT_BASE_URL2 = "https://hookstream.io";
|
|
186
|
+
function openBrowser(url) {
|
|
187
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
188
|
+
exec(cmd, () => {
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async function sleep(ms) {
|
|
192
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
193
|
+
}
|
|
194
|
+
var loginCommand = new Command("login").description("Authenticate with an API key").option("--api-key <key>", "API key (or enter interactively)").option("--base-url <url>", "Override base URL").option("-i, --interactive", "Enter API key manually (skip browser)").action(async (opts) => {
|
|
181
195
|
let apiKey = opts.apiKey;
|
|
182
|
-
|
|
196
|
+
const baseUrl = (opts.baseUrl || process.env.__HS_CLI_BASE_URL || process.env.HOOKSTREAM_BASE_URL || DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
197
|
+
if (apiKey) {
|
|
198
|
+
await validateAndSave(apiKey, baseUrl, opts);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (opts.interactive) {
|
|
183
202
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
184
203
|
apiKey = await rl.question("Enter your API key: ");
|
|
185
204
|
rl.close();
|
|
205
|
+
if (!apiKey || !apiKey.trim()) {
|
|
206
|
+
printError("API key cannot be empty.");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
await validateAndSave(apiKey.trim(), baseUrl, opts);
|
|
210
|
+
return;
|
|
186
211
|
}
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
try {
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(chalk2.bold(" hookstream CLI Login"));
|
|
215
|
+
console.log();
|
|
216
|
+
const res = await fetch(`${baseUrl}/v1/cli/auth`, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: { "Content-Type": "application/json" }
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
console.log(chalk2.dim(" Browser auth not available, falling back to interactive mode."));
|
|
222
|
+
console.log();
|
|
223
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
224
|
+
apiKey = await rl.question(" Enter your API key: ");
|
|
225
|
+
rl.close();
|
|
226
|
+
if (!apiKey?.trim()) {
|
|
227
|
+
printError("API key cannot be empty.");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
await validateAndSave(apiKey.trim(), baseUrl, opts);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const session = await res.json();
|
|
234
|
+
console.log(" Opening browser to authenticate...");
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(` ${chalk2.dim("If the browser doesn't open, visit:")}`);
|
|
237
|
+
console.log(` ${chalk2.cyan(session.url)}`);
|
|
238
|
+
console.log();
|
|
239
|
+
console.log(chalk2.dim(" Waiting for authorization..."));
|
|
240
|
+
openBrowser(session.url);
|
|
241
|
+
const deadline = Date.now() + session.expires_in * 1e3;
|
|
242
|
+
let approved = false;
|
|
243
|
+
while (Date.now() < deadline) {
|
|
244
|
+
await sleep(2e3);
|
|
245
|
+
try {
|
|
246
|
+
const pollRes = await fetch(`${baseUrl}/v1/cli/auth/${session.code}`);
|
|
247
|
+
if (!pollRes.ok) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
const result = await pollRes.json();
|
|
251
|
+
if (result.status === "approved" && result.api_key) {
|
|
252
|
+
apiKey = result.api_key;
|
|
253
|
+
approved = true;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!approved || !apiKey) {
|
|
260
|
+
console.log();
|
|
261
|
+
printError("Authorization timed out. Try again or use: hookstream login -i");
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
const config = loadConfig();
|
|
265
|
+
config.api_key = apiKey;
|
|
266
|
+
config.base_url = baseUrl;
|
|
267
|
+
saveConfig(config);
|
|
268
|
+
console.log();
|
|
269
|
+
if (isJsonMode()) {
|
|
270
|
+
printJson({ success: true, prefix: apiKey.slice(0, 16) + "..." });
|
|
271
|
+
} else {
|
|
272
|
+
printSuccess(`Logged in as ${apiKey.slice(0, 16)}...`);
|
|
273
|
+
console.log();
|
|
274
|
+
console.log(chalk2.dim(" Config saved to ~/.config/hookstream/config.json"));
|
|
275
|
+
console.log(chalk2.dim(" Run `hookstream whoami` to verify."));
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
189
279
|
process.exit(1);
|
|
190
280
|
}
|
|
191
|
-
|
|
281
|
+
});
|
|
282
|
+
async function validateAndSave(apiKey, baseUrl, opts) {
|
|
192
283
|
if (!/^hs_(live|test)_[a-f0-9]{64}$/.test(apiKey)) {
|
|
193
284
|
printError("Invalid API key format. Expected: hs_live_<64 hex> or hs_test_<64 hex>");
|
|
194
285
|
process.exit(1);
|
|
195
286
|
}
|
|
196
287
|
try {
|
|
197
|
-
const client = new hookstreamClient({ apiKey, baseUrl
|
|
288
|
+
const client = new hookstreamClient({ apiKey, baseUrl });
|
|
198
289
|
await client.get("/sources");
|
|
199
290
|
} catch (err) {
|
|
200
291
|
printError(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -211,7 +302,7 @@ var loginCommand = new Command("login").description("Authenticate with an API ke
|
|
|
211
302
|
} else {
|
|
212
303
|
printSuccess(`Logged in as ${apiKey.slice(0, 16)}...`);
|
|
213
304
|
}
|
|
214
|
-
}
|
|
305
|
+
}
|
|
215
306
|
|
|
216
307
|
// src/commands/logout.ts
|
|
217
308
|
import { Command as Command2 } from "commander";
|
|
@@ -840,7 +931,7 @@ var connectionsDeleteCommand = new Command20("delete").description("Delete a con
|
|
|
840
931
|
var connectionsCommand = new Command21("connections").description("Manage source-to-destination connections").addCommand(connectionsListCommand).addCommand(connectionsCreateCommand).addCommand(connectionsGetCommand).addCommand(connectionsDeleteCommand);
|
|
841
932
|
|
|
842
933
|
// src/commands/events/index.ts
|
|
843
|
-
import { Command as
|
|
934
|
+
import { Command as Command26 } from "commander";
|
|
844
935
|
|
|
845
936
|
// src/commands/events/list.ts
|
|
846
937
|
import { Command as Command22 } from "commander";
|
|
@@ -893,7 +984,7 @@ var eventsListCommand = new Command22("list").description("List events").option(
|
|
|
893
984
|
|
|
894
985
|
// src/commands/events/get.ts
|
|
895
986
|
import { Command as Command23 } from "commander";
|
|
896
|
-
import
|
|
987
|
+
import chalk3 from "chalk";
|
|
897
988
|
var eventsGetCommand = new Command23("get").description("Get event details").argument("<id>", "Event ID").action(async (id) => {
|
|
898
989
|
try {
|
|
899
990
|
const client = new hookstreamClient();
|
|
@@ -918,15 +1009,15 @@ var eventsGetCommand = new Command23("get").description("Get event details").arg
|
|
|
918
1009
|
]);
|
|
919
1010
|
if (e.headers && Object.keys(e.headers).length > 0) {
|
|
920
1011
|
console.log();
|
|
921
|
-
console.log(
|
|
1012
|
+
console.log(chalk3.bold(" Headers"));
|
|
922
1013
|
for (const [key, val] of Object.entries(e.headers)) {
|
|
923
1014
|
if (key.startsWith("cf-") || key.startsWith("x-real-") || key === "cdn-loop") continue;
|
|
924
|
-
console.log(` ${
|
|
1015
|
+
console.log(` ${chalk3.dim(key)}: ${val}`);
|
|
925
1016
|
}
|
|
926
1017
|
}
|
|
927
1018
|
if (e.payload) {
|
|
928
1019
|
console.log();
|
|
929
|
-
console.log(
|
|
1020
|
+
console.log(chalk3.bold(" Payload"));
|
|
930
1021
|
try {
|
|
931
1022
|
const parsed = JSON.parse(e.payload);
|
|
932
1023
|
console.log(JSON.stringify(parsed, null, 2).split("\n").map((l) => ` ${l}`).join("\n"));
|
|
@@ -946,7 +1037,7 @@ var eventsGetCommand = new Command23("get").description("Get event details").arg
|
|
|
946
1037
|
|
|
947
1038
|
// src/commands/events/replay.ts
|
|
948
1039
|
import { Command as Command24 } from "commander";
|
|
949
|
-
import
|
|
1040
|
+
import chalk4 from "chalk";
|
|
950
1041
|
var SKIP_HEADERS = /* @__PURE__ */ new Set([
|
|
951
1042
|
"cf-connecting-ip",
|
|
952
1043
|
"cf-ipcountry",
|
|
@@ -998,7 +1089,7 @@ var eventsReplayCommand = new Command24("replay").description("Replay an event t
|
|
|
998
1089
|
});
|
|
999
1090
|
return;
|
|
1000
1091
|
}
|
|
1001
|
-
const statusStr = res.ok ?
|
|
1092
|
+
const statusStr = res.ok ? chalk4.green(String(res.status)) : chalk4.red(String(res.status));
|
|
1002
1093
|
printSuccess(`Replayed event ${id.slice(0, 12)} to ${opts.to}`);
|
|
1003
1094
|
console.log();
|
|
1004
1095
|
printKeyValue([
|
|
@@ -1013,15 +1104,32 @@ var eventsReplayCommand = new Command24("replay").description("Replay an event t
|
|
|
1013
1104
|
}
|
|
1014
1105
|
});
|
|
1015
1106
|
|
|
1107
|
+
// src/commands/events/retry.ts
|
|
1108
|
+
import { Command as Command25 } from "commander";
|
|
1109
|
+
var eventsRetryCommand = new Command25("retry").description("Retry all deliveries for an event").argument("<id>", "Event ID").action(async (id) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const client = new hookstreamClient();
|
|
1112
|
+
const data = await client.post(`/events/${id}/retry`);
|
|
1113
|
+
if (isJsonMode()) {
|
|
1114
|
+
printJson({ event_id: id, retried: data.retried });
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
printSuccess(`Retried ${data.retried} delivery attempt(s) for event ${id.slice(0, 12)}`);
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1016
1124
|
// src/commands/events/index.ts
|
|
1017
|
-
var eventsCommand = new
|
|
1125
|
+
var eventsCommand = new Command26("events").description("Browse and replay events").addCommand(eventsListCommand).addCommand(eventsGetCommand).addCommand(eventsReplayCommand).addCommand(eventsRetryCommand);
|
|
1018
1126
|
|
|
1019
1127
|
// src/commands/deliveries/index.ts
|
|
1020
|
-
import { Command as
|
|
1128
|
+
import { Command as Command31 } from "commander";
|
|
1021
1129
|
|
|
1022
1130
|
// src/commands/deliveries/list.ts
|
|
1023
|
-
import { Command as
|
|
1024
|
-
var deliveriesListCommand = new
|
|
1131
|
+
import { Command as Command27 } from "commander";
|
|
1132
|
+
var deliveriesListCommand = new Command27("list").description("List delivery attempts").option("--event <id>", "Filter by event ID").option("--connection <id>", "Filter by connection ID").option("--destination <id>", "Filter by destination ID").option("--status <status>", "Filter by status (success, failed, timeout, error, dlq)").option("--dlq", "Show only DLQ items").option("--limit <n>", "Max results", "20").action(async (opts) => {
|
|
1025
1133
|
try {
|
|
1026
1134
|
const client = new hookstreamClient();
|
|
1027
1135
|
const params = new URLSearchParams();
|
|
@@ -1073,8 +1181,8 @@ var deliveriesListCommand = new Command26("list").description("List delivery att
|
|
|
1073
1181
|
});
|
|
1074
1182
|
|
|
1075
1183
|
// src/commands/deliveries/get.ts
|
|
1076
|
-
import { Command as
|
|
1077
|
-
var deliveriesGetCommand = new
|
|
1184
|
+
import { Command as Command28 } from "commander";
|
|
1185
|
+
var deliveriesGetCommand = new Command28("get").description("Get delivery attempt details").argument("<id>", "Delivery attempt ID").action(async (id) => {
|
|
1078
1186
|
try {
|
|
1079
1187
|
const client = new hookstreamClient();
|
|
1080
1188
|
const data = await client.get(`/delivery-attempts/${id}`);
|
|
@@ -1115,8 +1223,8 @@ var deliveriesGetCommand = new Command27("get").description("Get delivery attemp
|
|
|
1115
1223
|
});
|
|
1116
1224
|
|
|
1117
1225
|
// src/commands/deliveries/retry.ts
|
|
1118
|
-
import { Command as
|
|
1119
|
-
var deliveriesRetryCommand = new
|
|
1226
|
+
import { Command as Command29 } from "commander";
|
|
1227
|
+
var deliveriesRetryCommand = new Command29("retry").description("Retry a failed delivery attempt").argument("<id>", "Delivery attempt ID").action(async (id) => {
|
|
1120
1228
|
try {
|
|
1121
1229
|
const client = new hookstreamClient();
|
|
1122
1230
|
const data = await client.post(
|
|
@@ -1143,8 +1251,8 @@ var deliveriesRetryCommand = new Command28("retry").description("Retry a failed
|
|
|
1143
1251
|
});
|
|
1144
1252
|
|
|
1145
1253
|
// src/commands/deliveries/dlq.ts
|
|
1146
|
-
import { Command as
|
|
1147
|
-
var deliveriesDlqCommand = new
|
|
1254
|
+
import { Command as Command30 } from "commander";
|
|
1255
|
+
var deliveriesDlqCommand = new Command30("dlq").description("List dead-letter queue items").option("--destination <id>", "Filter by destination ID").option("--limit <n>", "Max results", "50").action(async (opts) => {
|
|
1148
1256
|
try {
|
|
1149
1257
|
const client = new hookstreamClient();
|
|
1150
1258
|
const params = new URLSearchParams();
|
|
@@ -1195,14 +1303,14 @@ var deliveriesDlqCommand = new Command29("dlq").description("List dead-letter qu
|
|
|
1195
1303
|
});
|
|
1196
1304
|
|
|
1197
1305
|
// src/commands/deliveries/index.ts
|
|
1198
|
-
var deliveriesCommand = new
|
|
1306
|
+
var deliveriesCommand = new Command31("deliveries").description("Manage delivery attempts").addCommand(deliveriesListCommand).addCommand(deliveriesGetCommand).addCommand(deliveriesRetryCommand).addCommand(deliveriesDlqCommand);
|
|
1199
1307
|
|
|
1200
1308
|
// src/commands/metrics/index.ts
|
|
1201
|
-
import { Command as
|
|
1309
|
+
import { Command as Command34 } from "commander";
|
|
1202
1310
|
|
|
1203
1311
|
// src/commands/metrics/overview.ts
|
|
1204
|
-
import { Command as
|
|
1205
|
-
var metricsOverviewCommand = new
|
|
1312
|
+
import { Command as Command32 } from "commander";
|
|
1313
|
+
var metricsOverviewCommand = new Command32("overview").description("Show metrics overview").action(async () => {
|
|
1206
1314
|
try {
|
|
1207
1315
|
const client = new hookstreamClient();
|
|
1208
1316
|
const data = await client.get("/metrics/overview");
|
|
@@ -1228,9 +1336,9 @@ var metricsOverviewCommand = new Command31("overview").description("Show metrics
|
|
|
1228
1336
|
});
|
|
1229
1337
|
|
|
1230
1338
|
// src/commands/metrics/volume.ts
|
|
1231
|
-
import { Command as
|
|
1232
|
-
import
|
|
1233
|
-
var metricsVolumeCommand = new
|
|
1339
|
+
import { Command as Command33 } from "commander";
|
|
1340
|
+
import chalk5 from "chalk";
|
|
1341
|
+
var metricsVolumeCommand = new Command33("volume").description("Show event volume over time").option("--source <id>", "Filter by source ID").option("--after <iso>", "Start time (ISO 8601)").option("--before <iso>", "End time (ISO 8601)").option("--granularity <period>", "hour or day", "hour").action(async (opts) => {
|
|
1234
1342
|
try {
|
|
1235
1343
|
const client = new hookstreamClient();
|
|
1236
1344
|
const params = new URLSearchParams();
|
|
@@ -1250,7 +1358,7 @@ var metricsVolumeCommand = new Command32("volume").description("Show event volum
|
|
|
1250
1358
|
return;
|
|
1251
1359
|
}
|
|
1252
1360
|
if (data.series.length === 0) {
|
|
1253
|
-
console.log(
|
|
1361
|
+
console.log(chalk5.dim("\n No volume data for this time range.\n"));
|
|
1254
1362
|
return;
|
|
1255
1363
|
}
|
|
1256
1364
|
console.log();
|
|
@@ -1285,19 +1393,19 @@ function renderBar(value, max) {
|
|
|
1285
1393
|
if (max === 0) return "";
|
|
1286
1394
|
const width = 15;
|
|
1287
1395
|
const filled = Math.round(value / max * width);
|
|
1288
|
-
return
|
|
1396
|
+
return chalk5.cyan("\u2588".repeat(filled)) + chalk5.dim("\u2591".repeat(width - filled));
|
|
1289
1397
|
}
|
|
1290
1398
|
|
|
1291
1399
|
// src/commands/metrics/index.ts
|
|
1292
|
-
var metricsCommand = new
|
|
1400
|
+
var metricsCommand = new Command34("metrics").description("View metrics and stats").addCommand(metricsOverviewCommand).addCommand(metricsVolumeCommand);
|
|
1293
1401
|
|
|
1294
1402
|
// src/commands/test.ts
|
|
1295
1403
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1296
|
-
import { Command as
|
|
1297
|
-
var
|
|
1298
|
-
var testCommand = new
|
|
1404
|
+
import { Command as Command35 } from "commander";
|
|
1405
|
+
var DEFAULT_BASE_URL3 = "https://hookstream.io";
|
|
1406
|
+
var testCommand = new Command35("test").description("Send a test event to a source").argument("<source-id>", "Source ID to send the test event to").option("--payload <json>", "JSON payload string").option("--file <path>", "Read payload from a file").option("--method <method>", "HTTP method", "POST").option("-H, --header <header...>", "Headers in key:value format").option("--base-url <url>", "Override base URL").action(async (sourceId, opts) => {
|
|
1299
1407
|
const config = loadConfig();
|
|
1300
|
-
const baseUrl = (opts.baseUrl || process.env.HOOKSTREAM_BASE_URL || config.base_url ||
|
|
1408
|
+
const baseUrl = (opts.baseUrl || process.env.HOOKSTREAM_BASE_URL || config.base_url || DEFAULT_BASE_URL3).replace(/\/$/, "");
|
|
1301
1409
|
let payload;
|
|
1302
1410
|
if (opts.file) {
|
|
1303
1411
|
try {
|
|
@@ -1380,9 +1488,9 @@ var testCommand = new Command34("test").description("Send a test event to a sour
|
|
|
1380
1488
|
});
|
|
1381
1489
|
|
|
1382
1490
|
// src/commands/listen.ts
|
|
1383
|
-
import { Command as
|
|
1384
|
-
import
|
|
1385
|
-
var
|
|
1491
|
+
import { Command as Command36 } from "commander";
|
|
1492
|
+
import chalk6 from "chalk";
|
|
1493
|
+
var DEFAULT_BASE_URL4 = "https://hookstream.io";
|
|
1386
1494
|
var PROVIDER_SIGNATURES = [
|
|
1387
1495
|
{ header: "stripe-signature", name: "Stripe" },
|
|
1388
1496
|
{ header: "x-shopify-hmac-sha256", name: "Shopify" },
|
|
@@ -1410,13 +1518,13 @@ function formatBytes2(bytes) {
|
|
|
1410
1518
|
if (bytes < 1024) return `${bytes}B`;
|
|
1411
1519
|
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
1412
1520
|
}
|
|
1413
|
-
function
|
|
1521
|
+
function truncate2(str, max) {
|
|
1414
1522
|
if (str.length <= max) return str;
|
|
1415
1523
|
return str.slice(0, max - 3) + "...";
|
|
1416
1524
|
}
|
|
1417
|
-
var listenCommand = new
|
|
1525
|
+
var listenCommand = new Command36("listen").description("Create a test URL and stream webhooks in real-time").option("--forward <url>", "Forward received webhooks to this URL").option("--full", "Show full payload (no truncation)").option("--base-url <url>", "Override base URL").action(async (opts) => {
|
|
1418
1526
|
const config = loadConfig();
|
|
1419
|
-
const baseUrl = (opts.baseUrl || process.env.HOOKSTREAM_BASE_URL || config.base_url ||
|
|
1527
|
+
const baseUrl = (opts.baseUrl || process.env.HOOKSTREAM_BASE_URL || config.base_url || DEFAULT_BASE_URL4).replace(/\/$/, "");
|
|
1420
1528
|
const jsonMode2 = isJsonMode();
|
|
1421
1529
|
try {
|
|
1422
1530
|
let connect2 = function() {
|
|
@@ -1453,14 +1561,14 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1453
1561
|
}
|
|
1454
1562
|
})();
|
|
1455
1563
|
const provider = detectProviderCli(headers);
|
|
1456
|
-
const methodColor = method === "POST" ?
|
|
1564
|
+
const methodColor = method === "POST" ? chalk6.green : method === "GET" ? chalk6.blue : method === "PUT" ? chalk6.yellow : method === "DELETE" ? chalk6.red : chalk6.white;
|
|
1457
1565
|
console.log(
|
|
1458
|
-
` ${
|
|
1566
|
+
` ${chalk6.dim(`[${time}]`)} ${methodColor(method.padEnd(6))} ${chalk6.dim(ct.padEnd(24))} ${size.padEnd(8)} ${provider ? chalk6.magenta(`\u2190 ${provider}`) : ""}`
|
|
1459
1567
|
);
|
|
1460
1568
|
const payload = detail.payload ?? detail.payload_inline ?? "";
|
|
1461
1569
|
if (payload) {
|
|
1462
|
-
const display = opts.full ? payload :
|
|
1463
|
-
console.log(` ${
|
|
1570
|
+
const display = opts.full ? payload : truncate2(payload, 120);
|
|
1571
|
+
console.log(` ${chalk6.dim(" ")} ${chalk6.dim(display)}`);
|
|
1464
1572
|
}
|
|
1465
1573
|
}
|
|
1466
1574
|
} catch {
|
|
@@ -1470,9 +1578,9 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1470
1578
|
if (!jsonMode2) {
|
|
1471
1579
|
const status = msg.data.status;
|
|
1472
1580
|
const latency = msg.data.latency_ms;
|
|
1473
|
-
const color = status >= 200 && status < 300 ?
|
|
1581
|
+
const color = status >= 200 && status < 300 ? chalk6.green : chalk6.red;
|
|
1474
1582
|
console.log(
|
|
1475
|
-
` ${
|
|
1583
|
+
` ${chalk6.dim(" ")} ${chalk6.dim("\u2192 Forward:")} ${color(String(status))} ${chalk6.dim(`${latency}ms`)}`
|
|
1476
1584
|
);
|
|
1477
1585
|
}
|
|
1478
1586
|
}
|
|
@@ -1493,7 +1601,7 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1493
1601
|
if (!jsonMode2) {
|
|
1494
1602
|
console.log();
|
|
1495
1603
|
console.log(
|
|
1496
|
-
|
|
1604
|
+
chalk6.dim(` Session active for 14 days at ${baseUrl.replace(/https:\/\/hookstream\.\w+\.workers\.dev/, "https://hookstream.io")}/test/${session.session_id}`)
|
|
1497
1605
|
);
|
|
1498
1606
|
console.log();
|
|
1499
1607
|
}
|
|
@@ -1516,12 +1624,12 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1516
1624
|
const session = await createRes.json();
|
|
1517
1625
|
if (!jsonMode2) {
|
|
1518
1626
|
console.log();
|
|
1519
|
-
console.log(
|
|
1627
|
+
console.log(chalk6.bold.magenta(" hookstream Webhook Listener"));
|
|
1520
1628
|
console.log();
|
|
1521
|
-
console.log(` ${
|
|
1522
|
-
console.log(` ${
|
|
1523
|
-
console.log(` ${
|
|
1524
|
-
console.log(` ${
|
|
1629
|
+
console.log(` ${chalk6.dim("URL:")} ${chalk6.cyan(session.url)}`);
|
|
1630
|
+
console.log(` ${chalk6.dim("Inspector:")} ${chalk6.cyan(`${baseUrl.replace(/https:\/\/hookstream\.\w+\.workers\.dev/, "https://hookstream.io")}/test/${session.session_id}`)}`);
|
|
1631
|
+
console.log(` ${chalk6.dim("Expires:")} ${new Date(session.expires_at).toLocaleDateString()} (14 days)`);
|
|
1632
|
+
console.log(` ${chalk6.dim("Limit:")} ${session.request_limit} requests`);
|
|
1525
1633
|
console.log();
|
|
1526
1634
|
}
|
|
1527
1635
|
if (opts.forward) {
|
|
@@ -1532,18 +1640,18 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1532
1640
|
body: JSON.stringify({ url: opts.forward })
|
|
1533
1641
|
});
|
|
1534
1642
|
if (!jsonMode2) {
|
|
1535
|
-
console.log(` ${
|
|
1643
|
+
console.log(` ${chalk6.dim("Forward:")} ${chalk6.yellow(opts.forward)}`);
|
|
1536
1644
|
console.log();
|
|
1537
1645
|
}
|
|
1538
1646
|
} catch {
|
|
1539
1647
|
if (!jsonMode2) {
|
|
1540
|
-
console.log(
|
|
1648
|
+
console.log(chalk6.yellow(" Warning: Could not configure forwarding"));
|
|
1541
1649
|
console.log();
|
|
1542
1650
|
}
|
|
1543
1651
|
}
|
|
1544
1652
|
}
|
|
1545
1653
|
if (!jsonMode2) {
|
|
1546
|
-
console.log(
|
|
1654
|
+
console.log(chalk6.dim(" Listening... (Ctrl+C to stop)"));
|
|
1547
1655
|
console.log();
|
|
1548
1656
|
}
|
|
1549
1657
|
const wsUrl = baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
@@ -1560,11 +1668,11 @@ var listenCommand = new Command35("listen").description("Create a test URL and s
|
|
|
1560
1668
|
});
|
|
1561
1669
|
|
|
1562
1670
|
// src/commands/topics/index.ts
|
|
1563
|
-
import { Command as
|
|
1671
|
+
import { Command as Command44 } from "commander";
|
|
1564
1672
|
|
|
1565
1673
|
// src/commands/topics/list.ts
|
|
1566
|
-
import { Command as
|
|
1567
|
-
var topicsListCommand = new
|
|
1674
|
+
import { Command as Command37 } from "commander";
|
|
1675
|
+
var topicsListCommand = new Command37("list").description("List all topics").option("--status <status>", "Filter by status (active, paused)").action(async (opts) => {
|
|
1568
1676
|
try {
|
|
1569
1677
|
const client = new hookstreamClient();
|
|
1570
1678
|
const params = new URLSearchParams();
|
|
@@ -1600,11 +1708,11 @@ var topicsListCommand = new Command36("list").description("List all topics").opt
|
|
|
1600
1708
|
});
|
|
1601
1709
|
|
|
1602
1710
|
// src/commands/topics/create.ts
|
|
1603
|
-
import { Command as
|
|
1711
|
+
import { Command as Command38 } from "commander";
|
|
1604
1712
|
function slugify3(name) {
|
|
1605
1713
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1606
1714
|
}
|
|
1607
|
-
var topicsCreateCommand = new
|
|
1715
|
+
var topicsCreateCommand = new Command38("create").description("Create a new topic").argument("<name>", "Topic name").option("--slug <slug>", "URL slug (auto-generated from name if omitted)").option("--description <desc>", "Topic description").action(async (name, opts) => {
|
|
1608
1716
|
try {
|
|
1609
1717
|
const client = new hookstreamClient();
|
|
1610
1718
|
const slug = opts.slug || slugify3(name);
|
|
@@ -1635,8 +1743,8 @@ var topicsCreateCommand = new Command37("create").description("Create a new topi
|
|
|
1635
1743
|
});
|
|
1636
1744
|
|
|
1637
1745
|
// src/commands/topics/get.ts
|
|
1638
|
-
import { Command as
|
|
1639
|
-
var topicsGetCommand = new
|
|
1746
|
+
import { Command as Command39 } from "commander";
|
|
1747
|
+
var topicsGetCommand = new Command39("get").description("Get topic details").argument("<id>", "Topic ID").action(async (id) => {
|
|
1640
1748
|
try {
|
|
1641
1749
|
const client = new hookstreamClient();
|
|
1642
1750
|
const data = await client.get(`/topics/${id}`);
|
|
@@ -1686,8 +1794,8 @@ var topicsGetCommand = new Command38("get").description("Get topic details").arg
|
|
|
1686
1794
|
// src/commands/topics/delete.ts
|
|
1687
1795
|
import { createInterface as createInterface5 } from "readline/promises";
|
|
1688
1796
|
import { stdin as stdin5, stdout as stdout5 } from "process";
|
|
1689
|
-
import { Command as
|
|
1690
|
-
var topicsDeleteCommand = new
|
|
1797
|
+
import { Command as Command40 } from "commander";
|
|
1798
|
+
var topicsDeleteCommand = new Command40("delete").description("Delete a topic").argument("<id>", "Topic ID").option("-f, --force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
1691
1799
|
try {
|
|
1692
1800
|
if (!opts.force) {
|
|
1693
1801
|
const rl = createInterface5({ input: stdin5, output: stdout5 });
|
|
@@ -1712,8 +1820,8 @@ var topicsDeleteCommand = new Command39("delete").description("Delete a topic").
|
|
|
1712
1820
|
});
|
|
1713
1821
|
|
|
1714
1822
|
// src/commands/topics/subscribe.ts
|
|
1715
|
-
import { Command as
|
|
1716
|
-
var topicsSubscribeCommand = new
|
|
1823
|
+
import { Command as Command41 } from "commander";
|
|
1824
|
+
var topicsSubscribeCommand = new Command41("subscribe").description("Subscribe a destination to a topic").argument("<topic-id>", "Topic ID").argument("<destination-id>", "Destination ID").option("--filter <json>", "Filter rules as JSON").option("--transform <expr>", "JSONata transform expression").action(async (topicId, destinationId, opts) => {
|
|
1717
1825
|
try {
|
|
1718
1826
|
const client = new hookstreamClient();
|
|
1719
1827
|
const body = {
|
|
@@ -1751,8 +1859,8 @@ var topicsSubscribeCommand = new Command40("subscribe").description("Subscribe a
|
|
|
1751
1859
|
});
|
|
1752
1860
|
|
|
1753
1861
|
// src/commands/topics/unsubscribe.ts
|
|
1754
|
-
import { Command as
|
|
1755
|
-
var topicsUnsubscribeCommand = new
|
|
1862
|
+
import { Command as Command42 } from "commander";
|
|
1863
|
+
var topicsUnsubscribeCommand = new Command42("unsubscribe").description("Remove a subscription from a topic").argument("<topic-id>", "Topic ID").argument("<subscription-id>", "Subscription ID").action(async (topicId, subscriptionId) => {
|
|
1756
1864
|
try {
|
|
1757
1865
|
const client = new hookstreamClient();
|
|
1758
1866
|
await client.del(`/topics/${topicId}/subscriptions/${subscriptionId}`);
|
|
@@ -1768,8 +1876,8 @@ var topicsUnsubscribeCommand = new Command41("unsubscribe").description("Remove
|
|
|
1768
1876
|
});
|
|
1769
1877
|
|
|
1770
1878
|
// src/commands/topics/publish.ts
|
|
1771
|
-
import { Command as
|
|
1772
|
-
var topicsPublishCommand = new
|
|
1879
|
+
import { Command as Command43 } from "commander";
|
|
1880
|
+
var topicsPublishCommand = new Command43("publish").description("Publish an event to a topic").argument("<slug>", "Topic slug").requiredOption("--data <json>", "Event payload as JSON").action(async (slug, opts) => {
|
|
1773
1881
|
try {
|
|
1774
1882
|
const client = new hookstreamClient();
|
|
1775
1883
|
const payload = JSON.parse(opts.data);
|
|
@@ -1792,14 +1900,14 @@ var topicsPublishCommand = new Command42("publish").description("Publish an even
|
|
|
1792
1900
|
});
|
|
1793
1901
|
|
|
1794
1902
|
// src/commands/topics/index.ts
|
|
1795
|
-
var topicsCommand = new
|
|
1903
|
+
var topicsCommand = new Command44("topics").description("Manage topics and pub/sub").addCommand(topicsListCommand).addCommand(topicsCreateCommand).addCommand(topicsGetCommand).addCommand(topicsDeleteCommand).addCommand(topicsSubscribeCommand).addCommand(topicsUnsubscribeCommand).addCommand(topicsPublishCommand);
|
|
1796
1904
|
|
|
1797
1905
|
// src/commands/replay/index.ts
|
|
1798
|
-
import { Command as
|
|
1906
|
+
import { Command as Command47 } from "commander";
|
|
1799
1907
|
|
|
1800
1908
|
// src/commands/replay/create.ts
|
|
1801
|
-
import { Command as
|
|
1802
|
-
var replayCreateCommand = new
|
|
1909
|
+
import { Command as Command45 } from "commander";
|
|
1910
|
+
var replayCreateCommand = new Command45("create").description("Create an event replay job").requiredOption("--destination <id>", "Destination ID to replay events to").requiredOption("--from <iso>", "Start time (ISO 8601)").requiredOption("--to <iso>", "End time (ISO 8601)").option("--source <id>", "Filter by source ID").option("--rate-limit <n>", "Max events per second", parseInt).option("--max-events <n>", "Maximum events to replay", parseInt).action(async (opts) => {
|
|
1803
1911
|
try {
|
|
1804
1912
|
const client = new hookstreamClient();
|
|
1805
1913
|
const body = {
|
|
@@ -1836,8 +1944,8 @@ var replayCreateCommand = new Command44("create").description("Create an event r
|
|
|
1836
1944
|
});
|
|
1837
1945
|
|
|
1838
1946
|
// src/commands/replay/status.ts
|
|
1839
|
-
import { Command as
|
|
1840
|
-
var replayStatusCommand = new
|
|
1947
|
+
import { Command as Command46 } from "commander";
|
|
1948
|
+
var replayStatusCommand = new Command46("status").description("Get replay job status").argument("<id>", "Replay job ID").action(async (id) => {
|
|
1841
1949
|
try {
|
|
1842
1950
|
const client = new hookstreamClient();
|
|
1843
1951
|
const data = await client.get(`/replay/${id}`);
|
|
@@ -1870,15 +1978,15 @@ var replayStatusCommand = new Command45("status").description("Get replay job st
|
|
|
1870
1978
|
});
|
|
1871
1979
|
|
|
1872
1980
|
// src/commands/replay/index.ts
|
|
1873
|
-
var replayCommand = new
|
|
1981
|
+
var replayCommand = new Command47("replay").description("Replay historical events").addCommand(replayCreateCommand).addCommand(replayStatusCommand);
|
|
1874
1982
|
|
|
1875
1983
|
// src/commands/billing/index.ts
|
|
1876
|
-
import { Command as
|
|
1984
|
+
import { Command as Command50 } from "commander";
|
|
1877
1985
|
|
|
1878
1986
|
// src/commands/billing/usage.ts
|
|
1879
|
-
import { Command as
|
|
1880
|
-
import
|
|
1881
|
-
var billingUsageCommand = new
|
|
1987
|
+
import { Command as Command48 } from "commander";
|
|
1988
|
+
import chalk7 from "chalk";
|
|
1989
|
+
var billingUsageCommand = new Command48("usage").description("Show current plan usage").action(async () => {
|
|
1882
1990
|
try {
|
|
1883
1991
|
const client = new hookstreamClient();
|
|
1884
1992
|
const data = await client.get("/billing/usage");
|
|
@@ -1888,13 +1996,13 @@ var billingUsageCommand = new Command47("usage").description("Show current plan
|
|
|
1888
1996
|
}
|
|
1889
1997
|
const pct = data.events.percentage;
|
|
1890
1998
|
const bar = progressBar(pct, 30);
|
|
1891
|
-
const pctColor = pct >= 90 ?
|
|
1999
|
+
const pctColor = pct >= 90 ? chalk7.red : pct >= 80 ? chalk7.yellow : chalk7.green;
|
|
1892
2000
|
console.log();
|
|
1893
|
-
console.log(
|
|
2001
|
+
console.log(chalk7.bold(" Plan:"), chalk7.cyan(data.plan.toUpperCase()));
|
|
1894
2002
|
console.log();
|
|
1895
|
-
console.log(
|
|
2003
|
+
console.log(chalk7.dim(" Events this month"));
|
|
1896
2004
|
console.log(` ${bar} ${pctColor(`${pct}%`)}`);
|
|
1897
|
-
console.log(
|
|
2005
|
+
console.log(chalk7.dim(` ${data.events.used.toLocaleString()} / ${data.events.limit === Infinity ? "Unlimited" : data.events.limit.toLocaleString()}`));
|
|
1898
2006
|
console.log();
|
|
1899
2007
|
printKeyValue([
|
|
1900
2008
|
["Sources", data.sources.limit === Infinity ? `${data.sources.used} (unlimited)` : `${data.sources.used} / ${data.sources.limit}`],
|
|
@@ -1910,14 +2018,14 @@ var billingUsageCommand = new Command47("usage").description("Show current plan
|
|
|
1910
2018
|
function progressBar(pct, width) {
|
|
1911
2019
|
const filled = Math.round(Math.min(pct, 100) / 100 * width);
|
|
1912
2020
|
const empty = width - filled;
|
|
1913
|
-
const color = pct >= 90 ?
|
|
1914
|
-
return color("\u2588".repeat(filled)) +
|
|
2021
|
+
const color = pct >= 90 ? chalk7.red : pct >= 80 ? chalk7.yellow : chalk7.green;
|
|
2022
|
+
return color("\u2588".repeat(filled)) + chalk7.dim("\u2591".repeat(empty));
|
|
1915
2023
|
}
|
|
1916
2024
|
|
|
1917
2025
|
// src/commands/billing/subscription.ts
|
|
1918
|
-
import { Command as
|
|
1919
|
-
import
|
|
1920
|
-
var billingSubscriptionCommand = new
|
|
2026
|
+
import { Command as Command49 } from "commander";
|
|
2027
|
+
import chalk8 from "chalk";
|
|
2028
|
+
var billingSubscriptionCommand = new Command49("subscription").description("Show current subscription details").action(async () => {
|
|
1921
2029
|
try {
|
|
1922
2030
|
const client = new hookstreamClient();
|
|
1923
2031
|
const data = await client.get("/billing/subscription");
|
|
@@ -1927,17 +2035,17 @@ var billingSubscriptionCommand = new Command48("subscription").description("Show
|
|
|
1927
2035
|
}
|
|
1928
2036
|
console.log();
|
|
1929
2037
|
if (!data.subscription) {
|
|
1930
|
-
console.log(
|
|
1931
|
-
console.log(
|
|
2038
|
+
console.log(chalk8.dim(" No active subscription. You are on the Free plan."));
|
|
2039
|
+
console.log(chalk8.dim(" Upgrade at https://hookstream.io/settings?tab=billing"));
|
|
1932
2040
|
} else {
|
|
1933
2041
|
const sub = data.subscription;
|
|
1934
|
-
const statusColor2 = sub.status === "active" ?
|
|
2042
|
+
const statusColor2 = sub.status === "active" ? chalk8.green : sub.status === "past_due" ? chalk8.yellow : chalk8.red;
|
|
1935
2043
|
printKeyValue([
|
|
1936
|
-
["Plan",
|
|
2044
|
+
["Plan", chalk8.cyan(sub.plan.toUpperCase())],
|
|
1937
2045
|
["Status", statusColor2(sub.status)],
|
|
1938
2046
|
["Period Start", formatDate(sub.current_period_start)],
|
|
1939
2047
|
["Period End", formatDate(sub.current_period_end)],
|
|
1940
|
-
["Cancel at Period End", sub.cancel_at_period_end ?
|
|
2048
|
+
["Cancel at Period End", sub.cancel_at_period_end ? chalk8.yellow("Yes") : "No"],
|
|
1941
2049
|
["Canceled At", sub.canceled_at ? formatDate(sub.canceled_at) : "\u2014"]
|
|
1942
2050
|
]);
|
|
1943
2051
|
}
|
|
@@ -1949,15 +2057,750 @@ var billingSubscriptionCommand = new Command48("subscription").description("Show
|
|
|
1949
2057
|
});
|
|
1950
2058
|
|
|
1951
2059
|
// src/commands/billing/index.ts
|
|
1952
|
-
var billingCommand = new
|
|
2060
|
+
var billingCommand = new Command50("billing").description("View billing, usage, and subscription info").addCommand(billingUsageCommand).addCommand(billingSubscriptionCommand);
|
|
2061
|
+
|
|
2062
|
+
// src/commands/issues/index.ts
|
|
2063
|
+
import { Command as Command55 } from "commander";
|
|
2064
|
+
|
|
2065
|
+
// src/commands/issues/list.ts
|
|
2066
|
+
import { Command as Command51 } from "commander";
|
|
2067
|
+
var issuesListCommand = new Command51("list").description("List delivery issues").option("--status <status>", "Filter by status (open, acknowledged, resolved, all)", "open").option("--destination <id>", "Filter by destination ID").option("--limit <n>", "Max results", "50").action(async (opts) => {
|
|
2068
|
+
try {
|
|
2069
|
+
const client = new hookstreamClient();
|
|
2070
|
+
const params = new URLSearchParams();
|
|
2071
|
+
if (opts.status) params.set("status", opts.status);
|
|
2072
|
+
if (opts.destination) params.set("destination_id", opts.destination);
|
|
2073
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
2074
|
+
const qs = params.toString();
|
|
2075
|
+
const data = await client.get(`/issues${qs ? `?${qs}` : ""}`);
|
|
2076
|
+
if (isJsonMode()) {
|
|
2077
|
+
printJson(data);
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
console.log();
|
|
2081
|
+
printTable(
|
|
2082
|
+
data.issues.map((i) => ({
|
|
2083
|
+
id: i.id.slice(0, 12),
|
|
2084
|
+
status: statusColor(i.status),
|
|
2085
|
+
type: i.issue_type,
|
|
2086
|
+
destination: truncate(i.destination_name || i.destination_id, 18),
|
|
2087
|
+
occurrences: String(i.occurrence_count),
|
|
2088
|
+
last_seen: formatDate(i.last_seen_at)
|
|
2089
|
+
})),
|
|
2090
|
+
[
|
|
2091
|
+
{ key: "id", label: "ID", width: 14 },
|
|
2092
|
+
{ key: "status", label: "Status", width: 14 },
|
|
2093
|
+
{ key: "type", label: "Type", width: 16 },
|
|
2094
|
+
{ key: "destination", label: "Destination", width: 20 },
|
|
2095
|
+
{ key: "occurrences", label: "Occurrences", width: 13 },
|
|
2096
|
+
{ key: "last_seen", label: "Last Seen" }
|
|
2097
|
+
]
|
|
2098
|
+
);
|
|
2099
|
+
console.log();
|
|
2100
|
+
} catch (err) {
|
|
2101
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2102
|
+
process.exit(1);
|
|
2103
|
+
}
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
// src/commands/issues/get.ts
|
|
2107
|
+
import { Command as Command52 } from "commander";
|
|
2108
|
+
var issuesGetCommand = new Command52("get").description("Get issue details").argument("<id>", "Issue ID").action(async (id) => {
|
|
2109
|
+
try {
|
|
2110
|
+
const client = new hookstreamClient();
|
|
2111
|
+
const data = await client.get(`/issues/${id}`);
|
|
2112
|
+
const i = data.issue;
|
|
2113
|
+
if (isJsonMode()) {
|
|
2114
|
+
printJson(i);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
console.log();
|
|
2118
|
+
printKeyValue([
|
|
2119
|
+
["ID", i.id],
|
|
2120
|
+
["Status", statusColor(i.status)],
|
|
2121
|
+
["Type", i.issue_type],
|
|
2122
|
+
["Destination", i.destination_name || i.destination_id],
|
|
2123
|
+
["Error Pattern", i.error_pattern || "\u2014"],
|
|
2124
|
+
["Occurrences", String(i.occurrence_count)],
|
|
2125
|
+
["First Seen", formatDate(i.first_seen_at)],
|
|
2126
|
+
["Last Seen", formatDate(i.last_seen_at)],
|
|
2127
|
+
["Resolved At", formatDate(i.resolved_at)],
|
|
2128
|
+
["Created", formatDate(i.created_at)],
|
|
2129
|
+
["Updated", formatDate(i.updated_at)]
|
|
2130
|
+
]);
|
|
2131
|
+
console.log();
|
|
2132
|
+
} catch (err) {
|
|
2133
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2134
|
+
process.exit(1);
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
|
|
2138
|
+
// src/commands/issues/update.ts
|
|
2139
|
+
import { Command as Command53 } from "commander";
|
|
2140
|
+
var issuesUpdateCommand = new Command53("update").description("Update issue status").argument("<id>", "Issue ID").requiredOption("--status <status>", "New status (open, acknowledged, resolved)").action(async (id, opts) => {
|
|
2141
|
+
try {
|
|
2142
|
+
const validStatuses = ["open", "acknowledged", "resolved"];
|
|
2143
|
+
if (!validStatuses.includes(opts.status)) {
|
|
2144
|
+
printError(`Invalid status. Must be one of: ${validStatuses.join(", ")}`);
|
|
2145
|
+
process.exit(1);
|
|
2146
|
+
}
|
|
2147
|
+
const client = new hookstreamClient();
|
|
2148
|
+
const data = await client.patch(`/issues/${id}`, {
|
|
2149
|
+
status: opts.status
|
|
2150
|
+
});
|
|
2151
|
+
if (isJsonMode()) {
|
|
2152
|
+
printJson(data.issue);
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
printSuccess(`Issue ${id} updated to ${opts.status}.`);
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2158
|
+
process.exit(1);
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
// src/commands/issues/retry.ts
|
|
2163
|
+
import { Command as Command54 } from "commander";
|
|
2164
|
+
var issuesRetryCommand = new Command54("retry").description("Retry failed deliveries for an issue").argument("<id>", "Issue ID").action(async (id) => {
|
|
2165
|
+
try {
|
|
2166
|
+
const client = new hookstreamClient();
|
|
2167
|
+
const data = await client.post(`/issues/${id}/retry`);
|
|
2168
|
+
if (isJsonMode()) {
|
|
2169
|
+
printJson(data);
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
printSuccess(`Retried ${data.retried}/${data.total} failed deliveries.`);
|
|
2173
|
+
} catch (err) {
|
|
2174
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2175
|
+
process.exit(1);
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// src/commands/issues/index.ts
|
|
2180
|
+
var issuesCommand = new Command55("issues").description("View and manage delivery issues").addCommand(issuesListCommand).addCommand(issuesGetCommand).addCommand(issuesUpdateCommand).addCommand(issuesRetryCommand);
|
|
2181
|
+
|
|
2182
|
+
// src/commands/alert-rules/index.ts
|
|
2183
|
+
import { Command as Command60 } from "commander";
|
|
2184
|
+
|
|
2185
|
+
// src/commands/alert-rules/list.ts
|
|
2186
|
+
import { Command as Command56 } from "commander";
|
|
2187
|
+
var alertsListCommand = new Command56("list").description("List all alert rules").action(async () => {
|
|
2188
|
+
try {
|
|
2189
|
+
const client = new hookstreamClient();
|
|
2190
|
+
const data = await client.get("/alert-rules");
|
|
2191
|
+
if (isJsonMode()) {
|
|
2192
|
+
printJson(data.alert_rules);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
console.log();
|
|
2196
|
+
printTable(
|
|
2197
|
+
data.alert_rules.map((r) => ({
|
|
2198
|
+
id: r.id.slice(0, 12),
|
|
2199
|
+
name: r.name,
|
|
2200
|
+
type: r.rule_type,
|
|
2201
|
+
status: statusColor(r.enabled ? "active" : "disabled"),
|
|
2202
|
+
cooldown: `${r.cooldown_minutes}m`,
|
|
2203
|
+
last_triggered: formatDate(r.last_triggered_at)
|
|
2204
|
+
})),
|
|
2205
|
+
[
|
|
2206
|
+
{ key: "id", label: "ID", width: 14 },
|
|
2207
|
+
{ key: "name", label: "Name", width: 22 },
|
|
2208
|
+
{ key: "type", label: "Type", width: 16 },
|
|
2209
|
+
{ key: "status", label: "Status", width: 10 },
|
|
2210
|
+
{ key: "cooldown", label: "Cooldown", width: 10 },
|
|
2211
|
+
{ key: "last_triggered", label: "Last Triggered" }
|
|
2212
|
+
]
|
|
2213
|
+
);
|
|
2214
|
+
console.log();
|
|
2215
|
+
} catch (err) {
|
|
2216
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2217
|
+
process.exit(1);
|
|
2218
|
+
}
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2221
|
+
// src/commands/alert-rules/create.ts
|
|
2222
|
+
import { Command as Command57 } from "commander";
|
|
2223
|
+
var alertsCreateCommand = new Command57("create").description("Create a new alert rule").argument("<name>", "Alert rule name").requiredOption("--type <type>", "Rule type (failure_rate, latency, dlq, volume_drop, issue_opened)").option("--description <text>", "Rule description").option("--source <id>", "Source ID to scope the alert to").option("--destination <id>", "Destination ID to scope the alert to").option("--channel <id>", "Notification channel ID (repeatable)", (val, acc) => {
|
|
2224
|
+
acc.push(val);
|
|
2225
|
+
return acc;
|
|
2226
|
+
}, []).option("--cooldown <minutes>", "Cooldown between alerts in minutes", "60").option("--config <json>", "Rule config as JSON (e.g. threshold values)").action(async (name, opts) => {
|
|
2227
|
+
try {
|
|
2228
|
+
const validTypes = ["failure_rate", "latency", "dlq", "volume_drop", "issue_opened"];
|
|
2229
|
+
if (!validTypes.includes(opts.type)) {
|
|
2230
|
+
printError(`Invalid type. Must be one of: ${validTypes.join(", ")}`);
|
|
2231
|
+
process.exit(1);
|
|
2232
|
+
}
|
|
2233
|
+
let ruleConfig = {};
|
|
2234
|
+
if (opts.config) {
|
|
2235
|
+
try {
|
|
2236
|
+
ruleConfig = JSON.parse(opts.config);
|
|
2237
|
+
} catch {
|
|
2238
|
+
printError("Invalid JSON for --config");
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const client = new hookstreamClient();
|
|
2243
|
+
const data = await client.post("/alert-rules", {
|
|
2244
|
+
name,
|
|
2245
|
+
rule_type: opts.type,
|
|
2246
|
+
description: opts.description || void 0,
|
|
2247
|
+
source_id: opts.source || void 0,
|
|
2248
|
+
destination_id: opts.destination || void 0,
|
|
2249
|
+
channel_ids: opts.channel.length > 0 ? opts.channel : void 0,
|
|
2250
|
+
cooldown_minutes: parseInt(opts.cooldown, 10),
|
|
2251
|
+
rule_config: Object.keys(ruleConfig).length > 0 ? ruleConfig : void 0
|
|
2252
|
+
});
|
|
2253
|
+
const r = data.alert_rule;
|
|
2254
|
+
if (isJsonMode()) {
|
|
2255
|
+
printJson(r);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
printSuccess(`Alert rule created: ${r.name}`);
|
|
2259
|
+
console.log();
|
|
2260
|
+
printKeyValue([
|
|
2261
|
+
["ID", r.id],
|
|
2262
|
+
["Name", r.name],
|
|
2263
|
+
["Type", r.rule_type],
|
|
2264
|
+
["Cooldown", `${r.cooldown_minutes}m`]
|
|
2265
|
+
]);
|
|
2266
|
+
console.log();
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2269
|
+
process.exit(1);
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
// src/commands/alert-rules/get.ts
|
|
2274
|
+
import { Command as Command58 } from "commander";
|
|
2275
|
+
var alertsGetCommand = new Command58("get").description("Get alert rule details").argument("<id>", "Alert rule ID").action(async (id) => {
|
|
2276
|
+
try {
|
|
2277
|
+
const client = new hookstreamClient();
|
|
2278
|
+
const data = await client.get(`/alert-rules/${id}`);
|
|
2279
|
+
const r = data.alert_rule;
|
|
2280
|
+
if (isJsonMode()) {
|
|
2281
|
+
printJson(data);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
console.log();
|
|
2285
|
+
printKeyValue([
|
|
2286
|
+
["ID", r.id],
|
|
2287
|
+
["Name", r.name],
|
|
2288
|
+
["Description", r.description || "\u2014"],
|
|
2289
|
+
["Type", r.rule_type],
|
|
2290
|
+
["Status", statusColor(r.enabled ? "active" : "disabled")],
|
|
2291
|
+
["Source", r.source_id || "all"],
|
|
2292
|
+
["Destination", r.destination_id || "all"],
|
|
2293
|
+
["Channels", r.channel_ids || "none"],
|
|
2294
|
+
["Cooldown", `${r.cooldown_minutes}m`],
|
|
2295
|
+
["Config", r.rule_config || "{}"],
|
|
2296
|
+
["Last Triggered", formatDate(r.last_triggered_at)],
|
|
2297
|
+
["Created", formatDate(r.created_at)],
|
|
2298
|
+
["Updated", formatDate(r.updated_at)]
|
|
2299
|
+
]);
|
|
2300
|
+
if (data.history && data.history.length > 0) {
|
|
2301
|
+
console.log();
|
|
2302
|
+
console.log(` Recent history: ${data.history.length} entries`);
|
|
2303
|
+
}
|
|
2304
|
+
console.log();
|
|
2305
|
+
} catch (err) {
|
|
2306
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2307
|
+
process.exit(1);
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
// src/commands/alert-rules/delete.ts
|
|
2312
|
+
import { createInterface as createInterface6 } from "readline/promises";
|
|
2313
|
+
import { stdin as stdin6, stdout as stdout6 } from "process";
|
|
2314
|
+
import { Command as Command59 } from "commander";
|
|
2315
|
+
var alertsDeleteCommand = new Command59("delete").description("Delete an alert rule").argument("<id>", "Alert rule ID").option("-f, --force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
2316
|
+
try {
|
|
2317
|
+
if (!opts.force) {
|
|
2318
|
+
const rl = createInterface6({ input: stdin6, output: stdout6 });
|
|
2319
|
+
const answer = await rl.question(`Delete alert rule ${id}? This cannot be undone. [y/N] `);
|
|
2320
|
+
rl.close();
|
|
2321
|
+
if (answer.toLowerCase() !== "y") {
|
|
2322
|
+
console.log("Cancelled.");
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
const client = new hookstreamClient();
|
|
2327
|
+
await client.del(`/alert-rules/${id}`);
|
|
2328
|
+
if (isJsonMode()) {
|
|
2329
|
+
printJson({ success: true, id });
|
|
2330
|
+
} else {
|
|
2331
|
+
printSuccess(`Alert rule ${id} deleted.`);
|
|
2332
|
+
}
|
|
2333
|
+
} catch (err) {
|
|
2334
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2335
|
+
process.exit(1);
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
// src/commands/alert-rules/index.ts
|
|
2340
|
+
var alertsCommand = new Command60("alerts").description("Manage alert rules").addCommand(alertsListCommand).addCommand(alertsCreateCommand).addCommand(alertsGetCommand).addCommand(alertsDeleteCommand);
|
|
2341
|
+
|
|
2342
|
+
// src/commands/channels/index.ts
|
|
2343
|
+
import { Command as Command66 } from "commander";
|
|
2344
|
+
|
|
2345
|
+
// src/commands/channels/list.ts
|
|
2346
|
+
import { Command as Command61 } from "commander";
|
|
2347
|
+
var channelsListCommand = new Command61("list").description("List all notification channels").action(async () => {
|
|
2348
|
+
try {
|
|
2349
|
+
const client = new hookstreamClient();
|
|
2350
|
+
const data = await client.get("/notification-channels");
|
|
2351
|
+
if (isJsonMode()) {
|
|
2352
|
+
printJson(data.channels);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
console.log();
|
|
2356
|
+
printTable(
|
|
2357
|
+
data.channels.map((ch) => ({
|
|
2358
|
+
id: ch.id.slice(0, 12),
|
|
2359
|
+
name: ch.name,
|
|
2360
|
+
type: ch.channel_type,
|
|
2361
|
+
status: statusColor(ch.enabled ? "active" : "disabled"),
|
|
2362
|
+
created: formatDate(ch.created_at)
|
|
2363
|
+
})),
|
|
2364
|
+
[
|
|
2365
|
+
{ key: "id", label: "ID", width: 14 },
|
|
2366
|
+
{ key: "name", label: "Name", width: 22 },
|
|
2367
|
+
{ key: "type", label: "Type", width: 10 },
|
|
2368
|
+
{ key: "status", label: "Status", width: 10 },
|
|
2369
|
+
{ key: "created", label: "Created" }
|
|
2370
|
+
]
|
|
2371
|
+
);
|
|
2372
|
+
console.log();
|
|
2373
|
+
} catch (err) {
|
|
2374
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2375
|
+
process.exit(1);
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
|
|
2379
|
+
// src/commands/channels/create.ts
|
|
2380
|
+
import { Command as Command62 } from "commander";
|
|
2381
|
+
var channelsCreateCommand = new Command62("create").description("Create a new notification channel").argument("<name>", "Channel name").requiredOption("--type <type>", "Channel type (webhook, slack, discord, email)").requiredOption("--url <url>", "Webhook URL").option("--config <json>", "Extra config as JSON").action(async (name, opts) => {
|
|
2382
|
+
try {
|
|
2383
|
+
const validTypes = ["webhook", "slack", "discord", "email"];
|
|
2384
|
+
if (!validTypes.includes(opts.type)) {
|
|
2385
|
+
printError(`Invalid type. Must be one of: ${validTypes.join(", ")}`);
|
|
2386
|
+
process.exit(1);
|
|
2387
|
+
}
|
|
2388
|
+
let extraConfig = {};
|
|
2389
|
+
if (opts.config) {
|
|
2390
|
+
try {
|
|
2391
|
+
extraConfig = JSON.parse(opts.config);
|
|
2392
|
+
} catch {
|
|
2393
|
+
printError("Invalid JSON for --config");
|
|
2394
|
+
process.exit(1);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
const config = {
|
|
2398
|
+
url: opts.url,
|
|
2399
|
+
...extraConfig
|
|
2400
|
+
};
|
|
2401
|
+
const client = new hookstreamClient();
|
|
2402
|
+
const data = await client.post("/notification-channels", {
|
|
2403
|
+
name,
|
|
2404
|
+
channel_type: opts.type,
|
|
2405
|
+
config
|
|
2406
|
+
});
|
|
2407
|
+
const ch = data.channel;
|
|
2408
|
+
if (isJsonMode()) {
|
|
2409
|
+
printJson(ch);
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
printSuccess(`Notification channel created: ${ch.name}`);
|
|
2413
|
+
console.log();
|
|
2414
|
+
printKeyValue([
|
|
2415
|
+
["ID", ch.id],
|
|
2416
|
+
["Name", ch.name],
|
|
2417
|
+
["Type", ch.channel_type]
|
|
2418
|
+
]);
|
|
2419
|
+
console.log();
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2422
|
+
process.exit(1);
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
// src/commands/channels/get.ts
|
|
2427
|
+
import { Command as Command63 } from "commander";
|
|
2428
|
+
var channelsGetCommand = new Command63("get").description("Get notification channel details").argument("<id>", "Channel ID").action(async (id) => {
|
|
2429
|
+
try {
|
|
2430
|
+
const client = new hookstreamClient();
|
|
2431
|
+
const data = await client.get(`/notification-channels/${id}`);
|
|
2432
|
+
const ch = data.channel;
|
|
2433
|
+
if (isJsonMode()) {
|
|
2434
|
+
printJson(ch);
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
console.log();
|
|
2438
|
+
printKeyValue([
|
|
2439
|
+
["ID", ch.id],
|
|
2440
|
+
["Name", ch.name],
|
|
2441
|
+
["Type", ch.channel_type],
|
|
2442
|
+
["Status", statusColor(ch.enabled ? "active" : "disabled")],
|
|
2443
|
+
["Config", ch.config || "{}"],
|
|
2444
|
+
["Last Sent", formatDate(ch.last_sent_at)],
|
|
2445
|
+
["Error Count", String(ch.error_count)],
|
|
2446
|
+
["Last Error", ch.last_error || "\u2014"],
|
|
2447
|
+
["Created", formatDate(ch.created_at)],
|
|
2448
|
+
["Updated", formatDate(ch.updated_at)]
|
|
2449
|
+
]);
|
|
2450
|
+
console.log();
|
|
2451
|
+
} catch (err) {
|
|
2452
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
// src/commands/channels/delete.ts
|
|
2458
|
+
import { createInterface as createInterface7 } from "readline/promises";
|
|
2459
|
+
import { stdin as stdin7, stdout as stdout7 } from "process";
|
|
2460
|
+
import { Command as Command64 } from "commander";
|
|
2461
|
+
var channelsDeleteCommand = new Command64("delete").description("Delete a notification channel").argument("<id>", "Channel ID").option("-f, --force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
2462
|
+
try {
|
|
2463
|
+
if (!opts.force) {
|
|
2464
|
+
const rl = createInterface7({ input: stdin7, output: stdout7 });
|
|
2465
|
+
const answer = await rl.question(`Delete notification channel ${id}? This cannot be undone. [y/N] `);
|
|
2466
|
+
rl.close();
|
|
2467
|
+
if (answer.toLowerCase() !== "y") {
|
|
2468
|
+
console.log("Cancelled.");
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
const client = new hookstreamClient();
|
|
2473
|
+
await client.del(`/notification-channels/${id}`);
|
|
2474
|
+
if (isJsonMode()) {
|
|
2475
|
+
printJson({ success: true, id });
|
|
2476
|
+
} else {
|
|
2477
|
+
printSuccess(`Notification channel ${id} deleted.`);
|
|
2478
|
+
}
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2481
|
+
process.exit(1);
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
// src/commands/channels/test.ts
|
|
2486
|
+
import { Command as Command65 } from "commander";
|
|
2487
|
+
var channelsTestCommand = new Command65("test").description("Send a test notification to a channel").argument("<id>", "Channel ID").action(async (id) => {
|
|
2488
|
+
try {
|
|
2489
|
+
const client = new hookstreamClient();
|
|
2490
|
+
const data = await client.post(`/notification-channels/${id}/test`);
|
|
2491
|
+
if (isJsonMode()) {
|
|
2492
|
+
printJson(data);
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
if (data.success) {
|
|
2496
|
+
printSuccess("Test notification sent successfully.");
|
|
2497
|
+
} else {
|
|
2498
|
+
printError(`Test notification failed: ${data.error || "unknown error"}`);
|
|
2499
|
+
process.exit(1);
|
|
2500
|
+
}
|
|
2501
|
+
} catch (err) {
|
|
2502
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2503
|
+
process.exit(1);
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
|
|
2507
|
+
// src/commands/channels/index.ts
|
|
2508
|
+
var channelsCommand = new Command66("channels").description("Manage notification channels").addCommand(channelsListCommand).addCommand(channelsCreateCommand).addCommand(channelsGetCommand).addCommand(channelsDeleteCommand).addCommand(channelsTestCommand);
|
|
2509
|
+
|
|
2510
|
+
// src/commands/collections/index.ts
|
|
2511
|
+
import { Command as Command71 } from "commander";
|
|
2512
|
+
|
|
2513
|
+
// src/commands/collections/list.ts
|
|
2514
|
+
import { Command as Command67 } from "commander";
|
|
2515
|
+
var collectionsListCommand = new Command67("list").description("List all collections").action(async () => {
|
|
2516
|
+
try {
|
|
2517
|
+
const client = new hookstreamClient();
|
|
2518
|
+
const data = await client.get("/collections");
|
|
2519
|
+
if (isJsonMode()) {
|
|
2520
|
+
printJson(data.collections);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
console.log();
|
|
2524
|
+
printTable(
|
|
2525
|
+
data.collections.map((c) => ({
|
|
2526
|
+
id: c.id.slice(0, 12),
|
|
2527
|
+
name: c.name,
|
|
2528
|
+
slug: c.slug,
|
|
2529
|
+
source: truncate(c.source_name || c.source_id, 16),
|
|
2530
|
+
records: String(c.record_count),
|
|
2531
|
+
created: formatDate(c.created_at)
|
|
2532
|
+
})),
|
|
2533
|
+
[
|
|
2534
|
+
{ key: "id", label: "ID", width: 14 },
|
|
2535
|
+
{ key: "name", label: "Name", width: 20 },
|
|
2536
|
+
{ key: "slug", label: "Slug", width: 16 },
|
|
2537
|
+
{ key: "source", label: "Source", width: 18 },
|
|
2538
|
+
{ key: "records", label: "Records", width: 10 },
|
|
2539
|
+
{ key: "created", label: "Created" }
|
|
2540
|
+
]
|
|
2541
|
+
);
|
|
2542
|
+
console.log();
|
|
2543
|
+
} catch (err) {
|
|
2544
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2545
|
+
process.exit(1);
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
// src/commands/collections/get.ts
|
|
2550
|
+
import { Command as Command68 } from "commander";
|
|
2551
|
+
var collectionsGetCommand = new Command68("get").description("Get collection details").argument("<id>", "Collection ID").action(async (id) => {
|
|
2552
|
+
try {
|
|
2553
|
+
const client = new hookstreamClient();
|
|
2554
|
+
const data = await client.get(`/collections/${id}`);
|
|
2555
|
+
const c = data.collection;
|
|
2556
|
+
if (isJsonMode()) {
|
|
2557
|
+
printJson(c);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
console.log();
|
|
2561
|
+
printKeyValue([
|
|
2562
|
+
["ID", c.id],
|
|
2563
|
+
["Name", c.name],
|
|
2564
|
+
["Slug", c.slug],
|
|
2565
|
+
["Status", statusColor(c.status)],
|
|
2566
|
+
["Source", c.source_name || c.source_id],
|
|
2567
|
+
["Primary Key", c.primary_key_path],
|
|
2568
|
+
["Event Type Field", c.event_type_field || "\u2014"],
|
|
2569
|
+
["Description", c.description || "\u2014"],
|
|
2570
|
+
["Records", String(c.record_count)],
|
|
2571
|
+
["Last Sync", formatDate(c.last_sync_at)],
|
|
2572
|
+
["Created", formatDate(c.created_at)],
|
|
2573
|
+
["Updated", formatDate(c.updated_at)]
|
|
2574
|
+
]);
|
|
2575
|
+
console.log();
|
|
2576
|
+
} catch (err) {
|
|
2577
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2578
|
+
process.exit(1);
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
|
|
2582
|
+
// src/commands/collections/stats.ts
|
|
2583
|
+
import { Command as Command69 } from "commander";
|
|
2584
|
+
var collectionsStatsCommand = new Command69("stats").description("Get collection statistics").argument("<id>", "Collection ID").action(async (id) => {
|
|
2585
|
+
try {
|
|
2586
|
+
const client = new hookstreamClient();
|
|
2587
|
+
const data = await client.get(`/collections/${id}/stats`);
|
|
2588
|
+
if (isJsonMode()) {
|
|
2589
|
+
printJson(data);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
console.log();
|
|
2593
|
+
printKeyValue([
|
|
2594
|
+
["Record Count", String(data.record_count)],
|
|
2595
|
+
["Changelog Entries", String(data.changelog_entries)],
|
|
2596
|
+
["Last Sync", formatDate(data.last_sync_at)]
|
|
2597
|
+
]);
|
|
2598
|
+
console.log();
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2601
|
+
process.exit(1);
|
|
2602
|
+
}
|
|
2603
|
+
});
|
|
2604
|
+
|
|
2605
|
+
// src/commands/collections/export.ts
|
|
2606
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2607
|
+
import { Command as Command70 } from "commander";
|
|
2608
|
+
var collectionsExportCommand = new Command70("export").description("Export collection data as NDJSON or CSV").argument("<id>", "Collection ID").option("--format <format>", "Export format (ndjson, csv)", "ndjson").option("--output <path>", "Output file path (prints to stdout if omitted)").action(async (id, opts) => {
|
|
2609
|
+
try {
|
|
2610
|
+
const validFormats = ["ndjson", "csv"];
|
|
2611
|
+
if (!validFormats.includes(opts.format)) {
|
|
2612
|
+
printError(`Invalid format. Must be one of: ${validFormats.join(", ")}`);
|
|
2613
|
+
process.exit(1);
|
|
2614
|
+
}
|
|
2615
|
+
const client = new hookstreamClient();
|
|
2616
|
+
const config = loadConfig();
|
|
2617
|
+
const apiKey = process.env.__HS_CLI_API_KEY || process.env.HOOKSTREAM_API_KEY || config.api_key || "";
|
|
2618
|
+
const apiFormat = opts.format === "ndjson" ? "json" : "csv";
|
|
2619
|
+
const url = `${client.getBaseUrl()}/v1/collections/${id}/export?format=${apiFormat}`;
|
|
2620
|
+
const res = await fetch(url, {
|
|
2621
|
+
headers: {
|
|
2622
|
+
"X-API-Key": apiKey
|
|
2623
|
+
}
|
|
2624
|
+
});
|
|
2625
|
+
if (!res.ok) {
|
|
2626
|
+
const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
2627
|
+
throw new Error(body.error || `HTTP ${res.status}`);
|
|
2628
|
+
}
|
|
2629
|
+
const text = await res.text();
|
|
2630
|
+
if (opts.output) {
|
|
2631
|
+
writeFileSync2(opts.output, text, "utf-8");
|
|
2632
|
+
if (isJsonMode()) {
|
|
2633
|
+
printJson({ success: true, path: opts.output, bytes: text.length });
|
|
2634
|
+
} else {
|
|
2635
|
+
printSuccess(`Exported to ${opts.output} (${text.length} bytes)`);
|
|
2636
|
+
}
|
|
2637
|
+
} else {
|
|
2638
|
+
process.stdout.write(text);
|
|
2639
|
+
}
|
|
2640
|
+
} catch (err) {
|
|
2641
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2642
|
+
process.exit(1);
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
|
|
2646
|
+
// src/commands/collections/index.ts
|
|
2647
|
+
var collectionsCommand = new Command71("collections").description("Manage instant database collections").addCommand(collectionsListCommand).addCommand(collectionsGetCommand).addCommand(collectionsStatsCommand).addCommand(collectionsExportCommand);
|
|
2648
|
+
|
|
2649
|
+
// src/commands/applications/index.ts
|
|
2650
|
+
import { Command as Command76 } from "commander";
|
|
2651
|
+
|
|
2652
|
+
// src/commands/applications/list.ts
|
|
2653
|
+
import { Command as Command72 } from "commander";
|
|
2654
|
+
var applicationsListCommand = new Command72("list").description("List all outbound webhook applications").option("--search <query>", "Search by name or UID").option("--limit <n>", "Max results", "50").action(async (opts) => {
|
|
2655
|
+
try {
|
|
2656
|
+
const client = new hookstreamClient();
|
|
2657
|
+
const params = new URLSearchParams();
|
|
2658
|
+
if (opts.search) params.set("search", opts.search);
|
|
2659
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
2660
|
+
const qs = params.toString();
|
|
2661
|
+
const data = await client.get(`/applications${qs ? `?${qs}` : ""}`);
|
|
2662
|
+
if (isJsonMode()) {
|
|
2663
|
+
printJson(data.applications);
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
console.log();
|
|
2667
|
+
printTable(
|
|
2668
|
+
data.applications.map((a) => ({
|
|
2669
|
+
id: a.id.slice(0, 12),
|
|
2670
|
+
name: a.name,
|
|
2671
|
+
status: statusColor(a.status),
|
|
2672
|
+
endpoints: String(a.endpoint_count),
|
|
2673
|
+
messages: String(a.message_count),
|
|
2674
|
+
created: formatDate(a.created_at)
|
|
2675
|
+
})),
|
|
2676
|
+
[
|
|
2677
|
+
{ key: "id", label: "ID", width: 14 },
|
|
2678
|
+
{ key: "name", label: "Name", width: 22 },
|
|
2679
|
+
{ key: "status", label: "Status", width: 10 },
|
|
2680
|
+
{ key: "endpoints", label: "Endpoints", width: 11 },
|
|
2681
|
+
{ key: "messages", label: "Messages", width: 10 },
|
|
2682
|
+
{ key: "created", label: "Created" }
|
|
2683
|
+
]
|
|
2684
|
+
);
|
|
2685
|
+
console.log();
|
|
2686
|
+
} catch (err) {
|
|
2687
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2688
|
+
process.exit(1);
|
|
2689
|
+
}
|
|
2690
|
+
});
|
|
2691
|
+
|
|
2692
|
+
// src/commands/applications/create.ts
|
|
2693
|
+
import { Command as Command73 } from "commander";
|
|
2694
|
+
var applicationsCreateCommand = new Command73("create").description("Create a new outbound webhook application").argument("<name>", "Application name").option("--uid <uid>", "Unique application identifier").option("--metadata <json>", "Application metadata as JSON").action(async (name, opts) => {
|
|
2695
|
+
try {
|
|
2696
|
+
let metadata;
|
|
2697
|
+
if (opts.metadata) {
|
|
2698
|
+
try {
|
|
2699
|
+
metadata = JSON.parse(opts.metadata);
|
|
2700
|
+
} catch {
|
|
2701
|
+
printError("Invalid JSON for --metadata");
|
|
2702
|
+
process.exit(1);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const uid = opts.uid || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2706
|
+
const client = new hookstreamClient();
|
|
2707
|
+
const data = await client.post("/applications", {
|
|
2708
|
+
name,
|
|
2709
|
+
uid,
|
|
2710
|
+
metadata
|
|
2711
|
+
});
|
|
2712
|
+
const a = data.application;
|
|
2713
|
+
if (isJsonMode()) {
|
|
2714
|
+
printJson(a);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
printSuccess(`Application created: ${a.name}`);
|
|
2718
|
+
console.log();
|
|
2719
|
+
printKeyValue([
|
|
2720
|
+
["ID", a.id],
|
|
2721
|
+
["Name", a.name],
|
|
2722
|
+
["UID", a.uid],
|
|
2723
|
+
["Status", a.status]
|
|
2724
|
+
]);
|
|
2725
|
+
console.log();
|
|
2726
|
+
} catch (err) {
|
|
2727
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2728
|
+
process.exit(1);
|
|
2729
|
+
}
|
|
2730
|
+
});
|
|
2731
|
+
|
|
2732
|
+
// src/commands/applications/get.ts
|
|
2733
|
+
import { Command as Command74 } from "commander";
|
|
2734
|
+
var applicationsGetCommand = new Command74("get").description("Get application details").argument("<id>", "Application ID").action(async (id) => {
|
|
2735
|
+
try {
|
|
2736
|
+
const client = new hookstreamClient();
|
|
2737
|
+
const data = await client.get(`/applications/${id}`);
|
|
2738
|
+
const a = data.application;
|
|
2739
|
+
if (isJsonMode()) {
|
|
2740
|
+
printJson(a);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
console.log();
|
|
2744
|
+
printKeyValue([
|
|
2745
|
+
["ID", a.id],
|
|
2746
|
+
["Name", a.name],
|
|
2747
|
+
["UID", a.uid],
|
|
2748
|
+
["Status", statusColor(a.status)],
|
|
2749
|
+
["Description", a.description || "\u2014"],
|
|
2750
|
+
["Metadata", a.metadata || "\u2014"],
|
|
2751
|
+
["Rate Limit", a.rate_limit ? String(a.rate_limit) : "\u2014"],
|
|
2752
|
+
["Endpoints", String(a.endpoint_count)],
|
|
2753
|
+
["Messages", String(a.message_count)],
|
|
2754
|
+
["Created", formatDate(a.created_at)],
|
|
2755
|
+
["Updated", formatDate(a.updated_at)]
|
|
2756
|
+
]);
|
|
2757
|
+
console.log();
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2760
|
+
process.exit(1);
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
|
|
2764
|
+
// src/commands/applications/delete.ts
|
|
2765
|
+
import { createInterface as createInterface8 } from "readline/promises";
|
|
2766
|
+
import { stdin as stdin8, stdout as stdout8 } from "process";
|
|
2767
|
+
import { Command as Command75 } from "commander";
|
|
2768
|
+
var applicationsDeleteCommand = new Command75("delete").description("Delete an application").argument("<id>", "Application ID").option("-f, --force", "Skip confirmation prompt").action(async (id, opts) => {
|
|
2769
|
+
try {
|
|
2770
|
+
if (!opts.force) {
|
|
2771
|
+
const rl = createInterface8({ input: stdin8, output: stdout8 });
|
|
2772
|
+
const answer = await rl.question(`Delete application ${id}? This will remove all endpoints and messages. [y/N] `);
|
|
2773
|
+
rl.close();
|
|
2774
|
+
if (answer.toLowerCase() !== "y") {
|
|
2775
|
+
console.log("Cancelled.");
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
const client = new hookstreamClient();
|
|
2780
|
+
await client.del(`/applications/${id}`);
|
|
2781
|
+
if (isJsonMode()) {
|
|
2782
|
+
printJson({ success: true, id });
|
|
2783
|
+
} else {
|
|
2784
|
+
printSuccess(`Application ${id} deleted.`);
|
|
2785
|
+
}
|
|
2786
|
+
} catch (err) {
|
|
2787
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
2788
|
+
process.exit(1);
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2792
|
+
// src/commands/applications/index.ts
|
|
2793
|
+
var applicationsCommand = new Command76("applications").description("Manage outbound webhook applications").addCommand(applicationsListCommand).addCommand(applicationsCreateCommand).addCommand(applicationsGetCommand).addCommand(applicationsDeleteCommand);
|
|
1953
2794
|
|
|
1954
2795
|
// src/index.ts
|
|
1955
|
-
var program = new
|
|
1956
|
-
program.name("hookstream").description("hookstream CLI \u2014 manage webhooks from the terminal").version("0.1
|
|
2796
|
+
var program = new Command77();
|
|
2797
|
+
program.name("hookstream").description("hookstream CLI \u2014 manage webhooks from the terminal").version("0.2.1").option("--json", "Output as JSON").option("--api-key <key>", "API key (overrides config + env)").option("--base-url <url>", "Base URL (overrides config + env)").hook("preAction", (thisCommand) => {
|
|
1957
2798
|
const opts = thisCommand.optsWithGlobals();
|
|
1958
2799
|
if (opts.json) {
|
|
1959
2800
|
setJsonMode(true);
|
|
1960
2801
|
}
|
|
2802
|
+
if (opts.apiKey) process.env.__HS_CLI_API_KEY = opts.apiKey;
|
|
2803
|
+
if (opts.baseUrl) process.env.__HS_CLI_BASE_URL = opts.baseUrl;
|
|
1961
2804
|
});
|
|
1962
2805
|
program.addCommand(loginCommand);
|
|
1963
2806
|
program.addCommand(logoutCommand);
|
|
@@ -1973,4 +2816,9 @@ program.addCommand(listenCommand);
|
|
|
1973
2816
|
program.addCommand(topicsCommand);
|
|
1974
2817
|
program.addCommand(replayCommand);
|
|
1975
2818
|
program.addCommand(billingCommand);
|
|
2819
|
+
program.addCommand(issuesCommand);
|
|
2820
|
+
program.addCommand(alertsCommand);
|
|
2821
|
+
program.addCommand(channelsCommand);
|
|
2822
|
+
program.addCommand(collectionsCommand);
|
|
2823
|
+
program.addCommand(applicationsCommand);
|
|
1976
2824
|
program.parse();
|