Skip to content

Commit 85a306c

Browse files
yolo mode policing
1 parent 23801e9 commit 85a306c

10 files changed

Lines changed: 200 additions & 19 deletions

File tree

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
VITE_IS_PLATFORM=true
2+
INSTANT_START=true
3+
PORT=8876

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ VITE_CONTEXT_WINDOW=160000
4343
CONTEXT_WINDOW=160000
4444

4545
# VITE_IS_PLATFORM=false
46+
47+
# Auto-provision a user on startup (username: "username", password: "password")
48+
# Git name/email are read from global git config. Onboarding is auto-completed.
49+
# Use together with VITE_IS_PLATFORM=true for instant access with no auth screens.
50+
# INSTANT_START=false

package-lock.json

Lines changed: 23 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/index.js

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes
6161
import cliAuthRoutes from './routes/cli-auth.js';
6262
import userRoutes from './routes/user.js';
6363
import codexRoutes from './routes/codex.js';
64-
import { initializeDatabase } from './database/db.js';
64+
import bcrypt from 'bcrypt';
65+
import { initializeDatabase, userDb } from './database/db.js';
6566
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
6667
import { IS_PLATFORM } from './constants/config.js';
68+
import { getSystemGitConfig } from './utils/gitConfig.js';
69+
import yoloRoutes from './routes/yolo.js';
6770

6871
// File system watchers for provider project/session folders
6972
const PROVIDER_WATCH_PATHS = [
@@ -206,6 +209,60 @@ async function setupProjectsWatcher() {
206209
}
207210
}
208211

212+
// YOLO file watcher - watches /home/coder/.yolo for bypass permissions gating
213+
const YOLO_FILE_PATH = '/home/coder/.yolo';
214+
const YOLO_PARENT_DIR = path.dirname(YOLO_FILE_PATH);
215+
const YOLO_FILE_NAME = path.basename(YOLO_FILE_PATH);
216+
217+
async function setupYoloWatcher() {
218+
try {
219+
const chokidar = (await import('chokidar')).default;
220+
const { checkYoloStatus } = await import('./routes/yolo.js');
221+
222+
// Ensure parent directory exists before watching
223+
try {
224+
await fsPromises.mkdir(YOLO_PARENT_DIR, { recursive: true });
225+
} catch {
226+
// Directory may already exist or be inaccessible
227+
}
228+
229+
const watcher = chokidar.watch(YOLO_PARENT_DIR, {
230+
ignored: (filePath) => {
231+
// Only watch the .yolo file itself and the parent dir
232+
const base = path.basename(filePath);
233+
return filePath !== YOLO_PARENT_DIR && base !== YOLO_FILE_NAME;
234+
},
235+
persistent: true,
236+
ignoreInitial: true,
237+
followSymlinks: false,
238+
depth: 0,
239+
awaitWriteFinish: {
240+
stabilityThreshold: 100,
241+
pollInterval: 50
242+
}
243+
});
244+
245+
const broadcastYoloStatus = () => {
246+
const allowed = checkYoloStatus();
247+
const message = JSON.stringify({ type: 'yolo_status', allowed });
248+
connectedClients.forEach(client => {
249+
if (client.readyState === WebSocket.OPEN) {
250+
client.send(message);
251+
}
252+
});
253+
};
254+
255+
watcher
256+
.on('add', broadcastYoloStatus)
257+
.on('change', broadcastYoloStatus)
258+
.on('unlink', broadcastYoloStatus)
259+
.on('error', (error) => {
260+
console.error('[ERROR] YOLO watcher error:', error);
261+
});
262+
} catch (error) {
263+
console.error('[WARN] Failed to setup YOLO file watcher:', error);
264+
}
265+
}
209266

210267
const app = express();
211268
const server = http.createServer(app);
@@ -379,6 +436,9 @@ app.use('/api/user', authenticateToken, userRoutes);
379436
// Codex API Routes (protected)
380437
app.use('/api/codex', authenticateToken, codexRoutes);
381438

