peakurl 1.1.0 → 1.1.2
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 +37 -7
- package/bin/peakurl.js +788 -25
- package/man/peakurl.1 +105 -14
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The official command-line interface for PeakURL.
|
|
|
6
6
|
|
|
7
7
|
Use `peakurl` to create short links, inspect existing links, and manage your PeakURL account from the terminal.
|
|
8
8
|
|
|
9
|
-
Learn more in the full CLI docs
|
|
9
|
+
Learn more in the full CLI docs [here.](https://go.peakurl.org/2aae02)
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -66,6 +66,7 @@ export PEAKURL_API_KEY=0123456789abcdef0123456789abcdef0123456789abcdef
|
|
|
66
66
|
| `peakurl status` | Show the current system status snapshot for the site. |
|
|
67
67
|
| `peakurl core download` | Download and extract the latest PeakURL core package. |
|
|
68
68
|
| `peakurl create <url>` | Create a new short link. |
|
|
69
|
+
| `peakurl edit <id-or-alias>` | Update an existing link's destination URL or metadata. |
|
|
69
70
|
| `peakurl import <file>` | Import links from a local CSV, JSON, or XML file. |
|
|
70
71
|
| `peakurl export` | Export accessible links as CSV, JSON, or XML. |
|
|
71
72
|
| `peakurl list` | List links in your account. |
|
|
@@ -73,6 +74,7 @@ export PEAKURL_API_KEY=0123456789abcdef0123456789abcdef0123456789abcdef
|
|
|
73
74
|
| `peakurl delete [id-or-alias...]` | Delete links by ID or alias, in bulk, or clear all links. |
|
|
74
75
|
| `peakurl activity <subcommand>` | View audit logs, delete activity records, or clear history. |
|
|
75
76
|
| `peakurl webhook <subcommand>` | List, create, delete, and inspect supported webhook events. |
|
|
77
|
+
| `peakurl job <cmd>` | Manage server-side scheduled jobs. |
|
|
76
78
|
| `peakurl update` | Show the latest available CLI version and install command. |
|
|
77
79
|
|
|
78
80
|
## Examples
|
|
@@ -83,9 +85,24 @@ Create a short link:
|
|
|
83
85
|
peakurl create \
|
|
84
86
|
https://example.com \
|
|
85
87
|
--alias example \
|
|
86
|
-
--title "Example"
|
|
88
|
+
--title "Example" \
|
|
89
|
+
--social-title "Example — Official Site" \
|
|
90
|
+
--social-description "Explore the Example platform." \
|
|
91
|
+
--social-image-url https://example.com/og.png
|
|
87
92
|
```
|
|
88
93
|
|
|
94
|
+
Edit an existing short link:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
peakurl edit example \
|
|
98
|
+
--url https://example.com/updated-page \
|
|
99
|
+
--social-title "Example — Updated Preview" \
|
|
100
|
+
--social-description "Updated Open Graph description." \
|
|
101
|
+
--social-image-url https://example.com/new-og.png
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
You can update a link's destination URL (`--url`), title, status, expiration, password, and social preview fields.
|
|
105
|
+
|
|
89
106
|
List links as JSON:
|
|
90
107
|
|
|
91
108
|
```bash
|
|
@@ -228,6 +245,19 @@ Check the latest available CLI version:
|
|
|
228
245
|
peakurl update --check
|
|
229
246
|
```
|
|
230
247
|
|
|
248
|
+
Manage scheduled jobs:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# List all registered scheduled jobs
|
|
252
|
+
peakurl job list
|
|
253
|
+
|
|
254
|
+
# Force a job to run immediately
|
|
255
|
+
peakurl job run peakurl_version_check
|
|
256
|
+
|
|
257
|
+
# Trigger all currently due jobs
|
|
258
|
+
peakurl job run-due
|
|
259
|
+
```
|
|
260
|
+
|
|
231
261
|
Show the recommended install command:
|
|
232
262
|
|
|
233
263
|
```bash
|
|
@@ -254,8 +284,8 @@ export PEAKURL_DISABLE_UPDATE_CHECK=1
|
|
|
254
284
|
|
|
255
285
|
## Links
|
|
256
286
|
|
|
257
|
-
- Website
|
|
258
|
-
- CLI docs
|
|
259
|
-
- API docs
|
|
260
|
-
- npm package
|
|
261
|
-
- Issues
|
|
287
|
+
- [Website](https://peakurl.org/)
|
|
288
|
+
- [CLI docs](https://go.peakurl.org/2aae02)
|
|
289
|
+
- [API docs](https://go.peakurl.org/d373f6)
|
|
290
|
+
- [npm package](https://go.peakurl.org/cli)
|
|
291
|
+
- [Issues](https://github.com/PeakURL/CLI/issues)
|
package/bin/peakurl.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFile as
|
|
4
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5
5
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
6
6
|
|
|
7
|
+
// src/api/client.ts
|
|
8
|
+
import { readFile, stat } from "fs/promises";
|
|
9
|
+
import { basename, extname } from "path";
|
|
10
|
+
|
|
7
11
|
// src/lib/errors.ts
|
|
8
12
|
var CliError = class extends Error {
|
|
9
13
|
exitCode;
|
|
@@ -100,6 +104,54 @@ function normalizeWebhookUrl(value) {
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
// src/api/client.ts
|
|
107
|
+
var IMAGE_MIME_BY_EXTENSION = {
|
|
108
|
+
".jpg": "image/jpeg",
|
|
109
|
+
".jpeg": "image/jpeg",
|
|
110
|
+
".png": "image/png",
|
|
111
|
+
".webp": "image/webp",
|
|
112
|
+
".gif": "image/gif"
|
|
113
|
+
};
|
|
114
|
+
var MAX_SOCIAL_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
115
|
+
async function toFormData(fields, socialImagePath) {
|
|
116
|
+
const ext = extname(socialImagePath).toLowerCase();
|
|
117
|
+
const mimeType = IMAGE_MIME_BY_EXTENSION[ext];
|
|
118
|
+
if (!mimeType) {
|
|
119
|
+
throw new CliError(
|
|
120
|
+
"Invalid social image file type. Only JPG, PNG, WEBP, and GIF images are allowed."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
let fileStats;
|
|
124
|
+
try {
|
|
125
|
+
fileStats = await stat(socialImagePath);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new CliError(
|
|
128
|
+
`Could not read social image file ${socialImagePath}.`,
|
|
129
|
+
1,
|
|
130
|
+
{
|
|
131
|
+
cause: error instanceof Error ? error : void 0
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (fileStats.size > MAX_SOCIAL_IMAGE_BYTES) {
|
|
136
|
+
throw new CliError(
|
|
137
|
+
"Social image file is too large. Maximum size is 5MB."
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const buffer = await readFile(socialImagePath);
|
|
141
|
+
const formData = new FormData();
|
|
142
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
143
|
+
if (key === "socialImagePath" || typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
formData.append(key, String(value));
|
|
147
|
+
}
|
|
148
|
+
formData.append(
|
|
149
|
+
"socialImage",
|
|
150
|
+
new Blob([buffer], { type: mimeType }),
|
|
151
|
+
basename(socialImagePath)
|
|
152
|
+
);
|
|
153
|
+
return formData;
|
|
154
|
+
}
|
|
103
155
|
function isApiResponse(value) {
|
|
104
156
|
return Boolean(
|
|
105
157
|
value && typeof value === "object" && "success" in value && "message" in value && "timestamp" in value
|
|
@@ -120,7 +172,6 @@ var ApiClient = class {
|
|
|
120
172
|
constructor(config) {
|
|
121
173
|
this.config = config;
|
|
122
174
|
}
|
|
123
|
-
config;
|
|
124
175
|
/**
|
|
125
176
|
* Loads the currently authenticated user.
|
|
126
177
|
*
|
|
@@ -146,8 +197,38 @@ var ApiClient = class {
|
|
|
146
197
|
* @param payload Request body accepted by `POST /api/v1/urls`.
|
|
147
198
|
* @returns API response envelope containing the created link.
|
|
148
199
|
*/
|
|
149
|
-
createUrl(payload) {
|
|
150
|
-
|
|
200
|
+
async createUrl(payload) {
|
|
201
|
+
if (payload.socialImagePath) {
|
|
202
|
+
const formData = await toFormData(
|
|
203
|
+
payload,
|
|
204
|
+
payload.socialImagePath
|
|
205
|
+
);
|
|
206
|
+
return this.request("POST", "urls", formData);
|
|
207
|
+
}
|
|
208
|
+
const { socialImagePath: _unused, ...jsonPayload } = payload;
|
|
209
|
+
return this.request("POST", "urls", jsonPayload);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Updates an existing short URL by its stable row ID.
|
|
213
|
+
*
|
|
214
|
+
* Sends `POST /api/v1/urls/{id}` with `multipart/form-data` when uploading a
|
|
215
|
+
* local social preview image, or `PUT /api/v1/urls/{id}` with JSON otherwise.
|
|
216
|
+
*
|
|
217
|
+
* @param id Stable link row ID.
|
|
218
|
+
* @param payload Fields to update on the short link.
|
|
219
|
+
* @returns API response envelope containing the updated link.
|
|
220
|
+
*/
|
|
221
|
+
async updateUrl(id, payload) {
|
|
222
|
+
const path = `urls/${encodeURIComponent(id)}`;
|
|
223
|
+
if (payload.socialImagePath) {
|
|
224
|
+
const formData = await toFormData(
|
|
225
|
+
payload,
|
|
226
|
+
payload.socialImagePath
|
|
227
|
+
);
|
|
228
|
+
return this.request("POST", path, formData);
|
|
229
|
+
}
|
|
230
|
+
const { socialImagePath: _unused, ...jsonPayload } = payload;
|
|
231
|
+
return this.request("PUT", path, jsonPayload);
|
|
151
232
|
}
|
|
152
233
|
/**
|
|
153
234
|
* Lists short URLs with optional pagination and filtering.
|
|
@@ -314,6 +395,86 @@ var ApiClient = class {
|
|
|
314
395
|
`webhooks/${encodeURIComponent(id)}`
|
|
315
396
|
);
|
|
316
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Loads the current status of the cron scheduler.
|
|
400
|
+
*
|
|
401
|
+
* @returns API response envelope containing the scheduler status.
|
|
402
|
+
*/
|
|
403
|
+
getJobStatus() {
|
|
404
|
+
return this.request("GET", "system/cron");
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Triggers all due cron jobs to run.
|
|
408
|
+
*
|
|
409
|
+
* @returns API response envelope containing the execution results.
|
|
410
|
+
*/
|
|
411
|
+
runDueJobs() {
|
|
412
|
+
return this.request("POST", "system/cron/run");
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Forces a specific cron job to run immediately.
|
|
416
|
+
*
|
|
417
|
+
* @param id Job identifier.
|
|
418
|
+
* @returns API response envelope containing the execution result.
|
|
419
|
+
*/
|
|
420
|
+
runJob(id) {
|
|
421
|
+
return this.request(
|
|
422
|
+
"POST",
|
|
423
|
+
`system/cron/run/${encodeURIComponent(id)}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Clears cron execution history.
|
|
428
|
+
*
|
|
429
|
+
* @param jobId Optional job identifier to clear history only for one job.
|
|
430
|
+
* @returns API response envelope containing the deleted count.
|
|
431
|
+
*/
|
|
432
|
+
clearJobHistory(jobId) {
|
|
433
|
+
return this.request(
|
|
434
|
+
"POST",
|
|
435
|
+
"system/cron/history/clear",
|
|
436
|
+
jobId ? { job_id: jobId } : void 0
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Updates the schedule configuration for a cron job.
|
|
441
|
+
*
|
|
442
|
+
* @param id Job identifier.
|
|
443
|
+
* @param payload New configuration options.
|
|
444
|
+
* @returns API response envelope containing the updated job.
|
|
445
|
+
*/
|
|
446
|
+
updateJobSchedule(id, payload) {
|
|
447
|
+
return this.request(
|
|
448
|
+
"PATCH",
|
|
449
|
+
`system/cron/jobs/${encodeURIComponent(id)}`,
|
|
450
|
+
payload
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Resets a cron job schedule to its default configuration.
|
|
455
|
+
*
|
|
456
|
+
* @param id Job identifier.
|
|
457
|
+
* @returns API response envelope containing the restored job.
|
|
458
|
+
*/
|
|
459
|
+
resetJobSchedule(id) {
|
|
460
|
+
return this.request(
|
|
461
|
+
"POST",
|
|
462
|
+
`system/cron/jobs/${encodeURIComponent(id)}/reset`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Updates the global cron settings, such as history retention.
|
|
467
|
+
*
|
|
468
|
+
* @param payload New global settings.
|
|
469
|
+
* @returns API response envelope containing the updated retention settings.
|
|
470
|
+
*/
|
|
471
|
+
updateJobSettings(payload) {
|
|
472
|
+
return this.request(
|
|
473
|
+
"POST",
|
|
474
|
+
"system/cron/settings",
|
|
475
|
+
payload
|
|
476
|
+
);
|
|
477
|
+
}
|
|
317
478
|
/**
|
|
318
479
|
* Performs one authenticated API request and normalizes the response.
|
|
319
480
|
*
|
|
@@ -327,15 +488,16 @@ var ApiClient = class {
|
|
|
327
488
|
async request(method, path, body, query) {
|
|
328
489
|
const url = buildApiUrl(this.config.apiBaseUrl, path, query);
|
|
329
490
|
let response;
|
|
491
|
+
const isFormData = body instanceof FormData;
|
|
330
492
|
try {
|
|
331
493
|
response = await fetch(url, {
|
|
332
494
|
method,
|
|
333
495
|
headers: {
|
|
334
496
|
Accept: "application/json",
|
|
335
497
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
336
|
-
...body ? { "Content-Type": "application/json" } : {}
|
|
498
|
+
...body && !isFormData ? { "Content-Type": "application/json" } : {}
|
|
337
499
|
},
|
|
338
|
-
body: body ? JSON.stringify(body) : void 0
|
|
500
|
+
body: isFormData ? body : body ? JSON.stringify(body) : void 0
|
|
339
501
|
});
|
|
340
502
|
} catch (error) {
|
|
341
503
|
throw new CliError(networkError(this.config.apiBaseUrl, error), 1, {
|
|
@@ -613,7 +775,7 @@ function formatActivitySummary(data, count) {
|
|
|
613
775
|
}
|
|
614
776
|
|
|
615
777
|
// src/config/store.ts
|
|
616
|
-
import { chmod, mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
778
|
+
import { chmod, mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
|
|
617
779
|
import { dirname, join } from "path";
|
|
618
780
|
import envPaths from "env-paths";
|
|
619
781
|
var CONFIG_FILENAME = "config.json";
|
|
@@ -652,7 +814,7 @@ var ConfigStore = class {
|
|
|
652
814
|
*/
|
|
653
815
|
async load() {
|
|
654
816
|
try {
|
|
655
|
-
const content = await
|
|
817
|
+
const content = await readFile2(this.filePath, "utf8");
|
|
656
818
|
const parsed = JSON.parse(content);
|
|
657
819
|
const apiBaseUrl = typeof parsed?.apiBaseUrl === "string" ? parsed.apiBaseUrl : typeof parsed?.baseUrl === "string" ? parsed.baseUrl : void 0;
|
|
658
820
|
if (typeof apiBaseUrl !== "string" || typeof parsed?.apiKey !== "string") {
|
|
@@ -742,7 +904,7 @@ var StateStore = class {
|
|
|
742
904
|
*/
|
|
743
905
|
async load() {
|
|
744
906
|
try {
|
|
745
|
-
const content = await
|
|
907
|
+
const content = await readFile2(this.filePath, "utf8");
|
|
746
908
|
const parsed = JSON.parse(content);
|
|
747
909
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
748
910
|
} catch {
|
|
@@ -1161,7 +1323,10 @@ var EXPORT_HEADERS = [
|
|
|
1161
1323
|
"short_url",
|
|
1162
1324
|
"clicks",
|
|
1163
1325
|
"unique_clicks",
|
|
1164
|
-
"created_at"
|
|
1326
|
+
"created_at",
|
|
1327
|
+
"social_title",
|
|
1328
|
+
"social_description",
|
|
1329
|
+
"social_image_url"
|
|
1165
1330
|
];
|
|
1166
1331
|
function text(value) {
|
|
1167
1332
|
if (typeof value === "string") {
|
|
@@ -1220,7 +1385,10 @@ function buildExportRows(links) {
|
|
|
1220
1385
|
short_url: text(link.shortUrl),
|
|
1221
1386
|
clicks: typeof link.clicks === "number" ? link.clicks : "",
|
|
1222
1387
|
unique_clicks: typeof link.uniqueClicks === "number" ? link.uniqueClicks : "",
|
|
1223
|
-
created_at: text(link.createdAt)
|
|
1388
|
+
created_at: text(link.createdAt),
|
|
1389
|
+
social_title: text(link.socialTitle) || text(link.socialPreview?.title),
|
|
1390
|
+
social_description: text(link.socialDescription) || text(link.socialPreview?.description),
|
|
1391
|
+
social_image_url: text(link.socialImageUrl) || text(link.socialPreview?.externalImageUrl) || text(link.socialPreview?.imageUrl)
|
|
1224
1392
|
}));
|
|
1225
1393
|
}
|
|
1226
1394
|
function serializeLinkExport(links, format) {
|
|
@@ -1240,6 +1408,9 @@ function serializeLinkExport(links, format) {
|
|
|
1240
1408
|
<clicks>${xmlValue(row2.clicks)}</clicks>
|
|
1241
1409
|
<uniqueClicks>${xmlValue(row2.unique_clicks)}</uniqueClicks>
|
|
1242
1410
|
<createdAt>${xmlValue(row2.created_at)}</createdAt>
|
|
1411
|
+
<socialTitle>${xmlValue(row2.social_title)}</socialTitle>
|
|
1412
|
+
<socialDescription>${xmlValue(row2.social_description)}</socialDescription>
|
|
1413
|
+
<socialImageUrl>${xmlValue(row2.social_image_url)}</socialImageUrl>
|
|
1243
1414
|
</url>`
|
|
1244
1415
|
).join("\n");
|
|
1245
1416
|
return `<urls>
|
|
@@ -1258,7 +1429,7 @@ ${body}
|
|
|
1258
1429
|
}
|
|
1259
1430
|
|
|
1260
1431
|
// src/lib/imports.ts
|
|
1261
|
-
import { readFile as
|
|
1432
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1262
1433
|
function text2(value) {
|
|
1263
1434
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1264
1435
|
}
|
|
@@ -1345,7 +1516,16 @@ function normalizeImportRow(value) {
|
|
|
1345
1516
|
...text2(value.utmMedium) ? { utmMedium: text2(value.utmMedium) } : {},
|
|
1346
1517
|
...text2(value.utmCampaign) ? { utmCampaign: text2(value.utmCampaign) } : {},
|
|
1347
1518
|
...text2(value.utmTerm) ? { utmTerm: text2(value.utmTerm) } : {},
|
|
1348
|
-
...text2(value.utmContent) ? { utmContent: text2(value.utmContent) } : {}
|
|
1519
|
+
...text2(value.utmContent) ? { utmContent: text2(value.utmContent) } : {},
|
|
1520
|
+
...text2(value.socialTitle) || text2(value.social_title) ? {
|
|
1521
|
+
socialTitle: text2(value.socialTitle) || text2(value.social_title)
|
|
1522
|
+
} : {},
|
|
1523
|
+
...text2(value.socialDescription) || text2(value.social_description) ? {
|
|
1524
|
+
socialDescription: text2(value.socialDescription) || text2(value.social_description)
|
|
1525
|
+
} : {},
|
|
1526
|
+
...text2(value.socialImageUrl) || text2(value.social_image_url) ? {
|
|
1527
|
+
socialImageUrl: text2(value.socialImageUrl) || text2(value.social_image_url)
|
|
1528
|
+
} : {}
|
|
1349
1529
|
};
|
|
1350
1530
|
}
|
|
1351
1531
|
function parseJson(text6) {
|
|
@@ -1416,6 +1596,18 @@ function parseCsv(text6) {
|
|
|
1416
1596
|
}
|
|
1417
1597
|
if (header === "utmcontent") {
|
|
1418
1598
|
entry.utmContent = value;
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (header === "socialtitle") {
|
|
1602
|
+
entry.socialTitle = value;
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (header === "socialdescription") {
|
|
1606
|
+
entry.socialDescription = value;
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (header === "socialimageurl" || header === "socialimage") {
|
|
1610
|
+
entry.socialImageUrl = value;
|
|
1419
1611
|
}
|
|
1420
1612
|
});
|
|
1421
1613
|
const link = normalizeImportRow(entry);
|
|
@@ -1453,7 +1645,10 @@ function parseXml(text6) {
|
|
|
1453
1645
|
utmMedium: getValue("utmMedium"),
|
|
1454
1646
|
utmCampaign: getValue("utmCampaign"),
|
|
1455
1647
|
utmTerm: getValue("utmTerm"),
|
|
1456
|
-
utmContent: getValue("utmContent")
|
|
1648
|
+
utmContent: getValue("utmContent"),
|
|
1649
|
+
socialTitle: getValue("socialTitle"),
|
|
1650
|
+
socialDescription: getValue("socialDescription"),
|
|
1651
|
+
socialImageUrl: getValue("socialImageUrl")
|
|
1457
1652
|
});
|
|
1458
1653
|
}).filter((item) => Boolean(item));
|
|
1459
1654
|
}
|
|
@@ -1472,7 +1667,7 @@ function getImportFormat(filePath, value) {
|
|
|
1472
1667
|
async function readImportRows(filePath, format) {
|
|
1473
1668
|
let textContent;
|
|
1474
1669
|
try {
|
|
1475
|
-
textContent = await
|
|
1670
|
+
textContent = await readFile3(filePath, "utf8");
|
|
1476
1671
|
} catch (error) {
|
|
1477
1672
|
throw new CliError(`Could not read import file ${filePath}.`, 1, {
|
|
1478
1673
|
cause: error instanceof Error ? error : void 0
|
|
@@ -1593,6 +1788,10 @@ function getQuietLinkValue(link) {
|
|
|
1593
1788
|
return getLinkShortUrl(link) || getLinkAlias(link) || getLinkId(link) || "";
|
|
1594
1789
|
}
|
|
1595
1790
|
function formatLinkDetails(link) {
|
|
1791
|
+
const socialPreview = asObject2(link.socialPreview);
|
|
1792
|
+
const socialTitle = asString3(link.socialTitle) || asString3(socialPreview?.title);
|
|
1793
|
+
const socialDescription = asString3(link.socialDescription) || asString3(socialPreview?.description);
|
|
1794
|
+
const socialImage = asString3(link.socialImageUrl) || asString3(socialPreview?.externalImageUrl) || asString3(socialPreview?.imageUrl);
|
|
1596
1795
|
const rows2 = [
|
|
1597
1796
|
["ID", getLinkId(link)],
|
|
1598
1797
|
["Alias", getLinkAlias(link)],
|
|
@@ -1600,6 +1799,9 @@ function formatLinkDetails(link) {
|
|
|
1600
1799
|
["Destination", getLinkDestination(link)],
|
|
1601
1800
|
["Title", asString3(link.title)],
|
|
1602
1801
|
["Status", asString3(link.status)],
|
|
1802
|
+
["Social Title", socialTitle],
|
|
1803
|
+
["Social Description", socialDescription],
|
|
1804
|
+
["Social Image", socialImage],
|
|
1603
1805
|
[
|
|
1604
1806
|
"Clicks",
|
|
1605
1807
|
asNumber2(link.clicks) === void 0 ? void 0 : String(link.clicks)
|
|
@@ -1985,6 +2187,121 @@ ${checks}` : void 0,
|
|
|
1985
2187
|
return sections.length > 0 ? sections.join("\n\n") : "No system status fields returned.";
|
|
1986
2188
|
}
|
|
1987
2189
|
|
|
2190
|
+
// src/lib/job.ts
|
|
2191
|
+
function formatInterval(seconds) {
|
|
2192
|
+
if (seconds < 60) return `${seconds}s`;
|
|
2193
|
+
const minutes = Math.floor(seconds / 60);
|
|
2194
|
+
if (minutes < 60) return `${minutes}m`;
|
|
2195
|
+
const hours = Math.floor(minutes / 60);
|
|
2196
|
+
if (hours < 24) return `${hours}h`;
|
|
2197
|
+
const days = Math.floor(hours / 24);
|
|
2198
|
+
return `${days}d`;
|
|
2199
|
+
}
|
|
2200
|
+
function formatDate(date) {
|
|
2201
|
+
if (!date) return "never";
|
|
2202
|
+
return new Date(date).toISOString().replace("T", " ").substring(0, 19);
|
|
2203
|
+
}
|
|
2204
|
+
function formatJobsList(status2) {
|
|
2205
|
+
const rows2 = status2.jobs.map((job) => [
|
|
2206
|
+
job.id,
|
|
2207
|
+
job.title,
|
|
2208
|
+
job.status,
|
|
2209
|
+
job.is_enabled ? "yes" : "no",
|
|
2210
|
+
formatDate(job.next_run_at)
|
|
2211
|
+
]);
|
|
2212
|
+
const table = formatTable(
|
|
2213
|
+
["ID", "Job", "Status", "Enabled", "Next Run"],
|
|
2214
|
+
rows2
|
|
2215
|
+
);
|
|
2216
|
+
const summary = `
|
|
2217
|
+
${status2.jobs_count} jobs registered.
|
|
2218
|
+
Timezone: ${status2.timezone}
|
|
2219
|
+
History retention: ${status2.retention_days} days`;
|
|
2220
|
+
return `${table}${summary}`;
|
|
2221
|
+
}
|
|
2222
|
+
function formatJobDetails(job) {
|
|
2223
|
+
const rows2 = [
|
|
2224
|
+
["ID", job.id],
|
|
2225
|
+
["Title", job.title],
|
|
2226
|
+
["Status", job.status],
|
|
2227
|
+
["Enabled", job.is_enabled ? "yes" : "no"],
|
|
2228
|
+
["Current interval", formatInterval(job.interval_seconds)],
|
|
2229
|
+
[
|
|
2230
|
+
"Recommended interval",
|
|
2231
|
+
formatInterval(job.recommended_interval_seconds)
|
|
2232
|
+
],
|
|
2233
|
+
["Preferred run time", job.preferred_run_time || "none"],
|
|
2234
|
+
["Customized", job.is_customized ? "yes" : "no"],
|
|
2235
|
+
["Next run", formatDate(job.next_run_at)],
|
|
2236
|
+
["Last run", formatDate(job.last_run_at)],
|
|
2237
|
+
["Last finished", formatDate(job.last_finished_at)],
|
|
2238
|
+
["Attempts", String(job.attempts)],
|
|
2239
|
+
["Maximum attempts", String(job.max_attempts)],
|
|
2240
|
+
["Last error", job.last_error || "none"]
|
|
2241
|
+
];
|
|
2242
|
+
let out = formatDetailsTable(rows2);
|
|
2243
|
+
if (job.recent_runs && job.recent_runs.length > 0) {
|
|
2244
|
+
out += `
|
|
2245
|
+
|
|
2246
|
+
Recent Runs:
|
|
2247
|
+
${formatJobHistory(job.recent_runs)}`;
|
|
2248
|
+
}
|
|
2249
|
+
return out;
|
|
2250
|
+
}
|
|
2251
|
+
function formatJobHistory(runs) {
|
|
2252
|
+
if (!runs || runs.length === 0) {
|
|
2253
|
+
return "No recent runs.";
|
|
2254
|
+
}
|
|
2255
|
+
const rows2 = runs.map((run) => [
|
|
2256
|
+
run.id,
|
|
2257
|
+
run.status,
|
|
2258
|
+
String(run.attempt),
|
|
2259
|
+
formatDate(run.started_at),
|
|
2260
|
+
formatDate(run.finished_at),
|
|
2261
|
+
run.duration_ms ? `${run.duration_ms}ms` : "-",
|
|
2262
|
+
run.output_summary || "-",
|
|
2263
|
+
run.error_message || "-"
|
|
2264
|
+
]);
|
|
2265
|
+
return formatTable(
|
|
2266
|
+
[
|
|
2267
|
+
"Run ID",
|
|
2268
|
+
"Status",
|
|
2269
|
+
"Attempt",
|
|
2270
|
+
"Started",
|
|
2271
|
+
"Finished",
|
|
2272
|
+
"Duration",
|
|
2273
|
+
"Summary",
|
|
2274
|
+
"Error"
|
|
2275
|
+
],
|
|
2276
|
+
rows2
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
function formatRunJobResult(result) {
|
|
2280
|
+
let out = `Job: ${result.job_id}
|
|
2281
|
+
Status: ${result.status}`;
|
|
2282
|
+
if (result.summary) {
|
|
2283
|
+
out += `
|
|
2284
|
+
Summary: ${result.summary}`;
|
|
2285
|
+
}
|
|
2286
|
+
if (result.error) {
|
|
2287
|
+
out += `
|
|
2288
|
+
Error: ${result.error}`;
|
|
2289
|
+
}
|
|
2290
|
+
return out;
|
|
2291
|
+
}
|
|
2292
|
+
function formatRunDueResult(result) {
|
|
2293
|
+
if (!result.results || result.results.length === 0) {
|
|
2294
|
+
return "No jobs were due.";
|
|
2295
|
+
}
|
|
2296
|
+
const rows2 = result.results.map((r) => [
|
|
2297
|
+
r.job_id,
|
|
2298
|
+
r.status,
|
|
2299
|
+
r.summary || "-",
|
|
2300
|
+
r.error || "-"
|
|
2301
|
+
]);
|
|
2302
|
+
return formatTable(["Job ID", "Status", "Summary", "Error"], rows2);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
1988
2305
|
// src/lib/update.ts
|
|
1989
2306
|
var PACKAGE_NAME = "peakurl";
|
|
1990
2307
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
@@ -2473,6 +2790,11 @@ function normalizeExpiresAt(value) {
|
|
|
2473
2790
|
return value;
|
|
2474
2791
|
}
|
|
2475
2792
|
async function createLink(destinationUrl, options) {
|
|
2793
|
+
if (options.socialImage && options.socialImageUrl) {
|
|
2794
|
+
throw new CliError(
|
|
2795
|
+
"Use either --social-image or --social-image-url, not both."
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2476
2798
|
const config = await getAuthConfig(process.env);
|
|
2477
2799
|
const response = await new ApiClient(config).createUrl({
|
|
2478
2800
|
destinationUrl: normalizeDestinationUrl(destinationUrl),
|
|
@@ -2485,7 +2807,15 @@ async function createLink(destinationUrl, options) {
|
|
|
2485
2807
|
...options.utmMedium ? { utmMedium: options.utmMedium } : {},
|
|
2486
2808
|
...options.utmCampaign ? { utmCampaign: options.utmCampaign } : {},
|
|
2487
2809
|
...options.utmTerm ? { utmTerm: options.utmTerm } : {},
|
|
2488
|
-
...options.utmContent ? { utmContent: options.utmContent } : {}
|
|
2810
|
+
...options.utmContent ? { utmContent: options.utmContent } : {},
|
|
2811
|
+
...options.socialTitle ? { socialTitle: options.socialTitle } : {},
|
|
2812
|
+
...options.socialDescription ? { socialDescription: options.socialDescription } : {},
|
|
2813
|
+
...options.socialImageUrl ? {
|
|
2814
|
+
socialImageUrl: normalizeDestinationUrl(
|
|
2815
|
+
options.socialImageUrl
|
|
2816
|
+
)
|
|
2817
|
+
} : {},
|
|
2818
|
+
...options.socialImage ? { socialImagePath: resolve2(options.socialImage) } : {}
|
|
2489
2819
|
});
|
|
2490
2820
|
if (options.json) {
|
|
2491
2821
|
writeJson(response);
|
|
@@ -2498,6 +2828,67 @@ async function createLink(destinationUrl, options) {
|
|
|
2498
2828
|
writeStdout(successLine(response.message));
|
|
2499
2829
|
writeStdout(formatLinkDetails(response.data));
|
|
2500
2830
|
}
|
|
2831
|
+
async function editLink(idOrAlias, options) {
|
|
2832
|
+
if (options.socialImage && options.socialImageUrl) {
|
|
2833
|
+
throw new CliError(
|
|
2834
|
+
"Use either --social-image or --social-image-url, not both."
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
if (options.removeSocialImage && (options.socialImage || options.socialImageUrl)) {
|
|
2838
|
+
throw new CliError(
|
|
2839
|
+
"Cannot combine --remove-social-image with --social-image or --social-image-url."
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
if (options.password !== void 0 && options.clearPassword) {
|
|
2843
|
+
throw new CliError(
|
|
2844
|
+
"Use either --password or --clear-password, not both."
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
if (options.expiresAt !== void 0 && options.clearExpiresAt) {
|
|
2848
|
+
throw new CliError(
|
|
2849
|
+
"Use either --expires-at or --clear-expires-at, not both."
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
const payload = {
|
|
2853
|
+
...options.url !== void 0 ? { destinationUrl: normalizeDestinationUrl(options.url) } : {},
|
|
2854
|
+
...options.title !== void 0 ? { title: options.title } : {},
|
|
2855
|
+
...options.clearPassword ? { clearPassword: true } : options.password !== void 0 ? { password: options.password } : {},
|
|
2856
|
+
...options.status !== void 0 ? { status: options.status } : {},
|
|
2857
|
+
...options.clearExpiresAt ? { expiresAt: "" } : options.expiresAt !== void 0 ? { expiresAt: normalizeExpiresAt(options.expiresAt) } : {},
|
|
2858
|
+
...options.socialTitle !== void 0 ? { socialTitle: options.socialTitle } : {},
|
|
2859
|
+
...options.socialDescription !== void 0 ? { socialDescription: options.socialDescription } : {},
|
|
2860
|
+
...options.socialImageUrl !== void 0 ? {
|
|
2861
|
+
socialImageUrl: options.socialImageUrl ? normalizeDestinationUrl(options.socialImageUrl) : ""
|
|
2862
|
+
} : {},
|
|
2863
|
+
...options.socialImage ? { socialImagePath: resolve2(options.socialImage) } : {},
|
|
2864
|
+
...options.removeSocialImage ? { removeSocialImage: true } : {}
|
|
2865
|
+
};
|
|
2866
|
+
if (Object.keys(payload).length === 0) {
|
|
2867
|
+
throw new CliError(
|
|
2868
|
+
"Specify at least one field to update (for example --title, --url, --social-title, --social-description, or --social-image-url)."
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
const config = await getAuthConfig(process.env);
|
|
2872
|
+
const client = new ApiClient(config);
|
|
2873
|
+
const lookupResponse = await client.getUrl(idOrAlias.trim());
|
|
2874
|
+
const resolvedId = getLinkId(lookupResponse.data);
|
|
2875
|
+
if (!resolvedId) {
|
|
2876
|
+
throw new CliError(
|
|
2877
|
+
"PeakURL returned a link record without an ID, so the CLI cannot update it safely."
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
const response = await client.updateUrl(resolvedId, payload);
|
|
2881
|
+
if (options.json) {
|
|
2882
|
+
writeJson(response);
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
if (options.quiet) {
|
|
2886
|
+
writeStdout(getQuietLinkValue(response.data));
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
writeStdout(successLine(response.message || "Short URL updated."));
|
|
2890
|
+
writeStdout(formatLinkDetails(response.data));
|
|
2891
|
+
}
|
|
2501
2892
|
async function importLinks(filePath, options) {
|
|
2502
2893
|
const format = getImportFormat(filePath, options.format);
|
|
2503
2894
|
const urls = await readImportRows(filePath, format);
|
|
@@ -2927,6 +3318,230 @@ async function whoami(options) {
|
|
|
2927
3318
|
writeStdout(userTable(response.data, config.apiBaseUrl));
|
|
2928
3319
|
}
|
|
2929
3320
|
|
|
3321
|
+
// src/commands/job.ts
|
|
3322
|
+
async function getClient() {
|
|
3323
|
+
const config = await getAuthConfig(process.env);
|
|
3324
|
+
return new ApiClient(config);
|
|
3325
|
+
}
|
|
3326
|
+
async function listJobs(options) {
|
|
3327
|
+
const client = await getClient();
|
|
3328
|
+
const response = await client.getJobStatus();
|
|
3329
|
+
if (options.json) {
|
|
3330
|
+
writeJson(response);
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
if (options.quiet) {
|
|
3334
|
+
const ids = response.data.jobs.map((job) => job.id).join("\n");
|
|
3335
|
+
if (ids) {
|
|
3336
|
+
writeStdout(ids);
|
|
3337
|
+
}
|
|
3338
|
+
return;
|
|
3339
|
+
}
|
|
3340
|
+
writeStdout(successLine(response.message));
|
|
3341
|
+
writeStdout();
|
|
3342
|
+
writeStdout(formatJobsList(response.data));
|
|
3343
|
+
}
|
|
3344
|
+
async function getJob(id, options) {
|
|
3345
|
+
const client = await getClient();
|
|
3346
|
+
const response = await client.getJobStatus();
|
|
3347
|
+
const job = response.data.jobs.find((j) => j.id === id);
|
|
3348
|
+
if (!job) {
|
|
3349
|
+
throw new CliError(`Job '${id}' not found.`, 1);
|
|
3350
|
+
}
|
|
3351
|
+
if (options.json) {
|
|
3352
|
+
writeJson({
|
|
3353
|
+
success: true,
|
|
3354
|
+
message: "Job loaded.",
|
|
3355
|
+
data: job,
|
|
3356
|
+
timestamp: response.timestamp
|
|
3357
|
+
});
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
if (options.quiet) {
|
|
3361
|
+
writeStdout(job.id);
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
writeStdout(successLine(`Job ${job.id} loaded.`));
|
|
3365
|
+
writeStdout();
|
|
3366
|
+
writeStdout(formatJobDetails(job));
|
|
3367
|
+
}
|
|
3368
|
+
async function runJob(id, options) {
|
|
3369
|
+
const client = await getClient();
|
|
3370
|
+
const response = await client.runJob(id);
|
|
3371
|
+
if (options.json) {
|
|
3372
|
+
writeJson(response);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
if (options.quiet) {
|
|
3376
|
+
writeStdout(response.data.status);
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
writeStdout(successLine(response.message));
|
|
3380
|
+
writeStdout();
|
|
3381
|
+
writeStdout(formatRunJobResult(response.data));
|
|
3382
|
+
}
|
|
3383
|
+
async function runDueJobs(options) {
|
|
3384
|
+
const client = await getClient();
|
|
3385
|
+
const response = await client.runDueJobs();
|
|
3386
|
+
if (options.json) {
|
|
3387
|
+
writeJson(response);
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
if (options.quiet) {
|
|
3391
|
+
const statuses = response.data.results.map((r) => r.status).join("\n");
|
|
3392
|
+
if (statuses) {
|
|
3393
|
+
writeStdout(statuses);
|
|
3394
|
+
}
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
writeStdout(successLine(response.message));
|
|
3398
|
+
writeStdout();
|
|
3399
|
+
writeStdout(formatRunDueResult(response.data));
|
|
3400
|
+
}
|
|
3401
|
+
async function listJobHistory(id, options) {
|
|
3402
|
+
const client = await getClient();
|
|
3403
|
+
const response = await client.getJobStatus();
|
|
3404
|
+
const job = response.data.jobs.find((j) => j.id === id);
|
|
3405
|
+
if (!job) {
|
|
3406
|
+
throw new CliError(`Job '${id}' not found.`, 1);
|
|
3407
|
+
}
|
|
3408
|
+
if (options.json) {
|
|
3409
|
+
writeJson({
|
|
3410
|
+
success: true,
|
|
3411
|
+
message: "History loaded.",
|
|
3412
|
+
data: job.recent_runs || [],
|
|
3413
|
+
timestamp: response.timestamp
|
|
3414
|
+
});
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
if (options.quiet) {
|
|
3418
|
+
const ids = (job.recent_runs || []).map((r) => r.id).join("\n");
|
|
3419
|
+
if (ids) {
|
|
3420
|
+
writeStdout(ids);
|
|
3421
|
+
}
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
writeStdout(successLine(`History for job ${job.id} loaded.`));
|
|
3425
|
+
writeStdout();
|
|
3426
|
+
writeStdout(formatJobHistory(job.recent_runs || []));
|
|
3427
|
+
}
|
|
3428
|
+
async function clearJobHistory(options) {
|
|
3429
|
+
const client = await getClient();
|
|
3430
|
+
const response = await client.clearJobHistory(options.job);
|
|
3431
|
+
if (options.json) {
|
|
3432
|
+
writeJson(response);
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
if (options.quiet) {
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
writeStdout(successLine(response.message));
|
|
3439
|
+
}
|
|
3440
|
+
async function updateJobSchedule(id, options) {
|
|
3441
|
+
const client = await getClient();
|
|
3442
|
+
if (options.enabled && options.disabled) {
|
|
3443
|
+
throw new CliError("Cannot specify both --enabled and --disabled.", 1);
|
|
3444
|
+
}
|
|
3445
|
+
const payload = {};
|
|
3446
|
+
if (options.interval !== void 0) {
|
|
3447
|
+
const interval = parseInt(options.interval, 10);
|
|
3448
|
+
if (isNaN(interval) || interval <= 0) {
|
|
3449
|
+
throw new CliError("Interval must be a positive integer.", 1);
|
|
3450
|
+
}
|
|
3451
|
+
payload.interval_seconds = interval;
|
|
3452
|
+
}
|
|
3453
|
+
if (options.preferredTime !== void 0) {
|
|
3454
|
+
if (options.preferredTime.toLowerCase() === "none" || options.preferredTime === "") {
|
|
3455
|
+
payload.preferred_run_time = null;
|
|
3456
|
+
} else if (/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test(options.preferredTime)) {
|
|
3457
|
+
payload.preferred_run_time = options.preferredTime;
|
|
3458
|
+
} else {
|
|
3459
|
+
throw new CliError(
|
|
3460
|
+
"Preferred time must be in HH:MM format or 'none'.",
|
|
3461
|
+
1
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
if (options.enabled !== void 0) {
|
|
3466
|
+
payload.is_enabled = true;
|
|
3467
|
+
} else if (options.disabled !== void 0) {
|
|
3468
|
+
payload.is_enabled = false;
|
|
3469
|
+
}
|
|
3470
|
+
if (Object.keys(payload).length === 0) {
|
|
3471
|
+
throw new CliError("No schedule changes requested.", 1);
|
|
3472
|
+
}
|
|
3473
|
+
const response = await client.updateJobSchedule(id, payload);
|
|
3474
|
+
if (options.json) {
|
|
3475
|
+
writeJson(response);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
if (options.quiet) {
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
writeStdout(successLine(response.message));
|
|
3482
|
+
writeStdout();
|
|
3483
|
+
writeStdout(formatJobDetails(response.data));
|
|
3484
|
+
}
|
|
3485
|
+
async function resetJobSchedule(id, options) {
|
|
3486
|
+
const client = await getClient();
|
|
3487
|
+
const response = await client.resetJobSchedule(id);
|
|
3488
|
+
if (options.json) {
|
|
3489
|
+
writeJson(response);
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
if (options.quiet) {
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
writeStdout(successLine(response.message));
|
|
3496
|
+
writeStdout();
|
|
3497
|
+
writeStdout(formatJobDetails(response.data));
|
|
3498
|
+
}
|
|
3499
|
+
async function updateJobSettings(options) {
|
|
3500
|
+
const client = await getClient();
|
|
3501
|
+
if (options.retentionDays !== void 0) {
|
|
3502
|
+
const days = parseInt(options.retentionDays, 10);
|
|
3503
|
+
if (isNaN(days) || days < 0) {
|
|
3504
|
+
throw new CliError(
|
|
3505
|
+
"Retention days must be a non-negative integer.",
|
|
3506
|
+
1
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
const response2 = await client.updateJobSettings({
|
|
3510
|
+
retention_days: days
|
|
3511
|
+
});
|
|
3512
|
+
if (options.json) {
|
|
3513
|
+
writeJson(response2);
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
if (options.quiet) {
|
|
3517
|
+
writeStdout(String(response2.data.retention_days));
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
writeStdout(successLine(response2.message));
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
const response = await client.getJobStatus();
|
|
3524
|
+
if (options.json) {
|
|
3525
|
+
writeJson({
|
|
3526
|
+
success: true,
|
|
3527
|
+
message: "Settings loaded.",
|
|
3528
|
+
data: {
|
|
3529
|
+
retention_days: response.data.retention_days,
|
|
3530
|
+
timezone: response.data.timezone
|
|
3531
|
+
},
|
|
3532
|
+
timestamp: response.timestamp
|
|
3533
|
+
});
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
if (options.quiet) {
|
|
3537
|
+
writeStdout(String(response.data.retention_days));
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
writeStdout(successLine("Settings loaded."));
|
|
3541
|
+
writeStdout(`History retention: ${response.data.retention_days} days`);
|
|
3542
|
+
writeStdout(`Timezone: ${response.data.timezone}`);
|
|
3543
|
+
}
|
|
3544
|
+
|
|
2930
3545
|
// src/index.ts
|
|
2931
3546
|
function parseNumber(label) {
|
|
2932
3547
|
return (value) => {
|
|
@@ -2947,12 +3562,12 @@ Examples:
|
|
|
2947
3562
|
${lines.map((line) => ` ${line}`).join("\n")}
|
|
2948
3563
|
|
|
2949
3564
|
Documentation:
|
|
2950
|
-
https://peakurl.org/
|
|
3565
|
+
https://go.peakurl.org/2aae02`
|
|
2951
3566
|
);
|
|
2952
3567
|
}
|
|
2953
3568
|
async function getCliVersion() {
|
|
2954
3569
|
const packageJson = new URL("../package.json", import.meta.url);
|
|
2955
|
-
const content = await
|
|
3570
|
+
const content = await readFile4(packageJson, "utf8");
|
|
2956
3571
|
const parsed = JSON.parse(content);
|
|
2957
3572
|
return parsed.version || "0.0.0";
|
|
2958
3573
|
}
|
|
@@ -2961,7 +3576,7 @@ function getRetryCommandName(argv) {
|
|
|
2961
3576
|
if (!first || first.startsWith("-")) {
|
|
2962
3577
|
return void 0;
|
|
2963
3578
|
}
|
|
2964
|
-
if (first === "webhook" || first === "
|
|
3579
|
+
if (first === "webhook" || first === "activity" || first === "job") {
|
|
2965
3580
|
const second = argv[3]?.trim();
|
|
2966
3581
|
if (second && !second.startsWith("-")) {
|
|
2967
3582
|
return `${first} ${second}`;
|
|
@@ -2969,10 +3584,33 @@ function getRetryCommandName(argv) {
|
|
|
2969
3584
|
}
|
|
2970
3585
|
return first;
|
|
2971
3586
|
}
|
|
3587
|
+
var COMMAND_SUGGESTIONS = {
|
|
3588
|
+
activities: "activity",
|
|
3589
|
+
webhooks: "webhook",
|
|
3590
|
+
jobs: "job",
|
|
3591
|
+
cron: "job",
|
|
3592
|
+
"scheduled-jobs": "job",
|
|
3593
|
+
links: "list",
|
|
3594
|
+
urls: "list"
|
|
3595
|
+
};
|
|
2972
3596
|
async function main() {
|
|
2973
3597
|
const program = new Command();
|
|
2974
3598
|
const version = await getCliVersion();
|
|
2975
|
-
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").
|
|
3599
|
+
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({
|
|
3600
|
+
outputError: (str, write) => {
|
|
3601
|
+
const match = /error: unknown command '([^']+)'/.exec(str);
|
|
3602
|
+
if (match && COMMAND_SUGGESTIONS[match[1]]) {
|
|
3603
|
+
const suggestion = COMMAND_SUGGESTIONS[match[1]];
|
|
3604
|
+
write(
|
|
3605
|
+
`error: unknown command '${match[1]}'. Did you mean 'peakurl ${suggestion}'?
|
|
3606
|
+
|
|
3607
|
+
`
|
|
3608
|
+
);
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
write(str);
|
|
3612
|
+
}
|
|
3613
|
+
}).showHelpAfterError().showSuggestionAfterError().addHelpText(
|
|
2976
3614
|
"after",
|
|
2977
3615
|
`
|
|
2978
3616
|
Get Started:
|
|
@@ -2984,13 +3622,16 @@ Common Commands:
|
|
|
2984
3622
|
peakurl status
|
|
2985
3623
|
peakurl core download
|
|
2986
3624
|
peakurl list --limit 10
|
|
3625
|
+
peakurl edit docs --social-title "Docs" --social-image-url https://example.com/og.png
|
|
2987
3626
|
peakurl import ./links.csv
|
|
2988
3627
|
peakurl export --format csv
|
|
3628
|
+
peakurl activity list
|
|
3629
|
+
peakurl job list
|
|
2989
3630
|
peakurl webhook list
|
|
2990
3631
|
peakurl update --check
|
|
2991
3632
|
|
|
2992
3633
|
Documentation:
|
|
2993
|
-
https://peakurl.org/
|
|
3634
|
+
https://go.peakurl.org/2aae02
|
|
2994
3635
|
|
|
2995
3636
|
Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
2996
3637
|
).exitOverride();
|
|
@@ -3045,13 +3686,50 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
|
3045
3686
|
).option(
|
|
3046
3687
|
"--expires-at <iso>",
|
|
3047
3688
|
"Expiration timestamp in ISO-8601 format"
|
|
3048
|
-
).option("--utm-source <value>", "UTM source").option("--utm-medium <value>", "UTM medium").option("--utm-campaign <value>", "UTM campaign").option("--utm-term <value>", "UTM term").option("--utm-content <value>", "UTM content").option("--
|
|
3689
|
+
).option("--utm-source <value>", "UTM source").option("--utm-medium <value>", "UTM medium").option("--utm-campaign <value>", "UTM campaign").option("--utm-term <value>", "UTM term").option("--utm-content <value>", "UTM content").option("--social-title <title>", "Social preview title").option("--social-description <text>", "Social preview description").option(
|
|
3690
|
+
"--social-image-url <url>",
|
|
3691
|
+
"Remote image URL for social preview"
|
|
3692
|
+
).option(
|
|
3693
|
+
"--social-image <path>",
|
|
3694
|
+
"Local image file path for social preview"
|
|
3695
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print only the created short URL").action(createLink),
|
|
3049
3696
|
[
|
|
3050
3697
|
"peakurl create https://example.com/docs --alias docs",
|
|
3051
|
-
'peakurl create https://example.com/launch --title "Launch Page"',
|
|
3698
|
+
'peakurl create https://example.com/launch --title "Launch Page" --social-title "Launch Page" --social-image-url https://example.com/og.png',
|
|
3052
3699
|
"peakurl create https://example.com --json"
|
|
3053
3700
|
]
|
|
3054
3701
|
);
|
|
3702
|
+
addExamples(
|
|
3703
|
+
program.command("edit").summary("Edit an existing short link").description(
|
|
3704
|
+
"Update an existing short link's destination URL, settings, or social preview by ID or alias."
|
|
3705
|
+
).helpOption("-h, --help", "Show help").argument("<id-or-alias>", "Link identifier or alias").option("--url <url>", "Updated destination URL").option("--title <title>", "Updated title").option(
|
|
3706
|
+
"--password <password>",
|
|
3707
|
+
"Set or update password protection"
|
|
3708
|
+
).option("--clear-password", "Remove password protection").option(
|
|
3709
|
+
"--status <status>",
|
|
3710
|
+
"Updated link status, for example active, inactive, or expired"
|
|
3711
|
+
).option(
|
|
3712
|
+
"--expires-at <iso>",
|
|
3713
|
+
"Updated expiration timestamp in ISO-8601 format"
|
|
3714
|
+
).option("--clear-expires-at", "Remove expiration timestamp").option("--social-title <title>", "Updated social preview title").option(
|
|
3715
|
+
"--social-description <text>",
|
|
3716
|
+
"Updated social preview description"
|
|
3717
|
+
).option(
|
|
3718
|
+
"--social-image-url <url>",
|
|
3719
|
+
"Updated remote image URL for social preview"
|
|
3720
|
+
).option(
|
|
3721
|
+
"--social-image <path>",
|
|
3722
|
+
"Upload a local image file for social preview"
|
|
3723
|
+
).option(
|
|
3724
|
+
"--remove-social-image",
|
|
3725
|
+
"Remove existing social preview image"
|
|
3726
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print only the short URL").action(editLink),
|
|
3727
|
+
[
|
|
3728
|
+
'peakurl edit docs --social-title "PeakURL Docs" --social-description "Documentation and guides" --social-image-url https://peakurl.org/og.png',
|
|
3729
|
+
'peakurl edit docs --url https://peakurl.org/docs/v2 --title "Updated Docs"',
|
|
3730
|
+
"peakurl edit url_123 --remove-social-image --json"
|
|
3731
|
+
]
|
|
3732
|
+
);
|
|
3055
3733
|
addExamples(
|
|
3056
3734
|
program.command("import").summary("Import links from a file").description(
|
|
3057
3735
|
"Import multiple short links from a local CSV, JSON, or XML file."
|
|
@@ -3104,7 +3782,7 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
|
3104
3782
|
"peakurl delete --all"
|
|
3105
3783
|
]
|
|
3106
3784
|
);
|
|
3107
|
-
const activity = program.command("activity").
|
|
3785
|
+
const activity = program.command("activity").summary("View and manage activity logs").helpOption("-h, --help", "Show help").description(
|
|
3108
3786
|
"View audit log activity entries, delete specific records, or clear all history."
|
|
3109
3787
|
);
|
|
3110
3788
|
addExamples(activity, [
|
|
@@ -3150,7 +3828,92 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
|
3150
3828
|
),
|
|
3151
3829
|
["peakurl update", "peakurl update --check", "peakurl update --json"]
|
|
3152
3830
|
);
|
|
3153
|
-
const
|
|
3831
|
+
const jobCmd = program.command("job").summary("Manage scheduled jobs").description(
|
|
3832
|
+
"Manage server-side scheduled jobs, view their execution history, and run them manually."
|
|
3833
|
+
).helpOption("-h, --help", "Show help");
|
|
3834
|
+
addExamples(jobCmd, [
|
|
3835
|
+
"peakurl job",
|
|
3836
|
+
"peakurl job list",
|
|
3837
|
+
"peakurl job get peakurl_version_check",
|
|
3838
|
+
"peakurl job run peakurl_version_check",
|
|
3839
|
+
"peakurl job run-due"
|
|
3840
|
+
]);
|
|
3841
|
+
addExamples(
|
|
3842
|
+
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),
|
|
3843
|
+
[
|
|
3844
|
+
"peakurl job",
|
|
3845
|
+
"peakurl job list",
|
|
3846
|
+
"peakurl job list --json",
|
|
3847
|
+
"peakurl job list --quiet"
|
|
3848
|
+
]
|
|
3849
|
+
);
|
|
3850
|
+
addExamples(
|
|
3851
|
+
jobCmd.command("get").summary("Show job details").description(
|
|
3852
|
+
"Show detailed configuration and status for one scheduled job."
|
|
3853
|
+
).helpOption("-h, --help", "Show help").argument("<id>", "Job identifier").option("--json", "Print machine-readable output").option("--quiet", "Print only the job ID").action(getJob),
|
|
3854
|
+
[
|
|
3855
|
+
"peakurl job get peakurl_version_check",
|
|
3856
|
+
"peakurl job get peakurl_version_check --json"
|
|
3857
|
+
]
|
|
3858
|
+
);
|
|
3859
|
+
addExamples(
|
|
3860
|
+
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),
|
|
3861
|
+
[
|
|
3862
|
+
"peakurl job run peakurl_version_check",
|
|
3863
|
+
"peakurl job run peakurl_version_check --json"
|
|
3864
|
+
]
|
|
3865
|
+
);
|
|
3866
|
+
addExamples(
|
|
3867
|
+
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),
|
|
3868
|
+
["peakurl job run-due", "peakurl job run-due --json"]
|
|
3869
|
+
);
|
|
3870
|
+
addExamples(
|
|
3871
|
+
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),
|
|
3872
|
+
[
|
|
3873
|
+
"peakurl job history peakurl_version_check",
|
|
3874
|
+
"peakurl job history peakurl_version_check --json"
|
|
3875
|
+
]
|
|
3876
|
+
);
|
|
3877
|
+
addExamples(
|
|
3878
|
+
jobCmd.command("clear-history").summary("Clear job history").description(
|
|
3879
|
+
"Clear execution history for all jobs or a specific job."
|
|
3880
|
+
).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),
|
|
3881
|
+
[
|
|
3882
|
+
"peakurl job clear-history",
|
|
3883
|
+
"peakurl job clear-history --job peakurl_version_check",
|
|
3884
|
+
"peakurl job clear-history --json"
|
|
3885
|
+
]
|
|
3886
|
+
);
|
|
3887
|
+
addExamples(
|
|
3888
|
+
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(
|
|
3889
|
+
"--preferred-time <time>",
|
|
3890
|
+
"Preferred run time (HH:MM or 'none')"
|
|
3891
|
+
).option("--enabled", "Enable the job").option("--disabled", "Disable the job").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(updateJobSchedule),
|
|
3892
|
+
[
|
|
3893
|
+
"peakurl job schedule peakurl_version_check --interval 43200",
|
|
3894
|
+
"peakurl job schedule peakurl_version_check --preferred-time 03:00",
|
|
3895
|
+
"peakurl job schedule peakurl_version_check --disabled"
|
|
3896
|
+
]
|
|
3897
|
+
);
|
|
3898
|
+
addExamples(
|
|
3899
|
+
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),
|
|
3900
|
+
[
|
|
3901
|
+
"peakurl job reset peakurl_version_check",
|
|
3902
|
+
"peakurl job reset peakurl_version_check --json"
|
|
3903
|
+
]
|
|
3904
|
+
);
|
|
3905
|
+
addExamples(
|
|
3906
|
+
jobCmd.command("settings").summary("Manage scheduler settings").description("View or update global scheduler settings.").helpOption("-h, --help", "Show help").option(
|
|
3907
|
+
"--retention-days <days>",
|
|
3908
|
+
"Number of days to keep execution history"
|
|
3909
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print minimal output").action(updateJobSettings),
|
|
3910
|
+
[
|
|
3911
|
+
"peakurl job settings",
|
|
3912
|
+
"peakurl job settings --retention-days 14",
|
|
3913
|
+
"peakurl job settings --json"
|
|
3914
|
+
]
|
|
3915
|
+
);
|
|
3916
|
+
const webhook = program.command("webhook").summary("Manage webhooks").helpOption("-h, --help", "Show help").description("Manage outbound webhook integrations.");
|
|
3154
3917
|
addExamples(webhook, [
|
|
3155
3918
|
"peakurl webhook",
|
|
3156
3919
|
"peakurl webhook list",
|
package/man/peakurl.1
CHANGED
|
@@ -25,6 +25,24 @@
|
|
|
25
25
|
.Ar url
|
|
26
26
|
.Op Fl -alias Ar alias
|
|
27
27
|
.Op Fl -title Ar title
|
|
28
|
+
.Op Fl -social-title Ar title
|
|
29
|
+
.Op Fl -social-description Ar text
|
|
30
|
+
.Op Fl -social-image-url Ar url
|
|
31
|
+
.Op Fl -social-image Ar path
|
|
32
|
+
.Op Fl -json
|
|
33
|
+
.Op Fl -quiet
|
|
34
|
+
.Pp
|
|
35
|
+
.Nm
|
|
36
|
+
.Cm edit
|
|
37
|
+
.Ar id-or-alias
|
|
38
|
+
.Op Fl -url Ar url
|
|
39
|
+
.Op Fl -alias Ar alias
|
|
40
|
+
.Op Fl -title Ar title
|
|
41
|
+
.Op Fl -social-title Ar title
|
|
42
|
+
.Op Fl -social-description Ar text
|
|
43
|
+
.Op Fl -social-image-url Ar url
|
|
44
|
+
.Op Fl -social-image Ar path
|
|
45
|
+
.Op Fl -remove-social-image
|
|
28
46
|
.Op Fl -json
|
|
29
47
|
.Op Fl -quiet
|
|
30
48
|
.Pp
|
|
@@ -44,8 +62,8 @@
|
|
|
44
62
|
.Sh DESCRIPTION
|
|
45
63
|
.Nm
|
|
46
64
|
is the official command-line interface for PeakURL.
|
|
47
|
-
It wraps the PeakURL HTTP API for creating, listing, importing,
|
|
48
|
-
and deleting short links.
|
|
65
|
+
It wraps the PeakURL HTTP API for creating, editing, listing, importing,
|
|
66
|
+
exporting, and deleting short links.
|
|
49
67
|
It also supports authenticated account inspection, site system status,
|
|
50
68
|
outbound webhooks, CLI update checks, and public PeakURL core package
|
|
51
69
|
downloads.
|
|
@@ -136,7 +154,15 @@ peakurl core download --force
|
|
|
136
154
|
Create a short link for a destination URL.
|
|
137
155
|
.Bd -literal -offset indent
|
|
138
156
|
peakurl create https://example.com/docs --alias docs
|
|
139
|
-
peakurl create https://example.com/launch --title "Launch Page"
|
|
157
|
+
peakurl create https://example.com/launch --title "Launch Page" --social-title "Launch Page" --social-image-url https://example.com/og.png
|
|
158
|
+
.Ed
|
|
159
|
+
.It Cm edit Ar id-or-alias
|
|
160
|
+
Update an existing short link's destination URL, title, status, expiration,
|
|
161
|
+
password, or social preview fields by ID, alias, or short code.
|
|
162
|
+
.Bd -literal -offset indent
|
|
163
|
+
peakurl edit docs --social-title "PeakURL Docs" --social-description "Documentation and guides" --social-image-url https://peakurl.org/og.png
|
|
164
|
+
peakurl edit docs --url https://peakurl.org/docs/v2 --title "Updated Docs"
|
|
165
|
+
peakurl edit url_123 --remove-social-image
|
|
140
166
|
.Ed
|
|
141
167
|
.It Cm import Ar file
|
|
142
168
|
Import short links from a local CSV, JSON, or XML file.
|
|
@@ -195,6 +221,54 @@ List supported webhook event identifiers.
|
|
|
195
221
|
.Bd -literal -offset indent
|
|
196
222
|
peakurl webhook events
|
|
197
223
|
.Ed
|
|
224
|
+
.It Cm job list
|
|
225
|
+
List all registered scheduled jobs.
|
|
226
|
+
.Bd -literal -offset indent
|
|
227
|
+
peakurl job list
|
|
228
|
+
peakurl job list --json
|
|
229
|
+
.Ed
|
|
230
|
+
.It Cm job get Ar id
|
|
231
|
+
Fetch one scheduled job by ID.
|
|
232
|
+
.Bd -literal -offset indent
|
|
233
|
+
peakurl job get peakurl_version_check
|
|
234
|
+
.Ed
|
|
235
|
+
.It Cm job run Ar id
|
|
236
|
+
Force a specific scheduled job to run immediately.
|
|
237
|
+
.Bd -literal -offset indent
|
|
238
|
+
peakurl job run peakurl_version_check
|
|
239
|
+
.Ed
|
|
240
|
+
.It Cm job run-due
|
|
241
|
+
Trigger all scheduled jobs that are currently due.
|
|
242
|
+
.Bd -literal -offset indent
|
|
243
|
+
peakurl job run-due
|
|
244
|
+
.Ed
|
|
245
|
+
.It Cm job history Ar id
|
|
246
|
+
View recent execution history for a scheduled job.
|
|
247
|
+
.Bd -literal -offset indent
|
|
248
|
+
peakurl job history peakurl_version_check
|
|
249
|
+
.Ed
|
|
250
|
+
.It Cm job clear-history
|
|
251
|
+
Clear execution history for all jobs or a specific job.
|
|
252
|
+
.Bd -literal -offset indent
|
|
253
|
+
peakurl job clear-history
|
|
254
|
+
peakurl job clear-history --job peakurl_version_check
|
|
255
|
+
.Ed
|
|
256
|
+
.It Cm job schedule Ar id
|
|
257
|
+
Update the schedule configuration for a job.
|
|
258
|
+
.Bd -literal -offset indent
|
|
259
|
+
peakurl job schedule peakurl_version_check --interval 43200 --preferred-time 03:00 --enabled
|
|
260
|
+
.Ed
|
|
261
|
+
.It Cm job reset Ar id
|
|
262
|
+
Reset a job's schedule to its default configuration.
|
|
263
|
+
.Bd -literal -offset indent
|
|
264
|
+
peakurl job reset peakurl_version_check
|
|
265
|
+
.Ed
|
|
266
|
+
.It Cm job settings
|
|
267
|
+
View or update global scheduler settings.
|
|
268
|
+
.Bd -literal -offset indent
|
|
269
|
+
peakurl job settings
|
|
270
|
+
peakurl job settings --retention-days 14
|
|
271
|
+
.Ed
|
|
198
272
|
.It Cm update
|
|
199
273
|
Check for a newer CLI version and print the npm install command when an
|
|
200
274
|
update is available.
|
|
@@ -220,14 +294,18 @@ Print machine-readable JSON where supported.
|
|
|
220
294
|
Print minimal output for scripts or suppress success output for destructive
|
|
221
295
|
commands.
|
|
222
296
|
.El
|
|
223
|
-
.Ss Link Creation Options
|
|
224
|
-
.Bl -tag -width "--
|
|
297
|
+
.Ss Link Creation And Editing Options
|
|
298
|
+
.Bl -tag -width "--remove-social-image"
|
|
299
|
+
.It Fl -url Ar url
|
|
300
|
+
Updated destination URL when editing an existing short link.
|
|
225
301
|
.It Fl -alias Ar alias
|
|
226
|
-
Custom alias
|
|
302
|
+
Custom alias when creating a short link.
|
|
227
303
|
.It Fl -title Ar title
|
|
228
304
|
Title stored with the short link.
|
|
229
305
|
.It Fl -password Ar password
|
|
230
306
|
Password-protect the short link.
|
|
307
|
+
.It Fl -clear-password
|
|
308
|
+
Remove password protection when editing a short link.
|
|
231
309
|
.It Fl -status Ar status
|
|
232
310
|
Link status, for example
|
|
233
311
|
.Sy active
|
|
@@ -235,16 +313,28 @@ or
|
|
|
235
313
|
.Sy paused .
|
|
236
314
|
.It Fl -expires-at Ar iso
|
|
237
315
|
Expiration timestamp in ISO-8601 format.
|
|
316
|
+
.It Fl -clear-expires-at
|
|
317
|
+
Remove the expiration timestamp when editing a short link.
|
|
238
318
|
.It Fl -utm-source Ar value
|
|
239
|
-
UTM source value.
|
|
319
|
+
UTM source value when creating a short link.
|
|
240
320
|
.It Fl -utm-medium Ar value
|
|
241
|
-
UTM medium value.
|
|
321
|
+
UTM medium value when creating a short link.
|
|
242
322
|
.It Fl -utm-campaign Ar value
|
|
243
|
-
UTM campaign value.
|
|
323
|
+
UTM campaign value when creating a short link.
|
|
244
324
|
.It Fl -utm-term Ar value
|
|
245
|
-
UTM term value.
|
|
325
|
+
UTM term value when creating a short link.
|
|
246
326
|
.It Fl -utm-content Ar value
|
|
247
|
-
UTM content value.
|
|
327
|
+
UTM content value when creating a short link.
|
|
328
|
+
.It Fl -social-title Ar title
|
|
329
|
+
Social preview (Open Graph / Twitter Card) title.
|
|
330
|
+
.It Fl -social-description Ar text
|
|
331
|
+
Social preview description.
|
|
332
|
+
.It Fl -social-image-url Ar url
|
|
333
|
+
Remote image URL for the social preview card.
|
|
334
|
+
.It Fl -social-image Ar path
|
|
335
|
+
Local image file (JPG, PNG, WEBP, or GIF up to 5 MB) to upload for the social preview card.
|
|
336
|
+
.It Fl -remove-social-image
|
|
337
|
+
Remove the existing social preview image when editing a short link.
|
|
248
338
|
.El
|
|
249
339
|
.Ss List Options
|
|
250
340
|
.Bl -tag -width "--sort-order"
|
|
@@ -346,6 +436,7 @@ when environment credentials should no longer apply.
|
|
|
346
436
|
.Bd -literal -offset indent
|
|
347
437
|
peakurl login --base-url https://example.com/api/v1 --api-key YOUR_API_KEY
|
|
348
438
|
peakurl create https://example.com/docs --alias docs
|
|
439
|
+
peakurl edit docs --social-title "PeakURL Docs" --social-image-url https://peakurl.org/og.png
|
|
349
440
|
peakurl list --limit 10
|
|
350
441
|
peakurl get docs
|
|
351
442
|
peakurl export --format json --stdout
|
|
@@ -353,11 +444,11 @@ peakurl webhook create https://example.com/api/webhooks/peakurl --event link.cli
|
|
|
353
444
|
peakurl core download
|
|
354
445
|
.Ed
|
|
355
446
|
.Sh SEE ALSO
|
|
356
|
-
.Lk https://peakurl.org/
|
|
447
|
+
.Lk https://go.peakurl.org/2aae02 "PeakURL CLI documentation"
|
|
357
448
|
.Pp
|
|
358
|
-
.Lk https://peakurl.org/
|
|
449
|
+
.Lk https://go.peakurl.org/d373f6 "PeakURL API documentation"
|
|
359
450
|
.Pp
|
|
360
|
-
.Lk https://
|
|
451
|
+
.Lk https://go.peakurl.org/cli "peakurl npm package"
|
|
361
452
|
.Sh BUGS
|
|
362
453
|
Report issues at
|
|
363
454
|
.Lk https://github.com/PeakURL/CLI/issues .
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peakurl",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Official CLI for creating, listing, and managing PeakURL short links from the terminal",
|
|
5
5
|
"homepage": "https://peakurl.org",
|
|
6
6
|
"bugs": {
|
|
@@ -55,16 +55,16 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/js": "^10.0.1",
|
|
58
|
-
"@types/node": "^26.
|
|
59
|
-
"eslint": "^10.
|
|
58
|
+
"@types/node": "^26.6.2",
|
|
59
|
+
"eslint": "^10.11.0",
|
|
60
60
|
"eslint-config-prettier": "^10.1.8",
|
|
61
61
|
"eslint-plugin-prettier": "^5.5.6",
|
|
62
62
|
"globals": "^17.12.0",
|
|
63
|
-
"prettier": "^3.9.
|
|
63
|
+
"prettier": "^3.9.8",
|
|
64
64
|
"tsup": "^8.5.1",
|
|
65
|
-
"tsx": "^4.23.
|
|
65
|
+
"tsx": "^4.23.15",
|
|
66
66
|
"typescript": "^5.9.3",
|
|
67
|
-
"typescript-eslint": "^8.
|
|
67
|
+
"typescript-eslint": "^8.70.1"
|
|
68
68
|
},
|
|
69
69
|
"license": "MIT"
|
|
70
70
|
}
|