Session command with list subcommand added

Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot] 2025-11-20 21:52:12 +00:00
parent 8ab206b443
commit d5c15f70d0
3 changed files with 225 additions and 0 deletions

View file

@ -0,0 +1,206 @@
/**
* Session management commands
*
* Provides commands to list and manage opencode sessions.
* The main use case is to help users find session IDs for use with --session flag.
*
* Examples:
* opencode session list # List all sessions in default format
* opencode session list --format ids # List only session IDs (CI-friendly)
* opencode session list --format json # List sessions as JSON
* opencode session list --limit 5 # Show only 5 most recent sessions
*/
import type { Argv } from "yargs"
import { Session } from "../../session"
import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap"
import { UI } from "../ui"
import { EOL } from "os"
export const SessionListCommand = cmd({
command: "list",
describe: "list all sessions with their IDs",
builder: (yargs: Argv) => {
return yargs
.option("format", {
alias: "f",
describe: "output format",
type: "string",
choices: ["default", "json", "ids"],
default: "default",
})
.option("limit", {
alias: "l",
describe: "limit number of sessions shown",
type: "number",
})
},
handler: async (args) => {
await bootstrap(process.cwd(), async () => {
// Collect all sessions from storage
const sessions = []
for await (const session of Session.list()) {
sessions.push(session)
}
// Handle empty case
if (sessions.length === 0) {
UI.println("No sessions found")
return
}
// Sort by most recently updated
sessions.sort((a, b) => b.time.updated - a.time.updated)
// Apply limit if specified
if (args.limit) {
sessions.splice(args.limit)
}
// JSON format - full session data
if (args.format === "json") {
process.stdout.write(JSON.stringify(sessions, null, 2) + EOL)
return
}
// IDs format - just the session IDs (CI-friendly)
if (args.format === "ids") {
sessions.forEach(session => {
process.stdout.write(session.id + EOL)
})
return
}
// Default format - git log style table
const terminalWidth = process.stdout.columns || 80
const idWidth = Math.min(20, Math.max(8, Math.max(...sessions.map(s => s.id.length))))
const timeWidth = 19
const titleWidth = terminalWidth - idWidth - timeWidth - 4
// Header
UI.println(
UI.Style.TEXT_DIM + "Session ID".padEnd(idWidth) + " " +
"Last Updated".padEnd(timeWidth) + " " +
"Title"
)
// Session rows
sessions.forEach(session => {
const shortId = session.id.slice(-idWidth).padStart(idWidth)
const time = new Date(session.time.updated).toLocaleString().padEnd(timeWidth)
const title = session.title.length > titleWidth
? session.title.slice(0, titleWidth - 3) + "..."
: session.title.padEnd(titleWidth)
UI.println(
UI.Style.TEXT_INFO + shortId + " " +
UI.Style.TEXT_NORMAL + time + " " +
UI.Style.TEXT_HIGHLIGHT + title
)
})
})
},
})
export const SessionCommand = cmd({
command: "session",
describe: "manage sessions",
builder: (yargs) =>
yargs
.command(SessionListCommand)
.demandCommand(),
async handler() {},
})
.command({
command: "list",
describe: "list all sessions with their IDs",
builder: (yargs: Argv) => {
return yargs
.option("format", {
alias: "f",
describe: "output format",
type: "string",
choices: ["default", "json", "ids"],
default: "default",
})
.option("limit", {
alias: "l",
describe: "limit number of sessions shown",
type: "number",
})
},
handler: async (args) => {
await bootstrap(process.cwd(), async () => {
const sessions = []
for await (const session of Session.list()) {
sessions.push(session)
}
if (sessions.length === 0) {
UI.println("No sessions found")
return
}
sessions.sort((a, b) => b.time.updated - a.time.updated)
if (args.limit) {
sessions.splice(args.limit)
}
if (args.format === "json") {
process.stdout.write(JSON.stringify(sessions, null, 2) + EOL)
return
}
if (args.format === "ids") {
sessions.forEach((session) => {
process.stdout.write(session.id + EOL)
})
return
}
// Default format - similar to git log
const terminalWidth = process.stdout.columns || 80
const idWidth = Math.min(20, Math.max(8, Math.max(...sessions.map((s) => s.id.length))))
const timeWidth = 19
const titleWidth = terminalWidth - idWidth - timeWidth - 4
UI.println(
UI.Style.TEXT_DIM +
"Session ID".padEnd(idWidth) +
" " +
"Last Updated".padEnd(timeWidth) +
" " +
"Title",
)
sessions.forEach((session) => {
const shortId = session.id.slice(-idWidth).padStart(idWidth)
const time = new Date(session.time.updated).toLocaleString().padEnd(timeWidth)
const title =
session.title.length > titleWidth
? session.title.slice(0, titleWidth - 3) + "..."
: session.title.padEnd(titleWidth)
UI.println(
UI.Style.TEXT_INFO +
shortId +
" " +
UI.Style.TEXT_NORMAL +
time +
" " +
UI.Style.TEXT_HIGHLIGHT +
title,
)
})
})
},
})
.demandCommand(1, "You need to specify a subcommand")
.strict()
},
handler: async () => {
// This handler won't be called due to demandCommand
},
})

View file

@ -25,6 +25,7 @@ import { AcpCommand } from "./cli/cmd/acp"
import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
process.on("unhandledRejection", (e) => {
Log.Default.error("rejection", {
@ -92,6 +93,7 @@ const cli = yargs(hideBin(process.argv))
.command(ImportCommand)
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
.fail((msg) => {
if (
msg.startsWith("Unknown argument") ||

View file

@ -0,0 +1,17 @@
import { describe, test, expect } from "bun:test"
import { SessionListCommand } from "../../src/cli/cmd/session"
describe("SessionCommand", () => {
test("should have correct command structure", () => {
expect(SessionListCommand.command).toBe("list")
expect(SessionListCommand.describe).toBe("list all sessions with their IDs")
})
test("should have builder function", () => {
expect(typeof SessionListCommand.builder).toBe("function")
})
test("should have handler function", () => {
expect(typeof SessionListCommand.handler).toBe("function")
})
})