trekoon 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.
Files changed (45) hide show
  1. package/.agents/skills/trekoon/SKILL.md +91 -0
  2. package/AGENTS.md +54 -0
  3. package/CONTRIBUTING.md +18 -0
  4. package/README.md +151 -0
  5. package/bin/trekoon +5 -0
  6. package/bun.lock +28 -0
  7. package/package.json +24 -0
  8. package/src/commands/arg-parser.ts +93 -0
  9. package/src/commands/dep.ts +105 -0
  10. package/src/commands/epic.ts +539 -0
  11. package/src/commands/help.ts +61 -0
  12. package/src/commands/init.ts +24 -0
  13. package/src/commands/quickstart.ts +61 -0
  14. package/src/commands/subtask.ts +187 -0
  15. package/src/commands/sync.ts +128 -0
  16. package/src/commands/task.ts +554 -0
  17. package/src/commands/wipe.ts +39 -0
  18. package/src/domain/tracker-domain.ts +576 -0
  19. package/src/domain/types.ts +99 -0
  20. package/src/index.ts +21 -0
  21. package/src/io/human-table.ts +191 -0
  22. package/src/io/output.ts +70 -0
  23. package/src/runtime/cli-shell.ts +158 -0
  24. package/src/runtime/command-types.ts +33 -0
  25. package/src/storage/database.ts +35 -0
  26. package/src/storage/migrations.ts +46 -0
  27. package/src/storage/path.ts +22 -0
  28. package/src/storage/schema.ts +116 -0
  29. package/src/storage/types.ts +15 -0
  30. package/src/sync/branch-db.ts +49 -0
  31. package/src/sync/event-writes.ts +49 -0
  32. package/src/sync/git-context.ts +67 -0
  33. package/src/sync/service.ts +654 -0
  34. package/src/sync/types.ts +31 -0
  35. package/tests/commands/dep.test.ts +101 -0
  36. package/tests/commands/epic.test.ts +383 -0
  37. package/tests/commands/subtask.test.ts +132 -0
  38. package/tests/commands/sync/sync-command.test.ts +1 -0
  39. package/tests/commands/sync.test.ts +199 -0
  40. package/tests/commands/task.test.ts +474 -0
  41. package/tests/integration/sync-workflow.test.ts +279 -0
  42. package/tests/io/human-table.test.ts +81 -0
  43. package/tests/runtime/output-mode.test.ts +54 -0
  44. package/tests/storage/database.test.ts +91 -0
  45. package/tsconfig.json +19 -0
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: trekoon
3
+ description: Use Trekoon to create issues/tasks, plan backlog and sprints, create epics, update status, track progress, and manage dependencies/sync across repository workflows.
4
+ ---
5
+
6
+ # Trekoon Skill
7
+
8
+ Trekoon is a local-first issue tracker for epics, tasks, and subtasks.
9
+
10
+ Use long flags (`--status`, `--description`, etc.) and ALWAYS prefer `--toon` for machine-readable output.
11
+
12
+ ## 1) Load existing work first
13
+
14
+ Before creating or changing anything, inspect current context:
15
+
16
+ ```bash
17
+ trekoon epic list --toon
18
+ trekoon task list --toon
19
+ trekoon epic show <id> --all --toon
20
+ trekoon task show <id> --all --toon
21
+ ```
22
+
23
+ - `epic list` / `task list` defaults:
24
+ - open work only (`in_progress`, `in-progress`, `todo`)
25
+ - prioritized as `in_progress`/`in-progress` first, then `todo`
26
+ - default limit `10`
27
+ - Filter list explicitly when needed:
28
+
29
+ ```bash
30
+ trekoon task list --status in_progress,todo --limit 20 --toon
31
+ trekoon epic list --status done --toon
32
+ trekoon task list --all --toon
33
+ ```
34
+
35
+ - `--all` cannot be combined with `--status` or `--limit`.
36
+ - `epic show <id> --all --toon`: full epic tree (tasks + subtasks)
37
+ - `task show <id> --all --toon`: task plus its subtasks
38
+
39
+ ## 2) Create work (epic/task/subtask)
40
+
41
+ ```bash
42
+ trekoon epic create --title "..." --description "..." --status todo --toon
43
+ trekoon task create --epic <epic-id> --title "..." --description "..." --status todo --toon
44
+ trekoon subtask create --task <task-id> --title "..." --description "..." --status todo --toon
45
+ ```
46
+
47
+ Notes:
48
+ - `description` is required for epic/task create and it must be well written.
49
+ - `status` defaults to `todo` if omitted.
50
+
51
+ ## 3) Update work
52
+
53
+ ### Single-item update
54
+
55
+ ```bash
56
+ trekoon epic update <epic-id> --title "..." --description "..." --status in_progress --toon
57
+ trekoon task update <task-id> --title "..." --description "..." --status in_progress --toon
58
+ trekoon subtask update <subtask-id> --title "..." --description "..." --status in_progress --toon
59
+ ```
60
+
61
+ ### Bulk update (task/epic)
62
+
63
+ ```bash
64
+ trekoon task update --all --append "..." --status in_progress --toon
65
+ trekoon task update --ids id1,id2 --append "..." --status in_progress --toon
66
+
67
+ trekoon epic update --all --append "..." --status in_progress --toon
68
+ trekoon epic update --ids id1,id2 --append "..." --status in_progress --toon
69
+ ```
70
+
71
+ Rules:
72
+ - `--all` and `--ids` are mutually exclusive.
73
+ - In bulk mode, do not pass a positional ID.
74
+ - Bulk update supports `--append` and/or `--status`.
75
+
76
+ ## 4) Setup/install/init (if `trekoon` is unavailable)
77
+
78
+ 1. Install Trekoon (or make sure it is on `PATH`).
79
+ 2. In the target repository/worktree, initialize tracker state:
80
+
81
+ ```bash
82
+ trekoon init
83
+ ```
84
+ 3. You can always run `trekoon quickstart` or `trekoon --help` to get more information.
85
+
86
+ If `.trekoon/trekoon.db` is missing, initialize before any create/update commands.
87
+
88
+ ## 5) Safety
89
+
90
+ - Never edit `.trekoon/trekoon.db` directly.
91
+ - `trekoon wipe --yes` is prohibited unless the user explicitly confirms they want a destructive wipe.
package/AGENTS.md ADDED
@@ -0,0 +1,54 @@
1
+ # AGENTS.md
2
+
3
+ This file contains guidelines for agents.
4
+
5
+ ## Mandatory: Atomic Commit Policy
6
+
7
+ Every code change MUST be followed by an immediate commit.
8
+
9
+ **Commit format**:
10
+ ```
11
+ <imperative verb> <what changed> ← Line 1: max 50 chars
12
+ <blank line> ← Line 2: blank
13
+ <why/context, one point per line> ← Body: max 72 chars per line
14
+ ```
15
+
16
+ **Rules**:
17
+ 1. One commit per logical change
18
+ 2. Small, atomic commits - one file per commit preferred
19
+ 3. Never batch unrelated changes
20
+
21
+ **Enforcement**:
22
+ - After any file modification, stop and commit before modifying another file
23
+ - Run `git status --short` after each commit to verify clean tree
24
+ - At milestones: run `bun run build && bun run lint && bun run test`
25
+
26
+ **Anti-patterns**:
27
+ - ❌ Multiple unrelated files in one commit
28
+ - ❌ Generic messages like "Update file", "WIP", "Fix stuff"
29
+ - ❌ Commit message over 50 chars on first line
30
+
31
+ ## Coding Conventions
32
+
33
+ For Bun/TypeScript code:
34
+ - **Imports**: Group order (stdlib → third-party → local), explicit named imports, remove unused
35
+ - **Formatting**: Consistent quotes, avoid mixed tabs/spaces
36
+ - **Types**: Prefer explicit types at API boundaries, avoid `any` unless justified
37
+ - **Naming**: `camelCase` (vars/functions), `PascalCase` (types), `UPPER_SNAKE_CASE` (constants)
38
+
39
+ **Error handling**:
40
+ - Never silently swallow errors
41
+ - Include actionable context (operation + endpoint + status code)
42
+ - Redact secrets from errors and logs
43
+
44
+ ## Agent Behavior
45
+
46
+ - Prefer minimal, targeted edits; avoid broad rewrites
47
+ - Preserve existing examples unless fixing factual issues
48
+ - For CLI changes, prioritize startup speed and low-latency UX
49
+ - Keep commands compatible with macOS and Linux shells
50
+
51
+ ## Security
52
+
53
+ - Never commit secrets (tokens, credentials)
54
+ - Redact secrets from errors and logs
@@ -0,0 +1,18 @@
1
+ # Contributing to Trekoon
2
+
3
+ ## No-copy implementation policy
4
+
5
+ Trekoon is implemented in this repository root. The `trekker/` directory is
6
+ reference-only.
7
+
8
+ - Do not copy code or files from `trekker/` into root `src/`.
9
+ - Do not mirror file layout one-to-one from `trekker/`.
10
+ - Write root implementation code directly, with Trekoon-native structure.
11
+
12
+ ## PR checklist
13
+
14
+ - [ ] Any new logic was written directly in root project files.
15
+ - [ ] Changes were reviewed for suspiciously identical blocks/comments versus
16
+ `trekker/` reference code.
17
+ - [ ] Sync-related writes preserve git context (`branch`, `head`, `worktree`).
18
+ - [ ] README command/flag examples match actual implemented CLI behavior.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Trekoon
2
+
3
+ AI-first issue tracking for humans and agents.
4
+
5
+ Trekoon is a Bun-powered CLI focused on execution workflows where AI agents and humans share the same task graph.
6
+
7
+ ## What Trekoon is
8
+
9
+ - Local-first CLI issue tracker
10
+ - Structured hierarchy: **epic → task → subtask**
11
+ - UUID-based references for durable linking across branches/worktrees
12
+ - Dependency-aware planning and execution
13
+ - Output modes:
14
+ - **Human mode** for terminal users
15
+ - **JSON mode** for stable machine parsing
16
+ - **TOON mode** for true TOON-encoded payloads
17
+
18
+ ## What Trekoon aims to accomplish
19
+
20
+ 1. Make issue tracking fast enough for daily terminal use.
21
+ 2. Make issue data deterministic and machine-readable for AI automation.
22
+ 3. Keep branch/worktree-aware state so parallel execution can be coordinated safely.
23
+ 4. Stay minimal in code size while preserving robustness and clear boundaries.
24
+
25
+ ## Command surface
26
+
27
+ - `trekoon init`
28
+ - `trekoon quickstart`
29
+ - `trekoon epic <create|list|show|update|delete>`
30
+ - `trekoon task <create|list|show|update|delete>`
31
+ - `trekoon subtask <create|list|update|delete>`
32
+ - `trekoon dep <add|remove|list>`
33
+ - `trekoon sync <status|pull|resolve>`
34
+ - `trekoon wipe --yes`
35
+
36
+ Global output modes:
37
+
38
+ - `--json` for structured JSON output
39
+ - `--toon` for true TOON-encoded output (not JSON text)
40
+ - `--help` for root and command help
41
+ - `--version` for CLI version
42
+
43
+ Global options can be used before or after the command:
44
+
45
+ ```bash
46
+ trekoon --toon quickstart
47
+ trekoon quickstart --toon
48
+ trekoon --json quickstart
49
+ trekoon quickstart --json
50
+ ```
51
+
52
+ Trekoon currently accepts long option form (`--option`).
53
+
54
+ Human view options:
55
+
56
+ - List and show commands default to table output in human mode.
57
+ - Use `--view compact` to restore compact pipe output.
58
+ - `epic list` and `task list` support `--view table|compact`.
59
+ - `epic show` and `task show` support `--view table|compact|tree|detail`.
60
+
61
+ List defaults and filters (`epic list`, `task list`):
62
+
63
+ - Default scope: open work only (`in_progress`, `in-progress`, `todo`)
64
+ - Default limit: `10`
65
+ - Status filter: `--status in_progress,todo` (CSV)
66
+ - Custom limit: `--limit <n>`
67
+ - All rows and statuses: `--all`
68
+ - `--all` is mutually exclusive with `--status` and `--limit`
69
+
70
+ Bulk updates (`epic update`, `task update`):
71
+
72
+ - Target all rows: `--all`
73
+ - Target specific rows: `--ids <id1,id2,...>`
74
+ - Bulk updates support only `--append <text>` and/or `--status <status>`
75
+ - In bulk mode, do not pass a positional id
76
+ - `--all` and `--ids` are mutually exclusive
77
+ - `--append` and `--description` are mutually exclusive
78
+
79
+ Examples:
80
+
81
+ ```bash
82
+ trekoon task update --all --status in_progress
83
+ trekoon task update --ids <task-1>,<task-2> --append "\nFollow-up note"
84
+ trekoon epic update --ids <epic-1>,<epic-2> --status done
85
+ ```
86
+
87
+ ## Quickstart
88
+
89
+ Trekoon is local-first: each worktree uses its own `.trekoon/trekoon.db`.
90
+ Git does not merge this DB file; Trekoon sync commands merge tracker state.
91
+
92
+ ### 1) Initialize
93
+
94
+ ```bash
95
+ trekoon init
96
+ trekoon --version
97
+ ```
98
+
99
+ ### 2) Create epic → task → subtask
100
+
101
+ ```bash
102
+ trekoon epic create --title "Agent backlog stabilization" --description "Track stabilization work" --status todo
103
+ trekoon task create --title "Implement sync status" --description "Add status reporting" --epic <epic-id> --status todo
104
+ trekoon subtask create --task <task-id> --title "Add cursor model" --status todo
105
+ trekoon task list
106
+ trekoon task list --status done
107
+ trekoon task list --limit 25
108
+ trekoon task list --all --view compact
109
+ ```
110
+
111
+ ### 3) Add dependencies
112
+
113
+ ```bash
114
+ trekoon dep add <task-id> <depends-on-id>
115
+ trekoon dep list <task-id>
116
+ ```
117
+
118
+ ### 4) Use JSON or TOON output for agents
119
+
120
+ ```bash
121
+ trekoon --json epic show <epic-id>
122
+ trekoon --json task show <task-id>
123
+ trekoon --toon epic show <epic-id>
124
+ trekoon --toon task show <task-id>
125
+ ```
126
+
127
+ ### 5) Sync workflow for worktrees
128
+
129
+ - Run `trekoon sync status` at session start and before PR/merge.
130
+ - Run `trekoon sync pull --from main` before merge to align tracker state.
131
+ - If conflicts exist, resolve explicitly:
132
+
133
+ ```bash
134
+ trekoon sync status
135
+ trekoon sync pull --from main
136
+ trekoon sync resolve <conflict-id> --use ours
137
+ ```
138
+
139
+ ### 6) Pre-merge checklist
140
+
141
+ - [ ] `trekoon sync status` shows no unresolved conflicts
142
+ - [ ] done tasks/subtasks are marked completed
143
+ - [ ] dependency graph has no stale blockers
144
+ - [ ] final AI check: `trekoon --json epic show <epic-id>`
145
+
146
+ ## Implementation principles
147
+
148
+ - Minimal, composable modules
149
+ - Strict validation at command boundaries
150
+ - Stable automation envelope for JSON/TOON modes
151
+ - No unnecessary feature sprawl
package/bin/trekoon ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { run } from "../src/index";
4
+
5
+ await run(process.argv.slice(2));
package/bun.lock ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "trekoon",
6
+ "dependencies": {
7
+ "@toon-format/toon": "^2.1.0",
8
+ },
9
+ "devDependencies": {
10
+ "@types/bun": "^1.3.0",
11
+ "typescript": "^5.9.2",
12
+ },
13
+ },
14
+ },
15
+ "packages": {
16
+ "@toon-format/toon": ["@toon-format/toon@2.1.0", "", {}, "sha512-JwWptdF5eOA0HaQxbKAzkpQtR4wSWTEfDlEy/y3/4okmOAX1qwnpLZMmtEWr+ncAhTTY1raCKH0kteHhSXnQqg=="],
17
+
18
+ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
19
+
20
+ "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
21
+
22
+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
23
+
24
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
25
+
26
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
27
+ }
28
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "trekoon",
3
+ "version": "0.1.0",
4
+ "description": "AI-first local issue tracker CLI.",
5
+ "type": "module",
6
+ "bin": {
7
+ "trekoon": "./bin/trekoon"
8
+ },
9
+ "scripts": {
10
+ "run": "bun run ./src/index.ts",
11
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun",
12
+ "test": "bun test ./tests"
13
+ },
14
+ "devDependencies": {
15
+ "@types/bun": "^1.3.9",
16
+ "typescript": "^5.9.3"
17
+ },
18
+ "dependencies": {
19
+ "@toon-format/toon": "^2.1.0"
20
+ },
21
+ "engines": {
22
+ "bun": ">=1.2.0"
23
+ }
24
+ }
@@ -0,0 +1,93 @@
1
+ export interface ParsedArgs {
2
+ readonly positional: readonly string[];
3
+ readonly options: ReadonlyMap<string, string>;
4
+ readonly flags: ReadonlySet<string>;
5
+ readonly missingOptionValues: ReadonlySet<string>;
6
+ }
7
+
8
+ const LONG_PREFIX = "--";
9
+
10
+ export function parseArgs(args: readonly string[]): ParsedArgs {
11
+ const positional: string[] = [];
12
+ const options = new Map<string, string>();
13
+ const flags = new Set<string>();
14
+ const missingOptionValues = new Set<string>();
15
+
16
+ for (let index = 0; index < args.length; index += 1) {
17
+ const token: string | undefined = args[index];
18
+ if (!token) {
19
+ continue;
20
+ }
21
+
22
+ if (!token.startsWith(LONG_PREFIX)) {
23
+ positional.push(token);
24
+ continue;
25
+ }
26
+
27
+ const key = token.slice(LONG_PREFIX.length);
28
+ const value = args[index + 1];
29
+ if (!value || value.startsWith(LONG_PREFIX)) {
30
+ flags.add(key);
31
+ missingOptionValues.add(key);
32
+ continue;
33
+ }
34
+
35
+ options.set(key, value);
36
+ index += 1;
37
+ }
38
+
39
+ return {
40
+ positional,
41
+ options,
42
+ flags,
43
+ missingOptionValues,
44
+ };
45
+ }
46
+
47
+ export function readOption(options: ReadonlyMap<string, string>, ...keys: string[]): string | undefined {
48
+ for (const key of keys) {
49
+ const value: string | undefined = options.get(key);
50
+ if (value !== undefined) {
51
+ return value;
52
+ }
53
+ }
54
+
55
+ return undefined;
56
+ }
57
+
58
+ export function hasFlag(flags: ReadonlySet<string>, ...keys: string[]): boolean {
59
+ return keys.some((key) => flags.has(key));
60
+ }
61
+
62
+ export function readMissingOptionValue(
63
+ missingOptionValues: ReadonlySet<string>,
64
+ ...keys: string[]
65
+ ): string | undefined {
66
+ return keys.find((key) => missingOptionValues.has(key));
67
+ }
68
+
69
+ export function parseStrictPositiveInt(rawValue: string | undefined): number | undefined {
70
+ if (rawValue === undefined) {
71
+ return undefined;
72
+ }
73
+
74
+ const parsed = Number.parseInt(rawValue, 10);
75
+ if (!Number.isInteger(parsed) || parsed < 1 || `${parsed}` !== rawValue.trim()) {
76
+ return Number.NaN;
77
+ }
78
+
79
+ return parsed;
80
+ }
81
+
82
+ export function readEnumOption<const T extends readonly string[]>(
83
+ options: ReadonlyMap<string, string>,
84
+ allowed: T,
85
+ ...keys: string[]
86
+ ): T[number] | undefined {
87
+ const value: string | undefined = readOption(options, ...keys);
88
+ if (value === undefined) {
89
+ return undefined;
90
+ }
91
+
92
+ return allowed.includes(value) ? value : undefined;
93
+ }
@@ -0,0 +1,105 @@
1
+ import { parseArgs } from "./arg-parser";
2
+
3
+ import { DomainError } from "../domain/types";
4
+ import { TrackerDomain } from "../domain/tracker-domain";
5
+ import { failResult, okResult } from "../io/output";
6
+ import { type CliContext, type CliResult } from "../runtime/command-types";
7
+ import { openTrekoonDatabase } from "../storage/database";
8
+
9
+ function failFromError(error: unknown): CliResult {
10
+ if (error instanceof DomainError) {
11
+ return failResult({
12
+ command: "dep",
13
+ human: error.message,
14
+ data: {
15
+ code: error.code,
16
+ ...(error.details ?? {}),
17
+ },
18
+ error: {
19
+ code: error.code,
20
+ message: error.message,
21
+ },
22
+ });
23
+ }
24
+
25
+ return failResult({
26
+ command: "dep",
27
+ human: "Unexpected dep command failure",
28
+ data: {},
29
+ error: {
30
+ code: "internal_error",
31
+ message: "Unexpected dep command failure",
32
+ },
33
+ });
34
+ }
35
+
36
+ export async function runDep(context: CliContext): Promise<CliResult> {
37
+ const database = openTrekoonDatabase(context.cwd);
38
+
39
+ try {
40
+ const parsed = parseArgs(context.args);
41
+ const subcommand: string | undefined = parsed.positional[0];
42
+ const sourceId: string = parsed.positional[1] ?? "";
43
+ const dependsOnId: string = parsed.positional[2] ?? "";
44
+ const domain = new TrackerDomain(database.db);
45
+
46
+ switch (subcommand) {
47
+ case "add": {
48
+ const dependency = domain.addDependency(sourceId, dependsOnId);
49
+
50
+ return okResult({
51
+ command: "dep.add",
52
+ human: `Added dependency ${dependency.sourceId} -> ${dependency.dependsOnId}`,
53
+ data: { dependency },
54
+ });
55
+ }
56
+ case "remove": {
57
+ const removed: number = domain.removeDependency(sourceId, dependsOnId);
58
+
59
+ return okResult({
60
+ command: "dep.remove",
61
+ human:
62
+ removed > 0
63
+ ? `Removed dependency ${sourceId} -> ${dependsOnId}`
64
+ : `No dependency found for ${sourceId} -> ${dependsOnId}`,
65
+ data: {
66
+ sourceId,
67
+ dependsOnId,
68
+ removed,
69
+ },
70
+ });
71
+ }
72
+ case "list": {
73
+ const dependencies = domain.listDependencies(sourceId);
74
+
75
+ return okResult({
76
+ command: "dep.list",
77
+ human:
78
+ dependencies.length === 0
79
+ ? `No dependencies for ${sourceId}`
80
+ : dependencies.map((item) => `${item.sourceId} -> ${item.dependsOnId}`).join("\n"),
81
+ data: {
82
+ sourceId,
83
+ dependencies,
84
+ },
85
+ });
86
+ }
87
+ default:
88
+ return failResult({
89
+ command: "dep",
90
+ human: "Usage: trekoon dep <add|remove|list>",
91
+ data: {
92
+ args: context.args,
93
+ },
94
+ error: {
95
+ code: "invalid_subcommand",
96
+ message: "Invalid dep subcommand",
97
+ },
98
+ });
99
+ }
100
+ } catch (error: unknown) {
101
+ return failFromError(error);
102
+ } finally {
103
+ database.close();
104
+ }
105
+ }