@vovy-ai/go 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/index.js +126 -8
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @vovy-ai/go
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `vovy` CLI — one command drops project-aware skills, scope-aware code search, and git-committed project memory into the AI coding tool you already use. Free forever.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npx @vovy-ai/go install
|
|
@@ -11,24 +11,28 @@ That's it. It detects which AI coding tool you have installed — [Claude Code](
|
|
|
11
11
|
```
|
|
12
12
|
npx @vovy-ai/go doctor # check everything is installed correctly
|
|
13
13
|
npx @vovy-ai/go uninstall # remove everything Vovy wrote, cleanly
|
|
14
|
+
npx @vovy-ai/go statusline # one-line badge for your tool's status bar
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
Installed globally (`npm i -g @vovy-ai/go`), the command is just `vovy`.
|
|
17
18
|
|
|
18
|
-
`npx @vovy-ai/go doctor` also reports a deterministic "always-on token footprint" — every installed skill file plus every registered MCP tool definition, the tokens a session pays whether or not any of it ever fires.
|
|
19
|
+
`npx @vovy-ai/go doctor` also reports a deterministic "always-on token footprint" — every installed skill file plus every registered MCP tool definition, the tokens a session pays whether or not any of it ever fires — and tells you which Context Engine backend your project gets: `typescript` (scope-aware, resolved through your project's own TypeScript) or `tree-sitter` (name-matching fallback), with the one-line upgrade if you're on the fallback.
|
|
19
20
|
|
|
20
|
-
It's MIT-licensed and runs entirely on your machine — no account, no API key, nothing
|
|
21
|
+
It's MIT-licensed and runs entirely on your machine — no account, no API key, no background telemetry. The only network call in the CLI is a one-time, two-question install survey that sends answers **only if you type them**; skip it (or set `VOVY_NO_SURVEY=1`) and nothing is ever sent.
|
|
21
22
|
|
|
22
23
|
## What it installs
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Five skills plus a local MCP server that give AI coding assistants deterministic project context, durable memory, and safety rails:
|
|
25
26
|
|
|
26
27
|
| Skill | What it does |
|
|
27
28
|
|---|---|
|
|
28
|
-
| **Prompt Rescoper** | Rewrites vague, oversized requests into a small, reviewable spec before any code is written. |
|
|
29
|
+
| **Prompt Rescoper** | Rewrites vague, oversized requests into a small, reviewable spec — including how the change will be verified and what imperfections are acceptable — before any code is written. |
|
|
29
30
|
| **Project Skill Drafter** | Analyzes your actual project and drafts a project-specific skill so future requests already know your stack. |
|
|
30
31
|
| **Founder Explainer** | Explains destructive/high-stakes actions in plain English before they happen, and flags common vibe-coding security mistakes. |
|
|
31
|
-
| **Context Scoper** |
|
|
32
|
+
| **Context Scoper** | Finds the exact symbol, method, or file before reading whole files — via your project's own TypeScript when available (scope-aware, so two same-named functions don't get confused), tree-sitter otherwise. Includes `impact`: "what breaks if I change this", walked transitively. |
|
|
33
|
+
| **Memory Keeper** | Records decisions (with what was rejected and why), mistakes (with how to avoid repeating them), and constraints (with the why) into `.vovy/memory/` — plain markdown, committed to git, so rationale survives across sessions, machines, teammates, and AI tools. |
|
|
34
|
+
|
|
35
|
+
With `--scope project`, skill descriptions are tailored to your detected stack (a Next.js + TypeScript project gets descriptions that say so), which helps them trigger at the right moments at zero extra token cost.
|
|
32
36
|
|
|
33
37
|
Full docs, architecture, and source: **[github.com/Vovy-AI/vovy-cli](https://github.com/Vovy-AI/vovy-cli)**
|
|
34
38
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { realpathSync } from "fs";
|
|
4
|
+
import { readFileSync as readFileSync3, realpathSync } from "fs";
|
|
5
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
6
|
+
import { createInterface } from "readline/promises";
|
|
5
7
|
import { fileURLToPath } from "url";
|
|
6
8
|
import { parseArgs } from "util";
|
|
7
9
|
import { ADAPTERS as ADAPTERS2 } from "@vovy-ai/host-detect";
|
|
@@ -164,8 +166,109 @@ function countMemoryEntries(cwd) {
|
|
|
164
166
|
return count;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
// src/commands/survey.ts
|
|
170
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
171
|
+
import { dirname, join as join3 } from "path";
|
|
172
|
+
var SURVEY_URL = "https://swrrttvhfnsiesmkbwia.supabase.co/rest/v1/vovy_cli_survey";
|
|
173
|
+
var SURVEY_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InN3cnJ0dHZoZm5zaWVzbWtid2lhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDQ0ODI1MjQsImV4cCI6MjA2MDA1ODUyNH0.5BMG6KY-49J_Gz8Qg02Hf3DEHgiy3evtiXqP77non_A";
|
|
174
|
+
var SOURCES = [
|
|
175
|
+
{ key: "1", value: "x_twitter", label: "X / Twitter" },
|
|
176
|
+
{ key: "2", value: "github", label: "GitHub" },
|
|
177
|
+
{ key: "3", value: "friend", label: "A friend or colleague" },
|
|
178
|
+
{ key: "4", value: "search", label: "Search" },
|
|
179
|
+
{ key: "5", value: "youtube", label: "YouTube" },
|
|
180
|
+
{ key: "6", value: "other", label: "Other" }
|
|
181
|
+
];
|
|
182
|
+
function statePath(env) {
|
|
183
|
+
return join3(env.home, ".vovy", "cli-state.json");
|
|
184
|
+
}
|
|
185
|
+
function readState(env) {
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(readFileSync(statePath(env), "utf8"));
|
|
188
|
+
} catch {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function markSurveyOffered(env) {
|
|
193
|
+
const path = statePath(env);
|
|
194
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
195
|
+
writeFileSync(path, `${JSON.stringify({ ...readState(env), surveyOffered: true }, null, 2)}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
function shouldOfferSurvey(env, isTty) {
|
|
199
|
+
if (!isTty) return false;
|
|
200
|
+
if (process.env.VOVY_NO_SURVEY) return false;
|
|
201
|
+
if (process.env.CI) return false;
|
|
202
|
+
if (readState(env).surveyOffered) return false;
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
function buildSurveyPayload(answers, cliVersion2) {
|
|
206
|
+
if (answers.source === void 0 && answers.rating === void 0) return null;
|
|
207
|
+
const payload = { cli_version: cliVersion2.slice(0, 20) };
|
|
208
|
+
if (answers.source !== void 0) payload.source = answers.source;
|
|
209
|
+
if (answers.source === "other" && answers.sourceOther) {
|
|
210
|
+
payload.source_other = answers.sourceOther.slice(0, 120);
|
|
211
|
+
}
|
|
212
|
+
if (answers.rating !== void 0) payload.rating = answers.rating;
|
|
213
|
+
return payload;
|
|
214
|
+
}
|
|
215
|
+
async function askSurveyQuestions(rl) {
|
|
216
|
+
const answers = {};
|
|
217
|
+
const sourceMenu = SOURCES.map((s) => ` [${s.key}] ${s.label}`).join("\n");
|
|
218
|
+
const sourceInput = (await rl.question(`
|
|
219
|
+
How did you hear about Vovy?
|
|
220
|
+
${sourceMenu}
|
|
221
|
+
> `)).trim();
|
|
222
|
+
const source = SOURCES.find((s) => s.key === sourceInput);
|
|
223
|
+
if (source) {
|
|
224
|
+
answers.source = source.value;
|
|
225
|
+
if (source.value === "other") {
|
|
226
|
+
const detail = (await rl.question("Where? (optional) > ")).trim();
|
|
227
|
+
if (detail) answers.sourceOther = detail;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const ratingInput = (await rl.question("\nExperience so far, 1 (rough) to 5 (great)? > ")).trim();
|
|
231
|
+
const rating = Number.parseInt(ratingInput, 10);
|
|
232
|
+
if (Number.isInteger(rating) && rating >= 1 && rating <= 5) answers.rating = rating;
|
|
233
|
+
return answers;
|
|
234
|
+
}
|
|
235
|
+
async function sendSurvey(payload, url = SURVEY_URL) {
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(url, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers: { apikey: SURVEY_ANON_KEY, "Content-Type": "application/json" },
|
|
240
|
+
body: JSON.stringify(payload),
|
|
241
|
+
signal: AbortSignal.timeout(2500)
|
|
242
|
+
});
|
|
243
|
+
return response.ok;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function offerSurvey(env, cliVersion2, makeInterface) {
|
|
249
|
+
markSurveyOffered(env);
|
|
250
|
+
console.log(
|
|
251
|
+
"\nOne-time question (never asked again \u2014 press Enter to skip either one).\nIf you answer, the answers and the CLI version are sent to the Vovy team; skipping sends nothing."
|
|
252
|
+
);
|
|
253
|
+
const rl = makeInterface();
|
|
254
|
+
try {
|
|
255
|
+
const answers = await askSurveyQuestions(rl);
|
|
256
|
+
const payload = buildSurveyPayload(answers, cliVersion2);
|
|
257
|
+
if (!payload) {
|
|
258
|
+
console.log("Skipped \u2014 nothing was sent.");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const sent = await sendSurvey(payload);
|
|
262
|
+
console.log(
|
|
263
|
+
sent ? "Thanks \u2014 that helps more than you'd think." : "Thanks! (Couldn't reach the survey endpoint \u2014 nothing else to do.)"
|
|
264
|
+
);
|
|
265
|
+
} finally {
|
|
266
|
+
rl.close();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
167
270
|
// src/commands/uninstall.ts
|
|
168
|
-
import { existsSync as
|
|
271
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
169
272
|
import {
|
|
170
273
|
removeCodexMcpEntry,
|
|
171
274
|
removeJsonMcpEntry
|
|
@@ -180,19 +283,19 @@ function runUninstall(opts) {
|
|
|
180
283
|
const removedSkillPaths = [];
|
|
181
284
|
for (const skill of SKILL_MANIFEST) {
|
|
182
285
|
const path = adapter.skillFilePath(opts.env, scope, skill.id);
|
|
183
|
-
if (
|
|
286
|
+
if (existsSync3(path)) {
|
|
184
287
|
removedSkillPaths.push(path);
|
|
185
288
|
if (!dryRun) rmSync(path, { force: true });
|
|
186
289
|
}
|
|
187
290
|
}
|
|
188
291
|
let removedMcpEntry = null;
|
|
189
292
|
const mcpPath = adapter.mcpConfigPath(opts.env, scope);
|
|
190
|
-
if (mcpPath &&
|
|
191
|
-
const existing =
|
|
293
|
+
if (mcpPath && existsSync3(mcpPath)) {
|
|
294
|
+
const existing = readFileSync2(mcpPath, "utf8");
|
|
192
295
|
const next = adapter.id === "codex" ? removeCodexMcpEntry(existing, VOVY_ID) : removeJsonMcpEntry(existing, VOVY_ID);
|
|
193
296
|
if (next !== null) {
|
|
194
297
|
removedMcpEntry = mcpPath;
|
|
195
|
-
if (!dryRun)
|
|
298
|
+
if (!dryRun) writeFileSync2(mcpPath, next, "utf8");
|
|
196
299
|
}
|
|
197
300
|
}
|
|
198
301
|
return { adapter, removedSkillPaths, removedMcpEntry };
|
|
@@ -206,7 +309,15 @@ function realEnv() {
|
|
|
206
309
|
}
|
|
207
310
|
|
|
208
311
|
// src/index.ts
|
|
209
|
-
|
|
312
|
+
function cliVersion() {
|
|
313
|
+
try {
|
|
314
|
+
const pkgPath = join4(dirname2(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
315
|
+
return JSON.parse(readFileSync3(pkgPath, "utf8")).version ?? "unknown";
|
|
316
|
+
} catch {
|
|
317
|
+
return "unknown";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
var HELP = `Vovy \u2014 drop-in skills for vibe coding safely with the AI tool you already use. Run via \`npx @vovy-ai/go\` (or \`vovy\` if installed globally).
|
|
210
321
|
|
|
211
322
|
Usage:
|
|
212
323
|
npx @vovy-ai/go install [options] Write Vovy's skills into detected (or specified) host tools
|
|
@@ -251,7 +362,7 @@ function actionSymbol(action) {
|
|
|
251
362
|
if (action === "created") return "+";
|
|
252
363
|
return "~";
|
|
253
364
|
}
|
|
254
|
-
function cmdInstall(argv) {
|
|
365
|
+
async function cmdInstall(argv) {
|
|
255
366
|
const flags = parseCommonFlags(argv);
|
|
256
367
|
if (flags.help) return console.log(HELP);
|
|
257
368
|
const env = realEnv();
|
|
@@ -288,6 +399,13 @@ Add to ~/.claude/settings.json:
|
|
|
288
399
|
(Faster variant: npm i -g @vovy-ai/go, then use "vovy statusline" as the command.)`
|
|
289
400
|
);
|
|
290
401
|
}
|
|
402
|
+
if (!flags.dryRun && shouldOfferSurvey(env, process.stdout.isTTY === true)) {
|
|
403
|
+
await offerSurvey(
|
|
404
|
+
env,
|
|
405
|
+
cliVersion(),
|
|
406
|
+
() => createInterface({ input: process.stdin, output: process.stdout })
|
|
407
|
+
);
|
|
408
|
+
}
|
|
291
409
|
}
|
|
292
410
|
function cmdStatusline() {
|
|
293
411
|
console.log(buildStatusline(realEnv()));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vovy-ai/go",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Drop-in MCP server + skill pack that teaches non-technical founders to vibe code safely — free forever. npx @vovy-ai/go install",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vibe-coding",
|
|
7
7
|
"ai-coding-assistant",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@vovy-ai/host-detect": "0.1.
|
|
46
|
-
"@vovy-ai/skills": "0.4.
|
|
45
|
+
"@vovy-ai/host-detect": "0.1.2",
|
|
46
|
+
"@vovy-ai/skills": "0.4.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"tsup": "^8.3.5",
|
|
50
50
|
"typescript": "^5.7.2",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@vovy-ai/mcp-server": "0.3.
|
|
52
|
+
"@vovy-ai/mcp-server": "0.3.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsup src/index.ts --format esm --dts --clean",
|