@tomkapa/tayto 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/README.md +217 -0
- package/dist/chunk-6NQOFUIQ.js +133 -0
- package/dist/chunk-6NQOFUIQ.js.map +1 -0
- package/dist/index.js +1962 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/001_initial.sql +37 -0
- package/dist/migrations/002_dependencies_and_fts.sql +47 -0
- package/dist/migrations/003_soft_delete.sql +39 -0
- package/dist/tui-JNZRBEIQ.js +2352 -0
- package/dist/tui-JNZRBEIQ.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# tayto
|
|
2
|
+
|
|
3
|
+
Task management for solo developers and AI agents. Two interfaces, one database.
|
|
4
|
+
|
|
5
|
+
- **CLI** returns structured JSON — built for AI agents and scripting
|
|
6
|
+
- **TUI** renders rich markdown in the terminal — built for humans
|
|
7
|
+
|
|
8
|
+
SQLite-backed. No server. No login. Just projects and tasks.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Install
|
|
16
|
+
npm install && npm run build
|
|
17
|
+
|
|
18
|
+
# Create a project
|
|
19
|
+
tayto project create -n "my-app" --default
|
|
20
|
+
|
|
21
|
+
# Create tasks
|
|
22
|
+
tayto task create -n "Fix auth bug" -t bug --priority 1
|
|
23
|
+
tayto task create -n "Add dashboard" -t story --priority 2
|
|
24
|
+
|
|
25
|
+
# Launch the terminal UI
|
|
26
|
+
tayto
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
**Requirements:** Node.js >= 18
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone <repo-url> && cd tayto
|
|
35
|
+
npm install
|
|
36
|
+
npm run build
|
|
37
|
+
npm link # makes `tayto` available globally
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Terminal UI
|
|
43
|
+
|
|
44
|
+
Run `tayto` with no arguments to launch the interactive TUI.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
tayto # launch TUI (default)
|
|
48
|
+
tayto tui # explicit launch
|
|
49
|
+
tayto tui -p "my-app" # start with a specific project
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Keyboard Shortcuts
|
|
53
|
+
|
|
54
|
+
| Key | Action |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `j` / `k` / `arrows` | Navigate up/down |
|
|
57
|
+
| `Enter` | Open task detail |
|
|
58
|
+
| `c` | Create task |
|
|
59
|
+
| `e` | Edit task |
|
|
60
|
+
| `d` | Delete task (with confirmation) |
|
|
61
|
+
| `s` | Cycle status forward |
|
|
62
|
+
| `/` | Search tasks |
|
|
63
|
+
| `f` | Cycle status filter |
|
|
64
|
+
| `t` | Cycle type filter |
|
|
65
|
+
| `1`-`5` | Toggle priority filter |
|
|
66
|
+
| `0` | Clear all filters |
|
|
67
|
+
| `p` | Switch project |
|
|
68
|
+
| `Esc` / `b` | Go back |
|
|
69
|
+
| `?` | Show help |
|
|
70
|
+
| `q` | Quit |
|
|
71
|
+
|
|
72
|
+
The task detail view renders **markdown**, **code blocks**, and **technical notes** directly in the terminal.
|
|
73
|
+
|
|
74
|
+
### CLI Commands
|
|
75
|
+
|
|
76
|
+
All commands output JSON to stdout. Errors go to stderr with exit code 1.
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
// success
|
|
80
|
+
{ "ok": true, "data": { ... } }
|
|
81
|
+
|
|
82
|
+
// error
|
|
83
|
+
{ "ok": false, "error": { "code": "NOT_FOUND", "message": "..." } }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Project
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
tayto project create -n "my-app" -d "Description" --default
|
|
90
|
+
tayto project list
|
|
91
|
+
tayto project update <id> -n "new-name" --default
|
|
92
|
+
tayto project delete <id>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Task
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Create
|
|
99
|
+
tayto task create \
|
|
100
|
+
-n "Fix login bug" \
|
|
101
|
+
-t bug \
|
|
102
|
+
-s todo \
|
|
103
|
+
--priority 1 \
|
|
104
|
+
-p "my-app" \
|
|
105
|
+
-d "Login fails on mobile" \
|
|
106
|
+
--technical-notes "Check JWT expiry" \
|
|
107
|
+
--additional-requirements "Must work on iOS Safari"
|
|
108
|
+
|
|
109
|
+
# Read
|
|
110
|
+
tayto task list
|
|
111
|
+
tayto task list --status in-progress --type bug --search "login"
|
|
112
|
+
tayto task list --priority 1 --parent <parent-id>
|
|
113
|
+
tayto task show <id>
|
|
114
|
+
|
|
115
|
+
# Update
|
|
116
|
+
tayto task update <id> -s in-progress
|
|
117
|
+
tayto task update <id> --append-notes "Root cause: token not refreshed"
|
|
118
|
+
tayto task update <id> --append-requirements "Also fix on Android"
|
|
119
|
+
|
|
120
|
+
# Delete
|
|
121
|
+
tayto task delete <id>
|
|
122
|
+
|
|
123
|
+
# Breakdown (create subtasks from JSON)
|
|
124
|
+
tayto task breakdown <parent-id> -f subtasks.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**subtasks.json** example:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
[
|
|
131
|
+
{ "name": "Implement API endpoint", "type": "story", "priority": 2 },
|
|
132
|
+
{ "name": "Write integration tests", "type": "story", "priority": 3 }
|
|
133
|
+
]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Data Model
|
|
137
|
+
|
|
138
|
+
### Task Types
|
|
139
|
+
|
|
140
|
+
| Type | Description |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `story` | Feature or user story |
|
|
143
|
+
| `tech-debt` | Refactoring or cleanup |
|
|
144
|
+
| `bug` | Defect or issue |
|
|
145
|
+
|
|
146
|
+
### Task Statuses
|
|
147
|
+
|
|
148
|
+
`backlog` → `todo` → `in-progress` → `review` → `done`
|
|
149
|
+
|
|
150
|
+
`cancelled` is also available for abandoned tasks.
|
|
151
|
+
|
|
152
|
+
### Priority
|
|
153
|
+
|
|
154
|
+
| Level | Label |
|
|
155
|
+
|---|---|
|
|
156
|
+
| 1 | Critical |
|
|
157
|
+
| 2 | High |
|
|
158
|
+
| 3 | Medium (default) |
|
|
159
|
+
| 4 | Low |
|
|
160
|
+
| 5 | Lowest |
|
|
161
|
+
|
|
162
|
+
### Task Breakdown
|
|
163
|
+
|
|
164
|
+
Tasks support a `parent_id` field for hierarchical decomposition. Use `task breakdown` to batch-create subtasks under a parent, or pass `--parent <id>` on `task create`.
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
Configure via environment variables.
|
|
169
|
+
|
|
170
|
+
| Variable | Default | Description |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `TASK_DB_PATH` | `~/.task/data.db` | Path to SQLite database |
|
|
173
|
+
| `TASK_DATA_DIR` | `~/.task` | Data directory |
|
|
174
|
+
| `TASK_LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warn`, `error`) |
|
|
175
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OpenTelemetry collector endpoint |
|
|
176
|
+
|
|
177
|
+
The database and data directory are created automatically on first run.
|
|
178
|
+
|
|
179
|
+
## Architecture
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
CLI Commands ──┐
|
|
183
|
+
├──> Service Layer ──> Repository Layer ──> SQLite
|
|
184
|
+
Terminal UI ───┘
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- **Service layer** handles validation (Zod), business logic, and project resolution
|
|
188
|
+
- **Repository layer** handles SQL queries with parameterized statements
|
|
189
|
+
- **Result\<T\>** return type across all layers — no thrown exceptions for business logic
|
|
190
|
+
- **OpenTelemetry** spans on every service and repository operation
|
|
191
|
+
- **ULID** identifiers — sortable, no database round-trip
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
src/
|
|
195
|
+
cli/ # Commander.js commands, JSON output
|
|
196
|
+
tui/ # Ink (React) terminal UI components
|
|
197
|
+
service/ # Business logic
|
|
198
|
+
repository/ # Data access
|
|
199
|
+
db/ # SQLite connection, migrations
|
|
200
|
+
types/ # Zod schemas, enums, Result type
|
|
201
|
+
errors/ # Typed error hierarchy
|
|
202
|
+
logging/ # OpenTelemetry tracer
|
|
203
|
+
config/ # Environment-based configuration
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npm run dev # build in watch mode
|
|
210
|
+
npm run check # prettier + eslint
|
|
211
|
+
npm run test # run tests
|
|
212
|
+
npm run build # production build
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/logging/logger.ts
|
|
4
|
+
import { appendFileSync, readdirSync, unlinkSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
7
|
+
var tracer = trace.getTracer("task");
|
|
8
|
+
var LOG_RETENTION_DAYS = 7;
|
|
9
|
+
function formatTimestamp() {
|
|
10
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
11
|
+
}
|
|
12
|
+
function formatAttrs(attrs) {
|
|
13
|
+
if (!attrs || Object.keys(attrs).length === 0) return "";
|
|
14
|
+
return " " + JSON.stringify(attrs);
|
|
15
|
+
}
|
|
16
|
+
var Logger = class {
|
|
17
|
+
logFilePath = null;
|
|
18
|
+
init(logDir) {
|
|
19
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
20
|
+
this.logFilePath = join(logDir, `task-${date}.log`);
|
|
21
|
+
this.pruneOldLogs(logDir);
|
|
22
|
+
}
|
|
23
|
+
info(message, attrs) {
|
|
24
|
+
this.write("INFO", message, attrs);
|
|
25
|
+
const span = trace.getActiveSpan();
|
|
26
|
+
if (span) {
|
|
27
|
+
span.addEvent(message, attrs);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
warn(message, attrs) {
|
|
31
|
+
this.write("WARN", message, attrs);
|
|
32
|
+
const span = trace.getActiveSpan();
|
|
33
|
+
if (span) {
|
|
34
|
+
span.addEvent(`WARN: ${message}`, attrs);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
error(message, error, attrs) {
|
|
38
|
+
const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : "";
|
|
39
|
+
this.write("ERROR", `${message}${errorDetail}`, attrs);
|
|
40
|
+
const span = trace.getActiveSpan();
|
|
41
|
+
if (span) {
|
|
42
|
+
span.addEvent(`ERROR: ${message}`, attrs);
|
|
43
|
+
if (error instanceof Error) {
|
|
44
|
+
span.recordException(error);
|
|
45
|
+
}
|
|
46
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
startSpan(name, fn) {
|
|
50
|
+
return tracer.startActiveSpan(name, (span) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = fn(span);
|
|
53
|
+
span.end();
|
|
54
|
+
return result;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e instanceof Error) {
|
|
57
|
+
span.recordException(e);
|
|
58
|
+
}
|
|
59
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
60
|
+
span.end();
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
write(level, message, attrs) {
|
|
66
|
+
if (!this.logFilePath) return;
|
|
67
|
+
const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}
|
|
68
|
+
`;
|
|
69
|
+
try {
|
|
70
|
+
appendFileSync(this.logFilePath, line);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
pruneOldLogs(logDir) {
|
|
75
|
+
try {
|
|
76
|
+
const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
77
|
+
const files = readdirSync(logDir).filter((f) => f.startsWith("task-") && f.endsWith(".log"));
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
const dateStr = file.slice("task-".length, -".log".length);
|
|
80
|
+
const fileDate = new Date(dateStr).getTime();
|
|
81
|
+
if (!isNaN(fileDate) && fileDate < cutoff) {
|
|
82
|
+
unlinkSync(join(logDir, file));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var logger = new Logger();
|
|
90
|
+
|
|
91
|
+
// src/types/enums.ts
|
|
92
|
+
var TaskStatus = {
|
|
93
|
+
Backlog: "backlog",
|
|
94
|
+
Todo: "todo",
|
|
95
|
+
InProgress: "in-progress",
|
|
96
|
+
Review: "review",
|
|
97
|
+
Done: "done",
|
|
98
|
+
Cancelled: "cancelled"
|
|
99
|
+
};
|
|
100
|
+
var TaskType = {
|
|
101
|
+
Story: "story",
|
|
102
|
+
TechDebt: "tech-debt",
|
|
103
|
+
Bug: "bug"
|
|
104
|
+
};
|
|
105
|
+
var DependencyType = {
|
|
106
|
+
Blocks: "blocks",
|
|
107
|
+
RelatesTo: "relates-to",
|
|
108
|
+
Duplicates: "duplicates"
|
|
109
|
+
};
|
|
110
|
+
var UIDependencyType = {
|
|
111
|
+
...DependencyType,
|
|
112
|
+
BlockedBy: "blocked-by"
|
|
113
|
+
};
|
|
114
|
+
var RANK_GAP = 1e3;
|
|
115
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
116
|
+
TaskStatus.Done,
|
|
117
|
+
TaskStatus.Cancelled
|
|
118
|
+
]);
|
|
119
|
+
function isTerminalStatus(status) {
|
|
120
|
+
return TERMINAL_STATUSES.has(status);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
logger,
|
|
125
|
+
TaskStatus,
|
|
126
|
+
TaskType,
|
|
127
|
+
DependencyType,
|
|
128
|
+
UIDependencyType,
|
|
129
|
+
RANK_GAP,
|
|
130
|
+
TERMINAL_STATUSES,
|
|
131
|
+
isTerminalStatus
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=chunk-6NQOFUIQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logging/logger.ts","../src/types/enums.ts"],"sourcesContent":["import { appendFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { trace, type Span, SpanStatusCode } from '@opentelemetry/api';\n\nconst tracer = trace.getTracer('task');\nconst LOG_RETENTION_DAYS = 7;\n\nexport interface LogAttributes {\n [key: string]: string | number | boolean;\n}\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR';\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction formatAttrs(attrs?: LogAttributes): string {\n if (!attrs || Object.keys(attrs).length === 0) return '';\n return ' ' + JSON.stringify(attrs);\n}\n\nclass Logger {\n private logFilePath: string | null = null;\n\n init(logDir: string): void {\n const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n this.logFilePath = join(logDir, `task-${date}.log`);\n this.pruneOldLogs(logDir);\n }\n\n info(message: string, attrs?: LogAttributes): void {\n this.write('INFO', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(message, attrs);\n }\n }\n\n warn(message: string, attrs?: LogAttributes): void {\n this.write('WARN', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`WARN: ${message}`, attrs);\n }\n }\n\n error(message: string, error?: unknown, attrs?: LogAttributes): void {\n const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : '';\n this.write('ERROR', `${message}${errorDetail}`, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`ERROR: ${message}`, attrs);\n if (error instanceof Error) {\n span.recordException(error);\n }\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n }\n }\n\n startSpan<T>(name: string, fn: (span: Span) => T): T {\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.end();\n return result;\n } catch (e) {\n if (e instanceof Error) {\n span.recordException(e);\n }\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end();\n throw e;\n }\n });\n }\n\n private write(level: LogLevel, message: string, attrs?: LogAttributes): void {\n if (!this.logFilePath) return;\n const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}\\n`;\n try {\n appendFileSync(this.logFilePath, line);\n } catch {\n // Swallowing here is intentional: logging must never crash the app.\n // If the log file is unwritable, the OTel span still captures the event.\n }\n }\n\n private pruneOldLogs(logDir: string): void {\n try {\n const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;\n const files = readdirSync(logDir).filter((f) => f.startsWith('task-') && f.endsWith('.log'));\n for (const file of files) {\n const dateStr = file.slice('task-'.length, -'.log'.length);\n const fileDate = new Date(dateStr).getTime();\n if (!isNaN(fileDate) && fileDate < cutoff) {\n unlinkSync(join(logDir, file));\n }\n }\n } catch {\n // Best-effort cleanup — don't crash if pruning fails\n }\n }\n}\n\nexport const logger = new Logger();\n","export const TaskStatus = {\n Backlog: 'backlog',\n Todo: 'todo',\n InProgress: 'in-progress',\n Review: 'review',\n Done: 'done',\n Cancelled: 'cancelled',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport const TaskType = {\n Story: 'story',\n TechDebt: 'tech-debt',\n Bug: 'bug',\n} as const;\nexport type TaskType = (typeof TaskType)[keyof typeof TaskType];\n\n/** Types stored in the database. */\nexport const DependencyType = {\n Blocks: 'blocks',\n RelatesTo: 'relates-to',\n Duplicates: 'duplicates',\n} as const;\nexport type DependencyType = (typeof DependencyType)[keyof typeof DependencyType];\n\n/**\n * UI-level dependency types — includes BlockedBy which is a reverse-Blocks\n * relationship resolved before persisting to the database.\n */\nexport const UIDependencyType = {\n ...DependencyType,\n BlockedBy: 'blocked-by',\n} as const;\nexport type UIDependencyType = (typeof UIDependencyType)[keyof typeof UIDependencyType];\n\n/** Gap between consecutive rank values, used for insertion between neighbors. */\nexport const RANK_GAP = 1000.0;\n\n/** Statuses that represent terminal/completed task states. */\nexport const TERMINAL_STATUSES: ReadonlySet<string> = new Set([\n TaskStatus.Done,\n TaskStatus.Cancelled,\n]);\n\nexport function isTerminalStatus(status: string): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n"],"mappings":"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","names":[]}
|