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.
- package/.claude/settings.local.json +7 -0
- package/.github/workflows/publish.yml +24 -0
- package/AGENTS.md +151 -0
- package/README.md +15 -0
- package/config.schema.json +103 -0
- package/data.schema.json +144 -0
- package/package.json +37 -0
- package/scripts/generate-schema.ts +10 -0
- package/src/data/store.ts +86 -0
- package/src/index.ts +27 -0
- package/src/schema.ts +65 -0
- package/src/ui/components/TaskItem.ts +149 -0
- package/src/ui/components/TaskModal.ts +295 -0
- package/src/ui/theme.ts +87 -0
- package/src/ui/views/TaskListView.ts +427 -0
- package/test-store.ts +6 -0
- package/tsconfig.json +24 -0
|
@@ -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
|
+
}
|
package/data.schema.json
ADDED
|
@@ -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>;
|