vibemancer 1.0.1 → 1.0.3

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.
@@ -72,13 +72,16 @@ function getCoreCompileOptions() {
72
72
  }
73
73
  };
74
74
  }
75
+ var EXTRA_RESOLVABLE_BOTS = {
76
+ Hero: "Hero.ts"
77
+ };
75
78
  function getBuiltinBotNames() {
76
79
  return Object.keys(BUILTIN_BOTS);
77
80
  }
78
81
  function resolveOpponent(name) {
79
- const relativePath = BUILTIN_BOTS[name];
82
+ const relativePath = BUILTIN_BOTS[name] ?? EXTRA_RESOLVABLE_BOTS[name];
80
83
  if (!relativePath) {
81
- const available = Object.keys(BUILTIN_BOTS).join(", ");
84
+ const available = [...Object.keys(BUILTIN_BOTS), ...Object.keys(EXTRA_RESOLVABLE_BOTS)].join(", ");
82
85
  throw new Error(
83
86
  `Unknown opponent "${name}".
84
87
  Available built-in bots: ${available}`
@@ -98,4 +101,4 @@ export {
98
101
  getBuiltinBotNames,
99
102
  resolveOpponent
100
103
  };
101
- //# sourceMappingURL=chunk-4DUS7IHW.js.map
104
+ //# sourceMappingURL=chunk-CMTEJ5MU.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/opponent-resolver.ts"],"sourcesContent":["/**\r\n * Opponent Resolver\r\n *\r\n * Resolves an opponent name (e.g., \"Battlemage\", \"Turtle\") to a BotBundle\r\n * pointing at the built-in bot's TypeScript source file.\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport {fileURLToPath} from 'node:url';\r\nimport {BotBundle} from '@vibemancer/core';\r\nimport type {CompileOptions} from '@vibemancer/core';\r\n\r\n/**\r\n * Find the @vibemancer/core package's source directory.\r\n * The core package ships with both dist/ and src/ (TypeScript source).\r\n * esbuild needs the TypeScript source for compilation.\r\n */\r\nexport function findCoreSourceDir(): string\r\n{\r\n\t// When running from the monorepo (development), core is a sibling package\r\n\tconst monorepoCore = path.resolve(import.meta.dirname, '..', '..', 'core', 'src');\r\n\tif (fs.existsSync(path.join(monorepoCore, 'engine', 'simulation.ts')))\r\n\t{\r\n\t\treturn monorepoCore;\r\n\t}\r\n\r\n\t// When installed as a dependency, core is in node_modules\r\n\t// Use import.meta.resolve to find the package\r\n\ttry\r\n\t{\r\n\t\tconst coreIndex = import.meta.resolve('@vibemancer/core');\r\n\t\t// coreIndex = file:///path/to/node_modules/@vibemancer/core/dist/index.js\r\n\t\tconst coreIndexPath = fileURLToPath(coreIndex);\r\n\t\tconst coreRoot = path.resolve(path.dirname(coreIndexPath), '..');\r\n\t\tconst srcDir = path.join(coreRoot, 'src');\r\n\t\tif (fs.existsSync(path.join(srcDir, 'engine', 'simulation.ts')))\r\n\t\t{\r\n\t\t\treturn srcDir;\r\n\t\t}\r\n\t}\r\n\tcatch\r\n\t{\r\n\t\t// import.meta.resolve failed\r\n\t}\r\n\r\n\tthrow new Error(\r\n\t\t'Could not find @vibemancer/core source directory.\\n'\r\n\t\t+ 'Ensure @vibemancer/core is installed and includes its src/ directory.',\r\n\t);\r\n}\r\n\r\n/**\r\n * Built-in bot source file mapping.\r\n * Maps bot name → relative path from core's src/bots/ directory.\r\n *\r\n * Derived from the naming convention:\r\n * - Standalone: standalone/{Name}.ts\r\n * - Tiered: {group}/{nn}_{Name}.ts\r\n */\r\nconst BUILTIN_BOTS: Record<string, string> = {\r\n\t// Standalone\r\n\tTargetDummy: 'standalone/TargetDummy.ts',\r\n\tCritter: 'standalone/Critter.ts',\r\n\tRookie: 'standalone/Rookie.ts',\r\n\tHogger: 'standalone/Hogger.ts',\r\n\tDoombringer: 'standalone/Doombringer.ts',\r\n\t// Defensive\r\n\tTurtle: 'defensive/01_Turtle.ts',\r\n\tSentinel: 'defensive/02_Sentinel.ts',\r\n\tGolem: 'defensive/03_Golem.ts',\r\n\t// Melee\r\n\tShadowblade: 'melee/01_Shadowblade.ts',\r\n\tNightblade: 'melee/02_Nightblade.ts',\r\n\tVoidblade: 'melee/03_Voidblade.ts',\r\n\t// Homing\r\n\tBonemancer: 'homing/01_Bonemancer.ts',\r\n\tLich: 'homing/02_Lich.ts',\r\n\tArchlich: 'homing/03_Archlich.ts',\r\n\t// Caster\r\n\tFlamecaller: 'caster/01_Flamecaller.ts',\r\n\tPyromancer: 'caster/02_Pyromancer.ts',\r\n\tInfernalist: 'caster/03_Infernalist.ts',\r\n\t// Sniper\r\n\tSpellshot: 'sniper/01_Spellshot.ts',\r\n\tSpelltracer: 'sniper/02_Spelltracer.ts',\r\n\tSpellseeker: 'sniper/03_Spellseeker.ts',\r\n\t// Duelist\r\n\tBattlemage: 'duelist/01_Battlemage.ts',\r\n\tWarmage: 'duelist/02_Warmage.ts',\r\n\tArchmage: 'duelist/03_Archmage.ts',\r\n\t// Berserker\r\n\tStormchaser: 'berserker/01_Stormchaser.ts',\r\n\tStormcaller: 'berserker/02_Stormcaller.ts',\r\n\tStormforger: 'berserker/03_Stormforger.ts',\r\n\t// Kiter\r\n\tSpellspinner: 'kiter/01_Spellspinner.ts',\r\n\tSpellweaver: 'kiter/02_Spellweaver.ts',\r\n\tSpellbinder: 'kiter/03_Spellbinder.ts',\r\n};\r\n\r\n/**\r\n * Get CompileOptions that alias @vibemancer/core to the TypeScript source.\r\n * Required for compiling user bots that import from @vibemancer/core.\r\n */\r\nexport function getCoreCompileOptions(): CompileOptions\r\n{\r\n\tconst coreSourceDir = findCoreSourceDir();\r\n\treturn {\r\n\t\talias: {\r\n\t\t\t'@vibemancer/core': coreSourceDir + '/index-browser.ts',\r\n\t\t},\r\n\t};\r\n}\r\n\r\n/**\r\n * Get the list of all built-in bot names.\r\n */\r\nexport function getBuiltinBotNames(): string[]\r\n{\r\n\treturn Object.keys(BUILTIN_BOTS);\r\n}\r\n\r\n/**\r\n * Resolve an opponent name to a BotBundle.\r\n *\r\n * @param name - Built-in bot name (e.g., \"Battlemage\")\r\n * @returns BotBundle pointing at the bot's TypeScript source\r\n */\r\nexport function resolveOpponent(name: string): BotBundle\r\n{\r\n\tconst relativePath = BUILTIN_BOTS[name];\r\n\tif (!relativePath)\r\n\t{\r\n\t\tconst available = Object.keys(BUILTIN_BOTS).join(', ');\r\n\t\tthrow new Error(\r\n\t\t\t`Unknown opponent \"${name}\".\\n`\r\n\t\t\t+ `Available built-in bots: ${available}`,\r\n\t\t);\r\n\t}\r\n\r\n\tconst coreSourceDir = findCoreSourceDir();\r\n\tconst sourcePath = path.join(coreSourceDir, 'bots', relativePath);\r\n\r\n\tif (!fs.existsSync(sourcePath))\r\n\t{\r\n\t\tthrow new Error(`Built-in bot source not found: ${sourcePath}`);\r\n\t}\r\n\r\n\treturn new BotBundle(sourcePath, name);\r\n}\r\n"],"mappings":";;;AAOA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAQ,qBAAoB;AAC5B,SAAQ,iBAAgB;AAQjB,SAAS,oBAChB;AAEC,QAAM,eAAe,KAAK,QAAQ,YAAY,SAAS,MAAM,MAAM,QAAQ,KAAK;AAChF,MAAI,GAAG,WAAW,KAAK,KAAK,cAAc,UAAU,eAAe,CAAC,GACpE;AACC,WAAO;AAAA,EACR;AAIA,MACA;AACC,UAAM,YAAY,YAAY,QAAQ,kBAAkB;AAExD,UAAM,gBAAgB,cAAc,SAAS;AAC7C,UAAM,WAAW,KAAK,QAAQ,KAAK,QAAQ,aAAa,GAAG,IAAI;AAC/D,UAAM,SAAS,KAAK,KAAK,UAAU,KAAK;AACxC,QAAI,GAAG,WAAW,KAAK,KAAK,QAAQ,UAAU,eAAe,CAAC,GAC9D;AACC,aAAO;AAAA,IACR;AAAA,EACD,QAEA;AAAA,EAEA;AAEA,QAAM,IAAI;AAAA,IACT;AAAA,EAED;AACD;AAUA,IAAM,eAAuC;AAAA;AAAA,EAE5C,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,aAAa;AAAA;AAAA,EAEb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,OAAO;AAAA;AAAA,EAEP,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,UAAU;AAAA;AAAA,EAEV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EAEb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA;AAAA,EAEV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AACd;AAMO,SAAS,wBAChB;AACC,QAAM,gBAAgB,kBAAkB;AACxC,SAAO;AAAA,IACN,OAAO;AAAA,MACN,oBAAoB,gBAAgB;AAAA,IACrC;AAAA,EACD;AACD;AAKO,SAAS,qBAChB;AACC,SAAO,OAAO,KAAK,YAAY;AAChC;AAQO,SAAS,gBAAgB,MAChC;AACC,QAAM,eAAe,aAAa,IAAI;AACtC,MAAI,CAAC,cACL;AACC,UAAM,YAAY,OAAO,KAAK,YAAY,EAAE,KAAK,IAAI;AACrD,UAAM,IAAI;AAAA,MACT,qBAAqB,IAAI;AAAA,2BACK,SAAS;AAAA,IACxC;AAAA,EACD;AAEA,QAAM,gBAAgB,kBAAkB;AACxC,QAAM,aAAa,KAAK,KAAK,eAAe,QAAQ,YAAY;AAEhE,MAAI,CAAC,GAAG,WAAW,UAAU,GAC7B;AACC,UAAM,IAAI,MAAM,kCAAkC,UAAU,EAAE;AAAA,EAC/D;AAEA,SAAO,IAAI,UAAU,YAAY,IAAI;AACtC;","names":[]}
1
+ {"version":3,"sources":["../src/opponent-resolver.ts"],"sourcesContent":["/**\r\n * Opponent Resolver\r\n *\r\n * Resolves an opponent name (e.g., \"Battlemage\", \"Turtle\") to a BotBundle\r\n * pointing at the built-in bot's TypeScript source file.\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport {fileURLToPath} from 'node:url';\r\nimport {BotBundle} from '@vibemancer/core';\r\nimport type {CompileOptions} from '@vibemancer/core';\r\n\r\n/**\r\n * Find the @vibemancer/core package's source directory.\r\n * The core package ships with both dist/ and src/ (TypeScript source).\r\n * esbuild needs the TypeScript source for compilation.\r\n */\r\nexport function findCoreSourceDir(): string\r\n{\r\n\t// When running from the monorepo (development), core is a sibling package\r\n\tconst monorepoCore = path.resolve(import.meta.dirname, '..', '..', 'core', 'src');\r\n\tif (fs.existsSync(path.join(monorepoCore, 'engine', 'simulation.ts')))\r\n\t{\r\n\t\treturn monorepoCore;\r\n\t}\r\n\r\n\t// When installed as a dependency, core is in node_modules\r\n\t// Use import.meta.resolve to find the package\r\n\ttry\r\n\t{\r\n\t\tconst coreIndex = import.meta.resolve('@vibemancer/core');\r\n\t\t// coreIndex = file:///path/to/node_modules/@vibemancer/core/dist/index.js\r\n\t\tconst coreIndexPath = fileURLToPath(coreIndex);\r\n\t\tconst coreRoot = path.resolve(path.dirname(coreIndexPath), '..');\r\n\t\tconst srcDir = path.join(coreRoot, 'src');\r\n\t\tif (fs.existsSync(path.join(srcDir, 'engine', 'simulation.ts')))\r\n\t\t{\r\n\t\t\treturn srcDir;\r\n\t\t}\r\n\t}\r\n\tcatch\r\n\t{\r\n\t\t// import.meta.resolve failed\r\n\t}\r\n\r\n\tthrow new Error(\r\n\t\t'Could not find @vibemancer/core source directory.\\n'\r\n\t\t+ 'Ensure @vibemancer/core is installed and includes its src/ directory.',\r\n\t);\r\n}\r\n\r\n/**\r\n * Built-in bot source file mapping.\r\n * Maps bot name → relative path from core's src/bots/ directory.\r\n *\r\n * Derived from the naming convention:\r\n * - Standalone: standalone/{Name}.ts\r\n * - Tiered: {group}/{nn}_{Name}.ts\r\n */\r\nconst BUILTIN_BOTS: Record<string, string> = {\r\n\t// Standalone\r\n\tTargetDummy: 'standalone/TargetDummy.ts',\r\n\tCritter: 'standalone/Critter.ts',\r\n\tRookie: 'standalone/Rookie.ts',\r\n\tHogger: 'standalone/Hogger.ts',\r\n\tDoombringer: 'standalone/Doombringer.ts',\r\n\t// Defensive\r\n\tTurtle: 'defensive/01_Turtle.ts',\r\n\tSentinel: 'defensive/02_Sentinel.ts',\r\n\tGolem: 'defensive/03_Golem.ts',\r\n\t// Melee\r\n\tShadowblade: 'melee/01_Shadowblade.ts',\r\n\tNightblade: 'melee/02_Nightblade.ts',\r\n\tVoidblade: 'melee/03_Voidblade.ts',\r\n\t// Homing\r\n\tBonemancer: 'homing/01_Bonemancer.ts',\r\n\tLich: 'homing/02_Lich.ts',\r\n\tArchlich: 'homing/03_Archlich.ts',\r\n\t// Caster\r\n\tFlamecaller: 'caster/01_Flamecaller.ts',\r\n\tPyromancer: 'caster/02_Pyromancer.ts',\r\n\tInfernalist: 'caster/03_Infernalist.ts',\r\n\t// Sniper\r\n\tSpellshot: 'sniper/01_Spellshot.ts',\r\n\tSpelltracer: 'sniper/02_Spelltracer.ts',\r\n\tSpellseeker: 'sniper/03_Spellseeker.ts',\r\n\t// Duelist\r\n\tBattlemage: 'duelist/01_Battlemage.ts',\r\n\tWarmage: 'duelist/02_Warmage.ts',\r\n\tArchmage: 'duelist/03_Archmage.ts',\r\n\t// Berserker\r\n\tStormchaser: 'berserker/01_Stormchaser.ts',\r\n\tStormcaller: 'berserker/02_Stormcaller.ts',\r\n\tStormforger: 'berserker/03_Stormforger.ts',\r\n\t// Kiter\r\n\tSpellspinner: 'kiter/01_Spellspinner.ts',\r\n\tSpellweaver: 'kiter/02_Spellweaver.ts',\r\n\tSpellbinder: 'kiter/03_Spellbinder.ts',\r\n};\r\n\r\n/**\r\n * Get CompileOptions that alias @vibemancer/core to the TypeScript source.\r\n * Required for compiling user bots that import from @vibemancer/core.\r\n */\r\nexport function getCoreCompileOptions(): CompileOptions\r\n{\r\n\tconst coreSourceDir = findCoreSourceDir();\r\n\treturn {\r\n\t\talias: {\r\n\t\t\t'@vibemancer/core': coreSourceDir + '/index-browser.ts',\r\n\t\t},\r\n\t};\r\n}\r\n\r\n/**\r\n * Hero is resolvable but NOT part of the 29-bot roster.\r\n *\r\n * It is the adaptive SHOWCASE players build their own bot to beat, and `fight` advertises\r\n * \"all 29 built-in bots\" everywhere — scaffolder banner, README, scoring denominator — so\r\n * adding it to BUILTIN_BOTS would silently change what `fight` and `optimize` mean.\r\n *\r\n * But `vibemancer bots Hero` prints \"To fight: vibemancer fight --opponent Hero\", and that\r\n * command failed, because nothing could resolve the name. A tool printing a command it\r\n * cannot run is worse than omitting it: the user assumes they mistyped. So Hero resolves\r\n * for a DELIBERATE single fight, and stays out of the roster everything else counts.\r\n */\r\nconst EXTRA_RESOLVABLE_BOTS: Record<string, string> = {\r\n\tHero: 'Hero.ts',\r\n};\r\n\r\n/**\r\n * Get the list of all built-in bot names.\r\n */\r\nexport function getBuiltinBotNames(): string[]\r\n{\r\n\treturn Object.keys(BUILTIN_BOTS);\r\n}\r\n\r\n/**\r\n * Resolve an opponent name to a BotBundle.\r\n *\r\n * @param name - Built-in bot name (e.g., \"Battlemage\")\r\n * @returns BotBundle pointing at the bot's TypeScript source\r\n */\r\nexport function resolveOpponent(name: string): BotBundle\r\n{\r\n\tconst relativePath = BUILTIN_BOTS[name] ?? EXTRA_RESOLVABLE_BOTS[name];\r\n\tif (!relativePath)\r\n\t{\r\n\t\tconst available = [...Object.keys(BUILTIN_BOTS), ...Object.keys(EXTRA_RESOLVABLE_BOTS)].join(', ');\r\n\t\tthrow new Error(\r\n\t\t\t`Unknown opponent \"${name}\".\\n`\r\n\t\t\t+ `Available built-in bots: ${available}`,\r\n\t\t);\r\n\t}\r\n\r\n\tconst coreSourceDir = findCoreSourceDir();\r\n\tconst sourcePath = path.join(coreSourceDir, 'bots', relativePath);\r\n\r\n\tif (!fs.existsSync(sourcePath))\r\n\t{\r\n\t\tthrow new Error(`Built-in bot source not found: ${sourcePath}`);\r\n\t}\r\n\r\n\treturn new BotBundle(sourcePath, name);\r\n}\r\n"],"mappings":";;;AAOA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAQ,qBAAoB;AAC5B,SAAQ,iBAAgB;AAQjB,SAAS,oBAChB;AAEC,QAAM,eAAe,KAAK,QAAQ,YAAY,SAAS,MAAM,MAAM,QAAQ,KAAK;AAChF,MAAI,GAAG,WAAW,KAAK,KAAK,cAAc,UAAU,eAAe,CAAC,GACpE;AACC,WAAO;AAAA,EACR;AAIA,MACA;AACC,UAAM,YAAY,YAAY,QAAQ,kBAAkB;AAExD,UAAM,gBAAgB,cAAc,SAAS;AAC7C,UAAM,WAAW,KAAK,QAAQ,KAAK,QAAQ,aAAa,GAAG,IAAI;AAC/D,UAAM,SAAS,KAAK,KAAK,UAAU,KAAK;AACxC,QAAI,GAAG,WAAW,KAAK,KAAK,QAAQ,UAAU,eAAe,CAAC,GAC9D;AACC,aAAO;AAAA,IACR;AAAA,EACD,QAEA;AAAA,EAEA;AAEA,QAAM,IAAI;AAAA,IACT;AAAA,EAED;AACD;AAUA,IAAM,eAAuC;AAAA;AAAA,EAE5C,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,aAAa;AAAA;AAAA,EAEb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,OAAO;AAAA;AAAA,EAEP,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,UAAU;AAAA;AAAA,EAEV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EAEb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA;AAAA,EAEV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AACd;AAMO,SAAS,wBAChB;AACC,QAAM,gBAAgB,kBAAkB;AACxC,SAAO;AAAA,IACN,OAAO;AAAA,MACN,oBAAoB,gBAAgB;AAAA,IACrC;AAAA,EACD;AACD;AAcA,IAAM,wBAAgD;AAAA,EACrD,MAAM;AACP;AAKO,SAAS,qBAChB;AACC,SAAO,OAAO,KAAK,YAAY;AAChC;AAQO,SAAS,gBAAgB,MAChC;AACC,QAAM,eAAe,aAAa,IAAI,KAAK,sBAAsB,IAAI;AACrE,MAAI,CAAC,cACL;AACC,UAAM,YAAY,CAAC,GAAG,OAAO,KAAK,YAAY,GAAG,GAAG,OAAO,KAAK,qBAAqB,CAAC,EAAE,KAAK,IAAI;AACjG,UAAM,IAAI;AAAA,MACT,qBAAqB,IAAI;AAAA,2BACK,SAAS;AAAA,IACxC;AAAA,EACD;AAEA,QAAM,gBAAgB,kBAAkB;AACxC,QAAM,aAAa,KAAK,KAAK,eAAe,QAAQ,YAAY;AAEhE,MAAI,CAAC,GAAG,WAAW,UAAU,GAC7B;AACC,UAAM,IAAI,MAAM,kCAAkC,UAAU,EAAE;AAAA,EAC/D;AAEA,SAAO,IAAI,UAAU,YAAY,IAAI;AACtC;","names":[]}
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/auth/oauth-client.ts
4
+ import { createHash, randomBytes } from "crypto";
5
+
6
+ // src/auth/credentials-store.ts
7
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ function configDir() {
11
+ return process.env.VIBEMANCER_CONFIG_DIR ?? join(homedir(), ".vibemancer");
12
+ }
13
+ function credentialsPath() {
14
+ return join(configDir(), "credentials.json");
15
+ }
16
+ function loadCredentials() {
17
+ const path = credentialsPath();
18
+ if (!existsSync(path)) return null;
19
+ try {
20
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
21
+ if (typeof parsed === "object" && parsed !== null && "refreshToken" in parsed && typeof parsed.refreshToken === "string" && "accessToken" in parsed && typeof parsed.accessToken === "string" && "expiresAt" in parsed && typeof parsed.expiresAt === "number") {
22
+ return { refreshToken: parsed.refreshToken, accessToken: parsed.accessToken, expiresAt: parsed.expiresAt };
23
+ }
24
+ return null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ function saveCredentials(credentials) {
30
+ mkdirSync(configDir(), { recursive: true });
31
+ const path = credentialsPath();
32
+ writeFileSync(path, JSON.stringify(credentials, null, 2), { mode: 384 });
33
+ try {
34
+ chmodSync(path, 384);
35
+ } catch {
36
+ }
37
+ }
38
+ function clearCredentials() {
39
+ try {
40
+ rmSync(credentialsPath(), { force: true });
41
+ } catch {
42
+ }
43
+ }
44
+
45
+ // src/auth/oauth-client.ts
46
+ var MCP_BASE_URL = process.env.VIBEMANCER_MCP_URL ?? "https://mcp.vibemancer.com";
47
+ var NotLoggedInError = class extends Error {
48
+ constructor() {
49
+ super("Not logged in. Run: vibemancer login");
50
+ this.name = "NotLoggedInError";
51
+ }
52
+ };
53
+ function generatePkce() {
54
+ const verifier = randomBytes(32).toString("base64url");
55
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
56
+ return { verifier, challenge };
57
+ }
58
+ async function registerClient(redirectUri) {
59
+ const res = await fetch(`${MCP_BASE_URL}/register`, {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({ redirect_uris: [redirectUri], client_name: "vibemancer-cli" })
63
+ });
64
+ if (!res.ok) throw new Error(`Client registration failed (${res.status}).`);
65
+ const json = await res.json();
66
+ const clientId = typeof json === "object" && json !== null && "client_id" in json && typeof json.client_id === "string" ? json.client_id : "";
67
+ if (!clientId) throw new Error("Client registration returned no client_id.");
68
+ return clientId;
69
+ }
70
+ function parseTokenResponse(json, fallbackRefresh) {
71
+ if (typeof json !== "object" || json === null) throw new Error("Invalid token response.");
72
+ const accessToken = "access_token" in json && typeof json.access_token === "string" ? json.access_token : "";
73
+ if (!accessToken) throw new Error("Token response missing access_token.");
74
+ const refreshToken = "refresh_token" in json && typeof json.refresh_token === "string" ? json.refresh_token : fallbackRefresh;
75
+ const expiresIn = "expires_in" in json && typeof json.expires_in === "number" ? json.expires_in : 3600;
76
+ return { accessToken, refreshToken, expiresAt: Date.now() + expiresIn * 1e3 };
77
+ }
78
+ async function exchangeCode(code, codeVerifier) {
79
+ const res = await fetch(`${MCP_BASE_URL}/token`, {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({ grant_type: "authorization_code", code, code_verifier: codeVerifier })
83
+ });
84
+ if (!res.ok) throw new Error(`Token exchange failed (${res.status}): ${await res.text()}`);
85
+ const creds = parseTokenResponse(await res.json(), "");
86
+ saveCredentials(creds);
87
+ return creds;
88
+ }
89
+ async function refreshCredentials(refreshToken) {
90
+ const res = await fetch(`${MCP_BASE_URL}/token`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({ grant_type: "refresh_token", refresh_token: refreshToken })
94
+ });
95
+ if (!res.ok) throw new NotLoggedInError();
96
+ const creds = parseTokenResponse(await res.json(), refreshToken);
97
+ saveCredentials(creds);
98
+ return creds;
99
+ }
100
+ var REFRESH_SKEW_MS = 6e4;
101
+ async function getAccessToken() {
102
+ const creds = loadCredentials();
103
+ if (!creds) throw new NotLoggedInError();
104
+ if (creds.expiresAt - Date.now() > REFRESH_SKEW_MS) return creds.accessToken;
105
+ const refreshed = await refreshCredentials(creds.refreshToken);
106
+ return refreshed.accessToken;
107
+ }
108
+
109
+ export {
110
+ loadCredentials,
111
+ clearCredentials,
112
+ MCP_BASE_URL,
113
+ NotLoggedInError,
114
+ generatePkce,
115
+ registerClient,
116
+ exchangeCode,
117
+ getAccessToken
118
+ };
119
+ //# sourceMappingURL=chunk-MCU2B4PZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/oauth-client.ts","../src/auth/credentials-store.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention -- OAuth 2.0 wire-format field names (grant_type, access_token, …) are snake_case by spec */\n\n/**\n * CLI OAuth client for the Vibemancer MCP gateway.\n *\n * The CLI is a PKCE OAuth client of mcp.vibemancer.com (the same gateway the MCP\n * AI clients use). `vibemancer login` runs the authorization-code flow; the\n * resulting session token carries the canonical Firebase uid. This module owns\n * PKCE, the token exchange/refresh, and handing callers a fresh access token.\n */\n\nimport {createHash, randomBytes} from 'node:crypto';\nimport {loadCredentials, saveCredentials, type Credentials} from './credentials-store.js';\n\n/** Gateway base URL; overridable for emulator/E2E via VIBEMANCER_MCP_URL. */\nexport const MCP_BASE_URL = process.env.VIBEMANCER_MCP_URL ?? 'https://mcp.vibemancer.com';\n\nexport class NotLoggedInError extends Error\n{\n\tconstructor()\n\t{\n\t\tsuper('Not logged in. Run: vibemancer login');\n\t\tthis.name = 'NotLoggedInError';\n\t}\n}\n\nexport interface Pkce\n{\n\tverifier: string;\n\tchallenge: string;\n}\n\nexport function generatePkce(): Pkce\n{\n\tconst verifier = randomBytes(32).toString('base64url');\n\tconst challenge = createHash('sha256').update(verifier).digest('base64url');\n\treturn {verifier, challenge};\n}\n\n/** Dynamic client registration → client_id (localhost/https redirect allowed). */\nexport async function registerClient(redirectUri: string): Promise<string>\n{\n\tconst res = await fetch(`${MCP_BASE_URL}/register`, {\n\t\tmethod: 'POST',\n\t\theaders: {'Content-Type': 'application/json'},\n\t\tbody: JSON.stringify({redirect_uris: [redirectUri], client_name: 'vibemancer-cli'}),\n\t});\n\tif (!res.ok) throw new Error(`Client registration failed (${res.status}).`);\n\tconst json: unknown = await res.json();\n\tconst clientId = typeof json === 'object' && json !== null && 'client_id' in json && typeof json.client_id === 'string'\n\t\t? json.client_id\n\t\t: '';\n\tif (!clientId) throw new Error('Client registration returned no client_id.');\n\treturn clientId;\n}\n\nfunction parseTokenResponse(json: unknown, fallbackRefresh: string): Credentials\n{\n\tif (typeof json !== 'object' || json === null) throw new Error('Invalid token response.');\n\tconst accessToken = 'access_token' in json && typeof json.access_token === 'string' ? json.access_token : '';\n\tif (!accessToken) throw new Error('Token response missing access_token.');\n\tconst refreshToken = 'refresh_token' in json && typeof json.refresh_token === 'string' ? json.refresh_token : fallbackRefresh;\n\tconst expiresIn = 'expires_in' in json && typeof json.expires_in === 'number' ? json.expires_in : 3600;\n\treturn {accessToken, refreshToken, expiresAt: Date.now() + expiresIn * 1000};\n}\n\n/** Exchange an authorization code (PKCE) for tokens, and persist them. */\nexport async function exchangeCode(code: string, codeVerifier: string): Promise<Credentials>\n{\n\tconst res = await fetch(`${MCP_BASE_URL}/token`, {\n\t\tmethod: 'POST',\n\t\theaders: {'Content-Type': 'application/json'},\n\t\tbody: JSON.stringify({grant_type: 'authorization_code', code, code_verifier: codeVerifier}),\n\t});\n\tif (!res.ok) throw new Error(`Token exchange failed (${res.status}): ${await res.text()}`);\n\tconst creds = parseTokenResponse(await res.json(), '');\n\tsaveCredentials(creds);\n\treturn creds;\n}\n\nasync function refreshCredentials(refreshToken: string): Promise<Credentials>\n{\n\tconst res = await fetch(`${MCP_BASE_URL}/token`, {\n\t\tmethod: 'POST',\n\t\theaders: {'Content-Type': 'application/json'},\n\t\tbody: JSON.stringify({grant_type: 'refresh_token', refresh_token: refreshToken}),\n\t});\n\tif (!res.ok) throw new NotLoggedInError();\n\tconst creds = parseTokenResponse(await res.json(), refreshToken);\n\tsaveCredentials(creds);\n\treturn creds;\n}\n\nconst REFRESH_SKEW_MS = 60_000;\n\n/** A valid access token, refreshing silently when within 60s of expiry. */\nexport async function getAccessToken(): Promise<string>\n{\n\tconst creds = loadCredentials();\n\tif (!creds) throw new NotLoggedInError();\n\tif (creds.expiresAt - Date.now() > REFRESH_SKEW_MS) return creds.accessToken;\n\tconst refreshed = await refreshCredentials(creds.refreshToken);\n\treturn refreshed.accessToken;\n}\n","/**\n * Local credential store for the CLI's OAuth session.\n *\n * Persists the refresh + access tokens at ~/.vibemancer/credentials.json so the\n * user stays logged in between commands. The directory is overridable via\n * VIBEMANCER_CONFIG_DIR (tests, or users who relocate their config).\n */\n\nimport {chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs';\nimport {homedir} from 'node:os';\nimport {join} from 'node:path';\n\nexport interface Credentials\n{\n\trefreshToken: string;\n\taccessToken: string;\n\t/** Epoch milliseconds at which the access token expires. */\n\texpiresAt: number;\n}\n\nfunction configDir(): string\n{\n\treturn process.env.VIBEMANCER_CONFIG_DIR ?? join(homedir(), '.vibemancer');\n}\n\nfunction credentialsPath(): string\n{\n\treturn join(configDir(), 'credentials.json');\n}\n\nexport function loadCredentials(): Credentials | null\n{\n\tconst path = credentialsPath();\n\tif (!existsSync(path)) return null;\n\ttry\n\t{\n\t\tconst parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));\n\t\tif (\n\t\t\ttypeof parsed === 'object' && parsed !== null\n\t\t\t&& 'refreshToken' in parsed && typeof parsed.refreshToken === 'string'\n\t\t\t&& 'accessToken' in parsed && typeof parsed.accessToken === 'string'\n\t\t\t&& 'expiresAt' in parsed && typeof parsed.expiresAt === 'number'\n\t\t)\n\t\t{\n\t\t\treturn {refreshToken: parsed.refreshToken, accessToken: parsed.accessToken, expiresAt: parsed.expiresAt};\n\t\t}\n\t\treturn null;\n\t}\n\tcatch\n\t{\n\t\treturn null;\n\t}\n}\n\nexport function saveCredentials(credentials: Credentials): void\n{\n\tmkdirSync(configDir(), {recursive: true});\n\tconst path = credentialsPath();\n\twriteFileSync(path, JSON.stringify(credentials, null, 2), {mode: 0o600});\n\t// Best-effort tighten perms (no-op semantics on Windows).\n\ttry\n\t{\n\t\tchmodSync(path, 0o600);\n\t}\n\tcatch\n\t{\n\t\t// ignore — Windows / restricted FS\n\t}\n}\n\nexport function clearCredentials(): void\n{\n\ttry\n\t{\n\t\trmSync(credentialsPath(), {force: true});\n\t}\n\tcatch\n\t{\n\t\t// ignore\n\t}\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -4,10 +4,17 @@ import {
4
4
  getBuiltinBotNames,
5
5
  getCoreCompileOptions,
6
6
  resolveOpponent
7
- } from "./chunk-4DUS7IHW.js";
7
+ } from "./chunk-CMTEJ5MU.js";
8
8
  import {
9
- FIREBASE_CONFIG
10
- } from "./chunk-AA2UJPPN.js";
9
+ MCP_BASE_URL,
10
+ NotLoggedInError,
11
+ clearCredentials,
12
+ exchangeCode,
13
+ generatePkce,
14
+ getAccessToken,
15
+ loadCredentials,
16
+ registerClient
17
+ } from "./chunk-MCU2B4PZ.js";
11
18
 
12
19
  // src/commands/dev.ts
13
20
  import { execFile } from "child_process";
@@ -217,6 +224,8 @@ function startServer(options) {
217
224
  res.setHeader("Access-Control-Allow-Origin", "*");
218
225
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
219
226
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
227
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
228
+ res.setHeader("Access-Control-Allow-Local-Network-Access", "true");
220
229
  if (req.method === "OPTIONS") {
221
230
  res.writeHead(204);
222
231
  res.end();
@@ -329,10 +338,10 @@ import { spawn } from "child_process";
329
338
  async function runTest(_options) {
330
339
  console.log("\n Running tests...\n");
331
340
  const code = await new Promise((resolve) => {
332
- const child = spawn("npx", ["vitest", "run"], {
341
+ const child = spawn("npx vitest run", {
333
342
  stdio: "inherit",
334
343
  cwd: process.cwd(),
335
- shell: process.platform === "win32"
344
+ shell: true
336
345
  });
337
346
  child.on("error", () => resolve(1));
338
347
  child.on("close", (status) => resolve(status ?? 1));
@@ -352,6 +361,16 @@ import { initializeApp, getApps } from "firebase/app";
352
361
  import { getFirestore, collection, query, where, limit, getDocs, connectFirestoreEmulator } from "firebase/firestore";
353
362
  import { getStorage, ref, getBytes, connectStorageEmulator } from "firebase/storage";
354
363
  import { isBannedBotName } from "@vibemancer/core";
364
+
365
+ // src/firebase-config.ts
366
+ var FIREBASE_CONFIG = {
367
+ apiKey: "AIzaSyDSUJ2jlk5_vlpGOypMtvqKjHhDmbgomIY",
368
+ authDomain: "le-vibemancer.firebaseapp.com",
369
+ projectId: "le-vibemancer",
370
+ storageBucket: "le-vibemancer.firebasestorage.app"
371
+ };
372
+
373
+ // src/remote-opponent.ts
355
374
  function isHandleSelector(opponent) {
356
375
  const trimmed = opponent.trim();
357
376
  const slash = trimmed.indexOf("/");
@@ -913,7 +932,8 @@ function runBots(options) {
913
932
  listAllBots();
914
933
  }
915
934
  function listAllBots() {
916
- console.log("\n Built-in Bots (29 total, ranked weakest \u2192 strongest)\n");
935
+ console.log("\n Built-in Bots (29 total, by round-robin points \u2014 weakest first)");
936
+ console.log(" Matchups are non-transitive: a low-ranked bot may still counter yours.\n");
917
937
  for (const group of BOT_GROUPS) {
918
938
  console.log(` ${group.label}:`);
919
939
  for (const bot of group.bots) {
@@ -994,121 +1014,15 @@ function getStyleDescription(bot) {
994
1014
  // src/commands/upload.ts
995
1015
  import fs4 from "fs";
996
1016
 
997
- // src/auth/oauth-client.ts
998
- import { createHash, randomBytes } from "crypto";
999
-
1000
- // src/auth/credentials-store.ts
1001
- import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
1002
- import { homedir } from "os";
1003
- import { join } from "path";
1004
- function configDir() {
1005
- return process.env.VIBEMANCER_CONFIG_DIR ?? join(homedir(), ".vibemancer");
1006
- }
1007
- function credentialsPath() {
1008
- return join(configDir(), "credentials.json");
1009
- }
1010
- function loadCredentials() {
1011
- const path7 = credentialsPath();
1012
- if (!existsSync(path7)) return null;
1013
- try {
1014
- const parsed = JSON.parse(readFileSync2(path7, "utf-8"));
1015
- if (typeof parsed === "object" && parsed !== null && "refreshToken" in parsed && typeof parsed.refreshToken === "string" && "accessToken" in parsed && typeof parsed.accessToken === "string" && "expiresAt" in parsed && typeof parsed.expiresAt === "number") {
1016
- return { refreshToken: parsed.refreshToken, accessToken: parsed.accessToken, expiresAt: parsed.expiresAt };
1017
- }
1018
- return null;
1019
- } catch {
1020
- return null;
1021
- }
1022
- }
1023
- function saveCredentials(credentials) {
1024
- mkdirSync(configDir(), { recursive: true });
1025
- const path7 = credentialsPath();
1026
- writeFileSync2(path7, JSON.stringify(credentials, null, 2), { mode: 384 });
1027
- try {
1028
- chmodSync(path7, 384);
1029
- } catch {
1030
- }
1031
- }
1032
- function clearCredentials() {
1033
- try {
1034
- rmSync(credentialsPath(), { force: true });
1035
- } catch {
1036
- }
1037
- }
1038
-
1039
- // src/auth/oauth-client.ts
1040
- var MCP_BASE_URL = process.env.VIBEMANCER_MCP_URL ?? "https://mcp.vibemancer.com";
1041
- var NotLoggedInError = class extends Error {
1042
- constructor() {
1043
- super("Not logged in. Run: vibemancer login");
1044
- this.name = "NotLoggedInError";
1045
- }
1046
- };
1047
- function generatePkce() {
1048
- const verifier = randomBytes(32).toString("base64url");
1049
- const challenge = createHash("sha256").update(verifier).digest("base64url");
1050
- return { verifier, challenge };
1051
- }
1052
- async function registerClient(redirectUri) {
1053
- const res = await fetch(`${MCP_BASE_URL}/register`, {
1054
- method: "POST",
1055
- headers: { "Content-Type": "application/json" },
1056
- body: JSON.stringify({ redirect_uris: [redirectUri], client_name: "vibemancer-cli" })
1057
- });
1058
- if (!res.ok) throw new Error(`Client registration failed (${res.status}).`);
1059
- const json = await res.json();
1060
- const clientId = typeof json === "object" && json !== null && "client_id" in json && typeof json.client_id === "string" ? json.client_id : "";
1061
- if (!clientId) throw new Error("Client registration returned no client_id.");
1062
- return clientId;
1063
- }
1064
- function parseTokenResponse(json, fallbackRefresh) {
1065
- if (typeof json !== "object" || json === null) throw new Error("Invalid token response.");
1066
- const accessToken = "access_token" in json && typeof json.access_token === "string" ? json.access_token : "";
1067
- if (!accessToken) throw new Error("Token response missing access_token.");
1068
- const refreshToken = "refresh_token" in json && typeof json.refresh_token === "string" ? json.refresh_token : fallbackRefresh;
1069
- const expiresIn = "expires_in" in json && typeof json.expires_in === "number" ? json.expires_in : 3600;
1070
- return { accessToken, refreshToken, expiresAt: Date.now() + expiresIn * 1e3 };
1071
- }
1072
- async function exchangeCode(code, codeVerifier) {
1073
- const res = await fetch(`${MCP_BASE_URL}/token`, {
1074
- method: "POST",
1075
- headers: { "Content-Type": "application/json" },
1076
- body: JSON.stringify({ grant_type: "authorization_code", code, code_verifier: codeVerifier })
1077
- });
1078
- if (!res.ok) throw new Error(`Token exchange failed (${res.status}): ${await res.text()}`);
1079
- const creds = parseTokenResponse(await res.json(), "");
1080
- saveCredentials(creds);
1081
- return creds;
1082
- }
1083
- async function refreshCredentials(refreshToken) {
1084
- const res = await fetch(`${MCP_BASE_URL}/token`, {
1085
- method: "POST",
1086
- headers: { "Content-Type": "application/json" },
1087
- body: JSON.stringify({ grant_type: "refresh_token", refresh_token: refreshToken })
1088
- });
1089
- if (!res.ok) throw new NotLoggedInError();
1090
- const creds = parseTokenResponse(await res.json(), refreshToken);
1091
- saveCredentials(creds);
1092
- return creds;
1093
- }
1094
- var REFRESH_SKEW_MS = 6e4;
1095
- async function getAccessToken() {
1096
- const creds = loadCredentials();
1097
- if (!creds) throw new NotLoggedInError();
1098
- if (creds.expiresAt - Date.now() > REFRESH_SKEW_MS) return creds.accessToken;
1099
- const refreshed = await refreshCredentials(creds.refreshToken);
1100
- return refreshed.accessToken;
1101
- }
1102
-
1103
1017
  // src/auth/env-headers.ts
1104
1018
  import os from "os";
1105
- import { readFileSync as readFileSync3 } from "fs";
1019
+ import { readFileSync as readFileSync2 } from "fs";
1106
1020
  import path4 from "path";
1107
1021
  import { fileURLToPath } from "url";
1108
1022
  function findCliVersionFrom(here) {
1109
1023
  for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
1110
1024
  try {
1111
- const parsed = JSON.parse(readFileSync3(path4.resolve(here, rel), "utf8"));
1025
+ const parsed = JSON.parse(readFileSync2(path4.resolve(here, rel), "utf8"));
1112
1026
  if (typeof parsed !== "object" || parsed === null) continue;
1113
1027
  if (!("name" in parsed) || !("version" in parsed)) continue;
1114
1028
  const { name, version } = parsed;
@@ -1251,7 +1165,7 @@ async function runPull(options) {
1251
1165
 
1252
1166
  // src/commands/login.ts
1253
1167
  import { createServer } from "http";
1254
- import { randomBytes as randomBytes2 } from "crypto";
1168
+ import { randomBytes } from "crypto";
1255
1169
  import { spawn as spawn2 } from "child_process";
1256
1170
  import { createInterface } from "readline/promises";
1257
1171
  function buildAuthorizeUrl(baseUrl, params) {
@@ -1343,7 +1257,7 @@ async function runPasteLogin(challenge, verifier, state) {
1343
1257
  }
1344
1258
  async function runLogin(options) {
1345
1259
  const { verifier, challenge } = generatePkce();
1346
- const state = randomBytes2(16).toString("hex");
1260
+ const state = randomBytes(16).toString("hex");
1347
1261
  if (options.noBrowser) {
1348
1262
  await runPasteLogin(challenge, verifier, state);
1349
1263
  } else {
@@ -1380,22 +1294,49 @@ async function runLogout() {
1380
1294
  }
1381
1295
 
1382
1296
  // src/commands/feedback.ts
1383
- async function runFeedback(options) {
1297
+ async function defaultTokenLookup() {
1298
+ const { getAccessToken: getAccessToken2 } = await import("./oauth-client-AX3HNK6P.js");
1299
+ return await getAccessToken2();
1300
+ }
1301
+ async function runFeedback(options, tokenLookup = defaultTokenLookup) {
1384
1302
  const message = options.message.trim();
1385
1303
  if (!message) {
1386
1304
  console.error(" Error: feedback message cannot be empty.");
1387
1305
  process.exit(1);
1306
+ return;
1388
1307
  }
1389
1308
  console.log("\n Submitting feedback...");
1309
+ let token;
1390
1310
  try {
1391
- const { initializeApp: initializeApp2 } = await import("firebase/app");
1392
- const { getFunctions, httpsCallable } = await import("firebase/functions");
1393
- const { FIREBASE_CONFIG: FIREBASE_CONFIG2 } = await import("./firebase-config-F6COE3NY.js");
1394
- const app = initializeApp2(FIREBASE_CONFIG2);
1395
- const functions = getFunctions(app, "us-central1");
1396
- const submitFn = httpsCallable(functions, "submitFeedback");
1397
- await submitFn({ message });
1398
- console.log(" Sent! Thanks for the feedback.\n");
1311
+ token = await tokenLookup();
1312
+ } catch {
1313
+ token = null;
1314
+ }
1315
+ const headers = { "Content-Type": "application/json", ...envHeaders() };
1316
+ if (token) headers.Authorization = `Bearer ${token}`;
1317
+ try {
1318
+ const res = await fetch(`${MCP_BASE_URL}/feedback`, {
1319
+ method: "POST",
1320
+ headers,
1321
+ body: JSON.stringify({ message }),
1322
+ signal: AbortSignal.timeout(15e3)
1323
+ });
1324
+ if (!res.ok) {
1325
+ const body = await res.text();
1326
+ let detail = body;
1327
+ try {
1328
+ const parsed = JSON.parse(body);
1329
+ if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
1330
+ detail = String(parsed.error);
1331
+ }
1332
+ } catch {
1333
+ }
1334
+ console.error(` Failed to submit: ${detail}
1335
+ `);
1336
+ process.exit(1);
1337
+ return;
1338
+ }
1339
+ console.log(token ? " Sent! Thanks for the feedback.\n" : " Sent! Thanks for the feedback. (Not signed in, so we have no way to reply.)\n");
1399
1340
  } catch (err) {
1400
1341
  const msg = err instanceof Error ? err.message : String(err);
1401
1342
  console.error(` Failed to submit: ${msg}
@@ -1594,7 +1535,7 @@ async function main() {
1594
1535
  case "trace": {
1595
1536
  const opponent = parseFlag("--opponent");
1596
1537
  if (!opponent) {
1597
- const { getBuiltinBotNames: getBuiltinBotNames2 } = await import("./opponent-resolver-U2UK2QEJ.js");
1538
+ const { getBuiltinBotNames: getBuiltinBotNames2 } = await import("./opponent-resolver-DJLF4CPO.js");
1598
1539
  const names = getBuiltinBotNames2();
1599
1540
  const list = names.map((n) => ` --opponent ${n}`).join("\n");
1600
1541
  console.error("Error: --opponent is required for trace.\n");