@towles/tool 0.0.18 → 0.0.41
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/LICENSE +21 -0
- package/LICENSE.md +9 -10
- package/README.md +121 -78
- package/bin/run.ts +5 -0
- package/package.json +63 -53
- package/patches/prompts.patch +34 -0
- package/src/commands/base.ts +42 -0
- package/src/commands/config.test.ts +15 -0
- package/src/commands/config.ts +43 -0
- package/src/commands/doctor.ts +133 -0
- package/src/commands/gh/branch-clean.ts +110 -0
- package/src/commands/gh/branch.test.ts +124 -0
- package/src/commands/gh/branch.ts +132 -0
- package/src/commands/gh/pr.ts +168 -0
- package/src/commands/index.ts +55 -0
- package/src/commands/install.ts +148 -0
- package/src/commands/journal/daily-notes.ts +66 -0
- package/src/commands/journal/meeting.ts +83 -0
- package/src/commands/journal/note.ts +83 -0
- package/src/commands/journal/utils.ts +399 -0
- package/src/commands/observe/graph.test.ts +89 -0
- package/src/commands/observe/graph.ts +1640 -0
- package/src/commands/observe/report.ts +166 -0
- package/src/commands/observe/session.ts +385 -0
- package/src/commands/observe/setup.ts +180 -0
- package/src/commands/observe/status.ts +146 -0
- package/src/commands/ralph/lib/execution.ts +302 -0
- package/src/commands/ralph/lib/formatter.ts +298 -0
- package/src/commands/ralph/lib/index.ts +4 -0
- package/src/commands/ralph/lib/marker.ts +108 -0
- package/src/commands/ralph/lib/state.ts +191 -0
- package/src/commands/ralph/marker/create.ts +23 -0
- package/src/commands/ralph/plan.ts +73 -0
- package/src/commands/ralph/progress.ts +44 -0
- package/src/commands/ralph/ralph.test.ts +673 -0
- package/src/commands/ralph/run.ts +408 -0
- package/src/commands/ralph/task/add.ts +105 -0
- package/src/commands/ralph/task/done.ts +73 -0
- package/src/commands/ralph/task/list.test.ts +48 -0
- package/src/commands/ralph/task/list.ts +110 -0
- package/src/commands/ralph/task/remove.ts +62 -0
- package/src/config/context.ts +7 -0
- package/src/config/settings.ts +155 -0
- package/src/constants.ts +3 -0
- package/src/types/journal.ts +16 -0
- package/src/utils/anthropic/types.ts +158 -0
- package/src/utils/date-utils.test.ts +96 -0
- package/src/utils/date-utils.ts +54 -0
- package/src/utils/exec.ts +8 -0
- package/src/utils/git/gh-cli-wrapper.test.ts +14 -0
- package/src/utils/git/gh-cli-wrapper.ts +54 -0
- package/src/utils/git/git-wrapper.test.ts +26 -0
- package/src/utils/git/git-wrapper.ts +15 -0
- package/src/utils/git/git.ts +25 -0
- package/src/utils/render.test.ts +71 -0
- package/src/utils/render.ts +34 -0
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -794
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
import { x } from "tinyexec";
|
|
3
|
+
|
|
4
|
+
export const isGithubCliInstalled = async (): Promise<boolean> => {
|
|
5
|
+
try {
|
|
6
|
+
const proc = await x(`gh`, ["--version"]);
|
|
7
|
+
return proc.stdout.indexOf("https://github.com/cli/cli") > 0;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface Issue {
|
|
14
|
+
labels: {
|
|
15
|
+
name: string;
|
|
16
|
+
color: string;
|
|
17
|
+
}[];
|
|
18
|
+
number: number;
|
|
19
|
+
title: string;
|
|
20
|
+
state: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getIssues = async ({
|
|
24
|
+
assignedToMe,
|
|
25
|
+
cwd,
|
|
26
|
+
}: {
|
|
27
|
+
assignedToMe: boolean;
|
|
28
|
+
cwd: string;
|
|
29
|
+
}): Promise<Issue[]> => {
|
|
30
|
+
let issues: Issue[] = [];
|
|
31
|
+
|
|
32
|
+
const flags = ["issue", "list", "--json", "labels,number,title,state"];
|
|
33
|
+
|
|
34
|
+
if (assignedToMe) {
|
|
35
|
+
flags.push("--assignee");
|
|
36
|
+
flags.push("@me");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//console.log('Current working directory:', cwd.stdout.trim())
|
|
40
|
+
|
|
41
|
+
const result = await x(`gh`, flags);
|
|
42
|
+
// Setting NO_COLOR=1 didn't remove colors so had to use stripAnsi
|
|
43
|
+
const stripped = stripAnsi(result.stdout);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
issues = JSON.parse(stripped);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Failed to parse GitHub CLI output as JSON. Raw output: ${stripped.slice(0, 200)}${stripped.length > 200 ? "..." : ""}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return issues;
|
|
54
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isGitDirectory } from "./git-wrapper";
|
|
3
|
+
|
|
4
|
+
describe.skip("git-wrapper", () => {
|
|
5
|
+
it("isGitDirectory", async () => {
|
|
6
|
+
const result = await isGitDirectory();
|
|
7
|
+
expect(result).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// it('getMergedBranches', async () => {
|
|
11
|
+
// const result = await getMergedBranches()
|
|
12
|
+
|
|
13
|
+
// if (result.length > 0) // may not have branches to cleanup so just check for no errors
|
|
14
|
+
// expect(result[0].trim()).toBe(result[0])
|
|
15
|
+
// })
|
|
16
|
+
|
|
17
|
+
// it('getLocalBranchNames', async () => {
|
|
18
|
+
// const result = await getLocalBranchNames()
|
|
19
|
+
// expect(result.includes('main')).toBeDefined()
|
|
20
|
+
// })
|
|
21
|
+
|
|
22
|
+
// it('getDefaultMainBranchName', async () => {
|
|
23
|
+
// const result = await getDefaultMainBranchName()
|
|
24
|
+
// expect(result).toBe('main')
|
|
25
|
+
// })
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { exec } from "tinyexec";
|
|
2
|
+
|
|
3
|
+
export const isGitDirectory = async (): Promise<boolean> => {
|
|
4
|
+
try {
|
|
5
|
+
const result = await exec(`git`, ["status"]);
|
|
6
|
+
return result.stdout.includes("On branch");
|
|
7
|
+
} catch (e) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const createBranch = async ({ branchName }: { branchName: string }): Promise<string> => {
|
|
13
|
+
const result = await exec(`git`, ["checkout", "-b", branchName]);
|
|
14
|
+
return result.stdout;
|
|
15
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// unJs/changelogen has some nice utilities for git
|
|
2
|
+
import { execCommand } from "../exec";
|
|
3
|
+
|
|
4
|
+
// using logic from https://github.com/unjs/changelogen/blob/main/src/git.ts
|
|
5
|
+
|
|
6
|
+
export async function getGitDiff(cwd: string): Promise<string> {
|
|
7
|
+
// https://git-scm.com/docs/pretty-formats
|
|
8
|
+
let r = execCommand(
|
|
9
|
+
// `git --no-pager log "${from ? `${from}...` : ''}${to}" `,
|
|
10
|
+
// using --cached because staged didn't work on MacOS
|
|
11
|
+
`git --no-pager diff --cached`,
|
|
12
|
+
// --name-status
|
|
13
|
+
// --pretty="----%n%s|%h|%an|%ae%n%b"
|
|
14
|
+
cwd,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Limit output to 1000 lines maximum
|
|
18
|
+
const lines = r.split("\n");
|
|
19
|
+
if (lines.length > 1000) {
|
|
20
|
+
r = lines.slice(0, 1000).join("\n");
|
|
21
|
+
r += `\n\n[Output truncated to 1000 lines]`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return r;
|
|
25
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { printWithHexColor } from "./render";
|
|
3
|
+
|
|
4
|
+
describe("printWithHexColor", () => {
|
|
5
|
+
it("should handle hex colors with # prefix", () => {
|
|
6
|
+
const result = printWithHexColor({ msg: "test", hex: "#ff0000" });
|
|
7
|
+
expect(result).toBe("\x1B[38;2;255;0;0mtest\x1B[0m");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should handle hex colors without # prefix", () => {
|
|
11
|
+
const result = printWithHexColor({ msg: "test", hex: "ff0000" });
|
|
12
|
+
expect(result).toBe("\x1B[38;2;255;0;0mtest\x1B[0m");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should handle green color correctly", () => {
|
|
16
|
+
const result = printWithHexColor({ msg: "green", hex: "#00ff00" });
|
|
17
|
+
expect(result).toBe("\x1B[38;2;0;255;0mgreen\x1B[0m");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should handle blue color correctly", () => {
|
|
21
|
+
const result = printWithHexColor({ msg: "blue", hex: "0000ff" });
|
|
22
|
+
expect(result).toBe("\x1B[38;2;0;0;255mblue\x1B[0m");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should handle mixed RGB values", () => {
|
|
26
|
+
const result = printWithHexColor({ msg: "purple", hex: "#800080" });
|
|
27
|
+
expect(result).toBe("\x1B[38;2;128;0;128mpurple\x1B[0m");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle GitHub-style label colors", () => {
|
|
31
|
+
// GitHub red label color
|
|
32
|
+
const result = printWithHexColor({ msg: "bug", hex: "d73a49" });
|
|
33
|
+
expect(result).toBe("\x1B[38;2;215;58;73mbug\x1B[0m");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should handle lowercase hex values", () => {
|
|
37
|
+
const result = printWithHexColor({ msg: "test", hex: "abc123" });
|
|
38
|
+
expect(result).toBe("\x1B[38;2;171;193;35mtest\x1B[0m");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle uppercase hex values", () => {
|
|
42
|
+
const result = printWithHexColor({ msg: "test", hex: "ABC123" });
|
|
43
|
+
expect(result).toBe("\x1B[38;2;171;193;35mtest\x1B[0m");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should handle empty message", () => {
|
|
47
|
+
const result = printWithHexColor({ msg: "", hex: "#ff0000" });
|
|
48
|
+
expect(result).toBe("\x1B[38;2;255;0;0m\x1B[0m");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle message with spaces", () => {
|
|
52
|
+
const result = printWithHexColor({ msg: "hello world", hex: "#ffffff" });
|
|
53
|
+
expect(result).toBe("\x1B[38;2;255;255;255mhello world\x1B[0m");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should handle black color (000000)", () => {
|
|
57
|
+
const result = printWithHexColor({ msg: "black", hex: "000000" });
|
|
58
|
+
expect(result).toBe("\x1B[38;2;0;0;0mblack\x1B[0m");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should handle white color (ffffff)", () => {
|
|
62
|
+
const result = printWithHexColor({ msg: "white", hex: "ffffff" });
|
|
63
|
+
expect(result).toBe("\x1B[38;2;255;255;255mwhite\x1B[0m");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should preserve message content exactly", () => {
|
|
67
|
+
const specialMessage = "special-chars_123!@#";
|
|
68
|
+
const result = printWithHexColor({ msg: specialMessage, hex: "#123456" });
|
|
69
|
+
expect(result).toBe(`\x1B[38;2;18;52;86m${specialMessage}\x1B[0m`);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { colors } from "consola/utils";
|
|
2
|
+
|
|
3
|
+
export const getTerminalColumns = () => process.stdout?.columns || 80;
|
|
4
|
+
|
|
5
|
+
export const limitText = (text: string, maxWidth: number): string => {
|
|
6
|
+
if (text.length <= maxWidth) return text;
|
|
7
|
+
// subtract 1 so room for the ellipsis
|
|
8
|
+
return `${text.slice(0, maxWidth - 1)}${colors.dim("…")}`;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert hex color to RGB values
|
|
13
|
+
*/
|
|
14
|
+
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
15
|
+
const cleanHex = hex.replace("#", "");
|
|
16
|
+
const r = Number.parseInt(cleanHex.slice(0, 2), 16);
|
|
17
|
+
const g = Number.parseInt(cleanHex.slice(2, 4), 16);
|
|
18
|
+
const b = Number.parseInt(cleanHex.slice(4, 6), 16);
|
|
19
|
+
return { r, g, b };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply hex color to text using ANSI 24-bit color codes
|
|
24
|
+
*/
|
|
25
|
+
export function printWithHexColor({ msg, hex }: { msg: string; hex: string }): string {
|
|
26
|
+
const colorWithHex = hex.startsWith("#") ? hex : `#${hex}`;
|
|
27
|
+
const { r, g, b } = hexToRgb(colorWithHex);
|
|
28
|
+
|
|
29
|
+
// Use ANSI 24-bit color: \x1B[38;2;r;g;bm for foreground color
|
|
30
|
+
const colorStart = `\x1B[38;2;${r};${g};${b}m`;
|
|
31
|
+
const colorEnd = "\x1B[0m"; // Reset color
|
|
32
|
+
|
|
33
|
+
return `${colorStart}${msg}${colorEnd}`;
|
|
34
|
+
}
|
package/dist/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|