Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/create-agent-bundle-scaffolder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"create-agent-bundle": minor
---

New package: the `create-agent-bundle` scaffolder (RFC #50 Phase 3).
`npm create agent-bundle` / `npx create-agent-bundle` emits a ready-to-run
plugin project from one of three checked-in templates — `minimal`
(skills-only), `mcp-server` (one conventional `src/mcp/<server-id>.ts`
factory entry plus an artifact script), and `cli-tool` (the `src/cli.ts` bin
convention plus a `src/index.ts` library export). Interactive prompts cover
name, template, and host targets, with full non-interactive flags
(`--template`, `--targets`, `--package-manager`, `--no-install`,
`--framework-version`). Scaffolded projects pin `agent-bundle` to the
pkg.pr.new preview of the same commit the scaffolder shipped from, carry a
`check` gate (validate + build + typecheck + test), and validate with zero
diagnostics, including the `AB473x` convention nudges.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ Use a PR number or the SHA of a commit whose package-preview run succeeded (ever

## Quick start

Describe the plugin in `agent-bundle.config.ts` at the project root:
The fastest start is the scaffolder — it prompts for a name, a template
(`minimal`, `mcp-server`, or `cli-tool`), and the host targets, then emits a
project that already passes its own `check`:

```sh
npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@<sha-or-pr> my-plugin
```

(`npm create agent-bundle` once npm releases exist. See the
[create-agent-bundle README](packages/create-agent-bundle/README.md) for
templates and flags.)

Or describe the plugin by hand in `agent-bundle.config.ts` at the project root:

```ts
import { defineConfig } from 'agent-bundle/config';
Expand Down
11 changes: 11 additions & 0 deletions docs/preview-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@1
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/rsc-runtime@1
```

The `create-agent-bundle` scaffolder is published to the same channel and is
meant to be run rather than installed:

```sh
npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@<sha-or-pr> my-plugin
```

A scaffolded project pins `agent-bundle` to the preview of the same commit
the scaffolder came from, so both sides of the pairing rule below hold
automatically.

`@1` resolves to the last preview published for PR #1 — commit `5685521` at the
time of its merge, which is the state that landed on `main`.

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@
},
"packageManager": "pnpm@11.23.0",
"scripts": {
"build": "pnpm --filter agent-bundle build && pnpm --filter @agent-bundle/rsc-runtime build",
"lint:package": "publint packages/agent-bundle",
"build": "pnpm --filter agent-bundle build && pnpm --filter @agent-bundle/rsc-runtime build && pnpm --filter create-agent-bundle build",
"lint:package": "publint packages/agent-bundle && publint packages/create-agent-bundle",
"test": "pnpm test:unit && pnpm test:integration",
"test:unit": "rstest --config rstest.unit.config.ts",
"test:integration": "pnpm build && pnpm test:integration:run",
"test:integration:run": "AGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 rstest --config rstest.integration.config.ts",
"test:watch": "rstest --config rstest.config.ts --watch",
"lint": "rslint .",
"typecheck": "tsc --noEmit && tsc --project packages/workbench/tsconfig.json",
"typecheck": "tsc --noEmit && tsc --project packages/workbench/tsconfig.json && tsc --project packages/create-agent-bundle/tsconfig.json",
"check": "pnpm build && pnpm test:unit && pnpm test:integration:run && pnpm lint && pnpm typecheck",
"docs:runtime-topology": "node scripts/rsc-runtime-topology.mjs --root . --output docs/architecture/rsc-runtime-workbench.md",
"eval:spot": "pnpm build && pnpm --filter @agent-bundle/rsc-agent-runtime-demo build && pnpm --filter @agent-bundle/rsc-agent-runtime-demo exec rstest run tests/micro-eval.spot.test.ts --config rstest.config.ts",
"check:runtime-topology": "node scripts/rsc-runtime-topology.mjs --root . --output docs/architecture/rsc-runtime-workbench.md --check",
"test:examples:browser": "rstest --config rstest.config.ts packages/workbench/tests/examples-real.e2e.test.ts",
"test:packed": "rstest --config rstest.config.ts packages/agent-bundle/tests/release-audit.test.ts packages/agent-bundle/tests/packed-consumer.test.ts packages/agent-bundle/tests/dev-workbench-packaging.test.ts packages/agent-bundle/tests/public-api-packed.test.ts packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts packages/agent-bundle/tests/packed-native-smoke.test.ts packages/workbench/tests/packed-release.e2e.test.ts",
"test:packed": "rstest --config rstest.config.ts packages/agent-bundle/tests/release-audit.test.ts packages/agent-bundle/tests/packed-consumer.test.ts packages/agent-bundle/tests/dev-workbench-packaging.test.ts packages/agent-bundle/tests/public-api-packed.test.ts packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts packages/agent-bundle/tests/packed-native-smoke.test.ts packages/create-agent-bundle/tests/scaffold-packed.e2e.test.ts packages/workbench/tests/packed-release.e2e.test.ts",
"test:packed:native": "rstest --config rstest.config.ts packages/agent-bundle/tests/packed-native-smoke.test.ts",
"test:packed:native:claude": "pnpm build && AGENT_BUNDLE_PACKED_NATIVE_CLAUDE_SMOKE=1 pnpm test:packed:native",
"test:packed:native:codex": "pnpm build && AGENT_BUNDLE_PACKED_NATIVE_CODEX_SMOKE=1 pnpm test:packed:native",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "pnpm build && changeset publish",
"preview:publish": "pkg-pr-new publish --previewVersion --peerDeps --no-compact --no-template './packages/agent-bundle' './packages/rsc-runtime'",
"preview:publish": "pkg-pr-new publish --previewVersion --peerDeps --no-compact --no-template './packages/agent-bundle' './packages/rsc-runtime' './packages/create-agent-bundle'",
"pack:dry-run": "pnpm build && npm pack ./packages/agent-bundle --dry-run --json",
"audit:release": "pnpm lint:package && attw --pack --profile esm-only packages/agent-bundle && node scripts/audit-packed-release.mjs",
"check:release": "pnpm pack:dry-run && pnpm audit:release && pnpm test:packed",
Expand Down
59 changes: 59 additions & 0 deletions packages/create-agent-bundle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# create-agent-bundle

Scaffold a new [agent-bundle](https://github.com/ScriptedAlchemy/agent-bundle)
plugin project from a checked-in template: one `agent-bundle.config.ts`, the
entry-file conventions, a passing test, and a delivery gate, ready to run.

```sh
npm create agent-bundle@latest my-plugin
# or
npx create-agent-bundle my-plugin --template mcp-server
```

Until the first npm release is cut, install the scaffolder from the
[pkg.pr.new preview channel](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md)
instead of the npm registry:

```sh
npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@<sha-or-pr> my-plugin
```

Interactive runs prompt for the project name, the template, and the host
targets. A run that names both a directory and a template is treated as
scripted and asks nothing — the remaining values fall back to their defaults.

## Options

| Flag | Meaning |
| --- | --- |
| `-d, --dir <dir>` | Project directory (also the first positional argument). `foo/bar` scaffolds into `foo/bar` and names the package `bar`; `@scope/name` keeps the scoped package name. |
| `-t, --template <name>` | `minimal`, `mcp-server`, or `cli-tool`. |
| `--targets <list>` | Comma-separated host targets: `portable`, `claude`, `codex`, `cursor`, `plugin`. Default: `portable,codex,claude`. |
| `--package-manager <name>` | `npm`, `pnpm`, `yarn`, or `bun`. Default: detected from the invoking client. |
| `--no-install` | Skip installing dependencies after scaffolding. |
| `--framework-version <spec>` | Pin the project's `agent-bundle` dependency to this spec (a version, a tarball path, or a URL). |
| `-h, --help` | Show usage. |

## Templates

| Template | What you get |
| --- | --- |
| `minimal` | A skills-only plugin: one Skill directory and nothing else. |
| `mcp-server` | A stdio MCP server through the `src/mcp/<server-id>.ts` convention (factory export, framework lifecycle shell) plus one artifact script. |
| `cli-tool` | An installable CLI through the `src/cli.ts` bin convention plus a `src/index.ts` library export with declarations. |

Every template ships a `check` script (validate + build + typecheck + test)
and validates with zero diagnostics — including the `AB473x` migration
nudges, because the templates are written against the entry conventions from
the start.

## The framework dependency

Scaffolded projects pin `agent-bundle` to an exact
[pkg.pr.new](https://pkg.pr.new) preview tarball. Without
`--framework-version`, the pin is derived from this scaffolder's own preview
version: pkg.pr.new publishes every workspace package of one commit under the
same `-preview-<sha>` suffix, so the scaffolder and the framework it pins
always come from the same commit. A non-preview build of the scaffolder has
no derivable default (the `agent-bundle` name on npm currently belongs to an
unrelated project) and requires `--framework-version` explicitly.
7 changes: 7 additions & 0 deletions packages/create-agent-bundle/bin/create-agent-bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

import process from 'node:process';

import { runCli } from '../dist/index.js';

process.exitCode = await runCli(process.argv.slice(2));
48 changes: 48 additions & 0 deletions packages/create-agent-bundle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "create-agent-bundle",
"version": "0.0.0",
"description": "Scaffold a new agent-bundle plugin project from a checked-in template.",
"license": "MIT",
"keywords": [
"agent-bundle",
"create",
"scaffold",
"cli"
],
"homepage": "https://github.com/ScriptedAlchemy/agent-bundle#readme",
"bugs": {
"url": "https://github.com/ScriptedAlchemy/agent-bundle/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ScriptedAlchemy/agent-bundle.git",
"directory": "packages/create-agent-bundle"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"type": "module",
"engines": {
"node": ">=22.19.0"
},
"files": [
"bin",
"dist",
"templates",
"README.md"
],
"bin": {
"create-agent-bundle": "./bin/create-agent-bundle.js"
},
"scripts": {
"build": "rslib build",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
"@clack/prompts": "1.7.0",
"@rslib/core": "0.23.2",
"@rstest/core": "0.11.10",
"@types/node": "26.4.0"
}
}
30 changes: 30 additions & 0 deletions packages/create-agent-bundle/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineConfig } from '@rslib/core';

/**
* Single self-contained ESM bundle: `@clack/prompts` is a devDependency so
* Rslib inlines it and the published package has zero runtime dependencies,
* the same shape `create-rstack` ships (its npm tarball declares no
* dependencies and bundles the prompt toolkit).
*/
export default defineConfig({
lib: [
{
bundle: true,
dts: false,
format: 'esm',
syntax: 'es2022',
},
],
output: {
cleanDistPath: true,
filenameHash: false,
target: 'node',
},
root: import.meta.dirname,
source: {
entry: {
index: './src/index.ts',
},
tsconfigPath: './tsconfig.build.json',
},
});
29 changes: 29 additions & 0 deletions packages/create-agent-bundle/src/framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { UsageError } from './options.ts';

const previewPattern = /-preview-([0-9a-f]{7,40})$/u;

export const previewFrameworkSpec = (sha: string): string =>
`https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@${sha}`;

/**
* Resolve the dependency spec the scaffolded project pins `agent-bundle` to.
*
* `--framework-version` wins verbatim (a version, a `file:` tarball, or any
* URL npm accepts). Otherwise the sha is derived from this scaffolder's own
* preview version: pkg.pr.new publishes every workspace package of one
* commit under the same `<version>-preview-<sha>` string, so the paired
* `agent-bundle` preview of the very build that shipped this scaffolder is
* always the right default. There is no derivable default outside a preview
* build — the `agent-bundle` name on npm belongs to an unrelated project, so
* falling back to a semver range would install the wrong package.
*/
export const resolveFrameworkSpec = (ownVersion: string, flag: string | undefined): string => {
if (flag !== undefined && flag.trim() !== '') return flag.trim();
const preview = previewPattern.exec(ownVersion);
if (preview !== null) return previewFrameworkSpec(preview[1]!);
throw new UsageError(
`This build of create-agent-bundle (${ownVersion}) is not a pkg.pr.new preview, so it cannot derive `
+ 'a default agent-bundle version. Pass --framework-version <spec> — for example '
+ '--framework-version https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@<sha>.',
);
};
Loading
Loading