@aion0/forge 0.2.17 → 0.2.19

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.
@@ -1,39 +1,40 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { join } from 'node:path';
3
- import { lstatSync } from 'node:fs';
4
3
  import { execSync } from 'node:child_process';
5
4
 
6
5
  export async function POST() {
7
6
  try {
8
- // Check if installed via npm link (symlink = local dev)
9
- let isLinked = false;
10
- try { isLinked = lstatSync(join(process.cwd())).isSymbolicLink(); } catch {}
7
+ // Run upgrade with cache bypass
8
+ const output = execSync(
9
+ 'cd /tmp && npm install -g @aion0/forge@latest --prefer-online 2>&1',
10
+ { encoding: 'utf-8', timeout: 120000 }
11
+ );
11
12
 
12
- if (isLinked) {
13
- return NextResponse.json({
14
- ok: false,
15
- error: 'Local dev install (npm link). Run: git pull && pnpm install && pnpm build',
16
- });
17
- }
18
-
19
- // Upgrade from npm
20
- execSync('cd /tmp && npm install -g @aion0/forge', { timeout: 120000 });
21
-
22
- // Install devDependencies for build (npm -g doesn't install them)
13
+ // Verify the installed version
23
14
  const pkgRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
24
15
  const forgeRoot = join(pkgRoot, '@aion0', 'forge');
16
+
17
+ // Install devDependencies for build (npm -g doesn't install them)
18
+ try {
19
+ execSync('npm install --include=dev 2>&1', { cwd: forgeRoot, timeout: 120000 });
20
+ } catch {}
21
+
22
+ // Read installed version
23
+ let installedVersion = '';
25
24
  try {
26
- execSync('npm install --include=dev', { cwd: forgeRoot, timeout: 120000 });
25
+ const pkg = JSON.parse(require('fs').readFileSync(join(forgeRoot, 'package.json'), 'utf-8'));
26
+ installedVersion = pkg.version;
27
27
  } catch {}
28
28
 
29
29
  return NextResponse.json({
30
30
  ok: true,
31
- message: 'Upgraded. Restart server to apply.',
31
+ message: `Upgraded to v${installedVersion}. Restart server to apply.`,
32
32
  });
33
33
  } catch (e) {
34
+ const msg = e instanceof Error ? e.message : String(e);
34
35
  return NextResponse.json({
35
36
  ok: false,
36
- error: `Upgrade failed: ${e instanceof Error ? e.message : String(e)}`,
37
+ error: `Upgrade failed: ${msg.slice(0, 200)}`,
37
38
  });
38
39
  }
39
40
  }
@@ -16,8 +16,8 @@ const CURRENT_VERSION = (() => {
16
16
  let cachedLatest: { version: string; checkedAt: number } | null = null;
17
17
  const CACHE_TTL = 10 * 60 * 1000; // 10 minutes
18
18
 
19
- async function getLatestVersion(): Promise<string> {
20
- if (cachedLatest && Date.now() - cachedLatest.checkedAt < CACHE_TTL) {
19
+ async function getLatestVersion(force = false): Promise<string> {
20
+ if (!force && cachedLatest && Date.now() - cachedLatest.checkedAt < CACHE_TTL) {
21
21
  return cachedLatest.version;
22
22
  }
23
23
  try {
@@ -47,9 +47,11 @@ function compareVersions(a: string, b: string): number {
47
47
  return 0;
48
48
  }
49
49
 
50
- export async function GET() {
50
+ export async function GET(req: Request) {
51
+ const { searchParams } = new URL(req.url);
52
+ const force = searchParams.has('force');
51
53
  const current = CURRENT_VERSION;
52
- const latest = await getLatestVersion();
54
+ const latest = await getLatestVersion(force);
53
55
  const hasUpdate = latest && compareVersions(current, latest) < 0;
54
56
 
55
57
  return NextResponse.json({
@@ -122,11 +122,24 @@ export default function Dashboard({ user }: { user: any }) {
122
122
  setUpgrading(false);
123
123
  }}
124
124
  className="text-[9px] px-1.5 py-0.5 bg-[var(--accent)] text-white rounded hover:opacity-90 disabled:opacity-50"
125
- title={`Update to v${versionInfo.latest}`}
125
+ title={`Update to v${versionInfo.latest}\nOr run: forge upgrade`}
126
126
  >
127
127
  {upgrading ? 'Upgrading...' : `Update v${versionInfo.latest}`}
128
128
  </button>
129
129
  )}
130
+ {!versionInfo.hasUpdate && !upgradeResult && (
131
+ <button
132
+ onClick={async () => {
133
+ const res = await fetch('/api/version?force=1');
134
+ const data = await res.json();
135
+ setVersionInfo(data);
136
+ }}
137
+ className="text-[9px] px-1 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
138
+ title="Check for updates"
139
+ >
140
+
141
+ </button>
142
+ )}
130
143
  {upgradeResult && (
131
144
  <span className="text-[9px] text-[var(--green)] max-w-[200px] truncate" title={upgradeResult}>
132
145
  {upgradeResult}
@@ -187,16 +200,6 @@ export default function Dashboard({ user }: { user: any }) {
187
200
  >
188
201
  Pipelines
189
202
  </button>
190
- <button
191
- onClick={() => setViewMode('sessions')}
192
- className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
193
- viewMode === 'sessions'
194
- ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
195
- : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
196
- }`}
197
- >
198
- Sessions
199
- </button>
200
203
  <button
201
204
  onClick={() => setViewMode('preview')}
202
205
  className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
@@ -234,6 +237,12 @@ export default function Dashboard({ user }: { user: any }) {
234
237
  )}
235
238
  </span>
236
239
  )}
240
+ <button
241
+ onClick={() => setViewMode('sessions')}
242
+ className={`text-xs ${viewMode === 'sessions' ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
243
+ >
244
+ Monitor
245
+ </button>
237
246
  <button
238
247
  onClick={() => setShowSettings(true)}
239
248
  className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
@@ -64,7 +64,6 @@ export default function SessionView({
64
64
  const [batchMode, setBatchMode] = useState(false);
65
65
  const [selectedIds, setSelectedIds] = useState<Map<string, Set<string>>>(new Map());
66
66
  const [monitor, setMonitor] = useState<MonitorData | null>(null);
67
- const [monitorOpen, setMonitorOpen] = useState(true);
68
67
  const bottomRef = useRef<HTMLDivElement>(null);
69
68
 
70
69
  // Load cached sessions tree
@@ -348,51 +347,41 @@ export default function SessionView({
348
347
  </div>
349
348
  )}
350
349
 
351
- {/* Monitor */}
350
+ {/* Monitor — always visible */}
352
351
  {monitor && (
353
- <div className="border-b border-[var(--border)]">
354
- <button
355
- onClick={() => setMonitorOpen(v => !v)}
356
- className="w-full flex items-center gap-1.5 px-2 py-1.5 hover:bg-[var(--bg-tertiary)] transition-colors"
357
- >
358
- <span className="text-[10px] text-[var(--text-secondary)]">{monitorOpen ? '▼' : '▶'}</span>
359
- <span className="text-[9px] font-semibold text-[var(--text-secondary)] uppercase">Monitor</span>
352
+ <div className="border-b border-[var(--border)] px-2 py-2 space-y-1.5">
353
+ <div className="flex items-center justify-between">
354
+ <span className="text-[9px] font-semibold text-[var(--text-secondary)] uppercase">Processes</span>
360
355
  {monitor.uptime && (
361
- <span className="text-[8px] text-[var(--text-secondary)] ml-auto">{monitor.uptime}</span>
356
+ <span className="text-[8px] text-[var(--text-secondary)]">up {monitor.uptime}</span>
362
357
  )}
363
- </button>
364
- {monitorOpen && (
365
- <div className="px-2 pb-2 space-y-1.5">
366
- {/* Processes */}
367
- {[
368
- { label: 'Next.js', ...monitor.processes.nextjs },
369
- { label: 'Terminal', ...monitor.processes.terminal },
370
- { label: 'Telegram', ...monitor.processes.telegram },
371
- { label: 'Tunnel', ...monitor.processes.tunnel },
372
- ].map(p => (
373
- <div key={p.label} className="flex items-center gap-1.5 text-[10px]">
374
- <span className={p.running ? 'text-green-400' : 'text-gray-500'}>●</span>
375
- <span className="text-[var(--text-primary)]">{p.label}</span>
376
- <span className="text-[var(--text-secondary)] font-mono ml-auto">{p.running ? `pid:${p.pid}` : 'stopped'}</span>
377
- </div>
378
- ))}
379
- {monitor.processes.tunnel.running && monitor.processes.tunnel.url && (
380
- <div className="text-[9px] text-[var(--accent)] truncate pl-4">{monitor.processes.tunnel.url}</div>
381
- )}
358
+ </div>
359
+ {[
360
+ { label: 'Next.js', ...monitor.processes.nextjs },
361
+ { label: 'Terminal', ...monitor.processes.terminal },
362
+ { label: 'Telegram', ...monitor.processes.telegram },
363
+ { label: 'Tunnel', ...monitor.processes.tunnel },
364
+ ].map(p => (
365
+ <div key={p.label} className="flex items-center gap-1.5 text-[10px]">
366
+ <span className={p.running ? 'text-green-400' : 'text-gray-500'}>●</span>
367
+ <span className="text-[var(--text-primary)]">{p.label}</span>
368
+ <span className="text-[var(--text-secondary)] font-mono ml-auto">{p.running ? `pid:${p.pid}` : 'stopped'}</span>
369
+ </div>
370
+ ))}
371
+ {monitor.processes.tunnel.running && monitor.processes.tunnel.url && (
372
+ <div className="text-[9px] text-[var(--accent)] truncate pl-4">{monitor.processes.tunnel.url}</div>
373
+ )}
382
374
 
383
- {/* Tmux sessions */}
384
- {monitor.sessions.length > 0 && (
385
- <div className="pt-1">
386
- <span className="text-[8px] font-semibold text-[var(--text-secondary)] uppercase">Tmux ({monitor.sessions.length})</span>
387
- {monitor.sessions.map(s => (
388
- <div key={s.name} className="flex items-center gap-1.5 text-[10px] mt-0.5">
389
- <span className={s.attached ? 'text-green-400' : 'text-yellow-500'}>●</span>
390
- <span className="font-mono text-[var(--text-primary)] truncate flex-1">{s.name}</span>
391
- <span className="text-[8px] text-[var(--text-secondary)]">{s.attached ? 'attached' : 'detached'}</span>
392
- </div>
393
- ))}
375
+ {monitor.sessions.length > 0 && (
376
+ <div className="pt-1">
377
+ <span className="text-[9px] font-semibold text-[var(--text-secondary)] uppercase">Tmux ({monitor.sessions.length})</span>
378
+ {monitor.sessions.map(s => (
379
+ <div key={s.name} className="flex items-center gap-1.5 text-[10px] mt-0.5">
380
+ <span className={s.attached ? 'text-green-400' : 'text-yellow-500'}>●</span>
381
+ <span className="font-mono text-[var(--text-primary)] truncate flex-1">{s.name}</span>
382
+ <span className="text-[8px] text-[var(--text-secondary)]">{s.attached ? 'attached' : 'detached'}</span>
394
383
  </div>
395
- )}
384
+ ))}
396
385
  </div>
397
386
  )}
398
387
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {