@aion0/forge 0.2.18 → 0.2.20

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
  }
@@ -8,6 +8,7 @@ import SessionView from './SessionView';
8
8
  import NewTaskModal from './NewTaskModal';
9
9
  import SettingsModal from './SettingsModal';
10
10
  import TunnelToggle from './TunnelToggle';
11
+ import MonitorPanel from './MonitorPanel';
11
12
  import type { Task } from '@/src/types';
12
13
  import type { WebTerminalHandle } from './WebTerminal';
13
14
 
@@ -44,6 +45,7 @@ export default function Dashboard({ user }: { user: any }) {
44
45
  const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
45
46
  const [showNewTask, setShowNewTask] = useState(false);
46
47
  const [showSettings, setShowSettings] = useState(false);
48
+ const [showMonitor, setShowMonitor] = useState(false);
47
49
  const [usage, setUsage] = useState<UsageSummary[]>([]);
48
50
  const [providers, setProviders] = useState<ProviderInfo[]>([]);
49
51
  const [projects, setProjects] = useState<ProjectInfo[]>([]);
@@ -122,7 +124,7 @@ export default function Dashboard({ user }: { user: any }) {
122
124
  setUpgrading(false);
123
125
  }}
124
126
  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}`}
127
+ title={`Update to v${versionInfo.latest}\nOr run: forge upgrade`}
126
128
  >
127
129
  {upgrading ? 'Upgrading...' : `Update v${versionInfo.latest}`}
128
130
  </button>
@@ -134,7 +136,7 @@ export default function Dashboard({ user }: { user: any }) {
134
136
  const data = await res.json();
135
137
  setVersionInfo(data);
136
138
  }}
137
- className="text-[9px] px-1.5 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
139
+ className="text-[9px] px-1 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
138
140
  title="Check for updates"
139
141
  >
140
142
 
@@ -200,6 +202,16 @@ export default function Dashboard({ user }: { user: any }) {
200
202
  >
201
203
  Pipelines
202
204
  </button>
205
+ <button
206
+ onClick={() => setViewMode('sessions')}
207
+ className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
208
+ viewMode === 'sessions'
209
+ ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
210
+ : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
211
+ }`}
212
+ >
213
+ Sessions
214
+ </button>
203
215
  <button
204
216
  onClick={() => setViewMode('preview')}
205
217
  className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
@@ -238,10 +250,10 @@ export default function Dashboard({ user }: { user: any }) {
238
250
  </span>
239
251
  )}
240
252
  <button
241
- onClick={() => setViewMode('sessions')}
242
- className={`text-xs ${viewMode === 'sessions' ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
253
+ onClick={() => setShowMonitor(true)}
254
+ className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
243
255
  >
244
- System Status
256
+ Monitor
245
257
  </button>
246
258
  <button
247
259
  onClick={() => setShowSettings(true)}
@@ -414,6 +426,8 @@ export default function Dashboard({ user }: { user: any }) {
414
426
  />
415
427
  )}
416
428
 
429
+ {showMonitor && <MonitorPanel onClose={() => setShowMonitor(false)} />}
430
+
417
431
  {showSettings && (
418
432
  <SettingsModal onClose={() => { setShowSettings(false); fetchData(); }} />
419
433
  )}
@@ -32,16 +32,6 @@ interface Watcher {
32
32
  createdAt: string;
33
33
  }
34
34
 
35
- interface MonitorData {
36
- processes: {
37
- nextjs: { running: boolean; pid: string };
38
- terminal: { running: boolean; pid: string };
39
- telegram: { running: boolean; pid: string };
40
- tunnel: { running: boolean; pid: string; url: string };
41
- };
42
- sessions: { name: string; created: string; attached: boolean; windows: number }[];
43
- uptime: string;
44
- }
45
35
 
46
36
  export default function SessionView({
47
37
  projectName,
@@ -63,8 +53,6 @@ export default function SessionView({
63
53
  const [watchers, setWatchers] = useState<Watcher[]>([]);
64
54
  const [batchMode, setBatchMode] = useState(false);
65
55
  const [selectedIds, setSelectedIds] = useState<Map<string, Set<string>>>(new Map());
66
- const [monitor, setMonitor] = useState<MonitorData | null>(null);
67
- const [monitorOpen, setMonitorOpen] = useState(true);
68
56
  const bottomRef = useRef<HTMLDivElement>(null);
69
57
 
70
58
  // Load cached sessions tree
@@ -92,17 +80,10 @@ export default function SessionView({
92
80
  } catch {}
93
81
  }, []);
94
82
 
95
- const refreshMonitor = useCallback(() => {
96
- fetch('/api/monitor').then(r => r.json()).then(setMonitor).catch(() => {});
97
- }, []);
98
-
99
83
  useEffect(() => {
100
84
  loadTree(true);
101
85
  loadWatchers();
102
- refreshMonitor();
103
- const timer = setInterval(refreshMonitor, 5000);
104
- return () => clearInterval(timer);
105
- }, [loadTree, loadWatchers, refreshMonitor]);
86
+ }, [loadTree, loadWatchers]);
106
87
 
107
88
  // Auto-expand project if only one or if pre-selected
108
89
  useEffect(() => {
@@ -348,56 +329,6 @@ export default function SessionView({
348
329
  </div>
349
330
  )}
350
331
 
351
- {/* Monitor */}
352
- {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>
360
- {monitor.uptime && (
361
- <span className="text-[8px] text-[var(--text-secondary)] ml-auto">{monitor.uptime}</span>
362
- )}
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
- )}
382
-
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
- ))}
394
- </div>
395
- )}
396
- </div>
397
- )}
398
- </div>
399
- )}
400
-
401
332
  {/* Tree */}
402
333
  <div className="flex-1 overflow-y-auto">
403
334
  {Object.keys(sessionTree).length === 0 && (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {