@vovy-ai/go 0.3.1 → 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 +1 -1
- package/dist/index.js +125 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Installed globally (`npm i -g @vovy-ai/go`), the command is just `vovy`.
|
|
|
18
18
|
|
|
19
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.
|
|
20
20
|
|
|
21
|
-
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.
|
|
22
22
|
|
|
23
23
|
## What it installs
|
|
24
24
|
|
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,6 +309,14 @@ function realEnv() {
|
|
|
206
309
|
}
|
|
207
310
|
|
|
208
311
|
// src/index.ts
|
|
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
|
+
}
|
|
209
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:
|
|
@@ -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