@zoulabo/line-hive 0.1.0

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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +457 -0
  3. package/bin/line-hive.js +44 -0
  4. package/dist/cli/config-writer.js +320 -0
  5. package/dist/cli/config-writer.js.map +1 -0
  6. package/dist/cli/doctor.js.map +1 -0
  7. package/dist/cli/init.js +85 -0
  8. package/dist/cli/init.js.map +1 -0
  9. package/dist/cli/prompts.js +35 -0
  10. package/dist/cli/prompts.js.map +1 -0
  11. package/dist/cli/test-connection.js +135 -0
  12. package/dist/cli/test-connection.js.map +1 -0
  13. package/dist/cli/webhook-capture.js +31 -0
  14. package/dist/cli/webhook-capture.js.map +1 -0
  15. package/dist/config.js +62 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/constants.js +15 -0
  18. package/dist/constants.js.map +1 -0
  19. package/dist/i18n.js +100 -0
  20. package/dist/i18n.js.map +1 -0
  21. package/dist/index.js +126 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/line/client.js +72 -0
  24. package/dist/line/client.js.map +1 -0
  25. package/dist/line/messages.js +217 -0
  26. package/dist/line/messages.js.map +1 -0
  27. package/dist/line/webhook.js +485 -0
  28. package/dist/line/webhook.js.map +1 -0
  29. package/dist/logger.js +5 -0
  30. package/dist/logger.js.map +1 -0
  31. package/dist/server.js +143 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/src/cli/config-writer.js +18 -0
  34. package/dist/src/cli/config-writer.js.map +1 -0
  35. package/dist/src/cli/init.js +11 -0
  36. package/dist/src/cli/init.js.map +1 -0
  37. package/dist/src/cli/prompts.js +38 -0
  38. package/dist/src/cli/prompts.js.map +1 -0
  39. package/dist/src/cli/webhook-capture.js +37 -0
  40. package/dist/src/cli/webhook-capture.js.map +1 -0
  41. package/dist/src/config.js +44 -0
  42. package/dist/src/config.js.map +1 -0
  43. package/dist/src/index.js +53 -0
  44. package/dist/src/index.js.map +1 -0
  45. package/dist/src/line/client.js +20 -0
  46. package/dist/src/line/client.js.map +1 -0
  47. package/dist/src/line/webhook.js +131 -0
  48. package/dist/src/line/webhook.js.map +1 -0
  49. package/dist/src/logger.js +11 -0
  50. package/dist/src/logger.js.map +1 -0
  51. package/dist/src/server.js +140 -0
  52. package/dist/src/server.js.map +1 -0
  53. package/dist/src/store/messageStore.js +222 -0
  54. package/dist/src/store/messageStore.js.map +1 -0
  55. package/dist/src/store/statusStore.js +87 -0
  56. package/dist/src/store/statusStore.js.map +1 -0
  57. package/dist/src/tools/cancelWait.js +26 -0
  58. package/dist/src/tools/cancelWait.js.map +1 -0
  59. package/dist/src/tools/checkMessages.js +16 -0
  60. package/dist/src/tools/checkMessages.js.map +1 -0
  61. package/dist/src/tools/getReply.js +15 -0
  62. package/dist/src/tools/getReply.js.map +1 -0
  63. package/dist/src/tools/sendMessage.js +58 -0
  64. package/dist/src/tools/sendMessage.js.map +1 -0
  65. package/dist/src/tools/setStatus.js +23 -0
  66. package/dist/src/tools/setStatus.js.map +1 -0
  67. package/dist/src/tools/waitForReply.js +85 -0
  68. package/dist/src/tools/waitForReply.js.map +1 -0
  69. package/dist/src/types/index.js +3 -0
  70. package/dist/src/types/index.js.map +1 -0
  71. package/dist/store/messageStore.js +513 -0
  72. package/dist/store/messageStore.js.map +1 -0
  73. package/dist/store/statusStore.js +320 -0
  74. package/dist/store/statusStore.js.map +1 -0
  75. package/dist/tools/ask.js +240 -0
  76. package/dist/tools/ask.js.map +1 -0
  77. package/dist/tools/cancelWait.js +30 -0
  78. package/dist/tools/cancelWait.js.map +1 -0
  79. package/dist/tools/checkMessages.js +23 -0
  80. package/dist/tools/checkMessages.js.map +1 -0
  81. package/dist/tools/getReply.js +12 -0
  82. package/dist/tools/getReply.js.map +1 -0
  83. package/dist/tools/listAgents.js +25 -0
  84. package/dist/tools/listAgents.js.map +1 -0
  85. package/dist/tools/sendMessage.js +99 -0
  86. package/dist/tools/sendMessage.js.map +1 -0
  87. package/dist/tools/setStatus.js +48 -0
  88. package/dist/tools/setStatus.js.map +1 -0
  89. package/dist/tools/waitForReply.js +36 -0
  90. package/dist/tools/waitForReply.js.map +1 -0
  91. package/dist/tools/waitForReplyInternal.js +66 -0
  92. package/dist/tools/waitForReplyInternal.js.map +1 -0
  93. package/dist/tunnel.js +124 -0
  94. package/dist/tunnel.js.map +1 -0
  95. package/dist/types/index.js +2 -0
  96. package/dist/types/index.js.map +1 -0
  97. package/dist/util/persistUserId.js.map +1 -0
  98. package/dist/util/sendWithTokenPool.js +51 -0
  99. package/dist/util/sendWithTokenPool.js.map +1 -0
  100. package/dist/util/sleep.js +5 -0
  101. package/dist/util/sleep.js.map +1 -0
  102. package/dist/util/toolHelpers.js +60 -0
  103. package/dist/util/toolHelpers.js.map +1 -0
  104. package/package.json +61 -0
  105. package/templates/line-notification.instructions.md +105 -0
