mcp-3d-printer-server 1.2.25 → 1.2.26
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/CHANGELOG.md +55 -0
- package/CONTRIBUTORS.md +5 -1
- package/README.md +64 -29
- package/dist/ams-mapping.d.ts +38 -0
- package/dist/ams-mapping.js +125 -0
- package/dist/bambu-network-bridge.d.ts +200 -0
- package/dist/bambu-network-bridge.js +881 -0
- package/dist/bambu-network-certs.d.ts +13 -0
- package/dist/bambu-network-certs.js +152 -0
- package/dist/control-reply.d.ts +21 -0
- package/dist/control-reply.js +44 -0
- package/dist/counterpart/camera-sources.d.ts +16 -0
- package/dist/counterpart/camera-sources.js +138 -0
- package/dist/counterpart/camera-stream.d.ts +66 -0
- package/dist/counterpart/camera-stream.js +246 -0
- package/dist/counterpart/camera-tools.d.ts +247 -0
- package/dist/counterpart/camera-tools.js +35 -0
- package/dist/counterpart/contract.d.ts +21 -5
- package/dist/counterpart/contract.js +8 -2
- package/dist/counterpart/route.d.ts +31 -0
- package/dist/counterpart/route.js +70 -0
- package/dist/counterpart/service.d.ts +60 -4
- package/dist/counterpart/service.js +142 -16
- package/dist/design-guidance.d.ts +26 -0
- package/dist/design-guidance.js +177 -0
- package/dist/fulu-orca.d.ts +7 -0
- package/dist/fulu-orca.js +18 -2
- package/dist/index.js +512 -62
- package/dist/printers/bambu-filaments.d.ts +68 -0
- package/dist/printers/bambu-filaments.js +262 -0
- package/dist/printers/bambu.d.ts +99 -6
- package/dist/printers/bambu.js +478 -72
- package/dist/printers/creality.d.ts +2 -1
- package/dist/printers/creality.js +5 -1
- package/dist/printers/generic-base.d.ts +11 -6
- package/dist/printers/generic-base.js +21 -25
- package/dist/printers/klipper.d.ts +2 -1
- package/dist/printers/klipper.js +10 -7
- package/dist/printers/octoprint.d.ts +2 -1
- package/dist/printers/octoprint.js +15 -8
- package/dist/printers/printer-factory.d.ts +1 -1
- package/dist/printers/printer-factory.js +3 -3
- package/dist/printers/prusa.js +0 -1
- package/dist/safety/confirmation.d.ts +11 -16
- package/dist/safety/confirmation.js +8 -32
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/limits.d.ts +8 -2
- package/dist/safety/limits.js +14 -5
- package/dist/safety/print-file.d.ts +5 -0
- package/dist/safety/print-file.js +156 -19
- package/dist/safety/printer-state.d.ts +59 -8
- package/dist/safety/printer-state.js +174 -39
- package/dist/slicer/bambu-cli.d.ts +2 -0
- package/dist/slicer/bambu-cli.js +7 -5
- package/dist/slicer/layer-pauses.d.ts +47 -0
- package/dist/slicer/layer-pauses.js +130 -0
- package/dist/stl/stl-manipulator.d.ts +1 -0
- package/dist/stl/stl-manipulator.js +40 -0
- package/dist/studio-print-options.d.ts +57 -0
- package/dist/studio-print-options.js +179 -0
- package/package.json +25 -4
- package/server.json +2 -2
- package/src/ams-mapping.ts +132 -0
- package/src/bambu-network-bridge.ts +1075 -0
- package/src/bambu-network-certs.ts +154 -0
- package/src/control-reply.ts +41 -0
- package/src/counterpart/camera-sources.ts +102 -0
- package/src/counterpart/camera-stream.ts +195 -0
- package/src/counterpart/camera-tools.ts +35 -0
- package/src/counterpart/contract.ts +12 -4
- package/src/counterpart/route.ts +82 -0
- package/src/counterpart/service.ts +150 -16
- package/src/design-guidance.ts +188 -0
- package/src/fulu-orca.ts +18 -2
- package/src/index.ts +526 -75
- package/src/printers/bambu-filaments.ts +346 -0
- package/src/printers/bambu.ts +506 -81
- package/src/printers/creality.ts +8 -3
- package/src/printers/generic-base.ts +33 -40
- package/src/printers/klipper.ts +12 -8
- package/src/printers/octoprint.ts +16 -9
- package/src/printers/printer-factory.ts +3 -3
- package/src/printers/prusa.ts +0 -1
- package/src/safety/confirmation.ts +15 -38
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/limits.ts +15 -5
- package/src/safety/print-file.ts +139 -17
- package/src/safety/printer-state.ts +179 -40
- package/src/slicer/bambu-cli.ts +8 -5
- package/src/slicer/layer-pauses.ts +143 -0
- package/src/stl/stl-manipulator.ts +45 -0
- package/src/studio-print-options.ts +201 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _CameraStream_instances, _CameraStream_source, _CameraStream_timer, _CameraStream_generation, _CameraStream_processing, _CameraStream_lastAccepted, _CameraStream_latest, _CameraStream_references, _CameraStream_waiters, _CameraStream_stopped, _CameraStream_reconnected, _CameraStream_reason, _CameraStream_seq, _CameraStream_wake, _CameraStream_connect, _CameraStream_lost, _CameraStream_accept, _CameraStream_encoded, _CameraStream_result, _CameraStreams_streams, _CameraStreams_sequence;
|
|
13
|
+
let native;
|
|
14
|
+
async function nativeSharp() {
|
|
15
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
16
|
+
if (major < 20 || (major === 20 && minor < 9))
|
|
17
|
+
throw new Error("Native camera processing requires Node 20.9+");
|
|
18
|
+
const optionalPackage = "sharp";
|
|
19
|
+
native ?? (native = import(optionalPackage).then(module => {
|
|
20
|
+
const processor = module.default;
|
|
21
|
+
processor.cache({ memory: 8, files: 0, items: 32 });
|
|
22
|
+
return processor;
|
|
23
|
+
}));
|
|
24
|
+
return native;
|
|
25
|
+
}
|
|
26
|
+
export async function cameraNativeAvailable() {
|
|
27
|
+
try {
|
|
28
|
+
await nativeSharp();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function thumbnailChange(a, b) {
|
|
36
|
+
let total = 0;
|
|
37
|
+
for (let i = 0; i < a.length; i++)
|
|
38
|
+
total += Math.abs(a[i] - b[i]);
|
|
39
|
+
return total / (a.length * 255);
|
|
40
|
+
}
|
|
41
|
+
export class CameraStream {
|
|
42
|
+
constructor(options, nextSeq, now = Date.now) {
|
|
43
|
+
_CameraStream_instances.add(this);
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.nextSeq = nextSeq;
|
|
46
|
+
this.now = now;
|
|
47
|
+
this.startedAt = new Date().toISOString();
|
|
48
|
+
_CameraStream_source.set(this, void 0);
|
|
49
|
+
_CameraStream_timer.set(this, void 0);
|
|
50
|
+
_CameraStream_generation.set(this, 0);
|
|
51
|
+
_CameraStream_processing.set(this, false);
|
|
52
|
+
_CameraStream_lastAccepted.set(this, -Infinity);
|
|
53
|
+
_CameraStream_latest.set(this, void 0);
|
|
54
|
+
// Keep thumbnails of frames actually delivered, not every camera frame. A quiet view may keep
|
|
55
|
+
// comparing with its first frame for hours; a rolling JPEG history would repeatedly resend it.
|
|
56
|
+
_CameraStream_references.set(this, new Map());
|
|
57
|
+
_CameraStream_waiters.set(this, new Set());
|
|
58
|
+
_CameraStream_stopped.set(this, false);
|
|
59
|
+
_CameraStream_reconnected.set(this, false);
|
|
60
|
+
_CameraStream_reason.set(this, void 0);
|
|
61
|
+
_CameraStream_seq.set(this, 0);
|
|
62
|
+
this.transport = options.transport;
|
|
63
|
+
this.fps = options.fps;
|
|
64
|
+
}
|
|
65
|
+
get stopped() { return __classPrivateFieldGet(this, _CameraStream_stopped, "f"); }
|
|
66
|
+
start() { this.touch(); __classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_connect).call(this); }
|
|
67
|
+
touch(idleTimeoutMs) {
|
|
68
|
+
if (__classPrivateFieldGet(this, _CameraStream_stopped, "f"))
|
|
69
|
+
return;
|
|
70
|
+
if (idleTimeoutMs !== undefined)
|
|
71
|
+
this.options.idleTimeoutMs = idleTimeoutMs;
|
|
72
|
+
clearTimeout(__classPrivateFieldGet(this, _CameraStream_timer, "f"));
|
|
73
|
+
__classPrivateFieldSet(this, _CameraStream_timer, setTimeout(() => this.stop(), this.options.idleTimeoutMs), "f");
|
|
74
|
+
__classPrivateFieldGet(this, _CameraStream_timer, "f").unref();
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
var _a;
|
|
78
|
+
if (__classPrivateFieldGet(this, _CameraStream_stopped, "f"))
|
|
79
|
+
return;
|
|
80
|
+
__classPrivateFieldSet(this, _CameraStream_stopped, true, "f");
|
|
81
|
+
__classPrivateFieldSet(this, _CameraStream_generation, (_a = __classPrivateFieldGet(this, _CameraStream_generation, "f"), _a++, _a), "f");
|
|
82
|
+
clearTimeout(__classPrivateFieldGet(this, _CameraStream_timer, "f"));
|
|
83
|
+
__classPrivateFieldGet(this, _CameraStream_source, "f")?.close();
|
|
84
|
+
__classPrivateFieldSet(this, _CameraStream_source, undefined, "f");
|
|
85
|
+
__classPrivateFieldSet(this, _CameraStream_latest, undefined, "f");
|
|
86
|
+
__classPrivateFieldGet(this, _CameraStream_references, "f").clear();
|
|
87
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_wake).call(this);
|
|
88
|
+
}
|
|
89
|
+
async frame(options = {}) {
|
|
90
|
+
const since = options.sinceSeq ?? 0, deadline = Date.now() + (options.waitMs ?? 0);
|
|
91
|
+
this.touch();
|
|
92
|
+
for (;;) {
|
|
93
|
+
options.signal?.throwIfAborted();
|
|
94
|
+
if (__classPrivateFieldGet(this, _CameraStream_reason, "f"))
|
|
95
|
+
return { status: "unavailable", seq: __classPrivateFieldGet(this, _CameraStream_seq, "f"), reason: __classPrivateFieldGet(this, _CameraStream_reason, "f") };
|
|
96
|
+
if (__classPrivateFieldGet(this, _CameraStream_stopped, "f"))
|
|
97
|
+
return { status: "stopped", seq: __classPrivateFieldGet(this, _CameraStream_seq, "f") };
|
|
98
|
+
const latest = __classPrivateFieldGet(this, _CameraStream_latest, "f");
|
|
99
|
+
if (latest && latest.seq > since) {
|
|
100
|
+
const reference = __classPrivateFieldGet(this, _CameraStream_references, "f").get(since);
|
|
101
|
+
const change = reference ? thumbnailChange(latest.thumb, reference) : 1;
|
|
102
|
+
if (options.changedOnly === false || change >= (options.minChange ?? 0.02))
|
|
103
|
+
return __classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_result).call(this, latest, change, options.maxWidth, options.maxBytes);
|
|
104
|
+
}
|
|
105
|
+
const left = deadline - Date.now();
|
|
106
|
+
if (left <= 0)
|
|
107
|
+
return { status: "unchanged", seq: since, ...(latest ? { capturedAt: new Date(latest.at).toISOString() } : {}) };
|
|
108
|
+
await new Promise(resolve => {
|
|
109
|
+
const done = () => { clearTimeout(timer); __classPrivateFieldGet(this, _CameraStream_waiters, "f").delete(done); options.signal?.removeEventListener("abort", done); resolve(); };
|
|
110
|
+
const timer = setTimeout(done, left);
|
|
111
|
+
__classPrivateFieldGet(this, _CameraStream_waiters, "f").add(done);
|
|
112
|
+
options.signal?.addEventListener("abort", done, { once: true });
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async latest(waitMs) {
|
|
117
|
+
const result = await this.frame({ waitMs, changedOnly: false });
|
|
118
|
+
return result.status === "frame" ? Buffer.from(result.base64, "base64") : undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
_CameraStream_source = new WeakMap(), _CameraStream_timer = new WeakMap(), _CameraStream_generation = new WeakMap(), _CameraStream_processing = new WeakMap(), _CameraStream_lastAccepted = new WeakMap(), _CameraStream_latest = new WeakMap(), _CameraStream_references = new WeakMap(), _CameraStream_waiters = new WeakMap(), _CameraStream_stopped = new WeakMap(), _CameraStream_reconnected = new WeakMap(), _CameraStream_reason = new WeakMap(), _CameraStream_seq = new WeakMap(), _CameraStream_instances = new WeakSet(), _CameraStream_wake = function _CameraStream_wake() { for (const wake of [...__classPrivateFieldGet(this, _CameraStream_waiters, "f")])
|
|
122
|
+
wake(); }, _CameraStream_connect = function _CameraStream_connect() {
|
|
123
|
+
var _a;
|
|
124
|
+
const generation = __classPrivateFieldSet(this, _CameraStream_generation, (_a = __classPrivateFieldGet(this, _CameraStream_generation, "f"), ++_a), "f");
|
|
125
|
+
let buffered, live = !this.options.skipFirstFrame;
|
|
126
|
+
const current = () => !__classPrivateFieldGet(this, _CameraStream_stopped, "f") && generation === __classPrivateFieldGet(this, _CameraStream_generation, "f");
|
|
127
|
+
try {
|
|
128
|
+
const source = this.options.open({
|
|
129
|
+
onFrame: bytes => {
|
|
130
|
+
if (!current())
|
|
131
|
+
return;
|
|
132
|
+
// P1/A1 replay their stale buffered frame, sometimes more than once.
|
|
133
|
+
if (!live) {
|
|
134
|
+
if (!buffered) {
|
|
135
|
+
buffered = bytes;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (bytes.equals(buffered))
|
|
139
|
+
return;
|
|
140
|
+
buffered = undefined;
|
|
141
|
+
live = true;
|
|
142
|
+
}
|
|
143
|
+
if (__classPrivateFieldGet(this, _CameraStream_processing, "f") || this.now() - __classPrivateFieldGet(this, _CameraStream_lastAccepted, "f") < 1000 / this.fps)
|
|
144
|
+
return;
|
|
145
|
+
__classPrivateFieldSet(this, _CameraStream_lastAccepted, this.now(), "f");
|
|
146
|
+
__classPrivateFieldSet(this, _CameraStream_processing, true, "f");
|
|
147
|
+
void __classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_accept).call(this, bytes, generation).catch(() => { if (current())
|
|
148
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_lost).call(this, "unreachable"); })
|
|
149
|
+
.finally(() => { __classPrivateFieldSet(this, _CameraStream_processing, false, "f"); });
|
|
150
|
+
},
|
|
151
|
+
onError: error => { if (current())
|
|
152
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_lost).call(this, /auth|payload size|401|403/i.test(error.message) ? "auth" : "unreachable"); },
|
|
153
|
+
onEnd: () => { if (current())
|
|
154
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_lost).call(this, "unreachable"); },
|
|
155
|
+
});
|
|
156
|
+
// A transport may fail synchronously. Never keep its abandoned source after reconnect/stop.
|
|
157
|
+
if (current())
|
|
158
|
+
__classPrivateFieldSet(this, _CameraStream_source, source, "f");
|
|
159
|
+
else
|
|
160
|
+
source.close();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
if (current())
|
|
164
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_lost).call(this, "unreachable");
|
|
165
|
+
}
|
|
166
|
+
}, _CameraStream_lost = function _CameraStream_lost(reason) {
|
|
167
|
+
var _a;
|
|
168
|
+
__classPrivateFieldSet(this, _CameraStream_generation, (_a = __classPrivateFieldGet(this, _CameraStream_generation, "f"), _a++, _a), "f");
|
|
169
|
+
__classPrivateFieldGet(this, _CameraStream_source, "f")?.close();
|
|
170
|
+
__classPrivateFieldSet(this, _CameraStream_source, undefined, "f");
|
|
171
|
+
if (!__classPrivateFieldGet(this, _CameraStream_reconnected, "f") && reason !== "auth") {
|
|
172
|
+
__classPrivateFieldSet(this, _CameraStream_reconnected, true, "f");
|
|
173
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_connect).call(this);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
__classPrivateFieldSet(this, _CameraStream_reason, reason, "f");
|
|
177
|
+
this.stop();
|
|
178
|
+
}, _CameraStream_accept = async function _CameraStream_accept(bytes, generation) {
|
|
179
|
+
if (bytes.length > 5000000)
|
|
180
|
+
throw Error("Camera JPEG too large");
|
|
181
|
+
const decoder = (await nativeSharp())(bytes, { limitInputPixels: 8000000 });
|
|
182
|
+
const metadata = await decoder.metadata();
|
|
183
|
+
const thumb = await decoder.resize(32, 18, { fit: "fill" }).greyscale().raw().toBuffer();
|
|
184
|
+
if (__classPrivateFieldGet(this, _CameraStream_stopped, "f") || generation !== __classPrivateFieldGet(this, _CameraStream_generation, "f"))
|
|
185
|
+
return;
|
|
186
|
+
__classPrivateFieldSet(this, _CameraStream_latest, { seq: __classPrivateFieldSet(this, _CameraStream_seq, this.nextSeq(), "f"), bytes, thumb, at: this.now(), width: metadata.width, height: metadata.height, scaled: new Map() }, "f");
|
|
187
|
+
__classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_wake).call(this);
|
|
188
|
+
}, _CameraStream_encoded = async function _CameraStream_encoded(frame, width, maxBytes) {
|
|
189
|
+
// Start near the byte target instead of always encoding a full-size frame that will be
|
|
190
|
+
// discarded. Source JPEG density is a cheap conservative estimate, not a size guarantee.
|
|
191
|
+
if (maxBytes && frame.bytes.length > maxBytes)
|
|
192
|
+
width = Math.min(width, Math.max(1, Math.floor(frame.width * Math.sqrt(maxBytes / frame.bytes.length) * 0.8)));
|
|
193
|
+
// At most four native encodes. The final 1-pixel fallback guarantees a bounded result
|
|
194
|
+
// even for pathological entropy; quality stays 70 and aspect ratio is preserved.
|
|
195
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
196
|
+
const encoded = await (await nativeSharp())(frame.bytes, { limitInputPixels: 8000000 }).resize({ width, withoutEnlargement: true }).jpeg({ quality: 70 }).toBuffer({ resolveWithObject: true });
|
|
197
|
+
if (!maxBytes || encoded.data.length <= maxBytes)
|
|
198
|
+
return encoded;
|
|
199
|
+
width = attempt === 2 ? 1 : Math.max(1, Math.floor(width * Math.min(0.8, Math.sqrt(maxBytes / encoded.data.length) * 0.9)));
|
|
200
|
+
}
|
|
201
|
+
throw new Error("Camera frame could not fit max_bytes");
|
|
202
|
+
}, _CameraStream_result = async function _CameraStream_result(frame, change, maxWidth, maxBytes) {
|
|
203
|
+
let bytes = frame.bytes, width = frame.width, height = frame.height;
|
|
204
|
+
if ((maxWidth && width > maxWidth) || (maxBytes && bytes.length > maxBytes)) {
|
|
205
|
+
const key = `${maxWidth ?? width}:${maxBytes ?? 0}`;
|
|
206
|
+
let scaled = frame.scaled.get(key);
|
|
207
|
+
if (!scaled) {
|
|
208
|
+
scaled = __classPrivateFieldGet(this, _CameraStream_instances, "m", _CameraStream_encoded).call(this, frame, Math.min(width, maxWidth ?? width), maxBytes);
|
|
209
|
+
if (frame.scaled.size < 4)
|
|
210
|
+
frame.scaled.set(key, scaled);
|
|
211
|
+
}
|
|
212
|
+
const resized = await scaled;
|
|
213
|
+
bytes = resized.data;
|
|
214
|
+
width = resized.info.width;
|
|
215
|
+
height = resized.info.height;
|
|
216
|
+
}
|
|
217
|
+
if (__classPrivateFieldGet(this, _CameraStream_stopped, "f"))
|
|
218
|
+
return __classPrivateFieldGet(this, _CameraStream_reason, "f") ? { status: "unavailable", seq: __classPrivateFieldGet(this, _CameraStream_seq, "f"), reason: __classPrivateFieldGet(this, _CameraStream_reason, "f") } : { status: "stopped", seq: __classPrivateFieldGet(this, _CameraStream_seq, "f") };
|
|
219
|
+
__classPrivateFieldGet(this, _CameraStream_references, "f").set(frame.seq, frame.thumb);
|
|
220
|
+
if (__classPrivateFieldGet(this, _CameraStream_references, "f").size > 256)
|
|
221
|
+
__classPrivateFieldGet(this, _CameraStream_references, "f").delete(__classPrivateFieldGet(this, _CameraStream_references, "f").keys().next().value);
|
|
222
|
+
return { status: "frame", seq: frame.seq, capturedAt: new Date(frame.at).toISOString(), width, height, format: "image/jpeg", base64: bytes.toString("base64"), change };
|
|
223
|
+
};
|
|
224
|
+
export class CameraStreams {
|
|
225
|
+
constructor(now = Date.now) {
|
|
226
|
+
this.now = now;
|
|
227
|
+
_CameraStreams_streams.set(this, new Map());
|
|
228
|
+
_CameraStreams_sequence.set(this, 0);
|
|
229
|
+
}
|
|
230
|
+
get(key) { return __classPrivateFieldGet(this, _CameraStreams_streams, "f").get(key); }
|
|
231
|
+
start(key, options) {
|
|
232
|
+
const existing = this.get(key);
|
|
233
|
+
if (existing && !existing.stopped) {
|
|
234
|
+
existing.touch(options.idleTimeoutMs);
|
|
235
|
+
return { stream: existing, status: "running" };
|
|
236
|
+
}
|
|
237
|
+
const stream = new CameraStream(options, () => { var _a; return __classPrivateFieldSet(this, _CameraStreams_sequence, (_a = __classPrivateFieldGet(this, _CameraStreams_sequence, "f"), ++_a), "f"); }, this.now);
|
|
238
|
+
__classPrivateFieldGet(this, _CameraStreams_streams, "f").set(key, stream);
|
|
239
|
+
stream.start();
|
|
240
|
+
return { stream, status: "started" };
|
|
241
|
+
}
|
|
242
|
+
stop(key) { this.get(key)?.stop(); }
|
|
243
|
+
stopAll() { for (const stream of __classPrivateFieldGet(this, _CameraStreams_streams, "f").values())
|
|
244
|
+
stream.stop(); __classPrivateFieldGet(this, _CameraStreams_streams, "f").clear(); }
|
|
245
|
+
}
|
|
246
|
+
_CameraStreams_streams = new WeakMap(), _CameraStreams_sequence = new WeakMap();
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
export declare const CAMERA_STREAM_TOOLS: ({
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
fps: {
|
|
8
|
+
type: string;
|
|
9
|
+
minimum: number;
|
|
10
|
+
maximum: number;
|
|
11
|
+
default: number;
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
idle_timeout_ms: {
|
|
15
|
+
type: string;
|
|
16
|
+
minimum: number;
|
|
17
|
+
maximum: number;
|
|
18
|
+
default: number;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
port: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
type: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
api_key: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
host: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
bambu_serial: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
bambu_token: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
additionalProperties: boolean;
|
|
47
|
+
};
|
|
48
|
+
outputSchema: {
|
|
49
|
+
type: "object";
|
|
50
|
+
properties: {
|
|
51
|
+
status: {
|
|
52
|
+
type: string;
|
|
53
|
+
enum: string[];
|
|
54
|
+
const?: undefined;
|
|
55
|
+
};
|
|
56
|
+
reason: {
|
|
57
|
+
type: string;
|
|
58
|
+
enum: string[];
|
|
59
|
+
};
|
|
60
|
+
transport: {
|
|
61
|
+
type: string;
|
|
62
|
+
enum: string[];
|
|
63
|
+
};
|
|
64
|
+
fps: {
|
|
65
|
+
type: string;
|
|
66
|
+
};
|
|
67
|
+
startedAt: {
|
|
68
|
+
type: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
required: string[];
|
|
72
|
+
oneOf: ({
|
|
73
|
+
properties: {
|
|
74
|
+
status: {
|
|
75
|
+
enum: string[];
|
|
76
|
+
const?: undefined;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
required: string[];
|
|
80
|
+
} | {
|
|
81
|
+
properties: {
|
|
82
|
+
status: {
|
|
83
|
+
const: string;
|
|
84
|
+
enum?: undefined;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
required: string[];
|
|
88
|
+
})[];
|
|
89
|
+
};
|
|
90
|
+
} | {
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object";
|
|
95
|
+
properties: {
|
|
96
|
+
since_seq: {
|
|
97
|
+
type: string;
|
|
98
|
+
minimum: number;
|
|
99
|
+
default: number;
|
|
100
|
+
description: string;
|
|
101
|
+
};
|
|
102
|
+
wait_ms: {
|
|
103
|
+
type: string;
|
|
104
|
+
minimum: number;
|
|
105
|
+
maximum: number;
|
|
106
|
+
default: number;
|
|
107
|
+
description: string;
|
|
108
|
+
};
|
|
109
|
+
max_width: {
|
|
110
|
+
type: string;
|
|
111
|
+
minimum: number;
|
|
112
|
+
maximum: number;
|
|
113
|
+
description: string;
|
|
114
|
+
};
|
|
115
|
+
max_bytes: {
|
|
116
|
+
type: string;
|
|
117
|
+
minimum: number;
|
|
118
|
+
maximum: number;
|
|
119
|
+
description: string;
|
|
120
|
+
};
|
|
121
|
+
changed_only: {
|
|
122
|
+
type: string;
|
|
123
|
+
default: boolean;
|
|
124
|
+
description: string;
|
|
125
|
+
};
|
|
126
|
+
min_change: {
|
|
127
|
+
type: string;
|
|
128
|
+
minimum: number;
|
|
129
|
+
maximum: number;
|
|
130
|
+
default: number;
|
|
131
|
+
description: string;
|
|
132
|
+
};
|
|
133
|
+
port: {
|
|
134
|
+
type: string;
|
|
135
|
+
description: string;
|
|
136
|
+
};
|
|
137
|
+
type: {
|
|
138
|
+
type: string;
|
|
139
|
+
description: string;
|
|
140
|
+
};
|
|
141
|
+
api_key: {
|
|
142
|
+
type: string;
|
|
143
|
+
description: string;
|
|
144
|
+
};
|
|
145
|
+
host: {
|
|
146
|
+
type: string;
|
|
147
|
+
description: string;
|
|
148
|
+
};
|
|
149
|
+
bambu_serial: {
|
|
150
|
+
type: string;
|
|
151
|
+
description: string;
|
|
152
|
+
};
|
|
153
|
+
bambu_token: {
|
|
154
|
+
type: string;
|
|
155
|
+
description: string;
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
additionalProperties: boolean;
|
|
159
|
+
};
|
|
160
|
+
outputSchema: {
|
|
161
|
+
type: "object";
|
|
162
|
+
properties: {
|
|
163
|
+
status: {
|
|
164
|
+
type: string;
|
|
165
|
+
enum: string[];
|
|
166
|
+
};
|
|
167
|
+
seq: {
|
|
168
|
+
type: string;
|
|
169
|
+
minimum: number;
|
|
170
|
+
};
|
|
171
|
+
capturedAt: {
|
|
172
|
+
type: string;
|
|
173
|
+
};
|
|
174
|
+
width: {
|
|
175
|
+
type: string;
|
|
176
|
+
};
|
|
177
|
+
height: {
|
|
178
|
+
type: string;
|
|
179
|
+
};
|
|
180
|
+
format: {
|
|
181
|
+
type: string;
|
|
182
|
+
const: string;
|
|
183
|
+
};
|
|
184
|
+
base64: {
|
|
185
|
+
type: string;
|
|
186
|
+
};
|
|
187
|
+
change: {
|
|
188
|
+
type: string;
|
|
189
|
+
};
|
|
190
|
+
reason: {
|
|
191
|
+
type: string;
|
|
192
|
+
enum: string[];
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
required: string[];
|
|
196
|
+
};
|
|
197
|
+
} | {
|
|
198
|
+
name: string;
|
|
199
|
+
description: string;
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object";
|
|
202
|
+
properties: {
|
|
203
|
+
port: {
|
|
204
|
+
type: string;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
type: {
|
|
208
|
+
type: string;
|
|
209
|
+
description: string;
|
|
210
|
+
};
|
|
211
|
+
api_key: {
|
|
212
|
+
type: string;
|
|
213
|
+
description: string;
|
|
214
|
+
};
|
|
215
|
+
host: {
|
|
216
|
+
type: string;
|
|
217
|
+
description: string;
|
|
218
|
+
};
|
|
219
|
+
bambu_serial: {
|
|
220
|
+
type: string;
|
|
221
|
+
description: string;
|
|
222
|
+
};
|
|
223
|
+
bambu_token: {
|
|
224
|
+
type: string;
|
|
225
|
+
description: string;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
additionalProperties: boolean;
|
|
229
|
+
};
|
|
230
|
+
outputSchema: {
|
|
231
|
+
type: "object";
|
|
232
|
+
properties: {
|
|
233
|
+
status: {
|
|
234
|
+
type: string;
|
|
235
|
+
const: string;
|
|
236
|
+
enum?: undefined;
|
|
237
|
+
};
|
|
238
|
+
reason?: undefined;
|
|
239
|
+
transport?: undefined;
|
|
240
|
+
fps?: undefined;
|
|
241
|
+
startedAt?: undefined;
|
|
242
|
+
};
|
|
243
|
+
required: string[];
|
|
244
|
+
oneOf?: undefined;
|
|
245
|
+
};
|
|
246
|
+
})[];
|
|
247
|
+
export declare function cameraNumber(value: unknown, fallback: number, min: number, max: number, name: string, integer?: boolean): number;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Additive stream tools; discovery is the capability check for older clients. */
|
|
2
|
+
const routing = {
|
|
3
|
+
port: { type: "string", description: "Printer API port; defaults to PRINTER_PORT." },
|
|
4
|
+
type: { type: "string", description: "Printer backend; defaults to PRINTER_TYPE." },
|
|
5
|
+
api_key: { type: "string", description: "Printer API key; defaults to API_KEY." },
|
|
6
|
+
host: { type: "string", description: "Printer host or IP; defaults to PRINTER_HOST." },
|
|
7
|
+
bambu_serial: { type: "string", description: "Printer serial number; defaults to the configured serial and identifies its shared stream." },
|
|
8
|
+
bambu_token: { type: "string", description: "Printer LAN access code; defaults to the configured access code." },
|
|
9
|
+
};
|
|
10
|
+
const frameSchema = {
|
|
11
|
+
type: "object", properties: {
|
|
12
|
+
status: { type: "string", enum: ["frame", "unchanged", "stopped", "unavailable"] }, seq: { type: "integer", minimum: 0 },
|
|
13
|
+
capturedAt: { type: "string" }, width: { type: "integer" }, height: { type: "integer" },
|
|
14
|
+
format: { type: "string", const: "image/jpeg" }, base64: { type: "string" }, change: { type: "number" },
|
|
15
|
+
reason: { type: "string", enum: ["no-camera", "auth", "unreachable"] },
|
|
16
|
+
}, required: ["status", "seq"],
|
|
17
|
+
};
|
|
18
|
+
export const CAMERA_STREAM_TOOLS = [
|
|
19
|
+
{ name: "camera_stream_start", description: "Start or renew one persistent camera connection for this printer. Call only while watched; it closes automatically after idle_timeout_ms without a viewer request. camera_snapshot then reuses its latest frame. No printer motion or print commands.",
|
|
20
|
+
inputSchema: { type: "object", properties: { ...routing, fps: { type: "number", minimum: 0.2, maximum: 2, default: 1, description: "Maximum processed frames per second. The first viewer sets the rate until the stream closes." }, idle_timeout_ms: { type: "integer", minimum: 5000, maximum: 120000, default: 30000, description: "Close the shared connection after this many milliseconds without a viewer request." } }, additionalProperties: false },
|
|
21
|
+
outputSchema: { type: "object", properties: { status: { type: "string", enum: ["started", "running", "not-supported"] }, reason: { type: "string", enum: ["runtime"] }, transport: { type: "string", enum: ["http"] }, fps: { type: "number" }, startedAt: { type: "string" } }, required: ["status"], oneOf: [
|
|
22
|
+
{ properties: { status: { enum: ["started", "running"] } }, required: ["transport", "fps", "startedAt"] },
|
|
23
|
+
{ properties: { status: { const: "not-supported" } }, required: ["reason"] },
|
|
24
|
+
] } },
|
|
25
|
+
{ name: "camera_frame", description: "Read the shared camera stream, optionally waiting up to 10 seconds. Returns JPEG only when newer than since_seq and visibly changed (32×18 grayscale difference). Retain the last delivered frame's seq on unchanged. max_width resizes JPEG to the viewer. Does not start a stopped stream.",
|
|
26
|
+
inputSchema: { type: "object", properties: { ...routing, since_seq: { type: "integer", minimum: 0, default: 0, description: "Sequence of the last delivered JPEG; retain it when a result is unchanged." }, wait_ms: { type: "integer", minimum: 0, maximum: 10000, default: 0, description: "Maximum time to await a newer changed frame; zero returns immediately." }, max_width: { type: "integer", minimum: 1, maximum: 4096, description: "Optional maximum JPEG width in pixels, preserving aspect ratio and never enlarging." }, max_bytes: { type: "integer", minimum: 4096, maximum: 5000000, description: "Optional maximum encoded JPEG bytes. Downscales further at quality 70 when needed; base64 adds about one third plus protocol overhead. Use 42000 for a two-second 30 KB/s viewer budget." }, changed_only: { type: "boolean", default: true, description: "Return JPEG only when its thumbnail differs enough from the last delivered frame." }, min_change: { type: "number", minimum: 0, maximum: 1, default: 0.02, description: "Minimum mean absolute grayscale thumbnail difference, normalized from zero to one." } }, additionalProperties: false }, outputSchema: frameSchema },
|
|
27
|
+
{ name: "camera_stream_stop", description: "Close this printer's camera connection and free its cached frames. Other printers keep their own streams. Active viewers may restart it; normally let the idle timeout close a shared stream.",
|
|
28
|
+
inputSchema: { type: "object", properties: routing, additionalProperties: false }, outputSchema: { type: "object", properties: { status: { type: "string", const: "stopped" } }, required: ["status"] } },
|
|
29
|
+
];
|
|
30
|
+
export function cameraNumber(value, fallback, min, max, name, integer = false) {
|
|
31
|
+
const n = value === undefined ? fallback : value;
|
|
32
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n < min || n > max || (integer && !Number.isSafeInteger(n)))
|
|
33
|
+
throw new Error(`${name} must be ${integer ? "an integer" : "a number"} from ${min} to ${max}`);
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
@@ -48,6 +48,7 @@ export interface CounterpartCapabilities {
|
|
|
48
48
|
progress: boolean;
|
|
49
49
|
layers: boolean;
|
|
50
50
|
camera: CameraCapability;
|
|
51
|
+
cameraStream?: boolean;
|
|
51
52
|
filaments: FilamentCapability;
|
|
52
53
|
pause: boolean;
|
|
53
54
|
resume: boolean;
|
|
@@ -73,11 +74,12 @@ export interface CounterpartPrinterInfo {
|
|
|
73
74
|
capabilities: CounterpartCapabilities;
|
|
74
75
|
}
|
|
75
76
|
export interface FilamentSlot {
|
|
76
|
-
|
|
77
|
+
/** "ext" for a single-spool printer; Bambu: the absolute AMS tray ("0"-"15", "128"+) or external spool ("254", "255"). */
|
|
78
|
+
id: string;
|
|
77
79
|
material?: string;
|
|
78
80
|
color?: string;
|
|
79
81
|
name?: string;
|
|
80
|
-
source: "spoolman" | "unknown";
|
|
82
|
+
source: "spoolman" | "printer" | "unknown";
|
|
81
83
|
/** Absolute path of the slicer filament profile for this material, as slice_stl's load_filaments takes it. */
|
|
82
84
|
profile?: string;
|
|
83
85
|
}
|
|
@@ -148,6 +150,9 @@ export declare const PRINTER_INFO_OUTPUT_SCHEMA: {
|
|
|
148
150
|
readonly type: "string";
|
|
149
151
|
readonly enum: readonly ["bambu-tcp", "rtsp", "http-snapshot", "none"];
|
|
150
152
|
};
|
|
153
|
+
readonly cameraStream: {
|
|
154
|
+
readonly type: "boolean";
|
|
155
|
+
};
|
|
151
156
|
readonly filaments: {
|
|
152
157
|
readonly type: "string";
|
|
153
158
|
readonly enum: readonly ["ams", "single", "none"];
|
|
@@ -278,7 +283,7 @@ export declare const PRINT_PROGRESS_OUTPUT_SCHEMA: {
|
|
|
278
283
|
};
|
|
279
284
|
severity: {
|
|
280
285
|
type: string;
|
|
281
|
-
enum: ("error" | "
|
|
286
|
+
enum: ("error" | "info" | "fatal" | "warning")[];
|
|
282
287
|
};
|
|
283
288
|
message: {
|
|
284
289
|
type: string;
|
|
@@ -319,11 +324,22 @@ export declare const CAMERA_SNAPSHOT_OUTPUT_SCHEMA: {
|
|
|
319
324
|
};
|
|
320
325
|
};
|
|
321
326
|
};
|
|
322
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* outputSchema for get_printer_filaments: one external slot, or for Bambu every
|
|
329
|
+
* loaded AMS tray and external spool plus bambu-printer-mcp's inventory fields
|
|
330
|
+
* (trays, summary, recommended, load_filaments_all), which keep its nulls.
|
|
331
|
+
*/
|
|
323
332
|
export declare const FILAMENTS_OUTPUT_SCHEMA: {
|
|
324
333
|
readonly type: "object";
|
|
325
334
|
readonly required: readonly ["slots"];
|
|
326
335
|
readonly properties: {
|
|
336
|
+
readonly trays: {
|
|
337
|
+
readonly type: "array";
|
|
338
|
+
readonly description: "Bambu only: bambu-printer-mcp's AMS tray list (slot = ams_id * 4 + tray_id), empty trays included.";
|
|
339
|
+
readonly items: {
|
|
340
|
+
readonly type: "object";
|
|
341
|
+
};
|
|
342
|
+
};
|
|
327
343
|
readonly slots: {
|
|
328
344
|
readonly type: "array";
|
|
329
345
|
readonly items: {
|
|
@@ -344,7 +360,7 @@ export declare const FILAMENTS_OUTPUT_SCHEMA: {
|
|
|
344
360
|
};
|
|
345
361
|
readonly source: {
|
|
346
362
|
readonly type: "string";
|
|
347
|
-
readonly enum: readonly ["spoolman", "unknown"];
|
|
363
|
+
readonly enum: readonly ["spoolman", "printer", "unknown"];
|
|
348
364
|
};
|
|
349
365
|
readonly profile: {
|
|
350
366
|
readonly type: "string";
|
|
@@ -52,6 +52,7 @@ export const PRINTER_INFO_OUTPUT_SCHEMA = {
|
|
|
52
52
|
progress: { type: "boolean" },
|
|
53
53
|
layers: { type: "boolean" },
|
|
54
54
|
camera: { type: "string", enum: ["bambu-tcp", "rtsp", "http-snapshot", "none"] },
|
|
55
|
+
cameraStream: { type: "boolean" },
|
|
55
56
|
filaments: { type: "string", enum: ["ams", "single", "none"] },
|
|
56
57
|
pause: { type: "boolean" },
|
|
57
58
|
resume: { type: "boolean" },
|
|
@@ -109,11 +110,16 @@ export const CAMERA_SNAPSHOT_OUTPUT_SCHEMA = {
|
|
|
109
110
|
source: { type: "string" },
|
|
110
111
|
},
|
|
111
112
|
};
|
|
112
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* outputSchema for get_printer_filaments: one external slot, or for Bambu every
|
|
115
|
+
* loaded AMS tray and external spool plus bambu-printer-mcp's inventory fields
|
|
116
|
+
* (trays, summary, recommended, load_filaments_all), which keep its nulls.
|
|
117
|
+
*/
|
|
113
118
|
export const FILAMENTS_OUTPUT_SCHEMA = {
|
|
114
119
|
type: "object",
|
|
115
120
|
required: ["slots"],
|
|
116
121
|
properties: {
|
|
122
|
+
trays: { type: "array", description: "Bambu only: bambu-printer-mcp's AMS tray list (slot = ams_id * 4 + tray_id), empty trays included.", items: { type: "object" } },
|
|
117
123
|
slots: {
|
|
118
124
|
type: "array",
|
|
119
125
|
items: {
|
|
@@ -124,7 +130,7 @@ export const FILAMENTS_OUTPUT_SCHEMA = {
|
|
|
124
130
|
material: { type: "string" },
|
|
125
131
|
color: { type: "string" },
|
|
126
132
|
name: { type: "string" },
|
|
127
|
-
source: { type: "string", enum: ["spoolman", "unknown"] },
|
|
133
|
+
source: { type: "string", enum: ["spoolman", "printer", "unknown"] },
|
|
128
134
|
profile: {
|
|
129
135
|
type: "string",
|
|
130
136
|
description: "Absolute path of the installed slicer filament profile for this slot's material, which slice_stl accepts as load_filaments. Omitted when none resolves.",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lan: the local network through FULU's BambuNetwork bridge (or the configured X2D native or Bambu Connect mode).
|
|
3
|
+
* cloud: Bambu Cloud through FULU's bridge.
|
|
4
|
+
* developer: the local network straight to the printer (lan_mqtt_ftps), for a printer in LAN Only + Developer Mode.
|
|
5
|
+
*/
|
|
6
|
+
export type PrintRoute = "lan" | "cloud" | "developer";
|
|
7
|
+
export declare const PRINT_ROUTES: readonly PrintRoute[];
|
|
8
|
+
export interface Connection {
|
|
9
|
+
mode: string;
|
|
10
|
+
type: string;
|
|
11
|
+
}
|
|
12
|
+
/** One connection a person can choose, as counterpart_print_route reports it. */
|
|
13
|
+
export interface RouteOption {
|
|
14
|
+
id: PrintRoute;
|
|
15
|
+
label: string;
|
|
16
|
+
available: boolean;
|
|
17
|
+
/** Why it can't be chosen here, when it can't. */
|
|
18
|
+
reason?: string;
|
|
19
|
+
/** What Pause, Resume and Stop do on this connection. */
|
|
20
|
+
controls: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function routeOf(connection: Connection): PrintRoute;
|
|
23
|
+
/** Every connection, and whether this server can use it (bridgeConfigured: BAMBU_NETWORK_BRIDGE_COMMAND is set). */
|
|
24
|
+
export declare function routeOptions(current: Connection, bridgeConfigured: boolean): RouteOption[];
|
|
25
|
+
/**
|
|
26
|
+
* The configured defaults for a route. Cloud and the local network use FULU's BambuNetwork; an X2D native or
|
|
27
|
+
* Bambu Connect setup keeps its own mode for the local network. Developer Mode is the direct LAN route.
|
|
28
|
+
*/
|
|
29
|
+
export declare function connectionForRoute(route: PrintRoute, current: Connection, bridgeConfigured?: boolean): Connection;
|
|
30
|
+
/** Set KEY=value lines in a dotenv file, keeping every other line and the file's permissions. */
|
|
31
|
+
export declare function writeEnvValues(file: string, values: Record<string, string>): void;
|