ttrak 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(tree:*)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,24 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: oven-sh/setup-bun@v2
17
+ with:
18
+ bun-version: latest
19
+
20
+ - run: bun install
21
+
22
+ - run: bun publish --tolerate-republish --provenance
23
+ env:
24
+ NPM_CONFIG_PROVENANCE: true
package/AGENTS.md ADDED
@@ -0,0 +1,151 @@
1
+ # AGENTS.md
2
+
3
+ This guide is for agentic coding assistants working in this repository.
4
+
5
+ ## Build Commands
6
+
7
+ ### Development
8
+
9
+ - `bun dev` - Run in watch mode (builds schema, runs src/index.ts)
10
+ - `bun build` - Build the project (generates schemas only)
11
+
12
+ ### Running Scripts
13
+
14
+ - `bun run <script.ts>` - Execute any TypeScript file directly
15
+ - `bun run build:schema` - Generate JSON schemas from Zod schemas
16
+
17
+ ### Testing
18
+
19
+ - No test framework configured yet
20
+ - Manual testing: `bun run test-store.ts` to test data store loading
21
+
22
+ ## Code Style Guidelines
23
+
24
+ ### Import Conventions
25
+
26
+ - Use ES module syntax: `import { x } from "./path"`
27
+ - Type-only imports use `type` keyword: `import type { Task } from "../schema"`
28
+ - Relative imports with `./` and `../` (no path aliases like `@/`)
29
+ - Group external packages first, then internal modules
30
+
31
+ ### TypeScript Configuration
32
+
33
+ - Strict mode enabled with all strict checks
34
+ - `verbatimModuleSyntax: true` - explicit type imports required
35
+ - `noUncheckedIndexedAccess: true` - safer array/object access
36
+ - Type definitions inferred from Zod schemas: `export type Task = z.infer<typeof TaskSchema>`
37
+
38
+ ### Formatting
39
+
40
+ - 2-space indentation
41
+ - No semicolons
42
+ - No trailing whitespace
43
+ - Consistent spacing around operators
44
+ - Multiline objects/arrays aligned visually
45
+ - Avoid unnecessary comments - code should be self-documenting
46
+
47
+ ### Naming Conventions
48
+
49
+ - Classes: `PascalCase` (e.g., `TaskItem`, `TaskListView`)
50
+ - Interfaces: `PascalCase` (e.g., `Theme`, `TaskModalResult`)
51
+ - Types: `PascalCase` (e.g., `Task`, `DataStore`)
52
+ - Functions/Methods: `camelCase` (e.g., `loadDataStore`, `handleKeyPress`)
53
+ - Variables: `camelCase` (e.g., `selectedIndex`, `activeFilter`)
54
+ - Constants: `SCREAMING_SNAKE_CASE` (e.g., `CONFIG_DIR`)
55
+ - Private members: prefix with `#` (private fields) or keep private conventionally
56
+
57
+ ### File Structure
58
+
59
+ ```
60
+ src/
61
+ schema.ts # Zod schemas and type definitions
62
+ index.ts # Entry point
63
+ data/ # Data layer (store, persistence)
64
+ ui/
65
+ theme.ts # Theme configuration
66
+ components/ # Reusable UI components
67
+ views/ # Screen/page components
68
+ scripts/ # Build scripts
69
+ ```
70
+
71
+ ### Error Handling
72
+
73
+ - Use try/catch for file I/O operations
74
+ - Use `safeParse()` for Zod schema validation, return defaults on failure
75
+ - Log errors with `console.error()` for invalid data
76
+ - Don't let errors crash the app - provide sensible fallbacks
77
+
78
+ ### UI Component Patterns
79
+
80
+ - Class-based components with private properties
81
+ - Constructor takes dependencies (renderer, theme, store, callbacks)
82
+ - `destroy()` method for cleanup (remove event listeners, destroy renderables)
83
+ - Event handlers bound in constructor: `this.boundHandler = this.method.bind(this)`
84
+ - Use event emitter pattern: `renderer.keyInput.on("keypress", this.boundHandler)`
85
+ - Remember to cleanup in destroy(): `renderer.keyInput.off("keypress", this.boundHandler)`
86
+
87
+ ### State Management
88
+
89
+ - State stored in private class properties
90
+ - Modify state, then call `saveAndRender()` or `renderTasks()`
91
+ - Use immutable patterns where possible
92
+ - `saveDataStore()` persists to ~/.config/ttrak/data.json
93
+
94
+ ### Schema-Driven Development
95
+
96
+ - Define all data structures in src/schema.ts using Zod
97
+ - Use `.default()` for default values in schema definitions
98
+ - After modifying schemas, regenerate JSON schemas: `bun run build:schema`
99
+ - Types inferred from schemas ensure type safety throughout the codebase
100
+
101
+ ### Specific Patterns
102
+
103
+ #### Zod Schema Definitions
104
+
105
+ ```typescript
106
+ export const TaskSchema = z.object({
107
+ id: z.string().regex(/^(LOCAL|[A-Z]+)-\d+$/),
108
+ title: z.string().min(1).max(200),
109
+ // ...
110
+ });
111
+ export type Task = z.infer<typeof TaskSchema>;
112
+ ```
113
+
114
+ #### Data Loading
115
+
116
+ ```typescript
117
+ const result = DataStoreSchema.safeParse(data);
118
+ if (!result.success) {
119
+ console.error("Invalid data:", result.error);
120
+ return DataStoreSchema.parse({});
121
+ }
122
+ return result.data;
123
+ ```
124
+
125
+ #### Component Event Handlers
126
+
127
+ ```typescript
128
+ private boundHandler: (key: KeyEvent) => void;
129
+
130
+ constructor() {
131
+ this.boundHandler = this.handleKeyPress.bind(this);
132
+ this.renderer.keyInput.on("keypress", this.boundHandler);
133
+ }
134
+
135
+ destroy() {
136
+ this.renderer.keyInput.off("keypress", this.boundHandler);
137
+ }
138
+ ```
139
+
140
+ ## Key Dependencies
141
+
142
+ - `@opentui/core` - TUI framework (renderables, event handling)
143
+ - `zod` - Schema validation and type inference
144
+ - `@catppuccin/palette` - Theme colors
145
+ - `bun` - Runtime and standard library (file I/O, etc.)
146
+
147
+ ## Configuration
148
+
149
+ - Data stored in `~/.config/ttrak/data.json`
150
+ - Config in `~/.config/ttrak/config.json`
151
+ - JSON schemas auto-generated for IDE validation
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # core
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun dev
13
+ ```
14
+
15
+ This project was created using `bun create tui`. [create-tui](https://git.new/create-tui) is the easiest way to get started with OpenTUI.
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "default": "./config.schema.json",
7
+ "type": "string"
8
+ },
9
+ "version": {
10
+ "default": 1,
11
+ "type": "number"
12
+ },
13
+ "linear": {
14
+ "type": "object",
15
+ "properties": {
16
+ "apiKey": {
17
+ "type": "string"
18
+ },
19
+ "teamId": {
20
+ "type": "string"
21
+ }
22
+ },
23
+ "required": [
24
+ "apiKey"
25
+ ],
26
+ "additionalProperties": false
27
+ },
28
+ "github": {
29
+ "type": "object",
30
+ "properties": {
31
+ "token": {
32
+ "type": "string"
33
+ },
34
+ "repo": {
35
+ "type": "string"
36
+ }
37
+ },
38
+ "required": [
39
+ "token"
40
+ ],
41
+ "additionalProperties": false
42
+ },
43
+ "theme": {
44
+ "default": {
45
+ "mode": "auto",
46
+ "flavor": "mocha"
47
+ },
48
+ "type": "object",
49
+ "properties": {
50
+ "mode": {
51
+ "default": "auto",
52
+ "type": "string",
53
+ "enum": [
54
+ "auto",
55
+ "catppuccin",
56
+ "system"
57
+ ]
58
+ },
59
+ "flavor": {
60
+ "default": "mocha",
61
+ "type": "string",
62
+ "enum": [
63
+ "latte",
64
+ "frappe",
65
+ "macchiato",
66
+ "mocha"
67
+ ]
68
+ },
69
+ "overrides": {
70
+ "type": "object",
71
+ "propertyNames": {
72
+ "type": "string"
73
+ },
74
+ "additionalProperties": {
75
+ "type": "string"
76
+ }
77
+ }
78
+ },
79
+ "required": [
80
+ "mode",
81
+ "flavor"
82
+ ],
83
+ "additionalProperties": false
84
+ },
85
+ "defaultView": {
86
+ "default": "all",
87
+ "type": "string",
88
+ "enum": [
89
+ "all",
90
+ "todo",
91
+ "inProgress",
92
+ "done"
93
+ ]
94
+ }
95
+ },
96
+ "required": [
97
+ "$schema",
98
+ "version",
99
+ "theme",
100
+ "defaultView"
101
+ ],
102
+ "additionalProperties": false
103
+ }
@@ -0,0 +1,144 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "default": "./data.schema.json",
7
+ "type": "string"
8
+ },
9
+ "version": {
10
+ "default": 1,
11
+ "type": "number"
12
+ },
13
+ "tasks": {
14
+ "default": [],
15
+ "type": "array",
16
+ "items": {
17
+ "type": "object",
18
+ "properties": {
19
+ "id": {
20
+ "type": "string",
21
+ "pattern": "^(LOCAL|[A-Z]+)-\\d+$"
22
+ },
23
+ "title": {
24
+ "type": "string",
25
+ "minLength": 1,
26
+ "maxLength": 200
27
+ },
28
+ "description": {
29
+ "type": "string"
30
+ },
31
+ "status": {
32
+ "type": "string",
33
+ "enum": [
34
+ "todo",
35
+ "inProgress",
36
+ "done",
37
+ "cancelled"
38
+ ]
39
+ },
40
+ "priority": {
41
+ "type": "string",
42
+ "enum": [
43
+ "none",
44
+ "low",
45
+ "medium",
46
+ "high",
47
+ "urgent"
48
+ ]
49
+ },
50
+ "createdAt": {
51
+ "type": "string"
52
+ },
53
+ "updatedAt": {
54
+ "type": "string"
55
+ },
56
+ "linear": {
57
+ "type": "object",
58
+ "properties": {
59
+ "id": {
60
+ "type": "string"
61
+ },
62
+ "url": {
63
+ "type": "string"
64
+ },
65
+ "syncedAt": {
66
+ "type": "string"
67
+ }
68
+ },
69
+ "required": [
70
+ "id",
71
+ "url",
72
+ "syncedAt"
73
+ ],
74
+ "additionalProperties": false
75
+ },
76
+ "github": {
77
+ "type": "object",
78
+ "properties": {
79
+ "type": {
80
+ "type": "string",
81
+ "enum": [
82
+ "issue",
83
+ "pr"
84
+ ]
85
+ },
86
+ "number": {
87
+ "type": "number"
88
+ },
89
+ "url": {
90
+ "type": "string"
91
+ },
92
+ "syncedAt": {
93
+ "type": "string"
94
+ }
95
+ },
96
+ "required": [
97
+ "type",
98
+ "number",
99
+ "url",
100
+ "syncedAt"
101
+ ],
102
+ "additionalProperties": false
103
+ },
104
+ "tags": {
105
+ "type": "array",
106
+ "items": {
107
+ "type": "string"
108
+ }
109
+ },
110
+ "dueDate": {
111
+ "type": "string"
112
+ }
113
+ },
114
+ "required": [
115
+ "id",
116
+ "title",
117
+ "status",
118
+ "priority",
119
+ "createdAt",
120
+ "updatedAt"
121
+ ],
122
+ "additionalProperties": false
123
+ }
124
+ },
125
+ "lastSync": {
126
+ "default": null,
127
+ "anyOf": [
128
+ {
129
+ "type": "string"
130
+ },
131
+ {
132
+ "type": "null"
133
+ }
134
+ ]
135
+ }
136
+ },
137
+ "required": [
138
+ "$schema",
139
+ "version",
140
+ "tasks",
141
+ "lastSync"
142
+ ],
143
+ "additionalProperties": false
144
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "ttrak",
3
+ "version": "0.1.0",
4
+ "description": "A terminal task tracker",
5
+ "module": "src/index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "ttrak": "./src/index.ts"
9
+ },
10
+ "scripts": {
11
+ "build:schema": "bun run scripts/generate-schema.ts",
12
+ "dev": "bun run build:schema && bun run --watch src/index.ts",
13
+ "build": "bun run build:schema",
14
+ "prepublishOnly": "bun run build"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "latest"
18
+ },
19
+ "peerDependencies": {
20
+ "typescript": "^5"
21
+ },
22
+ "dependencies": {
23
+ "@catppuccin/palette": "^1.7.1",
24
+ "@opentui/core": "^0.1.63",
25
+ "zod": "^4.2.1"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/emots/ttrak.git"
33
+ },
34
+ "keywords": ["terminal", "task", "tracker", "tui", "cli"],
35
+ "author": "",
36
+ "license": "MIT"
37
+ }
@@ -0,0 +1,10 @@
1
+ import { DataStoreSchema, ConfigStoreSchema } from "../src/schema";
2
+
3
+ const dataJsonSchema = DataStoreSchema.toJSONSchema();
4
+ const configJsonSchema = ConfigStoreSchema.toJSONSchema();
5
+ Bun.write("data.schema.json", JSON.stringify(dataJsonSchema, null, 2));
6
+
7
+ Bun.write("config.schema.json", JSON.stringify(configJsonSchema, null, 2));
8
+
9
+ console.log("✓ Generated data.schema.json");
10
+ console.log("✓ Generated config.schema.json");
@@ -0,0 +1,86 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { mkdir } from "fs/promises";
4
+ import {
5
+ DataStoreSchema,
6
+ ConfigStoreSchema,
7
+ type DataStore,
8
+ type ConfigStore,
9
+ } from "../schema";
10
+
11
+ const CONFIG_DIR = join(homedir(), ".config", "ttrak");
12
+ const DATA_PATH = join(CONFIG_DIR, "data.json");
13
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
+
15
+ async function ensureConfigDir() {
16
+ await mkdir(CONFIG_DIR, { recursive: true });
17
+ }
18
+
19
+ export async function loadDataStore(): Promise<DataStore> {
20
+ await ensureConfigDir();
21
+
22
+ try {
23
+ const raw = await Bun.file(DATA_PATH).text();
24
+ const data = JSON.parse(raw);
25
+ const result = DataStoreSchema.safeParse(data);
26
+
27
+ if (!result.success) {
28
+ console.error("Invalid data store:", result.error);
29
+ return DataStoreSchema.parse({});
30
+ }
31
+
32
+ return result.data;
33
+ } catch (error) {
34
+ return DataStoreSchema.parse({});
35
+ }
36
+ }
37
+
38
+ export async function saveDataStore(store: DataStore): Promise<void> {
39
+ await ensureConfigDir();
40
+
41
+ const { $schema, ...rest } = store;
42
+ const output = {
43
+ $schema: "./data.schema.json",
44
+ ...rest,
45
+ };
46
+
47
+ await Bun.write(DATA_PATH, JSON.stringify(output, null, 2));
48
+ }
49
+
50
+ export async function loadConfigStore(): Promise<ConfigStore> {
51
+ await ensureConfigDir();
52
+
53
+ try {
54
+ const raw = await Bun.file(CONFIG_PATH).text();
55
+ const data = JSON.parse(raw);
56
+ const result = ConfigStoreSchema.safeParse(data);
57
+
58
+ if (!result.success) {
59
+ console.error("Invalid config store:", result.error);
60
+ return ConfigStoreSchema.parse({});
61
+ }
62
+
63
+ return result.data;
64
+ } catch (error) {
65
+ return ConfigStoreSchema.parse({});
66
+ }
67
+ }
68
+
69
+ export async function saveConfigStore(store: ConfigStore): Promise<void> {
70
+ await ensureConfigDir();
71
+
72
+ const output = {
73
+ ...store,
74
+ $schema: "./config.schema.json",
75
+ };
76
+
77
+ await Bun.write(CONFIG_PATH, JSON.stringify(output, null, 2));
78
+ }
79
+
80
+ export function getDataPath(): string {
81
+ return DATA_PATH;
82
+ }
83
+
84
+ export function getConfigPath(): string {
85
+ return CONFIG_PATH;
86
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bun
2
+ import { createCliRenderer } from "@opentui/core";
3
+ import { loadDataStore, loadConfigStore } from "./data/store";
4
+ import { getTheme } from "./ui/theme";
5
+ import { TaskListView } from "./ui/views/TaskListView";
6
+
7
+ async function main() {
8
+ const renderer = await createCliRenderer({
9
+ exitOnCtrlC: true,
10
+ targetFps: 30,
11
+ });
12
+
13
+ const [dataStore, configStore] = await Promise.all([
14
+ loadDataStore(),
15
+ loadConfigStore(),
16
+ ]);
17
+
18
+ const theme = await getTheme(renderer, configStore.theme);
19
+ renderer.setBackgroundColor(theme.bg);
20
+
21
+ const view = new TaskListView(renderer, theme, dataStore, () => {
22
+ view.destroy();
23
+ renderer.destroy();
24
+ });
25
+ }
26
+
27
+ main().catch(console.error);
package/src/schema.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+
3
+ export const TaskSchema = z.object({
4
+ id: z.string().regex(/^(LOCAL|[A-Z]+)-\d+$/),
5
+ title: z.string().min(1).max(200),
6
+ description: z.string().optional(),
7
+ status: z.enum(["todo", "inProgress", "done", "cancelled"]),
8
+ priority: z.enum(["none", "low", "medium", "high", "urgent"]),
9
+ createdAt: z.string(),
10
+ updatedAt: z.string(),
11
+ linear: z
12
+ .object({
13
+ id: z.string(),
14
+ url: z.string(),
15
+ syncedAt: z.string(),
16
+ })
17
+ .optional(),
18
+ github: z
19
+ .object({
20
+ type: z.enum(["issue", "pr"]),
21
+ number: z.number(),
22
+ url: z.string(),
23
+ syncedAt: z.string(),
24
+ })
25
+ .optional(),
26
+ tags: z.array(z.string()).optional(),
27
+ dueDate: z.string().optional(),
28
+ });
29
+
30
+ export const ThemeConfigSchema = z.object({
31
+ mode: z.enum(["auto", "catppuccin", "system"]).default("auto"),
32
+ flavor: z.enum(["latte", "frappe", "macchiato", "mocha"]).default("mocha"),
33
+ overrides: z.record(z.string(), z.string()).optional(),
34
+ });
35
+
36
+ export const DataStoreSchema = z.object({
37
+ $schema: z.string().default("./data.schema.json"),
38
+ version: z.number().default(1),
39
+ tasks: z.array(TaskSchema).default([]),
40
+ lastSync: z.string().nullable().default(null),
41
+ });
42
+
43
+ export const ConfigStoreSchema = z.object({
44
+ $schema: z.string().default("./config.schema.json"),
45
+ version: z.number().default(1),
46
+ linear: z
47
+ .object({
48
+ apiKey: z.string(),
49
+ teamId: z.string().optional(),
50
+ })
51
+ .optional(),
52
+ github: z
53
+ .object({
54
+ token: z.string(),
55
+ repo: z.string().optional(),
56
+ })
57
+ .optional(),
58
+ theme: ThemeConfigSchema.default({ mode: "auto", flavor: "mocha" }),
59
+ defaultView: z.enum(["all", "todo", "inProgress", "done"]).default("all"),
60
+ });
61
+
62
+ export type Task = z.infer<typeof TaskSchema>;
63
+ export type ThemeConfig = z.infer<typeof ThemeConfigSchema>;
64
+ export type DataStore = z.infer<typeof DataStoreSchema>;
65
+ export type ConfigStore = z.infer<typeof ConfigStoreSchema>;