@vibetasks/cli 0.6.10 → 0.6.11

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 +1641 -140
  2. package/package.json +2 -2
@@ -11,7 +11,7 @@ import {
11
11
  } from "../chunk-2KRLRG4G.js";
12
12
 
13
13
  // bin/vibetasks.ts
14
- import { Command as Command21 } from "commander";
14
+ import { Command as Command30 } from "commander";
15
15
  import { createRequire } from "module";
16
16
 
17
17
  // src/commands/login.ts
@@ -368,7 +368,7 @@ var loginCommand = new Command("login").description("Authenticate with VibeTasks
368
368
  import { Command as Command2 } from "commander";
369
369
  import ora2 from "ora";
370
370
  import chalk3 from "chalk";
371
- import { AuthManager as AuthManager2, TaskOperations } from "@vibetasks/core";
371
+ import { AuthManager as AuthManager3, TaskOperations } from "@vibetasks/core";
372
372
 
373
373
  // src/utils/date-parser.ts
374
374
  import { addDays, addWeeks, addMonths, format, parse, isValid } from "date-fns";
@@ -416,6 +416,7 @@ import { writeFile, readFile, mkdir } from "fs/promises";
416
416
  import { join } from "path";
417
417
  import { homedir } from "os";
418
418
  import { randomUUID } from "crypto";
419
+ import { AuthManager as AuthManager2 } from "@vibetasks/core";
419
420
  var ADJECTIVES = [
420
421
  "swift",
421
422
  "calm",
@@ -455,8 +456,102 @@ var NOUNS = [
455
456
  var SessionManager = class {
456
457
  storePath;
457
458
  store = null;
459
+ authManager;
458
460
  constructor() {
459
461
  this.storePath = join(homedir(), ".vibetasks", "sessions.json");
462
+ this.authManager = new AuthManager2();
463
+ }
464
+ /**
465
+ * Get the API base URL for syncing sessions
466
+ */
467
+ getApiBaseUrl() {
468
+ if (process.env.VIBETASKS_AUTH_URL) {
469
+ return process.env.VIBETASKS_AUTH_URL;
470
+ }
471
+ if (process.env.TASKFLOW_WEB_PORT) {
472
+ return `http://localhost:${process.env.TASKFLOW_WEB_PORT}`;
473
+ }
474
+ return "https://vibetasks.dev";
475
+ }
476
+ /**
477
+ * Sync a single action to the server in real-time
478
+ * Called after each action is logged locally
479
+ */
480
+ async syncActionToServer(sessionId, action) {
481
+ try {
482
+ const token = await this.authManager.getValidAccessToken();
483
+ if (!token) {
484
+ return false;
485
+ }
486
+ const apiUrl = `${this.getApiBaseUrl()}/api/sessions/${sessionId}`;
487
+ const payload = {
488
+ action_type: action.type,
489
+ description: action.description,
490
+ timestamp: action.timestamp,
491
+ task_id: action.taskId
492
+ };
493
+ const response = await fetch(apiUrl, {
494
+ method: "POST",
495
+ headers: {
496
+ "Content-Type": "application/json",
497
+ "Authorization": `Bearer ${token}`
498
+ },
499
+ body: JSON.stringify(payload)
500
+ });
501
+ return response.ok;
502
+ } catch {
503
+ return false;
504
+ }
505
+ }
506
+ /**
507
+ * Sync a session to the server database
508
+ * Called automatically when a session ends
509
+ */
510
+ async syncToServer(session) {
511
+ try {
512
+ const token = await this.authManager.getValidAccessToken();
513
+ if (!token) {
514
+ return false;
515
+ }
516
+ const apiUrl = `${this.getApiBaseUrl()}/api/sessions`;
517
+ const payload = {
518
+ session_id: session.id,
519
+ status: session.endedAt ? "ended" : "active",
520
+ started_at: session.startedAt,
521
+ ended_at: session.endedAt,
522
+ project_tag: session.project,
523
+ summary: session.summary,
524
+ handoff_notes: session.summary,
525
+ // Use summary as handoff notes
526
+ actions: session.actions.map((action) => ({
527
+ action_type: action.type,
528
+ description: action.description,
529
+ timestamp: action.timestamp,
530
+ task_id: action.taskId
531
+ }))
532
+ };
533
+ const response = await fetch(apiUrl, {
534
+ method: "POST",
535
+ headers: {
536
+ "Content-Type": "application/json",
537
+ "Authorization": `Bearer ${token}`
538
+ },
539
+ body: JSON.stringify(payload)
540
+ });
541
+ if (!response.ok) {
542
+ const error = await response.text();
543
+ if (process.stderr.isTTY) {
544
+ console.error(`Session sync failed: ${error}`);
545
+ }
546
+ return false;
547
+ }
548
+ return true;
549
+ } catch (error) {
550
+ if (process.stderr.isTTY) {
551
+ console.error(`Session sync error: ${error.message}`);
552
+ }
553
+ return false;
554
+ }
460
555
  }
461
556
  /**
462
557
  * Generate a memorable session ID like "swift-fox-42"
@@ -510,6 +605,8 @@ var SessionManager = class {
510
605
  store.sessions.push(session);
511
606
  store.currentSession = session.id;
512
607
  await this.save();
608
+ await this.syncToServer(session).catch(() => {
609
+ });
513
610
  return session;
514
611
  }
515
612
  /**
@@ -532,23 +629,22 @@ var SessionManager = class {
532
629
  }
533
630
  /**
534
631
  * Log an action in the current session
632
+ * Syncs to server in real-time (non-blocking)
535
633
  */
536
634
  async logAction(action) {
537
635
  const store = await this.load();
538
- const session = store.sessions.find((s) => s.id === store.currentSession);
636
+ let session = store.sessions.find((s) => s.id === store.currentSession);
637
+ const actionWithTimestamp = {
638
+ ...action,
639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
640
+ };
539
641
  if (!session) {
540
- const newSession = await this.startSession();
541
- newSession.actions.push({
542
- ...action,
543
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
544
- });
545
- } else {
546
- session.actions.push({
547
- ...action,
548
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
549
- });
642
+ session = await this.startSession();
550
643
  }
644
+ session.actions.push(actionWithTimestamp);
551
645
  await this.save();
646
+ this.syncActionToServer(session.id, actionWithTimestamp).catch(() => {
647
+ });
552
648
  }
553
649
  /**
554
650
  * End the current session
@@ -561,6 +657,8 @@ var SessionManager = class {
561
657
  session.summary = summary;
562
658
  store.currentSession = void 0;
563
659
  await this.save();
660
+ this.syncToServer(session).catch(() => {
661
+ });
564
662
  }
565
663
  return session || null;
566
664
  }
@@ -622,6 +720,58 @@ var SessionManager = class {
622
720
  }
623
721
  return lines.filter(Boolean).join("\n");
624
722
  }
723
+ /**
724
+ * Generate a comprehensive handoff for the next AI
725
+ * Includes: vibing tasks with context, git status, files changed, next steps
726
+ */
727
+ async generateHandoff() {
728
+ const { execSync: execSync2 } = await import("child_process");
729
+ const lines = [];
730
+ lines.push("# AI Handoff");
731
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
732
+ lines.push("");
733
+ const currentSession = await this.getCurrentSession();
734
+ if (currentSession) {
735
+ lines.push(`## Current Session: ${currentSession.id}`);
736
+ lines.push(`Project: ${currentSession.project || "unknown"}`);
737
+ lines.push(`Started: ${new Date(currentSession.startedAt).toLocaleString()}`);
738
+ lines.push(`Actions: ${currentSession.actions.length}`);
739
+ lines.push("");
740
+ }
741
+ try {
742
+ const gitStatus = execSync2("git status --porcelain", { encoding: "utf-8", timeout: 5e3 }).trim();
743
+ if (gitStatus) {
744
+ const changedFiles = gitStatus.split("\n").slice(0, 20);
745
+ lines.push("## Uncommitted Changes");
746
+ changedFiles.forEach((f) => lines.push(`- ${f}`));
747
+ if (gitStatus.split("\n").length > 20) {
748
+ lines.push(`- ... and ${gitStatus.split("\n").length - 20} more files`);
749
+ }
750
+ lines.push("");
751
+ }
752
+ } catch {
753
+ }
754
+ try {
755
+ const recentCommits = execSync2("git log --oneline -3", { encoding: "utf-8", timeout: 5e3 }).trim();
756
+ if (recentCommits) {
757
+ lines.push("## Recent Commits");
758
+ recentCommits.split("\n").forEach((c) => lines.push(`- ${c}`));
759
+ lines.push("");
760
+ }
761
+ } catch {
762
+ }
763
+ lines.push("---");
764
+ lines.push("## For Next AI");
765
+ lines.push("");
766
+ lines.push("1. Check vibing tasks: `vibetasks list --status vibing --short-ids`");
767
+ lines.push('2. Read context_notes on vibing tasks for "where I left off"');
768
+ lines.push("3. Check git status for uncommitted work");
769
+ lines.push("");
770
+ if (currentSession) {
771
+ lines.push(`To continue this session's work: "Continue from session ${currentSession.id}"`);
772
+ }
773
+ return lines.join("\n");
774
+ }
625
775
  };
626
776
  var sessionManager = null;
627
777
  function getSessionManager() {
@@ -631,11 +781,84 @@ function getSessionManager() {
631
781
  return sessionManager;
632
782
  }
633
783
 
784
+ // src/utils/short-ids.ts
785
+ import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
786
+ import { join as join2 } from "path";
787
+ import { homedir as homedir2 } from "os";
788
+ var ShortIdManager = class {
789
+ storePath;
790
+ maxIds = 99;
791
+ constructor() {
792
+ this.storePath = join2(homedir2(), ".vibetasks", "short-ids.json");
793
+ }
794
+ /**
795
+ * Save mappings from a list of tasks with their short_ids
796
+ * Uses the stable short_id from the database
797
+ */
798
+ async saveMappings(tasks) {
799
+ const mappings = tasks.filter((t) => t.short_id).slice(0, this.maxIds).map((task, index) => ({
800
+ shortId: task.short_id,
801
+ // Use actual short_id from database
802
+ fullId: task.id,
803
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
804
+ }));
805
+ const store = {
806
+ mappings,
807
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
808
+ };
809
+ const dir = join2(homedir2(), ".vibetasks");
810
+ await mkdir2(dir, { recursive: true });
811
+ await writeFile2(this.storePath, JSON.stringify(store, null, 2));
812
+ }
813
+ /**
814
+ * Get the full UUID for a short ID
815
+ */
816
+ async getFullId(shortId) {
817
+ try {
818
+ const content = await readFile2(this.storePath, "utf-8");
819
+ const store = JSON.parse(content);
820
+ const mapping = store.mappings.find((m) => m.shortId === shortId);
821
+ return mapping?.fullId || null;
822
+ } catch {
823
+ return null;
824
+ }
825
+ }
826
+ /**
827
+ * Get the short ID for a full UUID (if it exists in current mappings)
828
+ */
829
+ async getShortId(fullId) {
830
+ try {
831
+ const content = await readFile2(this.storePath, "utf-8");
832
+ const store = JSON.parse(content);
833
+ const mapping = store.mappings.find((m) => m.fullId === fullId);
834
+ return mapping?.shortId || null;
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ /**
840
+ * Resolve an ID that could be either short (1-99) or full UUID
841
+ */
842
+ async resolveId(idOrShortId) {
843
+ const shortId = parseInt(idOrShortId, 10);
844
+ if (!isNaN(shortId) && shortId >= 1 && shortId <= this.maxIds) {
845
+ const fullId = await this.getFullId(shortId);
846
+ if (fullId) {
847
+ return fullId;
848
+ }
849
+ throw new Error(
850
+ `Short ID #${shortId} not found. Run 'vibetasks list --short-ids' first to generate mappings.`
851
+ );
852
+ }
853
+ return idOrShortId;
854
+ }
855
+ };
856
+
634
857
  // src/commands/add.ts
635
- 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) => {
858
+ 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").option("-a, --acceptance <criteria...>", 'Acceptance criteria - "DONE WHEN" conditions (use quotes for multi-word)').option("-f, --files <files...>", "Relevant files for this task").option("--non-goals <goals...>", "Explicit non-goals / out of scope items").action(async (title, options) => {
636
859
  const spinner = ora2("Creating task...").start();
637
860
  try {
638
- const authManager = new AuthManager2();
861
+ const authManager = new AuthManager3();
639
862
  const taskOps = await TaskOperations.fromAuthManager(authManager);
640
863
  let projectTag;
641
864
  if (options.project) {
@@ -675,6 +898,13 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
675
898
  title: subtaskTitle,
676
899
  done: false
677
900
  })) || [];
901
+ const acceptanceCriteria = options.acceptance?.map((text) => ({
902
+ id: randomUUID2(),
903
+ text,
904
+ done: false
905
+ })) || [];
906
+ const relevantFiles = options.files || [];
907
+ const nonGoals = options.nonGoals || [];
678
908
  let sessionId;
679
909
  let sessionHistory = [];
680
910
  const sessionManager2 = getSessionManager();
@@ -703,8 +933,12 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
703
933
  subtasks_json: subtasksJson,
704
934
  session_id: sessionId,
705
935
  // Current AI session
706
- session_history: sessionHistory
936
+ session_history: sessionHistory,
707
937
  // Full history of sessions
938
+ // New AI-assisted planning fields
939
+ acceptance_criteria: acceptanceCriteria.length > 0 ? acceptanceCriteria : void 0,
940
+ relevant_files: relevantFiles.length > 0 ? relevantFiles : void 0,
941
+ non_goals: nonGoals.length > 0 ? nonGoals : void 0
708
942
  });
709
943
  if (options.tags && options.tags.length > 0) {
710
944
  const tagIds = [];
@@ -715,6 +949,12 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
715
949
  await taskOps.linkTaskTags(task.id, tagIds);
716
950
  }
717
951
  spinner.succeed(chalk3.green("Task created!"));
952
+ try {
953
+ const shortIdManager = new ShortIdManager();
954
+ const allTasks = await taskOps.getTasks("all");
955
+ await shortIdManager.saveMappings(allTasks);
956
+ } catch (e) {
957
+ }
718
958
  if (currentSession) {
719
959
  await sessionManager2.logAction({
720
960
  type: "task_created",
@@ -760,6 +1000,18 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
760
1000
  console.log(chalk3.gray(` ${i + 1}. ${st.title}`));
761
1001
  });
762
1002
  }
1003
+ if (acceptanceCriteria.length > 0) {
1004
+ console.log(chalk3.cyan(`Done when:`));
1005
+ acceptanceCriteria.forEach((ac, i) => {
1006
+ console.log(chalk3.gray(` - ${ac.text}`));
1007
+ });
1008
+ }
1009
+ if (relevantFiles.length > 0) {
1010
+ console.log(chalk3.yellow(`Files: ${relevantFiles.join(", ")}`));
1011
+ }
1012
+ if (nonGoals.length > 0) {
1013
+ console.log(chalk3.gray(`Not in scope: ${nonGoals.join(", ")}`));
1014
+ }
763
1015
  console.log();
764
1016
  process.exit(0);
765
1017
  } catch (error) {
@@ -783,82 +1035,7 @@ function isRunningInClaudeCode() {
783
1035
  import { Command as Command3 } from "commander";
784
1036
  import chalk4 from "chalk";
785
1037
  import Table from "cli-table3";
786
- import { AuthManager as AuthManager3, TaskOperations as TaskOperations2 } from "@vibetasks/core";
787
-
788
- // src/utils/short-ids.ts
789
- import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
790
- import { join as join2 } from "path";
791
- import { homedir as homedir2 } from "os";
792
- var ShortIdManager = class {
793
- storePath;
794
- maxIds = 99;
795
- constructor() {
796
- this.storePath = join2(homedir2(), ".vibetasks", "short-ids.json");
797
- }
798
- /**
799
- * Save mappings from a list of tasks with their short_ids
800
- * Uses the stable short_id from the database
801
- */
802
- async saveMappings(tasks) {
803
- const mappings = tasks.filter((t) => t.short_id).slice(0, this.maxIds).map((task, index) => ({
804
- shortId: task.short_id,
805
- // Use actual short_id from database
806
- fullId: task.id,
807
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
808
- }));
809
- const store = {
810
- mappings,
811
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
812
- };
813
- const dir = join2(homedir2(), ".vibetasks");
814
- await mkdir2(dir, { recursive: true });
815
- await writeFile2(this.storePath, JSON.stringify(store, null, 2));
816
- }
817
- /**
818
- * Get the full UUID for a short ID
819
- */
820
- async getFullId(shortId) {
821
- try {
822
- const content = await readFile2(this.storePath, "utf-8");
823
- const store = JSON.parse(content);
824
- const mapping = store.mappings.find((m) => m.shortId === shortId);
825
- return mapping?.fullId || null;
826
- } catch {
827
- return null;
828
- }
829
- }
830
- /**
831
- * Get the short ID for a full UUID (if it exists in current mappings)
832
- */
833
- async getShortId(fullId) {
834
- try {
835
- const content = await readFile2(this.storePath, "utf-8");
836
- const store = JSON.parse(content);
837
- const mapping = store.mappings.find((m) => m.fullId === fullId);
838
- return mapping?.shortId || null;
839
- } catch {
840
- return null;
841
- }
842
- }
843
- /**
844
- * Resolve an ID that could be either short (1-99) or full UUID
845
- */
846
- async resolveId(idOrShortId) {
847
- const shortId = parseInt(idOrShortId, 10);
848
- if (!isNaN(shortId) && shortId >= 1 && shortId <= this.maxIds) {
849
- const fullId = await this.getFullId(shortId);
850
- if (fullId) {
851
- return fullId;
852
- }
853
- throw new Error(
854
- `Short ID #${shortId} not found. Run 'vibetasks list --short-ids' first to generate mappings.`
855
- );
856
- }
857
- return idOrShortId;
858
- }
859
- };
860
-
861
- // src/commands/list.ts
1038
+ import { AuthManager as AuthManager4, TaskOperations as TaskOperations2 } from "@vibetasks/core";
862
1039
  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("--session [id]", "Filter by session (current session if no ID given)").option("--my-session", "Only show tasks from current AI session").option("--short-ids", "Show short numeric IDs (1-99) for easy reference").action(async (filter, options) => {
863
1040
  try {
864
1041
  const validFilters = ["all", "today", "upcoming", "completed"];
@@ -867,7 +1044,7 @@ var listCommand = new Command3("list").description("List tasks").argument("[filt
867
1044
  console.error(chalk4.gray(`Valid filters: ${validFilters.join(", ")}`));
868
1045
  process.exit(1);
869
1046
  }
870
- const authManager = new AuthManager3();
1047
+ const authManager = new AuthManager4();
871
1048
  const taskOps = await TaskOperations2.fromAuthManager(authManager);
872
1049
  let tasks = await taskOps.getTasks(filter);
873
1050
  if (options.project) {
@@ -1030,11 +1207,11 @@ Error: ${error.message}
1030
1207
  import { Command as Command4 } from "commander";
1031
1208
  import ora3 from "ora";
1032
1209
  import chalk5 from "chalk";
1033
- import { AuthManager as AuthManager4, TaskOperations as TaskOperations3 } from "@vibetasks/core";
1210
+ import { AuthManager as AuthManager5, TaskOperations as TaskOperations3 } from "@vibetasks/core";
1034
1211
  var doneCommand = new Command4("done").description("Mark a task as complete").argument("<id>", "Task ID, short # (1-99), or first 8 characters").option("-n, --notes <notes>", "Completion notes - what was done, any final notes").option("-c, --context <context>", "Context notes (alias for --notes)").action(async (id, options) => {
1035
1212
  const spinner = ora3("Completing task...").start();
1036
1213
  try {
1037
- const authManager = new AuthManager4();
1214
+ const authManager = new AuthManager5();
1038
1215
  const taskOps = await TaskOperations3.fromAuthManager(authManager);
1039
1216
  const shortIdManager = new ShortIdManager();
1040
1217
  let taskId = await shortIdManager.resolveId(id);
@@ -1107,11 +1284,11 @@ import { Command as Command5 } from "commander";
1107
1284
  import chalk6 from "chalk";
1108
1285
  import ora4 from "ora";
1109
1286
  import inquirer from "inquirer";
1110
- import { AuthManager as AuthManager5, TaskOperations as TaskOperations4 } from "@vibetasks/core";
1287
+ import { AuthManager as AuthManager6, TaskOperations as TaskOperations4 } from "@vibetasks/core";
1111
1288
  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) => {
1112
1289
  const spinner = ora4();
1113
1290
  try {
1114
- const authManager = new AuthManager5();
1291
+ const authManager = new AuthManager6();
1115
1292
  const taskOps = await TaskOperations4.fromAuthManager(authManager);
1116
1293
  const shortIdManager = new ShortIdManager();
1117
1294
  let taskId;
@@ -1189,10 +1366,10 @@ var vibingCommand = new Command5("vibing").alias("start").alias("v").description
1189
1366
  import { Command as Command6 } from "commander";
1190
1367
  import chalk7 from "chalk";
1191
1368
  import Table2 from "cli-table3";
1192
- import { AuthManager as AuthManager6, TaskOperations as TaskOperations5 } from "@vibetasks/core";
1369
+ import { AuthManager as AuthManager7, TaskOperations as TaskOperations5 } from "@vibetasks/core";
1193
1370
  var searchCommand = new Command6("search").description("Search tasks by title").argument("<query>", "Search query").option("-l, --limit <number>", "Maximum number of results", "20").action(async (query, options) => {
1194
1371
  try {
1195
- const authManager = new AuthManager6();
1372
+ const authManager = new AuthManager7();
1196
1373
  const taskOps = await TaskOperations5.fromAuthManager(authManager);
1197
1374
  const limit = parseInt(options.limit, 10);
1198
1375
  const tasks = await taskOps.searchTasks(query, limit);
@@ -1249,7 +1426,7 @@ Error: ${error.message}
1249
1426
  import { Command as Command7 } from "commander";
1250
1427
  import ora5 from "ora";
1251
1428
  import chalk8 from "chalk";
1252
- import { AuthManager as AuthManager7, TaskOperations as TaskOperations6 } from "@vibetasks/core";
1429
+ import { AuthManager as AuthManager8, TaskOperations as TaskOperations6 } from "@vibetasks/core";
1253
1430
  var updateCommand = new Command7("update").description("Update a task").argument("<id>", "Task ID (full or first 8 characters)").option("-t, --title <title>", "New title").option("-n, --notes <notes>", "New notes").option("-d, --due <date>", "New due date").option("-p, --priority <level>", "New priority: low, medium, high, none").option("-s, --status <status>", "New status: todo, vibing, done").option("--project <name>", "New project tag").option("-e, --energy <level>", "Energy required: low, medium, high").option("-c, --context <notes>", "Update context notes").action(async (id, options) => {
1254
1431
  if (!options.title && !options.notes && !options.due && !options.priority && !options.status && !options.project && !options.energy && !options.context) {
1255
1432
  console.error(chalk8.red("\nError: No updates specified"));
@@ -1258,7 +1435,7 @@ var updateCommand = new Command7("update").description("Update a task").argument
1258
1435
  }
1259
1436
  const spinner = ora5("Updating task...").start();
1260
1437
  try {
1261
- const authManager = new AuthManager7();
1438
+ const authManager = new AuthManager8();
1262
1439
  const taskOps = await TaskOperations6.fromAuthManager(authManager);
1263
1440
  let taskId = id;
1264
1441
  if (id.length < 32) {
@@ -1325,10 +1502,10 @@ import { Command as Command8 } from "commander";
1325
1502
  import ora6 from "ora";
1326
1503
  import chalk9 from "chalk";
1327
1504
  import inquirer2 from "inquirer";
1328
- import { AuthManager as AuthManager8, TaskOperations as TaskOperations7 } from "@vibetasks/core";
1505
+ import { AuthManager as AuthManager9, TaskOperations as TaskOperations7 } from "@vibetasks/core";
1329
1506
  var deleteCommand = new Command8("delete").description("Delete a task").argument("<id>", "Task ID (full or first 8 characters)").option("-y, --yes", "Skip confirmation").action(async (id, options) => {
1330
1507
  try {
1331
- const authManager = new AuthManager8();
1508
+ const authManager = new AuthManager9();
1332
1509
  const taskOps = await TaskOperations7.fromAuthManager(authManager);
1333
1510
  let taskId = id;
1334
1511
  let taskTitle = "";
@@ -1384,10 +1561,10 @@ var deleteCommand = new Command8("delete").description("Delete a task").argument
1384
1561
  // src/commands/config.ts
1385
1562
  import { Command as Command9 } from "commander";
1386
1563
  import chalk10 from "chalk";
1387
- import { AuthManager as AuthManager9 } from "@vibetasks/core";
1564
+ import { AuthManager as AuthManager10 } from "@vibetasks/core";
1388
1565
  var configCommand = new Command9("config").description("View or set configuration").argument("[key]", "Configuration key").argument("[value]", "Configuration value").action(async (key, value) => {
1389
1566
  try {
1390
- const authManager = new AuthManager9();
1567
+ const authManager = new AuthManager10();
1391
1568
  if (!key) {
1392
1569
  const configManager = authManager["configManager"];
1393
1570
  const config = await configManager.getConfig();
@@ -1621,12 +1798,12 @@ import { Command as Command11 } from "commander";
1621
1798
  import inquirer3 from "inquirer";
1622
1799
  import ora8 from "ora";
1623
1800
  import chalk12 from "chalk";
1624
- import { AuthManager as AuthManager10 } from "@vibetasks/core";
1801
+ import { AuthManager as AuthManager11 } from "@vibetasks/core";
1625
1802
  import { detectProject as detectProject2 } from "@vibetasks/shared/utils/project-detector";
1626
1803
  var initCommand = new Command11("init").description("Initialize TaskFlow for current project (auto-detect from git)").option("--name <name>", "Manually specify project name").action(async (options) => {
1627
1804
  const spinner = ora8("Detecting project...").start();
1628
1805
  try {
1629
- const authManager = new AuthManager10();
1806
+ const authManager = new AuthManager11();
1630
1807
  const cwd = process.cwd();
1631
1808
  let project;
1632
1809
  if (options.name) {
@@ -1691,7 +1868,7 @@ import os from "os";
1691
1868
  import { createServer as createServer2 } from "http";
1692
1869
  import { exec as exec2 } from "child_process";
1693
1870
  import { promisify as promisify2 } from "util";
1694
- import { AuthManager as AuthManager11, TaskOperations as TaskOperations8, createSupabaseClient } from "@vibetasks/core";
1871
+ import { AuthManager as AuthManager12, TaskOperations as TaskOperations8, createSupabaseClient } from "@vibetasks/core";
1695
1872
  import { detectProject as detectProject3 } from "@vibetasks/shared/utils/project-detector";
1696
1873
  var execAsync2 = promisify2(exec2);
1697
1874
  var SUPABASE_URL = "https://ihmayqzxqyednchbezya.supabase.co";
@@ -1756,7 +1933,7 @@ function showWelcome() {
1756
1933
  }
1757
1934
  async function checkExistingAuth() {
1758
1935
  try {
1759
- const authManager = new AuthManager11();
1936
+ const authManager = new AuthManager12();
1760
1937
  const token = await authManager.getAccessToken();
1761
1938
  if (!token) return { authenticated: false };
1762
1939
  const supabaseUrl = await authManager.getConfig("supabase_url") || SUPABASE_URL;
@@ -1800,7 +1977,7 @@ async function runBrowserAuth() {
1800
1977
  return;
1801
1978
  }
1802
1979
  try {
1803
- const authManager = new AuthManager11();
1980
+ const authManager = new AuthManager12();
1804
1981
  await authManager.setAccessToken(accessToken);
1805
1982
  await authManager.setRefreshToken(refreshToken);
1806
1983
  if (supabaseUrl) await authManager.setConfig("supabase_url", supabaseUrl);
@@ -1893,7 +2070,7 @@ async function stepAuthentication() {
1893
2070
  ]);
1894
2071
  const spinner = ora9("Authenticating...").start();
1895
2072
  try {
1896
- const authManager = new AuthManager11();
2073
+ const authManager = new AuthManager12();
1897
2074
  await authManager.setConfig("supabase_url", SUPABASE_URL);
1898
2075
  await authManager.setConfig("supabase_key", SUPABASE_ANON_KEY);
1899
2076
  const supabase = createSupabaseClient({
@@ -2042,7 +2219,7 @@ async function stepProjectInit() {
2042
2219
  return { success: true, skipped: true };
2043
2220
  }
2044
2221
  try {
2045
- const authManager = new AuthManager11();
2222
+ const authManager = new AuthManager12();
2046
2223
  await authManager.setConfig(`project_${process.cwd()}`, project.name);
2047
2224
  console.log(chalk13.green("\n \u2713") + chalk13.white(` Project "${project.name}" configured`));
2048
2225
  return { success: true, projectName: project.name };
@@ -2059,7 +2236,7 @@ async function stepVerify() {
2059
2236
  };
2060
2237
  const supabaseSpinner = ora9("Testing Supabase connection...").start();
2061
2238
  try {
2062
- const authManager = new AuthManager11();
2239
+ const authManager = new AuthManager12();
2063
2240
  const taskOps = await TaskOperations8.fromAuthManager(authManager);
2064
2241
  const tasks = await taskOps.getTasks("all");
2065
2242
  result.supabaseConnected = true;
@@ -2232,7 +2409,7 @@ async function runNonInteractiveSetup() {
2232
2409
  if (authResult.success) {
2233
2410
  console.log(chalk13.gray("\nVerifying connection..."));
2234
2411
  try {
2235
- const authManager = new AuthManager11();
2412
+ const authManager = new AuthManager12();
2236
2413
  const taskOps = await TaskOperations8.fromAuthManager(authManager);
2237
2414
  const tasks = await taskOps.getTasks("all");
2238
2415
  console.log(chalk13.green("\u2713") + ` Connected (${tasks.length} tasks)`);
@@ -2303,7 +2480,7 @@ import { Command as Command13 } from "commander";
2303
2480
  import chalk14 from "chalk";
2304
2481
  import ora10 from "ora";
2305
2482
  import inquirer5 from "inquirer";
2306
- import { AuthManager as AuthManager12, TaskOperations as TaskOperations9 } from "@vibetasks/core";
2483
+ import { AuthManager as AuthManager13, TaskOperations as TaskOperations9 } from "@vibetasks/core";
2307
2484
  import { detectProject as detectProject4 } from "@vibetasks/shared/utils/project-detector";
2308
2485
  import { generateErrorTaskTitle } from "@vibetasks/shared";
2309
2486
  var watchCommand = new Command13("watch").description("Monitor clipboard for errors and offer to capture them as tasks").option("-i, --interval <ms>", "Polling interval in milliseconds", "1000").option("-a, --auto", "Auto-create tasks without prompting").option("-q, --quiet", "Minimal output (only show errors)").option("--project <name>", "Override auto-detected project").action(async (options) => {
@@ -2317,7 +2494,7 @@ var watchCommand = new Command13("watch").description("Monitor clipboard for err
2317
2494
  let authManager;
2318
2495
  let taskOps;
2319
2496
  try {
2320
- authManager = new AuthManager12();
2497
+ authManager = new AuthManager13();
2321
2498
  taskOps = await TaskOperations9.fromAuthManager(authManager);
2322
2499
  } catch (error) {
2323
2500
  if (error.message.includes("Not authenticated")) {
@@ -2527,11 +2704,11 @@ import { Command as Command14 } from "commander";
2527
2704
  import chalk15 from "chalk";
2528
2705
  import ora11 from "ora";
2529
2706
  import inquirer6 from "inquirer";
2530
- import { AuthManager as AuthManager13, TaskOperations as TaskOperations10 } from "@vibetasks/core";
2707
+ import { AuthManager as AuthManager14, TaskOperations as TaskOperations10 } from "@vibetasks/core";
2531
2708
  var archiveCommand = new Command14("archive").description("Archive a task or manage archived tasks").argument("[task-id]", "Task ID to archive (full or first 8 characters)").option("-l, --list", "List all archived tasks").option("-u, --unarchive <id>", "Unarchive a task by ID").option("--pick", "Pick from completed tasks to archive").action(async (taskId, options) => {
2532
2709
  const spinner = ora11();
2533
2710
  try {
2534
- const authManager = new AuthManager13();
2711
+ const authManager = new AuthManager14();
2535
2712
  const taskOps = await TaskOperations10.fromAuthManager(authManager);
2536
2713
  if (options.list) {
2537
2714
  spinner.start("Fetching archived tasks...");
@@ -2646,7 +2823,7 @@ import ora12 from "ora";
2646
2823
  import { spawn } from "child_process";
2647
2824
  import path3 from "path";
2648
2825
  import { fileURLToPath } from "url";
2649
- import { AuthManager as AuthManager14, TaskOperations as TaskOperations11 } from "@vibetasks/core";
2826
+ import { AuthManager as AuthManager15, TaskOperations as TaskOperations11 } from "@vibetasks/core";
2650
2827
  import { generateErrorTaskTitle as generateErrorTaskTitle2 } from "@vibetasks/shared";
2651
2828
  var __filename2 = fileURLToPath(import.meta.url);
2652
2829
  var __dirname2 = path3.dirname(__filename2);
@@ -2970,7 +3147,7 @@ async function showNotification(title, message, type = "info", duration) {
2970
3147
  async function createTaskFromError2(error) {
2971
3148
  const spinner = ora12("Creating task from error...").start();
2972
3149
  try {
2973
- const authManager = new AuthManager14();
3150
+ const authManager = new AuthManager15();
2974
3151
  const taskOps = await TaskOperations11.fromAuthManager(authManager);
2975
3152
  const title = generateErrorTaskTitle2(error);
2976
3153
  const notes = formatErrorForNotes(error);
@@ -3033,11 +3210,11 @@ function getTagColor2(category) {
3033
3210
  // src/commands/next.ts
3034
3211
  import { Command as Command16 } from "commander";
3035
3212
  import chalk17 from "chalk";
3036
- import { AuthManager as AuthManager15, TaskOperations as TaskOperations12 } from "@vibetasks/core";
3213
+ import { AuthManager as AuthManager16, TaskOperations as TaskOperations12 } from "@vibetasks/core";
3037
3214
  var PRIORITY_ORDER = { high: 0, medium: 1, low: 2, none: 3 };
3038
3215
  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) => {
3039
3216
  try {
3040
- const authManager = new AuthManager15();
3217
+ const authManager = new AuthManager16();
3041
3218
  const taskOps = await TaskOperations12.fromAuthManager(authManager);
3042
3219
  let tasks = await taskOps.getTasks("all");
3043
3220
  tasks = tasks.filter((t) => t.status !== "done" && t.status !== "archived" && !t.completed);
@@ -3198,7 +3375,7 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
3198
3375
  }
3199
3376
  })
3200
3377
  ).addCommand(
3201
- new Command17("end").description("End the current session").option("-s, --summary <text>", "Add a summary of what was done").action(async (options) => {
3378
+ new Command17("end").description("End the current session").option("-s, --summary <text>", "Add a summary of what was done").option("--no-stats", "Skip showing session statistics").action(async (options) => {
3202
3379
  const sessionManager2 = getSessionManager();
3203
3380
  const session = await sessionManager2.endSession(options.summary);
3204
3381
  if (!session) {
@@ -3206,10 +3383,51 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
3206
3383
  return;
3207
3384
  }
3208
3385
  console.log(chalk18.green("\u2713 Session ended:"), chalk18.bold(session.id));
3209
- console.log(chalk18.gray("Duration:"), formatDuration(session.startedAt, session.endedAt));
3210
- console.log(chalk18.gray("Actions:"), session.actions.length);
3386
+ console.log();
3387
+ if (options.stats !== false) {
3388
+ const duration = formatDuration(session.startedAt, session.endedAt);
3389
+ const tasksCreated = session.actions.filter((a) => a.type === "task_created").length;
3390
+ const tasksCompleted = session.actions.filter((a) => a.type === "task_completed").length;
3391
+ const tasksUpdated = session.actions.filter((a) => a.type === "task_updated").length;
3392
+ const filesModified = session.actions.filter((a) => a.type === "file_modified").length;
3393
+ const notes = session.actions.filter((a) => a.type === "note").length;
3394
+ console.log(chalk18.cyan("\u{1F4CA} Session Statistics"));
3395
+ console.log(chalk18.gray("\u2500".repeat(40)));
3396
+ console.log(chalk18.white(` Duration: ${chalk18.bold(duration)}`));
3397
+ console.log(chalk18.white(` Actions logged: ${chalk18.bold(session.actions.length.toString())}`));
3398
+ console.log();
3399
+ if (tasksCreated > 0 || tasksCompleted > 0 || tasksUpdated > 0) {
3400
+ console.log(chalk18.cyan(" Tasks:"));
3401
+ if (tasksCreated > 0) {
3402
+ console.log(chalk18.green(` \u2795 Created: ${tasksCreated}`));
3403
+ }
3404
+ if (tasksCompleted > 0) {
3405
+ console.log(chalk18.green(` \u2713 Completed: ${tasksCompleted}`));
3406
+ }
3407
+ if (tasksUpdated > 0) {
3408
+ console.log(chalk18.blue(` \u{1F4DD} Updated: ${tasksUpdated}`));
3409
+ }
3410
+ }
3411
+ if (filesModified > 0) {
3412
+ console.log(chalk18.yellow(` \u{1F4C4} Files modified: ${filesModified}`));
3413
+ }
3414
+ if (notes > 0) {
3415
+ console.log(chalk18.gray(` \u{1F4AC} Notes logged: ${notes}`));
3416
+ }
3417
+ console.log(chalk18.gray("\u2500".repeat(40)));
3418
+ console.log();
3419
+ }
3211
3420
  if (options.summary) {
3212
- console.log(chalk18.gray("Summary:"), options.summary);
3421
+ console.log(chalk18.cyan("Summary:"), options.summary);
3422
+ console.log();
3423
+ }
3424
+ const totalTasks = session.actions.filter(
3425
+ (a) => a.type === "task_created" || a.type === "task_completed"
3426
+ ).length;
3427
+ if (totalTasks === 0) {
3428
+ console.log(chalk18.yellow("\u{1F4A1} Tip: Use `vibetasks add` and `vibetasks done` to track work"));
3429
+ } else if (totalTasks >= 5) {
3430
+ console.log(chalk18.green("\u{1F525} Productive session!"));
3213
3431
  }
3214
3432
  })
3215
3433
  ).addCommand(
@@ -3235,7 +3453,7 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
3235
3453
  }
3236
3454
  })
3237
3455
  ).addCommand(
3238
- 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) => {
3456
+ new Command17("show").description("Show session details").argument("<session-id>", "Session ID (partial match supported)").action(async (sessionId) => {
3239
3457
  const sessionManager2 = getSessionManager();
3240
3458
  const handoff = await sessionManager2.getSessionHandoff(sessionId);
3241
3459
  console.log(handoff);
@@ -3249,9 +3467,26 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
3249
3467
  });
3250
3468
  console.log(chalk18.green("\u2713 Logged to session"));
3251
3469
  })
3252
- );
3253
- function formatDuration(start, end) {
3254
- const ms = new Date(end).getTime() - new Date(start).getTime();
3470
+ ).addCommand(
3471
+ new Command17("sync").description("Sync current session and all actions to the server").action(async () => {
3472
+ const sessionManager2 = getSessionManager();
3473
+ const session = await sessionManager2.getCurrentSession();
3474
+ if (!session) {
3475
+ console.log(chalk18.yellow("No active session to sync"));
3476
+ return;
3477
+ }
3478
+ console.log(chalk18.cyan("Syncing session:"), chalk18.bold(session.id));
3479
+ console.log(chalk18.gray(`Actions to sync: ${session.actions.length}`));
3480
+ const success = await sessionManager2.syncToServer(session);
3481
+ if (success) {
3482
+ console.log(chalk18.green("\u2713 Session synced to server"));
3483
+ } else {
3484
+ console.log(chalk18.yellow("\u26A0 Sync failed (not logged in or server unavailable)"));
3485
+ }
3486
+ })
3487
+ );
3488
+ function formatDuration(start, end) {
3489
+ const ms = new Date(end).getTime() - new Date(start).getTime();
3255
3490
  const minutes = Math.floor(ms / 6e4);
3256
3491
  const hours = Math.floor(minutes / 60);
3257
3492
  if (hours > 0) {
@@ -3264,7 +3499,7 @@ function formatDuration(start, end) {
3264
3499
  import { Command as Command18 } from "commander";
3265
3500
  import ora13 from "ora";
3266
3501
  import chalk19 from "chalk";
3267
- import { AuthManager as AuthManager16, TaskOperations as TaskOperations13 } from "@vibetasks/core";
3502
+ import { AuthManager as AuthManager17, TaskOperations as TaskOperations13 } from "@vibetasks/core";
3268
3503
  function parseIndices(args) {
3269
3504
  const indices = /* @__PURE__ */ new Set();
3270
3505
  for (const arg of args) {
@@ -3306,7 +3541,7 @@ var subtaskCommand = new Command18("subtask").description("Manage subtasks withi
3306
3541
  subtaskCommand.command("list").description("List subtasks for a task").argument("<task-id>", "Task ID, short # (1-99), or first 8 characters").action(async (taskIdArg) => {
3307
3542
  const spinner = ora13("Loading subtasks...").start();
3308
3543
  try {
3309
- const authManager = new AuthManager16();
3544
+ const authManager = new AuthManager17();
3310
3545
  const taskOps = await TaskOperations13.fromAuthManager(authManager);
3311
3546
  const shortIdManager = new ShortIdManager();
3312
3547
  let taskId = await shortIdManager.resolveId(taskIdArg);
@@ -3354,7 +3589,7 @@ Error: ${error.message}
3354
3589
  subtaskCommand.command("done").description("Mark subtasks as complete").argument("<task-id>", "Task ID, short # (1-99), or first 8 characters").argument("<indices...>", 'Subtask indices (1-based): "1 2 3", "1-5", "1,3,5", or partial title match').action(async (taskIdArg, indicesArgs) => {
3355
3590
  const spinner = ora13("Updating subtasks...").start();
3356
3591
  try {
3357
- const authManager = new AuthManager16();
3592
+ const authManager = new AuthManager17();
3358
3593
  const taskOps = await TaskOperations13.fromAuthManager(authManager);
3359
3594
  const shortIdManager = new ShortIdManager();
3360
3595
  let taskId = await shortIdManager.resolveId(taskIdArg);
@@ -3430,7 +3665,7 @@ Error: ${error.message}
3430
3665
  subtaskCommand.command("undo").description("Mark subtasks as incomplete").argument("<task-id>", "Task ID, short # (1-99), or first 8 characters").argument("<indices...>", 'Subtask indices (1-based): "1 2 3", "1-5", "1,3,5", or partial title match').action(async (taskIdArg, indicesArgs) => {
3431
3666
  const spinner = ora13("Updating subtasks...").start();
3432
3667
  try {
3433
- const authManager = new AuthManager16();
3668
+ const authManager = new AuthManager17();
3434
3669
  const taskOps = await TaskOperations13.fromAuthManager(authManager);
3435
3670
  const shortIdManager = new ShortIdManager();
3436
3671
  let taskId = await shortIdManager.resolveId(taskIdArg);
@@ -3504,7 +3739,7 @@ Error: ${error.message}
3504
3739
  import { Command as Command19 } from "commander";
3505
3740
  import chalk20 from "chalk";
3506
3741
  import ora14 from "ora";
3507
- import { AuthManager as AuthManager17, TaskOperations as TaskOperations14 } from "@vibetasks/core";
3742
+ import { AuthManager as AuthManager18, TaskOperations as TaskOperations14 } from "@vibetasks/core";
3508
3743
  import {
3509
3744
  parseError,
3510
3745
  generateErrorTaskTitle as generateErrorTaskTitle3,
@@ -3602,7 +3837,7 @@ var errorCommand = new Command19("error").description("Capture an error and crea
3602
3837
  process.exit(1);
3603
3838
  }
3604
3839
  spinner.start("Creating error task...");
3605
- const authManager = new AuthManager17();
3840
+ const authManager = new AuthManager18();
3606
3841
  const taskOps = await TaskOperations14.fromAuthManager(authManager);
3607
3842
  const errorContext = parseError(errorText, "terminal");
3608
3843
  const title = errorContext ? generateErrorTaskTitle3(errorContext) : `Error: ${errorText.split("\n")[0].slice(0, 80)}`;
@@ -3642,7 +3877,7 @@ ${errorText}
3642
3877
  import { Command as Command20 } from "commander";
3643
3878
  import chalk21 from "chalk";
3644
3879
  import Table3 from "cli-table3";
3645
- import { AuthManager as AuthManager18, TaskOperations as TaskOperations15 } from "@vibetasks/core";
3880
+ import { AuthManager as AuthManager19, TaskOperations as TaskOperations15 } from "@vibetasks/core";
3646
3881
  var sourceDisplay = {
3647
3882
  sentry: { label: "Sentry", color: chalk21.red },
3648
3883
  ci: { label: "CI", color: chalk21.yellow },
@@ -3669,7 +3904,7 @@ function formatSource(sourceType) {
3669
3904
  }
3670
3905
  var inboxCommand = new Command20("inbox").description("View inbox items from all sources (Sentry, GitHub, Email, Slack, CI)").option("-s, --source <source>", "Filter by source: sentry, ci, github, email, slack, errors, feedback").option("-p, --priority <priority>", "Filter by priority: high, medium, low").option("-a, --all", "Include acknowledged items").option("-l, --limit <number>", "Maximum items to show", "20").action(async (options) => {
3671
3906
  try {
3672
- const authManager = new AuthManager18();
3907
+ const authManager = new AuthManager19();
3673
3908
  const taskOps = await TaskOperations15.fromAuthManager(authManager);
3674
3909
  let sourceTypes;
3675
3910
  if (options.source) {
@@ -3778,7 +4013,7 @@ Error: ${error.message}
3778
4013
  });
3779
4014
  var inboxStatsCommand = new Command20("stats").description("Show inbox statistics").action(async () => {
3780
4015
  try {
3781
- const authManager = new AuthManager18();
4016
+ const authManager = new AuthManager19();
3782
4017
  const taskOps = await TaskOperations15.fromAuthManager(authManager);
3783
4018
  const stats = await taskOps.getInboxStats();
3784
4019
  console.log("\n" + chalk21.bold("Inbox Statistics") + "\n");
@@ -3834,10 +4069,1267 @@ Error: ${error.message}
3834
4069
  });
3835
4070
  inboxCommand.addCommand(inboxStatsCommand);
3836
4071
 
4072
+ // src/commands/handoff.ts
4073
+ import { Command as Command21 } from "commander";
4074
+ import chalk22 from "chalk";
4075
+ import { AuthManager as AuthManager20, TaskOperations as TaskOperations16 } from "@vibetasks/core";
4076
+ var handoffCommand = new Command21("handoff").description("Generate comprehensive handoff for the next AI (vibing tasks, context notes, git status)").argument("[session-id]", "Session ID (shows that session only)").option("--full", "Include full action history from session").action(async (sessionId, options) => {
4077
+ const sessionManager2 = getSessionManager();
4078
+ if (sessionId) {
4079
+ const handoff = await sessionManager2.getSessionHandoff(sessionId);
4080
+ console.log(handoff);
4081
+ return;
4082
+ }
4083
+ console.log(chalk22.cyan("# AI Handoff"));
4084
+ console.log(chalk22.gray(`Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}`));
4085
+ console.log();
4086
+ const currentSession = await sessionManager2.getCurrentSession();
4087
+ if (currentSession) {
4088
+ console.log(chalk22.yellow("## Session"));
4089
+ console.log(`ID: ${chalk22.bold(currentSession.id)}`);
4090
+ console.log(`Project: ${currentSession.project || "unknown"}`);
4091
+ console.log(`Started: ${new Date(currentSession.startedAt).toLocaleString()}`);
4092
+ console.log(`Actions: ${currentSession.actions.length}`);
4093
+ if (currentSession.summary) {
4094
+ console.log(`Summary: ${currentSession.summary}`);
4095
+ }
4096
+ console.log();
4097
+ }
4098
+ try {
4099
+ const authManager = new AuthManager20();
4100
+ const taskOps = await TaskOperations16.fromAuthManager(authManager);
4101
+ const allTasks = await taskOps.getTasks("all");
4102
+ const vibingTasks = allTasks.filter((t) => t.status === "vibing");
4103
+ if (vibingTasks.length > 0) {
4104
+ console.log(chalk22.yellow("## Vibing Tasks (Active Work)"));
4105
+ console.log();
4106
+ for (const task of vibingTasks) {
4107
+ const shortId = task.short_id || task.id.slice(0, 8);
4108
+ console.log(chalk22.green(`### #${shortId}: ${task.title}`));
4109
+ if (task.project_tag) {
4110
+ console.log(chalk22.gray(`Project: ${task.project_tag}`));
4111
+ }
4112
+ const subtasks = task.subtasks_json || task.subtasks || [];
4113
+ if (subtasks.length > 0) {
4114
+ const done = subtasks.filter((s) => s.done).length;
4115
+ console.log(`Progress: ${done}/${subtasks.length} subtasks`);
4116
+ subtasks.forEach((s, i) => {
4117
+ const status = s.done ? chalk22.green("\u2713") : chalk22.gray("\u25CB");
4118
+ const title = s.done ? chalk22.gray(s.title) : s.title;
4119
+ console.log(` ${status} ${i + 1}. ${title}`);
4120
+ });
4121
+ }
4122
+ if (task.context_notes) {
4123
+ console.log();
4124
+ console.log(chalk22.cyan("Where I left off:"));
4125
+ console.log(task.context_notes);
4126
+ } else {
4127
+ console.log();
4128
+ console.log(chalk22.yellow("\u26A0 No context notes - update with:"));
4129
+ console.log(chalk22.gray(` vibetasks update ${shortId} --context "..."`));
4130
+ }
4131
+ console.log();
4132
+ }
4133
+ } else {
4134
+ console.log(chalk22.yellow("## No Vibing Tasks"));
4135
+ console.log(chalk22.gray("All clear - ready for new work."));
4136
+ console.log();
4137
+ }
4138
+ } catch (error) {
4139
+ console.log(chalk22.gray("(Could not fetch tasks - may need to login)"));
4140
+ console.log();
4141
+ }
4142
+ try {
4143
+ const { execSync: execSync2 } = await import("child_process");
4144
+ const gitStatus = execSync2("git status --porcelain", { encoding: "utf-8", timeout: 5e3 }).trim();
4145
+ if (gitStatus) {
4146
+ const files = gitStatus.split("\n");
4147
+ console.log(chalk22.yellow(`## Uncommitted Changes (${files.length} files)`));
4148
+ files.slice(0, 15).forEach((f) => console.log(chalk22.gray(` ${f}`)));
4149
+ if (files.length > 15) {
4150
+ console.log(chalk22.gray(` ... and ${files.length - 15} more`));
4151
+ }
4152
+ console.log();
4153
+ }
4154
+ const commits = execSync2("git log --oneline -3", { encoding: "utf-8", timeout: 5e3 }).trim();
4155
+ if (commits) {
4156
+ console.log(chalk22.yellow("## Recent Commits"));
4157
+ commits.split("\n").forEach((c) => console.log(chalk22.gray(` ${c}`)));
4158
+ console.log();
4159
+ }
4160
+ } catch {
4161
+ }
4162
+ console.log(chalk22.cyan("\u2500".repeat(60)));
4163
+ console.log(chalk22.yellow("## For Next AI"));
4164
+ console.log();
4165
+ console.log("1. Run: `vibetasks session current || vibetasks session start`");
4166
+ console.log("2. Run: `vibetasks list --status vibing --short-ids`");
4167
+ console.log('3. Read context_notes above for "where I left off"');
4168
+ console.log("4. Continue the vibing task or ask user what to do");
4169
+ if (currentSession) {
4170
+ console.log();
4171
+ console.log(chalk22.gray(`To reference this session: "Continue from session ${currentSession.id}"`));
4172
+ }
4173
+ });
4174
+
4175
+ // src/commands/parse-prd.ts
4176
+ import { Command as Command22 } from "commander";
4177
+ import ora15 from "ora";
4178
+ import chalk23 from "chalk";
4179
+ import { readFile as readFile3 } from "fs/promises";
4180
+ import { existsSync } from "fs";
4181
+ import { AuthManager as AuthManager21, TaskOperations as TaskOperations17 } from "@vibetasks/core";
4182
+ import { detectProject as detectProject6 } from "@vibetasks/shared/utils/project-detector";
4183
+ import { randomUUID as randomUUID3 } from "crypto";
4184
+ var parsePrdCommand = new Command22("parse-prd").description("Parse a PRD/requirements document and create tasks").argument("<file>", "Path to PRD/requirements file (markdown)").option("-t, --title <title>", "Override task title (auto-generated if not provided)").option("-p, --priority <level>", "Priority: low, medium, high", "high").option("--project <name>", "Override auto-detected project").option("--dry-run", "Show what would be created without creating").option("-v, --verbose", "Show detailed parsing output").action(async (filePath, options) => {
4185
+ const spinner = ora15("Parsing PRD...").start();
4186
+ try {
4187
+ if (!existsSync(filePath)) {
4188
+ throw new Error(`File not found: ${filePath}`);
4189
+ }
4190
+ const content = await readFile3(filePath, "utf-8");
4191
+ spinner.text = "Analyzing document structure...";
4192
+ const parsed = parsePrdContent(content, options.verbose);
4193
+ if (options.verbose) {
4194
+ spinner.info("Parsed structure:");
4195
+ console.log(chalk23.gray(` Title: ${parsed.title}`));
4196
+ console.log(chalk23.gray(` Summary: ${parsed.summary?.substring(0, 100)}...`));
4197
+ console.log(chalk23.gray(` Sections: ${parsed.sections.length}`));
4198
+ console.log(chalk23.gray(` Tasks: ${parsed.tasks.length}`));
4199
+ spinner.start("Continuing...");
4200
+ }
4201
+ const finalTitle = options.title || parsed.title || "Implement PRD";
4202
+ const subtasksJson = parsed.tasks.map((task2, i) => ({
4203
+ id: randomUUID3(),
4204
+ title: task2,
4205
+ done: false
4206
+ }));
4207
+ const acceptanceCriteria = parsed.acceptanceCriteria || [];
4208
+ let notes = "";
4209
+ if (parsed.summary) {
4210
+ notes += `## Summary
4211
+ ${parsed.summary}
4212
+
4213
+ `;
4214
+ }
4215
+ if (acceptanceCriteria.length > 0) {
4216
+ notes += `DONE WHEN:
4217
+ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}
4218
+
4219
+ `;
4220
+ }
4221
+ if (parsed.nonGoals && parsed.nonGoals.length > 0) {
4222
+ notes += `## Out of Scope
4223
+ ${parsed.nonGoals.map((n) => `- ${n}`).join("\n")}
4224
+ `;
4225
+ }
4226
+ if (options.dryRun) {
4227
+ spinner.succeed(chalk23.yellow("Dry run - no task created"));
4228
+ console.log();
4229
+ console.log(chalk23.white.bold(`Task: ${finalTitle}`));
4230
+ console.log(chalk23.gray(`Priority: ${options.priority}`));
4231
+ console.log();
4232
+ if (subtasksJson.length > 0) {
4233
+ console.log(chalk23.cyan("Subtasks:"));
4234
+ subtasksJson.forEach((st, i) => {
4235
+ console.log(chalk23.gray(` ${i + 1}. ${st.title}`));
4236
+ });
4237
+ console.log();
4238
+ }
4239
+ if (acceptanceCriteria.length > 0) {
4240
+ console.log(chalk23.green("Acceptance Criteria:"));
4241
+ acceptanceCriteria.forEach((c, i) => {
4242
+ console.log(chalk23.gray(` \u2713 ${c}`));
4243
+ });
4244
+ console.log();
4245
+ }
4246
+ if (parsed.nonGoals && parsed.nonGoals.length > 0) {
4247
+ console.log(chalk23.red("Non-Goals (out of scope):"));
4248
+ parsed.nonGoals.forEach((n) => {
4249
+ console.log(chalk23.gray(` \u2717 ${n}`));
4250
+ });
4251
+ }
4252
+ process.exit(0);
4253
+ return;
4254
+ }
4255
+ spinner.text = "Creating task...";
4256
+ const authManager = new AuthManager21();
4257
+ const taskOps = await TaskOperations17.fromAuthManager(authManager);
4258
+ let projectTag;
4259
+ if (options.project) {
4260
+ projectTag = options.project;
4261
+ } else {
4262
+ try {
4263
+ const detected = await detectProject6(process.cwd());
4264
+ projectTag = detected.fullName || detected.name;
4265
+ } catch {
4266
+ }
4267
+ }
4268
+ let sessionId;
4269
+ let sessionHistory = [];
4270
+ const sessionManager2 = getSessionManager();
4271
+ const currentSession = await sessionManager2.getCurrentSession();
4272
+ if (currentSession) {
4273
+ sessionId = currentSession.id;
4274
+ sessionHistory = [{
4275
+ session_id: currentSession.id,
4276
+ action: "created",
4277
+ at: (/* @__PURE__ */ new Date()).toISOString()
4278
+ }];
4279
+ }
4280
+ const task = await taskOps.createTask({
4281
+ title: finalTitle,
4282
+ notes,
4283
+ notes_format: "markdown",
4284
+ priority: options.priority,
4285
+ project_tag: projectTag,
4286
+ created_by: "ai",
4287
+ status: "todo",
4288
+ subtasks_json: subtasksJson,
4289
+ session_id: sessionId,
4290
+ session_history: sessionHistory,
4291
+ is_plan: true
4292
+ // Mark as a plan task
4293
+ });
4294
+ try {
4295
+ const shortIdManager = new ShortIdManager();
4296
+ const allTasks = await taskOps.getTasks("all");
4297
+ await shortIdManager.saveMappings(allTasks);
4298
+ } catch {
4299
+ }
4300
+ if (currentSession) {
4301
+ await sessionManager2.logAction({
4302
+ type: "task_created",
4303
+ description: `Created task from PRD: "${finalTitle}"`,
4304
+ taskId: task.id,
4305
+ taskTitle: finalTitle
4306
+ });
4307
+ }
4308
+ spinner.succeed(chalk23.green("Task created from PRD!"));
4309
+ console.log();
4310
+ console.log(chalk23.white.bold(finalTitle));
4311
+ if (projectTag) {
4312
+ console.log(chalk23.gray(`\u{1F4C1} Project: ${chalk23.white(projectTag)}`));
4313
+ }
4314
+ console.log(chalk23.gray(`ID: ${task.id.substring(0, 8)}...`));
4315
+ console.log();
4316
+ if (subtasksJson.length > 0) {
4317
+ console.log(chalk23.cyan(`\u{1F4CB} ${subtasksJson.length} subtasks created:`));
4318
+ subtasksJson.slice(0, 10).forEach((st, i) => {
4319
+ console.log(chalk23.gray(` ${i + 1}. ${st.title}`));
4320
+ });
4321
+ if (subtasksJson.length > 10) {
4322
+ console.log(chalk23.gray(` ... and ${subtasksJson.length - 10} more`));
4323
+ }
4324
+ console.log();
4325
+ }
4326
+ if (acceptanceCriteria.length > 0) {
4327
+ console.log(chalk23.green(`\u2713 ${acceptanceCriteria.length} acceptance criteria defined`));
4328
+ }
4329
+ console.log();
4330
+ console.log(chalk23.gray("Start working with:"));
4331
+ console.log(chalk23.white(` vibetasks vibing ${task.short_id || task.id.substring(0, 8)}`));
4332
+ console.log();
4333
+ process.exit(0);
4334
+ } catch (error) {
4335
+ spinner.fail(chalk23.red("Failed to parse PRD"));
4336
+ if (error.message.includes("Not authenticated")) {
4337
+ console.error(chalk23.yellow("\n\u26A0\uFE0F You need to login first"));
4338
+ console.error(chalk23.gray("Run: vibetasks login\n"));
4339
+ } else {
4340
+ console.error(chalk23.red(`
4341
+ Error: ${error.message}
4342
+ `));
4343
+ }
4344
+ process.exit(1);
4345
+ }
4346
+ });
4347
+ function parsePrdContent(content, verbose = false) {
4348
+ const lines = content.split("\n");
4349
+ const result = {
4350
+ title: "",
4351
+ sections: [],
4352
+ tasks: [],
4353
+ acceptanceCriteria: [],
4354
+ nonGoals: []
4355
+ };
4356
+ const h1Match = content.match(/^#\s+(.+)$/m);
4357
+ if (h1Match) {
4358
+ result.title = h1Match[1].trim();
4359
+ }
4360
+ const summaryMatch = content.match(/^#\s+.+\n\n([\s\S]*?)(?=\n##|$)/);
4361
+ if (summaryMatch) {
4362
+ result.summary = summaryMatch[1].trim().split("\n\n")[0];
4363
+ }
4364
+ const sectionPattern = /^##\s+(.+)$/gm;
4365
+ let match;
4366
+ while ((match = sectionPattern.exec(content)) !== null) {
4367
+ result.sections.push(match[1].trim());
4368
+ }
4369
+ const taskPatterns = [
4370
+ /^[-*]\s+\[[ x]\]\s+(.+)$/gm,
4371
+ // Checkbox items
4372
+ /^[-*]\s+(.+)$/gm,
4373
+ // Regular bullet points under task-like headers
4374
+ /^\d+\.\s+(.+)$/gm
4375
+ // Numbered items
4376
+ ];
4377
+ const taskSectionPatterns = [
4378
+ /##\s*(?:tasks?|implementation|steps?|work breakdown|subtasks?)\s*\n([\s\S]*?)(?=\n##|$)/gi,
4379
+ /##\s*(?:requirements?|deliverables?|milestones?)\s*\n([\s\S]*?)(?=\n##|$)/gi
4380
+ ];
4381
+ for (const pattern of taskSectionPatterns) {
4382
+ let sectionMatch;
4383
+ while ((sectionMatch = pattern.exec(content)) !== null) {
4384
+ const sectionContent = sectionMatch[1];
4385
+ const bulletPattern = /^[-*]\s+(.+)$/gm;
4386
+ let bulletMatch;
4387
+ while ((bulletMatch = bulletPattern.exec(sectionContent)) !== null) {
4388
+ const task = bulletMatch[1].trim();
4389
+ const cleanTask = task.replace(/^\[[ x]\]\s*/, "").trim();
4390
+ if (cleanTask && !result.tasks.includes(cleanTask)) {
4391
+ result.tasks.push(cleanTask);
4392
+ }
4393
+ }
4394
+ const numberedPattern = /^\d+\.\s+(.+)$/gm;
4395
+ while ((bulletMatch = numberedPattern.exec(sectionContent)) !== null) {
4396
+ const task = bulletMatch[1].trim();
4397
+ if (task && !result.tasks.includes(task)) {
4398
+ result.tasks.push(task);
4399
+ }
4400
+ }
4401
+ }
4402
+ }
4403
+ if (result.tasks.length === 0) {
4404
+ const bulletPattern = /^[-*]\s+(.+)$/gm;
4405
+ while ((match = bulletPattern.exec(content)) !== null) {
4406
+ const task = match[1].trim();
4407
+ if (!task.toLowerCase().includes("done when") && !task.toLowerCase().includes("success") && !task.toLowerCase().includes("criterion")) {
4408
+ result.tasks.push(task);
4409
+ }
4410
+ }
4411
+ }
4412
+ const criteriaPatterns = [
4413
+ /##\s*(?:goals?|success criteria|acceptance criteria|done when|definition of done)\s*\n([\s\S]*?)(?=\n##|$)/gi
4414
+ ];
4415
+ for (const pattern of criteriaPatterns) {
4416
+ let criteriaMatch;
4417
+ while ((criteriaMatch = pattern.exec(content)) !== null) {
4418
+ const sectionContent = criteriaMatch[1];
4419
+ const bulletPattern = /^[-*]\s+(.+)$/gm;
4420
+ let bulletMatch;
4421
+ while ((bulletMatch = bulletPattern.exec(sectionContent)) !== null) {
4422
+ const criterion = bulletMatch[1].trim();
4423
+ if (criterion && !result.acceptanceCriteria.includes(criterion)) {
4424
+ result.acceptanceCriteria.push(criterion);
4425
+ }
4426
+ }
4427
+ }
4428
+ }
4429
+ const nonGoalPatterns = [
4430
+ /##\s*(?:non-goals?|out of scope|not in scope|exclusions?)\s*\n([\s\S]*?)(?=\n##|$)/gi
4431
+ ];
4432
+ for (const pattern of nonGoalPatterns) {
4433
+ let nonGoalMatch;
4434
+ while ((nonGoalMatch = pattern.exec(content)) !== null) {
4435
+ const sectionContent = nonGoalMatch[1];
4436
+ const bulletPattern = /^[-*]\s+(.+)$/gm;
4437
+ let bulletMatch;
4438
+ while ((bulletMatch = bulletPattern.exec(sectionContent)) !== null) {
4439
+ const nonGoal = bulletMatch[1].trim();
4440
+ if (nonGoal && !result.nonGoals.includes(nonGoal)) {
4441
+ result.nonGoals.push(nonGoal);
4442
+ }
4443
+ }
4444
+ }
4445
+ }
4446
+ if (result.tasks.length > 20) {
4447
+ result.tasks = result.tasks.slice(0, 20);
4448
+ }
4449
+ return result;
4450
+ }
4451
+
4452
+ // src/commands/checkpoint.ts
4453
+ import { Command as Command23 } from "commander";
4454
+ import chalk24 from "chalk";
4455
+ import { AuthManager as AuthManager22, TaskOperations as TaskOperations18 } from "@vibetasks/core";
4456
+ import { execSync } from "child_process";
4457
+ import * as readline from "readline";
4458
+ var checkpointCommand = new Command23("checkpoint").alias("cp").description("Quick context save for current work (interactive)").argument("[task-id]", "Task ID (defaults to your vibing task)").option("-q, --quick", "Quick mode: just git diff + timestamp, no prompts").option("-m, --message <msg>", "Context message (skip prompt)").option("--no-git", "Skip git diff attachment").action(async (taskId, options) => {
4459
+ try {
4460
+ const authManager = new AuthManager22();
4461
+ const taskOps = await TaskOperations18.fromAuthManager(authManager);
4462
+ const sessionManager2 = getSessionManager();
4463
+ let task;
4464
+ if (taskId) {
4465
+ const allTasks = await taskOps.getTasks("all");
4466
+ task = allTasks.find(
4467
+ (t) => t.id.startsWith(taskId) || t.short_id?.toString() === taskId
4468
+ );
4469
+ if (!task) {
4470
+ console.error(chalk24.red(`Task not found: ${taskId}`));
4471
+ process.exit(1);
4472
+ }
4473
+ } else {
4474
+ const currentSession2 = await sessionManager2.getCurrentSession();
4475
+ const allTasks = await taskOps.getTasks("all");
4476
+ const vibingTasks = allTasks.filter((t) => t.status === "vibing");
4477
+ if (vibingTasks.length === 0) {
4478
+ console.error(chalk24.yellow("No vibing tasks found"));
4479
+ console.error(chalk24.gray("Start one with: vibetasks vibing <task-id>"));
4480
+ process.exit(1);
4481
+ }
4482
+ if (vibingTasks.length === 1) {
4483
+ task = vibingTasks[0];
4484
+ } else {
4485
+ const sessionTask = currentSession2 ? vibingTasks.find((t) => t.session_id === currentSession2.fullId) : null;
4486
+ if (sessionTask) {
4487
+ task = sessionTask;
4488
+ } else {
4489
+ console.log(chalk24.yellow("Multiple vibing tasks found:"));
4490
+ vibingTasks.forEach((t, i) => {
4491
+ console.log(chalk24.gray(` ${i + 1}. [${t.short_id || t.id.slice(0, 8)}] ${t.title}`));
4492
+ });
4493
+ console.log(chalk24.gray("\nSpecify task: vibetasks checkpoint <task-id>"));
4494
+ process.exit(1);
4495
+ }
4496
+ }
4497
+ }
4498
+ console.log(chalk24.cyan("Checkpointing:"), chalk24.bold(task.title));
4499
+ console.log(chalk24.gray(`ID: ${task.short_id || task.id.slice(0, 8)}`));
4500
+ console.log();
4501
+ let contextParts = [];
4502
+ let gitDiff = "";
4503
+ if (options.git !== false) {
4504
+ try {
4505
+ gitDiff = getGitContext();
4506
+ if (gitDiff) {
4507
+ contextParts.push(`## Git Changes
4508
+ \`\`\`
4509
+ ${gitDiff}
4510
+ \`\`\``);
4511
+ console.log(chalk24.green("\u2713"), chalk24.gray("Captured git changes"));
4512
+ }
4513
+ } catch {
4514
+ }
4515
+ }
4516
+ if (options.quick) {
4517
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
4518
+ contextParts.unshift(`Checkpoint at ${timestamp}`);
4519
+ const contextNotes2 = contextParts.join("\n\n");
4520
+ await taskOps.updateTask(task.id, { context_notes: contextNotes2 });
4521
+ console.log(chalk24.green("\u2713 Quick checkpoint saved"));
4522
+ process.exit(0);
4523
+ }
4524
+ if (options.message) {
4525
+ contextParts.unshift(`## Progress
4526
+ ${options.message}`);
4527
+ const contextNotes2 = contextParts.join("\n\n");
4528
+ await taskOps.updateTask(task.id, { context_notes: contextNotes2 });
4529
+ console.log(chalk24.green("\u2713 Checkpoint saved"));
4530
+ process.exit(0);
4531
+ }
4532
+ const rl = readline.createInterface({
4533
+ input: process.stdin,
4534
+ output: process.stdout
4535
+ });
4536
+ const prompt = (question) => {
4537
+ return new Promise((resolve) => {
4538
+ rl.question(chalk24.cyan(question), (answer) => {
4539
+ resolve(answer.trim());
4540
+ });
4541
+ });
4542
+ };
4543
+ console.log(chalk24.gray("Press Enter to skip any question\n"));
4544
+ const completed = await prompt("What did you just complete? ");
4545
+ if (completed) {
4546
+ contextParts.unshift(`## Completed
4547
+ ${completed}`);
4548
+ }
4549
+ const nextStep = await prompt("What's the next step? ");
4550
+ if (nextStep) {
4551
+ contextParts.push(`## Next
4552
+ ${nextStep}`);
4553
+ }
4554
+ const blockers = await prompt("Any blockers? (or press Enter) ");
4555
+ if (blockers) {
4556
+ contextParts.push(`## Blockers
4557
+ ${blockers}`);
4558
+ }
4559
+ rl.close();
4560
+ if (contextParts.length === 0) {
4561
+ console.log(chalk24.yellow("Nothing to save (all fields empty)"));
4562
+ process.exit(0);
4563
+ }
4564
+ const contextNotes = contextParts.join("\n\n");
4565
+ await taskOps.updateTask(task.id, { context_notes: contextNotes });
4566
+ const currentSession = await sessionManager2.getCurrentSession();
4567
+ if (currentSession) {
4568
+ await sessionManager2.logAction({
4569
+ type: "task_updated",
4570
+ description: `Checkpoint: ${task.title}`,
4571
+ taskId: task.id,
4572
+ taskTitle: task.title
4573
+ });
4574
+ }
4575
+ console.log();
4576
+ console.log(chalk24.green("\u2713 Checkpoint saved"));
4577
+ console.log(chalk24.gray("Context will be available for the next AI session"));
4578
+ process.exit(0);
4579
+ } catch (error) {
4580
+ if (error.message.includes("Not authenticated")) {
4581
+ console.error(chalk24.yellow("\n\u26A0\uFE0F You need to login first"));
4582
+ console.error(chalk24.gray("Run: vibetasks login\n"));
4583
+ } else {
4584
+ console.error(chalk24.red(`
4585
+ Error: ${error.message}
4586
+ `));
4587
+ }
4588
+ process.exit(1);
4589
+ }
4590
+ });
4591
+ function getGitContext() {
4592
+ const parts = [];
4593
+ try {
4594
+ const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim();
4595
+ parts.push(`Branch: ${branch}`);
4596
+ const status = execSync("git status --porcelain", { encoding: "utf-8" }).trim();
4597
+ if (status) {
4598
+ const lines = status.split("\n").slice(0, 20);
4599
+ parts.push(`
4600
+ Changed files:
4601
+ ${lines.join("\n")}`);
4602
+ if (status.split("\n").length > 20) {
4603
+ parts.push(`... and ${status.split("\n").length - 20} more`);
4604
+ }
4605
+ }
4606
+ const log = execSync("git log --oneline -3", { encoding: "utf-8" }).trim();
4607
+ if (log) {
4608
+ parts.push(`
4609
+ Recent commits:
4610
+ ${log}`);
4611
+ }
4612
+ } catch {
4613
+ return "";
4614
+ }
4615
+ return parts.join("\n");
4616
+ }
4617
+
4618
+ // src/commands/log.ts
4619
+ import { Command as Command24 } from "commander";
4620
+ import chalk25 from "chalk";
4621
+ import { AuthManager as AuthManager23, TaskOperations as TaskOperations19 } from "@vibetasks/core";
4622
+ var logCommand = new Command24("log").description("Show change history for a task (like git log)").argument("[task]", "Task ID or short number").option("-n, --limit <number>", "Maximum number of changes to show", "20").option("--oneline", "Compact one-line format").option("--since <date>", "Show changes after date (YYYY-MM-DD)").option("--until <date>", "Show changes before date (YYYY-MM-DD)").option("--type <types>", "Filter by change type (comma-separated)").option("--grep <query>", "Search change messages (ignores task arg)").option("--session <id>", "Show changes from specific session").action(async (taskArg, options) => {
4623
+ try {
4624
+ const authManager = new AuthManager23();
4625
+ const taskOps = await TaskOperations19.fromAuthManager(authManager);
4626
+ if (options.grep) {
4627
+ const changes2 = await taskOps.searchChanges(options.grep, parseInt(options.limit, 10));
4628
+ if (changes2.length === 0) {
4629
+ console.log(chalk25.gray(`
4630
+ No changes found matching: "${options.grep}"
4631
+ `));
4632
+ process.exit(0);
4633
+ }
4634
+ console.log(chalk25.gray(`
4635
+ Found ${changes2.length} change(s) matching "${options.grep}":
4636
+ `));
4637
+ for (const change of changes2) {
4638
+ printChange(change, options.oneline);
4639
+ }
4640
+ process.exit(0);
4641
+ }
4642
+ if (!taskArg) {
4643
+ console.error(chalk25.red("Error: Task ID or number required"));
4644
+ console.error(chalk25.gray('Usage: vibetasks log <task-id> or vibetasks log --grep "query"'));
4645
+ process.exit(1);
4646
+ }
4647
+ let taskId = taskArg;
4648
+ if (/^\d+$/.test(taskArg)) {
4649
+ const shortIdManager = new ShortIdManager();
4650
+ const resolved = await shortIdManager.resolveId(taskArg);
4651
+ if (resolved) {
4652
+ taskId = resolved;
4653
+ } else {
4654
+ console.error(chalk25.red(`Task #${taskArg} not found`));
4655
+ console.error(chalk25.gray("Run: vibetasks list --short-ids to see available tasks"));
4656
+ process.exit(1);
4657
+ }
4658
+ }
4659
+ const task = await taskOps.getTask(taskId);
4660
+ const queryOptions = {
4661
+ limit: parseInt(options.limit, 10)
4662
+ };
4663
+ if (options.since) {
4664
+ queryOptions.since = new Date(options.since).toISOString();
4665
+ }
4666
+ if (options.until) {
4667
+ queryOptions.until = new Date(options.until).toISOString();
4668
+ }
4669
+ if (options.type) {
4670
+ queryOptions.changeTypes = options.type.split(",").map((t) => t.trim());
4671
+ }
4672
+ const changes = await taskOps.getTaskChanges(taskId, queryOptions);
4673
+ if (changes.length === 0) {
4674
+ console.log(chalk25.gray(`
4675
+ No change history found for task: ${task.title}
4676
+ `));
4677
+ process.exit(0);
4678
+ }
4679
+ console.log();
4680
+ console.log(chalk25.cyan.bold(`History for: ${task.title}`));
4681
+ console.log(chalk25.gray(`Task ID: ${taskId}`));
4682
+ console.log(chalk25.gray(`Status: ${task.status} | Changes: ${changes.length}`));
4683
+ console.log();
4684
+ for (const change of changes) {
4685
+ printChange(change, options.oneline, task.title);
4686
+ }
4687
+ console.log();
4688
+ process.exit(0);
4689
+ } catch (error) {
4690
+ if (error.message.includes("Not authenticated")) {
4691
+ console.error(chalk25.yellow("\nYou need to login first"));
4692
+ console.error(chalk25.gray("Run: vibetasks login\n"));
4693
+ } else {
4694
+ console.error(chalk25.red(`
4695
+ Error: ${error.message}
4696
+ `));
4697
+ }
4698
+ process.exit(1);
4699
+ }
4700
+ });
4701
+ function printChange(change, oneline = false, taskTitle) {
4702
+ const changeId = change.id.substring(0, 8);
4703
+ const date = new Date(change.created_at);
4704
+ const dateStr = date.toLocaleDateString();
4705
+ const timeStr = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
4706
+ const typeColors = {
4707
+ created: chalk25.green,
4708
+ status_changed: chalk25.cyan,
4709
+ title_changed: chalk25.yellow,
4710
+ context_updated: chalk25.blue,
4711
+ priority_changed: chalk25.magenta,
4712
+ subtask_done: chalk25.green,
4713
+ subtask_undone: chalk25.red,
4714
+ subtask_added: chalk25.cyan,
4715
+ archived: chalk25.gray,
4716
+ restored: chalk25.green,
4717
+ revert: chalk25.red,
4718
+ handoff: chalk25.magenta
4719
+ };
4720
+ const colorFn = typeColors[change.change_type] || chalk25.white;
4721
+ const actorIcon = change.actor_type === "ai" ? "\u{1F916}" : change.actor_type === "human" ? "\u{1F464}" : "\u2699\uFE0F";
4722
+ if (oneline) {
4723
+ const message = change.message || change.change_type;
4724
+ console.log(
4725
+ `${chalk25.yellow(changeId)} ${chalk25.gray(dateStr)} ${colorFn(change.change_type.padEnd(16))} ${actorIcon} ${message}`
4726
+ );
4727
+ } else {
4728
+ console.log(chalk25.yellow(`change ${change.id}`));
4729
+ console.log(`Actor: ${actorIcon} ${change.actor_type}`);
4730
+ console.log(`Date: ${dateStr} ${timeStr}`);
4731
+ if (change.session_id) {
4732
+ console.log(chalk25.gray(`Session: ${change.session_id.substring(0, 8)}`));
4733
+ }
4734
+ console.log();
4735
+ console.log(` ${colorFn(change.change_type)}: ${change.message || ""}`);
4736
+ if (change.old_value !== void 0 || change.new_value !== void 0) {
4737
+ if (change.field_changed === "status") {
4738
+ console.log(chalk25.gray(` ${chalk25.red(`- ${change.old_value}`)} ${chalk25.green(`+ ${change.new_value}`)}`));
4739
+ } else if (change.field_changed === "context_notes") {
4740
+ const oldVal = truncate(String(change.old_value || ""), 60);
4741
+ const newVal = truncate(String(change.new_value || ""), 60);
4742
+ if (oldVal) console.log(chalk25.red(` - ${oldVal}`));
4743
+ if (newVal) console.log(chalk25.green(` + ${newVal}`));
4744
+ } else if (change.field_changed === "subtasks") {
4745
+ const newSub = change.new_value;
4746
+ if (newSub?.title) {
4747
+ console.log(chalk25.gray(` Subtask: ${newSub.title}`));
4748
+ }
4749
+ } else if (change.old_value !== void 0 && change.new_value !== void 0) {
4750
+ console.log(chalk25.red(` - ${change.old_value}`));
4751
+ console.log(chalk25.green(` + ${change.new_value}`));
4752
+ }
4753
+ }
4754
+ console.log();
4755
+ }
4756
+ }
4757
+ function truncate(str, maxLength) {
4758
+ if (!str) return "";
4759
+ if (str.length <= maxLength) return str;
4760
+ return str.substring(0, maxLength - 3) + "...";
4761
+ }
4762
+
4763
+ // src/commands/show.ts
4764
+ import { Command as Command25 } from "commander";
4765
+ import chalk26 from "chalk";
4766
+ import { AuthManager as AuthManager24, TaskOperations as TaskOperations20 } from "@vibetasks/core";
4767
+ var showCommand = new Command25("show").description("Show details of a specific change (like git show)").argument("<change-id>", "Change ID (full or partial)").action(async (changeId, options) => {
4768
+ try {
4769
+ const authManager = new AuthManager24();
4770
+ const taskOps = await TaskOperations20.fromAuthManager(authManager);
4771
+ const change = await taskOps.getChange(changeId);
4772
+ const task = await taskOps.getTask(change.task_id);
4773
+ const date = new Date(change.created_at);
4774
+ const dateStr = date.toLocaleDateString();
4775
+ const timeStr = date.toLocaleTimeString();
4776
+ const actorIcon = change.actor_type === "ai" ? "\u{1F916}" : change.actor_type === "human" ? "\u{1F464}" : "\u2699\uFE0F";
4777
+ console.log();
4778
+ console.log(chalk26.yellow(`change ${change.id}`));
4779
+ console.log(`Task: ${chalk26.cyan(task.title)}`);
4780
+ console.log(` ${chalk26.gray(change.task_id)}`);
4781
+ console.log(`Actor: ${actorIcon} ${change.actor_type}`);
4782
+ console.log(`Date: ${dateStr} ${timeStr}`);
4783
+ if (change.session_id) {
4784
+ console.log(`Session: ${chalk26.gray(change.session_id)}`);
4785
+ }
4786
+ console.log();
4787
+ console.log(chalk26.bold(`${change.change_type}`));
4788
+ if (change.message) {
4789
+ console.log(` ${change.message}`);
4790
+ }
4791
+ console.log();
4792
+ if (change.field_changed) {
4793
+ console.log(chalk26.gray(`Field: ${change.field_changed}`));
4794
+ console.log();
4795
+ if (change.old_value !== void 0) {
4796
+ const oldVal = formatValue(change.old_value);
4797
+ console.log(chalk26.red(`- ${oldVal}`));
4798
+ }
4799
+ if (change.new_value !== void 0) {
4800
+ const newVal = formatValue(change.new_value);
4801
+ console.log(chalk26.green(`+ ${newVal}`));
4802
+ }
4803
+ }
4804
+ if (change.reverted_change_id) {
4805
+ console.log();
4806
+ console.log(chalk26.gray(`Reverted change: ${change.reverted_change_id}`));
4807
+ }
4808
+ console.log();
4809
+ process.exit(0);
4810
+ } catch (error) {
4811
+ if (error.message.includes("Not authenticated")) {
4812
+ console.error(chalk26.yellow("\nYou need to login first"));
4813
+ console.error(chalk26.gray("Run: vibetasks login\n"));
4814
+ } else if (error.code === "PGRST116") {
4815
+ console.error(chalk26.red(`
4816
+ Change not found: ${changeId}
4817
+ `));
4818
+ } else {
4819
+ console.error(chalk26.red(`
4820
+ Error: ${error.message}
4821
+ `));
4822
+ }
4823
+ process.exit(1);
4824
+ }
4825
+ });
4826
+ function formatValue(value) {
4827
+ if (value === null || value === void 0) {
4828
+ return "(empty)";
4829
+ }
4830
+ if (typeof value === "object") {
4831
+ return JSON.stringify(value, null, 2);
4832
+ }
4833
+ return String(value);
4834
+ }
4835
+
4836
+ // src/commands/pause.ts
4837
+ import { Command as Command26 } from "commander";
4838
+ import chalk27 from "chalk";
4839
+ import ora16 from "ora";
4840
+ import { AuthManager as AuthManager25, TaskOperations as TaskOperations21 } from "@vibetasks/core";
4841
+ var pauseCommand = new Command26("pause").description("Pause a task (like git stash) - can be resumed later").argument("[task]", "Task ID or short number to pause").option("-m, --message <reason>", "Reason for pausing").option("-l, --list", "List all paused tasks").action(async (taskArg, options) => {
4842
+ const spinner = ora16();
4843
+ try {
4844
+ const authManager = new AuthManager25();
4845
+ const taskOps = await TaskOperations21.fromAuthManager(authManager);
4846
+ if (options.list) {
4847
+ spinner.start("Fetching paused tasks...");
4848
+ const allTasks = await taskOps.getTasks("all");
4849
+ const pausedTasks = allTasks.filter((t) => t.status === "paused");
4850
+ spinner.stop();
4851
+ if (pausedTasks.length === 0) {
4852
+ console.log(chalk27.yellow("\nNo paused tasks found.\n"));
4853
+ console.log(chalk27.gray('Pause a task with: vibetasks pause <task-id> -m "reason"\n'));
4854
+ process.exit(0);
4855
+ }
4856
+ console.log(chalk27.white("\nPaused Tasks:\n"));
4857
+ pausedTasks.forEach((task2, index) => {
4858
+ console.log(
4859
+ chalk27.gray(`${index + 1}. `) + chalk27.yellow("\u23F8 ") + chalk27.white(task2.title) + chalk27.gray(` [${task2.id.slice(0, 8)}]`)
4860
+ );
4861
+ if (task2.context_notes) {
4862
+ const lastLine = task2.context_notes.split("\n").pop();
4863
+ if (lastLine) {
4864
+ console.log(chalk27.gray(` \u2514\u2500 ${lastLine}`));
4865
+ }
4866
+ }
4867
+ });
4868
+ console.log(chalk27.gray(`
4869
+ Total: ${pausedTasks.length} paused task(s)
4870
+ `));
4871
+ console.log(chalk27.gray("Resume with: vibetasks resume <task-id>\n"));
4872
+ process.exit(0);
4873
+ }
4874
+ if (!taskArg) {
4875
+ console.log(chalk27.yellow("\nUsage:"));
4876
+ console.log(chalk27.gray(" vibetasks pause <task-id> # Pause a specific task"));
4877
+ console.log(chalk27.gray(" vibetasks pause --list # List paused tasks\n"));
4878
+ process.exit(1);
4879
+ }
4880
+ let taskId = taskArg;
4881
+ if (/^\d+$/.test(taskArg)) {
4882
+ const shortIdManager = new ShortIdManager();
4883
+ const resolved = await shortIdManager.resolveId(taskArg);
4884
+ if (resolved) {
4885
+ taskId = resolved;
4886
+ } else {
4887
+ console.error(chalk27.red(`Task #${taskArg} not found`));
4888
+ console.error(chalk27.gray("Run: vibetasks list --short-ids to see available tasks"));
4889
+ process.exit(1);
4890
+ }
4891
+ } else if (taskArg.length < 32) {
4892
+ const allTasks = await taskOps.getTasks("all");
4893
+ const matchingTask = allTasks.find((t) => t.id.startsWith(taskArg));
4894
+ if (!matchingTask) {
4895
+ console.error(chalk27.red(`Task not found: ${taskArg}`));
4896
+ process.exit(1);
4897
+ }
4898
+ taskId = matchingTask.id;
4899
+ }
4900
+ const task = await taskOps.getTask(taskId);
4901
+ if (task.status === "paused") {
4902
+ console.log(chalk27.yellow(`
4903
+ Task is already paused: ${task.title}
4904
+ `));
4905
+ console.log(chalk27.gray("Resume with: vibetasks resume " + taskId.slice(0, 8) + "\n"));
4906
+ process.exit(0);
4907
+ }
4908
+ if (task.status === "done" || task.status === "archived") {
4909
+ console.error(chalk27.red(`
4910
+ Cannot pause a ${task.status} task.
4911
+ `));
4912
+ process.exit(1);
4913
+ }
4914
+ spinner.start("Pausing task...");
4915
+ const pausedTask = await taskOps.pauseTask(taskId, options.message);
4916
+ spinner.succeed(chalk27.green("Task paused!"));
4917
+ console.log();
4918
+ console.log(chalk27.yellow("\u23F8 ") + chalk27.white(pausedTask.title));
4919
+ if (options.message) {
4920
+ console.log(chalk27.gray(` Reason: ${options.message}`));
4921
+ }
4922
+ console.log();
4923
+ console.log(chalk27.gray(`Resume with: vibetasks resume ${taskId.slice(0, 8)}`));
4924
+ console.log(chalk27.gray(`View paused: vibetasks pause --list`));
4925
+ console.log();
4926
+ process.exit(0);
4927
+ } catch (error) {
4928
+ spinner.fail(chalk27.red("Failed to pause task"));
4929
+ if (error.message.includes("Not authenticated")) {
4930
+ console.error(chalk27.yellow("\nYou need to login first"));
4931
+ console.error(chalk27.gray("Run: vibetasks login\n"));
4932
+ } else {
4933
+ console.error(chalk27.red(`
4934
+ Error: ${error.message}
4935
+ `));
4936
+ }
4937
+ process.exit(1);
4938
+ }
4939
+ });
4940
+
4941
+ // src/commands/resume.ts
4942
+ import { Command as Command27 } from "commander";
4943
+ import chalk28 from "chalk";
4944
+ import ora17 from "ora";
4945
+ import inquirer7 from "inquirer";
4946
+ import { AuthManager as AuthManager26, TaskOperations as TaskOperations22 } from "@vibetasks/core";
4947
+ var resumeCommand = new Command27("resume").description("Resume a paused task (like git stash pop)").argument("[task]", "Task ID or short number to resume (or pick from list)").action(async (taskArg) => {
4948
+ const spinner = ora17();
4949
+ try {
4950
+ const authManager = new AuthManager26();
4951
+ const taskOps = await TaskOperations22.fromAuthManager(authManager);
4952
+ const allTasks = await taskOps.getTasks("all");
4953
+ const pausedTasks = allTasks.filter((t) => t.status === "paused");
4954
+ if (pausedTasks.length === 0) {
4955
+ console.log(chalk28.yellow("\nNo paused tasks to resume.\n"));
4956
+ console.log(chalk28.gray("Pause a task with: vibetasks pause <task-id>\n"));
4957
+ process.exit(0);
4958
+ }
4959
+ let taskId;
4960
+ if (!taskArg) {
4961
+ if (pausedTasks.length === 1) {
4962
+ taskId = pausedTasks[0].id;
4963
+ console.log(chalk28.gray(`
4964
+ Auto-selecting the only paused task...
4965
+ `));
4966
+ } else {
4967
+ const { selectedTask } = await inquirer7.prompt([{
4968
+ type: "list",
4969
+ name: "selectedTask",
4970
+ message: "Pick a paused task to resume:",
4971
+ choices: pausedTasks.map((t) => ({
4972
+ name: `${chalk28.yellow("\u23F8")} ${t.title}${t.context_notes ? chalk28.gray(` - ${t.context_notes.split("\n").pop()?.slice(0, 40)}...`) : ""}`,
4973
+ value: t.id
4974
+ }))
4975
+ }]);
4976
+ taskId = selectedTask;
4977
+ }
4978
+ } else {
4979
+ taskId = taskArg;
4980
+ if (/^\d+$/.test(taskArg)) {
4981
+ const shortIdManager = new ShortIdManager();
4982
+ const resolved = await shortIdManager.resolveId(taskArg);
4983
+ if (resolved) {
4984
+ taskId = resolved;
4985
+ } else {
4986
+ console.error(chalk28.red(`Task #${taskArg} not found`));
4987
+ console.error(chalk28.gray("Run: vibetasks list --short-ids to see available tasks"));
4988
+ process.exit(1);
4989
+ }
4990
+ } else if (taskArg.length < 32) {
4991
+ const matchingTask = pausedTasks.find((t) => t.id.startsWith(taskArg)) || allTasks.find((t) => t.id.startsWith(taskArg));
4992
+ if (!matchingTask) {
4993
+ console.error(chalk28.red(`Task not found: ${taskArg}`));
4994
+ process.exit(1);
4995
+ }
4996
+ taskId = matchingTask.id;
4997
+ }
4998
+ }
4999
+ const task = await taskOps.getTask(taskId);
5000
+ if (task.status !== "paused") {
5001
+ console.log(chalk28.yellow(`
5002
+ Task is not paused (status: ${task.status}): ${task.title}
5003
+ `));
5004
+ if (task.status === "todo") {
5005
+ console.log(chalk28.gray("Start vibing with: vibetasks vibing " + taskId.slice(0, 8) + "\n"));
5006
+ }
5007
+ process.exit(0);
5008
+ }
5009
+ spinner.start("Resuming task...");
5010
+ const resumedTask = await taskOps.resumeTask(taskId);
5011
+ spinner.succeed(chalk28.green("Task resumed!"));
5012
+ console.log();
5013
+ console.log(chalk28.magenta("\u26A1 ") + chalk28.white(resumedTask.title));
5014
+ console.log(chalk28.gray(` Status: vibing`));
5015
+ if (task.context_notes) {
5016
+ console.log(chalk28.gray(` Context: ${task.context_notes.split("\n").pop()?.slice(0, 60)}...`));
5017
+ }
5018
+ console.log();
5019
+ console.log(chalk28.gray(`Complete with: vibetasks done ${taskId.slice(0, 8)}`));
5020
+ console.log(chalk28.gray(`Pause again: vibetasks pause ${taskId.slice(0, 8)}`));
5021
+ console.log();
5022
+ process.exit(0);
5023
+ } catch (error) {
5024
+ spinner.fail(chalk28.red("Failed to resume task"));
5025
+ if (error.message.includes("Not authenticated")) {
5026
+ console.error(chalk28.yellow("\nYou need to login first"));
5027
+ console.error(chalk28.gray("Run: vibetasks login\n"));
5028
+ } else {
5029
+ console.error(chalk28.red(`
5030
+ Error: ${error.message}
5031
+ `));
5032
+ }
5033
+ process.exit(1);
5034
+ }
5035
+ });
5036
+
5037
+ // src/commands/revert.ts
5038
+ import { Command as Command28 } from "commander";
5039
+ import chalk29 from "chalk";
5040
+ import ora18 from "ora";
5041
+ import inquirer8 from "inquirer";
5042
+ import { AuthManager as AuthManager27, TaskOperations as TaskOperations23 } from "@vibetasks/core";
5043
+ var revertCommand = new Command28("revert").description("Revert a specific change (like git revert) - non-destructive").argument("[change-id]", "Change ID to revert (or pick from recent changes)").option("-t, --task <task>", "Task ID or number to show changes for").option("-m, --message <message>", "Custom revert message").action(async (changeId, options) => {
5044
+ const spinner = ora18();
5045
+ try {
5046
+ const authManager = new AuthManager27();
5047
+ const taskOps = await TaskOperations23.fromAuthManager(authManager);
5048
+ if (options.task || !changeId) {
5049
+ let taskId;
5050
+ if (options.task) {
5051
+ if (/^\d+$/.test(options.task)) {
5052
+ const shortIdManager = new ShortIdManager();
5053
+ const resolved = await shortIdManager.resolveId(options.task);
5054
+ if (resolved) {
5055
+ taskId = resolved;
5056
+ } else {
5057
+ console.error(chalk29.red(`Task #${options.task} not found`));
5058
+ process.exit(1);
5059
+ }
5060
+ } else if (options.task.length < 32) {
5061
+ const allTasks = await taskOps.getTasks("all");
5062
+ const matchingTask = allTasks.find((t) => t.id.startsWith(options.task));
5063
+ if (matchingTask) {
5064
+ taskId = matchingTask.id;
5065
+ }
5066
+ } else {
5067
+ taskId = options.task;
5068
+ }
5069
+ }
5070
+ if (!taskId) {
5071
+ console.log(chalk29.yellow("\nUsage:"));
5072
+ console.log(chalk29.gray(" vibetasks revert <change-id> # Revert specific change"));
5073
+ console.log(chalk29.gray(" vibetasks revert --task <task-id> # Pick from task changes"));
5074
+ console.log(chalk29.gray(" vibetasks log <task-id> # View changes first\n"));
5075
+ process.exit(1);
5076
+ }
5077
+ const changes = await taskOps.getTaskChanges(taskId, { limit: 20 });
5078
+ const task2 = await taskOps.getTask(taskId);
5079
+ if (changes.length === 0) {
5080
+ console.log(chalk29.yellow(`
5081
+ No changes found for task: ${task2.title}
5082
+ `));
5083
+ process.exit(0);
5084
+ }
5085
+ const revertableChanges = changes.filter((c) => c.change_type !== "created" && c.change_type !== "revert");
5086
+ if (revertableChanges.length === 0) {
5087
+ console.log(chalk29.yellow(`
5088
+ No revertable changes for task: ${task2.title}`));
5089
+ console.log(chalk29.gray("Only the initial creation exists.\n"));
5090
+ process.exit(0);
5091
+ }
5092
+ console.log(chalk29.cyan(`
5093
+ Recent changes for: ${task2.title}
5094
+ `));
5095
+ const { selectedChange } = await inquirer8.prompt([{
5096
+ type: "list",
5097
+ name: "selectedChange",
5098
+ message: "Select a change to revert:",
5099
+ choices: revertableChanges.map((c) => {
5100
+ const date = new Date(c.created_at).toLocaleDateString();
5101
+ const typeColor = getTypeColor(c.change_type);
5102
+ return {
5103
+ name: `${chalk29.gray(c.id.slice(0, 8))} ${chalk29.gray(date)} ${typeColor(c.change_type)} - ${c.message || "No message"}`,
5104
+ value: c.id
5105
+ };
5106
+ })
5107
+ }]);
5108
+ changeId = selectedChange;
5109
+ }
5110
+ spinner.start("Loading change...");
5111
+ const change = await taskOps.getChange(changeId);
5112
+ const task = await taskOps.getTask(change.task_id);
5113
+ spinner.stop();
5114
+ console.log();
5115
+ console.log(chalk29.cyan("Change to revert:"));
5116
+ console.log(chalk29.gray(` ID: ${change.id}`));
5117
+ console.log(chalk29.gray(` Task: ${task.title}`));
5118
+ console.log(chalk29.gray(` Type: ${change.change_type}`));
5119
+ console.log(chalk29.gray(` Message: ${change.message || "No message"}`));
5120
+ if (change.old_value !== void 0 || change.new_value !== void 0) {
5121
+ console.log(chalk29.gray(` Field: ${change.field_changed}`));
5122
+ if (change.old_value !== void 0) {
5123
+ console.log(chalk29.red(` Old: ${formatValue2(change.old_value)}`));
5124
+ }
5125
+ if (change.new_value !== void 0) {
5126
+ console.log(chalk29.green(` New: ${formatValue2(change.new_value)}`));
5127
+ }
5128
+ }
5129
+ console.log();
5130
+ const { confirm } = await inquirer8.prompt([{
5131
+ type: "confirm",
5132
+ name: "confirm",
5133
+ message: "Revert this change?",
5134
+ default: true
5135
+ }]);
5136
+ if (!confirm) {
5137
+ console.log(chalk29.gray("\nRevert cancelled.\n"));
5138
+ process.exit(0);
5139
+ }
5140
+ spinner.start("Reverting change...");
5141
+ const revertChange = await taskOps.revertChange(changeId, options.message);
5142
+ spinner.succeed(chalk29.green("Change reverted!"));
5143
+ console.log();
5144
+ console.log(chalk29.yellow("\u21A9 ") + chalk29.white(`Reverted: ${change.message || change.change_type}`));
5145
+ console.log(chalk29.gray(` Revert ID: ${revertChange.id.slice(0, 8)}`));
5146
+ console.log();
5147
+ console.log(chalk29.gray(`View history: vibetasks log ${task.id.slice(0, 8)}`));
5148
+ console.log();
5149
+ process.exit(0);
5150
+ } catch (error) {
5151
+ spinner.fail(chalk29.red("Failed to revert change"));
5152
+ if (error.message.includes("Not authenticated")) {
5153
+ console.error(chalk29.yellow("\nYou need to login first"));
5154
+ console.error(chalk29.gray("Run: vibetasks login\n"));
5155
+ } else if (error.code === "PGRST116") {
5156
+ console.error(chalk29.red(`
5157
+ Change not found: ${changeId}
5158
+ `));
5159
+ console.error(chalk29.gray("Use: vibetasks log <task-id> to see valid change IDs\n"));
5160
+ } else {
5161
+ console.error(chalk29.red(`
5162
+ Error: ${error.message}
5163
+ `));
5164
+ }
5165
+ process.exit(1);
5166
+ }
5167
+ });
5168
+ function getTypeColor(changeType) {
5169
+ const colors = {
5170
+ created: chalk29.green,
5171
+ status_changed: chalk29.cyan,
5172
+ title_changed: chalk29.yellow,
5173
+ context_updated: chalk29.blue,
5174
+ priority_changed: chalk29.magenta,
5175
+ subtask_done: chalk29.green,
5176
+ subtask_undone: chalk29.red,
5177
+ subtask_added: chalk29.cyan,
5178
+ archived: chalk29.gray,
5179
+ restored: chalk29.green,
5180
+ revert: chalk29.red
5181
+ };
5182
+ return colors[changeType] || chalk29.white;
5183
+ }
5184
+ function formatValue2(value) {
5185
+ if (value === null || value === void 0) {
5186
+ return "(empty)";
5187
+ }
5188
+ if (typeof value === "object") {
5189
+ return JSON.stringify(value);
5190
+ }
5191
+ const str = String(value);
5192
+ return str.length > 50 ? str.slice(0, 47) + "..." : str;
5193
+ }
5194
+
5195
+ // src/commands/blame.ts
5196
+ import { Command as Command29 } from "commander";
5197
+ import chalk30 from "chalk";
5198
+ import { AuthManager as AuthManager28, TaskOperations as TaskOperations24 } from "@vibetasks/core";
5199
+ var blameCommand = new Command29("blame").description("Show who changed what on a task (like git blame)").argument("<task>", "Task ID or short number").option("-f, --field <field>", "Focus on specific field (status, title, context_notes, priority)").action(async (taskArg, options) => {
5200
+ try {
5201
+ const authManager = new AuthManager28();
5202
+ const taskOps = await TaskOperations24.fromAuthManager(authManager);
5203
+ let taskId = taskArg;
5204
+ if (/^\d+$/.test(taskArg)) {
5205
+ const shortIdManager = new ShortIdManager();
5206
+ const resolved = await shortIdManager.resolveId(taskArg);
5207
+ if (resolved) {
5208
+ taskId = resolved;
5209
+ } else {
5210
+ console.error(chalk30.red(`Task #${taskArg} not found`));
5211
+ console.error(chalk30.gray("Run: vibetasks list --short-ids to see available tasks"));
5212
+ process.exit(1);
5213
+ }
5214
+ } else if (taskArg.length < 32) {
5215
+ const allTasks = await taskOps.getTasks("all", true);
5216
+ const matchingTask = allTasks.find((t) => t.id.startsWith(taskArg));
5217
+ if (!matchingTask) {
5218
+ console.error(chalk30.red(`Task not found: ${taskArg}`));
5219
+ process.exit(1);
5220
+ }
5221
+ taskId = matchingTask.id;
5222
+ }
5223
+ const task = await taskOps.getTask(taskId);
5224
+ const changes = await taskOps.getTaskChanges(taskId, { limit: 100 });
5225
+ if (changes.length === 0) {
5226
+ console.log(chalk30.yellow(`
5227
+ No change history for task: ${task.title}
5228
+ `));
5229
+ process.exit(0);
5230
+ }
5231
+ const blameMap = {};
5232
+ const currentValues = {
5233
+ status: task.status,
5234
+ title: task.title,
5235
+ priority: task.priority,
5236
+ context_notes: task.context_notes,
5237
+ description: task.description
5238
+ };
5239
+ for (const change of changes) {
5240
+ if (!change.field_changed) continue;
5241
+ if (options.field && change.field_changed !== options.field) continue;
5242
+ if (!blameMap[change.field_changed]) {
5243
+ blameMap[change.field_changed] = {
5244
+ currentValue: currentValues[change.field_changed],
5245
+ changes: []
5246
+ };
5247
+ }
5248
+ blameMap[change.field_changed].changes.push({
5249
+ changeId: change.id,
5250
+ actor: change.session_id?.slice(0, 8) || "unknown",
5251
+ actorType: change.actor_type,
5252
+ date: new Date(change.created_at),
5253
+ oldValue: change.old_value,
5254
+ newValue: change.new_value,
5255
+ message: change.message || change.change_type
5256
+ });
5257
+ }
5258
+ console.log();
5259
+ console.log(chalk30.cyan.bold(`Blame for: ${task.title}`));
5260
+ console.log(chalk30.gray(`Task ID: ${taskId}`));
5261
+ console.log();
5262
+ const fields = options.field ? [options.field] : Object.keys(blameMap).sort();
5263
+ if (fields.length === 0) {
5264
+ console.log(chalk30.yellow("No field changes tracked."));
5265
+ console.log(chalk30.gray("Only status, title, priority, context_notes changes are tracked.\n"));
5266
+ process.exit(0);
5267
+ }
5268
+ for (const field of fields) {
5269
+ const blame = blameMap[field];
5270
+ if (!blame) continue;
5271
+ console.log(chalk30.white.bold(`${field}:`));
5272
+ console.log(chalk30.gray(` Current: ${formatValue3(blame.currentValue)}`));
5273
+ console.log();
5274
+ for (const change of blame.changes.slice(0, 5)) {
5275
+ const actorIcon = change.actorType === "ai" ? "\u{1F916}" : change.actorType === "human" ? "\u{1F464}" : "\u2699\uFE0F";
5276
+ const dateStr = change.date.toLocaleDateString();
5277
+ const timeStr = change.date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
5278
+ console.log(
5279
+ chalk30.gray(` ${chalk30.yellow(change.changeId.slice(0, 8))} `) + chalk30.gray(`${dateStr} ${timeStr} `) + `${actorIcon} ` + chalk30.white(change.message)
5280
+ );
5281
+ if (change.oldValue !== void 0 || change.newValue !== void 0) {
5282
+ if (field === "status") {
5283
+ console.log(chalk30.gray(` ${chalk30.red(String(change.oldValue))} \u2192 ${chalk30.green(String(change.newValue))}`));
5284
+ } else {
5285
+ const oldStr = formatValue3(change.oldValue);
5286
+ const newStr = formatValue3(change.newValue);
5287
+ if (oldStr) console.log(chalk30.red(` - ${oldStr}`));
5288
+ if (newStr) console.log(chalk30.green(` + ${newStr}`));
5289
+ }
5290
+ }
5291
+ }
5292
+ if (blame.changes.length > 5) {
5293
+ console.log(chalk30.gray(` ... and ${blame.changes.length - 5} more changes`));
5294
+ }
5295
+ console.log();
5296
+ }
5297
+ const totalChanges = Object.values(blameMap).reduce((sum, b) => sum + b.changes.length, 0);
5298
+ const aiChanges = changes.filter((c) => c.actor_type === "ai").length;
5299
+ const humanChanges = changes.filter((c) => c.actor_type === "human").length;
5300
+ console.log(chalk30.gray("\u2500".repeat(50)));
5301
+ console.log(chalk30.gray(`Total changes: ${totalChanges} | \u{1F916} AI: ${aiChanges} | \u{1F464} Human: ${humanChanges}`));
5302
+ console.log(chalk30.gray(`
5303
+ Full history: vibetasks log ${taskId.slice(0, 8)}`));
5304
+ console.log();
5305
+ process.exit(0);
5306
+ } catch (error) {
5307
+ if (error.message.includes("Not authenticated")) {
5308
+ console.error(chalk30.yellow("\nYou need to login first"));
5309
+ console.error(chalk30.gray("Run: vibetasks login\n"));
5310
+ } else {
5311
+ console.error(chalk30.red(`
5312
+ Error: ${error.message}
5313
+ `));
5314
+ }
5315
+ process.exit(1);
5316
+ }
5317
+ });
5318
+ function formatValue3(value) {
5319
+ if (value === null || value === void 0) {
5320
+ return "(empty)";
5321
+ }
5322
+ if (typeof value === "object") {
5323
+ return JSON.stringify(value);
5324
+ }
5325
+ const str = String(value);
5326
+ return str.length > 60 ? str.slice(0, 57) + "..." : str;
5327
+ }
5328
+
3837
5329
  // bin/vibetasks.ts
3838
5330
  var require2 = createRequire(import.meta.url);
3839
5331
  var pkg = require2("../../package.json");
3840
- var program = new Command21();
5332
+ var program = new Command30();
3841
5333
  program.name("vibetasks").description("VibeTasks CLI - Fast task management for vibers").version(pkg.version);
3842
5334
  program.addCommand(setupCommand);
3843
5335
  program.addCommand(loginCommand);
@@ -3860,4 +5352,13 @@ program.addCommand(sessionCommand);
3860
5352
  program.addCommand(subtaskCommand);
3861
5353
  program.addCommand(errorCommand);
3862
5354
  program.addCommand(inboxCommand);
5355
+ program.addCommand(handoffCommand);
5356
+ program.addCommand(parsePrdCommand);
5357
+ program.addCommand(checkpointCommand);
5358
+ program.addCommand(logCommand);
5359
+ program.addCommand(showCommand);
5360
+ program.addCommand(pauseCommand);
5361
+ program.addCommand(resumeCommand);
5362
+ program.addCommand(revertCommand);
5363
+ program.addCommand(blameCommand);
3863
5364
  program.parse();