peakurl 1.0.3 → 1.1.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 +72 -28
- package/bin/peakurl.js +1464 -503
- package/man/peakurl.1 +48 -0
- package/package.json +14 -5
package/bin/peakurl.js
CHANGED
|
@@ -4,14 +4,6 @@
|
|
|
4
4
|
import { readFile as readFile3 } from "fs/promises";
|
|
5
5
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
6
6
|
|
|
7
|
-
// src/commands/core.ts
|
|
8
|
-
import { cwd } from "process";
|
|
9
|
-
|
|
10
|
-
// src/config/store.ts
|
|
11
|
-
import { chmod, mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
12
|
-
import { dirname, join } from "path";
|
|
13
|
-
import envPaths from "env-paths";
|
|
14
|
-
|
|
15
7
|
// src/lib/errors.ts
|
|
16
8
|
var CliError = class extends Error {
|
|
17
9
|
exitCode;
|
|
@@ -27,13 +19,682 @@ function ensureCliError(error) {
|
|
|
27
19
|
if (error instanceof CliError) {
|
|
28
20
|
return error;
|
|
29
21
|
}
|
|
30
|
-
if (error instanceof Error) {
|
|
31
|
-
return new CliError(error.message, 1, { cause: error });
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return new CliError(error.message, 1, { cause: error });
|
|
24
|
+
}
|
|
25
|
+
return new CliError("Unexpected error.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/lib/url.ts
|
|
29
|
+
function validateHttpUrl(parsed, label) {
|
|
30
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
31
|
+
throw new CliError(`${label} must use http or https.`);
|
|
32
|
+
}
|
|
33
|
+
if (parsed.username || parsed.password) {
|
|
34
|
+
throw new CliError(`${label} must not include embedded credentials.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function getApiBaseUrl(value) {
|
|
38
|
+
const input = value.trim();
|
|
39
|
+
if (!input) {
|
|
40
|
+
throw new CliError("A PeakURL API base URL is required.");
|
|
41
|
+
}
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = new URL(input);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new CliError(`Invalid API base URL: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
validateHttpUrl(parsed, "PeakURL API base URL");
|
|
49
|
+
parsed.hash = "";
|
|
50
|
+
parsed.search = "";
|
|
51
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
52
|
+
if (!/\/api\/v1$/i.test(pathname)) {
|
|
53
|
+
throw new CliError("PeakURL API base URL must end with /api/v1.");
|
|
54
|
+
}
|
|
55
|
+
return `${parsed.origin}${pathname}`;
|
|
56
|
+
}
|
|
57
|
+
function buildApiUrl(apiBaseUrl, path, query) {
|
|
58
|
+
const cleanBaseUrl = getApiBaseUrl(apiBaseUrl);
|
|
59
|
+
const cleanPath = path.replace(/^\/+/, "");
|
|
60
|
+
const url = new URL(cleanPath, `${cleanBaseUrl}/`);
|
|
61
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
62
|
+
if (value === void 0 || value === "") {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
url.searchParams.set(key, String(value));
|
|
66
|
+
}
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
function normalizeDestinationUrl(value) {
|
|
70
|
+
const input = value.trim();
|
|
71
|
+
if (!input) {
|
|
72
|
+
throw new CliError("A destination URL is required.");
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const parsed = new URL(input);
|
|
76
|
+
validateHttpUrl(parsed, "Destination URL");
|
|
77
|
+
return parsed.toString();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof CliError) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
throw new CliError(`Invalid destination URL: ${value}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function normalizeWebhookUrl(value) {
|
|
86
|
+
const input = value.trim();
|
|
87
|
+
if (!input) {
|
|
88
|
+
throw new CliError("A webhook URL is required.");
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const parsed = new URL(input);
|
|
92
|
+
validateHttpUrl(parsed, "Webhook URL");
|
|
93
|
+
return parsed.toString();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof CliError) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
throw new CliError(`Invalid webhook URL: ${value}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/api/client.ts
|
|
103
|
+
function isApiResponse(value) {
|
|
104
|
+
return Boolean(
|
|
105
|
+
value && typeof value === "object" && "success" in value && "message" in value && "timestamp" in value
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function networkError(apiBaseUrl, error) {
|
|
109
|
+
if (error instanceof Error && error.message) {
|
|
110
|
+
return `Could not reach PeakURL at ${apiBaseUrl}. ${error.message}`;
|
|
111
|
+
}
|
|
112
|
+
return `Could not reach PeakURL at ${apiBaseUrl}.`;
|
|
113
|
+
}
|
|
114
|
+
var ApiClient = class {
|
|
115
|
+
/**
|
|
116
|
+
* Creates a client bound to one resolved credential set.
|
|
117
|
+
*
|
|
118
|
+
* @param config Explicit API base URL plus bearer API key.
|
|
119
|
+
*/
|
|
120
|
+
constructor(config) {
|
|
121
|
+
this.config = config;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Loads the currently authenticated user.
|
|
125
|
+
*
|
|
126
|
+
* PeakURL accepts bearer API keys on `GET /users/me`, which is also the
|
|
127
|
+
* CLI login verification flow.
|
|
128
|
+
*
|
|
129
|
+
* @returns API response envelope containing the authenticated user.
|
|
130
|
+
*/
|
|
131
|
+
whoami() {
|
|
132
|
+
return this.request("GET", "users/me");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Loads the current system status snapshot for the authenticated site.
|
|
136
|
+
*
|
|
137
|
+
* @returns API response envelope containing system status sections.
|
|
138
|
+
*/
|
|
139
|
+
getStatus() {
|
|
140
|
+
return this.request("GET", "system/status");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates a short URL.
|
|
144
|
+
*
|
|
145
|
+
* @param payload Request body accepted by `POST /api/v1/urls`.
|
|
146
|
+
* @returns API response envelope containing the created link.
|
|
147
|
+
*/
|
|
148
|
+
createUrl(payload) {
|
|
149
|
+
return this.request("POST", "urls", payload);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Lists short URLs with optional pagination and filtering.
|
|
153
|
+
*
|
|
154
|
+
* The current PeakURL app returns `{ items, meta }` under `data`, but the
|
|
155
|
+
* CLI keeps a slightly broader compatibility type for future-proofing.
|
|
156
|
+
*
|
|
157
|
+
* @param query Optional query-string values.
|
|
158
|
+
* @returns API response envelope containing list data.
|
|
159
|
+
*/
|
|
160
|
+
listUrls(query) {
|
|
161
|
+
return this.request("GET", "urls", void 0, query);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Exports the full accessible link dataset for the authenticated user.
|
|
165
|
+
*
|
|
166
|
+
* @param query Optional search and sort values.
|
|
167
|
+
* @returns API response envelope containing the full export payload.
|
|
168
|
+
*/
|
|
169
|
+
exportUrls(query) {
|
|
170
|
+
return this.request(
|
|
171
|
+
"GET",
|
|
172
|
+
"urls/export",
|
|
173
|
+
void 0,
|
|
174
|
+
query
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Imports multiple short links in one bulk request.
|
|
179
|
+
*
|
|
180
|
+
* @param payload Request body accepted by `POST /api/v1/urls/bulk`.
|
|
181
|
+
* @returns API response envelope containing created rows plus row errors.
|
|
182
|
+
*/
|
|
183
|
+
importUrls(payload) {
|
|
184
|
+
return this.request("POST", "urls/bulk", payload);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Loads a single short URL by identifier or alias.
|
|
188
|
+
*
|
|
189
|
+
* PeakURL resolves IDs, short codes, and aliases through the same route.
|
|
190
|
+
*
|
|
191
|
+
* @param idOrAlias Link identifier, short code, or alias.
|
|
192
|
+
* @returns API response envelope containing the resolved link.
|
|
193
|
+
*/
|
|
194
|
+
getUrl(idOrAlias) {
|
|
195
|
+
return this.request(
|
|
196
|
+
"GET",
|
|
197
|
+
`urls/${encodeURIComponent(idOrAlias)}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Deletes a short URL by its stable row ID.
|
|
202
|
+
*
|
|
203
|
+
* The current PeakURL backend delete route expects the row ID. The CLI can
|
|
204
|
+
* still accept an alias at the command layer by resolving it first.
|
|
205
|
+
*
|
|
206
|
+
* @param id Stable link row ID.
|
|
207
|
+
* @returns API response envelope containing the deletion result.
|
|
208
|
+
*/
|
|
209
|
+
deleteUrl(id) {
|
|
210
|
+
return this.request(
|
|
211
|
+
"DELETE",
|
|
212
|
+
`urls/${encodeURIComponent(id)}`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Deletes multiple short URLs in a single bulk operation.
|
|
217
|
+
*
|
|
218
|
+
* @param ids Array of short URL IDs to delete.
|
|
219
|
+
* @returns API response envelope with deleted count.
|
|
220
|
+
*/
|
|
221
|
+
deleteUrlsBulk(ids) {
|
|
222
|
+
return this.request("DELETE", "urls/bulk", { ids });
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Deletes all accessible short URLs for the authenticated user.
|
|
226
|
+
*
|
|
227
|
+
* @returns API response envelope with deleted count.
|
|
228
|
+
*/
|
|
229
|
+
clearUrls() {
|
|
230
|
+
return this.request("DELETE", "urls");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Empties all short links currently in trash.
|
|
234
|
+
*
|
|
235
|
+
* @returns API response envelope with deleted count.
|
|
236
|
+
*/
|
|
237
|
+
emptyTrash() {
|
|
238
|
+
return this.request("DELETE", "urls/trash");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Lists audit log activity entries.
|
|
242
|
+
*
|
|
243
|
+
* @param query Optional query-string parameters for pagination or filters.
|
|
244
|
+
* @returns API response envelope containing activity items and meta.
|
|
245
|
+
*/
|
|
246
|
+
listActivity(query) {
|
|
247
|
+
return this.request(
|
|
248
|
+
"GET",
|
|
249
|
+
"analytics/activity",
|
|
250
|
+
void 0,
|
|
251
|
+
query
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Deletes a single audit log activity entry by its row ID.
|
|
256
|
+
*
|
|
257
|
+
* @param id Audit log row ID.
|
|
258
|
+
* @returns API response envelope confirming deletion.
|
|
259
|
+
*/
|
|
260
|
+
deleteActivity(id) {
|
|
261
|
+
return this.request(
|
|
262
|
+
"DELETE",
|
|
263
|
+
`analytics/activity/${encodeURIComponent(id)}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Deletes multiple audit log activity entries in bulk.
|
|
268
|
+
*
|
|
269
|
+
* @param ids Array of audit log row IDs.
|
|
270
|
+
* @returns API response envelope with deleted count.
|
|
271
|
+
*/
|
|
272
|
+
deleteActivityBulk(ids) {
|
|
273
|
+
return this.request(
|
|
274
|
+
"DELETE",
|
|
275
|
+
"analytics/activity/bulk",
|
|
276
|
+
{ ids }
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Clears all audit log activity records.
|
|
281
|
+
*
|
|
282
|
+
* @returns API response envelope confirming all logs were deleted.
|
|
283
|
+
*/
|
|
284
|
+
clearActivity() {
|
|
285
|
+
return this.request("DELETE", "analytics/activity");
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Lists outbound webhooks for the authenticated user.
|
|
289
|
+
*
|
|
290
|
+
* @returns API response envelope containing webhook rows.
|
|
291
|
+
*/
|
|
292
|
+
listWebhooks() {
|
|
293
|
+
return this.request("GET", "webhooks");
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Creates one outbound webhook subscription.
|
|
297
|
+
*
|
|
298
|
+
* @param payload Request body accepted by `POST /api/v1/webhooks`.
|
|
299
|
+
* @returns API response envelope containing the created webhook.
|
|
300
|
+
*/
|
|
301
|
+
createWebhook(payload) {
|
|
302
|
+
return this.request("POST", "webhooks", payload);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Deletes one webhook by its stable row ID.
|
|
306
|
+
*
|
|
307
|
+
* @param id Webhook identifier returned by the list/create endpoints.
|
|
308
|
+
* @returns API response envelope containing the deletion result.
|
|
309
|
+
*/
|
|
310
|
+
deleteWebhook(id) {
|
|
311
|
+
return this.request(
|
|
312
|
+
"DELETE",
|
|
313
|
+
`webhooks/${encodeURIComponent(id)}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Loads the current status of the cron scheduler.
|
|
318
|
+
*
|
|
319
|
+
* @returns API response envelope containing the scheduler status.
|
|
320
|
+
*/
|
|
321
|
+
getJobStatus() {
|
|
322
|
+
return this.request("GET", "system/cron");
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Triggers all due cron jobs to run.
|
|
326
|
+
*
|
|
327
|
+
* @returns API response envelope containing the execution results.
|
|
328
|
+
*/
|
|
329
|
+
runDueJobs() {
|
|
330
|
+
return this.request("POST", "system/cron/run");
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Forces a specific cron job to run immediately.
|
|
334
|
+
*
|
|
335
|
+
* @param id Job identifier.
|
|
336
|
+
* @returns API response envelope containing the execution result.
|
|
337
|
+
*/
|
|
338
|
+
runJob(id) {
|
|
339
|
+
return this.request(
|
|
340
|
+
"POST",
|
|
341
|
+
`system/cron/run/${encodeURIComponent(id)}`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Clears cron execution history.
|
|
346
|
+
*
|
|
347
|
+
* @param jobId Optional job identifier to clear history only for one job.
|
|
348
|
+
* @returns API response envelope containing the deleted count.
|
|
349
|
+
*/
|
|
350
|
+
clearJobHistory(jobId) {
|
|
351
|
+
return this.request(
|
|
352
|
+
"POST",
|
|
353
|
+
"system/cron/history/clear",
|
|
354
|
+
jobId ? { job_id: jobId } : void 0
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Updates the schedule configuration for a cron job.
|
|
359
|
+
*
|
|
360
|
+
* @param id Job identifier.
|
|
361
|
+
* @param payload New configuration options.
|
|
362
|
+
* @returns API response envelope containing the updated job.
|
|
363
|
+
*/
|
|
364
|
+
updateJobSchedule(id, payload) {
|
|
365
|
+
return this.request(
|
|
366
|
+
"PATCH",
|
|
367
|
+
`system/cron/jobs/${encodeURIComponent(id)}`,
|
|
368
|
+
payload
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Resets a cron job schedule to its default configuration.
|
|
373
|
+
*
|
|
374
|
+
* @param id Job identifier.
|
|
375
|
+
* @returns API response envelope containing the restored job.
|
|
376
|
+
*/
|
|
377
|
+
resetJobSchedule(id) {
|
|
378
|
+
return this.request(
|
|
379
|
+
"POST",
|
|
380
|
+
`system/cron/jobs/${encodeURIComponent(id)}/reset`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Updates the global cron settings, such as history retention.
|
|
385
|
+
*
|
|
386
|
+
* @param payload New global settings.
|
|
387
|
+
* @returns API response envelope containing the updated retention settings.
|
|
388
|
+
*/
|
|
389
|
+
updateJobSettings(payload) {
|
|
390
|
+
return this.request(
|
|
391
|
+
"POST",
|
|
392
|
+
"system/cron/settings",
|
|
393
|
+
payload
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Performs one authenticated API request and normalizes the response.
|
|
398
|
+
*
|
|
399
|
+
* @param method HTTP method to send.
|
|
400
|
+
* @param path Route path relative to `/api/v1`.
|
|
401
|
+
* @param body Optional JSON body.
|
|
402
|
+
* @param query Optional query-string values.
|
|
403
|
+
* @returns Parsed PeakURL response envelope.
|
|
404
|
+
* @throws {CliError} When the network request fails or the API returns an error.
|
|
405
|
+
*/
|
|
406
|
+
async request(method, path, body, query) {
|
|
407
|
+
const url = buildApiUrl(this.config.apiBaseUrl, path, query);
|
|
408
|
+
let response;
|
|
409
|
+
try {
|
|
410
|
+
response = await fetch(url, {
|
|
411
|
+
method,
|
|
412
|
+
headers: {
|
|
413
|
+
Accept: "application/json",
|
|
414
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
415
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
416
|
+
},
|
|
417
|
+
body: body ? JSON.stringify(body) : void 0
|
|
418
|
+
});
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new CliError(networkError(this.config.apiBaseUrl, error), 1, {
|
|
421
|
+
cause: error instanceof Error ? error : void 0
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
const rawText = await response.text();
|
|
425
|
+
if (!rawText) {
|
|
426
|
+
if (!response.ok) {
|
|
427
|
+
throw new CliError(
|
|
428
|
+
`PeakURL request failed with HTTP ${response.status}.`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
success: true,
|
|
433
|
+
message: "Request completed.",
|
|
434
|
+
data: void 0,
|
|
435
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
let parsed;
|
|
439
|
+
try {
|
|
440
|
+
parsed = JSON.parse(rawText);
|
|
441
|
+
} catch {
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
throw new CliError(
|
|
444
|
+
`PeakURL request failed with HTTP ${response.status}.`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
throw new CliError("PeakURL returned an invalid JSON response.");
|
|
448
|
+
}
|
|
449
|
+
if (!isApiResponse(parsed)) {
|
|
450
|
+
throw new CliError(
|
|
451
|
+
"PeakURL returned an unexpected response envelope."
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (!response.ok || !parsed.success) {
|
|
455
|
+
const statusCode = response.status === 401 ? 2 : 1;
|
|
456
|
+
throw new CliError(
|
|
457
|
+
parsed.message || `PeakURL request failed with HTTP ${response.status}.`,
|
|
458
|
+
statusCode
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
return parsed;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// src/lib/output.ts
|
|
466
|
+
function writeStdout(message = "") {
|
|
467
|
+
process.stdout.write(`${message}
|
|
468
|
+
`);
|
|
469
|
+
}
|
|
470
|
+
function writeStderr(message = "") {
|
|
471
|
+
process.stderr.write(`${message}
|
|
472
|
+
`);
|
|
473
|
+
}
|
|
474
|
+
function outputStream(target) {
|
|
475
|
+
return target === "stdout" ? process.stdout : process.stderr;
|
|
476
|
+
}
|
|
477
|
+
function useColor(target) {
|
|
478
|
+
return Boolean(outputStream(target).isTTY && !process.env.NO_COLOR);
|
|
479
|
+
}
|
|
480
|
+
function successLine(message, target = "stdout") {
|
|
481
|
+
const label = useColor(target) ? "\x1B[32mSuccess\x1B[39m" : "Success";
|
|
482
|
+
return `${label}: ${message}`;
|
|
483
|
+
}
|
|
484
|
+
function errorLine(message, target = "stderr") {
|
|
485
|
+
const label = useColor(target) ? "\x1B[31mError\x1B[39m" : "Error";
|
|
486
|
+
return `${label}: ${message}`;
|
|
487
|
+
}
|
|
488
|
+
function writeNoticeBox(title, lines, target = "stderr") {
|
|
489
|
+
const contentLines = lines.length > 0 ? lines : [""];
|
|
490
|
+
const width = Math.max(
|
|
491
|
+
title.length,
|
|
492
|
+
...contentLines.map((line) => line.length)
|
|
493
|
+
);
|
|
494
|
+
const stream = outputStream(target);
|
|
495
|
+
const useTuiBox = stream.isTTY;
|
|
496
|
+
const border = useTuiBox ? {
|
|
497
|
+
topLeft: "\u250C",
|
|
498
|
+
topRight: "\u2510",
|
|
499
|
+
bottomLeft: "\u2514",
|
|
500
|
+
bottomRight: "\u2518",
|
|
501
|
+
horizontal: "\u2500",
|
|
502
|
+
vertical: "\u2502",
|
|
503
|
+
separatorLeft: "\u251C",
|
|
504
|
+
separatorRight: "\u2524"
|
|
505
|
+
} : {
|
|
506
|
+
topLeft: "+",
|
|
507
|
+
topRight: "+",
|
|
508
|
+
bottomLeft: "+",
|
|
509
|
+
bottomRight: "+",
|
|
510
|
+
horizontal: "-",
|
|
511
|
+
vertical: "|",
|
|
512
|
+
separatorLeft: "+",
|
|
513
|
+
separatorRight: "+"
|
|
514
|
+
};
|
|
515
|
+
const topBorder = `${border.topLeft}${border.horizontal.repeat(width + 2)}${border.topRight}`;
|
|
516
|
+
const separator = `${border.separatorLeft}${border.horizontal.repeat(width + 2)}${border.separatorRight}`;
|
|
517
|
+
const bottomBorder = `${border.bottomLeft}${border.horizontal.repeat(width + 2)}${border.bottomRight}`;
|
|
518
|
+
const writeLine = target === "stdout" ? writeStdout : writeStderr;
|
|
519
|
+
writeLine(topBorder);
|
|
520
|
+
writeLine(`${border.vertical} ${title.padEnd(width)} ${border.vertical}`);
|
|
521
|
+
writeLine(separator);
|
|
522
|
+
for (const line of contentLines) {
|
|
523
|
+
writeLine(
|
|
524
|
+
`${border.vertical} ${line.padEnd(width)} ${border.vertical}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
writeLine(bottomBorder);
|
|
528
|
+
}
|
|
529
|
+
function formatTable(headers, rows2, target = "stdout") {
|
|
530
|
+
const stream = outputStream(target);
|
|
531
|
+
const useTuiBox = stream.isTTY;
|
|
532
|
+
const border = useTuiBox ? {
|
|
533
|
+
topLeft: "\u250C",
|
|
534
|
+
topRight: "\u2510",
|
|
535
|
+
bottomLeft: "\u2514",
|
|
536
|
+
bottomRight: "\u2518",
|
|
537
|
+
horizontal: "\u2500",
|
|
538
|
+
vertical: "\u2502",
|
|
539
|
+
separatorLeft: "\u251C",
|
|
540
|
+
separatorRight: "\u2524",
|
|
541
|
+
topJunction: "\u252C",
|
|
542
|
+
middleJunction: "\u253C",
|
|
543
|
+
bottomJunction: "\u2534"
|
|
544
|
+
} : {
|
|
545
|
+
topLeft: "+",
|
|
546
|
+
topRight: "+",
|
|
547
|
+
bottomLeft: "+",
|
|
548
|
+
bottomRight: "+",
|
|
549
|
+
horizontal: "-",
|
|
550
|
+
vertical: "|",
|
|
551
|
+
separatorLeft: "+",
|
|
552
|
+
separatorRight: "+",
|
|
553
|
+
topJunction: "+",
|
|
554
|
+
middleJunction: "+",
|
|
555
|
+
bottomJunction: "+"
|
|
556
|
+
};
|
|
557
|
+
const getLines = (value) => (value ?? "").split("\n");
|
|
558
|
+
const widths = headers.map(
|
|
559
|
+
(header, index) => Math.max(
|
|
560
|
+
header.length,
|
|
561
|
+
...rows2.flatMap(
|
|
562
|
+
(row2) => getLines(row2[index]).map((line) => line.length)
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
const formatTableBorder = (left, join3, right) => `${left}${widths.map((width) => border.horizontal.repeat(width + 2)).join(join3)}${right}`;
|
|
567
|
+
const formatTableRow = (cells) => {
|
|
568
|
+
const linesByCell = cells.map(getLines);
|
|
569
|
+
const rowHeight = Math.max(...linesByCell.map((lines) => lines.length));
|
|
570
|
+
return Array.from(
|
|
571
|
+
{ length: rowHeight },
|
|
572
|
+
(_value, rowIndex) => `${border.vertical}${linesByCell.map(
|
|
573
|
+
(lines, cellIndex) => ` ${(lines[rowIndex] ?? "").padEnd(widths[cellIndex])} `
|
|
574
|
+
).join(border.vertical)}${border.vertical}`
|
|
575
|
+
).join("\n");
|
|
576
|
+
};
|
|
577
|
+
return [
|
|
578
|
+
formatTableBorder(border.topLeft, border.topJunction, border.topRight),
|
|
579
|
+
formatTableRow(headers),
|
|
580
|
+
formatTableBorder(
|
|
581
|
+
border.separatorLeft,
|
|
582
|
+
border.middleJunction,
|
|
583
|
+
border.separatorRight
|
|
584
|
+
),
|
|
585
|
+
...rows2.map(formatTableRow),
|
|
586
|
+
formatTableBorder(
|
|
587
|
+
border.bottomLeft,
|
|
588
|
+
border.bottomJunction,
|
|
589
|
+
border.bottomRight
|
|
590
|
+
)
|
|
591
|
+
].join("\n");
|
|
592
|
+
}
|
|
593
|
+
function formatDetailsTable(rows2, target = "stdout") {
|
|
594
|
+
return formatTable(["Detail", "Information"], rows2, target);
|
|
595
|
+
}
|
|
596
|
+
function writeJson(value) {
|
|
597
|
+
writeStdout(JSON.stringify(value, null, 2));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/lib/activity.ts
|
|
601
|
+
var ACTIVITY_LIST_KEYS = [
|
|
602
|
+
"items",
|
|
603
|
+
"results",
|
|
604
|
+
"activities",
|
|
605
|
+
"history"
|
|
606
|
+
];
|
|
607
|
+
function asObject(value) {
|
|
608
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
609
|
+
}
|
|
610
|
+
function asString(value) {
|
|
611
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
612
|
+
}
|
|
613
|
+
function asNumber(value) {
|
|
614
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
615
|
+
}
|
|
616
|
+
function truncate(value, maxLength) {
|
|
617
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
|
|
618
|
+
}
|
|
619
|
+
function getActivityMeta(data) {
|
|
620
|
+
const record = asObject(data);
|
|
621
|
+
if (!record) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
const meta = asObject(record.meta);
|
|
625
|
+
if (meta) {
|
|
626
|
+
return {
|
|
627
|
+
page: asNumber(meta.page),
|
|
628
|
+
limit: asNumber(meta.limit),
|
|
629
|
+
totalItems: asNumber(meta.totalItems),
|
|
630
|
+
totalPages: asNumber(meta.totalPages)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
page: asNumber(record.page),
|
|
635
|
+
limit: asNumber(record.limit),
|
|
636
|
+
totalItems: asNumber(record.total),
|
|
637
|
+
totalPages: asNumber(record.totalPages)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function extractActivity(data) {
|
|
641
|
+
if (Array.isArray(data)) {
|
|
642
|
+
return data;
|
|
643
|
+
}
|
|
644
|
+
const record = asObject(data);
|
|
645
|
+
if (record) {
|
|
646
|
+
for (const key of ACTIVITY_LIST_KEYS) {
|
|
647
|
+
const value = record[key];
|
|
648
|
+
if (Array.isArray(value)) {
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
655
|
+
function formatActivityTable(items) {
|
|
656
|
+
if (items.length === 0) {
|
|
657
|
+
return "No activity logs found.";
|
|
658
|
+
}
|
|
659
|
+
const headers = [
|
|
660
|
+
"ID",
|
|
661
|
+
"Action",
|
|
662
|
+
"User",
|
|
663
|
+
"IP Address",
|
|
664
|
+
"Timestamp",
|
|
665
|
+
"Message"
|
|
666
|
+
];
|
|
667
|
+
const rows2 = items.map((item) => [
|
|
668
|
+
truncate(asString(item.id) || "-", 18),
|
|
669
|
+
truncate(asString(item.type) || "-", 18),
|
|
670
|
+
truncate(
|
|
671
|
+
asString(item.userName) || asString(item.userEmail) || "-",
|
|
672
|
+
18
|
|
673
|
+
),
|
|
674
|
+
truncate(asString(item.ipAddress) || "-", 16),
|
|
675
|
+
truncate(asString(item.createdAt) || "-", 22),
|
|
676
|
+
truncate(asString(item.message) || "-", 40)
|
|
677
|
+
]);
|
|
678
|
+
return formatTable(headers, rows2);
|
|
679
|
+
}
|
|
680
|
+
function formatActivitySummary(data, count) {
|
|
681
|
+
const meta = getActivityMeta(data);
|
|
682
|
+
if (!meta) {
|
|
683
|
+
return `${count} activity record${count === 1 ? "" : "s"} returned.`;
|
|
32
684
|
}
|
|
33
|
-
|
|
685
|
+
const total = meta.totalItems;
|
|
686
|
+
const page = meta.page;
|
|
687
|
+
const totalPages = meta.totalPages;
|
|
688
|
+
if (total !== void 0 && page !== void 0 && totalPages !== void 0) {
|
|
689
|
+
return `Page ${page} of ${totalPages}. ${total} total activity record${total === 1 ? "" : "s"}.`;
|
|
690
|
+
}
|
|
691
|
+
return `${count} activity record${count === 1 ? "" : "s"} returned.`;
|
|
34
692
|
}
|
|
35
693
|
|
|
36
694
|
// src/config/store.ts
|
|
695
|
+
import { chmod, mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
696
|
+
import { dirname, join } from "path";
|
|
697
|
+
import envPaths from "env-paths";
|
|
37
698
|
var CONFIG_FILENAME = "config.json";
|
|
38
699
|
var STATE_FILENAME = "state.json";
|
|
39
700
|
function getConfigPath() {
|
|
@@ -186,80 +847,6 @@ var StateStore = class {
|
|
|
186
847
|
}
|
|
187
848
|
};
|
|
188
849
|
|
|
189
|
-
// src/lib/url.ts
|
|
190
|
-
function validateHttpUrl(parsed, label) {
|
|
191
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
192
|
-
throw new CliError(`${label} must use http or https.`);
|
|
193
|
-
}
|
|
194
|
-
if (parsed.username || parsed.password) {
|
|
195
|
-
throw new CliError(`${label} must not include embedded credentials.`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function getApiBaseUrl(value) {
|
|
199
|
-
const input = value.trim();
|
|
200
|
-
if (!input) {
|
|
201
|
-
throw new CliError("A PeakURL API base URL is required.");
|
|
202
|
-
}
|
|
203
|
-
let parsed;
|
|
204
|
-
try {
|
|
205
|
-
parsed = new URL(input);
|
|
206
|
-
} catch {
|
|
207
|
-
throw new CliError(`Invalid API base URL: ${value}`);
|
|
208
|
-
}
|
|
209
|
-
validateHttpUrl(parsed, "PeakURL API base URL");
|
|
210
|
-
parsed.hash = "";
|
|
211
|
-
parsed.search = "";
|
|
212
|
-
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
213
|
-
if (!/\/api\/v1$/i.test(pathname)) {
|
|
214
|
-
throw new CliError("PeakURL API base URL must end with /api/v1.");
|
|
215
|
-
}
|
|
216
|
-
return `${parsed.origin}${pathname}`;
|
|
217
|
-
}
|
|
218
|
-
function buildApiUrl(apiBaseUrl, path, query) {
|
|
219
|
-
const cleanBaseUrl = getApiBaseUrl(apiBaseUrl);
|
|
220
|
-
const cleanPath = path.replace(/^\/+/, "");
|
|
221
|
-
const url = new URL(cleanPath, `${cleanBaseUrl}/`);
|
|
222
|
-
for (const [key, value] of Object.entries(query ?? {})) {
|
|
223
|
-
if (value === void 0 || value === "") {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
url.searchParams.set(key, String(value));
|
|
227
|
-
}
|
|
228
|
-
return url.toString();
|
|
229
|
-
}
|
|
230
|
-
function normalizeDestinationUrl(value) {
|
|
231
|
-
const input = value.trim();
|
|
232
|
-
if (!input) {
|
|
233
|
-
throw new CliError("A destination URL is required.");
|
|
234
|
-
}
|
|
235
|
-
try {
|
|
236
|
-
const parsed = new URL(input);
|
|
237
|
-
validateHttpUrl(parsed, "Destination URL");
|
|
238
|
-
return parsed.toString();
|
|
239
|
-
} catch (error) {
|
|
240
|
-
if (error instanceof CliError) {
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
throw new CliError(`Invalid destination URL: ${value}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function normalizeWebhookUrl(value) {
|
|
247
|
-
const input = value.trim();
|
|
248
|
-
if (!input) {
|
|
249
|
-
throw new CliError("A webhook URL is required.");
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
const parsed = new URL(input);
|
|
253
|
-
validateHttpUrl(parsed, "Webhook URL");
|
|
254
|
-
return parsed.toString();
|
|
255
|
-
} catch (error) {
|
|
256
|
-
if (error instanceof CliError) {
|
|
257
|
-
throw error;
|
|
258
|
-
}
|
|
259
|
-
throw new CliError(`Invalid webhook URL: ${value}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
850
|
// src/lib/auth.ts
|
|
264
851
|
var AUTH_REQUIRED_MESSAGE = "PeakURL credentials are not configured.";
|
|
265
852
|
var EXAMPLE_BASE_URL = "https://example.com/api/v1";
|
|
@@ -326,143 +913,6 @@ import { tmpdir } from "os";
|
|
|
326
913
|
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
327
914
|
import { posix as pathPosix } from "path";
|
|
328
915
|
import { inflateRawSync } from "zlib";
|
|
329
|
-
|
|
330
|
-
// src/lib/output.ts
|
|
331
|
-
function writeStdout(message = "") {
|
|
332
|
-
process.stdout.write(`${message}
|
|
333
|
-
`);
|
|
334
|
-
}
|
|
335
|
-
function writeStderr(message = "") {
|
|
336
|
-
process.stderr.write(`${message}
|
|
337
|
-
`);
|
|
338
|
-
}
|
|
339
|
-
function outputStream(target) {
|
|
340
|
-
return target === "stdout" ? process.stdout : process.stderr;
|
|
341
|
-
}
|
|
342
|
-
function useColor(target) {
|
|
343
|
-
return Boolean(outputStream(target).isTTY && !process.env.NO_COLOR);
|
|
344
|
-
}
|
|
345
|
-
function successLine(message, target = "stdout") {
|
|
346
|
-
const label = useColor(target) ? "\x1B[32mSuccess\x1B[39m" : "Success";
|
|
347
|
-
return `${label}: ${message}`;
|
|
348
|
-
}
|
|
349
|
-
function errorLine(message, target = "stderr") {
|
|
350
|
-
const label = useColor(target) ? "\x1B[31mError\x1B[39m" : "Error";
|
|
351
|
-
return `${label}: ${message}`;
|
|
352
|
-
}
|
|
353
|
-
function writeNoticeBox(title, lines, target = "stderr") {
|
|
354
|
-
const contentLines = lines.length > 0 ? lines : [""];
|
|
355
|
-
const width = Math.max(
|
|
356
|
-
title.length,
|
|
357
|
-
...contentLines.map((line) => line.length)
|
|
358
|
-
);
|
|
359
|
-
const stream = outputStream(target);
|
|
360
|
-
const useTuiBox = stream.isTTY;
|
|
361
|
-
const border = useTuiBox ? {
|
|
362
|
-
topLeft: "\u250C",
|
|
363
|
-
topRight: "\u2510",
|
|
364
|
-
bottomLeft: "\u2514",
|
|
365
|
-
bottomRight: "\u2518",
|
|
366
|
-
horizontal: "\u2500",
|
|
367
|
-
vertical: "\u2502",
|
|
368
|
-
separatorLeft: "\u251C",
|
|
369
|
-
separatorRight: "\u2524"
|
|
370
|
-
} : {
|
|
371
|
-
topLeft: "+",
|
|
372
|
-
topRight: "+",
|
|
373
|
-
bottomLeft: "+",
|
|
374
|
-
bottomRight: "+",
|
|
375
|
-
horizontal: "-",
|
|
376
|
-
vertical: "|",
|
|
377
|
-
separatorLeft: "+",
|
|
378
|
-
separatorRight: "+"
|
|
379
|
-
};
|
|
380
|
-
const topBorder = `${border.topLeft}${border.horizontal.repeat(width + 2)}${border.topRight}`;
|
|
381
|
-
const separator = `${border.separatorLeft}${border.horizontal.repeat(width + 2)}${border.separatorRight}`;
|
|
382
|
-
const bottomBorder = `${border.bottomLeft}${border.horizontal.repeat(width + 2)}${border.bottomRight}`;
|
|
383
|
-
const writeLine = target === "stdout" ? writeStdout : writeStderr;
|
|
384
|
-
writeLine(topBorder);
|
|
385
|
-
writeLine(`${border.vertical} ${title.padEnd(width)} ${border.vertical}`);
|
|
386
|
-
writeLine(separator);
|
|
387
|
-
for (const line of contentLines) {
|
|
388
|
-
writeLine(
|
|
389
|
-
`${border.vertical} ${line.padEnd(width)} ${border.vertical}`
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
writeLine(bottomBorder);
|
|
393
|
-
}
|
|
394
|
-
function formatTable(headers, rows2, target = "stdout") {
|
|
395
|
-
const stream = outputStream(target);
|
|
396
|
-
const useTuiBox = stream.isTTY;
|
|
397
|
-
const border = useTuiBox ? {
|
|
398
|
-
topLeft: "\u250C",
|
|
399
|
-
topRight: "\u2510",
|
|
400
|
-
bottomLeft: "\u2514",
|
|
401
|
-
bottomRight: "\u2518",
|
|
402
|
-
horizontal: "\u2500",
|
|
403
|
-
vertical: "\u2502",
|
|
404
|
-
separatorLeft: "\u251C",
|
|
405
|
-
separatorRight: "\u2524",
|
|
406
|
-
topJunction: "\u252C",
|
|
407
|
-
middleJunction: "\u253C",
|
|
408
|
-
bottomJunction: "\u2534"
|
|
409
|
-
} : {
|
|
410
|
-
topLeft: "+",
|
|
411
|
-
topRight: "+",
|
|
412
|
-
bottomLeft: "+",
|
|
413
|
-
bottomRight: "+",
|
|
414
|
-
horizontal: "-",
|
|
415
|
-
vertical: "|",
|
|
416
|
-
separatorLeft: "+",
|
|
417
|
-
separatorRight: "+",
|
|
418
|
-
topJunction: "+",
|
|
419
|
-
middleJunction: "+",
|
|
420
|
-
bottomJunction: "+"
|
|
421
|
-
};
|
|
422
|
-
const getLines = (value) => (value ?? "").split("\n");
|
|
423
|
-
const widths = headers.map(
|
|
424
|
-
(header, index) => Math.max(
|
|
425
|
-
header.length,
|
|
426
|
-
...rows2.flatMap(
|
|
427
|
-
(row2) => getLines(row2[index]).map((line) => line.length)
|
|
428
|
-
)
|
|
429
|
-
)
|
|
430
|
-
);
|
|
431
|
-
const formatTableBorder = (left, join3, right) => `${left}${widths.map((width) => border.horizontal.repeat(width + 2)).join(join3)}${right}`;
|
|
432
|
-
const formatTableRow = (cells) => {
|
|
433
|
-
const linesByCell = cells.map(getLines);
|
|
434
|
-
const rowHeight = Math.max(...linesByCell.map((lines) => lines.length));
|
|
435
|
-
return Array.from(
|
|
436
|
-
{ length: rowHeight },
|
|
437
|
-
(_value, rowIndex) => `${border.vertical}${linesByCell.map(
|
|
438
|
-
(lines, cellIndex) => ` ${(lines[rowIndex] ?? "").padEnd(widths[cellIndex])} `
|
|
439
|
-
).join(border.vertical)}${border.vertical}`
|
|
440
|
-
).join("\n");
|
|
441
|
-
};
|
|
442
|
-
return [
|
|
443
|
-
formatTableBorder(border.topLeft, border.topJunction, border.topRight),
|
|
444
|
-
formatTableRow(headers),
|
|
445
|
-
formatTableBorder(
|
|
446
|
-
border.separatorLeft,
|
|
447
|
-
border.middleJunction,
|
|
448
|
-
border.separatorRight
|
|
449
|
-
),
|
|
450
|
-
...rows2.map(formatTableRow),
|
|
451
|
-
formatTableBorder(
|
|
452
|
-
border.bottomLeft,
|
|
453
|
-
border.bottomJunction,
|
|
454
|
-
border.bottomRight
|
|
455
|
-
)
|
|
456
|
-
].join("\n");
|
|
457
|
-
}
|
|
458
|
-
function formatDetailsTable(rows2, target = "stdout") {
|
|
459
|
-
return formatTable(["Detail", "Information"], rows2, target);
|
|
460
|
-
}
|
|
461
|
-
function writeJson(value) {
|
|
462
|
-
writeStdout(JSON.stringify(value, null, 2));
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/lib/core.ts
|
|
466
916
|
var DEFAULT_RELEASE_API_URL = "https://api.peakurl.org/v1/update";
|
|
467
917
|
var DEFAULT_CORE_PACKAGE_URL = "https://peakurl.org/latest.zip";
|
|
468
918
|
var EOCD_SIGNATURE = 101010256;
|
|
@@ -481,7 +931,7 @@ function getCorePackageUrl(env) {
|
|
|
481
931
|
"package download"
|
|
482
932
|
);
|
|
483
933
|
}
|
|
484
|
-
function
|
|
934
|
+
function asString2(value) {
|
|
485
935
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
486
936
|
}
|
|
487
937
|
function validateUrl(value, label) {
|
|
@@ -500,7 +950,7 @@ function validateUrl(value, label) {
|
|
|
500
950
|
return parsed.toString();
|
|
501
951
|
}
|
|
502
952
|
function normalizeSha256(value) {
|
|
503
|
-
const candidate =
|
|
953
|
+
const candidate = asString2(value)?.toLowerCase();
|
|
504
954
|
if (!candidate || !/^[a-f0-9]{64}$/.test(candidate)) {
|
|
505
955
|
throw new CliError(
|
|
506
956
|
"PeakURL release metadata is missing a valid SHA-256 checksum."
|
|
@@ -718,13 +1168,13 @@ async function getCoreRelease(env) {
|
|
|
718
1168
|
throw new CliError("PeakURL release metadata could not be loaded.");
|
|
719
1169
|
}
|
|
720
1170
|
const payload = await response.json();
|
|
721
|
-
const version =
|
|
1171
|
+
const version = asString2(payload.version) || "latest";
|
|
722
1172
|
return {
|
|
723
1173
|
version,
|
|
724
1174
|
downloadUrl: getCorePackageUrl(env),
|
|
725
1175
|
checksumSha256: normalizeSha256(payload.checksumSha256),
|
|
726
|
-
releasedAt:
|
|
727
|
-
releaseNotesUrl:
|
|
1176
|
+
releasedAt: asString2(payload.releasedAt),
|
|
1177
|
+
releaseNotesUrl: asString2(payload.releaseNotesUrl)
|
|
728
1178
|
};
|
|
729
1179
|
}
|
|
730
1180
|
async function downloadCorePackage(release, targetPath, force = false) {
|
|
@@ -793,17 +1243,23 @@ var EXPORT_HEADERS = [
|
|
|
793
1243
|
"created_at"
|
|
794
1244
|
];
|
|
795
1245
|
function text(value) {
|
|
796
|
-
|
|
1246
|
+
if (typeof value === "string") {
|
|
1247
|
+
return value;
|
|
1248
|
+
}
|
|
1249
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1250
|
+
return String(value);
|
|
1251
|
+
}
|
|
1252
|
+
return "";
|
|
797
1253
|
}
|
|
798
1254
|
function csvValue(value) {
|
|
799
|
-
const content = value
|
|
1255
|
+
const content = value === null || value === void 0 ? "" : text(value);
|
|
800
1256
|
if (/[",\r\n]/.test(content)) {
|
|
801
1257
|
return `"${content.replace(/"/g, '""')}"`;
|
|
802
1258
|
}
|
|
803
1259
|
return content;
|
|
804
1260
|
}
|
|
805
1261
|
function xmlValue(value) {
|
|
806
|
-
return
|
|
1262
|
+
return text(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
807
1263
|
}
|
|
808
1264
|
function aliasValue(link) {
|
|
809
1265
|
return text(link.alias) || text(link.shortCode);
|
|
@@ -1093,7 +1549,7 @@ function getImportFormat(filePath, value) {
|
|
|
1093
1549
|
);
|
|
1094
1550
|
}
|
|
1095
1551
|
async function readImportRows(filePath, format) {
|
|
1096
|
-
let textContent
|
|
1552
|
+
let textContent;
|
|
1097
1553
|
try {
|
|
1098
1554
|
textContent = await readFile2(filePath, "utf8");
|
|
1099
1555
|
} catch (error) {
|
|
@@ -1138,53 +1594,53 @@ function formatImportSummary(data) {
|
|
|
1138
1594
|
|
|
1139
1595
|
// src/lib/links.ts
|
|
1140
1596
|
var LIST_KEYS = ["urls", "items", "results"];
|
|
1141
|
-
function
|
|
1597
|
+
function asObject2(value) {
|
|
1142
1598
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1143
1599
|
}
|
|
1144
|
-
function
|
|
1600
|
+
function asString3(value) {
|
|
1145
1601
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1146
1602
|
}
|
|
1147
|
-
function
|
|
1603
|
+
function asNumber2(value) {
|
|
1148
1604
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1149
1605
|
}
|
|
1150
1606
|
function pickText(link, keys) {
|
|
1151
1607
|
for (const key of keys) {
|
|
1152
|
-
const value =
|
|
1608
|
+
const value = asString3(link[key]);
|
|
1153
1609
|
if (value) {
|
|
1154
1610
|
return value;
|
|
1155
1611
|
}
|
|
1156
1612
|
}
|
|
1157
1613
|
return void 0;
|
|
1158
1614
|
}
|
|
1159
|
-
function
|
|
1615
|
+
function truncate2(value, maxLength) {
|
|
1160
1616
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
|
|
1161
1617
|
}
|
|
1162
1618
|
function getListMeta(data) {
|
|
1163
|
-
const record =
|
|
1619
|
+
const record = asObject2(data);
|
|
1164
1620
|
if (!record) {
|
|
1165
1621
|
return null;
|
|
1166
1622
|
}
|
|
1167
|
-
const meta =
|
|
1623
|
+
const meta = asObject2(record.meta);
|
|
1168
1624
|
if (meta) {
|
|
1169
1625
|
return {
|
|
1170
|
-
page:
|
|
1171
|
-
limit:
|
|
1172
|
-
totalItems:
|
|
1173
|
-
totalPages:
|
|
1626
|
+
page: asNumber2(meta.page),
|
|
1627
|
+
limit: asNumber2(meta.limit),
|
|
1628
|
+
totalItems: asNumber2(meta.totalItems),
|
|
1629
|
+
totalPages: asNumber2(meta.totalPages)
|
|
1174
1630
|
};
|
|
1175
1631
|
}
|
|
1176
1632
|
return {
|
|
1177
|
-
page:
|
|
1178
|
-
limit:
|
|
1179
|
-
totalItems:
|
|
1180
|
-
totalPages:
|
|
1633
|
+
page: asNumber2(record.page),
|
|
1634
|
+
limit: asNumber2(record.limit),
|
|
1635
|
+
totalItems: asNumber2(record.total),
|
|
1636
|
+
totalPages: asNumber2(record.totalPages)
|
|
1181
1637
|
};
|
|
1182
1638
|
}
|
|
1183
1639
|
function extractLinks(data) {
|
|
1184
1640
|
if (Array.isArray(data)) {
|
|
1185
1641
|
return data;
|
|
1186
1642
|
}
|
|
1187
|
-
const record =
|
|
1643
|
+
const record = asObject2(data);
|
|
1188
1644
|
if (record) {
|
|
1189
1645
|
for (const key of LIST_KEYS) {
|
|
1190
1646
|
const value = record[key];
|
|
@@ -1221,14 +1677,14 @@ function formatLinkDetails(link) {
|
|
|
1221
1677
|
["Alias", getLinkAlias(link)],
|
|
1222
1678
|
["Short URL", getLinkShortUrl(link)],
|
|
1223
1679
|
["Destination", getLinkDestination(link)],
|
|
1224
|
-
["Title",
|
|
1225
|
-
["Status",
|
|
1680
|
+
["Title", asString3(link.title)],
|
|
1681
|
+
["Status", asString3(link.status)],
|
|
1226
1682
|
[
|
|
1227
1683
|
"Clicks",
|
|
1228
|
-
|
|
1684
|
+
asNumber2(link.clicks) === void 0 ? void 0 : String(link.clicks)
|
|
1229
1685
|
],
|
|
1230
|
-
["Created",
|
|
1231
|
-
["Updated",
|
|
1686
|
+
["Created", asString3(link.createdAt)],
|
|
1687
|
+
["Updated", asString3(link.updatedAt)]
|
|
1232
1688
|
].filter((entry) => Boolean(entry[1]));
|
|
1233
1689
|
if (rows2.length === 0) {
|
|
1234
1690
|
return "No link fields returned.";
|
|
@@ -1241,11 +1697,11 @@ function formatLinksTable(links) {
|
|
|
1241
1697
|
}
|
|
1242
1698
|
const headers = ["ID", "Alias", "Short URL", "Destination", "Status"];
|
|
1243
1699
|
const rows2 = links.map((link) => [
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1700
|
+
truncate2(getLinkId(link) || "-", 18),
|
|
1701
|
+
truncate2(getLinkAlias(link) || "-", 12),
|
|
1702
|
+
truncate2(getLinkShortUrl(link) || "-", 36),
|
|
1703
|
+
truncate2(getLinkDestination(link) || "-", 52),
|
|
1704
|
+
truncate2(asString3(link.status) || "-", 12)
|
|
1249
1705
|
]);
|
|
1250
1706
|
return formatTable(headers, rows2);
|
|
1251
1707
|
}
|
|
@@ -1527,6 +1983,33 @@ function locationRows(location) {
|
|
|
1527
1983
|
row("Download command", text3(location.downloadCommand), 60)
|
|
1528
1984
|
]);
|
|
1529
1985
|
}
|
|
1986
|
+
function cacheRows(cache) {
|
|
1987
|
+
if (!cache) {
|
|
1988
|
+
return [];
|
|
1989
|
+
}
|
|
1990
|
+
const redisEndpoint = cache.redis?.available || cache.redis?.configured ? `${text3(cache.redis.host) ?? "127.0.0.1"}:${formatCount(cache.redis.port) ?? "6379"}` : void 0;
|
|
1991
|
+
const redisStatus = cache.redis?.available ? cache.redis.serverVersion ? `Connected (v${cache.redis.serverVersion})` : "Connected" : cache.redis?.configured ? "Configured, unavailable" : void 0;
|
|
1992
|
+
const apcuStatus = cache.apcu?.available ? "Available" : cache.apcu?.extensionLoaded ? "Loaded, disabled" : cache.apcu !== void 0 && cache.apcu !== null ? "Missing" : void 0;
|
|
1993
|
+
return rows([
|
|
1994
|
+
row("Status", formatState(cache.status)),
|
|
1995
|
+
row("Enabled", yesNo(cache.enabled, "Enabled", "Disabled")),
|
|
1996
|
+
row("Active driver", text3(cache.activeDriver)),
|
|
1997
|
+
row("Configured driver", text3(cache.configuredDriver)),
|
|
1998
|
+
row("Cache size", formatSize(cache.sizeBytes)),
|
|
1999
|
+
row(
|
|
2000
|
+
cache.activeDriver === "redis" || cache.activeDriver === "apcu" ? "Cached items" : "Cached files",
|
|
2001
|
+
formatCount(cache.fileCount)
|
|
2002
|
+
),
|
|
2003
|
+
row("Default TTL", formatSeconds(cache.defaultTtl)),
|
|
2004
|
+
row("Negative TTL", formatSeconds(cache.negativeTtl)),
|
|
2005
|
+
row("Cache directory", text3(cache.path), 60),
|
|
2006
|
+
row("Directory exists", yesNo(cache.directoryExists)),
|
|
2007
|
+
row("Directory writable", yesNo(cache.writable)),
|
|
2008
|
+
row("Redis server", redisEndpoint),
|
|
2009
|
+
row("Redis status", redisStatus),
|
|
2010
|
+
row("APCu extension", apcuStatus)
|
|
2011
|
+
]);
|
|
2012
|
+
}
|
|
1530
2013
|
function dataRows(data) {
|
|
1531
2014
|
if (!data) {
|
|
1532
2015
|
return [];
|
|
@@ -1575,11 +2058,127 @@ ${checks}` : void 0,
|
|
|
1575
2058
|
section("Storage", storageRows(status2.storage)),
|
|
1576
2059
|
section("Mail", mailRows(status2.mail)),
|
|
1577
2060
|
section("Location", locationRows(status2.location)),
|
|
2061
|
+
section("Cache", cacheRows(status2.cache)),
|
|
1578
2062
|
section("Data", dataRows(status2.data))
|
|
1579
2063
|
].filter((value) => Boolean(value));
|
|
1580
2064
|
return sections.length > 0 ? sections.join("\n\n") : "No system status fields returned.";
|
|
1581
2065
|
}
|
|
1582
2066
|
|
|
2067
|
+
// src/lib/job.ts
|
|
2068
|
+
function formatInterval(seconds) {
|
|
2069
|
+
if (seconds < 60) return `${seconds}s`;
|
|
2070
|
+
const minutes = Math.floor(seconds / 60);
|
|
2071
|
+
if (minutes < 60) return `${minutes}m`;
|
|
2072
|
+
const hours = Math.floor(minutes / 60);
|
|
2073
|
+
if (hours < 24) return `${hours}h`;
|
|
2074
|
+
const days = Math.floor(hours / 24);
|
|
2075
|
+
return `${days}d`;
|
|
2076
|
+
}
|
|
2077
|
+
function formatDate(date) {
|
|
2078
|
+
if (!date) return "never";
|
|
2079
|
+
return new Date(date).toISOString().replace("T", " ").substring(0, 19);
|
|
2080
|
+
}
|
|
2081
|
+
function formatJobsList(status2) {
|
|
2082
|
+
const rows2 = status2.jobs.map((job) => [
|
|
2083
|
+
job.id,
|
|
2084
|
+
job.title,
|
|
2085
|
+
job.status,
|
|
2086
|
+
job.is_enabled ? "yes" : "no",
|
|
2087
|
+
formatDate(job.next_run_at)
|
|
2088
|
+
]);
|
|
2089
|
+
const table = formatTable(
|
|
2090
|
+
["ID", "Job", "Status", "Enabled", "Next Run"],
|
|
2091
|
+
rows2
|
|
2092
|
+
);
|
|
2093
|
+
const summary = `
|
|
2094
|
+
${status2.jobs_count} jobs registered.
|
|
2095
|
+
Timezone: ${status2.timezone}
|
|
2096
|
+
History retention: ${status2.retention_days} days`;
|
|
2097
|
+
return `${table}${summary}`;
|
|
2098
|
+
}
|
|
2099
|
+
function formatJobDetails(job) {
|
|
2100
|
+
const rows2 = [
|
|
2101
|
+
["ID", job.id],
|
|
2102
|
+
["Title", job.title],
|
|
2103
|
+
["Status", job.status],
|
|
2104
|
+
["Enabled", job.is_enabled ? "yes" : "no"],
|
|
2105
|
+
["Current interval", formatInterval(job.interval_seconds)],
|
|
2106
|
+
[
|
|
2107
|
+
"Recommended interval",
|
|
2108
|
+
formatInterval(job.recommended_interval_seconds)
|
|
2109
|
+
],
|
|
2110
|
+
["Preferred run time", job.preferred_run_time || "none"],
|
|
2111
|
+
["Customized", job.is_customized ? "yes" : "no"],
|
|
2112
|
+
["Next run", formatDate(job.next_run_at)],
|
|
2113
|
+
["Last run", formatDate(job.last_run_at)],
|
|
2114
|
+
["Last finished", formatDate(job.last_finished_at)],
|
|
2115
|
+
["Attempts", String(job.attempts)],
|
|
2116
|
+
["Maximum attempts", String(job.max_attempts)],
|
|
2117
|
+
["Last error", job.last_error || "none"]
|
|
2118
|
+
];
|
|
2119
|
+
let out = formatDetailsTable(rows2);
|
|
2120
|
+
if (job.recent_runs && job.recent_runs.length > 0) {
|
|
2121
|
+
out += `
|
|
2122
|
+
|
|
2123
|
+
Recent Runs:
|
|
2124
|
+
${formatJobHistory(job.recent_runs)}`;
|
|
2125
|
+
}
|
|
2126
|
+
return out;
|
|
2127
|
+
}
|
|
2128
|
+
function formatJobHistory(runs) {
|
|
2129
|
+
if (!runs || runs.length === 0) {
|
|
2130
|
+
return "No recent runs.";
|
|
2131
|
+
}
|
|
2132
|
+
const rows2 = runs.map((run) => [
|
|
2133
|
+
run.id,
|
|
2134
|
+
run.status,
|
|
2135
|
+
String(run.attempt),
|
|
2136
|
+
formatDate(run.started_at),
|
|
2137
|
+
formatDate(run.finished_at),
|
|
2138
|
+
run.duration_ms ? `${run.duration_ms}ms` : "-",
|
|
2139
|
+
run.output_summary || "-",
|
|
2140
|
+
run.error_message || "-"
|
|
2141
|
+
]);
|
|
2142
|
+
return formatTable(
|
|
2143
|
+
[
|
|
2144
|
+
"Run ID",
|
|
2145
|
+
"Status",
|
|
2146
|
+
"Attempt",
|
|
2147
|
+
"Started",
|
|
2148
|
+
"Finished",
|
|
2149
|
+
"Duration",
|
|
2150
|
+
"Summary",
|
|
2151
|
+
"Error"
|
|
2152
|
+
],
|
|
2153
|
+
rows2
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
function formatRunJobResult(result) {
|
|
2157
|
+
let out = `Job: ${result.job_id}
|
|
2158
|
+
Status: ${result.status}`;
|
|
2159
|
+
if (result.summary) {
|
|
2160
|
+
out += `
|
|
2161
|
+
Summary: ${result.summary}`;
|
|
2162
|
+
}
|
|
2163
|
+
if (result.error) {
|
|
2164
|
+
out += `
|
|
2165
|
+
Error: ${result.error}`;
|
|
2166
|
+
}
|
|
2167
|
+
return out;
|
|
2168
|
+
}
|
|
2169
|
+
function formatRunDueResult(result) {
|
|
2170
|
+
if (!result.results || result.results.length === 0) {
|
|
2171
|
+
return "No jobs were due.";
|
|
2172
|
+
}
|
|
2173
|
+
const rows2 = result.results.map((r) => [
|
|
2174
|
+
r.job_id,
|
|
2175
|
+
r.status,
|
|
2176
|
+
r.summary || "-",
|
|
2177
|
+
r.error || "-"
|
|
2178
|
+
]);
|
|
2179
|
+
return formatTable(["Job ID", "Status", "Summary", "Error"], rows2);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
1583
2182
|
// src/lib/update.ts
|
|
1584
2183
|
var PACKAGE_NAME = "peakurl";
|
|
1585
2184
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
@@ -1846,7 +2445,7 @@ function text5(value) {
|
|
|
1846
2445
|
function textList(value) {
|
|
1847
2446
|
return Array.isArray(value) ? value.map((item) => text5(item)).filter((item) => Boolean(item)) : [];
|
|
1848
2447
|
}
|
|
1849
|
-
function
|
|
2448
|
+
function truncate3(value, maxLength) {
|
|
1850
2449
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
|
|
1851
2450
|
}
|
|
1852
2451
|
function parseWebhookEvents(value, previous = []) {
|
|
@@ -1893,11 +2492,11 @@ function formatWebhooksTable(webhooks) {
|
|
|
1893
2492
|
return formatTable(
|
|
1894
2493
|
["ID", "URL", "Events", "Status", "Secret"],
|
|
1895
2494
|
webhooks.map((webhook) => [
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2495
|
+
truncate3(getWebhookId(webhook) || "-", 18),
|
|
2496
|
+
truncate3(getWebhookUrl(webhook) || "-", 42),
|
|
2497
|
+
truncate3(getWebhookEvents(webhook).join(", ") || "-", 30),
|
|
1899
2498
|
webhook.isActive === false ? "inactive" : "active",
|
|
1900
|
-
|
|
2499
|
+
truncate3(text5(webhook.secretHint) || "-", 18)
|
|
1901
2500
|
])
|
|
1902
2501
|
);
|
|
1903
2502
|
}
|
|
@@ -1923,249 +2522,141 @@ function formatWebhooksSummary(webhooks) {
|
|
|
1923
2522
|
return `${webhooks.length} webhook${webhooks.length === 1 ? "" : "s"} returned.`;
|
|
1924
2523
|
}
|
|
1925
2524
|
|
|
1926
|
-
// src/commands/
|
|
1927
|
-
async function
|
|
1928
|
-
const
|
|
1929
|
-
const
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
);
|
|
1934
|
-
const
|
|
1935
|
-
success: true,
|
|
1936
|
-
message: "PeakURL downloaded.",
|
|
1937
|
-
data: result,
|
|
1938
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1939
|
-
};
|
|
2525
|
+
// src/commands/activity.ts
|
|
2526
|
+
async function listActivity(options) {
|
|
2527
|
+
const config = await getAuthConfig(process.env);
|
|
2528
|
+
const response = await new ApiClient(config).listActivity({
|
|
2529
|
+
page: options.page,
|
|
2530
|
+
limit: options.limit,
|
|
2531
|
+
search: options.search
|
|
2532
|
+
});
|
|
2533
|
+
const items = extractActivity(response.data);
|
|
1940
2534
|
if (options.json) {
|
|
1941
|
-
writeJson(
|
|
2535
|
+
writeJson(response);
|
|
1942
2536
|
return;
|
|
1943
2537
|
}
|
|
1944
2538
|
if (options.quiet) {
|
|
1945
|
-
|
|
2539
|
+
for (const item of items) {
|
|
2540
|
+
if (item.id) {
|
|
2541
|
+
writeStdout(String(item.id));
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
1946
2544
|
return;
|
|
1947
2545
|
}
|
|
1948
|
-
writeStdout(successLine(
|
|
1949
|
-
writeStdout(
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
// src/commands/links.ts
|
|
1953
|
-
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1954
|
-
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
1955
|
-
|
|
1956
|
-
// src/api/client.ts
|
|
1957
|
-
function isApiResponse(value) {
|
|
1958
|
-
return Boolean(
|
|
1959
|
-
value && typeof value === "object" && "success" in value && "message" in value && "timestamp" in value
|
|
1960
|
-
);
|
|
1961
|
-
}
|
|
1962
|
-
function networkError(apiBaseUrl, error) {
|
|
1963
|
-
if (error instanceof Error && error.message) {
|
|
1964
|
-
return `Could not reach PeakURL at ${apiBaseUrl}. ${error.message}`;
|
|
1965
|
-
}
|
|
1966
|
-
return `Could not reach PeakURL at ${apiBaseUrl}.`;
|
|
2546
|
+
writeStdout(successLine(response.message || "Activity loaded."));
|
|
2547
|
+
writeStdout(formatActivityTable(items));
|
|
2548
|
+
writeStdout(formatActivitySummary(response.data, items.length));
|
|
1967
2549
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
* CLI login verification flow.
|
|
1983
|
-
*
|
|
1984
|
-
* @returns API response envelope containing the authenticated user.
|
|
1985
|
-
*/
|
|
1986
|
-
whoami() {
|
|
1987
|
-
return this.request("GET", "users/me");
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Loads the current system status snapshot for the authenticated site.
|
|
1991
|
-
*
|
|
1992
|
-
* @returns API response envelope containing system status sections.
|
|
1993
|
-
*/
|
|
1994
|
-
getStatus() {
|
|
1995
|
-
return this.request("GET", "system/status");
|
|
1996
|
-
}
|
|
1997
|
-
/**
|
|
1998
|
-
* Creates a short URL.
|
|
1999
|
-
*
|
|
2000
|
-
* @param payload Request body accepted by `POST /api/v1/urls`.
|
|
2001
|
-
* @returns API response envelope containing the created link.
|
|
2002
|
-
*/
|
|
2003
|
-
createUrl(payload) {
|
|
2004
|
-
return this.request("POST", "urls", payload);
|
|
2005
|
-
}
|
|
2006
|
-
/**
|
|
2007
|
-
* Lists short URLs with optional pagination and filtering.
|
|
2008
|
-
*
|
|
2009
|
-
* The current PeakURL app returns `{ items, meta }` under `data`, but the
|
|
2010
|
-
* CLI keeps a slightly broader compatibility type for future-proofing.
|
|
2011
|
-
*
|
|
2012
|
-
* @param query Optional query-string values.
|
|
2013
|
-
* @returns API response envelope containing list data.
|
|
2014
|
-
*/
|
|
2015
|
-
listUrls(query) {
|
|
2016
|
-
return this.request("GET", "urls", void 0, query);
|
|
2017
|
-
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Exports the full accessible link dataset for the authenticated user.
|
|
2020
|
-
*
|
|
2021
|
-
* @param query Optional search and sort values.
|
|
2022
|
-
* @returns API response envelope containing the full export payload.
|
|
2023
|
-
*/
|
|
2024
|
-
exportUrls(query) {
|
|
2025
|
-
return this.request(
|
|
2026
|
-
"GET",
|
|
2027
|
-
"urls/export",
|
|
2028
|
-
void 0,
|
|
2029
|
-
query
|
|
2030
|
-
);
|
|
2031
|
-
}
|
|
2032
|
-
/**
|
|
2033
|
-
* Imports multiple short links in one bulk request.
|
|
2034
|
-
*
|
|
2035
|
-
* @param payload Request body accepted by `POST /api/v1/urls/bulk`.
|
|
2036
|
-
* @returns API response envelope containing created rows plus row errors.
|
|
2037
|
-
*/
|
|
2038
|
-
importUrls(payload) {
|
|
2039
|
-
return this.request("POST", "urls/bulk", payload);
|
|
2040
|
-
}
|
|
2041
|
-
/**
|
|
2042
|
-
* Loads a single short URL by identifier or alias.
|
|
2043
|
-
*
|
|
2044
|
-
* PeakURL resolves IDs, short codes, and aliases through the same route.
|
|
2045
|
-
*
|
|
2046
|
-
* @param idOrAlias Link identifier, short code, or alias.
|
|
2047
|
-
* @returns API response envelope containing the resolved link.
|
|
2048
|
-
*/
|
|
2049
|
-
getUrl(idOrAlias) {
|
|
2050
|
-
return this.request(
|
|
2051
|
-
"GET",
|
|
2052
|
-
`urls/${encodeURIComponent(idOrAlias)}`
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
/**
|
|
2056
|
-
* Deletes a short URL by its stable row ID.
|
|
2057
|
-
*
|
|
2058
|
-
* The current PeakURL backend delete route expects the row ID. The CLI can
|
|
2059
|
-
* still accept an alias at the command layer by resolving it first.
|
|
2060
|
-
*
|
|
2061
|
-
* @param id Stable link row ID.
|
|
2062
|
-
* @returns API response envelope containing the deletion result.
|
|
2063
|
-
*/
|
|
2064
|
-
deleteUrl(id) {
|
|
2065
|
-
return this.request(
|
|
2066
|
-
"DELETE",
|
|
2067
|
-
`urls/${encodeURIComponent(id)}`
|
|
2068
|
-
);
|
|
2069
|
-
}
|
|
2070
|
-
/**
|
|
2071
|
-
* Lists outbound webhooks for the authenticated user.
|
|
2072
|
-
*
|
|
2073
|
-
* @returns API response envelope containing webhook rows.
|
|
2074
|
-
*/
|
|
2075
|
-
listWebhooks() {
|
|
2076
|
-
return this.request("GET", "webhooks");
|
|
2077
|
-
}
|
|
2078
|
-
/**
|
|
2079
|
-
* Creates one outbound webhook subscription.
|
|
2080
|
-
*
|
|
2081
|
-
* @param payload Request body accepted by `POST /api/v1/webhooks`.
|
|
2082
|
-
* @returns API response envelope containing the created webhook.
|
|
2083
|
-
*/
|
|
2084
|
-
createWebhook(payload) {
|
|
2085
|
-
return this.request("POST", "webhooks", payload);
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Deletes one webhook by its stable row ID.
|
|
2089
|
-
*
|
|
2090
|
-
* @param id Webhook identifier returned by the list/create endpoints.
|
|
2091
|
-
* @returns API response envelope containing the deletion result.
|
|
2092
|
-
*/
|
|
2093
|
-
deleteWebhook(id) {
|
|
2094
|
-
return this.request(
|
|
2095
|
-
"DELETE",
|
|
2096
|
-
`webhooks/${encodeURIComponent(id)}`
|
|
2550
|
+
async function deleteActivity(identifiers, options) {
|
|
2551
|
+
const config = await getAuthConfig(process.env);
|
|
2552
|
+
const client = new ApiClient(config);
|
|
2553
|
+
if (options.all) {
|
|
2554
|
+
const response2 = await client.clearActivity();
|
|
2555
|
+
if (options.json) {
|
|
2556
|
+
writeJson(response2);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
if (options.quiet) {
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
writeStdout(
|
|
2563
|
+
successLine(response2.message || "All activity logs deleted.")
|
|
2097
2564
|
);
|
|
2565
|
+
return;
|
|
2098
2566
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
* @returns Parsed PeakURL response envelope.
|
|
2107
|
-
* @throws {CliError} When the network request fails or the API returns an error.
|
|
2108
|
-
*/
|
|
2109
|
-
async request(method, path, body, query) {
|
|
2110
|
-
const url = buildApiUrl(this.config.apiBaseUrl, path, query);
|
|
2111
|
-
let response;
|
|
2112
|
-
try {
|
|
2113
|
-
response = await fetch(url, {
|
|
2114
|
-
method,
|
|
2115
|
-
headers: {
|
|
2116
|
-
Accept: "application/json",
|
|
2117
|
-
Authorization: `Bearer ${this.config.apiKey}`,
|
|
2118
|
-
...body ? { "Content-Type": "application/json" } : {}
|
|
2119
|
-
},
|
|
2120
|
-
body: body ? JSON.stringify(body) : void 0
|
|
2121
|
-
});
|
|
2122
|
-
} catch (error) {
|
|
2123
|
-
throw new CliError(networkError(this.config.apiBaseUrl, error), 1, {
|
|
2124
|
-
cause: error instanceof Error ? error : void 0
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
2127
|
-
const rawText = await response.text();
|
|
2128
|
-
if (!rawText) {
|
|
2129
|
-
if (!response.ok) {
|
|
2130
|
-
throw new CliError(
|
|
2131
|
-
`PeakURL request failed with HTTP ${response.status}.`
|
|
2132
|
-
);
|
|
2567
|
+
const rawTargets = [];
|
|
2568
|
+
if (typeof identifiers === "string" && identifiers.trim()) {
|
|
2569
|
+
rawTargets.push(identifiers.trim());
|
|
2570
|
+
} else if (Array.isArray(identifiers)) {
|
|
2571
|
+
for (const item of identifiers) {
|
|
2572
|
+
if (typeof item === "string" && item.trim()) {
|
|
2573
|
+
rawTargets.push(item.trim());
|
|
2133
2574
|
}
|
|
2134
|
-
return {
|
|
2135
|
-
success: true,
|
|
2136
|
-
message: "Request completed.",
|
|
2137
|
-
data: void 0,
|
|
2138
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2139
|
-
};
|
|
2140
2575
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
if (
|
|
2146
|
-
|
|
2147
|
-
`PeakURL request failed with HTTP ${response.status}.`
|
|
2148
|
-
);
|
|
2576
|
+
}
|
|
2577
|
+
if (options.ids) {
|
|
2578
|
+
for (const id of options.ids.split(",")) {
|
|
2579
|
+
const trimmed = id.trim();
|
|
2580
|
+
if (trimmed) {
|
|
2581
|
+
rawTargets.push(trimmed);
|
|
2149
2582
|
}
|
|
2150
|
-
throw new CliError("PeakURL returned an invalid JSON response.");
|
|
2151
2583
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2584
|
+
}
|
|
2585
|
+
const uniqueTargets = Array.from(new Set(rawTargets));
|
|
2586
|
+
if (uniqueTargets.length === 0) {
|
|
2587
|
+
throw new CliError(
|
|
2588
|
+
"Specify one or more activity IDs to delete, or use --all to delete all activity logs."
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
if (uniqueTargets.length === 1) {
|
|
2592
|
+
const response2 = await client.deleteActivity(uniqueTargets[0]);
|
|
2593
|
+
if (options.json) {
|
|
2594
|
+
writeJson(response2);
|
|
2595
|
+
return;
|
|
2156
2596
|
}
|
|
2157
|
-
if (
|
|
2158
|
-
|
|
2159
|
-
throw new CliError(
|
|
2160
|
-
parsed.message || `PeakURL request failed with HTTP ${response.status}.`,
|
|
2161
|
-
statusCode
|
|
2162
|
-
);
|
|
2597
|
+
if (options.quiet) {
|
|
2598
|
+
return;
|
|
2163
2599
|
}
|
|
2164
|
-
|
|
2600
|
+
writeStdout(successLine(response2.message || "Activity log deleted."));
|
|
2601
|
+
return;
|
|
2165
2602
|
}
|
|
2166
|
-
|
|
2603
|
+
const response = await client.deleteActivityBulk(uniqueTargets);
|
|
2604
|
+
if (options.json) {
|
|
2605
|
+
writeJson(response);
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
if (options.quiet) {
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
writeStdout(
|
|
2612
|
+
successLine(
|
|
2613
|
+
response.message || `Deleted ${uniqueTargets.length} activity record${uniqueTargets.length === 1 ? "" : "s"}.`
|
|
2614
|
+
)
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
async function clearActivity(options) {
|
|
2618
|
+
const config = await getAuthConfig(process.env);
|
|
2619
|
+
const response = await new ApiClient(config).clearActivity();
|
|
2620
|
+
if (options.json) {
|
|
2621
|
+
writeJson(response);
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
if (options.quiet) {
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
writeStdout(successLine(response.message || "All activity logs deleted."));
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
// src/commands/core.ts
|
|
2631
|
+
import { cwd } from "process";
|
|
2632
|
+
async function downloadCore(options) {
|
|
2633
|
+
const release = await getCoreRelease(process.env);
|
|
2634
|
+
const result = await downloadCorePackage(
|
|
2635
|
+
release,
|
|
2636
|
+
cwd(),
|
|
2637
|
+
Boolean(options.force)
|
|
2638
|
+
);
|
|
2639
|
+
const responseBody = {
|
|
2640
|
+
success: true,
|
|
2641
|
+
message: "PeakURL downloaded.",
|
|
2642
|
+
data: result,
|
|
2643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2644
|
+
};
|
|
2645
|
+
if (options.json) {
|
|
2646
|
+
writeJson(responseBody);
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
if (options.quiet) {
|
|
2650
|
+
writeStdout(result.path);
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
writeStdout(successLine(responseBody.message));
|
|
2654
|
+
writeStdout(formatCoreDownload(result));
|
|
2655
|
+
}
|
|
2167
2656
|
|
|
2168
2657
|
// src/commands/links.ts
|
|
2658
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
2659
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
2169
2660
|
function normalizeExpiresAt(value) {
|
|
2170
2661
|
if (!value) {
|
|
2171
2662
|
return void 0;
|
|
@@ -2308,17 +2799,93 @@ async function getLink(idOrAlias, options) {
|
|
|
2308
2799
|
writeStdout(successLine(response.message));
|
|
2309
2800
|
writeStdout(formatLinkDetails(response.data));
|
|
2310
2801
|
}
|
|
2311
|
-
async function deleteLink(
|
|
2802
|
+
async function deleteLink(identifiers, options) {
|
|
2312
2803
|
const config = await getAuthConfig(process.env);
|
|
2313
2804
|
const client = new ApiClient(config);
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2805
|
+
if (options.all) {
|
|
2806
|
+
const response2 = await client.clearUrls();
|
|
2807
|
+
if (options.json) {
|
|
2808
|
+
writeJson(response2);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
if (options.quiet) {
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
writeStdout(
|
|
2815
|
+
successLine(response2.message || "All short links deleted.")
|
|
2816
|
+
);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (options.trash || options.emptyTrash) {
|
|
2820
|
+
const response2 = await client.emptyTrash();
|
|
2821
|
+
if (options.json) {
|
|
2822
|
+
writeJson(response2);
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
if (options.quiet) {
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
writeStdout(successLine(response2.message || "Trash emptied."));
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
const rawTargets = [];
|
|
2832
|
+
if (typeof identifiers === "string" && identifiers.trim()) {
|
|
2833
|
+
rawTargets.push(identifiers.trim());
|
|
2834
|
+
} else if (Array.isArray(identifiers)) {
|
|
2835
|
+
for (const item of identifiers) {
|
|
2836
|
+
if (typeof item === "string" && item.trim()) {
|
|
2837
|
+
rawTargets.push(item.trim());
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
if (options.ids) {
|
|
2842
|
+
for (const id of options.ids.split(",")) {
|
|
2843
|
+
const trimmed = id.trim();
|
|
2844
|
+
if (trimmed) {
|
|
2845
|
+
rawTargets.push(trimmed);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
const uniqueTargets = Array.from(new Set(rawTargets));
|
|
2850
|
+
if (uniqueTargets.length === 0) {
|
|
2317
2851
|
throw new CliError(
|
|
2318
|
-
"
|
|
2852
|
+
"Specify one or more link identifiers or aliases to delete, or use --all to delete all links."
|
|
2319
2853
|
);
|
|
2320
2854
|
}
|
|
2321
|
-
|
|
2855
|
+
if (uniqueTargets.length === 1) {
|
|
2856
|
+
const lookupResponse = await client.getUrl(uniqueTargets[0]);
|
|
2857
|
+
const resolvedId = getLinkId(lookupResponse.data);
|
|
2858
|
+
if (!resolvedId) {
|
|
2859
|
+
throw new CliError(
|
|
2860
|
+
"PeakURL returned a link record without an ID, so the CLI cannot delete it safely."
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
const response2 = await client.deleteUrl(resolvedId);
|
|
2864
|
+
if (options.json) {
|
|
2865
|
+
writeJson(response2);
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
if (options.quiet) {
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
writeStdout(successLine(response2.message));
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
const resolvedIds = [];
|
|
2875
|
+
for (const target of uniqueTargets) {
|
|
2876
|
+
try {
|
|
2877
|
+
const lookup = await client.getUrl(target);
|
|
2878
|
+
const id = getLinkId(lookup.data);
|
|
2879
|
+
if (id) {
|
|
2880
|
+
resolvedIds.push(id);
|
|
2881
|
+
} else {
|
|
2882
|
+
resolvedIds.push(target);
|
|
2883
|
+
}
|
|
2884
|
+
} catch {
|
|
2885
|
+
resolvedIds.push(target);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
const response = await client.deleteUrlsBulk(resolvedIds);
|
|
2322
2889
|
if (options.json) {
|
|
2323
2890
|
writeJson(response);
|
|
2324
2891
|
return;
|
|
@@ -2326,7 +2893,11 @@ async function deleteLink(idOrAlias, options) {
|
|
|
2326
2893
|
if (options.quiet) {
|
|
2327
2894
|
return;
|
|
2328
2895
|
}
|
|
2329
|
-
writeStdout(
|
|
2896
|
+
writeStdout(
|
|
2897
|
+
successLine(
|
|
2898
|
+
response.message || `Deleted ${resolvedIds.length} short link${resolvedIds.length === 1 ? "" : "s"}.`
|
|
2899
|
+
)
|
|
2900
|
+
);
|
|
2330
2901
|
}
|
|
2331
2902
|
|
|
2332
2903
|
// src/commands/login.ts
|
|
@@ -2513,7 +3084,7 @@ async function deleteWebhook(id, options) {
|
|
|
2513
3084
|
}
|
|
2514
3085
|
writeStdout(successLine(response.message));
|
|
2515
3086
|
}
|
|
2516
|
-
|
|
3087
|
+
function listWebhookEvents(options) {
|
|
2517
3088
|
const response = {
|
|
2518
3089
|
success: true,
|
|
2519
3090
|
message: "Webhook events loaded.",
|
|
@@ -2550,6 +3121,230 @@ async function whoami(options) {
|
|
|
2550
3121
|
writeStdout(userTable(response.data, config.apiBaseUrl));
|
|
2551
3122
|
}
|
|
2552
3123
|
|
|
3124
|
+
// src/commands/job.ts
|
|
3125
|
+
async function getClient() {
|
|
3126
|
+
const config = await getAuthConfig(process.env);
|
|
3127
|
+
return new ApiClient(config);
|
|
3128
|
+
}
|
|
3129
|
+
async function listJobs(options) {
|
|
3130
|
+
const client = await getClient();
|
|
3131
|
+
const response = await client.getJobStatus();
|
|
3132
|
+
if (options.json) {
|
|
3133
|
+
writeJson(response);
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
if (options.quiet) {
|
|
3137
|
+
const ids = response.data.jobs.map((job) => job.id).join("\n");
|
|
3138
|
+
if (ids) {
|
|
3139
|
+
writeStdout(ids);
|
|
3140
|
+
}
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
writeStdout(successLine(response.message));
|
|
3144
|
+
writeStdout();
|
|
3145
|
+
writeStdout(formatJobsList(response.data));
|
|
3146
|
+
}
|
|
3147
|
+
async function getJob(id, options) {
|
|
3148
|
+
const client = await getClient();
|
|
3149
|
+
const response = await client.getJobStatus();
|
|
3150
|
+
const job = response.data.jobs.find((j) => j.id === id);
|
|
3151
|
+
if (!job) {
|
|
3152
|
+
throw new CliError(`Job '${id}' not found.`, 1);
|
|
3153
|
+
}
|
|
3154
|
+
if (options.json) {
|
|
3155
|
+
writeJson({
|
|
3156
|
+
success: true,
|
|
3157
|
+
message: "Job loaded.",
|
|
3158
|
+
data: job,
|
|
3159
|
+
timestamp: response.timestamp
|
|
3160
|
+
});
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (options.quiet) {
|
|
3164
|
+
writeStdout(job.id);
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
writeStdout(successLine(`Job ${job.id} loaded.`));
|
|
3168
|
+
writeStdout();
|
|
3169
|
+
writeStdout(formatJobDetails(job));
|
|
3170
|
+
}
|
|
3171
|
+
async function runJob(id, options) {
|
|
3172
|
+
const client = await getClient();
|
|
3173
|
+
const response = await client.runJob(id);
|
|
3174
|
+
if (options.json) {
|
|
3175
|
+
writeJson(response);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
if (options.quiet) {
|
|
3179
|
+
writeStdout(response.data.status);
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
writeStdout(successLine(response.message));
|
|
3183
|
+
writeStdout();
|
|
3184
|
+
writeStdout(formatRunJobResult(response.data));
|
|
3185
|
+
}
|
|
3186
|
+
async function runDueJobs(options) {
|
|
3187
|
+
const client = await getClient();
|
|
3188
|
+
const response = await client.runDueJobs();
|
|
3189
|
+
if (options.json) {
|
|
3190
|
+
writeJson(response);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
if (options.quiet) {
|
|
3194
|
+
const statuses = response.data.results.map((r) => r.status).join("\n");
|
|
3195
|
+
if (statuses) {
|
|
3196
|
+
writeStdout(statuses);
|
|
3197
|
+
}
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
writeStdout(successLine(response.message));
|
|
3201
|
+
writeStdout();
|
|
3202
|
+
writeStdout(formatRunDueResult(response.data));
|
|
3203
|
+
}
|
|
3204
|
+
async function listJobHistory(id, options) {
|
|
3205
|
+
const client = await getClient();
|
|
3206
|
+
const response = await client.getJobStatus();
|
|
3207
|
+
const job = response.data.jobs.find((j) => j.id === id);
|
|
3208
|
+
if (!job) {
|
|
3209
|
+
throw new CliError(`Job '${id}' not found.`, 1);
|
|
3210
|
+
}
|
|
3211
|
+
if (options.json) {
|
|
3212
|
+
writeJson({
|
|
3213
|
+
success: true,
|
|
3214
|
+
message: "History loaded.",
|
|
3215
|
+
data: job.recent_runs || [],
|
|
3216
|
+
timestamp: response.timestamp
|
|
3217
|
+
});
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
if (options.quiet) {
|
|
3221
|
+
const ids = (job.recent_runs || []).map((r) => r.id).join("\n");
|
|
3222
|
+
if (ids) {
|
|
3223
|
+
writeStdout(ids);
|
|
3224
|
+
}
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
writeStdout(successLine(`History for job ${job.id} loaded.`));
|
|
3228
|
+
writeStdout();
|
|
3229
|
+
writeStdout(formatJobHistory(job.recent_runs || []));
|
|
3230
|
+
}
|
|
3231
|
+
async function clearJobHistory(options) {
|
|
3232
|
+
const client = await getClient();
|
|
3233
|
+
const response = await client.clearJobHistory(options.job);
|
|
3234
|
+
if (options.json) {
|
|
3235
|
+
writeJson(response);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
if (options.quiet) {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
writeStdout(successLine(response.message));
|
|
3242
|
+
}
|
|
3243
|
+
async function updateJobSchedule(id, options) {
|
|
3244
|
+
const client = await getClient();
|
|
3245
|
+
if (options.enabled && options.disabled) {
|
|
3246
|
+
throw new CliError("Cannot specify both --enabled and --disabled.", 1);
|
|
3247
|
+
}
|
|
3248
|
+
const payload = {};
|
|
3249
|
+
if (options.interval !== void 0) {
|
|
3250
|
+
const interval = parseInt(options.interval, 10);
|
|
3251
|
+
if (isNaN(interval) || interval <= 0) {
|
|
3252
|
+
throw new CliError("Interval must be a positive integer.", 1);
|
|
3253
|
+
}
|
|
3254
|
+
payload.interval_seconds = interval;
|
|
3255
|
+
}
|
|
3256
|
+
if (options.preferredTime !== void 0) {
|
|
3257
|
+
if (options.preferredTime.toLowerCase() === "none" || options.preferredTime === "") {
|
|
3258
|
+
payload.preferred_run_time = null;
|
|
3259
|
+
} else if (/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test(options.preferredTime)) {
|
|
3260
|
+
payload.preferred_run_time = options.preferredTime;
|
|
3261
|
+
} else {
|
|
3262
|
+
throw new CliError(
|
|
3263
|
+
"Preferred time must be in HH:MM format or 'none'.",
|
|
3264
|
+
1
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
if (options.enabled !== void 0) {
|
|
3269
|
+
payload.is_enabled = true;
|
|
3270
|
+
} else if (options.disabled !== void 0) {
|
|
3271
|
+
payload.is_enabled = false;
|
|
3272
|
+
}
|
|
3273
|
+
if (Object.keys(payload).length === 0) {
|
|
3274
|
+
throw new CliError("No schedule changes requested.", 1);
|
|
3275
|
+
}
|
|
3276
|
+
const response = await client.updateJobSchedule(id, payload);
|
|
3277
|
+
if (options.json) {
|
|
3278
|
+
writeJson(response);
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
if (options.quiet) {
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
writeStdout(successLine(response.message));
|
|
3285
|
+
writeStdout();
|
|
3286
|
+
writeStdout(formatJobDetails(response.data));
|
|
3287
|
+
}
|
|
3288
|
+
async function resetJobSchedule(id, options) {
|
|
3289
|
+
const client = await getClient();
|
|
3290
|
+
const response = await client.resetJobSchedule(id);
|
|
3291
|
+
if (options.json) {
|
|
3292
|
+
writeJson(response);
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
if (options.quiet) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
writeStdout(successLine(response.message));
|
|
3299
|
+
writeStdout();
|
|
3300
|
+
writeStdout(formatJobDetails(response.data));
|
|
3301
|
+
}
|
|
3302
|
+
async function updateJobSettings(options) {
|
|
3303
|
+
const client = await getClient();
|
|
3304
|
+
if (options.retentionDays !== void 0) {
|
|
3305
|
+
const days = parseInt(options.retentionDays, 10);
|
|
3306
|
+
if (isNaN(days) || days < 0) {
|
|
3307
|
+
throw new CliError(
|
|
3308
|
+
"Retention days must be a non-negative integer.",
|
|
3309
|
+
1
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
const response2 = await client.updateJobSettings({
|
|
3313
|
+
retention_days: days
|
|
3314
|
+
});
|
|
3315
|
+
if (options.json) {
|
|
3316
|
+
writeJson(response2);
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
if (options.quiet) {
|
|
3320
|
+
writeStdout(String(response2.data.retention_days));
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
writeStdout(successLine(response2.message));
|
|
3324
|
+
return;
|
|
3325
|
+
}
|
|
3326
|
+
const response = await client.getJobStatus();
|
|
3327
|
+
if (options.json) {
|
|
3328
|
+
writeJson({
|
|
3329
|
+
success: true,
|
|
3330
|
+
message: "Settings loaded.",
|
|
3331
|
+
data: {
|
|
3332
|
+
retention_days: response.data.retention_days,
|
|
3333
|
+
timezone: response.data.timezone
|
|
3334
|
+
},
|
|
3335
|
+
timestamp: response.timestamp
|
|
3336
|
+
});
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
if (options.quiet) {
|
|
3340
|
+
writeStdout(String(response.data.retention_days));
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
writeStdout(successLine("Settings loaded."));
|
|
3344
|
+
writeStdout(`History retention: ${response.data.retention_days} days`);
|
|
3345
|
+
writeStdout(`Timezone: ${response.data.timezone}`);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
2553
3348
|
// src/index.ts
|
|
2554
3349
|
function parseNumber(label) {
|
|
2555
3350
|
return (value) => {
|
|
@@ -2584,7 +3379,7 @@ function getRetryCommandName(argv) {
|
|
|
2584
3379
|
if (!first || first.startsWith("-")) {
|
|
2585
3380
|
return void 0;
|
|
2586
3381
|
}
|
|
2587
|
-
if (first === "webhook" || first === "
|
|
3382
|
+
if (first === "webhook" || first === "activity" || first === "job") {
|
|
2588
3383
|
const second = argv[3]?.trim();
|
|
2589
3384
|
if (second && !second.startsWith("-")) {
|
|
2590
3385
|
return `${first} ${second}`;
|
|
@@ -2592,10 +3387,33 @@ function getRetryCommandName(argv) {
|
|
|
2592
3387
|
}
|
|
2593
3388
|
return first;
|
|
2594
3389
|
}
|
|
3390
|
+
var COMMAND_SUGGESTIONS = {
|
|
3391
|
+
activities: "activity",
|
|
3392
|
+
webhooks: "webhook",
|
|
3393
|
+
jobs: "job",
|
|
3394
|
+
cron: "job",
|
|
3395
|
+
"scheduled-jobs": "job",
|
|
3396
|
+
links: "list",
|
|
3397
|
+
urls: "list"
|
|
3398
|
+
};
|
|
2595
3399
|
async function main() {
|
|
2596
3400
|
const program = new Command();
|
|
2597
3401
|
const version = await getCliVersion();
|
|
2598
|
-
program.name("peakurl").description("Manage your PeakURL site from the terminal.").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").version(version, "-v, --version", "Show CLI version").
|
|
3402
|
+
program.name("peakurl").description("Manage your PeakURL site from the terminal.").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").version(version, "-v, --version", "Show CLI version").configureOutput({
|
|
3403
|
+
outputError: (str, write) => {
|
|
3404
|
+
const match = /error: unknown command '([^']+)'/.exec(str);
|
|
3405
|
+
if (match && COMMAND_SUGGESTIONS[match[1]]) {
|
|
3406
|
+
const suggestion = COMMAND_SUGGESTIONS[match[1]];
|
|
3407
|
+
write(
|
|
3408
|
+
`error: unknown command '${match[1]}'. Did you mean 'peakurl ${suggestion}'?
|
|
3409
|
+
|
|
3410
|
+
`
|
|
3411
|
+
);
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
write(str);
|
|
3415
|
+
}
|
|
3416
|
+
}).showHelpAfterError().showSuggestionAfterError().addHelpText(
|
|
2599
3417
|
"after",
|
|
2600
3418
|
`
|
|
2601
3419
|
Get Started:
|
|
@@ -2609,6 +3427,8 @@ Common Commands:
|
|
|
2609
3427
|
peakurl list --limit 10
|
|
2610
3428
|
peakurl import ./links.csv
|
|
2611
3429
|
peakurl export --format csv
|
|
3430
|
+
peakurl activity list
|
|
3431
|
+
peakurl job list
|
|
2612
3432
|
peakurl webhook list
|
|
2613
3433
|
peakurl update --check
|
|
2614
3434
|
|
|
@@ -2710,8 +3530,57 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
|
2710
3530
|
["peakurl get docs", "peakurl get url_123 --json"]
|
|
2711
3531
|
);
|
|
2712
3532
|
addExamples(
|
|
2713
|
-
program.command("delete").summary("Delete
|
|
2714
|
-
|
|
3533
|
+
program.command("delete").summary("Delete short links").description(
|
|
3534
|
+
"Delete PeakURL short links by ID or alias, in bulk, or clear all links."
|
|
3535
|
+
).helpOption("-h, --help", "Show help").argument("[id-or-alias...]", "Link identifier(s) or alias(es)").option("--all", "Delete all accessible short links").option(
|
|
3536
|
+
"--trash, --empty-trash",
|
|
3537
|
+
"Empty all short links currently in trash"
|
|
3538
|
+
).option(
|
|
3539
|
+
"--ids <ids>",
|
|
3540
|
+
"Comma-separated list of link IDs to bulk delete"
|
|
3541
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(deleteLink),
|
|
3542
|
+
[
|
|
3543
|
+
"peakurl delete docs",
|
|
3544
|
+
"peakurl delete docs pricing launch",
|
|
3545
|
+
"peakurl delete --ids url_1,url_2,url_3",
|
|
3546
|
+
"peakurl delete --empty-trash",
|
|
3547
|
+
"peakurl delete --all"
|
|
3548
|
+
]
|
|
3549
|
+
);
|
|
3550
|
+
const activity = program.command("activity").summary("View and manage activity logs").helpOption("-h, --help", "Show help").description(
|
|
3551
|
+
"View audit log activity entries, delete specific records, or clear all history."
|
|
3552
|
+
);
|
|
3553
|
+
addExamples(activity, [
|
|
3554
|
+
"peakurl activity list",
|
|
3555
|
+
"peakurl activity delete act_123 act_456",
|
|
3556
|
+
"peakurl activity clear"
|
|
3557
|
+
]);
|
|
3558
|
+
addExamples(
|
|
3559
|
+
activity.command("list", { isDefault: true }).summary("List activity logs").description("List audit log activity records.").helpOption("-h, --help", "Show help").option("--page <number>", "Page number", parseNumber("page")).option("--limit <number>", "Page size", parseNumber("limit")).option("--search <query>", "Search term").option("--json", "Print machine-readable output").option("--quiet", "Print only activity record IDs").action(listActivity),
|
|
3560
|
+
[
|
|
3561
|
+
"peakurl activity",
|
|
3562
|
+
"peakurl activity list",
|
|
3563
|
+
"peakurl activity list --limit 25 --page 1",
|
|
3564
|
+
"peakurl activity list --search delete --json"
|
|
3565
|
+
]
|
|
3566
|
+
);
|
|
3567
|
+
addExamples(
|
|
3568
|
+
activity.command("delete").summary("Delete activity logs").description(
|
|
3569
|
+
"Delete one or more activity logs by ID, or clear all logs with --all."
|
|
3570
|
+
).helpOption("-h, --help", "Show help").argument("[ids...]", "One or more activity log IDs to delete").option("--all", "Delete all activity log history").option(
|
|
3571
|
+
"--ids <ids>",
|
|
3572
|
+
"Comma-separated list of activity log IDs to delete"
|
|
3573
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(deleteActivity),
|
|
3574
|
+
[
|
|
3575
|
+
"peakurl activity delete act_123",
|
|
3576
|
+
"peakurl activity delete act_123 act_456",
|
|
3577
|
+
"peakurl activity delete --ids act_1,act_2",
|
|
3578
|
+
"peakurl activity delete --all"
|
|
3579
|
+
]
|
|
3580
|
+
);
|
|
3581
|
+
addExamples(
|
|
3582
|
+
activity.command("clear").summary("Clear all activity logs").description("Delete all audit log activity records permanently.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(clearActivity),
|
|
3583
|
+
["peakurl activity clear", "peakurl activity clear --json"]
|
|
2715
3584
|
);
|
|
2716
3585
|
addExamples(
|
|
2717
3586
|
program.command("update").summary("Check for CLI updates").description(
|
|
@@ -2719,18 +3588,110 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
|
2719
3588
|
).helpOption("-h, --help", "Show help").option(
|
|
2720
3589
|
"--check",
|
|
2721
3590
|
"Alias for checking update status without changing anything"
|
|
2722
|
-
).option("--json", "Print machine-readable output").option("--quiet", "Print minimal output").action(
|
|
3591
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print minimal output").action(
|
|
3592
|
+
(options) => checkUpdate(options, version)
|
|
3593
|
+
),
|
|
2723
3594
|
["peakurl update", "peakurl update --check", "peakurl update --json"]
|
|
2724
3595
|
);
|
|
2725
|
-
const
|
|
3596
|
+
const jobCmd = program.command("job").summary("Manage scheduled jobs").description(
|
|
3597
|
+
"Manage server-side scheduled jobs, view their execution history, and run them manually."
|
|
3598
|
+
).helpOption("-h, --help", "Show help");
|
|
3599
|
+
addExamples(jobCmd, [
|
|
3600
|
+
"peakurl job",
|
|
3601
|
+
"peakurl job list",
|
|
3602
|
+
"peakurl job get peakurl_version_check",
|
|
3603
|
+
"peakurl job run peakurl_version_check",
|
|
3604
|
+
"peakurl job run-due"
|
|
3605
|
+
]);
|
|
3606
|
+
addExamples(
|
|
3607
|
+
jobCmd.command("list", { isDefault: true }).summary("List scheduled jobs").description("List all registered scheduled jobs.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print only job IDs").action(listJobs),
|
|
3608
|
+
[
|
|
3609
|
+
"peakurl job",
|
|
3610
|
+
"peakurl job list",
|
|
3611
|
+
"peakurl job list --json",
|
|
3612
|
+
"peakurl job list --quiet"
|
|
3613
|
+
]
|
|
3614
|
+
);
|
|
3615
|
+
addExamples(
|
|
3616
|
+
jobCmd.command("get").summary("Show job details").description(
|
|
3617
|
+
"Show detailed configuration and status for one scheduled job."
|
|
3618
|
+
).helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--json", "Print machine-readable output").option("--quiet", "Print only the job ID").action(getJob),
|
|
3619
|
+
[
|
|
3620
|
+
"peakurl job get peakurl_version_check",
|
|
3621
|
+
"peakurl job get peakurl_version_check --json"
|
|
3622
|
+
]
|
|
3623
|
+
);
|
|
3624
|
+
addExamples(
|
|
3625
|
+
jobCmd.command("run").summary("Run a scheduled job").description("Force a specific scheduled job to run immediately.").helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--json", "Print machine-readable output").option("--quiet", "Print only the execution status").action(runJob),
|
|
3626
|
+
[
|
|
3627
|
+
"peakurl job run peakurl_version_check",
|
|
3628
|
+
"peakurl job run peakurl_version_check --json"
|
|
3629
|
+
]
|
|
3630
|
+
);
|
|
3631
|
+
addExamples(
|
|
3632
|
+
jobCmd.command("run-due").summary("Run due jobs").description("Trigger all scheduled jobs that are currently due.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print only the execution statuses").action(runDueJobs),
|
|
3633
|
+
["peakurl job run-due", "peakurl job run-due --json"]
|
|
3634
|
+
);
|
|
3635
|
+
addExamples(
|
|
3636
|
+
jobCmd.command("history").summary("View job history").description("View recent execution history for a scheduled job.").helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--json", "Print machine-readable output").option("--quiet", "Print only history record IDs").action(listJobHistory),
|
|
3637
|
+
[
|
|
3638
|
+
"peakurl job history peakurl_version_check",
|
|
3639
|
+
"peakurl job history peakurl_version_check --json"
|
|
3640
|
+
]
|
|
3641
|
+
);
|
|
3642
|
+
addExamples(
|
|
3643
|
+
jobCmd.command("clear-history").summary("Clear job history").description(
|
|
3644
|
+
"Clear execution history for all jobs or a specific job."
|
|
3645
|
+
).helpOption("-h, --help", "Show help").option("--job <id>", "Specific job identifier to clear").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(clearJobHistory),
|
|
3646
|
+
[
|
|
3647
|
+
"peakurl job clear-history",
|
|
3648
|
+
"peakurl job clear-history --job peakurl_version_check",
|
|
3649
|
+
"peakurl job clear-history --json"
|
|
3650
|
+
]
|
|
3651
|
+
);
|
|
3652
|
+
addExamples(
|
|
3653
|
+
jobCmd.command("schedule").summary("Update job schedule").description("Update the schedule configuration for a job.").helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--interval <seconds>", "Execution interval in seconds").option(
|
|
3654
|
+
"--preferred-time <time>",
|
|
3655
|
+
"Preferred run time (HH:MM or 'none')"
|
|
3656
|
+
).option("--enabled", "Enable the job").option("--disabled", "Disable the job").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(updateJobSchedule),
|
|
3657
|
+
[
|
|
3658
|
+
"peakurl job schedule peakurl_version_check --interval 43200",
|
|
3659
|
+
"peakurl job schedule peakurl_version_check --preferred-time 03:00",
|
|
3660
|
+
"peakurl job schedule peakurl_version_check --disabled"
|
|
3661
|
+
]
|
|
3662
|
+
);
|
|
3663
|
+
addExamples(
|
|
3664
|
+
jobCmd.command("reset").summary("Reset job schedule").description("Reset a job's schedule to its default configuration.").helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(resetJobSchedule),
|
|
3665
|
+
[
|
|
3666
|
+
"peakurl job reset peakurl_version_check",
|
|
3667
|
+
"peakurl job reset peakurl_version_check --json"
|
|
3668
|
+
]
|
|
3669
|
+
);
|
|
3670
|
+
addExamples(
|
|
3671
|
+
jobCmd.command("settings").summary("Manage scheduler settings").description("View or update global scheduler settings.").helpOption("-h, --help", "Show help").option(
|
|
3672
|
+
"--retention-days <days>",
|
|
3673
|
+
"Number of days to keep execution history"
|
|
3674
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print minimal output").action(updateJobSettings),
|
|
3675
|
+
[
|
|
3676
|
+
"peakurl job settings",
|
|
3677
|
+
"peakurl job settings --retention-days 14",
|
|
3678
|
+
"peakurl job settings --json"
|
|
3679
|
+
]
|
|
3680
|
+
);
|
|
3681
|
+
const webhook = program.command("webhook").summary("Manage webhooks").helpOption("-h, --help", "Show help").description("Manage outbound webhook integrations.");
|
|
2726
3682
|
addExamples(webhook, [
|
|
3683
|
+
"peakurl webhook",
|
|
2727
3684
|
"peakurl webhook list",
|
|
2728
3685
|
"peakurl webhook create https://example.com/api/webhooks/peakurl --event link.clicked",
|
|
2729
3686
|
"peakurl webhook events"
|
|
2730
3687
|
]);
|
|
2731
3688
|
addExamples(
|
|
2732
|
-
webhook.command("list").summary("List webhooks").description("List outbound webhooks.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print minimal webhook identifiers").action(listWebhooks),
|
|
2733
|
-
[
|
|
3689
|
+
webhook.command("list", { isDefault: true }).summary("List webhooks").description("List outbound webhooks.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print minimal webhook identifiers").action(listWebhooks),
|
|
3690
|
+
[
|
|
3691
|
+
"peakurl webhook",
|
|
3692
|
+
"peakurl webhook list",
|
|
3693
|
+
"peakurl webhook list --json"
|
|
3694
|
+
]
|
|
2734
3695
|
);
|
|
2735
3696
|
addExamples(
|
|
2736
3697
|
webhook.command("create").summary("Create a webhook").description("Create an outbound webhook.").helpOption("-h, --help", "Show help").argument("<url>", "Webhook endpoint URL").option(
|