opencode-replay 1.0.0 → 1.0.2
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 +5 -0
- package/dist/index.js +164 -4
- package/package.json +2 -1
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.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { parseArgs } from "util";
|
|
6
|
-
import { resolve as resolve2, join as
|
|
6
|
+
import { resolve as resolve2, join as join5 } from "path";
|
|
7
7
|
import { readdir as readdir2 } from "fs/promises";
|
|
8
8
|
|
|
9
9
|
// src/storage/reader.ts
|
|
@@ -2224,6 +2224,161 @@ function openBrowser(url) {
|
|
|
2224
2224
|
}
|
|
2225
2225
|
}
|
|
2226
2226
|
|
|
2227
|
+
// src/utils/update-notifier.ts
|
|
2228
|
+
import { homedir as homedir2 } from "os";
|
|
2229
|
+
import { join as join4 } from "path";
|
|
2230
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
2231
|
+
var PACKAGE_NAME = "opencode-replay";
|
|
2232
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
2233
|
+
function detectPackageManager() {
|
|
2234
|
+
const modulePath = import.meta.dir;
|
|
2235
|
+
if (modulePath.includes(".bun") || modulePath.includes("bun/install")) {
|
|
2236
|
+
return "bun";
|
|
2237
|
+
}
|
|
2238
|
+
if (process.env.BUN_INSTALL) {
|
|
2239
|
+
return "bun";
|
|
2240
|
+
}
|
|
2241
|
+
if (typeof Bun !== "undefined") {
|
|
2242
|
+
const bunInstallPath = join4(homedir2(), ".bun");
|
|
2243
|
+
try {
|
|
2244
|
+
if (Bun.file(bunInstallPath).size) {
|
|
2245
|
+
return "bun";
|
|
2246
|
+
}
|
|
2247
|
+
} catch {}
|
|
2248
|
+
}
|
|
2249
|
+
return "npm";
|
|
2250
|
+
}
|
|
2251
|
+
function getCachePath() {
|
|
2252
|
+
const cacheDir = process.env.XDG_CACHE_HOME || join4(homedir2(), ".cache");
|
|
2253
|
+
return join4(cacheDir, "opencode-replay", "update-check.json");
|
|
2254
|
+
}
|
|
2255
|
+
async function readCache() {
|
|
2256
|
+
try {
|
|
2257
|
+
const cachePath = getCachePath();
|
|
2258
|
+
const file = Bun.file(cachePath);
|
|
2259
|
+
if (await file.exists()) {
|
|
2260
|
+
return await file.json();
|
|
2261
|
+
}
|
|
2262
|
+
} catch {}
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
async function writeCache(data) {
|
|
2266
|
+
try {
|
|
2267
|
+
const cachePath = getCachePath();
|
|
2268
|
+
await mkdir2(join4(cachePath, ".."), { recursive: true });
|
|
2269
|
+
await Bun.write(cachePath, JSON.stringify(data));
|
|
2270
|
+
} catch {}
|
|
2271
|
+
}
|
|
2272
|
+
async function fetchLatestVersion() {
|
|
2273
|
+
try {
|
|
2274
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
2275
|
+
headers: {
|
|
2276
|
+
Accept: "application/json"
|
|
2277
|
+
},
|
|
2278
|
+
signal: AbortSignal.timeout(3000)
|
|
2279
|
+
});
|
|
2280
|
+
if (!response.ok) {
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
const data = await response.json();
|
|
2284
|
+
return data.version || null;
|
|
2285
|
+
} catch {
|
|
2286
|
+
return null;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
function isNewerVersion(current, latest) {
|
|
2290
|
+
const currentParts = current.split(".").map(Number);
|
|
2291
|
+
const latestParts = latest.split(".").map(Number);
|
|
2292
|
+
for (let i = 0;i < 3; i++) {
|
|
2293
|
+
const c = currentParts[i] || 0;
|
|
2294
|
+
const l = latestParts[i] || 0;
|
|
2295
|
+
if (l > c)
|
|
2296
|
+
return true;
|
|
2297
|
+
if (l < c)
|
|
2298
|
+
return false;
|
|
2299
|
+
}
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
async function checkForUpdates(currentVersion) {
|
|
2303
|
+
try {
|
|
2304
|
+
const cache = await readCache();
|
|
2305
|
+
const now = Date.now();
|
|
2306
|
+
if (cache && now - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
2307
|
+
if (cache.latestVersion && isNewerVersion(currentVersion, cache.latestVersion)) {
|
|
2308
|
+
const pm = detectPackageManager();
|
|
2309
|
+
return {
|
|
2310
|
+
current: currentVersion,
|
|
2311
|
+
latest: cache.latestVersion,
|
|
2312
|
+
packageManager: pm,
|
|
2313
|
+
updateCommand: pm === "bun" ? `bun update -g ${PACKAGE_NAME}` : `npm update -g ${PACKAGE_NAME}`
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
const latestVersion = await fetchLatestVersion();
|
|
2319
|
+
await writeCache({
|
|
2320
|
+
lastCheck: now,
|
|
2321
|
+
latestVersion
|
|
2322
|
+
});
|
|
2323
|
+
if (latestVersion && isNewerVersion(currentVersion, latestVersion)) {
|
|
2324
|
+
const pm = detectPackageManager();
|
|
2325
|
+
return {
|
|
2326
|
+
current: currentVersion,
|
|
2327
|
+
latest: latestVersion,
|
|
2328
|
+
packageManager: pm,
|
|
2329
|
+
updateCommand: pm === "bun" ? `bun update -g ${PACKAGE_NAME}` : `npm update -g ${PACKAGE_NAME}`
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
return null;
|
|
2333
|
+
} catch {
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function formatUpdateNotification(info) {
|
|
2338
|
+
const reset = "\x1B[0m";
|
|
2339
|
+
const dim = "\x1B[2m";
|
|
2340
|
+
const bold = "\x1B[1m";
|
|
2341
|
+
const yellow = "\x1B[33m";
|
|
2342
|
+
const cyan = "\x1B[36m";
|
|
2343
|
+
const green = "\x1B[32m";
|
|
2344
|
+
const topLeft = "\u250C";
|
|
2345
|
+
const topRight = "\u2510";
|
|
2346
|
+
const bottomLeft = "\u2514";
|
|
2347
|
+
const bottomRight = "\u2518";
|
|
2348
|
+
const horizontal = "\u2500";
|
|
2349
|
+
const vertical = "\u2502";
|
|
2350
|
+
const line1 = `Update available: ${dim}${info.current}${reset} \u2192 ${green}${bold}${info.latest}${reset}`;
|
|
2351
|
+
const line2 = `Run: ${cyan}${info.updateCommand}${reset}`;
|
|
2352
|
+
const contentWidth = Math.max(`Update available: ${info.current} \u2192 ${info.latest}`.length, `Run: ${info.updateCommand}`.length);
|
|
2353
|
+
const boxWidth = contentWidth + 4;
|
|
2354
|
+
const topBorder = `${yellow}${topLeft}${horizontal.repeat(boxWidth)}${topRight}${reset}`;
|
|
2355
|
+
const bottomBorder = `${yellow}${bottomLeft}${horizontal.repeat(boxWidth)}${bottomRight}${reset}`;
|
|
2356
|
+
const padLine = (line, visibleLength) => {
|
|
2357
|
+
const padding = boxWidth - visibleLength - 2;
|
|
2358
|
+
return `${yellow}${vertical}${reset} ${line}${" ".repeat(padding)} ${yellow}${vertical}${reset}`;
|
|
2359
|
+
};
|
|
2360
|
+
const paddedLine1 = padLine(line1, `Update available: ${info.current} \u2192 ${info.latest}`.length);
|
|
2361
|
+
const paddedLine2 = padLine(line2, `Run: ${info.updateCommand}`.length);
|
|
2362
|
+
return `
|
|
2363
|
+
${topBorder}
|
|
2364
|
+
${paddedLine1}
|
|
2365
|
+
${paddedLine2}
|
|
2366
|
+
${bottomBorder}
|
|
2367
|
+
`;
|
|
2368
|
+
}
|
|
2369
|
+
async function notifyIfUpdateAvailable(currentVersion) {
|
|
2370
|
+
if (!process.stdout.isTTY) {
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
if (process.env.NO_COLOR) {
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const updateInfo = await checkForUpdates(currentVersion);
|
|
2377
|
+
if (updateInfo) {
|
|
2378
|
+
console.error(formatUpdateNotification(updateInfo));
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2227
2382
|
// src/index.ts
|
|
2228
2383
|
var colors = {
|
|
2229
2384
|
reset: "\x1B[0m",
|
|
@@ -2429,9 +2584,10 @@ Examples:
|
|
|
2429
2584
|
`);
|
|
2430
2585
|
process.exit(0);
|
|
2431
2586
|
}
|
|
2587
|
+
var pkg = await Bun.file(resolve2(import.meta.dir, "..", "package.json")).json();
|
|
2588
|
+
var currentVersion = pkg.version;
|
|
2432
2589
|
if (values.version) {
|
|
2433
|
-
|
|
2434
|
-
console.log(pkg.version);
|
|
2590
|
+
console.log(currentVersion);
|
|
2435
2591
|
process.exit(0);
|
|
2436
2592
|
}
|
|
2437
2593
|
var storagePath = values.storage ?? getDefaultStoragePath();
|
|
@@ -2447,7 +2603,7 @@ if (isNaN(port) || port < 1 || port > 65535) {
|
|
|
2447
2603
|
}
|
|
2448
2604
|
try {
|
|
2449
2605
|
await readdir2(storagePath);
|
|
2450
|
-
const projectDir =
|
|
2606
|
+
const projectDir = join5(storagePath, "project");
|
|
2451
2607
|
try {
|
|
2452
2608
|
await readdir2(projectDir);
|
|
2453
2609
|
} catch {
|
|
@@ -2548,6 +2704,7 @@ if (values["no-generate"]) {
|
|
|
2548
2704
|
}
|
|
2549
2705
|
}
|
|
2550
2706
|
if (values.serve) {
|
|
2707
|
+
await notifyIfUpdateAvailable(currentVersion);
|
|
2551
2708
|
await serve({
|
|
2552
2709
|
directory: resolve2(outputDir),
|
|
2553
2710
|
port,
|
|
@@ -2557,6 +2714,9 @@ if (values.serve) {
|
|
|
2557
2714
|
const indexPath = resolve2(outputDir, "index.html");
|
|
2558
2715
|
openInBrowser(indexPath);
|
|
2559
2716
|
}
|
|
2717
|
+
if (!values.serve) {
|
|
2718
|
+
await notifyIfUpdateAvailable(currentVersion);
|
|
2719
|
+
}
|
|
2560
2720
|
function openInBrowser(target) {
|
|
2561
2721
|
const platform = process.platform;
|
|
2562
2722
|
if (platform === "darwin") {
|
package/package.json
CHANGED