@simonfestl/husky-cli 0.3.0 → 0.5.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 +228 -58
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +401 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +400 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +101 -1
- package/dist/commands/department.d.ts +2 -0
- package/dist/commands/department.js +240 -0
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +411 -0
- package/dist/commands/idea.d.ts +2 -0
- package/dist/commands/idea.js +340 -0
- package/dist/commands/interactive.d.ts +1 -0
- package/dist/commands/interactive.js +1397 -0
- package/dist/commands/jules.d.ts +2 -0
- package/dist/commands/jules.js +593 -0
- package/dist/commands/process.d.ts +2 -0
- package/dist/commands/process.js +289 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +473 -0
- package/dist/commands/roadmap.js +318 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +153 -0
- package/dist/commands/strategy.d.ts +2 -0
- package/dist/commands/strategy.js +706 -0
- package/dist/commands/task.js +244 -1
- package/dist/commands/vm-config.d.ts +2 -0
- package/dist/commands/vm-config.js +318 -0
- package/dist/commands/vm.d.ts +2 -0
- package/dist/commands/vm.js +621 -0
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +545 -0
- package/dist/index.js +35 -2
- package/package.json +8 -2
package/dist/commands/roadmap.js
CHANGED
|
@@ -235,6 +235,74 @@ roadmapCommand
|
|
|
235
235
|
process.exit(1);
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
|
+
// husky roadmap update <id>
|
|
239
|
+
roadmapCommand
|
|
240
|
+
.command("update <id>")
|
|
241
|
+
.description("Update a roadmap")
|
|
242
|
+
.option("-n, --name <name>", "New name")
|
|
243
|
+
.option("-d, --description <desc>", "New description")
|
|
244
|
+
.option("--type <type>", "New type (project, global)")
|
|
245
|
+
.option("--status <status>", "New status")
|
|
246
|
+
.option("--json", "Output as JSON")
|
|
247
|
+
.action(async (id, options) => {
|
|
248
|
+
const config = ensureConfig();
|
|
249
|
+
// Build update payload
|
|
250
|
+
const updateData = {};
|
|
251
|
+
if (options.name)
|
|
252
|
+
updateData.name = options.name;
|
|
253
|
+
if (options.description)
|
|
254
|
+
updateData.vision = options.description;
|
|
255
|
+
if (options.type) {
|
|
256
|
+
const validTypes = ["project", "global"];
|
|
257
|
+
if (!validTypes.includes(options.type)) {
|
|
258
|
+
console.error(`Error: Invalid type "${options.type}". Must be one of: ${validTypes.join(", ")}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
updateData.type = options.type;
|
|
262
|
+
}
|
|
263
|
+
if (options.status)
|
|
264
|
+
updateData.status = options.status;
|
|
265
|
+
if (Object.keys(updateData).length === 0) {
|
|
266
|
+
console.error("Error: No update options provided. Use -n/--name, -d/--description, --type, or --status");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${id}`, {
|
|
271
|
+
method: "PATCH",
|
|
272
|
+
headers: {
|
|
273
|
+
"Content-Type": "application/json",
|
|
274
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify(updateData),
|
|
277
|
+
});
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
if (res.status === 404) {
|
|
280
|
+
console.error(`Error: Roadmap ${id} not found`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
284
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
285
|
+
}
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const roadmap = await res.json();
|
|
289
|
+
if (options.json) {
|
|
290
|
+
console.log(JSON.stringify(roadmap, null, 2));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log(`✓ Roadmap updated successfully`);
|
|
294
|
+
console.log(` Name: ${roadmap.name}`);
|
|
295
|
+
console.log(` Type: ${roadmap.type}`);
|
|
296
|
+
if (roadmap.vision) {
|
|
297
|
+
console.log(` Vision: ${roadmap.vision}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
console.error("Error updating roadmap:", error);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
238
306
|
// husky roadmap delete <id>
|
|
239
307
|
roadmapCommand
|
|
240
308
|
.command("delete <id>")
|
|
@@ -262,6 +330,221 @@ roadmapCommand
|
|
|
262
330
|
process.exit(1);
|
|
263
331
|
}
|
|
264
332
|
});
|
|
333
|
+
// husky roadmap list-features <roadmapId>
|
|
334
|
+
roadmapCommand
|
|
335
|
+
.command("list-features <roadmapId>")
|
|
336
|
+
.description("List all features in a roadmap with their IDs and status")
|
|
337
|
+
.option("--json", "Output as JSON")
|
|
338
|
+
.option("--status <status>", "Filter by status (idea, planned, in_progress, done)")
|
|
339
|
+
.option("--priority <priority>", "Filter by priority (must, should, could, wont)")
|
|
340
|
+
.action(async (roadmapId, options) => {
|
|
341
|
+
const config = ensureConfig();
|
|
342
|
+
try {
|
|
343
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}`, {
|
|
344
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
345
|
+
});
|
|
346
|
+
if (!res.ok) {
|
|
347
|
+
if (res.status === 404) {
|
|
348
|
+
console.error(`Error: Roadmap ${roadmapId} not found`);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
console.error(`Error: API returned ${res.status}`);
|
|
352
|
+
}
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
const roadmap = await res.json();
|
|
356
|
+
let features = roadmap.features || [];
|
|
357
|
+
// Apply filters
|
|
358
|
+
if (options.status) {
|
|
359
|
+
features = features.filter((f) => f.status === options.status);
|
|
360
|
+
}
|
|
361
|
+
if (options.priority) {
|
|
362
|
+
features = features.filter((f) => f.priority === options.priority);
|
|
363
|
+
}
|
|
364
|
+
if (options.json) {
|
|
365
|
+
console.log(JSON.stringify(features, null, 2));
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
printFeaturesList(roadmap.name, features, roadmap.phases || []);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
console.error("Error fetching roadmap features:", error);
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
// husky roadmap update-feature <roadmapId> <featureId>
|
|
377
|
+
roadmapCommand
|
|
378
|
+
.command("update-feature <roadmapId> <featureId>")
|
|
379
|
+
.description("Update a roadmap feature")
|
|
380
|
+
.option("--status <status>", "New status (idea, planned, in_progress, done)")
|
|
381
|
+
.option("--name <name>", "New feature name/title")
|
|
382
|
+
.option("--priority <priority>", "New priority (must, should, could, wont)")
|
|
383
|
+
.option("--description <description>", "New description")
|
|
384
|
+
.option("--phase <phaseId>", "Move to different phase")
|
|
385
|
+
.action(async (roadmapId, featureId, options) => {
|
|
386
|
+
const config = ensureConfig();
|
|
387
|
+
// Build update payload
|
|
388
|
+
const updateData = {};
|
|
389
|
+
if (options.status) {
|
|
390
|
+
const validStatuses = ["idea", "planned", "in_progress", "done"];
|
|
391
|
+
if (!validStatuses.includes(options.status)) {
|
|
392
|
+
console.error(`Error: Invalid status "${options.status}". Must be one of: ${validStatuses.join(", ")}`);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
updateData.status = options.status;
|
|
396
|
+
}
|
|
397
|
+
if (options.name) {
|
|
398
|
+
updateData.title = options.name;
|
|
399
|
+
}
|
|
400
|
+
if (options.priority) {
|
|
401
|
+
const validPriorities = ["must", "should", "could", "wont"];
|
|
402
|
+
if (!validPriorities.includes(options.priority)) {
|
|
403
|
+
console.error(`Error: Invalid priority "${options.priority}". Must be one of: ${validPriorities.join(", ")}`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
updateData.priority = options.priority;
|
|
407
|
+
}
|
|
408
|
+
if (options.description) {
|
|
409
|
+
updateData.description = options.description;
|
|
410
|
+
}
|
|
411
|
+
if (options.phase) {
|
|
412
|
+
updateData.phaseId = options.phase;
|
|
413
|
+
}
|
|
414
|
+
if (Object.keys(updateData).length === 0) {
|
|
415
|
+
console.error("Error: No update options provided. Use --status, --name, --priority, --description, or --phase");
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/features/${featureId}`, {
|
|
420
|
+
method: "PATCH",
|
|
421
|
+
headers: {
|
|
422
|
+
"Content-Type": "application/json",
|
|
423
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
424
|
+
},
|
|
425
|
+
body: JSON.stringify(updateData),
|
|
426
|
+
});
|
|
427
|
+
if (!res.ok) {
|
|
428
|
+
if (res.status === 404) {
|
|
429
|
+
console.error(`Error: Roadmap or feature not found`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
433
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
434
|
+
}
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
const roadmap = await res.json();
|
|
438
|
+
const updatedFeature = roadmap.features.find((f) => f.id === featureId);
|
|
439
|
+
console.log(`✓ Feature updated successfully`);
|
|
440
|
+
if (updatedFeature) {
|
|
441
|
+
console.log(` Title: ${updatedFeature.title}`);
|
|
442
|
+
console.log(` Status: ${updatedFeature.status}`);
|
|
443
|
+
console.log(` Priority: ${updatedFeature.priority}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
console.error("Error updating feature:", error);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
// husky roadmap delete-feature <roadmapId> <featureId>
|
|
452
|
+
roadmapCommand
|
|
453
|
+
.command("delete-feature <roadmapId> <featureId>")
|
|
454
|
+
.description("Delete a feature from a roadmap")
|
|
455
|
+
.option("--force", "Skip confirmation")
|
|
456
|
+
.action(async (roadmapId, featureId, options) => {
|
|
457
|
+
const config = ensureConfig();
|
|
458
|
+
if (!options.force) {
|
|
459
|
+
console.log("Warning: This will permanently delete the feature.");
|
|
460
|
+
console.log("Use --force to confirm deletion.");
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/features/${featureId}`, {
|
|
465
|
+
method: "DELETE",
|
|
466
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
467
|
+
});
|
|
468
|
+
if (!res.ok) {
|
|
469
|
+
if (res.status === 404) {
|
|
470
|
+
console.error(`Error: Roadmap or feature not found`);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
474
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
475
|
+
}
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
console.log(`✓ Feature deleted`);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
console.error("Error deleting feature:", error);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
// husky roadmap convert-feature <roadmapId> <featureId>
|
|
486
|
+
roadmapCommand
|
|
487
|
+
.command("convert-feature <roadmapId> <featureId>")
|
|
488
|
+
.description("Convert a roadmap feature to a task")
|
|
489
|
+
.option("--priority <priority>", "Task priority (low, medium, high)")
|
|
490
|
+
.option("--assignee <assignee>", "Task assignee (human, llm, unassigned)")
|
|
491
|
+
.option("--json", "Output as JSON")
|
|
492
|
+
.action(async (roadmapId, featureId, options) => {
|
|
493
|
+
const config = ensureConfig();
|
|
494
|
+
try {
|
|
495
|
+
// Build optional body
|
|
496
|
+
const body = {};
|
|
497
|
+
if (options.priority) {
|
|
498
|
+
const validPriorities = ["low", "medium", "high"];
|
|
499
|
+
if (!validPriorities.includes(options.priority)) {
|
|
500
|
+
console.error(`Error: Invalid priority "${options.priority}". Must be one of: ${validPriorities.join(", ")}`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
body.priority = options.priority;
|
|
504
|
+
}
|
|
505
|
+
if (options.assignee) {
|
|
506
|
+
const validAssignees = ["human", "llm", "unassigned"];
|
|
507
|
+
if (!validAssignees.includes(options.assignee)) {
|
|
508
|
+
console.error(`Error: Invalid assignee "${options.assignee}". Must be one of: ${validAssignees.join(", ")}`);
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
body.assignee = options.assignee;
|
|
512
|
+
}
|
|
513
|
+
const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/features/${featureId}/convert`, {
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: {
|
|
516
|
+
"Content-Type": "application/json",
|
|
517
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
518
|
+
},
|
|
519
|
+
body: JSON.stringify(body),
|
|
520
|
+
});
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
if (res.status === 404) {
|
|
523
|
+
console.error(`Error: Roadmap or feature not found`);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
527
|
+
console.error(`Error: API returned ${res.status}`, errorBody.error || "");
|
|
528
|
+
}
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
const result = await res.json();
|
|
532
|
+
if (options.json) {
|
|
533
|
+
console.log(JSON.stringify(result, null, 2));
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
console.log(`✓ Feature converted to task successfully`);
|
|
537
|
+
console.log(` Task ID: ${result.task.id}`);
|
|
538
|
+
console.log(` Title: ${result.task.title}`);
|
|
539
|
+
console.log(` Priority: ${result.task.priority}`);
|
|
540
|
+
console.log(` Status: ${result.task.status}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
console.error("Error converting feature to task:", error);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
265
548
|
function printRoadmaps(roadmaps) {
|
|
266
549
|
if (roadmaps.length === 0) {
|
|
267
550
|
console.log("\n No roadmaps found.");
|
|
@@ -323,3 +606,38 @@ function printRoadmapDetail(roadmap) {
|
|
|
323
606
|
console.log(` Could Have: ${couldCount}`);
|
|
324
607
|
console.log("");
|
|
325
608
|
}
|
|
609
|
+
function printFeaturesList(roadmapName, features, phases) {
|
|
610
|
+
if (features.length === 0) {
|
|
611
|
+
console.log("\n No features found.");
|
|
612
|
+
console.log(" Add one with: husky roadmap add-feature <roadmapId> <phaseId> <title>\n");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
console.log(`\n FEATURES - ${roadmapName}`);
|
|
616
|
+
console.log(" " + "─".repeat(80));
|
|
617
|
+
console.log(` ${"ID".padEnd(24)} ${"TITLE".padEnd(30)} ${"STATUS".padEnd(12)} ${"PRIORITY".padEnd(8)} PHASE`);
|
|
618
|
+
console.log(" " + "─".repeat(80));
|
|
619
|
+
// Create phase lookup map
|
|
620
|
+
const phaseMap = new Map(phases.map((p) => [p.id, p.name]));
|
|
621
|
+
for (const feature of features) {
|
|
622
|
+
const statusIcon = feature.status === "done"
|
|
623
|
+
? "✓"
|
|
624
|
+
: feature.status === "in_progress"
|
|
625
|
+
? "▶"
|
|
626
|
+
: feature.status === "planned"
|
|
627
|
+
? "○"
|
|
628
|
+
: "·";
|
|
629
|
+
const phaseName = phaseMap.get(feature.phaseId) || feature.phaseId;
|
|
630
|
+
const truncatedTitle = feature.title.length > 28 ? feature.title.substring(0, 25) + "..." : feature.title;
|
|
631
|
+
const truncatedPhase = phaseName.length > 15 ? phaseName.substring(0, 12) + "..." : phaseName;
|
|
632
|
+
console.log(` ${feature.id.padEnd(24)} ${truncatedTitle.padEnd(30)} ${statusIcon} ${feature.status.padEnd(10)} ${feature.priority.padEnd(8)} ${truncatedPhase}`);
|
|
633
|
+
}
|
|
634
|
+
// Summary by status
|
|
635
|
+
const ideaCount = features.filter((f) => f.status === "idea").length;
|
|
636
|
+
const plannedCount = features.filter((f) => f.status === "planned").length;
|
|
637
|
+
const inProgressCount = features.filter((f) => f.status === "in_progress").length;
|
|
638
|
+
const doneCount = features.filter((f) => f.status === "done").length;
|
|
639
|
+
console.log(" " + "─".repeat(80));
|
|
640
|
+
console.log(`\n Summary: ${features.length} total`);
|
|
641
|
+
console.log(` · Idea: ${ideaCount} ○ Planned: ${plannedCount} ▶ In Progress: ${inProgressCount} ✓ Done: ${doneCount}`);
|
|
642
|
+
console.log("");
|
|
643
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfig } from "./config.js";
|
|
3
|
+
export const settingsCommand = new Command("settings")
|
|
4
|
+
.description("Manage application settings");
|
|
5
|
+
// Helper: Ensure API is configured
|
|
6
|
+
function ensureConfig() {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
if (!config.apiUrl) {
|
|
9
|
+
console.error("Error: API URL not configured. Run: husky config set api-url <url>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
// husky settings get [key]
|
|
15
|
+
settingsCommand
|
|
16
|
+
.command("get [key]")
|
|
17
|
+
.description("Get settings (all or specific key)")
|
|
18
|
+
.option("--json", "Output as JSON")
|
|
19
|
+
.action(async (key, options) => {
|
|
20
|
+
const config = ensureConfig();
|
|
21
|
+
try {
|
|
22
|
+
// If no key provided, get all settings
|
|
23
|
+
const url = key
|
|
24
|
+
? `${config.apiUrl}/api/settings/${encodeURIComponent(key)}`
|
|
25
|
+
: `${config.apiUrl}/api/settings`;
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
if (res.status === 404) {
|
|
31
|
+
console.error(`Error: Setting "${key}" not found`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.error(`Error: API returned ${res.status}`);
|
|
35
|
+
}
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const data = await res.json();
|
|
39
|
+
if (options.json) {
|
|
40
|
+
console.log(JSON.stringify(data, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (key) {
|
|
44
|
+
// Single setting
|
|
45
|
+
printSingleSetting(key, data);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// All settings
|
|
49
|
+
printAllSettings(data);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error("Error fetching settings:", error);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// husky settings set <key> <value>
|
|
59
|
+
settingsCommand
|
|
60
|
+
.command("set <key> <value>")
|
|
61
|
+
.description("Set a setting value")
|
|
62
|
+
.option("--json", "Output as JSON")
|
|
63
|
+
.action(async (key, value, options) => {
|
|
64
|
+
const config = ensureConfig();
|
|
65
|
+
// Try to parse value as JSON for complex types (arrays, objects, booleans, numbers)
|
|
66
|
+
let parsedValue = value;
|
|
67
|
+
try {
|
|
68
|
+
parsedValue = JSON.parse(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Keep as string if not valid JSON
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(`${config.apiUrl}/api/settings/${encodeURIComponent(key)}`, {
|
|
75
|
+
method: "PUT",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ value: parsedValue }),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const errorData = await res.json().catch(() => ({}));
|
|
84
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
85
|
+
}
|
|
86
|
+
const data = await res.json();
|
|
87
|
+
if (options.json) {
|
|
88
|
+
console.log(JSON.stringify(data, null, 2));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(`Setting updated successfully`);
|
|
92
|
+
console.log(` Key: ${key}`);
|
|
93
|
+
console.log(` Value: ${formatValue(parsedValue)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error("Error setting value:", error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// ============================================
|
|
102
|
+
// OUTPUT FORMATTERS
|
|
103
|
+
// ============================================
|
|
104
|
+
function formatValue(value) {
|
|
105
|
+
if (typeof value === "object") {
|
|
106
|
+
return JSON.stringify(value);
|
|
107
|
+
}
|
|
108
|
+
return String(value);
|
|
109
|
+
}
|
|
110
|
+
function printSingleSetting(key, data) {
|
|
111
|
+
const value = "value" in data ? data.value : data;
|
|
112
|
+
console.log(`\n Setting: ${key}`);
|
|
113
|
+
console.log(" " + "-".repeat(50));
|
|
114
|
+
console.log(` Value: ${formatValue(value)}`);
|
|
115
|
+
if ("updatedAt" in data && data.updatedAt) {
|
|
116
|
+
console.log(` Updated: ${new Date(data.updatedAt).toLocaleString()}`);
|
|
117
|
+
}
|
|
118
|
+
console.log("");
|
|
119
|
+
}
|
|
120
|
+
function printAllSettings(data) {
|
|
121
|
+
// Handle both array and object formats
|
|
122
|
+
const entries = [];
|
|
123
|
+
if (Array.isArray(data)) {
|
|
124
|
+
for (const item of data) {
|
|
125
|
+
entries.push({ key: item.key, value: item.value });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
for (const [key, value] of Object.entries(data)) {
|
|
130
|
+
if (typeof value === "object" && value !== null && "value" in value) {
|
|
131
|
+
entries.push({ key, value: value.value });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
entries.push({ key, value: value });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (entries.length === 0) {
|
|
139
|
+
console.log("\n No settings found.\n");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
console.log("\n SETTINGS");
|
|
143
|
+
console.log(" " + "-".repeat(70));
|
|
144
|
+
console.log(` ${"KEY".padEnd(30)} ${"VALUE".padEnd(38)}`);
|
|
145
|
+
console.log(" " + "-".repeat(70));
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const valueStr = formatValue(entry.value);
|
|
148
|
+
const truncatedValue = valueStr.length > 36 ? valueStr.substring(0, 33) + "..." : valueStr;
|
|
149
|
+
console.log(` ${entry.key.padEnd(30)} ${truncatedValue}`);
|
|
150
|
+
}
|
|
151
|
+
console.log(" " + "-".repeat(70));
|
|
152
|
+
console.log(` Total: ${entries.length} setting(s)\n`);
|
|
153
|
+
}
|