polycopy 0.1.1 → 0.1.4

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 CHANGED
@@ -155,7 +155,7 @@ function findPolycopyPids() {
155
155
  return pid;
156
156
  }).filter((pid) => pid !== null);
157
157
  }
158
- function stopDaemon() {
158
+ async function stopDaemon() {
159
159
  const pid = getRunningPid();
160
160
  if (!pid) {
161
161
  const fallbackPids = findPolycopyPids();
@@ -166,39 +166,46 @@ function stopDaemon() {
166
166
  console.log(
167
167
  `⚠️ 未找到 PID 文件,尝试停止前台/后台进程 (${fallbackPids.length} 个)`
168
168
  );
169
- fallbackPids.forEach((fallbackPid) => {
170
- try {
171
- process.kill(fallbackPid, "SIGTERM");
172
- console.log(`✅ 已发送停止信号 (PID: ${fallbackPid})`);
173
- } catch (error) {
174
- console.error(`❌ 停止失败 (PID: ${fallbackPid}):`, error);
175
- }
176
- });
169
+ await Promise.all(
170
+ fallbackPids.map((fallbackPid) => stopPid(fallbackPid))
171
+ );
177
172
  return;
178
173
  }
174
+ await stopPid(pid, true);
175
+ }
176
+ async function stopPid(pid, isPrimary = false) {
179
177
  try {
180
178
  process.kill(pid, "SIGTERM");
181
179
  console.log(`✅ 已发送停止信号 (PID: ${pid})`);
182
- let attempts = 0;
183
- const checkInterval = setInterval(() => {
184
- attempts++;
185
- try {
186
- process.kill(pid, 0);
187
- if (attempts >= 10) {
188
- process.kill(pid, "SIGKILL");
189
- console.log("⚠️ 进程未响应,已强制终止");
190
- clearInterval(checkInterval);
191
- removePid();
192
- }
193
- } catch {
194
- console.log("✅ 进程已停止");
195
- clearInterval(checkInterval);
180
+ } catch (error) {
181
+ console.error(`❌ 停止失败 (PID: ${pid}):`, error);
182
+ if (isPrimary) {
183
+ removePid();
184
+ }
185
+ return;
186
+ }
187
+ const maxAttempts = 20;
188
+ for (let attempts = 0; attempts < maxAttempts; attempts++) {
189
+ await new Promise((resolve) => setTimeout(resolve, 500));
190
+ try {
191
+ process.kill(pid, 0);
192
+ } catch {
193
+ console.log("✅ 进程已停止");
194
+ if (isPrimary) {
196
195
  removePid();
197
196
  }
198
- }, 500);
197
+ return;
198
+ }
199
+ }
200
+ try {
201
+ process.kill(pid, "SIGKILL");
202
+ console.log("⚠️ 进程未响应,已强制终止");
199
203
  } catch (error) {
200
- console.error("❌ 停止失败:", error);
201
- removePid();
204
+ console.error(`❌ 强制终止失败 (PID: ${pid}):`, error);
205
+ } finally {
206
+ if (isPrimary) {
207
+ removePid();
208
+ }
202
209
  }
203
210
  }
204
211
  async function runConfig() {
@@ -272,7 +279,7 @@ async function main() {
272
279
  const command = args[0];
273
280
  switch (command) {
274
281
  case "stop":
275
- stopDaemon();
282
+ await stopDaemon();
276
283
  break;
277
284
  case "log":
278
285
  case "logs":
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-C5cwUXyD.js");
2
+ const init = require("./init-Bs-N_AZi.js");
3
3
  async function main() {
4
4
  try {
5
5
  await init.runConfigWizard();
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-C5cwUXyD.js");
2
+ const init = require("./init-Bs-N_AZi.js");
3
3
  const grammy = require("grammy");
4
4
  const wallet = require("@ethersproject/wallet");
5
5
  const clobClient = require("@polymarket/clob-client");
@@ -32,23 +32,445 @@ function _interopNamespace(e) {
32
32
  }
33
33
  const setPromiseInterval__default = /* @__PURE__ */ _interopDefault(setPromiseInterval);
34
34
  const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
35
- class Notifier {
35
+ function parseSignalMetadata(messageText) {
36
+ const match = messageText.match(/🤖\s*({.*})/s);
37
+ if (!match) {
38
+ return null;
39
+ }
40
+ try {
41
+ const jsonStr = match[1];
42
+ const metadata = JSON.parse(jsonStr);
43
+ if (!metadata.eventId || !metadata.assetId || !metadata.outcome || !metadata.endTime) {
44
+ init.logger.error("解析失败: 缺少必需字段", {
45
+ hasEventId: !!metadata.eventId,
46
+ hasAssetId: !!metadata.assetId,
47
+ hasOutcome: !!metadata.outcome,
48
+ hasEndTime: !!metadata.endTime
49
+ });
50
+ return null;
51
+ }
52
+ if (metadata.outcome !== "Up" && metadata.outcome !== "Down") {
53
+ init.logger.error("解析失败: outcome 值无效", {
54
+ outcome: metadata.outcome,
55
+ expected: "Up 或 Down"
56
+ });
57
+ return null;
58
+ }
59
+ if (typeof metadata.endTime !== "number" || metadata.endTime <= 0) {
60
+ init.logger.error("解析失败: endTime 值无效", {
61
+ endTime: metadata.endTime,
62
+ expected: "毫秒级时间戳"
63
+ });
64
+ return null;
65
+ }
66
+ return metadata;
67
+ } catch (error) {
68
+ init.logger.error("解析信号元数据失败:", error);
69
+ if (error instanceof Error) {
70
+ init.logger.line("", `错误详情: ${error.message}`);
71
+ }
72
+ return null;
73
+ }
74
+ }
75
+ class TelegramListener {
76
+ bot;
77
+ onSignalCallback;
78
+ onChatIdsChangedCallback;
79
+ targetChatIds;
80
+ // 运行时监听的频道 ID 集合(自动管理)
81
+ constructor(bot, targetChatIds = []) {
82
+ this.bot = bot;
83
+ this.targetChatIds = new Set(targetChatIds);
84
+ this.setupHandlers();
85
+ }
86
+ /**
87
+ * 设置消息处理器
88
+ */
89
+ setupHandlers() {
90
+ this.bot.on("channel_post", async (ctx) => {
91
+ await this.handleChannelPost(ctx);
92
+ });
93
+ this.bot.on("edited_channel_post", async (ctx) => {
94
+ await this.handleChannelPost(ctx);
95
+ });
96
+ this.bot.on("my_chat_member", async (ctx) => {
97
+ await this.handleMyChatMemberUpdate(ctx);
98
+ });
99
+ this.bot.catch((err) => {
100
+ init.logger.error("Telegram Bot 错误:", err);
101
+ if (err.error) {
102
+ init.logger.line("", `错误详情: ${err.error}`);
103
+ }
104
+ if (err.ctx) {
105
+ init.logger.line("", `上下文信息:`, {
106
+ chatId: err.ctx.chat?.id,
107
+ messageId: err.ctx.message?.message_id
108
+ });
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * 处理 Bot 自己的成员状态变更
114
+ */
115
+ async handleMyChatMemberUpdate(ctx) {
116
+ const update = ctx.myChatMember;
117
+ if (!update) {
118
+ return;
119
+ }
120
+ const chatId = update.chat.id;
121
+ const chatTitle = update.chat.title || "未知频道";
122
+ const oldStatus = update.old_chat_member.status;
123
+ const newStatus = update.new_chat_member.status;
124
+ const chatType = update.chat.type;
125
+ if (chatType !== "channel") {
126
+ return;
127
+ }
128
+ const isAdmin = newStatus === "administrator" || newStatus === "creator";
129
+ const wasAdmin = oldStatus === "administrator" || oldStatus === "creator";
130
+ const isRemoved = newStatus === "left" || newStatus === "kicked";
131
+ const wasRemoved = oldStatus === "left" || oldStatus === "kicked";
132
+ if (oldStatus !== newStatus) {
133
+ let action = "";
134
+ let canListen = false;
135
+ let reason = "";
136
+ if (isRemoved) {
137
+ action = "❌ 频道主已移除 Bot";
138
+ canListen = false;
139
+ reason = "Bot 已被移除";
140
+ } else if (wasRemoved && isAdmin) {
141
+ action = "✅ 频道主已添加 Bot 为管理员";
142
+ canListen = true;
143
+ } else if (isAdmin && !wasAdmin) {
144
+ action = "✅ 频道主已授予 Bot 管理员权限";
145
+ canListen = true;
146
+ } else if (wasAdmin && !isAdmin) {
147
+ action = "⚠️ 频道主已移除 Bot 的管理员权限";
148
+ canListen = false;
149
+ reason = "Bot 不再是管理员";
150
+ } else if (wasRemoved && !isAdmin) {
151
+ action = "⚠️ 频道主已添加 Bot,但未授予管理员权限";
152
+ canListen = false;
153
+ reason = "需要管理员权限才能监听";
154
+ } else if (oldStatus === "restricted" && isAdmin) {
155
+ action = "✅ 频道主已授予 Bot 管理员权限";
156
+ canListen = true;
157
+ } else if (wasAdmin && newStatus === "restricted") {
158
+ action = "⚠️ 频道主已限制 Bot 的权限";
159
+ canListen = false;
160
+ reason = "Bot 权限受限,无法监听";
161
+ } else if (oldStatus === "restricted" && !isAdmin) {
162
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
163
+ canListen = false;
164
+ reason = "需要管理员权限才能监听";
165
+ } else {
166
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
167
+ canListen = isAdmin;
168
+ if (!canListen) {
169
+ reason = "需要管理员权限才能监听";
170
+ }
171
+ }
172
+ init.logger.info(`📢 频道状态更新: ${chatTitle}`);
173
+ init.logger.line("", action);
174
+ init.logger.line(
175
+ "",
176
+ `当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`
177
+ );
178
+ if (reason) {
179
+ init.logger.line("", `原因: ${reason}`);
180
+ }
181
+ }
182
+ if (isAdmin && !this.targetChatIds.has(chatId)) {
183
+ this.targetChatIds.add(chatId);
184
+ await this.saveChatIds();
185
+ init.logger.line("📡", "已自动开始监听此频道");
186
+ }
187
+ if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
188
+ this.targetChatIds.delete(chatId);
189
+ await this.saveChatIds();
190
+ if (isRemoved) {
191
+ init.logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
192
+ } else {
193
+ init.logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
194
+ }
195
+ }
196
+ }
197
+ /**
198
+ * 保存频道 ID 列表到配置(用于持久化)
199
+ */
200
+ async saveChatIds() {
201
+ if (!this.onChatIdsChangedCallback) {
202
+ return;
203
+ }
204
+ try {
205
+ const chatIds = Array.from(this.targetChatIds);
206
+ await this.onChatIdsChangedCallback(chatIds);
207
+ } catch (error) {
208
+ init.logger.error("保存频道 ID 列表时出错:", error);
209
+ }
210
+ }
211
+ /**
212
+ * 处理频道消息(channel_post 和 edited_channel_post)
213
+ */
214
+ async handleChannelPost(ctx) {
215
+ const message = ctx.channelPost || ctx.editedChannelPost;
216
+ if (!message) {
217
+ return;
218
+ }
219
+ await this.processMessage(message, ctx);
220
+ }
221
+ /**
222
+ * 处理频道消息(统一的消息处理逻辑)
223
+ */
224
+ async processMessage(message, ctx) {
225
+ if (!message) {
226
+ return;
227
+ }
228
+ const chatId = message.chat.id;
229
+ const chatTitle = message.chat.title || "未知频道";
230
+ const isChannel = chatId < 0 && chatId.toString().startsWith("-100");
231
+ const messageText = message.text || message.caption || "";
232
+ if (!messageText) {
233
+ return;
234
+ }
235
+ if (isChannel && !this.targetChatIds.has(chatId)) {
236
+ this.targetChatIds.add(chatId);
237
+ init.logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
238
+ await this.saveChatIds();
239
+ init.logger.line("💾", "已保存到配置文件");
240
+ }
241
+ if (!isChannel) {
242
+ return;
243
+ }
244
+ if (!this.targetChatIds.has(chatId)) {
245
+ return;
246
+ }
247
+ const metadata = parseSignalMetadata(messageText);
248
+ if (!metadata) {
249
+ if (messageText.includes("🤖")) {
250
+ const emojiIndex = messageText.indexOf("🤖");
251
+ const jsonStart = emojiIndex + 1;
252
+ const jsonEnd = Math.min(jsonStart + 200, messageText.length);
253
+ init.logger.warning(`解析预测消息失败:`);
254
+ init.logger.line("", `频道 ID: ${chatId}`);
255
+ init.logger.line(
256
+ "",
257
+ `消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`
258
+ );
259
+ init.logger.line("", "提示: 请检查消息格式是否正确");
260
+ }
261
+ return;
262
+ }
263
+ const parsedSignal = {
264
+ metadata,
265
+ rawMessage: messageText,
266
+ messageId: message.message_id,
267
+ chatId,
268
+ timestamp: message.date * 1e3
269
+ };
270
+ if (this.onSignalCallback) {
271
+ try {
272
+ await this.onSignalCallback(parsedSignal);
273
+ } catch (error) {
274
+ init.logger.error("处理信号时出错:", error);
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * 设置信号回调函数
280
+ */
281
+ onSignal(callback) {
282
+ this.onSignalCallback = callback;
283
+ }
284
+ /**
285
+ * 设置频道 ID 列表变更回调函数(当频道自动添加/移除时调用,用于持久化)
286
+ */
287
+ onChatIdsChanged(callback) {
288
+ this.onChatIdsChangedCallback = callback;
289
+ }
290
+ /**
291
+ * 设置监听的频道 ID 列表(用于启动时加载已保存的配置)
292
+ */
293
+ setTargetChatIds(chatIds) {
294
+ this.targetChatIds = new Set(chatIds);
295
+ }
296
+ /**
297
+ * 启动监听
298
+ * @param onReady 可选回调,在频道检查完成后、开始监听之前调用
299
+ */
300
+ async start(onReady) {
301
+ try {
302
+ init.logger.info("正在启动 Bot...");
303
+ const botInfo = await this.bot.api.getMe();
304
+ const botId = botInfo.id;
305
+ init.logger.info(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
306
+ await this.checkAndDisplayChannels(botId);
307
+ if (onReady) {
308
+ await onReady();
309
+ }
310
+ init.logger.info("开始监听预测消息...");
311
+ await this.bot.start({
312
+ drop_pending_updates: true,
313
+ allowed_updates: [
314
+ "channel_post",
315
+ "edited_channel_post",
316
+ "my_chat_member"
317
+ ]
318
+ });
319
+ } catch (error) {
320
+ init.logger.error("启动 Bot 失败:", error);
321
+ throw error;
322
+ }
323
+ }
324
+ /**
325
+ * 检查并显示 Bot 所在的频道
326
+ * 验证配置文件中保存的频道,检查 Bot 是否仍在频道中以及是否是管理员
327
+ */
328
+ async checkAndDisplayChannels(botId) {
329
+ const chatIds = Array.from(this.targetChatIds);
330
+ if (chatIds.length === 0) {
331
+ init.logger.info(`📋 当前没有保存的频道配置`);
332
+ init.logger.line(
333
+ "",
334
+ "提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表"
335
+ );
336
+ return;
337
+ }
338
+ init.logger.info(`📋 检查 Bot 所在的频道:`);
339
+ const validChannels = [];
340
+ const invalidChannels = [];
341
+ for (const chatId of chatIds) {
342
+ try {
343
+ const chat = await this.bot.api.getChat(chatId);
344
+ const chatTitle = chat.title || "未知频道";
345
+ try {
346
+ const member = await this.bot.api.getChatMember(chatId, botId);
347
+ const isAdmin = member.status === "administrator" || member.status === "creator";
348
+ validChannels.push({
349
+ id: chatId,
350
+ title: chatTitle,
351
+ isAdmin
352
+ });
353
+ } catch (error) {
354
+ invalidChannels.push({
355
+ id: chatId,
356
+ reason: "无法获取成员信息(可能已被移除)"
357
+ });
358
+ }
359
+ } catch (error) {
360
+ invalidChannels.push({
361
+ id: chatId,
362
+ reason: "无法获取频道信息(可能已被移除)"
363
+ });
364
+ }
365
+ }
366
+ if (validChannels.length > 0) {
367
+ const adminChannels = validChannels.filter((c) => c.isAdmin);
368
+ const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
369
+ if (adminChannels.length > 0) {
370
+ init.logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
371
+ adminChannels.forEach((channel, index) => {
372
+ init.logger.item(
373
+ `${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`,
374
+ 1
375
+ );
376
+ });
377
+ }
378
+ if (nonAdminChannels.length > 0) {
379
+ init.logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
380
+ nonAdminChannels.forEach((channel, index) => {
381
+ init.logger.item(`${index + 1}. ${channel.title}`, 1);
382
+ init.logger.item(`ID: ${channel.id}`, 2);
383
+ init.logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
384
+ init.logger.item(`原因: 需要管理员权限才能监听`, 2);
385
+ });
386
+ nonAdminChannels.forEach((channel) => {
387
+ this.targetChatIds.delete(channel.id);
388
+ });
389
+ if (nonAdminChannels.length > 0) {
390
+ await this.saveChatIds();
391
+ init.logger.line("💾", "已清理无法监听的频道并保存配置");
392
+ }
393
+ }
394
+ }
395
+ if (invalidChannels.length > 0) {
396
+ init.logger.error(`无效频道 (${invalidChannels.length} 个):`);
397
+ invalidChannels.forEach((channel, index) => {
398
+ init.logger.item(`${index + 1}. ID: ${channel.id}`, 1);
399
+ init.logger.item(`原因: ${channel.reason}`, 2);
400
+ });
401
+ invalidChannels.forEach((channel) => {
402
+ this.targetChatIds.delete(channel.id);
403
+ });
404
+ if (invalidChannels.length > 0) {
405
+ await this.saveChatIds();
406
+ init.logger.line("💾", "已清理无效频道并保存配置");
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * 停止监听
412
+ */
413
+ async stop() {
414
+ await this.bot.stop();
415
+ }
416
+ /**
417
+ * 获取 bot 信息(用于验证 token 是否有效)
418
+ */
419
+ async getBotInfo() {
420
+ try {
421
+ return this.bot.api.getMe();
422
+ } catch (error) {
423
+ throw new Error(`获取 Bot 信息失败: ${error}`);
424
+ }
425
+ }
426
+ }
427
+ class TelegramService {
36
428
  bot = null;
429
+ listener = null;
37
430
  adminChatId = null;
38
- initialized = false;
431
+ notifyEnabled = false;
39
432
  /**
40
- * 初始化通知器
433
+ * 初始化 Telegram 服务
41
434
  */
42
- init(botToken, adminChatId) {
435
+ init(botToken, adminChatId, targetChatIds = []) {
436
+ if (this.listener) {
437
+ return;
438
+ }
439
+ this.bot = new grammy.Bot(botToken);
440
+ this.listener = new TelegramListener(this.bot, targetChatIds);
43
441
  if (!adminChatId) {
44
442
  init.logger.warning("未配置管理员 Chat ID,通知功能已禁用");
443
+ this.notifyEnabled = false;
45
444
  return;
46
445
  }
47
- this.bot = new grammy.Bot(botToken);
48
446
  this.adminChatId = adminChatId;
49
- this.initialized = true;
447
+ this.notifyEnabled = true;
50
448
  init.logger.info("管理员通知已启用");
51
449
  }
450
+ /**
451
+ * 设置频道 ID 列表变更回调
452
+ */
453
+ onChatIdsChanged(callback) {
454
+ this.ensureListener().onChatIdsChanged(callback);
455
+ }
456
+ /**
457
+ * 设置预测信号回调
458
+ */
459
+ onSignal(callback) {
460
+ this.ensureListener().onSignal(callback);
461
+ }
462
+ /**
463
+ * 启动监听
464
+ */
465
+ async start(onReady) {
466
+ await this.ensureListener().start(onReady);
467
+ }
468
+ /**
469
+ * 停止监听
470
+ */
471
+ async stop() {
472
+ await this.ensureListener().stop();
473
+ }
52
474
  /**
53
475
  * 发送错误通知
54
476
  */
@@ -67,11 +489,17 @@ class Notifier {
67
489
  async success(message, details) {
68
490
  await this.send(`🟢 ${message}`, details);
69
491
  }
492
+ ensureListener() {
493
+ if (!this.listener) {
494
+ throw new Error("TelegramService 未初始化");
495
+ }
496
+ return this.listener;
497
+ }
70
498
  /**
71
499
  * 发送消息给管理员
72
500
  */
73
501
  async send(message, details) {
74
- if (!this.initialized || !this.bot || !this.adminChatId) {
502
+ if (!this.notifyEnabled || !this.bot || !this.adminChatId) {
75
503
  return;
76
504
  }
77
505
  const fullMessage = details ? `${message}
@@ -87,7 +515,7 @@ ${details}` : message;
87
515
  }
88
516
  }
89
517
  }
90
- const notifier = new Notifier();
518
+ const telegramService = new TelegramService();
91
519
  const CLOB_HOST = "https://clob.polymarket.com";
92
520
  class ClobClientWrapper {
93
521
  client = null;
@@ -658,7 +1086,10 @@ class Redeemer {
658
1086
  if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
659
1087
  const resetSeconds = this.parseResetSeconds(errorMsg);
660
1088
  init.logger.warning(`获取仓位限流,${resetSeconds} 秒后重试`);
661
- notifier.warning("API 限流", `获取仓位限流,${resetSeconds} 秒后重试`);
1089
+ telegramService.warning(
1090
+ "API 限流",
1091
+ `获取仓位限流,${resetSeconds} 秒后重试`
1092
+ );
662
1093
  this.pauseAndRestart((resetSeconds + 60) * 1e3);
663
1094
  return;
664
1095
  }
@@ -693,7 +1124,7 @@ class Redeemer {
693
1124
  init.logger.error(
694
1125
  `Redeem 失败: 执行成功但仍存在 ${record.matchedCount} 次 (conditionId: ${position.conditionId.slice(0, 10)}...)`
695
1126
  );
696
- notifier.error(
1127
+ telegramService.error(
697
1128
  "Redeem 失败",
698
1129
  `执行成功但仓位仍存在 ${record.matchedCount} 次
699
1130
  conditionId: ${position.conditionId}
@@ -710,7 +1141,7 @@ conditionId: ${position.conditionId}
710
1141
  if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
711
1142
  const resetSeconds = this.parseResetSeconds(errorMsg);
712
1143
  init.logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
713
- notifier.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
1144
+ telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
714
1145
  this.pauseAndRestart((resetSeconds + 60) * 1e3);
715
1146
  return;
716
1147
  }
@@ -719,7 +1150,7 @@ conditionId: ${position.conditionId}
719
1150
  `Redeem 异常 (第 ${record.failedCount} 次): ${errorMsg}`
720
1151
  );
721
1152
  if (record.failedCount >= MAX_MATCH_TIMES) {
722
- notifier.error(
1153
+ telegramService.error(
723
1154
  "Redeem 异常",
724
1155
  `重试 ${record.failedCount} 次仍失败
725
1156
  conditionId: ${position.conditionId}
@@ -743,7 +1174,7 @@ conditionId: ${position.conditionId}
743
1174
  if (successItems.length > 0) {
744
1175
  const successTitle = `自动 redeem: 成功 ${successItems.length} 个`;
745
1176
  const successLines = this.logPositions(successTitle, successItems);
746
- notifier.success(successTitle, successLines.join("\n"));
1177
+ telegramService.success(successTitle, successLines.join("\n"));
747
1178
  this.onRedeemSuccess?.();
748
1179
  }
749
1180
  init.logger.info(`redeem 状态: ${this.records.size} 个待确认`);
@@ -881,7 +1312,7 @@ class Trader {
881
1312
  if (amountMode === "fixed" && balance < amountValue) {
882
1313
  const msg = `余额不足: ${balance} USDC < ${amountValue} USDC`;
883
1314
  init.logger.warning(msg);
884
- notifier.warning(
1315
+ telegramService.warning(
885
1316
  "余额不足",
886
1317
  `当前余额: ${balance} USDC
887
1318
  需要金额: ${amountValue} USDC`
@@ -890,7 +1321,7 @@ class Trader {
890
1321
  }
891
1322
  if (amountMode === "percentage" && balance <= 0) {
892
1323
  init.logger.warning(`余额为零: ${balance} USDC`);
893
- notifier.warning("余额为零", `当前余额: ${balance} USDC`);
1324
+ telegramService.warning("余额为零", `当前余额: ${balance} USDC`);
894
1325
  return;
895
1326
  }
896
1327
  const orderAmount = amountMode === "fixed" ? amountValue : balance * amountValue;
@@ -901,7 +1332,7 @@ class Trader {
901
1332
  const buyResult = await this.client.createBuyOrder(assetId, buyPrice, size);
902
1333
  if (!buyResult.success) {
903
1334
  init.logger.error(`买单失败: ${buyResult.errorMsg}`);
904
- notifier.error(
1335
+ telegramService.error(
905
1336
  "买单失败",
906
1337
  `事件: ${metadata.eventId}
907
1338
  错误: ${buyResult.errorMsg}`
@@ -941,7 +1372,7 @@ class Trader {
941
1372
  } catch (error) {
942
1373
  const errorMsg = error instanceof Error ? error.message : String(error);
943
1374
  init.logger.error(`订单监控异常: ${errorMsg}`);
944
- notifier.error(
1375
+ telegramService.error(
945
1376
  "订单监控异常",
946
1377
  `事件: ${eventId}
947
1378
  订单: ${orderId}
@@ -969,11 +1400,11 @@ class Trader {
969
1400
  break;
970
1401
  case "FAILED":
971
1402
  init.logger.error("订单执行失败");
972
- notifier.error("订单执行失败", `订单 ID: ${orderId}`);
1403
+ telegramService.error("订单执行失败", `订单 ID: ${orderId}`);
973
1404
  break;
974
1405
  case "TIMEOUT":
975
1406
  init.logger.warning("订单超时,尝试取消");
976
- notifier.warning("订单超时", `订单 ID: ${orderId}
1407
+ telegramService.warning("订单超时", `订单 ID: ${orderId}
977
1408
  已尝试取消`);
978
1409
  await this.client.cancelOrder(orderId);
979
1410
  break;
@@ -1016,7 +1447,7 @@ class Trader {
1016
1447
  if (!this.relayClient.isInitialized()) {
1017
1448
  const msg = "仓位同步超时且 Redeem 不可用";
1018
1449
  init.logger.warning(msg);
1019
- notifier.warning(
1450
+ telegramService.warning(
1020
1451
  msg,
1021
1452
  `Asset: ${assetId.substring(0, 20)}...
1022
1453
  预期数量: ${expectedSize.toFixed(4)}
@@ -1040,7 +1471,7 @@ class Trader {
1040
1471
  init.logger.success(`卖单已提交: ${sellResult.orderId}`);
1041
1472
  } else {
1042
1473
  init.logger.error(`卖单提交失败: ${sellResult.errorMsg}`);
1043
- notifier.error("卖单提交失败", `错误: ${sellResult.errorMsg}`);
1474
+ telegramService.error("卖单提交失败", `错误: ${sellResult.errorMsg}`);
1044
1475
  }
1045
1476
  }
1046
1477
  /**
@@ -1092,7 +1523,11 @@ async function main() {
1092
1523
  init.logger.init();
1093
1524
  init.logger.section("PolyMarket 跟单机器人");
1094
1525
  const config = await init.ensureConfig();
1095
- notifier.init(config.telegram.botToken, config.telegram.adminChatId);
1526
+ telegramService.init(
1527
+ config.telegram.botToken,
1528
+ config.telegram.adminChatId,
1529
+ config.telegram.targetChatIds || []
1530
+ );
1096
1531
  const autoTradeEnabled = init.canAutoTrade(config);
1097
1532
  if (!autoTradeEnabled) {
1098
1533
  init.logger.warning("自动跟单功能未启用");
@@ -1122,11 +1557,7 @@ async function main() {
1122
1557
  init.logger.line("", "当前不会跟单任何预测消息");
1123
1558
  }
1124
1559
  }
1125
- const listener = new init.TelegramListener(
1126
- config.telegram.botToken,
1127
- config.telegram.targetChatIds || []
1128
- );
1129
- listener.onChatIdsChanged((chatIds) => {
1560
+ telegramService.onChatIdsChanged((chatIds) => {
1130
1561
  const telegram = init.configLocal.getItem("telegram");
1131
1562
  if (!telegram) {
1132
1563
  init.logger.error("警告: 无法保存频道 ID,telegram 配置不存在");
@@ -1136,7 +1567,7 @@ async function main() {
1136
1567
  init.configLocal.setItem("telegram", telegram);
1137
1568
  init.logger.line("💾", `已保存频道 ID 列表: ${chatIds.length} 个频道`);
1138
1569
  });
1139
- listener.onSignal(async (signal) => {
1570
+ telegramService.onSignal(async (signal) => {
1140
1571
  init.logger.info(`📨 收到预测信号:`);
1141
1572
  init.logger.line("", `事件: ${signal.metadata.eventId}`);
1142
1573
  init.logger.line(
@@ -1161,29 +1592,25 @@ async function main() {
1161
1592
  enterDaemonMode();
1162
1593
  return;
1163
1594
  }
1164
- const runMode = process.env.POLYCOPY_DAEMON_CHILD === "1" ? "后台模式" : "前台模式";
1165
- notifier.success("程序已启动", `模式: ${runMode}
1166
- PID: ${process.pid}`);
1167
1595
  let isShuttingDown = false;
1168
- const shutdown = () => {
1596
+ const shutdown = async () => {
1169
1597
  if (isShuttingDown) return;
1170
1598
  isShuttingDown = true;
1171
1599
  process.removeAllListeners("SIGINT");
1172
1600
  process.removeAllListeners("SIGTERM");
1173
1601
  init.logger.blank();
1174
1602
  init.logger.info("正在退出...");
1175
- notifier.warning("程序已停止", `模式: ${runMode}
1176
- PID: ${process.pid}`);
1177
- setTimeout(() => {
1178
- process.exit(0);
1179
- }, 1e3);
1180
- Promise.all([trader.shutdown(), listener.stop()]).finally(() => {
1181
- process.exit(0);
1182
- });
1603
+ await Promise.all([trader.shutdown(), telegramService.stop()]);
1604
+ await init.logger.flush();
1605
+ process.exit(0);
1183
1606
  };
1184
- process.on("SIGINT", shutdown);
1185
- process.on("SIGTERM", shutdown);
1186
- await listener.start(() => {
1607
+ process.prependListener("SIGINT", () => {
1608
+ shutdown();
1609
+ });
1610
+ process.prependListener("SIGTERM", () => {
1611
+ shutdown();
1612
+ });
1613
+ await telegramService.start(() => {
1187
1614
  if (trader.isInitialized()) {
1188
1615
  trader.startRedeemer();
1189
1616
  }
@@ -98,6 +98,7 @@ const winstonLogger = winston__default.default.createLogger({
98
98
  });
99
99
  class Logger {
100
100
  initialized = false;
101
+ flushing = false;
101
102
  /**
102
103
  * 初始化日志模块(输出日志路径信息)
103
104
  */
@@ -190,400 +191,27 @@ ${this.withTimestamp(`❌ ${formatted}`)}`);
190
191
  console.log(this.withTimestamp(formatted));
191
192
  winstonLogger.info(formatted);
192
193
  }
193
- }
194
- const logger = new Logger();
195
- function parseSignalMetadata(messageText) {
196
- const match = messageText.match(/🤖\s*({.*})/s);
197
- if (!match) {
198
- return null;
199
- }
200
- try {
201
- const jsonStr = match[1];
202
- const metadata = JSON.parse(jsonStr);
203
- if (!metadata.eventId || !metadata.assetId || !metadata.outcome || !metadata.endTime) {
204
- logger.error("解析失败: 缺少必需字段", {
205
- hasEventId: !!metadata.eventId,
206
- hasAssetId: !!metadata.assetId,
207
- hasOutcome: !!metadata.outcome,
208
- hasEndTime: !!metadata.endTime
209
- });
210
- return null;
211
- }
212
- if (metadata.outcome !== "Up" && metadata.outcome !== "Down") {
213
- logger.error("解析失败: outcome 值无效", {
214
- outcome: metadata.outcome,
215
- expected: "Up 或 Down"
216
- });
217
- return null;
218
- }
219
- if (typeof metadata.endTime !== "number" || metadata.endTime <= 0) {
220
- logger.error("解析失败: endTime 值无效", {
221
- endTime: metadata.endTime,
222
- expected: "毫秒级时间戳"
223
- });
224
- return null;
225
- }
226
- return metadata;
227
- } catch (error) {
228
- logger.error("解析信号元数据失败:", error);
229
- if (error instanceof Error) {
230
- logger.line("", `错误详情: ${error.message}`);
231
- }
232
- return null;
233
- }
234
- }
235
- class TelegramListener {
236
- bot;
237
- onSignalCallback;
238
- onChatIdsChangedCallback;
239
- targetChatIds;
240
- // 运行时监听的频道 ID 集合(自动管理)
241
- constructor(botToken, targetChatIds = []) {
242
- this.bot = new grammy.Bot(botToken);
243
- this.targetChatIds = new Set(targetChatIds);
244
- this.setupHandlers();
245
- }
246
- /**
247
- * 设置消息处理器
248
- */
249
- setupHandlers() {
250
- this.bot.on("channel_post", async (ctx) => {
251
- await this.handleChannelPost(ctx);
252
- });
253
- this.bot.on("edited_channel_post", async (ctx) => {
254
- await this.handleChannelPost(ctx);
255
- });
256
- this.bot.on("my_chat_member", async (ctx) => {
257
- await this.handleMyChatMemberUpdate(ctx);
258
- });
259
- this.bot.catch((err) => {
260
- logger.error("Telegram Bot 错误:", err);
261
- if (err.error) {
262
- logger.line("", `错误详情: ${err.error}`);
263
- }
264
- if (err.ctx) {
265
- logger.line("", `上下文信息:`, {
266
- chatId: err.ctx.chat?.id,
267
- messageId: err.ctx.message?.message_id
268
- });
269
- }
270
- });
271
- }
272
- /**
273
- * 处理 Bot 自己的成员状态变更
274
- */
275
- async handleMyChatMemberUpdate(ctx) {
276
- const update = ctx.myChatMember;
277
- if (!update) {
278
- return;
279
- }
280
- const chatId = update.chat.id;
281
- const chatTitle = update.chat.title || "未知频道";
282
- const oldStatus = update.old_chat_member.status;
283
- const newStatus = update.new_chat_member.status;
284
- const chatType = update.chat.type;
285
- if (chatType !== "channel") {
286
- return;
287
- }
288
- const isAdmin = newStatus === "administrator" || newStatus === "creator";
289
- const wasAdmin = oldStatus === "administrator" || oldStatus === "creator";
290
- const isRemoved = newStatus === "left" || newStatus === "kicked";
291
- const wasRemoved = oldStatus === "left" || oldStatus === "kicked";
292
- if (oldStatus !== newStatus) {
293
- let action = "";
294
- let canListen = false;
295
- let reason = "";
296
- if (isRemoved) {
297
- action = "❌ 频道主已移除 Bot";
298
- canListen = false;
299
- reason = "Bot 已被移除";
300
- } else if (wasRemoved && isAdmin) {
301
- action = "✅ 频道主已添加 Bot 为管理员";
302
- canListen = true;
303
- } else if (isAdmin && !wasAdmin) {
304
- action = "✅ 频道主已授予 Bot 管理员权限";
305
- canListen = true;
306
- } else if (wasAdmin && !isAdmin) {
307
- action = "⚠️ 频道主已移除 Bot 的管理员权限";
308
- canListen = false;
309
- reason = "Bot 不再是管理员";
310
- } else if (wasRemoved && !isAdmin) {
311
- action = "⚠️ 频道主已添加 Bot,但未授予管理员权限";
312
- canListen = false;
313
- reason = "需要管理员权限才能监听";
314
- } else if (oldStatus === "restricted" && isAdmin) {
315
- action = "✅ 频道主已授予 Bot 管理员权限";
316
- canListen = true;
317
- } else if (wasAdmin && newStatus === "restricted") {
318
- action = "⚠️ 频道主已限制 Bot 的权限";
319
- canListen = false;
320
- reason = "Bot 权限受限,无法监听";
321
- } else if (oldStatus === "restricted" && !isAdmin) {
322
- action = `状态变更: ${oldStatus} → ${newStatus}`;
323
- canListen = false;
324
- reason = "需要管理员权限才能监听";
325
- } else {
326
- action = `状态变更: ${oldStatus} → ${newStatus}`;
327
- canListen = isAdmin;
328
- if (!canListen) {
329
- reason = "需要管理员权限才能监听";
330
- }
331
- }
332
- logger.info(`📢 频道状态更新: ${chatTitle}`);
333
- logger.line("", action);
334
- logger.line(
335
- "",
336
- `当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`
337
- );
338
- if (reason) {
339
- logger.line("", `原因: ${reason}`);
340
- }
341
- }
342
- if (isAdmin && !this.targetChatIds.has(chatId)) {
343
- this.targetChatIds.add(chatId);
344
- await this.saveChatIds();
345
- logger.line("📡", "已自动开始监听此频道");
346
- }
347
- if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
348
- this.targetChatIds.delete(chatId);
349
- await this.saveChatIds();
350
- if (isRemoved) {
351
- logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
352
- } else {
353
- logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
354
- }
355
- }
356
- }
357
- /**
358
- * 保存频道 ID 列表到配置(用于持久化)
359
- */
360
- async saveChatIds() {
361
- if (!this.onChatIdsChangedCallback) {
362
- return;
363
- }
364
- try {
365
- const chatIds = Array.from(this.targetChatIds);
366
- await this.onChatIdsChangedCallback(chatIds);
367
- } catch (error) {
368
- logger.error("保存频道 ID 列表时出错:", error);
369
- }
370
- }
371
- /**
372
- * 处理频道消息(channel_post 和 edited_channel_post)
373
- */
374
- async handleChannelPost(ctx) {
375
- const message = ctx.channelPost || ctx.editedChannelPost;
376
- if (!message) {
377
- return;
378
- }
379
- await this.processMessage(message, ctx);
380
- }
381
- /**
382
- * 处理频道消息(统一的消息处理逻辑)
383
- */
384
- async processMessage(message, ctx) {
385
- if (!message) {
386
- return;
387
- }
388
- const chatId = message.chat.id;
389
- const chatTitle = message.chat.title || "未知频道";
390
- const isChannel = chatId < 0 && chatId.toString().startsWith("-100");
391
- const messageText = message.text || message.caption || "";
392
- if (!messageText) {
393
- return;
394
- }
395
- if (isChannel && !this.targetChatIds.has(chatId)) {
396
- this.targetChatIds.add(chatId);
397
- logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
398
- await this.saveChatIds();
399
- logger.line("💾", "已保存到配置文件");
400
- }
401
- if (!isChannel) {
402
- return;
403
- }
404
- if (!this.targetChatIds.has(chatId)) {
405
- return;
406
- }
407
- const metadata = parseSignalMetadata(messageText);
408
- if (!metadata) {
409
- if (messageText.includes("🤖")) {
410
- const emojiIndex = messageText.indexOf("🤖");
411
- const jsonStart = emojiIndex + 1;
412
- const jsonEnd = Math.min(jsonStart + 200, messageText.length);
413
- logger.warning(`解析预测消息失败:`);
414
- logger.line("", `频道 ID: ${chatId}`);
415
- logger.line(
416
- "",
417
- `消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`
418
- );
419
- logger.line("", "提示: 请检查消息格式是否正确");
420
- }
421
- return;
422
- }
423
- const parsedSignal = {
424
- metadata,
425
- rawMessage: messageText,
426
- messageId: message.message_id,
427
- chatId,
428
- timestamp: message.date * 1e3
429
- };
430
- if (this.onSignalCallback) {
431
- try {
432
- await this.onSignalCallback(parsedSignal);
433
- } catch (error) {
434
- logger.error("处理信号时出错:", error);
435
- }
436
- }
437
- }
438
194
  /**
439
- * 设置信号回调函数
195
+ * 刷新日志输出(用于进程退出前)
440
196
  */
441
- onSignal(callback) {
442
- this.onSignalCallback = callback;
443
- }
444
- /**
445
- * 设置频道 ID 列表变更回调函数(当频道自动添加/移除时调用,用于持久化)
446
- */
447
- onChatIdsChanged(callback) {
448
- this.onChatIdsChangedCallback = callback;
449
- }
450
- /**
451
- * 设置监听的频道 ID 列表(用于启动时加载已保存的配置)
452
- */
453
- setTargetChatIds(chatIds) {
454
- this.targetChatIds = new Set(chatIds);
455
- }
456
- /**
457
- * 启动监听
458
- * @param onReady 可选回调,在频道检查完成后、开始监听之前调用
459
- */
460
- async start(onReady) {
461
- try {
462
- logger.info("正在启动 Bot...");
463
- const botInfo = await this.bot.api.getMe();
464
- const botId = botInfo.id;
465
- logger.info(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
466
- await this.checkAndDisplayChannels(botId);
467
- if (onReady) {
468
- await onReady();
469
- }
470
- logger.info("开始监听预测消息...");
471
- await this.bot.start({
472
- drop_pending_updates: true,
473
- allowed_updates: [
474
- "channel_post",
475
- "edited_channel_post",
476
- "my_chat_member"
477
- ]
478
- });
479
- } catch (error) {
480
- logger.error("启动 Bot 失败:", error);
481
- throw error;
482
- }
483
- }
484
- /**
485
- * 检查并显示 Bot 所在的频道
486
- * 验证配置文件中保存的频道,检查 Bot 是否仍在频道中以及是否是管理员
487
- */
488
- async checkAndDisplayChannels(botId) {
489
- const chatIds = Array.from(this.targetChatIds);
490
- if (chatIds.length === 0) {
491
- logger.info(`📋 当前没有保存的频道配置`);
492
- logger.line(
493
- "",
494
- "提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表"
495
- );
197
+ async flush(timeoutMs = 1e3) {
198
+ if (this.flushing) {
496
199
  return;
497
200
  }
498
- logger.info(`📋 检查 Bot 所在的频道:`);
499
- const validChannels = [];
500
- const invalidChannels = [];
501
- for (const chatId of chatIds) {
502
- try {
503
- const chat = await this.bot.api.getChat(chatId);
504
- const chatTitle = chat.title || "未知频道";
505
- try {
506
- const member = await this.bot.api.getChatMember(chatId, botId);
507
- const isAdmin = member.status === "administrator" || member.status === "creator";
508
- validChannels.push({
509
- id: chatId,
510
- title: chatTitle,
511
- isAdmin
512
- });
513
- } catch (error) {
514
- invalidChannels.push({
515
- id: chatId,
516
- reason: "无法获取成员信息(可能已被移除)"
517
- });
518
- }
519
- } catch (error) {
520
- invalidChannels.push({
521
- id: chatId,
522
- reason: "无法获取频道信息(可能已被移除)"
523
- });
524
- }
525
- }
526
- if (validChannels.length > 0) {
527
- const adminChannels = validChannels.filter((c) => c.isAdmin);
528
- const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
529
- if (adminChannels.length > 0) {
530
- logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
531
- adminChannels.forEach((channel, index) => {
532
- logger.item(
533
- `${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`,
534
- 1
535
- );
536
- });
537
- }
538
- if (nonAdminChannels.length > 0) {
539
- logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
540
- nonAdminChannels.forEach((channel, index) => {
541
- logger.item(`${index + 1}. ${channel.title}`, 1);
542
- logger.item(`ID: ${channel.id}`, 2);
543
- logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
544
- logger.item(`原因: 需要管理员权限才能监听`, 2);
545
- });
546
- nonAdminChannels.forEach((channel) => {
547
- this.targetChatIds.delete(channel.id);
548
- });
549
- if (nonAdminChannels.length > 0) {
550
- await this.saveChatIds();
551
- logger.line("💾", "已清理无法监听的频道并保存配置");
552
- }
553
- }
554
- }
555
- if (invalidChannels.length > 0) {
556
- logger.error(`无效频道 (${invalidChannels.length} 个):`);
557
- invalidChannels.forEach((channel, index) => {
558
- logger.item(`${index + 1}. ID: ${channel.id}`, 1);
559
- logger.item(`原因: ${channel.reason}`, 2);
560
- });
561
- invalidChannels.forEach((channel) => {
562
- this.targetChatIds.delete(channel.id);
201
+ this.flushing = true;
202
+ await new Promise((resolve) => {
203
+ const timeout = setTimeout(() => {
204
+ resolve();
205
+ }, timeoutMs);
206
+ winstonLogger.once("finish", () => {
207
+ clearTimeout(timeout);
208
+ resolve();
563
209
  });
564
- if (invalidChannels.length > 0) {
565
- await this.saveChatIds();
566
- logger.line("💾", "已清理无效频道并保存配置");
567
- }
568
- }
569
- }
570
- /**
571
- * 停止监听
572
- */
573
- async stop() {
574
- await this.bot.stop();
575
- }
576
- /**
577
- * 获取 bot 信息(用于验证 token 是否有效)
578
- */
579
- async getBotInfo() {
580
- try {
581
- return this.bot.api.getMe();
582
- } catch (error) {
583
- throw new Error(`获取 Bot 信息失败: ${error}`);
584
- }
210
+ winstonLogger.end();
211
+ });
585
212
  }
586
213
  }
214
+ const logger = new Logger();
587
215
  let globalRl = null;
588
216
  function getReadlineInterface() {
589
217
  if (!globalRl) {
@@ -602,9 +230,8 @@ function question(query) {
602
230
  }
603
231
  async function validateBotToken(botToken) {
604
232
  try {
605
- const listener = new TelegramListener(botToken);
606
- await listener.getBotInfo();
607
- await listener.stop();
233
+ const bot = new grammy.Bot(botToken);
234
+ await bot.api.getMe();
608
235
  return true;
609
236
  } catch (error) {
610
237
  logger.error(`Bot Token 验证失败: ${error}`);
@@ -966,7 +593,6 @@ async function ensureConfig() {
966
593
  }
967
594
  return config;
968
595
  }
969
- exports.TelegramListener = TelegramListener;
970
596
  exports.canAutoTrade = canAutoTrade;
971
597
  exports.configLocal = configLocal;
972
598
  exports.ensureConfig = ensureConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polycopy",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "polycopy test",
5
5
  "main": "dist/index.js",
6
6
  "bin": {