@aion0/forge 0.1.4 → 0.1.6
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/README.md +14 -1
- package/bin/forge-server.mjs +14 -0
- package/instrumentation.ts +18 -1
- package/lib/terminal-standalone.ts +2 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,7 +166,8 @@ All config lives in `~/.forge/`:
|
|
|
166
166
|
|
|
167
167
|
```
|
|
168
168
|
~/.forge/
|
|
169
|
-
|
|
169
|
+
.env.local # Environment variables (AUTH_SECRET, API keys, etc.)
|
|
170
|
+
settings.yaml # Main configuration
|
|
170
171
|
password.json # Daily auto-generated login password
|
|
171
172
|
data.db # SQLite database (tasks, sessions)
|
|
172
173
|
terminal-state.json # Terminal tab layout
|
|
@@ -174,6 +175,18 @@ All config lives in `~/.forge/`:
|
|
|
174
175
|
bin/ # Auto-downloaded binaries (cloudflared)
|
|
175
176
|
```
|
|
176
177
|
|
|
178
|
+
### .env.local (optional)
|
|
179
|
+
|
|
180
|
+
```env
|
|
181
|
+
# Fixed auth secret (optional — auto-generated if not set)
|
|
182
|
+
AUTH_SECRET=<random-string>
|
|
183
|
+
|
|
184
|
+
# Optional: AI provider API keys for multi-model chat
|
|
185
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
186
|
+
# OPENAI_API_KEY=sk-...
|
|
187
|
+
# GOOGLE_GENERATIVE_AI_API_KEY=AI...
|
|
188
|
+
```
|
|
189
|
+
|
|
177
190
|
### settings.yaml
|
|
178
191
|
|
|
179
192
|
```yaml
|
package/bin/forge-server.mjs
CHANGED
|
@@ -28,6 +28,20 @@ const isStop = process.argv.includes('--stop');
|
|
|
28
28
|
process.chdir(ROOT);
|
|
29
29
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
30
30
|
|
|
31
|
+
// ── Load ~/.forge/.env.local ──
|
|
32
|
+
const envFile = join(DATA_DIR, '.env.local');
|
|
33
|
+
if (existsSync(envFile)) {
|
|
34
|
+
for (const line of readFileSync(envFile, 'utf-8').split('\n')) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
37
|
+
const eq = trimmed.indexOf('=');
|
|
38
|
+
if (eq === -1) continue;
|
|
39
|
+
const key = trimmed.slice(0, eq).trim();
|
|
40
|
+
const val = trimmed.slice(eq + 1).trim();
|
|
41
|
+
if (!process.env[key]) process.env[key] = val;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
// ── Stop ──
|
|
32
46
|
if (isStop) {
|
|
33
47
|
try {
|
package/instrumentation.ts
CHANGED
|
@@ -5,10 +5,27 @@
|
|
|
5
5
|
export async function register() {
|
|
6
6
|
// Only run on server, not Edge
|
|
7
7
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
8
|
+
// Load ~/.forge/.env.local if it exists (works for both pnpm dev and forge-server)
|
|
9
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
10
|
+
const { join } = await import('node:path');
|
|
11
|
+
const { homedir } = await import('node:os');
|
|
12
|
+
const envFile = join(homedir(), '.forge', '.env.local');
|
|
13
|
+
if (existsSync(envFile)) {
|
|
14
|
+
for (const line of readFileSync(envFile, 'utf-8').split('\n')) {
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
17
|
+
const eq = trimmed.indexOf('=');
|
|
18
|
+
if (eq === -1) continue;
|
|
19
|
+
const key = trimmed.slice(0, eq).trim();
|
|
20
|
+
const val = trimmed.slice(eq + 1).trim();
|
|
21
|
+
if (!process.env[key]) process.env[key] = val;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
const { getPassword } = await import('./lib/password');
|
|
9
26
|
const password = getPassword();
|
|
10
27
|
process.env.MW_PASSWORD = password;
|
|
11
28
|
console.log(`[init] Login password: ${password}`);
|
|
12
|
-
console.log('[init] Forgot password? Run:
|
|
29
|
+
console.log('[init] Forgot password? Run: forge password');
|
|
13
30
|
}
|
|
14
31
|
}
|
|
@@ -352,18 +352,7 @@ wss.on('connection', (ws: WebSocket) => {
|
|
|
352
352
|
if (sessionName) trackDetach(ws, sessionName);
|
|
353
353
|
createdAt.delete(ws);
|
|
354
354
|
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
setTimeout(() => {
|
|
358
|
-
const clients = sessionClients.get(disconnectedSession)?.size ?? 0;
|
|
359
|
-
if (clients === 0 && tmuxSessionExists(disconnectedSession)) {
|
|
360
|
-
const renamed = getRenamedSessions();
|
|
361
|
-
if (!renamed.has(disconnectedSession)) {
|
|
362
|
-
console.log(`[terminal] Auto-killing orphaned session "${disconnectedSession}" (no clients after 10s, not renamed)`);
|
|
363
|
-
killTmuxSession(disconnectedSession);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}, 10_000);
|
|
367
|
-
}
|
|
355
|
+
// Orphan cleanup is handled by the periodic cleanupOrphanedSessions() (every 30s)
|
|
356
|
+
// which checks sessionClients and getRenamedSessions() from terminal-state.json
|
|
368
357
|
});
|
|
369
358
|
});
|