diff --git a/.env.example b/.env.example index 800f16d3..e21ba5bf 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,16 @@ FACILITY_OAUTH_ISSUER=http://localhost:3400 FACILITY_OAUTH_JWKS= MCP_PUBLIC_URL=http://localhost:4400/mcp +# Local repositories: directories (":"-separated) whose Git repositories may be registered without +# GitHub. Empty disables local repositories. See apps/docs/docs/self-host/local-mode.md. +FACILITY_LOCAL_REPOSITORY_ROOTS= +# Optional: user ids allowed to own registered repositories (default: the Facility process user). +FACILITY_LOCAL_REPOSITORY_OWNER_UIDS= +FACILITY_LOCAL_GIT_NAME= +FACILITY_LOCAL_GIT_EMAIL= +# The API binds loopback by default; widen only for a deliberately exposed deployment. +FACILITY_LISTEN_HOST=localhost + # GitHub App authentication and full installation capability. GITHUB_APP_ID= GITHUB_APP_PRIVATE_KEY= diff --git a/Dockerfile b/Dockerfile index d24c1854..b49e8db7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -122,4 +122,7 @@ RUN printf '%s\n' \ # it proves that its production `postgres` dependency resolves. RUN ["facility", "instance", "bootstrap", "--help"] EXPOSE 4400 +# Inside a container the API must accept the container network; the host decides +# what is published (docker-compose.yml publishes on loopback by default). +ENV FACILITY_LISTEN_HOST=0.0.0.0 CMD ["node", "dist/start.js"] diff --git a/apps/docs/docs/guides/local-repository.md b/apps/docs/docs/guides/local-repository.md new file mode 100644 index 00000000..01cb5345 --- /dev/null +++ b/apps/docs/docs/guides/local-repository.md @@ -0,0 +1,126 @@ +--- +title: Local repositories +--- + +# Work on a local repository + +Facility can run stories against a Git repository on the machine that runs Facility. No GitHub +account, App, installation, hosted remote, or webhook is involved. Agents work in persistent local +Docker workspaces, you review and revise their commits in Facility, and you import the approved +result into your own repository. + +Model calls still go to the cloud provider configured for the project's Claude Code or Codex +engine, and they can include code and prompts. Repositories, workspaces, conversations, and review +stay on your machine. + +An operator must enable local repositories first; see [Local mode](../self-host/local-mode.md). + +## What Facility imports + +Facility imports **committed history from the default branch only**. Uncommitted edits and +untracked files in your checkout are never copied. Facility never writes to your repository: it +reads committed objects into its own staging copy and packages them as a Git bundle for the +workspace. Repository hooks, `core.fsmonitor`, and system or global Git configuration are ignored +while it reads. + +Submodules and Git LFS content are not imported yet. Registration and each import report them +explicitly; submodule directories appear empty and LFS files appear as pointer files. + +## 1. Register the repository + +The repository needs at least one commit and must be inside a directory the operator approved. + +From the UI, choose **New project → Use a Git repository on this machine**, then enter a project +name and the repository's absolute path on the Facility host. With the CLI and an API key that has +`repos:write`: + +```bash +export FACILITY_API_KEY=fak_... +facility repos add-local ~/code/shop --project=proj_... --alias=shop +``` + +The CLI lists the uncommitted and untracked paths that will stay behind before it registers +anything. The alias names the repository in `.facility.yml` as `local:`, so machine paths +never appear in committed configuration. A project uses either GitHub repositories or local +repositories, never both, and one organization owns a given host path. + +## 2. Add the starter configuration + +Facility proposes starter configuration as a patch rather than a pull request. The UI shows the +patch after registration; the API returns it from +`POST /v1/projects/:projectId/repos/:repoId/local-kickstart`. You can also write the files +directly: + +```bash +facility init --local=shop --start="pnpm dev" +``` + +Both produce `.facility.yml` with `primary: local:shop` and three agents (`architect`, `builder`, +and `reviewer`) that commit to the story branch and never push, open pull requests, or run `gh`. +Review the files, then commit them on the default branch. Facility reads only committed +configuration. + +Add `environment.checks` to run named checks against a story's exact commit during review: + +```yaml +environment: + start: pnpm dev + checks: + test: pnpm test + lint: pnpm lint +``` + +## 3. Run a story + +Start a story from the UI, MCP, or API as usual. On the first turn Facility resolves the default +branch once, reads `.facility.yml` and the agent catalog at that commit, imports the same commit +into the workspace, and creates the story branch from it. The turn's events record the commit and +configuration hash. + +Later turns keep the workspace's history and uncommitted work. Facility never resets a story +branch when your checkout changes. + +## 4. Review, revise, and approve + +Open **Review and export** on the story page. It shows the commits and files since the imported +source, uncommitted changes, check results for the current head, and the approval state. Opening +it wakes a suspended workspace only for people who can run workspaces. + +- **Request changes** records your note and opens the composer so you can ask an agent for the + revision. +- **Run checks** runs every configured check and attaches the results to the tested commit. +- **Approve** records approval of the exact head commit. Uncommitted changes are unfinished work + and must be committed first. Any later commit makes the approval stale. + +## 5. Export and import + +**Export approved commits** packages the approved commit range as a Git bundle and a patch. Download +the bundle next to your repository and import it into a new branch: + +```bash +git fetch ./sexp_....bundle "refs/heads/facility/:refs/heads/facility-review/-" +git log --oneline main..facility-review/- +git merge facility-review/- +``` + +Each export imports to its own branch name, so repeating an export never overwrites an earlier +import, and your working tree and default branch are untouched until you merge. Resolve any merge +conflicts as usual. You can instead apply the patch with `git am`. + +Facility records the review and the export, not a merge. The workspace stays available, so you +can keep revising the story and export again. + +## Pick up changes from your repository + +When your default branch moves on, choose **Refresh source from repository**. Facility imports the +new commit as the workspace's source base and records the new revision. The story branch is not +moved. The workspace's copy of the default branch only fast-forwards when it has not diverged, so +an agent can rebase or merge deliberately. + +## Limits of the first release + +- Facility never runs directly in your checkout and never merges into it. +- Local repositories need the Docker workspace driver. +- Submodules and Git LFS content are reported, not imported. +- Pull requests, issue synchronization, GitHub triggers, and CI mirroring do not apply to local + projects. Manual, UI, MCP, and scheduled starts work. diff --git a/apps/docs/docs/reference/agent-manifest.md b/apps/docs/docs/reference/agent-manifest.md index 81454540..9181d433 100644 --- a/apps/docs/docs/reference/agent-manifest.md +++ b/apps/docs/docs/reference/agent-manifest.md @@ -15,7 +15,7 @@ catalog at an exact commit of the primary repository. name: ci-doctor description: Diagnoses and repairs failing checks on the current story pull request. engine: codex -model: gpt-5.6-sol +model: gpt-6-luna options: reasoning_effort: high enabled: true diff --git a/apps/docs/docs/reference/api.md b/apps/docs/docs/reference/api.md index 43349f5e..ba2d7a67 100644 --- a/apps/docs/docs/reference/api.md +++ b/apps/docs/docs/reference/api.md @@ -49,6 +49,31 @@ at creation; store it as a secret. Repository connections are organization- and installation-bound. A project-scoped key requesting another project receives 404 rather than a distinguishable authorization error. +### Local repositories + +- `GET /v1/local-repositories/status` reports whether local repositories are enabled and the + approved roots (`repos:write`). +- `POST /v1/projects/:projectId/repos/local` registers `{ path, alias?, defaultBranch? }` + (`repos:write`). The path must resolve inside an approved root, be owned by a trusted user, and + be a repository's top level with at least one commit. Errors include + `local_repositories_disabled`, `local_repository_outside_roots`, `local_repository_path_invalid`, + `local_repository_empty`, `local_repository_exists`, `local_repository_claimed` (another + organization registered the path), and `repository_sources_mixed`. +- `POST /v1/projects/:projectId/repos/:repoId/local-kickstart` returns starter configuration as a + `git apply` patch (`projects:kickstart`). It never writes to the repository. +- `/v1/projects/:projectId/workspace-stories/:storyId/local-review` returns commits, changed files, + uncommitted work, checks for the head commit, approval, and exports (`stories:read`; it wakes a + suspended workspace only for callers with `workspaces:execute`). Its sub-routes are `approve` + and `request-changes` (`stories:write`); `checks`, `refresh-source`, and `exports` + (`workspaces:execute`); and `exports/:exportId/bundle` and `exports/:exportId/patch` + (`stories:read`). + +Approval names one commit: `review_commit_mismatch` rejects any other, `uncommitted_changes` +rejects a dirty workspace, and exporting requires an approval of the current head +(`approval_required`, `approval_stale`). Review actions return `turn_active` while a turn is +queued or running. A local registered path that later resolves elsewhere returns +`local_repository_path_changed` or `local_repository_outside_roots` on every use. + ### Disconnect a repository `DELETE /v1/projects/:projectId/repos/:repoId` requires `repos:write` and returns diff --git a/apps/docs/docs/reference/cli.md b/apps/docs/docs/reference/cli.md index 12891439..532f5cda 100644 --- a/apps/docs/docs/reference/cli.md +++ b/apps/docs/docs/reference/cli.md @@ -34,6 +34,7 @@ values without prompts. | `--yes`, `-y` | Run without interactive confirmation. | | `--force` | Overwrite the seven Facility-owned targets. | | `--repo=` | Set the primary GitHub repository. | +| `--local[=]` | Configure a [local repository](../guides/local-repository.md) as `local:` (default: the directory name) with the local `architect`, `builder`, and `reviewer` agents. | | `--provision=` | Set optional `environment.setup`. | | `--start=` | Set required `environment.start`. | | `--preview-readiness-command=` | Set optional `environment.ready`. | @@ -56,9 +57,29 @@ Init writes only `.facility.yml` and these manifests: It preserves each existing file independently unless `--force` is explicit. Review before using force: these files are project-owned configuration, not disposable generated output. +## `facility repos add-local` + +Registers a Git repository on the machine running Facility with an existing project: + +```bash +FACILITY_API_KEY=fak_... facility repos add-local ~/code/shop --project=proj_... --alias=shop +``` + +| Flag | Purpose | +| --- | --- | +| `--project=` | Project to register with; defaults to `FACILITY_PROJECT_ID`. | +| `--alias=` | Name used as `local:` in `.facility.yml`; defaults to the directory name. | +| `--branch=` | Default branch; defaults to the branch checked out in the repository. | +| `--api=` | Facility API origin; defaults to `FACILITY_API_URL` or `http://localhost:4400`. | +| `--json` | Print the API response or error as JSON. | + +The API key is read only from `FACILITY_API_KEY`, never from a flag. Before registering, the command +lists uncommitted and untracked paths, which Facility never imports. + ## `facility doctor` -Doctor checks the local seven-file kickstart contract and exits non-zero when it finds a problem. +Doctor checks the local kickstart contract and exits non-zero when it finds a problem. For a +`local:` manifest it expects the local `architect`, `builder`, and `reviewer` agents. Use `--dir=` to inspect another checkout and `--json` for machine-readable results: ```bash diff --git a/apps/docs/docs/reference/project-manifest.md b/apps/docs/docs/reference/project-manifest.md index 0f6279c9..b11fe8f4 100644 --- a/apps/docs/docs/reference/project-manifest.md +++ b/apps/docs/docs/reference/project-manifest.md @@ -53,9 +53,15 @@ environment: | Field | Required | Contract | | --- | --- | --- | -| `primary` | Yes | `github.com/owner/repository`; an HTTPS prefix and `.git` suffix are accepted and normalized. | +| `primary` | Yes | `github.com/owner/repository` (an HTTPS prefix and `.git` suffix are accepted and normalized), or `local:` for a [local repository](../guides/local-repository.md). | | `related` | No | Array in the same format; defaults to `[]`. | +A `local:` reference names a repository by the alias it was registered under, so host paths +never appear in the manifest. Aliases use letters, digits, `.`, `_`, and `-`, and cannot end in +`.git`. A project's repositories are all GitHub or all local. Facility imports local repositories +under `repos/_local/`, from the commit it read the manifest at, and does not fetch them +again on later turns. + The primary repository owns `.facility.yml`, `.agents/`, and the story branch. Facility checks out each connected repository under `repos//`, fetches updates, and configures a workspace Git identity. The primary checkout is switched to the durable story branch. Related @@ -75,6 +81,7 @@ connected to the Facility project and available through its GitHub App installat | `stop` | No | Accepted stop command. The story lifecycle suspends provider compute and does not invoke it automatically. | | `seed` | No | Shell command run after `setup` when setup is due. | | `browser_test` | No | Shell command used by the browser-test operation. | +| `checks` | No | Map of lowercase check names to shell commands, at most 20. Local review runs them in the primary repository and records each result against the tested commit. | | `secrets` | No | Array of declared secret names; defaults to `[]`. | | `variables` | No | Array of declared non-secret operator-value names; defaults to `[]`. | | `services` | No | Named preview services; defaults to `{}`. | diff --git a/apps/docs/docs/self-host/local-mode.md b/apps/docs/docs/self-host/local-mode.md new file mode 100644 index 00000000..caffa5f4 --- /dev/null +++ b/apps/docs/docs/self-host/local-mode.md @@ -0,0 +1,135 @@ +--- +title: Local mode +--- + +# Run Facility for local repositories + +Local mode runs Facility on your own machine against Git repositories on that machine. It needs +Docker and PostgreSQL. It needs no GitHub App, OAuth application, webhook, or hosted repository. +Users follow [Work on a local repository](../guides/local-repository.md) once it is running. + +## Install + +1. Install Docker and Node.js 24 with pnpm 11.20.0, and clone Facility. +2. Build the workspace image: `docker build -f runner/Dockerfile -t facility-runner:dev .` +3. Copy `.env.example` to `.env` and set `SECRET_MASTER_KEY` (`openssl rand -base64 32`). +4. Leave every `GITHUB_*` value empty. +5. Approve the directories Facility may read: + + ```bash + FACILITY_LOCAL_REPOSITORY_ROOTS=/home/you/code + ``` + + Separate several roots with `:`. Facility refuses paths outside them, symlinks that resolve + outside them, and worktrees whose Git directory lives outside them. +6. Run `pnpm dev`, open `http://localhost:3400`, and choose **continue locally**. + +`pnpm dev` starts PostgreSQL on `localhost:5461`, applies migrations, and runs the API, worker, and +UI. The API and worker both read local repositories, so both need the same roots. + +## Settings + +| Variable | Default | Purpose | +| --- | --- | --- | +| `FACILITY_LOCAL_REPOSITORY_ROOTS` | empty (disabled) | Directories whose repositories may be registered. | +| `FACILITY_LOCAL_REPOSITORY_OWNER_UIDS` | the Facility process user | Comma-separated user ids allowed to own registered repositories. | +| `FACILITY_LOCAL_SNAPSHOT_MAX_BYTES` | 512 MiB | Largest repository bundle Facility imports. | +| `FACILITY_LOCAL_GIT_NAME`, `FACILITY_LOCAL_GIT_EMAIL` | `Facility Agent`, `facility-agent@localhost` | Author identity for agent commits. | +| `FACILITY_LISTEN_HOST` | `localhost` | Interface the API binds. | + +## Model providers + +Local mode uses the existing Claude Code and Codex engines with cloud credentials. Configure them +per project exactly as for GitHub projects: declare the names under `environment.secrets` in +`.facility.yml`, then provide the values as project environment variables in **Settings** or as +`FACILITY_PROJECT__ANTHROPIC_API_KEY` / `..._OPENAI_API_KEY` in `.env`. Existing +secret redaction, usage accounting, and budgets apply unchanged. + +## Network exposure and authentication + +Facility binds the API to loopback by default. Docker Compose publishes the API and UI on +`127.0.0.1` unless `FACILITY_BIND_ADDRESS` says otherwise. Remote access is a separate deployment +mode: configure GitHub or OIDC sign-in and follow [production](production.md) before widening +either setting. + +The **continue locally** login (`FACILITY_INSECURE_DEV=1`) signs in as the local owner without a +password. It is supported for local mode only under these conditions, which Facility enforces: + +- it is refused when `NODE_ENV=production`; +- `PUBLIC_URL` and `WEB_URL` must be loopback URLs; +- the connection must come from a loopback address; and +- the browser-facing host must be `localhost`, `127.0.0.1`, or `[::1]`. This defeats + DNS-rebinding pages and other machines on the network. + +Because the proxy that serves the UI must connect from loopback, the shortcut is for the +`pnpm dev` setup. When Facility runs in containers, use GitHub or OIDC sign-in instead. Every API +call still passes the normal role, project-scope, and organization checks. + +## Containers + +To run the Compose stack in local mode, mount each approved root at the same path in the `api` and +`worker` services, list it in `FACILITY_LOCAL_REPOSITORY_ROOTS`, and set +`FACILITY_LOCAL_REPOSITORY_OWNER_UIDS` to the user id that owns the repositories on the host. Use +read-only mounts: Facility only reads source repositories. + +```yaml +services: + api: + volumes: + - /home/you/code:/home/you/code:ro + worker: + volumes: + - /home/you/code:/home/you/code:ro +``` + +## Back up and restore + +A local installation holds state in four places. Back up all of them together, while no turn is +running: + +1. **Database.** Stories, conversations, review decisions, check results, imported source revisions, + and export bundles live in PostgreSQL: + + ```bash + pg_dump --format=custom --file=facility.dump "$DATABASE_URL" + ``` + +2. **Workspace volumes.** Each story's files, story branch, and agent sessions live in a Docker + volume named `facility-ws-volume-`: + + ```bash + for volume in $(docker volume ls -q --filter label=facility.workload.kind=workspace-v2); do + docker volume inspect --format '{{ index .Labels "facility.workspace.id" }}' "$volume" \ + > "$volume.workspace-id" + docker run --rm -v "$volume:/workspace:ro" -v "$PWD:/backup" alpine \ + tar -C /workspace -czf "/backup/$volume.tar.gz" . + done + ``` + +3. **Source repositories.** Your repositories are not copied into Facility, except inside workspace + volumes. Back them up as you normally would. After a restore they must be at the same paths, + because Facility stores each repository's canonical path. +4. **Secrets.** Keep `SECRET_MASTER_KEY` and the project credentials in `.env`. Without the same + master key, restored encrypted project variables cannot be read. + +To restore: + +1. Recreate the database: `pg_restore --clean --dbname="$DATABASE_URL" facility.dump`. +2. Recreate each volume with the labels Facility uses to recognize its own workspaces, then extract + its archive: + + ```bash + for archive in facility-ws-volume-*.tar.gz; do + volume="${archive%.tar.gz}" + docker volume create \ + --label "facility.workspace.id=$(cat "$volume.workspace-id")" \ + --label facility.workload.kind=workspace-v2 "$volume" + docker run --rm -v "$volume:/workspace" -v "$PWD:/backup:ro" alpine \ + tar -C /workspace -xzf "/backup/$archive" + done + ``` + +3. Restore `.env` with the same `SECRET_MASTER_KEY`, and put source repositories back at their + registered paths. +4. Start Facility. Each workspace's compute is recreated from its volume the next time its story + runs. diff --git a/apps/docs/sidebars.ts b/apps/docs/sidebars.ts index 250e7357..1df14d28 100644 --- a/apps/docs/sidebars.ts +++ b/apps/docs/sidebars.ts @@ -23,6 +23,7 @@ const sidebars: SidebarsConfig = { items: [ "self-host/quickstart", "self-host/local-development", + "self-host/local-mode", "self-host/production", "self-host/aws", "self-host/authentication", @@ -36,6 +37,7 @@ const sidebars: SidebarsConfig = { items: [ "guides/kickstart", "guides/existing-repo", + "guides/local-repository", "guides/operate-story", "guides/project-overview", "guides/validate-workspace-loop", diff --git a/apps/docs/test/documentation-contract.test.mjs b/apps/docs/test/documentation-contract.test.mjs index acdd33a0..afc6b0f0 100644 --- a/apps/docs/test/documentation-contract.test.mjs +++ b/apps/docs/test/documentation-contract.test.mjs @@ -27,6 +27,32 @@ test("repository unlink documents cleanup, retention, permissions and primary re assert.match(guide, /Archiving does not remove/); }); +test("local repositories document the import contract, review, export, and safe operation", () => { + const guide = read("apps/docs/docs/guides/local-repository.md"); + for (const contract of [ + "committed history from the default branch only", + "Uncommitted edits and\nuntracked files", + "never writes to your repository", + "local:", + "Any later commit makes the approval stale", + "not a merge", + "Submodules and Git LFS", + ]) { + assert.ok(guide.includes(contract), contract); + } + const operator = read("apps/docs/docs/self-host/local-mode.md"); + for (const contract of [ + "FACILITY_LOCAL_REPOSITORY_ROOTS", + "Leave every `GITHUB_*` value empty", + "the connection must come from a loopback address", + "pg_dump", + "facility-ws-volume-", + "SECRET_MASTER_KEY", + ]) { + assert.ok(operator.includes(contract), contract); + } +}); + test("the published navigation covers user, operator, reference, and contributor paths", () => { const sidebar = read("apps/docs/sidebars.ts"); const requiredPages = [ @@ -36,6 +62,7 @@ test("the published navigation covers user, operator, reference, and contributor "concepts/agents-as-code", "guides/kickstart", "guides/existing-repo", + "guides/local-repository", "guides/operate-story", "guides/validate-workspace-loop", "guides/troubleshooting", diff --git a/apps/web/app/(app)/(org)/projects/new/local/page.tsx b/apps/web/app/(app)/(org)/projects/new/local/page.tsx new file mode 100644 index 00000000..8b4ed882 --- /dev/null +++ b/apps/web/app/(app)/(org)/projects/new/local/page.tsx @@ -0,0 +1,232 @@ +"use client"; + +import { Button, Eyebrow, Field, TextInput } from "@facility/ui"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useMemo, useState } from "react"; +import type { + LocalKickstart, + LocalRepositoryRegistration, + LocalRepositoryStatus, + Project, +} from "@/lib/api"; +import { clientApi } from "@/lib/client-api"; + +/** + * A project backed by a Git repository on the machine running Facility. No + * GitHub App, installation, or hosted remote is involved: Facility imports the + * committed history into its own workspaces, and starter configuration comes + * back as a patch the user reviews and commits themselves. + */ +export default function LocalProjectPage() { + const router = useRouter(); + const [status, setStatus] = useState(null); + const [statusError, setStatusError] = useState(""); + const [name, setName] = useState(""); + const [path, setPath] = useState(""); + const [alias, setAlias] = useState(""); + const [startCommand, setStartCommand] = useState(""); + const [project, setProject] = useState(null); + const [repository, setRepository] = useState(null); + const [kickstart, setKickstart] = useState(null); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + void clientApi("GET", "/v1/local-repositories/status").then((result) => { + if (result.ok) setStatus(result.data); + else setStatusError(result.message); + }); + }, []); + + const slug = useMemo( + () => + name + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 60), + [name], + ); + + async function register() { + setBusy(true); + setError(""); + const created = + project ?? + (await clientApi("POST", "/v1/projects", { + name: name.trim(), + slug, + }).then((result) => { + if (!result.ok) { + setError(result.message); + return null; + } + setProject(result.data); + return result.data; + })); + if (!created) { + setBusy(false); + return; + } + const registered = await clientApi( + "POST", + `/v1/projects/${encodeURIComponent(created.id)}/repos/local`, + { path: path.trim(), ...(alias.trim() ? { alias: alias.trim() } : {}) }, + ); + if (!registered.ok) { + setError(registered.message); + setBusy(false); + return; + } + setRepository(registered.data); + const starter = await clientApi( + "POST", + `/v1/projects/${encodeURIComponent(created.id)}/repos/${encodeURIComponent(registered.data.id)}/local-kickstart`, + { answers: startCommand.trim() ? { startCmd: startCommand.trim() } : {} }, + ); + if (starter.ok) setKickstart(starter.data); + else setError(`Registered, but the starter configuration failed: ${starter.message}`); + setBusy(false); + router.refresh(); + } + + return ( +
+
+ local repository +

