Scaffolding a project
create-skein-js generates a working skein-js project from an empty directory. It is the fastest path from nothing to a running agent server, and it is optional — everything it emits is a file you could write yourself, and the last section shows you exactly that.
For a guided walkthrough rather than a reference, start with your first agent.
Quick start
npm create skein-js@latest my-agentpnpm create skein-js my-agent
yarn create skein-js my-agent
npx create-skein-js my-agentKeep the @latest. Without it, npm's npx cache — and pnpm dlx's 24-hour cache — can serve a stale copy of the scaffolder.
What it generates
my-agent/
├── langgraph.json Points skein at your graphs; the LangGraph CLI's format
├── package.json The skein CLI lifecycle as scripts
├── tsconfig.json Strict, ESM, bundler resolution
├── vitest.config.ts
├── compose.dev.yaml Postgres + Redis — what `start` needs
├── .env Ready to use, gitignored — never overwritten if one already exists
├── .env.example A committed reference copy of it
├── .gitignore
├── README.md Explains each of these files
└── src/
├── echo-graph.ts Runs with no API key, no network
├── echo-graph.test.ts So `npm test` is green on the first commit
└── agent-graph.ts Only with --provider: a ReAct agent with a working toolThe scripts are the whole skein CLI lifecycle:
| Script | Command | What it does |
|---|---|---|
dev | skein dev --port 2024 | In-memory drivers, hot reload, state persisted to .skein/ |
dev:services | docker compose -f compose.dev.yaml up -d --wait | Postgres + Redis, for the durable dev and for start |
dev:postgres | skein dev … --store postgres --queue redis | The same hot reload, against the drivers production uses |
build | skein build --artifact-only | Graphs → plain JavaScript in .skein/build |
start | skein start -c .skein/build/langgraph.json | Serve that build — the production entrypoint |
typecheck | tsc --noEmit | |
test | vitest run |
That is the in-memory axis, which is the default. The storage prompt decides only which of the two dev spellings is which: pick Postgres and dev becomes the durable one, with the in-memory one emitted as dev:memory instead of dev:postgres. Both are always on disk, so the choice sets a default rather than removing an option.
dev:services uses --wait so it returns once both healthchecks pass, not merely once the containers exist — otherwise the very next command races first-boot initdb.
start names the artifact's own langgraph.json because skein start serves a build, not a source project: it wants schemas.json beside the config it loads, and that file only exists in .skein/build. Run from the project root like this, it also picks up the POSTGRES_URI and REDIS_URI from the project's .env — skein start reads a conventional .env from its working directory as well as from the config's, because an artifact deliberately carries none of its own (it is the Docker build context). So dev:services && build && start works with nothing to edit first.
Two deliberate choices worth knowing:
devnever needs a credential or a service. Theechograph is always present and always first ingraphs, so the very first request works with an empty.env.compose.dev.yamlis always generated, not hidden behind a flag, and thePOSTGRES_URI/REDIS_URIin.envare live rather than commented out.skein startis durable-only — it defaults to--store postgres --queue redisand fails without those two — so a project shipping astartscript has to ship both the services and the URIs that reach them, or the script is a trap. The values are the ones the generated compose file serves, so there was never anything to decide.
Options
create-skein-js [directory]
-m, --provider <name> none | google | anthropic | openai (default: prompted, else none)
--pm <name> npm | pnpm | yarn | bun (default: detected)
--no-install Skip installing dependencies (else: prompted, default yes)
--no-git Skip initializing a git repository (else: prompted)
-y, --yes Accept every default; never prompt
-f, --force Scaffold into a directory that is not empty
-v, --version
-h, --helpPassing flags through npm needs a -- separator. pnpm create and npx do not:
npm create skein-js@latest my-agent -- --provider anthropic
pnpm create skein-js my-agent --provider anthropic--provider
| Package added | .env.example gains | Emits agent-graph.ts? | |
|---|---|---|---|
none (default) | — | — | no |
google | @langchain/google-genai | GOOGLE_API_KEY | yes |
anthropic | @langchain/anthropic | ANTHROPIC_API_KEY | yes |
openai | @langchain/openai | OPENAI_API_KEY | yes |
With a provider you also get a ReAct agent wired to a live weather tool that needs no key of its own, so the agent is genuinely runnable the moment you add your model key.
Until the key is set, agent fails to load naming the variable it wants — a load-failure block, while echo keeps serving. skein dev watches .env, so filling the key in takes effect on save.
Behaviour you can rely on
- It never hangs unattended. Prompts appear only when both streams are a TTY,
--yeswas not passed, andCIis unset. Otherwise it takes the flag, then the default — safe inside a Dockerfile or a CI job. - A failed install is not fatal. Your files are already written; the closing output just adds
installback to the steps. - Scaffolding into a fresh clone works. A directory holding only
.git,LICENSE, editor folders or.DS_Storecounts as empty, so "create an empty repo, clone it, scaffold into it" needs no--force. - git is skipped inside an existing work tree, so it never nests a repository in yours — and the closing output says so, rather than leaving you to infer it from a missing
.git. A failure (git absent, or nouser.emailconfigured) is reported too, and distinctly: the two used to be the same silent non-event. - The version is pinned to a matching runtime. Because every
packages/*shares one version,create-skein-js@x.y.zpinsskein-js@^x.y.z— the scaffolder and the runtime it scaffolds are always the same release.
Nx and other monorepos
Scaffold into whatever directory you want — the generated project is self-contained, so it works inside a workspace as-is:
npm create skein-js@latest apps/my-agentThere is deliberately no skein Nx plugin. A generator collection would be permanent public API tracking Nx's release cadence, and it would buy you one file you can write once and own yourself. Here is that file — apps/my-agent/project.json:
{
"name": "my-agent",
"projectType": "application",
"targets": {
"dev": {
"executor": "nx:run-commands",
"cache": false,
"options": { "command": "skein dev --port 2024", "cwd": "apps/my-agent" }
},
"build": {
"executor": "nx:run-commands",
"options": { "command": "skein build --artifact-only", "cwd": "apps/my-agent" }
},
"start": {
"executor": "nx:run-commands",
"cache": false,
"options": { "command": "skein start", "cwd": "apps/my-agent" }
},
"typecheck": {
"executor": "nx:run-commands",
"options": { "command": "tsc --noEmit", "cwd": "apps/my-agent" }
}
}
}dev and start are marked "cache": false because a long-running server has no meaningful cached result. Explicit targets work on every Nx version, with no plugin to install and nothing to migrate.
To share the workspace's TypeScript settings, replace the generated tsconfig.json with one that extends your base:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "noEmit": true, "types": ["node"] },
"include": ["src/**/*.ts"]
}The same approach works for Turborepo, pnpm workspaces, or plain npm workspaces — the project is just a package with a langgraph.json in it.
If you don't want the scaffolder
Nothing here is load-bearing. Two alternatives:
Copy a runnable example. The examples/ directory has one project per framework and pattern:
npx degit skein-js/skein-js/examples/express-basic my-agentNote that this copies from main, which tracks unreleased work: the examples depend on workspace:* versions that only resolve inside the monorepo, so you will need to replace those with real version ranges. The scaffolder exists partly to avoid exactly that.
Write the three files yourself. A skein project is a graph, a langgraph.json, and the CLI:
npm install -D skein-js
npm install @langchain/core @langchain/langgraph// src/graph.ts
import { AIMessage } from "@langchain/core/messages";
import { MessagesAnnotation, StateGraph } from "@langchain/langgraph";
export const graph = new StateGraph(MessagesAnnotation)
.addNode("echo", (state) => ({
messages: [new AIMessage(`echo: ${state.messages.at(-1)?.content}`)],
}))
.addEdge("__start__", "echo")
.addEdge("echo", "__end__")
.compile();// langgraph.json
{ "node_version": "24", "graphs": { "agent": "./src/graph.ts:graph" }, "env": ".env" }Add "type": "module" to your package.json, then npx skein dev. That is the entire contract — see langgraph-cli-compat.md for every field it accepts.
See also
- Your first agent — the guided version of all of this
- Getting started — the paths for when you already have a graph
- LangGraph CLI compatibility — every
langgraph.jsonfield