@towles/tool 0.0.20 → 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.
Files changed (45) hide show
  1. package/{LICENSE.md → LICENSE} +1 -1
  2. package/README.md +86 -85
  3. package/bin/run.ts +5 -0
  4. package/package.json +84 -64
  5. package/patches/prompts.patch +34 -0
  6. package/src/commands/base.ts +27 -0
  7. package/src/commands/config.test.ts +15 -0
  8. package/src/commands/config.ts +44 -0
  9. package/src/commands/doctor.ts +136 -0
  10. package/src/commands/gh/branch-clean.ts +116 -0
  11. package/src/commands/gh/branch.test.ts +124 -0
  12. package/src/commands/gh/branch.ts +135 -0
  13. package/src/commands/gh/pr.ts +175 -0
  14. package/src/commands/graph-template.html +1214 -0
  15. package/src/commands/graph.test.ts +176 -0
  16. package/src/commands/graph.ts +970 -0
  17. package/src/commands/install.ts +154 -0
  18. package/src/commands/journal/daily-notes.ts +70 -0
  19. package/src/commands/journal/meeting.ts +89 -0
  20. package/src/commands/journal/note.ts +89 -0
  21. package/src/commands/ralph/plan/add.ts +75 -0
  22. package/src/commands/ralph/plan/done.ts +82 -0
  23. package/src/commands/ralph/plan/list.test.ts +48 -0
  24. package/src/commands/ralph/plan/list.ts +99 -0
  25. package/src/commands/ralph/plan/remove.ts +71 -0
  26. package/src/commands/ralph/run.test.ts +521 -0
  27. package/src/commands/ralph/run.ts +345 -0
  28. package/src/commands/ralph/show.ts +88 -0
  29. package/src/config/settings.ts +136 -0
  30. package/src/lib/journal/utils.ts +399 -0
  31. package/src/lib/ralph/execution.ts +292 -0
  32. package/src/lib/ralph/formatter.ts +238 -0
  33. package/src/lib/ralph/index.ts +4 -0
  34. package/src/lib/ralph/state.ts +166 -0
  35. package/src/types/journal.ts +16 -0
  36. package/src/utils/date-utils.test.ts +97 -0
  37. package/src/utils/date-utils.ts +54 -0
  38. package/src/utils/git/gh-cli-wrapper.test.ts +14 -0
  39. package/src/utils/git/gh-cli-wrapper.ts +54 -0
  40. package/src/utils/git/git-wrapper.test.ts +26 -0
  41. package/src/utils/git/git-wrapper.ts +15 -0
  42. package/src/utils/render.test.ts +71 -0
  43. package/src/utils/render.ts +34 -0
  44. package/dist/index.d.mts +0 -1
  45. package/dist/index.mjs +0 -805
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025-PRESENT Chris Towles<https://github.com/ChrisTowles>
3
+ Copyright (c) 2026 Chris Towles
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,116 +1,117 @@
1
1
  # Towles Tool
2
2
 
3
- [![npm version][npm-version-src]][npm-version-href]
4
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
- [![bundle][bundle-src]][bundle-href]
6
- [![JSDocs][jsdocs-src]][jsdocs-href]
7
- [![License][license-src]][license-href]
3
+ CLI tool with autonomous task runner and quality-of-life commands for daily development.
8
4
 
9
- One off quality of life scripts that I use on a daily basis.
5
+ ## Features
10
6
 
11
- ## Journal Type System
7
+ - **Ralph** - Autonomous task runner with session forking and context reuse
8
+ - **Observability** - Token usage visualization with interactive treemaps
9
+ - **Git workflows** - Branch creation, PR generation, and cleanup
10
+ - **Journaling** - Daily notes, meeting notes, and general notes
11
+ - **Claude Code plugins** - Personal plugin marketplace for Claude Code integration
12
12
 
13
- The journal system supports three types of files with different templates and organization:
13
+ ## Installation
14
14
 
