sphica 0.6.30 → 0.6.32

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 CHANGED
@@ -92,7 +92,8 @@ var require_src = __commonJS(function(exports, module) {
92
92
  });
93
93
 
94
94
  // server/src/cli.ts
95
- import fs9 from "node:fs";
95
+ import fs10 from "node:fs";
96
+ import os4 from "node:os";
96
97
  import path10 from "node:path";
97
98
 
98
99
  // server/node_modules/@clack/core/dist/index.mjs
@@ -2966,7 +2967,7 @@ async function run(app, inputs, context) {
2966
2967
  }
2967
2968
 
2968
2969
  // server/src/admin.ts
2969
- import fs8 from "node:fs";
2970
+ import fs9 from "node:fs";
2970
2971
  import path9 from "node:path";
2971
2972
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
2972
2973
 
@@ -3007,11 +3008,1114 @@ import os from "node:os";
3007
3008
  import path2 from "node:path";
3008
3009
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3009
3010
 
3011
+ // server/src/codex-trust.ts
3012
+ import crypto from "node:crypto";
3013
+
3014
+ // server/node_modules/smol-toml/dist/error.js
3015
+ /*!
3016
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3017
+ * SPDX-License-Identifier: BSD-3-Clause
3018
+ *
3019
+ * Redistribution and use in source and binary forms, with or without
3020
+ * modification, are permitted provided that the following conditions are met:
3021
+ *
3022
+ * 1. Redistributions of source code must retain the above copyright notice, this
3023
+ * list of conditions and the following disclaimer.
3024
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3025
+ * this list of conditions and the following disclaimer in the
3026
+ * documentation and/or other materials provided with the distribution.
3027
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3028
+ * may be used to endorse or promote products derived from this software without
3029
+ * specific prior written permission.
3030
+ *
3031
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3032
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3033
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3034
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3035
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3036
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3037
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3038
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3039
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3040
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3041
+ */
3042
+ function getLineColFromPtr(string, ptr) {
3043
+ let lines = string.slice(0, ptr).split(/\r?\n/);
3044
+ return [lines.length, lines.pop().length + 1];
3045
+ }
3046
+ function makeCodeBlock(string, line, column) {
3047
+ let lines = string.split(/\r?\n/);
3048
+ let codeblock = "";
3049
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
3050
+ for (let i2 = line - 1;i2 <= line + 1; i2++) {
3051
+ let l = lines[i2 - 1];
3052
+ if (!l)
3053
+ continue;
3054
+ codeblock += i2.toString().padEnd(numberLen, " ");
3055
+ codeblock += ": ";
3056
+ codeblock += l;
3057
+ codeblock += `
3058
+ `;
3059
+ if (i2 === line) {
3060
+ codeblock += " ".repeat(numberLen + column + 2);
3061
+ codeblock += `^
3062
+ `;
3063
+ }
3064
+ }
3065
+ return codeblock;
3066
+ }
3067
+
3068
+ class TomlError extends Error {
3069
+ line;
3070
+ column;
3071
+ codeblock;
3072
+ constructor(message, options) {
3073
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
3074
+ const codeblock = makeCodeBlock(options.toml, line, column);
3075
+ super(`Invalid TOML document: ${message}
3076
+
3077
+ ${codeblock}`, options);
3078
+ this.line = line;
3079
+ this.column = column;
3080
+ this.codeblock = codeblock;
3081
+ }
3082
+ static x(message, ctx, ptr) {
3083
+ throw new TomlError(message, { toml: ctx.s, ptr: ptr ?? ctx.p });
3084
+ }
3085
+ }
3086
+
3087
+ // server/node_modules/smol-toml/dist/primitive.js
3088
+ /*!
3089
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3090
+ * SPDX-License-Identifier: BSD-3-Clause
3091
+ *
3092
+ * Redistribution and use in source and binary forms, with or without
3093
+ * modification, are permitted provided that the following conditions are met:
3094
+ *
3095
+ * 1. Redistributions of source code must retain the above copyright notice, this
3096
+ * list of conditions and the following disclaimer.
3097
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3098
+ * this list of conditions and the following disclaimer in the
3099
+ * documentation and/or other materials provided with the distribution.
3100
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3101
+ * may be used to endorse or promote products derived from this software without
3102
+ * specific prior written permission.
3103
+ *
3104
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3105
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3106
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3107
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3108
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3109
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3110
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3111
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3112
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3113
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3114
+ */
3115
+ function parseString(ctx) {
3116
+ let startPtr = ctx.p;
3117
+ let c = ctx.s.charCodeAt(ctx.p++);
3118
+ let first = c;
3119
+ let isLiteral = c === 39;
3120
+ let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1);
3121
+ if (isMultiline) {
3122
+ if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10)
3123
+ ctx.p++;
3124
+ else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)
3125
+ ctx.p += 2;
3126
+ }
3127
+ let parsed = "";
3128
+ let sliceStart = ctx.p;
3129
+ let state = 0;
3130
+ for (;ctx.p < ctx.s.length; ctx.p++) {
3131
+ c = ctx.s.charCodeAt(ctx.p);
3132
+ if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
3133
+ state = state && 3;
3134
+ } else if (c < 32 && c !== 9 || c === 127) {
3135
+ TomlError.x("control characters are not allowed in strings", ctx);
3136
+ } else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
3137
+ if (isMultiline) {
3138
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
3139
+ ctx.p++;
3140
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
3141
+ ctx.p++;
3142
+ }
3143
+ if (!state) {
3144
+ let s = ctx.s.slice(sliceStart, ctx.p);
3145
+ parsed = parsed ? parsed + s : s;
3146
+ }
3147
+ ctx.p += isMultiline ? 3 : 1;
3148
+ return parsed;
3149
+ } else if (!state) {
3150
+ if (!isLiteral && c === 92) {
3151
+ parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
3152
+ state = 1;
3153
+ }
3154
+ } else if (state === 1) {
3155
+ if (c === 120 || c === 117 || c === 85) {
3156
+ let errPtr = ctx.p++ - 1;
3157
+ let value = 0;
3158
+ let len = c === 120 ? 2 : c === 117 ? 4 : 8;
3159
+ for (let j = 0;j < len; j++, ctx.p++) {
3160
+ let hex = ctx.s.charCodeAt(ctx.p);
3161
+ let digit = hex >= 48 && hex <= 57 ? hex - 48 : hex >= 65 && hex <= 70 ? hex - 65 + 10 : hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1;
3162
+ if (digit < 0)
3163
+ TomlError.x("invalid non-hex character in unicode escape", ctx);
3164
+ value = value << 4 | digit;
3165
+ }
3166
+ if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
3167
+ TomlError.x("invalid unicode escape", ctx, errPtr);
3168
+ }
3169
+ parsed += String.fromCodePoint(value);
3170
+ sliceStart = ctx.p--;
3171
+ state = 0;
3172
+ } else if (isMultiline && (c === 32 || c === 9)) {
3173
+ state = 2;
3174
+ } else {
3175
+ if (c === 98)
3176
+ parsed += "\b";
3177
+ else if (c === 116)
3178
+ parsed += "\t";
3179
+ else if (c === 110)
3180
+ parsed += `
3181
+ `;
3182
+ else if (c === 102)
3183
+ parsed += "\f";
3184
+ else if (c === 114)
3185
+ parsed += "\r";
3186
+ else if (c === 101)
3187
+ parsed += "\x1B";
3188
+ else if (c === 34)
3189
+ parsed += '"';
3190
+ else if (c === 92)
3191
+ parsed += "\\";
3192
+ else
3193
+ TomlError.x("unrecognised escape sequence", ctx);
3194
+ sliceStart = ctx.p + 1;
3195
+ state = 0;
3196
+ }
3197
+ } else if (c !== 32 && c !== 9) {
3198
+ if (state === 2)
3199
+ TomlError.x("invalid escape: only line-ending whitespace may be escaped", ctx, sliceStart);
3200
+ state = !isLiteral && c === 92 ? 1 : 0;
3201
+ sliceStart = ctx.p;
3202
+ }
3203
+ }
3204
+ TomlError.x("unfinished string", ctx, startPtr);
3205
+ }
3206
+
3207
+ // server/node_modules/smol-toml/dist/date.js
3208
+ /*!
3209
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3210
+ * SPDX-License-Identifier: BSD-3-Clause
3211
+ *
3212
+ * Redistribution and use in source and binary forms, with or without
3213
+ * modification, are permitted provided that the following conditions are met:
3214
+ *
3215
+ * 1. Redistributions of source code must retain the above copyright notice, this
3216
+ * list of conditions and the following disclaimer.
3217
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3218
+ * this list of conditions and the following disclaimer in the
3219
+ * documentation and/or other materials provided with the distribution.
3220
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3221
+ * may be used to endorse or promote products derived from this software without
3222
+ * specific prior written permission.
3223
+ *
3224
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3225
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3226
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3227
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3228
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3229
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3230
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3231
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3232
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3233
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3234
+ */
3235
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i;
3236
+
3237
+ class TomlDate extends Date {
3238
+ #hasDate = false;
3239
+ #hasTime = false;
3240
+ #offset = null;
3241
+ constructor(date, fasttype, unsafeDelim) {
3242
+ let hasDate = true;
3243
+ let hasTime = true;
3244
+ let offset = "Z";
3245
+ let c;
3246
+ if (typeof date === "string") {
3247
+ if (fasttype)
3248
+ prep: {
3249
+ if (fasttype < 3) {
3250
+ if (+date.slice(11, 13) > 23) {
3251
+ date = "";
3252
+ break prep;
3253
+ }
3254
+ if (fasttype === 2) {
3255
+ offset = null;
3256
+ date += "Z";
3257
+ } else if ((c = date.charCodeAt(date.length - 1)) !== 90 && c !== 122) {
3258
+ offset = date.slice(date.length - 6);
3259
+ }
3260
+ if (unsafeDelim)
3261
+ date = date.slice(0, 10) + "T" + date.slice(11);
3262
+ } else if (fasttype === 4) {
3263
+ date = +date.slice(0, 2) > 23 ? "" : `0000-01-01T${date}Z`;
3264
+ }
3265
+ hasDate = fasttype !== 4;
3266
+ hasTime = fasttype !== 3;
3267
+ }
3268
+ else {
3269
+ let match = date.match(DATE_TIME_RE);
3270
+ if (match) {
3271
+ if (!match[1]) {
3272
+ hasDate = false;
3273
+ date = `0000-01-01T${date}`;
3274
+ }
3275
+ hasTime = !!match[2];
3276
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
3277
+ if (match[2] && +match[2] > 23) {
3278
+ date = "";
3279
+ } else {
3280
+ offset = match[3] || null;
3281
+ if (!offset && hasTime)
3282
+ date += "Z";
3283
+ }
3284
+ } else {
3285
+ date = "";
3286
+ }
3287
+ }
3288
+ }
3289
+ super(date);
3290
+ if (!isNaN(this.getTime())) {
3291
+ this.#hasDate = hasDate;
3292
+ this.#hasTime = hasTime;
3293
+ this.#offset = offset;
3294
+ }
3295
+ }
3296
+ isDateTime() {
3297
+ return this.#hasDate && this.#hasTime;
3298
+ }
3299
+ isLocal() {
3300
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
3301
+ }
3302
+ isDate() {
3303
+ return this.#hasDate && !this.#hasTime;
3304
+ }
3305
+ isTime() {
3306
+ return this.#hasTime && !this.#hasDate;
3307
+ }
3308
+ isValid() {
3309
+ return this.#hasDate || this.#hasTime;
3310
+ }
3311
+ toISOString() {
3312
+ let iso = super.toISOString();
3313
+ if (this.isDate())
3314
+ return iso.slice(0, 10);
3315
+ if (this.isTime())
3316
+ return iso.slice(11, 23);
3317
+ if (this.#offset === null)
3318
+ return iso.slice(0, -1);
3319
+ if (this.#offset === "Z" || this.#offset === "z")
3320
+ return iso;
3321
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
3322
+ offset = this.#offset[0] === "-" ? offset : -offset;
3323
+ let offsetDate = new Date(this.getTime() - offset * 60000);
3324
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
3325
+ }
3326
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
3327
+ let date = new TomlDate(jsDate);
3328
+ date.#offset = offset;
3329
+ return date;
3330
+ }
3331
+ static wrapAsLocalDateTime(jsDate) {
3332
+ let date = new TomlDate(jsDate);
3333
+ date.#offset = null;
3334
+ return date;
3335
+ }
3336
+ static wrapAsLocalDate(jsDate) {
3337
+ let date = new TomlDate(jsDate);
3338
+ date.#hasTime = false;
3339
+ date.#offset = null;
3340
+ return date;
3341
+ }
3342
+ static wrapAsLocalTime(jsDate) {
3343
+ let date = new TomlDate(jsDate);
3344
+ date.#hasDate = false;
3345
+ date.#offset = null;
3346
+ return date;
3347
+ }
3348
+ }
3349
+
3350
+ // server/node_modules/smol-toml/dist/extract.js
3351
+ /*!
3352
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3353
+ * SPDX-License-Identifier: BSD-3-Clause
3354
+ *
3355
+ * Redistribution and use in source and binary forms, with or without
3356
+ * modification, are permitted provided that the following conditions are met:
3357
+ *
3358
+ * 1. Redistributions of source code must retain the above copyright notice, this
3359
+ * list of conditions and the following disclaimer.
3360
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3361
+ * this list of conditions and the following disclaimer in the
3362
+ * documentation and/or other materials provided with the distribution.
3363
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3364
+ * may be used to endorse or promote products derived from this software without
3365
+ * specific prior written permission.
3366
+ *
3367
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3368
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3369
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3370
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3371
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3372
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3373
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3374
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3375
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3376
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3377
+ */
3378
+ function isDigit(char, base = 10) {
3379
+ return base === 16 ? char > 47 && char < 58 || char > 64 && char < 71 || char > 96 && char < 103 : char > 47 && char < 48 + base;
3380
+ }
3381
+ function isEndOfValue(char, delim) {
3382
+ return char === 32 || char === 9 || char === 10 || char === 13 || delim && (char === delim || char === 44) || char === 35;
3383
+ }
3384
+ function extractValue(ctx, end) {
3385
+ let errPtr = ctx.p;
3386
+ let c = ctx.s.charCodeAt(ctx.p);
3387
+ if (c === 91 || c === 123) {
3388
+ ctx.d-- || TomlError.x("document contains excessively nested structures. aborting.", ctx);
3389
+ let value = c === 91 ? parseArray(ctx) : parseInlineTable(ctx);
3390
+ ctx.d++;
3391
+ return value;
3392
+ }
3393
+ if (c === 34 || c === 39) {
3394
+ return parseString(ctx);
3395
+ }
3396
+ if (c === 116) {
3397
+ if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101)
3398
+ TomlError.x("invalid value", ctx, errPtr);
3399
+ return ctx.p++, true;
3400
+ }
3401
+ if (c === 102) {
3402
+ if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101)
3403
+ TomlError.x("invalid value", ctx, errPtr);
3404
+ return ctx.p++, false;
3405
+ }
3406
+ if (c === 43 || c === 45) {
3407
+ return parseNumber(ctx, ctx.p, ctx.s.charCodeAt(++ctx.p), 44 - c, end);
3408
+ }
3409
+ if (ctx.s.charCodeAt(ctx.p + 4) === 45 && ctx.s.charCodeAt(ctx.p + 7) === 45) {
3410
+ return parseDate(ctx, c, end);
3411
+ }
3412
+ if (ctx.s.charCodeAt(ctx.p + 2) === 58) {
3413
+ return parseTime(ctx, c, end);
3414
+ }
3415
+ return parseNumber(ctx, ctx.p, c, 0, end);
3416
+ }
3417
+ function parseNumber(ctx, startPtr, startChr, sign, endChr) {
3418
+ let c = startChr;
3419
+ let state = 0;
3420
+ let hasUnderscores = false;
3421
+ if (c === 105) {
3422
+ if (ctx.s.charCodeAt(++ctx.p) !== 110 || ctx.s.charCodeAt(++ctx.p) !== 102)
3423
+ TomlError.x("invalid value", ctx, startPtr);
3424
+ return ctx.p++, (sign || 1) / 0;
3425
+ }
3426
+ if (c === 110) {
3427
+ if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 110)
3428
+ TomlError.x("invalid value", ctx, startPtr);
3429
+ return ctx.p++, NaN;
3430
+ }
3431
+ if (c === 48) {
3432
+ if (++ctx.p >= ctx.s.length || isEndOfValue(c = ctx.s.charCodeAt(ctx.p), endChr))
3433
+ return ctx.bi === true ? 0n : 0;
3434
+ if (!sign) {
3435
+ if (c === 120)
3436
+ return parseIntegerBaseN(ctx, startPtr, 16, endChr);
3437
+ else if (c === 98)
3438
+ return parseIntegerBaseN(ctx, startPtr, 2, endChr);
3439
+ else if (c === 111)
3440
+ return parseIntegerBaseN(ctx, startPtr, 8, endChr);
3441
+ }
3442
+ if (c === 46)
3443
+ state = 2;
3444
+ else if (c === 101 || c === 69)
3445
+ state = 4;
3446
+ else
3447
+ TomlError.x("illegal leading zero", ctx, startPtr);
3448
+ } else if (!isDigit(c))
3449
+ TomlError.x("invalid value", ctx, startPtr);
3450
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
3451
+ if (!state)
3452
+ state = 1;
3453
+ if (c === 95) {
3454
+ if (!(state & 1))
3455
+ TomlError.x("illegal underscore", ctx);
3456
+ state += 11;
3457
+ hasUnderscores = true;
3458
+ } else if (state === 1 && c === 46)
3459
+ state = 2;
3460
+ else if ((state === 1 || state === 3) && (c === 101 || c === 69))
3461
+ state = 4;
3462
+ else if (state === 4 && (c === 43 || c === 45)) {} else if (!isDigit(c))
3463
+ TomlError.x(`illegal character in numeric literal`, ctx);
3464
+ else if (state > 9)
3465
+ state -= 11;
3466
+ else if (!(state & 1))
3467
+ state++;
3468
+ }
3469
+ if (!state) {
3470
+ let val = (startChr - 48) * (sign || 1);
3471
+ return ctx.bi === true ? BigInt(val) : val;
3472
+ }
3473
+ if (!(state & 1))
3474
+ TomlError.x("unfinished numeric value", ctx, startPtr);
3475
+ let str = ctx.s.slice(startPtr, ctx.p);
3476
+ if (hasUnderscores)
3477
+ str = str.replaceAll("_", "");
3478
+ return state > 1 ? parseFloat(str) : parseInteger(ctx, str, 10, startPtr);
3479
+ }
3480
+ function parseIntegerBaseN(ctx, startPtr, base, endChr) {
3481
+ let c, underscore = 1;
3482
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
3483
+ if (c === 95) {
3484
+ if (underscore & 1)
3485
+ TomlError.x("illegal underscore", ctx);
3486
+ underscore = 3;
3487
+ } else if (!isDigit(c, base))
3488
+ TomlError.x(`illegal character in numeric literal`, ctx);
3489
+ else if (underscore & 1)
3490
+ underscore--;
3491
+ }
3492
+ if (underscore & 1)
3493
+ TomlError.x("unfinished numeric value", ctx);
3494
+ let str = ctx.s.slice(startPtr + 2, ctx.p);
3495
+ if (underscore)
3496
+ str = str.replaceAll("_", "");
3497
+ return parseInteger(ctx, str, base, startPtr);
3498
+ }
3499
+ function parseInteger(ctx, str, base, startPtr) {
3500
+ if (ctx.bi !== true)
3501
+ int: {
3502
+ let val = parseInt(str, base);
3503
+ if (!Number.isSafeInteger(val)) {
3504
+ if (ctx.bi)
3505
+ break int;
3506
+ TomlError.x("integer value cannot be represented losslessly", ctx, startPtr);
3507
+ }
3508
+ return val;
3509
+ }
3510
+ return base === 10 ? BigInt(str) : BigInt((base === 2 ? "0b" : base === 8 ? "0o" : "0x") + str);
3511
+ }
3512
+ function parseDate(ctx, c, endChr) {
3513
+ let startPtr = ctx.p++, unsafeSeparator;
3514
+ if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++))) {
3515
+ return parseNumber(ctx, ctx.p = startPtr, c, 0, endChr);
3516
+ }
3517
+ ctx.p += 5;
3518
+ if (!isDigit(ctx.s.charCodeAt(ctx.p++)))
3519
+ TomlError.x("invalid date-time: date part is malformed", ctx, startPtr);
3520
+ if (ctx.p >= ctx.s.length || ((c = ctx.s.charCodeAt(ctx.p)) !== 32 || (unsafeSeparator = true, !isDigit(ctx.s.charCodeAt(ctx.p + 1)))) && c !== 84 && c !== 116) {
3521
+ let t3 = ctx.s.slice(startPtr, ctx.p);
3522
+ return readDate(ctx, t3, 3, false, startPtr);
3523
+ }
3524
+ if (ctx.s.charCodeAt(ctx.p += 3) !== 58)
3525
+ TomlError.x("invalid date-time: time part is malformed", ctx, startPtr);
3526
+ if (ctx.s.charCodeAt(ctx.p += 3) === 58)
3527
+ ctx.p += 3;
3528
+ if (ctx.s.charCodeAt(ctx.p) === 46)
3529
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
3530
+ ;
3531
+ if (c = ctx.s.charCodeAt(ctx.p)) {
3532
+ if (c === 90 || c === 122) {
3533
+ let t3 = ctx.s.slice(startPtr, ++ctx.p);
3534
+ return readDate(ctx, t3, 1, unsafeSeparator, startPtr, "[+00:00]");
3535
+ }
3536
+ if (c === 43 || c === 45) {
3537
+ let t3 = ctx.s.slice(startPtr, ctx.p += 6);
3538
+ return readDate(ctx, t3, 1, unsafeSeparator, startPtr, !ctx.ld && "[" + ctx.s.slice(ctx.p - 6, ctx.p) + "]");
3539
+ }
3540
+ }
3541
+ let t2 = ctx.s.slice(startPtr, ctx.p);
3542
+ return readDate(ctx, t2, 2, unsafeSeparator, startPtr);
3543
+ }
3544
+ function parseTime(ctx, c, endChr) {
3545
+ let start = ctx.p;
3546
+ if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(++ctx.p))) {
3547
+ return parseNumber(ctx, --ctx.p, c, 0, endChr);
3548
+ }
3549
+ if (ctx.s.charCodeAt(ctx.p += 4) === 58)
3550
+ ctx.p += 3;
3551
+ if (ctx.s.charCodeAt(ctx.p) === 46)
3552
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
3553
+ ;
3554
+ let t2 = ctx.s.slice(start, ctx.p);
3555
+ return readDate(ctx, t2, 4, false, start);
3556
+ }
3557
+ function readDate(ctx, str, type, unsafeDelim, errPtr, temporalSuffix) {
3558
+ if (ctx.ld) {
3559
+ let date = new TomlDate(str, type, unsafeDelim);
3560
+ if (!date.isValid())
3561
+ TomlError.x("invalid date", ctx, errPtr);
3562
+ return date;
3563
+ }
3564
+ try {
3565
+ if (temporalSuffix)
3566
+ str += temporalSuffix;
3567
+ switch (type) {
3568
+ case 1:
3569
+ return Temporal.ZonedDateTime.from(str);
3570
+ case 2:
3571
+ return Temporal.PlainDateTime.from(str);
3572
+ case 3:
3573
+ return Temporal.PlainDate.from(str);
3574
+ case 4:
3575
+ return Temporal.PlainTime.from(str);
3576
+ }
3577
+ } catch (e) {
3578
+ TomlError.x(e instanceof Error ? e.message : "" + e, ctx, errPtr);
3579
+ }
3580
+ }
3581
+
3582
+ // server/node_modules/smol-toml/dist/util.js
3583
+ /*!
3584
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3585
+ * SPDX-License-Identifier: BSD-3-Clause
3586
+ *
3587
+ * Redistribution and use in source and binary forms, with or without
3588
+ * modification, are permitted provided that the following conditions are met:
3589
+ *
3590
+ * 1. Redistributions of source code must retain the above copyright notice, this
3591
+ * list of conditions and the following disclaimer.
3592
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3593
+ * this list of conditions and the following disclaimer in the
3594
+ * documentation and/or other materials provided with the distribution.
3595
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3596
+ * may be used to endorse or promote products derived from this software without
3597
+ * specific prior written permission.
3598
+ *
3599
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3600
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3601
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3602
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3603
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3604
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3605
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3606
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3607
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3608
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3609
+ */
3610
+ function skipComment(ctx) {
3611
+ for (;ctx.p < ctx.s.length; ctx.p++) {
3612
+ let c = ctx.s.charCodeAt(ctx.p);
3613
+ if (c === 10)
3614
+ break;
3615
+ if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) {
3616
+ ctx.p++;
3617
+ break;
3618
+ }
3619
+ if (c < 32 && c !== 9 || c === 127) {
3620
+ TomlError.x("control characters are not allowed in comments", ctx);
3621
+ }
3622
+ }
3623
+ }
3624
+ function skipVoid(ctx, banNewLines, banComments) {
3625
+ let c;
3626
+ while (ctx.p < ctx.s.length) {
3627
+ while (ctx.p < ctx.s.length && ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)))
3628
+ ctx.p++;
3629
+ if (banComments || c !== 35)
3630
+ break;
3631
+ skipComment(ctx);
3632
+ }
3633
+ }
3634
+
3635
+ // server/node_modules/smol-toml/dist/struct.js
3636
+ /*!
3637
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3638
+ * SPDX-License-Identifier: BSD-3-Clause
3639
+ *
3640
+ * Redistribution and use in source and binary forms, with or without
3641
+ * modification, are permitted provided that the following conditions are met:
3642
+ *
3643
+ * 1. Redistributions of source code must retain the above copyright notice, this
3644
+ * list of conditions and the following disclaimer.
3645
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3646
+ * this list of conditions and the following disclaimer in the
3647
+ * documentation and/or other materials provided with the distribution.
3648
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3649
+ * may be used to endorse or promote products derived from this software without
3650
+ * specific prior written permission.
3651
+ *
3652
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3653
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3654
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3655
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3656
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3657
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3658
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3659
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3660
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3661
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3662
+ */
3663
+ function parseKey(ctx, end = 61) {
3664
+ let startPtr;
3665
+ let state = 0;
3666
+ let parsed = [];
3667
+ let sliceStart;
3668
+ let c = ctx.s.charCodeAt(startPtr = ctx.p);
3669
+ do {
3670
+ if (c === end) {
3671
+ if (!state)
3672
+ TomlError.x("unexpected end of key", ctx);
3673
+ if (state === 1)
3674
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
3675
+ return ctx.p++, parsed;
3676
+ } else if (c === 46) {
3677
+ if (!state)
3678
+ TomlError.x("illegal empty bare key", ctx);
3679
+ if (state === 1)
3680
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
3681
+ state = 0;
3682
+ } else if (!state && (c === 34 || c === 39)) {
3683
+ if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2))
3684
+ TomlError.x("illegal quoted key: multiline strings are not allowed", ctx);
3685
+ parsed.push(parseString(ctx));
3686
+ state = 2;
3687
+ ctx.p--;
3688
+ } else if (c === 32 || c === 9) {
3689
+ if (state === 1) {
3690
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
3691
+ state = 2;
3692
+ }
3693
+ } else if (state === 2 || c < 48 && c !== 45 || c > 57 && c < 65 || c > 90 && c < 97 && c !== 95 || c > 122) {
3694
+ TomlError.x("illegal character in key", ctx);
3695
+ } else if (!state) {
3696
+ state = 1;
3697
+ sliceStart = ctx.p;
3698
+ }
3699
+ } while (c = ctx.s.charCodeAt(++ctx.p));
3700
+ TomlError.x("incomplete key-value: cannot find end of key", ctx, startPtr);
3701
+ }
3702
+ function parseInlineTable(ctx) {
3703
+ let startPtr = ctx.p++;
3704
+ let res = Object.create(null);
3705
+ let seen = new Set;
3706
+ let c;
3707
+ while (ctx.p < ctx.s.length) {
3708
+ skipVoid(ctx);
3709
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 125) {
3710
+ ctx.p++;
3711
+ return res;
3712
+ }
3713
+ let k;
3714
+ let t2 = res;
3715
+ let hasOwn = false;
3716
+ let errPtr = ctx.p;
3717
+ let key = parseKey(ctx);
3718
+ for (let i2 = 0;i2 < key.length; i2++) {
3719
+ if (i2)
3720
+ t2 = hasOwn ? t2[k] : t2[k] = Object.create(null);
3721
+ k = key[i2];
3722
+ if ((hasOwn = Object.hasOwn(t2, k)) && (typeof t2[k] !== "object" || seen.has(t2[k]))) {
3723
+ TomlError.x("trying to redefine an already defined value", ctx, errPtr);
3724
+ }
3725
+ let unsafe = k === "__proto__";
3726
+ if (ctx.uk && (unsafe || k === "constructor")) {
3727
+ t2 = ctx.uk !== 1 && TomlError.x("document contains an unsafe property", ctx, errPtr);
3728
+ break;
3729
+ }
3730
+ if (!hasOwn && unsafe) {
3731
+ Object.defineProperty(t2, k, { enumerable: true, configurable: true, writable: true });
3732
+ }
3733
+ }
3734
+ if (hasOwn) {
3735
+ TomlError.x("trying to redefine an already defined value", ctx, errPtr);
3736
+ }
3737
+ skipVoid(ctx, true, true);
3738
+ let value = extractValue(ctx, 125);
3739
+ if (t2 && typeof (t2[k] = value) === "object")
3740
+ seen.add(value);
3741
+ skipVoid(ctx);
3742
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) {
3743
+ return res;
3744
+ }
3745
+ if (c !== 44)
3746
+ TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
3747
+ }
3748
+ TomlError.x("unfinished table", ctx, startPtr);
3749
+ }
3750
+ function parseArray(ctx) {
3751
+ let startPtr = ctx.p++;
3752
+ let res = [];
3753
+ let c;
3754
+ while (ctx.p < ctx.s.length) {
3755
+ skipVoid(ctx);
3756
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 93) {
3757
+ ctx.p++;
3758
+ return res;
3759
+ }
3760
+ res.push(extractValue(ctx, 93));
3761
+ skipVoid(ctx);
3762
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) {
3763
+ return res;
3764
+ }
3765
+ if (c !== 44)
3766
+ TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
3767
+ }
3768
+ TomlError.x("unfinished array", ctx, startPtr);
3769
+ }
3770
+
3771
+ // server/node_modules/smol-toml/dist/parse.js
3772
+ /*!
3773
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3774
+ * SPDX-License-Identifier: BSD-3-Clause
3775
+ *
3776
+ * Redistribution and use in source and binary forms, with or without
3777
+ * modification, are permitted provided that the following conditions are met:
3778
+ *
3779
+ * 1. Redistributions of source code must retain the above copyright notice, this
3780
+ * list of conditions and the following disclaimer.
3781
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3782
+ * this list of conditions and the following disclaimer in the
3783
+ * documentation and/or other materials provided with the distribution.
3784
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3785
+ * may be used to endorse or promote products derived from this software without
3786
+ * specific prior written permission.
3787
+ *
3788
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3789
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3790
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3791
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3792
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3793
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3794
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3795
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3796
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3797
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3798
+ */
3799
+ function peekTable(ctx, key, table, meta, type) {
3800
+ let t2 = table;
3801
+ let m = meta;
3802
+ let k;
3803
+ let hasOwn = false;
3804
+ let state;
3805
+ for (let i2 = 0;i2 < key.length; i2++) {
3806
+ if (i2) {
3807
+ t2 = hasOwn ? t2[k] : t2[k] = Object.create(null);
3808
+ m = (state = m[k]).c;
3809
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
3810
+ return null;
3811
+ }
3812
+ if (state.t === 2) {
3813
+ let l = t2.length - 1;
3814
+ t2 = t2[l];
3815
+ m = m[l].c;
3816
+ }
3817
+ }
3818
+ k = key[i2];
3819
+ if ((hasOwn = Object.hasOwn(t2, k)) && m[k]?.t === 0 && m[k]?.d) {
3820
+ return null;
3821
+ }
3822
+ if (!hasOwn) {
3823
+ let unsafe = k === "__proto__";
3824
+ if (ctx.uk && (unsafe || k === "constructor"))
3825
+ return false;
3826
+ if (unsafe) {
3827
+ Object.defineProperty(t2, k, { enumerable: true, configurable: true, writable: true });
3828
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
3829
+ }
3830
+ m[k] = {
3831
+ t: i2 < key.length - 1 && type === 2 ? 3 : type,
3832
+ d: false,
3833
+ i: 0,
3834
+ c: Object.create(null)
3835
+ };
3836
+ }
3837
+ }
3838
+ state = m[k];
3839
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
3840
+ return null;
3841
+ }
3842
+ if (type === 2) {
3843
+ if (!state.d) {
3844
+ state.d = true;
3845
+ t2[k] = [];
3846
+ }
3847
+ t2[k].push(t2 = Object.create(null));
3848
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: Object.create(null) };
3849
+ }
3850
+ if (state.d) {
3851
+ return null;
3852
+ }
3853
+ state.d = true;
3854
+ if (type === 1) {
3855
+ t2 = hasOwn ? t2[k] : t2[k] = Object.create(null);
3856
+ } else if (type === 0 && hasOwn) {
3857
+ return null;
3858
+ }
3859
+ return [k, t2, state.c];
3860
+ }
3861
+ function validateTablePeek(ctx, peek, ptr) {
3862
+ if (peek === null || ctx.uk === 2)
3863
+ TomlError.x(peek === null ? "trying to redefine an already defined table or value" : "document contains an unsafe property", ctx, ptr);
3864
+ }
3865
+ function parse(toml, options = {}) {
3866
+ let ctx = {
3867
+ s: toml,
3868
+ p: 0,
3869
+ d: options.maxDepth ?? 1000,
3870
+ bi: options.integersAsBigInt ?? false,
3871
+ ld: options.useLegacyDate ?? true,
3872
+ uk: options.unsafeKeyBehaviour === "throw" ? 2 : options.unsafeKeyBehaviour === "drop" ? 1 : 0
3873
+ };
3874
+ let res = Object.create(null);
3875
+ let meta = Object.create(null);
3876
+ let tmp;
3877
+ let skipping = false;
3878
+ let tbl = res;
3879
+ let m = meta;
3880
+ if (toml.charCodeAt(0) === 65279)
3881
+ ctx.p++;
3882
+ skipVoid(ctx);
3883
+ while (ctx.p < toml.length) {
3884
+ if (toml.charCodeAt(ctx.p) === 91) {
3885
+ let isTableArray = toml.charCodeAt(++ctx.p) === 91;
3886
+ tmp = ctx.p += +isTableArray;
3887
+ skipping = false;
3888
+ let k = parseKey(ctx, 93);
3889
+ if (isTableArray) {
3890
+ if (toml.charCodeAt(ctx.p) !== 93) {
3891
+ TomlError.x("expected end of table array declaration", ctx);
3892
+ }
3893
+ ctx.p++;
3894
+ }
3895
+ let p = peekTable(ctx, k, res, meta, isTableArray ? 2 : 1);
3896
+ if (!p) {
3897
+ validateTablePeek(ctx, p, tmp);
3898
+ skipping = true;
3899
+ } else {
3900
+ m = p[2];
3901
+ tbl = p[1];
3902
+ }
3903
+ } else {
3904
+ tmp = ctx.p;
3905
+ let k = parseKey(ctx);
3906
+ let p = peekTable(ctx, k, tbl, m, 0);
3907
+ if (!p && !skipping)
3908
+ validateTablePeek(ctx, p, tmp);
3909
+ skipVoid(ctx, true, true);
3910
+ let v = extractValue(ctx, undefined);
3911
+ if (p && !skipping)
3912
+ p[1][p[0]] = v;
3913
+ }
3914
+ skipVoid(ctx, true);
3915
+ if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && (tmp !== 13 || toml.charCodeAt(ctx.p + 1) !== 10)) {
3916
+ TomlError.x("each key-value declaration must be followed by an end-of-line", ctx);
3917
+ }
3918
+ skipVoid(ctx);
3919
+ }
3920
+ return res;
3921
+ }
3922
+
3923
+ // server/node_modules/smol-toml/dist/stringify.js
3924
+ /*!
3925
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3926
+ * SPDX-License-Identifier: BSD-3-Clause
3927
+ *
3928
+ * Redistribution and use in source and binary forms, with or without
3929
+ * modification, are permitted provided that the following conditions are met:
3930
+ *
3931
+ * 1. Redistributions of source code must retain the above copyright notice, this
3932
+ * list of conditions and the following disclaimer.
3933
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3934
+ * this list of conditions and the following disclaimer in the
3935
+ * documentation and/or other materials provided with the distribution.
3936
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3937
+ * may be used to endorse or promote products derived from this software without
3938
+ * specific prior written permission.
3939
+ *
3940
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3941
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3942
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3943
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3944
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3945
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3946
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3947
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3948
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3949
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3950
+ */
3951
+ var HAS_WELLFORMED = !!"".isWellFormed;
3952
+
3953
+ // server/node_modules/smol-toml/dist/index.js
3954
+ /*!
3955
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
3956
+ * SPDX-License-Identifier: BSD-3-Clause
3957
+ *
3958
+ * Redistribution and use in source and binary forms, with or without
3959
+ * modification, are permitted provided that the following conditions are met:
3960
+ *
3961
+ * 1. Redistributions of source code must retain the above copyright notice, this
3962
+ * list of conditions and the following disclaimer.
3963
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
3964
+ * this list of conditions and the following disclaimer in the
3965
+ * documentation and/or other materials provided with the distribution.
3966
+ * 3. Neither the name of the copyright holder nor the names of its contributors
3967
+ * may be used to endorse or promote products derived from this software without
3968
+ * specific prior written permission.
3969
+ *
3970
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
3971
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3972
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3973
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
3974
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3975
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3976
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3977
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3978
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3979
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3980
+ */
3981
+
3982
+ // server/src/codex-trust.ts
3983
+ var CODEX_TRUST_VERIFIED = ["0.160.0"];
3984
+ var LABELS = {
3985
+ PreToolUse: "pre_tool_use",
3986
+ PermissionRequest: "permission_request",
3987
+ PostToolUse: "post_tool_use",
3988
+ PreCompact: "pre_compact",
3989
+ PostCompact: "post_compact",
3990
+ SessionStart: "session_start",
3991
+ SessionEnd: "session_end",
3992
+ UserPromptSubmit: "user_prompt_submit",
3993
+ SubagentStart: "subagent_start",
3994
+ SubagentStop: "subagent_stop",
3995
+ Stop: "stop",
3996
+ Interrupt: "interrupt"
3997
+ };
3998
+ var NO_MATCHER = new Set(["UserPromptSubmit", "Stop", "Interrupt"]);
3999
+ var CONTEXT = new Set(["PreToolUse", "PostToolUse", "SessionStart", "UserPromptSubmit", "SubagentStart"]);
4000
+ var DEFAULT_CONTEXT_LIMIT = 2500;
4001
+ var SHORT = new Set(["SessionEnd", "Interrupt"]);
4002
+ var RUST_SPACE = /^[\t\n\v\f\r \u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+|[\t\n\v\f\r \u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]+$/g;
4003
+ var isTable = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4004
+ var isCount = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
4005
+ function canonical(v) {
4006
+ if (Array.isArray(v))
4007
+ return `[${v.map(canonical).join(",")}]`;
4008
+ if (isTable(v))
4009
+ return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`;
4010
+ return JSON.stringify(v);
4011
+ }
4012
+ var sha256 = (text) => `sha256:${crypto.createHash("sha256").update(text).digest("hex")}`;
4013
+ function readHookStates(configToml) {
4014
+ const states = new Map;
4015
+ if (configToml === null)
4016
+ return states;
4017
+ let config;
4018
+ try {
4019
+ config = parse(configToml);
4020
+ } catch {
4021
+ return { unknown: "config.toml could not be parsed" };
4022
+ }
4023
+ const hooks = isTable(config) ? config.hooks : undefined;
4024
+ if (hooks === undefined)
4025
+ return states;
4026
+ const table = isTable(hooks) ? hooks.state : undefined;
4027
+ if (table === undefined)
4028
+ return states;
4029
+ if (!isTable(table))
4030
+ return { unknown: "hooks.state in config.toml is not a table" };
4031
+ for (const [raw, value] of Object.entries(table)) {
4032
+ const key = raw.replace(RUST_SPACE, "");
4033
+ if (!key || !isTable(value))
4034
+ continue;
4035
+ const { enabled, trusted_hash } = value;
4036
+ if (enabled !== undefined && typeof enabled !== "boolean" || trusted_hash !== undefined && typeof trusted_hash !== "string")
4037
+ continue;
4038
+ const s = states.get(key) ?? {};
4039
+ if (enabled !== undefined)
4040
+ s.enabled = enabled;
4041
+ if (trusted_hash !== undefined)
4042
+ s.trusted_hash = trusted_hash;
4043
+ states.set(key, s);
4044
+ }
4045
+ return states;
4046
+ }
4047
+ function hookTrust(hooksJson, pluginId, relativePath, platform, states) {
4048
+ let file;
4049
+ try {
4050
+ file = JSON.parse(hooksJson, (_key, value, context) => typeof value === "number" && /[.eE]/.test(context?.source ?? "") ? Number.NaN : value);
4051
+ } catch {
4052
+ return { unknown: "the hooks file is not JSON" };
4053
+ }
4054
+ if (isTable(file) && (Object.keys(file).some((k) => k !== "hooks" && k !== "description") || file.description != null && typeof file.description !== "string"))
4055
+ return { unknown: "the hooks file has a top-level field Codex would not read" };
4056
+ const events = isTable(file) ? file.hooks : undefined;
4057
+ if (!isTable(events))
4058
+ return { unknown: "the hooks file has no hooks table" };
4059
+ const hooks = [];
4060
+ for (const [event, groups] of Object.entries(events)) {
4061
+ const label = LABELS[event];
4062
+ if (!label)
4063
+ return { unknown: `the hooks file has an event Codex does not know (${event})` };
4064
+ if (!Array.isArray(groups))
4065
+ return { unknown: `${event} in the hooks file is not a list` };
4066
+ for (const [g, group2] of groups.entries()) {
4067
+ if (!isTable(group2) || !Array.isArray(group2.hooks))
4068
+ return { unknown: `a ${event} group has no hooks list` };
4069
+ if (group2.matcher !== undefined && group2.matcher !== null && typeof group2.matcher !== "string")
4070
+ return { unknown: `a ${event} matcher is not a string` };
4071
+ const matcher = NO_MATCHER.has(event) ? undefined : group2.matcher ?? undefined;
4072
+ for (const [h2, handler] of group2.hooks.entries()) {
4073
+ if (!isTable(handler) || handler.type !== "command")
4074
+ return { unknown: `a ${event} hook is not a command` };
4075
+ const { command, timeout, statusMessage, additionalContextLimit } = handler;
4076
+ if ("commandWindows" in handler && "command_windows" in handler)
4077
+ return { unknown: `a ${event} hook names its Windows command twice` };
4078
+ const commandWindows = handler.commandWindows ?? handler.command_windows;
4079
+ if (typeof command !== "string" || commandWindows !== undefined && commandWindows !== null && typeof commandWindows !== "string" || timeout !== undefined && timeout !== null && !isCount(timeout) || handler.async !== undefined && typeof handler.async !== "boolean" || statusMessage !== undefined && statusMessage !== null && typeof statusMessage !== "string" || additionalContextLimit !== undefined && additionalContextLimit !== null && !isCount(additionalContextLimit))
4080
+ return { unknown: `a ${event} hook has a field Codex would not read` };
4081
+ const chosen = platform === "win32" ? commandWindows ?? command : command;
4082
+ if (!chosen.trim())
4083
+ return { unknown: `a ${event} hook has an empty command` };
4084
+ const given = timeout ?? undefined;
4085
+ const seconds = SHORT.has(event) ? Math.min(Math.max(given ?? 1, 1), 3) : Math.max(given ?? 600, 1);
4086
+ const limit = CONTEXT.has(event) && additionalContextLimit != null && additionalContextLimit !== DEFAULT_CONTEXT_LIMIT ? additionalContextLimit : undefined;
4087
+ const normalized = {
4088
+ type: "command",
4089
+ command: chosen,
4090
+ timeout: seconds,
4091
+ async: handler.async === true
4092
+ };
4093
+ if (typeof statusMessage === "string")
4094
+ normalized.statusMessage = statusMessage;
4095
+ if (limit !== undefined)
4096
+ normalized.additionalContextLimit = limit;
4097
+ const identity = { event_name: label, hooks: [normalized] };
4098
+ if (typeof matcher === "string")
4099
+ identity.matcher = matcher;
4100
+ const hash = sha256(canonical(identity));
4101
+ const key = `${pluginId}:${relativePath}:${label}:${g}:${h2}`;
4102
+ const state = states.get(key);
4103
+ const stored = state?.trusted_hash;
4104
+ const trust = stored === undefined ? "untrusted" : stored === hash ? "trusted" : "modified";
4105
+ hooks.push({ key, hash, trust, enabled: state?.enabled !== false });
4106
+ }
4107
+ }
4108
+ }
4109
+ if (!hooks.length)
4110
+ return { unknown: "the hooks file defines no hooks" };
4111
+ return { hooks };
4112
+ }
4113
+
3010
4114
  // server/src/panel.ts
3011
4115
  import { stripVTControlCharacters as stripVTControlCharacters2, styleText as styleText2 } from "node:util";
3012
4116
 
3013
4117
  // server/src/text.ts
3014
- import crypto from "node:crypto";
4118
+ import crypto2 from "node:crypto";
3015
4119
  var segmenter = new Intl.Segmenter("ja", { granularity: "word" });
3016
4120
  var HIRAGANA_ONLY = /^[\p{Script=Hiragana}ー]+$/u;
3017
4121
  var STOP = new Set(["the", "a", "an", "of", "to", "in", "is", "and", "or", "for", "on", "it", "be"]);
@@ -3109,9 +4213,9 @@ var QUESTION = new Set([
3109
4213
  ...["理由", "仕組", "何", "方", "場合", "今", "件"]
3110
4214
  ]);
3111
4215
  var FOLDED_QUESTION = new Set([...QUESTION].flatMap((w) => terms(w)));
3112
- var sha256 = (s) => crypto.createHash("sha256").update(s).digest();
4216
+ var sha2562 = (s) => crypto2.createHash("sha256").update(s).digest();
3113
4217
  function uuidFrom(...parts) {
3114
- const b = crypto.createHash("sha256").update(parts.join("\x00")).digest().subarray(0, 16);
4218
+ const b = crypto2.createHash("sha256").update(parts.join("\x00")).digest().subarray(0, 16);
3115
4219
  b[6] = (b[6] ?? 0) & 15 | 128;
3116
4220
  b[8] = (b[8] ?? 0) & 63 | 128;
3117
4221
  const h2 = b.toString("hex");
@@ -3469,11 +4573,18 @@ function observe(cwdRoot, platform = process.platform, env = process.env, execPa
3469
4573
  } catch {}
3470
4574
  const codexCache = path2.join(codexHome, "plugins", "cache");
3471
4575
  const codex = [];
4576
+ const markets = [];
3472
4577
  for (const market of safeDirs(codexCache)) {
3473
4578
  for (const v of safeDirs(path2.join(codexCache, market, "sphica"))) {
3474
4579
  codex.push(install(path2.join(codexCache, market, "sphica", v)));
4580
+ markets.push(market);
3475
4581
  }
3476
4582
  }
4583
+ let codexHooks;
4584
+ if (codex.length > 1)
4585
+ codexHooks = { unknown: "more than one Codex cache" };
4586
+ else if (codex[0] && markets[0])
4587
+ codexHooks = codexHookTrust(codex[0].root, `sphica@${markets[0]}`, codexHome, platform, env, execPath);
3477
4588
  let running;
3478
4589
  if (platform === "win32")
3479
4590
  running = { unknown: "not checked on Windows" };
@@ -3527,7 +4638,88 @@ function observe(cwdRoot, platform = process.platform, env = process.env, execPa
3527
4638
  } catch {
3528
4639
  global = { unknown: "npm root -g failed" };
3529
4640
  }
3530
- return { repository, cli: install(ROOT), global, claude, claudeVersion, codex, codexCache, running };
4641
+ return {
4642
+ repository,
4643
+ cli: install(ROOT),
4644
+ global,
4645
+ claude,
4646
+ claudeVersion,
4647
+ codex,
4648
+ codexCache,
4649
+ codexHooks,
4650
+ running
4651
+ };
4652
+ }
4653
+ function codexCommand(platform, env, execPath) {
4654
+ if (platform !== "win32")
4655
+ return ["codex", ["--version"]];
4656
+ const exts = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
4657
+ for (const dir of (env.PATH ?? env.Path ?? "").split(";").filter(Boolean))
4658
+ for (const ext of exts) {
4659
+ const at = path2.join(dir, `codex${ext.toLowerCase()}`);
4660
+ if (!fs2.existsSync(at))
4661
+ continue;
4662
+ if (ext.toLowerCase() === ".exe")
4663
+ return [at, ["--version"]];
4664
+ const js = path2.join(dir, "node_modules", "@openai", "codex", "bin", "codex.js");
4665
+ return fs2.existsSync(js) ? [execPath, [js, "--version"]] : null;
4666
+ }
4667
+ return null;
4668
+ }
4669
+ function codexHookTrust(root, pluginId, codexHome, platform, env, execPath) {
4670
+ const command = codexCommand(platform, env, execPath);
4671
+ if (!command)
4672
+ return { unknown: "no codex.exe, or codex.js beside codex.cmd, on PATH" };
4673
+ let version2;
4674
+ try {
4675
+ const out = execFileSync(command[0], command[1], {
4676
+ encoding: "utf8",
4677
+ env,
4678
+ stdio: ["ignore", "pipe", "ignore"],
4679
+ timeout: 1e4
4680
+ });
4681
+ version2 = /^codex-cli (\d+\.\d+\.\d+)$/m.exec(out)?.[1];
4682
+ } catch {
4683
+ return { unknown: "codex --version failed" };
4684
+ }
4685
+ if (!version2)
4686
+ return { unknown: "codex --version printed no version" };
4687
+ if (!CODEX_TRUST_VERIFIED.includes(version2))
4688
+ return {
4689
+ unknown: `Codex ${version2}; Sphica reads its hook trust only for ${CODEX_TRUST_VERIFIED.join(", ")}`
4690
+ };
4691
+ let file;
4692
+ try {
4693
+ const manifest = JSON.parse(fs2.readFileSync(path2.join(root, ".codex-plugin", "plugin.json"), "utf8"));
4694
+ if (typeof manifest.hooks !== "string")
4695
+ return { unknown: "the Codex plugin manifest names no hooks file" };
4696
+ file = path2.resolve(root, manifest.hooks);
4697
+ } catch {
4698
+ return { unknown: "the Codex plugin manifest could not be read" };
4699
+ }
4700
+ const relative = path2.relative(root, file);
4701
+ if (!relative || relative.startsWith("..") || path2.isAbsolute(relative))
4702
+ return { unknown: "the hooks file is outside the plugin" };
4703
+ let hooksJson;
4704
+ try {
4705
+ hooksJson = fs2.readFileSync(file, "utf8");
4706
+ } catch {
4707
+ return { unknown: "the hooks file could not be read" };
4708
+ }
4709
+ const config = path2.join(codexHome, "config.toml");
4710
+ let toml;
4711
+ try {
4712
+ toml = fs2.readFileSync(config, "utf8");
4713
+ } catch (e) {
4714
+ if (e.code !== "ENOENT")
4715
+ return { unknown: "config.toml could not be read" };
4716
+ toml = null;
4717
+ }
4718
+ const states = readHookStates(toml);
4719
+ if (!(states instanceof Map))
4720
+ return states;
4721
+ const trust = hookTrust(hooksJson, pluginId, relative.split(path2.sep).join("/"), platform, states);
4722
+ return "unknown" in trust ? trust : { config, hooks: trust.hooks };
3531
4723
  }
3532
4724
  function safeDirs(dir) {
3533
4725
  try {
@@ -3651,6 +4843,29 @@ ${indent}${caution(note)}` : ""}`);
3651
4843
  todo.add("codex");
3652
4844
  row("Codex", x2, note);
3653
4845
  }
4846
+ if (unknown(s.codexHooks))
4847
+ say("none", "Codex hooks", `unknown (${s.codexHooks.unknown}). Check /hooks in Codex`);
4848
+ else if (s.codexHooks) {
4849
+ const { hooks, config } = s.codexHooks;
4850
+ const count = (t2) => hooks.filter((h2) => h2.trust === t2).length;
4851
+ const trusted = count("trusted");
4852
+ const disabled = hooks.filter((h2) => !h2.enabled).length;
4853
+ const summary = `${trusted} of ${hooks.length} trusted in ${short(config)}`;
4854
+ if (trusted === hooks.length && !disabled)
4855
+ say("ok", "Codex hooks", summary);
4856
+ else {
4857
+ const parts = [
4858
+ [count("modified"), "modified"],
4859
+ [count("untrusted"), "untrusted"],
4860
+ [disabled, "disabled"]
4861
+ ].filter(([n2]) => n2);
4862
+ const steps = [
4863
+ trusted < hooks.length ? "open /hooks in Codex and trust Sphica's hooks" : "",
4864
+ disabled ? "enable the disabled ones in /hooks if that was not intended" : ""
4865
+ ].filter(Boolean);
4866
+ say("warn", "Codex hooks", `${summary}; ${parts.map(([n2, w]) => `${n2} ${w}`).join(", ")}. ${steps.join("; ")}`);
4867
+ }
4868
+ }
3654
4869
  const x = s.codex.length === 1 ? s.codex[0] : undefined;
3655
4870
  if (!base && s.claude && !unknown(s.claude) && x && s.claude.version === x.version) {
3656
4871
  if (fs2.existsSync(s.claude.root) && differingFiles(s.claude.root, x.root).length) {
@@ -3799,7 +5014,7 @@ function backups(file = dbFile()) {
3799
5014
 
3800
5015
  // server/src/capture.ts
3801
5016
  import { spawn } from "node:child_process";
3802
- import fs7 from "node:fs";
5017
+ import fs8 from "node:fs";
3803
5018
  import path8 from "node:path";
3804
5019
 
3805
5020
  // server/node_modules/kysely/dist/util/object-utils.js
@@ -14511,36 +15726,36 @@ class ParseJSONResultsPlugin {
14511
15726
  async transformResult(args) {
14512
15727
  return {
14513
15728
  ...args.result,
14514
- rows: parseArray(args.result.rows, "$", this.#options)
15729
+ rows: parseArray2(args.result.rows, "$", this.#options)
14515
15730
  };
14516
15731
  }
14517
15732
  }
14518
- function parseArray(arr, jsonPath, options) {
15733
+ function parseArray2(arr, jsonPath, options) {
14519
15734
  const target = options.objectStrategy === "create" ? new Array(arr.length) : arr;
14520
15735
  for (let i2 = 0;i2 < arr.length; ++i2) {
14521
- target[i2] = parse(arr[i2], `${jsonPath}[${i2}]`, options);
15736
+ target[i2] = parse2(arr[i2], `${jsonPath}[${i2}]`, options);
14522
15737
  }
14523
15738
  return target;
14524
15739
  }
14525
- function parse(value, jsonPath, options) {
15740
+ function parse2(value, jsonPath, options) {
14526
15741
  if (isString(value)) {
14527
- return parseString(value, jsonPath, options);
15742
+ return parseString2(value, jsonPath, options);
14528
15743
  }
14529
15744
  if (Array.isArray(value)) {
14530
- return parseArray(value, jsonPath, options);
15745
+ return parseArray2(value, jsonPath, options);
14531
15746
  }
14532
15747
  if (isPlainObject(value)) {
14533
15748
  return parseObject(value, jsonPath, options);
14534
15749
  }
14535
15750
  return value;
14536
15751
  }
14537
- function parseString(str, jsonPath, options) {
15752
+ function parseString2(str, jsonPath, options) {
14538
15753
  const { shouldParse } = options;
14539
15754
  if (!shouldParse(str, jsonPath)) {
14540
15755
  return str;
14541
15756
  }
14542
15757
  try {
14543
- return parse(JSON.parse(str, (key, value, ...otherArgs) => {
15758
+ return parse2(JSON.parse(str, (key, value, ...otherArgs) => {
14544
15759
  if (key === "__proto__") {
14545
15760
  return;
14546
15761
  }
@@ -14567,7 +15782,7 @@ function parseObject(obj, jsonPath, options) {
14567
15782
  if (key === "__proto__") {
14568
15783
  continue;
14569
15784
  }
14570
- const parsed = parse(obj[key], `${jsonPath}."${key}"`, options);
15785
+ const parsed = parse2(obj[key], `${jsonPath}."${key}"`, options);
14571
15786
  if (key === "constructor" && isPlainObject(parsed) && Object.hasOwn(parsed, "prototype")) {
14572
15787
  delete parsed.prototype;
14573
15788
  }
@@ -14904,7 +16119,7 @@ var sessionId = (projectId, host, externalId) => uuidFrom(String(projectId), hos
14904
16119
 
14905
16120
  // server/src/project.ts
14906
16121
  import { execFileSync as execFileSync2 } from "node:child_process";
14907
- import fs5 from "node:fs";
16122
+ import fs6 from "node:fs";
14908
16123
  import os3 from "node:os";
14909
16124
  import path6 from "node:path";
14910
16125
 
@@ -14913,6 +16128,127 @@ import path5 from "node:path";
14913
16128
  var MAX_BYTES = 2 * 1024 * 1024;
14914
16129
  var leaves = (rel) => rel === ".." || rel.startsWith(`..${path5.sep}`) || path5.isAbsolute(rel);
14915
16130
 
16131
+ // server/src/file-lock.ts
16132
+ import fs5 from "node:fs";
16133
+ var WAIT_MS = 5000;
16134
+ var POLL_MS = 40;
16135
+ var TRIES = 5;
16136
+ var sleep = (ms) => {
16137
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
16138
+ };
16139
+ function retryBusy(op) {
16140
+ for (let attempt = 1;; attempt++) {
16141
+ try {
16142
+ op();
16143
+ return;
16144
+ } catch (e) {
16145
+ const code = e.code;
16146
+ if (attempt >= TRIES || !(code === "EPERM" || code === "EACCES" || code === "EBUSY"))
16147
+ throw e;
16148
+ sleep(50 * attempt);
16149
+ }
16150
+ }
16151
+ }
16152
+ function heldBy(lock) {
16153
+ let holder = 0;
16154
+ try {
16155
+ holder = Number(fs5.readFileSync(lock, "utf8").trim()) || 0;
16156
+ } catch {}
16157
+ if (holder <= 0)
16158
+ return `${lock} is held by another process. Run this again when it finishes.`;
16159
+ try {
16160
+ process.kill(holder, 0);
16161
+ } catch (e) {
16162
+ if (e.code === "ESRCH")
16163
+ return `${lock} is held by process ${holder}, which is not running. If no sphica init is running, delete ${lock} and run this again.`;
16164
+ }
16165
+ return `${lock} is held by process ${holder}. Run this again when it finishes.`;
16166
+ }
16167
+ function withFileLock(lock, fn, waitMs = WAIT_MS) {
16168
+ const mine = String(process.pid);
16169
+ const until = performance.now() + waitMs;
16170
+ for (;; ) {
16171
+ let fd = null;
16172
+ let refused = null;
16173
+ try {
16174
+ fd = fs5.openSync(lock, "wx");
16175
+ } catch (e) {
16176
+ const code = e.code;
16177
+ if (code === "EPERM" || code === "EACCES" || code === "EBUSY")
16178
+ refused = e;
16179
+ else if (code !== "EEXIST")
16180
+ throw e;
16181
+ }
16182
+ if (fd !== null) {
16183
+ try {
16184
+ try {
16185
+ fs5.writeFileSync(fd, mine);
16186
+ } finally {
16187
+ fs5.closeSync(fd);
16188
+ }
16189
+ } catch (e) {
16190
+ try {
16191
+ retryBusy(() => fs5.rmSync(lock, { force: true }));
16192
+ } catch {}
16193
+ throw e;
16194
+ }
16195
+ break;
16196
+ }
16197
+ if (performance.now() >= until)
16198
+ throw refused ?? new Error(heldBy(lock));
16199
+ sleep(POLL_MS);
16200
+ }
16201
+ let result;
16202
+ try {
16203
+ result = fn();
16204
+ } catch (e) {
16205
+ try {
16206
+ release(lock, mine);
16207
+ } catch {}
16208
+ throw e;
16209
+ }
16210
+ release(lock, mine);
16211
+ return result;
16212
+ }
16213
+ function release(lock, mine) {
16214
+ const cannot = (e) => new Error(`The work finished, but Sphica could not remove ${lock} (${e.message}). Delete it before running this again.`);
16215
+ let held = "";
16216
+ try {
16217
+ retryBusy(() => {
16218
+ held = fs5.readFileSync(lock, "utf8");
16219
+ });
16220
+ } catch (e) {
16221
+ if (e.code === "ENOENT")
16222
+ return;
16223
+ throw cannot(e);
16224
+ }
16225
+ if (held !== mine)
16226
+ return;
16227
+ try {
16228
+ retryBusy(() => fs5.rmSync(lock, { force: true }));
16229
+ } catch (e) {
16230
+ throw cannot(e);
16231
+ }
16232
+ }
16233
+ function replaceFile(file, text) {
16234
+ const tmp = `${file}.${process.pid}.tmp`;
16235
+ try {
16236
+ const fd = fs5.openSync(tmp, "w");
16237
+ try {
16238
+ fs5.writeFileSync(fd, text);
16239
+ fs5.fsyncSync(fd);
16240
+ } finally {
16241
+ fs5.closeSync(fd);
16242
+ }
16243
+ retryBusy(() => fs5.renameSync(tmp, file));
16244
+ } catch (e) {
16245
+ try {
16246
+ retryBusy(() => fs5.rmSync(tmp, { force: true }));
16247
+ } catch {}
16248
+ throw e;
16249
+ }
16250
+ }
16251
+
14916
16252
  // server/src/project.ts
14917
16253
  var localFile = () => path6.join(sphicaHome(), "projects.json");
14918
16254
  var LOCAL_KEY = /^[a-z0-9][a-z0-9._-]*$/;
@@ -14956,7 +16292,7 @@ var git = (dir, ...args) => {
14956
16292
  function localMap() {
14957
16293
  let raw;
14958
16294
  try {
14959
- raw = fs5.readFileSync(localFile(), "utf8");
16295
+ raw = fs6.readFileSync(localFile(), "utf8");
14960
16296
  } catch (e) {
14961
16297
  if (e.code === "ENOENT")
14962
16298
  return {};
@@ -15003,14 +16339,27 @@ function nameLocal(dir, name) {
15003
16339
  if (place?.key.startsWith("git:"))
15004
16340
  throw new Error(`${place.root} has a git remote, so its key is ${place.key}. Add it without --name.`);
15005
16341
  const root = rootOf(dir);
15006
- const m = localMap();
15007
- m[root] = name;
15008
- fs5.mkdirSync(path6.dirname(localFile()), { recursive: true, mode: 448 });
15009
- fs5.writeFileSync(localFile(), `${JSON.stringify(m, null, 2)}
16342
+ fs6.mkdirSync(path6.dirname(localFile()), { recursive: true, mode: 448 });
16343
+ withFileLock(`${localFile()}.lock`, () => {
16344
+ const m = localMap();
16345
+ m[root] = name;
16346
+ replaceFile(localFile(), `${JSON.stringify(m, null, 2)}
15010
16347
  `);
16348
+ });
15011
16349
  return { key: `local:${name}`, root, name };
15012
16350
  }
