@aion0/forge 0.2.2 → 0.2.4

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/CLAUDE.md CHANGED
@@ -19,11 +19,15 @@ npm install -g /Users/zliu/IdeaProjects/my-workflow
19
19
  npm install -g @aion0/forge
20
20
 
21
21
  # Run via npm global install
22
- forge-server # foreground (auto-builds if needed)
23
- forge-server --dev # dev mode
24
- forge-server --background # background, logs to ~/.forge/forge.log
25
- forge-server --stop # stop background server
26
- forge-server --rebuild # force rebuild
22
+ forge-server # foreground (default port 3000)
23
+ forge-server --dev # dev mode
24
+ forge-server --background # background, logs to ~/.forge/forge.log
25
+ forge-server --stop # stop background server
26
+ forge-server --restart # stop + start (safe for remote)
27
+ forge-server --rebuild # force rebuild
28
+ forge-server --port 4000 # custom web port
29
+ forge-server --terminal-port 4001 # custom terminal port
30
+ forge-server --dir ~/.forge-staging # custom data directory
27
31
 
28
32
  # CLI
29
33
  forge # help
package/README.md CHANGED
@@ -1,42 +1,51 @@
1
- # Forge
1
+ <p align="center">
2
+ <img src="app/icon.svg" width="80" height="80" alt="Forge">
3
+ </p>
2
4
 
3
- > Self-hosted AI workflow platform — web terminal, task orchestration, remote access.
5
+ <h1 align="center">Forge</h1>
4
6
 
