peakurl 1.1.0 → 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 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: <https://peakurl.org/docs/cli>
9
+ Learn more in the full CLI docs: <https://go.peakurl.org/7a0e0b>
10
10
 
11
11
  ## Install
12
12
 
@@ -73,6 +73,7 @@ export PEAKURL_API_KEY=0123456789abcdef0123456789abcdef0123456789abcdef
73
73
  | `peakurl delete [id-or-alias...]` | Delete links by ID or alias, in bulk, or clear all links. |
74
74
  | `peakurl activity <subcommand>` | View audit logs, delete activity records, or clear history. |
75
75
  | `peakurl webhook <subcommand>` | List, create, delete, and inspect supported webhook events. |
76
+ | `peakurl job <cmd>` | Manage server-side scheduled jobs. |
76
77
  | `peakurl update` | Show the latest available CLI version and install command. |
77
78
 
78
79
  ## Examples
@@ -228,6 +229,19 @@ Check the latest available CLI version:
228
229
  peakurl update --check
229
230
  ```
230
231
 
232
+ Manage scheduled jobs:
233
+
234
+ ```bash
235
+ # List all registered scheduled jobs
236
+ peakurl job list
237
+
238
+ # Force a job to run immediately
239
+ peakurl job run peakurl_version_check
240
+
241
+ # Trigger all currently due jobs
242
+ peakurl job run-due
243
+ ```
244
+
231
245
  Show the recommended install command:
232
246
 
233
247
  ```bash
@@ -255,7 +269,7 @@ export PEAKURL_DISABLE_UPDATE_CHECK=1
255
269
  ## Links
256
270
 
257
271
  - Website: <https://peakurl.org/>
258
- - CLI docs: <https://peakurl.org/docs/cli>
259
- - API docs: <https://peakurl.org/docs/api>
272
+ - CLI docs: <https://go.peakurl.org/7a0e0b>
273
+ - API docs: <https://go.peakurl.org/d373f6>
260
274
  - npm package: <https://www.npmjs.com/package/peakurl>
261
275
  - Issues: <https://github.com/PeakURL/CLI/issues>
