/** * Creates the "M15 · 原版商人 NPC 与交易经济系统 (1.13c Ground Truth)" Milestone and its 6 atomic Issues * in the Gitea repository https://git.projectdiablo2.cn/troytt/diablo2-web */ const BASE_URL = 'https://git.projectdiablo2.cn/api/v1/repos/troytt/diablo2-web' const TOKEN = '94c84cc3bd2eab804346cb889650c843d0cda244' async function giteaFetch(path: string, method = 'GET', body?: unknown): Promise { const res = await fetch(`${BASE_URL}${path}`, { method, headers: { Authorization: `token ${TOKEN}`, 'Content-Type': 'application/json', Accept: 'application/json', }, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), }) if (!res.ok) { const txt = await res.text() throw new Error(`Gitea API ${method} ${path} failed (${res.status}): ${txt}`) } return (await res.json()) as T } interface MilestoneResp { id: number title: string state: string } interface IssueResp { id: number number: number title: string html_url: string } async function main() { const existingMilestones = await giteaFetch('/milestones?state=all&limit=50') console.log('Existing milestones:', existingMilestones.map(m => `#${m.id}: ${m.title} (${m.state})`)) const targetTitle = '[M19] 原版商人 NPC 与交易经济系统 (1.13c Ground Truth)' let milestone = existingMilestones.find( m => m.id === 13 || m.title === targetTitle || m.title.includes('原版商人 NPC 与交易经济系统'), ) const milestoneDesc = `实装暗黑破坏神 II:毁灭之王 v1.13c 原版城镇商人(Vendor NPC)库存生成、物品交易/收购/修理/赌博定价引擎与四分页商店 UI。 ### 核心 Ground Truth 权威对标 (\`D2MOO\` + 官方 MPQ 数据表) 1. **商人全局物品缓存 (\`SUnitProxy.cpp\`)**: - \`SUNITPROXY_InitializeNpcControl\` / \`SUNITPROXY_FillGlobalItemCache\` / \`SUNITPROXY_FillIGlobaltemCacheRecordForNpc\` - 严格映射 17 位商人 ID (\`nNpcId = 0..16\`:阿卡拉、基德、恰西、法拉、雷山德、卓格南、赫拉铁力 \`Hralti*\`、阿尔科、奥玛斯、艾尔吉斯、艾席拉、凯恩、海尔布、贾梅拉、马拉、拉苏克、安亚/尼拉塞克),分离常驻商品 \`pPermCache\` (\`PermStoreItem = 1\`) 与动态随机库存 \`pItemCache\`。 2. **按商人 ID、游戏难度与角色等级动态生成出售物品 (\`SUnitNpc.cpp\`)**: - \`D2GAME_NPC_FillStoreInventory_6FCC7100\` & \`D2GAME_NPC_GenerateStoreItem_6FCC6A60\` - 商店物品等级公式:\`ilvl = max(1, min(99, cLvl + 5))\`,普通难度受 Act 1..5 幕数上限 \`[12, 20, 28, 36, 45]\` 钳制,噩梦/地狱难度无幕数上限。 - \`ilvl < 25\` 时滚动普通/劣质/超强底材;\`ilvl >= 25\` 时非固定库存 100% 升级为魔法品质 (\`ITEMQUAL_MAGIC\`) 且保底下限提升 (\`nMinItems = (rand & 1) + 2\`)。 - \`cLvl > 25\` 时在噩梦/地狱难度严格执行扩展级 (\`UberCode\`) / 精华级 (\`UltraCode\`) 升级骰子以及 \`NightmareUpgrade\` / \`HellUpgrade\` 药水替换。 3. **1.13c 交易价格与收购价格计算引擎 (\`Items.cpp\`)**: - \`ITEMS_CalculateTransactionCost (0x6FD9B1C0)\` 及辅助函数 \`0x6FD9AB90\` / \`0x6FD9ACE0\` / \`0x6FD9CB50\`。 - 精确计算购买价 (\`BUY\`)、商人收购价 (\`SELL\`)、修理费 (\`REPAIR\`) 与赌博价 (\`GAMBLE\`):涵盖防御比例缩放、Staffmods 单技能高额加价(售价 \`/1024\` vs 收购价 \`/4096\`)、词缀与 \`ItemStatCost.txt\` 逐点属性加价、镶嵌物半价累加、无形/职业专属收购价 \`/4\` 惩罚、\`Npc.txt\` 汇率与任务折扣、各难度最高收购上限(普通 \`5k/10k/15k/20k/25k\`、噩梦 \`30k\`、地狱 \`35k\`)及减价属性 (\`STAT_ITEM_REDUCEDPRICES\`)。 4. **城镇 NPC 交互菜单与四分页商店 UI**: - 原版风格 NPC 交互菜单(交谈 / 交易 / 赌博 / 修理 / 鉴定),左侧 10×10 四分页商店面板(护甲 / 武器 I / 武器 II / 杂物)与右侧玩家背包联动交易。` if (!milestone) { milestone = await giteaFetch('/milestones', 'POST', { title: targetTitle, description: milestoneDesc, state: 'open', }) console.log(`Created Milestone #${milestone.id}: ${milestone.title}`) } else { milestone = await giteaFetch(`/milestones/${milestone.id}`, 'PATCH', { title: targetTitle, description: milestoneDesc, state: 'open', }) console.log(`Updated Milestone #${milestone.id}: ${milestone.title}`) } const issuesToCreate = [ { key: 'P1', title: '[M19-P1] 烘焙 1.13c 商人经济与库存权威数据表 (Npc.txt / Vendor Columns / Gamble.txt / Books.txt / Stat & Skill Costs)', body: `## 背景与目标 为 1.13c 商人库存生成器与价格计算引擎提供零网络、100% 忠实于官方 MPQ 数据表的底层数据契约。 ## 1.13c Ground Truth 对标 (\`ItemsTbls.cpp\` / \`MonsterTbls.cpp\`) 1. **17 位商人配置列 (\`Weapons.txt\` / \`Armor.txt\` / \`Misc.txt\`)**: - 提取 \`nNpcId = 0..16\` 的 \`Min\`, \`Max\`, \`MagicMin\`, \`MagicMax\`, \`MagicLvl\`(注意 \`nNpcId = 6\` 赫拉铁力在原版表中的列名拼写为 \`HraltiMin\` / \`HraltiMax\` / \`HraltiMagicMin\` / \`HraltiMagicMax\` / \`HraltiMagicLvl\`)。 - 提取 \`PermStoreItem\`、\`multibuy\`、\`NightmareUpgrade\`、\`HellUpgrade\`、\`cost\`、\`gamble cost\`、\`bitfield1\`。 2. **\`Npc.txt\` 商人经济表 (\`src/game/npc-table.ts\`)**: - 解析 \`npc\`, \`buy mult\`, \`sell mult\`, \`rep mult\`, \`questflag A/B/C\`, \`questbuymult A/B/C\`, \`questsellmult A/B/C\`, \`questrepmult A/B/C\`, \`max buy\`, \`max buy (N)\`, \`max buy (H)\`。 - 建立 \`MonsterId (hcIdx)\` $\\leftrightarrow$ \`VendorNpcId (0..16)\` 及 NPC 服务能力矩阵(Trade / Gamble / Repair / Heal / Identify)。 3. **\`ItemTypes.txt\` / \`ItemStatCost.txt\` / \`Skills.txt\` / \`Gamble.txt\` / \`Books.txt\` / \`LowQualityItems.txt\`**: - \`ItemTypes.txt\`:解析 \`StorePage\` (\`armr\` / \`weap\` / \`misc\`)、\`StaffMods\` 与 \`Class\`(用于职业专属物品收购价 \`/4\` 折算)。 - \`ItemStatCost.txt\`:解析 \`ID\`, \`Stat\`, \`Encode\`, \`Multiply\`, \`Add\`, \`ValShift\`。 - \`Skills.txt\`:解析 \`Id\`, \`skill\`, \`charclass\`, \`cost mult\`, \`cost add\`。 - 烘焙 \`Gamble.txt\`(赌博底材代码池)与 \`Books.txt\`(回城/辨识书卷轴价格与充能成本)。 ## 验收标准 - [ ] \`canonical-drop-data.ts\` 与 \`embedded-drop-tables.ts\` 零网络同步导出上述全部权威表结构。 - [ ] 单元测试验证全部 17 位商人的列解析(含 \`Hralti*\`)及 \`Npc.txt\` 三难度 \`max buy\` 上限(普通 \`5000~25000\`,噩梦 \`30000\`,地狱 \`35000\`)准确无误。`, }, { key: 'P2', title: '[M19-P2] 实装 1.13c 商人库存生成引擎 (按商人 ID、游戏难度与角色等级生成出售物品)', body: `## 背景与目标 严格按照 \`D2Game.dll\` 反编译源码实装商人库存生成引擎 (\`src/game/vendor-generator.ts\`),根据**商人 ID (\`nNpcId\`)**、**游戏难度 (\`Normal/Nightmare/Hell\`)** 与**角色等级 (\`cLvl\`)** 生成四分页出售物品。 ## 1.13c Ground Truth 对标 (\`SUnitProxy.cpp\` & \`SUnitNpc.cpp\`) 1. **全局缓存构建 (\`SUNITPROXY_FillIGlobaltemCacheRecordForNpc\`)**: - 按 \`Weapons.txt\` $\\rightarrow$ \`Armor.txt\` $\\rightarrow$ \`Misc.txt\` 顺序扫描 \`nSpawnable != 0\` 且 \`(nVendorMax > 0 || nVendorMagicMax > 0)\` 的物品。 - \`PermStoreItem != 0\` 归入 \`pPermCache\`;其余归入 \`pItemCache\`。 2. **商店物品等级 \`ilvl\` 计算 (\`D2GAME_NPC_FillStoreInventory_6FCC7100\`)**: - \`nItemLevel = Math.max(1, Math.min(99, nPlayerLevel + 5))\`。 - 普通难度 (\`nDifficulty == 0\`) 按幕数 \`nAct = 0..4\` 强制上限 \`npcLevels[5] = [12, 20, 28, 36, 45]\`;噩梦与地狱难度无幕数上限。 3. **非固定商品生成 (\`pItemCache\`)**: - 跳过 \`base.level > nItemLevel\` 的底材。 - **当且仅当 \`nItemLevel < 25\` 且 \`nMax > 0\` 时**生成普通/劣质/超强底材: - \`nNormalItems = nMin + rand(nMax - nMin + 1)\`。 - 品质掷骰 \`rand(100)\`:\`ilvl < 5\` 时 \`> 90\` 为劣质 (\`ITEMQUAL_INFERIOR\`);\`5 <= ilvl < 10\` 时 \`> 85\` 为超强 (\`ITEMQUAL_SUPERIOR\`);\`10 <= ilvl < 25\` 时 \`>= 75\` 为超强;其余为普通白色 (\`ITEMQUAL_NORMAL\`)。 - **魔法商品生成 (\`nMagicMax > 0\` 且 \`bitfield1 & 1\`)**: - \`nMinItems = (nItemLevel >= 25) ? ((rand(2)) + 2) : 1\`。 - \`nRange = nMinItems + nMagicMax - nMagicMin\`,生成 \`ITEMQUAL_MAGIC\` 并调用词缀生成器赋予前后缀(若词缀骰子未出词缀则自动重掷确保有效魔法词缀)与法杖/手杖/权杖 Staffmods。 4. **噩梦/地狱难度底材升级与药水替换 (\`D2GAME_NPC_GenerateStoreItem_6FCC6A60\`)**: - 当 \`nPlayerLevel > 25\` 时: - 噩梦难度:\`rand(100000) < (nItemLevel << 6) + 4000\` 升级为 \`UberCode\`(若 \`Uber.level <= nItemLevel\`),否则应用 \`NightmareUpgrade\`。 - 地狱难度:\`nRand = rand(100000)\`,资料片 \`< 16 * nItemLevel + 1000\` 升级为 \`UltraCode\`,否则 \`< (nItemLevel << 7) + 5000\` 升级为 \`UberCode\`;随后应用 \`HellUpgrade\`。 5. **常驻商品 (\`pPermCache\`) 与四分页 10×10 网格排布**: - 常驻物品标记 \`bPermStore = true\`,箭袋 (\`aqv\`/\`cqv\`) 设为满数量 \`maxstack\`。 - 按 \`StorePage\` (\`armr -> Tab 0\`, \`weap -> Tab 1\` 溢出至 \`Tab 2\`, \`misc -> Tab 3\`) 自动进行 2D 网格碰撞排布。 ## 验收标准 - [ ] 完整实现 \`src/game/vendor-generator.ts\`。 - [ ] 覆盖 Act 1~5 全部 17 位商人在普通/噩梦/地狱及不同等级下的库存生成单元测试。`, }, { key: 'P3', title: '[M19-P3] 实装 1.13c 物品交易与收购价格计算引擎 (ITEMS_CalculateTransactionCost 0x6FD9B1C0)', body: `## 背景与目标 100% 还原 \`D2Common.dll\` 中的 \`ITEMS_CalculateTransactionCost (0x6FD9B1C0)\` 及其 3 个子函数,精确计算任意物品的**商人售价 (玩家购买价 \`BUY\`)**、**商人收购价 (玩家出售价 \`SELL\`)**、**铁匠修理费 (\`REPAIR\`)** 与**赌博价 (\`GAMBLE\`)**。 ## 1.13c Ground Truth 对标 (\`Items.cpp:1709-2495\`) 1. **特殊物品短路与基础成本 (\`BaseCost\`)**: - 初始装备 (\`IFLAG_STARTITEM\`) 恒返回 \`1\` 金币。 - 防具按当前防御值比例缩放:\`nAC * dwCost / dwMaxAc\`。 - 箭袋按堆叠缩放:\`qty * dwCost / 1024\`;书册 (\`Books.txt\`) 按书页数量累加:\`BaseCost + qty * CostPerCharge\`。 - 初始累加器:\`nSellCost = BaseCost\`, \`nBuyCost = Math.trunc(BaseCost / 2)\`, \`nRepCost = BaseCost\`。 2. **Staffmods 单技能高额加价 (\`ITEMS_CalculateAdditionalCostsForItemSkill 0x6FD9CB50\`)**: - 对非全职业固定技能底材 (\`ItemTypes.StaffMods != 7\`),遍历物品上的单技能 (\`STAT_ITEM_SINGLESKILL\`): - \`nSellCost\` / \`nRepCost\` 增加 \`(2 * slvl - 1) * (SkillCostAdd + Math.trunc(BaseCost * SkillCostMult / 1024))\`。 - \`nBuyCost\`(收购价)增加 \`(2 * slvl - 1) * (SkillCostAdd + Math.trunc(BaseCost * SkillCostMult / 4096))\`(严格 1/4 比例)。 3. **品质、词缀与属性逐点加价 (\`IFLAG_IDENTIFIED\`)**: - **Magic / Rare / Crafted**:累加前后缀 \`dwAdd + BaseCost * dwMultiply / 1024\`(收购价 \`/2048\`)并调用 \`ITEMS_CalculateAdditionalCostsForBonusStats (0x6FD9ACE0)\` 按 \`ItemStatCost.txt\` 的 \`Multiply\` 与 \`Add\` 逐点累加(若出现负向词缀导致低于 \`BaseCost\`,严格触发保底回退 \`nSellCost = BaseCost, nBuyCost = BaseCost / 2\`)。 - **Unique / Set**:按 \`UniqueItems.txt\` / \`SetItems.txt\` 的 \`cost mult\` 与 \`cost add\` 加成并累加属性成本。 - **Superior**:累加超强属性加成;**Inferior**:扣减 \`BaseCost / 2\`。 4. **镶嵌物、无形与职业专属惩罚**: - 遍历孔内已镶嵌物品,三者均加上 \`Math.trunc(Filler.dwCost / 2)\`。 - **无形惩罚 (\`IFLAG_ETHEREAL\`)**:收购价 \`nBuyCost = Math.trunc(nBuyCost / 4)\`(破损无形归 0)。 - **职业专属物品惩罚 (\`ItemTypes.Class < 7\`)**:收购价 \`nBuyCost = Math.trunc(nBuyCost / 4)\`。 5. **\`Npc.txt\` 汇率、任务折扣、难度最高收购上限 (\`max buy\`) 与减价属性**: - 应用 \`Npc.txt\` 的 \`sell mult\` / \`buy mult\` / \`rep mult\` (\`/ 1024\`) 及已完成任务的 \`quest*mult\` (\`/ 1024\`)。 - **难度收购封顶**:当 \`transactionType === 'sell'\` 时,\`nBuyCost = Math.min(nBuyCost, NpcTxt.maxBuy[difficulty])\`(普通 Act 1..5 为 \`5000 / 10000 / 15000 / 20000 / 25000\`,噩梦 \`30000\`,地狱 \`35000\`)。 - **减价属性 (\`STAT_ITEM_REDUCEDPRICES\`)**:仅对 \`BUY\`、\`REPAIR\`、\`GAMBLE\` 生效(不对 \`SELL\` 生效)。 ## 验收标准 - [ ] 完整实现 \`src/game/item-cost.ts\`。 - [ ] 全面的单元测试覆盖全部品质、Staffmods、无形/职业专属惩罚、任务折扣与三难度收购上限。`, }, { key: 'P4', title: '[M19-P4] 实装 1.13c 城镇 NPC 交互系统与服务逻辑 (交谈 / 交易 / 赌博 / 治疗 / 修理 / 凯恩鉴定)', body: `## 背景与目标 基于 \`SUnitNpc.cpp\` 实装全 5 幕城镇 NPC 的完整服务会话与业务逻辑 (\`src/game/vendor-service.ts\`)。 ## 1.13c Ground Truth 对标 (\`SUnitNpc.cpp\`) 1. **购买与批量购买 (\`sub_6FCC88B0\`)**: - 校验玩家金币与背包空间;扣除金币。 - 常驻物品 (\`bPermStore = true\`) 购买后克隆新实例放入玩家背包/腰带,原商店格物品保留;非常驻物品购买后从商店网格移除。 - **Shift + 右键批量购买 (\`bShiftBuy\`)**: - 药水:自动买满玩家腰带对应列空槽。 - 回城卷轴 (\`tsc\`) / 辨识卷轴 (\`isc\`):自动补满玩家背包中的回城之书 (\`tbk\`) / 辨识之书 (\`ibk\`) 至 20 张上限。 - 钥匙 (\`key\`):自动补满当前钥匙堆叠至 12 把上限。 2. **出售与商店回购 (\`D2GAME_STORES_SellItem_6FCC7680\`)**: - 禁止出售任务物品或不可出售物品(如赫拉迪克方块等)。 - 计算收购价并增加玩家金币(遵守角色背包金币携带上限 \`getInventoryGoldLimit(cLvl)\`)。 - 若该物品在商店对应分页 (10×10) 有空位且非消耗杂物,放入商店对应分页供玩家原价买回。 3. **铁匠修理 (\`D2GAME_NPC_Repair_6FCC95B0\`)**: - 支持单件修理与“全身装备一键修理”(修复已穿戴装备的耐久度与聚气技能充能,跳过无形装备)。 4. **医疗与凯恩鉴定 (\`D2GAME_NPC_Heal_6FCCB220\` & \`D2GAME_NPC_IdentifyAllItems_6FCC9C90\`)**: - 与阿卡拉、法拉、奥玛斯、贾梅拉、马拉交谈时自动回满生命、法力、体力并清除中毒/冰冻/诅咒状态。 - 凯恩支持一键鉴定玩家背包中所有未鉴定物品。 5. **赌博系统 (\`D2GAME_STORES_FillGamble_6FCCA9F0\`)**: - 基德、艾尔吉斯、阿尔科、贾梅拉、安亚/尼拉塞克支持赌博面板,购买前显示未鉴定基底与一口价赌博费,购买入包瞬间按 1.13c 概率揭晓品质(Magic 89.85%、Rare 10%、Set 0.1%、Unique 0.05%)与扩展/精华底材升级。 ## 验收标准 - [ ] 实现 \`src/game/vendor-service.ts\` 并通过全部交易事务单元测试。`, }, { key: 'P5', title: '[M19-P5] 实装原版四分页商人面板 UI 与买卖交互 (Panel\\NPCInv.dc6 + Buy/Sell/Repair/Gamble)', body: `## 背景与目标 打通城镇 NPC 点击菜单、左侧四分页商店面板 (\`Panel\\NPCInv.dc6\`)、右侧玩家背包联动与悬浮价格 Tooltip。 ## 核心实装内容 1. **城镇 NPC 交互弹窗菜单 (\`src/scene/act-scene.ts\` & \`src/ui/world-panels.ts\`)**: - 当玩家走近并点击城镇 NPC 时,除了触发治疗与头顶/面板问候外,弹出原版暗黑风格选项菜单: - 交易型 NPC:\`交谈\` / \`交易\` (或 \`交易/修理\`) / \`赌博\` (若支持) / \`取消\` - 凯恩:\`交谈\` / \`鉴定物品\` / \`取消\` 2. **左侧四分页商店面板 (\`src/ui/world-panels.ts\`)**: - 替换 \`drawLeftDockPanel\` 中的 \`'vendor'\` 占位符,使用 \`vendor-bg.png\` 与 \`buysellbtn.png\` 渲染: - 顶部 4 个分页标签:\`护甲\` / \`武器 I\` / \`武器 II\` / \`杂物\`。 - 中部 10×10 物品网格(每格 29×29 px),绘制商店物品图标、染色、堆叠角标、鼠标悬停高亮。 - 底部功能按钮栏:购买/出售、单件修理锤、全身修理(显示总修理费)、赌博刷新按钮(赌博模式)与关闭按钮。 3. **左右双栏联动与 Tooltip 价格展示 (\`src/ui/hud-manager.ts\` & \`src/ui/inventory.ts\`)**: - 打开左侧 \`vendor\` 面板时自动打开右侧玩家背包 (\`rightOpen.inventory = true\`)。 - 悬停商店物品时:Tooltip 显示原版属性 + **\`购买价格: X 金币\`**(金币不足时标红)。 - 悬停玩家背包/装备物品时:Tooltip 显示 **\`出售价格: X 金币\`**(修理光标激活时显示 **\`修理费用: X 金币\`**)。 - 支持右键快捷购买/出售、Shift+右键批量买满药水/卷轴、左键拖拽买卖。 ## 验收标准 - [ ] 左侧商店面板与右侧背包面板严丝合缝并排显示,所有买卖/修理/赌博操作流畅闭环。`, }, { key: 'P6', title: '[M19-P6] 全量 17 商人 × 3 难度差分测试与浏览器 E2E 闭环审计', body: `## 背景与目标 对 M19 商人 NPC 与交易经济系统进行全谱系自动化验证与浏览器 E2E 闭环审计。 ## 核心验证项 1. **17 商人 × 3 难度 × 多角色等级自动化差分审计**: - 验证 Act 1~5 共 17 位商人 ID 在 \`Normal / Nightmare / Hell\` 与 \`cLvl = 1, 12, 20, 25, 30, 60, 90\` 下的库存生成合法性(无越界等级物品、\`ilvl >= 25\` 零白装、常驻药水/卷轴/箭袋齐全)。 - 验证各类物品(普通、超强、劣质、魔法、亮金、暗金、套装、带 +3 技能 Staffmods 手杖/法杖、无形、职业专属)的售价与收购价严格符合 \`0x6FD9B1C0\` 公式及 \`Npc.txt\` 难度封顶。 2. **Playwright 浏览器端到端验证**: - 启动本地开发服并在无头浏览器中实测点击城镇商人 NPC、打开交互菜单、切换四分页商店、购买药水/装备、出售背包物品、铁匠修理及凯恩鉴定全流程,确保 Console / PageError 为 0。 ## 验收标准 - [ ] 新增单元测试与 E2E 验证全部 100% 绿灯通过。`, }, ] const existingIssues = await giteaFetch(`/issues?state=all&milestones=${milestone.id}&limit=50`) const createdUrls: string[] = [] for (const item of issuesToCreate) { const found = existingIssues.find( i => i.title === item.title || i.title.includes(`-${item.key}]`), ) if (found) { const updated = await giteaFetch(`/issues/${found.number}`, 'PATCH', { title: item.title, body: item.body, }) console.log(`Updated Issue #${updated.number}: ${updated.title} -> ${updated.html_url}`) createdUrls.push(`#${updated.number} ${updated.title}: ${updated.html_url}`) continue } const created = await giteaFetch('/issues', 'POST', { title: item.title, body: item.body, milestone: milestone.id, }) console.log(`Created Issue #${created.number}: ${created.title} -> ${created.html_url}`) createdUrls.push(`#${created.number} ${created.title}: ${created.html_url}`) } console.log('\n=== SUMMARY ===') console.log(`Milestone #${milestone.id}: ${milestone.title} (https://git.projectdiablo2.cn/troytt/diablo2-web/milestone/${milestone.id})`) for (const line of createdUrls) { console.log(`- ${line}`) } } main().catch(err => { console.error(err) process.exit(1) })