@aion0/forge 0.1.2 → 0.1.4
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 +4 -4
- package/bin/forge-server.mjs +51 -4
- package/cli/mw.ts +2 -2
- package/lib/auth.ts +8 -1
- package/lib/cloudflared.ts +2 -2
- package/lib/flows.ts +2 -2
- package/lib/password.ts +2 -2
- package/lib/session-watcher.ts +1 -1
- package/lib/settings.ts +1 -1
- package/lib/terminal-standalone.ts +1 -1
- package/package.json +1 -1
- package/src/config/index.ts +1 -1
package/README.md
CHANGED
|
@@ -121,10 +121,10 @@ forge retry <task-id>
|
|
|
121
121
|
|
|
122
122
|
## YAML Workflows
|
|
123
123
|
|
|
124
|
-
Define multi-step flows in `~/.
|
|
124
|
+
Define multi-step flows in `~/.forge/flows/`:
|
|
125
125
|
|
|
126
126
|
```yaml
|
|
127
|
-
# ~/.
|
|
127
|
+
# ~/.forge/flows/daily-review.yaml
|
|
128
128
|
name: daily-review
|
|
129
129
|
steps:
|
|
130
130
|
- project: my-app
|
|
@@ -162,10 +162,10 @@ Password-protected commands auto-delete your message to keep credentials safe.
|
|
|
162
162
|
|
|
163
163
|
## Configuration
|
|
164
164
|
|
|
165
|
-
All config lives in `~/.
|
|
165
|
+
All config lives in `~/.forge/`:
|
|
166
166
|
|
|
167
167
|
```
|
|
168
|
-
~/.
|
|
168
|
+
~/.forge/
|
|
169
169
|
settings.yaml # Main configuration
|
|
170
170
|
password.json # Daily auto-generated login password
|
|
171
171
|
data.db # SQLite database (tasks, sessions)
|
package/bin/forge-server.mjs
CHANGED
|
@@ -3,21 +3,69 @@
|
|
|
3
3
|
* forge-server — Start the Forge web platform.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* forge-server
|
|
7
|
-
* forge-server --dev
|
|
6
|
+
* forge-server Start in foreground (production mode)
|
|
7
|
+
* forge-server --dev Start in foreground (development mode)
|
|
8
|
+
* forge-server --background Start in background (production mode), logs to ~/.forge/forge.log
|
|
9
|
+
* forge-server --stop Stop background server
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { execSync, spawn } from 'node:child_process';
|
|
11
|
-
import { existsSync } from 'node:fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, openSync } from 'node:fs';
|
|
12
14
|
import { join, dirname } from 'node:path';
|
|
13
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { homedir } from 'node:os';
|
|
14
17
|
|
|
15
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
19
|
const ROOT = join(__dirname, '..');
|
|
20
|
+
const DATA_DIR = join(homedir(), '.forge');
|
|
21
|
+
const PID_FILE = join(DATA_DIR, 'forge.pid');
|
|
22
|
+
const LOG_FILE = join(DATA_DIR, 'forge.log');
|
|
23
|
+
|
|
17
24
|
const isDev = process.argv.includes('--dev');
|
|
25
|
+
const isBackground = process.argv.includes('--background');
|
|
26
|
+
const isStop = process.argv.includes('--stop');
|
|
18
27
|
|
|
19
28
|
process.chdir(ROOT);
|
|
29
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
30
|
+
|
|
31
|
+
// ── Stop ──
|
|
32
|
+
if (isStop) {
|
|
33
|
+
try {
|
|
34
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim());
|
|
35
|
+
process.kill(pid, 'SIGTERM');
|
|
36
|
+
unlinkSync(PID_FILE);
|
|
37
|
+
console.log(`[forge] Stopped (pid ${pid})`);
|
|
38
|
+
} catch {
|
|
39
|
+
console.log('[forge] No running server found');
|
|
40
|
+
}
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Background ──
|
|
45
|
+
if (isBackground) {
|
|
46
|
+
// Build if needed
|
|
47
|
+
if (!existsSync(join(ROOT, '.next'))) {
|
|
48
|
+
console.log('[forge] Building...');
|
|
49
|
+
execSync('npx next build', { cwd: ROOT, stdio: 'inherit' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const logFd = openSync(LOG_FILE, 'a');
|
|
53
|
+
const child = spawn('npx', ['next', 'start'], {
|
|
54
|
+
cwd: ROOT,
|
|
55
|
+
stdio: ['ignore', logFd, logFd],
|
|
56
|
+
env: { ...process.env },
|
|
57
|
+
detached: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
writeFileSync(PID_FILE, String(child.pid));
|
|
61
|
+
child.unref();
|
|
62
|
+
console.log(`[forge] Started in background (pid ${child.pid})`);
|
|
63
|
+
console.log(`[forge] Log: ${LOG_FILE}`);
|
|
64
|
+
console.log(`[forge] Stop: forge-server --stop`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
20
67
|
|
|
68
|
+
// ── Foreground ──
|
|
21
69
|
if (isDev) {
|
|
22
70
|
console.log('[forge] Starting in development mode...');
|
|
23
71
|
const child = spawn('npx', ['next', 'dev', '--turbopack'], {
|
|
@@ -27,7 +75,6 @@ if (isDev) {
|
|
|
27
75
|
});
|
|
28
76
|
child.on('exit', (code) => process.exit(code || 0));
|
|
29
77
|
} else {
|
|
30
|
-
// Build if .next doesn't exist
|
|
31
78
|
if (!existsSync(join(ROOT, '.next'))) {
|
|
32
79
|
console.log('[forge] Building...');
|
|
33
80
|
execSync('npx next build', { cwd: ROOT, stdio: 'inherit' });
|
package/cli/mw.ts
CHANGED
|
@@ -229,7 +229,7 @@ async function main() {
|
|
|
229
229
|
const flows = await api('/api/flows');
|
|
230
230
|
if (flows.length === 0) {
|
|
231
231
|
console.log('No flows defined.');
|
|
232
|
-
console.log(`Create flows in ~/.
|
|
232
|
+
console.log(`Create flows in ~/.forge/flows/*.yaml`);
|
|
233
233
|
break;
|
|
234
234
|
}
|
|
235
235
|
for (const f of flows) {
|
|
@@ -313,7 +313,7 @@ async function main() {
|
|
|
313
313
|
const { readFileSync } = await import('node:fs');
|
|
314
314
|
const { homedir } = await import('node:os');
|
|
315
315
|
const { join } = await import('node:path');
|
|
316
|
-
const pwFile = join(homedir(), '.
|
|
316
|
+
const pwFile = join(homedir(), '.forge', 'password.json');
|
|
317
317
|
try {
|
|
318
318
|
const data = JSON.parse(readFileSync(pwFile, 'utf-8'));
|
|
319
319
|
const today = new Date().toISOString().slice(0, 10);
|
package/lib/auth.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import NextAuth from 'next-auth';
|
|
2
2
|
import Google from 'next-auth/providers/google';
|
|
3
3
|
import Credentials from 'next-auth/providers/credentials';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
// Ensure AUTH_SECRET exists before NextAuth initializes
|
|
7
|
+
if (!process.env.AUTH_SECRET) {
|
|
8
|
+
process.env.AUTH_SECRET = randomBytes(32).toString('hex');
|
|
9
|
+
}
|
|
4
10
|
|
|
5
11
|
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
12
|
+
trustHost: true,
|
|
6
13
|
providers: [
|
|
7
14
|
// Google OAuth — for production use
|
|
8
15
|
Google({
|
|
@@ -20,7 +27,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
|
20
27
|
const { getPassword } = await import('./password');
|
|
21
28
|
const localPassword = getPassword();
|
|
22
29
|
if (credentials?.password === localPassword) {
|
|
23
|
-
return { id: 'local', name: 'zliu', email: 'local@
|
|
30
|
+
return { id: 'local', name: 'zliu', email: 'local@forge' };
|
|
24
31
|
}
|
|
25
32
|
return null;
|
|
26
33
|
},
|
package/lib/cloudflared.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { join } from 'node:path';
|
|
|
10
10
|
import https from 'node:https';
|
|
11
11
|
import http from 'node:http';
|
|
12
12
|
|
|
13
|
-
const BIN_DIR = join(homedir(), '.
|
|
13
|
+
const BIN_DIR = join(homedir(), '.forge', 'bin');
|
|
14
14
|
const BIN_NAME = platform() === 'win32' ? 'cloudflared.exe' : 'cloudflared';
|
|
15
15
|
const BIN_PATH = join(BIN_DIR, BIN_NAME);
|
|
16
16
|
|
|
@@ -40,7 +40,7 @@ function getDownloadUrl(): string {
|
|
|
40
40
|
function followRedirects(url: string, dest: string): Promise<void> {
|
|
41
41
|
return new Promise((resolve, reject) => {
|
|
42
42
|
const client = url.startsWith('https') ? https : http;
|
|
43
|
-
client.get(url, { headers: { 'User-Agent': '
|
|
43
|
+
client.get(url, { headers: { 'User-Agent': 'forge' } }, (res) => {
|
|
44
44
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
45
45
|
followRedirects(res.headers.location, dest).then(resolve, reject);
|
|
46
46
|
return;
|
package/lib/flows.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow (Flow) engine — loads YAML flow definitions and executes them.
|
|
3
3
|
*
|
|
4
|
-
* Flow files live in ~/.
|
|
4
|
+
* Flow files live in ~/.forge/flows/*.yaml
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
@@ -12,7 +12,7 @@ import { createTask } from './task-manager';
|
|
|
12
12
|
import { getProjectInfo } from './projects';
|
|
13
13
|
import type { Task } from '@/src/types';
|
|
14
14
|
|
|
15
|
-
const FLOWS_DIR = join(homedir(), '.
|
|
15
|
+
const FLOWS_DIR = join(homedir(), '.forge', 'flows');
|
|
16
16
|
|
|
17
17
|
export interface FlowStep {
|
|
18
18
|
project: string;
|
package/lib/password.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated login password.
|
|
3
|
-
* Rotates daily. Saved to ~/.
|
|
3
|
+
* Rotates daily. Saved to ~/.forge/password.json with date.
|
|
4
4
|
* CLI can read it via `mw password`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ import { homedir } from 'node:os';
|
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
10
10
|
import { randomBytes } from 'node:crypto';
|
|
11
11
|
|
|
12
|
-
const PASSWORD_FILE = join(homedir(), '.
|
|
12
|
+
const PASSWORD_FILE = join(homedir(), '.forge', 'password.json');
|
|
13
13
|
|
|
14
14
|
function generatePassword(): string {
|
|
15
15
|
// 8-char alphanumeric, easy to type
|
package/lib/session-watcher.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { scanProjects } from './projects';
|
|
20
20
|
import { loadSettings } from './settings';
|
|
21
21
|
|
|
22
|
-
const DB_PATH = join(homedir(), '.
|
|
22
|
+
const DB_PATH = join(homedir(), '.forge', 'data.db');
|
|
23
23
|
|
|
24
24
|
// ─── Types ───────────────────────────────────────────────────
|
|
25
25
|
|
package/lib/settings.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
|
|
6
|
-
const SETTINGS_FILE = join(homedir(), '.
|
|
6
|
+
const SETTINGS_FILE = join(homedir(), '.forge', 'settings.yaml');
|
|
7
7
|
|
|
8
8
|
export interface Settings {
|
|
9
9
|
projectRoots: string[]; // Multiple project directories
|
|
@@ -39,7 +39,7 @@ delete process.env.CLAUDECODE;
|
|
|
39
39
|
|
|
40
40
|
// ─── Shared state persistence ─────────────────────────────────
|
|
41
41
|
|
|
42
|
-
const STATE_DIR = join(homedir(), '.
|
|
42
|
+
const STATE_DIR = join(homedir(), '.forge');
|
|
43
43
|
const STATE_FILE = join(STATE_DIR, 'terminal-state.json');
|
|
44
44
|
|
|
45
45
|
function loadTerminalState(): unknown {
|
package/package.json
CHANGED
package/src/config/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
import type { AppConfig, ProviderName, SessionTemplate } from '@/src/types';
|
|
6
6
|
|
|
7
|
-
const CONFIG_DIR = join(homedir(), '.
|
|
7
|
+
const CONFIG_DIR = join(homedir(), '.forge');
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');
|
|
9
9
|
const TEMPLATES_DIR = join(CONFIG_DIR, 'templates');
|
|
10
10
|
|