@vibetasks/cli 0.6.9 → 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.
- package/dist/bin/vibetasks.js +1653 -153
- package/package.json +2 -2
package/dist/bin/vibetasks.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "../chunk-2KRLRG4G.js";
|
|
12
12
|
|
|
13
13
|
// bin/vibetasks.ts
|
|
14
|
-
import { Command as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
861
|
+
const authManager = new AuthManager3();
|
|
639
862
|
const taskOps = await TaskOperations.fromAuthManager(authManager);
|
|
640
863
|
let projectTag;
|
|
641
864
|
if (options.project) {
|
|
@@ -675,14 +898,21 @@ 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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
sessionId =
|
|
910
|
+
const sessionManager2 = getSessionManager();
|
|
911
|
+
const currentSession = await sessionManager2.getCurrentSession();
|
|
912
|
+
if (currentSession) {
|
|
913
|
+
sessionId = currentSession.id;
|
|
684
914
|
sessionHistory = [{
|
|
685
|
-
session_id:
|
|
915
|
+
session_id: currentSession.id,
|
|
686
916
|
action: "created",
|
|
687
917
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
688
918
|
}];
|
|
@@ -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,8 +949,13 @@ 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!"));
|
|
718
|
-
|
|
719
|
-
const
|
|
952
|
+
try {
|
|
953
|
+
const shortIdManager = new ShortIdManager();
|
|
954
|
+
const allTasks = await taskOps.getTasks("all");
|
|
955
|
+
await shortIdManager.saveMappings(allTasks);
|
|
956
|
+
} catch (e) {
|
|
957
|
+
}
|
|
958
|
+
if (currentSession) {
|
|
720
959
|
await sessionManager2.logAction({
|
|
721
960
|
type: "task_created",
|
|
722
961
|
description: `Created task: "${title}"`,
|
|
@@ -761,6 +1000,18 @@ var addCommand = new Command2("add").description("Add a new task").argument("<ti
|
|
|
761
1000
|
console.log(chalk3.gray(` ${i + 1}. ${st.title}`));
|
|
762
1001
|
});
|
|
763
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
|
+
}
|
|
764
1015
|
console.log();
|
|
765
1016
|
process.exit(0);
|
|
766
1017
|
} catch (error) {
|
|
@@ -784,82 +1035,7 @@ function isRunningInClaudeCode() {
|
|
|
784
1035
|
import { Command as Command3 } from "commander";
|
|
785
1036
|
import chalk4 from "chalk";
|
|
786
1037
|
import Table from "cli-table3";
|
|
787
|
-
import { AuthManager as
|
|
788
|
-
|
|
789
|
-
// src/utils/short-ids.ts
|
|
790
|
-
import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
|
|
791
|
-
import { join as join2 } from "path";
|
|
792
|
-
import { homedir as homedir2 } from "os";
|
|
793
|
-
var ShortIdManager = class {
|
|
794
|
-
storePath;
|
|
795
|
-
maxIds = 99;
|
|
796
|
-
constructor() {
|
|
797
|
-
this.storePath = join2(homedir2(), ".vibetasks", "short-ids.json");
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Save mappings from a list of tasks with their short_ids
|
|
801
|
-
* Uses the stable short_id from the database
|
|
802
|
-
*/
|
|
803
|
-
async saveMappings(tasks) {
|
|
804
|
-
const mappings = tasks.filter((t) => t.short_id).slice(0, this.maxIds).map((task, index) => ({
|
|
805
|
-
shortId: task.short_id,
|
|
806
|
-
// Use actual short_id from database
|
|
807
|
-
fullId: task.id,
|
|
808
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
809
|
-
}));
|
|
810
|
-
const store = {
|
|
811
|
-
mappings,
|
|
812
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
813
|
-
};
|
|
814
|
-
const dir = join2(homedir2(), ".vibetasks");
|
|
815
|
-
await mkdir2(dir, { recursive: true });
|
|
816
|
-
await writeFile2(this.storePath, JSON.stringify(store, null, 2));
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Get the full UUID for a short ID
|
|
820
|
-
*/
|
|
821
|
-
async getFullId(shortId) {
|
|
822
|
-
try {
|
|
823
|
-
const content = await readFile2(this.storePath, "utf-8");
|
|
824
|
-
const store = JSON.parse(content);
|
|
825
|
-
const mapping = store.mappings.find((m) => m.shortId === shortId);
|
|
826
|
-
return mapping?.fullId || null;
|
|
827
|
-
} catch {
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Get the short ID for a full UUID (if it exists in current mappings)
|
|
833
|
-
*/
|
|
834
|
-
async getShortId(fullId) {
|
|
835
|
-
try {
|
|
836
|
-
const content = await readFile2(this.storePath, "utf-8");
|
|
837
|
-
const store = JSON.parse(content);
|
|
838
|
-
const mapping = store.mappings.find((m) => m.fullId === fullId);
|
|
839
|
-
return mapping?.shortId || null;
|
|
840
|
-
} catch {
|
|
841
|
-
return null;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Resolve an ID that could be either short (1-99) or full UUID
|
|
846
|
-
*/
|
|
847
|
-
async resolveId(idOrShortId) {
|
|
848
|
-
const shortId = parseInt(idOrShortId, 10);
|
|
849
|
-
if (!isNaN(shortId) && shortId >= 1 && shortId <= this.maxIds) {
|
|
850
|
-
const fullId = await this.getFullId(shortId);
|
|
851
|
-
if (fullId) {
|
|
852
|
-
return fullId;
|
|
853
|
-
}
|
|
854
|
-
throw new Error(
|
|
855
|
-
`Short ID #${shortId} not found. Run 'vibetasks list --short-ids' first to generate mappings.`
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
return idOrShortId;
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
// src/commands/list.ts
|
|
1038
|
+
import { AuthManager as AuthManager4, TaskOperations as TaskOperations2 } from "@vibetasks/core";
|
|
863
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) => {
|
|
864
1040
|
try {
|
|
865
1041
|
const validFilters = ["all", "today", "upcoming", "completed"];
|
|
@@ -868,7 +1044,7 @@ var listCommand = new Command3("list").description("List tasks").argument("[filt
|
|
|
868
1044
|
console.error(chalk4.gray(`Valid filters: ${validFilters.join(", ")}`));
|
|
869
1045
|
process.exit(1);
|
|
870
1046
|
}
|
|
871
|
-
const authManager = new
|
|
1047
|
+
const authManager = new AuthManager4();
|
|
872
1048
|
const taskOps = await TaskOperations2.fromAuthManager(authManager);
|
|
873
1049
|
let tasks = await taskOps.getTasks(filter);
|
|
874
1050
|
if (options.project) {
|
|
@@ -1031,11 +1207,11 @@ Error: ${error.message}
|
|
|
1031
1207
|
import { Command as Command4 } from "commander";
|
|
1032
1208
|
import ora3 from "ora";
|
|
1033
1209
|
import chalk5 from "chalk";
|
|
1034
|
-
import { AuthManager as
|
|
1210
|
+
import { AuthManager as AuthManager5, TaskOperations as TaskOperations3 } from "@vibetasks/core";
|
|
1035
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) => {
|
|
1036
1212
|
const spinner = ora3("Completing task...").start();
|
|
1037
1213
|
try {
|
|
1038
|
-
const authManager = new
|
|
1214
|
+
const authManager = new AuthManager5();
|
|
1039
1215
|
const taskOps = await TaskOperations3.fromAuthManager(authManager);
|
|
1040
1216
|
const shortIdManager = new ShortIdManager();
|
|
1041
1217
|
let taskId = await shortIdManager.resolveId(id);
|
|
@@ -1108,11 +1284,11 @@ import { Command as Command5 } from "commander";
|
|
|
1108
1284
|
import chalk6 from "chalk";
|
|
1109
1285
|
import ora4 from "ora";
|
|
1110
1286
|
import inquirer from "inquirer";
|
|
1111
|
-
import { AuthManager as
|
|
1287
|
+
import { AuthManager as AuthManager6, TaskOperations as TaskOperations4 } from "@vibetasks/core";
|
|
1112
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) => {
|
|
1113
1289
|
const spinner = ora4();
|
|
1114
1290
|
try {
|
|
1115
|
-
const authManager = new
|
|
1291
|
+
const authManager = new AuthManager6();
|
|
1116
1292
|
const taskOps = await TaskOperations4.fromAuthManager(authManager);
|
|
1117
1293
|
const shortIdManager = new ShortIdManager();
|
|
1118
1294
|
let taskId;
|
|
@@ -1190,10 +1366,10 @@ var vibingCommand = new Command5("vibing").alias("start").alias("v").description
|
|
|
1190
1366
|
import { Command as Command6 } from "commander";
|
|
1191
1367
|
import chalk7 from "chalk";
|
|
1192
1368
|
import Table2 from "cli-table3";
|
|
1193
|
-
import { AuthManager as
|
|
1369
|
+
import { AuthManager as AuthManager7, TaskOperations as TaskOperations5 } from "@vibetasks/core";
|
|
1194
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) => {
|
|
1195
1371
|
try {
|
|
1196
|
-
const authManager = new
|
|
1372
|
+
const authManager = new AuthManager7();
|
|
1197
1373
|
const taskOps = await TaskOperations5.fromAuthManager(authManager);
|
|
1198
1374
|
const limit = parseInt(options.limit, 10);
|
|
1199
1375
|
const tasks = await taskOps.searchTasks(query, limit);
|
|
@@ -1250,7 +1426,7 @@ Error: ${error.message}
|
|
|
1250
1426
|
import { Command as Command7 } from "commander";
|
|
1251
1427
|
import ora5 from "ora";
|
|
1252
1428
|
import chalk8 from "chalk";
|
|
1253
|
-
import { AuthManager as
|
|
1429
|
+
import { AuthManager as AuthManager8, TaskOperations as TaskOperations6 } from "@vibetasks/core";
|
|
1254
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) => {
|
|
1255
1431
|
if (!options.title && !options.notes && !options.due && !options.priority && !options.status && !options.project && !options.energy && !options.context) {
|
|
1256
1432
|
console.error(chalk8.red("\nError: No updates specified"));
|
|
@@ -1259,7 +1435,7 @@ var updateCommand = new Command7("update").description("Update a task").argument
|
|
|
1259
1435
|
}
|
|
1260
1436
|
const spinner = ora5("Updating task...").start();
|
|
1261
1437
|
try {
|
|
1262
|
-
const authManager = new
|
|
1438
|
+
const authManager = new AuthManager8();
|
|
1263
1439
|
const taskOps = await TaskOperations6.fromAuthManager(authManager);
|
|
1264
1440
|
let taskId = id;
|
|
1265
1441
|
if (id.length < 32) {
|
|
@@ -1326,10 +1502,10 @@ import { Command as Command8 } from "commander";
|
|
|
1326
1502
|
import ora6 from "ora";
|
|
1327
1503
|
import chalk9 from "chalk";
|
|
1328
1504
|
import inquirer2 from "inquirer";
|
|
1329
|
-
import { AuthManager as
|
|
1505
|
+
import { AuthManager as AuthManager9, TaskOperations as TaskOperations7 } from "@vibetasks/core";
|
|
1330
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) => {
|
|
1331
1507
|
try {
|
|
1332
|
-
const authManager = new
|
|
1508
|
+
const authManager = new AuthManager9();
|
|
1333
1509
|
const taskOps = await TaskOperations7.fromAuthManager(authManager);
|
|
1334
1510
|
let taskId = id;
|
|
1335
1511
|
let taskTitle = "";
|
|
@@ -1385,10 +1561,10 @@ var deleteCommand = new Command8("delete").description("Delete a task").argument
|
|
|
1385
1561
|
// src/commands/config.ts
|
|
1386
1562
|
import { Command as Command9 } from "commander";
|
|
1387
1563
|
import chalk10 from "chalk";
|
|
1388
|
-
import { AuthManager as
|
|
1564
|
+
import { AuthManager as AuthManager10 } from "@vibetasks/core";
|
|
1389
1565
|
var configCommand = new Command9("config").description("View or set configuration").argument("[key]", "Configuration key").argument("[value]", "Configuration value").action(async (key, value) => {
|
|
1390
1566
|
try {
|
|
1391
|
-
const authManager = new
|
|
1567
|
+
const authManager = new AuthManager10();
|
|
1392
1568
|
if (!key) {
|
|
1393
1569
|
const configManager = authManager["configManager"];
|
|
1394
1570
|
const config = await configManager.getConfig();
|
|
@@ -1622,12 +1798,12 @@ import { Command as Command11 } from "commander";
|
|
|
1622
1798
|
import inquirer3 from "inquirer";
|
|
1623
1799
|
import ora8 from "ora";
|
|
1624
1800
|
import chalk12 from "chalk";
|
|
1625
|
-
import { AuthManager as
|
|
1801
|
+
import { AuthManager as AuthManager11 } from "@vibetasks/core";
|
|
1626
1802
|
import { detectProject as detectProject2 } from "@vibetasks/shared/utils/project-detector";
|
|
1627
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) => {
|
|
1628
1804
|
const spinner = ora8("Detecting project...").start();
|
|
1629
1805
|
try {
|
|
1630
|
-
const authManager = new
|
|
1806
|
+
const authManager = new AuthManager11();
|
|
1631
1807
|
const cwd = process.cwd();
|
|
1632
1808
|
let project;
|
|
1633
1809
|
if (options.name) {
|
|
@@ -1692,7 +1868,7 @@ import os from "os";
|
|
|
1692
1868
|
import { createServer as createServer2 } from "http";
|
|
1693
1869
|
import { exec as exec2 } from "child_process";
|
|
1694
1870
|
import { promisify as promisify2 } from "util";
|
|
1695
|
-
import { AuthManager as
|
|
1871
|
+
import { AuthManager as AuthManager12, TaskOperations as TaskOperations8, createSupabaseClient } from "@vibetasks/core";
|
|
1696
1872
|
import { detectProject as detectProject3 } from "@vibetasks/shared/utils/project-detector";
|
|
1697
1873
|
var execAsync2 = promisify2(exec2);
|
|
1698
1874
|
var SUPABASE_URL = "https://ihmayqzxqyednchbezya.supabase.co";
|
|
@@ -1757,7 +1933,7 @@ function showWelcome() {
|
|
|
1757
1933
|
}
|
|
1758
1934
|
async function checkExistingAuth() {
|
|
1759
1935
|
try {
|
|
1760
|
-
const authManager = new
|
|
1936
|
+
const authManager = new AuthManager12();
|
|
1761
1937
|
const token = await authManager.getAccessToken();
|
|
1762
1938
|
if (!token) return { authenticated: false };
|
|
1763
1939
|
const supabaseUrl = await authManager.getConfig("supabase_url") || SUPABASE_URL;
|
|
@@ -1801,7 +1977,7 @@ async function runBrowserAuth() {
|
|
|
1801
1977
|
return;
|
|
1802
1978
|
}
|
|
1803
1979
|
try {
|
|
1804
|
-
const authManager = new
|
|
1980
|
+
const authManager = new AuthManager12();
|
|
1805
1981
|
await authManager.setAccessToken(accessToken);
|
|
1806
1982
|
await authManager.setRefreshToken(refreshToken);
|
|
1807
1983
|
if (supabaseUrl) await authManager.setConfig("supabase_url", supabaseUrl);
|
|
@@ -1894,7 +2070,7 @@ async function stepAuthentication() {
|
|
|
1894
2070
|
]);
|
|
1895
2071
|
const spinner = ora9("Authenticating...").start();
|
|
1896
2072
|
try {
|
|
1897
|
-
const authManager = new
|
|
2073
|
+
const authManager = new AuthManager12();
|
|
1898
2074
|
await authManager.setConfig("supabase_url", SUPABASE_URL);
|
|
1899
2075
|
await authManager.setConfig("supabase_key", SUPABASE_ANON_KEY);
|
|
1900
2076
|
const supabase = createSupabaseClient({
|
|
@@ -2043,7 +2219,7 @@ async function stepProjectInit() {
|
|
|
2043
2219
|
return { success: true, skipped: true };
|
|
2044
2220
|
}
|
|
2045
2221
|
try {
|
|
2046
|
-
const authManager = new
|
|
2222
|
+
const authManager = new AuthManager12();
|
|
2047
2223
|
await authManager.setConfig(`project_${process.cwd()}`, project.name);
|
|
2048
2224
|
console.log(chalk13.green("\n \u2713") + chalk13.white(` Project "${project.name}" configured`));
|
|
2049
2225
|
return { success: true, projectName: project.name };
|
|
@@ -2060,7 +2236,7 @@ async function stepVerify() {
|
|
|
2060
2236
|
};
|
|
2061
2237
|
const supabaseSpinner = ora9("Testing Supabase connection...").start();
|
|
2062
2238
|
try {
|
|
2063
|
-
const authManager = new
|
|
2239
|
+
const authManager = new AuthManager12();
|
|
2064
2240
|
const taskOps = await TaskOperations8.fromAuthManager(authManager);
|
|
2065
2241
|
const tasks = await taskOps.getTasks("all");
|
|
2066
2242
|
result.supabaseConnected = true;
|
|
@@ -2233,7 +2409,7 @@ async function runNonInteractiveSetup() {
|
|
|
2233
2409
|
if (authResult.success) {
|
|
2234
2410
|
console.log(chalk13.gray("\nVerifying connection..."));
|
|
2235
2411
|
try {
|
|
2236
|
-
const authManager = new
|
|
2412
|
+
const authManager = new AuthManager12();
|
|
2237
2413
|
const taskOps = await TaskOperations8.fromAuthManager(authManager);
|
|
2238
2414
|
const tasks = await taskOps.getTasks("all");
|
|
2239
2415
|
console.log(chalk13.green("\u2713") + ` Connected (${tasks.length} tasks)`);
|
|
@@ -2304,7 +2480,7 @@ import { Command as Command13 } from "commander";
|
|
|
2304
2480
|
import chalk14 from "chalk";
|
|
2305
2481
|
import ora10 from "ora";
|
|
2306
2482
|
import inquirer5 from "inquirer";
|
|
2307
|
-
import { AuthManager as
|
|
2483
|
+
import { AuthManager as AuthManager13, TaskOperations as TaskOperations9 } from "@vibetasks/core";
|
|
2308
2484
|
import { detectProject as detectProject4 } from "@vibetasks/shared/utils/project-detector";
|
|
2309
2485
|
import { generateErrorTaskTitle } from "@vibetasks/shared";
|
|
2310
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) => {
|
|
@@ -2318,7 +2494,7 @@ var watchCommand = new Command13("watch").description("Monitor clipboard for err
|
|
|
2318
2494
|
let authManager;
|
|
2319
2495
|
let taskOps;
|
|
2320
2496
|
try {
|
|
2321
|
-
authManager = new
|
|
2497
|
+
authManager = new AuthManager13();
|
|
2322
2498
|
taskOps = await TaskOperations9.fromAuthManager(authManager);
|
|
2323
2499
|
} catch (error) {
|
|
2324
2500
|
if (error.message.includes("Not authenticated")) {
|
|
@@ -2528,11 +2704,11 @@ import { Command as Command14 } from "commander";
|
|
|
2528
2704
|
import chalk15 from "chalk";
|
|
2529
2705
|
import ora11 from "ora";
|
|
2530
2706
|
import inquirer6 from "inquirer";
|
|
2531
|
-
import { AuthManager as
|
|
2707
|
+
import { AuthManager as AuthManager14, TaskOperations as TaskOperations10 } from "@vibetasks/core";
|
|
2532
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) => {
|
|
2533
2709
|
const spinner = ora11();
|
|
2534
2710
|
try {
|
|
2535
|
-
const authManager = new
|
|
2711
|
+
const authManager = new AuthManager14();
|
|
2536
2712
|
const taskOps = await TaskOperations10.fromAuthManager(authManager);
|
|
2537
2713
|
if (options.list) {
|
|
2538
2714
|
spinner.start("Fetching archived tasks...");
|
|
@@ -2647,7 +2823,7 @@ import ora12 from "ora";
|
|
|
2647
2823
|
import { spawn } from "child_process";
|
|
2648
2824
|
import path3 from "path";
|
|
2649
2825
|
import { fileURLToPath } from "url";
|
|
2650
|
-
import { AuthManager as
|
|
2826
|
+
import { AuthManager as AuthManager15, TaskOperations as TaskOperations11 } from "@vibetasks/core";
|
|
2651
2827
|
import { generateErrorTaskTitle as generateErrorTaskTitle2 } from "@vibetasks/shared";
|
|
2652
2828
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
2653
2829
|
var __dirname2 = path3.dirname(__filename2);
|
|
@@ -2971,7 +3147,7 @@ async function showNotification(title, message, type = "info", duration) {
|
|
|
2971
3147
|
async function createTaskFromError2(error) {
|
|
2972
3148
|
const spinner = ora12("Creating task from error...").start();
|
|
2973
3149
|
try {
|
|
2974
|
-
const authManager = new
|
|
3150
|
+
const authManager = new AuthManager15();
|
|
2975
3151
|
const taskOps = await TaskOperations11.fromAuthManager(authManager);
|
|
2976
3152
|
const title = generateErrorTaskTitle2(error);
|
|
2977
3153
|
const notes = formatErrorForNotes(error);
|
|
@@ -3034,11 +3210,11 @@ function getTagColor2(category) {
|
|
|
3034
3210
|
// src/commands/next.ts
|
|
3035
3211
|
import { Command as Command16 } from "commander";
|
|
3036
3212
|
import chalk17 from "chalk";
|
|
3037
|
-
import { AuthManager as
|
|
3213
|
+
import { AuthManager as AuthManager16, TaskOperations as TaskOperations12 } from "@vibetasks/core";
|
|
3038
3214
|
var PRIORITY_ORDER = { high: 0, medium: 1, low: 2, none: 3 };
|
|
3039
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) => {
|
|
3040
3216
|
try {
|
|
3041
|
-
const authManager = new
|
|
3217
|
+
const authManager = new AuthManager16();
|
|
3042
3218
|
const taskOps = await TaskOperations12.fromAuthManager(authManager);
|
|
3043
3219
|
let tasks = await taskOps.getTasks("all");
|
|
3044
3220
|
tasks = tasks.filter((t) => t.status !== "done" && t.status !== "archived" && !t.completed);
|
|
@@ -3199,7 +3375,7 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
|
|
|
3199
3375
|
}
|
|
3200
3376
|
})
|
|
3201
3377
|
).addCommand(
|
|
3202
|
-
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) => {
|
|
3203
3379
|
const sessionManager2 = getSessionManager();
|
|
3204
3380
|
const session = await sessionManager2.endSession(options.summary);
|
|
3205
3381
|
if (!session) {
|
|
@@ -3207,18 +3383,59 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
|
|
|
3207
3383
|
return;
|
|
3208
3384
|
}
|
|
3209
3385
|
console.log(chalk18.green("\u2713 Session ended:"), chalk18.bold(session.id));
|
|
3210
|
-
console.log(
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
).
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
console.log(chalk18.
|
|
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
|
+
}
|
|
3420
|
+
if (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!"));
|
|
3431
|
+
}
|
|
3432
|
+
})
|
|
3433
|
+
).addCommand(
|
|
3434
|
+
new Command17("list").description("List recent sessions").option("-n, --limit <number>", "Number of sessions to show", "10").action(async (options) => {
|
|
3435
|
+
const sessionManager2 = getSessionManager();
|
|
3436
|
+
const sessions = await sessionManager2.getRecentSessions(parseInt(options.limit));
|
|
3437
|
+
if (sessions.length === 0) {
|
|
3438
|
+
console.log(chalk18.yellow("No sessions found"));
|
|
3222
3439
|
return;
|
|
3223
3440
|
}
|
|
3224
3441
|
console.log(chalk18.cyan("Recent Sessions:"));
|
|
@@ -3236,7 +3453,7 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
|
|
|
3236
3453
|
}
|
|
3237
3454
|
})
|
|
3238
3455
|
).addCommand(
|
|
3239
|
-
new Command17("show").
|
|
3456
|
+
new Command17("show").description("Show session details").argument("<session-id>", "Session ID (partial match supported)").action(async (sessionId) => {
|
|
3240
3457
|
const sessionManager2 = getSessionManager();
|
|
3241
3458
|
const handoff = await sessionManager2.getSessionHandoff(sessionId);
|
|
3242
3459
|
console.log(handoff);
|
|
@@ -3250,6 +3467,23 @@ var sessionCommand = new Command17("session").description("Manage AI sessions fo
|
|
|
3250
3467
|
});
|
|
3251
3468
|
console.log(chalk18.green("\u2713 Logged to session"));
|
|
3252
3469
|
})
|
|
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
|
+
})
|
|
3253
3487
|
);
|
|
3254
3488
|
function formatDuration(start, end) {
|
|
3255
3489
|
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
@@ -3265,7 +3499,7 @@ function formatDuration(start, end) {
|
|
|
3265
3499
|
import { Command as Command18 } from "commander";
|
|
3266
3500
|
import ora13 from "ora";
|
|
3267
3501
|
import chalk19 from "chalk";
|
|
3268
|
-
import { AuthManager as
|
|
3502
|
+
import { AuthManager as AuthManager17, TaskOperations as TaskOperations13 } from "@vibetasks/core";
|
|
3269
3503
|
function parseIndices(args) {
|
|
3270
3504
|
const indices = /* @__PURE__ */ new Set();
|
|
3271
3505
|
for (const arg of args) {
|
|
@@ -3307,7 +3541,7 @@ var subtaskCommand = new Command18("subtask").description("Manage subtasks withi
|
|
|
3307
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) => {
|
|
3308
3542
|
const spinner = ora13("Loading subtasks...").start();
|
|
3309
3543
|
try {
|
|
3310
|
-
const authManager = new
|
|
3544
|
+
const authManager = new AuthManager17();
|
|
3311
3545
|
const taskOps = await TaskOperations13.fromAuthManager(authManager);
|
|
3312
3546
|
const shortIdManager = new ShortIdManager();
|
|
3313
3547
|
let taskId = await shortIdManager.resolveId(taskIdArg);
|
|
@@ -3355,7 +3589,7 @@ Error: ${error.message}
|
|
|
3355
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) => {
|
|
3356
3590
|
const spinner = ora13("Updating subtasks...").start();
|
|
3357
3591
|
try {
|
|
3358
|
-
const authManager = new
|
|
3592
|
+
const authManager = new AuthManager17();
|
|
3359
3593
|
const taskOps = await TaskOperations13.fromAuthManager(authManager);
|
|
3360
3594
|
const shortIdManager = new ShortIdManager();
|
|
3361
3595
|
let taskId = await shortIdManager.resolveId(taskIdArg);
|
|
@@ -3431,7 +3665,7 @@ Error: ${error.message}
|
|
|
3431
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) => {
|
|
3432
3666
|
const spinner = ora13("Updating subtasks...").start();
|
|
3433
3667
|
try {
|
|
3434
|
-
const authManager = new
|
|
3668
|
+
const authManager = new AuthManager17();
|
|
3435
3669
|
const taskOps = await TaskOperations13.fromAuthManager(authManager);
|
|
3436
3670
|
const shortIdManager = new ShortIdManager();
|
|
3437
3671
|
let taskId = await shortIdManager.resolveId(taskIdArg);
|
|
@@ -3505,7 +3739,7 @@ Error: ${error.message}
|
|
|
3505
3739
|
import { Command as Command19 } from "commander";
|
|
3506
3740
|
import chalk20 from "chalk";
|
|
3507
3741
|
import ora14 from "ora";
|
|
3508
|
-
import { AuthManager as
|
|
3742
|
+
import { AuthManager as AuthManager18, TaskOperations as TaskOperations14 } from "@vibetasks/core";
|
|
3509
3743
|
import {
|
|
3510
3744
|
parseError,
|
|
3511
3745
|
generateErrorTaskTitle as generateErrorTaskTitle3,
|
|
@@ -3603,7 +3837,7 @@ var errorCommand = new Command19("error").description("Capture an error and crea
|
|
|
3603
3837
|
process.exit(1);
|
|
3604
3838
|
}
|
|
3605
3839
|
spinner.start("Creating error task...");
|
|
3606
|
-
const authManager = new
|
|
3840
|
+
const authManager = new AuthManager18();
|
|
3607
3841
|
const taskOps = await TaskOperations14.fromAuthManager(authManager);
|
|
3608
3842
|
const errorContext = parseError(errorText, "terminal");
|
|
3609
3843
|
const title = errorContext ? generateErrorTaskTitle3(errorContext) : `Error: ${errorText.split("\n")[0].slice(0, 80)}`;
|
|
@@ -3643,7 +3877,7 @@ ${errorText}
|
|
|
3643
3877
|
import { Command as Command20 } from "commander";
|
|
3644
3878
|
import chalk21 from "chalk";
|
|
3645
3879
|
import Table3 from "cli-table3";
|
|
3646
|
-
import { AuthManager as
|
|
3880
|
+
import { AuthManager as AuthManager19, TaskOperations as TaskOperations15 } from "@vibetasks/core";
|
|
3647
3881
|
var sourceDisplay = {
|
|
3648
3882
|
sentry: { label: "Sentry", color: chalk21.red },
|
|
3649
3883
|
ci: { label: "CI", color: chalk21.yellow },
|
|
@@ -3670,7 +3904,7 @@ function formatSource(sourceType) {
|
|
|
3670
3904
|
}
|
|
3671
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) => {
|
|
3672
3906
|
try {
|
|
3673
|
-
const authManager = new
|
|
3907
|
+
const authManager = new AuthManager19();
|
|
3674
3908
|
const taskOps = await TaskOperations15.fromAuthManager(authManager);
|
|
3675
3909
|
let sourceTypes;
|
|
3676
3910
|
if (options.source) {
|
|
@@ -3779,7 +4013,7 @@ Error: ${error.message}
|
|
|
3779
4013
|
});
|
|
3780
4014
|
var inboxStatsCommand = new Command20("stats").description("Show inbox statistics").action(async () => {
|
|
3781
4015
|
try {
|
|
3782
|
-
const authManager = new
|
|
4016
|
+
const authManager = new AuthManager19();
|
|
3783
4017
|
const taskOps = await TaskOperations15.fromAuthManager(authManager);
|
|
3784
4018
|
const stats = await taskOps.getInboxStats();
|
|
3785
4019
|
console.log("\n" + chalk21.bold("Inbox Statistics") + "\n");
|
|
@@ -3835,10 +4069,1267 @@ Error: ${error.message}
|
|
|
3835
4069
|
});
|
|
3836
4070
|
inboxCommand.addCommand(inboxStatsCommand);
|
|
3837
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
|
+
|
|
3838
5329
|
// bin/vibetasks.ts
|
|
3839
5330
|
var require2 = createRequire(import.meta.url);
|
|
3840
5331
|
var pkg = require2("../../package.json");
|
|
3841
|
-
var program = new
|
|
5332
|
+
var program = new Command30();
|
|
3842
5333
|
program.name("vibetasks").description("VibeTasks CLI - Fast task management for vibers").version(pkg.version);
|
|
3843
5334
|
program.addCommand(setupCommand);
|
|
3844
5335
|
program.addCommand(loginCommand);
|
|
@@ -3861,4 +5352,13 @@ program.addCommand(sessionCommand);
|
|
|
3861
5352
|
program.addCommand(subtaskCommand);
|
|
3862
5353
|
program.addCommand(errorCommand);
|
|
3863
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);
|
|
3864
5364
|
program.parse();
|