├── VERSION ├── tests └── vm │ ├── .gitkeep │ ├── test_install_ubuntu.sh │ └── resume_checks.sh ├── apps └── web │ ├── app │ ├── .gitkeep │ ├── favicon.ico │ ├── install │ │ └── route.ts │ ├── learn │ │ └── [slug] │ │ │ └── page.tsx │ └── layout.tsx │ ├── lib │ ├── .gitkeep │ ├── hooks │ │ ├── useReducedMotion.ts │ │ ├── useWizardAnalytics.ts │ │ └── useScrollReveal.ts │ └── utils.ts │ ├── components │ ├── .gitkeep │ ├── index.ts │ ├── motion │ │ ├── motion-provider.tsx │ │ └── index.tsx │ ├── ui │ │ ├── checkbox.tsx │ │ ├── card.tsx │ │ └── button.tsx │ ├── tracked-button.tsx │ ├── tracked-link.tsx │ ├── query-provider.tsx │ ├── third-party-scripts.tsx │ ├── command-ref-card.tsx │ └── alert-card.tsx │ ├── public │ ├── je_headshot.jpg │ ├── vercel.svg │ ├── logos │ │ ├── vercel.svg │ │ ├── anthropic.svg │ │ ├── tailscale.svg │ │ ├── openai.svg │ │ ├── google.svg │ │ ├── cloudflare.svg │ │ ├── github.svg │ │ └── supabase.svg │ ├── window.svg │ ├── file.svg │ ├── globe.svg │ └── next.svg │ ├── components.ts │ ├── postcss.config.mjs │ ├── vercel.json │ ├── components.json │ ├── eslint.config.mjs │ ├── .gitignore │ ├── next.config.ts │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── playwright.config.ts ├── scripts ├── sync │ ├── .gitkeep │ └── sync_ntm_palette.sh ├── providers │ ├── .gitkeep │ ├── screenshots │ │ └── .gitkeep │ ├── ovh.md │ └── contabo.md ├── hooks │ ├── install.sh │ └── pre-commit ├── templates │ └── acfs-upgrade-resume.service ├── acfs-update ├── acfs-global ├── lib │ ├── os_detect.sh │ └── contract.sh └── generated │ ├── install_users.sh │ ├── install_all.sh │ ├── install_network.sh │ └── install_base.sh ├── packages ├── installer │ └── .gitkeep ├── manifest │ ├── .gitkeep │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts └── onboard │ ├── .gitkeep │ └── .gitignore ├── acfs ├── onboard │ ├── assets │ │ └── diagrams │ │ │ └── .gitkeep │ └── lessons │ │ ├── 00_welcome.md │ │ ├── 01_linux_basics.md │ │ ├── 03_tmux_basics.md │ │ ├── 05_ntm_core.md │ │ ├── 02_ssh_basics.md │ │ ├── 06_ntm_command_palette.md │ │ ├── 04_agents_login.md │ │ ├── 08_keeping_updated.md │ │ └── 07_flywheel_loop.md ├── claude │ └── settings.json └── tmux │ └── tmux.conf ├── .beads ├── metadata.json ├── .gitignore ├── config.yaml └── README.md ├── .gitattributes ├── package.json ├── CHANGELOG.md ├── .github └── workflows │ ├── playwright.yml │ ├── website.yml │ ├── checksum-monitor.yml │ └── installer.yml ├── .gitignore ├── docs ├── tests │ ├── fixtures_catalog.md │ └── coverage_matrix.md ├── codex-auth-research.md └── MAINTAINER_GUIDE.md └── checksums.yaml /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /tests/vm/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/lib/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/sync/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/installer/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/manifest/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/onboard/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/providers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acfs/onboard/assets/diagrams/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/onboard/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /.beads/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": "beads.db", 3 | "jsonl_export": "issues.jsonl" 4 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # Use bd merge for beads JSONL files 3 | .beads/issues.jsonl merge=beads 4 | -------------------------------------------------------------------------------- /apps/web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dicklesworthstone/agentic_coding_flywheel_setup/HEAD/apps/web/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/je_headshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dicklesworthstone/agentic_coding_flywheel_setup/HEAD/apps/web/public/je_headshot.jpg -------------------------------------------------------------------------------- /apps/web/components.ts: -------------------------------------------------------------------------------- 1 | // Re-export barrel to avoid ambiguity with `components.json` (shadcn/ui config). 2 | export * from "./components/index"; 3 | 4 | -------------------------------------------------------------------------------- /apps/web/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /apps/web/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/public/logos/vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "framework": "nextjs", 4 | "buildCommand": "bun run build", 5 | "installCommand": "bun install" 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/public/logos/anthropic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/web/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /acfs/claude/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$comment": "ACFS Claude Code settings with git safety guard hook", 4 | "hooks": { 5 | "PreToolUse": [ 6 | { 7 | "matcher": "Bash", 8 | "hooks": [ 9 | { 10 | "type": "command", 11 | "command": "~/.claude/hooks/git_safety_guard.py" 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/components/index.ts: -------------------------------------------------------------------------------- 1 | // UI Components 2 | export { Button, buttonVariants } from "./ui/button"; 3 | export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent } from "./ui/card"; 4 | export { Checkbox } from "./ui/checkbox"; 5 | 6 | // Custom Components 7 | export { CommandCard } from "./command-card"; 8 | export type { CommandCardProps } from "./command-card"; 9 | export { Stepper, StepperMobile } from "./stepper"; 10 | export type { StepperProps, WizardStep } from "./stepper"; 11 | -------------------------------------------------------------------------------- /apps/web/public/logos/tailscale.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from "eslint/config"; 2 | import nextVitals from "eslint-config-next/core-web-vitals"; 3 | import nextTs from "eslint-config-next/typescript"; 4 | 5 | const eslintConfig = defineConfig([ 6 | ...nextVitals, 7 | ...nextTs, 8 | // Override default ignores of eslint-config-next. 9 | globalIgnores([ 10 | // Default ignores of eslint-config-next: 11 | ".next/**", 12 | "out/**", 13 | "build/**", 14 | "next-env.d.ts", 15 | ]), 16 | ]); 17 | 18 | export default eslintConfig; 19 | -------------------------------------------------------------------------------- /apps/web/public/logos/openai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agentic-coding-flywheel-setup", 3 | "version": "0.1.0", 4 | "private": true, 5 | "workspaces": [ 6 | "apps/*", 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "dev": "bun run --filter @acfs/web dev", 11 | "build": "bun run --filter @acfs/web build", 12 | "lint": "bun run --filter '*' lint", 13 | "type-check": "bun run --filter '*' type-check", 14 | "clean": "rm -rf apps/*/node_modules packages/*/node_modules node_modules" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5" 18 | }, 19 | "trustedDependencies": [ 20 | "sharp", 21 | "unrs-resolver" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/public/logos/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.beads/.gitignore: -------------------------------------------------------------------------------- 1 | # SQLite databases 2 | *.db 3 | *.db?* 4 | *.db-journal 5 | *.db-wal 6 | *.db-shm 7 | 8 | # Daemon runtime files 9 | daemon.lock 10 | daemon.log 11 | daemon.pid 12 | bd.sock 13 | 14 | # Local version tracking (prevents upgrade notification spam after git ops) 15 | .local_version 16 | 17 | # Legacy database files 18 | db.sqlite 19 | bd.db 20 | 21 | # Merge artifacts (temporary files from 3-way merge) 22 | beads.base.jsonl 23 | beads.base.meta.json 24 | beads.left.jsonl 25 | beads.left.meta.json 26 | beads.right.jsonl 27 | beads.right.meta.json 28 | 29 | # Keep JSONL exports and config (source of truth for git) 30 | !issues.jsonl 31 | !metadata.json 32 | !config.json 33 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | .env*.local 43 | -------------------------------------------------------------------------------- /apps/web/public/logos/cloudflare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /scripts/hooks/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Install ACFS git hooks 3 | # 4 | # Usage: ./scripts/hooks/install.sh 5 | 6 | set -euo pipefail 7 | 8 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" 10 | HOOKS_DIR="$REPO_ROOT/.git/hooks" 11 | 12 | echo "Installing ACFS git hooks..." 13 | 14 | # Install pre-commit hook 15 | cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit" 16 | chmod +x "$HOOKS_DIR/pre-commit" 17 | echo "✅ Installed pre-commit hook" 18 | 19 | echo "" 20 | echo "Hooks installed. They will:" 21 | echo " - Auto-regenerate scripts/generated/ when manifest changes" 22 | echo "" 23 | echo "To bypass: git commit --no-verify" 24 | echo "CI still enforces drift checks." 25 | -------------------------------------------------------------------------------- /apps/web/public/logos/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/web/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | import { dirname, resolve } from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | 5 | const configDir = dirname(fileURLToPath(import.meta.url)); 6 | const workspaceRoot = resolve(configDir, "../.."); 7 | 8 | const nextConfig: NextConfig = { 9 | turbopack: { 10 | // Bun workspaces install deps at the workspace root; Turbopack needs this 11 | // to resolve `next` and other packages when multiple lockfiles exist. 12 | root: workspaceRoot, 13 | }, 14 | images: { 15 | remotePatterns: [ 16 | { 17 | protocol: "https", 18 | hostname: "raw.githubusercontent.com", 19 | }, 20 | ], 21 | }, 22 | }; 23 | 24 | export default nextConfig; 25 | -------------------------------------------------------------------------------- /packages/manifest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*"], 24 | "exclude": ["node_modules", "dist", "src/**/*.test.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/public/logos/supabase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "react-jsx", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | ".next/types/**/*.ts", 30 | ".next/dev/types/**/*.ts", 31 | "**/*.mts" 32 | ], 33 | "exclude": ["node_modules", "playwright.config.ts", "test-mobile.ts"] 34 | } 35 | -------------------------------------------------------------------------------- /scripts/providers/screenshots/.gitkeep: -------------------------------------------------------------------------------- 1 | # Screenshot Placeholders 2 | 3 | This directory will contain provider-specific screenshots: 4 | 5 | ## OVH 6 | - ovh-step1-create-account.png 7 | - ovh-step2-select-vps.png 8 | - ovh-step3-choose-plan.png 9 | - ovh-step4-select-os.png 10 | - ovh-step5-add-ssh-key.png 11 | - ovh-step6-select-location.png 12 | - ovh-step7-complete-order.png 13 | - ovh-step8-find-ip.png 14 | 15 | ## Contabo 16 | - contabo-step1-select-vps.png 17 | - contabo-step2-choose-plan.png 18 | - contabo-step3-select-region.png 19 | - contabo-step4-select-os.png 20 | - contabo-step5-set-password.png 21 | - contabo-step6-add-ssh-key.png 22 | - contabo-step7-complete-order.png 23 | - contabo-step8-find-ip.png 24 | 25 | To add screenshots: 26 | 1. Take screenshots following the guide steps 27 | 2. Crop to relevant UI area 28 | 3. Save with exact filenames above 29 | 4. Optimize with: optipng *.png 30 | -------------------------------------------------------------------------------- /apps/web/app/install/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | // Ensure this route is always dynamic (never cached at build time) 4 | export const dynamic = "force-dynamic"; 5 | 6 | /** 7 | * GET /install 8 | * 9 | * Redirects to the raw install.sh script on GitHub. 10 | * This allows users to run: curl -fsSL https://agent-flywheel.com/install | bash 11 | * 12 | * The -L flag in curl follows redirects, so this works seamlessly. 13 | */ 14 | export async function GET() { 15 | const scriptUrl = 16 | "https://raw.githubusercontent.com/Dicklesworthstone/agentic_coding_flywheel_setup/main/install.sh"; 17 | 18 | // Create redirect response with cache-control headers to prevent caching 19 | const response = NextResponse.redirect(scriptUrl, 302); 20 | response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate"); 21 | response.headers.set("Pragma", "no-cache"); 22 | response.headers.set("Expires", "0"); 23 | 24 | return response; 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/components/motion/motion-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { LazyMotion, domAnimation, MotionConfig } from "framer-motion"; 4 | import { springs } from "./index"; 5 | 6 | interface MotionProviderProps { 7 | children: React.ReactNode; 8 | } 9 | 10 | /** 11 | * MotionProvider wraps the app with optimized Framer Motion configuration. 12 | * 13 | * Features: 14 | * - LazyMotion with domAnimation for smaller bundle (~13KB gzipped vs ~25KB full) 15 | * - MotionConfig sets default transition for consistent feel across app 16 | * - Strict mode catches missing features at build time 17 | * 18 | * Usage: Wrap your app's root layout with this provider: 19 | * {children} 20 | */ 21 | export function MotionProvider({ children }: MotionProviderProps) { 22 | return ( 23 | 24 | 28 | {children} 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/manifest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@acfs/manifest", 3 | "version": "0.1.0", 4 | "description": "Parser and utilities for ACFS manifest files", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "module": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.js", 12 | "types": "./dist/index.d.ts" 13 | } 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "build": "tsc", 20 | "dev": "tsc --watch", 21 | "type-check": "tsc --noEmit", 22 | "test": "bun test", 23 | "clean": "rm -rf dist", 24 | "generate": "bun run src/generate.ts", 25 | "generate:dry": "bun run src/generate.ts --dry-run --verbose", 26 | "generate:validate": "bun run src/generate.ts --validate", 27 | "generate:diff": "bun run src/generate.ts --diff" 28 | }, 29 | "dependencies": { 30 | "yaml": "^2.7.0", 31 | "zod": "^3.24.1" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^22.10.2", 35 | "typescript": "^5.7.2" 36 | }, 37 | "peerDependencies": { 38 | "typescript": ">=5.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/templates/acfs-upgrade-resume.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ACFS Ubuntu Upgrade Resume Service 3 | Documentation=https://github.com/Dicklesworthstone/agentic_coding_flywheel_setup 4 | 5 | # Wait for network to be fully online before running 6 | # This is critical because do-release-upgrade needs apt to work 7 | After=network-online.target 8 | Wants=network-online.target 9 | 10 | # Only run if the resume script exists (upgrade in progress) 11 | ConditionPathExists=/var/lib/acfs/upgrade_resume.sh 12 | 13 | # Don't run in rescue mode or during recovery 14 | ConditionPathExists=!/run/systemd/maintenance 15 | 16 | [Service] 17 | Type=oneshot 18 | 19 | # Run the resume script 20 | ExecStart=/bin/bash /var/lib/acfs/upgrade_resume.sh 21 | 22 | # Give plenty of time for upgrades (2 hours max) 23 | TimeoutStartSec=7200 24 | 25 | # Don't restart on failure - let user investigate 26 | Restart=no 27 | RemainAfterExit=no 28 | 29 | # Logging to journal 30 | StandardOutput=journal+console 31 | StandardError=journal+console 32 | 33 | # Run as root (required for apt and reboot) 34 | User=root 35 | Group=root 36 | 37 | [Install] 38 | WantedBy=multi-user.target 39 | -------------------------------------------------------------------------------- /scripts/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ACFS Pre-commit Hook: Auto-regenerate manifest outputs 3 | # 4 | # When acfs.manifest.yaml or packages/manifest/** change, 5 | # this hook regenerates scripts/generated/** and stages the changes. 6 | # 7 | # Install: ./scripts/hooks/install.sh 8 | # Bypass: git commit --no-verify 9 | # CI enforces: bun run generate --diff (blocks merge on drift) 10 | 11 | set -euo pipefail 12 | 13 | # Check if manifest-related files are staged 14 | if git diff --cached --name-only | grep -qE '^(acfs\.manifest\.yaml|packages/manifest/)'; then 15 | echo "📦 Manifest changes detected, regenerating scripts/generated/..." 16 | 17 | # Check if bun is available 18 | if ! command -v bun &>/dev/null; then 19 | echo "⚠️ bun not found. Skipping auto-regeneration." 20 | echo " Run manually: cd packages/manifest && bun run generate" 21 | exit 0 22 | fi 23 | 24 | # Regenerate 25 | (cd packages/manifest && bun install --silent && bun run generate) || { 26 | echo "❌ Generator failed. Fix issues before committing." 27 | exit 1 28 | } 29 | 30 | # Stage regenerated files 31 | git add scripts/generated/ 32 | echo "✅ Regenerated and staged scripts/generated/" 33 | fi 34 | -------------------------------------------------------------------------------- /apps/web/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { CheckIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Checkbox({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export { Checkbox } 33 | -------------------------------------------------------------------------------- /scripts/acfs-update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ============================================================ 3 | # ACFS Update Wrapper 4 | # Provides a global `acfs-update` command that dispatches to update.sh 5 | # ============================================================ 6 | 7 | set -euo pipefail 8 | 9 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 10 | 11 | find_update_script() { 12 | local candidate="" 13 | 14 | # Respect ACFS_HOME if set (installed location) 15 | if [[ -n "${ACFS_HOME:-}" ]] && [[ -f "${ACFS_HOME}/scripts/lib/update.sh" ]]; then 16 | candidate="${ACFS_HOME}/scripts/lib/update.sh" 17 | echo "$candidate" 18 | return 0 19 | fi 20 | 21 | # Repo-relative path (running from source) 22 | if [[ -f "${SCRIPT_DIR}/lib/update.sh" ]]; then 23 | candidate="${SCRIPT_DIR}/lib/update.sh" 24 | echo "$candidate" 25 | return 0 26 | fi 27 | 28 | # Installed path (after install.sh) 29 | if [[ -f "$HOME/.acfs/scripts/lib/update.sh" ]]; then 30 | candidate="$HOME/.acfs/scripts/lib/update.sh" 31 | echo "$candidate" 32 | return 0 33 | fi 34 | 35 | return 1 36 | } 37 | 38 | UPDATE_SCRIPT="$(find_update_script || true)" 39 | 40 | if [[ -z "${UPDATE_SCRIPT:-}" ]]; then 41 | echo "Error: update.sh not found." >&2 42 | echo "Run from the repo or install ACFS first." >&2 43 | exit 1 44 | fi 45 | 46 | exec bash "$UPDATE_SCRIPT" "$@" 47 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | bun install 9 | bun run dev 10 | ``` 11 | 12 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 13 | 14 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 15 | 16 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 17 | 18 | ## Learn More 19 | 20 | To learn more about Next.js, take a look at the following resources: 21 | 22 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 23 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 24 | 25 | ## Deploy on Vercel 26 | 27 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 28 | 29 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - **Automatic Ubuntu Upgrade**: The installer now automatically upgrades Ubuntu to 25.10 before running the main ACFS installation 13 | - Detects current Ubuntu version and calculates sequential upgrade path 14 | - Handles reboots automatically via systemd resume service 15 | - Supports upgrade chains: 22.04 → 24.04 → 24.10 → 25.04 → 25.10 16 | - Creates MOTD banners to show progress to reconnecting users 17 | - Includes preflight checks for disk space, network, and apt state 18 | - Provides graceful degradation if upgrade fails but system is functional 19 | - Skip with `--skip-ubuntu-upgrade` flag 20 | - Full documentation at `docs/ubuntu-upgrade.md` 21 | 22 | ## [0.1.0] - 2024-12-20 23 | 24 | ### Added 25 | 26 | - Initial release of ACFS (Agentic Coding Flywheel Setup) 27 | - One-liner installer for Ubuntu VPS environments 28 | - Web wizard at agent-flywheel.com for beginners 29 | - Full Dicklesworthstone stack integration (8 tools) 30 | - Three AI coding agents: Claude Code, Codex CLI, Gemini CLI 31 | - Manifest-driven tool definitions 32 | - Checkpointed, idempotent installation 33 | - Interactive onboarding TUI 34 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'apps/web/**' 8 | - '.github/workflows/playwright.yml' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - 'apps/web/**' 13 | - '.github/workflows/playwright.yml' 14 | 15 | jobs: 16 | test: 17 | timeout-minutes: 30 18 | runs-on: ubuntu-latest 19 | defaults: 20 | run: 21 | working-directory: apps/web 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Setup Bun 27 | uses: oven-sh/setup-bun@v2 28 | with: 29 | bun-version: latest 30 | 31 | - name: Install dependencies 32 | run: bun install 33 | 34 | - name: Install Playwright Browsers 35 | run: bunx playwright install --with-deps chromium 36 | 37 | - name: Run Playwright tests 38 | run: bunx playwright test --project=chromium 39 | 40 | - name: Upload Playwright Report 41 | uses: actions/upload-artifact@v4 42 | if: ${{ !cancelled() }} 43 | with: 44 | name: playwright-report 45 | path: apps/web/playwright-report/ 46 | retention-days: 30 47 | 48 | - name: Upload Test Results 49 | uses: actions/upload-artifact@v4 50 | if: failure() 51 | with: 52 | name: test-results 53 | path: apps/web/test-results/ 54 | retention-days: 7 55 | -------------------------------------------------------------------------------- /apps/web/components/tracked-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { forwardRef, type ComponentProps } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { trackInteraction, sendEvent } from '@/lib/analytics'; 6 | 7 | type ButtonProps = ComponentProps; 8 | 9 | interface TrackedButtonProps extends ButtonProps { 10 | trackingId: string; 11 | trackingCategory?: string; 12 | trackingLabel?: string; 13 | trackingValue?: number; 14 | } 15 | 16 | /** 17 | * Button component with built-in click tracking 18 | */ 19 | export const TrackedButton = forwardRef( 20 | ( 21 | { 22 | trackingId, 23 | trackingCategory = 'button', 24 | trackingLabel, 25 | trackingValue, 26 | onClick, 27 | children, 28 | ...props 29 | }, 30 | ref 31 | ) => { 32 | const handleClick = (e: React.MouseEvent) => { 33 | // Track the click 34 | trackInteraction('click', trackingId, trackingCategory, { 35 | label: trackingLabel || (typeof children === 'string' ? children : trackingId), 36 | value: trackingValue, 37 | }); 38 | 39 | // Also send as a named event for easier filtering 40 | sendEvent(`${trackingCategory}_click`, { 41 | button_id: trackingId, 42 | button_label: trackingLabel || (typeof children === 'string' ? children : trackingId), 43 | }); 44 | 45 | // Call original onClick if provided 46 | onClick?.(e); 47 | }; 48 | 49 | return ( 50 | 53 | ); 54 | } 55 | ); 56 | 57 | TrackedButton.displayName = 'TrackedButton'; 58 | 59 | export default TrackedButton; 60 | -------------------------------------------------------------------------------- /packages/manifest/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @acfs/manifest 3 | * TypeScript library for parsing and working with ACFS manifest files 4 | */ 5 | 6 | // Export types 7 | export type { 8 | Manifest, 9 | ManifestDefaults, 10 | Module, 11 | ModuleCategory, 12 | ValidationResult, 13 | ValidationError, 14 | ValidationWarning, 15 | ParseResult, 16 | ParseError, 17 | } from './types.js'; 18 | 19 | // Export schema types (inferred from Zod) 20 | export type { 21 | ManifestInput, 22 | ManifestOutput, 23 | ModuleInput, 24 | ModuleOutput, 25 | ManifestDefaultsInput, 26 | ManifestDefaultsOutput, 27 | } from './schema.js'; 28 | 29 | // Export Zod schemas for advanced usage 30 | export { 31 | ManifestSchema, 32 | ModuleSchema, 33 | ManifestDefaultsSchema, 34 | } from './schema.js'; 35 | 36 | // Export parser functions 37 | export { 38 | parseManifestFile, 39 | parseManifestString, 40 | validateManifest, 41 | } from './parser.js'; 42 | 43 | // Export utility functions 44 | export { 45 | getModuleCategory, 46 | getModulesByCategory, 47 | getModuleById, 48 | getModuleDependencies, 49 | getTransitiveDependencies, 50 | getDependents, 51 | getCategories, 52 | sortModulesByInstallOrder, 53 | groupModulesByCategory, 54 | searchModules, 55 | getManifestStats, 56 | } from './utils.js'; 57 | 58 | // Export stats interface 59 | export type { ManifestStats } from './utils.js'; 60 | 61 | // Export advanced validation API (bead mjt.3.2) 62 | export { 63 | validateDependencyExistence, 64 | detectDependencyCycles, 65 | validatePhaseOrdering, 66 | validateManifest as validateManifestAdvanced, 67 | formatValidationErrors, 68 | } from './validate.js'; 69 | 70 | export type { 71 | ValidationError as AdvancedValidationError, 72 | ValidationResult as AdvancedValidationResult, 73 | } from './validate.js'; 74 | -------------------------------------------------------------------------------- /apps/web/components/tracked-link.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { forwardRef, type AnchorHTMLAttributes } from 'react'; 4 | import Link from 'next/link'; 5 | import { trackOutboundLink, sendEvent } from '@/lib/analytics'; 6 | 7 | interface TrackedLinkProps extends AnchorHTMLAttributes { 8 | href: string; 9 | trackingId?: string; 10 | isExternal?: boolean; 11 | children: React.ReactNode; 12 | } 13 | 14 | /** 15 | * Link component with built-in click tracking 16 | * Automatically detects external links and tracks them 17 | */ 18 | export const TrackedLink = forwardRef( 19 | ({ href, trackingId, isExternal, onClick, children, ...props }, ref) => { 20 | // Detect if link is external 21 | const isExternalLink = 22 | isExternal ?? (href.startsWith('http://') || href.startsWith('https://')); 23 | 24 | const handleClick = (e: React.MouseEvent) => { 25 | const linkText = typeof children === 'string' ? children : trackingId || href; 26 | 27 | if (isExternalLink) { 28 | trackOutboundLink(href, linkText); 29 | } else { 30 | sendEvent('internal_link_click', { 31 | link_href: href, 32 | link_text: linkText, 33 | tracking_id: trackingId, 34 | }); 35 | } 36 | 37 | onClick?.(e); 38 | }; 39 | 40 | if (isExternalLink) { 41 | return ( 42 | 50 | {children} 51 | 52 | ); 53 | } 54 | 55 | return ( 56 | 57 | {children} 58 | 59 | ); 60 | } 61 | ); 62 | 63 | TrackedLink.displayName = 'TrackedLink'; 64 | 65 | export default TrackedLink; 66 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@acfs/web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint .", 10 | "type-check": "tsc --noEmit", 11 | "test": "playwright test", 12 | "test:ui": "playwright test --ui", 13 | "test:headed": "playwright test --headed", 14 | "test:debug": "playwright test --debug", 15 | "test:report": "playwright show-report" 16 | }, 17 | "dependencies": { 18 | "@next/third-parties": "^16.1.0", 19 | "@radix-ui/react-checkbox": "^1.3.3", 20 | "@radix-ui/react-slot": "^1.2.4", 21 | "@tanstack/query-sync-storage-persister": "^5.90.14", 22 | "@tanstack/react-form": "^1.27.5", 23 | "@tanstack/react-query": "^5.90.12", 24 | "@tanstack/react-query-persist-client": "^5.90.14", 25 | "@tanstack/zod-form-adapter": "^0.42.1", 26 | "@use-gesture/react": "^10.3.1", 27 | "@vercel/analytics": "^1.4.1", 28 | "@vercel/speed-insights": "^1.1.0", 29 | "class-variance-authority": "^0.7.1", 30 | "clsx": "^2.1.1", 31 | "framer-motion": "^12.23.26", 32 | "lucide-react": "^0.562.0", 33 | "next": "16.1.0", 34 | "react": "19.2.3", 35 | "react-dom": "19.2.3", 36 | "react-markdown": "^10.1.0", 37 | "rehype-highlight": "^7.0.2", 38 | "remark-gfm": "^4.0.1", 39 | "tailwind-merge": "^3.4.0" 40 | }, 41 | "devDependencies": { 42 | "@tailwindcss/postcss": "^4", 43 | "@types/node": "^20", 44 | "@types/react": "^19", 45 | "@types/react-dom": "^19", 46 | "eslint": "^9", 47 | "eslint-config-next": "16.1.0", 48 | "@playwright/test": "^1.57.0", 49 | "tailwindcss": "^4", 50 | "tw-animate-css": "^1.4.0", 51 | "typescript": "^5" 52 | }, 53 | "ignoreScripts": [ 54 | "sharp", 55 | "unrs-resolver" 56 | ], 57 | "trustedDependencies": [ 58 | "sharp", 59 | "unrs-resolver" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /apps/web/app/learn/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | import { Metadata } from "next"; 3 | import fs from "fs/promises"; 4 | import path from "path"; 5 | import { LESSONS, getLessonBySlug } from "@/lib/lessonProgress"; 6 | import { LessonContent } from "./lesson-content"; 7 | 8 | interface Props { 9 | params: Promise<{ slug: string }>; 10 | } 11 | 12 | // Generate static paths for all lessons 13 | export async function generateStaticParams() { 14 | return LESSONS.map((lesson) => ({ 15 | slug: lesson.slug, 16 | })); 17 | } 18 | 19 | // Generate metadata for each lesson 20 | export async function generateMetadata({ params }: Props): Promise { 21 | const { slug } = await params; 22 | const lesson = getLessonBySlug(slug); 23 | 24 | if (!lesson) { 25 | return { title: "Lesson Not Found" }; 26 | } 27 | 28 | return { 29 | title: `${lesson.title} | ACFS Learning Hub`, 30 | description: lesson.description, 31 | }; 32 | } 33 | 34 | // Load markdown content at build time 35 | async function loadLessonContent(filename: string): Promise { 36 | const lessonsDir = path.join(process.cwd(), "..", "..", "acfs", "onboard", "lessons"); 37 | const filePath = path.join(lessonsDir, filename); 38 | 39 | try { 40 | const content = await fs.readFile(filePath, "utf-8"); 41 | return content; 42 | } catch { 43 | // Fallback: try from project root 44 | const altPath = path.join(process.cwd(), "../../acfs/onboard/lessons", filename); 45 | try { 46 | return await fs.readFile(altPath, "utf-8"); 47 | } catch { 48 | return `# Content Not Found\n\nThe lesson content file \`${filename}\` could not be loaded.`; 49 | } 50 | } 51 | } 52 | 53 | export default async function LessonPage({ params }: Props) { 54 | const { slug } = await params; 55 | const lesson = getLessonBySlug(slug); 56 | 57 | if (!lesson) { 58 | notFound(); 59 | } 60 | 61 | const content = await loadLessonContent(lesson.file); 62 | 63 | return ; 64 | } 65 | -------------------------------------------------------------------------------- /apps/web/lib/hooks/useReducedMotion.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSyncExternalStore } from "react"; 4 | 5 | const PREFERS_REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)"; 6 | 7 | function subscribe(callback: () => void) { 8 | if (typeof window === "undefined") return () => {}; 9 | 10 | const mediaQuery = window.matchMedia(PREFERS_REDUCED_MOTION_QUERY); 11 | const handler = () => callback(); 12 | 13 | if (typeof mediaQuery.addEventListener === "function") { 14 | mediaQuery.addEventListener("change", handler); 15 | return () => mediaQuery.removeEventListener("change", handler); 16 | } 17 | 18 | // Deprecated fallback (Safari < 14). 19 | mediaQuery.addListener(handler); 20 | return () => mediaQuery.removeListener(handler); 21 | } 22 | 23 | function getSnapshot() { 24 | if (typeof window === "undefined") return false; 25 | return window.matchMedia(PREFERS_REDUCED_MOTION_QUERY).matches; 26 | } 27 | 28 | function getServerSnapshot() { 29 | return false; 30 | } 31 | 32 | /** 33 | * Hook to detect user's reduced motion preference. 34 | * Respects the `prefers-reduced-motion` media query for accessibility. 35 | * 36 | * @returns boolean - true if user prefers reduced motion 37 | * 38 | * @example 39 | * const prefersReducedMotion = useReducedMotion(); 40 | * const animation = prefersReducedMotion ? {} : { y: [20, 0], opacity: [0, 1] }; 41 | */ 42 | export function useReducedMotion(): boolean { 43 | return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); 44 | } 45 | 46 | /** 47 | * Returns animation props that respect reduced motion preferences. 48 | * Use this to wrap animation objects for accessible animations. 49 | * 50 | * @param animation - The animation properties when motion is enabled 51 | * @returns The animation or an empty object if reduced motion is preferred 52 | * 53 | * @example 54 | * const fadeUp = useAccessibleAnimation({ y: 20, opacity: 0 }); 55 | * 56 | */ 57 | export function useAccessibleAnimation(animation: T): T | Record { 58 | const prefersReducedMotion = useReducedMotion(); 59 | return prefersReducedMotion ? {} : animation; 60 | } 61 | -------------------------------------------------------------------------------- /acfs/onboard/lessons/00_welcome.md: -------------------------------------------------------------------------------- 1 | # Welcome to ACFS 2 | 3 | **Goal:** Understand what you have and what you're about to learn. 4 | 5 | --- 6 | 7 | > Prefer the web version? Visit https://agent-flywheel.com/learn for the Learning Hub, 8 | > or return to the setup wizard at https://agent-flywheel.com/wizard/os-selection. 9 | 10 | ## What You Now Have 11 | 12 | Congratulations! You've just set up a fully-armed **agentic engineering workstation**. 13 | 14 | Here's what's installed on your VPS: 15 | 16 | - **A beautiful terminal** with zsh, Oh My Zsh, and Powerlevel10k 17 | - **Modern CLI tools** like lsd, bat, ripgrep, fzf, and zoxide 18 | - **Language runtimes** for JavaScript (Bun), Python (uv), Rust, and Go 19 | - **Three coding agents** ready to help you build: 20 | - Claude Code (`cc`) 21 | - Codex CLI (`cod`) 22 | - Gemini CLI (`gmi`) 23 | - **The Dicklesworthstone stack** for agent coordination and memory 24 | 25 | --- 26 | 27 | ## The Mental Model 28 | 29 | Think of your setup like this: 30 | 31 | ``` 32 | Your laptop (cockpit) --SSH--> VPS (the engine room) 33 | | 34 | +-- tmux sessions (persistence) 35 | | 36 | +-- coding agents (the workers) 37 | | 38 | +-- NTM (the orchestrator) 39 | ``` 40 | 41 | Your laptop is just the remote control. The real work happens on the VPS. 42 | 43 | If your SSH connection drops? No problem. Your work continues in tmux. 44 | 45 | --- 46 | 47 | ## What This Tutorial Will Teach You 48 | 49 | 1. **Linux basics** - navigating the filesystem 50 | 2. **SSH fundamentals** - staying connected 51 | 3. **tmux essentials** - persistent sessions 52 | 4. **Agent commands** - talking to Claude, Codex, and Gemini 53 | 5. **NTM mastery** - orchestrating multiple agents 54 | 6. **The flywheel workflow** - putting it all together 55 | 56 | --- 57 | 58 | ## Ready? 59 | 60 | Run the next lesson: 61 | 62 | ```bash 63 | onboard 1 64 | ``` 65 | 66 | Or continue in the TUI menu. 67 | 68 | --- 69 | 70 | *Tip: If you ever break something, you can delete this VPS and re-run ACFS. That's the beauty of VPS development!* 71 | -------------------------------------------------------------------------------- /acfs/onboard/lessons/01_linux_basics.md: -------------------------------------------------------------------------------- 1 | # Linux Basics 2 | 3 | **Goal:** Navigate the filesystem like a pro in 3 minutes. 4 | 5 | --- 6 | 7 | ## Where Am I? 8 | 9 | ```bash 10 | pwd 11 | ``` 12 | 13 | This prints your current directory. You should see `/home/ubuntu`. 14 | 15 | --- 16 | 17 | ## What's Here? 18 | 19 | ```bash 20 | ls 21 | ``` 22 | 23 | With ACFS, this is aliased to `lsd` which shows beautiful icons. 24 | 25 | Try these variations: 26 | - `ll` - long format with details 27 | - `la` - show hidden files 28 | - `tree` - tree view of directories 29 | 30 | --- 31 | 32 | ## Moving Around 33 | 34 | ```bash 35 | cd /data/projects # Go to the projects directory 36 | cd ~ # Go home (shortcut) 37 | cd .. # Go up one level 38 | cd - # Go to previous directory 39 | ``` 40 | 41 | **Pro tip:** With zoxide installed, you can use `z projects` to jump to `/data/projects` after visiting it once! 42 | 43 | --- 44 | 45 | ## Creating Things 46 | 47 | ```bash 48 | mkdir my-project # Create a directory 49 | mkcd my-project # Create AND cd into it (ACFS function) 50 | touch file.txt # Create an empty file 51 | ``` 52 | 53 | --- 54 | 55 | ## Viewing Files 56 | 57 | ```bash 58 | cat file.txt # Print entire file (aliased to bat) 59 | less file.txt # Scroll through file (q to quit) 60 | head -20 file.txt # First 20 lines 61 | tail -20 file.txt # Last 20 lines 62 | ``` 63 | 64 | --- 65 | 66 | ## Deleting Things (Careful!) 67 | 68 | ```bash 69 | rm file.txt # Delete a file 70 | rm -rf directory/ # Delete a directory (DANGEROUS!) 71 | ``` 72 | 73 | **Warning:** There's no trash can in Linux. Deleted = gone. 74 | 75 | --- 76 | 77 | ## Searching 78 | 79 | ```bash 80 | rg "search term" # Search file contents (ripgrep) 81 | fd "pattern" # Find files by name (fd) 82 | ``` 83 | 84 | --- 85 | 86 | ## Verify You Learned It 87 | 88 | Try this sequence: 89 | 90 | ```bash 91 | cd /data/projects 92 | mkcd acfs-test 93 | pwd 94 | touch hello.txt 95 | ls 96 | cat hello.txt 97 | cd .. 98 | ls 99 | ``` 100 | 101 | If that all worked, you're ready for the next lesson! 102 | 103 | --- 104 | 105 | ## Next 106 | 107 | ```bash 108 | onboard 2 109 | ``` 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # =========================================== 2 | # ACFS - Agentic Coding Flywheel Setup 3 | # =========================================== 4 | 5 | # ----- Dependencies ----- 6 | node_modules/ 7 | .pnp 8 | .pnp.js 9 | 10 | # ----- Build outputs ----- 11 | dist/ 12 | build/ 13 | out/ 14 | .next/ 15 | .vercel/ 16 | 17 | # ----- Package manager ----- 18 | # Using bun exclusively 19 | bun.lockb 20 | *.lock 21 | !bun.lock 22 | 23 | # ----- Environment & secrets ----- 24 | .env 25 | .env.* 26 | !.env.example 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # ----- IDE & Editor ----- 33 | .idea/ 34 | .vscode/ 35 | *.swp 36 | *.swo 37 | *~ 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # ----- Testing ----- 42 | coverage/ 43 | .nyc_output/ 44 | *.lcov 45 | 46 | # ----- TypeScript ----- 47 | *.tsbuildinfo 48 | next-env.d.ts 49 | 50 | # ----- Logs ----- 51 | logs/ 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | .pnpm-debug.log* 57 | 58 | # ----- Temporary files ----- 59 | tmp/ 60 | temp/ 61 | *.tmp 62 | *.temp 63 | 64 | # ----- OS generated ----- 65 | .DS_Store 66 | .DS_Store? 67 | ._* 68 | .Spotlight-V100 69 | .Trashes 70 | ehthumbs.db 71 | 72 | # ----- Cache ----- 73 | .cache/ 74 | .eslintcache 75 | .turbo/ 76 | 77 | # ----- Beads ----- 78 | # Keep .beads directory but ignore sensitive data 79 | .beads/cache/ 80 | .beads/*.log 81 | .beads/*.sock 82 | .beads/*.sock.* 83 | 84 | # ----- Local testing artifacts ----- 85 | tests/vm/.vagrant/ 86 | tests/vm/*.box 87 | tests/vm/ubuntu-* 88 | apps/web/mobile-screenshots/ 89 | apps/web/test-mobile.ts 90 | apps/web/playwright-report/ 91 | apps/web/test-results/ 92 | research_screenshots/ 93 | 94 | # ----- Installer test outputs ----- 95 | *.log 96 | install_*.log 97 | 98 | # ----- Python (for any scripts) ----- 99 | __pycache__/ 100 | *.py[cod] 101 | *$py.class 102 | .Python 103 | *.so 104 | .venv/ 105 | venv/ 106 | ENV/ 107 | 108 | # ----- Rust (if building tools) ----- 109 | target/ 110 | Cargo.lock 111 | *.rs.bk 112 | 113 | # ----- Debug ----- 114 | *.pdb 115 | 116 | # ----- Source conversations ----- 117 | 2025-12-20-chatgpt-website_tech_stack_update.md 118 | 119 | # bv (beads viewer) local config and caches 120 | .bv/ 121 | .vercel 122 | .env*.local 123 | -------------------------------------------------------------------------------- /apps/web/components/query-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; 5 | import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; 6 | import { useState, useEffect, type ReactNode } from "react"; 7 | 8 | function makeQueryClient() { 9 | return new QueryClient({ 10 | defaultOptions: { 11 | queries: { 12 | // With localStorage persistence, we want long cache times 13 | staleTime: Infinity, 14 | gcTime: Infinity, 15 | // Don't refetch on window focus for localStorage-backed data 16 | refetchOnWindowFocus: false, 17 | refetchOnReconnect: false, 18 | retry: false, 19 | }, 20 | }, 21 | }); 22 | } 23 | 24 | export function QueryProvider({ children }: { children: ReactNode }) { 25 | const [queryClient] = useState(() => makeQueryClient()); 26 | const [persister, setPersister] = useState | null>(null); 27 | 28 | // Create persister only after mount to avoid hydration mismatch 29 | // Server and client both render QueryClientProvider initially, 30 | // then we upgrade to PersistQueryClientProvider on client after mount 31 | useEffect(() => { 32 | const storagePersister = createSyncStoragePersister({ 33 | storage: window.localStorage, 34 | key: "acfs-query-cache", 35 | }); 36 | 37 | // Defer state update to avoid setState-in-effect lint violations. 38 | let cancelled = false; 39 | Promise.resolve().then(() => { 40 | if (!cancelled) setPersister(storagePersister); 41 | }); 42 | 43 | return () => { 44 | cancelled = true; 45 | }; 46 | }, []); 47 | 48 | // Always render PersistQueryClientProvider once we have a persister, 49 | // but start with QueryClientProvider for SSR/hydration consistency 50 | if (persister) { 51 | return ( 52 | 56 | {children} 57 | 58 | ); 59 | } 60 | 61 | // Initial render (SSR + first client render before useEffect) 62 | return ( 63 | {children} 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /apps/web/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | /** 4 | * Playwright configuration for Agent Flywheel web e2e testing. 5 | * @see https://playwright.dev/docs/test-configuration 6 | */ 7 | const isCI = !!process.env.CI; 8 | 9 | const DEFAULT_PORT = 3000; 10 | const parsedPort = Number.parseInt(process.env.PW_PORT || process.env.PORT || "", 10); 11 | const port = Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : DEFAULT_PORT; 12 | 13 | const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://localhost:${port}`; 14 | 15 | const webServerCommand = (() => { 16 | // Default to production server for stability (matches CI behavior). 17 | // Override locally with PW_USE_DEV_SERVER=1 if needed. 18 | if (!isCI && process.env.PW_USE_DEV_SERVER === "1") { 19 | return `bun run dev -- --port ${port}`; 20 | } 21 | return `bun run build && bun run start -- -p ${port}`; 22 | })(); 23 | 24 | export default defineConfig({ 25 | testDir: "./e2e", 26 | fullyParallel: true, 27 | forbidOnly: !!process.env.CI, 28 | retries: process.env.CI ? 2 : 0, 29 | workers: process.env.CI ? 1 : undefined, 30 | reporter: process.env.CI ? "github" : "html", 31 | // Increase timeout for CI environments 32 | timeout: process.env.CI ? 60000 : 30000, 33 | // Give actions more time in CI 34 | expect: { 35 | timeout: process.env.CI ? 10000 : 5000, 36 | }, 37 | 38 | use: { 39 | baseURL, 40 | trace: "on-first-retry", 41 | screenshot: "only-on-failure", 42 | // Increase action timeout for CI 43 | actionTimeout: process.env.CI ? 15000 : 10000, 44 | navigationTimeout: process.env.CI ? 30000 : 15000, 45 | }, 46 | 47 | projects: [ 48 | { 49 | name: "chromium", 50 | use: { ...devices["Desktop Chrome"] }, 51 | }, 52 | { 53 | name: "firefox", 54 | use: { ...devices["Desktop Firefox"] }, 55 | }, 56 | { 57 | name: "webkit", 58 | use: { ...devices["Desktop Safari"] }, 59 | }, 60 | { 61 | name: "Mobile Chrome", 62 | use: { ...devices["Pixel 5"] }, 63 | }, 64 | { 65 | name: "Mobile Safari", 66 | use: { ...devices["iPhone 12"] }, 67 | }, 68 | ], 69 | 70 | webServer: { 71 | command: webServerCommand, 72 | url: baseURL, 73 | reuseExistingServer: !isCI, 74 | timeout: 180000, // 3 minutes for build + start 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /apps/web/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /docs/tests/fixtures_catalog.md: -------------------------------------------------------------------------------- 1 | # Real Fixtures Catalog (No Mocks) 2 | 3 | This catalog lists **existing** files used as real fixtures for tests. Do not 4 | introduce synthetic mocks for core behavior; prefer these artifacts. 5 | 6 | ## Manifests 7 | - `acfs.manifest.yaml` — Canonical module manifest (full real data set). 8 | 9 | ## Generated Artifacts (Real outputs) 10 | - `scripts/generated/manifest_index.sh` — Deterministic module index used by selection logic. 11 | - `scripts/generated/install_base.sh` — Generated module functions (base). 12 | - `scripts/generated/install_shell.sh` — Generated module functions (shell). 13 | - `scripts/generated/install_lang.sh` — Generated module functions (languages). 14 | - `scripts/generated/install_cli.sh` — Generated module functions (CLI tools). 15 | - `scripts/generated/install_agents.sh` — Generated module functions (agents). 16 | - `scripts/generated/install_cloud.sh` — Generated module functions (cloud). 17 | - `scripts/generated/install_db.sh` — Generated module functions (db). 18 | - `scripts/generated/install_stack.sh` — Generated module functions (stack). 19 | - `scripts/generated/install_tools.sh` — Generated module functions (tools). 20 | - `scripts/generated/install_users.sh` — Generated module functions (users). 21 | - `scripts/generated/install_acfs.sh` — Generated module functions (acfs). 22 | - `scripts/generated/doctor_checks.sh` — Generated doctor checks list. 23 | 24 | ## Installer + Libs (Real logic under test) 25 | - `install.sh` — Orchestrator entrypoint and CLI parsing. 26 | - `scripts/lib/install_helpers.sh` — Selection + run helpers. 27 | - `scripts/lib/contract.sh` — Module contract enforcement. 28 | - `scripts/lib/security.sh` — Checksum verification logic. 29 | - `scripts/preflight.sh` — Preflight validation script. 30 | 31 | ## E2E Harnesses (Real integration scripts) 32 | - `tests/vm/test_install_ubuntu.sh` — Installer E2E (Docker). 33 | - `tests/vm/test_acfs_update.sh` — Update E2E (Docker). 34 | - `tests/vm/resume_checks.sh` — Resume logic checks. 35 | 36 | ## Web E2E Fixtures 37 | - `apps/web/e2e/wizard-flow.spec.ts` — Playwright wizard flow tests. 38 | - `apps/web/playwright.config.ts` — Runner configuration. 39 | 40 | ## Notes 41 | - Generated artifacts should be regenerated via `bun run generate` before tests when manifest changes. 42 | - Avoid “fake” YAMLs; if smaller fixtures are needed, derive them from real manifest subsets and commit them as real files. 43 | -------------------------------------------------------------------------------- /.beads/config.yaml: -------------------------------------------------------------------------------- 1 | # Beads Configuration File 2 | # This file configures default behavior for all bd commands in this repository 3 | # All settings can also be set via environment variables (BD_* prefix) 4 | # or overridden with command-line flags 5 | 6 | # Issue prefix for this repository (used by bd init) 7 | # If not set, bd init will auto-detect from directory name 8 | # Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. 9 | # issue-prefix: "" 10 | 11 | # Use no-db mode: load from JSONL, no SQLite, write back after each command 12 | # When true, bd will use .beads/issues.jsonl as the source of truth 13 | # instead of SQLite database 14 | # no-db: false 15 | 16 | # Disable daemon for RPC communication (forces direct database access) 17 | # no-daemon: false 18 | 19 | # Disable auto-flush of database to JSONL after mutations 20 | # no-auto-flush: false 21 | 22 | # Disable auto-import from JSONL when it's newer than database 23 | # no-auto-import: false 24 | 25 | # Enable JSON output by default 26 | # json: false 27 | 28 | # Default actor for audit trails (overridden by BD_ACTOR or --actor) 29 | # actor: "" 30 | 31 | # Path to database (overridden by BEADS_DB or --db) 32 | # db: "" 33 | 34 | # Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) 35 | # auto-start-daemon: true 36 | 37 | # Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) 38 | # flush-debounce: "5s" 39 | 40 | # Git branch for beads commits (bd sync will commit to this branch) 41 | # IMPORTANT: Set this for team projects so all clones use the same sync branch. 42 | # This setting persists across clones (unlike database config which is gitignored). 43 | # Can also use BEADS_SYNC_BRANCH env var for local override. 44 | # If not set, bd sync will require you to run 'bd config set sync.branch '. 45 | # sync-branch: "beads-sync" 46 | 47 | # Multi-repo configuration (experimental - bd-307) 48 | # Allows hydrating from multiple repositories and routing writes to the correct JSONL 49 | # repos: 50 | # primary: "." # Primary repo (where this database lives) 51 | # additional: # Additional repos to hydrate from (read-only) 52 | # - ~/beads-planning # Personal planning repo 53 | # - ~/work-planning # Work planning repo 54 | 55 | # Integration settings (access with 'bd config get/set') 56 | # These are stored in the database, not in this file: 57 | # - jira.url 58 | # - jira.project 59 | # - linear.url 60 | # - linear.api-key 61 | # - github.org 62 | # - github.repo 63 | -------------------------------------------------------------------------------- /.beads/README.md: -------------------------------------------------------------------------------- 1 | # Beads - AI-Native Issue Tracking 2 | 3 | Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. 4 | 5 | ## What is Beads? 6 | 7 | Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. 8 | 9 | **Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) 10 | 11 | ## Quick Start 12 | 13 | ### Essential Commands 14 | 15 | ```bash 16 | # Create new issues 17 | bd create "Add user authentication" 18 | 19 | # View all issues 20 | bd list 21 | 22 | # View issue details 23 | bd show 24 | 25 | # Update issue status 26 | bd update --status in_progress 27 | bd update --status done 28 | 29 | # Sync with git remote 30 | bd sync 31 | ``` 32 | 33 | ### Working with Issues 34 | 35 | Issues in Beads are: 36 | - **Git-native**: Stored in `.beads/issues.jsonl` and synced like code 37 | - **AI-friendly**: CLI-first design works perfectly with AI coding agents 38 | - **Branch-aware**: Issues can follow your branch workflow 39 | - **Always in sync**: Auto-syncs with your commits 40 | 41 | ## Why Beads? 42 | 43 | ✨ **AI-Native Design** 44 | - Built specifically for AI-assisted development workflows 45 | - CLI-first interface works seamlessly with AI coding agents 46 | - No context switching to web UIs 47 | 48 | 🚀 **Developer Focused** 49 | - Issues live in your repo, right next to your code 50 | - Works offline, syncs when you push 51 | - Fast, lightweight, and stays out of your way 52 | 53 | 🔧 **Git Integration** 54 | - Automatic sync with git commits 55 | - Branch-aware issue tracking 56 | - Intelligent JSONL merge resolution 57 | 58 | ## Get Started with Beads 59 | 60 | Try Beads in your own projects: 61 | 62 | ```bash 63 | # Install Beads 64 | curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash 65 | 66 | # Initialize in your repo 67 | bd init 68 | 69 | # Create your first issue 70 | bd create "Try out Beads" 71 | ``` 72 | 73 | ## Learn More 74 | 75 | - **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) 76 | - **Quick Start Guide**: Run `bd quickstart` 77 | - **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) 78 | 79 | --- 80 | 81 | *Beads: Issue tracking that moves at the speed of thought* ⚡ 82 | -------------------------------------------------------------------------------- /apps/web/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | /** 9 | * Safe localStorage access utilities. 10 | * Handles cases where localStorage is unavailable (SSR, private browsing, quota exceeded). 11 | */ 12 | 13 | /** 14 | * Safely get an item from localStorage. 15 | * Returns null if localStorage is unavailable or the key doesn't exist. 16 | */ 17 | export function safeGetItem(key: string): string | null { 18 | if (typeof window === "undefined") return null; 19 | try { 20 | return localStorage.getItem(key); 21 | } catch { 22 | // localStorage unavailable (private browsing, quota exceeded, etc.) 23 | return null; 24 | } 25 | } 26 | 27 | /** 28 | * Safely set an item in localStorage. 29 | * Silently fails if localStorage is unavailable. 30 | */ 31 | export function safeSetItem(key: string, value: string): boolean { 32 | if (typeof window === "undefined") return false; 33 | try { 34 | localStorage.setItem(key, value); 35 | return true; 36 | } catch { 37 | // localStorage unavailable or quota exceeded 38 | return false; 39 | } 40 | } 41 | 42 | /** 43 | * Append the current URL query string to a path. 44 | * Useful for preserving wizard state when localStorage is unavailable. 45 | */ 46 | export function withCurrentSearch(path: string): string { 47 | if (typeof window === "undefined") return path; 48 | const search = window.location.search; 49 | return search ? `${path}${search}` : path; 50 | } 51 | 52 | /** 53 | * Safely remove an item from localStorage. 54 | */ 55 | export function safeRemoveItem(key: string): boolean { 56 | if (typeof window === "undefined") return false; 57 | try { 58 | localStorage.removeItem(key); 59 | return true; 60 | } catch { 61 | return false; 62 | } 63 | } 64 | 65 | /** 66 | * Safely parse JSON from localStorage. 67 | * Returns null if parsing fails or value doesn't exist. 68 | */ 69 | export function safeGetJSON(key: string): T | null { 70 | const value = safeGetItem(key); 71 | if (!value) return null; 72 | try { 73 | return JSON.parse(value) as T; 74 | } catch { 75 | // Invalid JSON 76 | return null; 77 | } 78 | } 79 | 80 | /** 81 | * Safely store JSON in localStorage. 82 | */ 83 | export function safeSetJSON(key: string, value: unknown): boolean { 84 | try { 85 | return safeSetItem(key, JSON.stringify(value)); 86 | } catch { 87 | // JSON.stringify failed (circular reference, etc.) 88 | return false; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /docs/tests/coverage_matrix.md: -------------------------------------------------------------------------------- 1 | # Test Coverage Matrix (No Mocks) 2 | 3 | This matrix defines **real-data** coverage goals. “No mocks” means tests must use 4 | actual files, generated scripts, and real flags/CLI behavior rather than stubs. 5 | 6 | ## Legend 7 | - **Current**: observed coverage today 8 | - **Target**: desired coverage state 9 | - **Fixtures**: real files used by tests 10 | 11 | ## Matrix 12 | 13 | | Component | Current | Target | Fixtures (real files) | Notes | 14 | | --- | --- | --- | --- | --- | 15 | | `packages/manifest` schema + parser | Partial (`test-edge-cases.ts`) | Full unit suite | `acfs.manifest.yaml`, `docs/MANIFEST_SCHEMA_VNEXT.md`, generated `scripts/generated/manifest_index.sh` | Add fixture-driven tests for schema fields + errors | 16 | | `packages/manifest` generator outputs | Minimal | Full unit suite | `acfs.manifest.yaml`, `scripts/generated/*.sh` | Validate deterministic ordering + content | 17 | | `scripts/lib/install_helpers.sh` (selection) | None | Unit + integration | `scripts/generated/manifest_index.sh` | Exercise `--only/--skip/--only-phase/--no-deps/--print-plan` via real arrays | 18 | | `scripts/lib/contract.sh` | None | Unit | `scripts/lib/contract.sh` | Verify error cases + required env checks | 19 | | `scripts/lib/security.sh` | Partial | Unit + integration | `checksums.yaml` | Verify checksum load + verify output (no network mocks) | 20 | | `scripts/preflight.sh` | None | Unit + integration | `scripts/preflight.sh` | Run in controlled envs; validate expected warnings/errors | 21 | | `install.sh` selection/introspection | Partial (manual) | Unit + integration | `install.sh`, `scripts/generated/manifest_index.sh` | Ensure plan output is stable + no state mutation | 22 | | `tests/vm/test_install_ubuntu.sh` | Present | Enhanced E2E | `tests/vm/test_install_ubuntu.sh` | Add explicit selection + bootstrap cases | 23 | | `tests/vm/test_acfs_update.sh` | Present | Enhanced E2E | `tests/vm/test_acfs_update.sh` | Standardize logging + artifacts | 24 | | `tests/vm/resume_checks.sh` | Present | Enhanced E2E | `tests/vm/resume_checks.sh` | Add selection/plan integration when ready | 25 | | Web wizard E2E | Present | Maintain + richer artifacts | `apps/web/e2e/wizard-flow.spec.ts` | Add runner script with logs + traces | 26 | 27 | ## Gaps (Immediate) 28 | - No unit coverage for bash libs (selection/contract/security/preflight). 29 | - Generator output tests rely on manual verification. 30 | - E2E logging is not standardized (timestamps/sections/artifacts). 31 | 32 | ## Next Steps 33 | - Implement fixture catalog (docs/tests/fixtures_catalog.md). 34 | - Create test harness for bash libs (shell-based, real files). 35 | - Add standardized logging helper for `tests/vm` scripts. 36 | -------------------------------------------------------------------------------- /apps/web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata, Viewport } from "next"; 2 | import { Suspense } from "react"; 3 | import { JetBrains_Mono, Instrument_Sans } from "next/font/google"; 4 | import { QueryProvider } from "@/components/query-provider"; 5 | import { AnalyticsProvider } from "@/components/analytics-provider"; 6 | import { ThirdPartyScripts } from "@/components/third-party-scripts"; 7 | import { MotionProvider } from "@/components/motion/motion-provider"; 8 | import "./globals.css"; 9 | 10 | const jetbrainsMono = JetBrains_Mono({ 11 | variable: "--font-jetbrains", 12 | subsets: ["latin"], 13 | display: "swap", 14 | }); 15 | 16 | const instrumentSans = Instrument_Sans({ 17 | variable: "--font-instrument", 18 | subsets: ["latin"], 19 | display: "swap", 20 | }); 21 | 22 | export const metadata: Metadata = { 23 | title: "Agent Flywheel - Agentic Coding Setup", 24 | description: 25 | "Transform a fresh cloud server into a fully-configured agentic coding environment. Claude Code, OpenAI Codex, Google Gemini: all pre-configured with 30+ modern developer tools. All totally free and open-source.", 26 | keywords: [ 27 | "VPS setup", 28 | "AI coding", 29 | "Claude", 30 | "Codex", 31 | "Gemini", 32 | "developer tools", 33 | "agentic coding", 34 | "Agent Flywheel", 35 | ], 36 | authors: [{ name: "Jeffrey Emanuel", url: "https://jeffreyemanuel.com/" }], 37 | openGraph: { 38 | title: "Agent Flywheel - Agentic Coding Setup", 39 | description: 40 | "From zero to fully-configured agentic coding VPS in 30 minutes.", 41 | type: "website", 42 | }, 43 | twitter: { 44 | card: "summary_large_image", 45 | title: "Agent Flywheel - Agentic Coding Setup", 46 | description: 47 | "From zero to fully-configured agentic coding VPS in 30 minutes.", 48 | }, 49 | }; 50 | 51 | export const viewport: Viewport = { 52 | themeColor: "#0a0a12", 53 | colorScheme: "dark", 54 | viewportFit: "cover", // Enable safe area insets for notch/home bar 55 | }; 56 | 57 | export default function RootLayout({ 58 | children, 59 | }: Readonly<{ 60 | children: React.ReactNode; 61 | }>) { 62 | return ( 63 | 64 | 67 | {/* Noise texture overlay */} 68 |
69 | 70 | 71 | 72 | 73 | {children} 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /acfs/onboard/lessons/03_tmux_basics.md: -------------------------------------------------------------------------------- 1 | # tmux Basics 2 | 3 | **Goal:** Never lose work when SSH drops. 4 | 5 | --- 6 | 7 | ## What Is tmux? 8 | 9 | tmux is a **terminal multiplexer**. It lets you: 10 | 11 | 1. Keep sessions running after you disconnect 12 | 2. Split your terminal into panes 13 | 3. Have multiple windows in one connection 14 | 15 | --- 16 | 17 | ## Essential Commands 18 | 19 | ### Start a New Session 20 | 21 | ```bash 22 | tmux new -s myproject 23 | ``` 24 | 25 | This creates a session named "myproject". 26 | 27 | ### Detach (Leave Session Running) 28 | 29 | Press: `Ctrl+a` then `d` 30 | 31 | Your session continues running in the background! 32 | 33 | ### List Sessions 34 | 35 | ```bash 36 | tmux ls 37 | ``` 38 | 39 | ### Reattach to a Session 40 | 41 | ```bash 42 | tmux attach -t myproject 43 | ``` 44 | 45 | Or just: 46 | ```bash 47 | tmux a 48 | ``` 49 | (Attaches to the most recent session) 50 | 51 | --- 52 | 53 | ## The Prefix Key 54 | 55 | In ACFS, the prefix key is `Ctrl+a` (not the default `Ctrl+b`). 56 | 57 | All tmux commands start with the prefix. 58 | 59 | --- 60 | 61 | ## Splitting Panes 62 | 63 | | Keys | Action | 64 | |------|--------| 65 | | `Ctrl+a` then `|` | Split vertically | 66 | | `Ctrl+a` then `-` | Split horizontally | 67 | | `Ctrl+a` then `h/j/k/l` | Move between panes | 68 | | `Ctrl+a` then `x` | Close current pane | 69 | 70 | --- 71 | 72 | ## Windows (Tabs) 73 | 74 | | Keys | Action | 75 | |------|--------| 76 | | `Ctrl+a` then `c` | New window | 77 | | `Ctrl+a` then `n` | Next window | 78 | | `Ctrl+a` then `p` | Previous window | 79 | | `Ctrl+a` then `0-9` | Go to window number | 80 | 81 | --- 82 | 83 | ## Copy Mode (Scrolling) 84 | 85 | | Keys | Action | 86 | |------|--------| 87 | | `Ctrl+a` then `[` | Enter copy mode | 88 | | Use arrow keys or `j/k` | Scroll | 89 | | `q` | Exit copy mode | 90 | | `v` | Start selection | 91 | | `y` | Copy selection | 92 | 93 | --- 94 | 95 | ## Try It Now 96 | 97 | ```bash 98 | # Create a session 99 | tmux new -s practice 100 | 101 | # Split the screen 102 | # Press Ctrl+a, then | 103 | 104 | # Move to the new pane 105 | # Press Ctrl+a, then l 106 | 107 | # Run something 108 | ls -la 109 | 110 | # Detach 111 | # Press Ctrl+a, then d 112 | 113 | # Verify it's still running 114 | tmux ls 115 | 116 | # Reattach 117 | tmux attach -t practice 118 | ``` 119 | 120 | --- 121 | 122 | ## Why This Matters for Agents 123 | 124 | Your coding agents (Claude, Codex, Gemini) run in tmux panes. 125 | 126 | If SSH drops, they keep running. When you reconnect and reattach, they're still there! 127 | 128 | --- 129 | 130 | ## Next 131 | 132 | Now let's meet your coding agents: 133 | 134 | ```bash 135 | onboard 4 136 | ``` 137 | -------------------------------------------------------------------------------- /apps/web/components/third-party-scripts.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Script from 'next/script'; 4 | import { useEffect } from 'react'; 5 | import { usePathname } from 'next/navigation'; 6 | import { Analytics } from '@vercel/analytics/react'; 7 | import { SpeedInsights } from '@vercel/speed-insights/next'; 8 | 9 | // Environment variables for third-party services 10 | // Note: GA4 is handled by AnalyticsProvider to avoid duplicate scripts 11 | const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID; 12 | const CLARITY_PROJECT_ID = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID; 13 | 14 | /** 15 | * Third-party scripts manager (non-GA4) 16 | * Handles: GTM, Microsoft Clarity, Vercel Analytics 17 | * Note: GA4 is loaded by AnalyticsProvider for comprehensive tracking 18 | */ 19 | export function ThirdPartyScripts() { 20 | const pathname = usePathname(); 21 | 22 | // Track virtual pageviews for GTM on SPA navigation 23 | useEffect(() => { 24 | if (GTM_ID && window.dataLayer) { 25 | window.dataLayer.push({ 26 | event: 'virtual_pageview', 27 | page_path: pathname, 28 | page_title: document.title, 29 | }); 30 | } 31 | }, [pathname]); 32 | 33 | return ( 34 | <> 35 | {/* Google Tag Manager - for advanced tag management */} 36 | {GTM_ID && ( 37 |