@aion0/forge 0.2.2 → 0.2.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 +166 -175
- package/app/api/pipelines/[id]/route.ts +28 -0
- package/app/api/pipelines/route.ts +52 -0
- package/components/Dashboard.tsx +19 -1
- package/components/DocsViewer.tsx +10 -1
- package/components/PipelineEditor.tsx +399 -0
- package/components/PipelineView.tsx +435 -0
- package/lib/pipeline.ts +514 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,42 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="app/icon.svg" width="80" height="80" alt="Forge">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
>
|
|
5
|
+
<h1 align="center">Forge</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Self-hosted Vibe Coding platform — browser terminal, task orchestration, remote access</strong>
|
|
9
|
+
</p>
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@aion0/forge"><img src="https://img.shields.io/npm/v/@aion0/forge" alt="npm"></a>
|
|
13
|
+
<a href="https://github.com/aiwatching/forge/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@aion0/forge" alt="license"></a>
|
|
14
|
+
<a href="https://github.com/aiwatching/forge"><img src="https://img.shields.io/github/stars/aiwatching/forge?style=social" alt="stars"></a>
|
|
15
|
+
</p>
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="#installation">Install</a> · <a href="#features">Features</a> · <a href="#quick-start">Quick Start</a> · <a href="#telegram-bot">Telegram</a> · <a href="#configuration">Config</a> · <a href="#roadmap">Roadmap</a>
|
|
19
|
+
</p>
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
- **Task Orchestration** — Submit tasks to Claude Code, queue them by project, track progress with live streaming output
|
|
13
|
-
- **Remote Access** — One-click Cloudflare Tunnel for a secure public URL (zero config, no account needed)
|
|
14
|
-
- **Session Continuity** — Tasks for the same project automatically continue the previous conversation context
|
|
15
|
-
- **YAML Workflows** — Define multi-step flows that chain tasks together
|
|
16
|
-
- **Bot Integration** — Telegram bot for mobile task management and tunnel control (extensible to other platforms)
|
|
17
|
-
- **Session Watcher** — Monitor Claude Code sessions for changes, idle state, keywords, or errors
|
|
18
|
-
- **CLI** — Full-featured command-line interface for task management
|
|
19
|
-
- **Auth** — Auto-generated daily rotating password + optional Google OAuth
|
|
21
|
+
---
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
Forge turns [Claude Code](https://docs.anthropic.com/en/docs/claude-code) into a full web-based vibe coding platform. Open your browser, start coding with AI from anywhere — your iPad, phone, or any device with a browser.
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
**No API keys required.** Runs on your existing Claude Code CLI subscription. Your code stays on your machine.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
| Feature | Description |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| **Vibe Coding** | Browser-based tmux terminal. Multiple tabs, persistent sessions that survive refresh, browser close, and server restart |
|
|
32
|
+
| **Remote Access** | One-click Cloudflare Tunnel — secure public URL, zero config, no account needed |
|
|
33
|
+
| **Task Queue** | Submit tasks to Claude Code in the background. Live streaming output, cost tracking, session continuity |
|
|
34
|
+
| **Docs Viewer** | Render Obsidian vaults / markdown directories with a dedicated Claude Console |
|
|
35
|
+
| **Project Manager** | Browse projects, view files, git status, commit, push, pull — all from the browser |
|
|
36
|
+
| **Demo Preview** | Preview local dev servers through the tunnel with a dedicated Cloudflare URL |
|
|
37
|
+
| **Telegram Bot** | Submit tasks, check status, control tunnel, take notes — all from your phone |
|
|
38
|
+
| **File Browser** | Code viewer with syntax highlighting, git changes, diff view, multi-repo support |
|
|
39
|
+
| **YAML Workflows** | Define multi-step flows that chain Claude Code tasks together |
|
|
40
|
+
| **CLI** | Full command-line interface for task management |
|
|
27
41
|
|
|
28
42
|
## Installation
|
|
29
43
|
|
|
30
|
-
###
|
|
44
|
+
### npm (recommended)
|
|
31
45
|
|
|
32
46
|
```bash
|
|
33
47
|
npm install -g @aion0/forge
|
|
34
|
-
|
|
35
|
-
# Start the server
|
|
36
48
|
forge-server
|
|
37
|
-
|
|
38
|
-
# Or in development mode
|
|
39
|
-
forge-server --dev
|
|
40
49
|
```
|
|
41
50
|
|
|
42
51
|
### From source
|
|
@@ -48,224 +57,206 @@ pnpm install
|
|
|
48
57
|
pnpm dev
|
|
49
58
|
```
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### 1. Log in
|
|
54
|
-
|
|
55
|
-
Open `http://localhost:3000`. A login password is auto-generated and printed in the console:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
[init] Login password: a7x9k2 (valid today)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The password rotates daily. Forgot it? Run:
|
|
60
|
+
### Options
|
|
62
61
|
|
|
63
62
|
```bash
|
|
64
|
-
forge
|
|
63
|
+
forge-server # Production (auto-builds if needed)
|
|
64
|
+
forge-server --dev # Development with hot-reload
|
|
65
|
+
forge-server --background # Run in background, logs to ~/.forge/forge.log
|
|
66
|
+
forge-server --stop # Stop background server
|
|
67
|
+
forge-server --rebuild # Force rebuild
|
|
65
68
|
```
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Open **Settings** (gear icon) and add your project root directories (e.g. `~/Projects`). Forge will scan for git repositories automatically.
|
|
70
|
-
|
|
71
|
-
## Web Terminal
|
|
72
|
-
|
|
73
|
-
The core feature. A browser-based terminal powered by tmux:
|
|
74
|
-
|
|
75
|
-
- **Persistent** — Sessions survive page refresh, browser close, and server restart
|
|
76
|
-
- **Multi-tab** — Create, rename, and manage multiple terminal tabs
|
|
77
|
-
- **Remote-ready** — Access your terminal from anywhere via Cloudflare Tunnel
|
|
78
|
-
- **Large scrollback** — 50,000 lines with mouse support
|
|
79
|
-
|
|
80
|
-
The terminal server runs on `localhost:3001` and is auto-proxied through the main app for remote access.
|
|
81
|
-
|
|
82
|
-
## Remote Access (Cloudflare Tunnel)
|
|
83
|
-
|
|
84
|
-
Access Forge from anywhere without port forwarding or DNS config:
|
|
85
|
-
|
|
86
|
-
1. Click the **tunnel icon** in the header bar, or go to **Settings > Remote Access**
|
|
87
|
-
2. Click **Start** — Forge auto-downloads `cloudflared` and creates a temporary public URL
|
|
88
|
-
3. The URL is protected by the daily login password
|
|
89
|
-
|
|
90
|
-
Enable **Auto-start** in Settings to start the tunnel on every server boot.
|
|
91
|
-
|
|
92
|
-
> The tunnel URL changes each time. Use the Telegram bot `/tunnel_password` command to get the current URL and password on your phone.
|
|
93
|
-
|
|
94
|
-
## Task Orchestration
|
|
95
|
-
|
|
96
|
-
Submit AI coding tasks that run in the background:
|
|
70
|
+
## Prerequisites
|
|
97
71
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
72
|
+
- **Node.js** >= 20
|
|
73
|
+
- **tmux** — `brew install tmux` (macOS) / `apt install tmux` (Linux)
|
|
74
|
+
- **Claude Code CLI** — `npm install -g @anthropic-ai/claude-code`
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
forge task my-app "Refactor the API layer" --new
|
|
76
|
+
## Quick Start
|
|
104
77
|
|
|
105
|
-
|
|
106
|
-
forge tasks # all
|
|
107
|
-
forge tasks running # filter by status
|
|
78
|
+
1. **Start Forge**
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
forge
|
|
80
|
+
```bash
|
|
81
|
+
forge-server
|
|
82
|
+
```
|
|
111
83
|
|
|
112
|
-
|
|
113
|
-
forge status <task-id>
|
|
84
|
+
2. **Open browser** → `http://localhost:3000`
|
|
114
85
|
|
|
115
|
-
|
|
116
|
-
forge cancel <task-id>
|
|
117
|
-
forge retry <task-id>
|
|
118
|
-
```
|
|
86
|
+
3. **Log in** — password is auto-generated and printed in the console:
|
|
119
87
|
|
|
120
|
-
|
|
88
|
+
```
|
|
89
|
+
[init] Login password: a7x9k2 (valid today)
|
|
90
|
+
```
|
|
121
91
|
|
|
122
|
-
|
|
92
|
+
Forgot it? Run `forge password`
|
|
123
93
|
|
|
124
|
-
|
|
94
|
+
4. **Configure projects** — Settings → add your project directories
|
|
125
95
|
|
|
126
|
-
|
|
127
|
-
# ~/.forge/flows/daily-review.yaml
|
|
128
|
-
name: daily-review
|
|
129
|
-
steps:
|
|
130
|
-
- project: my-app
|
|
131
|
-
prompt: "Review open TODOs and suggest fixes"
|
|
132
|
-
- project: my-api
|
|
133
|
-
prompt: "Check for any failing tests and fix them"
|
|
134
|
-
```
|
|
96
|
+
5. **Start vibe coding** — open a terminal tab, run `claude`, and go
|
|
135
97
|
|
|
136
|
-
|
|
98
|
+
## Remote Access
|
|
137
99
|
|
|
138
|
-
|
|
100
|
+
Access Forge from anywhere — your phone, iPad, or another computer:
|
|
139
101
|
|
|
140
|
-
|
|
102
|
+
1. Click the **tunnel button** in the header
|
|
103
|
+
2. Forge auto-downloads `cloudflared` and creates a temporary public URL
|
|
104
|
+
3. Open the URL on any device — protected by the daily login password
|
|
141
105
|
|
|
142
|
-
|
|
106
|
+
> The tunnel URL changes each time. Use the Telegram `/tunnel_password` command to get it on your phone.
|
|
143
107
|
|
|
144
|
-
|
|
145
|
-
2. In **Settings**, add your **Bot Token** and **Chat ID**
|
|
146
|
-
3. Optionally set a **Tunnel Password** for remote access control
|
|
108
|
+
## Telegram Bot
|
|
147
109
|
|
|
148
|
-
|
|
110
|
+
Control Forge from your phone. Create a bot via [@BotFather](https://t.me/botfather), add the token in Settings.
|
|
149
111
|
|
|
150
112
|
| Command | Description |
|
|
151
113
|
|---------|-------------|
|
|
114
|
+
| `/task` | Create a task (interactive project picker) |
|
|
152
115
|
| `/tasks` | List tasks with quick-action numbers |
|
|
153
|
-
| `/
|
|
154
|
-
| `/
|
|
155
|
-
| `/
|
|
156
|
-
| `/
|
|
157
|
-
| `/
|
|
116
|
+
| `/peek` | AI summary of a Claude session |
|
|
117
|
+
| `/docs` | Docs session summary or file search |
|
|
118
|
+
| `/note` | Quick note — sent to Docs Claude |
|
|
119
|
+
| `/tunnel_start` | Start Cloudflare Tunnel |
|
|
120
|
+
| `/tunnel_stop` | Stop tunnel |
|
|
158
121
|
| `/tunnel_password <pw>` | Get login password + tunnel URL |
|
|
159
|
-
| `/help` | Show all commands |
|
|
160
122
|
|
|
161
|
-
|
|
123
|
+
Whitelist-protected — only configured Chat IDs can interact with the bot.
|
|
162
124
|
|
|
163
|
-
##
|
|
164
|
-
|
|
165
|
-
All config lives in `~/.forge/`:
|
|
125
|
+
## CLI
|
|
166
126
|
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
127
|
+
```bash
|
|
128
|
+
forge task <project> <prompt> # Submit a task
|
|
129
|
+
forge tasks [status] # List tasks
|
|
130
|
+
forge watch <id> # Live stream output
|
|
131
|
+
forge status <id> # Task details + result
|
|
132
|
+
forge cancel <id> # Cancel a task
|
|
133
|
+
forge retry <id> # Retry a failed task
|
|
134
|
+
forge run <flow-name> # Run a YAML workflow
|
|
135
|
+
forge projects # List projects
|
|
136
|
+
forge password # Show login password
|
|
176
137
|
```
|
|
177
138
|
|
|
178
|
-
|
|
139
|
+
Shortcuts: `t`=task, `ls`=tasks, `w`=watch, `s`=status, `f`=flows, `p`=projects, `pw`=password
|
|
179
140
|
|
|
180
|
-
|
|
181
|
-
# Fixed auth secret (optional — auto-generated if not set)
|
|
182
|
-
AUTH_SECRET=<random-string>
|
|
141
|
+
## Configuration
|
|
183
142
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
143
|
+
All data lives in `~/.forge/`:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
~/.forge/
|
|
147
|
+
├── .env.local # Environment variables (optional)
|
|
148
|
+
├── settings.yaml # Main configuration
|
|
149
|
+
├── password.json # Daily auto-generated password
|
|
150
|
+
├── data.db # SQLite database
|
|
151
|
+
├── terminal-state.json # Terminal tab layout
|
|
152
|
+
├── preview.json # Demo preview config
|
|
153
|
+
├── flows/ # YAML workflow definitions
|
|
154
|
+
└── bin/ # Auto-downloaded binaries
|
|
188
155
|
```
|
|
189
156
|
|
|
190
|
-
|
|
157
|
+
<details>
|
|
158
|
+
<summary><strong>settings.yaml</strong></summary>
|
|
191
159
|
|
|
192
160
|
```yaml
|
|
193
|
-
# Project directories to scan
|
|
194
161
|
projectRoots:
|
|
195
162
|
- ~/Projects
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# Claude Code binary path (default: claude)
|
|
163
|
+
docRoots:
|
|
164
|
+
- ~/Documents/obsidian-vault
|
|
199
165
|
claudePath: claude
|
|
166
|
+
tunnelAutoStart: false
|
|
167
|
+
telegramBotToken: ""
|
|
168
|
+
telegramChatId: "" # Comma-separated for multiple users
|
|
169
|
+
telegramTunnelPassword: ""
|
|
170
|
+
notifyOnComplete: true
|
|
171
|
+
notifyOnFailure: true
|
|
172
|
+
```
|
|
200
173
|
|
|
201
|
-
|
|
202
|
-
tunnelAutoStart: false # Auto-start on server boot
|
|
174
|
+
</details>
|
|
203
175
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
telegramChatId: "" # Your chat ID
|
|
207
|
-
telegramTunnelPassword: "" # Password for tunnel commands
|
|
176
|
+
<details>
|
|
177
|
+
<summary><strong>.env.local</strong> (optional)</summary>
|
|
208
178
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
179
|
+
```env
|
|
180
|
+
# Fixed auth secret (auto-generated if not set)
|
|
181
|
+
AUTH_SECRET=<random-string>
|
|
182
|
+
|
|
183
|
+
# Optional: AI provider API keys for multi-model chat
|
|
184
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
185
|
+
# OPENAI_API_KEY=sk-...
|
|
212
186
|
```
|
|
213
187
|
|
|
188
|
+
</details>
|
|
189
|
+
|
|
214
190
|
## Architecture
|
|
215
191
|
|
|
216
192
|
```
|
|
217
|
-
|
|
218
|
-
│ Web Dashboard (Next.js + React)
|
|
219
|
-
│
|
|
220
|
-
│ │
|
|
221
|
-
│
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
│
|
|
227
|
-
│
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
├─────────────────────────────────────────────┤
|
|
233
|
-
│ Cloudflare Tunnel (optional) │
|
|
234
|
-
└─────────────────────────────────────────────┘
|
|
193
|
+
┌──────────────────────────────────────────────────┐
|
|
194
|
+
│ Web Dashboard (Next.js 16 + React 19) │
|
|
195
|
+
│ ┌─────────┐ ┌──────┐ ┌────────┐ ┌───────────┐ │
|
|
196
|
+
│ │ Vibe │ │ Docs │ │Projects│ │Demo │ │
|
|
197
|
+
│ │ Coding │ │ │ │ │ │Preview │ │
|
|
198
|
+
│ └─────────┘ └──────┘ └────────┘ └───────────┘ │
|
|
199
|
+
├──────────────────────────────────────────────────┤
|
|
200
|
+
│ API Layer (Next.js Route Handlers) │
|
|
201
|
+
├───────────┬───────────┬──────────────────────────┤
|
|
202
|
+
│ Claude │ Task │ Telegram Bot │
|
|
203
|
+
│ Code │ Runner │ + Notifications │
|
|
204
|
+
│ Process │ (Queue) │ │
|
|
205
|
+
├───────────┴───────────┴──────────────────────────┤
|
|
206
|
+
│ SQLite · Terminal Server · Cloudflare Tunnel │
|
|
207
|
+
└──────────────────────────────────────────────────┘
|
|
235
208
|
```
|
|
236
209
|
|
|
237
210
|
## Tech Stack
|
|
238
211
|
|
|
239
212
|
| Layer | Technology |
|
|
240
213
|
|-------|-----------|
|
|
241
|
-
| Frontend | Next.js 16, React 19, Tailwind CSS 4 |
|
|
242
|
-
| Backend | Next.js Route Handlers, SQLite |
|
|
243
|
-
| Terminal |
|
|
244
|
-
| Auth | NextAuth v5 |
|
|
245
|
-
| Tunnel | Cloudflare (
|
|
246
|
-
| Bot | Telegram Bot API
|
|
214
|
+
| Frontend | Next.js 16, React 19, Tailwind CSS 4, xterm.js |
|
|
215
|
+
| Backend | Next.js Route Handlers, SQLite (better-sqlite3) |
|
|
216
|
+
| Terminal | node-pty, tmux, WebSocket |
|
|
217
|
+
| Auth | NextAuth v5 (daily rotating password + OAuth) |
|
|
218
|
+
| Tunnel | Cloudflare cloudflared (zero-config) |
|
|
219
|
+
| Bot | Telegram Bot API |
|
|
247
220
|
|
|
248
221
|
## Troubleshooting
|
|
249
222
|
|
|
250
|
-
|
|
223
|
+
<details>
|
|
224
|
+
<summary><strong>macOS: "fork failed: Device not configured"</strong></summary>
|
|
251
225
|
|
|
252
|
-
|
|
226
|
+
PTY device limit exhausted. Increase it:
|
|
253
227
|
|
|
254
228
|
```bash
|
|
255
|
-
# Temporary (until reboot)
|
|
256
229
|
sudo sysctl kern.tty.ptmx_max=2048
|
|
257
230
|
|
|
258
231
|
# Permanent
|
|
259
232
|
echo 'kern.tty.ptmx_max=2048' | sudo tee -a /etc/sysctl.conf
|
|
260
233
|
```
|
|
261
234
|
|
|
235
|
+
</details>
|
|
236
|
+
|
|
237
|
+
<details>
|
|
238
|
+
<summary><strong>Session cookie invalid after restart</strong></summary>
|
|
239
|
+
|
|
240
|
+
Fix the AUTH_SECRET so it persists:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
echo "AUTH_SECRET=$(openssl rand -hex 32)" >> ~/.forge/.env.local
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
</details>
|
|
247
|
+
|
|
262
248
|
## Roadmap
|
|
263
249
|
|
|
264
|
-
- [ ] **Multi-Agent Workflow** — DAG-based pipelines where multiple Claude Code instances collaborate
|
|
250
|
+
- [ ] **Multi-Agent Workflow** — DAG-based pipelines where multiple Claude Code instances collaborate ([design doc](docs/roadmap-multi-agent-workflow.md))
|
|
265
251
|
- [ ] Pipeline UI — DAG visualization with real-time node status
|
|
266
|
-
- [ ] Additional bot platforms — Discord, Slack
|
|
267
|
-
- [ ]
|
|
252
|
+
- [ ] Additional bot platforms — Discord, Slack
|
|
253
|
+
- [ ] Excalidraw rendering in Docs viewer
|
|
254
|
+
- [ ] Multi-model chat (Anthropic, OpenAI, Google, xAI)
|
|
255
|
+
|
|
256
|
+
## Contributing
|
|
257
|
+
|
|
258
|
+
Contributions welcome! Please open an issue first to discuss what you'd like to change.
|
|
268
259
|
|
|
269
260
|
## License
|
|
270
261
|
|
|
271
|
-
MIT
|
|
262
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getPipeline, cancelPipeline, deletePipeline } from '@/lib/pipeline';
|
|
3
|
+
|
|
4
|
+
// GET /api/pipelines/:id
|
|
5
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params;
|
|
7
|
+
const pipeline = getPipeline(id);
|
|
8
|
+
if (!pipeline) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
9
|
+
return NextResponse.json(pipeline);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// POST /api/pipelines/:id — actions (cancel)
|
|
13
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
|
+
const { id } = await params;
|
|
15
|
+
const { action } = await req.json();
|
|
16
|
+
|
|
17
|
+
if (action === 'cancel') {
|
|
18
|
+
const ok = cancelPipeline(id);
|
|
19
|
+
return NextResponse.json({ ok });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (action === 'delete') {
|
|
23
|
+
const ok = deletePipeline(id);
|
|
24
|
+
return NextResponse.json({ ok });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { listPipelines, listWorkflows, startPipeline } from '@/lib/pipeline';
|
|
3
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import YAML from 'yaml';
|
|
7
|
+
|
|
8
|
+
const FLOWS_DIR = join(homedir(), '.forge', 'flows');
|
|
9
|
+
|
|
10
|
+
// GET /api/pipelines — list all pipelines + available workflows
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
const { searchParams } = new URL(req.url);
|
|
13
|
+
const type = searchParams.get('type');
|
|
14
|
+
|
|
15
|
+
if (type === 'workflows') {
|
|
16
|
+
return NextResponse.json(listWorkflows());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return NextResponse.json(listPipelines().sort((a, b) => b.createdAt.localeCompare(a.createdAt)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// POST /api/pipelines — start a pipeline or save a workflow
|
|
23
|
+
export async function POST(req: Request) {
|
|
24
|
+
const body = await req.json();
|
|
25
|
+
|
|
26
|
+
// Save workflow YAML from visual editor
|
|
27
|
+
if (body.action === 'save-workflow' && body.yaml) {
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(FLOWS_DIR, { recursive: true });
|
|
30
|
+
const parsed = YAML.parse(body.yaml);
|
|
31
|
+
const name = parsed.name || 'unnamed';
|
|
32
|
+
const filePath = join(FLOWS_DIR, `${name}.yaml`);
|
|
33
|
+
writeFileSync(filePath, body.yaml, 'utf-8');
|
|
34
|
+
return NextResponse.json({ ok: true, name, path: filePath });
|
|
35
|
+
} catch (e: any) {
|
|
36
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Start pipeline
|
|
41
|
+
const { workflow, input } = body;
|
|
42
|
+
if (!workflow) {
|
|
43
|
+
return NextResponse.json({ error: 'workflow name required' }, { status: 400 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const pipeline = startPipeline(workflow, input || {});
|
|
48
|
+
return NextResponse.json(pipeline);
|
|
49
|
+
} catch (e: any) {
|
|
50
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
}
|
package/components/Dashboard.tsx
CHANGED
|
@@ -16,6 +16,7 @@ const DocsViewer = lazy(() => import('./DocsViewer'));
|
|
|
16
16
|
const CodeViewer = lazy(() => import('./CodeViewer'));
|
|
17
17
|
const ProjectManager = lazy(() => import('./ProjectManager'));
|
|
18
18
|
const PreviewPanel = lazy(() => import('./PreviewPanel'));
|
|
19
|
+
const PipelineView = lazy(() => import('./PipelineView'));
|
|
19
20
|
|
|
20
21
|
interface UsageSummary {
|
|
21
22
|
provider: string;
|
|
@@ -38,7 +39,7 @@ interface ProjectInfo {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export default function Dashboard({ user }: { user: any }) {
|
|
41
|
-
const [viewMode, setViewMode] = useState<'tasks' | 'sessions' | 'terminal' | 'docs' | 'projects' | 'preview'>('terminal');
|
|
42
|
+
const [viewMode, setViewMode] = useState<'tasks' | 'sessions' | 'terminal' | 'docs' | 'projects' | 'preview' | 'pipelines'>('terminal');
|
|
42
43
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
43
44
|
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
44
45
|
const [showNewTask, setShowNewTask] = useState(false);
|
|
@@ -136,6 +137,16 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
136
137
|
>
|
|
137
138
|
Tasks
|
|
138
139
|
</button>
|
|
140
|
+
<button
|
|
141
|
+
onClick={() => setViewMode('pipelines')}
|
|
142
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
143
|
+
viewMode === 'pipelines'
|
|
144
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
145
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
146
|
+
}`}
|
|
147
|
+
>
|
|
148
|
+
Pipelines
|
|
149
|
+
</button>
|
|
139
150
|
<button
|
|
140
151
|
onClick={() => setViewMode('sessions')}
|
|
141
152
|
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
@@ -310,6 +321,13 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
310
321
|
</Suspense>
|
|
311
322
|
)}
|
|
312
323
|
|
|
324
|
+
{/* Pipelines */}
|
|
325
|
+
{viewMode === 'pipelines' && (
|
|
326
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
327
|
+
<PipelineView />
|
|
328
|
+
</Suspense>
|
|
329
|
+
)}
|
|
330
|
+
|
|
313
331
|
{/* Preview */}
|
|
314
332
|
{viewMode === 'preview' && (
|
|
315
333
|
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
@@ -97,6 +97,15 @@ export default function DocsViewer() {
|
|
|
97
97
|
|
|
98
98
|
useEffect(() => { fetchTree(activeRoot); }, [activeRoot, fetchTree]);
|
|
99
99
|
|
|
100
|
+
// Re-fetch when tab becomes visible (settings may have changed)
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const handleVisibility = () => {
|
|
103
|
+
if (!document.hidden) fetchTree(activeRoot);
|
|
104
|
+
};
|
|
105
|
+
document.addEventListener('visibilitychange', handleVisibility);
|
|
106
|
+
return () => document.removeEventListener('visibilitychange', handleVisibility);
|
|
107
|
+
}, [activeRoot, fetchTree]);
|
|
108
|
+
|
|
100
109
|
const [fileWarning, setFileWarning] = useState<string | null>(null);
|
|
101
110
|
|
|
102
111
|
// Fetch file content
|
|
@@ -167,7 +176,7 @@ export default function DocsViewer() {
|
|
|
167
176
|
{sidebarOpen && (
|
|
168
177
|
<aside className="w-56 border-r border-[var(--border)] flex flex-col shrink-0">
|
|
169
178
|
{/* Root selector */}
|
|
170
|
-
{roots.length >
|
|
179
|
+
{roots.length > 0 && (
|
|
171
180
|
<div className="p-2 border-b border-[var(--border)]">
|
|
172
181
|
<select
|
|
173
182
|
value={activeRoot}
|