@aion0/forge 0.2.31 → 0.2.32

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.
@@ -8,6 +8,7 @@ export default function LoginPage() {
8
8
  const [sessionCode, setSessionCode] = useState('');
9
9
  const [error, setError] = useState('');
10
10
  const [isRemote, setIsRemote] = useState(false);
11
+ const [showHelp, setShowHelp] = useState(false);
11
12
 
12
13
  useEffect(() => {
13
14
  const host = window.location.hostname;
@@ -76,6 +77,21 @@ export default function LoginPage() {
76
77
  Session code is generated when tunnel starts. Use /tunnel_code in Telegram to get it.
77
78
  </p>
78
79
  )}
80
+ <div className="text-center">
81
+ <button
82
+ type="button"
83
+ onClick={() => setShowHelp(v => !v)}
84
+ className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
85
+ >
86
+ Forgot password?
87
+ </button>
88
+ {showHelp && (
89
+ <p className="text-[10px] text-[var(--text-secondary)] mt-1 bg-[var(--bg-tertiary)] rounded p-2">
90
+ Run in terminal:<br />
91
+ <code className="text-[var(--accent)]">forge server start --reset-password</code>
92
+ </p>
93
+ )}
94
+ </div>
79
95
  </form>
80
96
 
81
97
  </div>
@@ -59,6 +59,7 @@ const isStop = process.argv.includes('--stop');
59
59
  const isRestart = process.argv.includes('--restart');
60
60
  const isRebuild = process.argv.includes('--rebuild');
61
61
  const resetTerminal = process.argv.includes('--reset-terminal');
62
+ const resetPassword = process.argv.includes('--reset-password');
62
63
 
63
64
  const webPort = parseInt(getArg('--port')) || 3000;
64
65
  const terminalPort = parseInt(getArg('--terminal-port')) || 3001;
@@ -89,6 +90,67 @@ process.env.PORT = String(webPort);
89
90
  process.env.TERMINAL_PORT = String(terminalPort);
90
91
  process.env.FORGE_DATA_DIR = DATA_DIR;
91
92
 
93
+ // ── Password setup (first run or --reset-password) ──
94
+ if (!isStop) {
95
+ const YAML = await import('yaml');
96
+ const settingsFile = join(DATA_DIR, 'settings.yaml');
97
+ let settings = {};
98
+ try { settings = YAML.parse(readFileSync(settingsFile, 'utf-8')) || {}; } catch {}
99
+
100
+ const hasPassword = !!settings.telegramTunnelPassword;
101
+
102
+ if (resetPassword || !hasPassword) {
103
+ if (resetPassword) {
104
+ console.log('[forge] Password reset requested');
105
+ } else {
106
+ console.log('[forge] First run — please set an admin password');
107
+ }
108
+
109
+ const readline = await import('node:readline');
110
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
111
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
112
+
113
+ let pw = '';
114
+ while (true) {
115
+ pw = await ask(' Enter admin password: ');
116
+ if (!pw || pw.length < 4) {
117
+ console.log(' Password must be at least 4 characters');
118
+ continue;
119
+ }
120
+ const confirm = await ask(' Confirm password: ');
121
+ if (pw !== confirm) {
122
+ console.log(' Passwords do not match, try again');
123
+ continue;
124
+ }
125
+ break;
126
+ }
127
+ rl.close();
128
+
129
+ // Encrypt and save
130
+ const crypto = await import('node:crypto');
131
+ const KEY_FILE = join(DATA_DIR, '.encrypt-key');
132
+ let encKey;
133
+ if (existsSync(KEY_FILE)) {
134
+ encKey = Buffer.from(readFileSync(KEY_FILE, 'utf-8').trim(), 'hex');
135
+ } else {
136
+ encKey = crypto.randomBytes(32);
137
+ writeFileSync(KEY_FILE, encKey.toString('hex'), { mode: 0o600 });
138
+ }
139
+ const iv = crypto.randomBytes(12);
140
+ const cipher = crypto.createCipheriv('aes-256-gcm', encKey, iv);
141
+ const encrypted = Buffer.concat([cipher.update(pw, 'utf-8'), cipher.final()]);
142
+ const tag = cipher.getAuthTag();
143
+ settings.telegramTunnelPassword = `enc:${iv.toString('base64')}.${tag.toString('base64')}.${encrypted.toString('base64')}`;
144
+ if (!existsSync(dirname(settingsFile))) mkdirSync(dirname(settingsFile), { recursive: true });
145
+ writeFileSync(settingsFile, YAML.stringify(settings), 'utf-8');
146
+ console.log('[forge] Admin password saved');
147
+
148
+ if (resetPassword && !isDev && !isBackground && !isRestart) {
149
+ process.exit(0);
150
+ }
151
+ }
152
+ }
153
+
92
154
  // ── Reset terminal server (kill port + tmux sessions) ──
93
155
  if (resetTerminal) {
94
156
  console.log(`[forge] Resetting terminal server (port ${terminalPort})...`);
@@ -795,6 +795,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
795
795
  isSet={!!secretStatus.telegramTunnelPassword}
796
796
  onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
797
797
  />
798
+ <p className="text-[9px] text-[var(--text-secondary)]">
799
+ Forgot? Run: <code className="text-[var(--accent)]">forge server start --reset-password</code>
800
+ </p>
798
801
  </div>
799
802
 
800
803
  {/* Actions */}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {