codesesh 0.1.0 → 0.1.3

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.
@@ -1,191 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { handleGetAgents, handleGetSessions, handleGetSessionData } from "../handlers.js";
3
- import type { ScanResult, SessionHead, SessionData } from "@codesesh/core";
4
- import { BaseAgent } from "@codesesh/core";
5
-
6
- // --- Helpers ---
7
-
8
- function makeSession(id: string, overrides?: Partial<SessionHead>): SessionHead {
9
- return {
10
- id,
11
- slug: `agent/${id}`,
12
- title: `Session ${id}`,
13
- time_created: 1000,
14
- time_updated: 1000,
15
- directory: "/home/user/project",
16
- ...overrides,
17
- };
18
- }
19
-
20
- function makeMockContext(
21
- overrides: {
22
- query?: Record<string, string>;
23
- param?: Record<string, string>;
24
- } = {},
25
- ) {
26
- const jsonFn = vi.fn().mockReturnValue({ status: 200 });
27
- return {
28
- req: {
29
- query: (key: string) => overrides.query?.[key] ?? "",
30
- param: (key: string) => overrides.param?.[key] ?? "",
31
- },
32
- json: jsonFn,
33
- } as any;
34
- }
35
-
36
- class MockAgent extends BaseAgent {
37
- readonly name = "claudecode";
38
- readonly displayName = "Claude Code";
39
-
40
- isAvailable() {
41
- return true;
42
- }
43
-
44
- scan(): SessionHead[] {
45
- return [];
46
- }
47
-
48
- getSessionData(_sessionId: string): SessionData {
49
- return {
50
- id: "s1",
51
- slug: "claudecode/s1",
52
- title: "Test Session",
53
- time_created: 1000,
54
- time_updated: 1000,
55
- messages: [],
56
- stats: {
57
- message_count: 0,
58
- total_input_tokens: 0,
59
- total_output_tokens: 0,
60
- total_cost: 0,
61
- },
62
- };
63
- }
64
- }
65
-
66
- function makeScanResult(overrides?: Partial<ScanResult>): ScanResult {
67
- const agent = new MockAgent();
68
- return {
69
- sessions: [makeSession("s1"), makeSession("s2")],
70
- byAgent: { claudecode: [makeSession("s1"), makeSession("s2")] },
71
- agents: [agent],
72
- ...overrides,
73
- };
74
- }
75
-
76
- // --- Tests ---
77
-
78
- describe("handleGetAgents", () => {
79
- it("returns agent info list", () => {
80
- const c = makeMockContext();
81
- const result = makeScanResult();
82
- handleGetAgents(c, result);
83
- expect(c.json).toHaveBeenCalled();
84
- const response = c.json.mock.calls[0]![0];
85
- expect(Array.isArray(response)).toBe(true);
86
- });
87
- });
88
-
89
- describe("handleGetSessions", () => {
90
- it("returns all sessions without filters", () => {
91
- const c = makeMockContext();
92
- const result = makeScanResult();
93
- handleGetSessions(c, result);
94
- const response = c.json.mock.calls[0]![0];
95
- expect(response.sessions).toHaveLength(2);
96
- });
97
-
98
- it("filters by agent", () => {
99
- const c = makeMockContext({ query: { agent: "claudecode" } });
100
- const result = makeScanResult();
101
- handleGetSessions(c, result);
102
- const response = c.json.mock.calls[0]![0];
103
- expect(response.sessions).toHaveLength(2);
104
- });
105
-
106
- it("falls back to all sessions when agent not found in byAgent", () => {
107
- const c = makeMockContext({ query: { agent: "nonexistent" } });
108
- const result = makeScanResult();
109
- handleGetSessions(c, result);
110
- const response = c.json.mock.calls[0]![0];
111
- // Falls back to scanResult.sessions
112
- expect(response.sessions).toHaveLength(2);
113
- });
114
-
115
- it("filters by q (title search)", () => {
116
- const c = makeMockContext({ query: { q: "s1" } });
117
- const result = makeScanResult();
118
- handleGetSessions(c, result);
119
- const response = c.json.mock.calls[0]![0];
120
- expect(response.sessions).toHaveLength(1);
121
- expect(response.sessions[0].id).toBe("s1");
122
- });
123
-
124
- it("filters by cwd (substring match)", () => {
125
- const c = makeMockContext({ query: { cwd: "project" } });
126
- const result = makeScanResult();
127
- handleGetSessions(c, result);
128
- const response = c.json.mock.calls[0]![0];
129
- expect(response.sessions).toHaveLength(2);
130
- });
131
-
132
- it("filters by from date", () => {
133
- const c = makeMockContext({ query: { from: "2024-01-01" } });
134
- const result = makeScanResult({
135
- sessions: [
136
- makeSession("old", { time_created: new Date("2023-01-01").getTime() }),
137
- makeSession("new", { time_created: new Date("2025-01-01").getTime() }),
138
- ],
139
- byAgent: {},
140
- });
141
- handleGetSessions(c, result);
142
- const response = c.json.mock.calls[0]![0];
143
- expect(response.sessions).toHaveLength(1);
144
- expect(response.sessions[0].id).toBe("new");
145
- });
146
-
147
- it("ignores invalid from date", () => {
148
- const c = makeMockContext({ query: { from: "not-a-date" } });
149
- const result = makeScanResult();
150
- handleGetSessions(c, result);
151
- const response = c.json.mock.calls[0]![0];
152
- // Invalid date → filter not applied
153
- expect(response.sessions).toHaveLength(2);
154
- });
155
- });
156
-
157
- describe("handleGetSessionData", () => {
158
- it("returns session data for valid agent and id", async () => {
159
- const c = makeMockContext({ param: { agent: "claudecode", id: "s1" } });
160
- const result = makeScanResult();
161
- await handleGetSessionData(c, result);
162
- expect(c.json).toHaveBeenCalled();
163
- const response = c.json.mock.calls[0]![0];
164
- expect(response.title).toBe("Test Session");
165
- });
166
-
167
- it("returns 400 when session ID is missing", async () => {
168
- const c = makeMockContext({ param: { agent: "claudecode", id: "" } });
169
- const result = makeScanResult();
170
- await handleGetSessionData(c, result);
171
- expect(c.json).toHaveBeenCalledWith({ error: "Missing session ID" }, 400);
172
- });
173
-
174
- it("returns 404 for unknown agent", async () => {
175
- const c = makeMockContext({ param: { agent: "unknown", id: "s1" } });
176
- const result = makeScanResult();
177
- await handleGetSessionData(c, result);
178
- expect(c.json).toHaveBeenCalledWith({ error: "Unknown agent: unknown" }, 404);
179
- });
180
-
181
- it("returns 500 when agent throws", async () => {
182
- const agent = new MockAgent();
183
- agent.getSessionData = () => {
184
- throw new Error("DB not found");
185
- };
186
- const c = makeMockContext({ param: { agent: "claudecode", id: "s1" } });
187
- const result = makeScanResult({ agents: [agent] });
188
- await handleGetSessionData(c, result);
189
- expect(c.json).toHaveBeenCalledWith({ error: "DB not found" }, 500);
190
- });
191
- });
@@ -1,16 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createApiRoutes } from "../routes.js";
3
- import type { ScanResult } from "@codesesh/core";
4
-
5
- describe("createApiRoutes", () => {
6
- it("returns a Hono instance with route handlers", () => {
7
- const scanResult = {
8
- sessions: [],
9
- byAgent: {},
10
- agents: [],
11
- } as unknown as ScanResult;
12
- const app = createApiRoutes(scanResult);
13
- expect(app).toBeDefined();
14
- expect(app.fetch).toBeDefined();
15
- });
16
- });
@@ -1,76 +0,0 @@
1
- import type { Context } from "hono";
2
- import type { ScanResult, SessionData, SessionHead } from "@codesesh/core";
3
- import { getAgentInfoMap } from "@codesesh/core";
4
-
5
- export function handleGetAgents(c: Context, scanResult: ScanResult) {
6
- const counts: Record<string, number> = {};
7
- for (const agent of scanResult.agents) {
8
- counts[agent.name] = scanResult.byAgent[agent.name]?.length ?? 0;
9
- }
10
- const info = getAgentInfoMap(counts);
11
- return c.json(info);
12
- }
13
-
14
- export function handleGetSessions(c: Context, scanResult: ScanResult) {
15
- const agent = c.req.query("agent");
16
- const q = c.req.query("q")?.toLowerCase();
17
- const cwd = c.req.query("cwd")?.toLowerCase();
18
- const from = c.req.query("from");
19
- const to = c.req.query("to");
20
-
21
- let sessions: SessionHead[] = [];
22
-
23
- // If agent filter is specified, use byAgent directly
24
- if (agent && scanResult.byAgent[agent]) {
25
- sessions = [...scanResult.byAgent[agent]!];
26
- } else {
27
- sessions = [...scanResult.sessions];
28
- }
29
-
30
- if (cwd) {
31
- sessions = sessions.filter((s) => s.directory.toLowerCase().includes(cwd));
32
- }
33
-
34
- if (from) {
35
- const fromTs = new Date(from).getTime();
36
- if (!Number.isNaN(fromTs)) {
37
- sessions = sessions.filter((s) => s.time_created >= fromTs);
38
- }
39
- }
40
-
41
- if (to) {
42
- const toTs = new Date(to).getTime();
43
- if (!Number.isNaN(toTs)) {
44
- sessions = sessions.filter((s) => s.time_created <= toTs);
45
- }
46
- }
47
-
48
- if (q) {
49
- sessions = sessions.filter((s) => s.title.toLowerCase().includes(q));
50
- }
51
-
52
- return c.json({ sessions });
53
- }
54
-
55
- export async function handleGetSessionData(c: Context, scanResult: ScanResult) {
56
- const agentName = c.req.param("agent");
57
- const sessionId = c.req.param("id");
58
-
59
- if (!sessionId) {
60
- return c.json({ error: "Missing session ID" }, 400);
61
- }
62
-
63
- const agent = scanResult.agents.find((a) => a.name === agentName);
64
-
65
- if (!agent) {
66
- return c.json({ error: `Unknown agent: ${agentName}` }, 404);
67
- }
68
-
69
- try {
70
- const data: SessionData = agent.getSessionData(sessionId);
71
- return c.json(data);
72
- } catch (err) {
73
- const message = err instanceof Error ? err.message : "Failed to load session";
74
- return c.json({ error: message }, 500);
75
- }
76
- }
package/src/api/routes.ts DELETED
@@ -1,13 +0,0 @@
1
- import { Hono } from "hono";
2
- import type { ScanResult } from "@codesesh/core";
3
- import { handleGetAgents, handleGetSessions, handleGetSessionData } from "./handlers.js";
4
-
5
- export function createApiRoutes(scanResult: ScanResult): Hono {
6
- const api = new Hono();
7
-
8
- api.get("/agents", (c) => handleGetAgents(c, scanResult));
9
- api.get("/sessions", (c) => handleGetSessions(c, scanResult));
10
- api.get("/sessions/:agent/:id", (c) => handleGetSessionData(c, scanResult));
11
-
12
- return api;
13
- }
@@ -1,78 +0,0 @@
1
- import { defineCommand } from "citty";
2
- import { createServer } from "../server.js";
3
- import { printScanResults } from "../output.js";
4
- import { scanSessions, createRegisteredAgents } from "@codesesh/core";
5
-
6
- export const serveCommand = defineCommand({
7
- meta: {
8
- name: "serve",
9
- description: "Scan sessions and start web server",
10
- },
11
- args: {
12
- port: {
13
- type: "string",
14
- alias: "p",
15
- default: "4321",
16
- },
17
- agent: {
18
- type: "string",
19
- alias: "a",
20
- },
21
- cwd: {
22
- type: "string",
23
- },
24
- from: {
25
- type: "string",
26
- },
27
- to: {
28
- type: "string",
29
- },
30
- json: {
31
- type: "boolean",
32
- alias: "j",
33
- default: false,
34
- },
35
- "no-open": {
36
- type: "boolean",
37
- default: false,
38
- },
39
- },
40
- async run({ args }) {
41
- const port = parseInt(args.port as string, 10) || 4321;
42
- const noOpen = args["no-open"] as boolean;
43
- const jsonOnly = args.json as boolean;
44
-
45
- // Scan sessions
46
- const result = scanSessions();
47
- const agents = createRegisteredAgents();
48
-
49
- if (jsonOnly) {
50
- const { getAgentInfoMap } = await import("@codesesh/core");
51
- const info = getAgentInfoMap(
52
- Object.fromEntries(Object.entries(result.byAgent).map(([k, v]) => [k, v.length])),
53
- );
54
- const output = {
55
- agents: info.map(({ name, displayName, count }) => ({
56
- name,
57
- displayName,
58
- count,
59
- available: count > 0,
60
- })),
61
- sessions: result.sessions,
62
- };
63
- console.log(JSON.stringify(output, null, 2));
64
- return;
65
- }
66
-
67
- // Print console output
68
- printScanResults(agents, result);
69
-
70
- // Start server
71
- const { url } = await createServer(port, result);
72
-
73
- if (!noOpen) {
74
- const open = (await import("open")).default;
75
- await open(url);
76
- }
77
- },
78
- });
package/src/index.ts DELETED
@@ -1,203 +0,0 @@
1
- import { defineCommand, runMain } from "citty";
2
- import { createServer } from "./server.js";
3
- import { printScanResults } from "./output.js";
4
- import {
5
- scanSessionsAsync,
6
- createRegisteredAgents,
7
- getAgentInfoMap,
8
- type ScanOptions,
9
- perf,
10
- } from "@codesesh/core";
11
-
12
- const VERSION = "0.1.0";
13
-
14
- function parseDateToTimestamp(dateStr: string): number {
15
- const date = new Date(dateStr);
16
- if (Number.isNaN(date.getTime())) {
17
- throw new Error(`Invalid date: ${dateStr}`);
18
- }
19
- return date.getTime();
20
- }
21
-
22
- function parseSessionUri(uri: string): { agent: string; sessionId: string } | null {
23
- const match = uri.match(/^([a-z]+):\/\/(.+)$/i);
24
- if (!match) return null;
25
- return { agent: match[1]!, sessionId: match[2]! };
26
- }
27
-
28
- const main = defineCommand({
29
- meta: {
30
- name: "codesesh",
31
- description: "Discover, aggregate, and visualize AI coding agent sessions",
32
- version: VERSION,
33
- },
34
- args: {
35
- port: {
36
- type: "string",
37
- alias: "p",
38
- description: "HTTP server port",
39
- default: "4321",
40
- },
41
- agent: {
42
- type: "string",
43
- alias: "a",
44
- description: "Filter to specific agent(s), comma-separated",
45
- },
46
- days: {
47
- type: "string",
48
- alias: "d",
49
- description: "Only include sessions from the last N days (0 = all time)",
50
- default: "7",
51
- },
52
- cwd: {
53
- type: "string",
54
- description: "Filter to sessions from a specific project directory (use '.' for current dir)",
55
- },
56
- from: {
57
- type: "string",
58
- description: "Sessions created after this date, YYYY-MM-DD (overrides --days)",
59
- },
60
- to: {
61
- type: "string",
62
- description: "Sessions created before this date (YYYY-MM-DD)",
63
- },
64
- session: {
65
- type: "string",
66
- alias: "s",
67
- description: "Directly open a specific session (agent://session-id)",
68
- },
69
- json: {
70
- type: "boolean",
71
- alias: "j",
72
- description: "Output session index as JSON to stdout (no server)",
73
- default: false,
74
- },
75
- noOpen: {
76
- type: "boolean",
77
- description: "Don't auto-open browser",
78
- default: false,
79
- },
80
- trace: {
81
- type: "boolean",
82
- description: "Show performance trace logs",
83
- default: false,
84
- },
85
- cache: {
86
- type: "boolean",
87
- description: "Use cached scan results if available",
88
- default: true,
89
- },
90
- "clear-cache": {
91
- type: "boolean",
92
- description: "Clear scan cache before starting",
93
- default: false,
94
- },
95
- },
96
- async run({ args }) {
97
- const port = parseInt(args.port as string, 10) || 4321;
98
- const noOpen = args.noOpen as boolean;
99
- const jsonOnly = args.json as boolean;
100
- const trace = args.trace as boolean;
101
- const useCache = args.cache as boolean;
102
- const clearCache = args["clear-cache"] as boolean;
103
-
104
- if (trace) {
105
- perf.enable();
106
- }
107
-
108
- if (clearCache) {
109
- const { clearCache: clear } = await import("@codesesh/core");
110
- clear();
111
- console.log("Cache cleared.");
112
- }
113
-
114
- // Parse session URI if provided
115
- let targetSession: { agent: string; sessionId: string } | null = null;
116
- if (args.session) {
117
- targetSession = parseSessionUri(args.session as string);
118
- if (!targetSession) {
119
- console.error(`Invalid session format: ${args.session}. Expected: agent://session-id`);
120
- process.exit(1);
121
- }
122
- }
123
-
124
- // Resolve cwd filter: '.' => process.cwd()
125
- let cwdFilter = args.cwd as string | undefined;
126
- if (cwdFilter === ".") {
127
- cwdFilter = process.cwd();
128
- }
129
-
130
- // Resolve from timestamp: --from takes priority over --days
131
- let fromTimestamp: number | undefined;
132
- if (args.from) {
133
- fromTimestamp = parseDateToTimestamp(args.from as string);
134
- } else {
135
- const days = parseInt(args.days as string, 10);
136
- if (!Number.isNaN(days) && days > 0) {
137
- fromTimestamp = Date.now() - days * 24 * 60 * 60 * 1000;
138
- }
139
- }
140
-
141
- // Build scan options
142
- const scanOptions: ScanOptions = {
143
- agents: targetSession
144
- ? [targetSession.agent]
145
- : args.agent
146
- ? (args.agent as string).split(",").map((a) => a.trim())
147
- : undefined,
148
- cwd: cwdFilter,
149
- from: fromTimestamp,
150
- to: args.to ? parseDateToTimestamp(args.to as string) : undefined,
151
- useCache: useCache,
152
- };
153
-
154
- // Scan sessions (parallel)
155
- const result = await scanSessionsAsync(scanOptions);
156
-
157
- if (trace) {
158
- console.log(perf.getReport());
159
- }
160
-
161
- if (jsonOnly) {
162
- const info = getAgentInfoMap(
163
- Object.fromEntries(Object.entries(result.byAgent).map(([k, v]) => [k, v.length])),
164
- );
165
- const output = {
166
- agents: info.map(({ name, displayName, count }) => ({
167
- name,
168
- displayName,
169
- count,
170
- available: count > 0,
171
- })),
172
- sessions: result.sessions,
173
- };
174
- console.log(JSON.stringify(output, null, 2));
175
- return;
176
- }
177
-
178
- // Print console output
179
- const agents = createRegisteredAgents();
180
- printScanResults(agents, result);
181
-
182
- // Start server
183
- const { url } = await createServer(port, result);
184
-
185
- console.log(` http://localhost:${port}`);
186
- console.log("");
187
-
188
- if (!noOpen) {
189
- const open = (await import("open")).default;
190
- const targetUrl = targetSession
191
- ? `${url}/${targetSession.agent.toLowerCase()}/${targetSession.sessionId}`
192
- : url;
193
- await open(targetUrl);
194
- }
195
- },
196
- });
197
-
198
- if (process.argv.slice(2).includes("-v")) {
199
- console.log(VERSION);
200
- process.exit(0);
201
- }
202
-
203
- runMain(main);
package/src/output.ts DELETED
@@ -1,47 +0,0 @@
1
- import { consola } from "consola";
2
- import type { BaseAgent } from "@codesesh/core";
3
- import type { ScanResult } from "@codesesh/core";
4
-
5
- export function printScanResults(agents: BaseAgent[], result: ScanResult): void {
6
- consola.log("");
7
- consola.box({
8
- title: "CodeSesh",
9
- message: `v0.1.0 • ${result.sessions.length} sessions discovered`,
10
- style: {
11
- padding: 1,
12
- borderColor: "cyan",
13
- },
14
- });
15
- consola.log("");
16
-
17
- const rows: string[] = [];
18
- let availableCount = 0;
19
-
20
- for (const agent of agents) {
21
- const sessions = result.byAgent[agent.name];
22
- const count = sessions?.length ?? 0;
23
- if (count > 0) {
24
- availableCount++;
25
- rows.push(` ${green("✔")} ${pad(agent.displayName)} ${dim(`${count} sessions`)}`);
26
- } else {
27
- rows.push(` ${dim("✖")} ${pad(agent.displayName)} ${dim("not found")}`);
28
- }
29
- }
30
-
31
- consola.log(rows.join("\n"));
32
- consola.log("");
33
- consola.info(`Active: ${availableCount}/${agents.length} agents`);
34
- consola.log("");
35
- }
36
-
37
- function pad(text: string, length = 16): string {
38
- return text.padEnd(length);
39
- }
40
-
41
- function green(text: string): string {
42
- return `\x1b[32m${text}\x1b[0m`;
43
- }
44
-
45
- function dim(text: string): string {
46
- return `\x1b[2m${text}\x1b[0m`;
47
- }
package/src/server.ts DELETED
@@ -1,56 +0,0 @@
1
- import { Hono } from "hono";
2
- import { serve } from "@hono/node-server";
3
- import { serveStatic } from "@hono/node-server/serve-static";
4
- import { logger } from "hono/logger";
5
- import { existsSync } from "node:fs";
6
- import { resolve, dirname } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import type { ScanResult } from "@codesesh/core";
9
- import { createApiRoutes } from "./api/routes.js";
10
-
11
- function findWebDistPath(): string | null {
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
-
14
- // Priority 1: Packaged web dist (copied during build)
15
- const packagedPath = resolve(__dirname, "../web");
16
- if (existsSync(packagedPath)) {
17
- return packagedPath;
18
- }
19
-
20
- // Priority 2: Development path (monorepo)
21
- const devPath = resolve(__dirname, "../../../apps/web/dist");
22
- if (existsSync(devPath)) {
23
- return devPath;
24
- }
25
-
26
- return null;
27
- }
28
-
29
- export async function createServer(
30
- port: number,
31
- scanResult: ScanResult,
32
- ): Promise<{ url: string; shutdown: () => void }> {
33
- const app = new Hono();
34
-
35
- app.use("*", logger());
36
-
37
- // API routes
38
- app.route("/api", createApiRoutes(scanResult));
39
-
40
- // Serve static files from web dist (if available)
41
- const webDistPath = findWebDistPath();
42
-
43
- if (webDistPath) {
44
- app.use("/*", serveStatic({ root: webDistPath }));
45
- app.get("/*", serveStatic({ root: webDistPath, path: "index.html" }));
46
- }
47
-
48
- const server = serve({ fetch: app.fetch, port });
49
-
50
- const url = `http://localhost:${port}`;
51
-
52
- return {
53
- url,
54
- shutdown: () => server.close(),
55
- };
56
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src",
6
- "types": ["node"]
7
- },
8
- "include": ["src"]
9
- }