5
- Forge is a self-hosted web platform built around [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It provides a browser-based terminal backed by tmux, a task queue for running Claude Code in the background, and one-click remote access via Cloudflare Tunnel — all behind a simple daily-rotating password.
7
+ <p align="center">
8
+ <strong>Self-hosted Vibe Coding platform — browser terminal, task orchestration, remote access</strong>
9
+ </p>
6
10
 
7
- No API keys required. Forge runs on your existing Claude Code subscription.
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
- ## Features
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
- - **Web Terminal** — Full tmux-backed terminal in the browser. Multiple tabs, persistent sessions that survive page refresh, browser close, and server restart
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
- ## Prerequisites
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
- - **Node.js** >= 20
24
- - **pnpm** (recommended) or npm
25
- - **tmux** — for web terminal sessions
26
- - **Claude Code CLI** — `npm install -g @anthropic-ai/claude-code`
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
- ### From npm
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
- ## Quick Start
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 password
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
- ### 4. Configure projects
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
- ```bash
99
- # Submit a task
100
- forge task my-app "Fix the login bug in auth.ts"
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
- # Force a fresh session (ignore previous context)
103
- forge task my-app "Refactor the API layer" --new
76
+ ## Quick Start
104
77
 
105
- # List tasks
106
- forge tasks # all
107
- forge tasks running # filter by status
78
+ 1. **Start Forge**
108
79
 
109
- # Watch task output live
110
- forge watch <task-id>
80
+ ```bash
81
+ forge-server
82
+ ```
111
83
 
112
- # Task details (result, git diff, cost)
113
- forge status <task-id>
84
+ 2. **Open browser** `http://localhost:3000`
114
85
 
115
- # Cancel / retry
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
- **All CLI shortcuts:** `t`=task, `r`=run, `ls`=tasks, `w`=watch, `l`=log, `s`=status, `f`=flows, `p`=projects, `pw`=password
88
+ ```
89
+ [init] Login password: a7x9k2 (valid today)
90
+ ```
121
91
 
122
- ## YAML Workflows
92
+ Forgot it? Run `forge password`
123
93
 
124
- Define multi-step flows in `~/.forge/flows/`:
94
+ 4. **Configure projects** Settings → add your project directories
125
95
 
126
- ```yaml
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
- Run with `forge run daily-review`.
98
+ ## Remote Access
137
99
 
138
- ## Bot Integration
100
+ Access Forge from anywhere — your phone, iPad, or another computer:
139
101
 
140
- Forge ships with a Telegram bot for mobile-friendly control. The bot system is designed to be extensible to other platforms in the future.
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
- ### Telegram Setup
106
+ > The tunnel URL changes each time. Use the Telegram `/tunnel_password` command to get it on your phone.
143
107
 
144
- 1. Create a bot via [@BotFather](https://t.me/botfather)
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
- ### Commands
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
- | `/tasks running` | Filter by status |
154
- | `/sessions` | Browse Claude Code sessions |
155
- | `/watch <project>` | Monitor a session for changes |
156
- | `/tunnel start <pw>` | Start Cloudflare Tunnel |
157
- | `/tunnel stop <pw>` | Stop tunnel |
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
- Password-protected commands auto-delete your message to keep credentials safe.
123
+ Whitelist-protected only configured Chat IDs can interact with the bot.
162
124
 
163
- ## Configuration
164
-
165
- All config lives in `~/.forge/`:
125
+ ## CLI
166
126
 
167
- ```
168
- ~/.forge/
169
- .env.local # Environment variables (AUTH_SECRET, API keys, etc.)
170
- settings.yaml # Main configuration
171
- password.json # Daily auto-generated login password
172
- data.db # SQLite database (tasks, sessions)
173
- terminal-state.json # Terminal tab layout
174
- flows/ # YAML workflow definitions
175
- bin/ # Auto-downloaded binaries (cloudflared)
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
- ### .env.local (optional)
139
+ Shortcuts: `t`=task, `ls`=tasks, `w`=watch, `s`=status, `f`=flows, `p`=projects, `pw`=password
179
140
 
180
- ```env
181
- # Fixed auth secret (optional — auto-generated if not set)
182
- AUTH_SECRET=<random-string>
141
+ ## Configuration
183
142
 
184
- # Optional: AI provider API keys for multi-model chat
185
- # ANTHROPIC_API_KEY=sk-ant-...
186
- # OPENAI_API_KEY=sk-...
187
- # GOOGLE_GENERATIVE_AI_API_KEY=AI...
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
- ### settings.yaml
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
- - ~/Work
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
- # Cloudflare Tunnel
202
- tunnelAutoStart: false # Auto-start on server boot
174
+ </details>
203
175
 
204
- # Telegram bot (optional)
205
- telegramBotToken: "" # Bot API token from @BotFather
206
- telegramChatId: "" # Your chat ID
207
- telegramTunnelPassword: "" # Password for tunnel commands
176
+ <details>
177
+ <summary><strong>.env.local</strong> (optional)</summary>
208
178
 
209
- # Task notifications (optional, requires Telegram)
210
- notifyOnComplete: true
211
- notifyOnFailure: true
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
- │ │ Tasks │ │ Sessions │ │ Terminal │ │
221
- └──────────┘ └──────────┘ └─────────────┘
222
- ├─────────────────────────────────────────────┤
223
- │ API Layer (Next.js Route Handlers) │
224
- ├──────────┬──────────┬───────────────────────┤
225
- │ Claude │ Task │ Bot Integration │
226
- Code Runner (Telegram, ...)
227
- Process (Queue)
228
- ├──────────┴──────────┴───────────────────────┤
229
- │ SQLite (better-sqlite3) │
230
- ├─────────────────────────────────────────────┤
231
- │ Terminal Server (node-pty + tmux + WS) │
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 | xterm.js, node-pty, tmux, WebSocket |
244
- | Auth | NextAuth v5 |
245
- | Tunnel | Cloudflare (cloudflared) |
246
- | Bot | Telegram Bot API (extensible) |
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
- ### macOS: "fork failed: Device not configured"
223
+ <details>
224
+ <summary><strong>macOS: "fork failed: Device not configured"</strong></summary>
251
225
 
252
- This means the system ran out of pseudo-terminal (PTY) devices. macOS defaults to 511, which can be tight when running IDEs and many terminal sessions. Increase the limit:
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, passing outputs between nodes with conditional routing and parallel execution. See [docs/roadmap-multi-agent-workflow.md](docs/roadmap-multi-agent-workflow.md).
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, etc.
267
- - [ ] Multi-model chat with API keys (Anthropic, OpenAI, Google, xAI)
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
+ }