439+
// YOLO status API Routes (protected)
440+
app.use('/api/yolo', authenticateToken, yoloRoutes);
441+
382442
// Agent API Routes (uses API key authentication)
383443
app.use('/api/agent', agentRoutes);
384444

@@ -1918,6 +1978,31 @@ async function startServer() {
19181978
// Initialize authentication database
19191979
await initializeDatabase();
19201980

1981+
// INSTANT_START: auto-provision user on startup
1982+
if (process.env.INSTANT_START === 'true') {
1983+
try {
1984+
const hasUsers = userDb.hasUsers();
1985+
if (!hasUsers) {
1986+
const passwordHash = await bcrypt.hash('password', 12);
1987+
const user = userDb.createUser('username', passwordHash);
1988+
console.log(`${c.ok('[OK]')} INSTANT_START: Created user "username" (id: ${user.id})`);
1989+
1990+
const gitConfig = await getSystemGitConfig();
1991+
if (gitConfig.git_name || gitConfig.git_email) {
1992+
userDb.updateGitConfig(user.id, gitConfig.git_name, gitConfig.git_email);
1993+
console.log(`${c.ok('[OK]')} INSTANT_START: Set git config: ${gitConfig.git_name} <${gitConfig.git_email}>`);
1994+
}
1995+
1996+
userDb.completeOnboarding(user.id);
1997+
console.log(`${c.ok('[OK]')} INSTANT_START: Onboarding marked complete`);
1998+
} else {
1999+
console.log(`${c.info('[INFO]')} INSTANT_START: User already exists, skipping auto-provisioning`);
2000+
}
2001+
} catch (error) {
2002+
console.error(`${c.warn('[WARN]')} INSTANT_START: Failed to auto-provision user:`, error);
2003+
}
2004+
}
2005+
19212006
// Check if running in production mode (dist folder exists)
19222007
const distIndexPath = path.join(__dirname, '../dist/index.html');
19232008
const isProduction = fs.existsSync(distIndexPath);
@@ -1945,6 +2030,9 @@ async function startServer() {
19452030

19462031
// Start watching the projects folder for changes
19472032
await setupProjectsWatcher();
2033+
2034+
// Start watching /home/coder/.yolo for YOLO mode gating
2035+
await setupYoloWatcher();
19482036
});
19492037
} catch (error) {
19502038
console.error('[ERROR] Failed to start server:', error);

server/routes/yolo.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import express from 'express';
2+
import fs from 'fs';
3+
4+
const router = express.Router();
5+
6+
const YOLO_FILE_PATH = '/home/coder/.yolo';
7+
8+
export function checkYoloStatus() {
9+
try {
10+
const content = fs.readFileSync(YOLO_FILE_PATH, 'utf8');
11+
return content.trim() === 'ON';
12+
} catch {
13+
return false;
14+
}
15+
}
16+
17+
router.get('/status', (req, res) => {
18+
res.json({ allowed: checkYoloStatus() });
19+
});
20+
21+
export default router;

src/components/chat/view/ChatInterface.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useChatProviderState } from '../hooks/useChatProviderState';
99
import { useChatSessionState } from '../hooks/useChatSessionState';
1010
import { useChatRealtimeHandlers } from '../hooks/useChatRealtimeHandlers';
1111
import { useChatComposerState } from '../hooks/useChatComposerState';
12+
import { useYoloStatus } from '../../../hooks/useYoloStatus';
1213
import type { Provider } from '../types/types';
1314

1415
type PendingViewSession = {
@@ -72,6 +73,8 @@ function ChatInterface({
7273
selectedSession,
7374
});
7475

76+
const { isYoloAllowed } = useYoloStatus();
77+
7578
const {
7679
chatMessages,
7780
setChatMessages,
@@ -329,6 +332,7 @@ function ChatInterface({
329332
onAbortSession={handleAbortSession}
330333
provider={provider}
331334
permissionMode={permissionMode}
335+
isYoloAllowed={isYoloAllowed}
332336
onModeSwitch={cyclePermissionMode}
333337
thinkingMode={thinkingMode}
334338
setThinkingMode={setThinkingMode}

0 commit comments

Comments
 (0)