@aion0/forge 0.5.49 → 0.5.50
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/RELEASE_NOTES.md +48 -7
- package/app/api/craft-system/build/route.ts +78 -0
- package/app/api/craft-system/delete/route.ts +28 -0
- package/app/api/craft-system/helpers/file/route.ts +20 -0
- package/app/api/craft-system/helpers/openapi/route.ts +27 -0
- package/app/api/craft-system/helpers/shell/route.ts +26 -0
- package/app/api/craft-system/inject/route.ts +41 -0
- package/app/api/craft-system/kill-session/route.ts +19 -0
- package/app/api/craft-system/manifest/route.ts +71 -0
- package/app/api/craft-system/marketplace/install/route.ts +11 -0
- package/app/api/craft-system/marketplace/route.ts +18 -0
- package/app/api/craft-system/marketplace/uninstall/route.ts +11 -0
- package/app/api/craft-system/marketplace/update/route.ts +10 -0
- package/app/api/craft-system/marketplace/updates/route.ts +17 -0
- package/app/api/craft-system/publish/auto/route.ts +173 -0
- package/app/api/craft-system/publish/route.ts +50 -0
- package/app/api/craft-system/registry/route.ts +16 -0
- package/app/api/craft-system/runtime/react/route.ts +26 -0
- package/app/api/craft-system/runtime/react-jsx/route.ts +11 -0
- package/app/api/craft-system/runtime/sdk/route.ts +18 -0
- package/app/api/craft-system/scaffold/route.ts +164 -0
- package/app/api/craft-system/sessions/route.ts +45 -0
- package/app/api/craft-system/storage/route.ts +44 -0
- package/app/api/craft-system/tmux-sessions/route.ts +62 -0
- package/app/api/craft-system/ui/route.ts +30 -0
- package/app/api/crafts/[name]/[...route]/route.ts +48 -0
- package/app/api/crafts/route.ts +29 -0
- package/components/CraftBuilder.tsx +241 -0
- package/components/CraftManifestEditor.tsx +258 -0
- package/components/CraftMarketplaceModal.tsx +207 -0
- package/components/CraftPublishModal.tsx +285 -0
- package/components/CraftTabs.tsx +279 -0
- package/components/CraftTerminal.tsx +305 -0
- package/components/CraftTerminalPicker.tsx +179 -0
- package/components/CraftsDropdown.tsx +186 -0
- package/components/CraftsMarketplacePanel.tsx +194 -0
- package/components/ProjectDetail.tsx +105 -1
- package/components/SkillsPanel.tsx +12 -4
- package/components/TaskDetail.tsx +49 -1
- package/lib/craft-sdk/client.tsx +260 -0
- package/lib/craft-sdk/server.ts +14 -0
- package/lib/crafts/loader.ts +117 -0
- package/lib/crafts/registry.ts +272 -0
- package/lib/crafts/runtime.ts +208 -0
- package/lib/crafts/types.ts +92 -0
- package/lib/forge-skills/craft-builder.md +231 -0
- package/lib/help-docs/15-crafts.md +127 -0
- package/lib/help-docs/CLAUDE.md +2 -0
- package/lib/terminal-standalone.ts +1 -0
- package/next.config.ts +1 -1
- package/package.json +2 -1
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: craft-builder
|
|
3
|
+
description: Build a Forge "Craft" — a project-scoped mini-app exposed as a tab in Forge. Use when the user asks Forge to "make a tab/dashboard/tool that does X" inside their project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Forge Craft Builder
|
|
7
|
+
|
|
8
|
+
A **Craft** is a project-scoped mini-app that appears as a tab in Forge. It can have:
|
|
9
|
+
|
|
10
|
+
- A React UI (`ui.tsx`) — renders inside Forge's project view
|
|
11
|
+
- An optional API server (`server.ts`) — handlers run on Forge's Node process
|
|
12
|
+
|
|
13
|
+
Crafts live at `<project>/.forge/crafts/<craft-name>/` and travel with the project (commit them to git so the team sees the same tabs).
|
|
14
|
+
|
|
15
|
+
## Your job
|
|
16
|
+
|
|
17
|
+
When invoked, you produce ALL of these files in `<project>/.forge/crafts/<name>/`:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
craft.yaml # manifest
|
|
21
|
+
ui.tsx # React component (default export)
|
|
22
|
+
server.ts # optional — only if user needs server-side work
|
|
23
|
+
prompt.md # the original user request + iteration history (you maintain this)
|
|
24
|
+
README.md # 1-paragraph "what it does"
|
|
25
|
+
data/ # auto-created when craft writes via useStore
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
After writing files, tell the user the new tab will appear in Forge after refresh (or hot-reload if dev mode).
|
|
29
|
+
|
|
30
|
+
## Naming
|
|
31
|
+
|
|
32
|
+
Pick a kebab-case `name` based on what the user asked for. Keep it short. Example: "API endpoint dashboard" → `api-dashboard`.
|
|
33
|
+
|
|
34
|
+
The `displayName` is the tab label — include an emoji prefix matching the function (📊 for dashboards, 🔍 for explorers, ⚡ for runners, 📝 for editors, 🧪 for testers).
|
|
35
|
+
|
|
36
|
+
## SDK — UI side (`ui.tsx`)
|
|
37
|
+
|
|
38
|
+
Import from `@forge/craft`. ONLY these hooks are available:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { useProject, useForgeFetch, useInject, useTask, useStore } from '@forge/craft';
|
|
42
|
+
|
|
43
|
+
// 1. Project context
|
|
44
|
+
const { projectPath, projectName } = useProject();
|
|
45
|
+
|
|
46
|
+
// 2. Fetch data — auto-appends ?projectPath=...; returns { data, loading, error, refetch }
|
|
47
|
+
const { data, loading, error, refetch } = useForgeFetch<MyType>('/api/crafts/<your-name>/items');
|
|
48
|
+
// or any Forge core API:
|
|
49
|
+
const git = useForgeFetch('/api/git/status');
|
|
50
|
+
|
|
51
|
+
// 3. Inject text into the project's bound tmux terminal (auto-resolves session)
|
|
52
|
+
const inject = useInject();
|
|
53
|
+
await inject('Run the test suite'); // sends text + Enter
|
|
54
|
+
|
|
55
|
+
// 4. Spawn a Forge background task in the project
|
|
56
|
+
const runTask = useTask();
|
|
57
|
+
const t = await runTask('Refactor the auth module per CLAUDE.md');
|
|
58
|
+
const stop = t.watch(entry => console.log(entry), final => console.log('done', final));
|
|
59
|
+
|
|
60
|
+
// 5. Persistent JSON storage in <project>/.forge/crafts/<name>/data/<file>.json
|
|
61
|
+
const [items, setItems, { loading, reload }] = useStore<Item[]>('items.json', []);
|
|
62
|
+
await setItems([...items!, newItem]); // writes to disk
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Component must `export default` a React component. Use Tailwind classes and Forge CSS variables (`var(--accent)`, `var(--bg-secondary)`, `var(--text-primary)`, `var(--text-secondary)`, `var(--border)`, `var(--bg-primary)`, `var(--bg-tertiary)`) so the tab matches Forge's theme.
|
|
66
|
+
|
|
67
|
+
The component is rendered inside `<div className="flex-1 flex flex-col min-h-0 overflow-hidden">` — the outermost element should be a fragment or `<div className="flex-1 ...">`.
|
|
68
|
+
|
|
69
|
+
**Do not** import React directly (it's auto-injected). Do not import any other npm package — only `@forge/craft`.
|
|
70
|
+
|
|
71
|
+
## SDK — Server side (`server.ts`, optional)
|
|
72
|
+
|
|
73
|
+
Skip this file entirely if the craft only needs to call existing Forge APIs.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { defineCraftServer } from '@forge/craft/server';
|
|
77
|
+
|
|
78
|
+
export default defineCraftServer({
|
|
79
|
+
routes: {
|
|
80
|
+
'GET /items': async ({ projectPath, query, forge }) => {
|
|
81
|
+
// Run shell in project cwd
|
|
82
|
+
const r = forge.exec('git log --oneline -20', { timeout: 10000 });
|
|
83
|
+
return { lines: r.stdout.split('\n').filter(Boolean) };
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
'POST /create': async ({ body, forge }) => {
|
|
87
|
+
forge.storage.write('records.json', body);
|
|
88
|
+
return { ok: true };
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
'GET /load-spec': async ({ forge }) => {
|
|
92
|
+
const spec = forge.openapi('docs/openapi.json');
|
|
93
|
+
return { paths: Object.keys(spec?.paths || {}) };
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
'POST /fix': async ({ body, forge }) => {
|
|
97
|
+
const t = forge.task({ prompt: body.prompt });
|
|
98
|
+
return { taskId: t.id };
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
'POST /run-cmd': async ({ body, forge }) => {
|
|
102
|
+
forge.inject(body.cmd); // paste into bound terminal
|
|
103
|
+
return { ok: true };
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`forge` injected helper API:
|
|
110
|
+
- `forge.project` — `{ path, name }`
|
|
111
|
+
- `forge.storage` — `read(file)`, `write(file, data)`, `listFiles()` (scoped to the craft's data dir)
|
|
112
|
+
- `forge.exec(cmd, opts?)` — synchronous shell exec in project cwd, returns `{ stdout, stderr, code }`
|
|
113
|
+
- `forge.task({ prompt, agent? })` — spawn Forge background task, returns `{ id }`
|
|
114
|
+
- `forge.inject(text, opts?)` — paste into bound tmux session
|
|
115
|
+
- `forge.openapi(specPath)` — load + parse OpenAPI JSON from project
|
|
116
|
+
- `forge.log(...)` — structured logging
|
|
117
|
+
|
|
118
|
+
Routes are auto-mounted at `/api/crafts/<craft-name>/<route>`. The UI calls them via `useForgeFetch`.
|
|
119
|
+
|
|
120
|
+
## Manifest (`craft.yaml`)
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
name: api-dashboard # kebab-case, dir name
|
|
124
|
+
displayName: 📊 API Dashboard # tab label (with emoji)
|
|
125
|
+
description: One-line summary of what it does
|
|
126
|
+
version: 0.1.0
|
|
127
|
+
icon: "📊" # optional, mainly cosmetic
|
|
128
|
+
author: aion0 # optional, shown in marketplace
|
|
129
|
+
tags: # optional — for marketplace browsing
|
|
130
|
+
- openapi
|
|
131
|
+
- dashboard
|
|
132
|
+
- migration
|
|
133
|
+
requires: # optional — project-type compatibility gate.
|
|
134
|
+
hasFile: # craft is hidden + can't install if NONE match.
|
|
135
|
+
- docs/openapi.json
|
|
136
|
+
- openapi.yaml
|
|
137
|
+
hasGlob:
|
|
138
|
+
- "**/*.java" # any of the matchers passing → compatible
|
|
139
|
+
ui:
|
|
140
|
+
tab: ui.tsx
|
|
141
|
+
showWhen: hasFile("docs/openapi.json") # optional extra UI condition
|
|
142
|
+
server:
|
|
143
|
+
entry: server.ts # omit this whole block if no server.ts
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Tags + requires guidance:**
|
|
147
|
+
|
|
148
|
+
- `tags` are free-form keywords used by the marketplace search. Common ones:
|
|
149
|
+
language (`java`, `typescript`, `python`), framework (`spring`, `react`),
|
|
150
|
+
use-case (`migration`, `testing`, `linting`, `debugging`).
|
|
151
|
+
- `requires.hasFile` lists files that MUST exist somewhere in the project for
|
|
152
|
+
the craft to make sense (any one match = compatible).
|
|
153
|
+
- `requires.hasGlob` is for broader matches like `**/*.java` (project has Java)
|
|
154
|
+
or `package.json` (Node project).
|
|
155
|
+
- Be specific — a craft tagged `java` + `requires.hasGlob: ["**/*.java"]` won't
|
|
156
|
+
show up in a TypeScript project's marketplace.
|
|
157
|
+
|
|
158
|
+
## prompt.md
|
|
159
|
+
|
|
160
|
+
Always write/update this file with:
|
|
161
|
+
- The original user request (verbatim)
|
|
162
|
+
- Each refine request and what you changed
|
|
163
|
+
- Used by future Refine runs as context
|
|
164
|
+
|
|
165
|
+
## Iteration
|
|
166
|
+
|
|
167
|
+
When called to refine an existing craft (the dir already exists), READ existing files first, KEEP what works, change only what the user asked. Append the refine request to `prompt.md`.
|
|
168
|
+
|
|
169
|
+
## Minimum viable example
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
# craft.yaml
|
|
173
|
+
name: hello
|
|
174
|
+
displayName: 👋 Hello
|
|
175
|
+
description: Demo craft — counts project files by extension
|
|
176
|
+
version: 0.1.0
|
|
177
|
+
ui:
|
|
178
|
+
tab: ui.tsx
|
|
179
|
+
server:
|
|
180
|
+
entry: server.ts
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// server.ts
|
|
185
|
+
import { defineCraftServer } from '@forge/craft/server';
|
|
186
|
+
|
|
187
|
+
export default defineCraftServer({
|
|
188
|
+
routes: {
|
|
189
|
+
'GET /count': async ({ forge }) => {
|
|
190
|
+
const r = forge.exec(`git ls-files | awk -F. 'NF>1{print $NF}' | sort | uniq -c | sort -rn | head -20`);
|
|
191
|
+
return { lines: r.stdout.split('\n').filter(Boolean) };
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// ui.tsx
|
|
199
|
+
import { useProject, useForgeFetch } from '@forge/craft';
|
|
200
|
+
|
|
201
|
+
export default function Tab() {
|
|
202
|
+
const { projectName } = useProject();
|
|
203
|
+
const { data, loading } = useForgeFetch<{ lines: string[] }>('/api/crafts/hello/count');
|
|
204
|
+
return (
|
|
205
|
+
<div className="flex-1 p-4 text-xs overflow-auto">
|
|
206
|
+
<h2 className="font-semibold mb-2">{projectName} — file counts</h2>
|
|
207
|
+
{loading && <div className="text-[var(--text-secondary)]">Loading…</div>}
|
|
208
|
+
{data?.lines.map((l, i) => <div key={i} className="font-mono">{l}</div>)}
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Style guide
|
|
215
|
+
|
|
216
|
+
- Tailwind classes only. Use Forge's color variables, not hardcoded colors.
|
|
217
|
+
- Text sizes: `text-xs` (default), `text-[11px]` for dense tables, `text-[10px]` for metadata.
|
|
218
|
+
- Buttons: `text-[10px] px-2 py-1 rounded bg-[var(--accent)]/20 text-[var(--accent)] hover:bg-[var(--accent)]/30`.
|
|
219
|
+
- Sections inside the tab: `flex-1 flex flex-col min-h-0 overflow-auto p-4 gap-3`.
|
|
220
|
+
- For tables/lists, prefer simple `<table>` or `<div>` grids — no extra deps.
|
|
221
|
+
- Match Forge's compact density (rows ~24-28px tall).
|
|
222
|
+
|
|
223
|
+
## Final report
|
|
224
|
+
|
|
225
|
+
After writing files, report:
|
|
226
|
+
1. What craft you created (name + displayName)
|
|
227
|
+
2. The route(s) registered (if any server)
|
|
228
|
+
3. The data files used (if any)
|
|
229
|
+
4. Any assumptions you made
|
|
230
|
+
|
|
231
|
+
End with `[FORGE_DONE]`.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Crafts — project-scoped mini-apps
|
|
2
|
+
|
|
3
|
+
A **Craft** is a tab inside Forge that lives at `<project>/.forge/crafts/<name>/`. It can be hand-written, AI-generated, or shipped as a Forge builtin (open-source samples). Crafts travel with the project — commit them to git so the team sees the same tabs.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
In any project, click **+ Craft** next to the project tabs. Type what you want (e.g. "show all our REST endpoints with migration status, allow batch run + AI fix"). Forge spawns a background task that uses the `craft-builder` skill to generate the files. After ~30-60s the new tab appears.
|
|
8
|
+
|
|
9
|
+
For an existing craft, switch to its tab and click the small **⚙** badge to refine it ("add a sort button" / "this column should be wider").
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
<project>/.forge/crafts/<name>/
|
|
15
|
+
├── craft.yaml # manifest (name, displayName, icon, conditions)
|
|
16
|
+
├── ui.tsx # React component (default export)
|
|
17
|
+
├── server.ts # optional API routes
|
|
18
|
+
├── prompt.md # original user request + iteration history
|
|
19
|
+
├── README.md # what this craft does
|
|
20
|
+
└── data/ # craft's persistent JSON storage
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## SDK
|
|
24
|
+
|
|
25
|
+
Imports from `@forge/craft` (UI side):
|
|
26
|
+
|
|
27
|
+
| Hook | What it does |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `useProject()` | `{ projectPath, projectName }` |
|
|
30
|
+
| `useForgeFetch(path)` | Fetch wrapper, auto-injects `?projectPath=...`, returns `{ data, loading, error, refetch }` |
|
|
31
|
+
| `useInject()` | `(text) => Promise` — paste prompt + Enter into the project's bound tmux session |
|
|
32
|
+
| `useTask()` | `(prompt) => TaskHandle` — spawn Forge background task, watch its log stream |
|
|
33
|
+
| `useStore(file, default)` | `[value, save, { loading, reload }]` — JSON storage in `data/<file>.json` |
|
|
34
|
+
| `useOpenAPI(path)` | Load + parse OpenAPI 3 spec from project |
|
|
35
|
+
| `useFile(path, { watch? })` | Read project file with optional polling |
|
|
36
|
+
| `useShell()` | `(cmd) => Promise<{ stdout, stderr, code }>` — exec in project cwd |
|
|
37
|
+
| `useGit()` | Git status / log info |
|
|
38
|
+
| `useToast()` | `(msg, kind)` — quick top notification |
|
|
39
|
+
|
|
40
|
+
Server side (`server.ts`):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { defineCraftServer } from '@forge/craft/server';
|
|
44
|
+
|
|
45
|
+
export default defineCraftServer({
|
|
46
|
+
routes: {
|
|
47
|
+
'GET /items': async ({ forge, query, params }) => {
|
|
48
|
+
const r = forge.exec('git log --oneline -20');
|
|
49
|
+
return { lines: r.stdout.split('\n') };
|
|
50
|
+
},
|
|
51
|
+
'POST /run': async ({ body, forge }) => {
|
|
52
|
+
const t = forge.task({ prompt: body.prompt });
|
|
53
|
+
return { taskId: t.id };
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`forge` injected helpers:
|
|
60
|
+
- `forge.project` — `{ path, name }`
|
|
61
|
+
- `forge.storage` — `read(file)`, `write(file, data)`, `listFiles()` (scoped to craft data dir)
|
|
62
|
+
- `forge.exec(cmd, opts?)` — sync shell exec in project cwd
|
|
63
|
+
- `forge.task({ prompt })` — spawn background task
|
|
64
|
+
- `forge.inject(text)` — paste into bound tmux session
|
|
65
|
+
- `forge.openapi(specPath)` — load + parse OpenAPI JSON
|
|
66
|
+
- `forge.log(...)` — structured logging
|
|
67
|
+
|
|
68
|
+
Routes are mounted at `/api/crafts/<craft-name>/<route>`. The UI calls them via `useForgeFetch`.
|
|
69
|
+
|
|
70
|
+
## Manifest
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
name: api-dashboard # kebab-case, dir name
|
|
74
|
+
displayName: 📊 API Dashboard # tab label
|
|
75
|
+
description: One-line summary
|
|
76
|
+
version: 0.1.0
|
|
77
|
+
icon: "📊"
|
|
78
|
+
ui:
|
|
79
|
+
tab: ui.tsx # default
|
|
80
|
+
showWhen: hasFile("docs/openapi.json") # optional condition
|
|
81
|
+
server:
|
|
82
|
+
entry: server.ts # default; omit if no server
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`showWhen` supports `hasFile("path")` (only show tab when file exists) or `always`.
|
|
86
|
+
|
|
87
|
+
## Builtins
|
|
88
|
+
|
|
89
|
+
`lib/builtin-crafts/<name>/` is the slot for crafts that ship with Forge by default. Currently empty — every craft is project-local at `<project>/.forge/crafts/<name>/`. Builtins (when present) appear automatically in every project; project-local crafts override builtins by name.
|
|
90
|
+
|
|
91
|
+
## Marketplace
|
|
92
|
+
|
|
93
|
+
Crafts can be published to a shared registry (default: `aiwatching/forge-crafts` on GitHub). The marketplace browser is reachable from the **Crafts ▾** dropdown in any project tab — pick **🛒 Marketplace** to see installable crafts filtered by your project's compatibility.
|
|
94
|
+
|
|
95
|
+
### Browse + install
|
|
96
|
+
- **Compatible / All / Installed** filter
|
|
97
|
+
- Shows version, author, tags, and a per-item Install / Update / Uninstall button
|
|
98
|
+
- Install copies the registry's files into `<project>/.forge/crafts/<name>/`; the new tab appears immediately
|
|
99
|
+
|
|
100
|
+
### Project-type filtering (`requires`)
|
|
101
|
+
|
|
102
|
+
Add a `requires` block to `craft.yaml` so the marketplace only suggests the craft to compatible projects:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
requires:
|
|
106
|
+
hasFile: # any of these files must exist
|
|
107
|
+
- docs/openapi.json
|
|
108
|
+
hasGlob:
|
|
109
|
+
- "**/*.java" # any of these globs must match
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Either matcher passing is enough (OR logic). With an empty/missing `requires`, the craft is compatible with every project.
|
|
113
|
+
|
|
114
|
+
### Publish
|
|
115
|
+
|
|
116
|
+
When a project-local craft is the active tab, the **📦** button next to ⚙ opens the publish modal. It shows:
|
|
117
|
+
1. **How to publish** — step-by-step (open a PR on the registry repo).
|
|
118
|
+
2. **registry.json entry** — JSON snippet to append under `crafts: [...]`.
|
|
119
|
+
3. **Files** — copy each file's contents (`craft.yaml`, `ui.tsx`, `server.ts`, `README.md`) to drop into the registry repo's `<name>/` folder.
|
|
120
|
+
|
|
121
|
+
Forge does NOT auto-push to GitHub. Submit the PR; once merged, all Forge users see it in their marketplace.
|
|
122
|
+
|
|
123
|
+
The repo URL is configurable via `craftsRepoUrl` in `~/.forge/data/settings.yaml` so teams can run their own private registry.
|
|
124
|
+
|
|
125
|
+
## Architectural model
|
|
126
|
+
|
|
127
|
+
Forge is the **orchestrator**: discovers crafts, mounts UI tabs + API routes, provides the SDK. The craft is **your project's content** — stored in `<project>/.forge/`, not in Forge core. Generic features (Migration Cockpit will eventually move here) end up as crafts that live in your repo, not in Forge.
|
package/lib/help-docs/CLAUDE.md
CHANGED
|
@@ -44,6 +44,7 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
|
|
|
44
44
|
| `11-workspace.md` | Workspace (Forge Smiths) — multi-agent orchestration, daemon, message bus, profiles |
|
|
45
45
|
| `12-usage.md` | Token usage analytics — charts, heatmap, cost estimation, by model/project/source |
|
|
46
46
|
| `13-ide-plugins.md` | VSCode extension + IntelliJ plugin — install, tabs, multi-connection, agent terminal launching |
|
|
47
|
+
| `15-crafts.md` | Crafts — project-scoped mini-app tabs with SDK; AI-generated via "+ Craft" button |
|
|
47
48
|
|
|
48
49
|
## Matching questions to docs
|
|
49
50
|
|
|
@@ -67,3 +68,4 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
|
|
|
67
68
|
- Sidebar collapse/project tabs/favorites → `07-projects.md`
|
|
68
69
|
- VSCode/IntelliJ/IDE plugin/extension/marketplace → `13-ide-plugins.md`
|
|
69
70
|
- vsce/vsix/JetBrains marketplace publish → `13-ide-plugins.md`
|
|
71
|
+
- Craft/custom tab/mini-app/extend project/AI-generated tab/builder → `15-crafts.md`
|
|
@@ -209,6 +209,7 @@ function cleanupOrphanedSessions() {
|
|
|
209
209
|
for (const s of sessions) {
|
|
210
210
|
if (s.attached) continue;
|
|
211
211
|
if (s.name.startsWith(`${SESSION_PREFIX}forge-`)) continue; // workspace agent session — managed by orchestrator
|
|
212
|
+
if (s.name.startsWith('mw-craft-')) continue; // craft session — managed by craft loader
|
|
212
213
|
if (knownSessions.has(s.name)) continue; // saved in terminal state — preserve
|
|
213
214
|
const clients = sessionClients.get(s.name)?.size ?? 0;
|
|
214
215
|
if (clients === 0) {
|
package/next.config.ts
CHANGED
|
@@ -10,7 +10,7 @@ const localIPs = Object.values(networkInterfaces())
|
|
|
10
10
|
.map(i => i!.address);
|
|
11
11
|
|
|
12
12
|
const nextConfig: NextConfig = {
|
|
13
|
-
serverExternalPackages: ['better-sqlite3'],
|
|
13
|
+
serverExternalPackages: ['better-sqlite3', 'esbuild'],
|
|
14
14
|
allowedDevOrigins: localIPs,
|
|
15
15
|
async rewrites() {
|
|
16
16
|
return [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aion0/forge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.50",
|
|
4
4
|
"description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@xyflow/react": "^12.10.1",
|
|
42
42
|
"ai": "^6.0.116",
|
|
43
43
|
"better-sqlite3": "^12.6.2",
|
|
44
|
+
"esbuild": "^0.27.3",
|
|
44
45
|
"next": "^16.2.1",
|
|
45
46
|
"next-auth": "5.0.0-beta.30",
|
|
46
47
|
"node-pty": "1.0.0",
|