@cadiraca/crowntrack-cli 0.1.1 → 0.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +207 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -79,12 +79,45 @@ async function deleteProject(id, confirm) {
79
79
  async function getStats() {
80
80
  return request("/api/stats");
81
81
  }
82
+ async function listActions(filters) {
83
+ const params = new URLSearchParams();
84
+ if (filters?.projectId) params.set("projectId", filters.projectId);
85
+ if (filters?.search) params.set("search", filters.search);
86
+ if (filters?.done) params.set("done", filters.done);
87
+ const qs = params.toString();
88
+ return request(`/api/actions${qs ? `?${qs}` : ""}`);
89
+ }
90
+ async function search(query) {
91
+ return request(`/api/search?q=${encodeURIComponent(query)}`);
92
+ }
93
+ async function fixDuplicateIds() {
94
+ return request("/api/fix-duplicate-ids", { method: "POST" });
95
+ }
96
+ async function fixDuplicateProjectIds() {
97
+ return request("/api/fix-duplicate-project-ids", { method: "POST" });
98
+ }
99
+ async function normalize() {
100
+ return request("/api/normalize", { method: "POST" });
101
+ }
102
+ async function exportData(format) {
103
+ const url = `${BASE_URL}/api/export?format=${format}`;
104
+ const res = await fetch(url);
105
+ if (!res.ok) {
106
+ const text = await res.text();
107
+ throw new Error(`HTTP ${res.status}: ${text}`);
108
+ }
109
+ if (format === "csv") {
110
+ return res.text();
111
+ }
112
+ return res.json();
113
+ }
82
114
 
83
115
  // src/index.ts
116
+ import { writeFileSync } from "fs";
84
117
  var program = new Command();
85
118
  var amber = chalk.hex("#f59e0b");
86
119
  var dim = chalk.gray;
87
- program.name("ct").description(amber("\u{1F451} CrownTrack CLI") + " \u2014 Rule your projects from the terminal").version("0.1.0");
120
+ program.name("ct").description(amber("\u{1F451} CrownTrack CLI") + " \u2014 Rule your projects from the terminal").version("0.2.0");
88
121
  program.command("projects").description("List all projects").option("-s, --status <status>", "Filter by status").option("-c, --category <category>", "Filter by category").option("-p, --priority <priority>", "Filter by priority").option("--search <query>", "Search by name").option("--tag <tag>", "Filter by tag").action(async (opts) => {
89
122
  try {
90
123
  const projects = await listProjects({
@@ -356,6 +389,179 @@ ${chalk.green("\u2713")} All active projects updated recently`);
356
389
  handleError(e);
357
390
  }
358
391
  });
392
+ program.command("actions").description("List actions across all projects").option("-a, --all", "Include completed actions").option("-p, --project <id>", "Filter by project ID or name").option("--search <query>", "Search action titles").action(async (opts) => {
393
+ try {
394
+ let projectId;
395
+ if (opts.project) {
396
+ const p = await resolveProject(opts.project);
397
+ projectId = p.id;
398
+ }
399
+ const actions = await listActions({
400
+ projectId,
401
+ search: opts.search,
402
+ done: opts.all ? "all" : "false"
403
+ });
404
+ if (actions.length === 0) {
405
+ console.log(dim("No actions found."));
406
+ return;
407
+ }
408
+ const table = new Table({
409
+ head: [
410
+ dim("Action ID"),
411
+ amber("Project"),
412
+ "Title",
413
+ "Priority",
414
+ "Status",
415
+ "Age"
416
+ ],
417
+ style: { head: [], border: ["gray"] }
418
+ });
419
+ for (const a of actions) {
420
+ table.push([
421
+ dim(a.id.slice(0, 8)),
422
+ a.project.name,
423
+ a.done ? dim(a.title) : a.title,
424
+ priorityColor(a.priority),
425
+ a.done ? chalk.green("done") : chalk.yellow("open"),
426
+ timeAgo(a.createdAt)
427
+ ]);
428
+ }
429
+ console.log(table.toString());
430
+ const open = actions.filter((a) => !a.done).length;
431
+ const done = actions.filter((a) => a.done).length;
432
+ console.log(dim(`
433
+ ${actions.length} action(s) \u2014 ${open} open, ${done} done`));
434
+ } catch (e) {
435
+ handleError(e);
436
+ }
437
+ });
438
+ program.command("search <query>").description("Search across all projects \u2014 actions, notes, decisions").action(async (query) => {
439
+ try {
440
+ const results = await search(query);
441
+ if (results.totalMatches === 0) {
442
+ console.log(dim(`No matches for "${query}"`));
443
+ return;
444
+ }
445
+ console.log(amber(`
446
+ \u{1F50D} Search results for "${query}"`) + dim(` (${results.totalMatches} match${results.totalMatches === 1 ? "" : "es"})
447
+ `));
448
+ if (results.results.actions.length > 0) {
449
+ console.log(amber("\u2705 Actions") + dim(` (${results.results.actions.length})`));
450
+ for (const a of results.results.actions) {
451
+ const status = a.done ? chalk.green("\u2713") : chalk.gray("\u25CB");
452
+ console.log(` ${status} ${a.title}`);
453
+ console.log(` ${dim(`${a.projectName} \xB7 ${a.priority} \xB7 ${a.id.slice(0, 8)}`)}`);
454
+ }
455
+ console.log();
456
+ }
457
+ if (results.results.notes.length > 0) {
458
+ console.log(amber("\u{1F4DD} Notes") + dim(` (${results.results.notes.length})`));
459
+ for (const n of results.results.notes) {
460
+ console.log(` \u2022 ${n.preview}`);
461
+ console.log(` ${dim(`${n.projectName} \xB7 ${n.id.slice(0, 8)}`)}`);
462
+ }
463
+ console.log();
464
+ }
465
+ if (results.results.decisions.length > 0) {
466
+ console.log(amber("\u{1F4CB} Decisions") + dim(` (${results.results.decisions.length})`));
467
+ for (const d of results.results.decisions) {
468
+ console.log(` \u2022 ${d.decision}`);
469
+ if (d.context) console.log(` ${dim(d.context)}`);
470
+ console.log(` ${dim(`${d.projectName} \xB7 ${d.id.slice(0, 8)}`)}`);
471
+ }
472
+ console.log();
473
+ }
474
+ } catch (e) {
475
+ handleError(e);
476
+ }
477
+ });
478
+ var fix = program.command("fix").description("Fix data issues");
479
+ fix.command("duplicates").description("Fix duplicate action and project IDs").action(async () => {
480
+ try {
481
+ console.log(amber("\u{1F527} Fixing duplicate action IDs..."));
482
+ const actionResult = await fixDuplicateIds();
483
+ if (actionResult.totalFixed > 0) {
484
+ console.log(chalk.green("\u2713") + ` Fixed ${actionResult.totalFixed} duplicate action ID(s)`);
485
+ for (const f of actionResult.fixes) {
486
+ console.log(dim(` ${f.oldId?.slice(0, 8)} \u2192 ${f.newId?.slice(0, 8)} (${f.title})`));
487
+ }
488
+ } else {
489
+ console.log(chalk.green("\u2713") + " No duplicate action IDs found");
490
+ }
491
+ console.log(amber("\n\u{1F527} Fixing duplicate project IDs..."));
492
+ const projectResult = await fixDuplicateProjectIds();
493
+ if (projectResult.totalFixed > 0) {
494
+ console.log(chalk.green("\u2713") + ` Fixed ${projectResult.totalFixed} duplicate project ID(s)`);
495
+ for (const f of projectResult.fixes) {
496
+ console.log(dim(` ${f.oldId?.slice(0, 8)} \u2192 ${f.newId?.slice(0, 8)} (${f.name})`));
497
+ }
498
+ } else {
499
+ console.log(chalk.green("\u2713") + " No duplicate project IDs found");
500
+ }
501
+ } catch (e) {
502
+ handleError(e);
503
+ }
504
+ });
505
+ fix.command("normalize").description("Normalize statuses and categories to Title Case").action(async () => {
506
+ try {
507
+ console.log(amber("\u{1F527} Normalizing statuses and categories..."));
508
+ const result = await normalize();
509
+ const total = result.statusFixes + result.categoryFixes + result.priorityFixes;
510
+ if (total > 0) {
511
+ console.log(chalk.green("\u2713") + ` Normalized ${total} field(s):`);
512
+ if (result.statusFixes > 0) {
513
+ console.log(dim(` Statuses: ${result.statusFixes}`));
514
+ for (const f of result.details.status) {
515
+ console.log(dim(` "${f.from}" \u2192 "${f.to}"`));
516
+ }
517
+ }
518
+ if (result.categoryFixes > 0) {
519
+ console.log(dim(` Categories: ${result.categoryFixes}`));
520
+ for (const f of result.details.category) {
521
+ console.log(dim(` "${f.from}" \u2192 "${f.to}"`));
522
+ }
523
+ }
524
+ if (result.priorityFixes > 0) {
525
+ console.log(dim(` Priorities: ${result.priorityFixes}`));
526
+ for (const f of result.details.priority) {
527
+ console.log(dim(` "${f.from}" \u2192 "${f.to}"`));
528
+ }
529
+ }
530
+ } else {
531
+ console.log(chalk.green("\u2713") + " Everything already normalized");
532
+ }
533
+ } catch (e) {
534
+ handleError(e);
535
+ }
536
+ });
537
+ var exp = program.command("export").description("Export project data");
538
+ exp.command("json").description("Export all projects as JSON").option("-f, --file <path>", "Write to file instead of stdout").action(async (opts) => {
539
+ try {
540
+ const data = await exportData("json");
541
+ const output = JSON.stringify(data, null, 2);
542
+ if (opts.file) {
543
+ writeFileSync(opts.file, output);
544
+ console.log(chalk.green("\u2713") + ` Exported to ${opts.file}`);
545
+ } else {
546
+ console.log(output);
547
+ }
548
+ } catch (e) {
549
+ handleError(e);
550
+ }
551
+ });
552
+ exp.command("csv").description("Export projects summary as CSV").option("-f, --file <path>", "Write to file instead of stdout").action(async (opts) => {
553
+ try {
554
+ const data = await exportData("csv");
555
+ if (opts.file) {
556
+ writeFileSync(opts.file, data);
557
+ console.log(chalk.green("\u2713") + ` Exported to ${opts.file}`);
558
+ } else {
559
+ console.log(data);
560
+ }
561
+ } catch (e) {
562
+ handleError(e);
563
+ }
564
+ });
359
565
  async function resolveProject(idOrPrefix) {
360
566
  try {
361
567
  return await getProject(idOrPrefix);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cadiraca/crowntrack-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "👑 CrownTrack CLI — Rule your projects from the terminal",
5
5
  "author": "Carlos Diego Ramírez <cadiraca>",
6
6
  "license": "MIT",