package/bin/peakurl.js CHANGED
@@ -120,7 +120,6 @@ var ApiClient = class {
120
120
  constructor(config) {
121
121
  this.config = config;
122
122
  }
123
- config;
124
123
  /**
125
124
  * Loads the currently authenticated user.
126
125
  *
@@ -314,6 +313,86 @@ var ApiClient = class {
314
313
  `webhooks/${encodeURIComponent(id)}`
315
314
  );
316
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
+ }
317
396
  /**
318
397
  * Performs one authenticated API request and normalizes the response.
319
398
  *
@@ -1985,6 +2064,121 @@ ${checks}` : void 0,
1985
2064
  return sections.length > 0 ? sections.join("\n\n") : "No system status fields returned.";
1986
2065
  }
1987
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
+
1988
2182
  // src/lib/update.ts
1989
2183
  var PACKAGE_NAME = "peakurl";
1990
2184
  var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
@@ -2927,6 +3121,230 @@ async function whoami(options) {
2927
3121
  writeStdout(userTable(response.data, config.apiBaseUrl));
2928
3122
  }
2929
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
+
2930
3348
  // src/index.ts
2931
3349
  function parseNumber(label) {
2932
3350
  return (value) => {
@@ -2961,7 +3379,7 @@ function getRetryCommandName(argv) {
2961
3379
  if (!first || first.startsWith("-")) {
2962
3380
  return void 0;
2963
3381
  }
2964
- if (first === "webhook" || first === "webhooks" || first === "activity" || first === "activities") {
3382
+ if (first === "webhook" || first === "activity" || first === "job") {
2965
3383
  const second = argv[3]?.trim();
2966
3384
  if (second && !second.startsWith("-")) {
2967
3385
  return `${first} ${second}`;
@@ -2969,10 +3387,33 @@ function getRetryCommandName(argv) {
2969
3387
  }
2970
3388
  return first;
2971
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
+ };
2972
3399
  async function main() {
2973
3400
  const program = new Command();
2974
3401
  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").showHelpAfterError().showSuggestionAfterError().addHelpText(
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(
2976
3417
  "after",
2977
3418
  `
2978
3419
  Get Started:
@@ -2986,6 +3427,8 @@ Common Commands:
2986
3427
  peakurl list --limit 10
2987
3428
  peakurl import ./links.csv
2988
3429
  peakurl export --format csv
3430
+ peakurl activity list
3431
+ peakurl job list
2989
3432
  peakurl webhook list
2990
3433
  peakurl update --check
2991
3434
 
@@ -3104,7 +3547,7 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
3104
3547
  "peakurl delete --all"
3105
3548
  ]
3106
3549
  );
3107
- const activity = program.command("activity").alias("activities").summary("View and manage activity logs").helpOption("-h, --help", "Show help").description(
3550
+ const activity = program.command("activity").summary("View and manage activity logs").helpOption("-h, --help", "Show help").description(
3108
3551
  "View audit log activity entries, delete specific records, or clear all history."
3109
3552
  );
3110
3553
  addExamples(activity, [
@@ -3150,7 +3593,92 @@ Run 'peakurl <command> --help' for command-specific flags and examples.`
3150
3593
  ),
3151
3594
  ["peakurl update", "peakurl update --check", "peakurl update --json"]
3152
3595
  );
3153
- const webhook = program.command("webhook").alias("webhooks").summary("Manage webhooks").helpOption("-h, --help", "Show help").description("Manage outbound webhook integrations.");
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.");
3154
3682
  addExamples(webhook, [
3155
3683
  "peakurl webhook",
3156
3684
  "peakurl webhook list",
package/man/peakurl.1 CHANGED
@@ -195,6 +195,54 @@ List supported webhook event identifiers.
195
195
  .Bd -literal -offset indent
196
196
  peakurl webhook events
197
197
  .Ed
198
+ .It Cm job list
199
+ List all registered scheduled jobs.
200
+ .Bd -literal -offset indent
201
+ peakurl job list
202
+ peakurl job list --json
203
+ .Ed
204
+ .It Cm job get Ar id
205
+ Fetch one scheduled job by ID.
206
+ .Bd -literal -offset indent
207
+ peakurl job get peakurl_version_check
208
+ .Ed
209
+ .It Cm job run Ar id
210
+ Force a specific scheduled job to run immediately.
211
+ .Bd -literal -offset indent
212
+ peakurl job run peakurl_version_check
213
+ .Ed
214
+ .It Cm job run-due
215
+ Trigger all scheduled jobs that are currently due.
216
+ .Bd -literal -offset indent
217
+ peakurl job run-due
218
+ .Ed
219
+ .It Cm job history Ar id
220
+ View recent execution history for a scheduled job.
221
+ .Bd -literal -offset indent
222
+ peakurl job history peakurl_version_check
223
+ .Ed
224
+ .It Cm job clear-history
225
+ Clear execution history for all jobs or a specific job.
226
+ .Bd -literal -offset indent
227
+ peakurl job clear-history
228
+ peakurl job clear-history --job peakurl_version_check
229
+ .Ed
230
+ .It Cm job schedule Ar id
231
+ Update the schedule configuration for a job.
232
+ .Bd -literal -offset indent
233
+ peakurl job schedule peakurl_version_check --interval 43200 --preferred-time 03:00 --enabled
234
+ .Ed
235
+ .It Cm job reset Ar id
236
+ Reset a job's schedule to its default configuration.
237
+ .Bd -literal -offset indent
238
+ peakurl job reset peakurl_version_check
239
+ .Ed
240
+ .It Cm job settings
241
+ View or update global scheduler settings.
242
+ .Bd -literal -offset indent
243
+ peakurl job settings
244
+ peakurl job settings --retention-days 14
245
+ .Ed
198
246
  .It Cm update
199
247
  Check for a newer CLI version and print the npm install command when an
200
248
  update is available.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peakurl",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
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.4.1",
59
- "eslint": "^10.9.1",
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.6",
63
+ "prettier": "^3.9.8",
64
64
  "tsup": "^8.5.1",
65
- "tsx": "^4.23.13",
65
+ "tsx": "^4.23.15",
66
66
  "typescript": "^5.9.3",
67
- "typescript-eslint": "^8.69.0"
67
+ "typescript-eslint": "^8.70.1"
68
68
  },
69
69
  "license": "MIT"
70
70
  }