From c63b01e47ae25f88592d8de660c8f8052f1689a1 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Sun, 5 Apr 2026 08:14:22 -0600 Subject: [PATCH] feat: auto-install skill on npm install, remove vit setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship the using-vit skill inside the npm package with a postinstall script that runs `npx skills add` globally. Delegate `vit learn` to `npx skills add` via temp dir instead of direct file writes. Remove `vit setup` command entirely — new onboarding is just `npm install -g vit` → `vit login` → open agent. --- CLAUDE.md | 2 +- COMMANDS.md | 14 +---- CONTRIBUTING.md | 4 +- README.md | 5 +- VOCAB.md | 9 +-- docs/decks/atmosphere-2026/index.html | 1 - docs/start/index.html | 11 ++-- package.json | 7 ++- skills/vit/COMMANDS.md | 18 +----- skills/vit/SKILL.md | 11 ++-- src/cli.js | 2 - src/cmd/doctor.js | 21 ++----- src/cmd/learn.js | 90 ++++++++++++++++----------- src/cmd/setup.js | 76 ---------------------- src/postinstall.js | 26 ++++++++ test/doctor.test.js | 7 +-- test/json-output.test.js | 2 +- test/setup.test.js | 28 --------- 18 files changed, 109 insertions(+), 225 deletions(-) delete mode 100644 src/cmd/setup.js create mode 100644 src/postinstall.js delete mode 100644 test/setup.test.js diff --git a/CLAUDE.md b/CLAUDE.md index 15b71f9..35d6a0e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ Development guidelines for vit, a social toolkit for personalized software built ## Project Overview vit is a Bun CLI for discovering, vetting, remixing, and shipping software capabilities: -- `vit setup`, `vit login`, `vit init`, `vit doctor` — authentication and environment setup +- `vit login`, `vit init`, `vit doctor` — authentication and environment setup - `vit beacon`, `vit config` — project beacon inspection and user configuration - `vit firehose` — listen to Jetstream for cap events - `vit ship`, `vit skim` — publish and read caps diff --git a/COMMANDS.md b/COMMANDS.md index a7fa0e6..43677af 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -9,19 +9,7 @@ vit is a human+agent collaboration tool. commands are labeled by who runs them: --- -## setup commands - -### setup - -**you run this** (terminal) - -check prerequisites and install the vit skill for your coding agent. - -```bash -vit setup -``` - -this is the first command you run. it verifies your environment (Node.js, git) and installs the agent skill so your coding agent knows how to use vit. +## getting started ### login diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4de47bb..e624ac0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,12 +17,12 @@ run these one-time steps to join vit's own project: **you run this** (terminal): ```bash -vit setup +npm install -g vit vit login .bsky.social vit adopt vit:github.com/solpbc/vit ``` -`setup` checks prerequisites and installs the vit skill for your agent. +installing vit auto-installs the agent skill. `login` authenticates with Bluesky via browser OAuth. `adopt` forks or clones the vit repo and initializes it. diff --git a/README.md b/README.md index b92b402..97810ea 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,10 @@ vit is a CLI where you and your coding agent discover, evaluate, and share softw ```bash npm install -g vit -vit setup vit login your-handle.bsky.social ``` -then open your coding agent (Claude Code, Codex CLI, or Gemini CLI) — it already knows how to use vit because `setup` installed the skill. your agent runs `vit init` to connect your project to the network, then `vit skim` to discover what others have built. +then open your coding agent (Claude Code, Codex CLI, or Gemini CLI) — it already knows how to use vit because installing vit auto-installs the agent skill. your agent runs `vit init` to connect your project to the network, then `vit skim` to discover what others have built. **[full getting started guide →](https://v-it.org/start/)** @@ -66,7 +65,7 @@ make install ## works with -vit is a human+agent collaboration tool. it works with [Claude Code](https://claude.ai/code), [Codex CLI](https://github.com/openai/codex), and [Gemini CLI](https://github.com/google-gemini/gemini-cli). some commands are for you (setup, login, vet), others are for your agent (skim, remix, ship). +vit is a human+agent collaboration tool. it works with [Claude Code](https://claude.ai/code), [Codex CLI](https://github.com/openai/codex), and [Gemini CLI](https://github.com/google-gemini/gemini-cli). some commands are for you (login, vet), others are for your agent (skim, remix, ship). ## reference diff --git a/VOCAB.md b/VOCAB.md index 600e216..c1e31a3 100644 --- a/VOCAB.md +++ b/VOCAB.md @@ -100,15 +100,10 @@ vit maintains explicit ancestry for traceability. check system readiness and configure vit for first use. ```bash -vit setup vit init vit doctor ``` -`setup` -- log in to Bluesky (invokes login flow) -- install skills (agent capabilities) - `init` - initialize `.vit/` in the current git repo - validates or sets beacon @@ -257,7 +252,7 @@ learn is the skill counterpart to remix — where remix integrates a cap into a setup (one-time): ```bash -vit setup +npm install -g vit vit adopt ``` @@ -279,7 +274,7 @@ vit vouch ``` conceptual lifecycle: -- setup prepares the system +- install prepares the system - init prepares the project environment - adopt joins a project via its beacon - beacon anchors the project diff --git a/docs/decks/atmosphere-2026/index.html b/docs/decks/atmosphere-2026/index.html index a490489..d8b7dc0 100644 --- a/docs/decks/atmosphere-2026/index.html +++ b/docs/decks/atmosphere-2026/index.html @@ -412,7 +412,6 @@

