@calltelemetry/cucm-mcp 0.1.7 → 0.2.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.
- package/README.md +61 -7
- package/dist/axl.js +235 -0
- package/dist/axl.js.map +7 -0
- package/dist/dime.js +231 -269
- package/dist/dime.js.map +7 -0
- package/dist/index.js +680 -207
- package/dist/index.js.map +7 -0
- package/dist/multipart.js +58 -68
- package/dist/multipart.js.map +7 -0
- package/dist/packetCapture.js +315 -373
- package/dist/packetCapture.js.map +7 -0
- package/dist/pcap-analyze.js +601 -0
- package/dist/pcap-analyze.js.map +7 -0
- package/dist/state.js +105 -104
- package/dist/state.js.map +7 -0
- package/dist/time.js +25 -23
- package/dist/time.js.map +7 -0
- package/package.json +4 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pcap-analyze.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * PCAP analysis via tshark (Wireshark CLI).\n *\n * Provides structured VoIP call analysis from captured .cap files:\n * SIP call flows, SCCP/Skinny messages, RTP stream quality, and protocol summaries.\n *\n * No npm dependencies \u2014 shells out to tshark which has full SIP/SCCP/RTP dissectors.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync, statSync } from \"node:fs\";\n\n// ---------------------------------------------------------------------------\n// tshark binary discovery\n// ---------------------------------------------------------------------------\n\nconst TSHARK_CANDIDATES = [\n process.env.TSHARK_PATH,\n \"tshark\",\n \"/Applications/Wireshark.app/Contents/MacOS/tshark\",\n \"/usr/local/bin/tshark\",\n \"/usr/bin/tshark\",\n \"/opt/homebrew/bin/tshark\",\n].filter(Boolean) as string[];\n\nlet cachedTsharkPath: string | null = null;\n\nfunction findTshark(): string {\n if (cachedTsharkPath) return cachedTsharkPath;\n for (const candidate of TSHARK_CANDIDATES) {\n try {\n if (existsSync(candidate)) {\n cachedTsharkPath = candidate;\n return candidate;\n }\n } catch {\n // Try next\n }\n }\n // Fall back to bare \"tshark\" and let execFile find it in PATH\n return \"tshark\";\n}\n\nconst TSHARK_TIMEOUT_MS = Number(process.env.CUCM_MCP_TSHARK_TIMEOUT_MS) || 60_000;\n\n// ---------------------------------------------------------------------------\n// Core tshark execution\n// ---------------------------------------------------------------------------\n\nexport function runTshark(args: string[], timeoutMs?: number): Promise<string> {\n const bin = findTshark();\n const timeout = timeoutMs ?? TSHARK_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n execFile(bin, args, { timeout, maxBuffer: 50 * 1024 * 1024 }, (err, stdout, stderr) => {\n if (err) {\n const msg = stderr?.trim() || err.message;\n if (msg.includes(\"No such file\") || msg.includes(\"doesn't exist\")) {\n reject(new Error(`File not found: ${args.find((a) => a.endsWith(\".cap\") || a.endsWith(\".pcap\")) || \"unknown\"}`));\n } else if (msg.includes(\"not found\") || msg.includes(\"ENOENT\")) {\n reject(\n new Error(\n `tshark not found. Install Wireshark or set TSHARK_PATH. Tried: ${TSHARK_CANDIDATES.join(\", \")}`\n )\n );\n } else {\n reject(new Error(`tshark error: ${msg}`));\n }\n return;\n }\n resolve(stdout);\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// File validation\n// ---------------------------------------------------------------------------\n\nfunction validateCapFile(filePath: string): void {\n if (!existsSync(filePath)) {\n throw new Error(`Capture file not found: ${filePath}`);\n }\n const stat = statSync(filePath);\n if (stat.size === 0) {\n throw new Error(`Capture file is empty: ${filePath}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// SCCP/Skinny message name lookup\n// ---------------------------------------------------------------------------\n\nconst SKINNY_MESSAGE_NAMES: Record<number, string> = {\n 0x0000: \"KeepAliveMessage\",\n 0x0001: \"RegisterMessage\",\n 0x0002: \"IpPortMessage\",\n 0x0003: \"KeypadButtonMessage\",\n 0x0004: \"EnblocCallMessage\",\n 0x0005: \"StimulusMessage\",\n 0x0006: \"OffHookMessage\",\n 0x0007: \"OnHookMessage\",\n 0x0008: \"HookFlashMessage\",\n 0x0009: \"ForwardStatReqMessage\",\n 0x000a: \"SpeedDialStatReqMessage\",\n 0x000b: \"LineStatReqMessage\",\n 0x000c: \"ConfigStatReqMessage\",\n 0x000d: \"TimeDateReqMessage\",\n 0x000e: \"ButtonTemplateReqMessage\",\n 0x000f: \"VersionReqMessage\",\n 0x0010: \"CapabilitiesResMessage\",\n 0x0020: \"AlarmMessage\",\n 0x0022: \"MulticastMediaReceptionAck\",\n 0x0023: \"OpenReceiveChannelAck\",\n 0x0024: \"ConnectionStatisticsRes\",\n 0x0025: \"OffHookWithCgpnMessage\",\n 0x0026: \"SoftKeySetReqMessage\",\n 0x0027: \"SoftKeyEventMessage\",\n 0x0029: \"UnregisterMessage\",\n 0x002a: \"SoftKeyTemplateReqMessage\",\n 0x002b: \"RegisterTokenReq\",\n 0x002c: \"MediaTransmissionFailure\",\n 0x002d: \"HeadsetStatusMessage\",\n 0x002e: \"MediaResourceNotification\",\n 0x002f: \"RegisterAvailableLinesMessage\",\n 0x0030: \"DeviceToUserDataMessage\",\n 0x0031: \"DeviceToUserDataResponseMessage\",\n 0x0032: \"UpdateCapabilitiesMessage\",\n 0x0034: \"ServiceURLStatReqMessage\",\n 0x0035: \"FeatureStatReqMessage\",\n 0x0048: \"DeviceToUserDataVersion1Message\",\n 0x0049: \"DeviceToUserDataResponseVersion1Message\",\n // Station \u2192 CallManager\n 0x0081: \"RegisterAckMessage\",\n 0x0082: \"StartToneMessage\",\n 0x0083: \"StopToneMessage\",\n 0x0085: \"SetRingerMessage\",\n 0x0086: \"SetLampMessage\",\n 0x0087: \"SetHkFDetectMessage\",\n 0x0088: \"SetSpeakerModeMessage\",\n 0x0089: \"SetMicroModeMessage\",\n 0x008a: \"StartMediaTransmission\",\n 0x008b: \"StopMediaTransmission\",\n 0x008f: \"CallInfoMessage\",\n 0x0090: \"ForwardStatMessage\",\n 0x0091: \"SpeedDialStatMessage\",\n 0x0092: \"LineStatMessage\",\n 0x0093: \"ConfigStatMessage\",\n 0x0094: \"DefineTimeDate\",\n 0x0095: \"StartSessionTransmission\",\n 0x0096: \"StopSessionTransmission\",\n 0x0097: \"ButtonTemplateMessage\",\n 0x0098: \"VersionMessage\",\n 0x0099: \"DisplayTextMessage\",\n 0x009a: \"ClearDisplay\",\n 0x009b: \"CapabilitiesReqMessage\",\n 0x009d: \"RegisterRejectMessage\",\n 0x009e: \"ServerResMessage\",\n 0x009f: \"Reset\",\n 0x0100: \"KeepAliveAckMessage\",\n 0x0101: \"StartMulticastMediaReception\",\n 0x0102: \"StartMulticastMediaTransmission\",\n 0x0103: \"StopMulticastMediaReception\",\n 0x0104: \"StopMulticastMediaTransmission\",\n 0x0105: \"OpenReceiveChannel\",\n 0x0106: \"CloseReceiveChannel\",\n 0x0107: \"ConnectionStatisticsReq\",\n 0x0108: \"SoftKeyTemplateResMessage\",\n 0x0109: \"SoftKeySetResMessage\",\n 0x0110: \"SelectSoftKeysMessage\",\n 0x0111: \"CallStateMessage\",\n 0x0112: \"DisplayPromptStatusMessage\",\n 0x0113: \"ClearPromptStatusMessage\",\n 0x0114: \"DisplayNotifyMessage\",\n 0x0115: \"ClearNotifyMessage\",\n 0x0116: \"ActivateCallPlaneMessage\",\n 0x0117: \"DeactivateCallPlaneMessage\",\n 0x0118: \"UnregisterAckMessage\",\n 0x0119: \"BackSpaceReqMessage\",\n 0x011a: \"RegisterTokenAck\",\n 0x011b: \"RegisterTokenReject\",\n 0x011c: \"StartMediaFailureDetection\",\n 0x011d: \"DialedNumberMessage\",\n 0x011e: \"UserToDeviceDataMessage\",\n 0x011f: \"FeatureStatMessage\",\n 0x0120: \"DisplayPriNotifyMessage\",\n 0x0121: \"ClearPriNotifyMessage\",\n 0x0122: \"StartAnnouncementMessage\",\n 0x0123: \"StopAnnouncementMessage\",\n 0x0124: \"AnnouncementFinishMessage\",\n 0x0127: \"NotifyDtmfToneMessage\",\n 0x0128: \"SendDtmfToneMessage\",\n 0x012a: \"SubscribeDtmfPayloadReqMessage\",\n 0x012b: \"SubscribeDtmfPayloadResMessage\",\n 0x012c: \"SubscribeDtmfPayloadErrMessage\",\n 0x012d: \"UnSubscribeDtmfPayloadReqMessage\",\n 0x012e: \"UnSubscribeDtmfPayloadResMessage\",\n 0x012f: \"UnSubscribeDtmfPayloadErrMessage\",\n 0x0130: \"ServiceURLStatMessage\",\n 0x013a: \"UserToDeviceDataVersion1Message\",\n 0x013f: \"DialedPhoneBookMessage\",\n 0x0141: \"XMLAlarmMessage\",\n 0x0143: \"SpeedDialStatDynamicMessage\",\n 0x0152: \"CallInfoMessage2\",\n};\n\nfunction skinnyMessageName(id: number): string {\n return SKINNY_MESSAGE_NAMES[id] ?? `Unknown(0x${id.toString(16).padStart(4, \"0\")})`;\n}\n\n// ---------------------------------------------------------------------------\n// RTP payload type lookup\n// ---------------------------------------------------------------------------\n\nconst RTP_PAYLOAD_TYPES: Record<number, string> = {\n 0: \"PCMU (G.711 u-law)\",\n 3: \"GSM\",\n 4: \"G.723\",\n 8: \"PCMA (G.711 A-law)\",\n 9: \"G.722\",\n 10: \"L16 stereo\",\n 11: \"L16 mono\",\n 13: \"CN (comfort noise)\",\n 18: \"G.729\",\n 31: \"H.261\",\n 32: \"MPV (MPEG video)\",\n 33: \"MP2T (MPEG transport)\",\n 34: \"H.263\",\n 96: \"dynamic (96)\",\n 97: \"dynamic (97)\",\n 98: \"dynamic (98)\",\n 99: \"dynamic (99)\",\n 100: \"dynamic (100)\",\n 101: \"telephone-event (DTMF)\",\n 110: \"dynamic (110)\",\n 111: \"dynamic (111)\",\n 112: \"dynamic (112)\",\n 114: \"iLBC\",\n 116: \"dynamic (116)\",\n 118: \"dynamic (118)\",\n 119: \"dynamic (119)\",\n 120: \"dynamic (120)\",\n 121: \"dynamic (121)\",\n 122: \"dynamic (122)\",\n 123: \"dynamic (123)\",\n 124: \"dynamic (124)\",\n 125: \"dynamic (125)\",\n 126: \"dynamic (126)\",\n 127: \"dynamic (127)\",\n};\n\nfunction rtpCodecName(pt: number): string {\n return RTP_PAYLOAD_TYPES[pt] ?? `PT ${pt}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tool: pcap_call_summary\n// ---------------------------------------------------------------------------\n\nexport async function pcapCallSummary(filePath: string): Promise<object> {\n validateCapFile(filePath);\n const stat = statSync(filePath);\n\n // Protocol hierarchy\n const phsRaw = await runTshark([\"-r\", filePath, \"-q\", \"-z\", \"io,phs\"]);\n\n // Parse protocol hierarchy for VoIP protocols\n const protocols: Record<string, { frames: number; bytes: number }> = {};\n for (const line of phsRaw.split(\"\\n\")) {\n const match = line.match(/^\\s*([\\w:]+)\\s+frames:(\\d+)\\s+bytes:(\\d+)/);\n if (match) {\n const proto = match[1].split(\":\").pop()!.toLowerCase();\n if ([\"sip\", \"skinny\", \"rtp\", \"rtcp\", \"sdp\", \"stun\", \"tcp\", \"udp\", \"ip\", \"eth\"].includes(proto)) {\n protocols[proto] = { frames: parseInt(match[2]), bytes: parseInt(match[3]) };\n }\n }\n }\n\n // SIP call count via Call-ID extraction\n let sipCallIds: string[] = [];\n try {\n const sipRaw = await runTshark([\n \"-r\", filePath, \"-Y\", \"sip\", \"-T\", \"fields\",\n \"-e\", \"sip.Call-ID\", \"-E\", \"header=n\",\n ]);\n sipCallIds = [...new Set(sipRaw.split(\"\\n\").map((l) => l.trim()).filter(Boolean))];\n } catch { /* no SIP packets */ }\n\n // RTP stream count\n let rtpStreamCount = 0;\n try {\n const rtpRaw = await runTshark([\"-r\", filePath, \"-q\", \"-z\", \"rtp,streams\"]);\n const rtpLines = rtpRaw.split(\"\\n\").filter((l) => /^\\s*\\d+\\.\\d+/.test(l));\n rtpStreamCount = rtpLines.length;\n } catch { /* no RTP */ }\n\n // IP conversations\n let endpoints: string[] = [];\n try {\n const convRaw = await runTshark([\"-r\", filePath, \"-q\", \"-z\", \"conv,ip\"]);\n const ips = new Set<string>();\n for (const line of convRaw.split(\"\\n\")) {\n const m = line.match(/(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s+<->\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+)/);\n if (m) { ips.add(m[1]); ips.add(m[2]); }\n }\n endpoints = [...ips].sort();\n } catch { /* no IP convos */ }\n\n // Capture duration\n let durationSec = 0;\n try {\n const capRaw = await runTshark([\n \"-r\", filePath, \"-T\", \"fields\", \"-e\", \"frame.time_relative\",\n \"-E\", \"header=n\", \"-c\", \"1\", \"-Y\", \"frame.number == 0\",\n ]);\n // Get last packet time instead\n const durRaw = await runTshark([\n \"-r\", filePath, \"-q\", \"-z\", \"io,stat,0\",\n ]);\n const durMatch = durRaw.match(/Duration:\\s+([\\d.]+)/);\n if (durMatch) durationSec = parseFloat(durMatch[1]);\n } catch { /* ignore */ }\n\n // Total packet count from capinfos-like output\n let totalPackets = 0;\n for (const p of Object.values(protocols)) {\n if (p.frames > totalPackets) totalPackets = p.frames;\n }\n // Use IP frame count as total if available\n totalPackets = protocols[\"ip\"]?.frames ?? protocols[\"eth\"]?.frames ?? totalPackets;\n\n return {\n file: filePath,\n bytes: stat.size,\n packets: totalPackets,\n duration: `${durationSec.toFixed(1)}s`,\n protocols: {\n sip: protocols[\"sip\"]?.frames ?? 0,\n skinny: protocols[\"skinny\"]?.frames ?? 0,\n rtp: protocols[\"rtp\"]?.frames ?? 0,\n rtcp: protocols[\"rtcp\"]?.frames ?? 0,\n sdp: protocols[\"sdp\"]?.frames ?? 0,\n },\n endpoints,\n sipCalls: sipCallIds.length,\n sipCallIds: sipCallIds.length <= 20 ? sipCallIds : sipCallIds.slice(0, 20),\n rtpStreams: rtpStreamCount,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool: pcap_sip_calls\n// ---------------------------------------------------------------------------\n\nexport interface SipMessage {\n time: string;\n src: string;\n dst: string;\n method?: string;\n statusCode?: number;\n statusLine?: string;\n callId: string;\n from: string;\n to: string;\n cseq: string;\n requestUri?: string;\n sdpMedia?: string;\n sdpConnectionInfo?: string;\n}\n\nexport interface SipCallFlow {\n callId: string;\n from: string;\n to: string;\n messages: SipMessage[];\n metrics: {\n firstSeen: string;\n lastSeen: string;\n messageCount: number;\n answered: boolean;\n setupTimeMs?: number;\n };\n}\n\nexport async function pcapSipCalls(filePath: string, callId?: string): Promise<SipCallFlow[]> {\n validateCapFile(filePath);\n\n const filter = callId ? `sip and sip.Call-ID == \"${callId}\"` : \"sip\";\n const raw = await runTshark([\n \"-r\", filePath, \"-Y\", filter,\n \"-T\", \"fields\",\n \"-e\", \"frame.time_relative\",\n \"-e\", \"ip.src\",\n \"-e\", \"ip.dst\",\n \"-e\", \"sip.Method\",\n \"-e\", \"sip.Status-Code\",\n \"-e\", \"sip.Status-Line\",\n \"-e\", \"sip.Call-ID\",\n \"-e\", \"sip.from.addr\",\n \"-e\", \"sip.to.addr\",\n \"-e\", \"sip.CSeq\",\n \"-e\", \"sip.r-uri\",\n \"-e\", \"sdp.media\",\n \"-e\", \"sdp.connection_info\",\n \"-E\", \"header=n\",\n \"-E\", \"separator=\\t\",\n \"-E\", \"occurrence=f\",\n ]);\n\n const callMap = new Map<string, SipMessage[]>();\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n const fields = line.split(\"\\t\");\n const [time, src, dst, method, statusCodeStr, statusLine, cid, from, to, cseq, ruri, sdpMedia, sdpConn] = fields;\n if (!cid) continue;\n\n const msg: SipMessage = {\n time: time || \"0\",\n src: src || \"\",\n dst: dst || \"\",\n callId: cid,\n from: from || \"\",\n to: to || \"\",\n cseq: cseq || \"\",\n };\n if (method) msg.method = method;\n if (statusCodeStr) msg.statusCode = parseInt(statusCodeStr);\n if (statusLine) msg.statusLine = statusLine;\n if (ruri) msg.requestUri = ruri;\n if (sdpMedia) msg.sdpMedia = sdpMedia;\n if (sdpConn) msg.sdpConnectionInfo = sdpConn;\n\n if (!callMap.has(cid)) callMap.set(cid, []);\n callMap.get(cid)!.push(msg);\n }\n\n const flows: SipCallFlow[] = [];\n for (const [cid, messages] of callMap) {\n const firstMsg = messages[0];\n const lastMsg = messages[messages.length - 1];\n const answered = messages.some((m) => m.statusCode === 200 && m.cseq?.includes(\"INVITE\"));\n\n // Setup time: INVITE \u2192 first 200 OK for INVITE\n let setupTimeMs: number | undefined;\n const invite = messages.find((m) => m.method === \"INVITE\");\n const ok200 = messages.find((m) => m.statusCode === 200 && m.cseq?.includes(\"INVITE\"));\n if (invite && ok200) {\n setupTimeMs = Math.round((parseFloat(ok200.time) - parseFloat(invite.time)) * 1000);\n }\n\n flows.push({\n callId: cid,\n from: firstMsg.from,\n to: firstMsg.to,\n messages,\n metrics: {\n firstSeen: firstMsg.time,\n lastSeen: lastMsg.time,\n messageCount: messages.length,\n answered,\n setupTimeMs,\n },\n });\n }\n\n return flows;\n}\n\n// ---------------------------------------------------------------------------\n// Tool: pcap_sccp_messages\n// ---------------------------------------------------------------------------\n\nexport interface ScppMessage {\n time: string;\n src: string;\n dst: string;\n messageId: number;\n messageName: string;\n callingPartyName?: string;\n callingPartyNumber?: string;\n calledPartyName?: string;\n calledPartyNumber?: string;\n callId?: string;\n lineInstance?: string;\n callState?: string;\n}\n\nexport interface ScppAnalysis {\n messages: ScppMessage[];\n devices: string[];\n messageTypes: Record<string, number>;\n totalMessages: number;\n}\n\nexport async function pcapScppMessages(filePath: string, deviceFilter?: string): Promise<ScppAnalysis> {\n validateCapFile(filePath);\n\n const filter = deviceFilter ? `skinny and ip.addr == ${deviceFilter}` : \"skinny\";\n const raw = await runTshark([\n \"-r\", filePath, \"-Y\", filter,\n \"-T\", \"fields\",\n \"-e\", \"frame.time_relative\",\n \"-e\", \"ip.src\",\n \"-e\", \"ip.dst\",\n \"-e\", \"skinny.messageId\",\n \"-e\", \"skinny.CallingPartyName\",\n \"-e\", \"skinny.CallingPartyNumber\",\n \"-e\", \"skinny.calledPartyName\",\n \"-e\", \"skinny.calledPartyNumber\",\n \"-e\", \"skinny.callIdentifier\",\n \"-e\", \"skinny.lineInstance\",\n \"-e\", \"skinny.callState\",\n \"-E\", \"header=n\",\n \"-E\", \"separator=\\t\",\n \"-E\", \"occurrence=f\",\n ]);\n\n const messages: ScppMessage[] = [];\n const devices = new Set<string>();\n const typeCounts: Record<string, number> = {};\n\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n const fields = line.split(\"\\t\");\n const [time, src, dst, msgIdStr, callingName, callingNum, calledName, calledNum, callIdStr, lineInst, callSt] = fields;\n\n const messageId = parseInt(msgIdStr || \"0\");\n const messageName = skinnyMessageName(messageId);\n\n devices.add(src || \"\");\n devices.add(dst || \"\");\n typeCounts[messageName] = (typeCounts[messageName] || 0) + 1;\n\n const msg: ScppMessage = {\n time: time || \"0\",\n src: src || \"\",\n dst: dst || \"\",\n messageId,\n messageName,\n };\n if (callingName) msg.callingPartyName = callingName;\n if (callingNum) msg.callingPartyNumber = callingNum;\n if (calledName) msg.calledPartyName = calledName;\n if (calledNum) msg.calledPartyNumber = calledNum;\n if (callIdStr) msg.callId = callIdStr;\n if (lineInst) msg.lineInstance = lineInst;\n if (callSt) msg.callState = callSt;\n\n messages.push(msg);\n }\n\n // Remove empty strings from devices\n devices.delete(\"\");\n\n return {\n messages,\n devices: [...devices].sort(),\n messageTypes: typeCounts,\n totalMessages: messages.length,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool: pcap_rtp_streams\n// ---------------------------------------------------------------------------\n\nexport interface RtpStream {\n src: string;\n dst: string;\n ssrc: string;\n payloadType: number;\n codec: string;\n packets: number;\n lost: number;\n lossPercent: number;\n maxDelta: number;\n maxJitter: number;\n meanJitter: number;\n}\n\nexport interface RtpAnalysis {\n streams: RtpStream[];\n summary: {\n totalStreams: number;\n worstLoss: string;\n worstJitter: string;\n };\n}\n\nexport async function pcapRtpStreams(filePath: string, ssrcFilter?: string): Promise<RtpAnalysis> {\n validateCapFile(filePath);\n\n const raw = await runTshark([\"-r\", filePath, \"-q\", \"-z\", \"rtp,streams\"]);\n\n // Parse the rtp,streams table output\n // Format: src_addr src_port dst_addr dst_port ssrc payload packets lost max_delta max_jitter mean_jitter ...\n const streams: RtpStream[] = [];\n const lines = raw.split(\"\\n\");\n let inTable = false;\n\n for (const line of lines) {\n if (line.includes(\"========================\")) {\n inTable = !inTable;\n continue;\n }\n if (!inTable) continue;\n if (!line.trim() || line.startsWith(\" Src\")) continue;\n\n // Parse whitespace-separated fields\n const parts = line.trim().split(/\\s+/);\n if (parts.length < 10) continue;\n\n const srcAddr = parts[0];\n const srcPort = parts[1];\n const dstAddr = parts[2];\n const dstPort = parts[3];\n const ssrc = parts[4];\n const ptStr = parts[5];\n const packets = parseInt(parts[6]) || 0;\n const lost = parseInt(parts[7]) || 0;\n const maxDelta = parseFloat(parts[8]) || 0;\n const maxJitter = parseFloat(parts[9]) || 0;\n const meanJitter = parseFloat(parts[10]) || 0;\n\n const pt = parseInt(ptStr) || 0;\n\n if (ssrcFilter && ssrc !== ssrcFilter) continue;\n\n streams.push({\n src: `${srcAddr}:${srcPort}`,\n dst: `${dstAddr}:${dstPort}`,\n ssrc,\n payloadType: pt,\n codec: rtpCodecName(pt),\n packets,\n lost,\n lossPercent: packets > 0 ? Math.round((lost / packets) * 10000) / 100 : 0,\n maxDelta,\n maxJitter,\n meanJitter,\n });\n }\n\n const worstLoss = streams.length ? Math.max(...streams.map((s) => s.lossPercent)) : 0;\n const worstJitter = streams.length ? Math.max(...streams.map((s) => s.maxJitter)) : 0;\n\n return {\n streams,\n summary: {\n totalStreams: streams.length,\n worstLoss: `${worstLoss}%`,\n worstJitter: `${worstJitter}ms`,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool: pcap_protocol_filter\n// ---------------------------------------------------------------------------\n\nexport async function pcapProtocolFilter(\n filePath: string,\n displayFilter: string,\n fields?: string[],\n maxPackets?: number\n): Promise<object[]> {\n validateCapFile(filePath);\n\n const max = Math.min(maxPackets ?? 100, 1000);\n const args = [\"-r\", filePath, \"-Y\", displayFilter, \"-c\", String(max)];\n\n if (fields && fields.length > 0) {\n args.push(\"-T\", \"fields\", \"-E\", \"header=n\", \"-E\", \"separator=\\t\");\n for (const f of fields) {\n args.push(\"-e\", f);\n }\n const raw = await runTshark(args);\n const results: object[] = [];\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n const values = line.split(\"\\t\");\n const obj: Record<string, string> = {};\n fields.forEach((f, i) => {\n obj[f] = values[i] || \"\";\n });\n results.push(obj);\n }\n return results;\n }\n\n // No specific fields: use ek (Elastic/JSON) output for full packet info\n // Use -T tabs with common fields as fallback (full JSON can be huge)\n args.push(\n \"-T\", \"fields\",\n \"-E\", \"header=n\",\n \"-E\", \"separator=\\t\",\n \"-e\", \"frame.number\",\n \"-e\", \"frame.time_relative\",\n \"-e\", \"ip.src\",\n \"-e\", \"ip.dst\",\n \"-e\", \"frame.protocols\",\n \"-e\", \"frame.len\",\n );\n\n const raw = await runTshark(args);\n const defaultFields = [\"frame.number\", \"frame.time_relative\", \"ip.src\", \"ip.dst\", \"frame.protocols\", \"frame.len\"];\n const results: object[] = [];\n for (const line of raw.split(\"\\n\")) {\n if (!line.trim()) continue;\n const values = line.split(\"\\t\");\n const obj: Record<string, string> = {};\n defaultFields.forEach((f, i) => {\n obj[f] = values[i] || \"\";\n });\n results.push(obj);\n }\n return results;\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,gBAAgB;AACzB,SAAS,YAAY,gBAAgB;AAMrC,MAAM,oBAAoB;AAAA,EACxB,QAAQ,IAAI;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,OAAO,OAAO;AAEhB,IAAI,mBAAkC;AAEtC,SAAS,aAAqB;AAC5B,MAAI,iBAAkB,QAAO;AAC7B,aAAW,aAAa,mBAAmB;AACzC,QAAI;AACF,UAAI,WAAW,SAAS,GAAG;AACzB,2BAAmB;AACnB,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,oBAAoB,OAAO,QAAQ,IAAI,0BAA0B,KAAK;AAMrE,SAAS,UAAU,MAAgB,WAAqC;AAC7E,QAAM,MAAM,WAAW;AACvB,QAAM,UAAU,aAAa;AAC7B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,aAAS,KAAK,MAAM,EAAE,SAAS,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,KAAK,QAAQ,WAAW;AACrF,UAAI,KAAK;AACP,cAAM,MAAM,QAAQ,KAAK,KAAK,IAAI;AAClC,YAAI,IAAI,SAAS,cAAc,KAAK,IAAI,SAAS,eAAe,GAAG;AACjE,iBAAO,IAAI,MAAM,mBAAmB,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;AAAA,QACjH,WAAW,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,QAAQ,GAAG;AAC9D;AAAA,YACE,IAAI;AAAA,cACF,kEAAkE,kBAAkB,KAAK,IAAI,CAAC;AAAA,YAChG;AAAA,UACF;AAAA,QACF,OAAO;AACL,iBAAO,IAAI,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAAA,QAC1C;AACA;AAAA,MACF;AACA,cAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH,CAAC;AACH;AAMA,SAAS,gBAAgB,UAAwB;AAC/C,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,MAAM,2BAA2B,QAAQ,EAAE;AAAA,EACvD;AACA,QAAM,OAAO,SAAS,QAAQ;AAC9B,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,IAAI,MAAM,0BAA0B,QAAQ,EAAE;AAAA,EACtD;AACF;AAMA,MAAM,uBAA+C;AAAA,EACnD,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,GAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA,EACR,IAAQ;AAAA;AAAA,EAER,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AAAA,EACR,KAAQ;AACV;AAEA,SAAS,kBAAkB,IAAoB;AAC7C,SAAO,qBAAqB,EAAE,KAAK,aAAa,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAClF;AAMA,MAAM,oBAA4C;AAAA,EAChD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAEA,SAAS,aAAa,IAAoB;AACxC,SAAO,kBAAkB,EAAE,KAAK,MAAM,EAAE;AAC1C;AAMA,eAAsB,gBAAgB,UAAmC;AACvE,kBAAgB,QAAQ;AACxB,QAAM,OAAO,SAAS,QAAQ;AAG9B,QAAM,SAAS,MAAM,UAAU,CAAC,MAAM,UAAU,MAAM,MAAM,QAAQ,CAAC;AAGrE,QAAM,YAA+D,CAAC;AACtE,aAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,UAAM,QAAQ,KAAK,MAAM,2CAA2C;AACpE,QAAI,OAAO;AACT,YAAM,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,EAAG,YAAY;AACrD,UAAI,CAAC,OAAO,UAAU,OAAO,QAAQ,OAAO,QAAQ,OAAO,OAAO,MAAM,KAAK,EAAE,SAAS,KAAK,GAAG;AAC9F,kBAAU,KAAK,IAAI,EAAE,QAAQ,SAAS,MAAM,CAAC,CAAC,GAAG,OAAO,SAAS,MAAM,CAAC,CAAC,EAAE;AAAA,MAC7E;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAuB,CAAC;AAC5B,MAAI;AACF,UAAM,SAAS,MAAM,UAAU;AAAA,MAC7B;AAAA,MAAM;AAAA,MAAU;AAAA,MAAM;AAAA,MAAO;AAAA,MAAM;AAAA,MACnC;AAAA,MAAM;AAAA,MAAe;AAAA,MAAM;AAAA,IAC7B,CAAC;AACD,iBAAa,CAAC,GAAG,IAAI,IAAI,OAAO,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AAAA,EACnF,QAAQ;AAAA,EAAuB;AAG/B,MAAI,iBAAiB;AACrB,MAAI;AACF,UAAM,SAAS,MAAM,UAAU,CAAC,MAAM,UAAU,MAAM,MAAM,aAAa,CAAC;AAC1E,UAAM,WAAW,OAAO,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,eAAe,KAAK,CAAC,CAAC;AACxE,qBAAiB,SAAS;AAAA,EAC5B,QAAQ;AAAA,EAAe;AAGvB,MAAI,YAAsB,CAAC;AAC3B,MAAI;AACF,UAAM,UAAU,MAAM,UAAU,CAAC,MAAM,UAAU,MAAM,MAAM,SAAS,CAAC;AACvE,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,YAAM,IAAI,KAAK,MAAM,mDAAmD;AACxE,UAAI,GAAG;AAAE,YAAI,IAAI,EAAE,CAAC,CAAC;AAAG,YAAI,IAAI,EAAE,CAAC,CAAC;AAAA,MAAG;AAAA,IACzC;AACA,gBAAY,CAAC,GAAG,GAAG,EAAE,KAAK;AAAA,EAC5B,QAAQ;AAAA,EAAqB;AAG7B,MAAI,cAAc;AAClB,MAAI;AACF,UAAM,SAAS,MAAM,UAAU;AAAA,MAC7B;AAAA,MAAM;AAAA,MAAU;AAAA,MAAM;AAAA,MAAU;AAAA,MAAM;AAAA,MACtC;AAAA,MAAM;AAAA,MAAY;AAAA,MAAM;AAAA,MAAK;AAAA,MAAM;AAAA,IACrC,CAAC;AAED,UAAM,SAAS,MAAM,UAAU;AAAA,MAC7B;AAAA,MAAM;AAAA,MAAU;AAAA,MAAM;AAAA,MAAM;AAAA,IAC9B,CAAC;AACD,UAAM,WAAW,OAAO,MAAM,sBAAsB;AACpD,QAAI,SAAU,eAAc,WAAW,SAAS,CAAC,CAAC;AAAA,EACpD,QAAQ;AAAA,EAAe;AAGvB,MAAI,eAAe;AACnB,aAAW,KAAK,OAAO,OAAO,SAAS,GAAG;AACxC,QAAI,EAAE,SAAS,aAAc,gBAAe,EAAE;AAAA,EAChD;AAEA,iBAAe,UAAU,IAAI,GAAG,UAAU,UAAU,KAAK,GAAG,UAAU;AAEtE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,KAAK;AAAA,IACZ,SAAS;AAAA,IACT,UAAU,GAAG,YAAY,QAAQ,CAAC,CAAC;AAAA,IACnC,WAAW;AAAA,MACT,KAAK,UAAU,KAAK,GAAG,UAAU;AAAA,MACjC,QAAQ,UAAU,QAAQ,GAAG,UAAU;AAAA,MACvC,KAAK,UAAU,KAAK,GAAG,UAAU;AAAA,MACjC,MAAM,UAAU,MAAM,GAAG,UAAU;AAAA,MACnC,KAAK,UAAU,KAAK,GAAG,UAAU;AAAA,IACnC;AAAA,IACA;AAAA,IACA,UAAU,WAAW;AAAA,IACrB,YAAY,WAAW,UAAU,KAAK,aAAa,WAAW,MAAM,GAAG,EAAE;AAAA,IACzE,YAAY;AAAA,EACd;AACF;AAoCA,eAAsB,aAAa,UAAkB,QAAyC;AAC5F,kBAAgB,QAAQ;AAExB,QAAM,SAAS,SAAS,2BAA2B,MAAM,MAAM;AAC/D,QAAM,MAAM,MAAM,UAAU;AAAA,IAC1B;AAAA,IAAM;AAAA,IAAU;AAAA,IAAM;AAAA,IACtB;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,EACR,CAAC;AAED,QAAM,UAAU,oBAAI,IAA0B;AAE9C,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,QAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAM,SAAS,KAAK,MAAM,GAAI;AAC9B,UAAM,CAAC,MAAM,KAAK,KAAK,QAAQ,eAAe,YAAY,KAAK,MAAM,IAAI,MAAM,MAAM,UAAU,OAAO,IAAI;AAC1G,QAAI,CAAC,IAAK;AAEV,UAAM,MAAkB;AAAA,MACtB,MAAM,QAAQ;AAAA,MACd,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,QAAQ;AAAA,MACR,MAAM,QAAQ;AAAA,MACd,IAAI,MAAM;AAAA,MACV,MAAM,QAAQ;AAAA,IAChB;AACA,QAAI,OAAQ,KAAI,SAAS;AACzB,QAAI,cAAe,KAAI,aAAa,SAAS,aAAa;AAC1D,QAAI,WAAY,KAAI,aAAa;AACjC,QAAI,KAAM,KAAI,aAAa;AAC3B,QAAI,SAAU,KAAI,WAAW;AAC7B,QAAI,QAAS,KAAI,oBAAoB;AAErC,QAAI,CAAC,QAAQ,IAAI,GAAG,EAAG,SAAQ,IAAI,KAAK,CAAC,CAAC;AAC1C,YAAQ,IAAI,GAAG,EAAG,KAAK,GAAG;AAAA,EAC5B;AAEA,QAAM,QAAuB,CAAC;AAC9B,aAAW,CAAC,KAAK,QAAQ,KAAK,SAAS;AACrC,UAAM,WAAW,SAAS,CAAC;AAC3B,UAAM,UAAU,SAAS,SAAS,SAAS,CAAC;AAC5C,UAAM,WAAW,SAAS,KAAK,CAAC,MAAM,EAAE,eAAe,OAAO,EAAE,MAAM,SAAS,QAAQ,CAAC;AAGxF,QAAI;AACJ,UAAM,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AACzD,UAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,eAAe,OAAO,EAAE,MAAM,SAAS,QAAQ,CAAC;AACrF,QAAI,UAAU,OAAO;AACnB,oBAAc,KAAK,OAAO,WAAW,MAAM,IAAI,IAAI,WAAW,OAAO,IAAI,KAAK,GAAI;AAAA,IACpF;AAEA,UAAM,KAAK;AAAA,MACT,QAAQ;AAAA,MACR,MAAM,SAAS;AAAA,MACf,IAAI,SAAS;AAAA,MACb;AAAA,MACA,SAAS;AAAA,QACP,WAAW,SAAS;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,cAAc,SAAS;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AA4BA,eAAsB,iBAAiB,UAAkB,cAA8C;AACrG,kBAAgB,QAAQ;AAExB,QAAM,SAAS,eAAe,yBAAyB,YAAY,KAAK;AACxE,QAAM,MAAM,MAAM,UAAU;AAAA,IAC1B;AAAA,IAAM;AAAA,IAAU;AAAA,IAAM;AAAA,IACtB;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,EACR,CAAC;AAED,QAAM,WAA0B,CAAC;AACjC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,aAAqC,CAAC;AAE5C,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,QAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAM,SAAS,KAAK,MAAM,GAAI;AAC9B,UAAM,CAAC,MAAM,KAAK,KAAK,UAAU,aAAa,YAAY,YAAY,WAAW,WAAW,UAAU,MAAM,IAAI;AAEhH,UAAM,YAAY,SAAS,YAAY,GAAG;AAC1C,UAAM,cAAc,kBAAkB,SAAS;AAE/C,YAAQ,IAAI,OAAO,EAAE;AACrB,YAAQ,IAAI,OAAO,EAAE;AACrB,eAAW,WAAW,KAAK,WAAW,WAAW,KAAK,KAAK;AAE3D,UAAM,MAAmB;AAAA,MACvB,MAAM,QAAQ;AAAA,MACd,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AACA,QAAI,YAAa,KAAI,mBAAmB;AACxC,QAAI,WAAY,KAAI,qBAAqB;AACzC,QAAI,WAAY,KAAI,kBAAkB;AACtC,QAAI,UAAW,KAAI,oBAAoB;AACvC,QAAI,UAAW,KAAI,SAAS;AAC5B,QAAI,SAAU,KAAI,eAAe;AACjC,QAAI,OAAQ,KAAI,YAAY;AAE5B,aAAS,KAAK,GAAG;AAAA,EACnB;AAGA,UAAQ,OAAO,EAAE;AAEjB,SAAO;AAAA,IACL;AAAA,IACA,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK;AAAA,IAC3B,cAAc;AAAA,IACd,eAAe,SAAS;AAAA,EAC1B;AACF;AA6BA,eAAsB,eAAe,UAAkB,YAA2C;AAChG,kBAAgB,QAAQ;AAExB,QAAM,MAAM,MAAM,UAAU,CAAC,MAAM,UAAU,MAAM,MAAM,aAAa,CAAC;AAIvE,QAAM,UAAuB,CAAC;AAC9B,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,MAAI,UAAU;AAEd,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,0BAA0B,GAAG;AAC7C,gBAAU,CAAC;AACX;AAAA,IACF;AACA,QAAI,CAAC,QAAS;AACd,QAAI,CAAC,KAAK,KAAK,KAAK,KAAK,WAAW,OAAO,EAAG;AAG9C,UAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,QAAI,MAAM,SAAS,GAAI;AAEvB,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,UAAU,SAAS,MAAM,CAAC,CAAC,KAAK;AACtC,UAAM,OAAO,SAAS,MAAM,CAAC,CAAC,KAAK;AACnC,UAAM,WAAW,WAAW,MAAM,CAAC,CAAC,KAAK;AACzC,UAAM,YAAY,WAAW,MAAM,CAAC,CAAC,KAAK;AAC1C,UAAM,aAAa,WAAW,MAAM,EAAE,CAAC,KAAK;AAE5C,UAAM,KAAK,SAAS,KAAK,KAAK;AAE9B,QAAI,cAAc,SAAS,WAAY;AAEvC,YAAQ,KAAK;AAAA,MACX,KAAK,GAAG,OAAO,IAAI,OAAO;AAAA,MAC1B,KAAK,GAAG,OAAO,IAAI,OAAO;AAAA,MAC1B;AAAA,MACA,aAAa;AAAA,MACb,OAAO,aAAa,EAAE;AAAA,MACtB;AAAA,MACA;AAAA,MACA,aAAa,UAAU,IAAI,KAAK,MAAO,OAAO,UAAW,GAAK,IAAI,MAAM;AAAA,MACxE;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,QAAQ,SAAS,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI;AACpF,QAAM,cAAc,QAAQ,SAAS,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI;AAEpF,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP,cAAc,QAAQ;AAAA,MACtB,WAAW,GAAG,SAAS;AAAA,MACvB,aAAa,GAAG,WAAW;AAAA,IAC7B;AAAA,EACF;AACF;AAMA,eAAsB,mBACpB,UACA,eACA,QACA,YACmB;AACnB,kBAAgB,QAAQ;AAExB,QAAM,MAAM,KAAK,IAAI,cAAc,KAAK,GAAI;AAC5C,QAAM,OAAO,CAAC,MAAM,UAAU,MAAM,eAAe,MAAM,OAAO,GAAG,CAAC;AAEpE,MAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,SAAK,KAAK,MAAM,UAAU,MAAM,YAAY,MAAM,aAAc;AAChE,eAAW,KAAK,QAAQ;AACtB,WAAK,KAAK,MAAM,CAAC;AAAA,IACnB;AACA,UAAMA,OAAM,MAAM,UAAU,IAAI;AAChC,UAAMC,WAAoB,CAAC;AAC3B,eAAW,QAAQD,KAAI,MAAM,IAAI,GAAG;AAClC,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,YAAM,SAAS,KAAK,MAAM,GAAI;AAC9B,YAAM,MAA8B,CAAC;AACrC,aAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,YAAI,CAAC,IAAI,OAAO,CAAC,KAAK;AAAA,MACxB,CAAC;AACD,MAAAC,SAAQ,KAAK,GAAG;AAAA,IAClB;AACA,WAAOA;AAAA,EACT;AAIA,OAAK;AAAA,IACH;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,IACN;AAAA,IAAM;AAAA,EACR;AAEA,QAAM,MAAM,MAAM,UAAU,IAAI;AAChC,QAAM,gBAAgB,CAAC,gBAAgB,uBAAuB,UAAU,UAAU,mBAAmB,WAAW;AAChH,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,QAAI,CAAC,KAAK,KAAK,EAAG;AAClB,UAAM,SAAS,KAAK,MAAM,GAAI;AAC9B,UAAM,MAA8B,CAAC;AACrC,kBAAc,QAAQ,CAAC,GAAG,MAAM;AAC9B,UAAI,CAAC,IAAI,OAAO,CAAC,KAAK;AAAA,IACxB,CAAC;AACD,YAAQ,KAAK,GAAG;AAAA,EAClB;AACA,SAAO;AACT;",
|
|
6
|
+
"names": ["raw", "results"]
|
|
7
|
+
}
|
package/dist/state.js
CHANGED
|
@@ -1,117 +1,118 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return envPath.trim();
|
|
8
|
-
// Default: a git-ignored file in the working directory.
|
|
9
|
-
// This matches typical MCP dev setup where command runs with --cwd cucm-mcp.
|
|
10
|
-
return `${process.cwd().replace(/\/+$/, "")}/.cucm-mcp-state.json`;
|
|
3
|
+
function defaultStatePath() {
|
|
4
|
+
const envPath = process.env.CUCM_MCP_STATE_PATH;
|
|
5
|
+
if (envPath && envPath.trim()) return envPath.trim();
|
|
6
|
+
return `${process.cwd().replace(/\/+$/, "")}/.cucm-mcp-state.json`;
|
|
11
7
|
}
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
function newEmptyState() {
|
|
9
|
+
return { version: 1, captures: {} };
|
|
14
10
|
}
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
function nowIso() {
|
|
12
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return t;
|
|
24
|
-
return t.slice(t.length - max);
|
|
14
|
+
function clampText(s, max = 2e3) {
|
|
15
|
+
if (s == null) return void 0;
|
|
16
|
+
const t = String(s);
|
|
17
|
+
if (t.length <= max) return t;
|
|
18
|
+
return t.slice(t.length - max);
|
|
25
19
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
function computeExpiresAt({
|
|
21
|
+
startedAt,
|
|
22
|
+
stoppedAt,
|
|
23
|
+
runningTtlMs,
|
|
24
|
+
stoppedTtlMs
|
|
25
|
+
}) {
|
|
26
|
+
const base = stoppedAt ? Date.parse(stoppedAt) : Date.parse(startedAt);
|
|
27
|
+
const ttl = stoppedAt ? stoppedTtlMs : runningTtlMs;
|
|
28
|
+
const ms = Number.isFinite(base) ? base + ttl : Date.now() + ttl;
|
|
29
|
+
return new Date(ms).toISOString();
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return exp <= atMs;
|
|
31
|
+
function isExpired(rec, atMs = Date.now()) {
|
|
32
|
+
const exp = Date.parse(rec.expiresAt);
|
|
33
|
+
if (!Number.isFinite(exp)) return false;
|
|
34
|
+
return exp <= atMs;
|
|
37
35
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
class CaptureStateStore {
|
|
37
|
+
path;
|
|
38
|
+
runningTtlMs;
|
|
39
|
+
stoppedTtlMs;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.path = opts?.path || defaultStatePath();
|
|
42
|
+
this.runningTtlMs = Math.max(6e4, opts?.runningTtlMs ?? 6 * 60 * 6e4);
|
|
43
|
+
this.stoppedTtlMs = Math.max(6e4, opts?.stoppedTtlMs ?? 24 * 60 * 6e4);
|
|
44
|
+
}
|
|
45
|
+
load() {
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(this.path, "utf8");
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
if (!parsed || parsed.version !== 1 || typeof parsed.captures !== "object") return newEmptyState();
|
|
50
|
+
return parsed;
|
|
51
|
+
} catch {
|
|
52
|
+
return newEmptyState();
|
|
46
53
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
}
|
|
55
|
+
save(state) {
|
|
56
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
57
|
+
const tmp = `${this.path}.${process.pid}.tmp`;
|
|
58
|
+
writeFileSync(tmp, `${JSON.stringify(state, null, 2)}
|
|
59
|
+
`, "utf8");
|
|
60
|
+
renameSync(tmp, this.path);
|
|
61
|
+
}
|
|
62
|
+
pruneExpired(state) {
|
|
63
|
+
const s = state || this.load();
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const captures = {};
|
|
66
|
+
for (const [k, v] of Object.entries(s.captures || {})) {
|
|
67
|
+
if (!v || typeof v !== "object") continue;
|
|
68
|
+
if (isExpired(v, now)) continue;
|
|
69
|
+
captures[k] = v;
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
runningTtlMs: this.runningTtlMs,
|
|
85
|
-
stoppedTtlMs: this.stoppedTtlMs,
|
|
86
|
-
});
|
|
87
|
-
state.captures[rec.id] = {
|
|
88
|
-
...rec,
|
|
89
|
-
lastStdout: clampText(rec.lastStdout),
|
|
90
|
-
lastStderr: clampText(rec.lastStderr),
|
|
91
|
-
updatedAt,
|
|
92
|
-
expiresAt,
|
|
93
|
-
};
|
|
94
|
-
this.save(state);
|
|
95
|
-
}
|
|
96
|
-
remove(id) {
|
|
97
|
-
const state = this.load();
|
|
98
|
-
if (state.captures?.[id]) {
|
|
99
|
-
delete state.captures[id];
|
|
100
|
-
this.save(state);
|
|
101
|
-
}
|
|
71
|
+
return { version: 1, captures };
|
|
72
|
+
}
|
|
73
|
+
upsert(rec) {
|
|
74
|
+
const state = this.pruneExpired(this.load());
|
|
75
|
+
const updatedAt = nowIso();
|
|
76
|
+
const expiresAt = computeExpiresAt({
|
|
77
|
+
startedAt: rec.startedAt,
|
|
78
|
+
stoppedAt: rec.stoppedAt,
|
|
79
|
+
runningTtlMs: this.runningTtlMs,
|
|
80
|
+
stoppedTtlMs: this.stoppedTtlMs
|
|
81
|
+
});
|
|
82
|
+
state.captures[rec.id] = {
|
|
83
|
+
...rec,
|
|
84
|
+
lastStdout: clampText(rec.lastStdout),
|
|
85
|
+
lastStderr: clampText(rec.lastStderr),
|
|
86
|
+
updatedAt,
|
|
87
|
+
expiresAt
|
|
88
|
+
};
|
|
89
|
+
this.save(state);
|
|
90
|
+
}
|
|
91
|
+
remove(id) {
|
|
92
|
+
const state = this.load();
|
|
93
|
+
if (state.captures?.[id]) {
|
|
94
|
+
delete state.captures[id];
|
|
95
|
+
this.save(state);
|
|
102
96
|
}
|
|
97
|
+
}
|
|
103
98
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return new CaptureStateStore({
|
|
113
|
-
path: defaultStatePath(),
|
|
114
|
-
runningTtlMs: Number.isFinite(running) ? running : undefined,
|
|
115
|
-
stoppedTtlMs: Number.isFinite(stopped) ? stopped : undefined,
|
|
116
|
-
});
|
|
99
|
+
function defaultStateStore() {
|
|
100
|
+
const running = process.env.CUCM_MCP_CAPTURE_RUNNING_TTL_MS ? Number.parseInt(process.env.CUCM_MCP_CAPTURE_RUNNING_TTL_MS, 10) : void 0;
|
|
101
|
+
const stopped = process.env.CUCM_MCP_CAPTURE_STOPPED_TTL_MS ? Number.parseInt(process.env.CUCM_MCP_CAPTURE_STOPPED_TTL_MS, 10) : void 0;
|
|
102
|
+
return new CaptureStateStore({
|
|
103
|
+
path: defaultStatePath(),
|
|
104
|
+
runningTtlMs: Number.isFinite(running) ? running : void 0,
|
|
105
|
+
stoppedTtlMs: Number.isFinite(stopped) ? stopped : void 0
|
|
106
|
+
});
|
|
117
107
|
}
|
|
108
|
+
export {
|
|
109
|
+
CaptureStateStore,
|
|
110
|
+
clampText,
|
|
111
|
+
computeExpiresAt,
|
|
112
|
+
defaultStatePath,
|
|
113
|
+
defaultStateStore,
|
|
114
|
+
isExpired,
|
|
115
|
+
newEmptyState,
|
|
116
|
+
nowIso
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/state.ts"],
|
|
4
|
+
"sourcesContent": ["import { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nexport type CaptureState = {\n version: 1;\n captures: Record<string, CaptureStateRecord>;\n};\n\nexport type CaptureStateRecord = {\n id: string;\n host: string;\n startedAt: string;\n stoppedAt?: string;\n iface: string;\n fileBase: string;\n remoteFilePath: string;\n remoteFileCandidates: string[];\n stopTimedOut?: boolean;\n lastStdout?: string;\n lastStderr?: string;\n exitCode?: number | null;\n updatedAt: string;\n expiresAt: string;\n};\n\nexport function defaultStatePath(): string {\n // Prefer explicit env var.\n const envPath = process.env.CUCM_MCP_STATE_PATH;\n if (envPath && envPath.trim()) return envPath.trim();\n\n // Default: a git-ignored file in the working directory.\n // This matches typical MCP dev setup where command runs with --cwd cucm-mcp.\n return `${process.cwd().replace(/\\/+$/, \"\")}/.cucm-mcp-state.json`;\n}\n\nexport function newEmptyState(): CaptureState {\n return { version: 1, captures: {} };\n}\n\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n\nexport function clampText(s: unknown, max = 2000): string | undefined {\n if (s == null) return undefined;\n const t = String(s);\n if (t.length <= max) return t;\n return t.slice(t.length - max);\n}\n\nexport function computeExpiresAt({\n startedAt,\n stoppedAt,\n runningTtlMs,\n stoppedTtlMs,\n}: {\n startedAt: string;\n stoppedAt?: string;\n runningTtlMs: number;\n stoppedTtlMs: number;\n}): string {\n const base = stoppedAt ? Date.parse(stoppedAt) : Date.parse(startedAt);\n const ttl = stoppedAt ? stoppedTtlMs : runningTtlMs;\n const ms = Number.isFinite(base) ? base + ttl : Date.now() + ttl;\n return new Date(ms).toISOString();\n}\n\nexport function isExpired(rec: CaptureStateRecord, atMs = Date.now()): boolean {\n const exp = Date.parse(rec.expiresAt);\n if (!Number.isFinite(exp)) return false;\n return exp <= atMs;\n}\n\nexport class CaptureStateStore {\n readonly path: string;\n readonly runningTtlMs: number;\n readonly stoppedTtlMs: number;\n\n constructor(opts?: { path?: string; runningTtlMs?: number; stoppedTtlMs?: number }) {\n this.path = opts?.path || defaultStatePath();\n this.runningTtlMs = Math.max(60_000, opts?.runningTtlMs ?? 6 * 60 * 60_000);\n this.stoppedTtlMs = Math.max(60_000, opts?.stoppedTtlMs ?? 24 * 60 * 60_000);\n }\n\n load(): CaptureState {\n try {\n const raw = readFileSync(this.path, \"utf8\");\n const parsed = JSON.parse(raw);\n if (!parsed || parsed.version !== 1 || typeof parsed.captures !== \"object\") return newEmptyState();\n return parsed as CaptureState;\n } catch {\n return newEmptyState();\n }\n }\n\n save(state: CaptureState) {\n mkdirSync(dirname(this.path), { recursive: true });\n const tmp = `${this.path}.${process.pid}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n renameSync(tmp, this.path);\n }\n\n pruneExpired(state?: CaptureState): CaptureState {\n const s = state || this.load();\n const now = Date.now();\n const captures: Record<string, CaptureStateRecord> = {};\n for (const [k, v] of Object.entries(s.captures || {})) {\n if (!v || typeof v !== \"object\") continue;\n if (isExpired(v as CaptureStateRecord, now)) continue;\n captures[k] = v as CaptureStateRecord;\n }\n return { version: 1, captures };\n }\n\n upsert(rec: Omit<CaptureStateRecord, \"updatedAt\" | \"expiresAt\">) {\n const state = this.pruneExpired(this.load());\n const updatedAt = nowIso();\n const expiresAt = computeExpiresAt({\n startedAt: rec.startedAt,\n stoppedAt: rec.stoppedAt,\n runningTtlMs: this.runningTtlMs,\n stoppedTtlMs: this.stoppedTtlMs,\n });\n\n state.captures[rec.id] = {\n ...rec,\n lastStdout: clampText(rec.lastStdout),\n lastStderr: clampText(rec.lastStderr),\n updatedAt,\n expiresAt,\n };\n this.save(state);\n }\n\n remove(id: string) {\n const state = this.load();\n if (state.captures?.[id]) {\n delete state.captures[id];\n this.save(state);\n }\n }\n}\n\nexport function defaultStateStore(): CaptureStateStore {\n // Allow env tuning.\n const running = process.env.CUCM_MCP_CAPTURE_RUNNING_TTL_MS\n ? Number.parseInt(process.env.CUCM_MCP_CAPTURE_RUNNING_TTL_MS, 10)\n : undefined;\n const stopped = process.env.CUCM_MCP_CAPTURE_STOPPED_TTL_MS\n ? Number.parseInt(process.env.CUCM_MCP_CAPTURE_STOPPED_TTL_MS, 10)\n : undefined;\n return new CaptureStateStore({\n path: defaultStatePath(),\n runningTtlMs: Number.isFinite(running) ? (running as number) : undefined,\n stoppedTtlMs: Number.isFinite(stopped) ? (stopped as number) : undefined,\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/time.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return `${date} ${time}`;
|
|
1
|
+
function formatCucmDateTime(d) {
|
|
2
|
+
const date = new Intl.DateTimeFormat("en-US", {
|
|
3
|
+
month: "2-digit",
|
|
4
|
+
day: "2-digit",
|
|
5
|
+
year: "2-digit"
|
|
6
|
+
}).format(d);
|
|
7
|
+
const time = new Intl.DateTimeFormat("en-US", {
|
|
8
|
+
hour: "numeric",
|
|
9
|
+
minute: "2-digit",
|
|
10
|
+
hour12: true
|
|
11
|
+
}).format(d);
|
|
12
|
+
return `${date} ${time}`;
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const m = abs % 60;
|
|
24
|
-
return `Client: (GMT${sign}${h}:${m})${tz}`;
|
|
14
|
+
function guessTimezoneString(now = /* @__PURE__ */ new Date()) {
|
|
15
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
16
|
+
const offsetMin = -now.getTimezoneOffset();
|
|
17
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
18
|
+
const abs = Math.abs(offsetMin);
|
|
19
|
+
const h = Math.floor(abs / 60);
|
|
20
|
+
const m = abs % 60;
|
|
21
|
+
return `Client: (GMT${sign}${h}:${m})${tz}`;
|
|
25
22
|
}
|
|
23
|
+
export {
|
|
24
|
+
formatCucmDateTime,
|
|
25
|
+
guessTimezoneString
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=time.js.map
|
package/dist/time.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/time.ts"],
|
|
4
|
+
"sourcesContent": ["export function formatCucmDateTime(d: Date): string {\n // DIME examples use: \"MM/DD/YY HH:MM AM\" (US locale)\n const date = new Intl.DateTimeFormat(\"en-US\", {\n month: \"2-digit\",\n day: \"2-digit\",\n year: \"2-digit\",\n }).format(d);\n const time = new Intl.DateTimeFormat(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true,\n }).format(d);\n return `${date} ${time}`;\n}\n\nexport function guessTimezoneString(now = new Date()): string {\n // Best-effort DIME timezone string:\n // \"Client: (GMT-8:0)America/Los_Angeles\"\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n const offsetMin = -now.getTimezoneOffset();\n const sign = offsetMin >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offsetMin);\n const h = Math.floor(abs / 60);\n const m = abs % 60;\n return `Client: (GMT${sign}${h}:${m})${tz}`;\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,mBAAmB,GAAiB;AAElD,QAAM,OAAO,IAAI,KAAK,eAAe,SAAS;AAAA,IAC5C,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,EACR,CAAC,EAAE,OAAO,CAAC;AACX,QAAM,OAAO,IAAI,KAAK,eAAe,SAAS;AAAA,IAC5C,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC,EAAE,OAAO,CAAC;AACX,SAAO,GAAG,IAAI,IAAI,IAAI;AACxB;AAEO,SAAS,oBAAoB,MAAM,oBAAI,KAAK,GAAW;AAG5D,QAAM,KAAK,KAAK,eAAe,EAAE,gBAAgB,EAAE,YAAY;AAC/D,QAAM,YAAY,CAAC,IAAI,kBAAkB;AACzC,QAAM,OAAO,aAAa,IAAI,MAAM;AACpC,QAAM,MAAM,KAAK,IAAI,SAAS;AAC9B,QAAM,IAAI,KAAK,MAAM,MAAM,EAAE;AAC7B,QAAM,IAAI,MAAM;AAChB,SAAO,eAAe,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calltelemetry/cucm-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"private": false,
|
|
3
|
+
"version": "0.2.3",
|
|
5
4
|
"type": "module",
|
|
6
5
|
"description": "MCP server for CUCM tooling (DIME logs, syslog, packet capture)",
|
|
7
6
|
"license": "UNLICENSED",
|
|
@@ -9,9 +8,7 @@
|
|
|
9
8
|
"type": "git",
|
|
10
9
|
"url": "git+https://github.com/calltelemetry/cucm-mcp.git"
|
|
11
10
|
},
|
|
12
|
-
"bin":
|
|
13
|
-
"cucm-mcp": "dist/index.js"
|
|
14
|
-
},
|
|
11
|
+
"bin": "dist/index.js",
|
|
15
12
|
"publishConfig": {
|
|
16
13
|
"access": "public"
|
|
17
14
|
},
|
|
@@ -25,7 +22,7 @@
|
|
|
25
22
|
},
|
|
26
23
|
"scripts": {
|
|
27
24
|
"start": "tsx src/index.ts",
|
|
28
|
-
"build": "
|
|
25
|
+
"build": "esbuild src/*.ts --platform=node --target=node18 --format=esm --outdir=dist --packages=external --sourcemap && node scripts/chmod-bin.mjs",
|
|
29
26
|
"typecheck": "tsc --noEmit",
|
|
30
27
|
"test": "yarn build && node --test test/*.test.js",
|
|
31
28
|
"prepack": "yarn test"
|
|
@@ -39,6 +36,7 @@
|
|
|
39
36
|
"devDependencies": {
|
|
40
37
|
"@types/node": "^25.1.0",
|
|
41
38
|
"@types/ssh2": "^1.15.4",
|
|
39
|
+
"esbuild": "^0.27.3",
|
|
42
40
|
"tsx": "^4.19.4",
|
|
43
41
|
"typescript": "^5.7.3"
|
|
44
42
|
}
|