@aion0/forge 0.1.2 → 0.1.3

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 CHANGED
@@ -121,10 +121,10 @@ forge retry <task-id>
121
121
 
122
122
  ## YAML Workflows
123
123
 
124
- Define multi-step flows in `~/.my-workflow/flows/`:
124
+ Define multi-step flows in `~/.forge/flows/`:
125
125
 
126
126
  ```yaml
127
- # ~/.my-workflow/flows/daily-review.yaml
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 `~/.my-workflow/`:
165
+ All config lives in `~/.forge/`:
166
166
 
167
167
  ```
168
- ~/.my-workflow/
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)
@@ -3,21 +3,69 @@
3
3
  * forge-server — Start the Forge web platform.
4
4
  *
5
5
  * Usage:
6
- * forge-server Start in production mode (builds first if needed)
7
- * forge-server --dev Start in development mode
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 ~/.my-workflow/flows/*.yaml`);
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(), '.my-workflow', 'password.json');
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
@@ -3,6 +3,7 @@ import Google from 'next-auth/providers/google';
3
3
  import Credentials from 'next-auth/providers/credentials';
4
4
 
5
5
  export const { handlers, signIn, signOut, auth } = NextAuth({
6
+ trustHost: true,
6
7
  providers: [
7
8
  // Google OAuth — for production use
8
9
  Google({
@@ -20,7 +21,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
20
21
  const { getPassword } = await import('./password');
21
22
  const localPassword = getPassword();
22
23
  if (credentials?.password === localPassword) {
23
- return { id: 'local', name: 'zliu', email: 'local@my-workflow' };
24
+ return { id: 'local', name: 'zliu', email: 'local@forge' };
24
25
  }
25
26
  return null;
26
27
  },
@@ -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(), '.my-workflow', 'bin');
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': 'my-workflow' } }, (res) => {
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 ~/.my-workflow/flows/*.yaml
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(), '.my-workflow', 'flows');
15
+ const FLOWS_DIR = join(homedir(), '.forge', 'flows');
16
16
 
17
17
  export interface FlowStep {
18
18
  project: string;
package/lib/init.ts CHANGED
@@ -19,6 +19,12 @@ export function ensureInitialized() {
19
19
  if (gInit[initKey]) return;
20
20
  gInit[initKey] = true;
21
21
 
22
+ // Ensure AUTH_SECRET is set (NextAuth requires it)
23
+ if (!process.env.AUTH_SECRET) {
24
+ const { randomBytes } = require('node:crypto');
25
+ process.env.AUTH_SECRET = randomBytes(32).toString('hex');
26
+ }
27
+
22
28
  // Display login password (auto-generated, rotates daily)
23
29
  const password = getPassword();
24
30
  console.log(`[init] Login password: ${password} (valid today)`);
package/lib/password.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated login password.
3
- * Rotates daily. Saved to ~/.my-workflow/password.json with date.
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(), '.my-workflow', 'password.json');
12
+ const PASSWORD_FILE = join(homedir(), '.forge', 'password.json');
13
13
 
14
14
  function generatePassword(): string {
15
15
  // 8-char alphanumeric, easy to type
@@ -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(), '.my-workflow', 'data.db');
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(), '.my-workflow', 'settings.yaml');
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(), '.my-workflow');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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(), '.my-workflow');
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