you

-

setup

login

vet

vouch

diff --git a/docs/start/index.html b/docs/start/index.html index 85c9902..d8802b1 100644 --- a/docs/start/index.html +++ b/docs/start/index.html @@ -297,11 +297,10 @@
-

1. install & set up

+

1. install

you run this (terminal) -
npm install -g vit
-vit setup
-

setup checks your prerequisites and installs the vit skill so your coding agent knows how to use vit. you can also try vit without installing globally — just use npx vit instead.

+
npm install -g vit
+

installing vit auto-installs the agent skill so your coding agent knows how to use vit. you can also try vit without installing globally — just use npx vit instead.

2. log in

you run this (terminal) @@ -320,7 +319,7 @@ vit setup

4. open your agent

-

open your coding agent (Claude Code, Codex, Gemini CLI). because you ran vit setup, your agent already has the vit skill installed and knows how to use every vit command.

+

open your coding agent (Claude Code, Codex, Gemini CLI). the vit skill was installed automatically, so your agent already knows how to use every vit command.

5. initialize your project

your agent runs this (inside Claude Code / Codex / Gemini CLI) @@ -351,7 +350,7 @@ vit setup

vit is a human+agent collaboration tool. some commands are for you, some are for your agent:

you run these (terminal):
-setup, login, vet — setup, authentication, and trust decisions stay with you.

+login, vet — authentication and trust decisions stay with you.

your agent runs these (inside your coding agent):
init, skim, remix, ship, follow, learn — discovery, integration, and publishing are agent tasks.

