diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index fb024ae..b3e444e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -17,6 +17,7 @@ "application-generation" ], "skills": [ - "./skills/create-full-stack-app" + "./skills/create-full-stack-app", + "./skills/setup-first-draft" ] } diff --git a/AGENTS.md b/AGENTS.md index 59b421b..cfca5cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ | Task | Route | |---|---| | Plan authoring or Compile behavior | [`skills/create-full-stack-app/SKILL.md`](skills/create-full-stack-app/SKILL.md), then only its routed reference section | +| Workshop prerequisites or local setup | [`docs/workshop-setup.md`](docs/workshop-setup.md), then [`skills/setup-first-draft/SKILL.md`](skills/setup-first-draft/SKILL.md) | | UI continuation or review | The consumer app's `UI.md`; [UI Skill auditions remain deferred](README.md#ui-continuation) | | Release work | [`RELEASING.md`](RELEASING.md) and [`release/compatibility.json`](release/compatibility.json) | | Evidence or prior rollout facts | [`evidence/README.md`](evidence/README.md), then one dated record | diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3d89a..159902b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,18 @@ Entries are newest first. Keep their headings stable. Publish material correctio new entries so readers who advanced their checkpoint see them. Entries explain what changed, why, when it applies, and any useful small example or verification; they need not reproduce commit logs. +## 2026-09-27: Mac setup with your agent + +**Skills — unreleased source candidate.** The new `setup-first-draft` Skill pairs a Mac prerequisite installer with +guidance for running a compiled app. The [workshop setup entry point](docs/workshop-setup.md) can be read before +Node or the First Draft plugin is installed, avoiding a circular setup requirement. + +The installer uses Homebrew and mise, then reads runtime versions from the compiled app. An existing runtime +manager stays in place; the agent uses it instead of migrating the user's projects. Application setup still belongs +to the emitted `bin/setup` and `bin/dev`. This is workshop tooling, not another dependency for emitted applications. +The [clean-Mac prerequisite trial](evidence/2026-09-27-mac-setup-prerequisites.md) verifies installation, a safe rerun, +and Desktop tool discovery. Compilation and local Rails preview in that VM remain pending. + ## 2026-09-27: Public Plan authoring references **Skills — merged source, unreleased 0.7.1 candidate.** The packaged authoring references now route to the existing diff --git a/README.md b/README.md index bef513b..9408dca 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ It preserves the planning workspace under `.firstdraft/design/` and defaults to Use explicit `--github` for server Publication. Its identities belong in [release compatibility](release/compatibility.json). The [public catalog](.claude-plugin/marketplace.json) owns the -installed version; source compatibility does not establish publication. Only `create-full-stack-app` is packaged; -the UI Skill auditions remain deferred source. +installed version; source compatibility does not establish publication. The candidate packages `create-full-stack-app` +and `setup-first-draft`. The UI Skill auditions remain deferred source. Trying First Draft as a tester? Start with the [local development guide](https://gist.github.com/raghubetina/3d424a97a1eaa6de8c406e67f32a237e). Start in an empty @@ -39,6 +39,7 @@ behavior. This repository teaches an agent how to use those contracts without cr |---|---| | Change the Skill or repository | [Agent instructions](AGENTS.md), then [documentation map](docs/README.md) | | Understand the installed workflow | [Skill entrypoint](skills/create-full-stack-app/SKILL.md) | +| Prepare a Mac for the workshop | [Setup entry point](docs/workshop-setup.md), then [setup Skill](skills/setup-first-draft/SKILL.md) | | Continue or review an app's UI | The app's `UI.md` and shared components; [UI Skill status](#ui-continuation) | | Review improvements for an existing app or workflow | [First Draft changelog](CHANGELOG.md) | | Change Plan authoring guidance | [Skill entrypoint](skills/create-full-stack-app/SKILL.md), then [modeling guide](skills/create-full-stack-app/references/modeling-guide.md) | @@ -84,6 +85,7 @@ conversation, run `codex resume` from the same workspace root. | Path | Responsibility | |---|---| | skills/create-full-stack-app/ | Canonical portable Skill and packaged references | +| skills/setup-first-draft/ | Mac prerequisite installer and local-app setup guidance | | skills/extend-app-ui/, skills/review-ui-consistency/ | Deferred UI Skill sources, excluded from the package | | .claude-plugin/, packages/ | Release-gated public catalog selection and plugin assembly, not a second editable Skill copy | | evals/ | Behavioral cases and evaluator contracts | diff --git a/docs/README.md b/docs/README.md index 48f3d08..8f9b286 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,7 @@ dated observation records an earlier value. ## Routes by task +- **Workshop setup:** [public entry point](workshop-setup.md), then the [setup Skill](../skills/setup-first-draft/SKILL.md). - **Skill authoring:** read the relevant Skill's `SKILL.md`, then only its applicable reference. Packaged bytes are the product surface; changing any of them changes the candidate digest. - **Foundation Plan examples or schema:** start with `references/examples.md` or the relevant prose subsection. Pass diff --git a/docs/examples/reading-list.foundation-plan.json b/docs/examples/reading-list.foundation-plan.json new file mode 100644 index 0000000..628b347 --- /dev/null +++ b/docs/examples/reading-list.foundation-plan.json @@ -0,0 +1,238 @@ +{ + "format": "firstdraft.foundation-plan.sketch/0.22", + "target": { + "id": "rails", + "profile": "rails-sketch/2026-09" + }, + "application": { + "key": "reading_list", + "name": "Reading List", + "native": { + "ios": {}, + "android": {} + }, + "delivery": {}, + "home_index": "book", + "entities": [ + { + "subject_uuid": "01a0e512-df9a-7619-b775-835c95e82dad", + "key": "book", + "name": "Book", + "icon": "bookmark", + "primary_descriptor": { + "field": "book.title" + }, + "fields": [ + { + "subject_uuid": "01a0e512-dd36-7fd0-9163-5df02d9b3187", + "key": "title", + "name": "Title", + "type": "short_text", + "required": true, + "normalizations": [ + "trim", + "blank_to_null" + ] + }, + { + "subject_uuid": "01a0e512-dde1-7812-91b5-30af80dcbb94", + "key": "author", + "name": "Author", + "type": "short_text", + "required": true, + "normalizations": [ + "trim", + "blank_to_null" + ] + }, + { + "subject_uuid": "01a0e512-de5e-715c-9520-a372678ff8c3", + "key": "note", + "name": "Note", + "type": "long_text", + "required": false + }, + { + "subject_uuid": "01a0e512-df0f-79e4-9879-27882ba6af93", + "key": "finished", + "name": "Finished", + "type": "boolean", + "required": true, + "default": { + "kind": "literal", + "value": false + } + } + ], + "scaffold": { + "resource_routes": [ + "index", + "show", + "new", + "create", + "edit", + "update", + "destroy" + ], + "index": { + "authorization": "public", + "projection": [ + { + "field": "book.title" + }, + { + "field": "book.author" + }, + { + "field": "book.note" + }, + { + "field": "book.finished" + } + ] + }, + "show": { + "authorization": "public", + "projection": [ + { + "field": "book.title" + }, + { + "field": "book.author" + }, + { + "field": "book.note" + }, + { + "field": "book.finished" + } + ] + }, + "create": { + "authorization": "public", + "inputs": [ + { + "field": "book.title" + }, + { + "field": "book.author" + }, + { + "field": "book.note" + }, + { + "field": "book.finished" + } + ] + }, + "update": { + "authorization": "public", + "inputs": [ + { + "field": "book.title" + }, + { + "field": "book.author" + }, + { + "field": "book.note" + }, + { + "field": "book.finished" + } + ] + }, + "destroy": { + "authorization": "public" + } + } + } + ], + "development_data": [ + { + "subject_uuid": "01a0e512-e047-7031-b879-8a51711f0562", + "key": "a_wizard_of_earthsea", + "entity": "book", + "fields": [ + { + "field": "book.title", + "value": { + "kind": "literal", + "value": "A Wizard of Earthsea" + } + }, + { + "field": "book.author", + "value": { + "kind": "literal", + "value": "Ursula K. Le Guin" + } + }, + { + "field": "book.finished", + "value": { + "kind": "literal", + "value": false + } + } + ] + }, + { + "subject_uuid": "01a0e512-e10c-7c17-afd5-7a4104878a85", + "key": "pride_and_prejudice", + "entity": "book", + "fields": [ + { + "field": "book.title", + "value": { + "kind": "literal", + "value": "Pride and Prejudice" + } + }, + { + "field": "book.author", + "value": { + "kind": "literal", + "value": "Jane Austen" + } + }, + { + "field": "book.finished", + "value": { + "kind": "literal", + "value": true + } + } + ] + }, + { + "subject_uuid": "01a0e512-e1b5-7efa-a6fa-c5b81bb1e7bc", + "key": "the_hobbit", + "entity": "book", + "fields": [ + { + "field": "book.title", + "value": { + "kind": "literal", + "value": "The Hobbit" + } + }, + { + "field": "book.author", + "value": { + "kind": "literal", + "value": "J. R. R. Tolkien" + } + }, + { + "field": "book.finished", + "value": { + "kind": "literal", + "value": false + } + } + ] + } + ] + } +} diff --git a/docs/workshop-setup.md b/docs/workshop-setup.md new file mode 100644 index 0000000..9eeaf1c --- /dev/null +++ b/docs/workshop-setup.md @@ -0,0 +1,53 @@ +# First Draft workshop setup + +This is the agent entry point for local workshop setup. The student has installed Claude Desktop, signed in, +opened the **Code** tab in a **Local** environment, and selected their project folder. A supported paid Claude +subscription is required. Use the scope in their request; setup alone does not request a deployment. + +Lead setup from this session: inspect the machine, use the standard recipe where it fits, and diagnose failures +from the install log. A Terminal handoff is only for steps that require it; the student should not have to +troubleshoot their toolchain before returning to you. + +## Get the setup Skill before Node or Git is installed + +On a Mac, download this repository into a temporary directory outside the student's app folder. The system's curl, +tar, and Bash are sufficient: + +```sh +setup_download=$(mktemp -d -t firstdraft-setup) +curl --fail --location --silent --show-error \ + https://github.com/firstdraft/skills/archive/refs/heads/main.tar.gz \ + -o "$setup_download/skills.tar.gz" +tar -xzf "$setup_download/skills.tar.gz" -C "$setup_download" +``` + +Read `$setup_download/skills-main/skills/setup-first-draft/SKILL.md`, and use that directory for its script paths. +Keep the resolved absolute path for later tool calls. This reads the canonical instructions directly; it does not +register a plugin. The Skill then guides the normal plugin installation after Node is available. + +If working from a candidate checkout, use its setup Skill directly so the trial exercises that candidate. The +published entry point above follows `main`; confirm the candidate is merged before directing students to it. + +For Windows, start with the [WSL reference](../skills/setup-first-draft/references/windows.md). The Mac script must +not be run in PowerShell or WSL. The workshop can use [Codespaces](https://github.com/firstdraft/drawing-board) +when local setup is unsuitable. + +## Continue the workshop + +The [setup Skill](../skills/setup-first-draft/SKILL.md) owns prerequisites and local startup. After it succeeds, +follow the student's workshop guide for the [prepared Reading List Plan](examples/reading-list.foundation-plan.json), +private GitHub publication, deployment, and Revyl. The Plan lives here so students can fetch it without access to +the private lesson repository. +Use the installed authoring Skill for the current Plan contract and Compilation commands, and the generated app's +documentation for sample data, deployment, and native preview. Do not install Xcode, Android Studio, a local tunnel, +or a separate editor merely to complete a preview that uses GitHub builds and the deployed app. + +## Maintainer smoke + +Replay the student's entry prompt in a clean Mac VM with a normal user account. Keep password prompts enabled. +Record the OS/architecture, setup revision, versions, manual actions, and actual local create/edit result. Then +rerun setup and repeat from a fresh agent session to check runtime discovery. An interrupted install should be +recoverable by correcting the reported step and rerunning; it should not require deleting the project. + +Machine setup and localhost are separate observations from service Compilation, deployed Rails, and a Revyl +session. Record untested boundaries explicitly. A Mac trial does not establish Windows/WSL compatibility. diff --git a/evidence/2026-09-27-mac-setup-prerequisites.md b/evidence/2026-09-27-mac-setup-prerequisites.md new file mode 100644 index 0000000..c8c713f --- /dev/null +++ b/evidence/2026-09-27-mac-setup-prerequisites.md @@ -0,0 +1,69 @@ +# Clean Mac prerequisite trial + +Observed on 2026-09-27 in an isolated Apple Silicon VM. This establishes machine +prerequisites and Desktop plugin installation, not a completed workshop journey. + +## Environment and source + +- Tart 2.39.0, four CPUs, 8 GiB RAM, 50 GB logical disk. +- macOS 26.6.2 (25G83), from Cirrus Labs' `macos-tahoe-vanilla` image at + `sha256:eeec54bfe1f076e27786c5d92b89187a05b1d109b5071eb2dcdf02d596e34640`. +- A new administrator account with a separate home and normal password prompts. + Gatekeeper was enabled and the image's automatic login disabled. No host + credentials or directories were copied into the VM. +- Initially no Homebrew, mise, Node, PostgreSQL, or Apple command-line tools. +- Official Claude Desktop 2.9939.2, authenticated by the user; Local Code sessions + used the default Auto permission mode and Sonnet 4.6 High. +- Initial setup source: `ea93076c9e03fc138dd75887e9a30c21fb607a24`. + The corrected readiness procedure and rerun used + `fd7e3a251781af79a3c3b39e9979cc18e6c9a13b`. + +## Observed results + +Claude fetched the public setup entry and Skill before Node or Git was available. +Its automatic permission classifier blocked the installer, so it supplied a +Terminal handoff. Normal administrator authentication worked; no permission mode +was weakened. Clipboard limitations in the VM automation required reading the +exact prompt from a temporary file instead of pasting it. + +One installer was started through an interactive SSH terminal. When another copy +was started in the guest's Terminal, the SSH copy was stopped and the guest copy +completed. This prevents treating the elapsed time as an uninterrupted install +measurement. + +A premature Desktop restart led Claude to probe `git --version`, which launched +Apple's redundant graphical installer. That GUI asked for 23.74 GB free, while +Homebrew's command-line installation succeeded. The installed command-line tools +occupied about 1.3 GiB; no disk resize was needed. The revised Skill checks +readiness without invoking Apple's developer-tool stubs and waits for completion. + +After installation, a fresh login shell passed `setup-macos.sh --check`: + +| Tool | Observed version | +|---|---| +| Homebrew | 7.0.6 | +| mise | 2026.9.15 | +| Node / npm | 24.21.0 / 11.19.0 | +| PostgreSQL server | 18.6 | +| Apple Git / GitHub CLI | 2.54.0 / 2.101.0 | + +PostgreSQL accepted connections on the default local socket. Rerunning setup +reused the installed packages, preserved shell/mise configuration checksums, and +left the existing PostgreSQL process running. + +After quitting and reopening Desktop, a fresh Local session found Node and npm +without a PATH override. The public marketplace installed First Draft 0.7.0 in +user scope and displayed its `create-full-stack-app` Skill. This was the public +package, not the unpublished setup candidate. The Local environment gear opened +the secure environment editor. + +The rehearsal also caught an incorrect `/api-tokens/new` link. Token creation is +on `/api-tokens`; the corrected route reached the First Draft sign-in page. + +## Remaining boundaries + +First Draft authentication, service Compilation, installation of the compiled +app's exact Ruby runtime, local Rails boot, a persisted browser edit, and the +Render/Revyl stages remain unqualified at this checkpoint. This VM trial does not +establish Intel Mac or Windows/WSL compatibility. Source and package validation +do not constitute publication. diff --git a/evidence/README.md b/evidence/README.md index 96b4949..630d470 100644 --- a/evidence/README.md +++ b/evidence/README.md @@ -10,6 +10,7 @@ The source/public split observed at the start of this documentation change is ca | Record | Observed boundary | |---|---| +| [`2026-09-27-mac-setup-prerequisites.md`](2026-09-27-mac-setup-prerequisites.md) | Clean Apple Silicon Mac prerequisite installation, preserved rerun state, fresh Desktop runtime discovery, and public plugin installation; service Compilation and local Rails preview remain separate | | [`2026-09-19-api-0.4-landed-source-amendment.md`](2026-09-19-api-0.4-landed-source-amendment.md) | Landed Service/CLI pins, reproduced 0.3.0 package, both Node suites, and isolated Codex/Claude package discovery; terminal review and PR78 CI remain separate gates | | [`2026-09-19-api-0.4-source-companion.md`](2026-09-19-api-0.4-source-companion.md) | Initial 0.3.0 source composition and local reviews; its Service/CLI pins and packed digest are superseded by the [landed-source amendment](2026-09-19-api-0.4-landed-source-amendment.md) | | [`2026-09-15-shared-plugin-0.2.4-public-install.md`](2026-09-15-shared-plugin-0.2.4-public-install.md) | Fresh public Claude/Codex installation from exact catalog 0.2.4, original logs and helper; [machine receipt](2026-09-15-shared-plugin-0.2.4-public-install.json) | diff --git a/packages/claude-plugin/.claude-plugin/plugin.json b/packages/claude-plugin/.claude-plugin/plugin.json index 8cfa008..6817141 100644 --- a/packages/claude-plugin/.claude-plugin/plugin.json +++ b/packages/claude-plugin/.claude-plugin/plugin.json @@ -17,6 +17,7 @@ "application-generation" ], "skills": [ - "./skills/create-full-stack-app" + "./skills/create-full-stack-app", + "./skills/setup-first-draft" ] } diff --git a/release/compatibility.json b/release/compatibility.json index a923419..d034b23 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -4,7 +4,7 @@ "version": "0.7.1", "plugin_source": { "package": "@firstdraft.com/claude-code", - "tarball_sha256": "b360587401ecd2190d2e83117d4042aaf22a1c14ec2e4b0d6a6fbc754e968e7e" + "tarball_sha256": "2399918ae0bf40869c2569bd09b772e6cd9aa876fbd7ef40ba3674e30ff7aaeb" }, "requires": { "api_contract": [ diff --git a/script/claude-plugin-boundaries.mjs b/script/claude-plugin-boundaries.mjs index fb865bd..b428fa0 100644 --- a/script/claude-plugin-boundaries.mjs +++ b/script/claude-plugin-boundaries.mjs @@ -27,10 +27,19 @@ export const canonicalSourceSkills = Object.freeze({ "agents/openai.yaml", "references/capture.md", ]), + "setup-first-draft": Object.freeze([ + "LICENSE.txt", + "SKILL.md", + "agents/openai.yaml", + "references/windows.md", + "scripts/Brewfile", + "scripts/setup-macos.sh", + ]), }); export const canonicalPluginSkills = Object.freeze({ "create-full-stack-app": canonicalSourceSkills["create-full-stack-app"], + "setup-first-draft": canonicalSourceSkills["setup-first-draft"], }); export const canonicalPluginSkillNames = Object.freeze(Object.keys(canonicalPluginSkills)); diff --git a/skills/setup-first-draft/LICENSE.txt b/skills/setup-first-draft/LICENSE.txt new file mode 100644 index 0000000..26f64f9 --- /dev/null +++ b/skills/setup-first-draft/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 First Draft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/setup-first-draft/SKILL.md b/skills/setup-first-draft/SKILL.md new file mode 100644 index 0000000..848c326 --- /dev/null +++ b/skills/setup-first-draft/SKILL.md @@ -0,0 +1,92 @@ +--- +name: "setup-first-draft" +description: "Prepare a Mac for First Draft and run a compiled Rails app locally. Use for first-time workshop setup, missing development tools, or installation troubleshooting. Windows uses the linked WSL guidance; this Skill does not deploy or author a Foundation Plan." +license: "MIT" +--- + +# Set up First Draft + +Get the user's computer ready, install the First Draft plugin, and run their compiled app. Keep the user's existing +project and tools. This Skill supplies machine prerequisites; the app's own `bin/setup` owns application setup. + +## Prepare the computer + +Inspect the OS, shell, existing runtime manager, and project folder. For Windows, read +[Windows and WSL](references/windows.md); do not run the Mac script. Codespaces already provides the prerequisites. + +If a different runtime manager already serves the user's projects, keep it. Select the app's required versions +with that manager and use [the Brewfile](scripts/Brewfile) as a reference for missing system packages, omitting mise. +The script below is for a fresh Mac or an existing mise setup. + +Resolve `` to this Skill's directory and run: + +```sh +bash "/scripts/setup-macos.sh" --check +``` + +Use this readiness check before invoking development tools such as `git`, `clang`, `make`, or `python3`. On a fresh Mac, +even `git --version` opens Apple's graphical installer. Homebrew handles command-line tools during setup; +do not start a second installer or probe those commands while it is still running. + +For an authorized setup, install missing prerequisites with the same script without `--check`. It uses Homebrew +and mise, installs Node for the plugin, and prepares PostgreSQL. If there is an existing app, pass its absolute +directory too; the script reads its Ruby, Node, and npm pins. Do not run the installer with sudo. + +Homebrew may need the user to run the command in Terminal and complete a Mac administrator prompt. Supply the +resolved absolute script path and save its output so you can diagnose machine-specific failures: + +```sh +( + set -o pipefail + bash "/scripts/setup-macos.sh" 2>&1 | tee "$HOME/firstdraft-setup.log" +) +``` + +Keep passwords in the system prompt, not the conversation. Leave that one installer running until it prints **Mac +prerequisites are ready** or an error. While it runs, the user can sign in to GitHub and First Draft. Do not ask them +to restart Claude yet. On failure, read the log yourself, identify the cause, and adapt or rerun the failed step. +Do not restart from scratch, delete a database, or replace unrelated working tools. + +The script configures zsh/bash login PATH. After confirming the initial installation finished, quit and reopen Claude Desktop, then verify +`node --version` and `npm --version` in a new local session. Desktop may need a full restart to +[reload its environment](https://code.claude.com/docs/en/desktop#session-not-finding-installed-tools). +If the current session needs to continue first, use Homebrew's absolute mise path and `mise exec node@lts -- ...`. +Later reruns from a session that already sees those tools do not need another restart. + +Before asking the user to restart or begin a new session, give them a short continuation prompt naming the project, +this Skill's path or setup URL, what succeeded, and the next step. Include no secrets. + +## Install and connect First Draft + +Use the current public `firstdraft/skills` marketplace selection, which includes a compatible CLI. In Claude +Desktop, open **Customize → Plugins → Add → Add marketplace**, choose `firstdraft/skills`, and sync it. In the +directory's **Code** tab, install **First Draft**. Complete authentication below before starting a new session with +the installed Skills. Do not install an unrelated global CLI alongside the bundled one. + +For First Draft authentication, have the user create a token at `https://firstdraft.com/api-tokens`. In Claude +Desktop, open the environment menu above the prompt, hover over **Local**, and click its gear to enter +`FIRSTDRAFT_API_TOKEN`. Then start a new session with that environment. Do not ask the user to paste the token into +chat, a command argument, or a tracked file. For another agent, follow its supported secret/environment entry +mechanism. Staging requires an explicitly chosen environment and separate credentials. + +Continue Plan work with the installed `create-full-stack-app` Skill. Its current CLI/contract checks and normal +review/Compile workflow remain authoritative; this setup Skill does not duplicate them. + +## Run the compiled application + +Read the generated README and agent instructions. With the Mac mise setup, install its exact runtimes: + +```sh +bash "/scripts/setup-macos.sh" "/absolute/path/to/app" +``` + +From that app directory, run `bin/setup --skip-server`, then keep `bin/dev` running. Use the app's documented sample +data command if separate. Open its local URL, create or edit a record, and verify the change persisted. Do not use +`--reset` to solve ordinary setup errors. + +If runtime selection differs between Terminal and the agent, check PATH and the app's `.ruby-version`, +`.node-version`, and `package.json`. `mise exec ruby@VERSION node@VERSION -- bin/setup --skip-server` selects those +versions explicitly without changing global Ruby or adding another version file to the app. + +Report the local URL and any unfinished setup. GitHub publication, deployment, and native preview follow the user's +requested next stage and the generated app's own guidance. They are not prerequisites for seeing the local app. diff --git a/skills/setup-first-draft/agents/openai.yaml b/skills/setup-first-draft/agents/openai.yaml new file mode 100644 index 0000000..0f3c674 --- /dev/null +++ b/skills/setup-first-draft/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Set up First Draft" + short_description: "Prepare a Mac and run a First Draft app locally" + default_prompt: "Use $setup-first-draft to prepare my Mac for First Draft and help me run my compiled app locally." diff --git a/skills/setup-first-draft/references/windows.md b/skills/setup-first-draft/references/windows.md new file mode 100644 index 0000000..d8e0339 --- /dev/null +++ b/skills/setup-first-draft/references/windows.md @@ -0,0 +1,15 @@ +# Windows and WSL + +Use WSL 2 with Ubuntu for the Rails application, following the +[Rails installation guide](https://guides.rubyonrails.org/install_ruby_on_rails.html#install-ruby-on-windows). +The Mac installer does not support Windows. This path needs its own fresh-device trial before the workshop can +claim equivalent automated setup. + +The user may need an administrator terminal, a restart, and the first Ubuntu launch to create their Linux username +and password. Keep the project in the Linux home directory, not `/mnt/c`. Install Git inside the distribution. + +In Claude Desktop's Code tab, choose the WSL distribution as the environment. Its tools then run inside Ubuntu. +[Desktop WSL sessions](https://code.claude.com/docs/en/desktop-wsl) currently lack plugins and an integrated terminal; +do not give the Mac plugin-install instructions as though they work there. Use the current supported standalone +Skill/CLI installation route or the workshop's Codespaces alternative. Commands for Ubuntu belong in its terminal, +not PowerShell. Use the generated application's runtime pins and normal `bin/setup`/`bin/dev` commands. diff --git a/skills/setup-first-draft/scripts/Brewfile b/skills/setup-first-draft/scripts/Brewfile new file mode 100644 index 0000000..e9d52cc --- /dev/null +++ b/skills/setup-first-draft/scripts/Brewfile @@ -0,0 +1,9 @@ +brew "mise" +brew "gh" +brew "postgresql@18" +brew "libyaml" +brew "openssl@3" +brew "readline" +brew "gmp" +brew "pkgconf" +brew "vips" diff --git a/skills/setup-first-draft/scripts/setup-macos.sh b/skills/setup-first-draft/scripts/setup-macos.sh new file mode 100644 index 0000000..4e0cc8a --- /dev/null +++ b/skills/setup-first-draft/scripts/setup-macos.sh @@ -0,0 +1,163 @@ +#!/bin/bash +set -euo pipefail +caller_path=$PATH + +usage() { + printf 'Usage: bash setup-macos.sh [--check] [APP_DIRECTORY]\n' + printf 'Without an app: prepare Mac prerequisites and Node for the First Draft plugin.\n' + printf 'With an app: also install its pinned Ruby, Node, and npm.\n' + printf '%s\n' '--check reports readiness without installing packages or changing configuration or services.' +} + +check=false +project= +while [ "$#" -gt 0 ]; do + case "$1" in + --check) check=true ;; + --help|-h) usage; exit 0 ;; + -*) usage >&2; exit 2 ;; + *) + if [ -n "$project" ]; then usage >&2; exit 2; fi + project=$1 + ;; + esac + shift +done + +fail() { printf '%s\n' "$*" >&2; exit 1; } +step="checking the machine" +trap 'printf "Setup stopped while %s. Read the error above, correct it, and rerun this command.\n" "$step" >&2' ERR + +[ "$(uname -s)" = Darwin ] || fail 'This installer supports macOS. Use the Windows/WSL guidance for Windows.' +[ "$(id -u)" != 0 ] || fail 'Run as your normal Mac user, not with sudo.' +case "$(uname -m)" in + arm64) brew_prefix=/opt/homebrew ;; + x86_64) brew_prefix=/usr/local ;; + *) fail 'Unsupported Mac architecture.' ;; +esac + +script_dir=$(cd "$(dirname "$0")" && pwd) +if [ -n "$project" ]; then + project=$(cd "$project" && pwd) + for file in .ruby-version .node-version package.json; do + [ -f "$project/$file" ] || fail "Missing $project/$file. Supply a compiled app directory." + done + ruby_version=$(tr -d '[:space:]' < "$project/.ruby-version") + ruby_version=${ruby_version#ruby-} + node_version=$(tr -d '[:space:]' < "$project/.node-version") + node_version=${node_version#v} + for version in "$ruby_version" "$node_version"; do + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "Expected a release version, got: $version" + done +fi + +if ! command -v brew >/dev/null 2>&1; then + export PATH="$brew_prefix/bin:$PATH" +fi +if ! command -v brew >/dev/null 2>&1; then + $check && fail 'Missing Homebrew. Run without --check to install prerequisites.' + [ -t 0 ] || fail 'Homebrew needs a Terminal for the Mac administrator prompt. Run this same command in Terminal, then return to your agent.' + step="installing Homebrew and Apple command-line tools" + installer=$(mktemp -t firstdraft-homebrew) + trap 'rm -f "$installer"' EXIT + curl --fail --location --silent --show-error https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -o "$installer" + /bin/bash "$installer" +fi +brew_prefix=$(brew --prefix) +eval "$(brew shellenv)" + +if ! xcode-select -p >/dev/null 2>&1; then + $check && fail 'Missing Apple command-line tools.' + fail 'Apple command-line tools are still missing. Finish or repair the Homebrew installation, then rerun setup.' +fi + +step="installing Mac packages" +if $check; then + brew bundle check --no-upgrade --file="$script_dir/Brewfile" +else + brew bundle install --no-upgrade --file="$script_dir/Brewfile" +fi +export PATH="$brew_prefix/opt/postgresql@18/bin:$PATH" +mise_shims="${MISE_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/mise}/shims" +export PATH="$mise_shims:$PATH" + +append_once() { + local file=$1 line=$2 + if ! [ -f "$file" ] || ! grep -Fqx -- "$line" "$file"; then + printf '\n%s\n' "$line" >> "$file" + fi +} + +if ! $check; then + step="configuring runtime discovery" + mise settings add idiomatic_version_file_enable_tools ruby + mise settings add idiomatic_version_file_enable_tools node + case "${SHELL:-/bin/zsh}" in + */zsh) profile="${ZDOTDIR:-$HOME}/.zprofile" ;; + */bash) profile="$HOME/.bash_profile" ;; + *) fail 'Use your shell documentation to put Homebrew and mise shims on PATH; this installer configures zsh or bash.' ;; + esac + append_once "$profile" "eval \"\$($brew_prefix/bin/brew shellenv)\"" + append_once "$profile" "export PATH=\"$mise_shims:$brew_prefix/opt/postgresql@18/bin:\$PATH\"" +fi + +step="preparing Node for First Draft" +if ! command -v node >/dev/null 2>&1; then + $check && fail 'Missing Node.js.' + mise use --global node@lts +fi +node -e 'if (Number(process.versions.node.split(".")[0]) < 22) { console.error("First Draft needs Node 22 or newer. Ask your agent to select it with your existing runtime manager."); process.exit(1); }' + +if [ -n "$project" ]; then + step="installing the app runtimes" + if ! $check; then + mise install "ruby@$ruby_version" "node@$node_version" + fi + runtime=(env MISE_AUTO_INSTALL=0 MISE_EXEC_AUTO_INSTALL=0 mise exec "ruby@$ruby_version" "node@$node_version" --) + [ "$("${runtime[@]}" ruby -e 'print RUBY_VERSION')" = "$ruby_version" ] || fail "Ruby $ruby_version is not available. Run setup without --check." + [ "$("${runtime[@]}" node -p 'process.versions.node')" = "$node_version" ] || fail "Node $node_version is not available. Run setup without --check." + "${runtime[@]}" ruby -v + "${runtime[@]}" node --version + npm_version=$("${runtime[@]}" node -e ' + const p = require(process.argv[1]); + const match = /^npm@([0-9]+\.[0-9]+\.[0-9]+)$/.exec(p.packageManager || ""); + if (!match) { console.error("Expected an exact npm version in packageManager"); process.exit(1); } + console.log(match[1]); + ' "$project/package.json") + if [ "$("${runtime[@]}" npm --version)" != "$npm_version" ]; then + $check && fail "The app requires npm $npm_version. Run setup without --check." + "${runtime[@]}" npm install --global "npm@$npm_version" + fi + "${runtime[@]}" npm --version +fi + +step="checking PostgreSQL" +if ! pg_isready -q; then + $check && fail 'PostgreSQL is not accepting connections.' + # An existing TCP-only server must not be replaced with a second local service. + if pg_isready -q -h localhost; then + fail 'PostgreSQL is running on TCP. Ask your agent to configure the app connection for that existing server.' + fi + brew services start postgresql@18 + for _attempt in {1..20}; do + if pg_isready -q; then break; fi + sleep 1 + done +fi +pg_isready +pg_version=$(psql --no-psqlrc --dbname=postgres --tuples-only --no-align --command='SHOW server_version_num;') +[ "$pg_version" -ge 180000 ] || fail "PostgreSQL $pg_version is running, but this app needs PostgreSQL 18 or newer. Ask your agent to select a compatible connection without replacing this server or its data." +git --version +gh --version | head -1 +node --version + +printf '\nMac prerequisites are ready.\n' +case ":$caller_path:" in + *":$mise_shims:"*) ;; + *) printf 'Restart your agent app or open a new Terminal to pick up PATH changes.\n' ;; +esac +if [ -n "$project" ]; then + printf 'Next, from %s: bin/setup --skip-server, then bin/dev.\n' "$project" +else + printf 'After Compilation, rerun this script with the app directory to install its pinned runtimes.\n' +fi diff --git a/test/mac-setup.test.mjs b/test/mac-setup.test.mjs new file mode 100644 index 0000000..147d62a --- /dev/null +++ b/test/mac-setup.test.mjs @@ -0,0 +1,118 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const installer = fileURLToPath(new URL("../skills/setup-first-draft/scripts/setup-macos.sh", import.meta.url)); + +function machine(t) { + const root = mkdtempSync(path.join(tmpdir(), "firstdraft-mac-setup-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + const bin = path.join(root, "bin"); + const profileDir = path.join(root, "profile"); + mkdirSync(bin); + mkdirSync(profileDir); + const log = path.join(root, "commands"); + writeFileSync(log, ""); + const command = (name, body) => writeFileSync(path.join(bin, name), `#!/bin/bash\nset -eu\nprintf '%s\\n' "${name} $*" >> "$SETUP_TEST_LOG"\n${body}\n`, { mode: 0o755 }); + command("uname", 'if [ "$1" = -s ]; then echo Darwin; else echo arm64; fi'); + command("id", 'echo "${SETUP_TEST_UID:-501}"'); + command("xcode-select", 'exit "${SETUP_TEST_CLT_STATUS:-0}"'); + command("brew", 'case "$1" in --prefix) echo "$SETUP_TEST_ROOT";; shellenv) :;; bundle|services) :;; *) exit 90;; esac'); + command("mise", 'if [ "$1" = exec ]; then while [ "$1" != -- ]; do shift; done; shift; exec "$@"; fi'); + command("node", 'exec "$SETUP_TEST_NODE" "$@"'); + command("ruby", 'echo 2.6.10'); + command("pg_isready", "exit 0"); + command("psql", 'echo "${SETUP_TEST_PG_VERSION:-180000}"'); + command("git", "echo git-version"); + command("gh", "echo gh-version"); + const env = { + ...process.env, + PATH: `${bin}:/usr/bin:/bin`, + SHELL: "/bin/zsh", + ZDOTDIR: profileDir, + MISE_DATA_DIR: path.join(root, "mise"), + SETUP_TEST_ROOT: root, + SETUP_TEST_LOG: log, + SETUP_TEST_NODE: process.execPath, + }; + return { + root, + profile: path.join(profileDir, ".zprofile"), + commands: () => readFileSync(log, "utf8"), + run: (args = [], overrides = {}) => spawnSync("/bin/bash", [installer, ...args], { + env: { ...env, ...overrides }, encoding: "utf8", + }), + }; +} + +test("readiness does not install packages, change shell configuration, or start services", (t) => { + const mac = machine(t); + writeFileSync(mac.profile, "# existing shell setup\n"); + const result = mac.run(["--check"]); + assert.equal(result.status, 0, result.stderr); + assert.equal(readFileSync(mac.profile, "utf8"), "# existing shell setup\n"); + assert.match(mac.commands(), /brew bundle check /); + assert.doesNotMatch(mac.commands(), /brew bundle install|brew services|mise (settings|use|install)/); +}); + +test("missing developer tools do not launch a GUI installer or probe Git", (t) => { + const mac = machine(t); + for (const args of [[], ["--check"]]) { + const result = mac.run(args, { SETUP_TEST_CLT_STATUS: "1" }); + assert.equal(result.status, 1); + assert.match(result.stderr, /command-line tools/); + assert.doesNotMatch(mac.commands(), /xcode-select --install|^git |brew bundle|mise /m); + } +}); + +test("rerunning setup preserves shell configuration and does not replace working runtimes or database", (t) => { + const mac = machine(t); + writeFileSync(mac.profile, "# existing shell setup\n"); + const first = mac.run(); + assert.equal(first.status, 0, first.stderr); + const profile = readFileSync(mac.profile, "utf8"); + const second = mac.run(); + assert.equal(second.status, 0, second.stderr); + assert.equal(readFileSync(mac.profile, "utf8"), profile); + assert.ok(profile.startsWith("# existing shell setup\n")); + assert.doesNotMatch(mac.commands(), /mise use|brew services|db:reset/); +}); + +test("a missing pinned Ruby cannot pass by falling back to the system Ruby", (t) => { + const mac = machine(t); + const app = path.join(mac.root, "my app"); + mkdirSync(app); + writeFileSync(path.join(app, ".ruby-version"), "ruby-4.0.5\n"); + writeFileSync(path.join(app, ".node-version"), `${process.versions.node}\n`); + writeFileSync(path.join(app, "package.json"), '{"packageManager":"npm@11.16.0"}\n'); + const result = mac.run(["--check", app]); + assert.equal(result.status, 1); + assert.match(result.stderr, /Ruby 4\.0\.5 is not available/); + assert.doesNotMatch(mac.commands(), /mise install|brew bundle install/); +}); + +test("an older PostgreSQL server is rejected without starting a replacement", (t) => { + const mac = machine(t); + for (const args of [[], ["--check"]]) { + const result = mac.run(args, { SETUP_TEST_PG_VERSION: "160009" }); + assert.equal(result.status, 1); + assert.match(result.stderr, /needs PostgreSQL 18 or newer/); + assert.doesNotMatch(result.stdout, /Mac prerequisites are ready/); + assert.doesNotMatch(mac.commands(), /brew services/); + } +}); + +test("root execution and an invalid app are rejected before package changes", (t) => { + const mac = machine(t); + const root = mac.run([], { SETUP_TEST_UID: "0" }); + assert.equal(root.status, 1); + assert.match(root.stderr, /not with sudo/); + const invalid = mac.run([mac.root]); + assert.equal(invalid.status, 1); + assert.match(invalid.stderr, /Supply a compiled app directory/); + assert.doesNotMatch(mac.commands(), /brew|mise/); +}); diff --git a/test/ui-foundation-authoring.test.mjs b/test/ui-foundation-authoring.test.mjs index 06b9211..f8c5404 100644 --- a/test/ui-foundation-authoring.test.mjs +++ b/test/ui-foundation-authoring.test.mjs @@ -16,7 +16,9 @@ ajv.addSchema(schema); const validate = (name, value) => ajv.getSchema(`${schema.$id}#/$defs/${name}`)(value); test("authoring release excludes the deferred UI Skill auditions", () => { - assert.deepEqual(canonicalPluginSkillNames, ["create-full-stack-app"]); + for (const name of ["extend-app-ui", "review-ui-consistency"]) { + assert.equal(canonicalPluginSkillNames.includes(name), false); + } }); test("ordinary form routes validate without return overrides", () => { diff --git a/test/workshop-plan.test.mjs b/test/workshop-plan.test.mjs new file mode 100644 index 0000000..8cb499e --- /dev/null +++ b/test/workshop-plan.test.mjs @@ -0,0 +1,13 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import Ajv2020 from "ajv/dist/2020.js"; + +test("the public workshop Plan conforms to the packaged authoring contract", async () => { + const readJson = async (relativePath) => JSON.parse(await readFile(new URL(relativePath, import.meta.url), "utf8")); + const schema = await readJson("../skills/create-full-stack-app/references/foundation-plan-0.22.schema.json"); + const plan = await readJson("../docs/examples/reading-list.foundation-plan.json"); + const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false }); + const validate = ajv.compile(schema); + assert.equal(validate(plan), true, ajv.errorsText(validate.errors)); +});