opencode-replay 1.0.1 → 1.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 +86 -3
- package/dist/assets/gist-preview.js +126 -0
- package/dist/index.js +1307 -215
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# opencode-replay
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-replay)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-replay)
|
|
5
|
+
|
|
3
6
|
A CLI tool that generates static HTML transcripts from [OpenCode](https://github.com/sst/opencode) sessions, enabling browsing, searching, and sharing of AI-assisted coding conversations.
|
|
4
7
|
|
|
8
|
+
**[Live Demo](https://ramtinj95.github.io/opencode-replay/)** - See example session transcripts
|
|
9
|
+
|
|
5
10
|
## Why?
|
|
6
11
|
|
|
7
12
|
OpenCode stores session data in `~/.local/share/opencode/storage/` as JSON files, but this data isn't easily browsable or shareable. `opencode-replay` transforms these sessions into clean, searchable, static HTML pages.
|
|
@@ -34,6 +39,47 @@ opencode-replay
|
|
|
34
39
|
opencode-replay --open
|
|
35
40
|
```
|
|
36
41
|
|
|
42
|
+
## Output Formats
|
|
43
|
+
|
|
44
|
+
### HTML (default)
|
|
45
|
+
|
|
46
|
+
Generate static HTML transcripts viewable in any browser:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
opencode-replay # Current project's sessions
|
|
50
|
+
opencode-replay --all # All projects
|
|
51
|
+
opencode-replay -o ./my-transcripts # Custom output directory
|
|
52
|
+
opencode-replay -a # Auto-name output (e.g., ./my-project-replay)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Markdown
|
|
56
|
+
|
|
57
|
+
Generate markdown for sharing or piping:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# To stdout (for piping)
|
|
61
|
+
opencode-replay -f md -s ses_xxx
|
|
62
|
+
opencode-replay -f md -s ses_xxx | gh gist create --filename transcript.md -
|
|
63
|
+
opencode-replay -f md -s ses_xxx | pbcopy
|
|
64
|
+
|
|
65
|
+
# To file
|
|
66
|
+
opencode-replay -f md -s ses_xxx -o transcript.md
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### GitHub Gist
|
|
70
|
+
|
|
71
|
+
Upload HTML directly to GitHub Gist with a shareable preview URL:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
opencode-replay --gist # Secret gist (default)
|
|
75
|
+
opencode-replay --gist --gist-public # Public gist
|
|
76
|
+
opencode-replay -s ses_xxx --gist # Upload specific session
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Requires [GitHub CLI](https://cli.github.com/) to be installed and authenticated (`gh auth login`).
|
|
80
|
+
|
|
81
|
+
The generated gist is viewable via [gisthost.github.io](https://gisthost.github.io/) which renders HTML files directly.
|
|
82
|
+
|
|
37
83
|
## Usage
|
|
38
84
|
|
|
39
85
|
### Basic Commands
|
|
@@ -80,19 +126,40 @@ The built-in server includes:
|
|
|
80
126
|
- Path traversal protection
|
|
81
127
|
- Auto-opens browser on start
|
|
82
128
|
|
|
129
|
+
### GitHub Integration
|
|
130
|
+
|
|
131
|
+
Add clickable links to GitHub commits in the rendered output:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
opencode-replay --repo owner/repo-name
|
|
135
|
+
|
|
136
|
+
# Example
|
|
137
|
+
opencode-replay --repo sst/opencode
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This adds links to commit hashes found in tool outputs, making it easy to navigate to the exact commits referenced during a session.
|
|
141
|
+
|
|
83
142
|
### All Options
|
|
84
143
|
|
|
85
144
|
| Option | Short | Description |
|
|
86
145
|
|--------|-------|-------------|
|
|
87
|
-
| `--all` |
|
|
88
|
-
| `--
|
|
146
|
+
| `--all` | | Generate for all projects (default: current project only) |
|
|
147
|
+
| `--auto` | `-a` | Auto-name output directory from project/session name |
|
|
148
|
+
| `--output <path>` | `-o` | Output directory (HTML) or file (markdown) |
|
|
89
149
|
| `--session <id>` | `-s` | Generate for a specific session only |
|
|
150
|
+
| `--format <type>` | `-f` | Output format: `html` (default), `md` |
|
|
151
|
+
| `--stdout` | | Output to stdout (markdown only, requires `--session`) |
|
|
152
|
+
| `--gist` | | Upload HTML to GitHub Gist after generation |
|
|
153
|
+
| `--gist-public` | | Make gist public (default: secret) |
|
|
90
154
|
| `--json` | | Include raw JSON export alongside HTML |
|
|
91
155
|
| `--open` | | Open in browser after generation |
|
|
92
156
|
| `--storage <path>` | | Custom storage path (default: `~/.local/share/opencode/storage`) |
|
|
93
157
|
| `--serve` | | Start HTTP server after generation |
|
|
94
158
|
| `--port <number>` | | Server port (default: `3000`) |
|
|
95
159
|
| `--no-generate` | | Skip generation, only serve existing output |
|
|
160
|
+
| `--repo <owner/name>` | | GitHub repo for commit links (e.g., `sst/opencode`) |
|
|
161
|
+
| `--quiet` | `-q` | Suppress non-essential output |
|
|
162
|
+
| `--verbose` | | Show detailed debug output |
|
|
96
163
|
| `--help` | `-h` | Show help message |
|
|
97
164
|
| `--version` | `-v` | Show version |
|
|
98
165
|
|
|
@@ -109,6 +176,9 @@ opencode-replay --all --open
|
|
|
109
176
|
# Export a specific session with JSON data
|
|
110
177
|
opencode-replay --session ses_abc123 --json -o ./session-export
|
|
111
178
|
|
|
179
|
+
# Auto-name output from project name
|
|
180
|
+
opencode-replay -a # Creates ./my-project-replay
|
|
181
|
+
|
|
112
182
|
# Use custom storage location
|
|
113
183
|
opencode-replay --storage /custom/path/to/storage
|
|
114
184
|
|
|
@@ -117,6 +187,18 @@ opencode-replay --serve --port 8080
|
|
|
117
187
|
|
|
118
188
|
# Quick preview of existing transcripts
|
|
119
189
|
opencode-replay --serve --no-generate -o ./my-transcripts
|
|
190
|
+
|
|
191
|
+
# Markdown to stdout for piping
|
|
192
|
+
opencode-replay -f md -s ses_xxx
|
|
193
|
+
|
|
194
|
+
# Create a GitHub Gist from a session
|
|
195
|
+
opencode-replay -s ses_xxx --gist
|
|
196
|
+
|
|
197
|
+
# Upload all projects to a public gist
|
|
198
|
+
opencode-replay --all --gist --gist-public
|
|
199
|
+
|
|
200
|
+
# Add GitHub commit links
|
|
201
|
+
opencode-replay --repo sst/opencode
|
|
120
202
|
```
|
|
121
203
|
|
|
122
204
|
## Output Structure
|
|
@@ -126,7 +208,7 @@ opencode-replay-output/
|
|
|
126
208
|
├── index.html # Master index (all sessions)
|
|
127
209
|
├── assets/
|
|
128
210
|
│ ├── styles.css # Stylesheet
|
|
129
|
-
│ └── search.js # Client-side search
|
|
211
|
+
│ └── search.js # Client-side search
|
|
130
212
|
├── projects/ # Only in --all mode
|
|
131
213
|
│ └── {project-name}/
|
|
132
214
|
│ └── index.html # Project-level session list
|
|
@@ -159,6 +241,7 @@ Each tool type has specialized rendering:
|
|
|
159
241
|
|
|
160
242
|
- [Bun](https://bun.sh) runtime (recommended) or Node.js 18+
|
|
161
243
|
- OpenCode sessions in standard storage location
|
|
244
|
+
- [GitHub CLI](https://cli.github.com/) (optional, for `--gist` feature)
|
|
162
245
|
|
|
163
246
|
## Development
|
|
164
247
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Replay - Gist Preview Link Rewriter
|
|
3
|
+
* Rewrites relative links for gisthost.github.io preview
|
|
4
|
+
*
|
|
5
|
+
* When HTML files are uploaded to GitHub Gist and viewed via gisthost.github.io,
|
|
6
|
+
* relative links need to be rewritten to include the gist ID in the URL format:
|
|
7
|
+
* ?GIST_ID/path/to/file.html
|
|
8
|
+
*
|
|
9
|
+
* This script runs only on gisthost.github.io and automatically rewrites all
|
|
10
|
+
* relative links in the document.
|
|
11
|
+
*/
|
|
12
|
+
(function initGistPreview() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// Only run on gisthost.github.io
|
|
16
|
+
if (!window.location.host.includes('gisthost.github.io')) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Extract gist ID from URL
|
|
21
|
+
// URL format: https://gisthost.github.io/?GIST_ID/filename.html
|
|
22
|
+
var search = window.location.search;
|
|
23
|
+
if (!search || search.length < 2) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Remove leading '?' and extract gist ID (everything before first '/')
|
|
28
|
+
var path = search.slice(1);
|
|
29
|
+
var slashIndex = path.indexOf('/');
|
|
30
|
+
if (slashIndex === -1) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var gistId = path.slice(0, slashIndex);
|
|
35
|
+
if (!gistId) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get current directory for resolving relative paths
|
|
40
|
+
var currentPath = path.slice(slashIndex + 1);
|
|
41
|
+
var currentDir = currentPath.substring(0, currentPath.lastIndexOf('/') + 1);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a relative path against the current directory
|
|
45
|
+
* Handles './' and '../' prefixes
|
|
46
|
+
*/
|
|
47
|
+
function resolvePath(href) {
|
|
48
|
+
// Already absolute or anchor-only
|
|
49
|
+
if (href.startsWith('http') || href.startsWith('#') || href.startsWith('?')) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var resolved = currentDir + href;
|
|
54
|
+
|
|
55
|
+
// Normalize path (handle ../)
|
|
56
|
+
var parts = resolved.split('/');
|
|
57
|
+
var normalized = [];
|
|
58
|
+
for (var i = 0; i < parts.length; i++) {
|
|
59
|
+
var part = parts[i];
|
|
60
|
+
if (part === '..') {
|
|
61
|
+
normalized.pop();
|
|
62
|
+
} else if (part !== '.' && part !== '') {
|
|
63
|
+
normalized.push(part);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return normalized.join('/');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Rewrite a single link element
|
|
72
|
+
*/
|
|
73
|
+
function rewriteLink(link) {
|
|
74
|
+
var href = link.getAttribute('href');
|
|
75
|
+
if (!href) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var resolved = resolvePath(href);
|
|
80
|
+
if (resolved !== null) {
|
|
81
|
+
link.setAttribute('href', '?' + gistId + '/' + resolved);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Rewrite all links in the document
|
|
87
|
+
*/
|
|
88
|
+
function rewriteAllLinks() {
|
|
89
|
+
var links = document.querySelectorAll('a[href]');
|
|
90
|
+
for (var i = 0; i < links.length; i++) {
|
|
91
|
+
rewriteLink(links[i]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Run on DOMContentLoaded or immediately if DOM is ready
|
|
96
|
+
if (document.readyState === 'loading') {
|
|
97
|
+
document.addEventListener('DOMContentLoaded', rewriteAllLinks);
|
|
98
|
+
} else {
|
|
99
|
+
rewriteAllLinks();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Also observe for dynamically added links (optional but useful)
|
|
103
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
104
|
+
var observer = new MutationObserver(function(mutations) {
|
|
105
|
+
mutations.forEach(function(mutation) {
|
|
106
|
+
mutation.addedNodes.forEach(function(node) {
|
|
107
|
+
if (node.nodeType === 1) { // Element node
|
|
108
|
+
if (node.tagName === 'A' && node.hasAttribute('href')) {
|
|
109
|
+
rewriteLink(node);
|
|
110
|
+
}
|
|
111
|
+
// Also check children
|
|
112
|
+
var childLinks = node.querySelectorAll ? node.querySelectorAll('a[href]') : [];
|
|
113
|
+
for (var i = 0; i < childLinks.length; i++) {
|
|
114
|
+
rewriteLink(childLinks[i]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
observer.observe(document.body || document.documentElement, {
|
|
122
|
+
childList: true,
|
|
123
|
+
subtree: true
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
})();
|