@@ -0,0 +1,31 @@
1
+ import http from "http";
2
+ export async function captureWebhookUserId() {
3
+ return new Promise((resolve) => {
4
+ const server = http.createServer((req, res) => {
5
+ const chunks = [];
6
+ req.on("data", (chunk) => chunks.push(chunk));
7
+ req.on("end", () => {
8
+ const body = Buffer.concat(chunks).toString("utf-8");
9
+ res.statusCode = 200;
10
+ res.end("ok");
11
+ try {
12
+ const payload = JSON.parse(body);
13
+ const userId = payload.events?.[0]?.source?.userId || null;
14
+ server.close();
15
+ resolve(userId);
16
+ }
17
+ catch {
18
+ server.close();
19
+ resolve(null);
20
+ }
21
+ });
22
+ });
23
+ server.listen(0, () => {
24
+ const address = server.address();
25
+ if (typeof address === "object" && address) {
26
+ console.log(`Webhook capture listening on port ${address.port}`);
27
+ }
28
+ });
29
+ });
30
+ }
31
+ //# sourceMappingURL=webhook-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-capture.js","sourceRoot":"","sources":["../../src/cli/webhook-capture.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC5C,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACrD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACd,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAE9B,CAAC;oBACF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC;oBAC3D,MAAM,CAAC,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClB,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,CAAC,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE;YACpB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,EAAE,CAAC;gBAC3C,OAAO,CAAC,GAAG,CAAC,qCAAqC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,62 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import crypto from "crypto";
4
+ import os from "os";
5
+ function numberEnv(name, fallback) {
6
+ const value = process.env[name];
7
+ if (!value) {
8
+ return fallback;
9
+ }
10
+ const parsed = Number(value);
11
+ if (!Number.isFinite(parsed)) {
12
+ throw new Error(`Invalid numeric env var: ${name}`);
13
+ }
14
+ return parsed;
15
+ }
16
+ /**
17
+ * Generate a stable agent ID from the workspace path.
18
+ * Same workspace always produces the same ID.
19
+ */
20
+ function generateAgentId(workspacePath) {
21
+ return crypto.createHash("sha256").update(workspacePath).digest("hex").slice(0, 12);
22
+ }
23
+ /**
24
+ * Derive a human-readable agent name from the workspace path.
25
+ */
26
+ function deriveAgentName(workspacePath) {
27
+ return path.basename(workspacePath) || "unknown";
28
+ }
29
+ /**
30
+ * Get the shared DB path — all agent instances use the same SQLite file.
31
+ * Default: ~/.line-hive/line-hive.db (overridden by SQLITE_PATH env).
32
+ */
33
+ function getSharedDbPath() {
34
+ if (process.env.SQLITE_PATH) {
35
+ return path.resolve(process.env.SQLITE_PATH);
36
+ }
37
+ return path.join(os.homedir(), ".line-hive", "line-hive.db");
38
+ }
39
+ export function loadConfig() {
40
+ const sqlitePath = getSharedDbPath();
41
+ const sqliteDir = path.dirname(sqlitePath);
42
+ if (!fs.existsSync(sqliteDir)) {
43
+ fs.mkdirSync(sqliteDir, { recursive: true, mode: 0o700 });
44
+ }
45
+ // Workspace path: use WORKSPACE_PATH env or cwd
46
+ const workspacePath = process.env.WORKSPACE_PATH || process.cwd();
47
+ return {
48
+ lineChannelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || "",
49
+ lineChannelSecret: process.env.LINE_CHANNEL_SECRET || "",
50
+ webhookPort: numberEnv("WEBHOOK_PORT", 19780),
51
+ webhookPath: process.env.WEBHOOK_PATH || "/webhook",
52
+ sqlitePath,
53
+ sessionTtlMs: numberEnv("SESSION_TTL_MS", 7200000),
54
+ cleanupIntervalMs: numberEnv("CLEANUP_INTERVAL_MS", 60000),
55
+ agentId: generateAgentId(workspacePath),
56
+ agentName: process.env.AGENT_NAME || deriveAgentName(workspacePath),
57
+ heartbeatIntervalMs: numberEnv("HEARTBEAT_INTERVAL_MS", 10000),
58
+ heartbeatTimeoutMs: numberEnv("HEARTBEAT_TIMEOUT_MS", 600000),
59
+ ngrokDomain: process.env.NGROK_DOMAIN?.trim() || undefined
60
+ };
61
+ }
62
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,SAAS,SAAS,CAAC,IAAY,EAAE,QAAgB;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,aAAqB;IAC5C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,aAAqB;IAC5C,OAAO,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,SAAS,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe;IACtB,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,gDAAgD;IAChD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAElE,OAAO;QACL,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,EAAE;QACnE,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE;QACxD,WAAW,EAAE,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC;QAC7C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,UAAU;QACnD,UAAU;QACV,YAAY,EAAE,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC;QAClD,iBAAiB,EAAE,SAAS,CAAC,qBAAqB,EAAE,KAAK,CAAC;QAC1D,OAAO,EAAE,eAAe,CAAC,aAAa,CAAC;QACvC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,eAAe,CAAC,aAAa,CAAC;QACnE,mBAAmB,EAAE,SAAS,CAAC,uBAAuB,EAAE,KAAK,CAAC;QAC9D,kBAAkB,EAAE,SAAS,CAAC,sBAAsB,EAAE,MAAM,CAAC;QAC7D,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,SAAS;KAC3D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared constants used across tools and handlers.
3
+ */
4
+ // LINE API limits
5
+ export const MAX_LINE_TEXT_LENGTH = 5000;
6
+ // Timeout clamping
7
+ export const MIN_TIMEOUT_MS = 100;
8
+ export const MAX_TIMEOUT_MS = 86400000; // 24 hours
9
+ // Poll interval for session/reply loops
10
+ export const POLL_INTERVAL_MS = 2000;
11
+ // Reply token pool
12
+ export const REPLY_TOKEN_MAX_AGE_MS = 900000; // 15 min
13
+ // Config keys
14
+ export const CONFIG_TARGETED_AGENT = "targeted_agent_id";
15
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kBAAkB;AAClB,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAEzC,mBAAmB;AACnB,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAClC,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,WAAW;AAEnD,wCAAwC;AACxC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,mBAAmB;AACnB,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,CAAC,SAAS;AAEvD,cAAc;AACd,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAC"}
package/dist/i18n.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Internationalization module for LINE-facing messages.
3
+ * Supports en (English) and ja (Japanese).
4
+ * Language is auto-detected from LINE user profile on first interaction.
5
+ */
6
+ /** Map LINE API language codes (BCP 47) to supported locales */
7
+ export function resolveLocale(language) {
8
+ if (language?.startsWith("ja"))
9
+ return "ja";
10
+ return "en";
11
+ }
12
+ // ─── Message Catalog ────────────────────────────────────────────────────────
13
+ const en = {
14
+ // Status display
15
+ idle: (age) => `Idle (${age})`,
16
+ working: (desc, age) => `Working on: ${desc} (${age})`,
17
+ workingNoDesc: (age) => `Working... (${age})`,
18
+ needsInput: (prompt, age) => `Needs input: ${prompt} (${age})`,
19
+ needsInputNoPrompt: (age) => `Waiting for input (${age})`,
20
+ error: (desc, age) => `Error: ${desc} (${age})`,
21
+ errorNoDesc: (age) => `Error (${age})`,
22
+ offline: (name, lastStatus, age) => `🔌 ${name}: offline (${lastStatus}, ${age})`,
23
+ noActiveAgents: () => "No active agents.",
24
+ // Queue
25
+ queuedCount: (n) => `📬 ${n} queued message${n > 1 ? "s" : ""}`,
26
+ noQueuedForAgent: () => "📬 No queued messages for this agent.",
27
+ noQueued: () => "📬 No queued messages.",
28
+ queuedForHeader: (agent) => `📬 Queued for ${agent}:`,
29
+ queuedHeader: () => "📬 Queued messages:",
30
+ ackQueued: (agent, info) => `📨 Queued for ${agent}${info}`,
31
+ nQueued: (n) => ` (${n} queued)`,
32
+ // Quick reply button labels
33
+ btnContinue: () => "▶️ Continue",
34
+ btnStatus: () => "📊 Status",
35
+ btnQueued: () => "📬 Queued",
36
+ btnOk: () => "✅ OK",
37
+ btnCancel: () => "❌ Cancel",
38
+ btnYes: () => "👍 Yes",
39
+ btnNo: () => "👎 No",
40
+ btnMore: (n) => `📬 ${n} more`,
41
+ // Notifications
42
+ switchNotif: (fulfilled, next) => `👍 ${fulfilled}\n\n❗ ${next} needs your reply`,
43
+ // Confirmations
44
+ selectConfirm: (name, statusLine) => `Select ${name}?\n${statusLine}`,
45
+ // Errors
46
+ noAgentN: (n) => `No agent #${n}. Tap 📊 Status to see the list.`,
47
+ agentOffline: (name) => `${name} is offline. Tap 📊 Status.`,
48
+ noAgentSelected: () => "⚠️ No agent selected — message not delivered. Tap Continue to pick one.",
49
+ // Time
50
+ justNow: () => "just now",
51
+ minutesAgo: (m) => `${m}m ago`,
52
+ hoursAgo: (h) => `${h}h ago`,
53
+ daysAgo: (d) => `${d}d ago`,
54
+ // Misc
55
+ tapToConnect: () => "\n\n⚡ Tap below to stay connected 👇",
56
+ waitingForReply: (duration) => `— Waiting for your reply (${duration} timeout)`,
57
+ };
58
+ const ja = {
59
+ idle: (age) => `待機中 (${age})`,
60
+ working: (desc, age) => `作業中: ${desc} (${age})`,
61
+ workingNoDesc: (age) => `作業中... (${age})`,
62
+ needsInput: (prompt, age) => `入力待ち: ${prompt} (${age})`,
63
+ needsInputNoPrompt: (age) => `入力待ち (${age})`,
64
+ error: (desc, age) => `エラー: ${desc} (${age})`,
65
+ errorNoDesc: (age) => `エラー (${age})`,
66
+ offline: (name, lastStatus, age) => `🔌 ${name}: オフライン (${lastStatus}, ${age})`,
67
+ noActiveAgents: () => "アクティブなエージェントはありません。",
68
+ queuedCount: (n) => `📬 ${n}件のメッセージ`,
69
+ noQueuedForAgent: () => "📬 このエージェントにキューされたメッセージはありません。",
70
+ noQueued: () => "📬 キューされたメッセージはありません。",
71
+ queuedForHeader: (agent) => `📬 ${agent}のキュー:`,
72
+ queuedHeader: () => "📬 キューされたメッセージ:",
73
+ ackQueued: (agent, info) => `📨 ${agent}にキュー${info}`,
74
+ nQueued: (n) => ` (${n}件キュー)`,
75
+ btnContinue: () => "▶️ 続ける",
76
+ btnStatus: () => "📊 状態",
77
+ btnQueued: () => "📬 キュー",
78
+ btnOk: () => "✅ OK",
79
+ btnCancel: () => "❌ キャンセル",
80
+ btnYes: () => "👍 はい",
81
+ btnNo: () => "👎 いいえ",
82
+ btnMore: (n) => `📬 あと${n}件`,
83
+ switchNotif: (fulfilled, next) => `👍 ${fulfilled}\n\n❗ ${next}が返信を待っています`,
84
+ selectConfirm: (name, statusLine) => `${name}を選択しますか?\n${statusLine}`,
85
+ noAgentN: (n) => `エージェント #${n}が見つかりません。📊 状態をタップしてください。`,
86
+ agentOffline: (name) => `${name}はオフラインです。📊 状態をタップ。`,
87
+ noAgentSelected: () => "⚠️ エージェント未選択 — メッセージは配信されません。続けるをタップしてください。",
88
+ justNow: () => "たった今",
89
+ minutesAgo: (m) => `${m}分前`,
90
+ hoursAgo: (h) => `${h}時間前`,
91
+ daysAgo: (d) => `${d}日前`,
92
+ tapToConnect: () => "\n\n⚡ 下をタップして接続を維持 👇",
93
+ waitingForReply: (duration) => `— 返信を待っています (${duration}タイムアウト)`,
94
+ };
95
+ const catalogs = { en, ja };
96
+ /** Get the message catalog for a locale */
97
+ export function t(locale) {
98
+ return catalogs[locale] || catalogs.en;
99
+ }
100
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ import Database from "better-sqlite3";
2
+ import net from "net";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { logger } from "./logger.js";
5
+ import { loadConfig } from "./config.js";
6
+ import { MessageStore } from "./store/messageStore.js";
7
+ import { StatusStore } from "./store/statusStore.js";
8
+ import { createLineClient } from "./line/client.js";
9
+ import { createServer } from "./server.js";
10
+ import { startWebhookServer } from "./line/webhook.js";
11
+ import { startTunnel, stopTunnel } from "./tunnel.js";
12
+ /** Check if a TCP port is available (not in use) */
13
+ function isPortAvailable(port) {
14
+ return new Promise((resolve) => {
15
+ const tester = net.createServer()
16
+ .once("error", () => resolve(false))
17
+ .once("listening", () => {
18
+ tester.close(() => resolve(true));
19
+ })
20
+ .listen(port);
21
+ });
22
+ }
23
+ async function main() {
24
+ const config = loadConfig();
25
+ // Friendly startup diagnostics
26
+ if (!config.lineChannelAccessToken) {
27
+ logger.warn("LINE_CHANNEL_ACCESS_TOKEN not set — LINE messaging disabled. Tools will be available for discovery but sending messages will fail.");
28
+ }
29
+ if (!config.lineChannelSecret) {
30
+ logger.warn("LINE_CHANNEL_SECRET not set — webhook signature verification disabled. Webhook events will be rejected.");
31
+ }
32
+ if (!config.lineChannelAccessToken && !config.lineChannelSecret) {
33
+ logger.warn("No LINE credentials configured. Set LINE_CHANNEL_ACCESS_TOKEN and LINE_CHANNEL_SECRET in mcp.json env. Run: npx line-hive init");
34
+ }
35
+ const db = new Database(config.sqlitePath);
36
+ // Enable WAL mode for safe concurrent access from multiple agents
37
+ db.pragma("journal_mode = WAL");
38
+ const messageStore = new MessageStore(db);
39
+ const statusStore = new StatusStore(db, config.agentId, config.agentName);
40
+ const lineClient = createLineClient(config);
41
+ logger.info({ agentId: config.agentId, agentName: config.agentName }, "Agent registered");
42
+ // Start webhook server — only the first agent claims the port
43
+ let webhookServer = null;
44
+ let tunnelHandle = null;
45
+ let ownsWebhook = false;
46
+ /** Attempt to claim the webhook port and start tunnel */
47
+ function tryClaimWebhook() {
48
+ if (ownsWebhook)
49
+ return; // Already owns it
50
+ try {
51
+ webhookServer = startWebhookServer({
52
+ config,
53
+ messageStore,
54
+ statusStore,
55
+ lineClient,
56
+ logger
57
+ });
58
+ webhookServer.on("listening", () => {
59
+ ownsWebhook = true;
60
+ logger.info({ port: config.webhookPort }, "Port migration: claimed webhook server");
61
+ if (config.ngrokDomain) {
62
+ tunnelHandle = startTunnel({
63
+ domain: config.ngrokDomain,
64
+ port: config.webhookPort,
65
+ logger
66
+ });
67
+ }
68
+ });
69
+ // If the server fails to bind (EADDRINUSE), the error handler in
70
+ // startWebhookServer logs it but doesn't throw. We need to detect this.
71
+ webhookServer.on("error", () => {
72
+ webhookServer = null;
73
+ });
74
+ }
75
+ catch {
76
+ // Port still in use — will retry on next heartbeat
77
+ }
78
+ }
79
+ // Initial attempt to claim the port
80
+ tryClaimWebhook();
81
+ const server = createServer({
82
+ config,
83
+ messageStore,
84
+ statusStore,
85
+ lineClient,
86
+ logger
87
+ });
88
+ const transport = new StdioServerTransport();
89
+ await server.connect(transport);
90
+ // Heartbeat timer — signals this agent is alive + port migration check
91
+ const heartbeatTimer = setInterval(async () => {
92
+ statusStore.heartbeat();
93
+ statusStore.removeDeadAgents(config.heartbeatTimeoutMs);
94
+ // Port migration: if we don't own the webhook, check if port is free
95
+ if (!ownsWebhook) {
96
+ const available = await isPortAvailable(config.webhookPort);
97
+ if (available) {
98
+ logger.info({ port: config.webhookPort }, "Port migration: port available, attempting claim");
99
+ tryClaimWebhook();
100
+ }
101
+ }
102
+ }, config.heartbeatIntervalMs);
103
+ const cleanupTimer = setInterval(() => {
104
+ messageStore.cleanup();
105
+ messageStore.expireSessions(Date.now());
106
+ messageStore.cleanupDeadAgentSessions(config.heartbeatTimeoutMs);
107
+ }, config.cleanupIntervalMs);
108
+ const shutdown = () => {
109
+ clearInterval(heartbeatTimer);
110
+ clearInterval(cleanupTimer);
111
+ statusStore.unregister();
112
+ stopTunnel(tunnelHandle, logger);
113
+ if (webhookServer) {
114
+ webhookServer.close();
115
+ }
116
+ db.close();
117
+ process.exit(0);
118
+ };
119
+ process.on("SIGINT", shutdown);
120
+ process.on("SIGTERM", shutdown);
121
+ }
122
+ main().catch((error) => {
123
+ logger.error({ error: error instanceof Error ? error.message : error }, "Failed to start LINE Hive service");
124
+ process.exit(1);
125
+ });
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { Client } from "@line/bot-sdk";
2
+ /** Extract messageId from LINE SDK response's sentMessages array */
3
+ function extractMessageId(response) {
4
+ const sentMessages = response.sentMessages;
5
+ return { messageId: sentMessages?.[0]?.id };
6
+ }
7
+ function unconfiguredStub() {
8
+ throw new Error("LINE client not configured: missing channel access token");
9
+ }
10
+ export function createLineClient(config) {
11
+ if (!config.lineChannelAccessToken) {
12
+ return {
13
+ async replyText() { unconfiguredStub(); },
14
+ async replyMessage() { return unconfiguredStub(); },
15
+ async pushText() { return unconfiguredStub(); },
16
+ async pushMessage() { return unconfiguredStub(); },
17
+ async showLoadingAnimation() { },
18
+ };
19
+ }
20
+ const client = new Client({
21
+ channelAccessToken: config.lineChannelAccessToken
22
+ });
23
+ return {
24
+ async replyText(replyToken, text) {
25
+ await client.replyMessage(replyToken, { type: "text", text });
26
+ },
27
+ async replyMessage(replyToken, message) {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const response = await client.replyMessage(replyToken, message);
30
+ return extractMessageId(response);
31
+ },
32
+ async pushText(userId, text) {
33
+ const response = await client.pushMessage(userId, { type: "text", text });
34
+ return extractMessageId(response);
35
+ },
36
+ async pushMessage(userId, message) {
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ const response = await client.pushMessage(userId, message);
39
+ return extractMessageId(response);
40
+ },
41
+ async showLoadingAnimation(userId, seconds = 20) {
42
+ try {
43
+ await fetch("https://api.line.me/v2/bot/chat/loading/start", {
44
+ method: "POST",
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ Authorization: `Bearer ${config.lineChannelAccessToken}`,
48
+ },
49
+ body: JSON.stringify({ chatId: userId, loadingSeconds: seconds }),
50
+ });
51
+ }
52
+ catch {
53
+ // Non-critical — silently ignore loading animation failures
54
+ }
55
+ },
56
+ async getProfile(userId) {
57
+ try {
58
+ const response = await fetch(`https://api.line.me/v2/bot/profile/${userId}`, {
59
+ headers: { Authorization: `Bearer ${config.lineChannelAccessToken}` },
60
+ });
61
+ if (!response.ok)
62
+ return null;
63
+ const data = await response.json();
64
+ return { displayName: data.displayName, language: data.language };
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/line/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAavC,oEAAoE;AACpE,SAAS,gBAAgB,CAAC,QAAiB;IACzC,MAAM,YAAY,GAAI,QAAqD,CAAC,YAAY,CAAC;IACzF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAqB;IACpD,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,CAAC;QACnC,OAAO;YACL,KAAK,CAAC,SAAS,KAAK,gBAAgB,EAAE,CAAC,CAAC,CAAC;YACzC,KAAK,CAAC,YAAY,KAAK,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC;YACnD,KAAK,CAAC,QAAQ,KAAK,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC;YAC/C,KAAK,CAAC,WAAW,KAAK,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC;YAClD,KAAK,CAAC,oBAAoB,KAAyC,CAAC;SACrE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,kBAAkB,EAAE,MAAM,CAAC,sBAAsB;KAClD,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI;YAC9B,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO;YACpC,8DAA8D;YAC9D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,OAAc,CAAC,CAAC;YACvE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI;YACzB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1E,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO;YAC/B,8DAA8D;YAC9D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,OAAc,CAAC,CAAC;YAClE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE;YAC7C,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,+CAA+C,EAAE;oBAC3D,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,aAAa,EAAE,UAAU,MAAM,CAAC,sBAAsB,EAAE;qBACzD;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;iBAClE,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;YAC9D,CAAC;QACH,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,MAAM;YACrB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,sCAAsC,MAAM,EAAE,EAAE;oBAC3E,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,sBAAsB,EAAE,EAAE;iBACtE,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,OAAO,IAAI,CAAC;gBAC9B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAgD,CAAC;gBACjF,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}