code-squad-cli 1.2.14 → 1.2.17

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,9 +1,13 @@
1
- import { Server, findFreePort } from './server/Server.js';
1
+ import { Server, findFreePort, findExistingServer } from './server/Server.js';
2
2
  import open from 'open';
3
3
  import path from 'path';
4
+ import fs from 'fs';
5
+ import os from 'os';
6
+ import http from 'http';
4
7
  import { execSync } from 'child_process';
5
8
  import { copyToClipboard } from './output/clipboard.js';
6
9
  const DEFAULT_PORT = 51234;
10
+ const MAX_PORT = 51240;
7
11
  // Use stderr for info messages (stdout goes to terminal input in coprocess mode)
8
12
  const log = (...args) => console.error(...args);
9
13
  function formatTime() {
@@ -13,6 +17,51 @@ function formatTime() {
13
17
  const secs = String(now.getSeconds()).padStart(2, '0');
14
18
  return `${hours}:${mins}:${secs}`;
15
19
  }
20
+ /**
21
+ * Generate a unique session ID from the current tty
22
+ */
23
+ function getSessionId() {
24
+ try {
25
+ const tty = execSync('tty', { encoding: 'utf-8' }).trim();
26
+ return tty;
27
+ }
28
+ catch {
29
+ // Fallback to random ID if tty not available
30
+ return `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
31
+ }
32
+ }
33
+ /**
34
+ * Register a session with the server
35
+ */
36
+ async function registerSession(port, sessionId, cwd) {
37
+ return new Promise((resolve) => {
38
+ const data = JSON.stringify({ session_id: sessionId, cwd });
39
+ const req = http.request({
40
+ hostname: '127.0.0.1',
41
+ port,
42
+ path: '/api/session/register',
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Content-Length': Buffer.byteLength(data),
47
+ },
48
+ timeout: 5000,
49
+ }, (res) => {
50
+ let body = '';
51
+ res.on('data', chunk => body += chunk);
52
+ res.on('end', () => {
53
+ resolve(res.statusCode === 200);
54
+ });
55
+ });
56
+ req.on('error', () => resolve(false));
57
+ req.on('timeout', () => {
58
+ req.destroy();
59
+ resolve(false);
60
+ });
61
+ req.write(data);
62
+ req.end();
63
+ });
64
+ }
16
65
  export async function runFlip(args) {
17
66
  // Parse --session flag from anywhere in args
18
67
  let sessionId;
@@ -57,52 +106,88 @@ export async function runFlip(args) {
57
106
  command = 'oneshot';
58
107
  }
59
108
  const cwd = pathArg ? path.resolve(pathArg) : process.cwd();
109
+ // Get or generate session ID
110
+ const finalSessionId = sessionId || getSessionId();
60
111
  switch (command) {
61
112
  case 'setup': {
62
113
  await setupHotkey();
63
114
  return;
64
115
  }
65
116
  case 'serve': {
66
- // Daemon mode: start server, keep running
67
- const port = await findFreePort(DEFAULT_PORT);
68
- log(`Server running at http://localhost:${port}`);
117
+ // Daemon mode: start or reuse server, keep running
118
+ let port = await findExistingServer(DEFAULT_PORT, MAX_PORT);
119
+ if (port) {
120
+ log(`[${formatTime()}] Found existing server at port ${port}`);
121
+ }
122
+ else {
123
+ port = await findFreePort(DEFAULT_PORT, MAX_PORT);
124
+ log(`[${formatTime()}] Starting new server at port ${port}`);
125
+ // Start server in background (non-blocking)
126
+ const server = new Server(port, { sessionTimeoutMs: 4000 });
127
+ server.run().catch(err => {
128
+ log(`[${formatTime()}] Server error:`, err);
129
+ });
130
+ }
131
+ log(`[${formatTime()}] Server running at http://localhost:${port}`);
69
132
  log('Press Ctrl+C to stop');
70
133
  log('');
71
134
  log('To open browser, run: csq flip open');
72
135
  log(`Or use hotkey to open: open http://localhost:${port}`);
73
- // Run server in loop (restarts after each submit/cancel)
74
- while (true) {
75
- const server = new Server(cwd, port);
76
- const result = await server.run();
77
- if (result) {
78
- log(`[${formatTime()}] Submitted ${result.length} characters`);
79
- }
80
- else {
81
- log(`[${formatTime()}] Cancelled`);
82
- }
83
- log(`[${formatTime()}] Ready for next session...`);
84
- }
136
+ // Keep the process alive
137
+ await new Promise(() => { });
85
138
  }
86
139
  case 'open': {
87
140
  // Just open browser to existing server
88
- const url = `http://localhost:${DEFAULT_PORT}`;
141
+ const port = await findExistingServer(DEFAULT_PORT, MAX_PORT);
142
+ if (!port) {
143
+ log('No csq flip server running.');
144
+ log('Start with: csq flip serve');
145
+ log('Or use: csq flip (one-shot mode)');
146
+ return;
147
+ }
148
+ // Register session
149
+ if (!await registerSession(port, finalSessionId, cwd)) {
150
+ log('Failed to register session with server');
151
+ return;
152
+ }
153
+ const url = `http://localhost:${port}?session=${encodeURIComponent(finalSessionId)}`;
89
154
  log(`Opening ${url} in browser...`);
90
155
  try {
91
156
  await open(url);
92
157
  }
93
158
  catch (e) {
94
159
  log('Failed to open browser:', e);
95
- log('Is the server running? Start with: csq flip serve');
160
+ log(`Please open ${url} manually`);
96
161
  }
97
162
  break;
98
163
  }
99
164
  case 'oneshot':
100
165
  default: {
101
- // Original behavior: start server, open browser, exit after submit/cancel
102
- const port = await findFreePort(DEFAULT_PORT);
103
- const url = sessionId
104
- ? `http://localhost:${port}?session=${sessionId}`
105
- : `http://localhost:${port}`;
166
+ // One-shot mode: find or start server, register session, open browser
167
+ let port = await findExistingServer(DEFAULT_PORT, MAX_PORT);
168
+ let serverPromise = null;
169
+ if (!port) {
170
+ port = await findFreePort(DEFAULT_PORT, MAX_PORT);
171
+ log(`Starting server at http://localhost:${port}...`);
172
+ // Start server with idle timeout
173
+ const server = new Server(port, {
174
+ sessionTimeoutMs: 4000, // 4s (polling is 2s, so 2 missed polls = dead)
175
+ idleTimeout: 5000,
176
+ });
177
+ // Run server and save promise
178
+ serverPromise = server.run();
179
+ // Wait a bit for server to start
180
+ await new Promise(resolve => setTimeout(resolve, 100));
181
+ }
182
+ else {
183
+ log(`Using existing server at http://localhost:${port}`);
184
+ }
185
+ // Register session
186
+ if (!await registerSession(port, finalSessionId, cwd)) {
187
+ log('Failed to register session with server');
188
+ return;
189
+ }
190
+ const url = `http://localhost:${port}?session=${encodeURIComponent(finalSessionId)}`;
106
191
  log(`Opening ${url} in browser...`);
107
192
  try {
108
193
  await open(url);
@@ -111,13 +196,12 @@ export async function runFlip(args) {
111
196
  log('Failed to open browser:', e);
112
197
  log(`Please open ${url} manually`);
113
198
  }
114
- const server = new Server(cwd, port);
115
- const result = await server.run();
116
- if (result) {
117
- log(`\nSubmitted ${result.length} characters`);
118
- }
119
- else {
120
- log('\nCancelled');
199
+ // If we started the server, wait for it to shutdown
200
+ if (serverPromise) {
201
+ log(`Session: ${finalSessionId}`);
202
+ log('Waiting for session to complete... (Ctrl+C to exit)');
203
+ await serverPromise;
204
+ log(`[${formatTime()}] Done`);
121
205
  }
122
206
  break;
123
207
  }
@@ -129,12 +213,12 @@ function printUsage() {
129
213
  console.error('Commands:');
130
214
  console.error(' serve [path] Start server in daemon mode (keeps running)');
131
215
  console.error(' open Open browser to existing server');
132
- console.error(' setup Setup Alt+; hotkey in shell config');
216
+ console.error(' setup Setup hotkey in shell config');
133
217
  console.error(' (no command) Start server + open browser (one-shot mode)');
134
218
  console.error('');
135
219
  console.error('Options:');
136
220
  console.error(' path Directory to serve (default: current directory)');
137
- console.error(' --session <uuid> Session ID for paste-back tracking');
221
+ console.error(' --session <id> Session ID for paste-back tracking (auto-generated from tty)');
138
222
  }
139
223
  async function setupHotkey() {
140
224
  let nodePath;
@@ -144,8 +228,47 @@ async function setupHotkey() {
144
228
  catch {
145
229
  nodePath = '/usr/local/bin/node';
146
230
  }
147
- const csqPath = new URL(import.meta.url).pathname;
148
- const command = `${nodePath} ${csqPath} flip`;
231
+ let csqPath;
232
+ try {
233
+ csqPath = execSync('which csq', { encoding: 'utf-8' }).trim();
234
+ }
235
+ catch {
236
+ csqPath = new URL(import.meta.url).pathname;
237
+ }
238
+ // Wrapper script that gets cwd from iTerm2 and passes to csq flip
239
+ const nodeDir = path.dirname(nodePath);
240
+ const wrapperScript = `#!/bin/bash
241
+ # Add node to PATH (coprocess doesn't inherit shell PATH)
242
+ export PATH="${nodeDir}:$PATH"
243
+
244
+ # Get current directory from iTerm2
245
+ CWD=$(osascript -e 'tell application "iTerm2" to tell current session of current tab of current window to return variable named "session.path"' 2>/dev/null)
246
+
247
+ # Fallback: try to get from tty
248
+ if [ -z "$CWD" ] || [ "$CWD" = "missing value" ]; then
249
+ TTY=$(osascript -e 'tell application "iTerm2" to tell current session of current tab of current window to return tty' 2>/dev/null)
250
+ if [ -n "$TTY" ]; then
251
+ PID=$(lsof -t "$TTY" 2>/dev/null | head -1)
252
+ if [ -n "$PID" ]; then
253
+ CWD=$(lsof -a -d cwd -p "$PID" -Fn 2>/dev/null | grep ^n | cut -c2-)
254
+ fi
255
+ fi
256
+ fi
257
+
258
+ # Run csq flip with cwd
259
+ if [ -n "$CWD" ] && [ "$CWD" != "missing value" ]; then
260
+ exec ${csqPath} flip "$CWD"
261
+ else
262
+ exec ${csqPath} flip
263
+ fi
264
+ `;
265
+ // Save wrapper script
266
+ const scriptDir = path.join(os.homedir(), '.config', 'csq');
267
+ const scriptPath = path.join(scriptDir, 'flip-hotkey.sh');
268
+ if (!fs.existsSync(scriptDir)) {
269
+ fs.mkdirSync(scriptDir, { recursive: true });
270
+ }
271
+ fs.writeFileSync(scriptPath, wrapperScript, { mode: 0o755 });
149
272
  console.log('');
150
273
  console.log('┌─────────────────────────────────────────────────┐');
151
274
  console.log('│ Flip Hotkey Setup (iTerm2) │');
@@ -157,10 +280,10 @@ async function setupHotkey() {
157
280
  console.log('4. Action: "Run Coprocess"');
158
281
  console.log('5. Command (아래 자동 복사됨):');
159
282
  console.log('');
160
- console.log(` ${command}`);
283
+ console.log(` ${scriptPath}`);
161
284
  console.log('');
162
285
  try {
163
- await copyToClipboard(command);
286
+ await copyToClipboard(scriptPath);
164
287
  console.log(' (Copied to clipboard!)');
165
288
  }
166
289
  catch {
@@ -1,4 +1,7 @@
1
1
  import type { Router as IRouter } from 'express';
2
+ export interface CancelRequest {
3
+ session_id: string;
4
+ }
2
5
  export interface CancelResponse {
3
6
  status: string;
4
7
  }
@@ -1,13 +1,16 @@
1
1
  import { Router } from 'express';
2
2
  const router = Router();
3
3
  // POST /api/cancel
4
- router.post('/', (req, res) => {
5
- const state = req.app.locals.state;
6
- // Send response before shutdown
7
- res.json({ status: 'ok' });
8
- // Trigger shutdown without output
9
- if (state.resolve) {
10
- state.resolve(null);
4
+ router.post('/', async (req, res) => {
5
+ const sessionManager = req.app.locals.sessionManager;
6
+ const body = req.body;
7
+ if (!body.session_id) {
8
+ res.status(400).json({ error: 'Missing session_id' });
9
+ return;
11
10
  }
11
+ // Send response before cleanup
12
+ res.json({ status: 'ok' });
13
+ // Unregister the session
14
+ await sessionManager.unregisterSession(body.session_id);
12
15
  });
13
16
  export { router as cancelRouter };
@@ -0,0 +1,8 @@
1
+ import type { Router as IRouter } from 'express';
2
+ import { SessionManager } from '../session/SessionManager.js';
3
+ export interface ChangesResponse {
4
+ filesChanged: boolean;
5
+ gitChanged: boolean;
6
+ changedFiles: string[];
7
+ }
8
+ export declare function createChangesRouter(sessionManager: SessionManager): IRouter;
@@ -0,0 +1,26 @@
1
+ import { Router } from 'express';
2
+ export function createChangesRouter(sessionManager) {
3
+ const router = Router();
4
+ // GET /api/changes?session_id=xxx - Get pending changes for a session (polling endpoint)
5
+ router.get('/', (req, res) => {
6
+ const sessionId = req.query.session_id;
7
+ if (!sessionId) {
8
+ res.status(400).json({ error: 'Missing session_id' });
9
+ return;
10
+ }
11
+ // Touch session to update activity
12
+ sessionManager.touchSession(sessionId);
13
+ const changes = sessionManager.consumePendingChanges(sessionId);
14
+ if (!changes) {
15
+ res.status(404).json({ error: 'Session not found' });
16
+ return;
17
+ }
18
+ const response = {
19
+ filesChanged: changes.filesChanged,
20
+ gitChanged: changes.gitChanged,
21
+ changedFiles: Array.from(changes.changedFiles),
22
+ };
23
+ res.json(response);
24
+ });
25
+ return router;
26
+ }
@@ -127,18 +127,37 @@ function detectLanguage(filePath) {
127
127
  }
128
128
  return 'text';
129
129
  }
130
- // GET /api/file?path=<path>
130
+ /**
131
+ * Get cwd from session_id query parameter
132
+ */
133
+ function getCwdFromSession(req, res) {
134
+ const sessionId = req.query.session_id;
135
+ if (!sessionId) {
136
+ res.status(400).json({ error: 'Missing session_id parameter' });
137
+ return null;
138
+ }
139
+ const sessionManager = req.app.locals.sessionManager;
140
+ const session = sessionManager.getSession(sessionId);
141
+ if (!session) {
142
+ res.status(404).json({ error: 'Session not found' });
143
+ return null;
144
+ }
145
+ return session.cwd;
146
+ }
147
+ // GET /api/file?session_id=xxx&path=<path>
131
148
  router.get('/', (req, res) => {
132
- const state = req.app.locals.state;
149
+ const cwd = getCwdFromSession(req, res);
150
+ if (!cwd)
151
+ return;
133
152
  const relativePath = req.query.path;
134
153
  if (!relativePath) {
135
154
  res.status(400).json({ error: 'Missing path parameter' });
136
155
  return;
137
156
  }
138
- const filePath = path.join(state.cwd, relativePath);
157
+ const filePath = path.join(cwd, relativePath);
139
158
  // Security check: ensure path is within cwd
140
159
  const resolvedPath = path.resolve(filePath);
141
- const resolvedCwd = path.resolve(state.cwd);
160
+ const resolvedCwd = path.resolve(cwd);
142
161
  if (!resolvedPath.startsWith(resolvedCwd)) {
143
162
  res.status(403).json({ error: 'Access denied' });
144
163
  return;
@@ -66,20 +66,41 @@ function collectFlatFiles(rootPath, currentPath, maxDepth, depth = 0, result = {
66
66
  }
67
67
  return result;
68
68
  }
69
- // GET /api/files - Get file tree structure
69
+ /**
70
+ * Get cwd from session_id query parameter
71
+ */
72
+ function getCwdFromSession(req, res) {
73
+ const sessionId = req.query.session_id;
74
+ if (!sessionId) {
75
+ res.status(400).json({ error: 'Missing session_id parameter' });
76
+ return null;
77
+ }
78
+ const sessionManager = req.app.locals.sessionManager;
79
+ const session = sessionManager.getSession(sessionId);
80
+ if (!session) {
81
+ res.status(404).json({ error: 'Session not found' });
82
+ return null;
83
+ }
84
+ return session.cwd;
85
+ }
86
+ // GET /api/files?session_id=xxx - Get file tree structure
70
87
  router.get('/', (req, res) => {
71
- const state = req.app.locals.state;
72
- const tree = buildFileTree(state.cwd, state.cwd, 10);
88
+ const cwd = getCwdFromSession(req, res);
89
+ if (!cwd)
90
+ return;
91
+ const tree = buildFileTree(cwd, cwd, 10);
73
92
  const response = {
74
- root: state.cwd,
93
+ root: cwd,
75
94
  tree,
76
95
  };
77
96
  res.json(response);
78
97
  });
79
- // GET /api/files/flat - Get flat list of all files with mtime
98
+ // GET /api/files/flat?session_id=xxx - Get flat list of all files with mtime
80
99
  router.get('/flat', (req, res) => {
81
- const state = req.app.locals.state;
82
- const result = collectFlatFiles(state.cwd, state.cwd, 10);
100
+ const cwd = getCwdFromSession(req, res);
101
+ if (!cwd)
102
+ return;
103
+ const result = collectFlatFiles(cwd, cwd, 10);
83
104
  // Sort files by path for consistent display
84
105
  result.files.sort((a, b) => a.path.localeCompare(b.path));
85
106
  result.filteredDirs.sort();
@@ -116,14 +116,33 @@ function createAddedFileDiff(content) {
116
116
  lines: diffLines,
117
117
  }];
118
118
  }
119
- // GET /api/git/status
119
+ /**
120
+ * Get cwd from session_id query parameter
121
+ */
122
+ function getCwdFromSession(req, res) {
123
+ const sessionId = req.query.session_id;
124
+ if (!sessionId) {
125
+ res.status(400).json({ error: 'Missing session_id parameter' });
126
+ return null;
127
+ }
128
+ const sessionManager = req.app.locals.sessionManager;
129
+ const session = sessionManager.getSession(sessionId);
130
+ if (!session) {
131
+ res.status(404).json({ error: 'Session not found' });
132
+ return null;
133
+ }
134
+ return session.cwd;
135
+ }
136
+ // GET /api/git/status?session_id=xxx
120
137
  router.get('/status', (req, res) => {
121
- const state = req.app.locals.state;
138
+ const cwd = getCwdFromSession(req, res);
139
+ if (!cwd)
140
+ return;
122
141
  // Check if it's a git repository
123
142
  let isGitRepo = false;
124
143
  try {
125
144
  execSync('git rev-parse --git-dir', {
126
- cwd: state.cwd,
145
+ cwd,
127
146
  stdio: 'pipe',
128
147
  });
129
148
  isGitRepo = true;
@@ -143,7 +162,7 @@ router.get('/status', (req, res) => {
143
162
  let unstaged = [];
144
163
  try {
145
164
  const output = execSync('git status --porcelain', {
146
- cwd: state.cwd,
165
+ cwd,
147
166
  encoding: 'utf-8',
148
167
  });
149
168
  unstaged = parseGitStatus(output);
@@ -157,20 +176,22 @@ router.get('/status', (req, res) => {
157
176
  };
158
177
  res.json(response);
159
178
  });
160
- // GET /api/git/diff?path=<file>
179
+ // GET /api/git/diff?session_id=xxx&path=<file>
161
180
  router.get('/diff', (req, res) => {
162
- const state = req.app.locals.state;
181
+ const cwd = getCwdFromSession(req, res);
182
+ if (!cwd)
183
+ return;
163
184
  const relativePath = req.query.path;
164
185
  if (!relativePath) {
165
186
  res.status(400).json({ error: 'Missing path parameter' });
166
187
  return;
167
188
  }
168
- const fullPath = path.join(state.cwd, relativePath);
189
+ const fullPath = path.join(cwd, relativePath);
169
190
  // Check git status for this file
170
191
  let fileStatus = 'modified';
171
192
  try {
172
193
  const statusOutput = execSync(`git status --porcelain -- "${relativePath}"`, {
173
- cwd: state.cwd,
194
+ cwd,
174
195
  encoding: 'utf-8',
175
196
  });
176
197
  if (statusOutput.startsWith('??')) {
@@ -205,7 +226,7 @@ router.get('/diff', (req, res) => {
205
226
  // Get diff for tracked file
206
227
  try {
207
228
  const diffOutput = execSync(`git diff --no-color -- "${relativePath}"`, {
208
- cwd: state.cwd,
229
+ cwd,
209
230
  encoding: 'utf-8',
210
231
  });
211
232
  if (!diffOutput.trim()) {
@@ -0,0 +1,6 @@
1
+ import type { Router as IRouter } from 'express';
2
+ export interface PingResponse {
3
+ id: string;
4
+ version: string;
5
+ }
6
+ export declare function createPingRouter(): IRouter;
@@ -0,0 +1,14 @@
1
+ import { Router } from 'express';
2
+ import { SERVER_ID } from '../server/Server.js';
3
+ export function createPingRouter() {
4
+ const router = Router();
5
+ // GET /api/ping - Server identification for discovery
6
+ router.get('/', (_req, res) => {
7
+ const response = {
8
+ id: SERVER_ID,
9
+ version: '1.0.0',
10
+ };
11
+ res.json(response);
12
+ });
13
+ return router;
14
+ }
@@ -0,0 +1,14 @@
1
+ import type { Router as IRouter } from 'express';
2
+ import { SessionManager } from '../session/SessionManager.js';
3
+ export interface RegisterSessionRequest {
4
+ session_id: string;
5
+ cwd: string;
6
+ }
7
+ export interface RegisterSessionResponse {
8
+ status: string;
9
+ session_id: string;
10
+ }
11
+ export interface UnregisterSessionResponse {
12
+ status: string;
13
+ }
14
+ export declare function createSessionRouter(sessionManager: SessionManager): IRouter;
@@ -0,0 +1,54 @@
1
+ import { Router } from 'express';
2
+ export function createSessionRouter(sessionManager) {
3
+ const router = Router();
4
+ // POST /api/session/register - Register a new session
5
+ router.post('/register', (req, res) => {
6
+ const body = req.body;
7
+ if (!body.session_id || !body.cwd) {
8
+ res.status(400).json({ error: 'Missing session_id or cwd' });
9
+ return;
10
+ }
11
+ // Clear idle timer on first session registration
12
+ if (!sessionManager.hasSessions && req.app.locals.clearIdleTimer) {
13
+ req.app.locals.clearIdleTimer();
14
+ }
15
+ sessionManager.registerSession(body.session_id, body.cwd);
16
+ const response = {
17
+ status: 'ok',
18
+ session_id: body.session_id,
19
+ };
20
+ res.json(response);
21
+ });
22
+ // POST /api/session/unregister - Unregister a session
23
+ router.post('/unregister', async (req, res) => {
24
+ const body = req.body;
25
+ if (!body.session_id) {
26
+ res.status(400).json({ error: 'Missing session_id' });
27
+ return;
28
+ }
29
+ await sessionManager.unregisterSession(body.session_id);
30
+ const response = {
31
+ status: 'ok',
32
+ };
33
+ res.json(response);
34
+ });
35
+ // GET /api/session/info?session_id=xxx - Get session info
36
+ router.get('/info', (req, res) => {
37
+ const sessionId = req.query.session_id;
38
+ if (!sessionId) {
39
+ res.status(400).json({ error: 'Missing session_id' });
40
+ return;
41
+ }
42
+ const session = sessionManager.getSession(sessionId);
43
+ if (!session) {
44
+ res.status(404).json({ error: 'Session not found' });
45
+ return;
46
+ }
47
+ res.json({
48
+ session_id: session.id,
49
+ cwd: session.cwd,
50
+ lastActivity: session.lastActivity,
51
+ });
52
+ });
53
+ return router;
54
+ }
@@ -3,8 +3,12 @@ import { formatComments, copyToClipboard, schedulePaste } from '../output/index.
3
3
  const router = Router();
4
4
  // POST /api/submit
5
5
  router.post('/', async (req, res) => {
6
- const state = req.app.locals.state;
6
+ const sessionManager = req.app.locals.sessionManager;
7
7
  const body = req.body;
8
+ if (!body.session_id) {
9
+ res.status(400).json({ error: 'Missing session_id' });
10
+ return;
11
+ }
8
12
  if (!body.items || body.items.length === 0) {
9
13
  res.status(400).json({ error: 'No items to submit' });
10
14
  return;
@@ -39,11 +43,9 @@ router.post('/', async (req, res) => {
39
43
  console.error('Failed to schedule paste:', e);
40
44
  }
41
45
  }
42
- // Send response before shutdown
46
+ // Send response before cleanup
43
47
  res.json({ status: 'ok' });
44
- // Trigger shutdown with the output
45
- if (state.resolve) {
46
- state.resolve(formatted);
47
- }
48
+ // Unregister the session
49
+ await sessionManager.unregisterSession(body.session_id);
48
50
  });
49
51
  export { router as submitRouter };