zenitel-client 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.
- package/README.md +526 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +338 -0
- package/dist/client.d.ts +136 -0
- package/dist/client.js +709 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/scanner.d.ts +10 -0
- package/dist/scanner.js +198 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.js +6 -0
- package/package.json +50 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ZenitelClient } from './client.js';
|
|
2
|
+
export { scanNetwork } from './scanner.js';
|
|
3
|
+
export type { ZenitelClientOptions, ZenitelDevice, ScanOptions, DeviceInfo, RelayOptions, RelayStatus, CallStatus, SIPConfig, ProvisionConfig, AudioSettings, AudioOutputDevice, AudioInputDevice, AECSettings, ANCSettings, FESSSettings, DRCSettings, AVCSettings, } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network scanner — Discover Zenitel intercoms on the local network
|
|
3
|
+
*
|
|
4
|
+
* Strategy A: ARP table + OUI filter (MAC prefix 00:13:cb = Zenitel)
|
|
5
|
+
* Strategy B: HTTP probe with fingerprinting (zenitel.js in HTML)
|
|
6
|
+
*
|
|
7
|
+
* Both strategies run in parallel and results are merged by IP.
|
|
8
|
+
*/
|
|
9
|
+
import type { ZenitelDevice, ScanOptions } from './types.js';
|
|
10
|
+
export declare function scanNetwork(opts?: ScanOptions): Promise<ZenitelDevice[]>;
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network scanner — Discover Zenitel intercoms on the local network
|
|
3
|
+
*
|
|
4
|
+
* Strategy A: ARP table + OUI filter (MAC prefix 00:13:cb = Zenitel)
|
|
5
|
+
* Strategy B: HTTP probe with fingerprinting (zenitel.js in HTML)
|
|
6
|
+
*
|
|
7
|
+
* Both strategies run in parallel and results are merged by IP.
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { platform } from 'node:os';
|
|
11
|
+
import { networkInterfaces } from 'node:os';
|
|
12
|
+
import { ZenitelClient } from './client.js';
|
|
13
|
+
const ZENITEL_OUI = '00:13:cb';
|
|
14
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
15
|
+
export async function scanNetwork(opts) {
|
|
16
|
+
const timeout = opts?.timeout ?? 5000;
|
|
17
|
+
const strategies = opts?.strategies ?? ['arp-oui', 'http-probe'];
|
|
18
|
+
const subnet = opts?.subnet ?? detectSubnet();
|
|
19
|
+
const results = new Map();
|
|
20
|
+
const tasks = [];
|
|
21
|
+
if (strategies.includes('arp-oui')) {
|
|
22
|
+
tasks.push(arpOuiScan().catch(() => []));
|
|
23
|
+
}
|
|
24
|
+
if (strategies.includes('http-probe') && subnet) {
|
|
25
|
+
tasks.push(httpProbeScan(subnet, timeout).catch(() => []));
|
|
26
|
+
}
|
|
27
|
+
const all = await Promise.all(tasks);
|
|
28
|
+
// Merge by IP (prefer entries with more data)
|
|
29
|
+
for (const batch of all) {
|
|
30
|
+
for (const dev of batch) {
|
|
31
|
+
const existing = results.get(dev.ip);
|
|
32
|
+
if (!existing || (!existing.model && dev.model)) {
|
|
33
|
+
results.set(dev.ip, { ...existing, ...dev });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Array.from(results.values());
|
|
38
|
+
}
|
|
39
|
+
// ── Strategy A: ARP + OUI ────────────────────────────────────────────────
|
|
40
|
+
async function arpOuiScan() {
|
|
41
|
+
const entries = await getArpTable();
|
|
42
|
+
const zenitels = entries.filter((e) => e.mac.toLowerCase().startsWith(ZENITEL_OUI));
|
|
43
|
+
// For each candidate, try to get device info
|
|
44
|
+
const results = await Promise.all(zenitels.map(async (entry) => {
|
|
45
|
+
const client = new ZenitelClient({ host: entry.ip, timeout: 3000 });
|
|
46
|
+
try {
|
|
47
|
+
const info = await client.getDeviceInfo();
|
|
48
|
+
return {
|
|
49
|
+
ip: entry.ip,
|
|
50
|
+
mac: entry.mac,
|
|
51
|
+
model: info.model,
|
|
52
|
+
firmware: info.firmware,
|
|
53
|
+
hasCamera: info.hasCamera,
|
|
54
|
+
hostname: info.hostname,
|
|
55
|
+
mode: info.mode,
|
|
56
|
+
serialNumber: info.serialNumber,
|
|
57
|
+
hardwareType: info.hardwareType,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Device unreachable or auth failed — return basic info
|
|
62
|
+
return {
|
|
63
|
+
ip: entry.ip,
|
|
64
|
+
mac: entry.mac,
|
|
65
|
+
hasCamera: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
async function getArpTable() {
|
|
72
|
+
const os = platform();
|
|
73
|
+
if (os === 'darwin' || os === 'linux') {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
execFile('arp', ['-a'], (err, stdout) => {
|
|
76
|
+
if (err)
|
|
77
|
+
return reject(err);
|
|
78
|
+
const entries = [];
|
|
79
|
+
for (const line of stdout.split('\n')) {
|
|
80
|
+
// macOS: ? (192.168.1.143) at 0:13:cb:28:35:ca on en0 ifscope [ethernet]
|
|
81
|
+
// Linux: ? (192.168.1.143) at 00:13:cb:28:35:ca [ether] on eth0
|
|
82
|
+
const match = line.match(/\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-f:]+)/i);
|
|
83
|
+
if (match) {
|
|
84
|
+
// Normalize MAC: "0:13:cb:28:35:ca" → "00:13:cb:28:35:ca"
|
|
85
|
+
const mac = match[2]
|
|
86
|
+
.split(':')
|
|
87
|
+
.map((p) => p.padStart(2, '0'))
|
|
88
|
+
.join(':')
|
|
89
|
+
.toLowerCase();
|
|
90
|
+
entries.push({ ip: match[1], mac });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
resolve(entries);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (os === 'win32') {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
execFile('powershell', [
|
|
100
|
+
'-Command',
|
|
101
|
+
`Get-NetNeighbor -AddressFamily IPv4 | Where-Object { $_.State -ne "Unreachable" } | Select-Object IPAddress,LinkLayerAddress | ConvertTo-Json`,
|
|
102
|
+
], (err, stdout) => {
|
|
103
|
+
if (err)
|
|
104
|
+
return reject(err);
|
|
105
|
+
try {
|
|
106
|
+
const raw = JSON.parse(stdout);
|
|
107
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
108
|
+
const entries = items
|
|
109
|
+
.filter((item) => item.LinkLayerAddress)
|
|
110
|
+
.map((item) => ({
|
|
111
|
+
ip: item.IPAddress,
|
|
112
|
+
// Windows uses "00-13-CB-28-35-CA" format
|
|
113
|
+
mac: item.LinkLayerAddress.replace(/-/g, ':').toLowerCase(),
|
|
114
|
+
}));
|
|
115
|
+
resolve(entries);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
resolve([]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
// ── Strategy B: HTTP Probe ───────────────────────────────────────────────
|
|
126
|
+
async function httpProbeScan(subnet, timeout) {
|
|
127
|
+
const ips = expandSubnet(subnet);
|
|
128
|
+
const results = [];
|
|
129
|
+
// Probe all IPs in parallel with short timeout
|
|
130
|
+
const probeTimeout = Math.min(timeout, 1500);
|
|
131
|
+
const batches = chunk(ips, 50); // 50 concurrent
|
|
132
|
+
for (const batch of batches) {
|
|
133
|
+
const probes = batch.map(async (ip) => {
|
|
134
|
+
try {
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
const timer = setTimeout(() => controller.abort(), probeTimeout);
|
|
137
|
+
const res = await fetch(`http://${ip}/`, {
|
|
138
|
+
signal: controller.signal,
|
|
139
|
+
redirect: 'follow',
|
|
140
|
+
});
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
const html = await res.text();
|
|
143
|
+
// Fingerprint: look for zenitel.js or Stentofon
|
|
144
|
+
if (html.includes('zenitel.js') ||
|
|
145
|
+
html.includes('Stentofon') ||
|
|
146
|
+
html.includes('zForm_header')) {
|
|
147
|
+
// Extract firmware from "zenitel.js?version=X.X.X.X"
|
|
148
|
+
const fwMatch = html.match(/version=(\d+\.\d+\.\d+\.\d+)/);
|
|
149
|
+
results.push({
|
|
150
|
+
ip,
|
|
151
|
+
mac: '',
|
|
152
|
+
firmware: fwMatch?.[1],
|
|
153
|
+
hasCamera: false,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Unreachable — skip
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
await Promise.all(probes);
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
166
|
+
function detectSubnet() {
|
|
167
|
+
const interfaces = networkInterfaces();
|
|
168
|
+
for (const iface of Object.values(interfaces)) {
|
|
169
|
+
if (!iface)
|
|
170
|
+
continue;
|
|
171
|
+
for (const addr of iface) {
|
|
172
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
173
|
+
// e.g. "192.168.1.42" + netmask "255.255.255.0" → "192.168.1.0/24"
|
|
174
|
+
const parts = addr.address.split('.');
|
|
175
|
+
parts[3] = '0';
|
|
176
|
+
return `${parts.join('.')}/24`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
function expandSubnet(cidr) {
|
|
183
|
+
// Simple /24 expansion — covers 99% of home/office networks
|
|
184
|
+
const base = cidr.split('/')[0];
|
|
185
|
+
const parts = base.split('.');
|
|
186
|
+
const ips = [];
|
|
187
|
+
for (let i = 1; i <= 254; i++) {
|
|
188
|
+
ips.push(`${parts[0]}.${parts[1]}.${parts[2]}.${i}`);
|
|
189
|
+
}
|
|
190
|
+
return ips;
|
|
191
|
+
}
|
|
192
|
+
function chunk(arr, size) {
|
|
193
|
+
const chunks = [];
|
|
194
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
195
|
+
chunks.push(arr.slice(i, i + size));
|
|
196
|
+
}
|
|
197
|
+
return chunks;
|
|
198
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pinecall/zenitel-client — Type definitions
|
|
3
|
+
*
|
|
4
|
+
* All interfaces for the Zenitel HTTP scraper, scanner, and CLI.
|
|
5
|
+
*/
|
|
6
|
+
export interface ZenitelClientOptions {
|
|
7
|
+
/** IP or hostname of the Zenitel intercom (e.g. "192.168.1.143") */
|
|
8
|
+
host: string;
|
|
9
|
+
/** Web UI username. Default: "admin" */
|
|
10
|
+
user?: string;
|
|
11
|
+
/** Web UI password. Default: "alphaadmin" */
|
|
12
|
+
password?: string;
|
|
13
|
+
/** Protocol. Default: "http" */
|
|
14
|
+
protocol?: 'http' | 'https';
|
|
15
|
+
/** HTTP request timeout in ms. Default: 5000 */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ZenitelDevice {
|
|
19
|
+
ip: string;
|
|
20
|
+
mac: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
firmware?: string;
|
|
23
|
+
hasCamera: boolean;
|
|
24
|
+
hostname?: string;
|
|
25
|
+
mode?: 'sip' | 'edge' | 'pulse';
|
|
26
|
+
serialNumber?: string;
|
|
27
|
+
hardwareType?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ScanOptions {
|
|
30
|
+
/** Scan timeout in ms. Default: 5000 */
|
|
31
|
+
timeout?: number;
|
|
32
|
+
/** Subnet CIDR (e.g. "192.168.1.0/24"). Auto-detected if omitted. */
|
|
33
|
+
subnet?: string;
|
|
34
|
+
/** Which strategies to run. Default: all three. */
|
|
35
|
+
strategies?: ('arp-oui' | 'http-probe')[];
|
|
36
|
+
}
|
|
37
|
+
export interface DeviceInfo {
|
|
38
|
+
model: string;
|
|
39
|
+
firmware: string;
|
|
40
|
+
mac: string;
|
|
41
|
+
ip: string;
|
|
42
|
+
hostname: string;
|
|
43
|
+
serialNumber: string;
|
|
44
|
+
hardwareType: string;
|
|
45
|
+
mode: 'sip' | 'edge' | 'pulse';
|
|
46
|
+
hasCamera: boolean;
|
|
47
|
+
sipDomain?: string;
|
|
48
|
+
sipRegistered?: boolean;
|
|
49
|
+
sipNumber?: string;
|
|
50
|
+
outboundProxy?: string;
|
|
51
|
+
uptime?: string;
|
|
52
|
+
webcallEnabled: boolean;
|
|
53
|
+
platform: string;
|
|
54
|
+
systemModelName?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface RelayOptions {
|
|
57
|
+
/** Relay ID. Default: "relay1". Options: relay1, gpio1-gpio6 */
|
|
58
|
+
relayId?: string;
|
|
59
|
+
/** Timer in seconds. 0 = toggle (stays active until deactivated). Default: 3 */
|
|
60
|
+
timer?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface RelayStatus {
|
|
63
|
+
relay1: 'Activated' | 'Deactivated';
|
|
64
|
+
gpio1: 'Activated' | 'Deactivated';
|
|
65
|
+
gpio2: 'Activated' | 'Deactivated';
|
|
66
|
+
gpio3: 'Activated' | 'Deactivated';
|
|
67
|
+
gpio4: 'Activated' | 'Deactivated';
|
|
68
|
+
gpio5: 'Activated' | 'Deactivated';
|
|
69
|
+
gpio6: 'Activated' | 'Deactivated';
|
|
70
|
+
}
|
|
71
|
+
export interface SIPConfig {
|
|
72
|
+
domain?: string;
|
|
73
|
+
outboundProxy?: string;
|
|
74
|
+
transport?: 'udp' | 'tcp' | 'tls';
|
|
75
|
+
displayName?: string;
|
|
76
|
+
directoryNumber?: string;
|
|
77
|
+
authUsername?: string;
|
|
78
|
+
authPassword?: string;
|
|
79
|
+
}
|
|
80
|
+
export type CallStatus = 'Idle' | 'Calling' | 'Connected' | 'Ringing';
|
|
81
|
+
/** Full provisioning config for a factory-reset Zenitel */
|
|
82
|
+
export interface ProvisionConfig {
|
|
83
|
+
/** SIP registration domain (e.g. "testing-mo16m3gw.sip.twilio.com") */
|
|
84
|
+
sipDomain: string;
|
|
85
|
+
/** SIP auth username (e.g. "zenitel01") */
|
|
86
|
+
sipAuthUser: string;
|
|
87
|
+
/** SIP auth password */
|
|
88
|
+
sipAuthPassword: string;
|
|
89
|
+
/** Outbound proxy address. Defaults to sipDomain if omitted. */
|
|
90
|
+
sipProxy?: string;
|
|
91
|
+
/** SIP transport. Default: "UDP" */
|
|
92
|
+
sipTransport?: 'UDP' | 'TCP' | 'TLS';
|
|
93
|
+
/** Station display name (e.g. "Lobby Intercom"). Defaults to agentNumber. */
|
|
94
|
+
stationName?: string;
|
|
95
|
+
/** The SIP number the call button will dial (e.g. "portia-ae3c") */
|
|
96
|
+
agentNumber: string;
|
|
97
|
+
/** Enable webcall + relay HTTP API. Default: true */
|
|
98
|
+
enableWebcall?: boolean;
|
|
99
|
+
/** Enable auto-answer mode (for AI agent). Default: true */
|
|
100
|
+
autoAnswer?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** Speaker / Line Out output device */
|
|
103
|
+
export interface AudioOutputDevice {
|
|
104
|
+
/** Device identifier: "internal_speaker" or "line_out" */
|
|
105
|
+
kid: string;
|
|
106
|
+
/** Playback gain in dB. Speaker: -10..+13, Line Out: -20..+20 */
|
|
107
|
+
gain: number;
|
|
108
|
+
/** Override gain for priority calls in dB. Speaker: -10..+23, Line Out: -20..+21 */
|
|
109
|
+
overrideGain: number;
|
|
110
|
+
/** Signal source (default: "main_audio_line") */
|
|
111
|
+
signalSource: string;
|
|
112
|
+
/** Output type (e.g. "standard_loudspeaker", "line_out_professional") */
|
|
113
|
+
outputType: string;
|
|
114
|
+
}
|
|
115
|
+
/** Microphone input device */
|
|
116
|
+
export interface AudioInputDevice {
|
|
117
|
+
/** Device identifier: "internal_mic" */
|
|
118
|
+
kid: string;
|
|
119
|
+
/** Mic gain in dB. Range: -10..+10 */
|
|
120
|
+
gain: number;
|
|
121
|
+
/** Input type (e.g. "digital_mic") */
|
|
122
|
+
inputType: string;
|
|
123
|
+
}
|
|
124
|
+
/** AEC — Acoustic Echo Cancellation */
|
|
125
|
+
export interface AECSettings {
|
|
126
|
+
/** Enable echo cancellation */
|
|
127
|
+
enabled: boolean;
|
|
128
|
+
/** Suppression level */
|
|
129
|
+
mode: 'moderate' | 'aggressive';
|
|
130
|
+
}
|
|
131
|
+
/** ANC — Active Noise Cancellation */
|
|
132
|
+
export interface ANCSettings {
|
|
133
|
+
/** Enable noise cancellation */
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
/** Suppression level */
|
|
136
|
+
mode: 'moderate' | 'aggressive';
|
|
137
|
+
}
|
|
138
|
+
/** FESS — Far-End Signal Squelch */
|
|
139
|
+
export interface FESSSettings {
|
|
140
|
+
/** Enable squelch on weak signals */
|
|
141
|
+
enabled: boolean;
|
|
142
|
+
/** Threshold in dBFS (-92..0). Signals below this are silenced. */
|
|
143
|
+
threshold: number;
|
|
144
|
+
/** Activation delay in ms (0..10000) */
|
|
145
|
+
delay: number;
|
|
146
|
+
}
|
|
147
|
+
/** DRC — Dynamic Range Compression (Loudspeaker) */
|
|
148
|
+
export interface DRCSettings {
|
|
149
|
+
/** Enable compression */
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
/** Added gain in dBA (0..20) */
|
|
152
|
+
gain: number;
|
|
153
|
+
}
|
|
154
|
+
/** AVC — Automatic Volume Control */
|
|
155
|
+
export interface AVCSettings {
|
|
156
|
+
/** Enable automatic volume adjustment based on ambient noise */
|
|
157
|
+
enabled: boolean;
|
|
158
|
+
/** Enable digital AVC variant */
|
|
159
|
+
digitalEnabled: boolean;
|
|
160
|
+
/** Ambient noise level (dB) to start adjusting */
|
|
161
|
+
threshold: number;
|
|
162
|
+
/** Maximum noise level (dB) — stops adjusting above this */
|
|
163
|
+
upperThreshold: number;
|
|
164
|
+
/** How fast volume increases (1..100) */
|
|
165
|
+
attackRate: number;
|
|
166
|
+
/** How fast volume decreases (1..100) */
|
|
167
|
+
decayRate: number;
|
|
168
|
+
/** dB margin to prevent volume bouncing */
|
|
169
|
+
hysteresis: number;
|
|
170
|
+
/** Seconds to ignore noise when speaker is active */
|
|
171
|
+
farEndLockoutTime: number;
|
|
172
|
+
}
|
|
173
|
+
/** Complete audio configuration for the Zenitel intercom */
|
|
174
|
+
export interface AudioSettings {
|
|
175
|
+
/** Internal speaker output */
|
|
176
|
+
speaker: AudioOutputDevice;
|
|
177
|
+
/** Line out (auxiliary connector) */
|
|
178
|
+
lineOut: AudioOutputDevice;
|
|
179
|
+
/** Internal microphone */
|
|
180
|
+
mic: AudioInputDevice;
|
|
181
|
+
/** Acoustic Echo Cancellation */
|
|
182
|
+
aec: AECSettings;
|
|
183
|
+
/** Active Noise Cancellation */
|
|
184
|
+
anc: ANCSettings;
|
|
185
|
+
/** Far-End Signal Squelch */
|
|
186
|
+
fess: FESSSettings;
|
|
187
|
+
/** Dynamic Range Compression */
|
|
188
|
+
drc: DRCSettings;
|
|
189
|
+
/** Automatic Volume Control */
|
|
190
|
+
avc: AVCSettings;
|
|
191
|
+
/** Audio mode: "Voice" or "Music" */
|
|
192
|
+
mode: string;
|
|
193
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zenitel-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HTTP client + network scanner for Zenitel intercom systems (TCIV-2+, TCIV-3). Control relays, SIP configuration, DAK provisioning, webcall, and camera feeds.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
|
|
11
|
+
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
|
|
12
|
+
},
|
|
13
|
+
"./scanner": {
|
|
14
|
+
"import": { "types": "./dist/scanner.d.ts", "default": "./dist/scanner.js" },
|
|
15
|
+
"require": { "types": "./dist/scanner.d.ts", "default": "./dist/scanner.js" }
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"zenitel": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"cli": "tsx src/cli.ts"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.8.0",
|
|
30
|
+
"tsx": "^4.21.0",
|
|
31
|
+
"@types/node": "^22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": ["dist", "README.md"],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"zenitel", "intercom", "TCIV", "SIP", "door-access",
|
|
36
|
+
"relay", "DTMF", "webcall", "building-automation"
|
|
37
|
+
],
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/pinecall/zenitel-client"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/pinecall/zenitel-client#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/pinecall/zenitel-client/issues"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
}
|
|
50
|
+
}
|