code-squad-cli 1.0.8 → 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.
- package/dist/flip/index.d.ts +1 -0
- package/dist/flip/index.js +246 -0
- package/dist/flip/output/autopaste.d.ts +5 -0
- package/dist/flip/output/autopaste.js +80 -0
- package/dist/flip/output/clipboard.d.ts +4 -0
- package/dist/flip/output/clipboard.js +7 -0
- package/dist/flip/output/formatter.d.ts +13 -0
- package/dist/flip/output/formatter.js +18 -0
- package/dist/flip/output/index.d.ts +4 -0
- package/dist/flip/output/index.js +3 -0
- package/dist/flip/routes/cancel.d.ts +6 -0
- package/dist/flip/routes/cancel.js +13 -0
- package/dist/flip/routes/file.d.ts +8 -0
- package/dist/flip/routes/file.js +77 -0
- package/dist/flip/routes/files.d.ts +16 -0
- package/dist/flip/routes/files.js +102 -0
- package/dist/flip/routes/git.d.ts +11 -0
- package/dist/flip/routes/git.js +83 -0
- package/dist/flip/routes/static.d.ts +2 -0
- package/dist/flip/routes/static.js +35 -0
- package/dist/flip/routes/submit.d.ts +20 -0
- package/dist/flip/routes/submit.js +42 -0
- package/dist/flip/server/Server.d.ts +11 -0
- package/dist/flip/server/Server.js +63 -0
- package/dist/flip-ui/dist/assets/index-2_ZJL2eQ.css +10 -0
- package/dist/flip-ui/dist/assets/index-BGxWXtBJ.js +66 -0
- package/dist/flip-ui/dist/index.html +13 -0
- package/dist/index.js +711 -22
- package/package.json +14 -4
|
@@ -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,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}
|