+

+
Cursor VPet
+
+ Digimon virtual pet for Cursor that evolves as you use Agent — shares progress with OpenCode VPet
+
+
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+[](LICENSE)
+
+[Features](#-features) • [Installation](#-installation) • [Usage](#-usage) •
+[Commands](#-commands) • [Settings](#%EF%B8%8F-settings) • [Storage](#-storage) •
+[How It Works](#-how-it-works) • [Authors](#-authors)
+
+[](https://open-vsx.org/extension/sbugallo/cursor-vpet)
+
+[](https://github.com/sbugallo/opencode-vpet/releases)
+
+---
+
+
+
+## 📖 About
+
+**Cursor Agent** turns every prompt into real work — but what if that effort also fed a partner
+that grows with you? **Cursor VPet** adds a Digimon virtual pet to the Explorer sidebar. Your
+partner gains experience from Agent token usage and evolves through classic Digimon stages.
+
+It shares the same `pet.db` database as
+[@sbugallo/opencode-vpet](https://www.npmjs.com/package/@sbugallo/opencode-vpet), so progress
+carries over between Cursor and OpenCode.
+
+
+
+**Built with ❤️ for the developer community**
+
+[⬆ Back to Top](#cursor-vpet)
+
+
diff --git a/packages/cursor-vpet/hook-bridge.js b/packages/cursor-vpet/hook-bridge.js
new file mode 100644
index 0000000..9c0119a
--- /dev/null
+++ b/packages/cursor-vpet/hook-bridge.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/**
+ * Cursor hook bridge — reads hook JSON from stdin, appends to events file, responds {}.
+ * Fail-open: never blocks Cursor on errors.
+ */
+import { appendFileSync, mkdirSync } from "node:fs"
+import { homedir } from "node:os"
+import { dirname, join } from "node:path"
+import { Readable } from "node:stream"
+
+const EVENTS_PATH =
+ process.env.VPET_HOOK_EVENTS_PATH ?? join(homedir(), ".cursor", "vpet-hook-events.jsonl")
+
+const readStdin = async () => {
+ const chunks = []
+ for await (const chunk of Readable.toWeb(process.stdin)) {
+ chunks.push(Buffer.from(chunk))
+ }
+ return Buffer.concat(chunks).toString("utf8")
+}
+
+const main = async () => {
+ const raw = await readStdin()
+ if (raw.trim().length === 0) {
+ process.stdout.write("{}")
+ return
+ }
+
+ let payload
+ try {
+ payload = JSON.parse(raw)
+ } catch {
+ process.stdout.write("{}")
+ return
+ }
+
+ const hookEventName =
+ typeof payload.hook_event_name === "string" ? payload.hook_event_name : process.argv[2] ?? "unknown"
+
+ const record = {
+ receivedAt: new Date().toISOString(),
+ hookEventName,
+ payload,
+ }
+
+ try {
+ mkdirSync(dirname(EVENTS_PATH), { recursive: true })
+ appendFileSync(EVENTS_PATH, `${JSON.stringify(record)}\n`, "utf8")
+ } catch (error) {
+ console.error("[vpet-hook-bridge]", error)
+ }
+
+ process.stdout.write("{}")
+}
+
+main().catch((error) => {
+ console.error("[vpet-hook-bridge]", error)
+ process.stdout.write("{}")
+})
diff --git a/packages/cursor-vpet/icon.png b/packages/cursor-vpet/icon.png
new file mode 100644
index 0000000..bd1aab4
Binary files /dev/null and b/packages/cursor-vpet/icon.png differ
diff --git a/packages/cursor-vpet/package.json b/packages/cursor-vpet/package.json
new file mode 100644
index 0000000..a12ed1f
--- /dev/null
+++ b/packages/cursor-vpet/package.json
@@ -0,0 +1,124 @@
+{
+ "name": "cursor-vpet",
+ "displayName": "Cursor VPet",
+ "description": "Digimon virtual pet for Cursor that evolves with Agent token usage.",
+ "version": "0.2.0-dev.0",
+ "publisher": "sbugallo",
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/sbugallo/opencode-vpet.git",
+ "directory": "packages/cursor-vpet"
+ },
+ "homepage": "https://github.com/sbugallo/opencode-vpet#readme",
+ "bugs": {
+ "url": "https://github.com/sbugallo/opencode-vpet/issues"
+ },
+ "keywords": [
+ "cursor",
+ "vpet",
+ "virtual-pet",
+ "digimon",
+ "agent",
+ "ai",
+ "sidebar",
+ "opencode"
+ ],
+ "icon": "icon.png",
+ "galleryBanner": {
+ "color": "#1a1a2e",
+ "theme": "dark"
+ },
+ "engines": {
+ "vscode": "^1.90.0",
+ "node": "24.11.0",
+ "bun": "1.3.5"
+ },
+ "categories": [
+ "Other"
+ ],
+ "activationEvents": [
+ "onStartupFinished"
+ ],
+ "main": "./dist/extension.js",
+ "contributes": {
+ "views": {
+ "explorer": [
+ {
+ "id": "cursorVpet.sidebar",
+ "name": "VPet",
+ "type": "webview"
+ }
+ ]
+ },
+ "commands": [
+ {
+ "command": "cursorVpet.spawn",
+ "title": "Cursor VPet: Spawn Partner"
+ },
+ {
+ "command": "cursorVpet.freeze",
+ "title": "Cursor VPet: Freeze"
+ },
+ {
+ "command": "cursorVpet.unfreeze",
+ "title": "Cursor VPet: Unfreeze"
+ },
+ {
+ "command": "cursorVpet.set",
+ "title": "Cursor VPet: Set Digimon"
+ },
+ {
+ "command": "cursorVpet.dex",
+ "title": "Cursor VPet: Open Dex"
+ },
+ {
+ "command": "cursorVpet.history",
+ "title": "Cursor VPet: Open History"
+ },
+ {
+ "command": "cursorVpet.installHooks",
+ "title": "Cursor VPet: Install Hooks"
+ },
+ {
+ "command": "cursorVpet.uninstallHooks",
+ "title": "Cursor VPet: Uninstall Hooks"
+ }
+ ],
+ "configuration": {
+ "title": "Cursor VPet",
+ "properties": {
+ "vpet.databasePath": {
+ "type": "string",
+ "default": "",
+ "description": "Override path to pet.db (default: shared opencode-vpet/pet.db)"
+ },
+ "vpet.usage.settleDelayMs": {
+ "type": "number",
+ "default": 4000,
+ "description": "Initial delay before API watermark fallback (ms)"
+ }
+ }
+ }
+ },
+ "scripts": {
+ "build": "bun scripts/build.ts",
+ "check": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.scripts.json",
+ "test": "bun test",
+ "package": "npx @vscode/vsce package --no-dependencies",
+ "publish:openvsx": "npx ovsx publish *.vsix -p \"$OVSX_PAT\"",
+ "vscode:prepublish": "bun run build"
+ },
+ "dependencies": {
+ "@sbugallo/vpet-core": "workspace:*",
+ "sql.js": "^1.12.0"
+ },
+ "devDependencies": {
+ "@types/bun": "1.3.14",
+ "@types/node": "24.10.1",
+ "@types/vscode": "1.90.0",
+ "@vscode/vsce": "3.2.1",
+ "ovsx": "0.10.0",
+ "typescript": "5.9.3"
+ }
+}
diff --git a/packages/cursor-vpet/scripts/build.ts b/packages/cursor-vpet/scripts/build.ts
new file mode 100644
index 0000000..126beee
--- /dev/null
+++ b/packages/cursor-vpet/scripts/build.ts
@@ -0,0 +1,30 @@
+import { copyFile, mkdir, rm } from "node:fs/promises"
+import { createRequire } from "node:module"
+import { resolve } from "node:path"
+
+import { stageChangelog } from "./stage-changelog.ts"
+
+const require = createRequire(import.meta.url)
+const packageRoot = resolve(import.meta.dir, "..")
+const distDirectory = resolve(packageRoot, "dist")
+const wasmSource = require.resolve("sql.js/dist/sql-wasm.wasm")
+
+await rm(distDirectory, { recursive: true, force: true })
+await mkdir(distDirectory, { recursive: true })
+await stageChangelog(packageRoot)
+
+const result = await Bun.build({
+ entrypoints: ["./src/extension.ts"],
+ outdir: "./dist",
+ target: "node",
+ format: "cjs",
+ external: ["vscode"],
+ minify: true,
+ sourcemap: false,
+})
+
+if (!result.success) {
+ throw new Error("cursor-vpet build failed")
+}
+
+await copyFile(wasmSource, resolve(distDirectory, "sql-wasm.wasm"))
diff --git a/packages/cursor-vpet/scripts/stage-changelog.ts b/packages/cursor-vpet/scripts/stage-changelog.ts
new file mode 100644
index 0000000..a3d37ea
--- /dev/null
+++ b/packages/cursor-vpet/scripts/stage-changelog.ts
@@ -0,0 +1,33 @@
+import { copyFile, rm } from "node:fs/promises"
+import { resolve } from "node:path"
+
+export const getMonorepoChangelogPath = (packageRoot: string): string => resolve(packageRoot, "../../CHANGELOG.md")
+
+export const getStagedChangelogPath = (packageRoot: string): string => resolve(packageRoot, "CHANGELOG.md")
+
+export const stageChangelog = async (packageRoot: string): Promise