vibemancer 1.0.34 → 1.0.36
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/dist/cli.js +107 -30
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1199,7 +1199,7 @@ async function listMyBotsPage() {
|
|
|
1199
1199
|
truncated: obj.truncated === true
|
|
1200
1200
|
};
|
|
1201
1201
|
}
|
|
1202
|
-
async function
|
|
1202
|
+
async function pullBot(name) {
|
|
1203
1203
|
const token = await getAccessToken();
|
|
1204
1204
|
const res = await fetch(`${MCP_BASE_URL}/api/pull?name=${encodeURIComponent(name)}`, {
|
|
1205
1205
|
headers: { Authorization: `Bearer ${token}`, ...envHeaders() },
|
|
@@ -1208,10 +1208,16 @@ async function pullSource(name) {
|
|
|
1208
1208
|
if (!res.ok) throw new Error(`Pull failed (${res.status}): ${await res.text()}`);
|
|
1209
1209
|
const data = await res.json();
|
|
1210
1210
|
const source = typeof data === "object" && data !== null && "sourceCode" in data && typeof data.sourceCode === "string" ? data.sourceCode : "";
|
|
1211
|
-
if (source)
|
|
1211
|
+
if (source) {
|
|
1212
|
+
const canonical = typeof data === "object" && data !== null && "name" in data && typeof data.name === "string" && data.name ? data.name : name;
|
|
1213
|
+
return { sourceCode: source, name: canonical };
|
|
1214
|
+
}
|
|
1212
1215
|
const error = typeof data === "object" && data !== null && "error" in data && typeof data.error === "string" ? data.error : "No source returned.";
|
|
1213
1216
|
throw new Error(error);
|
|
1214
1217
|
}
|
|
1218
|
+
async function pullSource(name) {
|
|
1219
|
+
return (await pullBot(name)).sourceCode;
|
|
1220
|
+
}
|
|
1215
1221
|
|
|
1216
1222
|
// src/commands/bots.ts
|
|
1217
1223
|
async function runBots(options) {
|
|
@@ -1370,7 +1376,52 @@ function getStyleDescription(bot) {
|
|
|
1370
1376
|
}
|
|
1371
1377
|
|
|
1372
1378
|
// src/commands/upload.ts
|
|
1379
|
+
import fs6 from "fs";
|
|
1380
|
+
|
|
1381
|
+
// src/pull-safety.ts
|
|
1382
|
+
import { createHash } from "crypto";
|
|
1373
1383
|
import fs5 from "fs";
|
|
1384
|
+
import path6 from "path";
|
|
1385
|
+
function hashContent(content) {
|
|
1386
|
+
const normalised = content.replace(/\r\n/g, "\n").replace(/\s+$/, "");
|
|
1387
|
+
return createHash("sha256").update(normalised, "utf8").digest("hex");
|
|
1388
|
+
}
|
|
1389
|
+
function pullDecision(situation) {
|
|
1390
|
+
if (!situation.fileExists) return "write";
|
|
1391
|
+
if (situation.force) return "write";
|
|
1392
|
+
const local = situation.localContent ?? "";
|
|
1393
|
+
if (hashContent(local) === hashContent(situation.incomingContent)) return "skip-identical";
|
|
1394
|
+
if (situation.recordedHash === void 0) return "refuse-unknown";
|
|
1395
|
+
return hashContent(local) === situation.recordedHash ? "write" : "refuse-modified";
|
|
1396
|
+
}
|
|
1397
|
+
var STATE_FILE = ".vibemancer-state.json";
|
|
1398
|
+
function statePath(projectDir) {
|
|
1399
|
+
return path6.join(projectDir, STATE_FILE);
|
|
1400
|
+
}
|
|
1401
|
+
function readState(projectDir) {
|
|
1402
|
+
try {
|
|
1403
|
+
const parsed = JSON.parse(fs5.readFileSync(statePath(projectDir), "utf-8"));
|
|
1404
|
+
if (typeof parsed !== "object" || parsed === null) return {};
|
|
1405
|
+
const out = {};
|
|
1406
|
+
for (const [k, v] of Object.entries(parsed)) if (typeof v === "string") out[k] = v;
|
|
1407
|
+
return out;
|
|
1408
|
+
} catch {
|
|
1409
|
+
return {};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function recordedHashFor(projectDir, filePath) {
|
|
1413
|
+
return readState(projectDir)[path6.relative(projectDir, filePath).split(path6.sep).join("/")];
|
|
1414
|
+
}
|
|
1415
|
+
function recordWritten(projectDir, filePath, content) {
|
|
1416
|
+
try {
|
|
1417
|
+
const state = readState(projectDir);
|
|
1418
|
+
state[path6.relative(projectDir, filePath).split(path6.sep).join("/")] = hashContent(content);
|
|
1419
|
+
fs5.writeFileSync(statePath(projectDir), JSON.stringify(state, null, 2) + String.fromCharCode(10));
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/commands/upload.ts
|
|
1374
1425
|
var MAX_BUNDLE_BYTES = 500 * 1024;
|
|
1375
1426
|
async function runUpload(options) {
|
|
1376
1427
|
const projectDir = process.cwd();
|
|
@@ -1386,7 +1437,7 @@ async function runUpload(options) {
|
|
|
1386
1437
|
}
|
|
1387
1438
|
let sourceCode = "";
|
|
1388
1439
|
try {
|
|
1389
|
-
sourceCode =
|
|
1440
|
+
sourceCode = fs6.readFileSync(botInfo.sourcePath, "utf-8");
|
|
1390
1441
|
} catch {
|
|
1391
1442
|
}
|
|
1392
1443
|
console.log(` Wizard: ${botInfo.exportName}`);
|
|
@@ -1399,6 +1450,7 @@ async function runUpload(options) {
|
|
|
1399
1450
|
sourceCode,
|
|
1400
1451
|
active: options.active
|
|
1401
1452
|
});
|
|
1453
|
+
if (sourceCode) recordWritten(process.cwd(), botInfo.sourcePath, sourceCode);
|
|
1402
1454
|
console.log(` \u2713 ${result.message}`);
|
|
1403
1455
|
if (result.wizardId) console.log(` Wizard ID: ${result.wizardId}`);
|
|
1404
1456
|
console.log(result.active === false ? ` "${botInfo.exportName}" is retired and no longer competing.
|
|
@@ -1416,20 +1468,20 @@ async function runUpload(options) {
|
|
|
1416
1468
|
}
|
|
1417
1469
|
|
|
1418
1470
|
// src/commands/pull.ts
|
|
1419
|
-
import
|
|
1420
|
-
import
|
|
1471
|
+
import fs7 from "fs";
|
|
1472
|
+
import path7 from "path";
|
|
1421
1473
|
function pullTargetPath(projectDir, name) {
|
|
1422
1474
|
const trimmed = name.trim();
|
|
1423
1475
|
if (!trimmed) throw new Error("A bot name is required.");
|
|
1424
1476
|
if (/[\u0000-\u001f\u007f]/.test(trimmed)) throw new Error(`Refusing a bot name with a control character: ${JSON.stringify(name)}`);
|
|
1425
|
-
if (trimmed !==
|
|
1477
|
+
if (trimmed !== path7.basename(trimmed) || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
1426
1478
|
throw new Error(`Refusing a bot name that is not a plain file name: ${JSON.stringify(name)}`);
|
|
1427
1479
|
}
|
|
1428
|
-
return
|
|
1480
|
+
return path7.resolve(projectDir, "src", `${trimmed}.ts`);
|
|
1429
1481
|
}
|
|
1430
1482
|
function readProjectConfig(projectDir) {
|
|
1431
1483
|
try {
|
|
1432
|
-
const raw =
|
|
1484
|
+
const raw = fs7.readFileSync(path7.join(projectDir, "vibemancer.json"), "utf-8");
|
|
1433
1485
|
const parsed = JSON.parse(raw);
|
|
1434
1486
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1435
1487
|
const bot = parsed.bot;
|
|
@@ -1448,23 +1500,47 @@ function pullDestination(projectDir, name, config) {
|
|
|
1448
1500
|
const configuredExport = config?.export;
|
|
1449
1501
|
if (!configured) return { path: fallback };
|
|
1450
1502
|
if (configuredExport && configuredExport.toLowerCase() === name.trim().toLowerCase()) {
|
|
1451
|
-
return { path:
|
|
1503
|
+
return { path: path7.resolve(projectDir, configured) };
|
|
1452
1504
|
}
|
|
1453
1505
|
return {
|
|
1454
1506
|
path: fallback,
|
|
1455
1507
|
warning: `Note: this project's vibemancer.json uses ${configuredExport ?? "another bot"} (${configured}), so upload/fight will keep using that, not the file just written.`
|
|
1456
1508
|
};
|
|
1457
1509
|
}
|
|
1458
|
-
async function pullByName(projectDir, name) {
|
|
1459
|
-
|
|
1460
|
-
const target = destination.path;
|
|
1461
|
-
const isNew = !fs6.existsSync(target);
|
|
1510
|
+
async function pullByName(projectDir, name, force) {
|
|
1511
|
+
pullTargetPath(projectDir, name);
|
|
1462
1512
|
console.log("");
|
|
1463
1513
|
console.log(` Pulling latest source for ${name}...`);
|
|
1464
1514
|
try {
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1515
|
+
const pulled = await pullBot(name);
|
|
1516
|
+
const sourceCode = pulled.sourceCode;
|
|
1517
|
+
const destination = pullDestination(projectDir, pulled.name, readProjectConfig(projectDir));
|
|
1518
|
+
const target = destination.path;
|
|
1519
|
+
const isNew = !fs7.existsSync(target);
|
|
1520
|
+
const decision = pullDecision({
|
|
1521
|
+
fileExists: !isNew,
|
|
1522
|
+
localContent: isNew ? void 0 : fs7.readFileSync(target, "utf-8"),
|
|
1523
|
+
incomingContent: sourceCode,
|
|
1524
|
+
recordedHash: recordedHashFor(projectDir, target),
|
|
1525
|
+
force
|
|
1526
|
+
});
|
|
1527
|
+
if (decision === "skip-identical") {
|
|
1528
|
+
console.log(` Already up to date: ${target}`);
|
|
1529
|
+
recordWritten(projectDir, target, sourceCode);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
if (decision !== "write") {
|
|
1533
|
+
const why = decision === "refuse-modified" ? "it has changes that were not pulled or uploaded from here" : "this CLI has never written it, so it may be work it did not create";
|
|
1534
|
+
console.error("");
|
|
1535
|
+
console.error(` Refusing to overwrite ${target}: ${why}.`);
|
|
1536
|
+
console.error(" Save or commit it first, or re-run with --force to replace it.");
|
|
1537
|
+
console.error("");
|
|
1538
|
+
process.exitCode = 1;
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
fs7.mkdirSync(path7.dirname(target), { recursive: true });
|
|
1542
|
+
fs7.writeFileSync(target, sourceCode);
|
|
1543
|
+
recordWritten(projectDir, target, sourceCode);
|
|
1468
1544
|
console.log(` \u2713 ${isNew ? "Created" : "Updated"} ${target}`);
|
|
1469
1545
|
console.log(` ${(sourceCode.length / 1024).toFixed(1)} KB written`);
|
|
1470
1546
|
if (destination.warning) console.log(` ${destination.warning}`);
|
|
@@ -1487,17 +1563,17 @@ function reportPullFailure(err, name) {
|
|
|
1487
1563
|
async function runPull(options) {
|
|
1488
1564
|
const projectDir = process.cwd();
|
|
1489
1565
|
if (options.name) {
|
|
1490
|
-
await pullByName(projectDir, options.name);
|
|
1566
|
+
await pullByName(projectDir, options.name, options.force === true);
|
|
1491
1567
|
return;
|
|
1492
1568
|
}
|
|
1493
1569
|
const botInfo = await discoverBot(projectDir, options.bot, { allowMissingFile: true });
|
|
1494
|
-
const isNew = !
|
|
1570
|
+
const isNew = !fs7.existsSync(botInfo.sourcePath);
|
|
1495
1571
|
console.log(`
|
|
1496
1572
|
Pulling latest source for ${botInfo.exportName}...`);
|
|
1497
1573
|
try {
|
|
1498
1574
|
const sourceCode = await pullSource(botInfo.exportName);
|
|
1499
|
-
|
|
1500
|
-
|
|
1575
|
+
fs7.mkdirSync(path7.dirname(botInfo.sourcePath), { recursive: true });
|
|
1576
|
+
fs7.writeFileSync(botInfo.sourcePath, sourceCode);
|
|
1501
1577
|
console.log(` \u2713 ${isNew ? "Created" : "Updated"} ${botInfo.sourcePath}`);
|
|
1502
1578
|
console.log(` ${(sourceCode.length / 1024).toFixed(1)} KB written`);
|
|
1503
1579
|
} catch (err) {
|
|
@@ -1722,8 +1798,8 @@ function runMissileCalc(options) {
|
|
|
1722
1798
|
|
|
1723
1799
|
// src/auth/telemetry.ts
|
|
1724
1800
|
import os2 from "os";
|
|
1725
|
-
import
|
|
1726
|
-
import
|
|
1801
|
+
import fs8 from "fs";
|
|
1802
|
+
import path8 from "path";
|
|
1727
1803
|
var TIMEOUT_MS = 1e3;
|
|
1728
1804
|
function isTelemetryEnabled(env) {
|
|
1729
1805
|
const off = (value) => {
|
|
@@ -1743,13 +1819,13 @@ function isTelemetryEnabled(env) {
|
|
|
1743
1819
|
}
|
|
1744
1820
|
var TELEMETRY_NOTICE = " Vibemancer records which commands are run, your OS and version numbers, to decide\n which platforms to support. No code, file paths or personal files are ever sent.\n Opt out any time with VIBEMANCER_TELEMETRY=0 (DO_NOT_TRACK is honoured too).\n";
|
|
1745
1821
|
function noticePath() {
|
|
1746
|
-
return
|
|
1822
|
+
return path8.join(os2.homedir(), ".vibemancer", "telemetry-notice-shown");
|
|
1747
1823
|
}
|
|
1748
1824
|
function showNoticeOnce(marker = noticePath()) {
|
|
1749
1825
|
try {
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1752
|
-
|
|
1826
|
+
if (fs8.existsSync(marker)) return false;
|
|
1827
|
+
fs8.mkdirSync(path8.dirname(marker), { recursive: true });
|
|
1828
|
+
fs8.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
|
|
1753
1829
|
console.log(TELEMETRY_NOTICE);
|
|
1754
1830
|
return true;
|
|
1755
1831
|
} catch {
|
|
@@ -1812,7 +1888,8 @@ Commands:
|
|
|
1812
1888
|
logout Sign out
|
|
1813
1889
|
upload Upload bot to VibeMancer for online competition
|
|
1814
1890
|
pull Download latest source code from VibeMancer
|
|
1815
|
-
(--name <bot> restores it with no local file needed
|
|
1891
|
+
(--name <bot> restores it with no local file needed;
|
|
1892
|
+
--force overwrites local changes it did not write)
|
|
1816
1893
|
feedback Submit a bug report or suggestion
|
|
1817
1894
|
missile-calc Calculate missile cast time and DPS for a config
|
|
1818
1895
|
|
|
@@ -1920,10 +1997,10 @@ var KNOWN_FLAGS = {
|
|
|
1920
1997
|
// pull was absent here for the same reason upload was, with the same consequence: a typo'd
|
|
1921
1998
|
// flag was accepted and dropped, so `pull --nmae Wiz2` fell through to file discovery and
|
|
1922
1999
|
// reported "could not find bot source file" about a flag the user had spelled wrong.
|
|
1923
|
-
pull: ["--bot", "--name"],
|
|
2000
|
+
pull: ["--bot", "--name", "--force"],
|
|
1924
2001
|
"missile-calc": ["--damage", "--speed", "--duration", "--turnRate"]
|
|
1925
2002
|
};
|
|
1926
|
-
var BOOLEAN_FLAGS = ["--mine", "--inactive", "--active", "--no-browser"];
|
|
2003
|
+
var BOOLEAN_FLAGS = ["--mine", "--inactive", "--active", "--no-browser", "--force"];
|
|
1927
2004
|
function flagsMissingValues(command2, argv) {
|
|
1928
2005
|
const known = KNOWN_FLAGS[command2];
|
|
1929
2006
|
if (!known) return [];
|
|
@@ -2110,7 +2187,7 @@ ${list}
|
|
|
2110
2187
|
case "pull": {
|
|
2111
2188
|
const bot = parseFlag("--bot");
|
|
2112
2189
|
const name = parseFlag("--name");
|
|
2113
|
-
await runPull({ bot, name });
|
|
2190
|
+
await runPull({ bot, name, force: args.includes("--force") });
|
|
2114
2191
|
break;
|
|
2115
2192
|
}
|
|
2116
2193
|
case "login": {
|