@vibetasks/cli 0.4.6 → 0.5.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/bin/vibetasks.js +615 -21
  2. package/package.json +3 -3
@@ -11,7 +11,7 @@ import {
11
11
  } from "../chunk-2KRLRG4G.js";
12
12
 
13
13
  // bin/vibetasks.ts
14
- import { Command as Command16 } from "commander";
14
+ import { Command as Command18 } from "commander";
15
15
  import { createRequire } from "module";
16
16
 
17
17
  // src/commands/login.ts
@@ -48,8 +48,17 @@ ${url}
48
48
  `));
49
49
  }
50
50
  }
51
+ function getAuthBaseUrl() {
52
+ if (process.env.VIBETASKS_AUTH_URL) {
53
+ return process.env.VIBETASKS_AUTH_URL;
54
+ }
55
+ if (process.env.TASKFLOW_WEB_PORT) {
56
+ return `http://localhost:${process.env.TASKFLOW_WEB_PORT}`;
57
+ }
58
+ return "https://vibetasks.dev";
59
+ }
51
60
  async function loginWithBrowser() {
52
- console.log(chalk.blue.bold("\n\u{1F510} TaskFlow Browser Login\n"));
61
+ console.log(chalk.blue.bold("\n\u{1F510} VibeTasks Browser Login\n"));
53
62
  const spinner = ora("Starting local server...").start();
54
63
  const port = await getAvailablePort();
55
64
  return new Promise((resolve, reject) => {
@@ -247,12 +256,12 @@ async function loginWithBrowser() {
247
256
  <h1>Authentication Successful!</h1>
248
257
  <p class="email">${email}</p>
249
258
  <p class="message">
250
- Your TaskFlow CLI is now connected to your account.<br>
259
+ Your VibeTasks CLI is now connected to your account.<br>
251
260
  You can close this window and return to your terminal.
252
261
  </p>
253
262
  <div class="success-badge">Ready to use</div>
254
263
  <div class="button-group">
255
- <a href="http://localhost:2843/dashboard" class="button button-primary">Go to Dashboard</a>
264
+ <a href="https://vibetasks.dev/dashboard" class="button button-primary">Go to Dashboard</a>
256
265
  <button onclick="window.close()" class="button button-secondary">Close Window</button>
257
266
  </div>
258
267
  </div>
@@ -263,7 +272,7 @@ async function loginWithBrowser() {
263
272
  console.log(chalk.gray(`
264
273
  Logged in as: ${email}`));
265
274
  console.log(chalk.gray(`Tokens stored in: ${storageLocation}`));
266
- console.log(chalk.blue.bold("\n\u2728 You can now use TaskFlow CLI commands\n"));
275
+ console.log(chalk.blue.bold("\n\u2728 You can now use VibeTasks CLI commands\n"));
267
276
  if (timeoutId) clearTimeout(timeoutId);
268
277
  resolve();
269
278
  server.close(() => {
@@ -308,11 +317,13 @@ Error: ${error.message}`));
308
317
  });
309
318
  server.listen(port, async () => {
310
319
  spinner.text = "Opening browser...";
311
- const taskflowPort = process.env.TASKFLOW_WEB_PORT || "2843";
312
- const authUrl = `http://localhost:${taskflowPort}/cli-auth?port=${port}`;
320
+ const baseUrl = getAuthBaseUrl();
321
+ const authUrl = `${baseUrl}/cli-auth?port=${port}`;
313
322
  spinner.text = "Waiting for authentication in browser...";
314
323
  console.log(chalk.gray(`
315
- If browser doesn't open, visit: ${authUrl}
324
+ Opening: ${authUrl}`));
325
+ console.log(chalk.gray(`
326
+ If browser doesn't open, visit the URL above.
316
327
  `));
317
328
  await openBrowser(authUrl);
318
329
  });
@@ -474,7 +485,227 @@ function parseDate(dateStr) {
474
485
 
475
486
  // src/commands/add.ts
476
487
  import { detectProject } from "@vibetasks/shared/utils/project-detector";
477
- var addCommand = new Command2("add").description("Add a new task").argument("<title>", "Task title").option("-n, --notes <notes>", "Task notes (markdown supported)").option("-d, --due <date>", 'Due date (YYYY-MM-DD, "today", "tomorrow", "+3d")').option("-p, --priority <level>", "Priority: low, medium, high", "none").option("-t, --tags <tags...>", "Tags (space-separated)").option("--project <name>", "Override auto-detected project").option("-e, --energy <level>", "Energy required: low, medium, high").action(async (title, options) => {
488
+ import { randomUUID as randomUUID2 } from "crypto";
489
+
490
+ // src/utils/session-manager.ts
491
+ import { writeFile, readFile, mkdir } from "fs/promises";
492
+ import { join } from "path";
493
+ import { homedir } from "os";
494
+ import { randomUUID } from "crypto";
495
+ var ADJECTIVES = [
496
+ "swift",
497
+ "calm",
498
+ "bold",
499
+ "warm",
500
+ "cool",
501
+ "bright",
502
+ "dark",
503
+ "quick",
504
+ "slow",
505
+ "wise",
506
+ "keen",
507
+ "soft",
508
+ "loud",
509
+ "deep",
510
+ "high",
511
+ "low"
512
+ ];
513
+ var NOUNS = [
514
+ "fox",
515
+ "owl",
516
+ "bear",
517
+ "wolf",
518
+ "hawk",
519
+ "moon",
520
+ "star",
521
+ "sun",
522
+ "tree",
523
+ "wave",
524
+ "wind",
525
+ "fire",
526
+ "snow",
527
+ "rain",
528
+ "leaf",
529
+ "stone"
530
+ ];
531
+ var SessionManager = class {
532
+ storePath;
533
+ store = null;
534
+ constructor() {
535
+ this.storePath = join(homedir(), ".vibetasks", "sessions.json");
536
+ }
537
+ /**
538
+ * Generate a memorable session ID like "swift-fox-42"
539
+ */
540
+ generateSessionId() {
541
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
542
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
543
+ const num = Math.floor(Math.random() * 100);
544
+ return `${adj}-${noun}-${num}`;
545
+ }
546
+ /**
547
+ * Load sessions from disk
548
+ */
549
+ async load() {
550
+ if (this.store) return this.store;
551
+ try {
552
+ const data = await readFile(this.storePath, "utf-8");
553
+ this.store = JSON.parse(data);
554
+ return this.store;
555
+ } catch {
556
+ this.store = { sessions: [] };
557
+ return this.store;
558
+ }
559
+ }
560
+ /**
561
+ * Save sessions to disk
562
+ */
563
+ async save() {
564
+ const dir = join(homedir(), ".vibetasks");
565
+ await mkdir(dir, { recursive: true });
566
+ await writeFile(this.storePath, JSON.stringify(this.store, null, 2));
567
+ }
568
+ /**
569
+ * Start a new session
570
+ */
571
+ async startSession(project) {
572
+ const store = await this.load();
573
+ if (store.currentSession) {
574
+ await this.endSession();
575
+ }
576
+ const session = {
577
+ id: this.generateSessionId(),
578
+ fullId: randomUUID(),
579
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
580
+ project,
581
+ actions: []
582
+ };
583
+ store.sessions.push(session);
584
+ store.currentSession = session.id;
585
+ await this.save();
586
+ return session;
587
+ }
588
+ /**
589
+ * Get current session (or start one if none exists)
590
+ */
591
+ async getCurrentSession() {
592
+ const store = await this.load();
593
+ if (!store.currentSession) {
594
+ return null;
595
+ }
596
+ return store.sessions.find((s) => s.id === store.currentSession) || null;
597
+ }
598
+ /**
599
+ * Get or create current session
600
+ */
601
+ async getOrCreateSession(project) {
602
+ const current = await this.getCurrentSession();
603
+ if (current) return current;
604
+ return this.startSession(project);
605
+ }
606
+ /**
607
+ * Log an action in the current session
608
+ */
609
+ async logAction(action) {
610
+ const store = await this.load();
611
+ const session = store.sessions.find((s) => s.id === store.currentSession);
612
+ if (!session) {
613
+ const newSession = await this.startSession();
614
+ newSession.actions.push({
615
+ ...action,
616
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
617
+ });
618
+ } else {
619
+ session.actions.push({
620
+ ...action,
621
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
622
+ });
623
+ }
624
+ await this.save();
625
+ }
626
+ /**
627
+ * End the current session
628
+ */
629
+ async endSession(summary) {
630
+ const store = await this.load();
631
+ const session = store.sessions.find((s) => s.id === store.currentSession);
632
+ if (session) {
633
+ session.endedAt = (/* @__PURE__ */ new Date()).toISOString();
634
+ session.summary = summary;
635
+ store.currentSession = void 0;
636
+ await this.save();
637
+ }
638
+ return session || null;
639
+ }
640
+ /**
641
+ * Get a session by ID (partial match supported)
642
+ */
643
+ async getSession(id) {
644
+ const store = await this.load();
645
+ let session = store.sessions.find((s) => s.id === id);
646
+ if (session) return session;
647
+ session = store.sessions.find((s) => s.id.includes(id) || s.fullId.startsWith(id));
648
+ return session || null;
649
+ }
650
+ /**
651
+ * Get recent sessions
652
+ */
653
+ async getRecentSessions(limit = 10) {
654
+ const store = await this.load();
655
+ return store.sessions.slice(-limit).reverse();
656
+ }
657
+ /**
658
+ * Get session summary for handoff to next AI
659
+ */
660
+ async getSessionHandoff(sessionId) {
661
+ const session = await this.getSession(sessionId);
662
+ if (!session) {
663
+ return `Session "${sessionId}" not found.`;
664
+ }
665
+ const lines = [
666
+ `## Session: ${session.id}`,
667
+ `Started: ${session.startedAt}`,
668
+ session.endedAt ? `Ended: ${session.endedAt}` : `Status: ACTIVE`,
669
+ session.project ? `Project: ${session.project}` : "",
670
+ "",
671
+ `### Actions (${session.actions.length}):`
672
+ ];
673
+ for (const action of session.actions) {
674
+ const time = new Date(action.timestamp).toLocaleTimeString();
675
+ switch (action.type) {
676
+ case "task_created":
677
+ lines.push(`- [${time}] Created task: "${action.taskTitle}" (${action.taskId})`);
678
+ break;
679
+ case "task_completed":
680
+ lines.push(`- [${time}] Completed: "${action.taskTitle}"`);
681
+ break;
682
+ case "task_updated":
683
+ lines.push(`- [${time}] Updated: "${action.taskTitle}"`);
684
+ break;
685
+ case "file_modified":
686
+ lines.push(`- [${time}] Modified: ${action.filePath}`);
687
+ break;
688
+ case "note":
689
+ lines.push(`- [${time}] Note: ${action.description}`);
690
+ break;
691
+ }
692
+ }
693
+ if (session.summary) {
694
+ lines.push("", "### Summary:", session.summary);
695
+ }
696
+ return lines.filter(Boolean).join("\n");
697
+ }
698
+ };
699
+ var sessionManager = null;
700
+ function getSessionManager() {
701
+ if (!sessionManager) {
702
+ sessionManager = new SessionManager();
703
+ }
704
+ return sessionManager;
705
+ }
706
+
707
+ // src/commands/add.ts
708
+ var addCommand = new Command2("add").description("Add a new task").argument("<title>", "Task title").option("-n, --notes <notes>", "Task notes (markdown supported)").option("-d, --due <date>", 'Due date (YYYY-MM-DD, "today", "tomorrow", "+3d")').option("-p, --priority <level>", "Priority: low, medium, high", "none").option("-t, --tags <tags...>", "Tags (space-separated)").option("-s, --subtasks <subtasks...>", "Subtasks (space-separated, use quotes for multi-word)").option("--project <name>", "Override auto-detected project").option("-e, --energy <level>", "Energy required: low, medium, high").action(async (title, options) => {
478
709
  const spinner = ora3("Creating task...").start();
479
710
  try {
480
711
  const authManager = new AuthManager3();
@@ -512,6 +743,11 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
512
743
  throw new Error(`Invalid energy level. Must be one of: ${validEnergy.join(", ")}`);
513
744
  }
514
745
  }
746
+ const subtasksJson = options.subtasks?.map((subtaskTitle) => ({
747
+ id: randomUUID2(),
748
+ title: subtaskTitle,
749
+ done: false
750
+ })) || [];
515
751
  const task = await taskOps.createTask({
516
752
  title,
517
753
  notes: options.notes,
@@ -524,7 +760,8 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
524
760
  // ai or human
525
761
  status: createdBy === "ai" ? "vibing" : "todo",
526
762
  // AI tasks start vibing
527
- energy_required: options.energy
763
+ energy_required: options.energy,
764
+ subtasks_json: subtasksJson
528
765
  });
529
766
  if (options.tags && options.tags.length > 0) {
530
767
  const tagIds = [];
@@ -535,6 +772,15 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
535
772
  await taskOps.linkTaskTags(task.id, tagIds);
536
773
  }
537
774
  spinner.succeed(chalk3.green("Task created!"));
775
+ if (createdBy === "ai") {
776
+ const sessionManager2 = getSessionManager();
777
+ await sessionManager2.logAction({
778
+ type: "task_created",
779
+ description: `Created task: "${title}"`,
780
+ taskId: task.id,
781
+ taskTitle: title
782
+ });
783
+ }
538
784
  console.log();
539
785
  if (projectTag) {
540
786
  console.log(chalk3.gray(`\u{1F4C1} Project: ${chalk3.white(projectTag)}`));
@@ -566,6 +812,12 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
566
812
  if (options.tags && options.tags.length > 0) {
567
813
  console.log(chalk3.magenta(`Tags: ${options.tags.join(", ")}`));
568
814
  }
815
+ if (subtasksJson.length > 0) {
816
+ console.log(chalk3.cyan(`Subtasks: ${subtasksJson.length}`));
817
+ subtasksJson.forEach((st, i) => {
818
+ console.log(chalk3.gray(` ${i + 1}. ${st.title}`));
819
+ });
820
+ }
569
821
  console.log();
570
822
  process.exit(0);
571
823
  } catch (error) {
@@ -590,7 +842,81 @@ import { Command as Command3 } from "commander";
590
842
  import chalk4 from "chalk";
591
843
  import Table from "cli-table3";
592
844
  import { AuthManager as AuthManager4, TaskOperations as TaskOperations2 } from "@vibetasks/core";
593
- var listCommand = new Command3("list").description("List tasks").argument("[filter]", "Filter: all, today, upcoming, completed", "all").option("-l, --limit <number>", "Maximum number of tasks to show", "50").option("--project <name>", "Filter by project tag").option("--created-by <source>", "Filter by creator: ai or human").option("--status <status>", "Filter by status: todo, vibing, done").action(async (filter, options) => {
845
+
846
+ // src/utils/short-ids.ts
847
+ import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
848
+ import { join as join2 } from "path";
849
+ import { homedir as homedir2 } from "os";
850
+ var ShortIdManager = class {
851
+ storePath;
852
+ maxIds = 99;
853
+ constructor() {
854
+ this.storePath = join2(homedir2(), ".vibetasks", "short-ids.json");
855
+ }
856
+ /**
857
+ * Save mappings from a list of task IDs
858
+ * Short IDs are assigned 1, 2, 3... based on array order
859
+ */
860
+ async saveMappings(taskIds) {
861
+ const mappings = taskIds.slice(0, this.maxIds).map((fullId, index) => ({
862
+ shortId: index + 1,
863
+ fullId,
864
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
865
+ }));
866
+ const store = {
867
+ mappings,
868
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
869
+ };
870
+ const dir = join2(homedir2(), ".vibetasks");
871
+ await mkdir2(dir, { recursive: true });
872
+ await writeFile2(this.storePath, JSON.stringify(store, null, 2));
873
+ }
874
+ /**
875
+ * Get the full UUID for a short ID
876
+ */
877
+ async getFullId(shortId) {
878
+ try {
879
+ const content = await readFile2(this.storePath, "utf-8");
880
+ const store = JSON.parse(content);
881
+ const mapping = store.mappings.find((m) => m.shortId === shortId);
882
+ return mapping?.fullId || null;
883
+ } catch {
884
+ return null;
885
+ }
886
+ }
887
+ /**
888
+ * Get the short ID for a full UUID (if it exists in current mappings)
889
+ */
890
+ async getShortId(fullId) {
891
+ try {
892
+ const content = await readFile2(this.storePath, "utf-8");
893
+ const store = JSON.parse(content);
894
+ const mapping = store.mappings.find((m) => m.fullId === fullId);
895
+ return mapping?.shortId || null;
896
+ } catch {
897
+ return null;
898
+ }
899
+ }
900
+ /**
901
+ * Resolve an ID that could be either short (1-99) or full UUID
902
+ */
903
+ async resolveId(idOrShortId) {
904
+ const shortId = parseInt(idOrShortId, 10);
905
+ if (!isNaN(shortId) && shortId >= 1 && shortId <= this.maxIds) {
906
+ const fullId = await this.getFullId(shortId);
907
+ if (fullId) {
908
+ return fullId;
909
+ }
910
+ throw new Error(
911
+ `Short ID #${shortId} not found. Run 'vibetasks list --short-ids' first to generate mappings.`
912
+ );
913
+ }
914
+ return idOrShortId;
915
+ }
916
+ };
917
+
918
+ // src/commands/list.ts
919
+ var listCommand = new Command3("list").description("List tasks").argument("[filter]", "Filter: all, today, upcoming, completed", "all").option("-l, --limit <number>", "Maximum number of tasks to show", "50").option("--project <name>", "Filter by project tag").option("--created-by <source>", "Filter by creator: ai or human").option("--status <status>", "Filter by status: todo, vibing, done").option("--short-ids", "Show short numeric IDs (1-99) for easy reference").action(async (filter, options) => {
594
920
  try {
595
921
  const validFilters = ["all", "today", "upcoming", "completed"];
596
922
  if (!validFilters.includes(filter)) {
@@ -634,8 +960,18 @@ ${filterMessages[filter] || "No tasks found"}.
634
960
  }
635
961
  const limit = parseInt(options.limit, 10);
636
962
  const displayTasks = tasks.slice(0, limit);
963
+ const shortIdManager = new ShortIdManager();
964
+ if (options.shortIds) {
965
+ await shortIdManager.saveMappings(displayTasks.map((t) => t.id));
966
+ }
637
967
  const table = new Table({
638
- head: [
968
+ head: options.shortIds ? [
969
+ chalk4.cyan("#"),
970
+ chalk4.cyan("Status"),
971
+ chalk4.cyan("Title"),
972
+ chalk4.cyan("Progress"),
973
+ chalk4.cyan("Due")
974
+ ] : [
639
975
  chalk4.cyan("ID"),
640
976
  chalk4.cyan("Status"),
641
977
  chalk4.cyan("Title"),
@@ -643,10 +979,10 @@ ${filterMessages[filter] || "No tasks found"}.
643
979
  chalk4.cyan("Priority"),
644
980
  chalk4.cyan("Due")
645
981
  ],
646
- colWidths: [10, 10, 35, 15, 10, 12],
982
+ colWidths: options.shortIds ? [5, 10, 40, 12, 12] : [10, 10, 35, 15, 10, 12],
647
983
  wordWrap: true
648
984
  });
649
- displayTasks.forEach((task) => {
985
+ displayTasks.forEach((task, index) => {
650
986
  const priorityColors = {
651
987
  high: chalk4.red,
652
988
  medium: chalk4.yellow,
@@ -665,6 +1001,7 @@ ${filterMessages[filter] || "No tasks found"}.
665
1001
  };
666
1002
  const priorityColor = priorityColors[task.priority || "none"];
667
1003
  const statusColor = statusColors[task.status || "todo"];
1004
+ const shortId = chalk4.yellow.bold(`${index + 1}`);
668
1005
  const id = task.id.substring(0, 8);
669
1006
  const status = statusColor(`${statusEmojis[task.status || "todo"]} ${task.status || "todo"}`);
670
1007
  const title = task.status === "done" ? chalk4.strikethrough(task.title) : task.title;
@@ -672,7 +1009,15 @@ ${filterMessages[filter] || "No tasks found"}.
672
1009
  const project = task.project_tag ? chalk4.blue(task.project_tag) : chalk4.gray("-");
673
1010
  const priority = priorityColor(task.priority || "none");
674
1011
  const dueDate = task.due_date ? task.due_date.split("T")[0] : chalk4.gray("-");
675
- table.push([id, status, titleWithAI, project, priority, dueDate]);
1012
+ const subtasks = task.subtasks_json || task.subtasks || [];
1013
+ const doneCount = subtasks.filter((s) => s.done).length;
1014
+ const totalCount = subtasks.length;
1015
+ const progress = totalCount > 0 ? chalk4.cyan(`${doneCount}/${totalCount}`) : chalk4.gray("-");
1016
+ if (options.shortIds) {
1017
+ table.push([shortId, status, titleWithAI, progress, dueDate]);
1018
+ } else {
1019
+ table.push([id, status, titleWithAI, project, priority, dueDate]);
1020
+ }
676
1021
  });
677
1022
  console.log("\n" + table.toString());
678
1023
  const filterLabels = {
@@ -686,6 +1031,10 @@ Total: ${tasks.length} ${filterLabels[filter]} task${tasks.length === 1 ? "" : "
686
1031
  if (tasks.length > limit) {
687
1032
  console.log(chalk4.yellow(`Showing ${limit} of ${tasks.length} tasks. Use --limit to show more.`));
688
1033
  }
1034
+ if (options.shortIds) {
1035
+ console.log(chalk4.gray(`
1036
+ Use: vibetasks vibing <#> to start working on a task`));
1037
+ }
689
1038
  console.log();
690
1039
  process.exit(0);
691
1040
  } catch (error) {
@@ -706,15 +1055,16 @@ import { Command as Command4 } from "commander";
706
1055
  import ora4 from "ora";
707
1056
  import chalk5 from "chalk";
708
1057
  import { AuthManager as AuthManager5, TaskOperations as TaskOperations3 } from "@vibetasks/core";
709
- var doneCommand = new Command4("done").description("Mark a task as complete").argument("<id>", "Task ID (full or first 8 characters)").action(async (id) => {
1058
+ var doneCommand = new Command4("done").description("Mark a task as complete").argument("<id>", "Task ID, short # (1-99), or first 8 characters").action(async (id) => {
710
1059
  const spinner = ora4("Completing task...").start();
711
1060
  try {
712
1061
  const authManager = new AuthManager5();
713
1062
  const taskOps = await TaskOperations3.fromAuthManager(authManager);
714
- let taskId = id;
715
- if (id.length < 32) {
1063
+ const shortIdManager = new ShortIdManager();
1064
+ let taskId = await shortIdManager.resolveId(id);
1065
+ if (taskId.length < 32) {
716
1066
  const allTasks = await taskOps.getTasks("all");
717
- const matchingTask = allTasks.find((t) => t.id.startsWith(id));
1067
+ const matchingTask = allTasks.find((t) => t.id.startsWith(taskId));
718
1068
  if (!matchingTask) {
719
1069
  spinner.fail(chalk5.red("Task not found"));
720
1070
  console.error(chalk5.gray(`
@@ -725,6 +1075,13 @@ No task found with ID starting with: ${id}
725
1075
  taskId = matchingTask.id;
726
1076
  }
727
1077
  const task = await taskOps.completeTask(taskId);
1078
+ const sessionManager2 = getSessionManager();
1079
+ await sessionManager2.logAction({
1080
+ type: "task_completed",
1081
+ description: `Completed: "${task.title}"`,
1082
+ taskId: task.id,
1083
+ taskTitle: task.title
1084
+ });
728
1085
  spinner.succeed(chalk5.green("Task completed!"));
729
1086
  console.log(chalk5.gray(`
730
1087
  \u2713 ${task.title}`));
@@ -752,11 +1109,16 @@ import ora5 from "ora";
752
1109
  import inquirer2 from "inquirer";
753
1110
  import { AuthManager as AuthManager6, TaskOperations as TaskOperations4 } from "@vibetasks/core";
754
1111
  var WIP_LIMIT = 3;
755
- var vibingCommand = new Command5("vibing").alias("start").alias("v").description("Start working on a task (move to vibing status)").argument("[task-id]", "Task ID to start vibing on").option("-p, --pick", "Pick from todo tasks interactively").action(async (taskId, options) => {
1112
+ var vibingCommand = new Command5("vibing").alias("start").alias("v").description("Start working on a task (move to vibing status)").argument("[task-id]", "Task ID or short # (1-99) to start vibing on").option("-p, --pick", "Pick from todo tasks interactively").action(async (taskIdInput, options) => {
756
1113
  const spinner = ora5();
757
1114
  try {
758
1115
  const authManager = new AuthManager6();
759
1116
  const taskOps = await TaskOperations4.fromAuthManager(authManager);
1117
+ const shortIdManager = new ShortIdManager();
1118
+ let taskId;
1119
+ if (taskIdInput) {
1120
+ taskId = await shortIdManager.resolveId(taskIdInput);
1121
+ }
760
1122
  const allTasks = await taskOps.getTasks("all");
761
1123
  const vibingTasks = allTasks.filter((t) => t.status === "vibing" && !t.completed);
762
1124
  if (vibingTasks.length >= WIP_LIMIT) {
@@ -2721,10 +3083,240 @@ function getTagColor2(category) {
2721
3083
  return colors[category] || "#FF6B6B";
2722
3084
  }
2723
3085
 
3086
+ // src/commands/next.ts
3087
+ import { Command as Command16 } from "commander";
3088
+ import chalk17 from "chalk";
3089
+ import { AuthManager as AuthManager16, TaskOperations as TaskOperations12 } from "@vibetasks/core";
3090
+ var PRIORITY_ORDER = { high: 0, medium: 1, low: 2, none: 3 };
3091
+ var nextCommand = new Command16("next").description("Show and optionally start the highest priority task").option("-s, --start", "Start vibing on the task immediately").option("--project <name>", "Filter by project tag").action(async (options) => {
3092
+ try {
3093
+ const authManager = new AuthManager16();
3094
+ const taskOps = await TaskOperations12.fromAuthManager(authManager);
3095
+ let tasks = await taskOps.getTasks("all");
3096
+ tasks = tasks.filter((t) => t.status !== "done" && t.status !== "archived" && !t.completed);
3097
+ if (options.project) {
3098
+ tasks = tasks.filter((t) => t.project_tag === options.project);
3099
+ }
3100
+ tasks.sort((a, b) => {
3101
+ const priorityDiff = (PRIORITY_ORDER[a.priority || "none"] || 3) - (PRIORITY_ORDER[b.priority || "none"] || 3);
3102
+ if (priorityDiff !== 0) return priorityDiff;
3103
+ if (a.status === "vibing" && b.status !== "vibing") return -1;
3104
+ if (b.status === "vibing" && a.status !== "vibing") return 1;
3105
+ return (a.position || 0) - (b.position || 0);
3106
+ });
3107
+ if (tasks.length === 0) {
3108
+ console.log(chalk17.gray("\n\u{1F389} No tasks in queue! You're all caught up.\n"));
3109
+ process.exit(0);
3110
+ }
3111
+ const nextTask = tasks[0];
3112
+ if (options.start && nextTask.status !== "vibing") {
3113
+ await taskOps.updateTaskStatus(nextTask.id, "vibing");
3114
+ console.log(chalk17.green(`
3115
+ \u26A1 Started vibing on task!
3116
+ `));
3117
+ }
3118
+ const priorityColors = {
3119
+ high: chalk17.red,
3120
+ medium: chalk17.yellow,
3121
+ low: chalk17.blue,
3122
+ none: chalk17.gray
3123
+ };
3124
+ const priorityEmojis = {
3125
+ high: "\u{1F534}",
3126
+ medium: "\u{1F7E1}",
3127
+ low: "\u{1F535}",
3128
+ none: "\u26AA"
3129
+ };
3130
+ const statusColors = {
3131
+ todo: chalk17.gray,
3132
+ vibing: chalk17.magenta,
3133
+ done: chalk17.green
3134
+ };
3135
+ const priority = nextTask.priority || "none";
3136
+ const priorityColor = priorityColors[priority];
3137
+ const priorityEmoji = priorityEmojis[priority];
3138
+ const statusColor = statusColors[nextTask.status || "todo"];
3139
+ const shortIdManager = new ShortIdManager();
3140
+ await shortIdManager.saveMappings([nextTask.id]);
3141
+ console.log("\n" + chalk17.bold("\u2501".repeat(50)));
3142
+ console.log(chalk17.bold.cyan("\n\u{1F4CD} NEXT UP:\n"));
3143
+ console.log(chalk17.bold.white(` ${nextTask.title}`));
3144
+ console.log();
3145
+ console.log(` ${priorityEmoji} Priority: ${priorityColor(priority.charAt(0).toUpperCase() + priority.slice(1))}`);
3146
+ console.log(` \u{1F4CA} Status: ${statusColor(nextTask.status || "todo")}`);
3147
+ if (nextTask.project_tag) {
3148
+ console.log(` \u{1F4C1} Project: ${chalk17.blue(nextTask.project_tag)}`);
3149
+ }
3150
+ if (nextTask.due_date) {
3151
+ const dueDate = new Date(nextTask.due_date);
3152
+ const today = /* @__PURE__ */ new Date();
3153
+ const isOverdue = dueDate < today;
3154
+ const dateStr = nextTask.due_date.split("T")[0];
3155
+ console.log(` \u{1F4C5} Due: ${isOverdue ? chalk17.red(dateStr + " (OVERDUE)") : chalk17.gray(dateStr)}`);
3156
+ }
3157
+ if (nextTask.context_notes) {
3158
+ console.log(`
3159
+ ${chalk17.blue("\u{1F4AC} Context:")}`);
3160
+ console.log(chalk17.gray(` ${nextTask.context_notes}`));
3161
+ }
3162
+ const subtasks = nextTask.subtasks_json || nextTask.subtasks || [];
3163
+ if (subtasks.length > 0) {
3164
+ const doneCount = subtasks.filter((s) => s.done).length;
3165
+ console.log(`
3166
+ ${chalk17.cyan("\u2713 Subtasks:")} ${doneCount}/${subtasks.length} done`);
3167
+ subtasks.slice(0, 5).forEach((s) => {
3168
+ const icon = s.done ? chalk17.green("\u2713") : chalk17.gray("\u25CB");
3169
+ const text = s.done ? chalk17.strikethrough.gray(s.title) : s.title;
3170
+ console.log(` ${icon} ${text}`);
3171
+ });
3172
+ if (subtasks.length > 5) {
3173
+ console.log(chalk17.gray(` ... and ${subtasks.length - 5} more`));
3174
+ }
3175
+ }
3176
+ console.log("\n" + chalk17.bold("\u2501".repeat(50)));
3177
+ console.log(chalk17.gray("\n Quick actions:"));
3178
+ if (nextTask.status !== "vibing") {
3179
+ console.log(chalk17.gray(` \u2022 ${chalk17.cyan("vibetasks next --start")} - Start vibing on this task`));
3180
+ }
3181
+ console.log(chalk17.gray(` \u2022 ${chalk17.cyan("vibetasks vibing 1")} - Start working (short ID #1)`));
3182
+ console.log(chalk17.gray(` \u2022 ${chalk17.cyan("vibetasks done 1")} - Mark as done`));
3183
+ console.log();
3184
+ process.exit(0);
3185
+ } catch (error) {
3186
+ if (error.message.includes("Not authenticated")) {
3187
+ console.error(chalk17.yellow("\n\u26A0\uFE0F You need to login first"));
3188
+ console.error(chalk17.gray("Run: vibetasks login\n"));
3189
+ } else {
3190
+ console.error(chalk17.red(`
3191
+ Error: ${error.message}
3192
+ `));
3193
+ }
3194
+ process.exit(1);
3195
+ }
3196
+ });
3197
+
3198
+ // src/commands/session.ts
3199
+ import { Command as Command17 } from "commander";
3200
+ import chalk18 from "chalk";
3201
+ import { detectProject as detectProject5 } from "@vibetasks/shared/utils/project-detector";
3202
+ var sessionCommand = new Command17("session").description("Manage AI sessions for crash recovery and handoffs").addCommand(
3203
+ new Command17("start").description("Start a new session").action(async () => {
3204
+ const sessionManager2 = getSessionManager();
3205
+ const project = await detectProject5();
3206
+ const session = await sessionManager2.startSession(project?.name);
3207
+ console.log(chalk18.green("\u2713 Session started"));
3208
+ console.log();
3209
+ console.log(chalk18.cyan("Session ID:"), chalk18.bold(session.id));
3210
+ console.log(chalk18.gray("Full ID:"), session.fullId.slice(0, 8));
3211
+ if (session.project) {
3212
+ console.log(chalk18.gray("Project:"), session.project);
3213
+ }
3214
+ console.log();
3215
+ console.log(chalk18.yellow("Tip:"), "If the AI crashes, tell the next one:");
3216
+ console.log(chalk18.white(` "Continue from session ${session.id}"`));
3217
+ })
3218
+ ).addCommand(
3219
+ new Command17("current").alias("status").description("Show current session status").action(async () => {
3220
+ const sessionManager2 = getSessionManager();
3221
+ const session = await sessionManager2.getCurrentSession();
3222
+ if (!session) {
3223
+ console.log(chalk18.yellow("No active session"));
3224
+ console.log(chalk18.gray("Start one with: vibetasks session start"));
3225
+ return;
3226
+ }
3227
+ console.log(chalk18.cyan("Current Session:"), chalk18.bold(session.id));
3228
+ console.log(chalk18.gray("Started:"), new Date(session.startedAt).toLocaleString());
3229
+ if (session.project) {
3230
+ console.log(chalk18.gray("Project:"), session.project);
3231
+ }
3232
+ console.log(chalk18.gray("Actions:"), session.actions.length);
3233
+ console.log();
3234
+ if (session.actions.length > 0) {
3235
+ console.log(chalk18.cyan("Recent actions:"));
3236
+ const recent = session.actions.slice(-5);
3237
+ for (const action of recent) {
3238
+ const time = new Date(action.timestamp).toLocaleTimeString();
3239
+ const icon = {
3240
+ task_created: "\u2795",
3241
+ task_completed: "\u2713",
3242
+ task_updated: "\u{1F4DD}",
3243
+ file_modified: "\u{1F4C4}",
3244
+ note: "\u{1F4AC}"
3245
+ }[action.type];
3246
+ console.log(chalk18.gray(` ${icon} [${time}]`), action.description);
3247
+ }
3248
+ if (session.actions.length > 5) {
3249
+ console.log(chalk18.gray(` ... and ${session.actions.length - 5} more`));
3250
+ }
3251
+ }
3252
+ })
3253
+ ).addCommand(
3254
+ new Command17("end").description("End the current session").option("-s, --summary <text>", "Add a summary of what was done").action(async (options) => {
3255
+ const sessionManager2 = getSessionManager();
3256
+ const session = await sessionManager2.endSession(options.summary);
3257
+ if (!session) {
3258
+ console.log(chalk18.yellow("No active session to end"));
3259
+ return;
3260
+ }
3261
+ console.log(chalk18.green("\u2713 Session ended:"), chalk18.bold(session.id));
3262
+ console.log(chalk18.gray("Duration:"), formatDuration(session.startedAt, session.endedAt));
3263
+ console.log(chalk18.gray("Actions:"), session.actions.length);
3264
+ if (options.summary) {
3265
+ console.log(chalk18.gray("Summary:"), options.summary);
3266
+ }
3267
+ })
3268
+ ).addCommand(
3269
+ new Command17("list").description("List recent sessions").option("-n, --limit <number>", "Number of sessions to show", "10").action(async (options) => {
3270
+ const sessionManager2 = getSessionManager();
3271
+ const sessions = await sessionManager2.getRecentSessions(parseInt(options.limit));
3272
+ if (sessions.length === 0) {
3273
+ console.log(chalk18.yellow("No sessions found"));
3274
+ return;
3275
+ }
3276
+ console.log(chalk18.cyan("Recent Sessions:"));
3277
+ console.log();
3278
+ for (const session of sessions) {
3279
+ const status = session.endedAt ? chalk18.gray("ended") : chalk18.green("active");
3280
+ const date = new Date(session.startedAt).toLocaleDateString();
3281
+ const actions = session.actions.length;
3282
+ console.log(
3283
+ chalk18.bold(session.id),
3284
+ status,
3285
+ chalk18.gray(`| ${date} | ${actions} actions`),
3286
+ session.project ? chalk18.blue(`[${session.project}]`) : ""
3287
+ );
3288
+ }
3289
+ })
3290
+ ).addCommand(
3291
+ new Command17("show").alias("handoff").description("Show session details for handoff to next AI").argument("<session-id>", "Session ID (partial match supported)").action(async (sessionId) => {
3292
+ const sessionManager2 = getSessionManager();
3293
+ const handoff = await sessionManager2.getSessionHandoff(sessionId);
3294
+ console.log(handoff);
3295
+ })
3296
+ ).addCommand(
3297
+ new Command17("log").description("Log an action or note to the current session").argument("<message>", "Note to log").action(async (message) => {
3298
+ const sessionManager2 = getSessionManager();
3299
+ await sessionManager2.logAction({
3300
+ type: "note",
3301
+ description: message
3302
+ });
3303
+ console.log(chalk18.green("\u2713 Logged to session"));
3304
+ })
3305
+ );
3306
+ function formatDuration(start, end) {
3307
+ const ms = new Date(end).getTime() - new Date(start).getTime();
3308
+ const minutes = Math.floor(ms / 6e4);
3309
+ const hours = Math.floor(minutes / 60);
3310
+ if (hours > 0) {
3311
+ return `${hours}h ${minutes % 60}m`;
3312
+ }
3313
+ return `${minutes}m`;
3314
+ }
3315
+
2724
3316
  // bin/vibetasks.ts
2725
3317
  var require2 = createRequire(import.meta.url);
2726
3318
  var pkg = require2("../../package.json");
2727
- var program = new Command16();
3319
+ var program = new Command18();
2728
3320
  program.name("vibetasks").description("VibeTasks CLI - Fast task management for vibers").version(pkg.version);
2729
3321
  program.addCommand(setupCommand);
2730
3322
  program.addCommand(loginCommand);
@@ -2742,4 +3334,6 @@ program.addCommand(watchCommand);
2742
3334
  program.addCommand(checkCommand);
2743
3335
  program.addCommand(archiveCommand);
2744
3336
  program.addCommand(daemonCommand);
3337
+ program.addCommand(nextCommand);
3338
+ program.addCommand(sessionCommand);
2745
3339
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/cli",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "VibeTasks CLI - Lightning-fast task management from your terminal. Works with Claude Code, Cursor, and all AI coding tools.",
5
5
  "author": "Vyas",
6
6
  "license": "MIT",
@@ -45,8 +45,8 @@
45
45
  "typecheck": "tsc --noEmit"
46
46
  },
47
47
  "dependencies": {
48
- "@vibetasks/core": "^0.4.2",
49
- "@vibetasks/shared": "^1.1.3",
48
+ "@vibetasks/core": "^0.5.0",
49
+ "@vibetasks/shared": "^1.2.0",
50
50
  "commander": "^11.1.0",
51
51
  "chalk": "^5.3.0",
52
52
  "ora": "^8.0.1",