From 65937fa99bb6f41b3f0d9f8e51f328dd5fce757a Mon Sep 17 00:00:00 2001 From: Luke Manning Date: Mon, 12 Oct 2026 01:46:06 +0100 Subject: [PATCH 1/2] docs: add projex agent skill (skills/projex) End-user agent skill teaching any coding agent to drive Projex end-to-end: init (incl. --github auto-discovery, non-interactive paths), components/themes + page wiring, content ops via flags, projex.config.ts field reference, provider env vars, and troubleshooting. Every flag and example verified against live --help output and source of @manningworks/projex v1.6.0 (2026-10-12). Full raw --help drift baseline in skills/projex/references/cli-help.md. --- skills/projex/SKILL.md | 442 +++++++++++++++++++++++++++ skills/projex/references/cli-help.md | 187 ++++++++++++ 2 files changed, 629 insertions(+) create mode 100644 skills/projex/SKILL.md create mode 100644 skills/projex/references/cli-help.md diff --git a/skills/projex/SKILL.md b/skills/projex/SKILL.md new file mode 100644 index 0000000..8f0a5f8 --- /dev/null +++ b/skills/projex/SKILL.md @@ -0,0 +1,442 @@ +--- +name: projex +description: Drive a Projex developer-portfolio "projects showcase" page end-to-end. Use when a user asks to build a projects/portfolio/showcase page with Projex, run the projex CLI (init, add, edit, remove, list), create or modify projex.config.ts entries (github, npm, youtube, gumroad, lemonsqueezy, product-hunt, devto, manual, hybrid project types), wire ProjectCard/ProjectView/ProjectGrid components into a Next.js app, or set provider env vars (GITHUB_TOKEN, DEV_TO_API_KEY, PRODUCT_HUNT_TOKEN, YOUTUBE_TOKEN, GUMROAD_TOKEN, LS_TOKEN). All CLI examples are verified against live --help output and run non-interactively. +--- + +# Projex agent skill + +Projex is a shadcn-style component library for developer portfolio pages. You +declare projects (GitHub repos, npm packages, Product Hunt launches, YouTube +channels, Gumroad/LemonSqueezy products, Dev.to accounts, manual entries) in a +single TypeScript config file, and components render them with auto-fetched +stats (stars, forks, downloads, upvotes, revenue...). All external data is +fetched **at build/render time** by `normalise()` — no runtime API calls. + +Target stack: **Next.js** (required for the documented page patterns), Node.js +>= 18, TypeScript. CLI package: `@manningworks/projex` — run as +`npx @manningworks/projex `. + +## Golden rules for agents + +1. **Never rely on interactive prompts.** Any omitted flag silently falls back + to a prompt; in a non-TTY shell a failed prompt prints `✖ Cancelled.` and + **exits 0**. Exit 0 is NOT proof of success — verify the `✓` line and + re-read `projex.config.ts` or run `projex list`. +2. **Every operation mutates `projex.config.ts` in place** (AST editing). Read + it before and after each mutation. +3. **`projex.config.ts` must exist first** (`init` creates it); all content ops + fail without it. +4. **Package import is the working component path**: `import { ProjectCard } + from '@manningworks/projex'`. (Known bug: `projex add ` from the + published package copies only `.d.ts` files — see Troubleshooting.) + +--- + +## 1. Init + +```bash +npx @manningworks/projex init -y # basic scaffold, no prompts +npx @manningworks/projex init --github -y # auto-discover GitHub repos, no prompts +npx @manningworks/projex init -i # guided interactive setup — HUMANS ONLY, do not run +``` + +| Flag | Effect | +| --- | --- | +| `-y, --yes` | Skip all prompts, use defaults; overwrites an existing `projex.config.ts` silently. | +| `--github` | Fetch the user's repos from the GitHub API and pre-populate the config. | +| `-i, --interactive` | Prompt-by-prompt setup. Do NOT run in an agent shell. | + +Verified non-interactive behavior: + +- `init -y` writes a scaffold with one placeholder `github` project + (`id: 'my-project'`, `repo: 'username/repo'`); detects Next.js (warns only + if absent); auto-installs the package (below). +- `init --github -y` derives the GitHub username from **`git config user.name`** + — if that is not a real GitHub handle (e.g. a full name) you get + `GitHub user "X" not found` or someone else's repos. Set + `git config user.name ` first. With `--yes`, forks are + excluded; archived repos, `*.template`, `*.github.io` and repos without a + description are filtered out. Generated entries: `type: 'github'`, + `status: 'active'`, `featured: false`, and **`background: null, why: null` — + must be fixed before the config validates** (see Troubleshooting). +- With no flags, `init` prompts for overwrite if the config exists; non-TTY + aborts and leaves the file untouched (exit 0). + +Auto-install side effect: `init` and `add` check the user's `package.json`; if +`@manningworks/projex` is missing they run `pnpm add` / `yarn add` / +`npm install` (chosen by lockfile). No `--legacy-peer-deps`, so peer-dependency +conflicts fail the command (see Troubleshooting). Pre-install the package +yourself for deterministic behavior — then the CLI skips installation. + +## 2. Components and themes + +Copy commands — `npx @manningworks/projex add [-f, --force]`: + +| Name | Kind | Copied to | +| --- | --- | --- | +| `project-card` | component | `components/projex/ProjectCard/` | +| `project-view` | component | `components/projex/ProjectView/` | +| `project-grid` | component | `components/projex/ProjectGrid/` | +| `project-list` | component | `components/projex/ProjectList/` | +| `featured-project` | component | `components/projex/FeaturedProject/` | +| `github-card` | component | `components/projex/GitHubCard/` | +| `npm-card` | component | `components/projex/NpmCard/` | +| `showcase-card` | component | `components/projex/ShowcaseCard/` | +| `theme-minimal` / `theme-dark` / `theme-gradient` | theme CSS | `styles/projex-.css` | + +`--force` overwrites without prompting (otherwise an interactive confirm fires +— pass `--force` when re-adding). Unknown names exit 1 and print the available +list. + +> **Known bug (published package):** the copy step only matches `.tsx`/`.ts` +> files, but the published dist ships `.js` + `.d.ts` — the copied directory +> contains type declarations **only**, so the import the CLI prints +> (`import { ProjectCard } from './components/projex/ProjectCard'`) has no +> runtime code and fails at build. **Import from the package instead** +> (verified): `import { ProjectCard } from '@manningworks/projex'`. Copying is +> still useful for owning the source when done from a git checkout. + +### Wiring into a Next.js page (verified pattern) + +`projex.config.ts` exports `defineProjects(...)`, which returns +`{ projects, options }` — **not an array**. Components render *normalized* +projects (`ProjexProject`), so run `normalise()` (async; performs the fetches): + +```tsx +// app/page.tsx — async server component +import { ProjectCard, ProjectGrid, FeaturedProject, normalise } from '@manningworks/projex' +import type { ProjexProject } from '@manningworks/projex' + +async function getProjects(): Promise { + const { projects: projectInputs } = await import('../projex.config') + return Promise.all(projectInputs.map((input) => normalise(input))) +} + +export default async function PortfolioPage() { + const projects = await getProjects() + const featuredProject = projects.find((p) => p.featured) + const otherProjects = projects.filter((p) => !p.featured) + + return ( +
+ {featuredProject && } + + {otherProjects.map((project) => ( + + + + + + + + + ))} + +
+ ) +} +``` + +Theme import (e.g. `app/layout.tsx`): `import './styles/projex-theme-minimal.css'` +(or `@import './styles/projex-theme-minimal.css';` in CSS). + +### Compound component API + +Subcomponents take `project: ProjexProject` (optional on ProjectCard parts): + +- **ProjectCard** — `.Header` `.Description` `.Tags` `.Stats` `.Status` `.Links` +- **ProjectView** — `{ project, onBack?, children }`; parts: + `.Section({ project, name })` with `name` ∈ `description | background | why | + stack | struggles | timeline | posts | commits`; `.Links` `.Stats` `.Commits` +- **ProjectGrid** / **ProjectList** — `{ children }` wrappers + (`div[data-projex-grid]` / `div[data-projex-list]`) +- **FeaturedProject** — `{ project }`; hero (image, background, why, stats, + links); renders nothing for null/undefined +- **GitHubCard** — `{ project?, showForks? = true, statsComponent?, children? }` +- **NpmCard** / **ShowcaseCard** — compound (`.Header`, `.Description`, ...) +- Also exported from the package (not CLI-copyable): `ProjectSearch`, + `ProjectSort`, `ProjectFilterBar`, `ProjectFilterTag`, + `ProjectStatusFilterBar`, `ProjectTypeFilterBar`, `CommitList`. + +### Styling hooks + +Zero CSS ships by default; every element carries `data-projex-*` attributes. +Main ones: `data-projex-card` (+ `-header`, `-description`, `-tags`, `-tag`, +`-stats`, `-stat`, `-status`, `-status-value`, `-links`, `-link`, `-tagline`), +`-grid`, `-list`, `-view` (+ `-section`, `-section-name`, `-stats`, `-links`), +`-featured` (+ `-image`), `-commit-list` (+ `-commit`, `-message`, `-date`, +`-author`, `-link`), `-search` (+ `-input`), `-sort` (+ `-option`), +`-filter-bar`, `-filter-tag`, `-status-filter-bar`, `-type-filter-bar`, +`-language` (+ `-color`), `-struggle` (+ `-type`), `-timeline-date/-note`, +`-post-title/-date/-link`, `-og-title/-description/-image`. + +```css +[data-projex-grid] { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; } +[data-projex-card] { border: 1px solid #e5e5e5; border-radius: 8px; padding: 1rem; } +``` + +## 3. Content operations (non-interactive recipes) + +All require `projex.config.ts`. "Required" = omitting it triggers a prompt +that cancels in a non-TTY shell. + +### add project + +```bash +npx @manningworks/projex add project --type --name "" --status \ + --stack "TypeScript, Next.js" --featured [--description ""] +``` + +- `id` derives from the name: lowercased, spaces → hyphens + (`"My Test App"` → `my-test-app`). Duplicates exit 1 (`already exists`). +- **`--stack` is required** even when empty: pass `--stack ""`. +- **`--featured` must be passed explicitly** (no `--no-featured` on `add`). + Non-featured project: add with `--featured`, then + `edit project --no-featured`. +- Type-specific flag (required per type): + +| `--type` | Required flag | +| --- | --- | +| `github` | `--repo ` | +| `npm` | `--package ` | +| `hybrid` | `--repo ` AND `--package ` | +| `product-hunt` | `--slug ` | +| `youtube` | `--channel-id ` | +| `gumroad` | `--product-id ` | +| `lemonsqueezy` | `--store-id ` | +| `devto` | `--username ` | +| `manual` | (none) | + +Verified example: + +```bash +npx @manningworks/projex add project --type github --name "My Test App" \ + --status active --repo user/repo --stack "TypeScript, Next.js" \ + --description "A test project" --featured +# → ✓ Project 'my-test-app' added to projex.config.ts +``` + +### add learning / challenge / timeline / post + +```bash +npx @manningworks/projex add learning --text "What I learned" +npx @manningworks/projex add challenge --text "Obstacle overcome" +npx @manningworks/projex add timeline --date 2026-01-15 --note "First commit" +npx @manningworks/projex add post --title "Announcing v1" --date 2026-01-16 --url https://example.com/v1 +``` + +- `` must exist, else exit 1 with the valid id list. +- learning + challenge both append to `struggles` + (`{ type: 'learning'|'challenge', text }`); use `challenge` for obstacles. +- `--date` must be `YYYY-MM-DD`. +- **`--url` on `add post` is required in practice**: omitting it prompts + (cancels non-interactively; nothing added). + +### edit project + +```bash +npx @manningworks/projex edit project --status shipped +npx @manningworks/projex edit project --featured +npx @manningworks/projex edit project --no-featured +npx @manningworks/projex edit project --stack "Rust, Wasm" +npx @manningworks/projex edit project --unset description +``` + +Flags: `--name --description --status --featured --no-featured --stack --repo +--package --slug --channel-id --product-id --store-id --username --unset +`. Verified rules: + +- `--unset ` is standalone (exits 1 combined with other flags). + Protected fields exit 1: `id, type, struggles, timeline, posts`. +- Type-specific flags are validated against the project's type (`--repo` on a + `manual` project exits 1). +- No flags at all → interactive field picker; never run bare. + +### remove + +```bash +npx @manningworks/projex remove project --force +npx @manningworks/projex remove learning --index 0 +npx @manningworks/projex remove challenge --index 1 +npx @manningworks/projex remove timeline --index 0 +npx @manningworks/projex remove post --index 0 +``` + +- `remove project` without `--force` asks for confirmation; non-TTY cancels + (exit 0, project kept). Always pass `--force` from an agent. +- `--index` is 0-based within `struggles` (learning + challenge share it), + `timeline`, or `posts`; omitting it opens a picker — always pass it. + Out of range exits 1 with valid bounds. + +### list + +```bash +npx @manningworks/projex list +# table: ID | Type | Status | Featured (★) | Name +``` + +No flags. Use it to discover ids before edit/remove. + +## 4. `projex.config.ts` field reference + +File: `projex.config.ts` at the project root. Shape: + +```ts +import { defineProjects } from '@manningworks/projex' + +export const projects = defineProjects([ + /* project entries */ +], { + commits: 3, // recent commits to fetch for github/hybrid (default 0) + fetchNpmTimestamps: false, // fetch npm createdAt/updatedAt for date sorting + onError: 'throw', // 'throw' | 'warn' | 'silent' — data-fetch error policy +}) +``` + +The export is `{ projects, options }`, **not an array**. The CLI requires a +`defineProjects([...])` call (it edits that array; ids must be string +literals). + +### Per-type required fields + +| `type` | Required | Optional type-specific | +| --- | --- | --- | +| `github` | `id`, `status`, `repo: 'owner/repo'` | `commits?: number`, `useGithubLinkFromRepo?: boolean` (default true), `useLiveLinkFromRepo?: boolean` (default true) | +| `manual` | `id`, `status` | — | +| `npm` | `id`, `status`, `package` | — | +| `hybrid` | `id`, `status`, `repo`, `package` | same github options as `github` | +| `product-hunt` | `id`, `status`, `slug` | — | +| `youtube` | `id`, `status`, `channelId` | — | +| `gumroad` | `id`, `status`, `productId` | — | +| `lemonsqueezy` | `id`, `status`, `storeId` | — | +| `devto` | `id`, `status`, `username` | — | + +### Common fields (all types) + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | `string` | Unique; referenced by edit/remove. | +| `status` | see below | Required. | +| `featured` | `boolean` | Featured projects render in the hero section. | +| `name` / `tagline` / `description` | `string` | Display name, one-liner, description. | +| `background` / `why` | `string` | **String or absent — `null` fails validation.** | +| `image` | `string` | URL/path; used by FeaturedProject. | +| `stack` | `string[]` | Tech tags. | +| `struggles` | `{ type: 'challenge' \| 'learning', text: string }[]` | via `add learning/challenge`. | +| `timeline` | `{ date: string, note: string }[]` | via `add timeline`. | +| `posts` | `{ title: string, date: string, url?: string }[]` | `url` must be a valid URL when present. | +| `links` | object | Optional URL strings: `github`, `live`, `npm`, `docs`, `demo`, `appStore`, `playStore`, `productHunt`; plus `custom: { label, url }[]`. | +| `linkOrder` | `string[]` | Reorder rendered links; keys: `github, live, docs, demo, npm, productHunt, youtube, custom`. | +| `stats` | object | Manual overrides, all optional: `stars?` (number), `forks?` (number), `downloads?` (string), `version?` (string), `upvotes?` (number), `comments?` (number), `launchDate?` (string). Provider stats are fetched automatically. | +| `createdAt` / `updatedAt` | `string` | | +| `override` | `{ name?, tagline?, description?, stack? }` | Override fetched values. | + +**Statuses (lifecycle):** `active`, `shipped`, `in-progress`, `coming-soon`, +`archived`, `for-sale`. + +Full entry: + +```ts +{ + id: 'my-awesome-lib', + type: 'github', + repo: 'yourusername/my-awesome-lib', + status: 'active', + featured: true, + name: 'My Awesome Lib', + tagline: 'Does the thing', + description: 'A library that does the thing well.', + background: 'The story behind the project.', + why: 'Why you built it.', + stack: ['TypeScript', 'React', 'Next.js'], + links: { live: 'https://example.com', docs: 'https://example.com/docs' }, + struggles: [{ type: 'learning', text: 'Hard won insight.' }], + timeline: [{ date: '2026-01-15', note: 'First commit' }], + posts: [{ title: 'Announcing v1', date: '2026-01-16', url: 'https://example.com/v1' }], +} +``` + +## 5. Provider environment variables + +Read by the data-fetching layer when the page renders/builds — **set them in +your build environment** (`.env.local` locally, CI secrets for deploys). + +| Variable | Required? | Unlocks | Where to create | +| --- | --- | --- | --- | +| `GITHUB_TOKEN` | optional (strongly recommended) | 5000 req/hr instead of 60 (shared, per-IP); `init --github`; stars/forks/commits | Fine-grained PAT at `github.com/settings/personal-access-token/new`; Contents read-only, only your referenced repos | +| `DEV_TO_API_KEY` | optional | Dev.to `totalViews`; without it views are 0 | `dev.to/settings/extensions` | +| `PRODUCT_HUNT_TOKEN` | required for `product-hunt` | Upvotes, comments, launch date | `api.producthunt.com` (OAuth application token) | +| `YOUTUBE_TOKEN` | required for `youtube` | Subscribers, views, latest video | Google Cloud Console; enable "YouTube Data API v3"; 10,000 units/day, fetch ≈ 2 units | +| `GUMROAD_TOKEN` | required for `gumroad` | Revenue, sales, subscribers | `app.gumroad.com/settings/api_tokens` | +| `LS_TOKEN` | required for `lemonsqueezy` | MRR, orders, customers | `app.lemonsqueezy.com/settings/api` | + +**Naming trap:** LemonSqueezy's variable is `LS_TOKEN`, not +`LEMONSQUEEZY_TOKEN`. + +## 6. Troubleshooting + +**`Validation failed ... Expected string, received null` after `init --github`.** +Generated entries contain `background: null` / `why: null`, but the schema +accepts only strings (or absence). Delete those lines from every generated +entry. Verified: config then validates and `normalise()` fetches data. + +**`✖ Cancelled.` with exit code 0.** An interactive prompt was reached and +aborted non-interactively; nothing changed. Add the missing flags per section +3 (`--stack`, `--featured`, `--url`, `--index`, `--force`) and re-run. + +**`init` reports `✖ Failed to create projex.config.ts` but the file exists.** +The auto-install step (bare `npm install @manningworks/projex`, no +`--legacy-peer-deps`) threw — e.g. a peer-dependency conflict +(`react@19` vs `next@15.0.0` → `ERESOLVE`). The config is written before the +install attempt: check it, then install the package yourself +(`pnpm add @manningworks/projex`) and re-run what you need. + +**`init --github` found no repos / wrong user.** Unauthenticated GitHub sees +only public repos; a `404` also means *private* repo (no token). Set +`GITHUB_TOKEN` (fine-grained PAT, Contents read, repo-scoped) for private +repos. `init --github -y` uses `git config user.name` as the username — if +that is a full name, `git config user.name ` first. + +**GitHub rate limit (`403` / `rate_limit`).** 60 req/hr unauthenticated (per +IP); 5000/hr with `GITHUB_TOKEN`. Builds warn when no token is set; set the +token and rebuild. + +**`projex add ` copies only `.d.ts` files.** Known bug in the +published package: the copy filter matches `.tsx`/`.ts`, but the shipped dist +has `.js` + `.d.ts` — `components/projex//` ends up with no runtime code +and `import ... from './components/projex/'` fails at build. Use +`import { ... } from '@manningworks/projex'` (verified; 53 exports incl. +`ProjectCard`, `ProjectView`, `ProjectGrid`, `FeaturedProject`, `normalise`, +`defineProjects`). + +**`import '@manningworks/projex'` fails outside a bundler.** The package is +ESM-only (`exports["."]` has an `import` condition only, no +`require`/`default`) and its internals use directory imports. Raw +`node`/CJS cannot load it; Next.js resolves it fine. Don't debug in raw Node. + +**`projex.config.ts not found. Run 'projex init' first.`** — run +`npx @manningworks/projex init -y` in the project root. + +**`Could not find defineProjects() call in config file`** — the CLI only edits +configs shaped `export const projects = defineProjects([...])`; wrap a hand +written array in `defineProjects(...)`. + +**`Project ID 'x' already exists.`** — ids derive from `--name`; pick another +name or edit the existing project. + +**`Project 'x' not found. Valid IDs: ...`** — the error lists valid ids; also +`npx @manningworks/projex list`. + +**`Invalid learning/timeline/post index N`** — 0-based, bounded by array +length; learning and challenge share the `struggles` array. + +**`'field' is not a valid field for type ''` (edit)** — type-specific +flags must match the project's declared type (section 4 table). + +## Drift baseline + +Every flag and example was verified against the live CLI (`--help` + source) +of `@manningworks/projex` v1.6.0 on 2026-10-12. Full raw `--help` capture for +all commands: [`references/cli-help.md`](references/cli-help.md). Re-capture +after any CLI change (`pnpm build`, then run each command with `--help`) and +diff before shipping docs. diff --git a/skills/projex/references/cli-help.md b/skills/projex/references/cli-help.md new file mode 100644 index 0000000..c7307a2 --- /dev/null +++ b/skills/projex/references/cli-help.md @@ -0,0 +1,187 @@ +# Projex CLI `--help` drift baseline + +Raw `--help` output for every command, captured **2026-10-12** from +`@manningworks/projex` v1.6.0 (branch `skill/projex-agent-skill`, built with +`pnpm build`, invoked as `node packages/core/dist/cli.js`). + +How to re-capture: in the repo root, `pnpm install && pnpm build`, then run +`node packages/core/dist/cli.js ` for each invocation below and diff. + +## `projex --version` + +``` +1.6.0 +``` + +## `projex --help` + +``` +Usage: projex [options] [command] + +CLI tool for Projex project showcase pages + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + init [options] Initialize a new Projex project + add [options] [id] Add a Projex component or content to your + project + edit [options] [id] Edit an existing project + remove [options] [id] Remove a project or content entry + list List all projects + help [command] display help for command +``` + +## `projex init --help` + +``` +Usage: projex init [options] + +Initialize a new Projex project + +Options: + --github Auto-detect projects from GitHub repos + -y, --yes Skip all prompts and use default values + -i, --interactive Guided interactive setup + -h, --help display help for command +``` + +## `projex add --help` + +``` +Usage: projex add [options] [id] + +Add a Projex component or content to your project + +Arguments: + type Content type (project, learning, challenge, + timeline, post) or component name + id Project ID (required for learning, timeline, + post) + +Options: + -f, --force Overwrite existing files without prompting + --type Project type (for add project) + --name Project name (for add project) + --status Project status (for add project) + --featured Mark as featured (for add project) + --stack Comma-separated tech stack (for add project) + --repo GitHub repository (for add project) + --package npm package name (for add project) + --slug Product Hunt slug (for add project) + --channel-id YouTube channel ID (for add project) + --product-id Gumroad product ID (for add project) + --store-id LemonSqueezy store ID (for add project) + --username Dev.to username (for add project) + --description Project description (for add project) + --text Text content (for add learning) + --date Date in YYYY-MM-DD format (for add timeline, + post) + --note Milestone note (for add timeline) + --title Post title (for add post) + --url <url> URL (for add post) + -h, --help display help for command +``` + +## `projex edit --help` + +``` +Usage: projex edit [options] <type> [id] + +Edit an existing project + +Arguments: + type Content type (project) + id Project ID + +Options: + --name <name> Project name + --description <description> Project description + --status <status> Project status + --featured Mark as featured + --no-featured Remove featured status + --stack <stack> Comma-separated tech stack + --repo <repo> GitHub repository + --package <package> npm package name + --slug <slug> Product Hunt slug + --channel-id <channelId> YouTube channel ID + --product-id <productId> Gumroad product ID + --store-id <storeId> LemonSqueezy store ID + --username <username> Dev.to username + --unset <field> Remove a field from the project + -h, --help display help for command +``` + +## `projex remove --help` + +``` +Usage: projex remove [options] <type> [id] + +Remove a project or content entry + +Arguments: + type Content type (project, learning, challenge, timeline, post) + id Project ID + +Options: + -f, --force Skip confirmation prompt + --index <index> Index of entry to remove (for learning, timeline, post) + -h, --help display help for command +``` + +## `projex list --help` + +``` +Usage: projex list [options] + +List all projects + +Options: + -h, --help display help for command +``` + +## Verified behavioral notes (beyond `--help`) + +Captured by executing the CLI in a throwaway Next.js-style sandbox +(2026-10-12, `@manningworks/projex` 1.6.0 from npm): + +1. `init -y` — writes `projex.config.ts` scaffold, then auto-installs the + package. If the install fails, the command exits 1 with + `✖ Failed to create projex.config.ts` **even though the file was written**. +2. `init --github -y` — GitHub username taken from `git config user.name`. + Excludes forks; filters archived / `*.template` / `*.github.io` / + no-description repos. Generated entries include `background: null` and + `why: null`, which **fail `defineProjects` validation** (`z.string().optional()` + does not accept null) until those lines are removed. +3. `add project` — prompts (cancels non-interactively, exit 0) when + `--stack` or `--featured` is omitted. `--stack ""` is accepted and skips + the stack prompt. There is no `--no-featured` on `add` (edit-only). +4. `add post` — prompts for `--url` even though it is "optional" (cancels + non-interactively, exit 0, nothing added). +5. `add learning` / `add challenge` — `--text` alone suffices (type defaults + to `learning` / is forced to `challenge` respectively). +6. `add timeline` — `--date` (YYYY-MM-DD) and `--note` both required. +7. `edit project` — no flags → interactive field picker. `--unset` is + standalone-only; protected: `id, type, struggles, timeline, posts`. + Type-specific flags validated against the project type. +8. `remove project` without `--force` — confirm prompt; non-TTY cancels, + exit 0, project kept. +9. `remove learning/challenge/timeline/post` without `--index` — interactive + picker; non-TTY cancels, exit 0, nothing removed. +10. `add <component>` — copies to `components/projex/<Name>/`; rewrites + `from '../types'` → `from '@manningworks/projex'`. From the published + package only `.d.ts` files are copied (filter matches `.tsx`/`.ts`; dist + ships `.js` + `.d.ts`) — no runtime code in the copied directory. +11. `add <theme>` — copies to `styles/projex-<name>.css`. +12. All cancelled prompts print `✖ Cancelled.` and exit **0**. +13. `import * as P from '@manningworks/projex'` (ESM, bundler-style + resolution) exposes 53 exports incl. `ProjectCard`, `ProjectView`, + `ProjectGrid`, `ProjectList`, `FeaturedProject`, `GitHubCard`, `NpmCard`, + `ShowcaseCard`, `CommitList`, `ProjectSearch`, `ProjectSort`, + `ProjectFilterBar`, `ProjectFilterTag`, `ProjectStatusFilterBar`, + `ProjectTypeFilterBar`, `defineProjects`, `normalise`. +14. Full pipeline verified: `defineProjects([...])` → + `Promise.all(inputs.map(input => normalise(input)))` → normalized + `ProjexProject` objects with fetched GitHub data and derived `links`. From a181262e3cd25644d39baabbd3d749d7778d04ee Mon Sep 17 00:00:00 2001 From: Luke Manning <luketmanning@gmail.com> Date: Mon, 12 Oct 2026 05:13:22 +0100 Subject: [PATCH 2/2] docs: apply PR #40 review fixes to projex agent skill --- CHANGELOG.md | 4 ++++ skills/projex/SKILL.md | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371ec7b..25b3937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **`skills/projex/` agent skill** — A new end-user agent skill (`SKILL.md` + `references/cli-help.md` drift baseline) that teaches any coding agent to drive Projex end-to-end: `init`, components and themes, non-interactive content operations, the `projex.config.ts` field reference, provider environment variables, and troubleshooting. Every CLI example is verified against the live `--help` output and run non-interactively. + --- ## [1.6.0] - 2026-08-19 diff --git a/skills/projex/SKILL.md b/skills/projex/SKILL.md index 8f0a5f8..c9f4388 100644 --- a/skills/projex/SKILL.md +++ b/skills/projex/SKILL.md @@ -34,6 +34,8 @@ Target stack: **Next.js** (required for the documented page patterns), Node.js ## 1. Init +> **Published-package gotcha:** `@manningworks/projex` declares its CLI runtime deps (`commander`, `ts-morph`, `chalk`, `@inquirer/prompts`) as *optional* peerDependencies, so a plain `npx @manningworks/projex …` or a fresh `npm install @manningworks/projex` **crashes with `ERR_MODULE_NOT_FOUND` on the first command** — install the four peers once (see Troubleshooting) or run from a source build (`pnpm build`). + ```bash npx @manningworks/projex init -y # basic scaffold, no prompts npx @manningworks/projex init --github -y # auto-discover GitHub repos, no prompts @@ -54,7 +56,10 @@ Verified non-interactive behavior: - `init --github -y` derives the GitHub username from **`git config user.name`** — if that is not a real GitHub handle (e.g. a full name) you get `GitHub user "X" not found` or someone else's repos. Set - `git config user.name <github-handle>` first. With `--yes`, forks are + `git config user.name <github-handle>` first. If `user.name` is + unset/empty, a warning (`⚠ Could not detect GitHub username from git + config`) is printed and the **basic scaffold is written instead** — no + error, exit 0. With `--yes`, forks are excluded; archived repos, `*.template`, `*.github.io` and repos without a description are filtered out. Generated entries: `type: 'github'`, `status: 'active'`, `featured: false`, and **`background: null, why: null` — @@ -228,7 +233,7 @@ npx @manningworks/projex add post <project-id> --title "Announcing v1" --date 20 - `<project-id>` must exist, else exit 1 with the valid id list. - learning + challenge both append to `struggles` (`{ type: 'learning'|'challenge', text }`); use `challenge` for obstacles. -- `--date` must be `YYYY-MM-DD`. +- `--date` expects format `YYYY-MM-DD` (prompt-validated; flag values are not checked). - **`--url` on `add post` is required in practice**: omitting it prompts (cancels non-interactively; nothing added). @@ -272,7 +277,7 @@ npx @manningworks/projex remove post <id> --index 0 ```bash npx @manningworks/projex list -# table: ID | Type | Status | Featured (★) | Name +# table: (index) | ID | Type | Status | Featured (★) | Name ``` No flags. Use it to discover ids before edit/remove. @@ -289,7 +294,7 @@ export const projects = defineProjects([ ], { commits: 3, // recent commits to fetch for github/hybrid (default 0) fetchNpmTimestamps: false, // fetch npm createdAt/updatedAt for date sorting - onError: 'throw', // 'throw' | 'warn' | 'silent' — data-fetch error policy + onError: 'throw', // 'throw' | 'warn' | 'silent' (default: 'warn') — data-fetch error policy }) ``` @@ -375,6 +380,8 @@ your build environment** (`.env.local` locally, CI secrets for deploys). ## 6. Troubleshooting +**`ERR_MODULE_NOT_FOUND` on the first CLI command (e.g. `Cannot find package 'commander'`).** The published `@manningworks/projex` declares its CLI runtime deps (`commander`, `ts-morph`, `chalk`, `@inquirer/prompts`) as *optional* `peerDependencies`, so npm/npx do not install them and the first command crashes before it runs. Fix: `npm install commander ts-morph chalk @inquirer/prompts` (install the package's declared `peerDependencies`; do not pin versions), or run from a source build (`pnpm build`) where the peers are present. + **`Validation failed ... Expected string, received null` after `init --github`.** Generated entries contain `background: null` / `why: null`, but the schema accepts only strings (or absence). Delete those lines from every generated