@aion0/forge 0.3.5 → 0.3.7

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.
@@ -0,0 +1,73 @@
1
+ # Pipelines (Workflows)
2
+
3
+ ## What Are Pipelines?
4
+
5
+ Pipelines chain multiple tasks into a DAG (directed acyclic graph). Each step can depend on previous steps, pass outputs forward, and run in parallel.
6
+
7
+ ## YAML Workflow Format
8
+
9
+ ```yaml
10
+ name: my-workflow
11
+ description: "What this workflow does"
12
+ input:
13
+ feature: "Feature description"
14
+ vars:
15
+ project: my-app
16
+ nodes:
17
+ design:
18
+ project: "{{vars.project}}"
19
+ prompt: "Design: {{input.feature}}"
20
+ outputs:
21
+ - name: spec
22
+ extract: result
23
+ implement:
24
+ project: "{{vars.project}}"
25
+ depends_on: [design]
26
+ prompt: "Implement: {{nodes.design.outputs.spec}}"
27
+ review:
28
+ project: "{{vars.project}}"
29
+ depends_on: [implement]
30
+ prompt: "Review the changes"
31
+ ```
32
+
33
+ ## Node Options
34
+
35
+ | Field | Description |
36
+ |-------|-------------|
37
+ | `project` | Project name (supports `{{vars.xxx}}` templates) |
38
+ | `prompt` | Claude Code prompt or shell command |
39
+ | `mode` | `claude` (default) or `shell` |
40
+ | `branch` | Auto-checkout branch before running |
41
+ | `depends_on` | List of node IDs that must complete first |
42
+ | `outputs` | Extract results: `result`, `git_diff`, or `stdout` |
43
+ | `routes` | Conditional routing to next nodes |
44
+
45
+ ## Template Variables
46
+
47
+ - `{{input.xxx}}` — pipeline input values
48
+ - `{{vars.xxx}}` — workflow variables
49
+ - `{{nodes.xxx.outputs.yyy}}` — outputs from previous nodes
50
+
51
+ ## Built-in Workflows
52
+
53
+ ### issue-auto-fix
54
+ Fetches a GitHub issue → fixes code on new branch → creates PR.
55
+
56
+ Input: `issue_id`, `project`, `base_branch` (optional)
57
+
58
+ ### pr-review
59
+ Fetches PR diff → AI code review → posts result.
60
+
61
+ Input: `pr_number`, `project`
62
+
63
+ ## CLI
64
+
65
+ ```bash
66
+ forge flows # list available workflows
67
+ forge run my-workflow # execute a workflow
68
+ ```
69
+
70
+ ## Storage
71
+
72
+ - Workflow YAML: `~/.forge/data/flows/`
73
+ - Execution state: `~/.forge/data/pipelines/`
@@ -0,0 +1,43 @@
1
+ # Skills Marketplace
2
+
3
+ ## Overview
4
+
5
+ Browse, install, and manage Claude Code skills and commands from the Forge Skills registry.
6
+
7
+ ## Types
8
+
9
+ | | Skills | Commands |
10
+ |---|---|---|
11
+ | Location | `~/.claude/skills/<name>/` | `~/.claude/commands/<name>.md` |
12
+ | Entry file | `SKILL.md` | Single `.md` file |
13
+ | Complexity | Multi-file with templates | Simple slash command |
14
+
15
+ Both register as `/slash-command` in Claude Code.
16
+
17
+ ## Install
18
+
19
+ 1. Go to **Skills** tab in Forge
20
+ 2. Click **Sync** to fetch latest registry
21
+ 3. Click **Install** on any skill → choose Global or specific project
22
+ 4. Use in Claude Code with `/<skill-name>`
23
+
24
+ ## Update
25
+
26
+ Skills with newer versions show a yellow "update" indicator. Click to update (checks for local modifications first).
27
+
28
+ ## Local Skills
29
+
30
+ The **Local** tab shows skills/commands installed on your machine (both from marketplace and manually created). You can:
31
+ - **Install to...** — Copy a local skill to another project or global
32
+ - **Delete** — Remove from project or global
33
+ - **Edit** — View and modify installed files
34
+
35
+ ## Registry
36
+
37
+ Default: `https://raw.githubusercontent.com/aiwatching/forge-skills/main`
38
+
39
+ Change in Settings → Skills Repo URL.
40
+
41
+ ## Custom Skills
42
+
43
+ Create your own: put a `.md` file in `<project>/.claude/commands/` or a directory in `<project>/.claude/skills/<name>/`.
@@ -0,0 +1,39 @@
1
+ # Projects
2
+
3
+ ## Setup
4
+
5
+ Add project directories in Settings → **Project Roots** (e.g. `~/Projects`). Forge scans subdirectories automatically.
6
+
7
+ ## Features
8
+
9
+ ### Code Tab
10
+ - File tree browser
11
+ - Syntax-highlighted code viewer
12
+ - Git diff view (click changed files)
13
+ - Git operations: commit, push, pull
14
+ - Commit history
15
+
16
+ ### Skills & Commands Tab
17
+ - View installed skills/commands for this project
18
+ - Scope indicator: G (global), P (project), G+P (both)
19
+ - Edit files, update from marketplace, uninstall
20
+
21
+ ### CLAUDE.md Tab
22
+ - View and edit project's CLAUDE.md
23
+ - Apply rule templates (built-in or custom)
24
+ - Templates auto-injected with dedup markers
25
+
26
+ ### Issues Tab
27
+ - Enable GitHub Issue Auto-fix per project
28
+ - Configure scan interval and label filters
29
+ - Manual trigger: enter issue # and click Fix Issue
30
+ - Processed issues history with retry/delete
31
+ - Auto-chains: fix → create PR → review
32
+
33
+ ## Favorites
34
+
35
+ Click ★ next to a project to favorite it. Favorites appear at the top of the sidebar.
36
+
37
+ ## Terminal
38
+
39
+ Click "Terminal" button in project header to open a Vibe Coding terminal for that project. Uses `claude -c` to continue last session.
@@ -0,0 +1,53 @@
1
+ # Rules (CLAUDE.md Templates)
2
+
3
+ ## What Are Rules?
4
+
5
+ Reusable markdown snippets that get appended to project CLAUDE.md files. They define coding conventions, security rules, workflow guidelines, etc.
6
+
7
+ ## Built-in Templates
8
+
9
+ | Template | Description |
10
+ |----------|-------------|
11
+ | TypeScript Rules | Coding conventions (const, types, early returns) |
12
+ | Git Workflow | Commit messages, branch naming |
13
+ | Obsidian Vault | Vault integration instructions |
14
+ | Security Rules | OWASP guidelines, no hardcoded secrets |
15
+
16
+ ## Manage Rules
17
+
18
+ **Skills tab → Rules sub-tab**:
19
+ - View all templates (built-in + custom)
20
+ - Create new: click "+ New"
21
+ - Edit any template (including built-in)
22
+ - Delete custom templates
23
+ - Set as "default" — auto-applied to new projects
24
+ - Batch apply: select template → check projects → click "Apply"
25
+
26
+ ## Apply to Project
27
+
28
+ **Project → CLAUDE.md tab**:
29
+ - Left sidebar shows CLAUDE.md content + template list
30
+ - Click "+ add" to inject a template
31
+ - Click "added" to remove
32
+ - Templates wrapped in `<!-- forge:template:id -->` markers (prevents duplicate injection)
33
+
34
+ ## Default Templates
35
+
36
+ Templates marked as "default" are automatically injected into new projects when they first appear in the project list.
37
+
38
+ ## Custom Templates
39
+
40
+ Stored in `~/.forge/data/claude-templates/`. Each is a `.md` file with YAML frontmatter:
41
+
42
+ ```markdown
43
+ ---
44
+ name: My Rule
45
+ description: What this rule does
46
+ tags: [category]
47
+ builtin: false
48
+ isDefault: false
49
+ ---
50
+
51
+ ## My Custom Rule
52
+ Your content here...
53
+ ```
@@ -0,0 +1,51 @@
1
+ # Issue Auto-fix
2
+
3
+ ## Overview
4
+
5
+ Automatically scan GitHub Issues, fix code, create PRs, and review — all hands-free.
6
+
7
+ ## Prerequisites
8
+
9
+ - `gh` CLI installed and authenticated: `gh auth login`
10
+ - Project has a GitHub remote
11
+
12
+ ## Setup
13
+
14
+ 1. Go to **Projects → select project → Issues tab**
15
+ 2. Enable **Issue Auto-fix**
16
+ 3. Configure:
17
+ - **Scan Interval**: minutes between scans (0 = manual only)
18
+ - **Base Branch**: leave empty for auto-detect (main/master)
19
+ - **Labels Filter**: comma-separated labels (empty = all issues)
20
+ 4. Click **Scan Now** to test
21
+
22
+ ## Flow
23
+
24
+ ```
25
+ Scan → Fetch Issue → Fix Code (new branch) → Push → Create PR → Auto Review → Notify
26
+ ```
27
+
28
+ 1. **Scan**: `gh issue list` finds open issues matching labels
29
+ 2. **Fix**: Claude Code analyzes issue and fixes code on `fix/<id>-<description>` branch
30
+ 3. **PR**: Pushes branch and creates Pull Request
31
+ 4. **Review**: Automatically triggers `pr-review` pipeline
32
+ 5. **Notify**: Results sent via Telegram (if configured)
33
+
34
+ ## Manual Trigger
35
+
36
+ Enter an issue number in "Manual Trigger" section and click "Fix Issue".
37
+
38
+ ## Retry
39
+
40
+ Failed fixes show a "Retry" button. Click to provide additional context (e.g. "rebase from main first") and re-run.
41
+
42
+ ## Safety
43
+
44
+ - Checks for uncommitted changes before starting (aborts if dirty)
45
+ - Always works on new branches (never modifies main)
46
+ - Switches back to original branch after completion
47
+ - Existing PRs are updated, not duplicated
48
+
49
+ ## Processed Issues
50
+
51
+ History shows all processed issues with status (processing/done/failed), PR number, and pipeline ID. Click pipeline ID to view details.
@@ -0,0 +1,82 @@
1
+ # Troubleshooting
2
+
3
+ ## Common Issues
4
+
5
+ ### "fork failed: Device not configured" (macOS)
6
+ PTY device limit exhausted:
7
+ ```bash
8
+ sudo sysctl kern.tty.ptmx_max=2048
9
+ echo 'kern.tty.ptmx_max=2048' | sudo tee -a /etc/sysctl.conf
10
+ ```
11
+
12
+ ### Session cookie invalid after restart
13
+ Fix AUTH_SECRET so it persists:
14
+ ```bash
15
+ echo "AUTH_SECRET=$(openssl rand -hex 32)" >> ~/.forge/data/.env.local
16
+ ```
17
+
18
+ ### Orphan processes after Ctrl+C
19
+ Use `forge server stop` or:
20
+ ```bash
21
+ pkill -f 'telegram-standalone|terminal-standalone|next-server|cloudflared'
22
+ ```
23
+
24
+ ### Tunnel stuck at "starting"
25
+ ```bash
26
+ pkill -f cloudflared
27
+ # Then restart tunnel from UI or Telegram
28
+ ```
29
+
30
+ ### Forgot admin password
31
+ ```bash
32
+ forge --reset-password
33
+ ```
34
+
35
+ ### Terminal tabs lost after restart
36
+ Terminal state is saved in `~/.forge/data/terminal-state.json`. If corrupted:
37
+ ```bash
38
+ rm ~/.forge/data/terminal-state.json
39
+ # Restart server — tabs will be empty but tmux sessions survive
40
+ ```
41
+
42
+ ### gh CLI not authenticated (Issue Scanner)
43
+ ```bash
44
+ gh auth login
45
+ ```
46
+
47
+ ### Skills not syncing
48
+ Click "Sync" in Skills tab. Check `skillsRepoUrl` in Settings points to valid registry.
49
+
50
+ ### Multiple instances conflict
51
+ Use different ports and data directories:
52
+ ```bash
53
+ forge server start --port 4000 --dir ~/.forge/data_demo
54
+ forge server stop --port 4000 --dir ~/.forge/data_demo
55
+ ```
56
+
57
+ ### Page shows "Failed to load chunk"
58
+ Clear build cache:
59
+ ```bash
60
+ rm -rf .next
61
+ pnpm build # or forge server rebuild
62
+ ```
63
+
64
+ ## Logs
65
+
66
+ - Background server: `~/.forge/data/forge.log`
67
+ - Dev mode: terminal output
68
+ - View with: `tail -f ~/.forge/data/forge.log`
69
+
70
+ ## Reset Everything
71
+
72
+ ```bash
73
+ # Stop server
74
+ forge server stop
75
+
76
+ # Reset password
77
+ forge --reset-password
78
+
79
+ # Clear all data (nuclear option)
80
+ rm -rf ~/.forge/data
81
+ # Restart — will create fresh data directory
82
+ ```
package/lib/init.ts CHANGED
@@ -64,6 +64,9 @@ export function ensureInitialized() {
64
64
  if (gInit[initKey]) return;
65
65
  gInit[initKey] = true;
66
66
 
67
+ // Add timestamps to all console output
68
+ try { const { initLogger } = require('./logger'); initLogger(); } catch {}
69
+
67
70
  // Migrate old data layout (~/.forge/* → ~/.forge/data/*) on first run
68
71
  try { const { migrateDataDir } = require('./dirs'); migrateDataDir(); } catch {}
69
72
 
package/lib/logger.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Logger — adds timestamps + writes to forge.log file.
3
+ * Call `initLogger()` once at startup.
4
+ * Works in both dev mode (terminal + file) and production (file via redirect).
5
+ */
6
+
7
+ import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ let initialized = false;
11
+
12
+ export function initLogger() {
13
+ if (initialized) return;
14
+ initialized = true;
15
+
16
+ // Determine log file path
17
+ let logFile: string | null = null;
18
+ try {
19
+ const { getDataDir } = require('./dirs');
20
+ const dataDir = getDataDir();
21
+ if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
22
+ logFile = join(dataDir, 'forge.log');
23
+ } catch {}
24
+
25
+ const origLog = console.log;
26
+ const origError = console.error;
27
+ const origWarn = console.warn;
28
+
29
+ const ts = () => new Date().toISOString().replace('T', ' ').slice(0, 19);
30
+
31
+ const writeToFile = (line: string) => {
32
+ if (!logFile) return;
33
+ try { appendFileSync(logFile, line + '\n'); } catch {}
34
+ };
35
+
36
+ const SENSITIVE_PATTERNS = [
37
+ /(\d{8,})/g, // session codes (8+ digits)
38
+ /(bot\d+:[A-Za-z0-9_-]{30,})/gi, // telegram bot tokens
39
+ /(enc:[A-Za-z0-9+/=.]+)/g, // encrypted values
40
+ /(sk-ant-[A-Za-z0-9_-]+)/g, // anthropic API keys
41
+ /(sk-[A-Za-z0-9]{20,})/g, // openai API keys
42
+ ];
43
+
44
+ const sanitize = (str: string): string => {
45
+ let result = str;
46
+ for (const pattern of SENSITIVE_PATTERNS) {
47
+ result = result.replace(pattern, (match) => match.slice(0, 4) + '****');
48
+ }
49
+ return result;
50
+ };
51
+
52
+ const format = (...args: any[]): string => {
53
+ return args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
54
+ };
55
+
56
+ console.log = (...args: any[]) => {
57
+ const line = `[${ts()}] ${format(...args)}`;
58
+ origLog(line);
59
+ writeToFile(sanitize(line));
60
+ };
61
+
62
+ console.error = (...args: any[]) => {
63
+ const line = `[${ts()}] [ERROR] ${format(...args)}`;
64
+ origError(line);
65
+ writeToFile(sanitize(line));
66
+ };
67
+
68
+ console.warn = (...args: any[]) => {
69
+ const line = `[${ts()}] [WARN] ${format(...args)}`;
70
+ origWarn(line);
71
+ writeToFile(sanitize(line));
72
+ };
73
+ }
package/lib/password.ts CHANGED
@@ -59,7 +59,7 @@ export function getSessionCode(): string {
59
59
  export function rotateSessionCode(): string {
60
60
  const code = generateSessionCode();
61
61
  saveSessionCode(code);
62
- console.log(`[password] New session code: ${code}`);
62
+ console.log(`[password] New session code: ****${code.slice(-2)}`);
63
63
  return code;
64
64
  }
65
65
 
package/lib/skills.ts CHANGED
@@ -87,6 +87,7 @@ function getInstalledVersion(name: string, type: string, basePath?: string): str
87
87
  // ─── Sync from registry ──────────────────────────────────────
88
88
 
89
89
  export async function syncSkills(): Promise<{ synced: number; error?: string }> {
90
+ console.log('[skills] Syncing from registry...');
90
91
  const baseUrl = getBaseUrl();
91
92
 
92
93
  try {
@@ -198,6 +199,7 @@ export async function syncSkills(): Promise<{ synced: number; error?: string }>
198
199
  }
199
200
  }
200
201
 
202
+ console.log(`[skills] Synced ${items.length} items`);
201
203
  return { synced: items.length };
202
204
  } catch (e) {
203
205
  return { synced: 0, error: e instanceof Error ? e.message : String(e) };
@@ -284,6 +286,7 @@ async function downloadToDir(files: { path: string; download_url: string }[], de
284
286
  // ─── Install ─────────────────────────────────────────────────
285
287
 
286
288
  export async function installGlobal(name: string): Promise<void> {
289
+ console.log(`[skills] Installing "${name}" globally`);
287
290
  const row = db().prepare('SELECT type, version FROM skills WHERE name = ?').get(name) as any;
288
291
  if (!row) throw new Error(`Not found: ${name}`);
289
292
  const type: ItemType = row.type || 'skill';
@@ -318,6 +321,7 @@ export async function installGlobal(name: string): Promise<void> {
318
321
  }
319
322
 
320
323
  export async function installProject(name: string, projectPath: string): Promise<void> {
324
+ console.log(`[skills] Installing "${name}" to ${projectPath.split('/').pop()}`);
321
325
  const row = db().prepare('SELECT type, version FROM skills WHERE name = ?').get(name) as any;
322
326
  if (!row) throw new Error(`Not found: ${name}`);
323
327
  const type: ItemType = row.type || 'skill';
@@ -357,6 +361,7 @@ export async function installProject(name: string, projectPath: string): Promise
357
361
  // ─── Uninstall ───────────────────────────────────────────────
358
362
 
359
363
  export function uninstallGlobal(name: string): void {
364
+ console.log(`[skills] Uninstalling "${name}" from global`);
360
365
  // Remove from all possible locations
361
366
  try { rmSync(join(GLOBAL_SKILLS_DIR, name), { recursive: true }); } catch {}
362
367
  try { unlinkSync(join(GLOBAL_COMMANDS_DIR, `${name}.md`)); } catch {}
@@ -367,6 +372,7 @@ export function uninstallGlobal(name: string): void {
367
372
  }
368
373
 
369
374
  export function uninstallProject(name: string, projectPath: string): void {
375
+ console.log(`[skills] Uninstalling "${name}" from ${projectPath.split('/').pop()}`);
370
376
  // Remove from all possible locations
371
377
  try { rmSync(join(projectPath, '.claude', 'skills', name), { recursive: true }); } catch {}
372
378
  try { unlinkSync(join(projectPath, '.claude', 'commands', `${name}.md`)); } catch {}
@@ -405,12 +405,14 @@ function executeTask(task: Task): Promise<void> {
405
405
  WHERE id = ?
406
406
  `).run(resultText, totalCost, task.id);
407
407
  emit(task.id, 'status', 'done');
408
+ console.log(`[task] Done: ${task.id} ${task.projectName} (cost: $${totalCost?.toFixed(4) || '0'})`);
408
409
  const doneTask = getTask(task.id);
409
410
  if (doneTask) notifyTaskComplete(doneTask).catch(() => {});
410
411
  notifyTerminalSession(task, 'done', sessionId);
411
412
  resolve();
412
413
  } else {
413
414
  const errMsg = `Process exited with code ${code}`;
415
+ console.error(`[task] Failed: ${task.id} ${task.projectName} — ${errMsg}`);
414
416
  updateTaskStatus(task.id, 'failed', errMsg);
415
417
  const failedTask = getTask(task.id);
416
418
  if (failedTask) notifyTaskFailed(failedTask).catch(() => {});
package/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {