vibemancer 1.0.35 → 1.0.37
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 +110 -35
- 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").trimEnd();
|
|
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,26 +1500,52 @@ 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
|
-
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1510
|
+
function writeGuarded(projectDir, target, sourceCode, force) {
|
|
1511
|
+
const isNew = !fs7.existsSync(target);
|
|
1512
|
+
const decision = pullDecision(isNew ? { fileExists: false, incomingContent: sourceCode, force } : {
|
|
1513
|
+
fileExists: true,
|
|
1514
|
+
localContent: fs7.readFileSync(target, "utf-8"),
|
|
1515
|
+
incomingContent: sourceCode,
|
|
1516
|
+
recordedHash: recordedHashFor(projectDir, target),
|
|
1517
|
+
force
|
|
1518
|
+
});
|
|
1519
|
+
if (decision === "skip-identical") {
|
|
1520
|
+
console.log(` Already up to date: ${target}`);
|
|
1521
|
+
recordWritten(projectDir, target, sourceCode);
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
if (decision !== "write") {
|
|
1525
|
+
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";
|
|
1526
|
+
console.error("");
|
|
1527
|
+
console.error(` Refusing to overwrite ${target}: ${why}.`);
|
|
1528
|
+
console.error(" Save or commit it first, or re-run with --force to replace it.");
|
|
1529
|
+
console.error("");
|
|
1530
|
+
process.exitCode = 1;
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
fs7.mkdirSync(path7.dirname(target), { recursive: true });
|
|
1534
|
+
fs7.writeFileSync(target, sourceCode);
|
|
1535
|
+
recordWritten(projectDir, target, sourceCode);
|
|
1536
|
+
console.log(` \u2713 ${isNew ? "Created" : "Updated"} ${target}`);
|
|
1537
|
+
console.log(` ${(sourceCode.length / 1024).toFixed(1)} KB written`);
|
|
1538
|
+
return true;
|
|
1539
|
+
}
|
|
1540
|
+
async function pullByName(projectDir, name, force) {
|
|
1541
|
+
pullTargetPath(projectDir, name);
|
|
1462
1542
|
console.log("");
|
|
1463
1543
|
console.log(` Pulling latest source for ${name}...`);
|
|
1464
1544
|
try {
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
console.log(`
|
|
1469
|
-
console.log(` ${(sourceCode.length / 1024).toFixed(1)} KB written`);
|
|
1470
|
-
if (destination.warning) console.log(` ${destination.warning}`);
|
|
1545
|
+
const pulled = await pullBot(name);
|
|
1546
|
+
const destination = pullDestination(projectDir, pulled.name, readProjectConfig(projectDir));
|
|
1547
|
+
const wrote = writeGuarded(projectDir, destination.path, pulled.sourceCode, force);
|
|
1548
|
+
if (wrote && destination.warning) console.log(` ${destination.warning}`);
|
|
1471
1549
|
} catch (err) {
|
|
1472
1550
|
reportPullFailure(err, name);
|
|
1473
1551
|
}
|
|
@@ -1487,19 +1565,15 @@ function reportPullFailure(err, name) {
|
|
|
1487
1565
|
async function runPull(options) {
|
|
1488
1566
|
const projectDir = process.cwd();
|
|
1489
1567
|
if (options.name) {
|
|
1490
|
-
await pullByName(projectDir, options.name);
|
|
1568
|
+
await pullByName(projectDir, options.name, options.force === true);
|
|
1491
1569
|
return;
|
|
1492
1570
|
}
|
|
1493
1571
|
const botInfo = await discoverBot(projectDir, options.bot, { allowMissingFile: true });
|
|
1494
|
-
const isNew = !fs6.existsSync(botInfo.sourcePath);
|
|
1495
1572
|
console.log(`
|
|
1496
1573
|
Pulling latest source for ${botInfo.exportName}...`);
|
|
1497
1574
|
try {
|
|
1498
1575
|
const sourceCode = await pullSource(botInfo.exportName);
|
|
1499
|
-
|
|
1500
|
-
fs6.writeFileSync(botInfo.sourcePath, sourceCode);
|
|
1501
|
-
console.log(` \u2713 ${isNew ? "Created" : "Updated"} ${botInfo.sourcePath}`);
|
|
1502
|
-
console.log(` ${(sourceCode.length / 1024).toFixed(1)} KB written`);
|
|
1576
|
+
writeGuarded(projectDir, botInfo.sourcePath, sourceCode, options.force === true);
|
|
1503
1577
|
} catch (err) {
|
|
1504
1578
|
reportPullFailure(err);
|
|
1505
1579
|
}
|
|
@@ -1722,8 +1796,8 @@ function runMissileCalc(options) {
|
|
|
1722
1796
|
|
|
1723
1797
|
// src/auth/telemetry.ts
|
|
1724
1798
|
import os2 from "os";
|
|
1725
|
-
import
|
|
1726
|
-
import
|
|
1799
|
+
import fs8 from "fs";
|
|
1800
|
+
import path8 from "path";
|
|
1727
1801
|
var TIMEOUT_MS = 1e3;
|
|
1728
1802
|
function isTelemetryEnabled(env) {
|
|
1729
1803
|
const off = (value) => {
|
|
@@ -1743,13 +1817,13 @@ function isTelemetryEnabled(env) {
|
|
|
1743
1817
|
}
|
|
1744
1818
|
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
1819
|
function noticePath() {
|
|
1746
|
-
return
|
|
1820
|
+
return path8.join(os2.homedir(), ".vibemancer", "telemetry-notice-shown");
|
|
1747
1821
|
}
|
|
1748
1822
|
function showNoticeOnce(marker = noticePath()) {
|
|
1749
1823
|
try {
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1752
|
-
|
|
1824
|
+
if (fs8.existsSync(marker)) return false;
|
|
1825
|
+
fs8.mkdirSync(path8.dirname(marker), { recursive: true });
|
|
1826
|
+
fs8.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
|
|
1753
1827
|
console.log(TELEMETRY_NOTICE);
|
|
1754
1828
|
return true;
|
|
1755
1829
|
} catch {
|
|
@@ -1812,7 +1886,8 @@ Commands:
|
|
|
1812
1886
|
logout Sign out
|
|
1813
1887
|
upload Upload bot to VibeMancer for online competition
|
|
1814
1888
|
pull Download latest source code from VibeMancer
|
|
1815
|
-
(--name <bot> restores it with no local file needed
|
|
1889
|
+
(--name <bot> restores it with no local file needed;
|
|
1890
|
+
--force overwrites local changes it did not write)
|
|
1816
1891
|
feedback Submit a bug report or suggestion
|
|
1817
1892
|
missile-calc Calculate missile cast time and DPS for a config
|
|
1818
1893
|
|
|
@@ -1920,10 +1995,10 @@ var KNOWN_FLAGS = {
|
|
|
1920
1995
|
// pull was absent here for the same reason upload was, with the same consequence: a typo'd
|
|
1921
1996
|
// flag was accepted and dropped, so `pull --nmae Wiz2` fell through to file discovery and
|
|
1922
1997
|
// reported "could not find bot source file" about a flag the user had spelled wrong.
|
|
1923
|
-
pull: ["--bot", "--name"],
|
|
1998
|
+
pull: ["--bot", "--name", "--force"],
|
|
1924
1999
|
"missile-calc": ["--damage", "--speed", "--duration", "--turnRate"]
|
|
1925
2000
|
};
|
|
1926
|
-
var BOOLEAN_FLAGS = ["--mine", "--inactive", "--active", "--no-browser"];
|
|
2001
|
+
var BOOLEAN_FLAGS = ["--mine", "--inactive", "--active", "--no-browser", "--force"];
|
|
1927
2002
|
function flagsMissingValues(command2, argv) {
|
|
1928
2003
|
const known = KNOWN_FLAGS[command2];
|
|
1929
2004
|
if (!known) return [];
|
|
@@ -2110,7 +2185,7 @@ ${list}
|
|
|
2110
2185
|
case "pull": {
|
|
2111
2186
|
const bot = parseFlag("--bot");
|
|
2112
2187
|
const name = parseFlag("--name");
|
|
2113
|
-
await runPull({ bot, name });
|
|
2188
|
+
await runPull({ bot, name, force: args.includes("--force") });
|
|
2114
2189
|
break;
|
|
2115
2190
|
}
|
|
2116
2191
|
case "login": {
|