diff --git a/package.json b/package.json index 5e9dad0..8dafc18 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,13 @@ }, "files": [ "bin/", - "src/" + "src/", + "skills/vit/" ], "type": "module", - "scripts": {}, + "scripts": { + "postinstall": "node src/postinstall.js" + }, "engines": { "node": ">=20.0.0" }, diff --git a/skills/vit/COMMANDS.md b/skills/vit/COMMANDS.md index 60fb10a..0d2d396 100644 --- a/skills/vit/COMMANDS.md +++ b/skills/vit/COMMANDS.md @@ -57,7 +57,7 @@ Options: - None. Output format: -- Text diagnostics for setup and beacon status. +- Text diagnostics for install and beacon status. Error conditions: - Config read/parse failures. @@ -195,22 +195,6 @@ Examples: ## Human-Only Commands -### `vit setup` -Usage: `vit setup` - -Options: -- None. - -Gate: -- `requireNotAgent()` - -Output format: -- Text status for tool prerequisites and login state. - -Error conditions: -- Running in an agent context. -- Missing required tools (`git` or `bun`). - ### `vit login ` Usage: `vit login ` diff --git a/skills/vit/SKILL.md b/skills/vit/SKILL.md index cf1636f..c4d68de 100644 --- a/skills/vit/SKILL.md +++ b/skills/vit/SKILL.md @@ -8,13 +8,13 @@ description: >- ## 1. Overview -vit is a Bun CLI for social software capabilities. Agents use it to initialize projects, follow accounts, skim caps from followed accounts, and ship new caps. Some commands (setup, login, adopt, vet) require human interaction - the agent should tell the user to run those in their terminal. +vit is a Bun CLI for social software capabilities. Agents use it to initialize projects, follow accounts, skim caps from followed accounts, and ship new caps. Some commands (login, adopt, vet) require human interaction - the agent should tell the user to run those in their terminal. ## 2. Prerequisites -Dependency chain: `setup → login → init → follow → skim/ship`. +Dependency chain: `login → init → follow → skim/ship`. -`setup` and `login` are human-only. The agent starts at `init`. Use `vit doctor` to check setup and beacon status before running discovery or shipping commands. +`login` is human-only. The agent starts at `init`. Use `vit doctor` to check beacon status before running discovery or shipping commands. ## 3. Agent Workflow @@ -64,10 +64,10 @@ Handoffs: ### Agent-usable commands ### `vit doctor` -- Description: Read-only diagnostic for setup and beacon status. +- Description: Read-only diagnostic for install and beacon status. - Usage: `vit doctor` - Key flags: none. -- Output: text status lines for setup and beacon. +- Output: text status lines for install and beacon. - Common errors: generic runtime or config read failures. ### `vit config [action] [key] [value]` @@ -163,7 +163,6 @@ When the user says "ship it", "vit ship", or asks you to publish a cap for work ## 5. Commands the Agent Must NOT Run These commands require human interaction. Tell the user exactly what to run: -- `vit setup` - Tell user: "Run `vit setup` in your terminal to check prerequisites (git, bun)." - `vit login ` - Tell user: "Run `vit login ` in your terminal to authenticate via browser OAuth." - `vit adopt ` - Tell user: "Run `vit adopt ` in your terminal to fork and clone a project." - `vit vet ` - Human review command. Tell the user to run it in their terminal. diff --git a/src/cli.js b/src/cli.js index 6693486..be8be15 100644 --- a/src/cli.js +++ b/src/cli.js @@ -19,7 +19,6 @@ import registerSkim from './cmd/skim.js'; import registerVet from './cmd/vet.js'; import registerVouch from './cmd/vouch.js'; import registerFollow from './cmd/follow.js'; -import registerSetup from './cmd/setup.js'; import registerHack from './cmd/hack.js'; import registerLink from './cmd/link.js'; import registerInbox from './cmd/inbox.js'; @@ -46,7 +45,6 @@ registerRemix(program); registerVet(program); registerVouch(program); registerFollow(program); -registerSetup(program); registerHack(program); registerLink(program); registerInbox(program); diff --git a/src/cmd/doctor.js b/src/cmd/doctor.js index 520f7f5..2816635 100644 --- a/src/cmd/doctor.js +++ b/src/cmd/doctor.js @@ -41,10 +41,6 @@ export default function register(program) { async function checkHealth(opts) { try { const config = loadConfig(); - const setup = { - done: !!config.setup_at, - at: config.setup_at ? new Date(config.setup_at * 1000).toISOString() : null, - }; let installType = 'not on PATH'; let vitPath = which(name); let installPath = vitPath || null; @@ -55,13 +51,6 @@ export default function register(program) { let blueskyOk = false; let pds = null; - if (config.setup_at) { - const when = new Date(config.setup_at * 1000).toISOString(); - if (!opts.json) console.log(`${mark} setup: ok (${when})`); - } else { - if (!opts.json) console.log(`${mark} setup: not done (run ${name} setup)`); - } - if (!vitPath) { installType = 'not on PATH'; if (!opts.json) console.log(`${mark} install: not on PATH`); @@ -91,12 +80,13 @@ export default function register(program) { if (!opts.json) console.log(`${mark} beacon: not set (run vit init)`); } - const skillPath = join(process.cwd(), '.claude', 'skills', 'using-vit', 'SKILL.md'); - skillInstalled = existsSync(skillPath); - if (existsSync(skillPath)) { + const projectSkillPath = join(process.cwd(), '.claude', 'skills', 'using-vit', 'SKILL.md'); + const userSkillPath = join(homedir(), '.claude', 'skills', 'using-vit', 'SKILL.md'); + skillInstalled = existsSync(projectSkillPath) || existsSync(userSkillPath); + if (skillInstalled) { if (!opts.json) console.log(`${mark} skill: ok (using-vit)`); } else { - if (!opts.json) console.log(`${mark} skill: not installed (run ${name} setup)`); + if (!opts.json) console.log(`${mark} skill: not installed (reinstall vit)`); } // Report installed skills @@ -162,7 +152,6 @@ export default function register(program) { if (opts.json) { jsonOk({ - setup, install: { type: installType, path: installPath }, beacon, skill: skillInstalled, diff --git a/src/cmd/learn.js b/src/cmd/learn.js index f0f157c..1f65b76 100644 --- a/src/cmd/learn.js +++ b/src/cmd/learn.js @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 sol pbc -import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { mkdirSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; import { join, dirname } from 'node:path'; -import { homedir } from 'node:os'; +import { homedir, tmpdir } from 'node:os'; import { requireDid } from '../lib/config.js'; import { SKILL_COLLECTION } from '../lib/constants.js'; import { restoreAgent } from '../lib/oauth.js'; @@ -169,48 +170,61 @@ export default function register(program) { const record = match.value; if (verbose) vlog(`[verbose] found skill: ${record.name} from ${match.uri}`); - // Determine install path - let installDir; - if (isUserInstall) { - installDir = join(homedir(), '.claude', 'skills', skillName); - } else { - installDir = join(process.cwd(), '.claude', 'skills', skillName); - } + // Install via skills CLI + const tempDir = mkdtempSync(join(tmpdir(), 'vit-learn-')); + try { + writeFileSync(join(tempDir, 'SKILL.md'), record.text); + if (verbose) vlog('[verbose] wrote SKILL.md to temp dir'); + + // Download resource blobs to temp dir + if (record.resources && record.resources.length > 0) { + const authorDid = match.uri.split('/')[2]; + const pds = await resolvePds(authorDid); + + for (const resource of record.resources) { + const resourcePath = join(tempDir, resource.path); + mkdirSync(dirname(resourcePath), { recursive: true }); - mkdirSync(installDir, { recursive: true }); - - // Write SKILL.md from text field — verbatim, no reconstruction - writeFileSync(join(installDir, 'SKILL.md'), record.text); - if (verbose) vlog(`[verbose] wrote SKILL.md to ${installDir}`); - - // Download and write resource blobs - if (record.resources && record.resources.length > 0) { - const authorDid = match.uri.split('/')[2]; - const pds = await resolvePds(authorDid); - - for (const resource of record.resources) { - const resourcePath = join(installDir, resource.path); - mkdirSync(dirname(resourcePath), { recursive: true }); - - try { - // Download blob from PDS - const blobCid = resource.blob?.ref?.$link || resource.blob?.cid; - if (blobCid) { - const blobUrl = new URL('/xrpc/com.atproto.sync.getBlob', pds); - blobUrl.searchParams.set('did', authorDid); - blobUrl.searchParams.set('cid', blobCid); - const blobRes = await fetch(blobUrl); - if (!blobRes.ok) throw new Error(`blob fetch failed: ${blobRes.status}`); - const blobData = Buffer.from(await blobRes.arrayBuffer()); - writeFileSync(resourcePath, blobData); - if (verbose) vlog(`[verbose] wrote resource: ${resource.path}`); + try { + // Download blob from PDS + const blobCid = resource.blob?.ref?.$link || resource.blob?.cid; + if (blobCid) { + const blobUrl = new URL('/xrpc/com.atproto.sync.getBlob', pds); + blobUrl.searchParams.set('did', authorDid); + blobUrl.searchParams.set('cid', blobCid); + const blobRes = await fetch(blobUrl); + if (!blobRes.ok) throw new Error(`blob fetch failed: ${blobRes.status}`); + const blobData = Buffer.from(await blobRes.arrayBuffer()); + writeFileSync(resourcePath, blobData); + if (verbose) vlog(`[verbose] wrote resource: ${resource.path}`); + } + } catch (err) { + console.error(`warning: failed to download resource ${resource.path}: ${err.message}`); } - } catch (err) { - console.error(`warning: failed to download resource ${resource.path}: ${err.message}`); } } + + // Delegate to skills CLI + const addArgs = ['skills', 'add', tempDir, '-a', 'claude-code', '-y']; + if (isUserInstall) addArgs.push('-g'); + const addResult = spawnSync('npx', addArgs, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + if (addResult.status !== 0) { + const errText = (addResult.stderr || addResult.stdout || '').trim(); + throw new Error(`skill install failed: ${errText || 'unknown error'}`); + } + if (verbose) vlog('[verbose] installed via npx skills add'); + } finally { + try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } + // Determine install path for logging + const installDir = isUserInstall + ? join(homedir(), '.claude', 'skills', skillName) + : join(process.cwd(), '.claude', 'skills', skillName); + // Log to learned.jsonl try { appendLog('learned.jsonl', { diff --git a/src/cmd/setup.js b/src/cmd/setup.js deleted file mode 100644 index 7ced45a..0000000 --- a/src/cmd/setup.js +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2026 sol pbc - -import { spawnSync } from 'node:child_process'; -import { loadConfig, saveConfig } from '../lib/config.js'; -import { requireNotAgent } from '../lib/agent.js'; -import { which } from '../lib/compat.js'; -import { mark, brand, name } from '../lib/brand.js'; - -export default function register(program) { - program - .command('setup') - .description('Initialize user-level vit setup') - .action(async () => { - try { - const gate = requireNotAgent(); - if (!gate.ok) { - console.error(`${name} setup must be run by a human. run it in your own terminal.`); - process.exitCode = 1; - return; - } - - const gitPath = which('git'); - console.log(`${mark} git: ${gitPath ? 'found' : 'not found'}`); - if (!gitPath) { - console.error('missing required tool: git'); - process.exitCode = 1; - return; - } - - // skill installation - const npxPath = which('npx'); - if (npxPath) { - try { - const result = spawnSync( - 'npx', ['skills', 'add', 'https://github.com/solpbc/vit/tree/main/skills/vit', '-a', 'claude-code', '-y'], - { - encoding: 'utf-8', - stdio: ['pipe', 'pipe', 'pipe'], - } - ); - if (result.status === 0) { - console.log(`${mark} skill: installed (using-vit)`); - } else { - const errText = (result.stderr || '').trim(); - console.log(`${mark} skill: failed (${errText || 'unknown error'})`); - } - } catch { - console.log(`${mark} skill: failed (could not run npx skills)`); - } - } else { - console.log(`${mark} skill: skipped (npx not found)`); - } - - const config = loadConfig(); - if (config.did) { - console.log(`${mark} login: ${config.did}`); - } else { - console.log(`${mark} login: not logged in`); - console.log(`next: run '${name} login ' to authenticate with Bluesky`); - } - - if (!config.setup_at) { - config.setup_at = Math.floor(Date.now() / 1000); - saveConfig(config); - } - - if (config.did) { - console.log(`${brand} setup complete`); - } - } catch (err) { - console.error(err instanceof Error ? err.message : String(err)); - process.exitCode = 1; - } - }); -} diff --git a/src/postinstall.js b/src/postinstall.js new file mode 100644 index 0000000..63a389c --- /dev/null +++ b/src/postinstall.js @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 sol pbc + +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +try { + const currentFile = fileURLToPath(import.meta.url); + const currentDir = dirname(currentFile); + const skillDir = join(currentDir, '..', 'skills', 'vit'); + const result = spawnSync( + 'npx', + ['skills', 'add', skillDir, '-g', '-a', 'claude-code', '-y'], + { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + } + ); + + if (result.status === 0) { + console.log('vit: skill installed (using-vit)'); + } +} catch { + process.exit(0); +} diff --git a/test/doctor.test.js b/test/doctor.test.js index eeda871..69b8ad9 100644 --- a/test/doctor.test.js +++ b/test/doctor.test.js @@ -5,11 +5,6 @@ import { describe, test, expect } from 'bun:test'; import { run } from './helpers.js'; describe('vit doctor', () => { - test('reports setup status', () => { - const result = run('doctor'); - expect(result.stdout).toMatch(/setup:/); - }); - test('reports beacon status', () => { const result = run('doctor'); expect(result.stdout).toMatch(/beacon:/); @@ -32,6 +27,6 @@ describe('vit doctor', () => { test('vit status is an alias for doctor', () => { const result = run('status'); - expect(result.stdout).toMatch(/setup:/); + expect(result.stdout).toMatch(/install:/); }); }); diff --git a/test/json-output.test.js b/test/json-output.test.js index 881abab..d207832 100644 --- a/test/json-output.test.js +++ b/test/json-output.test.js @@ -62,7 +62,7 @@ describe('--json flag', () => { const r = run('doctor --json'); const j = parseJson(r.stdout); expect(j.ok).toBe(true); - expect(j).toHaveProperty('setup'); + expect(j).toHaveProperty('install'); expect(j).toHaveProperty('beacon'); expect(j).toHaveProperty('bluesky'); }); diff --git a/test/setup.test.js b/test/setup.test.js deleted file mode 100644 index 6027e63..0000000 --- a/test/setup.test.js +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2026 sol pbc - -import { describe, test, expect } from 'bun:test'; -import { run } from './helpers.js'; - -describe('vit setup', () => { - test('rejects when run inside a coding agent', () => { - const result = run('setup', undefined, { CLAUDECODE: '1' }); - expect(result.exitCode).toBe(1); - expect(result.stderr).toContain('must be run by a human'); - }); - - test('checks for git', () => { - const result = run('setup', undefined, { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }); - expect(result.stdout).toContain('git: found'); - }); - - test('reports login status', () => { - const result = run('setup', undefined, { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }); - expect(result.stdout).toMatch(/login:/); - }); - - test('reports skill installation status', () => { - const result = run('setup', undefined, { CLAUDECODE: '', GEMINI_CLI: '', CODEX_CI: '' }); - expect(result.stdout).toMatch(/skill:/); - }); -}); -- 2.51.2