@aion0/forge 0.4.2 → 0.4.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="app/icon.svg" width="80" height="80" alt="Forge">
2
+ <img src="app/icon.png" width="80" height="80" alt="Forge">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">Forge</h1>
package/RELEASE_NOTES.md CHANGED
@@ -1,15 +1,28 @@
1
- # Forge v0.4.2
1
+ # Forge v0.4.4
2
2
 
3
3
  Released: 2026-03-21
4
4
 
5
- ## Changes since v0.4.1
5
+ ## Changes since v0.4.3
6
+
7
+ ### Features
8
+ - feat: add scheduled pipeline execution for project bindings
9
+ - feat: pipeline import/delete + editor cancel confirmation
6
10
 
7
11
  ### Bug Fixes
8
- - fix: prevent double console.log wrapping in production mode
9
- - fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
12
+ - fix: embed editor in right panel, button nesting, fetchData error handling
13
+
14
+ ### Refactoring
15
+ - refactor: generic project-pipeline bindings replace hardcoded issue scanner
16
+ - refactor: pipeline UI — workflow list in sidebar, project dropdown, per-workflow history
17
+ - refactor: merge issue-auto-fix + pr-review into single issue-fix-and-review pipeline
10
18
 
11
19
  ### Documentation
12
- - fix: auto-recover from PTY exhaustion + add npm ENOTEMPTY troubleshooting
20
+ - feat: pipeline import/delete + editor cancel confirmation
21
+
22
+ ### Other
23
+ - fix issue
24
+ - fix issue
25
+ - fix issues for no any issue on github
13
26
 
14
27
 
