diff --git a/src/cli.ts b/src/cli.ts index 2cbbbb3b3..eb7adb610 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -35,6 +35,8 @@ interface CliFlags { appendSystemPrompt?: string tools?: string[] messages: string[] + /** Set by `gsd sessions` when the user picks a specific session to resume */ + _selectedSessionPath?: string } function exitIfManagedResourcesAreNewer(currentAgentDir: string): void { @@ -115,6 +117,63 @@ if (cliFlags.messages[0] === 'update') { process.exit(0) } +// `gsd sessions` — list past sessions and pick one to resume +if (cliFlags.messages[0] === 'sessions') { + const cwd = process.cwd() + const safePath = `--${cwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--` + const projectSessionsDir = join(sessionsDir, safePath) + + process.stderr.write(chalk.dim(`Loading sessions for ${cwd}...\n`)) + const sessions = await SessionManager.list(cwd, projectSessionsDir) + + if (sessions.length === 0) { + process.stderr.write(chalk.yellow('No sessions found for this directory.\n')) + process.exit(0) + } + + process.stderr.write(chalk.bold(`\n Sessions (${sessions.length}):\n\n`)) + + const maxShow = 20 + const toShow = sessions.slice(0, maxShow) + for (let i = 0; i < toShow.length; i++) { + const s = toShow[i] + const date = s.modified.toLocaleString() + const msgs = s.messageCount + const name = s.name ? ` ${chalk.cyan(s.name)}` : '' + const preview = s.firstMessage + ? s.firstMessage.replace(/\n/g, ' ').substring(0, 80) + : chalk.dim('(empty)') + const num = String(i + 1).padStart(3) + process.stderr.write(` ${chalk.bold(num)}. ${chalk.green(date)} ${chalk.dim(`(${msgs} msgs)`)}${name}\n`) + process.stderr.write(` ${chalk.dim(preview)}\n\n`) + } + + if (sessions.length > maxShow) { + process.stderr.write(chalk.dim(` ... and ${sessions.length - maxShow} more\n\n`)) + } + + // Interactive selection + const readline = await import('node:readline') + const rl = readline.createInterface({ input: process.stdin, output: process.stderr }) + const answer = await new Promise((resolve) => { + rl.question(chalk.bold(' Enter session number to resume (or q to quit): '), resolve) + }) + rl.close() + + const choice = parseInt(answer, 10) + if (isNaN(choice) || choice < 1 || choice > toShow.length) { + process.stderr.write(chalk.dim('Cancelled.\n')) + process.exit(0) + } + + const selected = toShow[choice - 1] + process.stderr.write(chalk.green(`\nResuming session from ${selected.modified.toLocaleString()}...\n\n`)) + + // Mark for the interactive session below to open this specific session + cliFlags.continue = true + cliFlags._selectedSessionPath = selected.path +} + // Pi's tool bootstrap can mis-detect already-installed fd/rg on some systems // because spawnSync(..., ["--version"]) returns EPERM despite a zero exit code. // Provision local managed binaries first so Pi sees them without probing PATH. @@ -350,9 +409,11 @@ if (existsSync(sessionsDir)) { } } -const sessionManager = cliFlags.continue - ? SessionManager.continueRecent(cwd, projectSessionsDir) - : SessionManager.create(cwd, projectSessionsDir) +const sessionManager = cliFlags._selectedSessionPath + ? SessionManager.open(cliFlags._selectedSessionPath, projectSessionsDir) + : cliFlags.continue + ? SessionManager.continueRecent(cwd, projectSessionsDir) + : SessionManager.create(cwd, projectSessionsDir) exitIfManagedResourcesAreNewer(agentDir) initResources(agentDir) diff --git a/src/help-text.ts b/src/help-text.ts index 561a7a6fe..ed4e5fdbc 100644 --- a/src/help-text.ts +++ b/src/help-text.ts @@ -18,6 +18,19 @@ const SUBCOMMAND_HELP: Record = { '', 'Equivalent to: npm install -g gsd-pi@latest', ].join('\n'), + + sessions: [ + 'Usage: gsd sessions', + '', + 'List all saved sessions for the current directory and interactively', + 'pick one to resume. Shows date, message count, and a preview of the', + 'first message for each session.', + '', + 'Sessions are stored per-directory, so you only see sessions that were', + 'started from the current working directory.', + '', + 'Compare with --continue (-c) which always resumes the most recent session.', + ].join('\n'), } export function printHelp(version: string): void { @@ -37,6 +50,7 @@ export function printHelp(version: string): void { process.stdout.write('\nSubcommands:\n') process.stdout.write(' config Re-run the setup wizard\n') process.stdout.write(' update Update GSD to the latest version\n') + process.stdout.write(' sessions List and resume a past session\n') process.stdout.write('\nRun gsd --help for subcommand-specific help.\n') }