15013
- function localRoots(roots = [path6.join(os3.homedir(), "Projects")]) {
16351
+ function underHome(p, homes, api = path6) {
16352
+ for (const h2 of homes) {
16353
+ const rel = api.relative(h2, p);
16354
+ if (rel === "")
16355
+ return "~";
16356
+ if (rel !== ".." && !rel.startsWith(`..${api.sep}`) && !api.isAbsolute(rel))
16357
+ return `~${api.sep}${rel}`;
16358
+ }
16359
+ return p;
16360
+ }
16361
+ var projectsDir = () => path6.join(os3.homedir(), "Projects");
16362
+ function localRoots(roots = [projectsDir()]) {
15014
16363
  const seen = new Map;
15015
16364
  const add = (p) => {
15016
16365
  if (p)
@@ -15019,7 +16368,7 @@ function localRoots(roots = [path6.join(os3.homedir(), "Projects")]) {
15019
16368
  for (const r2 of roots) {
15020
16369
  let entries = [];
15021
16370
  try {
15022
- entries = fs5.readdirSync(r2, { withFileTypes: true });
16371
+ entries = fs6.readdirSync(r2, { withFileTypes: true });
15023
16372
  } catch {
15024
16373
  continue;
15025
16374
  }
@@ -15028,7 +16377,7 @@ function localRoots(roots = [path6.join(os3.homedir(), "Projects")]) {
15028
16377
  add(identify(path6.join(r2, e.name)));
15029
16378
  }
15030
16379
  for (const [root, name] of Object.entries(localMap())) {
15031
- if (LOCAL_KEY.test(name) && fs5.existsSync(root))
16380
+ if (LOCAL_KEY.test(name) && fs6.existsSync(root))
15032
16381
  add({ key: `local:${name}`, root, name });
15033
16382
  }
15034
16383
  const found = new Map;
@@ -15061,7 +16410,7 @@ function patchPaths(patch) {
15061
16410
 
15062
16411
  // server/src/worktree.ts
15063
16412
  import { execFileSync as execFileSync3 } from "node:child_process";
15064
- import fs6 from "node:fs";
16413
+ import fs7 from "node:fs";
15065
16414
  import path7 from "node:path";
15066
16415
  var MAX_PATHS = 200;
15067
16416
  var git2 = (root, args) => {
@@ -15096,7 +16445,7 @@ function snapshot(root) {
15096
16445
  continue;
15097
16446
  let st;
15098
16447
  try {
15099
- st = fs6.lstatSync(path7.join(root, p), { throwIfNoEntry: false });
16448
+ st = fs7.lstatSync(path7.join(root, p), { throwIfNoEntry: false });
15100
16449
  } catch {
15101
16450
  st = undefined;
15102
16451
  }
@@ -15174,7 +16523,7 @@ ${z}`,
15174
16523
  function prune(held) {
15175
16524
  let files;
15176
16525
  try {
15177
- files = fs7.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
16526
+ files = fs8.readdirSync(held).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort();
15178
16527
  } catch {
15179
16528
  return;
15180
16529
  }
@@ -15182,21 +16531,21 @@ function prune(held) {
15182
16531
  const stale = files.filter((f) => Number(f.split("-")[0]) < cutoff);
15183
16532
  const over = files.slice(0, Math.max(0, files.length - HOLD_MAX));
15184
16533
  for (const f of new Set([...stale, ...over]))
15185
- fs7.rmSync(path8.join(held, f), { force: true });
16534
+ fs8.rmSync(path8.join(held, f), { force: true });
15186
16535
  }
15187
16536
  function spool(record) {
15188
16537
  const dir = spoolDir();
15189
- fs7.mkdirSync(dir, { recursive: true, mode: 448 });
16538
+ fs8.mkdirSync(dir, { recursive: true, mode: 448 });
15190
16539
  const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}.json`;
15191
16540
  const tmp = path8.join(dir, `.${name}`);
15192
- fs7.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
15193
- fs7.renameSync(tmp, path8.join(dir, name));
16541
+ fs8.writeFileSync(tmp, JSON.stringify(record), { mode: 384 });
16542
+ fs8.renameSync(tmp, path8.join(dir, name));
15194
16543
  }
15195
16544
  var branchOf = (root) => {
15196
16545
  try {
15197
16546
  const dotgit = path8.join(root, ".git");
15198
- const gitdir = fs7.statSync(dotgit).isFile() ? path8.resolve(root, fs7.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
15199
- const h2 = fs7.readFileSync(path8.join(gitdir, "HEAD"), "utf8").trim();
16547
+ const gitdir = fs8.statSync(dotgit).isFile() ? path8.resolve(root, fs8.readFileSync(dotgit, "utf8").match(/^gitdir: (.+)$/m)?.[1]?.trim() ?? "") : dotgit;
16548
+ const h2 = fs8.readFileSync(path8.join(gitdir, "HEAD"), "utf8").trim();
15200
16549
  return h2.startsWith("ref: refs/heads/") ? h2.slice("ref: refs/heads/".length) : null;
15201
16550
  } catch {
15202
16551
  return null;
@@ -15218,7 +16567,7 @@ var INJECTED = [
15218
16567
  /^(?:\d+ background agents were stopped by the user:|Background agent ".*" was stopped by the user\.)/,
15219
16568
  /^(?:Another Claude|A peer) session sent a message(?: while you were working)?:/
15220
16569
  ];
15221
- var digest = (s) => sha256(s).toString("hex").slice(0, 16);
16570
+ var digest = (s) => sha2562(s).toString("hex").slice(0, 16);
15222
16571
  function answersOf(input) {
15223
16572
  const response = input.tool_response;
15224
16573
  const answers = response?.answers;
@@ -15242,7 +16591,7 @@ Notes: ${notes.trim()}` : "";
15242
16591
  };
15243
16592
  }
15244
16593
  function captureNotice(file = dbFile()) {
15245
- if (!fs7.existsSync(file))
16594
+ if (!fs8.existsSync(file))
15246
16595
  return panel("sphica: no database, so conversations are not recorded", [file], "Create it with sphica init");
15247
16596
  const s = readState();
15248
16597
  if (s.stuck)
@@ -15259,7 +16608,7 @@ var isStart = (v) => {
15259
16608
  };
15260
16609
  function readStart(file) {
15261
16610
  try {
15262
- const s = JSON.parse(fs7.readFileSync(file, "utf8"));
16611
+ const s = JSON.parse(fs8.readFileSync(file, "utf8"));
15263
16612
  return isStart(s) ? s : null;
15264
16613
  } catch {
15265
16614
  return null;
@@ -15268,7 +16617,7 @@ function readStart(file) {
15268
16617
  function readStarts(dir) {
15269
16618
  let names;
15270
16619
  try {
15271
- names = fs7.readdirSync(dir);
16620
+ names = fs8.readdirSync(dir);
15272
16621
  } catch (e) {
15273
16622
  return e.code === "ENOENT" ? [] : null;
15274
16623
  }
@@ -15291,10 +16640,10 @@ function newestTurn(starts) {
15291
16640
  return at.length === 1 ? at[0]?.turn ?? null : null;
15292
16641
  }
15293
16642
  function writeStart(dir, s, file = startFile(dir, s.turn)) {
15294
- fs7.mkdirSync(dir, { recursive: true, mode: 448 });
16643
+ fs8.mkdirSync(dir, { recursive: true, mode: 448 });
15295
16644
  const tmp = path8.join(dir, `.${path8.basename(file)}.${process.pid}.tmp`);
15296
- fs7.writeFileSync(tmp, JSON.stringify(s), { mode: 384 });
15297
- fs7.renameSync(tmp, file);
16645
+ fs8.writeFileSync(tmp, JSON.stringify(s), { mode: 384 });
16646
+ fs8.renameSync(tmp, file);
15298
16647
  }
15299
16648
  function openTurn(dir, turn, root) {
15300
16649
  if (readStart(startFile(dir, turn))?.running)
@@ -15324,22 +16673,22 @@ function stopTurn(dir, turn) {
15324
16673
  }
15325
16674
  function pruneBaselines() {
15326
16675
  const cutoff = Date.now() - HOLD_DAYS * 24 * 60 * 60 * 1000;
15327
- const fresh = (file) => (fs7.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) >= cutoff;
16676
+ const fresh = (file) => (fs8.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? Date.now()) >= cutoff;
15328
16677
  let names;
15329
16678
  try {
15330
- names = fs7.readdirSync(baselineDir());
16679
+ names = fs8.readdirSync(baselineDir());
15331
16680
  } catch {
15332
16681
  return;
15333
16682
  }
15334
16683
  for (const name of names) {
15335
16684
  const at = path8.join(baselineDir(), name);
15336
- if (!fs7.statSync(at, { throwIfNoEntry: false })?.isDirectory()) {
16685
+ if (!fs8.statSync(at, { throwIfNoEntry: false })?.isDirectory()) {
15337
16686
  if (!fresh(at))
15338
- fs7.rmSync(at, { force: true });
16687
+ fs8.rmSync(at, { force: true });
15339
16688
  } else {
15340
- const files = fs7.readdirSync(at);
16689
+ const files = fs8.readdirSync(at);
15341
16690
  if (files.length && !files.some((f) => fresh(path8.join(at, f))))
15342
- fs7.rmSync(at, { recursive: true, force: true });
16691
+ fs8.rmSync(at, { recursive: true, force: true });
15343
16692
  }
15344
16693
  }
15345
16694
  }
@@ -15358,7 +16707,7 @@ function onHook(host, input) {
15358
16707
  return { flush: false };
15359
16708
  const file = process.env.CLAUDE_ENV_FILE;
15360
16709
  if (file && input.session_id && /^[A-Za-z0-9_-]+$/.test(input.session_id)) {
15361
- fs7.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
16710
+ fs8.appendFileSync(file, `export SPHICA_PARENT_SESSION=${input.session_id}
15362
16711
  `);
15363
16712
  }
15364
16713
  attempt(pruneBaselines);
@@ -15440,13 +16789,13 @@ function onHook(host, input) {
15440
16789
  }
15441
16790
  function writeState(s) {
15442
16791
  try {
15443
- fs7.writeFileSync(stateFile(), JSON.stringify(s));
16792
+ fs8.writeFileSync(stateFile(), JSON.stringify(s));
15444
16793
  } catch {}
15445
16794
  }
15446
16795
  function readState() {
15447
16796
  const count = (dir) => {
15448
16797
  try {
15449
- return fs7.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
16798
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).length;
15450
16799
  } catch {
15451
16800
  return 0;
15452
16801
  }
@@ -15458,7 +16807,7 @@ function readState() {
15458
16807
  };
15459
16808
  let raw = {};
15460
16809
  try {
15461
- const parsed = JSON.parse(fs7.readFileSync(stateFile(), "utf8"));
16810
+ const parsed = JSON.parse(fs8.readFileSync(stateFile(), "utf8"));
15462
16811
  if (parsed && typeof parsed === "object")
15463
16812
  raw = parsed;
15464
16813
  } catch {}
@@ -15473,24 +16822,24 @@ function readState() {
15473
16822
  }
15474
16823
  function lock() {
15475
16824
  const file = path8.join(spoolDir(), ".lock");
15476
- fs7.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
16825
+ fs8.mkdirSync(spoolDir(), { recursive: true, mode: 448 });
15477
16826
  for (let attempt2 = 0;attempt2 < 2; attempt2++) {
15478
16827
  try {
15479
- fs7.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
16828
+ fs8.writeFileSync(file, String(process.pid), { flag: "wx", mode: 384 });
15480
16829
  return () => {
15481
16830
  try {
15482
- if (fs7.readFileSync(file, "utf8") === String(process.pid))
15483
- fs7.rmSync(file, { force: true });
16831
+ if (fs8.readFileSync(file, "utf8") === String(process.pid))
16832
+ fs8.rmSync(file, { force: true });
15484
16833
  } catch {}
15485
16834
  };
15486
16835
  } catch (e) {
15487
16836
  if (e.code !== "EEXIST")
15488
16837
  throw e;
15489
16838
  }
15490
- const st = fs7.statSync(file, { throwIfNoEntry: false });
16839
+ const st = fs8.statSync(file, { throwIfNoEntry: false });
15491
16840
  if (!st)
15492
16841
  continue;
15493
- const holder = Number(fs7.readFileSync(file, "utf8") || 0);
16842
+ const holder = Number(fs8.readFileSync(file, "utf8") || 0);
15494
16843
  const fresh = Date.now() - st.mtimeMs < 5 * 60000;
15495
16844
  const alive = (() => {
15496
16845
  if (holder <= 0)
@@ -15503,7 +16852,7 @@ function lock() {
15503
16852
  })();
15504
16853
  if (alive && fresh)
15505
16854
  return null;
15506
- fs7.rmSync(file, { force: true });
16855
+ fs8.rmSync(file, { force: true });
15507
16856
  }
15508
16857
  return null;
15509
16858
  }
@@ -15575,7 +16924,7 @@ async function write(db, batch) {
15575
16924
  truncated: x.m.truncated ? 1 : 0,
15576
16925
  redacted: x.m.redacted ? 1 : 0,
15577
16926
  original_bytes: x.m.originalBytes,
15578
- content_hash: sha256(x.m.body)
16927
+ content_hash: sha2562(x.m.body)
15579
16928
  }))).execute();
15580
16929
  const after = await present();
15581
16930
  const edits = batch.flatMap((r2) => {
@@ -15602,7 +16951,7 @@ var REJECTED = new Set([18, 19, 20, 25]);
15602
16951
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
15603
16952
  var queued = (from) => {
15604
16953
  try {
15605
- return fs7.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
16954
+ return fs8.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
15606
16955
  } catch {
15607
16956
  return [];
15608
16957
  }
@@ -15613,16 +16962,16 @@ async function sendBatch(db, names) {
15613
16962
  for (const { name, from } of names) {
15614
16963
  let r2;
15615
16964
  try {
15616
- r2 = current(JSON.parse(fs7.readFileSync(path8.join(from, name), "utf8")));
16965
+ r2 = current(JSON.parse(fs8.readFileSync(path8.join(from, name), "utf8")));
15617
16966
  } catch {
15618
- fs7.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
15619
- fs7.renameSync(path8.join(from, name), path8.join(rejectedDir(), name));
16967
+ fs8.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
16968
+ fs8.renameSync(path8.join(from, name), path8.join(rejectedDir(), name));
15620
16969
  continue;
15621
16970
  }
15622
16971
  if (r2)
15623
16972
  records.push({ name, from, r: r2 });
15624
16973
  else
15625
- fs7.rmSync(path8.join(from, name), { force: true });
16974
+ fs8.rmSync(path8.join(from, name), { force: true });
15626
16975
  }
15627
16976
  let sent = 0;
15628
16977
  let strayed = [];
@@ -15648,10 +16997,10 @@ async function sendBatch(db, names) {
15648
16997
  }
15649
16998
  }
15650
16999
  if (bad.length) {
15651
- fs7.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
17000
+ fs8.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
15652
17001
  for (const x of bad) {
15653
17002
  try {
15654
- fs7.renameSync(path8.join(x.from, x.name), path8.join(rejectedDir(), x.name));
17003
+ fs8.renameSync(path8.join(x.from, x.name), path8.join(rejectedDir(), x.name));
15655
17004
  } catch (e) {
15656
17005
  if (e.code !== "ENOENT")
15657
17006
  throw e;
@@ -15659,12 +17008,12 @@ async function sendBatch(db, names) {
15659
17008
  }
15660
17009
  }
15661
17010
  if (strayed.length) {
15662
- fs7.mkdirSync(held, { recursive: true, mode: 448 });
17011
+ fs8.mkdirSync(held, { recursive: true, mode: 448 });
15663
17012
  for (const x of strayed) {
15664
17013
  if (x.from === held)
15665
17014
  continue;
15666
17015
  try {
15667
- fs7.renameSync(path8.join(x.from, x.name), path8.join(held, x.name));
17016
+ fs8.renameSync(path8.join(x.from, x.name), path8.join(held, x.name));
15668
17017
  } catch (e) {
15669
17018
  if (e.code !== "ENOENT")
15670
17019
  throw e;
@@ -15674,7 +17023,7 @@ async function sendBatch(db, names) {
15674
17023
  const moved = new Set([...bad, ...strayed].map((x) => x.name));
15675
17024
  for (const x of records)
15676
17025
  if (!moved.has(x.name))
15677
- fs7.rmSync(path8.join(x.from, x.name), { force: true });
17026
+ fs8.rmSync(path8.join(x.from, x.name), { force: true });
15678
17027
  return { sent, rejected: bad.length };
15679
17028
  }
15680
17029
  async function flush(file = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
@@ -16134,7 +17483,7 @@ var STOP2 = "sphica_migration_stop";
16134
17483
  var NOTE = "sphica_migration_note";
16135
17484
  var KEEP2 = 3;
16136
17485
  function backUp(raw, file, from) {
16137
- fs8.mkdirSync(backupDir(file), { recursive: true, mode: 448 });
17486
+ fs9.mkdirSync(backupDir(file), { recursive: true, mode: 448 });
16138
17487
  const done = backupPath(file, from);
16139
17488
  const partial = `${done}.partial`;
16140
17489
  try {
@@ -16150,10 +17499,10 @@ function backUp(raw, file, from) {
16150
17499
  } finally {
16151
17500
  look.close();
16152
17501
  }
16153
- fs8.renameSync(partial, done);
17502
+ fs9.renameSync(partial, done);
16154
17503
  } catch (e) {
16155
17504
  for (const f of [partial, `${partial}-wal`, `${partial}-shm`])
16156
- fs8.rmSync(f, { force: true });
17505
+ fs9.rmSync(f, { force: true });
16157
17506
  throw new Error(`Could not back up ${file} before migrating it, so nothing was migrated (${e.message}). Check free space in ${backupDir(file)}.`);
16158
17507
  }
16159
17508
  return done;
@@ -16167,7 +17516,7 @@ function prune2(file, kept) {
16167
17516
  }
16168
17517
  for (const old of all.filter((b) => b !== kept).slice(KEEP2 - 1))
16169
17518
  try {
16170
- fs8.rmSync(old, { force: true });
17519
+ fs9.rmSync(old, { force: true });
16171
17520
  } catch {}
16172
17521
  }
16173
17522
  var versionOf = (raw) => raw.prepare("pragma user_version").get().user_version;
@@ -16259,20 +17608,20 @@ function migrate(file = dbFile(), dir = MIGRATIONS()) {
16259
17608
  try {
16260
17609
  for (let r2 = from + 1;r2 <= SCHEMA_REVISION; r2++) {
16261
17610
  const script = MIGRATION(dir, r2);
16262
- if (!fs8.existsSync(script))
17611
+ if (!fs9.existsSync(script))
16263
17612
  throw new Error(`This Sphica has no migration script for revision ${r2} (${script} is missing). Reinstall sphica (\`npm i -g sphica@${packageVersionAt(ROOT) ?? "latest"}\`), then run \`sphica init\` again.`);
16264
17613
  raw.exec("pragma foreign_keys = off");
16265
17614
  try {
16266
17615
  const notes = immediate(raw, () => {
16267
17616
  if (versionOf(raw) !== r2 - 1)
16268
17617
  return [];
16269
- if (fs8.existsSync(CHECK(dir, r2))) {
16270
- raw.exec(fs8.readFileSync(CHECK(dir, r2), "utf8"));
17618
+ if (fs9.existsSync(CHECK(dir, r2))) {
17619
+ raw.exec(fs9.readFileSync(CHECK(dir, r2), "utf8"));
16271
17620
  const stops = taken(raw, STOP2);
16272
17621
  if (stops.length)
16273
17622
  throw new Stop(r2, stops);
16274
17623
  }
16275
- raw.exec(fs8.readFileSync(script, "utf8"));
17624
+ raw.exec(fs9.readFileSync(script, "utf8"));
16276
17625
  if (r2 === 10)
16277
17626
  judgeEveryRecord(raw);
16278
17627
  const broken = raw.prepare("pragma foreign_key_check").all().length;
@@ -16297,7 +17646,7 @@ ${e.list}` : "";
16297
17646
  if (now === from) {
16298
17647
  let left = "the backup made for this run was removed";
16299
17648
  try {
16300
- fs8.rmSync(backup, { force: true });
17649
+ fs9.rmSync(backup, { force: true });
16301
17650
  } catch {
16302
17651
  left = `the backup made for this run is still at ${backup}; delete it yourself`;
16303
17652
  }
@@ -16314,7 +17663,7 @@ ${e.list}` : "";
16314
17663
  });
16315
17664
  }
16316
17665
  function dbInit(file = dbFile()) {
16317
- if (fs8.existsSync(file)) {
17666
+ if (fs9.existsSync(file)) {
16318
17667
  const look = new DatabaseSync3(file, { readOnly: true });
16319
17668
  let generation;
16320
17669
  try {
@@ -16335,27 +17684,32 @@ function dbInit(file = dbFile()) {
16335
17684
  }
16336
17685
  return;
16337
17686
  }
16338
- fs8.mkdirSync(path9.dirname(file), { recursive: true, mode: 448 });
17687
+ fs9.mkdirSync(path9.dirname(file), { recursive: true, mode: 448 });
16339
17688
  const tmp = `${file}.${process.pid}.tmp`;
16340
- fs8.rmSync(tmp, { force: true });
17689
+ fs9.rmSync(tmp, { force: true });
16341
17690
  try {
16342
17691
  withOwner(tmp, (raw) => {
16343
17692
  raw.exec("pragma journal_mode = wal");
16344
- raw.exec(fs8.readFileSync(SCHEMA(), "utf8"));
17693
+ raw.exec(fs9.readFileSync(SCHEMA(), "utf8"));
16345
17694
  if (versionOf(raw) !== SCHEMA_REVISION)
16346
17695
  throw new Error(`db/schema.sql has user_version ${versionOf(raw)}, but the code expects ${SCHEMA_REVISION}`);
16347
17696
  optimize(raw);
16348
17697
  }, true);
16349
- try {
16350
- fs8.linkSync(tmp, file);
16351
- } catch (e) {
16352
- if (e.code === "EEXIST" || fs8.existsSync(file))
16353
- throw new Error(`${file} already exists (another sphica init created it first). Run this again to check it.`);
16354
- fs8.renameSync(tmp, file);
16355
- }
17698
+ const taken2 = () => new Error(`${file} already exists (another sphica init created it first). Run this again to check it.`);
17699
+ withFileLock(`${file}.init.lock`, () => {
17700
+ if (fs9.existsSync(file))
17701
+ throw taken2();
17702
+ try {
17703
+ fs9.linkSync(tmp, file);
17704
+ } catch (e) {
17705
+ if (e.code === "EEXIST")
17706
+ throw taken2();
17707
+ fs9.renameSync(tmp, file);
17708
+ }
17709
+ });
16356
17710
  } finally {
16357
17711
  for (const f of [tmp, `${tmp}-wal`, `${tmp}-shm`])
16358
- fs8.rmSync(f, { force: true });
17712
+ fs9.rmSync(f, { force: true });
16359
17713
  }
16360
17714
  say(`Created: ${file} (revision ${SCHEMA_REVISION})`);
16361
17715
  }
@@ -16391,7 +17745,7 @@ function reindex(file = dbFile()) {
16391
17745
  say(`Rebuilt the index: ${plural(counts.units, "unit")}, ${plural(counts.sources, "source")}`);
16392
17746
  }
16393
17747
  function inspect(file = dbFile()) {
16394
- const size = (f) => fs8.existsSync(f) ? fs8.statSync(f).size : 0;
17748
+ const size = (f) => fs9.existsSync(f) ? fs9.statSync(f).size : 0;
16395
17749
  return withOwner(file, (raw) => {
16396
17750
  const check = (table) => {
16397
17751
  try {
@@ -17115,7 +18469,7 @@ async function doctor(cwd) {
17115
18469
  let usable2 = false;
17116
18470
  if (!runtime)
17117
18471
  say2("none", "DB", "cannot check until Node is upgraded");
17118
- else if (!fs9.existsSync(file))
18472
+ else if (!fs10.existsSync(file))
17119
18473
  say2("fail", "DB", `missing (${file}). Create it with sphica init`);
17120
18474
  else {
17121
18475
  try {
@@ -17139,7 +18493,12 @@ async function doctor(cwd) {
17139
18493
  const twice = await db.selectFrom("unit").select("project_id").where("lifecycle", "in", ["active", "candidate"]).groupBy(["project_id", "content_hash"]).having((eb) => eb.fn.countAll(), ">", 1).execute();
17140
18494
  if (twice.length)
17141
18495
  say2("warn", "Records", `${plural(twice.length, "set")} of live records hold the same words. Read them with Sphica's read and withdraw the extra ones with /sphica:glean`);
17142
- const { found } = localRoots();
18496
+ const { found, ambiguous } = localRoots();
18497
+ const homes = [os4.homedir()];
18498
+ try {
18499
+ homes.push(fs10.realpathSync.native(os4.homedir()));
18500
+ } catch {}
18501
+ const tilde = (p) => underHome(p, homes);
17143
18502
  const rows = await db.selectFrom("project as p").select((eb) => [
17144
18503
  "p.key",
17145
18504
  "p.name",
@@ -17150,7 +18509,8 @@ async function doctor(cwd) {
17150
18509
  console.log(section("Projects", true));
17151
18510
  const column = Math.max(...rows.map((x) => width(inline(x.name)))) + 2;
17152
18511
  for (const x of rows) {
17153
- const where = found.get(x.key) ? "" : " (not on this machine)";
18512
+ const copies = ambiguous.get(x.key);
18513
+ const where = copies ? ` (${copies.length} copies: ${copies.map((c) => inline(tilde(c))).sort().join(", ")})` : found.get(x.key) ? "" : ` (not found in ${inline(tilde(projectsDir()))} or the named projects)`;
17154
18514
  console.log(indent(` ${mark("none")} ${pad(inline(x.name), column)}${plural(Number(x.records ?? 0), "record")}${x.extracted ? ` / last extraction ${new Date(x.extracted).toLocaleString("sv-SE")}` : ""}${where}`));
17155
18515
  }
17156
18516
  });
@@ -17195,7 +18555,7 @@ async function bindGitHub() {
17195
18555
  }
17196
18556
  async function init(flags) {
17197
18557
  const cwd = flags.cwd ?? process.cwd();
17198
- if (!fs9.statSync(cwd, { throwIfNoEntry: false })?.isDirectory())
18558
+ if (!fs10.statSync(cwd, { throwIfNoEntry: false })?.isDirectory())
17199
18559
  throw new Error(`${cwd} is not a directory`);
17200
18560
  const found = identify(cwd);
17201
18561
  if (flags.name !== undefined) {
@@ -17234,7 +18594,7 @@ async function uninstall(yes) {
17234
18594
  await boxed("sphica uninstall", async () => {
17235
18595
  if (process.env.SPHICA_HOME)
17236
18596
  throw new Error("SPHICA_HOME is set; uninstall deletes only ~/.sphica. Unset SPHICA_HOME and run it again");
17237
- if (!fs9.existsSync(home))
18597
+ if (!fs10.existsSync(home))
17238
18598
  console.log(indent(`${mark("none")} ${home} does not exist`));
17239
18599
  else {
17240
18600
  if (!yes) {
@@ -17246,7 +18606,7 @@ async function uninstall(yes) {
17246
18606
  if (isCancel(answer) || !answer)
17247
18607
  return "cancelled";
17248
18608
  }
17249
- fs9.rmSync(home, { recursive: true, force: true });
18609
+ fs10.rmSync(home, { recursive: true, force: true });
17250
18610
  console.log(indent(`${mark("ok")} deleted ${home}`));
17251
18611
  }
17252
18612
  if (where.length > 1)