sidecarsync 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,4845 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+
31
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/date.js
32
+ var DATE_TIME_RE, TomlDate;
33
+ var init_date = __esm(() => {
34
+ /*!
35
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
36
+ * SPDX-License-Identifier: BSD-3-Clause
37
+ *
38
+ * Redistribution and use in source and binary forms, with or without
39
+ * modification, are permitted provided that the following conditions are met:
40
+ *
41
+ * 1. Redistributions of source code must retain the above copyright notice, this
42
+ * list of conditions and the following disclaimer.
43
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
44
+ * this list of conditions and the following disclaimer in the
45
+ * documentation and/or other materials provided with the distribution.
46
+ * 3. Neither the name of the copyright holder nor the names of its contributors
47
+ * may be used to endorse or promote products derived from this software without
48
+ * specific prior written permission.
49
+ *
50
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
51
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
54
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
55
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
56
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
57
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
58
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
59
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60
+ */
61
+ DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
62
+ TomlDate = class TomlDate extends Date {
63
+ #hasDate = false;
64
+ #hasTime = false;
65
+ #offset = null;
66
+ constructor(date) {
67
+ let hasDate = true;
68
+ let hasTime = true;
69
+ let offset = "Z";
70
+ if (typeof date === "string") {
71
+ let match = date.match(DATE_TIME_RE);
72
+ if (match) {
73
+ if (!match[1]) {
74
+ hasDate = false;
75
+ date = `0000-01-01T${date}`;
76
+ }
77
+ hasTime = !!match[2];
78
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
79
+ if (match[2] && +match[2] > 23) {
80
+ date = "";
81
+ } else {
82
+ offset = match[3] || null;
83
+ date = date.toUpperCase();
84
+ if (!offset && hasTime)
85
+ date += "Z";
86
+ }
87
+ } else {
88
+ date = "";
89
+ }
90
+ }
91
+ super(date);
92
+ if (!isNaN(this.getTime())) {
93
+ this.#hasDate = hasDate;
94
+ this.#hasTime = hasTime;
95
+ this.#offset = offset;
96
+ }
97
+ }
98
+ isDateTime() {
99
+ return this.#hasDate && this.#hasTime;
100
+ }
101
+ isLocal() {
102
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
103
+ }
104
+ isDate() {
105
+ return this.#hasDate && !this.#hasTime;
106
+ }
107
+ isTime() {
108
+ return this.#hasTime && !this.#hasDate;
109
+ }
110
+ isValid() {
111
+ return this.#hasDate || this.#hasTime;
112
+ }
113
+ toISOString() {
114
+ let iso = super.toISOString();
115
+ if (this.isDate())
116
+ return iso.slice(0, 10);
117
+ if (this.isTime())
118
+ return iso.slice(11, 23);
119
+ if (this.#offset === null)
120
+ return iso.slice(0, -1);
121
+ if (this.#offset === "Z")
122
+ return iso;
123
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
124
+ offset = this.#offset[0] === "-" ? offset : -offset;
125
+ let offsetDate = new Date(this.getTime() - offset * 60000);
126
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
127
+ }
128
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
129
+ let date = new TomlDate(jsDate);
130
+ date.#offset = offset;
131
+ return date;
132
+ }
133
+ static wrapAsLocalDateTime(jsDate) {
134
+ let date = new TomlDate(jsDate);
135
+ date.#offset = null;
136
+ return date;
137
+ }
138
+ static wrapAsLocalDate(jsDate) {
139
+ let date = new TomlDate(jsDate);
140
+ date.#hasTime = false;
141
+ date.#offset = null;
142
+ return date;
143
+ }
144
+ static wrapAsLocalTime(jsDate) {
145
+ let date = new TomlDate(jsDate);
146
+ date.#hasDate = false;
147
+ date.#offset = null;
148
+ return date;
149
+ }
150
+ };
151
+ });
152
+
153
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/error.js
154
+ function getLineColFromPtr(string, ptr) {
155
+ let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
156
+ return [lines.length, lines.pop().length + 1];
157
+ }
158
+ function makeCodeBlock(string, line, column) {
159
+ let lines = string.split(/\r\n|\n|\r/g);
160
+ let codeblock = "";
161
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
162
+ for (let i = line - 1;i <= line + 1; i++) {
163
+ let l = lines[i - 1];
164
+ if (!l)
165
+ continue;
166
+ codeblock += i.toString().padEnd(numberLen, " ");
167
+ codeblock += ": ";
168
+ codeblock += l;
169
+ codeblock += `
170
+ `;
171
+ if (i === line) {
172
+ codeblock += " ".repeat(numberLen + column + 2);
173
+ codeblock += `^
174
+ `;
175
+ }
176
+ }
177
+ return codeblock;
178
+ }
179
+ var TomlError;
180
+ var init_error = __esm(() => {
181
+ /*!
182
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
183
+ * SPDX-License-Identifier: BSD-3-Clause
184
+ *
185
+ * Redistribution and use in source and binary forms, with or without
186
+ * modification, are permitted provided that the following conditions are met:
187
+ *
188
+ * 1. Redistributions of source code must retain the above copyright notice, this
189
+ * list of conditions and the following disclaimer.
190
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
191
+ * this list of conditions and the following disclaimer in the
192
+ * documentation and/or other materials provided with the distribution.
193
+ * 3. Neither the name of the copyright holder nor the names of its contributors
194
+ * may be used to endorse or promote products derived from this software without
195
+ * specific prior written permission.
196
+ *
197
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
198
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
199
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
200
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
201
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
202
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
203
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
204
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
205
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
206
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
207
+ */
208
+ TomlError = class TomlError extends Error {
209
+ line;
210
+ column;
211
+ codeblock;
212
+ constructor(message, options) {
213
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
214
+ const codeblock = makeCodeBlock(options.toml, line, column);
215
+ super(`Invalid TOML document: ${message}
216
+
217
+ ${codeblock}`, options);
218
+ this.line = line;
219
+ this.column = column;
220
+ this.codeblock = codeblock;
221
+ }
222
+ };
223
+ });
224
+
225
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/primitive.js
226
+ function parseString(str, ptr) {
227
+ let c = str[ptr++];
228
+ let first = c;
229
+ let isLiteral = c === "'";
230
+ let isMultiline = c === str[ptr] && c === str[ptr + 1];
231
+ if (isMultiline) {
232
+ if (str[ptr += 2] === `
233
+ `)
234
+ ptr++;
235
+ else if (str[ptr] === "\r" && str[ptr + 1] === `
236
+ `)
237
+ ptr += 2;
238
+ }
239
+ let parsed = "";
240
+ let sliceStart = ptr;
241
+ let state = 0;
242
+ for (let i = ptr;i < str.length; i++) {
243
+ c = str[i];
244
+ if (isMultiline && (c === `
245
+ ` || c === "\r" && str[i + 1] === `
246
+ `)) {
247
+ state = state && 3;
248
+ } else if (c < " " && c !== "\t" || c === "") {
249
+ throw new TomlError("control characters are not allowed in strings", {
250
+ toml: str,
251
+ ptr: i
252
+ });
253
+ } else if ((!state || state === 3) && c === first && (!isMultiline || str[i + 1] === first && str[i + 2] === first)) {
254
+ if (isMultiline) {
255
+ if (str[i + 3] === first)
256
+ i++;
257
+ if (str[i + 3] === first)
258
+ i++;
259
+ }
260
+ return [
261
+ state ? parsed : parsed + str.slice(sliceStart, i),
262
+ i + (isMultiline ? 3 : 1)
263
+ ];
264
+ } else if (!state) {
265
+ if (!isLiteral && c === "\\") {
266
+ parsed += str.slice(sliceStart, sliceStart = i);
267
+ state = 1;
268
+ }
269
+ } else if (state === 1) {
270
+ if (c === "x" || c === "u" || c === "U") {
271
+ let value = 0;
272
+ let len = c === "x" ? 2 : c === "u" ? 4 : 8;
273
+ for (let j = 0;j < len; j++, i++) {
274
+ let hex = str.charCodeAt(i + 1);
275
+ let digit = hex >= 48 && hex <= 57 ? hex - 48 : hex >= 65 && hex <= 70 ? hex - 65 + 10 : hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1;
276
+ if (digit < 0)
277
+ throw new TomlError("invalid non-hex character in unicode escape", { toml: str, ptr: i + 1 });
278
+ value = value << 4 | digit;
279
+ }
280
+ if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
281
+ throw new TomlError("invalid unicode escape", { toml: str, ptr: i });
282
+ }
283
+ parsed += String.fromCodePoint(value);
284
+ sliceStart = i + 1;
285
+ state = 0;
286
+ } else if (c === " " || c === "\t") {
287
+ state = 2;
288
+ } else {
289
+ if (c === "b")
290
+ parsed += "\b";
291
+ else if (c === "t")
292
+ parsed += "\t";
293
+ else if (c === "n")
294
+ parsed += `
295
+ `;
296
+ else if (c === "f")
297
+ parsed += "\f";
298
+ else if (c === "r")
299
+ parsed += "\r";
300
+ else if (c === "e")
301
+ parsed += "\x1B";
302
+ else if (c === '"')
303
+ parsed += '"';
304
+ else if (c === "\\")
305
+ parsed += "\\";
306
+ else
307
+ throw new TomlError("unrecognized escape sequence", { toml: str, ptr: i });
308
+ sliceStart = i + 1;
309
+ state = 0;
310
+ }
311
+ } else if (c !== " " && c !== "\t") {
312
+ if (state === 2) {
313
+ throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
314
+ toml: str,
315
+ ptr: sliceStart
316
+ });
317
+ }
318
+ state = !isLiteral && c === "\\" ? 1 : 0;
319
+ sliceStart = i;
320
+ }
321
+ }
322
+ throw new TomlError("unfinished string", { toml: str, ptr });
323
+ }
324
+ function parseValue(value, toml, ptr, integersAsBigInt) {
325
+ if (value === "true")
326
+ return true;
327
+ if (value === "false")
328
+ return false;
329
+ if (value === "-inf")
330
+ return -Infinity;
331
+ if (value === "inf" || value === "+inf")
332
+ return Infinity;
333
+ if (value === "nan" || value === "+nan" || value === "-nan")
334
+ return NaN;
335
+ if (value === "-0")
336
+ return integersAsBigInt ? 0n : 0;
337
+ let isInt = INT_REGEX.test(value);
338
+ if (isInt || FLOAT_REGEX.test(value)) {
339
+ if (LEADING_ZERO.test(value)) {
340
+ throw new TomlError("leading zeroes are not allowed", {
341
+ toml,
342
+ ptr
343
+ });
344
+ }
345
+ value = value.replace(/_/g, "");
346
+ let numeric = +value;
347
+ if (isNaN(numeric)) {
348
+ throw new TomlError("invalid number", {
349
+ toml,
350
+ ptr
351
+ });
352
+ }
353
+ if (isInt) {
354
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
355
+ throw new TomlError("integer value cannot be represented losslessly", {
356
+ toml,
357
+ ptr
358
+ });
359
+ }
360
+ if (isInt || integersAsBigInt === true)
361
+ numeric = BigInt(value);
362
+ }
363
+ return numeric;
364
+ }
365
+ const date = new TomlDate(value);
366
+ if (!date.isValid()) {
367
+ throw new TomlError("invalid value", {
368
+ toml,
369
+ ptr
370
+ });
371
+ }
372
+ return date;
373
+ }
374
+ var INT_REGEX, FLOAT_REGEX, LEADING_ZERO;
375
+ var init_primitive = __esm(() => {
376
+ init_date();
377
+ init_error();
378
+ /*!
379
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
380
+ * SPDX-License-Identifier: BSD-3-Clause
381
+ *
382
+ * Redistribution and use in source and binary forms, with or without
383
+ * modification, are permitted provided that the following conditions are met:
384
+ *
385
+ * 1. Redistributions of source code must retain the above copyright notice, this
386
+ * list of conditions and the following disclaimer.
387
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
388
+ * this list of conditions and the following disclaimer in the
389
+ * documentation and/or other materials provided with the distribution.
390
+ * 3. Neither the name of the copyright holder nor the names of its contributors
391
+ * may be used to endorse or promote products derived from this software without
392
+ * specific prior written permission.
393
+ *
394
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
395
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
396
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
397
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
398
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
399
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
400
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
401
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
402
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
403
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
404
+ */
405
+ INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
406
+ FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
407
+ LEADING_ZERO = /^[+-]?0[0-9_]/;
408
+ });
409
+
410
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/util.js
411
+ function indexOfNewline(str, start = 0, end = str.length) {
412
+ let idx = str.indexOf(`
413
+ `, start);
414
+ if (str[idx - 1] === "\r")
415
+ idx--;
416
+ return idx <= end ? idx : -1;
417
+ }
418
+ function skipComment(str, ptr) {
419
+ for (let i = ptr;i < str.length; i++) {
420
+ let c = str[i];
421
+ if (c === `
422
+ `)
423
+ return i;
424
+ if (c === "\r" && str[i + 1] === `
425
+ `)
426
+ return i + 1;
427
+ if (c < " " && c !== "\t" || c === "") {
428
+ throw new TomlError("control characters are not allowed in comments", {
429
+ toml: str,
430
+ ptr
431
+ });
432
+ }
433
+ }
434
+ return str.length;
435
+ }
436
+ function skipVoid(str, ptr, banNewLines, banComments) {
437
+ let c;
438
+ while (true) {
439
+ while ((c = str[ptr]) === " " || c === "\t" || !banNewLines && (c === `
440
+ ` || c === "\r" && str[ptr + 1] === `
441
+ `))
442
+ ptr++;
443
+ if (banComments || c !== "#")
444
+ break;
445
+ ptr = skipComment(str, ptr);
446
+ }
447
+ return ptr;
448
+ }
449
+ function skipUntil(str, ptr, sep, end, banNewLines = false) {
450
+ if (!end) {
451
+ ptr = indexOfNewline(str, ptr);
452
+ return ptr < 0 ? str.length : ptr;
453
+ }
454
+ for (let i = ptr;i < str.length; i++) {
455
+ let c = str[i];
456
+ if (c === "#") {
457
+ i = indexOfNewline(str, i);
458
+ } else if (c === sep) {
459
+ return i + 1;
460
+ } else if (c === end || banNewLines && (c === `
461
+ ` || c === "\r" && str[i + 1] === `
462
+ `)) {
463
+ return i;
464
+ }
465
+ }
466
+ throw new TomlError("cannot find end of structure", {
467
+ toml: str,
468
+ ptr
469
+ });
470
+ }
471
+ var init_util = __esm(() => {
472
+ init_error();
473
+ /*!
474
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
475
+ * SPDX-License-Identifier: BSD-3-Clause
476
+ *
477
+ * Redistribution and use in source and binary forms, with or without
478
+ * modification, are permitted provided that the following conditions are met:
479
+ *
480
+ * 1. Redistributions of source code must retain the above copyright notice, this
481
+ * list of conditions and the following disclaimer.
482
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
483
+ * this list of conditions and the following disclaimer in the
484
+ * documentation and/or other materials provided with the distribution.
485
+ * 3. Neither the name of the copyright holder nor the names of its contributors
486
+ * may be used to endorse or promote products derived from this software without
487
+ * specific prior written permission.
488
+ *
489
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
490
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
491
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
492
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
493
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
494
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
495
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
496
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
497
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
498
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
499
+ */
500
+ });
501
+
502
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/extract.js
503
+ function sliceAndTrimEndOf(str, startPtr, endPtr) {
504
+ let value = str.slice(startPtr, endPtr);
505
+ let commentIdx = value.indexOf("#");
506
+ if (commentIdx > -1) {
507
+ skipComment(str, commentIdx);
508
+ value = value.slice(0, commentIdx);
509
+ }
510
+ return [value.trimEnd(), commentIdx];
511
+ }
512
+ function extractValue(str, ptr, end, depth, integersAsBigInt) {
513
+ if (depth === 0) {
514
+ throw new TomlError("document contains excessively nested structures. aborting.", {
515
+ toml: str,
516
+ ptr
517
+ });
518
+ }
519
+ let c = str[ptr];
520
+ if (c === "[" || c === "{") {
521
+ let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
522
+ if (end) {
523
+ endPtr2 = skipVoid(str, endPtr2);
524
+ if (str[endPtr2] === ",")
525
+ endPtr2++;
526
+ else if (str[endPtr2] !== end) {
527
+ throw new TomlError("expected comma or end of structure", {
528
+ toml: str,
529
+ ptr: endPtr2
530
+ });
531
+ }
532
+ }
533
+ return [value, endPtr2];
534
+ }
535
+ if (c === '"' || c === "'") {
536
+ let [parsed, endPtr2] = parseString(str, ptr);
537
+ if (end) {
538
+ endPtr2 = skipVoid(str, endPtr2);
539
+ if (str[endPtr2] && str[endPtr2] !== "," && str[endPtr2] !== end && str[endPtr2] !== `
540
+ ` && str[endPtr2] !== "\r") {
541
+ throw new TomlError("unexpected character encountered", {
542
+ toml: str,
543
+ ptr: endPtr2
544
+ });
545
+ }
546
+ if (str[endPtr2] === ",")
547
+ endPtr2++;
548
+ }
549
+ return [parsed, endPtr2];
550
+ }
551
+ let endPtr = skipUntil(str, ptr, ",", end);
552
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - (str[endPtr - 1] === "," ? 1 : 0));
553
+ if (!slice[0]) {
554
+ throw new TomlError("incomplete key-value declaration: no value specified", {
555
+ toml: str,
556
+ ptr
557
+ });
558
+ }
559
+ if (end && slice[1] > -1) {
560
+ endPtr = skipVoid(str, ptr + slice[1]);
561
+ if (str[endPtr] === ",")
562
+ endPtr++;
563
+ }
564
+ return [
565
+ parseValue(slice[0], str, ptr, integersAsBigInt),
566
+ endPtr
567
+ ];
568
+ }
569
+ var init_extract = __esm(() => {
570
+ init_primitive();
571
+ init_struct();
572
+ init_util();
573
+ init_error();
574
+ /*!
575
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
576
+ * SPDX-License-Identifier: BSD-3-Clause
577
+ *
578
+ * Redistribution and use in source and binary forms, with or without
579
+ * modification, are permitted provided that the following conditions are met:
580
+ *
581
+ * 1. Redistributions of source code must retain the above copyright notice, this
582
+ * list of conditions and the following disclaimer.
583
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
584
+ * this list of conditions and the following disclaimer in the
585
+ * documentation and/or other materials provided with the distribution.
586
+ * 3. Neither the name of the copyright holder nor the names of its contributors
587
+ * may be used to endorse or promote products derived from this software without
588
+ * specific prior written permission.
589
+ *
590
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
591
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
592
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
593
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
594
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
595
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
596
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
597
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
598
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
599
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
600
+ */
601
+ });
602
+
603
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/struct.js
604
+ function parseKey(str, ptr, end = "=") {
605
+ let dot = ptr - 1;
606
+ let parsed = [];
607
+ let endPtr = str.indexOf(end, ptr);
608
+ if (endPtr < 0) {
609
+ throw new TomlError("incomplete key-value: cannot find end of key", {
610
+ toml: str,
611
+ ptr
612
+ });
613
+ }
614
+ do {
615
+ let c = str[ptr = ++dot];
616
+ if (c !== " " && c !== "\t") {
617
+ if (c === '"' || c === "'") {
618
+ if (c === str[ptr + 1] && c === str[ptr + 2]) {
619
+ throw new TomlError("multiline strings are not allowed in keys", {
620
+ toml: str,
621
+ ptr
622
+ });
623
+ }
624
+ let [part, eos] = parseString(str, ptr);
625
+ dot = str.indexOf(".", eos);
626
+ let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
627
+ let newLine = indexOfNewline(strEnd);
628
+ if (newLine > -1) {
629
+ throw new TomlError("newlines are not allowed in keys", {
630
+ toml: str,
631
+ ptr: ptr + dot + newLine
632
+ });
633
+ }
634
+ if (strEnd.trimStart()) {
635
+ throw new TomlError("found extra tokens after the string part", {
636
+ toml: str,
637
+ ptr: eos
638
+ });
639
+ }
640
+ if (endPtr < eos) {
641
+ endPtr = str.indexOf(end, eos);
642
+ if (endPtr < 0) {
643
+ throw new TomlError("incomplete key-value: cannot find end of key", {
644
+ toml: str,
645
+ ptr
646
+ });
647
+ }
648
+ }
649
+ parsed.push(part);
650
+ } else {
651
+ dot = str.indexOf(".", ptr);
652
+ let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
653
+ if (!KEY_PART_RE.test(part)) {
654
+ throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
655
+ toml: str,
656
+ ptr
657
+ });
658
+ }
659
+ parsed.push(part.trimEnd());
660
+ }
661
+ }
662
+ } while (dot + 1 && dot < endPtr);
663
+ return [parsed, skipVoid(str, endPtr + 1, true, true)];
664
+ }
665
+ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
666
+ let res = {};
667
+ let seen = new Set;
668
+ let c;
669
+ ptr++;
670
+ while ((c = str[ptr++]) !== "}" && c) {
671
+ if (c === ",") {
672
+ throw new TomlError("expected value, found comma", {
673
+ toml: str,
674
+ ptr: ptr - 1
675
+ });
676
+ } else if (c === "#")
677
+ ptr = skipComment(str, ptr);
678
+ else if (c !== " " && c !== "\t" && c !== `
679
+ ` && c !== "\r") {
680
+ let k;
681
+ let t = res;
682
+ let hasOwn = false;
683
+ let [key, keyEndPtr] = parseKey(str, ptr - 1);
684
+ for (let i = 0;i < key.length; i++) {
685
+ if (i)
686
+ t = hasOwn ? t[k] : t[k] = {};
687
+ k = key[i];
688
+ if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
689
+ throw new TomlError("trying to redefine an already defined value", {
690
+ toml: str,
691
+ ptr
692
+ });
693
+ }
694
+ if (!hasOwn && k === "__proto__") {
695
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
696
+ }
697
+ }
698
+ if (hasOwn) {
699
+ throw new TomlError("trying to redefine an already defined value", {
700
+ toml: str,
701
+ ptr
702
+ });
703
+ }
704
+ let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
705
+ seen.add(value);
706
+ t[k] = value;
707
+ ptr = valueEndPtr;
708
+ }
709
+ }
710
+ if (!c) {
711
+ throw new TomlError("unfinished table encountered", {
712
+ toml: str,
713
+ ptr
714
+ });
715
+ }
716
+ return [res, ptr];
717
+ }
718
+ function parseArray(str, ptr, depth, integersAsBigInt) {
719
+ let res = [];
720
+ let c;
721
+ ptr++;
722
+ while ((c = str[ptr++]) !== "]" && c) {
723
+ if (c === ",") {
724
+ throw new TomlError("expected value, found comma", {
725
+ toml: str,
726
+ ptr: ptr - 1
727
+ });
728
+ } else if (c === "#")
729
+ ptr = skipComment(str, ptr);
730
+ else if (c !== " " && c !== "\t" && c !== `
731
+ ` && c !== "\r") {
732
+ let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
733
+ res.push(e[0]);
734
+ ptr = e[1];
735
+ }
736
+ }
737
+ if (!c) {
738
+ throw new TomlError("unfinished array encountered", {
739
+ toml: str,
740
+ ptr
741
+ });
742
+ }
743
+ return [res, ptr];
744
+ }
745
+ var KEY_PART_RE;
746
+ var init_struct = __esm(() => {
747
+ init_primitive();
748
+ init_extract();
749
+ init_util();
750
+ init_error();
751
+ /*!
752
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
753
+ * SPDX-License-Identifier: BSD-3-Clause
754
+ *
755
+ * Redistribution and use in source and binary forms, with or without
756
+ * modification, are permitted provided that the following conditions are met:
757
+ *
758
+ * 1. Redistributions of source code must retain the above copyright notice, this
759
+ * list of conditions and the following disclaimer.
760
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
761
+ * this list of conditions and the following disclaimer in the
762
+ * documentation and/or other materials provided with the distribution.
763
+ * 3. Neither the name of the copyright holder nor the names of its contributors
764
+ * may be used to endorse or promote products derived from this software without
765
+ * specific prior written permission.
766
+ *
767
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
768
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
769
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
770
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
771
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
772
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
773
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
774
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
775
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
776
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
777
+ */
778
+ KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
779
+ });
780
+
781
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/parse.js
782
+ function peekTable(key, table, meta, type) {
783
+ let t = table;
784
+ let m = meta;
785
+ let k;
786
+ let hasOwn = false;
787
+ let state;
788
+ for (let i = 0;i < key.length; i++) {
789
+ if (i) {
790
+ t = hasOwn ? t[k] : t[k] = {};
791
+ m = (state = m[k]).c;
792
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
793
+ return null;
794
+ }
795
+ if (state.t === 2) {
796
+ let l = t.length - 1;
797
+ t = t[l];
798
+ m = m[l].c;
799
+ }
800
+ }
801
+ k = key[i];
802
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
803
+ return null;
804
+ }
805
+ if (!hasOwn) {
806
+ if (k === "__proto__") {
807
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
808
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
809
+ }
810
+ m[k] = {
811
+ t: i < key.length - 1 && type === 2 ? 3 : type,
812
+ d: false,
813
+ i: 0,
814
+ c: {}
815
+ };
816
+ }
817
+ }
818
+ state = m[k];
819
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
820
+ return null;
821
+ }
822
+ if (type === 2) {
823
+ if (!state.d) {
824
+ state.d = true;
825
+ t[k] = [];
826
+ }
827
+ t[k].push(t = {});
828
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
829
+ }
830
+ if (state.d) {
831
+ return null;
832
+ }
833
+ state.d = true;
834
+ if (type === 1) {
835
+ t = hasOwn ? t[k] : t[k] = {};
836
+ } else if (type === 0 && hasOwn) {
837
+ return null;
838
+ }
839
+ return [k, t, state.c];
840
+ }
841
+ function parse(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
842
+ let res = {};
843
+ let meta = {};
844
+ let tbl = res;
845
+ let m = meta;
846
+ for (let ptr = skipVoid(toml, 0);ptr < toml.length; ) {
847
+ if (toml[ptr] === "[") {
848
+ let isTableArray = toml[++ptr] === "[";
849
+ let k = parseKey(toml, ptr += +isTableArray, "]");
850
+ if (isTableArray) {
851
+ if (toml[k[1] - 1] !== "]") {
852
+ throw new TomlError("expected end of table declaration", {
853
+ toml,
854
+ ptr: k[1] - 1
855
+ });
856
+ }
857
+ k[1]++;
858
+ }
859
+ let p = peekTable(k[0], res, meta, isTableArray ? 2 : 1);
860
+ if (!p) {
861
+ throw new TomlError("trying to redefine an already defined table or value", {
862
+ toml,
863
+ ptr
864
+ });
865
+ }
866
+ m = p[2];
867
+ tbl = p[1];
868
+ ptr = k[1];
869
+ } else {
870
+ let k = parseKey(toml, ptr);
871
+ let p = peekTable(k[0], tbl, m, 0);
872
+ if (!p) {
873
+ throw new TomlError("trying to redefine an already defined table or value", {
874
+ toml,
875
+ ptr
876
+ });
877
+ }
878
+ let v = extractValue(toml, k[1], undefined, maxDepth, integersAsBigInt);
879
+ p[1][p[0]] = v[0];
880
+ ptr = v[1];
881
+ }
882
+ ptr = skipVoid(toml, ptr, true);
883
+ if (toml[ptr] && toml[ptr] !== `
884
+ ` && toml[ptr] !== "\r") {
885
+ throw new TomlError("each key-value declaration must be followed by an end-of-line", {
886
+ toml,
887
+ ptr
888
+ });
889
+ }
890
+ ptr = skipVoid(toml, ptr);
891
+ }
892
+ return res;
893
+ }
894
+ var init_parse = __esm(() => {
895
+ init_struct();
896
+ init_extract();
897
+ init_util();
898
+ init_error();
899
+ /*!
900
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
901
+ * SPDX-License-Identifier: BSD-3-Clause
902
+ *
903
+ * Redistribution and use in source and binary forms, with or without
904
+ * modification, are permitted provided that the following conditions are met:
905
+ *
906
+ * 1. Redistributions of source code must retain the above copyright notice, this
907
+ * list of conditions and the following disclaimer.
908
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
909
+ * this list of conditions and the following disclaimer in the
910
+ * documentation and/or other materials provided with the distribution.
911
+ * 3. Neither the name of the copyright holder nor the names of its contributors
912
+ * may be used to endorse or promote products derived from this software without
913
+ * specific prior written permission.
914
+ *
915
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
916
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
917
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
918
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
919
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
920
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
921
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
922
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
923
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
924
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
925
+ */
926
+ });
927
+
928
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/stringify.js
929
+ var init_stringify = __esm(() => {
930
+ /*!
931
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
932
+ * SPDX-License-Identifier: BSD-3-Clause
933
+ *
934
+ * Redistribution and use in source and binary forms, with or without
935
+ * modification, are permitted provided that the following conditions are met:
936
+ *
937
+ * 1. Redistributions of source code must retain the above copyright notice, this
938
+ * list of conditions and the following disclaimer.
939
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
940
+ * this list of conditions and the following disclaimer in the
941
+ * documentation and/or other materials provided with the distribution.
942
+ * 3. Neither the name of the copyright holder nor the names of its contributors
943
+ * may be used to endorse or promote products derived from this software without
944
+ * specific prior written permission.
945
+ *
946
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
947
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
948
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
949
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
950
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
951
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
952
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
953
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
954
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
955
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
956
+ */
957
+ });
958
+
959
+ // ../../node_modules/.bun/smol-toml@1.7.0/node_modules/smol-toml/dist/index.js
960
+ var init_dist = __esm(() => {
961
+ init_parse();
962
+ init_stringify();
963
+ init_date();
964
+ init_error();
965
+ /*!
966
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
967
+ * SPDX-License-Identifier: BSD-3-Clause
968
+ *
969
+ * Redistribution and use in source and binary forms, with or without
970
+ * modification, are permitted provided that the following conditions are met:
971
+ *
972
+ * 1. Redistributions of source code must retain the above copyright notice, this
973
+ * list of conditions and the following disclaimer.
974
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
975
+ * this list of conditions and the following disclaimer in the
976
+ * documentation and/or other materials provided with the distribution.
977
+ * 3. Neither the name of the copyright holder nor the names of its contributors
978
+ * may be used to endorse or promote products derived from this software without
979
+ * specific prior written permission.
980
+ *
981
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
982
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
983
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
984
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
985
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
986
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
987
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
988
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
989
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
990
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
991
+ */
992
+ });
993
+
994
+ // src/color.ts
995
+ function colorLevel(stream = process.stdout) {
996
+ const env = process.env;
997
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== "")
998
+ return 0;
999
+ if (env.FORCE_COLOR === "0" || env.CLICOLOR === "0")
1000
+ return 0;
1001
+ const forced = env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "" || env.CLICOLOR_FORCE !== undefined && env.CLICOLOR_FORCE !== "0";
1002
+ if (!forced) {
1003
+ if (env.TERM === "dumb")
1004
+ return 0;
1005
+ if (!stream.isTTY)
1006
+ return 0;
1007
+ }
1008
+ const term = env.TERM ?? "";
1009
+ const colorterm = env.COLORTERM ?? "";
1010
+ if (/truecolor|24bit/i.test(colorterm))
1011
+ return 3;
1012
+ if (/-256(color)?$/i.test(term) || term === "xterm-kitty" || colorterm !== "")
1013
+ return 2;
1014
+ return 1;
1015
+ }
1016
+ function code(role, level) {
1017
+ switch (role) {
1018
+ case "label":
1019
+ case "quiet":
1020
+ return "2";
1021
+ case "ok":
1022
+ return "32";
1023
+ case "bad":
1024
+ return "31";
1025
+ case "repo":
1026
+ if (level === 3)
1027
+ return `38;2;${REPO.r};${REPO.g};${REPO.b}`;
1028
+ if (level === 2)
1029
+ return `38;5;${REPO_256}`;
1030
+ return "35";
1031
+ case "brand":
1032
+ case "attn": {
1033
+ const bold = role === "attn" ? "1;" : "";
1034
+ if (level === 3)
1035
+ return `${bold}38;2;${BRAND.r};${BRAND.g};${BRAND.b}`;
1036
+ if (level === 2)
1037
+ return `${bold}38;5;${BRAND_256}`;
1038
+ return `${bold}33`;
1039
+ }
1040
+ }
1041
+ }
1042
+ function paint(role, text, level = colorLevel()) {
1043
+ if (level === 0 || text === "")
1044
+ return text;
1045
+ const sgr = code(role, level);
1046
+ return sgr ? `\x1B[${sgr}m${text}\x1B[0m` : text;
1047
+ }
1048
+ function stripColor(text) {
1049
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
1050
+ }
1051
+ var BRAND, BRAND_256 = 214, REPO, REPO_256 = 99;
1052
+ var init_color = __esm(() => {
1053
+ BRAND = { r: 255, g: 198, b: 30 };
1054
+ REPO = { r: 139, g: 92, b: 246 };
1055
+ });
1056
+
1057
+ // src/redaction.ts
1058
+ function hasNoRedactPragma(text) {
1059
+ return text.split(/\r\n|\r|\n/, PRAGMA_SCAN_LINES).some((line) => NO_REDACT_PRAGMA_REGEX.test(line));
1060
+ }
1061
+ function redactText(input, mode = DEFAULT_REDACTION_MODE) {
1062
+ if (mode === "none")
1063
+ return input;
1064
+ let output = input.replace(PEM_PRIVATE_KEY_REGEX, "<PRIVATEKEY>").replace(AUTHORIZATION_HEADER_REGEX, (_match, prefix) => `${prefix}<TOKEN>`).replace(BARE_BEARER_TOKEN_REGEX, (_match, prefix) => `${prefix}<TOKEN>`).replace(URL_CREDENTIALS_REGEX, (_match, prefix) => `${prefix}:<SECRET>@`).replace(QUOTED_SECRET_REGEX, (match, ...args) => {
1065
+ const { keyQuote = "", key, separator, valueQuote } = args.at(-1);
1066
+ if (!isSensitiveKey(key))
1067
+ return match;
1068
+ return `${keyQuote}${key}${keyQuote}${separator}${valueQuote}${placeholderForKey(key)}${valueQuote}`;
1069
+ }).replace(BARE_ASSIGNMENT_SECRET_REGEX, (match, key, separator) => isSensitiveKey(key) ? `${key}${separator}${placeholderForKey(key)}` : match);
1070
+ for (const [pattern, replacement] of TOKEN_PATTERNS) {
1071
+ output = output.replace(pattern, replacement);
1072
+ }
1073
+ if (mode === "secrets")
1074
+ return output;
1075
+ return output.replace(EMAIL_REGEX, "<EMAIL>").replace(PHONE_REGEX, "<PHONENUMBER>").replace(SSN_REGEX, "<SSN>").replace(CREDIT_CARD_CANDIDATE_REGEX, (candidate) => isLikelyCreditCard(candidate) ? "<CREDITCARD>" : candidate);
1076
+ }
1077
+ function countRedactionPlaceholders(text) {
1078
+ return text.match(PLACEHOLDER_REGEX)?.length ?? 0;
1079
+ }
1080
+ function placeholderForKey(key) {
1081
+ if (/api[_-]?key/i.test(key))
1082
+ return "<API_KEY>";
1083
+ if (/password|passwd|pwd|passphrase|secret|private/i.test(key))
1084
+ return "<SECRET>";
1085
+ return "<TOKEN>";
1086
+ }
1087
+ function isSensitiveKey(key) {
1088
+ const normalized = key.replace(/-/g, "_");
1089
+ const lower = normalized.toLowerCase();
1090
+ const compact = lower.replace(/_/g, "");
1091
+ if (COMPACT_SENSITIVE_KEYS.has(compact))
1092
+ return true;
1093
+ const parts = normalized.toUpperCase().split("_").filter(Boolean);
1094
+ const last = parts.at(-1);
1095
+ if (["PASSWORD", "PASSWD", "PWD", "PASSPHRASE", "TOKEN", "SECRET"].includes(last ?? "")) {
1096
+ return true;
1097
+ }
1098
+ if (parts.includes("API") && parts.includes("KEY"))
1099
+ return true;
1100
+ if (parts.includes("ACCESS") && parts.includes("TOKEN"))
1101
+ return true;
1102
+ if (parts.includes("REFRESH") && parts.includes("TOKEN"))
1103
+ return true;
1104
+ if (parts.includes("SECRET") && (parts.includes("KEY") || parts.includes("ACCESS")))
1105
+ return true;
1106
+ if (parts.includes("PRIVATE") && parts.includes("KEY"))
1107
+ return true;
1108
+ return false;
1109
+ }
1110
+ function isLikelyCreditCard(value) {
1111
+ const digits = value.replace(/\D/g, "");
1112
+ if (digits.length < 13 || digits.length > 19)
1113
+ return false;
1114
+ if (!/[ -]/.test(value) && digits.length !== 15 && digits.length !== 16)
1115
+ return false;
1116
+ let sum = 0;
1117
+ let doubleDigit = false;
1118
+ for (let index = digits.length - 1;index >= 0; index -= 1) {
1119
+ let digit = Number(digits[index]);
1120
+ if (doubleDigit) {
1121
+ digit *= 2;
1122
+ if (digit > 9)
1123
+ digit -= 9;
1124
+ }
1125
+ sum += digit;
1126
+ doubleDigit = !doubleDigit;
1127
+ }
1128
+ return sum % 10 === 0;
1129
+ }
1130
+ var DEFAULT_REDACTION_MODE = "secrets", REDACTION_MODES, NO_REDACT_PRAGMA = "sidecar:no-redact", PRAGMA_SCAN_LINES = 30, NO_REDACT_PRAGMA_REGEX, KEY_NAME_PATTERN, QUOTED_SECRET_REGEX, BARE_ASSIGNMENT_SECRET_REGEX, AUTHORIZATION_HEADER_REGEX, PEM_PRIVATE_KEY_REGEX, URL_CREDENTIALS_REGEX, BARE_BEARER_TOKEN_REGEX, TOKEN_PATTERNS, EMAIL_REGEX, PHONE_REGEX, SSN_REGEX, CREDIT_CARD_CANDIDATE_REGEX, PLACEHOLDER_REGEX, COMPACT_SENSITIVE_KEYS;
1131
+ var init_redaction = __esm(() => {
1132
+ REDACTION_MODES = ["none", "secrets", "secrets+pii"];
1133
+ NO_REDACT_PRAGMA_REGEX = new RegExp(String.raw`^\s*[^\w\s]{0,4}\s*${NO_REDACT_PRAGMA}\b`);
1134
+ KEY_NAME_PATTERN = String.raw`[A-Za-z0-9_][A-Za-z0-9_-]*`;
1135
+ QUOTED_SECRET_REGEX = new RegExp(String.raw`(?:(?<keyQuote>["'])|\b)(?<key>${KEY_NAME_PATTERN})\k<keyQuote>` + String.raw`(?<separator>\s*[:=]\s*)(?<valueQuote>["'])(?:\\[^\r\n]|(?!\k<valueQuote>)[^\\\r\n])+\k<valueQuote>`, "g");
1136
+ BARE_ASSIGNMENT_SECRET_REGEX = new RegExp(String.raw`\b(${KEY_NAME_PATTERN})(\s*[:=]\s*)([^\s"',;` + "`" + String.raw`]+)`, "g");
1137
+ AUTHORIZATION_HEADER_REGEX = /\b(authorization\s*[:=]\s*(?:bearer|basic|token)\s+)([^\s"',;`]+)/gi;
1138
+ PEM_PRIVATE_KEY_REGEX = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----/g;
1139
+ URL_CREDENTIALS_REGEX = /(\/\/[^\s/:@"'`]+):([^\s/@"'`]+)@/g;
1140
+ BARE_BEARER_TOKEN_REGEX = /\b(Bearer\s+)(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|[A-Za-z0-9._~+/-]{20,})\b/g;
1141
+ TOKEN_PATTERNS = [
1142
+ [/\bAKIA[0-9A-Z]{16}\b/g, "<API_KEY>"],
1143
+ [/\bsk-ant-[A-Za-z0-9_-]{16,}\b/g, "<API_KEY>"],
1144
+ [/\bsk-[A-Za-z0-9_-]{16,}\b/g, "<API_KEY>"],
1145
+ [/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g, "<TOKEN>"],
1146
+ [/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "<TOKEN>"],
1147
+ [/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "<TOKEN>"],
1148
+ [/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "<TOKEN>"]
1149
+ ];
1150
+ EMAIL_REGEX = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
1151
+ PHONE_REGEX = /\b(?:\+?1[-.\s]?)?(?:\(\d{3}\)\s?|\d{3}[-.\s])\d{3}[-.\s]\d{4}\b/g;
1152
+ SSN_REGEX = /\b\d{3}-\d{2}-\d{4}\b/g;
1153
+ CREDIT_CARD_CANDIDATE_REGEX = /\b(?:\d[ -]*?){13,19}\b/g;
1154
+ PLACEHOLDER_REGEX = /<(?:API_KEY|TOKEN|SECRET|PRIVATEKEY|EMAIL|PHONENUMBER|SSN|CREDITCARD)>/g;
1155
+ COMPACT_SENSITIVE_KEYS = new Set([
1156
+ "apikey",
1157
+ "accesstoken",
1158
+ "refreshtoken",
1159
+ "idtoken",
1160
+ "authtoken",
1161
+ "githubtoken",
1162
+ "bearertoken",
1163
+ "clientsecret",
1164
+ "credential",
1165
+ "credentials",
1166
+ "secretkey",
1167
+ "privatekey",
1168
+ "password",
1169
+ "passwd",
1170
+ "pwd",
1171
+ "passphrase",
1172
+ "token",
1173
+ "secret"
1174
+ ]);
1175
+ });
1176
+
1177
+ // src/health.ts
1178
+ function healthBranch(user, checkoutId) {
1179
+ return `${HEALTH_BRANCH_PREFIX}${user}/${checkoutId}`;
1180
+ }
1181
+ function inboxPrefixCollidesWithHealth(inboxPrefix) {
1182
+ const prefix = inboxPrefix.replace(/^\/+/, "");
1183
+ return prefix.startsWith(HEALTH_BRANCH_PREFIX) || HEALTH_BRANCH_PREFIX.startsWith(prefix);
1184
+ }
1185
+ function isHealthBranch(remoteBranch) {
1186
+ const branch = remoteBranch.startsWith("origin/") ? remoteBranch.slice("origin/".length) : remoteBranch;
1187
+ return branch.startsWith(HEALTH_BRANCH_PREFIX);
1188
+ }
1189
+ function parseHealthRecord(text) {
1190
+ let raw;
1191
+ try {
1192
+ raw = JSON.parse(text);
1193
+ } catch {
1194
+ return;
1195
+ }
1196
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1197
+ return;
1198
+ const record = raw;
1199
+ const status = record.status === "failed" ? "failed" : record.status === "ok" ? "ok" : undefined;
1200
+ if (!status || typeof record.updatedAt !== "string")
1201
+ return;
1202
+ return {
1203
+ schema: typeof record.schema === "number" ? record.schema : 0,
1204
+ machine: typeof record.machine === "string" ? record.machine : "unknown",
1205
+ root: typeof record.root === "string" ? record.root : "",
1206
+ inbox: typeof record.inbox === "string" ? record.inbox : "",
1207
+ version: typeof record.version === "string" ? record.version : "",
1208
+ status,
1209
+ updatedAt: record.updatedAt,
1210
+ lastSuccessAt: typeof record.lastSuccessAt === "string" ? record.lastSuccessAt : undefined,
1211
+ lastFailureAt: typeof record.lastFailureAt === "string" ? record.lastFailureAt : undefined,
1212
+ consecutiveFailures: typeof record.consecutiveFailures === "number" && Number.isFinite(record.consecutiveFailures) ? record.consecutiveFailures : 0,
1213
+ stage: typeof record.stage === "string" ? record.stage : undefined,
1214
+ message: typeof record.message === "string" ? record.message : undefined
1215
+ };
1216
+ }
1217
+ function serializeHealthRecord(record) {
1218
+ return `${JSON.stringify(record, null, 2)}
1219
+ `;
1220
+ }
1221
+ function nextHealthRecord(previous, identity, outcome, now) {
1222
+ const base = {
1223
+ schema: HEALTH_SCHEMA,
1224
+ ...identity,
1225
+ status: outcome.status,
1226
+ updatedAt: now,
1227
+ lastSuccessAt: previous?.lastSuccessAt,
1228
+ lastFailureAt: previous?.lastFailureAt,
1229
+ consecutiveFailures: 0
1230
+ };
1231
+ if (outcome.status === "ok")
1232
+ return { ...base, lastSuccessAt: now };
1233
+ return {
1234
+ ...base,
1235
+ lastFailureAt: now,
1236
+ consecutiveFailures: (previous?.consecutiveFailures ?? 0) + 1,
1237
+ stage: outcome.stage,
1238
+ message: redactText(outcome.message).trim().slice(-MESSAGE_LIMIT)
1239
+ };
1240
+ }
1241
+ function shouldPublishHealth(previous, next, now = Date.now()) {
1242
+ if (!previous)
1243
+ return true;
1244
+ if (next.status === "failed" || previous.status !== next.status)
1245
+ return true;
1246
+ const last = Date.parse(previous.updatedAt);
1247
+ if (!Number.isFinite(last))
1248
+ return true;
1249
+ return now - last >= HEALTH_HEARTBEAT_MS || last > now;
1250
+ }
1251
+ function classifyHealthState(record, now = Date.now(), staleAfterMs = HEALTH_STALE_AFTER_MS) {
1252
+ if (record.status === "failed")
1253
+ return "failed";
1254
+ const updated = Date.parse(record.updatedAt);
1255
+ if (!Number.isFinite(updated))
1256
+ return "stale";
1257
+ return now - updated > staleAfterMs ? "stale" : "ok";
1258
+ }
1259
+ function summarizeHealthStates(states) {
1260
+ const counts = { ok: 0, failed: 0, stale: 0 };
1261
+ for (const state of states)
1262
+ counts[state] += 1;
1263
+ const parts = [];
1264
+ if (counts.ok)
1265
+ parts.push(`${counts.ok} ok`);
1266
+ if (counts.failed)
1267
+ parts.push(`${counts.failed} failed`);
1268
+ if (counts.stale)
1269
+ parts.push(`${counts.stale} stale`);
1270
+ return parts.join(", ") || "none";
1271
+ }
1272
+ var HEALTH_BRANCH_PREFIX = "sidecar-health/", HEALTH_FILE = "health.json", HEALTH_SCHEMA = 1, HEALTH_STALE_AFTER_MS, HEALTH_HEARTBEAT_MS, MESSAGE_LIMIT = 500;
1273
+ var init_health = __esm(() => {
1274
+ init_redaction();
1275
+ HEALTH_STALE_AFTER_MS = 24 * 60 * 60 * 1000;
1276
+ HEALTH_HEARTBEAT_MS = 60 * 60 * 1000;
1277
+ });
1278
+
1279
+ // src/daemon.ts
1280
+ var exports_daemon = {};
1281
+ __export(exports_daemon, {
1282
+ selectWatchTargets: () => selectWatchTargets,
1283
+ runDaemonLoop: () => runDaemonLoop,
1284
+ compileGitignoreMatcher: () => compileGitignoreMatcher,
1285
+ checkAndInstallUpdate: () => checkAndInstallUpdate,
1286
+ WATCH_LIMIT: () => WATCH_LIMIT
1287
+ });
1288
+ import fs from "node:fs";
1289
+ import path from "node:path";
1290
+ import { spawn } from "node:child_process";
1291
+ import { fileURLToPath } from "node:url";
1292
+ async function runDaemonLoop(options) {
1293
+ const state = {
1294
+ options,
1295
+ syncing: new Set,
1296
+ lastSyncEndAt: new Map,
1297
+ pendingTimers: new Map,
1298
+ trailingPending: new Set,
1299
+ failures: new Map,
1300
+ skipUntilCycle: new Map,
1301
+ misses: new Map,
1302
+ watchers: new Map,
1303
+ cycleCount: 0,
1304
+ lastWatchCount: -1,
1305
+ refreshing: false,
1306
+ staleNotified: false
1307
+ };
1308
+ console.log(`sidecar daemon polling every ${options.intervalSeconds}s`);
1309
+ logSidecarEvent("daemon-start", {
1310
+ intervalSeconds: options.intervalSeconds,
1311
+ debounceSeconds: options.debounceSeconds,
1312
+ once: options.once,
1313
+ pid: process.pid
1314
+ });
1315
+ if (options.once) {
1316
+ await runCycle(state);
1317
+ return 0;
1318
+ }
1319
+ await acquireDaemonPid();
1320
+ installShutdownHandlers();
1321
+ await watchRegistry(state);
1322
+ const bootVersion = packageVersion();
1323
+ while (true) {
1324
+ maybeAdoptNewerInstall(state, bootVersion);
1325
+ await runCycle(state);
1326
+ ensureDaemonServiceFile();
1327
+ await refreshWatchers(state);
1328
+ await maybeAutoUpdate();
1329
+ await delay(options.intervalSeconds * 1000);
1330
+ }
1331
+ }
1332
+ function maybeAdoptNewerInstall(state, bootVersion) {
1333
+ const diskVersion = packageVersion();
1334
+ if (diskVersion !== bootVersion) {
1335
+ logSidecarEvent("daemon-stale", { running: bootVersion, installed: diskVersion, reason: "in-place-update" });
1336
+ restartAfterUpdate();
1337
+ return;
1338
+ }
1339
+ const onPath = findGlobalSidecarExecutable();
1340
+ if (!onPath)
1341
+ return;
1342
+ if (realpathOr(onPath) === realpathOr(currentCliPath()))
1343
+ return;
1344
+ const pathVersion = globalSidecarVersion(onPath);
1345
+ if (!pathVersion || pathVersion === diskVersion)
1346
+ return;
1347
+ if (process.stdout.isTTY) {
1348
+ if (!state.staleNotified) {
1349
+ state.staleNotified = true;
1350
+ console.log(`sidecar v${pathVersion} is installed at ${onPath}; run \`sidecar daemon restart\` to switch to it`);
1351
+ }
1352
+ return;
1353
+ }
1354
+ logSidecarEvent("daemon-stale", {
1355
+ running: diskVersion,
1356
+ installed: pathVersion,
1357
+ executable: onPath,
1358
+ reason: "new-install"
1359
+ });
1360
+ const child = spawn(onPath, ["daemon", "restart"], {
1361
+ detached: true,
1362
+ stdio: "ignore",
1363
+ windowsHide: true,
1364
+ env: { ...process.env, [SKIP_LOCAL_EXEC_ENV]: "1", [GLOBAL_EXEC_ENV]: "1" }
1365
+ });
1366
+ child.unref();
1367
+ }
1368
+ async function runCycle(state) {
1369
+ const settings = readSettings();
1370
+ if (!settings.daemonEnabled) {
1371
+ logSidecarEvent("daemon-skip", { reason: "daemon-disabled" });
1372
+ return;
1373
+ }
1374
+ state.cycleCount += 1;
1375
+ let synced = 0;
1376
+ let failed = 0;
1377
+ let skipped = 0;
1378
+ for (const instance of readInstances()) {
1379
+ if (!fs.existsSync(instance.configPath)) {
1380
+ const misses = (state.misses.get(instance.root) ?? 0) + 1;
1381
+ state.misses.set(instance.root, misses);
1382
+ if (misses >= PRUNE_AFTER_MISSES) {
1383
+ pruneInstance(instance.root);
1384
+ state.misses.delete(instance.root);
1385
+ } else {
1386
+ logSidecarEvent("daemon-skip", { root: instance.root, reason: "config-missing", misses });
1387
+ }
1388
+ skipped += 1;
1389
+ continue;
1390
+ }
1391
+ state.misses.delete(instance.root);
1392
+ if (state.cycleCount < (state.skipUntilCycle.get(instance.root) ?? 0)) {
1393
+ skipped += 1;
1394
+ continue;
1395
+ }
1396
+ if (await syncInstance(state, instance.root, "cycle")) {
1397
+ synced += 1;
1398
+ } else {
1399
+ failed += 1;
1400
+ }
1401
+ }
1402
+ logSidecarEvent("daemon-cycle", { synced, failed, skipped });
1403
+ }
1404
+ function pruneInstance(root) {
1405
+ writeInstances(readInstances().filter((instance) => instance.root !== root));
1406
+ logSidecarEvent("daemon-prune", { root, reason: "config-missing" });
1407
+ }
1408
+ async function syncInstance(state, root, trigger) {
1409
+ if (state.syncing.has(root))
1410
+ return false;
1411
+ state.syncing.add(root);
1412
+ let succeeded = false;
1413
+ try {
1414
+ const localCli = localSidecarCliPath(root);
1415
+ const cli = localCli ?? currentCliPath();
1416
+ logSidecarEvent("daemon-sync-start", { root, trigger, local: Boolean(localCli) });
1417
+ const result = await runChild(process.execPath, [cli, "sync"], {
1418
+ cwd: root,
1419
+ env: { ...process.env, [SKIP_LOCAL_EXEC_ENV]: "1", [GLOBAL_EXEC_ENV]: "1", [SOFT_SYNC_ENV]: "1" },
1420
+ timeoutMs: SYNC_TIMEOUT_MS
1421
+ });
1422
+ if (result.status === 0) {
1423
+ state.failures.delete(root);
1424
+ state.skipUntilCycle.delete(root);
1425
+ logSidecarEvent("daemon-sync", { root, trigger, local: Boolean(localCli) });
1426
+ succeeded = true;
1427
+ } else {
1428
+ const failures = (state.failures.get(root) ?? 0) + 1;
1429
+ state.failures.set(root, failures);
1430
+ state.skipUntilCycle.set(root, state.cycleCount + Math.min(2 ** (failures - 1), MAX_BACKOFF_CYCLES));
1431
+ logSidecarEvent("failure", {
1432
+ command: "daemon",
1433
+ root,
1434
+ trigger,
1435
+ message: result.timedOut ? "sync timed out" : result.output.trim().slice(-500) || `sync exited ${result.status}`
1436
+ });
1437
+ }
1438
+ } finally {
1439
+ state.syncing.delete(root);
1440
+ state.lastSyncEndAt.set(root, Date.now());
1441
+ }
1442
+ if (succeeded)
1443
+ await followUpTrailingSync(state, root);
1444
+ else
1445
+ state.trailingPending.delete(root);
1446
+ return succeeded;
1447
+ }
1448
+ async function followUpTrailingSync(state, root) {
1449
+ if (!state.trailingPending.delete(root))
1450
+ return;
1451
+ if (await checkoutIsDirty(root)) {
1452
+ syncInstance(state, root, "watch-followup");
1453
+ }
1454
+ }
1455
+ async function checkoutIsDirty(root) {
1456
+ const sidecarPath = readInstances().find((instance) => instance.root === root)?.sidecarPath;
1457
+ if (!sidecarPath || !fs.existsSync(sidecarPath))
1458
+ return false;
1459
+ const result = await runChild("git", ["-C", sidecarPath, "status", "--porcelain"], { timeoutMs: 30000 });
1460
+ return result.status === 0 && Boolean(result.stdout.trim());
1461
+ }
1462
+ function localSidecarCliPath(root) {
1463
+ if (!projectDependsOnSidecar(root))
1464
+ return;
1465
+ const candidate = path.join(root, "node_modules", PACKAGE_NAME, "dist", "cli.js");
1466
+ if (!isFile(candidate))
1467
+ return;
1468
+ try {
1469
+ if (fs.realpathSync(candidate) === fs.realpathSync(currentCliPath()))
1470
+ return;
1471
+ } catch {}
1472
+ return candidate;
1473
+ }
1474
+ function currentCliPath() {
1475
+ return process.argv[1] || fileURLToPath(import.meta.url);
1476
+ }
1477
+ function selectWatchTargets(instances, limit = WATCH_LIMIT) {
1478
+ return [...instances].filter((instance) => fs.existsSync(instance.configPath) && fs.existsSync(instance.sidecarPath)).sort((left, right) => instanceRecency(right) - instanceRecency(left)).slice(0, limit);
1479
+ }
1480
+ function instanceRecency(instance) {
1481
+ const time = Date.parse(instance.lastSyncAt ?? instance.updatedAt ?? instance.registeredAt);
1482
+ return Number.isFinite(time) ? time : 0;
1483
+ }
1484
+ async function loadChokidar() {
1485
+ if (chokidarModule !== undefined)
1486
+ return chokidarModule;
1487
+ try {
1488
+ chokidarModule = await import("chokidar");
1489
+ } catch (error) {
1490
+ chokidarModule = null;
1491
+ logSidecarEvent("daemon-watch-unavailable", {
1492
+ message: error instanceof Error ? error.message : String(error)
1493
+ });
1494
+ console.log("file watching unavailable; relying on interval sync");
1495
+ }
1496
+ return chokidarModule;
1497
+ }
1498
+ async function refreshWatchers(state) {
1499
+ if (state.refreshing)
1500
+ return;
1501
+ state.refreshing = true;
1502
+ try {
1503
+ const chokidar = await loadChokidar();
1504
+ if (!chokidar)
1505
+ return;
1506
+ const targets = new Map(selectWatchTargets(readInstances()).map((instance) => [instance.root, instance.sidecarPath]));
1507
+ for (const [root, watcher] of [...state.watchers]) {
1508
+ if (targets.has(root))
1509
+ continue;
1510
+ state.watchers.delete(root);
1511
+ await watcher.close().catch(() => {
1512
+ return;
1513
+ });
1514
+ }
1515
+ for (const [root, sidecarPath] of targets) {
1516
+ if (state.watchers.has(root))
1517
+ continue;
1518
+ try {
1519
+ const watcher = chokidar.watch(sidecarPath, {
1520
+ ignored: watchIgnoreMatcher(sidecarPath),
1521
+ ignoreInitial: true,
1522
+ persistent: true
1523
+ });
1524
+ watcher.on("all", () => scheduleWatchSync(state, root));
1525
+ watcher.on("error", (error) => {
1526
+ logSidecarEvent("failure", {
1527
+ command: "daemon",
1528
+ root,
1529
+ message: `watcher error: ${error instanceof Error ? error.message : String(error)}`
1530
+ });
1531
+ });
1532
+ state.watchers.set(root, watcher);
1533
+ } catch (error) {
1534
+ logSidecarEvent("failure", {
1535
+ command: "daemon",
1536
+ root,
1537
+ message: `could not watch ${sidecarPath}: ${error instanceof Error ? error.message : String(error)}`
1538
+ });
1539
+ }
1540
+ }
1541
+ if (state.watchers.size !== state.lastWatchCount) {
1542
+ state.lastWatchCount = state.watchers.size;
1543
+ logSidecarEvent("daemon-watch", { watching: state.watchers.size });
1544
+ }
1545
+ } finally {
1546
+ state.refreshing = false;
1547
+ }
1548
+ }
1549
+ function scheduleWatchSync(state, root) {
1550
+ if (state.syncing.has(root)) {
1551
+ state.trailingPending.add(root);
1552
+ return;
1553
+ }
1554
+ if (Date.now() - (state.lastSyncEndAt.get(root) ?? 0) < SYNC_ECHO_GRACE_MS)
1555
+ return;
1556
+ if (state.pendingTimers.has(root)) {
1557
+ state.trailingPending.add(root);
1558
+ return;
1559
+ }
1560
+ logSidecarEvent("daemon-watch-debounce", { root, windowSeconds: state.options.debounceSeconds });
1561
+ const timer = setTimeout(() => {
1562
+ state.pendingTimers.delete(root);
1563
+ if (state.trailingPending.delete(root)) {
1564
+ if (state.syncing.has(root)) {
1565
+ state.trailingPending.add(root);
1566
+ } else {
1567
+ syncInstance(state, root, "watch-trailing");
1568
+ }
1569
+ }
1570
+ }, state.options.debounceSeconds * 1000);
1571
+ state.pendingTimers.set(root, timer);
1572
+ syncInstance(state, root, "watch");
1573
+ }
1574
+ async function watchRegistry(state) {
1575
+ const chokidar = await loadChokidar();
1576
+ if (!chokidar)
1577
+ return;
1578
+ try {
1579
+ const watcher = chokidar.watch(sidecarStateDir(), { ignoreInitial: true, depth: 0 });
1580
+ watcher.on("all", (...args) => {
1581
+ const filePath = typeof args[1] === "string" ? args[1] : "";
1582
+ if (path.basename(filePath) !== "instances.json")
1583
+ return;
1584
+ if (state.registryTimer)
1585
+ return;
1586
+ state.registryTimer = setTimeout(() => {
1587
+ state.registryTimer = undefined;
1588
+ refreshWatchers(state);
1589
+ }, 5000);
1590
+ });
1591
+ } catch (error) {
1592
+ logSidecarEvent("failure", {
1593
+ command: "daemon",
1594
+ message: `could not watch registry: ${error instanceof Error ? error.message : String(error)}`
1595
+ });
1596
+ }
1597
+ }
1598
+ function compileGitignoreMatcher(lines) {
1599
+ const rules = [];
1600
+ for (const rawLine of lines) {
1601
+ const line = rawLine.replace(/\r$/, "").trim();
1602
+ if (!line || line.startsWith("#") || line.startsWith("!"))
1603
+ continue;
1604
+ let pattern = line.replace(/\/+$/, "");
1605
+ const anchored = pattern.startsWith("/") || pattern.includes("/");
1606
+ pattern = pattern.replace(/^\/+/, "");
1607
+ const body = pattern.split("/").map((segment) => segment === "**" ? "\x00" : segment.split("*").map((piece) => piece.split("?").map(escapeRegex).join("[^/]")).join("[^/]*")).join("/").replaceAll("\x00/", "(?:.*/)?").replaceAll("/\x00", "(?:/.*)?").replaceAll("\x00", ".*");
1608
+ rules.push(new RegExp(`${anchored ? "^" : "(^|.*/)"}${body}(/.*)?$`));
1609
+ }
1610
+ return (relativePath) => {
1611
+ const normalized = relativePath.replace(/\\/g, "/").replace(/\/+$/, "");
1612
+ if (!normalized)
1613
+ return false;
1614
+ return rules.some((rule) => rule.test(normalized));
1615
+ };
1616
+ }
1617
+ function watchIgnoreMatcher(sidecarPath) {
1618
+ let gitignore;
1619
+ try {
1620
+ const ignoreFile = path.join(sidecarPath, ".gitignore");
1621
+ if (fs.existsSync(ignoreFile)) {
1622
+ gitignore = compileGitignoreMatcher(fs.readFileSync(ignoreFile, "utf8").split(`
1623
+ `));
1624
+ }
1625
+ } catch {}
1626
+ const root = path.resolve(sidecarPath);
1627
+ return (candidate) => {
1628
+ const relative = path.relative(root, candidate);
1629
+ if (!relative)
1630
+ return false;
1631
+ const normalized = relative.split(path.sep).join("/");
1632
+ if (normalized.startsWith(".."))
1633
+ return true;
1634
+ if (normalized === ".git" || normalized.startsWith(".git/"))
1635
+ return true;
1636
+ return gitignore ? gitignore(normalized) : false;
1637
+ };
1638
+ }
1639
+ async function checkAndInstallUpdate() {
1640
+ const current = packageVersion();
1641
+ const npm = findExecutableOnPath(process.platform === "win32" ? "npm.cmd" : "npm");
1642
+ if (!npm)
1643
+ return { status: "skipped", current, message: "npm not found on PATH" };
1644
+ const view = await runChild(npm, ["view", PACKAGE_NAME, "version"], { timeoutMs: 60000 });
1645
+ const latest = view.stdout.trim();
1646
+ if (view.status !== 0 || !/^\d+\.\d+\.\d+$/.test(latest)) {
1647
+ return {
1648
+ status: "failed",
1649
+ current,
1650
+ message: `version check failed: ${(latest || view.output.trim()).slice(-200)}`
1651
+ };
1652
+ }
1653
+ if (compareVersions(latest, current) <= 0) {
1654
+ return { status: "current", current, latest };
1655
+ }
1656
+ const source = readSettings().installSource;
1657
+ const usesBun = source ? source === "bun" : isInsidePath(realpathOr(currentCliPath()), realpathOr(bunGlobalRoot()));
1658
+ const bun = usesBun ? findExecutableOnPath(process.platform === "win32" ? "bun.exe" : "bun") : undefined;
1659
+ const installer = bun ?? npm;
1660
+ const args = bun ? ["add", "-g", `${PACKAGE_NAME}@${latest}`] : ["install", "-g", `${PACKAGE_NAME}@${latest}`];
1661
+ const install = await runChild(installer, args, { timeoutMs: 300000 });
1662
+ if (install.status !== 0) {
1663
+ return {
1664
+ status: "failed",
1665
+ current,
1666
+ latest,
1667
+ message: `install of ${latest} failed: ${install.output.trim().slice(-500)}`
1668
+ };
1669
+ }
1670
+ return { status: "updated", current, latest };
1671
+ }
1672
+ async function maybeAutoUpdate() {
1673
+ if (process.env[SKIP_UPDATE_ENV] === "1")
1674
+ return;
1675
+ const settings = readSettings();
1676
+ if (!settings.autoUpdate)
1677
+ return;
1678
+ const last = settings.lastUpdateCheckAt ? Date.parse(settings.lastUpdateCheckAt) : 0;
1679
+ if (Number.isFinite(last) && Date.now() - last < UPDATE_CHECK_INTERVAL_MS)
1680
+ return;
1681
+ writeSettings({ ...settings, lastUpdateCheckAt: new Date().toISOString() });
1682
+ const result = await checkAndInstallUpdate();
1683
+ if (result.status === "updated") {
1684
+ logSidecarEvent("daemon-update", { from: result.current, to: result.latest });
1685
+ ensureDaemonServiceFile();
1686
+ restartAfterUpdate();
1687
+ return;
1688
+ }
1689
+ if (result.status === "current") {
1690
+ logSidecarEvent("daemon-update-check", { current: result.current, latest: result.latest });
1691
+ return;
1692
+ }
1693
+ logSidecarEvent("daemon-update-skip", { reason: result.status, message: result.message });
1694
+ }
1695
+ function restartAfterUpdate() {
1696
+ if (process.stdout.isTTY) {
1697
+ console.log("sidecar updated; restart this daemon to pick up the new version");
1698
+ return;
1699
+ }
1700
+ removeOwnPidFile();
1701
+ if (process.platform === "win32")
1702
+ startDetachedDaemon();
1703
+ process.exit(0);
1704
+ }
1705
+ async function acquireDaemonPid() {
1706
+ const pidPath = daemonPidPath();
1707
+ fs.mkdirSync(path.dirname(pidPath), { recursive: true });
1708
+ while (true) {
1709
+ try {
1710
+ fs.writeFileSync(pidPath, `${process.pid}
1711
+ `, { encoding: "utf8", flag: "wx" });
1712
+ return;
1713
+ } catch (error) {
1714
+ if (error.code !== "EEXIST")
1715
+ throw error;
1716
+ }
1717
+ const holder = readPid(pidPath);
1718
+ if (holder === process.pid)
1719
+ return;
1720
+ if (holder && pidIsSidecarDaemon(holder)) {
1721
+ logSidecarEvent("daemon-wait", { holder });
1722
+ await delay(30000);
1723
+ continue;
1724
+ }
1725
+ logSidecarEvent("daemon-pid-heal", { holder: holder ?? null });
1726
+ fs.rmSync(pidPath, { force: true });
1727
+ }
1728
+ }
1729
+ function installShutdownHandlers() {
1730
+ const shutdown = () => {
1731
+ removeOwnPidFile();
1732
+ process.exit(0);
1733
+ };
1734
+ process.on("SIGTERM", shutdown);
1735
+ process.on("SIGINT", shutdown);
1736
+ process.on("exit", removeOwnPidFile);
1737
+ }
1738
+ function removeOwnPidFile() {
1739
+ try {
1740
+ if (readPid(daemonPidPath()) === process.pid)
1741
+ fs.rmSync(daemonPidPath(), { force: true });
1742
+ } catch {}
1743
+ }
1744
+ function readPid(pidPath) {
1745
+ try {
1746
+ const pid = Number(fs.readFileSync(pidPath, "utf8").trim());
1747
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
1748
+ } catch {
1749
+ return;
1750
+ }
1751
+ }
1752
+ function runChild(command, args, options) {
1753
+ return new Promise((resolve) => {
1754
+ const child = spawn(command, args, {
1755
+ cwd: options.cwd,
1756
+ env: options.env ?? process.env,
1757
+ stdio: ["ignore", "pipe", "pipe"],
1758
+ windowsHide: true
1759
+ });
1760
+ let output = "";
1761
+ let stdout = "";
1762
+ let timedOut = false;
1763
+ const append = (chunk) => {
1764
+ output = (output + chunk.toString("utf8")).slice(-8192);
1765
+ };
1766
+ child.stdout?.on("data", (chunk) => {
1767
+ stdout = (stdout + chunk.toString("utf8")).slice(-8192);
1768
+ append(chunk);
1769
+ });
1770
+ child.stderr?.on("data", append);
1771
+ const timer = setTimeout(() => {
1772
+ timedOut = true;
1773
+ child.kill("SIGKILL");
1774
+ }, options.timeoutMs);
1775
+ child.on("error", (error) => {
1776
+ clearTimeout(timer);
1777
+ resolve({ status: 1, output: output || String(error), stdout, timedOut });
1778
+ });
1779
+ child.on("close", (code2) => {
1780
+ clearTimeout(timer);
1781
+ resolve({ status: code2 ?? 1, output, stdout, timedOut });
1782
+ });
1783
+ });
1784
+ }
1785
+ function isFile(filePath) {
1786
+ try {
1787
+ return fs.statSync(filePath).isFile();
1788
+ } catch {
1789
+ return false;
1790
+ }
1791
+ }
1792
+ function realpathOr(filePath) {
1793
+ try {
1794
+ return fs.realpathSync(filePath);
1795
+ } catch {
1796
+ return path.resolve(filePath);
1797
+ }
1798
+ }
1799
+ function isInsidePath(child, parent) {
1800
+ const relative = path.relative(parent, child);
1801
+ return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
1802
+ }
1803
+ function escapeRegex(value) {
1804
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1805
+ }
1806
+ function delay(ms) {
1807
+ return new Promise((resolve) => setTimeout(resolve, ms));
1808
+ }
1809
+ var SKIP_LOCAL_EXEC_ENV = "SIDECAR_SKIP_LOCAL_EXEC", GLOBAL_EXEC_ENV = "SIDECAR_GLOBAL_EXEC", SKIP_UPDATE_ENV = "SIDECAR_SKIP_UPDATE", WATCH_LIMIT = 100, SYNC_TIMEOUT_MS, UPDATE_CHECK_INTERVAL_MS, PRUNE_AFTER_MISSES = 3, SYNC_ECHO_GRACE_MS = 5000, MAX_BACKOFF_CYCLES = 6, chokidarModule;
1810
+ var init_daemon = __esm(() => {
1811
+ init_cli();
1812
+ SYNC_TIMEOUT_MS = 10 * 60 * 1000;
1813
+ UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
1814
+ });
1815
+
1816
+ // src/cli.ts
1817
+ import crypto from "node:crypto";
1818
+ import fs2 from "node:fs";
1819
+ import os from "node:os";
1820
+ import path2 from "node:path";
1821
+ import { spawn as spawn2, spawnSync } from "node:child_process";
1822
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1823
+ async function main(argv = process.argv.slice(2)) {
1824
+ try {
1825
+ const status = await run(argv);
1826
+ const command = argv[0];
1827
+ if (command && command !== "redact" && command !== "deinit" && shouldUseGlobalRegistry()) {
1828
+ logSidecarEvent("command", { command, status });
1829
+ }
1830
+ return status;
1831
+ } catch (error) {
1832
+ const command = argv[0] || "unknown";
1833
+ if (command !== "redact" && command !== "deinit" && shouldUseGlobalRegistry()) {
1834
+ logSidecarEvent("failure", {
1835
+ command,
1836
+ message: error instanceof Error ? error.message : String(error)
1837
+ });
1838
+ }
1839
+ if (error instanceof SidecarError) {
1840
+ console.error(`${paint("bad", "sidecar:", colorLevel(process.stderr))} ${error.message}`);
1841
+ return 1;
1842
+ }
1843
+ if (error instanceof Error && error.name === "AbortError") {
1844
+ console.error("sidecar: stopped");
1845
+ return 130;
1846
+ }
1847
+ throw error;
1848
+ }
1849
+ }
1850
+ function run(argv) {
1851
+ const [command, ...rest] = argv;
1852
+ if (!command) {
1853
+ printUsage("stderr");
1854
+ return 1;
1855
+ }
1856
+ if (command === "--help" || command === "-h" || command === "help") {
1857
+ printUsage("stdout");
1858
+ return 0;
1859
+ }
1860
+ if (command === "--version" || command === "-v" || command === "version") {
1861
+ console.log(packageVersion());
1862
+ return 0;
1863
+ }
1864
+ switch (command) {
1865
+ case "init":
1866
+ return cmdInit(rest);
1867
+ case "clone":
1868
+ return cmdClone(rest);
1869
+ case "deinit":
1870
+ return cmdDeinit(rest);
1871
+ case "status":
1872
+ return cmdStatus(rest);
1873
+ case "health":
1874
+ return cmdHealth(rest);
1875
+ case "instances":
1876
+ return cmdInstances(rest);
1877
+ case "tail":
1878
+ return cmdTail(rest);
1879
+ case "daemon":
1880
+ return cmdDaemon(rest);
1881
+ case "register-install":
1882
+ return cmdRegisterInstall(rest);
1883
+ case "set-install-source":
1884
+ return cmdSetInstallSource(rest);
1885
+ case "update":
1886
+ return cmdUpdate(rest);
1887
+ case "snapshot":
1888
+ return cmdSnapshot(rest);
1889
+ case "sync":
1890
+ return cmdSync(rest);
1891
+ case "merge":
1892
+ return cmdMerge(rest);
1893
+ case "redact":
1894
+ return cmdRedact(rest);
1895
+ case "redactions":
1896
+ return cmdRedactions(rest);
1897
+ default: {
1898
+ const suggestion = closestCommand(command);
1899
+ throw new SidecarError(`unknown command ${JSON.stringify(command)}${suggestion ? `; did you mean ${JSON.stringify(suggestion)}?` : ""}`);
1900
+ }
1901
+ }
1902
+ }
1903
+ function closestCommand(input) {
1904
+ let best;
1905
+ for (const command of KNOWN_COMMANDS) {
1906
+ const distance = editDistance(input.toLowerCase(), command);
1907
+ if (!best || distance < best.distance)
1908
+ best = { command, distance };
1909
+ }
1910
+ return best && best.distance <= 2 ? best.command : undefined;
1911
+ }
1912
+ function editDistance(a, b) {
1913
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
1914
+ for (let i = 1;i <= a.length; i += 1) {
1915
+ let previous = row[0];
1916
+ row[0] = i;
1917
+ for (let j = 1;j <= b.length; j += 1) {
1918
+ const current = row[j];
1919
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
1920
+ previous = current;
1921
+ }
1922
+ }
1923
+ return row[b.length];
1924
+ }
1925
+ function printUsage(target) {
1926
+ const write = target === "stdout" ? console.log : console.error;
1927
+ const level = colorLevel(target === "stdout" ? process.stdout : process.stderr);
1928
+ const header = (text) => paint("label", text, level);
1929
+ write(`usage: sidecar <command> [options]
1930
+
1931
+ ${header("common:")}
1932
+ init [remote] [--path sidecar|.] [--branch main] [--inbox template] [--redaction none|secrets|secrets+pii] [--local-install]
1933
+ --path . makes this repo itself the sidecar (standalone)
1934
+ --local-install adds the devDependency so fresh clones self-register
1935
+ status [--json]
1936
+ health [--json] [--no-fetch]
1937
+ how every machine sharing this sidecar is syncing
1938
+ redactions preview what redaction rewrites before content is pushed
1939
+
1940
+ ${header("sync & daemon:")}
1941
+ sync [--no-snapshot] [--soft] [-m message]
1942
+ daemon status|enable|disable|restart|autoupdate on|off|run [--once] [--interval seconds]
1943
+ instances [--json]
1944
+ tail [-f|--follow] [-n|--lines count]
1945
+ update
1946
+
1947
+ ${header("advanced (mostly run for you by init, git, and the daemon):")}
1948
+ clone [--if-missing]
1949
+ deinit
1950
+ snapshot [--push] [-m message]
1951
+ merge [--fork-files] [--no-push]
1952
+ redact git clean filter: stdin -> redacted stdout
1953
+ register-install
1954
+ set-install-source npm|bun|curl [--if-unset]`);
1955
+ }
1956
+ function cmdDeinit(args) {
1957
+ if (args.length)
1958
+ throw new SidecarError("usage: sidecar deinit");
1959
+ const root = gitToplevelOptional(process.cwd());
1960
+ if (!root) {
1961
+ console.error("sidecar: warning: not inside a Git repository; nothing to remove");
1962
+ return 0;
1963
+ }
1964
+ const configPath = path2.join(root, ".sidecar");
1965
+ const leftovers = [];
1966
+ let config;
1967
+ if (fs2.existsSync(configPath)) {
1968
+ try {
1969
+ config = readConfig(configPath);
1970
+ } catch {
1971
+ leftovers.push(`could not read ${configPath}, so its checkout and ignore entries were left in place`);
1972
+ }
1973
+ } else {
1974
+ leftovers.push("no .sidecar config found; a leftover checkout or ignore entries may remain");
1975
+ }
1976
+ if (config && isStandalone(config)) {
1977
+ const leftover = releaseStandaloneCheckout(root, config);
1978
+ if (leftover)
1979
+ leftovers.push(leftover);
1980
+ } else if (!config) {
1981
+ removeRedactionFilter(root);
1982
+ }
1983
+ fs2.rmSync(configPath, { force: true });
1984
+ if (config && !isStandalone(config)) {
1985
+ const checkoutPath = path2.resolve(root, config.path);
1986
+ if (checkoutPath !== path2.resolve(root) && checkoutPath !== path2.parse(checkoutPath).root) {
1987
+ fs2.rmSync(checkoutPath, { recursive: true, force: true });
1988
+ }
1989
+ const ignoreEntry = ignoreEntryForSidecarPath(root, config.path);
1990
+ if (ignoreEntry) {
1991
+ removeIgnoreEntry(path2.join(root, ".gitignore"), ignoreEntry);
1992
+ removeIgnoreEntry(path2.join(gitCommonDir(root), "info", "exclude"), ignoreEntry);
1993
+ removeZedInclusion(root, ignoreEntry);
1994
+ }
1995
+ }
1996
+ removeLegacyGitHooks(root);
1997
+ unregisterInstance(root);
1998
+ console.log(`removed sidecar from ${paint("repo", root)}`);
1999
+ if (leftovers.length) {
2000
+ for (const leftover of leftovers) {
2001
+ console.error(`sidecar: warning: ${leftover}`);
2002
+ }
2003
+ console.error("sidecar: deinit could not fully complete; to finish removal, ask your agent to scrub any remaining traces of sidecar");
2004
+ }
2005
+ return 0;
2006
+ }
2007
+ function releaseStandaloneCheckout(root, config) {
2008
+ removeRedactionFilter(root);
2009
+ const current = git(root, ["branch", "--show-current"], { check: false }).stdout.trim();
2010
+ if (current === config.branch)
2011
+ return;
2012
+ if (config.redaction !== "none") {
2013
+ return `the repo is still on ${current || "a detached HEAD"}: switching to ${config.branch} would replace local files with their redacted pushed contents`;
2014
+ }
2015
+ if (git(root, ["switch", config.branch], { check: false }).status === 0) {
2016
+ console.log(`switched back to ${config.branch}`);
2017
+ return;
2018
+ }
2019
+ return `could not switch to ${config.branch}; the repo is still on ${current || "a detached HEAD"}`;
2020
+ }
2021
+ function cmdInit(args) {
2022
+ const parsed = parseOptions(args, {
2023
+ boolean: new Set(["--no-clone", "--no-bootstrap-main", "--local-install"]),
2024
+ value: new Set(["--path", "--branch", "--inbox", "--redaction"])
2025
+ });
2026
+ if (parsed.positional.length > 1) {
2027
+ throw new SidecarError("usage: sidecar init [remote] [--path sidecar] [--branch main] [--inbox template] [--redaction mode]");
2028
+ }
2029
+ const remote = parsed.positional[0];
2030
+ let existingRoot = remote ? undefined : findConfigRootOptional(process.cwd());
2031
+ const root = existingRoot ?? gitToplevel(process.cwd());
2032
+ const configPath = path2.join(root, ".sidecar");
2033
+ if (remote && fs2.existsSync(configPath)) {
2034
+ const existing = readConfig(configPath);
2035
+ const unchanged = existing.remote === remote && existing.path === getValue(parsed, "--path", existing.path) && existing.branch === getValue(parsed, "--branch", existing.branch) && existing.inbox === getValue(parsed, "--inbox", existing.inbox) && existing.redaction === getValue(parsed, "--redaction", existing.redaction);
2036
+ if (unchanged || !promptOverwriteConfig(configPath, existing.remote, remote)) {
2037
+ existingRoot = root;
2038
+ }
2039
+ }
2040
+ const config = existingRoot ? readConfig(configPath) : buildInitConfig(root, remote, parsed);
2041
+ if (!existingRoot) {
2042
+ validateRemote(config.remote);
2043
+ validateBranch(config.branch);
2044
+ validateInboxTemplate(config.inbox);
2045
+ writeConfig(configPath, config);
2046
+ }
2047
+ console.log(`${existingRoot ? "using" : "wrote"} ${paint("brand", configPath)}`);
2048
+ if (isStandalone(config)) {
2049
+ console.log(`standalone: ${paint("repo", root)} is the sidecar`);
2050
+ } else {
2051
+ printCheckoutVisibility(root, config);
2052
+ }
2053
+ offerLocalInstall(root, config, parsed.flags.has("--local-install"));
2054
+ if (removeLegacyGitHooks(root)) {
2055
+ console.log("removed legacy sidecar git hooks; syncing is manual or via the global daemon");
2056
+ }
2057
+ if (!parsed.flags.has("--no-clone")) {
2058
+ cloneOrUpdate(root, config, !parsed.flags.has("--no-bootstrap-main"));
2059
+ }
2060
+ registerCurrentInstance(root, config, { event: "init" });
2061
+ const globalSidecar = ensureGlobalSidecar();
2062
+ if (globalSidecar) {
2063
+ registerInstallWithGlobalSidecar(globalSidecar, root);
2064
+ ensureDaemonSetup(globalSidecar);
2065
+ }
2066
+ if (isStandalone(config) && !parsed.flags.has("--no-clone")) {
2067
+ const synced = withSyncLock(root, "skip", () => {
2068
+ syncProject(root, config, { snapshot: true });
2069
+ });
2070
+ if (synced)
2071
+ registerCurrentInstance(root, config, { event: "sync", lastSyncAt: nowIso() });
2072
+ }
2073
+ return 0;
2074
+ }
2075
+ function buildInitConfig(root, remote, parsed) {
2076
+ const rawPath = parsed.values.has("--path") ? getValue(parsed, "--path", DEFAULT_PATH) : promptSidecarPath(root);
2077
+ const sidecarPath = pathIsRepoRoot(root, rawPath) ? "." : rawPath;
2078
+ const standalone = isStandalonePath(sidecarPath);
2079
+ return {
2080
+ remote: remote ?? (standalone ? standaloneRemote(root) : promptRemote(root)),
2081
+ version: 1,
2082
+ path: sidecarPath,
2083
+ branch: getValue(parsed, "--branch", DEFAULT_BRANCH),
2084
+ inbox: getValue(parsed, "--inbox", DEFAULT_INBOX),
2085
+ redaction: parsed.values.has("--redaction") ? redactionModeConfigValue(getValue(parsed, "--redaction", DEFAULT_REDACTION_MODE), "--redaction") : promptRedactionMode()
2086
+ };
2087
+ }
2088
+ function printCheckoutVisibility(root, config) {
2089
+ const ignoreEntry = ensureSidecarIgnored(root, config.path);
2090
+ if (!ignoreEntry) {
2091
+ console.log(`sidecar path outside repo; not updating .gitignore`);
2092
+ return;
2093
+ }
2094
+ const name = ignoreEntry.replace(/\/+$/, "");
2095
+ console.log(`ignored ${name}/ via .gitignore`);
2096
+ if (hasZedInclusion(root, ignoreEntry)) {
2097
+ console.log(`included ${name}/ in Zed file search via .zed/settings.json`);
2098
+ } else if (promptYesNo(`include ${name}/ in Zed file search via .zed/settings.json?`)) {
2099
+ if (ensureZedInclusion(root, ignoreEntry)) {
2100
+ console.log(`included ${name}/ in Zed file search via .zed/settings.json`);
2101
+ } else {
2102
+ console.log(`could not parse .zed/settings.json; add "${name}/**" to file_scan_inclusions manually`);
2103
+ }
2104
+ }
2105
+ }
2106
+ function offerLocalInstall(root, config, forced) {
2107
+ const manifestPath = path2.join(root, "package.json");
2108
+ if (!fs2.existsSync(manifestPath)) {
2109
+ if (forced)
2110
+ throw new SidecarError("--local-install requires a package.json");
2111
+ return;
2112
+ }
2113
+ if (projectDependsOnSidecar(root))
2114
+ return;
2115
+ let source;
2116
+ let manifest;
2117
+ try {
2118
+ source = fs2.readFileSync(manifestPath, "utf8");
2119
+ const parsed = JSON.parse(source);
2120
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2121
+ throw new Error("not an object");
2122
+ manifest = parsed;
2123
+ } catch {
2124
+ console.error(`sidecar: warning: could not parse ${manifestPath}; add ${PACKAGE_NAME} to devDependencies manually so fresh clones self-register on install`);
2125
+ return;
2126
+ }
2127
+ if (!forced && !promptYesNo(`add ${PACKAGE_NAME} to devDependencies so fresh clones self-register on install?`)) {
2128
+ return;
2129
+ }
2130
+ manifest.devDependencies = {
2131
+ ...manifest.devDependencies,
2132
+ [PACKAGE_NAME]: `^${packageVersion()}`
2133
+ };
2134
+ const managers = detectPackageManagers(root);
2135
+ if (managers.has("bun")) {
2136
+ manifest.trustedDependencies = withEntry(manifest.trustedDependencies, PACKAGE_NAME);
2137
+ }
2138
+ if (managers.has("pnpm")) {
2139
+ const pnpm = { ...manifest.pnpm };
2140
+ pnpm.onlyBuiltDependencies = withEntry(pnpm.onlyBuiltDependencies, PACKAGE_NAME);
2141
+ manifest.pnpm = pnpm;
2142
+ }
2143
+ const indent = /^([ \t]+)"/m.exec(source)?.[1] ?? " ";
2144
+ fs2.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, indent)}
2145
+ `);
2146
+ console.log(`added ${paint("brand", PACKAGE_NAME)} to devDependencies; run your package manager's install to pin it`);
2147
+ if (managers.has("bun")) {
2148
+ console.log("trusted its postinstall via trustedDependencies (bun blocks lifecycle scripts by default)");
2149
+ }
2150
+ if (managers.has("pnpm")) {
2151
+ console.log("trusted its postinstall via pnpm.onlyBuiltDependencies (pnpm blocks lifecycle scripts by default)");
2152
+ }
2153
+ if (!managers.size) {
2154
+ console.error(`sidecar: warning: no lockfile found, so the package manager is unknown — bun and pnpm block postinstall scripts by default; if this repo uses one of them, add the trust entry manually`);
2155
+ }
2156
+ if (isStandalone(config) && git(root, ["check-ignore", "-q", "node_modules"], { check: false }).status !== 0) {
2157
+ console.error("sidecar: warning: node_modules is not gitignored; add it before installing or the next sync will snapshot the whole dependency tree");
2158
+ }
2159
+ }
2160
+ function withEntry(value, entry) {
2161
+ const entries = Array.isArray(value) ? value : [];
2162
+ return entries.includes(entry) ? entries : [...entries, entry];
2163
+ }
2164
+ function detectPackageManagers(root) {
2165
+ const lockfiles = [
2166
+ ["bun.lock", "bun"],
2167
+ ["bun.lockb", "bun"],
2168
+ ["pnpm-lock.yaml", "pnpm"],
2169
+ ["package-lock.json", "npm"],
2170
+ ["yarn.lock", "yarn"]
2171
+ ];
2172
+ return new Set(lockfiles.filter(([file]) => fs2.existsSync(path2.join(root, file))).map(([, manager]) => manager));
2173
+ }
2174
+ function ensureDaemonSetup(globalSidecar) {
2175
+ if (process.env[SKIP_SERVICE_ENV] === "1")
2176
+ return;
2177
+ if (!readSettings().daemonEnabled)
2178
+ return;
2179
+ const service = daemonServiceStatus();
2180
+ if (!service.available || service.installed && service.running)
2181
+ return;
2182
+ const result = spawnSync(globalSidecar, ["daemon", "enable"], {
2183
+ encoding: "utf8",
2184
+ env: {
2185
+ ...process.env,
2186
+ [SKIP_LOCAL_EXEC_ENV2]: "1",
2187
+ [GLOBAL_EXEC_ENV2]: "1"
2188
+ }
2189
+ });
2190
+ if (result.status !== 0) {
2191
+ console.log(`could not enable the sync daemon: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}; run \`sidecar daemon enable\` manually`);
2192
+ return;
2193
+ }
2194
+ console.log("enabled the sidecar daemon for background sync");
2195
+ }
2196
+ function ensureGlobalSidecar() {
2197
+ const installHint = `install with \`npm install -g ${PACKAGE_SPEC}\``;
2198
+ const globalSidecar = findGlobalSidecarExecutable();
2199
+ if (!globalSidecar) {
2200
+ if (!process.stdin.isTTY) {
2201
+ console.log(`no global sidecar found; ${installHint} to enable daemon auto sync`);
2202
+ return;
2203
+ }
2204
+ if (promptYesNo("no global sidecar found; install it now for daemon auto sync?")) {
2205
+ installGlobalSidecar();
2206
+ return findGlobalSidecarExecutable();
2207
+ }
2208
+ return;
2209
+ }
2210
+ const globalVersion = globalSidecarVersion(globalSidecar);
2211
+ const currentVersion = packageVersion();
2212
+ if (globalVersion && compareVersions(globalVersion, currentVersion) >= 0)
2213
+ return globalSidecar;
2214
+ const state = globalVersion ? `v${globalVersion}` : "an unknown version";
2215
+ if (!process.stdin.isTTY) {
2216
+ console.log(`global sidecar is ${state} (current v${currentVersion}); ${installHint.replace("install with", "update with")}`);
2217
+ return globalSidecar;
2218
+ }
2219
+ if (promptYesNo(`global sidecar is ${state} (current v${currentVersion}); update it now?`)) {
2220
+ installGlobalSidecar();
2221
+ return findGlobalSidecarExecutable() ?? globalSidecar;
2222
+ }
2223
+ return globalSidecar;
2224
+ }
2225
+ function registerInstallWithGlobalSidecar(executable, root) {
2226
+ const result = spawnSync(executable, ["register-install"], {
2227
+ cwd: root,
2228
+ encoding: "utf8",
2229
+ env: {
2230
+ ...process.env,
2231
+ [SKIP_LOCAL_EXEC_ENV2]: "1",
2232
+ [GLOBAL_EXEC_ENV2]: "1"
2233
+ }
2234
+ });
2235
+ if (result.status !== 0) {
2236
+ throw new SidecarError(`global sidecar registration failed: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}`);
2237
+ }
2238
+ }
2239
+ function findGlobalSidecarExecutable() {
2240
+ const names = process.platform === "win32" ? ["sidecar.cmd", "sidecar.ps1", "sidecar"] : ["sidecar"];
2241
+ for (const entry of (process.env.PATH || "").split(path2.delimiter).filter(Boolean)) {
2242
+ for (const name of names) {
2243
+ const candidate = path2.join(entry, name);
2244
+ if (!isFilePath(candidate))
2245
+ continue;
2246
+ if (isProjectLocalPath(realpathOr2(candidate)))
2247
+ continue;
2248
+ return candidate;
2249
+ }
2250
+ }
2251
+ return;
2252
+ }
2253
+ function globalSidecarVersion(executable) {
2254
+ const result = spawnSync(executable, ["--version"], {
2255
+ encoding: "utf8",
2256
+ env: { ...process.env, [SKIP_LOCAL_EXEC_ENV2]: "1" }
2257
+ });
2258
+ if (result.status !== 0)
2259
+ return;
2260
+ const version = result.stdout.trim();
2261
+ return /^\d+\.\d+\.\d+$/.test(version) ? version : undefined;
2262
+ }
2263
+ function installGlobalSidecar() {
2264
+ const bun = findExecutableOnPath(process.platform === "win32" ? "bun.exe" : "bun");
2265
+ const command = bun ? [bun, "add", "-g", PACKAGE_SPEC] : ["npm", "install", "-g", PACKAGE_SPEC];
2266
+ console.log(`running ${command.join(" ")}`);
2267
+ const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
2268
+ if (result.status !== 0) {
2269
+ throw new SidecarError(`global sidecar install failed; run \`${command.join(" ")}\` manually`);
2270
+ }
2271
+ writeSettings({ ...readSettings(), installSource: bun ? "bun" : "npm" });
2272
+ }
2273
+ function findExecutableOnPath(name) {
2274
+ for (const entry of (process.env.PATH || "").split(path2.delimiter).filter(Boolean)) {
2275
+ const candidate = path2.join(entry, name);
2276
+ if (isFilePath(candidate))
2277
+ return candidate;
2278
+ }
2279
+ return;
2280
+ }
2281
+ function isFilePath(filePath) {
2282
+ try {
2283
+ return fs2.statSync(filePath).isFile();
2284
+ } catch {
2285
+ return false;
2286
+ }
2287
+ }
2288
+ function compareVersions(a, b) {
2289
+ const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
2290
+ const right = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
2291
+ for (let index = 0;index < Math.max(left.length, right.length); index += 1) {
2292
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
2293
+ if (diff)
2294
+ return diff < 0 ? -1 : 1;
2295
+ }
2296
+ return 0;
2297
+ }
2298
+ function cmdClone(args) {
2299
+ const parsed = parseOptions(args, {
2300
+ boolean: new Set(["--no-bootstrap-main", "--if-missing"]),
2301
+ value: new Set
2302
+ });
2303
+ if (parsed.positional.length)
2304
+ throw new SidecarError("usage: sidecar clone [--if-missing] [--no-bootstrap-main]");
2305
+ const [root, config] = loadProject();
2306
+ removeLegacyGitHooks(root);
2307
+ if (parsed.flags.has("--if-missing")) {
2308
+ const sidecarPath = resolveSidecarPath(root, config);
2309
+ if (fs2.existsSync(sidecarPath) && hasGitMetadata(sidecarPath))
2310
+ return 0;
2311
+ }
2312
+ cloneOrUpdate(root, config, !parsed.flags.has("--no-bootstrap-main"));
2313
+ registerCurrentInstance(root, config, { event: "clone" });
2314
+ return 0;
2315
+ }
2316
+ function labelLine(width, label, value, role, indent = "") {
2317
+ const padded = `${label}:`.padEnd(width);
2318
+ console.log(`${indent}${paint("label", padded)} ${role ? paint(role, value) : value}`);
2319
+ }
2320
+ function statusLine(label, value, role) {
2321
+ labelLine(STATUS_LABEL_WIDTH, label, value, role);
2322
+ }
2323
+ function formatTimestampPair(iso) {
2324
+ const relative = formatRelativeTime(iso);
2325
+ const absolute = formatLocalTimestamp(iso);
2326
+ if (!relative || !absolute)
2327
+ return iso;
2328
+ return `${relative} ${paint("quiet", `(${absolute})`)}`;
2329
+ }
2330
+ function cmdStatus(args) {
2331
+ const parsed = parseOptions(args, { boolean: new Set(["--json"]), value: new Set });
2332
+ if (parsed.positional.length)
2333
+ throw new SidecarError("usage: sidecar status [--json]");
2334
+ if (parsed.flags.has("--json"))
2335
+ return cmdStatusJson();
2336
+ const [root, config] = loadProject();
2337
+ const sidecarPath = resolveSidecarPath(root, config);
2338
+ const checkoutPresent = hasGitMetadata(sidecarPath);
2339
+ const inbox = expandInbox(config, checkoutPresent ? sidecarPath : undefined);
2340
+ if (isStandalone(config)) {
2341
+ statusLine("standalone", root, "repo");
2342
+ } else {
2343
+ statusLine("main repo", root, "repo");
2344
+ statusLine("sidecar path", sidecarPath, "brand");
2345
+ }
2346
+ statusLine("remote", config.remote, "brand");
2347
+ statusLine("main branch", config.branch);
2348
+ statusLine("inbox branch", inbox);
2349
+ if (!checkoutPresent) {
2350
+ statusLine("checkout", "missing", "bad");
2351
+ printDaemonLine();
2352
+ printLastSyncLine(root);
2353
+ return 0;
2354
+ }
2355
+ const branch = git(sidecarPath, ["branch", "--show-current"]).stdout.trim();
2356
+ const dirty = Boolean(git(sidecarPath, ["status", "--porcelain"]).stdout.trim());
2357
+ statusLine("checkout", "present");
2358
+ if (!branch)
2359
+ statusLine("branch", "(detached)", "attn");
2360
+ else if (branch === inbox)
2361
+ statusLine("branch", branch);
2362
+ else
2363
+ statusLine("branch", `${branch} — not the inbox branch; sync will switch back`, "attn");
2364
+ statusLine("dirty", dirty ? "yes" : "no", dirty ? "attn" : "quiet");
2365
+ printDaemonLine();
2366
+ printLastSyncLine(root);
2367
+ const pending = pendingStatusInboxBranches(sidecarPath, config);
2368
+ if (pending.length) {
2369
+ statusLine("pending inbox", String(pending.length), "attn");
2370
+ for (const branchName of pending)
2371
+ console.log(` ${paint("brand", branchName)}`);
2372
+ } else {
2373
+ statusLine("pending inbox", "none", "quiet");
2374
+ }
2375
+ return 0;
2376
+ }
2377
+ function cmdStatusJson() {
2378
+ const [root, config] = loadProject();
2379
+ const sidecarPath = resolveSidecarPath(root, config);
2380
+ const checkoutPresent = hasGitMetadata(sidecarPath);
2381
+ const inbox = expandInbox(config, checkoutPresent ? sidecarPath : undefined);
2382
+ const branch = checkoutPresent ? git(sidecarPath, ["branch", "--show-current"]).stdout.trim() : undefined;
2383
+ const payload = {
2384
+ root,
2385
+ sidecarPath,
2386
+ standalone: isStandalone(config),
2387
+ remote: config.remote,
2388
+ branch: config.branch,
2389
+ inbox,
2390
+ checkout: checkoutPresent ? "present" : "missing",
2391
+ currentBranch: branch || undefined,
2392
+ dirty: checkoutPresent ? Boolean(git(sidecarPath, ["status", "--porcelain"]).stdout.trim()) : undefined,
2393
+ daemon: daemonHealth().text,
2394
+ lastSyncAt: readInstances().find((instance) => instance.root === root)?.lastSyncAt,
2395
+ pendingInbox: checkoutPresent ? pendingStatusInboxBranches(sidecarPath, config) : undefined
2396
+ };
2397
+ console.log(JSON.stringify(payload, null, 2));
2398
+ return 0;
2399
+ }
2400
+ function pendingStatusInboxBranches(sidecarPath, config) {
2401
+ fetch(sidecarPath, true, false);
2402
+ const base = remoteRefExists(sidecarPath, config.branch) ? `origin/${config.branch}` : branchExists(sidecarPath, config.branch) ? config.branch : "HEAD";
2403
+ return pendingInboxBranches(sidecarPath, config).filter((remoteBranch) => !isAncestor(sidecarPath, remoteBranch, base));
2404
+ }
2405
+ function daemonHealth() {
2406
+ if (!shouldUseGlobalRegistry())
2407
+ return { text: "no global install", role: "quiet" };
2408
+ const service = daemonServiceStatus();
2409
+ if (!service.available)
2410
+ return { text: service.message ?? "unavailable", role: "quiet" };
2411
+ if (service.running)
2412
+ return { text: "running", role: "ok" };
2413
+ if (!readSettings().daemonEnabled)
2414
+ return { text: "disabled", role: "attn" };
2415
+ if (!service.installed)
2416
+ return { text: "not installed — run `sidecar daemon enable`", role: "bad" };
2417
+ return { text: "stopped", role: "bad" };
2418
+ }
2419
+ function printDaemonLine() {
2420
+ const health = daemonHealth();
2421
+ statusLine("daemon", health.text, health.role);
2422
+ }
2423
+ function printLastSyncLine(root) {
2424
+ const lastSyncAt = readInstances().find((instance) => instance.root === root)?.lastSyncAt;
2425
+ if (!lastSyncAt) {
2426
+ statusLine("last sync", "never", "quiet");
2427
+ return;
2428
+ }
2429
+ statusLine("last sync", formatTimestampPair(lastSyncAt));
2430
+ }
2431
+ function formatRelativeTime(iso, now = Date.now()) {
2432
+ const then = Date.parse(iso);
2433
+ if (!Number.isFinite(then))
2434
+ return;
2435
+ const seconds = Math.max(0, Math.round((now - then) / 1000));
2436
+ if (seconds < 45)
2437
+ return "just now";
2438
+ const scales = [
2439
+ [60, "minute", 60],
2440
+ [3600, "hour", 24],
2441
+ [86400, "day", 14],
2442
+ [604800, "week", 9],
2443
+ [2592000, "month", 18],
2444
+ [31536000, "year", Number.POSITIVE_INFINITY]
2445
+ ];
2446
+ for (const [size, unit, limit] of scales) {
2447
+ const count = Math.max(1, Math.floor(seconds / size));
2448
+ if (count < limit)
2449
+ return `${count} ${unit}${count === 1 ? "" : "s"} ago`;
2450
+ }
2451
+ return "a very long time ago";
2452
+ }
2453
+ function formatLocalTimestamp(iso) {
2454
+ const date = new Date(iso);
2455
+ if (Number.isNaN(date.getTime()))
2456
+ return;
2457
+ const pad = (value) => String(value).padStart(2, "0");
2458
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` + ` ${pad(date.getHours())}:${pad(date.getMinutes())}`;
2459
+ }
2460
+ function cmdHealth(args) {
2461
+ const parsed = parseOptions(args, {
2462
+ boolean: new Set(["--json", "--no-fetch"]),
2463
+ value: new Set
2464
+ });
2465
+ if (parsed.positional.length)
2466
+ throw new SidecarError("usage: sidecar health [--json] [--no-fetch]");
2467
+ const [root, config] = loadProject();
2468
+ const sidecarPath = requireSidecarCheckout(root, config);
2469
+ if (!parsed.flags.has("--no-fetch"))
2470
+ fetch(sidecarPath, true, false);
2471
+ const entries = readFleetHealth(sidecarPath);
2472
+ if (parsed.flags.has("--json")) {
2473
+ console.log(JSON.stringify(entries, null, 2));
2474
+ return 0;
2475
+ }
2476
+ console.log(`${paint("label", "remote:")} ${paint("brand", config.remote)}`);
2477
+ console.log(`${paint("label", "fleet: ")} ${summarizeHealthStates(entries.map((entry) => entry.state))}`);
2478
+ if (!entries.length) {
2479
+ console.log("");
2480
+ console.log(paint("quiet", "no checkout has reported yet; each one publishes on its next sync"));
2481
+ return 0;
2482
+ }
2483
+ const width = "checkout:".length;
2484
+ const line = (label, value, role) => labelLine(width, label, value, role, " ");
2485
+ for (const { record, state, self } of entries) {
2486
+ console.log("");
2487
+ console.log(`${paint("repo", record.machine)}${self ? paint("quiet", " (this checkout)") : ""}`);
2488
+ const status = healthStatusLine(state, record);
2489
+ line("status", status.text, status.role);
2490
+ if (record.message)
2491
+ line("detail", record.message);
2492
+ if (record.consecutiveFailures > 1)
2493
+ line("failures", `${record.consecutiveFailures} in a row`, "attn");
2494
+ if (record.root)
2495
+ line("checkout", record.root);
2496
+ if (record.inbox)
2497
+ line("inbox", record.inbox);
2498
+ line("reported", formatTimestampPair(record.updatedAt));
2499
+ if (record.lastSuccessAt && record.lastSuccessAt !== record.updatedAt) {
2500
+ line("last ok", formatTimestampPair(record.lastSuccessAt));
2501
+ } else if (!record.lastSuccessAt) {
2502
+ line("last ok", "never", "attn");
2503
+ }
2504
+ if (record.version)
2505
+ line("version", record.version, "quiet");
2506
+ }
2507
+ return 0;
2508
+ }
2509
+ function healthStatusLine(state, record) {
2510
+ if (state === "failed") {
2511
+ return { text: record.stage ? `failed at ${record.stage}` : "failed", role: "bad" };
2512
+ }
2513
+ if (state === "stale") {
2514
+ const age = formatRelativeTime(record.updatedAt) ?? record.updatedAt;
2515
+ return { text: `stale — last reported ${age}`, role: "attn" };
2516
+ }
2517
+ return { text: "ok", role: "ok" };
2518
+ }
2519
+ function cmdInstances(args) {
2520
+ const parsed = parseOptions(args, {
2521
+ boolean: new Set(["--json"]),
2522
+ value: new Set
2523
+ });
2524
+ if (parsed.positional.length)
2525
+ throw new SidecarError("usage: sidecar instances [--json]");
2526
+ const statuses = listInstanceStatuses();
2527
+ if (parsed.flags.has("--json")) {
2528
+ console.log(`${JSON.stringify(statuses, null, 2)}`);
2529
+ return 0;
2530
+ }
2531
+ console.log(`${paint("label", "registry:")} ${paint("quiet", instancesPath())}`);
2532
+ console.log(`${paint("label", "log: ")} ${paint("quiet", sidecarLogPath())}`);
2533
+ if (!statuses.length) {
2534
+ console.log("instances: none");
2535
+ return 0;
2536
+ }
2537
+ const width = "checkout:".length;
2538
+ const line = (label, value, role) => labelLine(width, label, value, role, " ");
2539
+ for (const status of statuses) {
2540
+ console.log("");
2541
+ console.log(paint("repo", status.root));
2542
+ line("sidecar", status.sidecarPath, "brand");
2543
+ line("remote", status.remote, "brand");
2544
+ line("branch", status.currentBranch || "(unknown)");
2545
+ line("config", status.config, status.config === "ok" ? undefined : "bad");
2546
+ line("checkout", status.checkout, status.checkout === "present" ? undefined : "bad");
2547
+ line("dirty", status.dirty, status.dirty === "yes" ? "attn" : "quiet");
2548
+ line("updated", formatTimestampPair(status.updatedAt));
2549
+ if (status.lastSyncAt)
2550
+ line("synced", formatTimestampPair(status.lastSyncAt));
2551
+ }
2552
+ return 0;
2553
+ }
2554
+ function cmdTail(args) {
2555
+ const parsed = parseOptions(args, {
2556
+ boolean: new Set(["-f", "--follow"]),
2557
+ value: new Set(["-n", "--lines"])
2558
+ });
2559
+ if (parsed.positional.length)
2560
+ throw new SidecarError("usage: sidecar tail [-f|--follow] [-n|--lines count]");
2561
+ const rawLines = getValue(parsed, "--lines", getValue(parsed, "-n", "50"));
2562
+ const lines = Number.parseInt(rawLines, 10);
2563
+ if (!Number.isFinite(lines) || lines < 1 || String(lines) !== rawLines) {
2564
+ throw new SidecarError("--lines requires a positive integer");
2565
+ }
2566
+ const filePath = sidecarLogPath();
2567
+ if (!fs2.existsSync(filePath)) {
2568
+ if (parsed.flags.has("-f") || parsed.flags.has("--follow")) {
2569
+ followLog(filePath, 0);
2570
+ return 0;
2571
+ }
2572
+ return 0;
2573
+ }
2574
+ const stat = fs2.statSync(filePath);
2575
+ if (stat.size > 0) {
2576
+ process.stdout.write(lastLines(fs2.readFileSync(filePath, "utf8"), lines));
2577
+ }
2578
+ if (parsed.flags.has("-f") || parsed.flags.has("--follow")) {
2579
+ followLog(filePath, stat.size);
2580
+ }
2581
+ return 0;
2582
+ }
2583
+ function lastLines(content, count) {
2584
+ const trimmed = content.endsWith(`
2585
+ `) ? content.slice(0, -1) : content;
2586
+ if (!trimmed)
2587
+ return "";
2588
+ return `${trimmed.split(`
2589
+ `).slice(-count).join(`
2590
+ `)}
2591
+ `;
2592
+ }
2593
+ function cmdDaemon(args) {
2594
+ if (isProjectLocalPath(currentExecutablePath())) {
2595
+ throw new SidecarError("daemon commands must run from a globally installed sidecar, not a project-local dependency");
2596
+ }
2597
+ const [action, ...rest] = args;
2598
+ if (action === "status") {
2599
+ if (rest.length)
2600
+ throw new SidecarError("usage: sidecar daemon status");
2601
+ return cmdDaemonStatus();
2602
+ }
2603
+ if (action === "enable") {
2604
+ if (rest.length)
2605
+ throw new SidecarError("usage: sidecar daemon enable");
2606
+ return cmdDaemonEnable();
2607
+ }
2608
+ if (action === "disable") {
2609
+ if (rest.length)
2610
+ throw new SidecarError("usage: sidecar daemon disable");
2611
+ return cmdDaemonDisable();
2612
+ }
2613
+ if (action === "restart") {
2614
+ if (rest.length)
2615
+ throw new SidecarError("usage: sidecar daemon restart");
2616
+ return cmdDaemonRestart();
2617
+ }
2618
+ if (action === "autoupdate") {
2619
+ return cmdDaemonAutoUpdate(rest);
2620
+ }
2621
+ if (action === "run") {
2622
+ return cmdDaemonRun(rest);
2623
+ }
2624
+ if (!action || action.startsWith("-")) {
2625
+ return cmdDaemonRun(args);
2626
+ }
2627
+ throw new SidecarError("usage: sidecar daemon status|enable|disable|restart|autoupdate on|off|run [--once] [--interval seconds]");
2628
+ }
2629
+ function cmdDaemonAutoUpdate(args) {
2630
+ const [value, ...rest] = args;
2631
+ if (rest.length || value !== "on" && value !== "off") {
2632
+ throw new SidecarError("usage: sidecar daemon autoupdate on|off");
2633
+ }
2634
+ if (!shouldUseGlobalRegistry()) {
2635
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2636
+ }
2637
+ writeSettings({ ...readSettings(), autoUpdate: value === "on" });
2638
+ console.log(`autoupdate: ${value}`);
2639
+ return 0;
2640
+ }
2641
+ function daemonLine(label, value, role) {
2642
+ labelLine(DAEMON_LABEL_WIDTH, label, value, role);
2643
+ }
2644
+ function printDaemonBlock(service, enabled) {
2645
+ daemonLine("daemon", enabled ? "enabled" : "disabled", enabled ? "ok" : "attn");
2646
+ printServiceLines(service, enabled);
2647
+ daemonLine("settings", settingsPath(), "quiet");
2648
+ }
2649
+ function printServiceLines(service, enabled) {
2650
+ const role = service.running ? "ok" : !service.available ? "quiet" : enabled && service.installed ? "bad" : "quiet";
2651
+ daemonLine("service", daemonServiceLabel(service), role);
2652
+ if (service.path)
2653
+ daemonLine("agent", service.path, "quiet");
2654
+ if (service.message)
2655
+ daemonLine("detail", service.message);
2656
+ }
2657
+ function cmdDaemonStatus() {
2658
+ if (!shouldUseGlobalRegistry()) {
2659
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2660
+ }
2661
+ const settings = readSettings();
2662
+ const service = daemonServiceStatus();
2663
+ daemonLine("daemon", settings.daemonEnabled ? "enabled" : "disabled", settings.daemonEnabled ? "ok" : "attn");
2664
+ daemonLine("update", settings.autoUpdate ? "auto" : "manual");
2665
+ printServiceLines(service, settings.daemonEnabled);
2666
+ daemonLine("settings", settingsPath(), "quiet");
2667
+ daemonLine("log", sidecarLogPath(), "quiet");
2668
+ return 0;
2669
+ }
2670
+ function cmdDaemonEnable() {
2671
+ if (!shouldUseGlobalRegistry()) {
2672
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2673
+ }
2674
+ writeSettings({ ...readSettings(), daemonEnabled: true });
2675
+ const service = installDaemonService();
2676
+ logSidecarEvent("daemon-enable", { service });
2677
+ printDaemonBlock(service, true);
2678
+ return 0;
2679
+ }
2680
+ function cmdDaemonDisable() {
2681
+ if (!shouldUseGlobalRegistry()) {
2682
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2683
+ }
2684
+ writeSettings({ ...readSettings(), daemonEnabled: false });
2685
+ const service = stopDaemonService();
2686
+ logSidecarEvent("daemon-disable", { service });
2687
+ printDaemonBlock(service, false);
2688
+ return 0;
2689
+ }
2690
+ function cmdDaemonRestart() {
2691
+ if (!shouldUseGlobalRegistry()) {
2692
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2693
+ }
2694
+ writeSettings({ ...readSettings(), daemonEnabled: true });
2695
+ const service = installDaemonService();
2696
+ logSidecarEvent("daemon-restart", { service });
2697
+ printDaemonBlock(service, true);
2698
+ return 0;
2699
+ }
2700
+ async function cmdDaemonRun(args) {
2701
+ const parsed = parseOptions(args, {
2702
+ boolean: new Set(["--once"]),
2703
+ value: new Set(["--interval", "--debounce"])
2704
+ });
2705
+ if (parsed.positional.length)
2706
+ throw new SidecarError("usage: sidecar daemon run [--once] [--interval seconds]");
2707
+ if (!shouldUseGlobalRegistry()) {
2708
+ throw new SidecarError("daemon is only available from a globally installed sidecar");
2709
+ }
2710
+ const intervalSeconds = Number(getValue(parsed, "--interval", "600"));
2711
+ if (!Number.isFinite(intervalSeconds) || intervalSeconds <= 0) {
2712
+ throw new SidecarError("--interval must be > 0");
2713
+ }
2714
+ const debounceSeconds = Number(getValue(parsed, "--debounce", "60"));
2715
+ if (!Number.isFinite(debounceSeconds) || debounceSeconds < 0) {
2716
+ throw new SidecarError("--debounce must be >= 0");
2717
+ }
2718
+ const { runDaemonLoop: runDaemonLoop2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
2719
+ return runDaemonLoop2({
2720
+ once: parsed.flags.has("--once"),
2721
+ intervalSeconds,
2722
+ debounceSeconds
2723
+ });
2724
+ }
2725
+ async function cmdUpdate(args) {
2726
+ if (args.length)
2727
+ throw new SidecarError("usage: sidecar update");
2728
+ if (isProjectLocalPath(currentExecutablePath())) {
2729
+ throw new SidecarError("update must run from a globally installed sidecar; update local installs with your package manager");
2730
+ }
2731
+ console.log(`checking npm for ${PACKAGE_NAME} updates...`);
2732
+ const { checkAndInstallUpdate: checkAndInstallUpdate2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
2733
+ const result = await checkAndInstallUpdate2();
2734
+ logSidecarEvent("manual-update", { ...result });
2735
+ if (result.status === "current") {
2736
+ console.log(`sidecar v${result.current} is up to date`);
2737
+ return 0;
2738
+ }
2739
+ if (result.status !== "updated") {
2740
+ throw new SidecarError(result.message ?? `update ${result.status}`);
2741
+ }
2742
+ console.log(`updated sidecar v${result.current} -> v${result.latest}`);
2743
+ const service = installDaemonService();
2744
+ printServiceLines(service, readSettings().daemonEnabled);
2745
+ return 0;
2746
+ }
2747
+ function cmdSetInstallSource(args) {
2748
+ const parsed = parseOptions(args, { boolean: new Set(["--if-unset"]), value: new Set });
2749
+ const [source, ...extra] = parsed.positional;
2750
+ if (!source || extra.length || !INSTALL_SOURCES.has(source)) {
2751
+ throw new SidecarError("usage: sidecar set-install-source npm|bun|curl [--if-unset]");
2752
+ }
2753
+ if (isProjectLocalPath(currentExecutablePath())) {
2754
+ throw new SidecarError("set-install-source must run from a globally installed sidecar");
2755
+ }
2756
+ const settings = readSettings();
2757
+ if (parsed.flags.has("--if-unset") && settings.installSource) {
2758
+ console.log(`install source: ${settings.installSource} (kept)`);
2759
+ return 0;
2760
+ }
2761
+ writeSettings({ ...settings, installSource: source });
2762
+ console.log(`install source: ${source}`);
2763
+ return 0;
2764
+ }
2765
+ function cmdRegisterInstall(args) {
2766
+ if (args.length)
2767
+ throw new SidecarError("usage: sidecar register-install");
2768
+ if (!shouldUseGlobalRegistry()) {
2769
+ throw new SidecarError("install registration requires a global sidecar executable");
2770
+ }
2771
+ const [root, config] = loadProject();
2772
+ registerCurrentInstance(root, config, { event: "install-register" });
2773
+ return 0;
2774
+ }
2775
+ function cmdSnapshot(args) {
2776
+ const parsed = parseOptions(args, {
2777
+ boolean: new Set(["--push"]),
2778
+ value: new Set(["-m", "--message"])
2779
+ });
2780
+ if (parsed.positional.length)
2781
+ throw new SidecarError("usage: sidecar snapshot [--push] [-m message]");
2782
+ const [root, config] = loadProject();
2783
+ const sidecarPath = requireSidecarCheckout(root, config);
2784
+ withSyncLock(root, "throw", () => {
2785
+ const inbox = expandInbox(config, sidecarPath);
2786
+ ensureCommitIdentity(sidecarPath);
2787
+ ensureInboxBranch(sidecarPath, config, inbox);
2788
+ const committed = snapshot(sidecarPath, root, inbox, getValue(parsed, "--message", getValue(parsed, "-m", "")) || undefined, config.redaction);
2789
+ if (committed && parsed.flags.has("--push")) {
2790
+ syncBranchBeforePush(sidecarPath, inbox);
2791
+ pushBranch(sidecarPath, inbox);
2792
+ }
2793
+ });
2794
+ return 0;
2795
+ }
2796
+ function cmdSync(args) {
2797
+ const parsed = parseOptions(args, {
2798
+ boolean: new Set(["--no-snapshot", "--soft"]),
2799
+ value: new Set(["-m", "--message"])
2800
+ });
2801
+ if (parsed.positional.length)
2802
+ throw new SidecarError("usage: sidecar sync [--no-snapshot] [--soft] [-m message]");
2803
+ const [root, config] = loadProject();
2804
+ removeLegacyGitHooks(root);
2805
+ const soft = parsed.flags.has("--soft") || process.env[SOFT_SYNC_ENV] === "1";
2806
+ let stage = "start";
2807
+ let synced;
2808
+ try {
2809
+ synced = withSyncLock(root, soft ? "skip" : "throw", () => {
2810
+ syncProject(root, config, {
2811
+ snapshot: !parsed.flags.has("--no-snapshot"),
2812
+ message: getValue(parsed, "--message", getValue(parsed, "-m", "")) || undefined,
2813
+ onStage: (name) => {
2814
+ stage = name;
2815
+ }
2816
+ });
2817
+ });
2818
+ } catch (error) {
2819
+ reportSyncHealth(root, config, {
2820
+ status: "failed",
2821
+ stage,
2822
+ message: error instanceof Error ? error.message : String(error)
2823
+ });
2824
+ throw error;
2825
+ }
2826
+ if (synced) {
2827
+ registerCurrentInstance(root, config, { event: "sync", lastSyncAt: nowIso() });
2828
+ reportSyncHealth(root, config, { status: "ok" });
2829
+ }
2830
+ return 0;
2831
+ }
2832
+ function syncProject(root, config, options) {
2833
+ const stage = (name) => options.onStage?.(name);
2834
+ stage("checkout");
2835
+ const sidecarPath = ensureSidecarCheckout(root, config);
2836
+ const inbox = expandInbox(config, sidecarPath);
2837
+ ensureCommitIdentity(sidecarPath);
2838
+ fetch(sidecarPath, true, false);
2839
+ ensureInboxBranch(sidecarPath, config, inbox);
2840
+ stage("snapshot");
2841
+ if (options.snapshot) {
2842
+ snapshot(sidecarPath, root, inbox, options.message, config.redaction);
2843
+ } else {
2844
+ ensureRedactionFilter(sidecarPath, config.redaction);
2845
+ }
2846
+ stage("push-inbox");
2847
+ syncBranchBeforePush(sidecarPath, inbox);
2848
+ pushBranch(sidecarPath, inbox);
2849
+ stage("merge");
2850
+ mergeInboxBranches(sidecarPath, config, { forkFiles: true, push: true });
2851
+ stage("refresh");
2852
+ refreshInboxFromMain(sidecarPath, config, inbox);
2853
+ }
2854
+ function healthBranchFor(sidecarPath) {
2855
+ return healthBranch(slug(currentUser()), checkoutRandom(sidecarPath));
2856
+ }
2857
+ function reportSyncHealth(root, config, outcome) {
2858
+ try {
2859
+ const sidecarPath = resolveSidecarPath(root, config);
2860
+ if (!hasGitMetadata(sidecarPath))
2861
+ return;
2862
+ const branch = healthBranchFor(sidecarPath);
2863
+ const previous = readHealthRecordAt(sidecarPath, `origin/${branch}`);
2864
+ const identity = {
2865
+ machine: `${currentUser()}@${currentHost()}`,
2866
+ root,
2867
+ inbox: expandInbox(config, sidecarPath),
2868
+ version: packageVersion()
2869
+ };
2870
+ const record = nextHealthRecord(previous, identity, outcome, nowIso());
2871
+ if (!shouldPublishHealth(previous, record))
2872
+ return;
2873
+ publishHealthRecord(sidecarPath, branch, record);
2874
+ logSidecarEvent("health", {
2875
+ branch,
2876
+ status: record.status,
2877
+ stage: record.stage,
2878
+ consecutiveFailures: record.consecutiveFailures
2879
+ });
2880
+ } catch (error) {
2881
+ logSidecarEvent("failure", {
2882
+ command: "health",
2883
+ root,
2884
+ message: `could not publish health: ${error instanceof Error ? error.message : String(error)}`
2885
+ });
2886
+ }
2887
+ }
2888
+ function publishHealthRecord(sidecarPath, branch, record) {
2889
+ const blob = git(sidecarPath, ["hash-object", "-w", "--stdin"], {
2890
+ input: serializeHealthRecord(record)
2891
+ }).stdout.trim();
2892
+ const tree = git(sidecarPath, ["mktree"], {
2893
+ input: `100644 blob ${blob} ${HEALTH_FILE}
2894
+ `
2895
+ }).stdout.trim();
2896
+ const commit = git(sidecarPath, [
2897
+ "-c",
2898
+ `user.name=${currentUser()}`,
2899
+ "-c",
2900
+ `user.email=${slug(currentUser())}@${slug(currentHost())}.local`,
2901
+ "commit-tree",
2902
+ tree,
2903
+ "-m",
2904
+ `health: ${record.status} — ${record.machine}`
2905
+ ]).stdout.trim();
2906
+ git(sidecarPath, ["push", "--force", "origin", `${commit}:refs/heads/${branch}`]);
2907
+ }
2908
+ function readHealthRecordAt(sidecarPath, ref) {
2909
+ const result = git(sidecarPath, ["show", `${ref}:${HEALTH_FILE}`], { check: false });
2910
+ if (result.status !== 0)
2911
+ return;
2912
+ return parseHealthRecord(result.stdout);
2913
+ }
2914
+ function readFleetHealth(sidecarPath) {
2915
+ const self = healthBranchFor(sidecarPath);
2916
+ const refs = git(sidecarPath, ["branch", "-r", "--format=%(refname:short)"]).stdout.split(/\r?\n/).map((ref) => ref.trim()).filter((ref) => ref && ref !== "origin/HEAD" && isHealthBranch(ref));
2917
+ const entries = [];
2918
+ for (const ref of refs) {
2919
+ const record = readHealthRecordAt(sidecarPath, ref);
2920
+ if (!record)
2921
+ continue;
2922
+ const branch = remoteBranchName(ref);
2923
+ entries.push({ branch, self: branch === self, state: classifyHealthState(record), record });
2924
+ }
2925
+ const rank = { failed: 0, stale: 1, ok: 2 };
2926
+ return entries.sort((left, right) => rank[left.state] - rank[right.state] || left.record.machine.localeCompare(right.record.machine) || left.branch.localeCompare(right.branch));
2927
+ }
2928
+ function cmdMerge(args) {
2929
+ const parsed = parseOptions(args, {
2930
+ boolean: new Set(["--fork-files", "--llm", "--delete-merged-inbox", "--no-push"]),
2931
+ value: new Set
2932
+ });
2933
+ if (parsed.positional.length)
2934
+ throw new SidecarError("usage: sidecar merge [--fork-files] [--no-push]");
2935
+ if (parsed.flags.has("--llm")) {
2936
+ throw new SidecarError("--llm is reserved for a configured resolver; use --fork-files for now");
2937
+ }
2938
+ if (parsed.flags.has("--delete-merged-inbox")) {
2939
+ throw new SidecarError("--delete-merged-inbox is no longer supported; merged inbox branches are kept and skipped by ancestry");
2940
+ }
2941
+ if (!parsed.flags.has("--fork-files")) {
2942
+ console.log("sidecar: conflicts will stop the merge; pass --fork-files to preserve all versions");
2943
+ }
2944
+ const [root, config] = loadProject();
2945
+ const sidecarPath = requireSidecarCheckout(root, config);
2946
+ ensureRedactionFilter(sidecarPath, config.redaction);
2947
+ mergeInboxBranches(sidecarPath, config, {
2948
+ forkFiles: parsed.flags.has("--fork-files"),
2949
+ push: !parsed.flags.has("--no-push")
2950
+ });
2951
+ return 0;
2952
+ }
2953
+ function mergeInboxBranches(sidecarPath, config, options) {
2954
+ ensureClean(sidecarPath);
2955
+ ensureCommitIdentity(sidecarPath);
2956
+ fetch(sidecarPath, false);
2957
+ if (mainMatchesRemote(sidecarPath, config) && !hasPendingInboxWork(sidecarPath, config)) {
2958
+ console.log("no inbox branches to merge");
2959
+ return 0;
2960
+ }
2961
+ if (!hasAnyCommit(sidecarPath)) {
2962
+ return mergeInboxBranchesAt(sidecarPath, config, options);
2963
+ }
2964
+ if (git(sidecarPath, ["branch", "--show-current"]).stdout.trim() === config.branch) {
2965
+ ensureInboxBranch(sidecarPath, config, expandInbox(config, sidecarPath));
2966
+ }
2967
+ const scratch = path2.join(os.tmpdir(), `sidecar-merge-${crypto.createHash("sha1").update(sidecarPath).digest("hex").slice(0, 12)}`);
2968
+ const worktree = path2.join(scratch, "checkout");
2969
+ git(sidecarPath, ["worktree", "remove", "--force", worktree], { check: false });
2970
+ fs2.rmSync(scratch, { recursive: true, force: true });
2971
+ git(sidecarPath, ["worktree", "prune", "--expire", "now"], { check: false });
2972
+ try {
2973
+ git(sidecarPath, ["worktree", "add", "--detach", worktree]);
2974
+ return mergeInboxBranchesAt(worktree, config, options);
2975
+ } finally {
2976
+ git(sidecarPath, ["worktree", "remove", "--force", worktree], { check: false });
2977
+ fs2.rmSync(scratch, { recursive: true, force: true });
2978
+ }
2979
+ }
2980
+ function mainMatchesRemote(repo, config) {
2981
+ if (!branchExists(repo, config.branch) || !remoteRefExists(repo, config.branch))
2982
+ return false;
2983
+ const local = git(repo, ["rev-parse", `refs/heads/${config.branch}`]).stdout.trim();
2984
+ const remote = git(repo, ["rev-parse", `refs/remotes/origin/${config.branch}`]).stdout.trim();
2985
+ return local === remote;
2986
+ }
2987
+ function hasPendingInboxWork(repo, config) {
2988
+ const remoteMain = `origin/${config.branch}`;
2989
+ return pendingInboxBranches(repo, config).some((branch) => !isAncestor(repo, branch, remoteMain));
2990
+ }
2991
+ function mergeInboxBranchesAt(sidecarPath, config, options) {
2992
+ const maxAttempts = 3;
2993
+ for (let attempt = 1;; attempt += 1) {
2994
+ if (attempt > 1)
2995
+ fetch(sidecarPath, false);
2996
+ ensureMainBranch(sidecarPath, config);
2997
+ const inboxBranches = pendingInboxBranches(sidecarPath, config).filter((remoteBranch) => !isAncestor(sidecarPath, remoteBranch, "HEAD"));
2998
+ if (!inboxBranches.length && attempt === 1) {
2999
+ console.log("no inbox branches to merge");
3000
+ return 0;
3001
+ }
3002
+ const merged = [];
3003
+ for (const remoteBranch of inboxBranches) {
3004
+ console.log(`merging ${paint("brand", remoteBranch)}`);
3005
+ const result = git(sidecarPath, ["merge", "--no-ff", "-m", `Merge ${remoteBranch}`, remoteBranch], { check: false });
3006
+ if (result.status === 0) {
3007
+ merged.push(remoteBranch);
3008
+ continue;
3009
+ }
3010
+ if (!hasUnmergedPaths(sidecarPath)) {
3011
+ throw new SidecarError(result.stderr.trim() || `merge failed for ${remoteBranch}`);
3012
+ }
3013
+ if (!options.forkFiles) {
3014
+ git(sidecarPath, ["merge", "--abort"], { check: false });
3015
+ throw new SidecarError(`merge conflict in ${remoteBranch}; rerun with --fork-files`);
3016
+ }
3017
+ forkConflicts(sidecarPath, remoteBranch);
3018
+ git(sidecarPath, ["commit", "-m", `Merge ${remoteBranch} with forked conflict files`]);
3019
+ merged.push(remoteBranch);
3020
+ }
3021
+ if (options.push) {
3022
+ const push = git(sidecarPath, ["push", "-u", "origin", `HEAD:refs/heads/${config.branch}`], { check: false });
3023
+ if (push.status !== 0) {
3024
+ if (attempt >= maxAttempts) {
3025
+ throw new SidecarError(push.stderr.trim() || `could not push ${config.branch}`);
3026
+ }
3027
+ console.log(`push of ${config.branch} was rejected; refetching and retrying`);
3028
+ continue;
3029
+ }
3030
+ console.log(`pushed ${config.branch}`);
3031
+ }
3032
+ console.log(`merged ${merged.length} inbox branch(es)`);
3033
+ return merged.length;
3034
+ }
3035
+ }
3036
+ function cmdRedactions(args) {
3037
+ const parsed = parseOptions(args, { boolean: new Set, value: new Set });
3038
+ if (parsed.positional.length)
3039
+ throw new SidecarError("usage: sidecar redactions");
3040
+ const [root, config] = loadProject();
3041
+ const sidecarPath = requireSidecarCheckout(root, config);
3042
+ if (config.redaction === "none") {
3043
+ console.log('redaction is disabled (redaction = "none" in .sidecar)');
3044
+ return 0;
3045
+ }
3046
+ const files = [
3047
+ ...new Set(git(sidecarPath, ["-c", "core.quotePath=false", "ls-files", "--cached", "--others", "--exclude-standard"]).stdout.split(`
3048
+ `).filter(Boolean))
3049
+ ];
3050
+ let shown = 0;
3051
+ let items = 0;
3052
+ for (const relPath of files) {
3053
+ const delta = fileRedactionDelta(path2.join(sidecarPath, relPath), config.redaction);
3054
+ if (!delta)
3055
+ continue;
3056
+ if (shown)
3057
+ console.log("");
3058
+ console.log(`${relPath}:`);
3059
+ printRedactionDiff(delta.text, delta.redacted);
3060
+ shown += 1;
3061
+ items += delta.items;
3062
+ }
3063
+ if (!shown) {
3064
+ console.log(`no redactions pending (mode: ${config.redaction})`);
3065
+ return 0;
3066
+ }
3067
+ console.log(`
3068
+ ${items} redaction(s) in ${shown} file(s) will be pushed this way (mode: ${config.redaction}).`);
3069
+ console.log(`local files are untouched; add "${NO_REDACT_PRAGMA}" to a file's first lines to push it verbatim`);
3070
+ return 0;
3071
+ }
3072
+ function printRedactionDiff(original, redacted) {
3073
+ const scratch = fs2.mkdtempSync(path2.join(os.tmpdir(), "sidecar-redactions-"));
3074
+ try {
3075
+ const localPath = path2.join(scratch, "local");
3076
+ const pushedPath = path2.join(scratch, "pushed");
3077
+ fs2.writeFileSync(localPath, original, "utf8");
3078
+ fs2.writeFileSync(pushedPath, redacted, "utf8");
3079
+ const color = colorLevel() > 0 ? ["--color"] : [];
3080
+ const diff = gitRaw(["diff", "--no-index", ...color, "--", localPath, pushedPath], { check: false });
3081
+ const lines = diff.stdout.split(`
3082
+ `);
3083
+ const firstHunk = lines.findIndex((line) => stripColor(line).startsWith("@@"));
3084
+ const body = firstHunk === -1 ? "" : lines.slice(firstHunk).join(`
3085
+ `).trimEnd();
3086
+ if (body)
3087
+ console.log(body);
3088
+ } finally {
3089
+ fs2.rmSync(scratch, { recursive: true, force: true });
3090
+ }
3091
+ }
3092
+ function cmdRedact(args) {
3093
+ const parsed = parseOptions(args, { boolean: new Set, value: new Set(["--mode"]) });
3094
+ const mode = redactionModeConfigValue(getValue(parsed, "--mode", DEFAULT_REDACTION_MODE), "--mode");
3095
+ const output = redactBuffer(fs2.readFileSync(0), mode);
3096
+ let offset = 0;
3097
+ while (offset < output.length) {
3098
+ offset += fs2.writeSync(1, output, offset, output.length - offset);
3099
+ }
3100
+ return 0;
3101
+ }
3102
+ function cloneOrUpdate(root, config, bootstrapMain) {
3103
+ const sidecarPath = resolveSidecarPath(root, config);
3104
+ if (fs2.existsSync(sidecarPath) && !hasGitMetadata(sidecarPath)) {
3105
+ if (fs2.readdirSync(sidecarPath).length) {
3106
+ throw new SidecarError(`${sidecarPath} exists and is not an empty Git repo`);
3107
+ }
3108
+ fs2.rmdirSync(sidecarPath);
3109
+ }
3110
+ if (!fs2.existsSync(sidecarPath)) {
3111
+ gitRaw(["clone", "--", config.remote, sidecarPath]);
3112
+ } else if (hasGitMetadata(sidecarPath)) {
3113
+ const existing = git(sidecarPath, ["remote", "get-url", "origin"], { check: false });
3114
+ if (existing.status !== 0) {
3115
+ git(sidecarPath, ["remote", "add", "origin", config.remote]);
3116
+ } else if (existing.stdout.trim() !== config.remote) {
3117
+ if (!isStandalone(config)) {
3118
+ throw new SidecarError(`sidecar origin is ${existing.stdout.trim()}; expected ${config.remote}`);
3119
+ }
3120
+ console.log(`using origin ${paint("brand", existing.stdout.trim())} ${paint("quiet", `(.sidecar says ${config.remote})`)}`);
3121
+ }
3122
+ fetch(sidecarPath, true);
3123
+ } else {
3124
+ throw new SidecarError(`${sidecarPath} is not usable as a sidecar checkout`);
3125
+ }
3126
+ ensureCommitIdentity(sidecarPath);
3127
+ ensureRedactionFilter(sidecarPath, config.redaction);
3128
+ if (bootstrapMain)
3129
+ bootstrapMainBranch(sidecarPath, config);
3130
+ const inbox = expandInbox(config, sidecarPath);
3131
+ ensureInboxBranch(sidecarPath, config, inbox);
3132
+ console.log(`sidecar checkout ready at ${paint("brand", sidecarPath)}`);
3133
+ }
3134
+ function bootstrapMainBranch(repo, config) {
3135
+ if (remoteRefExists(repo, config.branch))
3136
+ return;
3137
+ if (hasAnyCommit(repo)) {
3138
+ const current = git(repo, ["branch", "--show-current"]).stdout.trim();
3139
+ if (current !== config.branch) {
3140
+ if (branchExists(repo, config.branch)) {
3141
+ git(repo, ["switch", config.branch]);
3142
+ } else {
3143
+ git(repo, ["switch", "-c", config.branch]);
3144
+ }
3145
+ }
3146
+ pushBranch(repo, config.branch);
3147
+ return;
3148
+ }
3149
+ git(repo, ["switch", "--orphan", config.branch]);
3150
+ if (isStandalone(config)) {
3151
+ git(repo, ["commit", "--allow-empty", "-m", "Initialize sidecar"]);
3152
+ pushBranch(repo, config.branch);
3153
+ return;
3154
+ }
3155
+ fs2.writeFileSync(path2.join(repo, "README.md"), `# Sidecar
3156
+
3157
+ Scratch space for a code repository: plans, notes, and agent context.
3158
+ This is a plain git repo you own — read it, edit it, clone it anywhere.
3159
+ Kept in sync by [sidecar](https://github.com/anteprojector/sidecar).
3160
+ `, "utf8");
3161
+ git(repo, ["add", "README.md"]);
3162
+ git(repo, ["commit", "-m", "Initialize sidecar"]);
3163
+ pushBranch(repo, config.branch);
3164
+ }
3165
+ function ensureMainBranch(repo, config) {
3166
+ if (branchExists(repo, config.branch)) {
3167
+ git(repo, ["switch", config.branch]);
3168
+ } else if (remoteRefExists(repo, config.branch)) {
3169
+ git(repo, ["switch", "-c", config.branch, "--track", `origin/${config.branch}`]);
3170
+ } else if (hasAnyCommit(repo)) {
3171
+ git(repo, ["switch", "-c", config.branch]);
3172
+ } else {
3173
+ bootstrapMainBranch(repo, config);
3174
+ return;
3175
+ }
3176
+ if (!remoteRefExists(repo, config.branch))
3177
+ return;
3178
+ const remoteBranch = `origin/${config.branch}`;
3179
+ if (isAncestor(repo, remoteBranch, "HEAD"))
3180
+ return;
3181
+ if (isAncestor(repo, "HEAD", remoteBranch)) {
3182
+ git(repo, ["merge", "--ff-only", remoteBranch]);
3183
+ return;
3184
+ }
3185
+ const tip = git(repo, ["rev-parse", "--short", "HEAD"]).stdout.trim();
3186
+ const discarded = `refs/sidecar-discarded/${config.branch}/${utcTimestamp()}-${tip}`;
3187
+ git(repo, ["update-ref", discarded, "HEAD"], { check: false });
3188
+ console.log(`${config.branch} diverged from ${remoteBranch}; old tip kept at ${paint("brand", discarded)}`);
3189
+ git(repo, ["reset", "--hard", remoteBranch]);
3190
+ }
3191
+ function ensureInboxBranch(repo, config, inbox) {
3192
+ const current = git(repo, ["branch", "--show-current"]).stdout.trim();
3193
+ if (current === inbox)
3194
+ return;
3195
+ if (branchExists(repo, inbox)) {
3196
+ git(repo, ["switch", inbox]);
3197
+ return;
3198
+ }
3199
+ if (remoteRefExists(repo, inbox)) {
3200
+ git(repo, ["switch", "-c", inbox, "--track", `origin/${inbox}`]);
3201
+ return;
3202
+ }
3203
+ if (isStandalone(config) && hasAnyCommit(repo)) {
3204
+ git(repo, ["switch", "-c", inbox]);
3205
+ return;
3206
+ }
3207
+ if (remoteRefExists(repo, config.branch)) {
3208
+ git(repo, ["switch", "-c", inbox, `origin/${config.branch}`]);
3209
+ return;
3210
+ }
3211
+ if (branchExists(repo, config.branch)) {
3212
+ git(repo, ["switch", "-c", inbox, config.branch]);
3213
+ return;
3214
+ }
3215
+ if (hasAnyCommit(repo)) {
3216
+ git(repo, ["switch", "-c", inbox]);
3217
+ return;
3218
+ }
3219
+ bootstrapMainBranch(repo, config);
3220
+ git(repo, ["switch", "-c", inbox, config.branch]);
3221
+ }
3222
+ function snapshot(repo, mainRoot, inbox, message = "sidecar snapshot", redactionMode = DEFAULT_REDACTION_MODE) {
3223
+ if (ensureRedactionFilter(repo, redactionMode) && hasAnyCommit(repo)) {
3224
+ git(repo, ["add", "--renormalize", "."]);
3225
+ }
3226
+ git(repo, ["add", "-A"]);
3227
+ if (git(repo, ["diff", "--cached", "--quiet"], { check: false }).status === 0) {
3228
+ console.log("no sidecar changes to snapshot");
3229
+ return false;
3230
+ }
3231
+ const staged = git(repo, ["-c", "core.quotePath=false", "diff", "--cached", "--name-only", "--diff-filter=d"]).stdout.split(`
3232
+ `).filter(Boolean);
3233
+ const source = `${currentUser()}@${currentHost()}`;
3234
+ const body = [message, "", `source: ${source}`];
3235
+ if (path2.resolve(repo) !== path2.resolve(mainRoot)) {
3236
+ const mainHead = git(mainRoot, ["rev-parse", "--short", "HEAD"], { check: false });
3237
+ body.push(`main-head: ${mainHead.status === 0 ? mainHead.stdout.trim() : "unborn"}`);
3238
+ }
3239
+ body.push(`inbox: ${inbox}`);
3240
+ git(repo, ["commit", "-m", body.join(`
3241
+ `)]);
3242
+ console.log(`committed sidecar snapshot to ${paint("brand", inbox)}`);
3243
+ reportRedactions(repo, staged, redactionMode);
3244
+ return true;
3245
+ }
3246
+ function reportRedactions(repo, staged, mode) {
3247
+ if (mode === "none")
3248
+ return;
3249
+ let files = 0;
3250
+ let items = 0;
3251
+ for (const relPath of staged) {
3252
+ const delta = fileRedactionDelta(path2.join(repo, relPath), mode);
3253
+ if (!delta)
3254
+ continue;
3255
+ files += 1;
3256
+ items += delta.items;
3257
+ }
3258
+ if (!files)
3259
+ return;
3260
+ console.log(`redacted ${items} item(s) in ${files} file(s); review with \`sidecar redactions\`, or add "${NO_REDACT_PRAGMA}" to a file's first lines to opt it out`);
3261
+ logSidecarEvent("redaction", { files, items });
3262
+ }
3263
+ function fileRedactionDelta(filePath, mode) {
3264
+ let data;
3265
+ try {
3266
+ data = fs2.readFileSync(filePath);
3267
+ } catch {
3268
+ return;
3269
+ }
3270
+ const text = decodeUtf8Text(data);
3271
+ if (text === undefined || hasNoRedactPragma(text))
3272
+ return;
3273
+ const redacted = redactText(text, mode);
3274
+ if (redacted === text)
3275
+ return;
3276
+ const items = Math.max(1, countRedactionPlaceholders(redacted) - countRedactionPlaceholders(text));
3277
+ return { text, redacted, items };
3278
+ }
3279
+ function ensureRedactionFilter(repo, mode = DEFAULT_REDACTION_MODE) {
3280
+ const command = mode === "none" ? "cat" : `${filterCommandQuote(process.execPath)} ${filterCommandQuote(redactCliPath())} redact --mode=${mode}`;
3281
+ const wanted = [
3282
+ [`filter.${REDACTION_FILTER_NAME}.clean`, command],
3283
+ [`filter.${REDACTION_FILTER_NAME}.smudge`, "cat"],
3284
+ [`filter.${REDACTION_FILTER_NAME}.required`, "true"]
3285
+ ];
3286
+ const attributesPath = path2.join(gitCommonDir(repo), "info", "attributes");
3287
+ const line = `* filter=${REDACTION_FILTER_NAME}`;
3288
+ const configured = git(repo, ["config", "--get-regexp", `^filter\\.${REDACTION_FILTER_NAME}\\.`], {
3289
+ check: false
3290
+ });
3291
+ const current = new Map(configured.stdout.split(`
3292
+ `).filter(Boolean).map((entry) => {
3293
+ const space = entry.indexOf(" ");
3294
+ return [entry.slice(0, space), entry.slice(space + 1)];
3295
+ }));
3296
+ const configOk = wanted.every(([key, value]) => current.get(key) === value);
3297
+ let attributes = "";
3298
+ try {
3299
+ attributes = fs2.readFileSync(attributesPath, "utf8");
3300
+ } catch {}
3301
+ const attributesOk = attributes.split(/\r?\n/).includes(line);
3302
+ if (configOk && attributesOk)
3303
+ return false;
3304
+ for (const [key, value] of wanted) {
3305
+ git(repo, ["config", key, value]);
3306
+ }
3307
+ if (!attributesOk) {
3308
+ fs2.mkdirSync(path2.dirname(attributesPath), { recursive: true });
3309
+ fs2.appendFileSync(attributesPath, attributes && !attributes.endsWith(`
3310
+ `) ? `
3311
+ ${line}
3312
+ ` : `${line}
3313
+ `, "utf8");
3314
+ }
3315
+ return true;
3316
+ }
3317
+ function removeRedactionFilter(repo) {
3318
+ git(repo, ["config", "--remove-section", `filter.${REDACTION_FILTER_NAME}`], { check: false });
3319
+ const attributesPath = path2.join(gitCommonDir(repo), "info", "attributes");
3320
+ const line = `* filter=${REDACTION_FILTER_NAME}`;
3321
+ let contents;
3322
+ try {
3323
+ contents = fs2.readFileSync(attributesPath, "utf8");
3324
+ } catch {
3325
+ return;
3326
+ }
3327
+ const lines = contents.split(/\r?\n/);
3328
+ const kept = lines.filter((entry) => entry !== line);
3329
+ if (kept.length === lines.length)
3330
+ return;
3331
+ if (kept.every((entry) => !entry.trim())) {
3332
+ fs2.rmSync(attributesPath, { force: true });
3333
+ } else {
3334
+ fs2.writeFileSync(attributesPath, `${kept.join(`
3335
+ `).replace(/\s+$/g, "")}
3336
+ `, "utf8");
3337
+ }
3338
+ }
3339
+ function redactCliPath() {
3340
+ const self = fileURLToPath2(import.meta.url);
3341
+ return self.endsWith(".ts") ? path2.join(path2.dirname(self), "..", "dist", "cli.js") : self;
3342
+ }
3343
+ function filterCommandQuote(value) {
3344
+ return `"${value.replace(/([\\"$`])/g, "\\$1")}"`;
3345
+ }
3346
+ function redactBuffer(data, mode) {
3347
+ const text = decodeUtf8Text(data);
3348
+ if (text === undefined || hasNoRedactPragma(text))
3349
+ return data;
3350
+ const redacted = redactText(text, mode);
3351
+ return redacted === text ? data : Buffer.from(redacted, "utf8");
3352
+ }
3353
+ function decodeUtf8Text(data) {
3354
+ if (data.includes(0))
3355
+ return;
3356
+ try {
3357
+ return new TextDecoder("utf-8", { fatal: true }).decode(data);
3358
+ } catch {
3359
+ return;
3360
+ }
3361
+ }
3362
+ function syncBranchBeforePush(repo, branch) {
3363
+ fetch(repo, true, false);
3364
+ if (!remoteRefExists(repo, branch))
3365
+ return;
3366
+ const remoteBranch = `origin/${branch}`;
3367
+ if (isAncestor(repo, remoteBranch, "HEAD"))
3368
+ return;
3369
+ if (isDirty(repo)) {
3370
+ throw new SidecarError(`${remoteBranch} has commits not in local ${branch}, and the sidecar checkout has uncommitted changes`);
3371
+ }
3372
+ if (isAncestor(repo, "HEAD", remoteBranch)) {
3373
+ git(repo, ["merge", "--ff-only", remoteBranch]);
3374
+ return;
3375
+ }
3376
+ const result = git(repo, ["rebase", remoteBranch], { check: false });
3377
+ if (result.status !== 0) {
3378
+ git(repo, ["rebase", "--abort"], { check: false });
3379
+ throw new SidecarError(result.stderr.trim() || `could not rebase ${branch} onto ${remoteBranch}`);
3380
+ }
3381
+ }
3382
+ function refreshInboxFromMain(repo, config, inbox) {
3383
+ if (!branchExists(repo, inbox) || !branchExists(repo, config.branch))
3384
+ return;
3385
+ ensureClean(repo);
3386
+ git(repo, ["switch", inbox]);
3387
+ const result = git(repo, ["merge", "--ff-only", config.branch], { check: false });
3388
+ if (result.status !== 0) {
3389
+ throw new SidecarError(result.stderr.trim() || `could not fast-forward ${inbox} to ${config.branch}`);
3390
+ }
3391
+ }
3392
+ function pushBranch(repo, branch) {
3393
+ git(repo, ["push", "-u", "origin", `HEAD:refs/heads/${branch}`]);
3394
+ console.log(`pushed ${paint("brand", branch)}`);
3395
+ }
3396
+ function forkConflicts(repo, remoteBranch) {
3397
+ const conflicts = unmergedPaths(repo);
3398
+ if (!Object.keys(conflicts).length) {
3399
+ throw new SidecarError("merge reported conflicts, but no unmerged paths were found");
3400
+ }
3401
+ const timestamp = utcTimestamp();
3402
+ const branch = remoteBranchName(remoteBranch) || remoteBranch;
3403
+ const branchLabel = slug(branch);
3404
+ const manifestLabel = fileLabel(branch);
3405
+ const manifest = {
3406
+ timestamp,
3407
+ resolved_by: "fork-files",
3408
+ source_branch: branch,
3409
+ paths: []
3410
+ };
3411
+ for (const [conflictPath, stages] of Object.entries(conflicts).sort(([left], [right]) => left.localeCompare(right))) {
3412
+ const versions = [];
3413
+ for (const [stage, label] of [
3414
+ [2, "main"],
3415
+ [3, branchLabel]
3416
+ ]) {
3417
+ const blob = showStage(repo, stage, conflictPath);
3418
+ if (!blob)
3419
+ continue;
3420
+ const oid = stages[stage] ?? "";
3421
+ const outPath = forkPath(conflictPath, label, oid);
3422
+ const fullOut = path2.join(repo, outPath);
3423
+ fs2.mkdirSync(path2.dirname(fullOut), { recursive: true });
3424
+ fs2.writeFileSync(fullOut, blob);
3425
+ versions.push({
3426
+ stage,
3427
+ label,
3428
+ oid,
3429
+ path: outPath,
3430
+ sha256: crypto.createHash("sha256").update(blob).digest("hex")
3431
+ });
3432
+ }
3433
+ git(repo, ["rm", "-f", "--ignore-unmatch", "--", conflictPath], { check: false });
3434
+ const original = path2.join(repo, conflictPath);
3435
+ if (fs2.existsSync(original) && fs2.statSync(original).isFile())
3436
+ fs2.unlinkSync(original);
3437
+ manifest.paths.push({ path: conflictPath, versions });
3438
+ }
3439
+ const manifestDir = path2.join(repo, ".sidecar-conflicts");
3440
+ fs2.mkdirSync(manifestDir, { recursive: true });
3441
+ const manifestPath = path2.join(manifestDir, `${timestamp}-${manifestLabel}.json`);
3442
+ fs2.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
3443
+ `, "utf8");
3444
+ git(repo, ["add", "-A"]);
3445
+ if (hasUnmergedPaths(repo)) {
3446
+ throw new SidecarError("fork-files did not clear all unmerged paths");
3447
+ }
3448
+ }
3449
+ function forkPath(conflictPath, label, oid) {
3450
+ const parsed = path2.parse(conflictPath);
3451
+ const shortOid = oid ? oid.slice(0, 7) : "missing";
3452
+ const safeLabel = fileLabel(label);
3453
+ const forkName = parsed.ext ? `${parsed.name}.conflict.${safeLabel}.${shortOid}${parsed.ext}` : `${parsed.name}.conflict.${safeLabel}.${shortOid}`;
3454
+ return path2.join(parsed.dir, forkName);
3455
+ }
3456
+ function fileLabel(value) {
3457
+ return slug(value).replaceAll("/", "-");
3458
+ }
3459
+ function unmergedPaths(repo) {
3460
+ const result = gitBytes(repo, ["ls-files", "-u", "-z"]);
3461
+ const paths = {};
3462
+ for (const record of result.stdout.toString("binary").split("\x00")) {
3463
+ if (!record)
3464
+ continue;
3465
+ const separator = record.indexOf("\t");
3466
+ const meta = record.slice(0, separator);
3467
+ const rawPath = record.slice(separator + 1);
3468
+ const parts = meta.split(/\s+/);
3469
+ const oid = parts[1] ?? "";
3470
+ const stage = Number(parts[2]);
3471
+ paths[rawPath] ??= {};
3472
+ paths[rawPath][stage] = oid;
3473
+ }
3474
+ return paths;
3475
+ }
3476
+ function hasUnmergedPaths(repo) {
3477
+ return Object.keys(unmergedPaths(repo)).length > 0;
3478
+ }
3479
+ function showStage(repo, stage, conflictPath) {
3480
+ const result = gitBytes(repo, ["show", `:${stage}:${conflictPath}`], { check: false });
3481
+ return result.status === 0 ? result.stdout : undefined;
3482
+ }
3483
+ function pendingInboxBranches(repo, config) {
3484
+ const match = inboxBranchMatcher(config);
3485
+ const refs = git(repo, ["branch", "-r", "--format=%(refname:short)"]).stdout.split(/\r?\n/);
3486
+ return refs.map((ref) => ref.trim()).filter((ref) => ref !== "origin/HEAD" && match(ref)).sort();
3487
+ }
3488
+ function remoteBranchName(remoteBranch) {
3489
+ return remoteBranch.startsWith("origin/") ? remoteBranch.slice("origin/".length) : remoteBranch;
3490
+ }
3491
+ function expandInbox(config, repo) {
3492
+ validateInboxTemplate(config.inbox);
3493
+ const values = {
3494
+ user: slug(currentUser()),
3495
+ host: slug(currentHost()),
3496
+ random: repo ? checkoutRandom(repo) : "pending"
3497
+ };
3498
+ const inbox = config.inbox.replace(/\{([a-zA-Z0-9_-]+)\}/g, (_match, key) => {
3499
+ const value = values[key];
3500
+ if (value === undefined)
3501
+ throw new SidecarError(`unknown inbox template variable {${key}}`);
3502
+ return value;
3503
+ }).replace(/^\/+|\/+$/g, "");
3504
+ validateBranch(inbox);
3505
+ return inbox;
3506
+ }
3507
+ function checkoutRandom(repo) {
3508
+ const gitDirectory = gitDir(repo);
3509
+ const idPath = path2.join(gitDirectory, "sidecar-id");
3510
+ if (fs2.existsSync(idPath)) {
3511
+ const existing = slug(fs2.readFileSync(idPath, "utf8"));
3512
+ if (existing)
3513
+ return existing;
3514
+ }
3515
+ const id = crypto.randomBytes(6).toString("hex");
3516
+ fs2.writeFileSync(idPath, `${id}
3517
+ `, { encoding: "utf8", mode: 384 });
3518
+ return id;
3519
+ }
3520
+ function validateBranch(branch) {
3521
+ const result = gitRaw(["check-ref-format", "--branch", branch], { check: false });
3522
+ if (result.status !== 0)
3523
+ throw new SidecarError(`invalid branch name ${JSON.stringify(branch)}`);
3524
+ }
3525
+ function validateRemote(remote) {
3526
+ const allowedScheme = /^(https?|ssh|git|file):\/\//i;
3527
+ const scpLike = /^[A-Za-z0-9._~-]+@[A-Za-z0-9._-]+:/;
3528
+ const ok = remote.length > 0 && !remote.startsWith("-") && (allowedScheme.test(remote) || scpLike.test(remote) || path2.isAbsolute(remote));
3529
+ if (!ok) {
3530
+ throw new SidecarError(`unsupported sidecar remote ${JSON.stringify(remote)}; use an https://, ssh://, git://, or file:// URL, user@host:path, or an absolute path`);
3531
+ }
3532
+ }
3533
+ function validateInboxTemplate(template) {
3534
+ const prefix = inboxBranchPrefix(template);
3535
+ if (template.includes("{") && !prefix.endsWith("/")) {
3536
+ throw new SidecarError("inbox template must place variables under a static branch namespace, like sidecar-inbox/{user}/{random}");
3537
+ }
3538
+ if (inboxPrefixCollidesWithHealth(prefix)) {
3539
+ throw new SidecarError(`inbox template must not use the ${HEALTH_BRANCH_PREFIX} namespace, which sidecar reserves for health branches`);
3540
+ }
3541
+ }
3542
+ function slug(value) {
3543
+ const slugged = value.trim().toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").replace(/^[./]+|[./]+$/g, "");
3544
+ return slugged || "unknown";
3545
+ }
3546
+ function sidecarStateDir() {
3547
+ if (process.env[STATE_DIR_ENV])
3548
+ return path2.resolve(process.env[STATE_DIR_ENV]);
3549
+ if (process.platform === "darwin")
3550
+ return path2.join(os.homedir(), "Library", "Application Support", "sidecar");
3551
+ if (process.platform === "win32") {
3552
+ return path2.join(process.env.APPDATA || path2.join(os.homedir(), "AppData", "Roaming"), "sidecar");
3553
+ }
3554
+ return path2.join(process.env.XDG_STATE_HOME || path2.join(os.homedir(), ".local", "state"), "sidecar");
3555
+ }
3556
+ function instancesPath() {
3557
+ return path2.join(sidecarStateDir(), "instances.json");
3558
+ }
3559
+ function sidecarLogPath() {
3560
+ return path2.join(sidecarStateDir(), "sidecar.log");
3561
+ }
3562
+ function settingsPath() {
3563
+ return path2.join(sidecarStateDir(), "settings.json");
3564
+ }
3565
+ function daemonLaunchAgentPath() {
3566
+ if (process.platform !== "darwin")
3567
+ return;
3568
+ return path2.join(os.homedir(), "Library", "LaunchAgents", `${DAEMON_LABEL}.plist`);
3569
+ }
3570
+ function readSettings() {
3571
+ const filePath = settingsPath();
3572
+ if (!fs2.existsSync(filePath))
3573
+ return { ...DEFAULT_SETTINGS };
3574
+ try {
3575
+ const raw = JSON.parse(fs2.readFileSync(filePath, "utf8"));
3576
+ if (!raw || typeof raw !== "object")
3577
+ return { ...DEFAULT_SETTINGS };
3578
+ const record = raw;
3579
+ return {
3580
+ daemonEnabled: typeof record.daemonEnabled === "boolean" ? record.daemonEnabled : true,
3581
+ autoUpdate: typeof record.autoUpdate === "boolean" ? record.autoUpdate : true,
3582
+ lastUpdateCheckAt: typeof record.lastUpdateCheckAt === "string" ? record.lastUpdateCheckAt : undefined,
3583
+ installSource: INSTALL_SOURCES.has(record.installSource) ? record.installSource : undefined
3584
+ };
3585
+ } catch (error) {
3586
+ logSidecarEvent("failure", {
3587
+ command: "daemon",
3588
+ message: `could not read ${filePath}: ${error instanceof Error ? error.message : String(error)}`
3589
+ });
3590
+ return { ...DEFAULT_SETTINGS };
3591
+ }
3592
+ }
3593
+ function writeSettings(settings) {
3594
+ ensureStateDir();
3595
+ const record = {
3596
+ daemonEnabled: settings.daemonEnabled,
3597
+ autoUpdate: settings.autoUpdate
3598
+ };
3599
+ if (settings.lastUpdateCheckAt)
3600
+ record.lastUpdateCheckAt = settings.lastUpdateCheckAt;
3601
+ if (settings.installSource)
3602
+ record.installSource = settings.installSource;
3603
+ fs2.writeFileSync(settingsPath(), `${JSON.stringify(record, null, 2)}
3604
+ `, "utf8");
3605
+ }
3606
+ function readInstances() {
3607
+ const filePath = instancesPath();
3608
+ if (!fs2.existsSync(filePath))
3609
+ return [];
3610
+ try {
3611
+ const raw = JSON.parse(fs2.readFileSync(filePath, "utf8"));
3612
+ if (!Array.isArray(raw))
3613
+ return [];
3614
+ return raw.filter(isSidecarInstance);
3615
+ } catch (error) {
3616
+ logSidecarEvent("failure", {
3617
+ command: "instances",
3618
+ message: `could not read ${filePath}: ${error instanceof Error ? error.message : String(error)}`
3619
+ });
3620
+ return [];
3621
+ }
3622
+ }
3623
+ function writeInstances(instances) {
3624
+ ensureStateDir();
3625
+ fs2.writeFileSync(instancesPath(), `${JSON.stringify(instances, null, 2)}
3626
+ `, "utf8");
3627
+ }
3628
+ function unregisterInstance(root) {
3629
+ const instances = readInstances();
3630
+ const remaining = instances.filter((instance) => realpathOr2(instance.root) !== realpathOr2(root));
3631
+ if (remaining.length !== instances.length)
3632
+ writeInstances(remaining);
3633
+ }
3634
+ function registerCurrentInstance(root, config, options) {
3635
+ if (!shouldUseGlobalRegistry())
3636
+ return;
3637
+ const sidecarPath = resolveSidecarPath(root, config);
3638
+ const existing = readInstances();
3639
+ const previous = existing.find((instance2) => instance2.root === root);
3640
+ const timestamp = nowIso();
3641
+ const instance = {
3642
+ root,
3643
+ configPath: path2.join(root, ".sidecar"),
3644
+ sidecarPath,
3645
+ remote: config.remote,
3646
+ branch: config.branch,
3647
+ inbox: hasGitMetadata(sidecarPath) ? expandInbox(config, sidecarPath) : expandInbox(config),
3648
+ registeredAt: previous?.registeredAt ?? timestamp,
3649
+ updatedAt: timestamp,
3650
+ lastSyncAt: options.lastSyncAt ?? previous?.lastSyncAt
3651
+ };
3652
+ const next = [instance, ...existing.filter((entry) => entry.root !== root)].sort((left, right) => left.root.localeCompare(right.root));
3653
+ writeInstances(next);
3654
+ logSidecarEvent(options.event, {
3655
+ root: instance.root,
3656
+ sidecarPath: instance.sidecarPath,
3657
+ remote: instance.remote,
3658
+ inbox: instance.inbox
3659
+ });
3660
+ }
3661
+ function listInstanceStatuses() {
3662
+ return readInstances().map((instance) => instanceStatus(instance));
3663
+ }
3664
+ function daemonServicePath() {
3665
+ if (process.platform === "darwin")
3666
+ return daemonLaunchAgentPath();
3667
+ if (process.platform === "linux") {
3668
+ const configDir = process.env.XDG_CONFIG_HOME || path2.join(os.homedir(), ".config");
3669
+ return path2.join(configDir, "systemd", "user", `${DAEMON_LABEL}.service`);
3670
+ }
3671
+ if (process.platform === "win32") {
3672
+ const appData = process.env.APPDATA || path2.join(os.homedir(), "AppData", "Roaming");
3673
+ return path2.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "sidecar-daemon.vbs");
3674
+ }
3675
+ return;
3676
+ }
3677
+ function daemonPidPath() {
3678
+ return path2.join(sidecarStateDir(), "daemon.pid");
3679
+ }
3680
+ function readDaemonPid() {
3681
+ try {
3682
+ const pid = Number(fs2.readFileSync(daemonPidPath(), "utf8").trim());
3683
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
3684
+ } catch {
3685
+ return;
3686
+ }
3687
+ }
3688
+ function pidIsSidecarDaemon(pid) {
3689
+ try {
3690
+ process.kill(pid, 0);
3691
+ } catch (error) {
3692
+ if (error.code !== "EPERM")
3693
+ return false;
3694
+ }
3695
+ if (process.platform === "win32")
3696
+ return true;
3697
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
3698
+ if (result.status !== 0)
3699
+ return false;
3700
+ const command = (result.stdout ?? "").trim();
3701
+ return command.includes("daemon") && /sidecar|cli\.js/.test(command);
3702
+ }
3703
+ function isDaemonRunning() {
3704
+ const pid = readDaemonPid();
3705
+ return pid !== undefined && pidIsSidecarDaemon(pid);
3706
+ }
3707
+ function daemonServiceFileContents(invocation) {
3708
+ if (process.platform === "darwin")
3709
+ return daemonPlist(invocation);
3710
+ if (process.platform === "linux")
3711
+ return daemonSystemdUnit(invocation);
3712
+ return daemonWindowsStartupScript(invocation);
3713
+ }
3714
+ function daemonServiceStatus() {
3715
+ if (process.env[SKIP_SERVICE_ENV] === "1") {
3716
+ return { available: false, installed: false, running: false, message: "skipped" };
3717
+ }
3718
+ const servicePath = daemonServicePath();
3719
+ if (!servicePath)
3720
+ return { available: false, installed: false, running: false, message: "unsupported platform" };
3721
+ const message = process.platform === "linux" && !findExecutableOnPath("systemctl") ? "systemd unavailable; run `sidecar daemon run` manually" : undefined;
3722
+ return {
3723
+ available: true,
3724
+ installed: fs2.existsSync(servicePath),
3725
+ running: isDaemonRunning(),
3726
+ path: servicePath,
3727
+ message
3728
+ };
3729
+ }
3730
+ function installDaemonService() {
3731
+ if (process.env[SKIP_SERVICE_ENV] === "1") {
3732
+ return { available: false, installed: false, running: false, message: "skipped" };
3733
+ }
3734
+ const servicePath = daemonServicePath();
3735
+ if (!servicePath)
3736
+ return { available: false, installed: false, running: false, message: "unsupported platform" };
3737
+ if (typeof process.getuid === "function" && process.getuid() === 0) {
3738
+ return { available: false, installed: false, running: false, path: servicePath, message: "root install skipped" };
3739
+ }
3740
+ fs2.mkdirSync(sidecarStateDir(), { recursive: true });
3741
+ fs2.mkdirSync(path2.dirname(servicePath), { recursive: true });
3742
+ const invocation = currentExecutableInvocation();
3743
+ fs2.writeFileSync(servicePath, daemonServiceFileContents(invocation), "utf8");
3744
+ if (process.platform === "darwin") {
3745
+ const domain = launchctlDomain();
3746
+ spawnSync("launchctl", ["bootout", domain, servicePath], { stdio: "ignore" });
3747
+ const bootstrap = spawnSync("launchctl", ["bootstrap", domain, servicePath], { encoding: "utf8" });
3748
+ if (bootstrap.status !== 0) {
3749
+ return {
3750
+ available: true,
3751
+ installed: true,
3752
+ running: false,
3753
+ path: servicePath,
3754
+ message: bootstrap.stderr.trim() || bootstrap.stdout.trim() || "launchctl bootstrap failed"
3755
+ };
3756
+ }
3757
+ spawnSync("launchctl", ["enable", `${domain}/${DAEMON_LABEL}`], { stdio: "ignore" });
3758
+ spawnSync("launchctl", ["kickstart", "-k", `${domain}/${DAEMON_LABEL}`], { stdio: "ignore" });
3759
+ return daemonServiceStatus();
3760
+ }
3761
+ if (process.platform === "linux") {
3762
+ if (!findExecutableOnPath("systemctl")) {
3763
+ return {
3764
+ available: true,
3765
+ installed: true,
3766
+ running: isDaemonRunning(),
3767
+ path: servicePath,
3768
+ message: "systemd unavailable; run `sidecar daemon run` manually"
3769
+ };
3770
+ }
3771
+ spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
3772
+ const enable = spawnSync("systemctl", ["--user", "enable", "--now", `${DAEMON_LABEL}.service`], {
3773
+ encoding: "utf8"
3774
+ });
3775
+ spawnSync("systemctl", ["--user", "restart", `${DAEMON_LABEL}.service`], { stdio: "ignore" });
3776
+ if (enable.status !== 0) {
3777
+ return {
3778
+ available: true,
3779
+ installed: true,
3780
+ running: isDaemonRunning(),
3781
+ path: servicePath,
3782
+ message: enable.stderr.trim() || enable.stdout.trim() || "systemctl enable failed"
3783
+ };
3784
+ }
3785
+ return daemonServiceStatus();
3786
+ }
3787
+ stopDaemonProcess();
3788
+ startDetachedDaemon(invocation);
3789
+ return daemonServiceStatus();
3790
+ }
3791
+ function stopDaemonService() {
3792
+ if (process.env[SKIP_SERVICE_ENV] === "1") {
3793
+ return { available: false, installed: false, running: false, message: "skipped" };
3794
+ }
3795
+ const servicePath = daemonServicePath();
3796
+ if (!servicePath)
3797
+ return { available: false, installed: false, running: false, message: "unsupported platform" };
3798
+ if (process.platform === "darwin") {
3799
+ spawnSync("launchctl", ["bootout", launchctlDomain(), servicePath], { stdio: "ignore" });
3800
+ } else if (process.platform === "linux" && findExecutableOnPath("systemctl")) {
3801
+ spawnSync("systemctl", ["--user", "disable", "--now", `${DAEMON_LABEL}.service`], { stdio: "ignore" });
3802
+ } else if (process.platform === "win32" && fs2.existsSync(servicePath)) {
3803
+ fs2.rmSync(servicePath, { force: true });
3804
+ }
3805
+ stopDaemonProcess();
3806
+ return { available: true, installed: fs2.existsSync(servicePath), running: false, path: servicePath };
3807
+ }
3808
+ function stopDaemonProcess() {
3809
+ const pid = readDaemonPid();
3810
+ if (!pid || pid === process.pid)
3811
+ return;
3812
+ if (!pidIsSidecarDaemon(pid)) {
3813
+ fs2.rmSync(daemonPidPath(), { force: true });
3814
+ return;
3815
+ }
3816
+ try {
3817
+ process.kill(pid, "SIGTERM");
3818
+ } catch {}
3819
+ }
3820
+ function startDetachedDaemon(invocation = currentExecutableInvocation()) {
3821
+ const child = spawn2(invocation[0], invocation.slice(1), {
3822
+ detached: true,
3823
+ stdio: "ignore",
3824
+ windowsHide: true,
3825
+ env: { ...process.env, [SKIP_LOCAL_EXEC_ENV2]: "1", [GLOBAL_EXEC_ENV2]: "1" }
3826
+ });
3827
+ child.unref();
3828
+ }
3829
+ function ensureDaemonServiceFile() {
3830
+ if (process.env[SKIP_SERVICE_ENV] === "1")
3831
+ return;
3832
+ const servicePath = daemonServicePath();
3833
+ if (!servicePath || fs2.existsSync(servicePath))
3834
+ return;
3835
+ try {
3836
+ fs2.mkdirSync(path2.dirname(servicePath), { recursive: true });
3837
+ fs2.writeFileSync(servicePath, daemonServiceFileContents(currentExecutableInvocation()), "utf8");
3838
+ logSidecarEvent("daemon-service-heal", { path: servicePath });
3839
+ } catch (error) {
3840
+ logSidecarEvent("failure", {
3841
+ command: "daemon",
3842
+ message: `could not restore service file: ${error instanceof Error ? error.message : String(error)}`
3843
+ });
3844
+ }
3845
+ }
3846
+ function daemonServiceLabel(service) {
3847
+ if (!service.available)
3848
+ return "unavailable";
3849
+ if (!service.installed)
3850
+ return "uninstalled";
3851
+ return service.running ? "running" : "stopped";
3852
+ }
3853
+ function launchctlDomain() {
3854
+ const uid = typeof process.getuid === "function" ? process.getuid() : os.userInfo().uid;
3855
+ return `gui/${uid}`;
3856
+ }
3857
+ function currentExecutableInvocation() {
3858
+ return [process.execPath, currentExecutablePath(), "daemon", "run"];
3859
+ }
3860
+ function currentExecutablePath() {
3861
+ return realpathOr2(process.argv[1] || fileURLToPath2(import.meta.url));
3862
+ }
3863
+ function currentExecutableStamp(programArguments) {
3864
+ const executable = programArguments[1];
3865
+ if (!executable)
3866
+ return "unknown";
3867
+ try {
3868
+ const stat = fs2.statSync(executable);
3869
+ return `${executable}:${stat.size}:${Math.trunc(stat.mtimeMs)}`;
3870
+ } catch {
3871
+ return executable;
3872
+ }
3873
+ }
3874
+ function daemonPlist(programArguments) {
3875
+ return plist({
3876
+ Label: DAEMON_LABEL,
3877
+ ProgramArguments: programArguments,
3878
+ RunAtLoad: true,
3879
+ KeepAlive: true,
3880
+ StandardOutPath: path2.join(sidecarStateDir(), "daemon.out.log"),
3881
+ StandardErrorPath: path2.join(sidecarStateDir(), "daemon.err.log"),
3882
+ EnvironmentVariables: {
3883
+ PATH: process.env.PATH || "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin",
3884
+ SIDECAR_DAEMON_EXECUTABLE: currentExecutableStamp(programArguments)
3885
+ }
3886
+ });
3887
+ }
3888
+ function daemonSystemdUnit(programArguments) {
3889
+ const execStart = programArguments.map((part) => `"${part.replaceAll('"', "\\\"")}"`).join(" ");
3890
+ return [
3891
+ "[Unit]",
3892
+ "Description=sidecar background sync daemon",
3893
+ "",
3894
+ "[Service]",
3895
+ `ExecStart=${execStart}`,
3896
+ "Restart=always",
3897
+ "RestartSec=10",
3898
+ `Environment="PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}"`,
3899
+ `Environment="SIDECAR_DAEMON_EXECUTABLE=${currentExecutableStamp(programArguments)}"`,
3900
+ "",
3901
+ "[Install]",
3902
+ "WantedBy=default.target",
3903
+ ""
3904
+ ].join(`
3905
+ `);
3906
+ }
3907
+ function daemonWindowsStartupScript(programArguments) {
3908
+ const command = programArguments.map((part) => `""${part}""`).join(" ");
3909
+ return `CreateObject("WScript.Shell").Run "${command}", 0, False\r
3910
+ `;
3911
+ }
3912
+ function plist(value) {
3913
+ const body = Object.entries(value).map(([key, item]) => ` <key>${escapeXml(key)}</key>
3914
+ ${plistValue(item, 2)}`).join("");
3915
+ return `<?xml version="1.0" encoding="UTF-8"?>
3916
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3917
+ <plist version="1.0">
3918
+ <dict>
3919
+ ${body}</dict>
3920
+ </plist>
3921
+ `;
3922
+ }
3923
+ function plistValue(value, indent) {
3924
+ const pad = " ".repeat(indent);
3925
+ if (typeof value === "string")
3926
+ return `${pad}<string>${escapeXml(value)}</string>
3927
+ `;
3928
+ if (typeof value === "boolean")
3929
+ return `${pad}<${value ? "true" : "false"}/>
3930
+ `;
3931
+ if (Array.isArray(value)) {
3932
+ return `${pad}<array>
3933
+ ${value.map((item) => plistValue(item, indent + 2)).join("")}${pad}</array>
3934
+ `;
3935
+ }
3936
+ if (value && typeof value === "object") {
3937
+ return `${pad}<dict>
3938
+ ${Object.entries(value).map(([key, item]) => `${" ".repeat(indent + 2)}<key>${escapeXml(key)}</key>
3939
+ ${plistValue(item, indent + 2)}`).join("")}${pad}</dict>
3940
+ `;
3941
+ }
3942
+ return `${pad}<string></string>
3943
+ `;
3944
+ }
3945
+ function escapeXml(value) {
3946
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
3947
+ }
3948
+ function redactLogValue(value) {
3949
+ if (typeof value === "string")
3950
+ return redactText(value);
3951
+ if (Array.isArray(value))
3952
+ return value.map(redactLogValue);
3953
+ if (value && typeof value === "object") {
3954
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, redactLogValue(entry)]));
3955
+ }
3956
+ return value;
3957
+ }
3958
+ function logSidecarEvent(event, fields = {}) {
3959
+ try {
3960
+ ensureStateDir();
3961
+ const logPath = sidecarLogPath();
3962
+ try {
3963
+ if (fs2.statSync(logPath).size > LOG_ROTATE_BYTES) {
3964
+ fs2.renameSync(logPath, `${logPath}.1`);
3965
+ }
3966
+ } catch {}
3967
+ const record = {
3968
+ timestamp: nowIso(),
3969
+ event,
3970
+ ...redactLogValue(fields)
3971
+ };
3972
+ fs2.appendFileSync(logPath, `${JSON.stringify(record)}
3973
+ `, "utf8");
3974
+ } catch {}
3975
+ }
3976
+ function followLog(filePath, startOffset) {
3977
+ let offset = startOffset;
3978
+ while (true) {
3979
+ sleep(1000);
3980
+ let stat;
3981
+ try {
3982
+ stat = fs2.statSync(filePath);
3983
+ } catch {
3984
+ offset = 0;
3985
+ continue;
3986
+ }
3987
+ if (stat.size < offset)
3988
+ offset = 0;
3989
+ if (stat.size <= offset)
3990
+ continue;
3991
+ const fd = fs2.openSync(filePath, "r");
3992
+ try {
3993
+ const length = stat.size - offset;
3994
+ const buffer = Buffer.alloc(length);
3995
+ const bytesRead = fs2.readSync(fd, buffer, 0, length, offset);
3996
+ if (bytesRead > 0) {
3997
+ process.stdout.write(buffer.subarray(0, bytesRead).toString("utf8"));
3998
+ offset += bytesRead;
3999
+ }
4000
+ } finally {
4001
+ fs2.closeSync(fd);
4002
+ }
4003
+ }
4004
+ }
4005
+ function ensureStateDir() {
4006
+ fs2.mkdirSync(sidecarStateDir(), { recursive: true });
4007
+ }
4008
+ function isSidecarInstance(value) {
4009
+ if (!value || typeof value !== "object")
4010
+ return false;
4011
+ const record = value;
4012
+ return typeof record.root === "string" && typeof record.configPath === "string" && typeof record.sidecarPath === "string" && typeof record.remote === "string" && typeof record.branch === "string" && typeof record.inbox === "string" && typeof record.registeredAt === "string" && typeof record.updatedAt === "string";
4013
+ }
4014
+ function instanceStatus(instance) {
4015
+ let config = "ok";
4016
+ if (!fs2.existsSync(instance.configPath)) {
4017
+ config = "missing";
4018
+ } else {
4019
+ try {
4020
+ readConfig(instance.configPath);
4021
+ } catch {
4022
+ config = "invalid";
4023
+ }
4024
+ }
4025
+ const checkout = hasGitMetadata(instance.sidecarPath) ? "present" : "missing";
4026
+ let dirty = "unknown";
4027
+ let currentBranch = "";
4028
+ if (checkout === "present") {
4029
+ const branch = git(instance.sidecarPath, ["branch", "--show-current"], { check: false });
4030
+ if (branch.status === 0)
4031
+ currentBranch = branch.stdout.trim();
4032
+ const status = git(instance.sidecarPath, ["status", "--porcelain"], { check: false });
4033
+ if (status.status === 0)
4034
+ dirty = status.stdout.trim() ? "yes" : "no";
4035
+ }
4036
+ return {
4037
+ ...instance,
4038
+ config,
4039
+ checkout,
4040
+ dirty,
4041
+ currentBranch
4042
+ };
4043
+ }
4044
+ function shouldUseGlobalRegistry() {
4045
+ return process.env[GLOBAL_EXEC_ENV2] === "1" || !findDependencyRoot(process.cwd());
4046
+ }
4047
+ function isProjectLocalPath(executable) {
4048
+ const depRoot = findDependencyRoot(path2.dirname(executable));
4049
+ if (!depRoot)
4050
+ return false;
4051
+ if (realpathOr2(depRoot) === realpathOr2(bunGlobalRoot()))
4052
+ return false;
4053
+ return isInsidePath2(executable, path2.join(depRoot, "node_modules"));
4054
+ }
4055
+ function bunGlobalRoot() {
4056
+ return path2.join(process.env.BUN_INSTALL || path2.join(os.homedir(), ".bun"), "install", "global");
4057
+ }
4058
+ function realpathOr2(filePath) {
4059
+ try {
4060
+ return fs2.realpathSync(filePath);
4061
+ } catch {
4062
+ return path2.resolve(filePath);
4063
+ }
4064
+ }
4065
+ function isInsidePath2(child, parent) {
4066
+ const relative = path2.relative(parent, child);
4067
+ return Boolean(relative) && !relative.startsWith("..") && !path2.isAbsolute(relative);
4068
+ }
4069
+ function packageVersion() {
4070
+ let current = path2.dirname(fileURLToPath2(import.meta.url));
4071
+ while (true) {
4072
+ const manifestPath = path2.join(current, "package.json");
4073
+ if (fs2.existsSync(manifestPath)) {
4074
+ try {
4075
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
4076
+ if (manifest.name === PACKAGE_NAME && manifest.version)
4077
+ return manifest.version;
4078
+ } catch {}
4079
+ }
4080
+ const parent = path2.dirname(current);
4081
+ if (parent === current)
4082
+ return "0.0.0";
4083
+ current = parent;
4084
+ }
4085
+ }
4086
+ function findDependencyRoot(start) {
4087
+ let current = path2.resolve(start);
4088
+ while (true) {
4089
+ if (projectDependsOnSidecar(current))
4090
+ return current;
4091
+ const parent = path2.dirname(current);
4092
+ if (parent === current)
4093
+ return;
4094
+ current = parent;
4095
+ }
4096
+ }
4097
+ function projectDependsOnSidecar(projectRoot) {
4098
+ const manifestPath = path2.join(projectRoot, "package.json");
4099
+ if (!fs2.existsSync(manifestPath))
4100
+ return false;
4101
+ try {
4102
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
4103
+ return Boolean(manifest.dependencies?.[PACKAGE_NAME] || manifest.devDependencies?.[PACKAGE_NAME] || manifest.optionalDependencies?.[PACKAGE_NAME] || manifest.peerDependencies?.[PACKAGE_NAME]);
4104
+ } catch {
4105
+ return false;
4106
+ }
4107
+ }
4108
+ function promptSidecarPath(root) {
4109
+ if (!process.stdin.isTTY)
4110
+ return DEFAULT_PATH;
4111
+ console.log(`sidecar keeps its files in a directory inside this repo — "." makes this repo itself the sidecar.`);
4112
+ for (let attempt = 0;attempt < 3; attempt += 1) {
4113
+ const answer = promptLine(`sidecar path ${paint("quiet", `[${DEFAULT_PATH}]`)}: `) || DEFAULT_PATH;
4114
+ if (!isStandalonePath(answer))
4115
+ return answer;
4116
+ console.log(`standalone mode makes ${paint("repo", root)} itself the sidecar:`);
4117
+ console.log(" sidecar owns this repo's branches, commits every change, and syncs it to its own remote.");
4118
+ console.log(" your own commits still work; leave branch management to sidecar.");
4119
+ if (promptYesNoDefaultNo("use standalone mode?"))
4120
+ return ".";
4121
+ }
4122
+ console.log(`keeping the default (${DEFAULT_PATH})`);
4123
+ return DEFAULT_PATH;
4124
+ }
4125
+ function standaloneRemote(root) {
4126
+ const origin = git(root, ["remote", "get-url", "origin"], { check: false });
4127
+ const remote = origin.status === 0 ? origin.stdout.trim() : "";
4128
+ if (!remote) {
4129
+ throw new SidecarError("standalone mode syncs this repo to its own origin, but it has none; add one with `git remote add origin <url>`, or name a remote with `sidecar init <remote> --path .`");
4130
+ }
4131
+ validateRemote(remote);
4132
+ console.log(`standalone remote: ${paint("brand", remote)} ${paint("quiet", "(this repo's origin)")}`);
4133
+ return remote;
4134
+ }
4135
+ function promptRemote(root) {
4136
+ if (!process.stdin.isTTY) {
4137
+ throw new SidecarError("remote URL is required when no .sidecar config exists");
4138
+ }
4139
+ console.log("sidecar stores its files in a separate git repo that you own — any empty repo works.");
4140
+ for (let attempt = 0;attempt < 3; attempt += 1) {
4141
+ const remote = promptLine(`sidecar remote URL ${paint("quiet", "(leave blank to create one with gh)")}: `);
4142
+ if (!remote)
4143
+ return createRemoteWithGh(root);
4144
+ try {
4145
+ validateRemote(remote);
4146
+ return remote;
4147
+ } catch (error) {
4148
+ console.log(error instanceof SidecarError ? `sidecar: ${error.message}` : String(error));
4149
+ }
4150
+ }
4151
+ throw new SidecarError("no valid remote URL provided");
4152
+ }
4153
+ function promptRedactionMode() {
4154
+ if (!process.stdin.isTTY)
4155
+ return DEFAULT_REDACTION_MODE;
4156
+ console.log("redaction rewrites sensitive values out of pushed content; your local files are never touched.");
4157
+ const describe = (mode, text) => ` ${mode.padEnd(11)} ${text}${mode === DEFAULT_REDACTION_MODE ? ` ${paint("quiet", "(recommended)")}` : ""}`;
4158
+ console.log(describe("secrets+pii", "redact API keys, tokens, emails, and other PII"));
4159
+ console.log(describe("secrets", "redact API keys and tokens only"));
4160
+ console.log(describe("none", "push content verbatim"));
4161
+ for (let attempt = 0;attempt < 3; attempt += 1) {
4162
+ const answer = promptLine(`redaction mode ${paint("quiet", `[${DEFAULT_REDACTION_MODE}]`)}: `).toLowerCase();
4163
+ if (!answer)
4164
+ return DEFAULT_REDACTION_MODE;
4165
+ if (REDACTION_MODES.includes(answer))
4166
+ return answer;
4167
+ console.log(`invalid redaction mode; expected one of ${REDACTION_MODES.join(", ")}`);
4168
+ }
4169
+ console.log(`keeping the default (${DEFAULT_REDACTION_MODE})`);
4170
+ return DEFAULT_REDACTION_MODE;
4171
+ }
4172
+ function createRemoteWithGh(root) {
4173
+ const gh = findExecutableOnPath(process.platform === "win32" ? "gh.exe" : "gh");
4174
+ if (!gh) {
4175
+ throw new SidecarError("gh not found on PATH; install the GitHub CLI (https://cli.github.com) or rerun with `sidecar init <remote>`");
4176
+ }
4177
+ const origin = git(root, ["remote", "get-url", "origin"], { check: false }).stdout.trim() || undefined;
4178
+ const parsedOrigin = origin ? parseGitHubRemote(origin) : undefined;
4179
+ const owner = parsedOrigin?.owner ?? ghLogin(gh);
4180
+ const baseName = parsedOrigin?.repo ?? path2.basename(root);
4181
+ const suggested = owner ? `${owner}/${baseName}-sidecar` : `${baseName}-sidecar`;
4182
+ const answer = promptLine(`repository to create ${paint("quiet", `[${suggested}]`)}: `) || suggested;
4183
+ const fullName = answer.includes("/") ? answer : owner ? `${owner}/${answer}` : undefined;
4184
+ if (!fullName) {
4185
+ throw new SidecarError("could not determine the repository owner; enter it as owner/name");
4186
+ }
4187
+ console.log(`running gh repo create ${fullName} --private`);
4188
+ const create = spawnSync(gh, ["repo", "create", fullName, "--private"], { stdio: "inherit" });
4189
+ if (create.status !== 0) {
4190
+ throw new SidecarError("gh repo create failed; create the repo yourself and rerun `sidecar init <remote>`");
4191
+ }
4192
+ const ssh = origin ? origin.startsWith("git@") || origin.startsWith("ssh://") : ghGitProtocol(gh) === "ssh";
4193
+ return ssh ? `git@github.com:${fullName}.git` : `https://github.com/${fullName}.git`;
4194
+ }
4195
+ function parseGitHubRemote(url) {
4196
+ const match = /^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/.exec(url) ?? /^(?:https|ssh):\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(url);
4197
+ if (!match)
4198
+ return;
4199
+ return { owner: match[1], repo: match[2] };
4200
+ }
4201
+ function ghLogin(gh) {
4202
+ const result = spawnSync(gh, ["api", "user", "-q", ".login"], { encoding: "utf8" });
4203
+ if (result.status !== 0)
4204
+ return;
4205
+ const login = result.stdout.trim();
4206
+ return login || undefined;
4207
+ }
4208
+ function ghGitProtocol(gh) {
4209
+ const result = spawnSync(gh, ["config", "get", "git_protocol"], { encoding: "utf8" });
4210
+ if (result.status !== 0)
4211
+ return "https";
4212
+ return result.stdout.trim() || "https";
4213
+ }
4214
+ function promptOverwriteConfig(configPath, existingRemote, newRemote) {
4215
+ if (!process.stdin.isTTY) {
4216
+ throw new SidecarError(`${configPath} already exists (remote ${existingRemote}); delete it to reinitialize with ${newRemote}`);
4217
+ }
4218
+ console.log(`${configPath} already exists (remote ${existingRemote})`);
4219
+ const answer = promptLine(`overwrite it with the new settings? ${paint("quiet", "[y/N]")} `).toLowerCase();
4220
+ return answer === "y" || answer === "yes";
4221
+ }
4222
+ function promptYesNo(question) {
4223
+ if (!process.stdin.isTTY)
4224
+ return false;
4225
+ const answer = promptLine(`${question} ${paint("quiet", "[Y/n]")} `).toLowerCase();
4226
+ return answer === "" || answer === "y" || answer === "yes";
4227
+ }
4228
+ function promptYesNoDefaultNo(question) {
4229
+ if (!process.stdin.isTTY)
4230
+ return false;
4231
+ const answer = promptLine(`${question} ${paint("quiet", "[y/N]")} `).toLowerCase();
4232
+ return answer === "y" || answer === "yes";
4233
+ }
4234
+ function promptLine(prompt) {
4235
+ fs2.writeSync(1, prompt);
4236
+ const fd = fs2.openSync(process.platform === "win32" ? "CONIN$" : "/dev/tty", "r");
4237
+ try {
4238
+ const chunks = [];
4239
+ const buffer = Buffer.alloc(1);
4240
+ while (true) {
4241
+ const bytesRead = fs2.readSync(fd, buffer, 0, 1, null);
4242
+ if (bytesRead === 0)
4243
+ break;
4244
+ const char = buffer.toString("utf8", 0, bytesRead);
4245
+ if (char === `
4246
+ ` || char === "\r")
4247
+ break;
4248
+ chunks.push(char);
4249
+ }
4250
+ return chunks.join("").trim();
4251
+ } finally {
4252
+ fs2.closeSync(fd);
4253
+ }
4254
+ }
4255
+ function loadProject() {
4256
+ const root = findConfigRoot(process.cwd());
4257
+ return [root, readConfig(path2.join(root, ".sidecar"))];
4258
+ }
4259
+ function findConfigRoot(start) {
4260
+ const root = findConfigRootOptional(start);
4261
+ if (root)
4262
+ return root;
4263
+ throw new SidecarError("could not find .sidecar");
4264
+ }
4265
+ function findConfigRootOptional(start) {
4266
+ let current = path2.resolve(start);
4267
+ while (true) {
4268
+ if (fs2.existsSync(path2.join(current, ".sidecar")))
4269
+ return current;
4270
+ const parent = path2.dirname(current);
4271
+ if (parent === current)
4272
+ return;
4273
+ current = parent;
4274
+ }
4275
+ }
4276
+ function gitToplevel(cwd) {
4277
+ const root = gitToplevelOptional(cwd);
4278
+ if (!root)
4279
+ throw new SidecarError("not inside a Git repository");
4280
+ return root;
4281
+ }
4282
+ function gitToplevelOptional(cwd) {
4283
+ const result = gitRaw(["-C", cwd, "rev-parse", "--show-toplevel"], { check: false });
4284
+ if (result.status !== 0)
4285
+ return;
4286
+ return result.stdout.trim();
4287
+ }
4288
+ function gitCommonDir(root) {
4289
+ const result = gitRaw(["-C", root, "rev-parse", "--git-common-dir"], { check: false });
4290
+ if (result.status !== 0)
4291
+ throw new SidecarError("not inside a Git repository");
4292
+ return path2.resolve(root, result.stdout.trim());
4293
+ }
4294
+ function requireSidecarCheckout(root, config) {
4295
+ const sidecarPath = resolveSidecarPath(root, config);
4296
+ if (!hasGitMetadata(sidecarPath)) {
4297
+ throw new SidecarError(`missing sidecar checkout at ${sidecarPath}; run \`sidecar clone\``);
4298
+ }
4299
+ return sidecarPath;
4300
+ }
4301
+ function ensureSidecarCheckout(root, config) {
4302
+ const sidecarPath = resolveSidecarPath(root, config);
4303
+ if (!hasGitMetadata(sidecarPath)) {
4304
+ cloneOrUpdate(root, config, true);
4305
+ }
4306
+ return requireSidecarCheckout(root, config);
4307
+ }
4308
+ function writeConfig(configPath, config) {
4309
+ const text = [
4310
+ `version = ${config.version}`,
4311
+ `remote = ${JSON.stringify(config.remote)}`,
4312
+ `path = ${JSON.stringify(config.path)}`,
4313
+ `branch = ${JSON.stringify(config.branch)}`,
4314
+ `inbox = ${JSON.stringify(config.inbox)}`,
4315
+ `redaction = ${JSON.stringify(config.redaction ?? DEFAULT_REDACTION_MODE)}`,
4316
+ ""
4317
+ ].join(`
4318
+ `);
4319
+ fs2.writeFileSync(configPath, text, "utf8");
4320
+ }
4321
+ function readConfig(configPath) {
4322
+ let values;
4323
+ try {
4324
+ const parsed = parse(fs2.readFileSync(configPath, "utf8"));
4325
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4326
+ throw new SidecarError(`${configPath} must contain a TOML table`);
4327
+ }
4328
+ values = parsed;
4329
+ } catch (error) {
4330
+ if (error instanceof SidecarError)
4331
+ throw error;
4332
+ throw new SidecarError(`${configPath} is not valid TOML: ${error instanceof Error ? error.message : String(error)}`);
4333
+ }
4334
+ const remote = optionalStringConfigValue(configPath, values, "remote");
4335
+ if (!remote)
4336
+ throw new SidecarError(`${configPath} is missing remote`);
4337
+ const config = {
4338
+ remote,
4339
+ version: numberConfigValue(configPath, values, "version", 1),
4340
+ path: stringConfigValue(configPath, values, "path", DEFAULT_PATH),
4341
+ branch: stringConfigValue(configPath, values, "branch", DEFAULT_BRANCH),
4342
+ inbox: stringConfigValue(configPath, values, "inbox", DEFAULT_INBOX),
4343
+ redaction: redactionModeConfigValue(stringConfigValue(configPath, values, "redaction", DEFAULT_REDACTION_MODE), configPath)
4344
+ };
4345
+ validateRemote(config.remote);
4346
+ validateBranch(config.branch);
4347
+ validateInboxTemplate(config.inbox);
4348
+ return config;
4349
+ }
4350
+ function redactionModeConfigValue(value, source) {
4351
+ if (REDACTION_MODES.includes(value))
4352
+ return value;
4353
+ throw new SidecarError(`${source}: invalid redaction mode ${JSON.stringify(value)}; expected one of ${REDACTION_MODES.join(", ")}`);
4354
+ }
4355
+ function removeLegacyGitHooks(root) {
4356
+ let removed = false;
4357
+ try {
4358
+ const commonDir = gitCommonDir(root);
4359
+ const hooksDir = path2.join(commonDir, "hooks");
4360
+ for (const name of LEGACY_HOOK_NAMES) {
4361
+ const hookPath = path2.join(hooksDir, name);
4362
+ if (!fs2.existsSync(hookPath))
4363
+ continue;
4364
+ const lines = fs2.readFileSync(hookPath, "utf8").split(`
4365
+ `);
4366
+ const kept = lines.filter((line) => !line.includes(LEGACY_HOOK_MARKER));
4367
+ if (kept.length === lines.length)
4368
+ continue;
4369
+ if (kept.every((line) => !line.trim() || line.trim() === "#!/bin/sh")) {
4370
+ fs2.rmSync(hookPath);
4371
+ } else {
4372
+ fs2.writeFileSync(hookPath, `${kept.join(`
4373
+ `).replace(/\n*$/, `
4374
+ `)}`, "utf8");
4375
+ }
4376
+ removed = true;
4377
+ }
4378
+ const helperPath = path2.join(hooksDir, LEGACY_HOOK_HELPER);
4379
+ if (fs2.existsSync(helperPath)) {
4380
+ fs2.rmSync(helperPath);
4381
+ removed = true;
4382
+ }
4383
+ fs2.rmSync(path2.join(commonDir, LEGACY_SYNC_STAMP_FILE), { force: true });
4384
+ } catch {}
4385
+ if (removed)
4386
+ logSidecarEvent("legacy-hooks-removed", { root });
4387
+ return removed;
4388
+ }
4389
+ function acquireSyncLock(root) {
4390
+ const lockDir = path2.join(gitCommonDir(root), "sidecar-sync-lock");
4391
+ for (let attempt = 0;attempt < 2; attempt++) {
4392
+ try {
4393
+ fs2.mkdirSync(lockDir);
4394
+ fs2.writeFileSync(path2.join(lockDir, "pid"), String(process.pid), "utf8");
4395
+ return () => fs2.rmSync(lockDir, { recursive: true, force: true });
4396
+ } catch (error) {
4397
+ if (error.code !== "EEXIST")
4398
+ throw error;
4399
+ if (!syncLockIsStale(lockDir))
4400
+ return;
4401
+ fs2.rmSync(lockDir, { recursive: true, force: true });
4402
+ }
4403
+ }
4404
+ return;
4405
+ }
4406
+ function acquireSyncLockOrThrow(root) {
4407
+ const release = acquireSyncLock(root);
4408
+ if (release)
4409
+ return release;
4410
+ throw new SidecarError("another sidecar sync is already running; try again once it finishes");
4411
+ }
4412
+ function withSyncLock(root, onBusy, fn) {
4413
+ const releaseLock = onBusy === "skip" ? acquireSyncLock(root) : acquireSyncLockOrThrow(root);
4414
+ if (!releaseLock) {
4415
+ console.log("another sidecar sync is already running; skipping this soft sync");
4416
+ return false;
4417
+ }
4418
+ try {
4419
+ fn();
4420
+ return true;
4421
+ } finally {
4422
+ releaseLock();
4423
+ }
4424
+ }
4425
+ function syncLockIsStale(lockDir) {
4426
+ let pid;
4427
+ try {
4428
+ pid = Number(fs2.readFileSync(path2.join(lockDir, "pid"), "utf8").trim());
4429
+ } catch {
4430
+ try {
4431
+ return Date.now() - fs2.statSync(lockDir).mtimeMs > 600000;
4432
+ } catch {
4433
+ return true;
4434
+ }
4435
+ }
4436
+ if (!Number.isInteger(pid) || pid <= 0)
4437
+ return true;
4438
+ try {
4439
+ process.kill(pid, 0);
4440
+ return false;
4441
+ } catch (error) {
4442
+ return error.code !== "EPERM";
4443
+ }
4444
+ }
4445
+ function ensureSidecarIgnored(root, sidecarPath) {
4446
+ const entry = ignoreEntryForSidecarPath(root, sidecarPath);
4447
+ if (!entry)
4448
+ return;
4449
+ ensureIgnoreEntry(path2.join(root, ".gitignore"), entry);
4450
+ removeIgnoreEntry(path2.join(gitCommonDir(root), "info", "exclude"), entry);
4451
+ return entry;
4452
+ }
4453
+ function ensureIgnoreEntry(ignorePath, sidecarPath) {
4454
+ const stripped = sidecarPath.replace(/^\/+|\/+$/g, "");
4455
+ const entry = `/${stripped}/`;
4456
+ const lines = fs2.existsSync(ignorePath) ? fs2.readFileSync(ignorePath, "utf8").split(/\r?\n/) : [];
4457
+ if (!lines.includes(entry)) {
4458
+ lines.push(entry);
4459
+ fs2.writeFileSync(ignorePath, `${lines.join(`
4460
+ `).replace(/\s+$/g, "")}
4461
+ `, "utf8");
4462
+ }
4463
+ }
4464
+ function removeIgnoreEntry(ignorePath, sidecarPath) {
4465
+ if (!fs2.existsSync(ignorePath))
4466
+ return;
4467
+ const stripped = sidecarPath.replace(/^\/+|\/+$/g, "");
4468
+ const entry = `/${stripped}/`;
4469
+ const lines = fs2.readFileSync(ignorePath, "utf8").split(/\r?\n/);
4470
+ const kept = lines.filter((line) => line !== entry);
4471
+ if (kept.length === lines.length)
4472
+ return;
4473
+ if (kept.every((line) => !line.trim())) {
4474
+ fs2.rmSync(ignorePath);
4475
+ } else {
4476
+ fs2.writeFileSync(ignorePath, `${kept.join(`
4477
+ `).replace(/\s+$/g, "")}
4478
+ `, "utf8");
4479
+ }
4480
+ }
4481
+ function hasZedInclusion(root, sidecarPath) {
4482
+ const settingsPath2 = path2.join(root, ".zed", "settings.json");
4483
+ if (!fs2.existsSync(settingsPath2))
4484
+ return false;
4485
+ try {
4486
+ const parsed = JSON.parse(fs2.readFileSync(settingsPath2, "utf8"));
4487
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
4488
+ return false;
4489
+ const inclusions = parsed.file_scan_inclusions;
4490
+ return Array.isArray(inclusions) && inclusions.includes(zedInclusionGlob(sidecarPath));
4491
+ } catch {
4492
+ return false;
4493
+ }
4494
+ }
4495
+ function zedInclusionGlob(sidecarPath) {
4496
+ return `${sidecarPath.replace(/^\/+|\/+$/g, "")}/**`;
4497
+ }
4498
+ function ensureZedInclusion(root, sidecarPath) {
4499
+ const glob = zedInclusionGlob(sidecarPath);
4500
+ const settingsPath2 = path2.join(root, ".zed", "settings.json");
4501
+ let settings = {};
4502
+ if (fs2.existsSync(settingsPath2)) {
4503
+ try {
4504
+ const parsed = JSON.parse(fs2.readFileSync(settingsPath2, "utf8"));
4505
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
4506
+ return false;
4507
+ settings = parsed;
4508
+ } catch {
4509
+ return false;
4510
+ }
4511
+ }
4512
+ const inclusions = Array.isArray(settings.file_scan_inclusions) ? settings.file_scan_inclusions : [".env*"];
4513
+ if (!inclusions.includes(glob)) {
4514
+ inclusions.push(glob);
4515
+ settings.file_scan_inclusions = inclusions;
4516
+ fs2.mkdirSync(path2.dirname(settingsPath2), { recursive: true });
4517
+ fs2.writeFileSync(settingsPath2, `${JSON.stringify(settings, null, 2)}
4518
+ `, "utf8");
4519
+ }
4520
+ return true;
4521
+ }
4522
+ function removeZedInclusion(root, sidecarPath) {
4523
+ const settingsPath2 = path2.join(root, ".zed", "settings.json");
4524
+ if (!fs2.existsSync(settingsPath2))
4525
+ return;
4526
+ try {
4527
+ const settings = JSON.parse(fs2.readFileSync(settingsPath2, "utf8"));
4528
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
4529
+ return;
4530
+ const inclusions = settings.file_scan_inclusions;
4531
+ if (!Array.isArray(inclusions))
4532
+ return;
4533
+ const glob = zedInclusionGlob(sidecarPath);
4534
+ const remaining = inclusions.filter((entry) => entry !== glob);
4535
+ if (remaining.length === inclusions.length)
4536
+ return;
4537
+ if (remaining.length) {
4538
+ settings.file_scan_inclusions = remaining;
4539
+ } else {
4540
+ delete settings.file_scan_inclusions;
4541
+ }
4542
+ fs2.writeFileSync(settingsPath2, `${JSON.stringify(settings, null, 2)}
4543
+ `, "utf8");
4544
+ } catch {
4545
+ console.error(`sidecar: warning: could not safely remove the Zed inclusion from ${settingsPath2}`);
4546
+ }
4547
+ }
4548
+ function ignoreEntryForSidecarPath(root, sidecarPath) {
4549
+ const resolvedRoot = path2.resolve(root);
4550
+ const resolvedSidecarPath = path2.resolve(root, sidecarPath);
4551
+ const relative = path2.relative(resolvedRoot, resolvedSidecarPath);
4552
+ if (!relative || relative.startsWith("..") || path2.isAbsolute(relative))
4553
+ return;
4554
+ return relative;
4555
+ }
4556
+ function ensureClean(repo) {
4557
+ if (isDirty(repo))
4558
+ throw new SidecarError("sidecar checkout has uncommitted changes");
4559
+ }
4560
+ function ensureCommitIdentity(repo) {
4561
+ if (git(repo, ["config", "user.name"], { check: false }).status !== 0) {
4562
+ git(repo, ["config", "user.name", currentUser()]);
4563
+ }
4564
+ if (git(repo, ["config", "user.email"], { check: false }).status !== 0) {
4565
+ git(repo, ["config", "user.email", `${slug(currentUser())}@${slug(currentHost())}.local`]);
4566
+ }
4567
+ }
4568
+ function currentUser() {
4569
+ return process.env.USER || os.userInfo().username || "unknown";
4570
+ }
4571
+ function currentHost() {
4572
+ return os.hostname().split(".", 1)[0] || "unknown";
4573
+ }
4574
+ function fetch(repo, quiet, check = true) {
4575
+ const args = ["fetch", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"];
4576
+ if (quiet)
4577
+ args.splice(1, 0, "--quiet");
4578
+ git(repo, args, { check });
4579
+ }
4580
+ function hasAnyCommit(repo) {
4581
+ return git(repo, ["rev-parse", "--verify", "HEAD"], { check: false }).status === 0;
4582
+ }
4583
+ function branchExists(repo, branch) {
4584
+ return git(repo, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], { check: false }).status === 0;
4585
+ }
4586
+ function remoteRefExists(repo, branch) {
4587
+ return git(repo, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], {
4588
+ check: false
4589
+ }).status === 0;
4590
+ }
4591
+ function isAncestor(repo, maybeAncestor, descendant) {
4592
+ return git(repo, ["merge-base", "--is-ancestor", maybeAncestor, descendant], { check: false }).status === 0;
4593
+ }
4594
+ function git(repo, args, options = {}) {
4595
+ return gitRaw(["-C", repo, ...args], options);
4596
+ }
4597
+ function gitBytes(repo, args, options = {}) {
4598
+ const check = options.check ?? true;
4599
+ const result = spawnSync("git", ["-C", repo, ...args], {
4600
+ encoding: "buffer",
4601
+ maxBuffer: 104857600
4602
+ });
4603
+ const status = result.status ?? 1;
4604
+ const stdout = Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
4605
+ const stderr = Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.from(result.stderr ?? "");
4606
+ if (check && status !== 0) {
4607
+ throw new SidecarError(stderr.toString("utf8").trim() || stdout.toString("utf8").trim());
4608
+ }
4609
+ return { status, stdout, stderr };
4610
+ }
4611
+ function gitRaw(args, options = {}) {
4612
+ const check = options.check ?? true;
4613
+ const result = spawnSync("git", args, {
4614
+ encoding: "utf8",
4615
+ input: options.input,
4616
+ maxBuffer: 104857600
4617
+ });
4618
+ const status = result.status ?? 1;
4619
+ const stdout = result.stdout ?? "";
4620
+ const stderr = result.stderr ?? "";
4621
+ if (check && status !== 0) {
4622
+ throw new SidecarError(stderr.trim() || stdout.trim());
4623
+ }
4624
+ return { status, stdout, stderr };
4625
+ }
4626
+ function parseOptions(args, spec) {
4627
+ const flags = new Set;
4628
+ const values = new Map;
4629
+ const positional = [];
4630
+ for (let index = 0;index < args.length; index += 1) {
4631
+ const arg = args[index];
4632
+ if (arg === "--") {
4633
+ positional.push(...args.slice(index + 1));
4634
+ break;
4635
+ }
4636
+ if (!arg.startsWith("-") || arg === "-") {
4637
+ positional.push(arg);
4638
+ continue;
4639
+ }
4640
+ const equals = arg.indexOf("=");
4641
+ const [name, inlineValue] = equals === -1 ? [arg, undefined] : [arg.slice(0, equals), arg.slice(equals + 1)];
4642
+ if (spec.value.has(name)) {
4643
+ const value = inlineValue ?? args[++index];
4644
+ if (value === undefined)
4645
+ throw new SidecarError(`${name} requires a value`);
4646
+ values.set(name, value);
4647
+ continue;
4648
+ }
4649
+ if (inlineValue !== undefined)
4650
+ throw new SidecarError(`${name} does not take a value`);
4651
+ if (spec.boolean.has(name)) {
4652
+ flags.add(name);
4653
+ continue;
4654
+ }
4655
+ throw new SidecarError(`unknown option ${name}`);
4656
+ }
4657
+ return { flags, values, positional };
4658
+ }
4659
+ function getValue(parsed, name, fallback) {
4660
+ return parsed.values.get(name) ?? fallback;
4661
+ }
4662
+ function resolveSidecarPath(root, config) {
4663
+ return path2.resolve(root, config.path);
4664
+ }
4665
+ function isStandalone(config) {
4666
+ return isStandalonePath(config.path);
4667
+ }
4668
+ function isStandalonePath(sidecarPath) {
4669
+ return path2.normalize(sidecarPath).replace(/[/\\]+$/, "") === ".";
4670
+ }
4671
+ function pathIsRepoRoot(root, candidate) {
4672
+ const resolved = path2.resolve(root, candidate);
4673
+ if (resolved === path2.resolve(root))
4674
+ return true;
4675
+ try {
4676
+ return fs2.realpathSync(resolved) === fs2.realpathSync(root);
4677
+ } catch {
4678
+ return false;
4679
+ }
4680
+ }
4681
+ function hasGitMetadata(repo) {
4682
+ return fs2.existsSync(path2.join(repo, ".git"));
4683
+ }
4684
+ function isDirty(repo) {
4685
+ return Boolean(git(repo, ["status", "--porcelain"]).stdout.trim());
4686
+ }
4687
+ function gitDir(repo) {
4688
+ const result = git(repo, ["rev-parse", "--git-dir"]).stdout.trim();
4689
+ return path2.isAbsolute(result) ? result : path2.resolve(repo, result);
4690
+ }
4691
+ function stringConfigValue(configPath, values, key, fallback) {
4692
+ const value = values[key] ?? fallback;
4693
+ if (typeof value !== "string")
4694
+ throw new SidecarError(`${configPath} ${key} must be a string`);
4695
+ return value;
4696
+ }
4697
+ function optionalStringConfigValue(configPath, values, key) {
4698
+ const value = values[key];
4699
+ if (value === undefined)
4700
+ return;
4701
+ if (typeof value !== "string")
4702
+ throw new SidecarError(`${configPath} ${key} must be a string`);
4703
+ return value;
4704
+ }
4705
+ function numberConfigValue(configPath, values, key, fallback) {
4706
+ const value = values[key] ?? fallback;
4707
+ if (typeof value !== "number" || !Number.isInteger(value)) {
4708
+ throw new SidecarError(`${configPath} ${key} must be an integer`);
4709
+ }
4710
+ return value;
4711
+ }
4712
+ function inboxBranchMatcher(config) {
4713
+ const prefix = `origin/${inboxBranchPrefix(config.inbox)}`;
4714
+ if (prefix.endsWith("/"))
4715
+ return (remoteBranch) => remoteBranch.startsWith(prefix);
4716
+ return (remoteBranch) => remoteBranch === prefix;
4717
+ }
4718
+ function inboxBranchPrefix(template) {
4719
+ const variableIndex = template.indexOf("{");
4720
+ if (variableIndex === -1)
4721
+ return template.replace(/^\/+|\/+$/g, "");
4722
+ const staticPrefix = template.slice(0, variableIndex).replace(/^\/+/, "");
4723
+ const slashIndex = staticPrefix.lastIndexOf("/");
4724
+ return slashIndex === -1 ? staticPrefix : staticPrefix.slice(0, slashIndex + 1);
4725
+ }
4726
+ function utcTimestamp() {
4727
+ return new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
4728
+ }
4729
+ function nowIso() {
4730
+ return new Date().toISOString();
4731
+ }
4732
+ function sleep(ms) {
4733
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
4734
+ }
4735
+ var DEFAULT_PATH = "sidecar", DEFAULT_BRANCH = "main", DEFAULT_INBOX = "sidecar-inbox/{user}/{random}", PACKAGE_NAME = "sidecarsync", PACKAGE_SPEC = "sidecarsync", GLOBAL_EXEC_ENV2 = "SIDECAR_GLOBAL_EXEC", SKIP_LOCAL_EXEC_ENV2 = "SIDECAR_SKIP_LOCAL_EXEC", STATE_DIR_ENV = "SIDECAR_STATE_DIR", SOFT_SYNC_ENV = "SIDECAR_SYNC_SOFT", SKIP_SERVICE_ENV = "SIDECAR_SKIP_SERVICE", DAEMON_LABEL = "com.anteprojector.sidecar", SidecarError, INSTALL_SOURCES, KNOWN_COMMANDS, STATUS_LABEL_WIDTH, DAEMON_LABEL_WIDTH, REDACTION_FILTER_NAME = "sidecar-redact", DEFAULT_SETTINGS, LOG_ROTATE_BYTES = 5242880, LEGACY_HOOK_NAMES, LEGACY_HOOK_HELPER = "sidecar-sync-hook", LEGACY_HOOK_MARKER = "sidecar-sync", LEGACY_SYNC_STAMP_FILE = "sidecar-last-sync";
4736
+ var init_cli = __esm(() => {
4737
+ init_dist();
4738
+ init_color();
4739
+ init_health();
4740
+ init_redaction();
4741
+ SidecarError = class SidecarError extends Error {
4742
+ constructor(message) {
4743
+ super(message);
4744
+ this.name = "SidecarError";
4745
+ }
4746
+ };
4747
+ INSTALL_SOURCES = new Set(["npm", "bun", "curl"]);
4748
+ KNOWN_COMMANDS = [
4749
+ "init",
4750
+ "clone",
4751
+ "deinit",
4752
+ "status",
4753
+ "health",
4754
+ "instances",
4755
+ "tail",
4756
+ "daemon",
4757
+ "register-install",
4758
+ "set-install-source",
4759
+ "update",
4760
+ "snapshot",
4761
+ "sync",
4762
+ "merge",
4763
+ "redact",
4764
+ "redactions",
4765
+ "version",
4766
+ "help"
4767
+ ];
4768
+ STATUS_LABEL_WIDTH = "pending inbox:".length;
4769
+ DAEMON_LABEL_WIDTH = "settings:".length;
4770
+ DEFAULT_SETTINGS = { daemonEnabled: true, autoUpdate: true };
4771
+ LEGACY_HOOK_NAMES = ["post-commit", "pre-push"];
4772
+ });
4773
+
4774
+ // src/bin.ts
4775
+ init_cli();
4776
+ import fs3 from "node:fs";
4777
+ import path3 from "node:path";
4778
+ import { spawnSync as spawnSync2 } from "node:child_process";
4779
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
4780
+ var SKIP_LOCAL_EXEC_ENV3 = "SIDECAR_SKIP_LOCAL_EXEC";
4781
+ var GLOBAL_EXEC_ENV3 = "SIDECAR_GLOBAL_EXEC";
4782
+ var PACKAGE_NAME2 = "sidecarsync";
4783
+ var GLOBAL_ONLY_COMMANDS = new Set(["daemon", "deinit", "register-install", "set-install-source", "update"]);
4784
+ if (!process.env[SKIP_LOCAL_EXEC_ENV3]) {
4785
+ const localExecutable = findLocalExecutable(process.cwd(), fileURLToPath3(import.meta.url));
4786
+ if (localExecutable) {
4787
+ if (GLOBAL_ONLY_COMMANDS.has(process.argv[2])) {
4788
+ process.env[GLOBAL_EXEC_ENV3] = "1";
4789
+ } else {
4790
+ const result = spawnSync2(process.execPath, [localExecutable, ...process.argv.slice(2)], {
4791
+ stdio: "inherit",
4792
+ env: {
4793
+ ...process.env,
4794
+ [SKIP_LOCAL_EXEC_ENV3]: "1",
4795
+ [GLOBAL_EXEC_ENV3]: "1"
4796
+ }
4797
+ });
4798
+ if (result.signal) {
4799
+ process.kill(process.pid, result.signal);
4800
+ }
4801
+ process.exit(result.status ?? 1);
4802
+ }
4803
+ }
4804
+ }
4805
+ process.exit(await main());
4806
+ function findLocalExecutable(start, self) {
4807
+ let current = path3.resolve(start);
4808
+ while (true) {
4809
+ if (projectDependsOnSidecar2(current)) {
4810
+ const candidate = path3.join(current, "node_modules", PACKAGE_NAME2, "dist", "cli.js");
4811
+ if (isFile2(candidate) && !sameFile(candidate, self)) {
4812
+ return candidate;
4813
+ }
4814
+ }
4815
+ const parent = path3.dirname(current);
4816
+ if (parent === current)
4817
+ return;
4818
+ current = parent;
4819
+ }
4820
+ }
4821
+ function projectDependsOnSidecar2(projectRoot) {
4822
+ const manifestPath = path3.join(projectRoot, "package.json");
4823
+ if (!isFile2(manifestPath))
4824
+ return false;
4825
+ try {
4826
+ const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
4827
+ return Boolean(manifest.dependencies?.[PACKAGE_NAME2] || manifest.devDependencies?.[PACKAGE_NAME2] || manifest.optionalDependencies?.[PACKAGE_NAME2] || manifest.peerDependencies?.[PACKAGE_NAME2]);
4828
+ } catch {
4829
+ return false;
4830
+ }
4831
+ }
4832
+ function isFile2(filePath) {
4833
+ try {
4834
+ return fs3.statSync(filePath).isFile();
4835
+ } catch {
4836
+ return false;
4837
+ }
4838
+ }
4839
+ function sameFile(first, second) {
4840
+ try {
4841
+ return fs3.realpathSync(first) === fs3.realpathSync(second);
4842
+ } catch {
4843
+ return false;
4844
+ }
4845
+ }