code-squad-cli 1.0.7 → 1.1.1

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.
@@ -0,0 +1,83 @@
1
+ import { Router } from 'express';
2
+ import { execSync } from 'child_process';
3
+ const router = Router();
4
+ function parseGitStatus(output) {
5
+ const files = [];
6
+ for (const line of output.split('\n')) {
7
+ if (line.length < 3)
8
+ continue;
9
+ const statusCode = line.substring(0, 2);
10
+ let filePath = line.substring(3);
11
+ // Remove quotes if present (git uses quotes for paths with special chars)
12
+ filePath = filePath.replace(/^"|"$/g, '');
13
+ let status;
14
+ if (statusCode === '??') {
15
+ status = 'untracked';
16
+ }
17
+ else if (statusCode.includes('M')) {
18
+ status = 'modified';
19
+ }
20
+ else if (statusCode.includes('D')) {
21
+ status = 'deleted';
22
+ }
23
+ else if (statusCode.includes('A')) {
24
+ status = 'added';
25
+ }
26
+ else if (statusCode.includes('R')) {
27
+ status = 'renamed';
28
+ }
29
+ else if (statusCode.includes('C')) {
30
+ status = 'copied';
31
+ }
32
+ else if (statusCode === 'UU') {
33
+ status = 'unmerged';
34
+ }
35
+ else {
36
+ status = 'modified';
37
+ }
38
+ files.push({ path: filePath, status });
39
+ }
40
+ return files;
41
+ }
42
+ // GET /api/git/status
43
+ router.get('/status', (req, res) => {
44
+ const state = req.app.locals.state;
45
+ // Check if it's a git repository
46
+ let isGitRepo = false;
47
+ try {
48
+ execSync('git rev-parse --git-dir', {
49
+ cwd: state.cwd,
50
+ stdio: 'pipe',
51
+ });
52
+ isGitRepo = true;
53
+ }
54
+ catch {
55
+ // Not a git repo
56
+ }
57
+ if (!isGitRepo) {
58
+ const response = {
59
+ isGitRepo: false,
60
+ unstaged: [],
61
+ };
62
+ res.json(response);
63
+ return;
64
+ }
65
+ // Get status using porcelain format
66
+ let unstaged = [];
67
+ try {
68
+ const output = execSync('git status --porcelain', {
69
+ cwd: state.cwd,
70
+ encoding: 'utf-8',
71
+ });
72
+ unstaged = parseGitStatus(output);
73
+ }
74
+ catch {
75
+ // Error getting status
76
+ }
77
+ const response = {
78
+ isGitRepo: true,
79
+ unstaged,
80
+ };
81
+ res.json(response);
82
+ });
83
+ export { router as gitRouter };
@@ -0,0 +1,2 @@
1
+ import type { Router as IRouter } from 'express';
2
+ export declare function createStaticRouter(): IRouter;
@@ -0,0 +1,35 @@
1
+ import { Router } from 'express';
2
+ import express from 'express';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import fs from 'fs';
6
+ export function createStaticRouter() {
7
+ const router = Router();
8
+ // Get __dirname equivalent in ESM
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ // Path to the built web-ui (relative to dist/ after bundling)
12
+ const distPath = path.resolve(__dirname, 'flip-ui/dist');
13
+ // Check if dist exists (for development vs production)
14
+ if (fs.existsSync(distPath)) {
15
+ // Serve static files
16
+ router.use(express.static(distPath));
17
+ // SPA fallback - serve index.html for all non-API routes
18
+ router.get('*', (req, res) => {
19
+ const indexPath = path.join(distPath, 'index.html');
20
+ if (fs.existsSync(indexPath)) {
21
+ res.sendFile(indexPath);
22
+ }
23
+ else {
24
+ res.status(404).send('Not Found');
25
+ }
26
+ });
27
+ }
28
+ else {
29
+ // Development mode - redirect to Vite dev server
30
+ router.get('*', (req, res) => {
31
+ res.redirect(`http://localhost:5173${req.url}`);
32
+ });
33
+ }
34
+ return router;
35
+ }
@@ -0,0 +1,20 @@
1
+ import type { Router as IRouter } from 'express';
2
+ export interface SubmitItem {
3
+ filePath: string;
4
+ startLine: number;
5
+ endLine: number;
6
+ comment: string;
7
+ }
8
+ export interface SubmitRequest {
9
+ session_id: string;
10
+ items: SubmitItem[];
11
+ }
12
+ export interface SubmitResponse {
13
+ status: string;
14
+ }
15
+ export interface SubmitResult {
16
+ output: string;
17
+ sessionId: string;
18
+ }
19
+ declare const router: IRouter;
20
+ export { router as submitRouter };
@@ -0,0 +1,42 @@
1
+ import { Router } from 'express';
2
+ import { formatComments, copyToClipboard, schedulePaste } from '../output/index.js';
3
+ const router = Router();
4
+ // POST /api/submit
5
+ router.post('/', async (req, res) => {
6
+ const state = req.app.locals.state;
7
+ const body = req.body;
8
+ if (!body.items || body.items.length === 0) {
9
+ res.status(400).json({ error: 'No items to submit' });
10
+ return;
11
+ }
12
+ // Convert SubmitItems to CommentLike objects
13
+ const comments = body.items.map((item) => ({
14
+ file: item.filePath,
15
+ line: item.startLine,
16
+ endLine: item.endLine !== item.startLine ? item.endLine : undefined,
17
+ text: item.comment,
18
+ }));
19
+ // Format the output
20
+ const formatted = formatComments(comments);
21
+ // Copy to clipboard
22
+ try {
23
+ await copyToClipboard(formatted);
24
+ }
25
+ catch (e) {
26
+ console.error('Failed to copy to clipboard:', e);
27
+ }
28
+ // Schedule paste (will execute after server shuts down)
29
+ try {
30
+ await schedulePaste(body.session_id);
31
+ }
32
+ catch (e) {
33
+ console.error('Failed to schedule paste:', e);
34
+ }
35
+ // Send response before shutdown
36
+ res.json({ status: 'ok' });
37
+ // Trigger shutdown with the output
38
+ if (state.resolve) {
39
+ state.resolve(formatted);
40
+ }
41
+ });
42
+ export { router as submitRouter };
@@ -0,0 +1,11 @@
1
+ export interface AppState {
2
+ cwd: string;
3
+ resolve: ((output: string | null) => void) | null;
4
+ }
5
+ export declare class Server {
6
+ private cwd;
7
+ private port;
8
+ constructor(cwd: string, port: number);
9
+ run(): Promise<string | null>;
10
+ }
11
+ export declare function findFreePort(preferred: number): Promise<number>;
@@ -0,0 +1,63 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import http from 'http';
4
+ import net from 'net';
5
+ import { filesRouter } from '../routes/files.js';
6
+ import { fileRouter } from '../routes/file.js';
7
+ import { gitRouter } from '../routes/git.js';
8
+ import { submitRouter } from '../routes/submit.js';
9
+ import { cancelRouter } from '../routes/cancel.js';
10
+ import { createStaticRouter } from '../routes/static.js';
11
+ export class Server {
12
+ cwd;
13
+ port;
14
+ constructor(cwd, port) {
15
+ this.cwd = cwd;
16
+ this.port = port;
17
+ }
18
+ async run() {
19
+ return new Promise((resolve) => {
20
+ const state = {
21
+ cwd: this.cwd,
22
+ resolve: null,
23
+ };
24
+ const app = express();
25
+ // Middleware
26
+ app.use(cors());
27
+ app.use(express.json());
28
+ // Store state in app locals
29
+ app.locals.state = state;
30
+ // API routes
31
+ app.use('/api/files', filesRouter);
32
+ app.use('/api/file', fileRouter);
33
+ app.use('/api/git', gitRouter);
34
+ app.use('/api/submit', submitRouter);
35
+ app.use('/api/cancel', cancelRouter);
36
+ // Static files (fallback to web-ui dist)
37
+ app.use(createStaticRouter());
38
+ const server = http.createServer(app);
39
+ // Set up the shutdown mechanism
40
+ state.resolve = (output) => {
41
+ server.close();
42
+ resolve(output);
43
+ };
44
+ server.listen(this.port, '127.0.0.1', () => {
45
+ console.log(`Server running at http://localhost:${this.port}`);
46
+ });
47
+ });
48
+ }
49
+ }
50
+ export async function findFreePort(preferred) {
51
+ return new Promise((resolve) => {
52
+ const server = net.createServer();
53
+ server.listen(preferred, '127.0.0.1', () => {
54
+ server.close(() => {
55
+ resolve(preferred);
56
+ });
57
+ });
58
+ server.on('error', () => {
59
+ // Port in use, try next
60
+ findFreePort(preferred + 1).then(resolve);
61
+ });
62
+ });
63
+ }
@@ -0,0 +1,10 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub Dark
3
+ Description: Dark theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-dark
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}*{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary: #1e1e1e;--bg-secondary: #252526;--bg-tertiary: #2d2d2d;--bg-hover: #3c3c3c;--bg-selected: #094771;--text-primary: #d4d4d4;--text-secondary: #858585;--text-muted: #6a6a6a;--border-color: #3c3c3c;--accent-primary: #0e639c;--accent-primary-hover: #1177bb;--git-modified: #d19a66;--git-untracked: #98c379;--git-deleted: #e06c75;--git-added: #98c379;--line-number-color: #6e7681;--selection-bg: #264f78}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-size:14px;background-color:var(--bg-primary);color:var(--text-primary);height:100vh;overflow:hidden}#root{height:100%}.app{display:flex;flex-direction:column;height:100%}.main-content{display:flex;flex:1;overflow:hidden}.sidebar{width:250px;background-color:var(--bg-secondary);border-right:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column}.content{flex:1;overflow:hidden;display:flex;flex-direction:column}.staging-panel{width:300px;background-color:var(--bg-secondary);border-left:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column}.footer{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background-color:var(--bg-secondary);border-top:1px solid var(--border-color);gap:16px}.footer-actions{display:flex;gap:8px}.file-tree{display:flex;flex-direction:column;height:100%}.file-tree-header{padding:12px 16px;font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);border-bottom:1px solid var(--border-color)}.file-tree-content{flex:1;overflow-y:auto;padding:4px 0}.tree-item{display:flex;align-items:center;padding:4px 8px;cursor:pointer;-webkit-user-select:none;user-select:none;gap:6px}.tree-item:hover{background-color:var(--bg-hover)}.tree-icon{font-size:10px;width:14px;text-align:center;color:var(--text-secondary)}.tree-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.git-badge{font-size:10px;font-weight:600;padding:1px 4px;border-radius:3px}.git-badge-modified{color:var(--git-modified);background-color:#d19a6626}.git-badge-untracked{color:var(--git-untracked);background-color:#98c37926}.git-badge-deleted{color:var(--git-deleted);background-color:#e06c7526}.git-badge-added{color:var(--git-added);background-color:#98c37926}.code-viewer{display:flex;flex-direction:column;height:100%;overflow:hidden}.code-viewer-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-secondary);gap:8px}.code-viewer-empty .hint{font-size:12px;color:var(--text-muted)}.code-viewer-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color)}.file-path{font-size:13px}.file-language{font-size:11px;color:var(--text-secondary);text-transform:uppercase}.code-viewer-content{flex:1;overflow:auto;font-family:SF Mono,Menlo,Monaco,Consolas,monospace;font-size:13px;line-height:1.5}.code-line{display:flex;min-height:20px;cursor:pointer}.code-line:hover{background-color:var(--bg-hover)}.code-line-selected{background-color:var(--selection-bg)!important}.line-number{display:inline-block;min-width:50px;padding:0 12px;text-align:right;color:var(--line-number-color);-webkit-user-select:none;user-select:none;background-color:var(--bg-tertiary);border-right:1px solid var(--border-color)}.code-line code{flex:1;padding:0 12px;white-space:pre;background:transparent}.code-line code.hljs{background:transparent;padding:0 12px}.staging-list{display:flex;flex-direction:column;height:100%}.staging-list-empty{padding:16px}.staging-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);border-bottom:1px solid var(--border-color)}.staging-hint{font-size:12px;color:var(--text-muted);line-height:1.5}.staging-items{flex:1;overflow-y:auto;padding:8px}.staging-item{background-color:var(--bg-tertiary);border-radius:4px;padding:8px 12px;margin-bottom:8px}.staging-item-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.staging-location{font-family:SF Mono,monospace;font-size:12px;color:var(--text-secondary)}.staging-comment{font-size:13px;line-height:1.4}.comment-input{display:flex;align-items:center;gap:12px;flex:1}.comment-selection{font-family:SF Mono,monospace;font-size:12px;color:var(--accent-primary-hover);white-space:nowrap}.comment-field{flex:1;padding:8px 12px;background-color:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);font-size:14px}.comment-field:focus{outline:none;border-color:var(--accent-primary)}.comment-hint{color:var(--text-muted);font-size:13px}.fuzzy-finder-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#00000080;display:flex;justify-content:center;padding-top:80px;z-index:100}.fuzzy-finder{background-color:var(--bg-secondary);border-radius:8px;box-shadow:0 8px 32px #00000080;width:600px;max-height:400px;display:flex;flex-direction:column;overflow:hidden}.fuzzy-finder-input{padding:16px;background-color:var(--bg-tertiary);border:none;border-bottom:1px solid var(--border-color);color:var(--text-primary);font-size:16px}.fuzzy-finder-input:focus{outline:none}.fuzzy-finder-results{flex:1;overflow-y:auto}.fuzzy-finder-item{padding:10px 16px;cursor:pointer;font-family:SF Mono,monospace;font-size:13px}.fuzzy-finder-item:hover,.fuzzy-finder-item-selected{background-color:var(--bg-selected)}.fuzzy-finder-empty{padding:16px;color:var(--text-muted);text-align:center}.btn{padding:8px 16px;border:none;border-radius:4px;font-size:13px;cursor:pointer;transition:background-color .15s}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-primary{background-color:var(--accent-primary);color:#fff}.btn-primary:hover:not(:disabled){background-color:var(--accent-primary-hover)}.btn-secondary{background-color:var(--bg-tertiary);color:var(--text-primary)}.btn-secondary:hover:not(:disabled){background-color:var(--bg-hover)}.btn-small{padding:4px 12px}.btn-link{background:none;border:none;color:var(--accent-primary-hover);cursor:pointer;font-size:11px}.btn-link:hover{text-decoration:underline}.btn-icon{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:2px 6px}.btn-icon:hover{color:var(--text-primary)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--bg-hover);border-radius:5px}::-webkit-scrollbar-thumb:hover{background:#505050}