+ Start a project from a repository on this machine +

+

+ Agents work on Facility-managed copies in local Docker workspaces. You review, request + revisions, and import approved commits back into your repository. Model calls still go to + your configured AI provider.{" "} + + Use GitHub instead + +

+
+ + {statusError ? ( +

+ Couldn't check local repository support — {statusError} +

+ ) : status && !status.enabled ? ( +
+ Local repositories are disabled on this Facility instance. An operator enables them by + setting FACILITY_LOCAL_REPOSITORY_ROOTS to the directories Facility may read, + then restarting the API and worker. +
+ ) : null} + + {error ? ( +
+ {error} +
+ ) : null} + + {status?.enabled && !repository ? ( +
{ + event.preventDefault(); + void register(); + }} + > + + setName(event.target.value)} required /> + + + setPath(event.target.value)} + placeholder={`${status.roots[0] ?? "/path/to/code"}/my-app`} + required + /> + + + setAlias(event.target.value)} /> + + + setStartCommand(event.target.value)} + placeholder="pnpm dev" + /> + +

+ Facility imports committed history from the default branch only. + Uncommitted and untracked files in your checkout are never copied, and Facility never + writes to your repository. +

+
+ +
+
+ ) : null} + + {repository ? ( +
+

Registered {repository.manifestName}

+

+ {repository.sourcePath} · {repository.defaultBranch} at{" "} + {repository.headSha.slice(0, 12)} +

+ {repository.warnings.map((warning) => ( +

+ {warning} +

+ ))} + {kickstart ? ( + kickstart.files.length === 0 ? ( +

+ The repository already has .facility.yml and its agents. Start a story when you're + ready. +

+ ) : ( + <> +

+ Review this starter configuration, save it as{" "} + facility-kickstart.patch in your repository, then apply and commit + it: +

+
+                  {kickstart.instructions.join("\n")}
+                
+