15
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.1...v0.4.2
28
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.3...v0.4.4
@@ -61,7 +61,7 @@ export async function POST(req: Request) {
61
61
  const config = getConfig(body.projectPath);
62
62
  const projectName = config?.projectName || body.projectName;
63
63
  try {
64
- const pipeline = startPipeline('issue-auto-fix', {
64
+ const pipeline = startPipeline('issue-fix-and-review', {
65
65
  issue_id: String(body.issueId),
66
66
  project: projectName,
67
67
  base_branch: config?.baseBranch || body.baseBranch || 'auto-detect',
@@ -93,7 +93,7 @@ export async function POST(req: Request) {
93
93
  // Reset the processed record first, then re-create with new pipeline
94
94
  resetProcessedIssue(body.projectPath, body.issueId);
95
95
  try {
96
- const pipeline = startPipeline('issue-auto-fix', {
96
+ const pipeline = startPipeline('issue-fix-and-review', {
97
97
  issue_id: String(body.issueId),
98
98
  project: projectName,
99
99
  base_branch: config?.baseBranch || 'auto-detect',
@@ -61,6 +61,20 @@ export async function POST(req: Request) {
61
61
  }
62
62
  }
63
63
 
64
+ // Delete workflow
65
+ if (body.action === 'delete-workflow' && body.name) {
66
+ const { existsSync: ex, unlinkSync: ul } = await import('node:fs');
67
+ const filePath = join(FLOWS_DIR, `${body.name}.yaml`);
68
+ const altPath = join(FLOWS_DIR, `${body.name}.yml`);
69
+ const path = ex(filePath) ? filePath : ex(altPath) ? altPath : null;
70
+ if (!path) return NextResponse.json({ error: 'Not found' }, { status: 404 });
71
+ // Check if built-in
72
+ const w = listWorkflows().find(w => w.name === body.name);
73
+ if (w?.builtin) return NextResponse.json({ error: 'Cannot delete built-in workflow' }, { status: 400 });
74
+ ul(path);
75
+ return NextResponse.json({ ok: true });
76
+ }
77
+
64
78
  // Start pipeline
65
79
  const { workflow, input } = body;
66
80
  if (!workflow) {
@@ -0,0 +1,68 @@
1
+ import { NextResponse } from 'next/server';
2
+ import {
3
+ getBindings,
4
+ addBinding,
5
+ removeBinding,
6
+ updateBinding,
7
+ getRuns,
8
+ deleteRun,
9
+ triggerPipeline,
10
+ getNextRunTime,
11
+ } from '@/lib/pipeline-scheduler';
12
+ import { listWorkflows } from '@/lib/pipeline';
13
+
14
+ // GET /api/project-pipelines?project=PATH
15
+ export async function GET(req: Request) {
16
+ const { searchParams } = new URL(req.url);
17
+ const projectPath = searchParams.get('project');
18
+ if (!projectPath) return NextResponse.json({ error: 'project required' }, { status: 400 });
19
+
20
+ const bindings = getBindings(projectPath).map(b => ({
21
+ ...b,
22
+ nextRunAt: getNextRunTime(b),
23
+ }));
24
+ const runs = getRuns(projectPath);
25
+ const workflows = listWorkflows().map(w => ({ name: w.name, description: w.description, builtin: w.builtin }));
26
+
27
+ return NextResponse.json({ bindings, runs, workflows });
28
+ }
29
+
30
+ // POST /api/project-pipelines
31
+ export async function POST(req: Request) {
32
+ const body = await req.json();
33
+
34
+ if (body.action === 'add') {
35
+ const { projectPath, projectName, workflowName, config } = body;
36
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
37
+ addBinding(projectPath, projectName || projectPath.split('/').pop(), workflowName, config);
38
+ return NextResponse.json({ ok: true });
39
+ }
40
+
41
+ if (body.action === 'remove') {
42
+ removeBinding(body.projectPath, body.workflowName);
43
+ return NextResponse.json({ ok: true });
44
+ }
45
+
46
+ if (body.action === 'update') {
47
+ updateBinding(body.projectPath, body.workflowName, { enabled: body.enabled, config: body.config });
48
+ return NextResponse.json({ ok: true });
49
+ }
50
+
51
+ if (body.action === 'trigger') {
52
+ const { projectPath, projectName, workflowName, input } = body;
53
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
54
+ try {
55
+ const result = triggerPipeline(projectPath, projectName || projectPath.split('/').pop(), workflowName, input);
56
+ return NextResponse.json({ ok: true, ...result });
57
+ } catch (e: any) {
58
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
59
+ }
60
+ }
61
+
62
+ if (body.action === 'delete-run') {
63
+ deleteRun(body.id);
64
+ return NextResponse.json({ ok: true });
65
+ }
66
+
67
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
68
+ }
package/app/icon.png ADDED
Binary file
@@ -38,6 +38,7 @@ export default function LoginPage() {
38
38
  <div className="min-h-screen flex items-center justify-center">
39
39
  <div className="w-80 space-y-6">
40
40
  <div className="text-center">
41
+ <img src="/icon.png" alt="Forge" width={48} height={48} className="rounded mx-auto mb-2" />
41
42
  <h1 className="text-2xl font-bold text-[var(--text-primary)]">Forge</h1>
42
43
  <p className="text-sm text-[var(--text-secondary)] mt-1">
43
44
  {isRemote ? 'Remote Access' : 'Local Access'}
@@ -137,18 +137,16 @@ export default function Dashboard({ user }: { user: any }) {
137
137
  }, []);
138
138
 
139
139
  const fetchData = useCallback(async () => {
140
- const [tasksRes, statusRes, projectsRes] = await Promise.all([
141
- fetch('/api/tasks'),
142
- fetch('/api/status'),
143
- fetch('/api/projects'),
144
- ]);
145
- const tasksData = await tasksRes.json();
146
- const statusData = await statusRes.json();
147
- const projectsData = await projectsRes.json();
148
- setTasks(tasksData);
149
- setProviders(statusData.providers);
150
- setUsage(statusData.usage);
151
- setProjects(projectsData);
140
+ try {
141
+ const [tasksRes, statusRes, projectsRes] = await Promise.all([
142
+ fetch('/api/tasks'),
143
+ fetch('/api/status'),
144
+ fetch('/api/projects'),
145
+ ]);
146
+ if (tasksRes.ok) setTasks(await tasksRes.json());
147
+ if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
148
+ if (projectsRes.ok) setProjects(await projectsRes.json());
149
+ } catch {}
152
150
  }, []);
153
151
 
154
152
  useEffect(() => {
@@ -166,6 +164,7 @@ export default function Dashboard({ user }: { user: any }) {
166
164
  {/* Top bar */}
167
165
  <header className="h-12 border-b-2 border-[var(--border)] flex items-center justify-between px-4 shrink-0 bg-[var(--bg-secondary)]">
168
166
  <div className="flex items-center gap-4">
167
+ <img src="/icon.png" alt="Forge" width={28} height={28} className="rounded" />
169
168
  <span className="text-sm font-bold text-[var(--accent)]">Forge</span>
170
169
  {versionInfo && (
171
170
  <span className="flex items-center gap-1.5">
@@ -360,7 +360,9 @@ export default function PipelineEditor({ onSave, onClose, initialYaml }: {
360
360
  Save
361
361
  </button>
362
362
  <button
363
- onClick={onClose}
363
+ onClick={() => {
364
+ if (confirm('Discard unsaved changes?')) onClose();
365
+ }}
364
366
  className="text-xs px-3 py-1 text-gray-400 hover:text-white"
365
367
  >
366
368
  Cancel