15
- ### Daily Notes (`journal daily-notes`)
16
- - **Purpose**: Weekly files with daily sections for ongoing work and notes
17
- - **File structure**: `YYYY/daily-notes/YYYY-MM-DD-week-log.md` (Monday's date)
18
- - **Template**: Includes sections for Monday through Friday with date headers
19
- - **Use case**: Regular daily journaling, work logs, scratch pad for notes
15
+ ### Claude Code Plugin
20
16
 
21
- ### Meeting Files (`journal meeting [title]`)
22
- - **Purpose**: Structured meeting notes with agenda and action items
23
- - **File structure**: `YYYY/meetings/YYYY-MM-DD-HHMM-meeting-[title].md`
24
- - **Template**: Includes Date, Time, Attendees, Agenda, Notes, Action Items, and Follow-up sections
25
- - **Use case**: Meeting preparation, note-taking, and action item tracking
17
+ ```bash
18
+ claude plugin marketplace add ChrisTowles/towles-tool
19
+ claude plugin install tt@towles-tool
20
+ ```
26
21
 
27
- ### Note Files (`journal note [title]`)
28
- - **Purpose**: General-purpose notes with structured sections
29
- - **File structure**: `YYYY/notes/YYYY-MM-DD-HHMM-note-[title].md`
30
- - **Template**: Includes Summary, Details, and References sections
31
- - **Use case**: Research notes, documentation, general information capture
22
+ ### From Source
32
23
 
33
- ### Commands
34
- - `journal` or `journal daily-notes` - Create/open current week's daily notes
35
- - `journal meeting [title]` - Create a new meeting file with optional title
36
- - `journal note [title]` - Create a new note file with optional title
24
+ ```bash
25
+ git clone https://github.com/ChrisTowles/towles-tool.git
26
+ cd towles-tool
27
+ pnpm install
28
+ pnpm start # Run directly with tsx
29
+ ```
37
30
 
38
- ## Tools to add
39
- - [x] Journal system - creates and opens markdown files with templates for daily-notes, meetings, and notes
40
- - [ ] use claude code to generate git commits with multiple options for the commit message.
31
+ ## CLI Commands
41
32
 
42
- ## Install from repository
33
+ ### Ralph (autonomous runner)
43
34
 
44
- ```bash
45
- pnpm add --global @towles/tool
35
+ | Command | Description |
36
+ | --------------------------- | --------------------------------------------- |
37
+ | `tt ralph plan add <desc>` | Add task to plan |
38
+ | `tt ralph plan list` | View tasks |
39
+ | `tt ralph plan done <id>` | Mark task complete |
40
+ | `tt ralph plan remove <id>` | Remove task |
41
+ | `tt ralph run` | Run autonomous loop (auto-commits by default) |
42
+ | `tt ralph show` | Show plan with mermaid graph |
46
43
 
47
- ## followed by
48
- tt
44
+ ### Observability
49
45
 
50
- # or
51
- towles-tool
52
- ```
46
+ | Command | Description |
47
+ | ------------------------- | ------------------------------------- |
48
+ | `tt graph` | Generate HTML treemap of all sessions |
49
+ | `tt graph --session <id>` | Single session treemap |
50
+ | `tt graph --open` | Auto-open in browser |
53
51
 
54
- ## Unisntall
52
+ Treemap colors indicate input/output token ratio: green <2:1, yellow 2-5:1, red >5:1.
55
53
 
56
- ```bash
57
- pnpm remove --global @towles/tool
58
- ```
54
+ ### Git
59
55
 
60
- ## If command not found
56
+ | Command | Alias | Description |
57
+ | -------------------- | ------- | ------------------------------- |
58
+ | `tt gh branch` | | Create branch from GitHub issue |
59
+ | `tt gh pr` | `tt pr` | Create pull request |
60
+ | `tt gh branch-clean` | | Delete merged branches |
61
61
 
62
- try running with pnpm
63
- ```bash
64
- pnpm tt
65
- ```
62
+ ### Journaling
63
+
64
+ | Command | Alias | Description |
65
+ | ------------------------ | ---------- | -------------------------------- |
66
+ | `tt journal daily-notes` | `tt today` | Weekly files with daily sections |
67
+ | `tt journal meeting` | `tt m` | Meeting notes |
68
+ | `tt journal note` | `tt n` | General notes |
66
69
 
67
- if that works, then you need to add the pnpm global bin directory to your PATH.
68
-
69
- ## packages to consider
70
- - [@anthropic-ai/claude-code](https://github.com/anthropic-ai/claude-code) - A library for interacting with the Claude code
71
- - [zod](https://github.com/colinhacks/zod) - TypeScript-first schema validation
72
- - [Consola](https://github.com/unjs/consola) console wrapper and colors
73
- - ~~[c12](https://github.com/unjs/c12) configuration loader and utilities~~
74
- - referted stayed to json config
75
- - [rolldown-vite](https://voidzero.dev/posts/announcing-rolldown-vite) - A Vite plugin for rolling down your code
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
- - [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)
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.
80
- - ~~[ink](https://github.com/vadimdemedes/ink) - React for interactive command-line apps~~
81
- - wanted hotkey support and more complex UI but this was overkill for this project.
82
- - [publint](https://publint.dev/)
83
- - [e18e.dev](https://e18e.dev/guide/resources.html)
84
-
85
- ## Document verbose and debug options
70
+ ### Utilities
71
+
72
+ | Command | Alias | Description |
73
+ | ------------ | -------- | ------------------------------ |
74
+ | `tt config` | `tt cfg` | Show configuration |
75
+ | `tt doctor` | | Check dependencies |
76
+ | `tt install` | | Configure Claude Code settings |
77
+
78
+ ## Claude Code Plugin Skills
79
+
80
+ Available via `/tt:<command>`:
81
+
82
+ | Command | Description |
83
+ | ------------- | --------------------------------------------- |
84
+ | `/tt:commit` | AI-powered conventional commit messages |
85
+ | `/tt:plan` | Interview user and create implementation plan |
86
+ | `/tt:improve` | Explore codebase and suggest improvements |
87
+ | `/tt:refine` | Fix grammar/spelling in files |
88
+
89
+ ## Development
86
90
 
87
91
  ```bash
88
- export DEBUG=1
92
+ pnpm start # Run CLI with tsx
93
+ pnpm test # Run tests
94
+ pnpm lint # Run oxlint
95
+ pnpm format # Format with oxfmt
96
+ pnpm typecheck # Type check
89
97
  ```
90
98
 
91
- TODO add verbose option.
99
+ ### Releasing
92
100
 
93
- ## Development
101
+ ```bash
102
+ gh workflow run release.yml -f bump_type=patch # or minor/major
103
+ gh run watch
104
+ ```
94
105
 
95
- For information on how releases are managed, see the [Release Process](docs/release-process.md) documentation.
106
+ ## Resources
96
107
 
97
- ## History
108
+ ### Claude Code Plugin Development
98
109
 
99
- I'm using a lot of inspiration from [Anthony Fu](https://github.com/antfu) for this projects codebase.
110
+ - [Claude Code Plugins Announcement](https://www.anthropic.com/news/claude-code-plugins)
111
+ - [Official Claude Code Plugins](https://github.com/anthropics/claude-code/tree/main/plugins)
112
+ - [Skills Guide](https://docs.claude.com/en/api/skills-guide)
113
+ - [Best Practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices)
100
114
 
101
115
  ## License
102
116
 
103
117
  [MIT](./LICENSE) License © [Chris Towles](https://github.com/ChrisTowles)
104
-
105
- <!-- Badges -->
106
-
107
- [npm-version-src]: https://img.shields.io/npm/v/@towles/tool?style=flat&colorA=080f12&colorB=1fa669
108
- [npm-version-href]: https://npmjs.com/package/@towles/tool
109
- [npm-downloads-src]: https://img.shields.io/npm/dm/@towles/tool?style=flat&colorA=080f12&colorB=1fa669
110
- [npm-downloads-href]: https://npmjs.com/package/@towles/tool
111
- [bundle-src]: https://img.shields.io/bundlephobia/minzip/@towles/tool?style=flat&colorA=080f12&colorB=1fa669&label=minzip
112
- [bundle-href]: https://bundlephobia.com/result?p=@towles/tool
113
- [license-src]: https://img.shields.io/github/license/ChrisTowles/towles-tool.svg?style=flat&colorA=080f12&colorB=1fa669
114
- [license-href]: https://github.com/ChrisTowles/towles-tool/blob/main/LICENSE.md
115
- [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
116
- [jsdocs-href]: https://www.jsdocs.io/package/@towles/tool
package/bin/run.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { execute } from "@oclif/core";
4
+
5
+ await execute({ dir: import.meta.url });
package/package.json CHANGED
@@ -1,91 +1,111 @@
1
1
  {
2
2
  "name": "@towles/tool",
3
- "type": "module",
4
- "version": "0.0.20",
5
- "description": "One off quality of life scripts that I use on a daily basis.",
6
- "author": "Chris Towles <Chris.Towles.Dev@gmail.com>",
7
- "license": "MIT",
8
- "funding": {
9
- "url": "https://github.com/sponsors/ChrisTowles"
10
- },
3
+ "version": "0.0.48",
4
+ "description": "CLI tool with autonomous task runner (ralph), observability, and quality-of-life commands for daily development.",
5
+ "keywords": [
6
+ "autonomic",
7
+ "claude",
8
+ "cli",
9
+ "git",
10
+ "journal",
11
+ "oclif",
12
+ "ralph"
13
+ ],
11
14
  "homepage": "https://github.com/ChrisTowles/towles-tool#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/ChrisTowles/towles-tool/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Chris Towles <Chris.Towles.Dev@gmail.com>",
12
20
  "repository": {
13
21
  "type": "git",
14
22
  "url": "git+https://github.com/ChrisTowles/towles-tool.git"
15
23
  },
16
- "bugs": {
17
- "url": "https://github.com/ChrisTowles/towles-tool/issues"
18
- },
19
- "keywords": [],
20
- "sideEffects": false,
21
- "exports": {
22
- ".": "./dist/index.mjs",
23
- "./package.json": "./package.json"
24
+ "funding": {
25
+ "url": "https://github.com/sponsors/ChrisTowles"
24
26
  },
25
- "main": "./dist/index.mjs",
26
- "module": "./dist/index.mjs",
27
- "types": "./dist/index.d.mts",
28
27
  "bin": {
29
- "towles-tool": "./dist/index.mjs",
30
- "tt": "./dist/index.mjs"
28
+ "towles-tool": "./bin/run.ts",
29
+ "tt": "./bin/run.ts"
31
30
  },
32
31
  "files": [
33
- "dist"
32
+ "bin",
33
+ "patches",
34
+ "src"
34
35
  ],
36
+ "type": "module",
37
+ "main": "bin/run.ts",
38
+ "scripts": {
39
+ "start": "tsx ./bin/run.ts",
40
+ "typecheck": "tsc --noEmit",
41
+ "lint": "oxlint",
42
+ "lint:fix": "oxlint --fix",
43
+ "format": "oxfmt --write",
44
+ "format:check": "oxfmt --check",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest watch",
47
+ "prepare": "simple-git-hooks",
48
+ "version:sync": "tsx scripts/sync-versions.ts"
49
+ },
35
50
  "dependencies": {
36
- "@anthropic-ai/claude-code": "^1.0.51",
37
- "@anthropic-ai/sdk": "^0.56.0",
38
- "@clack/prompts": "^0.11.0",
39
- "comment-json": "^4.2.5",
51
+ "@oclif/core": "^4.3.16",
40
52
  "consola": "^3.4.2",
53
+ "d3-hierarchy": "^3.1.2",
41
54
  "fzf": "^0.5.2",
42
- "luxon": "^3.7.1",
43
- "neverthrow": "^8.2.0",
55
+ "globby": "^14.1.0",
56
+ "luxon": "^3.5.0",
57
+ "open": "^10.1.1",
58
+ "picocolors": "^1.1.1",
44
59
  "prompts": "^2.4.2",
45
- "strip-ansi": "^7.1.0",
46
- "tinyexec": "^0.3.2",
47
- "yargs": "^17.7.2",
48
- "zod": "^4.0.5"
60
+ "strip-ansi": "^7.1.2",
61
+ "tinyexec": "^1.0.2",
62
+ "zod": "^3.25.67"
49
63
  },
50
64
  "devDependencies": {
51
- "@antfu/ni": "^25.0.0",
52
- "@types/luxon": "^3.6.2",
53
- "@types/node": "^22.16.3",
65
+ "@oclif/test": "^4.1.13",
66
+ "@total-typescript/tsconfig": "^1.0.4",
67
+ "@tsconfig/strictest": "^2.0.5",
68
+ "@types/d3-hierarchy": "^3.1.7",
69
+ "@types/luxon": "^3.4.2",
70
+ "@types/node": "^22.10.10",
54
71
  "@types/prompts": "^2.4.9",
55
- "@types/yargs": "^17.0.32",
56
- "bumpp": "^10.2.0",
57
- "lint-staged": "^15.5.2",
58
- "oxlint": "^1.7.0",
59
- "simple-git-hooks": "^2.13.0",
60
- "tsx": "^4.20.3",
72
+ "bumpp": "^10.4.0",
73
+ "lint-staged": "^15.5.1",
74
+ "oxfmt": "^0.24.0",
75
+ "oxlint": "^1.2.0",
76
+ "simple-git-hooks": "^2.11.1",
77
+ "tsx": "^4.19.2",
61
78
  "typescript": "^5.8.3",
62
- "unbuild": "^3.5.0",
63
- "vite": "^6.3.5",
64
- "vitest": "^3.2.4",
65
- "vitest-package-exports": "^0.1.1",
66
- "yaml": "^2.8.0"
79
+ "vitest": "^3.1.3"
67
80
  },
68
81
  "simple-git-hooks": {
69
- "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged"
82
+ "pre-commit": "pnpm lint-staged && pnpm typecheck"
70
83
  },
71
84
  "lint-staged": {
72
- "*": "oxlint --fix"
85
+ "package.json": "oxfmt --write",
86
+ "*.{ts,tsx,mts,cts,js,cjs,mjs}": [
87
+ "oxfmt --write",
88
+ "oxlint --fix"
89
+ ],
90
+ "*.{json,md,yaml,yml}": "oxfmt --write"
73
91
  },
74
- "directories": {
75
- "test": "test"
92
+ "oclif": {
93
+ "bin": "tt",
94
+ "commands": {
95
+ "strategy": "pattern",
96
+ "target": "./src/commands"
97
+ },
98
+ "dirname": "towles-tool",
99
+ "plugins": [],
100
+ "topicSeparator": " "
76
101
  },
77
- "scripts": {
78
- "build": "unbuild",
79
- "dev": "unbuild --stub",
80
- "lint": "oxlint",
81
- "lint:fix": "oxlint --fix",
82
- "lint:fix_all": "oxlint --fix .",
83
- "lint:package": "pnpm dlx publint --fix && pnpm dlx knip",
84
- "release:local": "bumpp && pnpm publish --no-git-checks -r --access public",
85
- "release": "bumpp && echo \"github action will run and publish to npm\"",
86
- "start": "tsx src/index.ts",
87
- "test": "vitest --run",
88
- "test:watch": "CI=DisableCallingClaude vitest --watch",
89
- "typecheck": "tsc --noEmit"
102
+ "engines": {
103
+ "node": ">=18.0.0"
104
+ },
105
+ "packageManager": "pnpm@10.11.0",
106
+ "pnpm": {
107
+ "patchedDependencies": {
108
+ "prompts@2.4.2": "patches/prompts.patch"
109
+ }
90
110
  }
91
- }
111
+ }
@@ -0,0 +1,34 @@
1
+ # prompts patch
2
+ #
3
+ # Used by: src/commands/gh/branch.ts (autocomplete with fzf fuzzy filtering)
4
+ #
5
+ # Fix: On exit (Ctrl+C/Ctrl+D), reject the promise instead of resolving.
6
+ # Without this, exiting a prompt resolves with undefined and code continues
7
+ # as if user submitted empty input. This lets us catch cancellation properly.
8
+
9
+ diff --git a/dist/prompts.js b/dist/prompts.js
10
+ index 31f2648a7d215d61aff736424a6f2d66d07e3273..2b8a0ac36fd22ed17f6096ce4f52a4db95ba2633 100644
11
+ --- a/dist/prompts.js
12
+ +++ b/dist/prompts.js
13
+ @@ -14,7 +14,7 @@ function toPrompt(type, args, opts = {}) {
14
+ const onExit = opts.onExit || noop;
15
+ p.on('state', args.onState || noop);
16
+ p.on('submit', x => res(onSubmit(x)));
17
+ - p.on('exit', x => res(onExit(x)));
18
+ + p.on('exit', x => rej(onExit(x)));
19
+ p.on('abort', x => rej(onAbort(x)));
20
+ });
21
+ }
22
+ diff --git a/lib/prompts.js b/lib/prompts.js
23
+ index 9f625564601da8f79040698de197a6ff2fec3859..e08923fc9f66ab0700788a62455da89ad23c5f35 100644
24
+ --- a/lib/prompts.js
25
+ +++ b/lib/prompts.js
26
+ @@ -11,7 +11,7 @@ function toPrompt(type, args, opts={}) {
27
+ const onExit = opts.onExit || noop;
28
+ p.on('state', args.onState || noop);
29
+ p.on('submit', x => res(onSubmit(x)));
30
+ - p.on('exit', x => res(onExit(x)));
31
+ + p.on('exit', x => rej(onExit(x)));
32
+ p.on('abort', x => rej(onAbort(x)));
33
+ });
34
+ }
@@ -0,0 +1,27 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+ import type { SettingsFile } from "../config/settings.js";
3
+ import { loadSettings } from "../config/settings.js";
4
+
5
+ /**
6
+ * Base command that all towles-tool commands extend.
7
+ * Provides shared functionality like settings loading and debug flag.
8
+ */
9
+ export abstract class BaseCommand extends Command {
10
+ static baseFlags = {
11
+ debug: Flags.boolean({
12
+ char: "d",
13
+ description: "Enable debug output",
14
+ default: false,
15
+ }),
16
+ };
17
+
18
+ protected settings!: SettingsFile;
19
+
20
+ /**
21
+ * Called before run(). Loads user settings.
22
+ */
23
+ async init(): Promise<void> {
24
+ await super.init();
25
+ this.settings = await loadSettings();
26
+ }
27
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Integration tests for oclif config command
3
+ * Note: consola outputs to stderr with different log levels
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { runCommand } from "@oclif/test";
7
+
8
+ describe("config command", () => {
9
+ it("runs config and outputs settings info", async () => {
10
+ const { stderr } = await runCommand(["config"]);
11
+ // consola.warn outputs captured in stderr
12
+ expect(stderr).toContain("User Config");
13
+ expect(stderr).toContain("Working Directory");
14
+ });
15
+ });
@@ -0,0 +1,44 @@
1
+ import consola from "consola";
2
+ import { BaseCommand } from "./base.js";
3
+
4
+ /**
5
+ * Display current configuration settings
6
+ */
7
+ export default class Config extends BaseCommand {
8
+ static override description = "Display current configuration settings";
9
+
10
+ static override examples = [
11
+ { description: "Display configuration", command: "<%= config.bin %> <%= command.id %>" },
12
+ { description: "Use alias", command: "<%= config.bin %> cfg" },
13
+ ];
14
+
15
+ async run(): Promise<void> {
16
+ await this.parse(Config);
17
+
18
+ consola.info("Configuration");
19
+ consola.log("");
20
+
21
+ consola.info(`Settings File: ${this.settings.path}`);
22
+ consola.log("");
23
+
24
+ consola.warn("User Config:");
25
+ consola.log(
26
+ ` Daily Path Template: ${this.settings.settings.journalSettings.dailyPathTemplate}`,
27
+ );
28
+ consola.log(
29
+ ` Meeting Path Template: ${this.settings.settings.journalSettings.meetingPathTemplate}`,
30
+ );
31
+ consola.log(` Note Path Template: ${this.settings.settings.journalSettings.notePathTemplate}`);
32
+ consola.log(` Editor: ${this.settings.settings.preferredEditor}`);
33
+ consola.log("");
34
+
35
+ consola.warn("Working Directory:");
36
+ consola.log(` ${process.cwd()}`);
37
+ consola.log("");
38
+
39
+ consola.info("Shell Completions:");
40
+ consola.log(" Run `tt completion` to generate shell completions");
41
+ consola.log(" Bash/Zsh: tt completion >> ~/.bashrc (or ~/.zshrc)");
42
+ consola.log(" Fish: tt completion > ~/.config/fish/completions/tt.fish");
43
+ }
44
+ }
@@ -0,0 +1,136 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { x } from "tinyexec";
4
+ import pc from "picocolors";
5
+ import { BaseCommand } from "./base.js";
6
+
7
+ interface CheckResult {
8
+ name: string;
9
+ version: string | null;
10
+ ok: boolean;
11
+ warning?: string;
12
+ }
13
+
14
+ /**
15
+ * Check system dependencies and environment
16
+ */
17
+ export default class Doctor extends BaseCommand {
18
+ static override description = "Check system dependencies and environment";
19
+
20
+ static override examples = [
21
+ { description: "Check system dependencies", command: "<%= config.bin %> <%= command.id %>" },
22
+ { description: "Verify environment after setup", command: "<%= config.bin %> doctor" },
23
+ ];
24
+
25
+ async run(): Promise<void> {
26
+ await this.parse(Doctor);
27
+
28
+ this.log("Checking dependencies...\n");
29
+
30
+ const checks: CheckResult[] = await Promise.all([
31
+ this.checkCommand("git", ["--version"], /git version ([\d.]+)/),
32
+ this.checkCommand("gh", ["--version"], /gh version ([\d.]+)/),
33
+ this.checkCommand("node", ["--version"], /v?([\d.]+)/),
34
+ this.checkCommand("bun", ["--version"], /([\d.]+)/),
35
+ ]);
36
+
37
+ // Display results
38
+ for (const check of checks) {
39
+ const icon = check.ok ? pc.green("✓") : pc.red("✗");
40
+ const version = check.version ?? "not found";
41
+ this.log(`${icon} ${check.name}: ${version}`);
42
+ if (check.warning) {
43
+ this.log(` ${pc.yellow("⚠")} ${check.warning}`);
44
+ }
45
+ }
46
+
47
+ // Check gh auth
48
+ this.log("");
49
+ const ghAuth = await this.checkGhAuth();
50
+ const authIcon = ghAuth.ok ? pc.green("✓") : pc.yellow("⚠");
51
+ this.log(`${authIcon} gh auth: ${ghAuth.ok ? "authenticated" : "not authenticated"}`);
52
+ if (!ghAuth.ok) {
53
+ this.log(` ${pc.dim("Run: gh auth login")}`);
54
+ }
55
+
56
+ // Node version check
57
+ const nodeCheck = checks.find((c) => c.name === "node");
58
+ if (nodeCheck?.version) {
59
+ const major = Number.parseInt(nodeCheck.version.split(".")[0], 10);
60
+ if (major < 18) {
61
+ this.log("");
62
+ this.log(`${pc.yellow("⚠")} Node.js 18+ recommended (found ${nodeCheck.version})`);
63
+ }
64
+ }
65
+
66
+ // Check ralph files in .gitignore
67
+ this.log("");
68
+ const gitignoreCheck = this.checkRalphGitignore();
69
+ const gitignoreIcon = gitignoreCheck.ok ? pc.green("✓") : pc.yellow("⚠");
70
+ this.log(
71
+ `${gitignoreIcon} .gitignore: ${gitignoreCheck.ok ? "ralph-* excluded" : "ralph-* NOT excluded"}`,
72
+ );
73
+ if (!gitignoreCheck.ok) {
74
+ this.log(` ${pc.dim('Add "ralph-*" to .gitignore to exclude local ralph state files')}`);
75
+ }
76
+
77
+ // Summary
78
+ const allOk = checks.every((c) => c.ok) && ghAuth.ok && gitignoreCheck.ok;
79
+ this.log("");
80
+ if (allOk) {
81
+ this.log(pc.green("All checks passed!"));
82
+ } else {
83
+ this.log(pc.yellow("Some checks failed. See above for details."));
84
+ }
85
+ }
86
+
87
+ private async checkCommand(
88
+ name: string,
89
+ args: string[],
90
+ versionPattern: RegExp,
91
+ ): Promise<CheckResult> {
92
+ try {
93
+ // tinyexec is safe - uses execFile internally, no shell injection risk
94
+ const result = await x(name, args);
95
+ const output = result.stdout + result.stderr;
96
+ const match = output.match(versionPattern);
97
+ return {
98
+ name,
99
+ version: match?.[1] ?? output.trim().slice(0, 20),
100
+ ok: true,
101
+ };
102
+ } catch {
103
+ return { name, version: null, ok: false };
104
+ }
105
+ }
106
+
107
+ private async checkGhAuth(): Promise<{ ok: boolean }> {
108
+ try {
109
+ // tinyexec is safe - uses execFile internally, no shell injection risk
110
+ const result = await x("gh", ["auth", "status"]);
111
+ return { ok: result.exitCode === 0 };
112
+ } catch {
113
+ return { ok: false };
114
+ }
115
+ }
116
+
117
+ private checkRalphGitignore(): { ok: boolean } {
118
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
119
+ try {
120
+ if (!fs.existsSync(gitignorePath)) {
121
+ return { ok: false };
122
+ }
123
+ const content = fs.readFileSync(gitignorePath, "utf-8");
124
+ // Check for ralph-* pattern or specific ralph files
125
+ const hasRalphPattern = content.split("\n").some((line) => {
126
+ const trimmed = line.trim();
127
+ return (
128
+ trimmed === "ralph-*" || trimmed === "ralph-*.json" || trimmed === "ralph-state.json"
129
+ );
130
+ });
131
+ return { ok: hasRalphPattern };
132
+ } catch {
133
+ return { ok: false };
134
+ }
135
+ }
136
+ }