@towles/tool 0.0.16 → 0.0.17
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 +1 -0
- package/dist/index.mjs +175 -28
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ if that works, then you need to add the pnpm global bin directory to your PATH.
|
|
|
75
75
|
- [rolldown-vite](https://voidzero.dev/posts/announcing-rolldown-vite) - A Vite plugin for rolling down your code
|
|
76
76
|
- ~~[zx](https://github.com/google/zx) google created library to write shell scripts in a more powerful and expressive way via the Anthropic API.~~
|
|
77
77
|
- [prompts](https://github.com/terkelg/prompts) - A library for creating beautiful command-line prompts, with fuzzy search and other features.
|
|
78
|
+
- had to patch it so `esc` cancels the selection with [pnpm-patch-i](https://github.com/antfu/pnpm-patch-i)
|
|
78
79
|
- [yargs](https://github.com/yargs/yargs) - A modern, feature-rich command-line argument parser with enhanced error handling, TypeScript support, and flexible command configuration.
|
|
79
80
|
- ~~[ink](https://github.com/vadimdemedes/ink) - React for interactive command-line apps~~
|
|
80
81
|
- wanted hotkey support and more complex UI but this was overkill for this project.
|
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,9 @@ import prompts from 'prompts';
|
|
|
15
15
|
import { execSync, exec } from 'node:child_process';
|
|
16
16
|
import { promisify } from 'node:util';
|
|
17
17
|
import { DateTime } from 'luxon';
|
|
18
|
+
import { Fzf } from 'fzf';
|
|
19
|
+
import stripAnsi from 'strip-ansi';
|
|
20
|
+
import { exec as exec$1 } from 'tinyexec';
|
|
18
21
|
|
|
19
22
|
async function loadTowlesToolContext({
|
|
20
23
|
cwd,
|
|
@@ -118,8 +121,13 @@ async function loadSettings() {
|
|
|
118
121
|
);
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
const version = "0.0.
|
|
124
|
+
const version = "0.0.17";
|
|
122
125
|
|
|
126
|
+
const JOURNAL_TYPES = {
|
|
127
|
+
DAILY_NOTES: "daily-notes",
|
|
128
|
+
MEETING: "meeting",
|
|
129
|
+
NOTE: "note"
|
|
130
|
+
};
|
|
123
131
|
async function parseArguments(argv) {
|
|
124
132
|
let parsedResult = null;
|
|
125
133
|
const parser = yargs(hideBin(argv)).scriptName(AppInfo.toolName).usage("Usage: $0 <command> [options]").version(version).demandCommand(1, "You need at least one command").recommendCommands().strict().help().wrap(yargs().terminalWidth());
|
|
@@ -132,7 +140,7 @@ async function parseArguments(argv) {
|
|
|
132
140
|
"Weekly files with daily sections for ongoing work and notes",
|
|
133
141
|
{},
|
|
134
142
|
(argv2) => {
|
|
135
|
-
parsedResult = { command: "journal", args: {
|
|
143
|
+
parsedResult = { command: "journal", args: { jouralType: "daily-notes", title: "" } };
|
|
136
144
|
}
|
|
137
145
|
).command(
|
|
138
146
|
["meeting [title]", "m"],
|
|
@@ -144,7 +152,7 @@ async function parseArguments(argv) {
|
|
|
144
152
|
}
|
|
145
153
|
},
|
|
146
154
|
(argv2) => {
|
|
147
|
-
parsedResult = { command: "journal", args: {
|
|
155
|
+
parsedResult = { command: "journal", args: { jouralType: "meeting", title: argv2.title || "" } };
|
|
148
156
|
}
|
|
149
157
|
).command(
|
|
150
158
|
["note [title]", "n"],
|
|
@@ -156,7 +164,7 @@ async function parseArguments(argv) {
|
|
|
156
164
|
}
|
|
157
165
|
},
|
|
158
166
|
(argv2) => {
|
|
159
|
-
parsedResult = { command: "journal", args: {
|
|
167
|
+
parsedResult = { command: "journal", args: { jouralType: "note", title: argv2.title || "" } };
|
|
160
168
|
}
|
|
161
169
|
).demandCommand(1, "You need to specify a journal subcommand").help();
|
|
162
170
|
},
|
|
@@ -178,6 +186,20 @@ async function parseArguments(argv) {
|
|
|
178
186
|
parsedResult = { command: "git-commit", args: { message: argv2.message } };
|
|
179
187
|
}
|
|
180
188
|
);
|
|
189
|
+
parser.command(
|
|
190
|
+
["gh-branch [assignedToMe...]", "branch", "br"],
|
|
191
|
+
"Create git branch from github issue",
|
|
192
|
+
{
|
|
193
|
+
assignedToMe: {
|
|
194
|
+
type: "boolean",
|
|
195
|
+
describe: "filter issues based on if assigned to you by default",
|
|
196
|
+
default: false
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
(argv2) => {
|
|
200
|
+
parsedResult = { command: "gh-branch", args: { assignedToMe: argv2.assignedToMe } };
|
|
201
|
+
}
|
|
202
|
+
);
|
|
181
203
|
parser.command(
|
|
182
204
|
["config", "cfg"],
|
|
183
205
|
"set or show configuration file.",
|
|
@@ -321,11 +343,6 @@ function formatDate(date) {
|
|
|
321
343
|
}
|
|
322
344
|
|
|
323
345
|
const execAsync = promisify(exec);
|
|
324
|
-
const JOURNAL_TYPES = {
|
|
325
|
-
DAILY_NOTES: "daily-notes",
|
|
326
|
-
MEETING: "meeting",
|
|
327
|
-
NOTE: "note"
|
|
328
|
-
};
|
|
329
346
|
function ensureDirectoryExists(folderPath) {
|
|
330
347
|
if (!existsSync(folderPath)) {
|
|
331
348
|
consola.info(`Creating journal directory: ${colors.cyan(folderPath)}`);
|
|
@@ -420,7 +437,7 @@ function resolvePathTemplate(template, title, date, mondayDate) {
|
|
|
420
437
|
}
|
|
421
438
|
});
|
|
422
439
|
}
|
|
423
|
-
function generateJournalFileInfoByType({ journalSettings, date = /* @__PURE__ */ new Date(), type
|
|
440
|
+
function generateJournalFileInfoByType({ journalSettings, date = /* @__PURE__ */ new Date(), type, title }) {
|
|
424
441
|
const currentDate = new Date(date);
|
|
425
442
|
let templatePath = "";
|
|
426
443
|
let mondayDate = getMondayOfWeek(currentDate);
|
|
@@ -442,7 +459,7 @@ function generateJournalFileInfoByType({ journalSettings, date = /* @__PURE__ */
|
|
|
442
459
|
break;
|
|
443
460
|
}
|
|
444
461
|
default:
|
|
445
|
-
throw new Error(`Unknown
|
|
462
|
+
throw new Error(`Unknown JournalType: ${type}`);
|
|
446
463
|
}
|
|
447
464
|
const resolvedPath = resolvePathTemplate(templatePath, title, currentDate, mondayDate);
|
|
448
465
|
return {
|
|
@@ -508,31 +525,161 @@ async function configCommand(context) {
|
|
|
508
525
|
consola.log(` ${context.cwd}`);
|
|
509
526
|
}
|
|
510
527
|
|
|
528
|
+
const isGithubCliInstalled = async () => {
|
|
529
|
+
try {
|
|
530
|
+
const proc = await exec$1(`gh`, ["--version"]);
|
|
531
|
+
return proc.stdout.indexOf("https://github.com/cli/cli") > 0;
|
|
532
|
+
} catch (e) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
const getIssues = async ({ assignedToMe, cwd }) => {
|
|
537
|
+
let issues = [];
|
|
538
|
+
const flags = [
|
|
539
|
+
"issue",
|
|
540
|
+
"list",
|
|
541
|
+
"--json",
|
|
542
|
+
"labels,number,title,state"
|
|
543
|
+
];
|
|
544
|
+
if (assignedToMe) {
|
|
545
|
+
flags.push("--assignee");
|
|
546
|
+
flags.push("@me");
|
|
547
|
+
}
|
|
548
|
+
const result = await exec$1(`gh`, flags);
|
|
549
|
+
const striped = stripAnsi(result.stdout);
|
|
550
|
+
issues = JSON.parse(striped);
|
|
551
|
+
return issues;
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const createBranch = async ({ branchName }) => {
|
|
555
|
+
const result = await exec$1(`git`, ["checkout", "-b", branchName]);
|
|
556
|
+
return result.stdout;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const getTerminalColumns = () => process.stdout?.columns || 80;
|
|
560
|
+
const limitText = (text, maxWidth) => {
|
|
561
|
+
if (text.length <= maxWidth)
|
|
562
|
+
return text;
|
|
563
|
+
return `${text.slice(0, maxWidth - 1)}${colors.dim("\u2026")}`;
|
|
564
|
+
};
|
|
565
|
+
function hexToRgb(hex) {
|
|
566
|
+
const cleanHex = hex.replace("#", "");
|
|
567
|
+
const r = Number.parseInt(cleanHex.slice(0, 2), 16);
|
|
568
|
+
const g = Number.parseInt(cleanHex.slice(2, 4), 16);
|
|
569
|
+
const b = Number.parseInt(cleanHex.slice(4, 6), 16);
|
|
570
|
+
return { r, g, b };
|
|
571
|
+
}
|
|
572
|
+
function printWithHexColor({ msg, hex }) {
|
|
573
|
+
const colorWithHex = hex.startsWith("#") ? hex : `#${hex}`;
|
|
574
|
+
const { r, g, b } = hexToRgb(colorWithHex);
|
|
575
|
+
const colorStart = `\x1B[38;2;${r};${g};${b}m`;
|
|
576
|
+
const colorEnd = "\x1B[0m";
|
|
577
|
+
return `${colorStart}${msg}${colorEnd}`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const checkPreqrequisites = async () => {
|
|
581
|
+
const cliInstalled = await isGithubCliInstalled();
|
|
582
|
+
if (!cliInstalled) {
|
|
583
|
+
consola.log("Github CLI not installed");
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
function customTrimEnd(str, charsToTrim) {
|
|
588
|
+
let i = str.length - 1;
|
|
589
|
+
while (i >= 0 && charsToTrim.includes(str[i])) {
|
|
590
|
+
i--;
|
|
591
|
+
}
|
|
592
|
+
return str.substring(0, i + 1);
|
|
593
|
+
}
|
|
594
|
+
const createBranchNameFromIssue = (selectedIssue) => {
|
|
595
|
+
let slug = selectedIssue.title.toLowerCase();
|
|
596
|
+
slug = slug.trim();
|
|
597
|
+
slug = slug.replaceAll(" ", "-");
|
|
598
|
+
slug = slug.replace(/[^0-9a-zA-Z_]/g, "-");
|
|
599
|
+
slug = slug.replaceAll("--", "-");
|
|
600
|
+
slug = slug.replaceAll("--", "-");
|
|
601
|
+
slug = slug.replaceAll("--", "-");
|
|
602
|
+
slug = customTrimEnd(slug, ["-"]);
|
|
603
|
+
const branchName = `feature/${selectedIssue.number}-${slug}`;
|
|
604
|
+
return branchName;
|
|
605
|
+
};
|
|
606
|
+
async function githubBranchCommand(context, args) {
|
|
607
|
+
await checkPreqrequisites();
|
|
608
|
+
const assignedToMe = Boolean(args.assignedToMe);
|
|
609
|
+
consola.log("Assigned to me:", assignedToMe);
|
|
610
|
+
const currentIssues = await getIssues({ assignedToMe, cwd: context.cwd });
|
|
611
|
+
if (currentIssues.length === 0) {
|
|
612
|
+
consola.log(colors.yellow("No issues found, check assignments"));
|
|
613
|
+
process.exit(1);
|
|
614
|
+
} else {
|
|
615
|
+
consola.log(colors.green(`${currentIssues.length} Issues found assigned to you`));
|
|
616
|
+
}
|
|
617
|
+
let lineMaxLength = getTerminalColumns();
|
|
618
|
+
const longestNumber = Math.max(...currentIssues.map((i) => i.number.toString().length));
|
|
619
|
+
const longestLabels = Math.max(...currentIssues.map((i) => i.labels.map((x) => x.name).join(", ").length));
|
|
620
|
+
lineMaxLength = lineMaxLength > 130 ? 130 : lineMaxLength;
|
|
621
|
+
const descriptionLength = lineMaxLength - longestNumber - longestLabels - 15;
|
|
622
|
+
const choices = currentIssues.map(
|
|
623
|
+
(i) => {
|
|
624
|
+
const labelText = i.labels.map((l) => printWithHexColor({ msg: l.name, hex: l.color })).join(", ");
|
|
625
|
+
const labelTextNoColor = i.labels.map((l) => l.name).join(", ");
|
|
626
|
+
const labelStartpad = longestLabels - labelTextNoColor.length;
|
|
627
|
+
return {
|
|
628
|
+
title: i.number.toString(),
|
|
629
|
+
value: i.number,
|
|
630
|
+
description: `${limitText(i.title, descriptionLength).padEnd(descriptionLength)} ${"".padStart(labelStartpad)}${labelText}`
|
|
631
|
+
// pads to make sure the labels are aligned, no diea why padStart doesn't work on labelText
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
choices.push({ title: "Cancel", value: "cancel" });
|
|
636
|
+
const fzf = new Fzf(choices, {
|
|
637
|
+
selector: (item) => `${item.value} ${item.description}`,
|
|
638
|
+
casing: "case-insensitive"
|
|
639
|
+
});
|
|
640
|
+
try {
|
|
641
|
+
const result = await prompts({
|
|
642
|
+
name: "issueNumber",
|
|
643
|
+
message: "Github issue to create branch for:",
|
|
644
|
+
type: "autocomplete",
|
|
645
|
+
choices,
|
|
646
|
+
async suggest(input, choices2) {
|
|
647
|
+
consola.log(input);
|
|
648
|
+
const results = fzf.find(input);
|
|
649
|
+
return results.map((r) => choices2.find((c) => c.value === r.item.value));
|
|
650
|
+
}
|
|
651
|
+
}, {
|
|
652
|
+
// when escape is used just cancel
|
|
653
|
+
onCancel: () => {
|
|
654
|
+
consola.info(colors.dim("Canceled"));
|
|
655
|
+
process.exit(0);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
if (result.issueNumber === "cancel") {
|
|
659
|
+
consola.log(colors.dim("Canceled"));
|
|
660
|
+
process.exit(0);
|
|
661
|
+
}
|
|
662
|
+
const selectedIssue = currentIssues.find((i) => i.number === result.issueNumber);
|
|
663
|
+
consola.log(`Selected issue ${colors.green(selectedIssue.number)} - ${colors.green(selectedIssue.title)}`);
|
|
664
|
+
const branchName = createBranchNameFromIssue(selectedIssue);
|
|
665
|
+
createBranch({ branchName });
|
|
666
|
+
} catch (e) {
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
511
671
|
async function executeCommand(parsedArgs, context) {
|
|
512
672
|
switch (parsedArgs.command) {
|
|
513
673
|
case "journal": {
|
|
514
|
-
|
|
515
|
-
let journalType;
|
|
516
|
-
switch (subcommand) {
|
|
517
|
-
case "daily-notes":
|
|
518
|
-
case "today":
|
|
519
|
-
journalType = JOURNAL_TYPES.DAILY_NOTES;
|
|
520
|
-
break;
|
|
521
|
-
case "meeting":
|
|
522
|
-
journalType = JOURNAL_TYPES.MEETING;
|
|
523
|
-
break;
|
|
524
|
-
case "note":
|
|
525
|
-
journalType = JOURNAL_TYPES.NOTE;
|
|
526
|
-
break;
|
|
527
|
-
default:
|
|
528
|
-
throw new Error(`Unknown journal subcommand: ${subcommand}`);
|
|
529
|
-
}
|
|
530
|
-
await createJournalFile({ context, type: journalType, title: title || "" });
|
|
674
|
+
await createJournalFile({ context, type: parsedArgs.args.jouralType, title: parsedArgs.args.title || "" });
|
|
531
675
|
break;
|
|
532
676
|
}
|
|
533
677
|
case "git-commit":
|
|
534
678
|
await gitCommitCommand(context, parsedArgs.args.message);
|
|
535
679
|
break;
|
|
680
|
+
case "gh-branch":
|
|
681
|
+
await githubBranchCommand(context, parsedArgs.args);
|
|
682
|
+
break;
|
|
536
683
|
case "config":
|
|
537
684
|
await configCommand(context);
|
|
538
685
|
break;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towles/tool",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.17",
|
|
5
5
|
"description": "One off quality of life scripts that I use on a daily basis.",
|
|
6
6
|
"author": "Chris Towles <Chris.Towles.Dev@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,11 +37,13 @@
|
|
|
37
37
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
38
38
|
"@clack/prompts": "^0.11.0",
|
|
39
39
|
"comment-json": "^4.2.5",
|
|
40
|
+
"consola": "^3.4.2",
|
|
40
41
|
"fzf": "^0.5.2",
|
|
41
42
|
"luxon": "^3.7.1",
|
|
42
43
|
"neverthrow": "^8.2.0",
|
|
43
44
|
"prompts": "^2.4.2",
|
|
44
|
-
"
|
|
45
|
+
"strip-ansi": "^7.1.0",
|
|
46
|
+
"tinyexec": "^0.3.2",
|
|
45
47
|
"yargs": "^17.7.2",
|
|
46
48
|
"zod": "^4.0.5"
|
|
47
49
|
},
|
|
@@ -55,7 +57,6 @@
|
|
|
55
57
|
"lint-staged": "^15.5.2",
|
|
56
58
|
"oxlint": "^1.7.0",
|
|
57
59
|
"simple-git-hooks": "^2.13.0",
|
|
58
|
-
"tinyexec": "^0.3.2",
|
|
59
60
|
"tsx": "^4.20.3",
|
|
60
61
|
"typescript": "^5.8.3",
|
|
61
62
|
"unbuild": "^3.5.0",
|