agent-trajectories 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/.beads/.local_version +1 -0
- package/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/issues.jsonl +0 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/IMPLEMENTATION-PROPOSAL.md +1598 -0
- package/PROPOSAL-trajectories.md +1582 -0
- package/README.md +275 -0
- package/biome.json +20 -0
- package/docs/architecture/core.md +477 -0
- package/docs/architecture/memory-integration.md +186 -0
- package/docs/architecture/web-viewer.md +213 -0
- package/package.json +47 -0
- package/src/cli/commands/abandon.ts +32 -0
- package/src/cli/commands/complete.ts +51 -0
- package/src/cli/commands/decision.ts +49 -0
- package/src/cli/commands/export.ts +100 -0
- package/src/cli/commands/index.ts +39 -0
- package/src/cli/commands/list.ts +90 -0
- package/src/cli/commands/show.ts +98 -0
- package/src/cli/commands/start.ts +53 -0
- package/src/cli/commands/status.ts +68 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/runner.ts +67 -0
- package/src/core/id.ts +62 -0
- package/src/core/index.ts +54 -0
- package/src/core/schema.ts +272 -0
- package/src/core/trajectory.ts +283 -0
- package/src/core/types.ts +272 -0
- package/src/export/index.ts +10 -0
- package/src/export/json.ts +26 -0
- package/src/export/markdown.ts +216 -0
- package/src/export/pr-summary.ts +57 -0
- package/src/export/timeline.ts +69 -0
- package/src/index.ts +69 -0
- package/src/storage/file.ts +394 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/interface.ts +92 -0
- package/src/web/generator.ts +347 -0
- package/src/web/index.ts +6 -0
- package/src/web/styles.ts +355 -0
- package/src/workspace/index.ts +6 -0
- package/src/workspace/storage.ts +231 -0
- package/src/workspace/types.ts +88 -0
- package/tests/cli/commands.test.ts +476 -0
- package/tests/core/trajectory.test.ts +426 -0
- package/tests/export/export.test.ts +376 -0
- package/tests/storage/storage.test.ts +410 -0
- package/tests/web/generator.test.ts +197 -0
- package/tests/workspace/storage.test.ts +275 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +14 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Web Viewer Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A lightweight local web interface for humans to browse and read trajectories in a Notion-like format.
|
|
6
|
+
|
|
7
|
+
## Design Goals
|
|
8
|
+
|
|
9
|
+
1. **Zero external dependencies** - No cloud services, runs entirely local
|
|
10
|
+
2. **Instant startup** - `traj view` opens browser immediately
|
|
11
|
+
3. **No build step** - Works without webpack/vite in dev
|
|
12
|
+
4. **Offline-first** - All data from local `.trajectories/`
|
|
13
|
+
|
|
14
|
+
## Architecture Options
|
|
15
|
+
|
|
16
|
+
### Option A: Static HTML Export (Recommended for v1)
|
|
17
|
+
|
|
18
|
+
Generate a self-contained HTML file with embedded data:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
traj view # Opens generated HTML in browser
|
|
22
|
+
traj view --serve # Start live server with hot reload
|
|
23
|
+
traj view --export site/ # Generate static site
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Pros:**
|
|
27
|
+
- No server needed for basic viewing
|
|
28
|
+
- Can be committed to repo for sharing
|
|
29
|
+
- Works offline, shareable via email
|
|
30
|
+
|
|
31
|
+
**Cons:**
|
|
32
|
+
- No live updates without regeneration
|
|
33
|
+
|
|
34
|
+
### Option B: Local Dev Server
|
|
35
|
+
|
|
36
|
+
Run a lightweight HTTP server:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
traj serve # Start server at localhost:3847
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Stack:**
|
|
43
|
+
- Node HTTP server (built-in, no Express needed)
|
|
44
|
+
- Preact for UI (3KB, no build step with htm)
|
|
45
|
+
- CSS: Simple stylesheet, no framework
|
|
46
|
+
|
|
47
|
+
**Pros:**
|
|
48
|
+
- Live updates as trajectories change
|
|
49
|
+
- Search and filter in UI
|
|
50
|
+
|
|
51
|
+
**Cons:**
|
|
52
|
+
- Requires running server
|
|
53
|
+
|
|
54
|
+
### Option C: Hybrid (Recommended)
|
|
55
|
+
|
|
56
|
+
Combine both: static HTML for sharing, local server for active use.
|
|
57
|
+
|
|
58
|
+
## UI Components
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
62
|
+
│ 🛤️ Trajectories [Search...] [⚙️] │
|
|
63
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
64
|
+
│ │
|
|
65
|
+
│ SIDEBAR │ MAIN VIEW │
|
|
66
|
+
│ ───────── │ ───────── │
|
|
67
|
+
│ │ │
|
|
68
|
+
│ ▸ Active (1) │ 📋 Implement User Auth │
|
|
69
|
+
│ └─ traj_abc123 │ ━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
|
70
|
+
│ │ │
|
|
71
|
+
│ ▸ This Week (5) │ Status: ● Active │
|
|
72
|
+
│ │ Started: 2h ago │
|
|
73
|
+
│ ▸ Completed (23) │ Agent: Claude │
|
|
74
|
+
│ │ │
|
|
75
|
+
│ ▸ Abandoned (2) │ ▾ Summary │
|
|
76
|
+
│ │ JWT auth with refresh tokens │
|
|
77
|
+
│ ────────────── │ │
|
|
78
|
+
│ 📁 Workspace │ ▾ Key Decisions (2) │
|
|
79
|
+
│ ├─ Decisions │ ├─ JWT over sessions │
|
|
80
|
+
│ ├─ Patterns │ └─ Bcrypt for passwords │
|
|
81
|
+
│ └─ Knowledge │ │
|
|
82
|
+
│ │ ▾ Timeline │
|
|
83
|
+
│ │ 10:00 Started │
|
|
84
|
+
│ │ 10:15 Research phase │
|
|
85
|
+
│ │ 10:30 Decision: JWT │
|
|
86
|
+
│ │ 11:00 Implementation │
|
|
87
|
+
│ │ │
|
|
88
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Implementation Plan
|
|
92
|
+
|
|
93
|
+
### Phase 1: Static HTML Generator
|
|
94
|
+
- Generate single HTML file with all trajectory data
|
|
95
|
+
- Embedded CSS (light/dark mode)
|
|
96
|
+
- Collapsible sections for chapters, decisions
|
|
97
|
+
- `traj view <id>` opens single trajectory
|
|
98
|
+
- `traj view --all` generates index + all trajectories
|
|
99
|
+
|
|
100
|
+
### Phase 2: Local Server
|
|
101
|
+
- Add `traj serve` command
|
|
102
|
+
- WebSocket for live updates
|
|
103
|
+
- Search across all trajectories
|
|
104
|
+
- Filtering by status, date, agent
|
|
105
|
+
|
|
106
|
+
### Phase 3: Workspace Integration
|
|
107
|
+
- Sidebar shows workspace knowledge
|
|
108
|
+
- Link decisions to source trajectories
|
|
109
|
+
- Pattern library view
|
|
110
|
+
- "Related trajectories" for context
|
|
111
|
+
|
|
112
|
+
## File Structure
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
src/
|
|
116
|
+
├── web/
|
|
117
|
+
│ ├── server.ts # HTTP server
|
|
118
|
+
│ ├── templates/
|
|
119
|
+
│ │ ├── index.html # Main shell
|
|
120
|
+
│ │ ├── trajectory.html # Single trajectory view
|
|
121
|
+
│ │ └── styles.css # Embedded styles
|
|
122
|
+
│ ├── generator.ts # Static HTML generator
|
|
123
|
+
│ └── components/ # Preact components (Phase 2)
|
|
124
|
+
│ ├── Sidebar.tsx
|
|
125
|
+
│ ├── Timeline.tsx
|
|
126
|
+
│ └── DecisionCard.tsx
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## CLI Integration
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# View commands
|
|
133
|
+
traj view # Open active trajectory in browser
|
|
134
|
+
traj view <id> # Open specific trajectory
|
|
135
|
+
traj view --all # Open index of all trajectories
|
|
136
|
+
|
|
137
|
+
# Server commands
|
|
138
|
+
traj serve # Start local server
|
|
139
|
+
traj serve --port 8080 # Custom port
|
|
140
|
+
|
|
141
|
+
# Export for sharing
|
|
142
|
+
traj export <id> --format html --output trajectory.html
|
|
143
|
+
traj export --all --format html --output ./docs/trajectories/
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Workspace Layer
|
|
147
|
+
|
|
148
|
+
The workspace extracts durable knowledge from trajectories:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
.agent-workspace/
|
|
152
|
+
├── decisions/
|
|
153
|
+
│ ├── index.json # All decisions with links
|
|
154
|
+
│ └── auth-approach.md # Individual decision doc
|
|
155
|
+
├── patterns/
|
|
156
|
+
│ ├── index.json
|
|
157
|
+
│ └── api-endpoint.md # Reusable pattern
|
|
158
|
+
├── knowledge/
|
|
159
|
+
│ ├── architecture.md # Codebase knowledge
|
|
160
|
+
│ └── conventions.md # Team conventions
|
|
161
|
+
└── config.json # Workspace settings
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Decision Extraction
|
|
165
|
+
|
|
166
|
+
When a trajectory completes, decisions can be promoted to workspace:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
traj workspace promote-decision <trajectory-id> <decision-index>
|
|
170
|
+
# or interactively after completion
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Query Interface
|
|
174
|
+
|
|
175
|
+
Agents query the workspace:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface WorkspaceQuery {
|
|
179
|
+
type: 'decision' | 'pattern' | 'trajectory' | 'any';
|
|
180
|
+
query: string;
|
|
181
|
+
context?: string; // Current task context
|
|
182
|
+
limit?: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Example: Agent asks about auth
|
|
186
|
+
workspace.query({
|
|
187
|
+
type: 'any',
|
|
188
|
+
query: 'authentication',
|
|
189
|
+
context: 'implementing login flow'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Returns:
|
|
193
|
+
// - Decision: "Use JWT for auth"
|
|
194
|
+
// - Pattern: "Auth middleware template"
|
|
195
|
+
// - Trajectory: traj_abc123 (implemented auth before)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Technology Choices
|
|
199
|
+
|
|
200
|
+
| Component | Choice | Rationale |
|
|
201
|
+
|-----------|--------|-----------|
|
|
202
|
+
| Server | Node http | Zero deps, built-in |
|
|
203
|
+
| UI (v1) | Vanilla JS | No build step |
|
|
204
|
+
| UI (v2) | Preact + htm | 3KB, no build needed |
|
|
205
|
+
| CSS | Custom | Simple, themed |
|
|
206
|
+
| Icons | Unicode emoji | No icon deps |
|
|
207
|
+
|
|
208
|
+
## Questions to Resolve
|
|
209
|
+
|
|
210
|
+
1. **Workspace location**: `.agent-workspace/` vs inside `.trajectories/workspace/`?
|
|
211
|
+
2. **Knowledge format**: Markdown files vs JSON?
|
|
212
|
+
3. **Pattern templates**: How to make patterns executable/reusable?
|
|
213
|
+
4. **Multi-repo**: How does workspace work across repos?
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-trajectories",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Capture the complete train of thought of agent work as first-class artifacts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"trail": "dist/cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"test:run": "vitest run",
|
|
16
|
+
"lint": "biome check .",
|
|
17
|
+
"lint:fix": "biome check --write .",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"agent",
|
|
23
|
+
"trajectory",
|
|
24
|
+
"ai",
|
|
25
|
+
"claude",
|
|
26
|
+
"llm",
|
|
27
|
+
"debugging",
|
|
28
|
+
"tracing"
|
|
29
|
+
],
|
|
30
|
+
"author": "",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@clack/prompts": "^0.7.0",
|
|
37
|
+
"commander": "^12.0.0",
|
|
38
|
+
"zod": "^3.23.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "^1.8.0",
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"tsup": "^8.0.0",
|
|
44
|
+
"typescript": "^5.4.0",
|
|
45
|
+
"vitest": "^2.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail abandon command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import { abandonTrajectory } from "../../core/trajectory.js";
|
|
7
|
+
import { FileStorage } from "../../storage/file.js";
|
|
8
|
+
|
|
9
|
+
export function registerAbandonCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("abandon")
|
|
12
|
+
.description("Abandon the active trajectory")
|
|
13
|
+
.option("-r, --reason <text>", "Reason for abandonment")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const storage = new FileStorage();
|
|
16
|
+
await storage.initialize();
|
|
17
|
+
|
|
18
|
+
const active = await storage.getActive();
|
|
19
|
+
if (!active) {
|
|
20
|
+
console.error("Error: No active trajectory");
|
|
21
|
+
throw new Error("No active trajectory");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const abandoned = abandonTrajectory(active, options.reason);
|
|
25
|
+
await storage.save(abandoned);
|
|
26
|
+
|
|
27
|
+
console.log(`✓ Trajectory abandoned: ${abandoned.id}`);
|
|
28
|
+
if (options.reason) {
|
|
29
|
+
console.log(` Reason: ${options.reason}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail complete command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import { completeTrajectory } from "../../core/trajectory.js";
|
|
7
|
+
import { FileStorage } from "../../storage/file.js";
|
|
8
|
+
|
|
9
|
+
export function registerCompleteCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("complete")
|
|
12
|
+
.description("Complete the active trajectory with retrospective")
|
|
13
|
+
.option("--summary <text>", "Summary of what was accomplished")
|
|
14
|
+
.option("--approach <text>", "How the work was approached")
|
|
15
|
+
.option("--confidence <number>", "Confidence level 0-1", parseFloat)
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
const storage = new FileStorage();
|
|
18
|
+
await storage.initialize();
|
|
19
|
+
|
|
20
|
+
const active = await storage.getActive();
|
|
21
|
+
if (!active) {
|
|
22
|
+
console.error("Error: No active trajectory");
|
|
23
|
+
console.error("Start one with: trail start \"Task description\"");
|
|
24
|
+
throw new Error("No active trajectory");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Require summary and confidence
|
|
28
|
+
if (!options.summary) {
|
|
29
|
+
console.error("Error: --summary is required");
|
|
30
|
+
throw new Error("Summary required");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const confidence = options.confidence ?? 0.8;
|
|
34
|
+
if (confidence < 0 || confidence > 1) {
|
|
35
|
+
console.error("Error: --confidence must be between 0 and 1");
|
|
36
|
+
throw new Error("Invalid confidence");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const completed = completeTrajectory(active, {
|
|
40
|
+
summary: options.summary,
|
|
41
|
+
approach: options.approach || "Standard approach",
|
|
42
|
+
confidence,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await storage.save(completed);
|
|
46
|
+
|
|
47
|
+
console.log(`✓ Trajectory completed: ${completed.id}`);
|
|
48
|
+
console.log(` Summary: ${options.summary}`);
|
|
49
|
+
console.log(` Confidence: ${Math.round(confidence * 100)}%`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail decision command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import { addDecision } from "../../core/trajectory.js";
|
|
7
|
+
import { FileStorage } from "../../storage/file.js";
|
|
8
|
+
|
|
9
|
+
export function registerDecisionCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("decision <choice>")
|
|
12
|
+
.description("Record a decision")
|
|
13
|
+
.option("-r, --reasoning <text>", "Why this choice was made (optional for minor decisions)")
|
|
14
|
+
.option("-a, --alternatives <items>", "Comma-separated alternatives considered")
|
|
15
|
+
.action(async (choice: string, options) => {
|
|
16
|
+
const storage = new FileStorage();
|
|
17
|
+
await storage.initialize();
|
|
18
|
+
|
|
19
|
+
const active = await storage.getActive();
|
|
20
|
+
if (!active) {
|
|
21
|
+
console.error("Error: No active trajectory");
|
|
22
|
+
console.error("Start one with: trail start \"Task description\"");
|
|
23
|
+
throw new Error("No active trajectory");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const alternatives = options.alternatives
|
|
27
|
+
? options.alternatives.split(",").map((s: string) => s.trim())
|
|
28
|
+
: [];
|
|
29
|
+
|
|
30
|
+
const reasoning = options.reasoning || "";
|
|
31
|
+
|
|
32
|
+
const updated = addDecision(active, {
|
|
33
|
+
question: choice,
|
|
34
|
+
chosen: choice,
|
|
35
|
+
alternatives,
|
|
36
|
+
reasoning,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await storage.save(updated);
|
|
40
|
+
|
|
41
|
+
console.log(`✓ Decision recorded: ${choice}`);
|
|
42
|
+
if (reasoning) {
|
|
43
|
+
console.log(` Reasoning: ${reasoning}`);
|
|
44
|
+
}
|
|
45
|
+
if (alternatives.length > 0) {
|
|
46
|
+
console.log(` Alternatives: ${alternatives.join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail export command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { exec } from "node:child_process";
|
|
9
|
+
import { FileStorage } from "../../storage/file.js";
|
|
10
|
+
import { exportToMarkdown } from "../../export/markdown.js";
|
|
11
|
+
import { exportToJSON } from "../../export/json.js";
|
|
12
|
+
import { exportToTimeline } from "../../export/timeline.js";
|
|
13
|
+
import { generateTrajectoryHtml } from "../../web/generator.js";
|
|
14
|
+
|
|
15
|
+
export function registerExportCommand(program: Command): void {
|
|
16
|
+
program
|
|
17
|
+
.command("export [id]")
|
|
18
|
+
.description("Export a trajectory")
|
|
19
|
+
.option("-f, --format <format>", "Export format (md, json, timeline, html)", "md")
|
|
20
|
+
.option("-o, --output <path>", "Output file path")
|
|
21
|
+
.option("--open", "Open in browser (html format only)")
|
|
22
|
+
.action(async (id: string | undefined, options) => {
|
|
23
|
+
const storage = new FileStorage();
|
|
24
|
+
await storage.initialize();
|
|
25
|
+
|
|
26
|
+
// If no ID provided, use active trajectory
|
|
27
|
+
let trajectory;
|
|
28
|
+
if (id) {
|
|
29
|
+
trajectory = await storage.get(id);
|
|
30
|
+
if (!trajectory) {
|
|
31
|
+
console.error(`Error: Trajectory not found: ${id}`);
|
|
32
|
+
throw new Error("Trajectory not found");
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
trajectory = await storage.getActive();
|
|
36
|
+
if (!trajectory) {
|
|
37
|
+
console.error("Error: No active trajectory and no ID provided");
|
|
38
|
+
console.error("Usage: trail export <id> or trail export (with active trajectory)");
|
|
39
|
+
throw new Error("No trajectory specified");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let output: string;
|
|
44
|
+
|
|
45
|
+
switch (options.format) {
|
|
46
|
+
case "json":
|
|
47
|
+
output = exportToJSON(trajectory);
|
|
48
|
+
break;
|
|
49
|
+
case "timeline":
|
|
50
|
+
output = exportToTimeline(trajectory);
|
|
51
|
+
break;
|
|
52
|
+
case "html":
|
|
53
|
+
output = generateTrajectoryHtml(trajectory);
|
|
54
|
+
break;
|
|
55
|
+
case "md":
|
|
56
|
+
case "markdown":
|
|
57
|
+
default:
|
|
58
|
+
output = exportToMarkdown(trajectory);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options.output) {
|
|
63
|
+
await writeFile(options.output, output, "utf-8");
|
|
64
|
+
console.log(`✓ Exported to ${options.output}`);
|
|
65
|
+
|
|
66
|
+
if (options.open && options.format === "html") {
|
|
67
|
+
openInBrowser(options.output);
|
|
68
|
+
}
|
|
69
|
+
} else if (options.open && options.format === "html") {
|
|
70
|
+
// Write to temp location and open
|
|
71
|
+
const outputDir = join(process.cwd(), ".trajectories", "html");
|
|
72
|
+
await mkdir(outputDir, { recursive: true });
|
|
73
|
+
const filePath = join(outputDir, `${trajectory.id}.html`);
|
|
74
|
+
await writeFile(filePath, output, "utf-8");
|
|
75
|
+
console.log(`✓ Generated: ${filePath}`);
|
|
76
|
+
openInBrowser(filePath);
|
|
77
|
+
} else {
|
|
78
|
+
console.log(output);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function openInBrowser(path: string): void {
|
|
84
|
+
const platform = process.platform;
|
|
85
|
+
let command: string;
|
|
86
|
+
|
|
87
|
+
if (platform === "darwin") {
|
|
88
|
+
command = `open "${path}"`;
|
|
89
|
+
} else if (platform === "win32") {
|
|
90
|
+
command = `start "" "${path}"`;
|
|
91
|
+
} else {
|
|
92
|
+
command = `xdg-open "${path}"`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exec(command, (error) => {
|
|
96
|
+
if (error) {
|
|
97
|
+
console.log(`Open manually: file://${path}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Command Registration
|
|
3
|
+
*
|
|
4
|
+
* Registers all commands with the program.
|
|
5
|
+
*
|
|
6
|
+
* Core commands (8 total):
|
|
7
|
+
* - start: Begin tracking a new task
|
|
8
|
+
* - status: Show current trajectory state
|
|
9
|
+
* - decision: Record a decision point
|
|
10
|
+
* - complete: Finish with retrospective
|
|
11
|
+
* - abandon: Stop without completing
|
|
12
|
+
* - list: Browse trajectories (with --search)
|
|
13
|
+
* - show: View trajectory details
|
|
14
|
+
* - export: Output in various formats (with --open)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Command } from "commander";
|
|
18
|
+
import { registerStartCommand } from "./start.js";
|
|
19
|
+
import { registerStatusCommand } from "./status.js";
|
|
20
|
+
import { registerCompleteCommand } from "./complete.js";
|
|
21
|
+
import { registerAbandonCommand } from "./abandon.js";
|
|
22
|
+
import { registerDecisionCommand } from "./decision.js";
|
|
23
|
+
import { registerListCommand } from "./list.js";
|
|
24
|
+
import { registerShowCommand } from "./show.js";
|
|
25
|
+
import { registerExportCommand } from "./export.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register all CLI commands
|
|
29
|
+
*/
|
|
30
|
+
export function registerCommands(program: Command): void {
|
|
31
|
+
registerStartCommand(program);
|
|
32
|
+
registerStatusCommand(program);
|
|
33
|
+
registerCompleteCommand(program);
|
|
34
|
+
registerAbandonCommand(program);
|
|
35
|
+
registerDecisionCommand(program);
|
|
36
|
+
registerListCommand(program);
|
|
37
|
+
registerShowCommand(program);
|
|
38
|
+
registerExportCommand(program);
|
|
39
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail list command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import { FileStorage } from "../../storage/file.js";
|
|
7
|
+
import type { TrajectoryStatus } from "../../core/types.js";
|
|
8
|
+
|
|
9
|
+
export function registerListCommand(program: Command): void {
|
|
10
|
+
program
|
|
11
|
+
.command("list")
|
|
12
|
+
.description("List and search trajectories")
|
|
13
|
+
.option("-s, --status <status>", "Filter by status (active, completed, abandoned)")
|
|
14
|
+
.option("-l, --limit <number>", "Limit results", parseInt)
|
|
15
|
+
.option("--search <query>", "Search trajectories by title or content")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
const storage = new FileStorage();
|
|
18
|
+
await storage.initialize();
|
|
19
|
+
|
|
20
|
+
let trajectories = await storage.list({
|
|
21
|
+
status: options.status as TrajectoryStatus | undefined,
|
|
22
|
+
limit: options.search ? undefined : options.limit, // Apply limit after search
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Apply search filter if provided
|
|
26
|
+
if (options.search) {
|
|
27
|
+
const query = options.search.toLowerCase();
|
|
28
|
+
trajectories = trajectories.filter((traj) => {
|
|
29
|
+
// Search in title
|
|
30
|
+
if (traj.title.toLowerCase().includes(query)) return true;
|
|
31
|
+
// Search in ID
|
|
32
|
+
if (traj.id.toLowerCase().includes(query)) return true;
|
|
33
|
+
return false;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Apply limit after search
|
|
37
|
+
if (options.limit) {
|
|
38
|
+
trajectories = trajectories.slice(0, options.limit);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (trajectories.length === 0) {
|
|
43
|
+
if (options.search) {
|
|
44
|
+
console.log(`No trajectories found matching "${options.search}"`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log("No trajectories found");
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const searchNote = options.search ? ` matching "${options.search}"` : "";
|
|
52
|
+
console.log(`Found ${trajectories.length} trajectories${searchNote}:\n`);
|
|
53
|
+
|
|
54
|
+
for (const traj of trajectories) {
|
|
55
|
+
const statusIcon = getStatusIcon(traj.status);
|
|
56
|
+
const confidence = traj.confidence
|
|
57
|
+
? ` (${Math.round(traj.confidence * 100)}%)`
|
|
58
|
+
: "";
|
|
59
|
+
|
|
60
|
+
console.log(`${statusIcon} ${traj.id}`);
|
|
61
|
+
console.log(` ${traj.title}${confidence}`);
|
|
62
|
+
console.log(` Started: ${formatDate(traj.startedAt)}`);
|
|
63
|
+
if (traj.completedAt) {
|
|
64
|
+
console.log(` Completed: ${formatDate(traj.completedAt)}`);
|
|
65
|
+
}
|
|
66
|
+
console.log("");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getStatusIcon(status: string): string {
|
|
72
|
+
switch (status) {
|
|
73
|
+
case "active":
|
|
74
|
+
return "🔄";
|
|
75
|
+
case "completed":
|
|
76
|
+
return "✅";
|
|
77
|
+
case "abandoned":
|
|
78
|
+
return "❌";
|
|
79
|
+
default:
|
|
80
|
+
return "•";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatDate(isoString: string): string {
|
|
85
|
+
return new Date(isoString).toLocaleDateString("en-US", {
|
|
86
|
+
month: "short",
|
|
87
|
+
day: "numeric",
|
|
88
|
+
year: "numeric",
|
|
89
|
+
});
|
|
90
|
+
}
|