Tool to send cross-session opencode messages, including as request-response pattern
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

move list command to session module with store.ts, types.ts, format.ts; rename sensor/trait.ts to types.ts; fix all imports to use .ts extensions

rektide f68ff181 d0d4ead7

+124 -117
+2 -104
src/commands/list.ts
··· 1 - import { readdir, readFile, access } from "node:fs/promises"; 2 - import { join } from "node:path"; 3 - import { homedir } from "node:os"; 4 1 import { define } from "gunshi"; 5 - 6 - interface Session { 7 - id: string; 8 - slug: string; 9 - projectID: string; 10 - directory: string; 11 - parentID?: string; 12 - title: string; 13 - version: string; 14 - time: { 15 - created: number; 16 - updated: number; 17 - compacting?: number; 18 - archived?: number; 19 - }; 20 - summary?: { 21 - additions: number; 22 - deletions: number; 23 - files: number; 24 - }; 25 - share?: { 26 - url: string; 27 - }; 28 - } 29 - 30 - interface ListOptions { 31 - dir?: string; 32 - } 33 - 34 - async function getOpenCodeStoragePath(): Promise<string> { 35 - const override = process.env.OPENCODE_TEST_HOME; 36 - if (override) { 37 - return join(override, "storage"); 38 - } 39 - return join(homedir(), ".local", "share", "opencode", "storage"); 40 - } 41 - 42 - function matchesPattern(sessionDir: string, pattern: string): boolean { 43 - const hasWildcard = pattern.includes("*"); 44 - 45 - if (!hasWildcard) { 46 - return sessionDir === pattern; 47 - } 48 - 49 - const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, "."); 50 - const regex = new RegExp(`^${regexPattern}$`); 51 - 52 - return regex.test(sessionDir); 53 - } 54 - 55 - async function listSessions(dir?: string): Promise<Session[]> { 56 - const storagePath = await getOpenCodeStoragePath(); 57 - const sessionPath = join(storagePath, "session"); 58 - 59 - try { 60 - await access(sessionPath); 61 - } catch { 62 - return []; 63 - } 2 + import { listSessions, displaySessions } from "../session/index.ts"; 64 3 65 - const projectDirs = await readdir(sessionPath, { withFileTypes: true }); 66 - const sessions: Session[] = []; 67 - 68 - for (const projectDir of projectDirs) { 69 - if (!projectDir.isDirectory()) continue; 70 - 71 - const projectPath = join(sessionPath, projectDir.name); 72 - const sessionFiles = await readdir(projectPath); 73 - 74 - for (const sessionFile of sessionFiles) { 75 - if (!sessionFile.endsWith(".json")) continue; 76 - 77 - const sessionFilePath = join(projectPath, sessionFile); 78 - const content = await readFile(sessionFilePath, "utf-8"); 79 - const session: Session = JSON.parse(content); 80 - 81 - if (dir && !matchesPattern(session.directory, dir)) continue; 82 - 83 - sessions.push(session); 84 - } 85 - } 86 - 87 - return sessions.sort((a, b) => b.time.updated - a.time.updated); 88 - } 89 - 90 - function displaySessions(sessions: Session[]) { 91 - if (sessions.length === 0) { 92 - return; 93 - } 94 - 95 - console.log("id\ttitle\tupdated\tdirectory"); 96 - 97 - for (const session of sessions) { 98 - const title = (session.title || "Untitled").replace(/\n/g, "\\n"); 99 - const updated = new Date(session.time.updated).toISOString(); 100 - const directory = session.directory; 101 - 102 - console.log(`${session.id}\t${title}\t${updated}\t${directory}`); 103 - } 104 - } 105 - 106 - export const list = define<ListOptions>({ 4 + export const list = define({ 107 5 name: "list", 108 6 description: "List all OpenCode sessions", 109 7 options: {
+1 -1
src/sensor/cache.ts
··· 1 - import { Instance, Sensor } from "./trait.ts"; 1 + import type { Instance, Sensor } from "./types.ts"; 2 2 import { mergeGenerators } from "../util/generator.ts"; 3 3 4 4 export interface CacheOptions {
+1 -1
src/sensor/index.ts
··· 1 - export { Instance, Sensor } from "./trait.ts"; 1 + export { Instance, Sensor } from "./types.ts"; 2 2 export { MdnsSensor } from "./mdns.ts"; 3 3 export { ProcSensor } from "./proc.ts"; 4 4 export { CacheSensor, type CacheOptions } from "./cache.ts";
+1 -1
src/sensor/mdns.ts
··· 1 1 import { Browser } from "bonjour-service"; 2 - import { Instance, Sensor } from "./trait.ts"; 2 + import type { Instance, Sensor } from "./types.ts"; 3 3 4 4 export class MdnsSensor implements Sensor { 5 5 private browser?: Browser;
+1 -1
src/sensor/proc.ts
··· 1 1 import { readdir, readFile } from "node:fs/promises"; 2 2 import { join } from "node:path"; 3 - import { Instance, Sensor } from "./trait.ts"; 3 + import type { Instance, Sensor } from "./types.ts"; 4 4 5 5 interface ProcInfo { 6 6 pid: number;
src/sensor/trait.ts src/sensor/types.ts
+2 -2
src/session/active.ts
··· 1 - import { Instance } from "../sensor/trait.ts"; 2 - import { Session } from "./session.ts"; 1 + import type { Instance } from "../sensor/types.ts"; 2 + import type { Session } from "./session.ts"; 3 3 4 4 export type ActiveSession = Session & { 5 5 port: number;
+1 -1
src/session/filter.ts
··· 1 - import { Session } from "./session.ts"; 1 + import type { Session } from "./types.ts"; 2 2 3 3 export type SessionPredicate = (session: Session) => boolean; 4 4
+7 -3
src/session/index.ts
··· 1 - export { Session } from "./session.ts"; 2 - export { ActiveSession, getActiveSessions } from "./active.ts"; 3 - export { SessionFilter, filterSessions } from "./filter.ts"; 1 + export type { Session } from "./types.ts"; 2 + export type { ActiveSession } from "./active.ts"; 3 + export { getOpenCodeStoragePath, listSessions } from "./store.ts"; 4 + export { getActiveSessions } from "./active.ts"; 5 + export type { SessionFilter } from "./filter.ts"; 6 + export { filterSessions } from "./filter.ts"; 7 + export { displaySessions, matchesPattern } from "./types.ts";
+4 -3
src/session/session.ts
··· 1 - export interface Session { 2 - sessionID: string; 1 + import type { Session as PersistedSession } from "./types.ts"; 2 + 3 + export type Session = Omit<PersistedSession, "time" | "summary" | "share"> & { 3 4 port?: number; 4 5 hostname?: string; 5 6 pid?: number; ··· 9 10 retryAttempt?: number; 10 11 retryMessage?: string; 11 12 retryNext?: number; 12 - } 13 + };
+52
src/session/store.ts
··· 1 + import { readdir, readFile, access } from "node:fs/promises"; 2 + import { join } from "node:path"; 3 + import { homedir } from "node:os"; 4 + import type { Session } from "./types.ts"; 5 + 6 + export async function getOpenCodeStoragePath(): Promise<string> { 7 + const override = process.env.OPENCODE_TEST_HOME; 8 + if (override) { 9 + return join(override, "storage"); 10 + } 11 + return join(homedir(), ".local", "share", "opencode", "storage"); 12 + } 13 + 14 + export async function listSessions(dir?: string): Promise<Session[]> { 15 + const storagePath = await getOpenCodeStoragePath(); 16 + const sessionPath = join(storagePath, "session"); 17 + 18 + try { 19 + await access(sessionPath); 20 + } catch { 21 + return []; 22 + } 23 + 24 + const projectDirs = await readdir(sessionPath, { withFileTypes: true }); 25 + const sessions: Session[] = []; 26 + 27 + for (const projectDir of projectDirs) { 28 + if (!projectDir.isDirectory()) continue; 29 + 30 + const projectPath = join(sessionPath, projectDir.name); 31 + const sessionFiles = await readdir(projectPath); 32 + 33 + for (const sessionFile of sessionFiles) { 34 + if (!sessionFile.endsWith(".json")) continue; 35 + 36 + const sessionFilePath = join(projectPath, sessionFile); 37 + const content = await readFile(sessionFilePath, "utf-8"); 38 + const session: Session = JSON.parse(content); 39 + 40 + if (dir && !session.directory.match(patternForDir(dir))) continue; 41 + 42 + sessions.push(session); 43 + } 44 + } 45 + 46 + return sessions.sort((a, b) => b.time.updated - a.time.updated); 47 + } 48 + 49 + function patternForDir(dir: string | undefined): RegExp | undefined { 50 + if (!dir) return undefined; 51 + return new RegExp(`^${dir.replace(/\*/g, ".*").replace(/\?/g, ".")}(?:/.*)?$`); 52 + }
+52
src/session/types.ts
··· 1 + export interface Session { 2 + id: string; 3 + slug: string; 4 + projectID: string; 5 + directory: string; 6 + parentID?: string; 7 + title: string; 8 + version: string; 9 + time: { 10 + created: number; 11 + updated: number; 12 + compacting?: number; 13 + archived?: number; 14 + }; 15 + summary?: { 16 + additions: number; 17 + deletions: number; 18 + files: number; 19 + }; 20 + share?: { 21 + url: string; 22 + }; 23 + } 24 + 25 + export function matchesPattern(sessionDir: string, pattern: string): boolean { 26 + const hasWildcard = pattern.includes("*"); 27 + 28 + if (!hasWildcard) { 29 + return sessionDir === pattern; 30 + } 31 + 32 + const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, "."); 33 + const regex = new RegExp(`^${regexPattern}$`); 34 + 35 + return regex.test(sessionDir); 36 + } 37 + 38 + export function displaySessions(sessions: Session[]): void { 39 + if (sessions.length === 0) { 40 + return; 41 + } 42 + 43 + console.log("id\ttitle\tupdated\tdirectory"); 44 + 45 + for (const session of sessions) { 46 + const title = (session.title || "Untitled").replace(/\n/g, "\\n"); 47 + const updated = new Date(session.time.updated).toISOString(); 48 + const directory = session.directory; 49 + 50 + console.log(`${session.id}\t${title}\t${updated}\t${directory}`); 51 + } 52 + }