diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml
index 9c334764..0fabed5a 100644
--- a/.github/workflows/canary.yml
+++ b/.github/workflows/canary.yml
@@ -27,13 +27,18 @@ jobs:
         run: |
           jq --arg prop "workspaces" 'del(.[$prop])' package.json > package.tmp.json && rm package.json && cp package.tmp.json package.json && rm package.tmp.json
           cd src
-          npm --no-git-tag-version version 0.0.0
+          npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S')
+          cd ../create-vocs
           npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S')
 
       - name: Build
         run: bun run build
 
       - name: Publish to npm
-        run: cd src && npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//')
+        run: | 
+          cd src 
+          npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//')
+          cd ../create-vocs
+          npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//')
         env:
           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/bun.lockb b/bun.lockb
index 2d2c6c1a..7d0e8c3a 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/create-farc/bin.ts b/create-farc/bin.ts
new file mode 100644
index 00000000..a241c751
--- /dev/null
+++ b/create-farc/bin.ts
@@ -0,0 +1,18 @@
+#!/usr/bin/env node
+import { createRequire } from 'node:module'
+import { cac } from 'cac'
+import { type CreateParameters, create } from './create.js'
+
+const require = createRequire(import.meta.url)
+const pkg = require('../package.json')
+
+const cli = cac('create-vocs')
+
+cli.usage('[options]').option('-n, --name [name]', 'Name of project')
+
+cli.help()
+cli.version(pkg.version)
+
+const { options } = cli.parse()
+
+if (!options.help) create(options as CreateParameters)
diff --git a/create-farc/create.ts b/create-farc/create.ts
new file mode 100644
index 00000000..0ceae64d
--- /dev/null
+++ b/create-farc/create.ts
@@ -0,0 +1,95 @@
+import { dirname, resolve } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { intro, log, outro, text } from '@clack/prompts'
+import { default as fs } from 'fs-extra'
+import { default as pc } from 'picocolors'
+
+export type CreateParameters = { name: string }
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+
+export async function create(params: CreateParameters) {
+  intro('Welcome to Farc!')
+
+  const templateDir = resolve(__dirname, '../templates/default')
+
+  const displayName =
+    params.name ||
+    ((await text({
+      message: 'Enter the name of your project',
+      placeholder: 'my-first-farc',
+      validate(value) {
+        if (!value) return 'Please enter a name.'
+        return
+      },
+    })) as string)
+  const name = kebabcase(displayName)
+
+  const destDir = resolve(process.cwd(), name)
+
+  // Copy contents
+  fs.copySync(templateDir, destDir)
+
+  // Replace dotfiles
+  for (const file of fs.readdirSync(destDir)) {
+    if (!file.startsWith('_')) continue
+    fs.renameSync(resolve(destDir, file), resolve(destDir, `.${file.slice(1)}`))
+  }
+
+  // Replace package.json properties
+  const pkgJson = fs.readJsonSync(resolve(destDir, 'package.json'))
+  pkgJson.name = name
+  fs.writeJsonSync(resolve(destDir, 'package.json'), pkgJson, { spaces: 2 })
+
+  // Wrap up
+  log.success(`Project successfully scaffolded in ${pc.blue(destDir)}!`)
+
+  const pkgManager = detectPackageManager()
+
+  log.message('Next steps:')
+  log.step(`1. ${pc.blue(`cd ./${name}`)} - Navigate to project`)
+  log.step(
+    `2. ${pc.blue(
+      pkgManagerInstallCommand(pkgManager),
+    )} - Install dependencies`,
+  )
+  log.step(
+    `3. ${pc.blue(pkgManagerRunCommand(pkgManager, 'dev'))} - Start dev server`,
+  )
+  log.step(`4. Head to ${pc.blue('http://localhost:3000')}`)
+
+  outro('Done! 🤠')
+}
+
+type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun'
+
+function detectPackageManager(): PackageManager {
+  const userAgent = process.env.npm_config_user_agent
+  if (!userAgent) return 'npm'
+  if (userAgent.includes('bun')) return 'bun'
+  if (userAgent.includes('yarn')) return 'yarn'
+  if (userAgent.includes('pnpm')) return 'pnpm'
+  if (userAgent.includes('npm')) return 'npm'
+  return 'npm'
+}
+
+function pkgManagerInstallCommand(pkgManager: PackageManager) {
+  if (pkgManager === 'bun') return 'bun install'
+  if (pkgManager === 'yarn') return 'yarn'
+  if (pkgManager === 'pnpm') return 'pnpm install'
+  return 'npm install'
+}
+
+function pkgManagerRunCommand(pkgManager: PackageManager, command: string) {
+  if (pkgManager === 'bun') return `bun run ${command}`
+  if (pkgManager === 'yarn') return `yarn ${command}`
+  if (pkgManager === 'pnpm') return `pnpm ${command}`
+  return `npm run ${command}`
+}
+
+function kebabcase(str: string) {
+  return str
+    .replace(/([a-z])([A-Z])/g, '$1-$2')
+    .replace(/[\s_]+/g, '-')
+    .toLowerCase()
+}
diff --git a/create-farc/package.json b/create-farc/package.json
new file mode 100644
index 00000000..f23589eb
--- /dev/null
+++ b/create-farc/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "create-farc",
+  "version": "0.0.1",
+  "type": "module",
+  "bin": {
+    "create-farc": "./_lib/bin.js"
+  },
+  "dependencies": {
+    "@clack/prompts": "^0.7.0",
+    "cac": "^6.7.14",
+    "detect-package-manager": "^3.0.1",
+    "fs-extra": "^11.1.1",
+    "picocolors": "^1.0.0"
+  },
+  "repository": "wevm/farc"
+}
diff --git a/create-farc/templates/default/.gitignore b/create-farc/templates/default/.gitignore
new file mode 100644
index 00000000..d654dad5
--- /dev/null
+++ b/create-farc/templates/default/.gitignore
@@ -0,0 +1,21 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# production
+/docs/dist
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# typescript
+*.tsbuildinfo
diff --git a/create-farc/templates/default/README.md b/create-farc/templates/default/README.md
new file mode 100644
index 00000000..61b52124
--- /dev/null
+++ b/create-farc/templates/default/README.md
@@ -0,0 +1 @@
+This is a [Farc](https://farc.dev) project bootstrapped with `create-farc`.
diff --git a/create-farc/templates/default/package.json b/create-farc/templates/default/package.json
new file mode 100644
index 00000000..d1aee99d
--- /dev/null
+++ b/create-farc/templates/default/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "Example",
+  "private": true,
+  "scripts": {
+    "dev": "bun run --hot src/index.tsx"
+  },
+  "dependencies": {
+    "bun": "latest",
+    "farc": "main",
+    "hono": "^3.12.8"
+  },
+  "devDependencies": {
+    "@types/bun": "latest"
+  }
+}
diff --git a/create-farc/templates/default/src/index.tsx b/create-farc/templates/default/src/index.tsx
new file mode 100644
index 00000000..3fe31784
--- /dev/null
+++ b/create-farc/templates/default/src/index.tsx
@@ -0,0 +1,58 @@
+import { Button, Farc, TextInput } from 'farc'
+
+const app = new Farc()
+
+app.frame('/', (context) => {
+  const { buttonValue, inputText, status } = context
+  const fruit = inputText || buttonValue
+  return {
+    image: (
+      <div
+        style={{
+          alignItems: 'center',
+          background:
+            status === 'response'
+              ? 'linear-gradient(to right, #432889, #17101F)'
+              : 'black',
+          backgroundSize: '100% 100%',
+          display: 'flex',
+          flexDirection: 'column',
+          flexWrap: 'nowrap',
+          height: '100%',
+          justifyContent: 'center',
+          textAlign: 'center',
+          width: '100%',
+        }}
+      >
+        <div
+          style={{
+            color: 'white',
+            fontSize: 60,
+            fontStyle: 'normal',
+            letterSpacing: '-0.025em',
+            lineHeight: 1.4,
+            marginTop: 30,
+            padding: '0 120px',
+            whiteSpace: 'pre-wrap',
+          }}
+        >
+          {status === 'response'
+            ? `Nice choice.${fruit ? ` ${fruit.toUpperCase()}!!` : ''}`
+            : 'Welcome!'}
+        </div>
+      </div>
+    ),
+    intents: [
+      <TextInput placeholder="Enter custom fruit..." />,
+      <Button value="apples">Apples</Button>,
+      <Button value="oranges">Oranges</Button>,
+      <Button value="bananas">Bananas</Button>,
+      status === 'response' && <Button.Reset>Reset</Button.Reset>,
+    ],
+  }
+})
+
+export default {
+  port: 3000,
+  fetch: app.fetch,
+}
diff --git a/create-farc/templates/default/tsconfig.json b/create-farc/templates/default/tsconfig.json
new file mode 100644
index 00000000..a7c4db3d
--- /dev/null
+++ b/create-farc/templates/default/tsconfig.json
@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "jsxImportSource": "hono/jsx",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["**/*.ts", "**/*.tsx"]
+}
diff --git a/create-farc/tsconfig.build.json b/create-farc/tsconfig.build.json
new file mode 100644
index 00000000..5279b9e9
--- /dev/null
+++ b/create-farc/tsconfig.build.json
@@ -0,0 +1,15 @@
+{
+  // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
+  "extends": "../tsconfig.base.json",
+  "exclude": ["_lib", "node_modules"],
+  "include": ["*"],
+  "compilerOptions": {
+    "allowSyntheticDefaultImports": true,
+    "declaration": true,
+    "declarationMap": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "outDir": "./_lib",
+    "sourceMap": true
+  }
+}
diff --git a/package.json b/package.json
index 2e397b9f..f01e4409 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,10 @@
 {
-  "workspaces": ["example", "src"],
+  "workspaces": ["create-farc", "example", "src"],
   "scripts": {
     "dev": "bun run --hot ./example/src/index.tsx",
-    "build": "bun run clean && tsc --project ./tsconfig.build.json",
+    "build": "bun run clean && bun run build:farc && bun run build:create-farc",
+    "build:farc": "tsc --project ./tsconfig.build.json",
+    "build:create-farc": "rimraf create-farc/_lib && tsc -p create-farc/tsconfig.build.json",
     "changeset": "changeset",
     "changeset:release": "bun run build && changeset publish",
     "changeset:version": "changeset version && bun install --lockfile-only",
@@ -18,6 +20,7 @@
     "@changesets/changelog-github": "^0.5.0",
     "@changesets/cli": "^2.27.1",
     "@types/bun": "latest",
+    "@types/fs-extra": "^11.0.4",
     "@vitest/coverage-v8": "^1.2.2",
     "hono": "^3.12.8",
     "rimraf": "^5.0.5",