@towles/tool 0.0.41 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -109
- package/package.json +51 -41
- package/src/commands/base.ts +3 -18
- package/src/commands/config.ts +9 -8
- package/src/commands/doctor.ts +4 -1
- package/src/commands/gh/branch-clean.ts +10 -4
- package/src/commands/gh/branch.ts +6 -3
- package/src/commands/gh/pr.ts +10 -3
- package/src/commands/graph-template.html +1214 -0
- package/src/commands/graph.test.ts +176 -0
- package/src/commands/graph.ts +970 -0
- package/src/commands/install.ts +8 -2
- package/src/commands/journal/daily-notes.ts +9 -5
- package/src/commands/journal/meeting.ts +12 -6
- package/src/commands/journal/note.ts +12 -6
- package/src/commands/ralph/plan/add.ts +75 -0
- package/src/commands/ralph/plan/done.ts +82 -0
- package/src/commands/ralph/{task → plan}/list.test.ts +5 -5
- package/src/commands/ralph/{task → plan}/list.ts +28 -39
- package/src/commands/ralph/plan/remove.ts +71 -0
- package/src/commands/ralph/run.test.ts +521 -0
- package/src/commands/ralph/run.ts +126 -189
- package/src/commands/ralph/show.ts +88 -0
- package/src/config/settings.ts +8 -27
- package/src/{commands/ralph/lib → lib/ralph}/execution.ts +4 -14
- package/src/lib/ralph/formatter.ts +238 -0
- package/src/{commands/ralph/lib → lib/ralph}/state.ts +17 -42
- package/src/utils/date-utils.test.ts +2 -1
- package/src/utils/date-utils.ts +2 -2
- package/LICENSE.md +0 -20
- package/src/commands/index.ts +0 -55
- package/src/commands/observe/graph.test.ts +0 -89
- package/src/commands/observe/graph.ts +0 -1640
- package/src/commands/observe/report.ts +0 -166
- package/src/commands/observe/session.ts +0 -385
- package/src/commands/observe/setup.ts +0 -180
- package/src/commands/observe/status.ts +0 -146
- package/src/commands/ralph/lib/formatter.ts +0 -298
- package/src/commands/ralph/lib/marker.ts +0 -108
- package/src/commands/ralph/marker/create.ts +0 -23
- package/src/commands/ralph/plan.ts +0 -73
- package/src/commands/ralph/progress.ts +0 -44
- package/src/commands/ralph/ralph.test.ts +0 -673
- package/src/commands/ralph/task/add.ts +0 -105
- package/src/commands/ralph/task/done.ts +0 -73
- package/src/commands/ralph/task/remove.ts +0 -62
- package/src/config/context.ts +0 -7
- package/src/constants.ts +0 -3
- package/src/utils/anthropic/types.ts +0 -158
- package/src/utils/exec.ts +0 -8
- package/src/utils/git/git.ts +0 -25
- /package/src/{commands → lib}/journal/utils.ts +0 -0
- /package/src/{commands/ralph/lib → lib/ralph}/index.ts +0 -0
package/src/commands/install.ts
CHANGED
|
@@ -23,8 +23,14 @@ export default class Install extends BaseCommand {
|
|
|
23
23
|
"Configure Claude Code settings and optionally enable observability";
|
|
24
24
|
|
|
25
25
|
static override examples = [
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
{
|
|
27
|
+
description: "Configure Claude Code settings",
|
|
28
|
+
command: "<%= config.bin %> <%= command.id %>",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
description: "Include OTEL setup instructions",
|
|
32
|
+
command: "<%= config.bin %> <%= command.id %> --observability",
|
|
33
|
+
},
|
|
28
34
|
];
|
|
29
35
|
|
|
30
36
|
static override flags = {
|
|
@@ -11,24 +11,28 @@ import {
|
|
|
11
11
|
ensureTemplatesExist,
|
|
12
12
|
generateJournalFileInfoByType,
|
|
13
13
|
openInEditor,
|
|
14
|
-
} from "
|
|
14
|
+
} from "../../lib/journal/utils.js";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Create or open daily notes journal file
|
|
18
18
|
*/
|
|
19
19
|
export default class DailyNotes extends BaseCommand {
|
|
20
|
+
static override aliases = ["today"];
|
|
20
21
|
static override description = "Weekly files with daily sections for ongoing work and notes";
|
|
21
22
|
|
|
22
23
|
static override examples = [
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
{
|
|
25
|
+
description: "Open weekly notes for today",
|
|
26
|
+
command: "<%= config.bin %> <%= command.id %>",
|
|
27
|
+
},
|
|
28
|
+
{ description: "Using alias", command: "<%= config.bin %> today" },
|
|
25
29
|
];
|
|
26
30
|
|
|
27
31
|
async run(): Promise<void> {
|
|
28
32
|
await this.parse(DailyNotes);
|
|
29
33
|
|
|
30
34
|
try {
|
|
31
|
-
const journalSettings = this.settings.
|
|
35
|
+
const journalSettings = this.settings.settings.journalSettings;
|
|
32
36
|
const templateDir = journalSettings.templateDir;
|
|
33
37
|
|
|
34
38
|
// Ensure templates exist on first run
|
|
@@ -54,7 +58,7 @@ export default class DailyNotes extends BaseCommand {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
await openInEditor({
|
|
57
|
-
editor: this.settings.
|
|
61
|
+
editor: this.settings.settings.preferredEditor,
|
|
58
62
|
filePath: fileInfo.fullPath,
|
|
59
63
|
folderPath: journalSettings.baseFolder,
|
|
60
64
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ensureTemplatesExist,
|
|
13
13
|
generateJournalFileInfoByType,
|
|
14
14
|
openInEditor,
|
|
15
|
-
} from "
|
|
15
|
+
} from "../../lib/journal/utils.js";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Create or open meeting notes file
|
|
@@ -28,16 +28,22 @@ export default class Meeting extends BaseCommand {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
static override examples = [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
{
|
|
32
|
+
description: "Create meeting note (prompts for title)",
|
|
33
|
+
command: "<%= config.bin %> <%= command.id %>",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
description: "Create with title",
|
|
37
|
+
command: '<%= config.bin %> <%= command.id %> "Sprint Planning"',
|
|
38
|
+
},
|
|
39
|
+
{ description: "Using alias", command: '<%= config.bin %> m "Standup"' },
|
|
34
40
|
];
|
|
35
41
|
|
|
36
42
|
async run(): Promise<void> {
|
|
37
43
|
const { args } = await this.parse(Meeting);
|
|
38
44
|
|
|
39
45
|
try {
|
|
40
|
-
const journalSettings = this.settings.
|
|
46
|
+
const journalSettings = this.settings.settings.journalSettings;
|
|
41
47
|
const templateDir = journalSettings.templateDir;
|
|
42
48
|
|
|
43
49
|
// Ensure templates exist on first run
|
|
@@ -71,7 +77,7 @@ export default class Meeting extends BaseCommand {
|
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
await openInEditor({
|
|
74
|
-
editor: this.settings.
|
|
80
|
+
editor: this.settings.settings.preferredEditor,
|
|
75
81
|
filePath: fileInfo.fullPath,
|
|
76
82
|
folderPath: journalSettings.baseFolder,
|
|
77
83
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ensureTemplatesExist,
|
|
13
13
|
generateJournalFileInfoByType,
|
|
14
14
|
openInEditor,
|
|
15
|
-
} from "
|
|
15
|
+
} from "../../lib/journal/utils.js";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Create or open general-purpose note file
|
|
@@ -28,16 +28,22 @@ export default class Note extends BaseCommand {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
static override examples = [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
{
|
|
32
|
+
description: "Create note (prompts for title)",
|
|
33
|
+
command: "<%= config.bin %> <%= command.id %>",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
description: "Create with title",
|
|
37
|
+
command: '<%= config.bin %> <%= command.id %> "Research Notes"',
|
|
38
|
+
},
|
|
39
|
+
{ description: "Using alias", command: '<%= config.bin %> n "Ideas"' },
|
|
34
40
|
];
|
|
35
41
|
|
|
36
42
|
async run(): Promise<void> {
|
|
37
43
|
const { args } = await this.parse(Note);
|
|
38
44
|
|
|
39
45
|
try {
|
|
40
|
-
const journalSettings = this.settings.
|
|
46
|
+
const journalSettings = this.settings.settings.journalSettings;
|
|
41
47
|
const templateDir = journalSettings.templateDir;
|
|
42
48
|
|
|
43
49
|
// Ensure templates exist on first run
|
|
@@ -71,7 +77,7 @@ export default class Note extends BaseCommand {
|
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
await openInEditor({
|
|
74
|
-
editor: this.settings.
|
|
80
|
+
editor: this.settings.settings.preferredEditor,
|
|
75
81
|
filePath: fileInfo.fullPath,
|
|
76
82
|
folderPath: journalSettings.baseFolder,
|
|
77
83
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { Flags } from "@oclif/core";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import { colors } from "consola/utils";
|
|
6
|
+
import { BaseCommand } from "../../base.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_STATE_FILE,
|
|
9
|
+
loadState,
|
|
10
|
+
saveState,
|
|
11
|
+
createInitialState,
|
|
12
|
+
addPlanToState,
|
|
13
|
+
resolveRalphPath,
|
|
14
|
+
} from "../../../lib/ralph/state.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add a new plan to ralph state from a file
|
|
18
|
+
*/
|
|
19
|
+
export default class PlanAdd extends BaseCommand {
|
|
20
|
+
static override description = "Add a new plan from a file";
|
|
21
|
+
|
|
22
|
+
static override examples = [
|
|
23
|
+
{
|
|
24
|
+
description: "Add a plan from a markdown file",
|
|
25
|
+
command: "<%= config.bin %> <%= command.id %> --file docs/plans/2025-01-18-feature.md",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
static override flags = {
|
|
30
|
+
...BaseCommand.baseFlags,
|
|
31
|
+
file: Flags.string({
|
|
32
|
+
char: "f",
|
|
33
|
+
description: "Path to plan file (markdown)",
|
|
34
|
+
required: true,
|
|
35
|
+
}),
|
|
36
|
+
stateFile: Flags.string({
|
|
37
|
+
char: "s",
|
|
38
|
+
description: `State file path (default: ${DEFAULT_STATE_FILE})`,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async run(): Promise<void> {
|
|
43
|
+
const { flags } = await this.parse(PlanAdd);
|
|
44
|
+
const ralphSettings = this.settings.settings.ralphSettings;
|
|
45
|
+
const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
|
|
46
|
+
|
|
47
|
+
const filePath = resolve(flags.file);
|
|
48
|
+
|
|
49
|
+
if (!existsSync(filePath)) {
|
|
50
|
+
this.error(`Plan file not found: ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const description = readFileSync(filePath, "utf-8").trim();
|
|
54
|
+
|
|
55
|
+
if (!description || description.length < 3) {
|
|
56
|
+
this.error("Plan file is empty or too short (min 3 chars)");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let state = loadState(stateFile);
|
|
60
|
+
|
|
61
|
+
if (!state) {
|
|
62
|
+
state = createInitialState();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const newPlan = addPlanToState(state, description);
|
|
66
|
+
saveState(state, stateFile);
|
|
67
|
+
|
|
68
|
+
// Show truncated description for display
|
|
69
|
+
const displayDesc = description.length > 80 ? `${description.slice(0, 80)}...` : description;
|
|
70
|
+
consola.log(colors.green(`✓ Added plan #${newPlan.id} from ${flags.file}`));
|
|
71
|
+
consola.log(colors.dim(` ${displayDesc.split("\n")[0]}`));
|
|
72
|
+
consola.log(colors.dim(`State saved to: ${stateFile}`));
|
|
73
|
+
consola.log(colors.dim(`Total plans: ${state.plans.length}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { colors } from "consola/utils";
|
|
4
|
+
import { BaseCommand } from "../../base.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_STATE_FILE,
|
|
7
|
+
loadState,
|
|
8
|
+
saveState,
|
|
9
|
+
resolveRalphPath,
|
|
10
|
+
} from "../../../lib/ralph/state.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mark a ralph plan as done
|
|
14
|
+
*/
|
|
15
|
+
export default class PlanDone extends BaseCommand {
|
|
16
|
+
static override description = "Mark a plan as done by ID";
|
|
17
|
+
|
|
18
|
+
static override examples = [
|
|
19
|
+
{ description: "Mark plan #1 as done", command: "<%= config.bin %> <%= command.id %> 1" },
|
|
20
|
+
{
|
|
21
|
+
description: "Mark done using custom state file",
|
|
22
|
+
command: "<%= config.bin %> <%= command.id %> 5 --stateFile custom-state.json",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
static override args = {
|
|
27
|
+
id: Args.integer({
|
|
28
|
+
description: "Plan ID to mark done",
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static override flags = {
|
|
34
|
+
...BaseCommand.baseFlags,
|
|
35
|
+
stateFile: Flags.string({
|
|
36
|
+
char: "s",
|
|
37
|
+
description: `State file path (default: ${DEFAULT_STATE_FILE})`,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
async run(): Promise<void> {
|
|
42
|
+
const { args, flags } = await this.parse(PlanDone);
|
|
43
|
+
const ralphSettings = this.settings.settings.ralphSettings;
|
|
44
|
+
const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
|
|
45
|
+
|
|
46
|
+
const planId = args.id;
|
|
47
|
+
|
|
48
|
+
if (planId < 1) {
|
|
49
|
+
this.error("Invalid plan ID");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const state = loadState(stateFile);
|
|
53
|
+
|
|
54
|
+
if (!state) {
|
|
55
|
+
this.error(`No state file found at: ${stateFile}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const plan = state.plans.find((p) => p.id === planId);
|
|
59
|
+
|
|
60
|
+
if (!plan) {
|
|
61
|
+
this.error(`Plan #${planId} not found. Use: tt ralph plan list`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (plan.status === "done") {
|
|
65
|
+
consola.log(colors.yellow(`Plan #${planId} is already done.`));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
plan.status = "done";
|
|
70
|
+
plan.completedAt = new Date().toISOString();
|
|
71
|
+
saveState(state, stateFile);
|
|
72
|
+
|
|
73
|
+
consola.log(colors.green(`✓ Marked plan #${planId} as done: ${plan.description}`));
|
|
74
|
+
|
|
75
|
+
const remaining = state.plans.filter((p) => p.status !== "done").length;
|
|
76
|
+
if (remaining === 0) {
|
|
77
|
+
consola.log(colors.bold(colors.green("All plans complete!")));
|
|
78
|
+
} else {
|
|
79
|
+
consola.log(colors.dim(`Remaining plans: ${remaining}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Integration tests for oclif ralph
|
|
2
|
+
* Integration tests for oclif ralph plan list command
|
|
3
3
|
* Note: --help output goes through oclif's own routing
|
|
4
4
|
*/
|
|
5
5
|
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
@@ -8,11 +8,11 @@ import { join } from "node:path";
|
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { writeFileSync, unlinkSync, existsSync } from "node:fs";
|
|
10
10
|
|
|
11
|
-
describe("ralph
|
|
11
|
+
describe("ralph plan list command", () => {
|
|
12
12
|
const tempStateFile = join(tmpdir(), `ralph-test-list-${Date.now()}.json`);
|
|
13
13
|
|
|
14
14
|
beforeAll(() => {
|
|
15
|
-
// Create state file with one
|
|
15
|
+
// Create state file with one plan to minimize output during tests
|
|
16
16
|
writeFileSync(
|
|
17
17
|
tempStateFile,
|
|
18
18
|
JSON.stringify({
|
|
@@ -31,13 +31,13 @@ describe("ralph task list command", () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it("runs task list without error", async () => {
|
|
34
|
-
const { error } = await runCommand(["ralph:
|
|
34
|
+
const { error } = await runCommand(["ralph:plan:list", "-s", tempStateFile]);
|
|
35
35
|
expect(error).toBeUndefined();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it("supports --format flag", async () => {
|
|
39
39
|
const { error } = await runCommand([
|
|
40
|
-
"ralph:
|
|
40
|
+
"ralph:plan:list",
|
|
41
41
|
"--format",
|
|
42
42
|
"markdown",
|
|
43
43
|
"-s",
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { BaseCommand } from "../../base.js";
|
|
4
|
-
import { DEFAULT_STATE_FILE, loadState, resolveRalphPath } from "
|
|
5
|
-
import {
|
|
4
|
+
import { DEFAULT_STATE_FILE, loadState, resolveRalphPath } from "../../../lib/ralph/state.js";
|
|
5
|
+
import { formatPlansAsMarkdown } from "../../../lib/ralph/formatter.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* List all ralph
|
|
8
|
+
* List all ralph plans
|
|
9
9
|
*/
|
|
10
|
-
export default class
|
|
11
|
-
static override description = "List all
|
|
10
|
+
export default class PlanList extends BaseCommand {
|
|
11
|
+
static override description = "List all plans";
|
|
12
12
|
|
|
13
13
|
static override examples = [
|
|
14
|
-
"<%= config.bin %>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
{ description: "List all plans", command: "<%= config.bin %> <%= command.id %>" },
|
|
15
|
+
{
|
|
16
|
+
description: "Output as markdown",
|
|
17
|
+
command: "<%= config.bin %> <%= command.id %> --format markdown",
|
|
18
|
+
},
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
static override flags = {
|
|
@@ -28,15 +30,11 @@ export default class TaskList extends BaseCommand {
|
|
|
28
30
|
default: "default",
|
|
29
31
|
options: ["default", "markdown"],
|
|
30
32
|
}),
|
|
31
|
-
label: Flags.string({
|
|
32
|
-
char: "l",
|
|
33
|
-
description: "Filter tasks by label",
|
|
34
|
-
}),
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
async run(): Promise<void> {
|
|
38
|
-
const { flags } = await this.parse(
|
|
39
|
-
const ralphSettings = this.settings.
|
|
36
|
+
const { flags } = await this.parse(PlanList);
|
|
37
|
+
const ralphSettings = this.settings.settings.ralphSettings;
|
|
40
38
|
const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
|
|
41
39
|
|
|
42
40
|
const state = loadState(stateFile);
|
|
@@ -46,63 +44,54 @@ export default class TaskList extends BaseCommand {
|
|
|
46
44
|
return;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
let tasks = state.tasks;
|
|
51
|
-
if (flags.label) {
|
|
52
|
-
tasks = tasks.filter((t) => t.label === flags.label);
|
|
53
|
-
if (tasks.length === 0) {
|
|
54
|
-
this.log(pc.yellow(`No tasks with label: ${flags.label}`));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
47
|
+
const plans = state.plans;
|
|
58
48
|
|
|
59
|
-
if (
|
|
60
|
-
this.log(pc.yellow("No
|
|
61
|
-
this.log(pc.dim('Use: tt ralph
|
|
49
|
+
if (plans.length === 0) {
|
|
50
|
+
this.log(pc.yellow("No plans in state file."));
|
|
51
|
+
this.log(pc.dim('Use: tt ralph plan add "description"'));
|
|
62
52
|
return;
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
if (flags.format === "markdown") {
|
|
66
|
-
this.log(
|
|
56
|
+
this.log(formatPlansAsMarkdown(plans));
|
|
67
57
|
return;
|
|
68
58
|
}
|
|
69
59
|
|
|
70
60
|
// Default format output - compact with truncation
|
|
71
|
-
const ready =
|
|
72
|
-
const done =
|
|
61
|
+
const ready = plans.filter((p) => p.status === "ready");
|
|
62
|
+
const done = plans.filter((p) => p.status === "done");
|
|
73
63
|
|
|
74
64
|
const truncate = (s: string, len: number) => (s.length > len ? s.slice(0, len - 1) + "…" : s);
|
|
75
65
|
const termWidth = process.stdout.columns || 120;
|
|
76
66
|
|
|
77
67
|
// Summary header
|
|
78
|
-
const labelInfo = flags.label ? ` [${flags.label}]` : "";
|
|
79
68
|
this.log(
|
|
80
|
-
pc.bold(
|
|
69
|
+
pc.bold("\nPlans: ") +
|
|
81
70
|
pc.green(`${done.length} done`) +
|
|
82
71
|
pc.dim(" / ") +
|
|
83
72
|
pc.yellow(`${ready.length} ready`),
|
|
84
73
|
);
|
|
85
74
|
this.log();
|
|
86
75
|
|
|
87
|
-
// Show ready
|
|
76
|
+
// Show ready plans first (these are actionable)
|
|
88
77
|
// Reserve ~10 chars for " ○ #XX " prefix
|
|
89
78
|
const descWidth = Math.max(40, termWidth - 12);
|
|
90
79
|
|
|
91
80
|
if (ready.length > 0) {
|
|
92
|
-
for (const
|
|
81
|
+
for (const plan of ready) {
|
|
93
82
|
const icon = pc.dim("○");
|
|
94
|
-
const id = pc.cyan(`#${
|
|
95
|
-
const desc = truncate(
|
|
83
|
+
const id = pc.cyan(`#${plan.id}`);
|
|
84
|
+
const desc = truncate(plan.description, descWidth);
|
|
96
85
|
this.log(` ${icon} ${id} ${desc}`);
|
|
97
86
|
}
|
|
98
87
|
}
|
|
99
88
|
|
|
100
|
-
// Show done
|
|
89
|
+
// Show done plans collapsed
|
|
101
90
|
if (done.length > 0) {
|
|
102
91
|
this.log(pc.dim(` ─── ${done.length} completed ───`));
|
|
103
|
-
for (const
|
|
104
|
-
const desc = truncate(
|
|
105
|
-
this.log(pc.dim(` ✓ #${
|
|
92
|
+
for (const plan of done) {
|
|
93
|
+
const desc = truncate(plan.description, descWidth - 5);
|
|
94
|
+
this.log(pc.dim(` ✓ #${plan.id} ${desc}`));
|
|
106
95
|
}
|
|
107
96
|
}
|
|
108
97
|
this.log();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { colors } from "consola/utils";
|
|
4
|
+
import { BaseCommand } from "../../base.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_STATE_FILE,
|
|
7
|
+
loadState,
|
|
8
|
+
saveState,
|
|
9
|
+
resolveRalphPath,
|
|
10
|
+
} from "../../../lib/ralph/state.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove a ralph plan by ID
|
|
14
|
+
*/
|
|
15
|
+
export default class PlanRemove extends BaseCommand {
|
|
16
|
+
static override description = "Remove a plan by ID";
|
|
17
|
+
|
|
18
|
+
static override examples = [
|
|
19
|
+
{ description: "Remove plan #1", command: "<%= config.bin %> <%= command.id %> 1" },
|
|
20
|
+
{
|
|
21
|
+
description: "Remove from custom state file",
|
|
22
|
+
command: "<%= config.bin %> <%= command.id %> 5 --stateFile custom-state.json",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
static override args = {
|
|
27
|
+
id: Args.integer({
|
|
28
|
+
description: "Plan ID to remove",
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static override flags = {
|
|
34
|
+
...BaseCommand.baseFlags,
|
|
35
|
+
stateFile: Flags.string({
|
|
36
|
+
char: "s",
|
|
37
|
+
description: `State file path (default: ${DEFAULT_STATE_FILE})`,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
async run(): Promise<void> {
|
|
42
|
+
const { args, flags } = await this.parse(PlanRemove);
|
|
43
|
+
const ralphSettings = this.settings.settings.ralphSettings;
|
|
44
|
+
const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
|
|
45
|
+
|
|
46
|
+
const planId = args.id;
|
|
47
|
+
|
|
48
|
+
if (planId < 1) {
|
|
49
|
+
this.error("Invalid plan ID");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const state = loadState(stateFile);
|
|
53
|
+
|
|
54
|
+
if (!state) {
|
|
55
|
+
this.error(`No state file found at: ${stateFile}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const planIndex = state.plans.findIndex((p) => p.id === planId);
|
|
59
|
+
|
|
60
|
+
if (planIndex === -1) {
|
|
61
|
+
this.error(`Plan #${planId} not found. Use: tt ralph plan list`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const removedPlan = state.plans[planIndex];
|
|
65
|
+
state.plans.splice(planIndex, 1);
|
|
66
|
+
saveState(state, stateFile);
|
|
67
|
+
|
|
68
|
+
consola.log(colors.green(`✓ Removed plan #${planId}: ${removedPlan.description}`));
|
|
69
|
+
consola.log(colors.dim(`Remaining plans: ${state.plans.length}`));
|
|
70
|
+
}
|
|
71
|
+
}
|