balladeer 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +95 -6
- package/dist/commands/guidance.d.ts +19 -0
- package/dist/commands/guidance.js +136 -0
- package/dist/commands/mcp.js +52 -1
- package/dist/commands/propose.js +7 -2
- package/dist/commands/setup.js +26 -3
- package/dist/conventions.d.ts +1 -1
- package/dist/conventions.js +1 -1
- package/dist/copy.d.ts +7 -5
- package/dist/copy.js +78 -85
- package/dist/guidance-hook.mjs +3114 -0
- package/dist/guidance-install.d.ts +29 -0
- package/dist/guidance-install.js +420 -0
- package/dist/guidance-runtime-entry.d.ts +1 -0
- package/dist/guidance-runtime-entry.js +2 -0
- package/dist/guidance-runtime.d.ts +15 -0
- package/dist/guidance-runtime.js +149 -0
- package/dist/guidance.d.ts +39 -0
- package/dist/guidance.js +293 -0
- package/dist/install.d.ts +2 -0
- package/dist/install.js +7 -1
- package/dist/quiet.d.ts +25 -0
- package/dist/quiet.js +73 -0
- package/dist/user-scope.d.ts +55 -0
- package/dist/user-scope.js +234 -0
- package/dist/wire.d.ts +8 -2
- package/dist/wire.js +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,3114 @@
|
|
|
1
|
+
import { createRequire as guidanceCreateRequire } from "node:module"; const require = guidanceCreateRequire(import.meta.url);
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/parser.js
|
|
35
|
+
var require_parser = __commonJS({
|
|
36
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/parser.js"(exports2, module2) {
|
|
37
|
+
"use strict";
|
|
38
|
+
var ParserEND = 1114112;
|
|
39
|
+
var ParserError = class _ParserError extends Error {
|
|
40
|
+
/* istanbul ignore next */
|
|
41
|
+
constructor(msg, filename, linenumber) {
|
|
42
|
+
super("[ParserError] " + msg, filename, linenumber);
|
|
43
|
+
this.name = "ParserError";
|
|
44
|
+
this.code = "ParserError";
|
|
45
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, _ParserError);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var State = class {
|
|
49
|
+
constructor(parser) {
|
|
50
|
+
this.parser = parser;
|
|
51
|
+
this.buf = "";
|
|
52
|
+
this.returned = null;
|
|
53
|
+
this.result = null;
|
|
54
|
+
this.resultTable = null;
|
|
55
|
+
this.resultArr = null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var Parser = class {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.pos = 0;
|
|
61
|
+
this.col = 0;
|
|
62
|
+
this.line = 0;
|
|
63
|
+
this.obj = {};
|
|
64
|
+
this.ctx = this.obj;
|
|
65
|
+
this.stack = [];
|
|
66
|
+
this._buf = "";
|
|
67
|
+
this.char = null;
|
|
68
|
+
this.ii = 0;
|
|
69
|
+
this.state = new State(this.parseStart);
|
|
70
|
+
}
|
|
71
|
+
parse(str) {
|
|
72
|
+
if (str.length === 0 || str.length == null) return;
|
|
73
|
+
this._buf = String(str);
|
|
74
|
+
this.ii = -1;
|
|
75
|
+
this.char = -1;
|
|
76
|
+
let getNext;
|
|
77
|
+
while (getNext === false || this.nextChar()) {
|
|
78
|
+
getNext = this.runOne();
|
|
79
|
+
}
|
|
80
|
+
this._buf = null;
|
|
81
|
+
}
|
|
82
|
+
nextChar() {
|
|
83
|
+
if (this.char === 10) {
|
|
84
|
+
++this.line;
|
|
85
|
+
this.col = -1;
|
|
86
|
+
}
|
|
87
|
+
++this.ii;
|
|
88
|
+
this.char = this._buf.codePointAt(this.ii);
|
|
89
|
+
++this.pos;
|
|
90
|
+
++this.col;
|
|
91
|
+
return this.haveBuffer();
|
|
92
|
+
}
|
|
93
|
+
haveBuffer() {
|
|
94
|
+
return this.ii < this._buf.length;
|
|
95
|
+
}
|
|
96
|
+
runOne() {
|
|
97
|
+
return this.state.parser.call(this, this.state.returned);
|
|
98
|
+
}
|
|
99
|
+
finish() {
|
|
100
|
+
this.char = ParserEND;
|
|
101
|
+
let last;
|
|
102
|
+
do {
|
|
103
|
+
last = this.state.parser;
|
|
104
|
+
this.runOne();
|
|
105
|
+
} while (this.state.parser !== last);
|
|
106
|
+
this.ctx = null;
|
|
107
|
+
this.state = null;
|
|
108
|
+
this._buf = null;
|
|
109
|
+
return this.obj;
|
|
110
|
+
}
|
|
111
|
+
next(fn) {
|
|
112
|
+
if (typeof fn !== "function") throw new ParserError("Tried to set state to non-existent state: " + JSON.stringify(fn));
|
|
113
|
+
this.state.parser = fn;
|
|
114
|
+
}
|
|
115
|
+
goto(fn) {
|
|
116
|
+
this.next(fn);
|
|
117
|
+
return this.runOne();
|
|
118
|
+
}
|
|
119
|
+
call(fn, returnWith) {
|
|
120
|
+
if (returnWith) this.next(returnWith);
|
|
121
|
+
this.stack.push(this.state);
|
|
122
|
+
this.state = new State(fn);
|
|
123
|
+
}
|
|
124
|
+
callNow(fn, returnWith) {
|
|
125
|
+
this.call(fn, returnWith);
|
|
126
|
+
return this.runOne();
|
|
127
|
+
}
|
|
128
|
+
return(value) {
|
|
129
|
+
if (this.stack.length === 0) throw this.error(new ParserError("Stack underflow"));
|
|
130
|
+
if (value === void 0) value = this.state.buf;
|
|
131
|
+
this.state = this.stack.pop();
|
|
132
|
+
this.state.returned = value;
|
|
133
|
+
}
|
|
134
|
+
returnNow(value) {
|
|
135
|
+
this.return(value);
|
|
136
|
+
return this.runOne();
|
|
137
|
+
}
|
|
138
|
+
consume() {
|
|
139
|
+
if (this.char === ParserEND) throw this.error(new ParserError("Unexpected end-of-buffer"));
|
|
140
|
+
this.state.buf += this._buf[this.ii];
|
|
141
|
+
}
|
|
142
|
+
error(err) {
|
|
143
|
+
err.line = this.line;
|
|
144
|
+
err.col = this.col;
|
|
145
|
+
err.pos = this.pos;
|
|
146
|
+
return err;
|
|
147
|
+
}
|
|
148
|
+
/* istanbul ignore next */
|
|
149
|
+
parseStart() {
|
|
150
|
+
throw new ParserError("Must declare a parseStart method");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
Parser.END = ParserEND;
|
|
154
|
+
Parser.Error = ParserError;
|
|
155
|
+
module2.exports = Parser;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-datetime.js
|
|
160
|
+
var require_create_datetime = __commonJS({
|
|
161
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-datetime.js"(exports2, module2) {
|
|
162
|
+
"use strict";
|
|
163
|
+
module2.exports = (value) => {
|
|
164
|
+
const date = new Date(value);
|
|
165
|
+
if (isNaN(date)) {
|
|
166
|
+
throw new TypeError("Invalid Datetime");
|
|
167
|
+
} else {
|
|
168
|
+
return date;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/format-num.js
|
|
175
|
+
var require_format_num = __commonJS({
|
|
176
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/format-num.js"(exports2, module2) {
|
|
177
|
+
"use strict";
|
|
178
|
+
module2.exports = (d, num) => {
|
|
179
|
+
num = String(num);
|
|
180
|
+
while (num.length < d) num = "0" + num;
|
|
181
|
+
return num;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-datetime-float.js
|
|
187
|
+
var require_create_datetime_float = __commonJS({
|
|
188
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-datetime-float.js"(exports2, module2) {
|
|
189
|
+
"use strict";
|
|
190
|
+
var f = require_format_num();
|
|
191
|
+
var FloatingDateTime = class extends Date {
|
|
192
|
+
constructor(value) {
|
|
193
|
+
super(value + "Z");
|
|
194
|
+
this.isFloating = true;
|
|
195
|
+
}
|
|
196
|
+
toISOString() {
|
|
197
|
+
const date = `${this.getUTCFullYear()}-${f(2, this.getUTCMonth() + 1)}-${f(2, this.getUTCDate())}`;
|
|
198
|
+
const time = `${f(2, this.getUTCHours())}:${f(2, this.getUTCMinutes())}:${f(2, this.getUTCSeconds())}.${f(3, this.getUTCMilliseconds())}`;
|
|
199
|
+
return `${date}T${time}`;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
module2.exports = (value) => {
|
|
203
|
+
const date = new FloatingDateTime(value);
|
|
204
|
+
if (isNaN(date)) {
|
|
205
|
+
throw new TypeError("Invalid Datetime");
|
|
206
|
+
} else {
|
|
207
|
+
return date;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-date.js
|
|
214
|
+
var require_create_date = __commonJS({
|
|
215
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-date.js"(exports2, module2) {
|
|
216
|
+
"use strict";
|
|
217
|
+
var f = require_format_num();
|
|
218
|
+
var DateTime = global.Date;
|
|
219
|
+
var Date2 = class extends DateTime {
|
|
220
|
+
constructor(value) {
|
|
221
|
+
super(value);
|
|
222
|
+
this.isDate = true;
|
|
223
|
+
}
|
|
224
|
+
toISOString() {
|
|
225
|
+
return `${this.getUTCFullYear()}-${f(2, this.getUTCMonth() + 1)}-${f(2, this.getUTCDate())}`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
module2.exports = (value) => {
|
|
229
|
+
const date = new Date2(value);
|
|
230
|
+
if (isNaN(date)) {
|
|
231
|
+
throw new TypeError("Invalid Datetime");
|
|
232
|
+
} else {
|
|
233
|
+
return date;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-time.js
|
|
240
|
+
var require_create_time = __commonJS({
|
|
241
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/create-time.js"(exports2, module2) {
|
|
242
|
+
"use strict";
|
|
243
|
+
var f = require_format_num();
|
|
244
|
+
var Time = class extends Date {
|
|
245
|
+
constructor(value) {
|
|
246
|
+
super(`0000-01-01T${value}Z`);
|
|
247
|
+
this.isTime = true;
|
|
248
|
+
}
|
|
249
|
+
toISOString() {
|
|
250
|
+
return `${f(2, this.getUTCHours())}:${f(2, this.getUTCMinutes())}:${f(2, this.getUTCSeconds())}.${f(3, this.getUTCMilliseconds())}`;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
module2.exports = (value) => {
|
|
254
|
+
const date = new Time(value);
|
|
255
|
+
if (isNaN(date)) {
|
|
256
|
+
throw new TypeError("Invalid Datetime");
|
|
257
|
+
} else {
|
|
258
|
+
return date;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/toml-parser.js
|
|
265
|
+
var require_toml_parser = __commonJS({
|
|
266
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/lib/toml-parser.js"(exports, module) {
|
|
267
|
+
"use strict";
|
|
268
|
+
module.exports = makeParserClass(require_parser());
|
|
269
|
+
module.exports.makeParserClass = makeParserClass;
|
|
270
|
+
var TomlError = class _TomlError extends Error {
|
|
271
|
+
constructor(msg) {
|
|
272
|
+
super(msg);
|
|
273
|
+
this.name = "TomlError";
|
|
274
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, _TomlError);
|
|
275
|
+
this.fromTOML = true;
|
|
276
|
+
this.wrapped = null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
TomlError.wrap = (err) => {
|
|
280
|
+
const terr = new TomlError(err.message);
|
|
281
|
+
terr.code = err.code;
|
|
282
|
+
terr.wrapped = err;
|
|
283
|
+
return terr;
|
|
284
|
+
};
|
|
285
|
+
module.exports.TomlError = TomlError;
|
|
286
|
+
var createDateTime = require_create_datetime();
|
|
287
|
+
var createDateTimeFloat = require_create_datetime_float();
|
|
288
|
+
var createDate = require_create_date();
|
|
289
|
+
var createTime = require_create_time();
|
|
290
|
+
var CTRL_I = 9;
|
|
291
|
+
var CTRL_J = 10;
|
|
292
|
+
var CTRL_M = 13;
|
|
293
|
+
var CTRL_CHAR_BOUNDARY = 31;
|
|
294
|
+
var CHAR_SP = 32;
|
|
295
|
+
var CHAR_QUOT = 34;
|
|
296
|
+
var CHAR_NUM = 35;
|
|
297
|
+
var CHAR_APOS = 39;
|
|
298
|
+
var CHAR_PLUS = 43;
|
|
299
|
+
var CHAR_COMMA = 44;
|
|
300
|
+
var CHAR_HYPHEN = 45;
|
|
301
|
+
var CHAR_PERIOD = 46;
|
|
302
|
+
var CHAR_0 = 48;
|
|
303
|
+
var CHAR_1 = 49;
|
|
304
|
+
var CHAR_7 = 55;
|
|
305
|
+
var CHAR_9 = 57;
|
|
306
|
+
var CHAR_COLON = 58;
|
|
307
|
+
var CHAR_EQUALS = 61;
|
|
308
|
+
var CHAR_A = 65;
|
|
309
|
+
var CHAR_E = 69;
|
|
310
|
+
var CHAR_F = 70;
|
|
311
|
+
var CHAR_T = 84;
|
|
312
|
+
var CHAR_U = 85;
|
|
313
|
+
var CHAR_Z = 90;
|
|
314
|
+
var CHAR_LOWBAR = 95;
|
|
315
|
+
var CHAR_a = 97;
|
|
316
|
+
var CHAR_b = 98;
|
|
317
|
+
var CHAR_e = 101;
|
|
318
|
+
var CHAR_f = 102;
|
|
319
|
+
var CHAR_i = 105;
|
|
320
|
+
var CHAR_l = 108;
|
|
321
|
+
var CHAR_n = 110;
|
|
322
|
+
var CHAR_o = 111;
|
|
323
|
+
var CHAR_r = 114;
|
|
324
|
+
var CHAR_s = 115;
|
|
325
|
+
var CHAR_t = 116;
|
|
326
|
+
var CHAR_u = 117;
|
|
327
|
+
var CHAR_x = 120;
|
|
328
|
+
var CHAR_z = 122;
|
|
329
|
+
var CHAR_LCUB = 123;
|
|
330
|
+
var CHAR_RCUB = 125;
|
|
331
|
+
var CHAR_LSQB = 91;
|
|
332
|
+
var CHAR_BSOL = 92;
|
|
333
|
+
var CHAR_RSQB = 93;
|
|
334
|
+
var CHAR_DEL = 127;
|
|
335
|
+
var SURROGATE_FIRST = 55296;
|
|
336
|
+
var SURROGATE_LAST = 57343;
|
|
337
|
+
var escapes = {
|
|
338
|
+
[CHAR_b]: "\b",
|
|
339
|
+
[CHAR_t]: " ",
|
|
340
|
+
[CHAR_n]: "\n",
|
|
341
|
+
[CHAR_f]: "\f",
|
|
342
|
+
[CHAR_r]: "\r",
|
|
343
|
+
[CHAR_QUOT]: '"',
|
|
344
|
+
[CHAR_BSOL]: "\\"
|
|
345
|
+
};
|
|
346
|
+
function isDigit(cp) {
|
|
347
|
+
return cp >= CHAR_0 && cp <= CHAR_9;
|
|
348
|
+
}
|
|
349
|
+
function isHexit(cp) {
|
|
350
|
+
return cp >= CHAR_A && cp <= CHAR_F || cp >= CHAR_a && cp <= CHAR_f || cp >= CHAR_0 && cp <= CHAR_9;
|
|
351
|
+
}
|
|
352
|
+
function isBit(cp) {
|
|
353
|
+
return cp === CHAR_1 || cp === CHAR_0;
|
|
354
|
+
}
|
|
355
|
+
function isOctit(cp) {
|
|
356
|
+
return cp >= CHAR_0 && cp <= CHAR_7;
|
|
357
|
+
}
|
|
358
|
+
function isAlphaNumQuoteHyphen(cp) {
|
|
359
|
+
return cp >= CHAR_A && cp <= CHAR_Z || cp >= CHAR_a && cp <= CHAR_z || cp >= CHAR_0 && cp <= CHAR_9 || cp === CHAR_APOS || cp === CHAR_QUOT || cp === CHAR_LOWBAR || cp === CHAR_HYPHEN;
|
|
360
|
+
}
|
|
361
|
+
function isAlphaNumHyphen(cp) {
|
|
362
|
+
return cp >= CHAR_A && cp <= CHAR_Z || cp >= CHAR_a && cp <= CHAR_z || cp >= CHAR_0 && cp <= CHAR_9 || cp === CHAR_LOWBAR || cp === CHAR_HYPHEN;
|
|
363
|
+
}
|
|
364
|
+
var _type = Symbol("type");
|
|
365
|
+
var _declared = Symbol("declared");
|
|
366
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
367
|
+
var defineProperty = Object.defineProperty;
|
|
368
|
+
var descriptor = { configurable: true, enumerable: true, writable: true, value: void 0 };
|
|
369
|
+
function hasKey(obj, key) {
|
|
370
|
+
if (hasOwnProperty.call(obj, key)) return true;
|
|
371
|
+
if (key === "__proto__") defineProperty(obj, "__proto__", descriptor);
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
var INLINE_TABLE = Symbol("inline-table");
|
|
375
|
+
function InlineTable() {
|
|
376
|
+
return Object.defineProperties({}, {
|
|
377
|
+
[_type]: { value: INLINE_TABLE }
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function isInlineTable(obj) {
|
|
381
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
382
|
+
return obj[_type] === INLINE_TABLE;
|
|
383
|
+
}
|
|
384
|
+
var TABLE = Symbol("table");
|
|
385
|
+
function Table() {
|
|
386
|
+
return Object.defineProperties({}, {
|
|
387
|
+
[_type]: { value: TABLE },
|
|
388
|
+
[_declared]: { value: false, writable: true }
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function isTable(obj) {
|
|
392
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
393
|
+
return obj[_type] === TABLE;
|
|
394
|
+
}
|
|
395
|
+
var _contentType = Symbol("content-type");
|
|
396
|
+
var INLINE_LIST = Symbol("inline-list");
|
|
397
|
+
function InlineList(type) {
|
|
398
|
+
return Object.defineProperties([], {
|
|
399
|
+
[_type]: { value: INLINE_LIST },
|
|
400
|
+
[_contentType]: { value: type }
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function isInlineList(obj) {
|
|
404
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
405
|
+
return obj[_type] === INLINE_LIST;
|
|
406
|
+
}
|
|
407
|
+
var LIST = Symbol("list");
|
|
408
|
+
function List() {
|
|
409
|
+
return Object.defineProperties([], {
|
|
410
|
+
[_type]: { value: LIST }
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function isList(obj) {
|
|
414
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
415
|
+
return obj[_type] === LIST;
|
|
416
|
+
}
|
|
417
|
+
var _custom;
|
|
418
|
+
try {
|
|
419
|
+
const utilInspect = eval("require('util').inspect");
|
|
420
|
+
_custom = utilInspect.custom;
|
|
421
|
+
} catch (_) {
|
|
422
|
+
}
|
|
423
|
+
var _inspect = _custom || "inspect";
|
|
424
|
+
var BoxedBigInt = class {
|
|
425
|
+
constructor(value) {
|
|
426
|
+
try {
|
|
427
|
+
this.value = global.BigInt.asIntN(64, value);
|
|
428
|
+
} catch (_) {
|
|
429
|
+
this.value = null;
|
|
430
|
+
}
|
|
431
|
+
Object.defineProperty(this, _type, { value: INTEGER });
|
|
432
|
+
}
|
|
433
|
+
isNaN() {
|
|
434
|
+
return this.value === null;
|
|
435
|
+
}
|
|
436
|
+
/* istanbul ignore next */
|
|
437
|
+
toString() {
|
|
438
|
+
return String(this.value);
|
|
439
|
+
}
|
|
440
|
+
/* istanbul ignore next */
|
|
441
|
+
[_inspect]() {
|
|
442
|
+
return `[BigInt: ${this.toString()}]}`;
|
|
443
|
+
}
|
|
444
|
+
valueOf() {
|
|
445
|
+
return this.value;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var INTEGER = Symbol("integer");
|
|
449
|
+
function Integer(value) {
|
|
450
|
+
let num = Number(value);
|
|
451
|
+
if (Object.is(num, -0)) num = 0;
|
|
452
|
+
if (global.BigInt && !Number.isSafeInteger(num)) {
|
|
453
|
+
return new BoxedBigInt(value);
|
|
454
|
+
} else {
|
|
455
|
+
return Object.defineProperties(new Number(num), {
|
|
456
|
+
isNaN: { value: function() {
|
|
457
|
+
return isNaN(this);
|
|
458
|
+
} },
|
|
459
|
+
[_type]: { value: INTEGER },
|
|
460
|
+
[_inspect]: { value: () => `[Integer: ${value}]` }
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function isInteger(obj) {
|
|
465
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
466
|
+
return obj[_type] === INTEGER;
|
|
467
|
+
}
|
|
468
|
+
var FLOAT = Symbol("float");
|
|
469
|
+
function Float(value) {
|
|
470
|
+
return Object.defineProperties(new Number(value), {
|
|
471
|
+
[_type]: { value: FLOAT },
|
|
472
|
+
[_inspect]: { value: () => `[Float: ${value}]` }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function isFloat(obj) {
|
|
476
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
477
|
+
return obj[_type] === FLOAT;
|
|
478
|
+
}
|
|
479
|
+
function tomlType(value) {
|
|
480
|
+
const type = typeof value;
|
|
481
|
+
if (type === "object") {
|
|
482
|
+
if (value === null) return "null";
|
|
483
|
+
if (value instanceof Date) return "datetime";
|
|
484
|
+
if (_type in value) {
|
|
485
|
+
switch (value[_type]) {
|
|
486
|
+
case INLINE_TABLE:
|
|
487
|
+
return "inline-table";
|
|
488
|
+
case INLINE_LIST:
|
|
489
|
+
return "inline-list";
|
|
490
|
+
/* istanbul ignore next */
|
|
491
|
+
case TABLE:
|
|
492
|
+
return "table";
|
|
493
|
+
/* istanbul ignore next */
|
|
494
|
+
case LIST:
|
|
495
|
+
return "list";
|
|
496
|
+
case FLOAT:
|
|
497
|
+
return "float";
|
|
498
|
+
case INTEGER:
|
|
499
|
+
return "integer";
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return type;
|
|
504
|
+
}
|
|
505
|
+
function makeParserClass(Parser) {
|
|
506
|
+
class TOMLParser extends Parser {
|
|
507
|
+
constructor() {
|
|
508
|
+
super();
|
|
509
|
+
this.ctx = this.obj = Table();
|
|
510
|
+
}
|
|
511
|
+
/* MATCH HELPER */
|
|
512
|
+
atEndOfWord() {
|
|
513
|
+
return this.char === CHAR_NUM || this.char === CTRL_I || this.char === CHAR_SP || this.atEndOfLine();
|
|
514
|
+
}
|
|
515
|
+
atEndOfLine() {
|
|
516
|
+
return this.char === Parser.END || this.char === CTRL_J || this.char === CTRL_M;
|
|
517
|
+
}
|
|
518
|
+
parseStart() {
|
|
519
|
+
if (this.char === Parser.END) {
|
|
520
|
+
return null;
|
|
521
|
+
} else if (this.char === CHAR_LSQB) {
|
|
522
|
+
return this.call(this.parseTableOrList);
|
|
523
|
+
} else if (this.char === CHAR_NUM) {
|
|
524
|
+
return this.call(this.parseComment);
|
|
525
|
+
} else if (this.char === CTRL_J || this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
|
|
526
|
+
return null;
|
|
527
|
+
} else if (isAlphaNumQuoteHyphen(this.char)) {
|
|
528
|
+
return this.callNow(this.parseAssignStatement);
|
|
529
|
+
} else {
|
|
530
|
+
throw this.error(new TomlError(`Unknown character "${this.char}"`));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// HELPER, this strips any whitespace and comments to the end of the line
|
|
534
|
+
// then RETURNS. Last state in a production.
|
|
535
|
+
parseWhitespaceToEOL() {
|
|
536
|
+
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
|
|
537
|
+
return null;
|
|
538
|
+
} else if (this.char === CHAR_NUM) {
|
|
539
|
+
return this.goto(this.parseComment);
|
|
540
|
+
} else if (this.char === Parser.END || this.char === CTRL_J) {
|
|
541
|
+
return this.return();
|
|
542
|
+
} else {
|
|
543
|
+
throw this.error(new TomlError("Unexpected character, expected only whitespace or comments till end of line"));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/* ASSIGNMENT: key = value */
|
|
547
|
+
parseAssignStatement() {
|
|
548
|
+
return this.callNow(this.parseAssign, this.recordAssignStatement);
|
|
549
|
+
}
|
|
550
|
+
recordAssignStatement(kv) {
|
|
551
|
+
let target = this.ctx;
|
|
552
|
+
let finalKey = kv.key.pop();
|
|
553
|
+
for (let kw of kv.key) {
|
|
554
|
+
if (hasKey(target, kw) && (!isTable(target[kw]) || target[kw][_declared])) {
|
|
555
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
556
|
+
}
|
|
557
|
+
target = target[kw] = target[kw] || Table();
|
|
558
|
+
}
|
|
559
|
+
if (hasKey(target, finalKey)) {
|
|
560
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
561
|
+
}
|
|
562
|
+
if (isInteger(kv.value) || isFloat(kv.value)) {
|
|
563
|
+
target[finalKey] = kv.value.valueOf();
|
|
564
|
+
} else {
|
|
565
|
+
target[finalKey] = kv.value;
|
|
566
|
+
}
|
|
567
|
+
return this.goto(this.parseWhitespaceToEOL);
|
|
568
|
+
}
|
|
569
|
+
/* ASSSIGNMENT expression, key = value possibly inside an inline table */
|
|
570
|
+
parseAssign() {
|
|
571
|
+
return this.callNow(this.parseKeyword, this.recordAssignKeyword);
|
|
572
|
+
}
|
|
573
|
+
recordAssignKeyword(key) {
|
|
574
|
+
if (this.state.resultTable) {
|
|
575
|
+
this.state.resultTable.push(key);
|
|
576
|
+
} else {
|
|
577
|
+
this.state.resultTable = [key];
|
|
578
|
+
}
|
|
579
|
+
return this.goto(this.parseAssignKeywordPreDot);
|
|
580
|
+
}
|
|
581
|
+
parseAssignKeywordPreDot() {
|
|
582
|
+
if (this.char === CHAR_PERIOD) {
|
|
583
|
+
return this.next(this.parseAssignKeywordPostDot);
|
|
584
|
+
} else if (this.char !== CHAR_SP && this.char !== CTRL_I) {
|
|
585
|
+
return this.goto(this.parseAssignEqual);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
parseAssignKeywordPostDot() {
|
|
589
|
+
if (this.char !== CHAR_SP && this.char !== CTRL_I) {
|
|
590
|
+
return this.callNow(this.parseKeyword, this.recordAssignKeyword);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
parseAssignEqual() {
|
|
594
|
+
if (this.char === CHAR_EQUALS) {
|
|
595
|
+
return this.next(this.parseAssignPreValue);
|
|
596
|
+
} else {
|
|
597
|
+
throw this.error(new TomlError('Invalid character, expected "="'));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
parseAssignPreValue() {
|
|
601
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
602
|
+
return null;
|
|
603
|
+
} else {
|
|
604
|
+
return this.callNow(this.parseValue, this.recordAssignValue);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
recordAssignValue(value) {
|
|
608
|
+
return this.returnNow({ key: this.state.resultTable, value });
|
|
609
|
+
}
|
|
610
|
+
/* COMMENTS: #...eol */
|
|
611
|
+
parseComment() {
|
|
612
|
+
do {
|
|
613
|
+
if (this.char === Parser.END || this.char === CTRL_J) {
|
|
614
|
+
return this.return();
|
|
615
|
+
}
|
|
616
|
+
} while (this.nextChar());
|
|
617
|
+
}
|
|
618
|
+
/* TABLES AND LISTS, [foo] and [[foo]] */
|
|
619
|
+
parseTableOrList() {
|
|
620
|
+
if (this.char === CHAR_LSQB) {
|
|
621
|
+
this.next(this.parseList);
|
|
622
|
+
} else {
|
|
623
|
+
return this.goto(this.parseTable);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/* TABLE [foo.bar.baz] */
|
|
627
|
+
parseTable() {
|
|
628
|
+
this.ctx = this.obj;
|
|
629
|
+
return this.goto(this.parseTableNext);
|
|
630
|
+
}
|
|
631
|
+
parseTableNext() {
|
|
632
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
633
|
+
return null;
|
|
634
|
+
} else {
|
|
635
|
+
return this.callNow(this.parseKeyword, this.parseTableMore);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
parseTableMore(keyword) {
|
|
639
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
640
|
+
return null;
|
|
641
|
+
} else if (this.char === CHAR_RSQB) {
|
|
642
|
+
if (hasKey(this.ctx, keyword) && (!isTable(this.ctx[keyword]) || this.ctx[keyword][_declared])) {
|
|
643
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
644
|
+
} else {
|
|
645
|
+
this.ctx = this.ctx[keyword] = this.ctx[keyword] || Table();
|
|
646
|
+
this.ctx[_declared] = true;
|
|
647
|
+
}
|
|
648
|
+
return this.next(this.parseWhitespaceToEOL);
|
|
649
|
+
} else if (this.char === CHAR_PERIOD) {
|
|
650
|
+
if (!hasKey(this.ctx, keyword)) {
|
|
651
|
+
this.ctx = this.ctx[keyword] = Table();
|
|
652
|
+
} else if (isTable(this.ctx[keyword])) {
|
|
653
|
+
this.ctx = this.ctx[keyword];
|
|
654
|
+
} else if (isList(this.ctx[keyword])) {
|
|
655
|
+
this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1];
|
|
656
|
+
} else {
|
|
657
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
658
|
+
}
|
|
659
|
+
return this.next(this.parseTableNext);
|
|
660
|
+
} else {
|
|
661
|
+
throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/* LIST [[a.b.c]] */
|
|
665
|
+
parseList() {
|
|
666
|
+
this.ctx = this.obj;
|
|
667
|
+
return this.goto(this.parseListNext);
|
|
668
|
+
}
|
|
669
|
+
parseListNext() {
|
|
670
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
671
|
+
return null;
|
|
672
|
+
} else {
|
|
673
|
+
return this.callNow(this.parseKeyword, this.parseListMore);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
parseListMore(keyword) {
|
|
677
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
678
|
+
return null;
|
|
679
|
+
} else if (this.char === CHAR_RSQB) {
|
|
680
|
+
if (!hasKey(this.ctx, keyword)) {
|
|
681
|
+
this.ctx[keyword] = List();
|
|
682
|
+
}
|
|
683
|
+
if (isInlineList(this.ctx[keyword])) {
|
|
684
|
+
throw this.error(new TomlError("Can't extend an inline array"));
|
|
685
|
+
} else if (isList(this.ctx[keyword])) {
|
|
686
|
+
const next = Table();
|
|
687
|
+
this.ctx[keyword].push(next);
|
|
688
|
+
this.ctx = next;
|
|
689
|
+
} else {
|
|
690
|
+
throw this.error(new TomlError("Can't redefine an existing key"));
|
|
691
|
+
}
|
|
692
|
+
return this.next(this.parseListEnd);
|
|
693
|
+
} else if (this.char === CHAR_PERIOD) {
|
|
694
|
+
if (!hasKey(this.ctx, keyword)) {
|
|
695
|
+
this.ctx = this.ctx[keyword] = Table();
|
|
696
|
+
} else if (isInlineList(this.ctx[keyword])) {
|
|
697
|
+
throw this.error(new TomlError("Can't extend an inline array"));
|
|
698
|
+
} else if (isInlineTable(this.ctx[keyword])) {
|
|
699
|
+
throw this.error(new TomlError("Can't extend an inline table"));
|
|
700
|
+
} else if (isList(this.ctx[keyword])) {
|
|
701
|
+
this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1];
|
|
702
|
+
} else if (isTable(this.ctx[keyword])) {
|
|
703
|
+
this.ctx = this.ctx[keyword];
|
|
704
|
+
} else {
|
|
705
|
+
throw this.error(new TomlError("Can't redefine an existing key"));
|
|
706
|
+
}
|
|
707
|
+
return this.next(this.parseListNext);
|
|
708
|
+
} else {
|
|
709
|
+
throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
parseListEnd(keyword) {
|
|
713
|
+
if (this.char === CHAR_RSQB) {
|
|
714
|
+
return this.next(this.parseWhitespaceToEOL);
|
|
715
|
+
} else {
|
|
716
|
+
throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/* VALUE string, number, boolean, inline list, inline object */
|
|
720
|
+
parseValue() {
|
|
721
|
+
if (this.char === Parser.END) {
|
|
722
|
+
throw this.error(new TomlError("Key without value"));
|
|
723
|
+
} else if (this.char === CHAR_QUOT) {
|
|
724
|
+
return this.next(this.parseDoubleString);
|
|
725
|
+
}
|
|
726
|
+
if (this.char === CHAR_APOS) {
|
|
727
|
+
return this.next(this.parseSingleString);
|
|
728
|
+
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
|
|
729
|
+
return this.goto(this.parseNumberSign);
|
|
730
|
+
} else if (this.char === CHAR_i) {
|
|
731
|
+
return this.next(this.parseInf);
|
|
732
|
+
} else if (this.char === CHAR_n) {
|
|
733
|
+
return this.next(this.parseNan);
|
|
734
|
+
} else if (isDigit(this.char)) {
|
|
735
|
+
return this.goto(this.parseNumberOrDateTime);
|
|
736
|
+
} else if (this.char === CHAR_t || this.char === CHAR_f) {
|
|
737
|
+
return this.goto(this.parseBoolean);
|
|
738
|
+
} else if (this.char === CHAR_LSQB) {
|
|
739
|
+
return this.call(this.parseInlineList, this.recordValue);
|
|
740
|
+
} else if (this.char === CHAR_LCUB) {
|
|
741
|
+
return this.call(this.parseInlineTable, this.recordValue);
|
|
742
|
+
} else {
|
|
743
|
+
throw this.error(new TomlError("Unexpected character, expecting string, number, datetime, boolean, inline array or inline table"));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
recordValue(value) {
|
|
747
|
+
return this.returnNow(value);
|
|
748
|
+
}
|
|
749
|
+
parseInf() {
|
|
750
|
+
if (this.char === CHAR_n) {
|
|
751
|
+
return this.next(this.parseInf2);
|
|
752
|
+
} else {
|
|
753
|
+
throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
parseInf2() {
|
|
757
|
+
if (this.char === CHAR_f) {
|
|
758
|
+
if (this.state.buf === "-") {
|
|
759
|
+
return this.return(-Infinity);
|
|
760
|
+
} else {
|
|
761
|
+
return this.return(Infinity);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'));
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
parseNan() {
|
|
768
|
+
if (this.char === CHAR_a) {
|
|
769
|
+
return this.next(this.parseNan2);
|
|
770
|
+
} else {
|
|
771
|
+
throw this.error(new TomlError('Unexpected character, expected "nan"'));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
parseNan2() {
|
|
775
|
+
if (this.char === CHAR_n) {
|
|
776
|
+
return this.return(NaN);
|
|
777
|
+
} else {
|
|
778
|
+
throw this.error(new TomlError('Unexpected character, expected "nan"'));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/* KEYS, barewords or basic, literal, or dotted */
|
|
782
|
+
parseKeyword() {
|
|
783
|
+
if (this.char === CHAR_QUOT) {
|
|
784
|
+
return this.next(this.parseBasicString);
|
|
785
|
+
} else if (this.char === CHAR_APOS) {
|
|
786
|
+
return this.next(this.parseLiteralString);
|
|
787
|
+
} else {
|
|
788
|
+
return this.goto(this.parseBareKey);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/* KEYS: barewords */
|
|
792
|
+
parseBareKey() {
|
|
793
|
+
do {
|
|
794
|
+
if (this.char === Parser.END) {
|
|
795
|
+
throw this.error(new TomlError("Key ended without value"));
|
|
796
|
+
} else if (isAlphaNumHyphen(this.char)) {
|
|
797
|
+
this.consume();
|
|
798
|
+
} else if (this.state.buf.length === 0) {
|
|
799
|
+
throw this.error(new TomlError("Empty bare keys are not allowed"));
|
|
800
|
+
} else {
|
|
801
|
+
return this.returnNow();
|
|
802
|
+
}
|
|
803
|
+
} while (this.nextChar());
|
|
804
|
+
}
|
|
805
|
+
/* STRINGS, single quoted (literal) */
|
|
806
|
+
parseSingleString() {
|
|
807
|
+
if (this.char === CHAR_APOS) {
|
|
808
|
+
return this.next(this.parseLiteralMultiStringMaybe);
|
|
809
|
+
} else {
|
|
810
|
+
return this.goto(this.parseLiteralString);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
parseLiteralString() {
|
|
814
|
+
do {
|
|
815
|
+
if (this.char === CHAR_APOS) {
|
|
816
|
+
return this.return();
|
|
817
|
+
} else if (this.atEndOfLine()) {
|
|
818
|
+
throw this.error(new TomlError("Unterminated string"));
|
|
819
|
+
} else if (this.char === CHAR_DEL || this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I) {
|
|
820
|
+
throw this.errorControlCharInString();
|
|
821
|
+
} else {
|
|
822
|
+
this.consume();
|
|
823
|
+
}
|
|
824
|
+
} while (this.nextChar());
|
|
825
|
+
}
|
|
826
|
+
parseLiteralMultiStringMaybe() {
|
|
827
|
+
if (this.char === CHAR_APOS) {
|
|
828
|
+
return this.next(this.parseLiteralMultiString);
|
|
829
|
+
} else {
|
|
830
|
+
return this.returnNow();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
parseLiteralMultiString() {
|
|
834
|
+
if (this.char === CTRL_M) {
|
|
835
|
+
return null;
|
|
836
|
+
} else if (this.char === CTRL_J) {
|
|
837
|
+
return this.next(this.parseLiteralMultiStringContent);
|
|
838
|
+
} else {
|
|
839
|
+
return this.goto(this.parseLiteralMultiStringContent);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
parseLiteralMultiStringContent() {
|
|
843
|
+
do {
|
|
844
|
+
if (this.char === CHAR_APOS) {
|
|
845
|
+
return this.next(this.parseLiteralMultiEnd);
|
|
846
|
+
} else if (this.char === Parser.END) {
|
|
847
|
+
throw this.error(new TomlError("Unterminated multi-line string"));
|
|
848
|
+
} else if (this.char === CHAR_DEL || this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I && this.char !== CTRL_J && this.char !== CTRL_M) {
|
|
849
|
+
throw this.errorControlCharInString();
|
|
850
|
+
} else {
|
|
851
|
+
this.consume();
|
|
852
|
+
}
|
|
853
|
+
} while (this.nextChar());
|
|
854
|
+
}
|
|
855
|
+
parseLiteralMultiEnd() {
|
|
856
|
+
if (this.char === CHAR_APOS) {
|
|
857
|
+
return this.next(this.parseLiteralMultiEnd2);
|
|
858
|
+
} else {
|
|
859
|
+
this.state.buf += "'";
|
|
860
|
+
return this.goto(this.parseLiteralMultiStringContent);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
parseLiteralMultiEnd2() {
|
|
864
|
+
if (this.char === CHAR_APOS) {
|
|
865
|
+
return this.return();
|
|
866
|
+
} else {
|
|
867
|
+
this.state.buf += "''";
|
|
868
|
+
return this.goto(this.parseLiteralMultiStringContent);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
/* STRINGS double quoted */
|
|
872
|
+
parseDoubleString() {
|
|
873
|
+
if (this.char === CHAR_QUOT) {
|
|
874
|
+
return this.next(this.parseMultiStringMaybe);
|
|
875
|
+
} else {
|
|
876
|
+
return this.goto(this.parseBasicString);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
parseBasicString() {
|
|
880
|
+
do {
|
|
881
|
+
if (this.char === CHAR_BSOL) {
|
|
882
|
+
return this.call(this.parseEscape, this.recordEscapeReplacement);
|
|
883
|
+
} else if (this.char === CHAR_QUOT) {
|
|
884
|
+
return this.return();
|
|
885
|
+
} else if (this.atEndOfLine()) {
|
|
886
|
+
throw this.error(new TomlError("Unterminated string"));
|
|
887
|
+
} else if (this.char === CHAR_DEL || this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I) {
|
|
888
|
+
throw this.errorControlCharInString();
|
|
889
|
+
} else {
|
|
890
|
+
this.consume();
|
|
891
|
+
}
|
|
892
|
+
} while (this.nextChar());
|
|
893
|
+
}
|
|
894
|
+
recordEscapeReplacement(replacement) {
|
|
895
|
+
this.state.buf += replacement;
|
|
896
|
+
return this.goto(this.parseBasicString);
|
|
897
|
+
}
|
|
898
|
+
parseMultiStringMaybe() {
|
|
899
|
+
if (this.char === CHAR_QUOT) {
|
|
900
|
+
return this.next(this.parseMultiString);
|
|
901
|
+
} else {
|
|
902
|
+
return this.returnNow();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
parseMultiString() {
|
|
906
|
+
if (this.char === CTRL_M) {
|
|
907
|
+
return null;
|
|
908
|
+
} else if (this.char === CTRL_J) {
|
|
909
|
+
return this.next(this.parseMultiStringContent);
|
|
910
|
+
} else {
|
|
911
|
+
return this.goto(this.parseMultiStringContent);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
parseMultiStringContent() {
|
|
915
|
+
do {
|
|
916
|
+
if (this.char === CHAR_BSOL) {
|
|
917
|
+
return this.call(this.parseMultiEscape, this.recordMultiEscapeReplacement);
|
|
918
|
+
} else if (this.char === CHAR_QUOT) {
|
|
919
|
+
return this.next(this.parseMultiEnd);
|
|
920
|
+
} else if (this.char === Parser.END) {
|
|
921
|
+
throw this.error(new TomlError("Unterminated multi-line string"));
|
|
922
|
+
} else if (this.char === CHAR_DEL || this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I && this.char !== CTRL_J && this.char !== CTRL_M) {
|
|
923
|
+
throw this.errorControlCharInString();
|
|
924
|
+
} else {
|
|
925
|
+
this.consume();
|
|
926
|
+
}
|
|
927
|
+
} while (this.nextChar());
|
|
928
|
+
}
|
|
929
|
+
errorControlCharInString() {
|
|
930
|
+
let displayCode = "\\u00";
|
|
931
|
+
if (this.char < 16) {
|
|
932
|
+
displayCode += "0";
|
|
933
|
+
}
|
|
934
|
+
displayCode += this.char.toString(16);
|
|
935
|
+
return this.error(new TomlError(`Control characters (codes < 0x1f and 0x7f) are not allowed in strings, use ${displayCode} instead`));
|
|
936
|
+
}
|
|
937
|
+
recordMultiEscapeReplacement(replacement) {
|
|
938
|
+
this.state.buf += replacement;
|
|
939
|
+
return this.goto(this.parseMultiStringContent);
|
|
940
|
+
}
|
|
941
|
+
parseMultiEnd() {
|
|
942
|
+
if (this.char === CHAR_QUOT) {
|
|
943
|
+
return this.next(this.parseMultiEnd2);
|
|
944
|
+
} else {
|
|
945
|
+
this.state.buf += '"';
|
|
946
|
+
return this.goto(this.parseMultiStringContent);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
parseMultiEnd2() {
|
|
950
|
+
if (this.char === CHAR_QUOT) {
|
|
951
|
+
return this.return();
|
|
952
|
+
} else {
|
|
953
|
+
this.state.buf += '""';
|
|
954
|
+
return this.goto(this.parseMultiStringContent);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
parseMultiEscape() {
|
|
958
|
+
if (this.char === CTRL_M || this.char === CTRL_J) {
|
|
959
|
+
return this.next(this.parseMultiTrim);
|
|
960
|
+
} else if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
961
|
+
return this.next(this.parsePreMultiTrim);
|
|
962
|
+
} else {
|
|
963
|
+
return this.goto(this.parseEscape);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
parsePreMultiTrim() {
|
|
967
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
968
|
+
return null;
|
|
969
|
+
} else if (this.char === CTRL_M || this.char === CTRL_J) {
|
|
970
|
+
return this.next(this.parseMultiTrim);
|
|
971
|
+
} else {
|
|
972
|
+
throw this.error(new TomlError("Can't escape whitespace"));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
parseMultiTrim() {
|
|
976
|
+
if (this.char === CTRL_J || this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
|
|
977
|
+
return null;
|
|
978
|
+
} else {
|
|
979
|
+
return this.returnNow();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
parseEscape() {
|
|
983
|
+
if (this.char in escapes) {
|
|
984
|
+
return this.return(escapes[this.char]);
|
|
985
|
+
} else if (this.char === CHAR_u) {
|
|
986
|
+
return this.call(this.parseSmallUnicode, this.parseUnicodeReturn);
|
|
987
|
+
} else if (this.char === CHAR_U) {
|
|
988
|
+
return this.call(this.parseLargeUnicode, this.parseUnicodeReturn);
|
|
989
|
+
} else {
|
|
990
|
+
throw this.error(new TomlError("Unknown escape character: " + this.char));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
parseUnicodeReturn(char) {
|
|
994
|
+
try {
|
|
995
|
+
const codePoint = parseInt(char, 16);
|
|
996
|
+
if (codePoint >= SURROGATE_FIRST && codePoint <= SURROGATE_LAST) {
|
|
997
|
+
throw this.error(new TomlError("Invalid unicode, character in range 0xD800 - 0xDFFF is reserved"));
|
|
998
|
+
}
|
|
999
|
+
return this.returnNow(String.fromCodePoint(codePoint));
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
throw this.error(TomlError.wrap(err));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
parseSmallUnicode() {
|
|
1005
|
+
if (!isHexit(this.char)) {
|
|
1006
|
+
throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));
|
|
1007
|
+
} else {
|
|
1008
|
+
this.consume();
|
|
1009
|
+
if (this.state.buf.length >= 4) return this.return();
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
parseLargeUnicode() {
|
|
1013
|
+
if (!isHexit(this.char)) {
|
|
1014
|
+
throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));
|
|
1015
|
+
} else {
|
|
1016
|
+
this.consume();
|
|
1017
|
+
if (this.state.buf.length >= 8) return this.return();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/* NUMBERS */
|
|
1021
|
+
parseNumberSign() {
|
|
1022
|
+
this.consume();
|
|
1023
|
+
return this.next(this.parseMaybeSignedInfOrNan);
|
|
1024
|
+
}
|
|
1025
|
+
parseMaybeSignedInfOrNan() {
|
|
1026
|
+
if (this.char === CHAR_i) {
|
|
1027
|
+
return this.next(this.parseInf);
|
|
1028
|
+
} else if (this.char === CHAR_n) {
|
|
1029
|
+
return this.next(this.parseNan);
|
|
1030
|
+
} else {
|
|
1031
|
+
return this.callNow(this.parseNoUnder, this.parseNumberIntegerStart);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
parseNumberIntegerStart() {
|
|
1035
|
+
if (this.char === CHAR_0) {
|
|
1036
|
+
this.consume();
|
|
1037
|
+
return this.next(this.parseNumberIntegerExponentOrDecimal);
|
|
1038
|
+
} else {
|
|
1039
|
+
return this.goto(this.parseNumberInteger);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
parseNumberIntegerExponentOrDecimal() {
|
|
1043
|
+
if (this.char === CHAR_PERIOD) {
|
|
1044
|
+
this.consume();
|
|
1045
|
+
return this.call(this.parseNoUnder, this.parseNumberFloat);
|
|
1046
|
+
} else if (this.char === CHAR_E || this.char === CHAR_e) {
|
|
1047
|
+
this.consume();
|
|
1048
|
+
return this.next(this.parseNumberExponentSign);
|
|
1049
|
+
} else {
|
|
1050
|
+
return this.returnNow(Integer(this.state.buf));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
parseNumberInteger() {
|
|
1054
|
+
if (isDigit(this.char)) {
|
|
1055
|
+
this.consume();
|
|
1056
|
+
} else if (this.char === CHAR_LOWBAR) {
|
|
1057
|
+
return this.call(this.parseNoUnder);
|
|
1058
|
+
} else if (this.char === CHAR_E || this.char === CHAR_e) {
|
|
1059
|
+
this.consume();
|
|
1060
|
+
return this.next(this.parseNumberExponentSign);
|
|
1061
|
+
} else if (this.char === CHAR_PERIOD) {
|
|
1062
|
+
this.consume();
|
|
1063
|
+
return this.call(this.parseNoUnder, this.parseNumberFloat);
|
|
1064
|
+
} else {
|
|
1065
|
+
const result = Integer(this.state.buf);
|
|
1066
|
+
if (result.isNaN()) {
|
|
1067
|
+
throw this.error(new TomlError("Invalid number"));
|
|
1068
|
+
} else {
|
|
1069
|
+
return this.returnNow(result);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
parseNoUnder() {
|
|
1074
|
+
if (this.char === CHAR_LOWBAR || this.char === CHAR_PERIOD || this.char === CHAR_E || this.char === CHAR_e) {
|
|
1075
|
+
throw this.error(new TomlError("Unexpected character, expected digit"));
|
|
1076
|
+
} else if (this.atEndOfWord()) {
|
|
1077
|
+
throw this.error(new TomlError("Incomplete number"));
|
|
1078
|
+
}
|
|
1079
|
+
return this.returnNow();
|
|
1080
|
+
}
|
|
1081
|
+
parseNoUnderHexOctBinLiteral() {
|
|
1082
|
+
if (this.char === CHAR_LOWBAR || this.char === CHAR_PERIOD) {
|
|
1083
|
+
throw this.error(new TomlError("Unexpected character, expected digit"));
|
|
1084
|
+
} else if (this.atEndOfWord()) {
|
|
1085
|
+
throw this.error(new TomlError("Incomplete number"));
|
|
1086
|
+
}
|
|
1087
|
+
return this.returnNow();
|
|
1088
|
+
}
|
|
1089
|
+
parseNumberFloat() {
|
|
1090
|
+
if (this.char === CHAR_LOWBAR) {
|
|
1091
|
+
return this.call(this.parseNoUnder, this.parseNumberFloat);
|
|
1092
|
+
} else if (isDigit(this.char)) {
|
|
1093
|
+
this.consume();
|
|
1094
|
+
} else if (this.char === CHAR_E || this.char === CHAR_e) {
|
|
1095
|
+
this.consume();
|
|
1096
|
+
return this.next(this.parseNumberExponentSign);
|
|
1097
|
+
} else {
|
|
1098
|
+
return this.returnNow(Float(this.state.buf));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
parseNumberExponentSign() {
|
|
1102
|
+
if (isDigit(this.char)) {
|
|
1103
|
+
return this.goto(this.parseNumberExponent);
|
|
1104
|
+
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
|
|
1105
|
+
this.consume();
|
|
1106
|
+
this.call(this.parseNoUnder, this.parseNumberExponent);
|
|
1107
|
+
} else {
|
|
1108
|
+
throw this.error(new TomlError("Unexpected character, expected -, + or digit"));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
parseNumberExponent() {
|
|
1112
|
+
if (isDigit(this.char)) {
|
|
1113
|
+
this.consume();
|
|
1114
|
+
} else if (this.char === CHAR_LOWBAR) {
|
|
1115
|
+
return this.call(this.parseNoUnder);
|
|
1116
|
+
} else {
|
|
1117
|
+
return this.returnNow(Float(this.state.buf));
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
/* NUMBERS or DATETIMES */
|
|
1121
|
+
parseNumberOrDateTime() {
|
|
1122
|
+
if (this.char === CHAR_0) {
|
|
1123
|
+
this.consume();
|
|
1124
|
+
return this.next(this.parseNumberBaseOrDateTime);
|
|
1125
|
+
} else {
|
|
1126
|
+
return this.goto(this.parseNumberOrDateTimeOnly);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
parseNumberOrDateTimeOnly() {
|
|
1130
|
+
if (this.char === CHAR_LOWBAR) {
|
|
1131
|
+
return this.call(this.parseNoUnder, this.parseNumberInteger);
|
|
1132
|
+
} else if (isDigit(this.char)) {
|
|
1133
|
+
this.consume();
|
|
1134
|
+
if (this.state.buf.length > 4) this.next(this.parseNumberInteger);
|
|
1135
|
+
} else if (this.char === CHAR_E || this.char === CHAR_e) {
|
|
1136
|
+
this.consume();
|
|
1137
|
+
return this.next(this.parseNumberExponentSign);
|
|
1138
|
+
} else if (this.char === CHAR_PERIOD) {
|
|
1139
|
+
this.consume();
|
|
1140
|
+
return this.call(this.parseNoUnder, this.parseNumberFloat);
|
|
1141
|
+
} else if (this.char === CHAR_HYPHEN) {
|
|
1142
|
+
return this.goto(this.parseDateTime);
|
|
1143
|
+
} else if (this.char === CHAR_COLON) {
|
|
1144
|
+
return this.goto(this.parseOnlyTimeHour);
|
|
1145
|
+
} else {
|
|
1146
|
+
return this.returnNow(Integer(this.state.buf));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
parseDateTimeOnly() {
|
|
1150
|
+
if (this.state.buf.length < 4) {
|
|
1151
|
+
if (isDigit(this.char)) {
|
|
1152
|
+
return this.consume();
|
|
1153
|
+
} else if (this.char === CHAR_COLON) {
|
|
1154
|
+
return this.goto(this.parseOnlyTimeHour);
|
|
1155
|
+
} else {
|
|
1156
|
+
throw this.error(new TomlError("Expected digit while parsing year part of a date"));
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
if (this.char === CHAR_HYPHEN) {
|
|
1160
|
+
return this.goto(this.parseDateTime);
|
|
1161
|
+
} else {
|
|
1162
|
+
throw this.error(new TomlError("Expected hyphen (-) while parsing year part of date"));
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
parseNumberBaseOrDateTime() {
|
|
1167
|
+
if (this.char === CHAR_b) {
|
|
1168
|
+
this.consume();
|
|
1169
|
+
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerBin);
|
|
1170
|
+
} else if (this.char === CHAR_o) {
|
|
1171
|
+
this.consume();
|
|
1172
|
+
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerOct);
|
|
1173
|
+
} else if (this.char === CHAR_x) {
|
|
1174
|
+
this.consume();
|
|
1175
|
+
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerHex);
|
|
1176
|
+
} else if (this.char === CHAR_PERIOD) {
|
|
1177
|
+
return this.goto(this.parseNumberInteger);
|
|
1178
|
+
} else if (isDigit(this.char)) {
|
|
1179
|
+
return this.goto(this.parseDateTimeOnly);
|
|
1180
|
+
} else {
|
|
1181
|
+
return this.returnNow(Integer(this.state.buf));
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
parseIntegerHex() {
|
|
1185
|
+
if (isHexit(this.char)) {
|
|
1186
|
+
this.consume();
|
|
1187
|
+
} else if (this.char === CHAR_LOWBAR) {
|
|
1188
|
+
return this.call(this.parseNoUnderHexOctBinLiteral);
|
|
1189
|
+
} else {
|
|
1190
|
+
const result = Integer(this.state.buf);
|
|
1191
|
+
if (result.isNaN()) {
|
|
1192
|
+
throw this.error(new TomlError("Invalid number"));
|
|
1193
|
+
} else {
|
|
1194
|
+
return this.returnNow(result);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
parseIntegerOct() {
|
|
1199
|
+
if (isOctit(this.char)) {
|
|
1200
|
+
this.consume();
|
|
1201
|
+
} else if (this.char === CHAR_LOWBAR) {
|
|
1202
|
+
return this.call(this.parseNoUnderHexOctBinLiteral);
|
|
1203
|
+
} else {
|
|
1204
|
+
const result = Integer(this.state.buf);
|
|
1205
|
+
if (result.isNaN()) {
|
|
1206
|
+
throw this.error(new TomlError("Invalid number"));
|
|
1207
|
+
} else {
|
|
1208
|
+
return this.returnNow(result);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
parseIntegerBin() {
|
|
1213
|
+
if (isBit(this.char)) {
|
|
1214
|
+
this.consume();
|
|
1215
|
+
} else if (this.char === CHAR_LOWBAR) {
|
|
1216
|
+
return this.call(this.parseNoUnderHexOctBinLiteral);
|
|
1217
|
+
} else {
|
|
1218
|
+
const result = Integer(this.state.buf);
|
|
1219
|
+
if (result.isNaN()) {
|
|
1220
|
+
throw this.error(new TomlError("Invalid number"));
|
|
1221
|
+
} else {
|
|
1222
|
+
return this.returnNow(result);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/* DATETIME */
|
|
1227
|
+
parseDateTime() {
|
|
1228
|
+
if (this.state.buf.length < 4) {
|
|
1229
|
+
throw this.error(new TomlError("Years less than 1000 must be zero padded to four characters"));
|
|
1230
|
+
}
|
|
1231
|
+
this.state.result = this.state.buf;
|
|
1232
|
+
this.state.buf = "";
|
|
1233
|
+
return this.next(this.parseDateMonth);
|
|
1234
|
+
}
|
|
1235
|
+
parseDateMonth() {
|
|
1236
|
+
if (this.char === CHAR_HYPHEN) {
|
|
1237
|
+
if (this.state.buf.length < 2) {
|
|
1238
|
+
throw this.error(new TomlError("Months less than 10 must be zero padded to two characters"));
|
|
1239
|
+
}
|
|
1240
|
+
this.state.result += "-" + this.state.buf;
|
|
1241
|
+
this.state.buf = "";
|
|
1242
|
+
return this.next(this.parseDateDay);
|
|
1243
|
+
} else if (isDigit(this.char)) {
|
|
1244
|
+
this.consume();
|
|
1245
|
+
} else {
|
|
1246
|
+
throw this.error(new TomlError("Incomplete datetime"));
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
parseDateDay() {
|
|
1250
|
+
if (this.char === CHAR_T || this.char === CHAR_SP) {
|
|
1251
|
+
if (this.state.buf.length < 2) {
|
|
1252
|
+
throw this.error(new TomlError("Days less than 10 must be zero padded to two characters"));
|
|
1253
|
+
}
|
|
1254
|
+
this.state.result += "-" + this.state.buf;
|
|
1255
|
+
this.state.buf = "";
|
|
1256
|
+
return this.next(this.parseStartTimeHour);
|
|
1257
|
+
} else if (this.atEndOfWord()) {
|
|
1258
|
+
return this.returnNow(createDate(this.state.result + "-" + this.state.buf));
|
|
1259
|
+
} else if (isDigit(this.char)) {
|
|
1260
|
+
this.consume();
|
|
1261
|
+
} else {
|
|
1262
|
+
throw this.error(new TomlError("Incomplete datetime"));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
parseStartTimeHour() {
|
|
1266
|
+
if (this.atEndOfWord()) {
|
|
1267
|
+
return this.returnNow(createDate(this.state.result));
|
|
1268
|
+
} else {
|
|
1269
|
+
return this.goto(this.parseTimeHour);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
parseTimeHour() {
|
|
1273
|
+
if (this.char === CHAR_COLON) {
|
|
1274
|
+
if (this.state.buf.length < 2) {
|
|
1275
|
+
throw this.error(new TomlError("Hours less than 10 must be zero padded to two characters"));
|
|
1276
|
+
}
|
|
1277
|
+
this.state.result += "T" + this.state.buf;
|
|
1278
|
+
this.state.buf = "";
|
|
1279
|
+
return this.next(this.parseTimeMin);
|
|
1280
|
+
} else if (isDigit(this.char)) {
|
|
1281
|
+
this.consume();
|
|
1282
|
+
} else {
|
|
1283
|
+
throw this.error(new TomlError("Incomplete datetime"));
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
parseTimeMin() {
|
|
1287
|
+
if (this.state.buf.length < 2 && isDigit(this.char)) {
|
|
1288
|
+
this.consume();
|
|
1289
|
+
} else if (this.state.buf.length === 2 && this.char === CHAR_COLON) {
|
|
1290
|
+
this.state.result += ":" + this.state.buf;
|
|
1291
|
+
this.state.buf = "";
|
|
1292
|
+
return this.next(this.parseTimeSec);
|
|
1293
|
+
} else {
|
|
1294
|
+
throw this.error(new TomlError("Incomplete datetime"));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
parseTimeSec() {
|
|
1298
|
+
if (isDigit(this.char)) {
|
|
1299
|
+
this.consume();
|
|
1300
|
+
if (this.state.buf.length === 2) {
|
|
1301
|
+
this.state.result += ":" + this.state.buf;
|
|
1302
|
+
this.state.buf = "";
|
|
1303
|
+
return this.next(this.parseTimeZoneOrFraction);
|
|
1304
|
+
}
|
|
1305
|
+
} else {
|
|
1306
|
+
throw this.error(new TomlError("Incomplete datetime"));
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
parseOnlyTimeHour() {
|
|
1310
|
+
if (this.char === CHAR_COLON) {
|
|
1311
|
+
if (this.state.buf.length < 2) {
|
|
1312
|
+
throw this.error(new TomlError("Hours less than 10 must be zero padded to two characters"));
|
|
1313
|
+
}
|
|
1314
|
+
this.state.result = this.state.buf;
|
|
1315
|
+
this.state.buf = "";
|
|
1316
|
+
return this.next(this.parseOnlyTimeMin);
|
|
1317
|
+
} else {
|
|
1318
|
+
throw this.error(new TomlError("Incomplete time"));
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
parseOnlyTimeMin() {
|
|
1322
|
+
if (this.state.buf.length < 2 && isDigit(this.char)) {
|
|
1323
|
+
this.consume();
|
|
1324
|
+
} else if (this.state.buf.length === 2 && this.char === CHAR_COLON) {
|
|
1325
|
+
this.state.result += ":" + this.state.buf;
|
|
1326
|
+
this.state.buf = "";
|
|
1327
|
+
return this.next(this.parseOnlyTimeSec);
|
|
1328
|
+
} else {
|
|
1329
|
+
throw this.error(new TomlError("Incomplete time"));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
parseOnlyTimeSec() {
|
|
1333
|
+
if (isDigit(this.char)) {
|
|
1334
|
+
this.consume();
|
|
1335
|
+
if (this.state.buf.length === 2) {
|
|
1336
|
+
return this.next(this.parseOnlyTimeFractionMaybe);
|
|
1337
|
+
}
|
|
1338
|
+
} else {
|
|
1339
|
+
throw this.error(new TomlError("Incomplete time"));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
parseOnlyTimeFractionMaybe() {
|
|
1343
|
+
this.state.result += ":" + this.state.buf;
|
|
1344
|
+
if (this.char === CHAR_PERIOD) {
|
|
1345
|
+
this.state.buf = "";
|
|
1346
|
+
this.next(this.parseOnlyTimeFraction);
|
|
1347
|
+
} else {
|
|
1348
|
+
return this.return(createTime(this.state.result));
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
parseOnlyTimeFraction() {
|
|
1352
|
+
if (isDigit(this.char)) {
|
|
1353
|
+
this.consume();
|
|
1354
|
+
} else if (this.atEndOfWord()) {
|
|
1355
|
+
if (this.state.buf.length === 0) throw this.error(new TomlError("Expected digit in milliseconds"));
|
|
1356
|
+
return this.returnNow(createTime(this.state.result + "." + this.state.buf));
|
|
1357
|
+
} else {
|
|
1358
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"));
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
parseTimeZoneOrFraction() {
|
|
1362
|
+
if (this.char === CHAR_PERIOD) {
|
|
1363
|
+
this.consume();
|
|
1364
|
+
this.next(this.parseDateTimeFraction);
|
|
1365
|
+
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
|
|
1366
|
+
this.consume();
|
|
1367
|
+
this.next(this.parseTimeZoneHour);
|
|
1368
|
+
} else if (this.char === CHAR_Z) {
|
|
1369
|
+
this.consume();
|
|
1370
|
+
return this.return(createDateTime(this.state.result + this.state.buf));
|
|
1371
|
+
} else if (this.atEndOfWord()) {
|
|
1372
|
+
return this.returnNow(createDateTimeFloat(this.state.result + this.state.buf));
|
|
1373
|
+
} else {
|
|
1374
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
parseDateTimeFraction() {
|
|
1378
|
+
if (isDigit(this.char)) {
|
|
1379
|
+
this.consume();
|
|
1380
|
+
} else if (this.state.buf.length === 1) {
|
|
1381
|
+
throw this.error(new TomlError("Expected digit in milliseconds"));
|
|
1382
|
+
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
|
|
1383
|
+
this.consume();
|
|
1384
|
+
this.next(this.parseTimeZoneHour);
|
|
1385
|
+
} else if (this.char === CHAR_Z) {
|
|
1386
|
+
this.consume();
|
|
1387
|
+
return this.return(createDateTime(this.state.result + this.state.buf));
|
|
1388
|
+
} else if (this.atEndOfWord()) {
|
|
1389
|
+
return this.returnNow(createDateTimeFloat(this.state.result + this.state.buf));
|
|
1390
|
+
} else {
|
|
1391
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z"));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
parseTimeZoneHour() {
|
|
1395
|
+
if (isDigit(this.char)) {
|
|
1396
|
+
this.consume();
|
|
1397
|
+
if (/\d\d$/.test(this.state.buf)) return this.next(this.parseTimeZoneSep);
|
|
1398
|
+
} else {
|
|
1399
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected digit"));
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
parseTimeZoneSep() {
|
|
1403
|
+
if (this.char === CHAR_COLON) {
|
|
1404
|
+
this.consume();
|
|
1405
|
+
this.next(this.parseTimeZoneMin);
|
|
1406
|
+
} else {
|
|
1407
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected colon"));
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
parseTimeZoneMin() {
|
|
1411
|
+
if (isDigit(this.char)) {
|
|
1412
|
+
this.consume();
|
|
1413
|
+
if (/\d\d$/.test(this.state.buf)) return this.return(createDateTime(this.state.result + this.state.buf));
|
|
1414
|
+
} else {
|
|
1415
|
+
throw this.error(new TomlError("Unexpected character in datetime, expected digit"));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
/* BOOLEAN */
|
|
1419
|
+
parseBoolean() {
|
|
1420
|
+
if (this.char === CHAR_t) {
|
|
1421
|
+
this.consume();
|
|
1422
|
+
return this.next(this.parseTrue_r);
|
|
1423
|
+
} else if (this.char === CHAR_f) {
|
|
1424
|
+
this.consume();
|
|
1425
|
+
return this.next(this.parseFalse_a);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
parseTrue_r() {
|
|
1429
|
+
if (this.char === CHAR_r) {
|
|
1430
|
+
this.consume();
|
|
1431
|
+
return this.next(this.parseTrue_u);
|
|
1432
|
+
} else {
|
|
1433
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
parseTrue_u() {
|
|
1437
|
+
if (this.char === CHAR_u) {
|
|
1438
|
+
this.consume();
|
|
1439
|
+
return this.next(this.parseTrue_e);
|
|
1440
|
+
} else {
|
|
1441
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
parseTrue_e() {
|
|
1445
|
+
if (this.char === CHAR_e) {
|
|
1446
|
+
return this.return(true);
|
|
1447
|
+
} else {
|
|
1448
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
parseFalse_a() {
|
|
1452
|
+
if (this.char === CHAR_a) {
|
|
1453
|
+
this.consume();
|
|
1454
|
+
return this.next(this.parseFalse_l);
|
|
1455
|
+
} else {
|
|
1456
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
parseFalse_l() {
|
|
1460
|
+
if (this.char === CHAR_l) {
|
|
1461
|
+
this.consume();
|
|
1462
|
+
return this.next(this.parseFalse_s);
|
|
1463
|
+
} else {
|
|
1464
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
parseFalse_s() {
|
|
1468
|
+
if (this.char === CHAR_s) {
|
|
1469
|
+
this.consume();
|
|
1470
|
+
return this.next(this.parseFalse_e);
|
|
1471
|
+
} else {
|
|
1472
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
parseFalse_e() {
|
|
1476
|
+
if (this.char === CHAR_e) {
|
|
1477
|
+
return this.return(false);
|
|
1478
|
+
} else {
|
|
1479
|
+
throw this.error(new TomlError("Invalid boolean, expected true or false"));
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
/* INLINE LISTS */
|
|
1483
|
+
parseInlineList() {
|
|
1484
|
+
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M || this.char === CTRL_J) {
|
|
1485
|
+
return null;
|
|
1486
|
+
} else if (this.char === Parser.END) {
|
|
1487
|
+
throw this.error(new TomlError("Unterminated inline array"));
|
|
1488
|
+
} else if (this.char === CHAR_NUM) {
|
|
1489
|
+
return this.call(this.parseComment);
|
|
1490
|
+
} else if (this.char === CHAR_RSQB) {
|
|
1491
|
+
return this.return(this.state.resultArr || InlineList());
|
|
1492
|
+
} else {
|
|
1493
|
+
return this.callNow(this.parseValue, this.recordInlineListValue);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
recordInlineListValue(value) {
|
|
1497
|
+
if (this.state.resultArr) {
|
|
1498
|
+
const listType = this.state.resultArr[_contentType];
|
|
1499
|
+
const valueType = tomlType(value);
|
|
1500
|
+
if (listType !== valueType) {
|
|
1501
|
+
throw this.error(new TomlError(`Inline lists must be a single type, not a mix of ${listType} and ${valueType}`));
|
|
1502
|
+
}
|
|
1503
|
+
} else {
|
|
1504
|
+
this.state.resultArr = InlineList(tomlType(value));
|
|
1505
|
+
}
|
|
1506
|
+
if (isFloat(value) || isInteger(value)) {
|
|
1507
|
+
this.state.resultArr.push(value.valueOf());
|
|
1508
|
+
} else {
|
|
1509
|
+
this.state.resultArr.push(value);
|
|
1510
|
+
}
|
|
1511
|
+
return this.goto(this.parseInlineListNext);
|
|
1512
|
+
}
|
|
1513
|
+
parseInlineListNext() {
|
|
1514
|
+
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M || this.char === CTRL_J) {
|
|
1515
|
+
return null;
|
|
1516
|
+
} else if (this.char === CHAR_NUM) {
|
|
1517
|
+
return this.call(this.parseComment);
|
|
1518
|
+
} else if (this.char === CHAR_COMMA) {
|
|
1519
|
+
return this.next(this.parseInlineList);
|
|
1520
|
+
} else if (this.char === CHAR_RSQB) {
|
|
1521
|
+
return this.goto(this.parseInlineList);
|
|
1522
|
+
} else {
|
|
1523
|
+
throw this.error(new TomlError("Invalid character, expected whitespace, comma (,) or close bracket (])"));
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
/* INLINE TABLE */
|
|
1527
|
+
parseInlineTable() {
|
|
1528
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
1529
|
+
return null;
|
|
1530
|
+
} else if (this.char === Parser.END || this.char === CHAR_NUM || this.char === CTRL_J || this.char === CTRL_M) {
|
|
1531
|
+
throw this.error(new TomlError("Unterminated inline array"));
|
|
1532
|
+
} else if (this.char === CHAR_RCUB) {
|
|
1533
|
+
return this.return(this.state.resultTable || InlineTable());
|
|
1534
|
+
} else {
|
|
1535
|
+
if (!this.state.resultTable) this.state.resultTable = InlineTable();
|
|
1536
|
+
return this.callNow(this.parseAssign, this.recordInlineTableValue);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
recordInlineTableValue(kv) {
|
|
1540
|
+
let target = this.state.resultTable;
|
|
1541
|
+
let finalKey = kv.key.pop();
|
|
1542
|
+
for (let kw of kv.key) {
|
|
1543
|
+
if (hasKey(target, kw) && (!isTable(target[kw]) || target[kw][_declared])) {
|
|
1544
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
1545
|
+
}
|
|
1546
|
+
target = target[kw] = target[kw] || Table();
|
|
1547
|
+
}
|
|
1548
|
+
if (hasKey(target, finalKey)) {
|
|
1549
|
+
throw this.error(new TomlError("Can't redefine existing key"));
|
|
1550
|
+
}
|
|
1551
|
+
if (isInteger(kv.value) || isFloat(kv.value)) {
|
|
1552
|
+
target[finalKey] = kv.value.valueOf();
|
|
1553
|
+
} else {
|
|
1554
|
+
target[finalKey] = kv.value;
|
|
1555
|
+
}
|
|
1556
|
+
return this.goto(this.parseInlineTableNext);
|
|
1557
|
+
}
|
|
1558
|
+
parseInlineTableNext() {
|
|
1559
|
+
if (this.char === CHAR_SP || this.char === CTRL_I) {
|
|
1560
|
+
return null;
|
|
1561
|
+
} else if (this.char === Parser.END || this.char === CHAR_NUM || this.char === CTRL_J || this.char === CTRL_M) {
|
|
1562
|
+
throw this.error(new TomlError("Unterminated inline array"));
|
|
1563
|
+
} else if (this.char === CHAR_COMMA) {
|
|
1564
|
+
return this.next(this.parseInlineTable);
|
|
1565
|
+
} else if (this.char === CHAR_RCUB) {
|
|
1566
|
+
return this.goto(this.parseInlineTable);
|
|
1567
|
+
} else {
|
|
1568
|
+
throw this.error(new TomlError("Invalid character, expected whitespace, comma (,) or close bracket (])"));
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return TOMLParser;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-pretty-error.js
|
|
1578
|
+
var require_parse_pretty_error = __commonJS({
|
|
1579
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-pretty-error.js"(exports2, module2) {
|
|
1580
|
+
"use strict";
|
|
1581
|
+
module2.exports = prettyError;
|
|
1582
|
+
function prettyError(err, buf) {
|
|
1583
|
+
if (err.pos == null || err.line == null) return err;
|
|
1584
|
+
let msg = err.message;
|
|
1585
|
+
msg += ` at row ${err.line + 1}, col ${err.col + 1}, pos ${err.pos}:
|
|
1586
|
+
`;
|
|
1587
|
+
if (buf && buf.split) {
|
|
1588
|
+
const lines = buf.split(/\n/);
|
|
1589
|
+
const lineNumWidth = String(Math.min(lines.length, err.line + 3)).length;
|
|
1590
|
+
let linePadding = " ";
|
|
1591
|
+
while (linePadding.length < lineNumWidth) linePadding += " ";
|
|
1592
|
+
for (let ii = Math.max(0, err.line - 1); ii < Math.min(lines.length, err.line + 2); ++ii) {
|
|
1593
|
+
let lineNum = String(ii + 1);
|
|
1594
|
+
if (lineNum.length < lineNumWidth) lineNum = " " + lineNum;
|
|
1595
|
+
if (err.line === ii) {
|
|
1596
|
+
msg += lineNum + "> " + lines[ii] + "\n";
|
|
1597
|
+
msg += linePadding + " ";
|
|
1598
|
+
for (let hh = 0; hh < err.col; ++hh) {
|
|
1599
|
+
msg += " ";
|
|
1600
|
+
}
|
|
1601
|
+
msg += "^\n";
|
|
1602
|
+
} else {
|
|
1603
|
+
msg += lineNum + ": " + lines[ii] + "\n";
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
err.message = msg + "\n";
|
|
1608
|
+
return err;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-string.js
|
|
1614
|
+
var require_parse_string = __commonJS({
|
|
1615
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-string.js"(exports2, module2) {
|
|
1616
|
+
"use strict";
|
|
1617
|
+
module2.exports = parseString;
|
|
1618
|
+
var TOMLParser = require_toml_parser();
|
|
1619
|
+
var prettyError = require_parse_pretty_error();
|
|
1620
|
+
function parseString(str) {
|
|
1621
|
+
if (global.Buffer && global.Buffer.isBuffer(str)) {
|
|
1622
|
+
str = str.toString("utf8");
|
|
1623
|
+
}
|
|
1624
|
+
const parser = new TOMLParser();
|
|
1625
|
+
try {
|
|
1626
|
+
parser.parse(str);
|
|
1627
|
+
return parser.finish();
|
|
1628
|
+
} catch (err) {
|
|
1629
|
+
throw prettyError(err, str);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-async.js
|
|
1636
|
+
var require_parse_async = __commonJS({
|
|
1637
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-async.js"(exports2, module2) {
|
|
1638
|
+
"use strict";
|
|
1639
|
+
module2.exports = parseAsync;
|
|
1640
|
+
var TOMLParser = require_toml_parser();
|
|
1641
|
+
var prettyError = require_parse_pretty_error();
|
|
1642
|
+
function parseAsync(str, opts) {
|
|
1643
|
+
if (!opts) opts = {};
|
|
1644
|
+
const index = 0;
|
|
1645
|
+
const blocksize = opts.blocksize || 40960;
|
|
1646
|
+
const parser = new TOMLParser();
|
|
1647
|
+
return new Promise((resolve2, reject) => {
|
|
1648
|
+
setImmediate(parseAsyncNext, index, blocksize, resolve2, reject);
|
|
1649
|
+
});
|
|
1650
|
+
function parseAsyncNext(index2, blocksize2, resolve2, reject) {
|
|
1651
|
+
if (index2 >= str.length) {
|
|
1652
|
+
try {
|
|
1653
|
+
return resolve2(parser.finish());
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
return reject(prettyError(err, str));
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
try {
|
|
1659
|
+
parser.parse(str.slice(index2, index2 + blocksize2));
|
|
1660
|
+
setImmediate(parseAsyncNext, index2 + blocksize2, blocksize2, resolve2, reject);
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
reject(prettyError(err, str));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-stream.js
|
|
1670
|
+
var require_parse_stream = __commonJS({
|
|
1671
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse-stream.js"(exports2, module2) {
|
|
1672
|
+
"use strict";
|
|
1673
|
+
module2.exports = parseStream;
|
|
1674
|
+
var stream = __require("stream");
|
|
1675
|
+
var TOMLParser = require_toml_parser();
|
|
1676
|
+
function parseStream(stm) {
|
|
1677
|
+
if (stm) {
|
|
1678
|
+
return parseReadable(stm);
|
|
1679
|
+
} else {
|
|
1680
|
+
return parseTransform(stm);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
function parseReadable(stm) {
|
|
1684
|
+
const parser = new TOMLParser();
|
|
1685
|
+
stm.setEncoding("utf8");
|
|
1686
|
+
return new Promise((resolve2, reject) => {
|
|
1687
|
+
let readable;
|
|
1688
|
+
let ended = false;
|
|
1689
|
+
let errored = false;
|
|
1690
|
+
function finish() {
|
|
1691
|
+
ended = true;
|
|
1692
|
+
if (readable) return;
|
|
1693
|
+
try {
|
|
1694
|
+
resolve2(parser.finish());
|
|
1695
|
+
} catch (err) {
|
|
1696
|
+
reject(err);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function error(err) {
|
|
1700
|
+
errored = true;
|
|
1701
|
+
reject(err);
|
|
1702
|
+
}
|
|
1703
|
+
stm.once("end", finish);
|
|
1704
|
+
stm.once("error", error);
|
|
1705
|
+
readNext();
|
|
1706
|
+
function readNext() {
|
|
1707
|
+
readable = true;
|
|
1708
|
+
let data;
|
|
1709
|
+
while ((data = stm.read()) !== null) {
|
|
1710
|
+
try {
|
|
1711
|
+
parser.parse(data);
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
return error(err);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
readable = false;
|
|
1717
|
+
if (ended) return finish();
|
|
1718
|
+
if (errored) return;
|
|
1719
|
+
stm.once("readable", readNext);
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
function parseTransform() {
|
|
1724
|
+
const parser = new TOMLParser();
|
|
1725
|
+
return new stream.Transform({
|
|
1726
|
+
objectMode: true,
|
|
1727
|
+
transform(chunk, encoding, cb) {
|
|
1728
|
+
try {
|
|
1729
|
+
parser.parse(chunk.toString(encoding));
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
this.emit("error", err);
|
|
1732
|
+
}
|
|
1733
|
+
cb();
|
|
1734
|
+
},
|
|
1735
|
+
flush(cb) {
|
|
1736
|
+
try {
|
|
1737
|
+
this.push(parser.finish());
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
this.emit("error", err);
|
|
1740
|
+
}
|
|
1741
|
+
cb();
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse.js
|
|
1749
|
+
var require_parse = __commonJS({
|
|
1750
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/parse.js"(exports2, module2) {
|
|
1751
|
+
"use strict";
|
|
1752
|
+
module2.exports = require_parse_string();
|
|
1753
|
+
module2.exports.async = require_parse_async();
|
|
1754
|
+
module2.exports.stream = require_parse_stream();
|
|
1755
|
+
module2.exports.prettyError = require_parse_pretty_error();
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/stringify.js
|
|
1760
|
+
var require_stringify = __commonJS({
|
|
1761
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/stringify.js"(exports2, module2) {
|
|
1762
|
+
"use strict";
|
|
1763
|
+
module2.exports = stringify;
|
|
1764
|
+
module2.exports.value = stringifyInline;
|
|
1765
|
+
function stringify(obj) {
|
|
1766
|
+
if (obj === null) throw typeError("null");
|
|
1767
|
+
if (obj === void 0) throw typeError("undefined");
|
|
1768
|
+
if (typeof obj !== "object") throw typeError(typeof obj);
|
|
1769
|
+
if (typeof obj.toJSON === "function") obj = obj.toJSON();
|
|
1770
|
+
if (obj == null) return null;
|
|
1771
|
+
const type = tomlType2(obj);
|
|
1772
|
+
if (type !== "table") throw typeError(type);
|
|
1773
|
+
return stringifyObject("", "", obj);
|
|
1774
|
+
}
|
|
1775
|
+
function typeError(type) {
|
|
1776
|
+
return new Error("Can only stringify objects, not " + type);
|
|
1777
|
+
}
|
|
1778
|
+
function arrayOneTypeError() {
|
|
1779
|
+
return new Error("Array values can't have mixed types");
|
|
1780
|
+
}
|
|
1781
|
+
function getInlineKeys(obj) {
|
|
1782
|
+
return Object.keys(obj).filter((key) => isInline(obj[key]));
|
|
1783
|
+
}
|
|
1784
|
+
function getComplexKeys(obj) {
|
|
1785
|
+
return Object.keys(obj).filter((key) => !isInline(obj[key]));
|
|
1786
|
+
}
|
|
1787
|
+
function toJSON(obj) {
|
|
1788
|
+
let nobj = Array.isArray(obj) ? [] : Object.prototype.hasOwnProperty.call(obj, "__proto__") ? { ["__proto__"]: void 0 } : {};
|
|
1789
|
+
for (let prop of Object.keys(obj)) {
|
|
1790
|
+
if (obj[prop] && typeof obj[prop].toJSON === "function" && !("toISOString" in obj[prop])) {
|
|
1791
|
+
nobj[prop] = obj[prop].toJSON();
|
|
1792
|
+
} else {
|
|
1793
|
+
nobj[prop] = obj[prop];
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return nobj;
|
|
1797
|
+
}
|
|
1798
|
+
function stringifyObject(prefix, indent, obj) {
|
|
1799
|
+
obj = toJSON(obj);
|
|
1800
|
+
var inlineKeys;
|
|
1801
|
+
var complexKeys;
|
|
1802
|
+
inlineKeys = getInlineKeys(obj);
|
|
1803
|
+
complexKeys = getComplexKeys(obj);
|
|
1804
|
+
var result = [];
|
|
1805
|
+
var inlineIndent = indent || "";
|
|
1806
|
+
inlineKeys.forEach((key) => {
|
|
1807
|
+
var type = tomlType2(obj[key]);
|
|
1808
|
+
if (type !== "undefined" && type !== "null") {
|
|
1809
|
+
result.push(inlineIndent + stringifyKey(key) + " = " + stringifyAnyInline(obj[key], true));
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
if (result.length > 0) result.push("");
|
|
1813
|
+
var complexIndent = prefix && inlineKeys.length > 0 ? indent + " " : "";
|
|
1814
|
+
complexKeys.forEach((key) => {
|
|
1815
|
+
result.push(stringifyComplex(prefix, complexIndent, key, obj[key]));
|
|
1816
|
+
});
|
|
1817
|
+
return result.join("\n");
|
|
1818
|
+
}
|
|
1819
|
+
function isInline(value) {
|
|
1820
|
+
switch (tomlType2(value)) {
|
|
1821
|
+
case "undefined":
|
|
1822
|
+
case "null":
|
|
1823
|
+
case "integer":
|
|
1824
|
+
case "nan":
|
|
1825
|
+
case "float":
|
|
1826
|
+
case "boolean":
|
|
1827
|
+
case "string":
|
|
1828
|
+
case "datetime":
|
|
1829
|
+
return true;
|
|
1830
|
+
case "array":
|
|
1831
|
+
return value.length === 0 || tomlType2(value[0]) !== "table";
|
|
1832
|
+
case "table":
|
|
1833
|
+
return Object.keys(value).length === 0;
|
|
1834
|
+
/* istanbul ignore next */
|
|
1835
|
+
default:
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function tomlType2(value) {
|
|
1840
|
+
if (value === void 0) {
|
|
1841
|
+
return "undefined";
|
|
1842
|
+
} else if (value === null) {
|
|
1843
|
+
return "null";
|
|
1844
|
+
} else if (typeof value === "bigint" || Number.isInteger(value) && !Object.is(value, -0)) {
|
|
1845
|
+
return "integer";
|
|
1846
|
+
} else if (typeof value === "number") {
|
|
1847
|
+
return "float";
|
|
1848
|
+
} else if (typeof value === "boolean") {
|
|
1849
|
+
return "boolean";
|
|
1850
|
+
} else if (typeof value === "string") {
|
|
1851
|
+
return "string";
|
|
1852
|
+
} else if ("toISOString" in value) {
|
|
1853
|
+
return isNaN(value) ? "undefined" : "datetime";
|
|
1854
|
+
} else if (Array.isArray(value)) {
|
|
1855
|
+
return "array";
|
|
1856
|
+
} else {
|
|
1857
|
+
return "table";
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
function stringifyKey(key) {
|
|
1861
|
+
var keyStr = String(key);
|
|
1862
|
+
if (/^[-A-Za-z0-9_]+$/.test(keyStr)) {
|
|
1863
|
+
return keyStr;
|
|
1864
|
+
} else {
|
|
1865
|
+
return stringifyBasicString(keyStr);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
function stringifyBasicString(str) {
|
|
1869
|
+
return '"' + escapeString(str).replace(/"/g, '\\"') + '"';
|
|
1870
|
+
}
|
|
1871
|
+
function stringifyLiteralString(str) {
|
|
1872
|
+
return "'" + str + "'";
|
|
1873
|
+
}
|
|
1874
|
+
function numpad(num, str) {
|
|
1875
|
+
while (str.length < num) str = "0" + str;
|
|
1876
|
+
return str;
|
|
1877
|
+
}
|
|
1878
|
+
function escapeString(str) {
|
|
1879
|
+
return str.replace(/\\/g, "\\\\").replace(/[\b]/g, "\\b").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\f/g, "\\f").replace(/\r/g, "\\r").replace(/([\u0000-\u001f\u007f])/, (c) => "\\u" + numpad(4, c.codePointAt(0).toString(16)));
|
|
1880
|
+
}
|
|
1881
|
+
function stringifyMultilineString(str) {
|
|
1882
|
+
let escaped = str.split(/\n/).map((str2) => {
|
|
1883
|
+
return escapeString(str2).replace(/"(?="")/g, '\\"');
|
|
1884
|
+
}).join("\n");
|
|
1885
|
+
if (escaped.slice(-1) === '"') escaped += "\\\n";
|
|
1886
|
+
return '"""\n' + escaped + '"""';
|
|
1887
|
+
}
|
|
1888
|
+
function stringifyAnyInline(value, multilineOk) {
|
|
1889
|
+
let type = tomlType2(value);
|
|
1890
|
+
if (type === "string") {
|
|
1891
|
+
if (multilineOk && /\n/.test(value)) {
|
|
1892
|
+
type = "string-multiline";
|
|
1893
|
+
} else if (!/[\b\t\n\f\r']/.test(value) && /"/.test(value)) {
|
|
1894
|
+
type = "string-literal";
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return stringifyInline(value, type);
|
|
1898
|
+
}
|
|
1899
|
+
function stringifyInline(value, type) {
|
|
1900
|
+
if (!type) type = tomlType2(value);
|
|
1901
|
+
switch (type) {
|
|
1902
|
+
case "string-multiline":
|
|
1903
|
+
return stringifyMultilineString(value);
|
|
1904
|
+
case "string":
|
|
1905
|
+
return stringifyBasicString(value);
|
|
1906
|
+
case "string-literal":
|
|
1907
|
+
return stringifyLiteralString(value);
|
|
1908
|
+
case "integer":
|
|
1909
|
+
return stringifyInteger(value);
|
|
1910
|
+
case "float":
|
|
1911
|
+
return stringifyFloat(value);
|
|
1912
|
+
case "boolean":
|
|
1913
|
+
return stringifyBoolean(value);
|
|
1914
|
+
case "datetime":
|
|
1915
|
+
return stringifyDatetime(value);
|
|
1916
|
+
case "array":
|
|
1917
|
+
return stringifyInlineArray(value.filter((_) => tomlType2(_) !== "null" && tomlType2(_) !== "undefined" && tomlType2(_) !== "nan"));
|
|
1918
|
+
case "table":
|
|
1919
|
+
return stringifyInlineTable(value);
|
|
1920
|
+
/* istanbul ignore next */
|
|
1921
|
+
default:
|
|
1922
|
+
throw typeError(type);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function stringifyInteger(value) {
|
|
1926
|
+
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, "_");
|
|
1927
|
+
}
|
|
1928
|
+
function stringifyFloat(value) {
|
|
1929
|
+
if (value === Infinity) {
|
|
1930
|
+
return "inf";
|
|
1931
|
+
} else if (value === -Infinity) {
|
|
1932
|
+
return "-inf";
|
|
1933
|
+
} else if (Object.is(value, NaN)) {
|
|
1934
|
+
return "nan";
|
|
1935
|
+
} else if (Object.is(value, -0)) {
|
|
1936
|
+
return "-0.0";
|
|
1937
|
+
}
|
|
1938
|
+
var chunks = String(value).split(".");
|
|
1939
|
+
var int = chunks[0];
|
|
1940
|
+
var dec = chunks[1] || 0;
|
|
1941
|
+
return stringifyInteger(int) + "." + dec;
|
|
1942
|
+
}
|
|
1943
|
+
function stringifyBoolean(value) {
|
|
1944
|
+
return String(value);
|
|
1945
|
+
}
|
|
1946
|
+
function stringifyDatetime(value) {
|
|
1947
|
+
return value.toISOString();
|
|
1948
|
+
}
|
|
1949
|
+
function isNumber(type) {
|
|
1950
|
+
return type === "float" || type === "integer";
|
|
1951
|
+
}
|
|
1952
|
+
function arrayType(values) {
|
|
1953
|
+
var contentType = tomlType2(values[0]);
|
|
1954
|
+
if (values.every((_) => tomlType2(_) === contentType)) return contentType;
|
|
1955
|
+
if (values.every((_) => isNumber(tomlType2(_)))) return "float";
|
|
1956
|
+
return "mixed";
|
|
1957
|
+
}
|
|
1958
|
+
function validateArray(values) {
|
|
1959
|
+
const type = arrayType(values);
|
|
1960
|
+
if (type === "mixed") {
|
|
1961
|
+
throw arrayOneTypeError();
|
|
1962
|
+
}
|
|
1963
|
+
return type;
|
|
1964
|
+
}
|
|
1965
|
+
function stringifyInlineArray(values) {
|
|
1966
|
+
values = toJSON(values);
|
|
1967
|
+
const type = validateArray(values);
|
|
1968
|
+
var result = "[";
|
|
1969
|
+
var stringified = values.map((_) => stringifyInline(_, type));
|
|
1970
|
+
if (stringified.join(", ").length > 60 || /\n/.test(stringified)) {
|
|
1971
|
+
result += "\n " + stringified.join(",\n ") + "\n";
|
|
1972
|
+
} else {
|
|
1973
|
+
result += " " + stringified.join(", ") + (stringified.length > 0 ? " " : "");
|
|
1974
|
+
}
|
|
1975
|
+
return result + "]";
|
|
1976
|
+
}
|
|
1977
|
+
function stringifyInlineTable(value) {
|
|
1978
|
+
value = toJSON(value);
|
|
1979
|
+
var result = [];
|
|
1980
|
+
Object.keys(value).forEach((key) => {
|
|
1981
|
+
result.push(stringifyKey(key) + " = " + stringifyAnyInline(value[key], false));
|
|
1982
|
+
});
|
|
1983
|
+
return "{ " + result.join(", ") + (result.length > 0 ? " " : "") + "}";
|
|
1984
|
+
}
|
|
1985
|
+
function stringifyComplex(prefix, indent, key, value) {
|
|
1986
|
+
var valueType = tomlType2(value);
|
|
1987
|
+
if (valueType === "array") {
|
|
1988
|
+
return stringifyArrayOfTables(prefix, indent, key, value);
|
|
1989
|
+
} else if (valueType === "table") {
|
|
1990
|
+
return stringifyComplexTable(prefix, indent, key, value);
|
|
1991
|
+
} else {
|
|
1992
|
+
throw typeError(valueType);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function stringifyArrayOfTables(prefix, indent, key, values) {
|
|
1996
|
+
values = toJSON(values);
|
|
1997
|
+
validateArray(values);
|
|
1998
|
+
var firstValueType = tomlType2(values[0]);
|
|
1999
|
+
if (firstValueType !== "table") throw typeError(firstValueType);
|
|
2000
|
+
var fullKey = prefix + stringifyKey(key);
|
|
2001
|
+
var result = "";
|
|
2002
|
+
values.forEach((table) => {
|
|
2003
|
+
if (result.length > 0) result += "\n";
|
|
2004
|
+
result += indent + "[[" + fullKey + "]]\n";
|
|
2005
|
+
result += stringifyObject(fullKey + ".", indent, table);
|
|
2006
|
+
});
|
|
2007
|
+
return result;
|
|
2008
|
+
}
|
|
2009
|
+
function stringifyComplexTable(prefix, indent, key, value) {
|
|
2010
|
+
var fullKey = prefix + stringifyKey(key);
|
|
2011
|
+
var result = "";
|
|
2012
|
+
if (getInlineKeys(value).length > 0) {
|
|
2013
|
+
result += indent + "[" + fullKey + "]\n";
|
|
2014
|
+
}
|
|
2015
|
+
return result + stringifyObject(fullKey + ".", indent, value);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
// ../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/toml.js
|
|
2021
|
+
var require_toml = __commonJS({
|
|
2022
|
+
"../../node_modules/.pnpm/@iarna+toml@2.2.5/node_modules/@iarna/toml/toml.js"(exports2) {
|
|
2023
|
+
"use strict";
|
|
2024
|
+
exports2.parse = require_parse();
|
|
2025
|
+
exports2.stringify = require_stringify();
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
// src/wire.ts
|
|
2030
|
+
var CLI_VERSION = "1.0.8";
|
|
2031
|
+
var CLIENT_HEADER = "x-balladeer-client";
|
|
2032
|
+
var CLIENT_HEADER_VALUE = `balladeer/${CLI_VERSION}`;
|
|
2033
|
+
var DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
|
|
2034
|
+
|
|
2035
|
+
// src/store.ts
|
|
2036
|
+
import {
|
|
2037
|
+
closeSync,
|
|
2038
|
+
fchmodSync,
|
|
2039
|
+
fsyncSync,
|
|
2040
|
+
lstatSync,
|
|
2041
|
+
mkdirSync,
|
|
2042
|
+
openSync,
|
|
2043
|
+
readFileSync,
|
|
2044
|
+
renameSync,
|
|
2045
|
+
unlinkSync,
|
|
2046
|
+
writeSync
|
|
2047
|
+
} from "node:fs";
|
|
2048
|
+
import { homedir } from "node:os";
|
|
2049
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
2050
|
+
var StoreError = class extends Error {
|
|
2051
|
+
code;
|
|
2052
|
+
constructor(code, message, options) {
|
|
2053
|
+
super(message, options);
|
|
2054
|
+
this.name = "StoreError";
|
|
2055
|
+
this.code = code;
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
var EMPTY = { version: 1, pendingPairings: [], sessions: [], agents: [] };
|
|
2059
|
+
function configHome(environment = process.env) {
|
|
2060
|
+
const explicit = environment.BALLADEER_CONFIG_HOME?.trim();
|
|
2061
|
+
if (explicit) return resolve(explicit);
|
|
2062
|
+
const xdg = environment.XDG_CONFIG_HOME?.trim();
|
|
2063
|
+
if (xdg) return resolve(join(xdg, "balladeer"));
|
|
2064
|
+
return resolve(join(homedir(), ".config", "balladeer"));
|
|
2065
|
+
}
|
|
2066
|
+
function credentialsPath(environment = process.env) {
|
|
2067
|
+
return join(configHome(environment), "credentials.json");
|
|
2068
|
+
}
|
|
2069
|
+
function assertPrivate(path, kind) {
|
|
2070
|
+
const stats = lstatSync(path);
|
|
2071
|
+
if (stats.isSymbolicLink()) {
|
|
2072
|
+
throw new StoreError(
|
|
2073
|
+
"credential_store_unsafe",
|
|
2074
|
+
`${path} is a symbolic link. Balladeer will not follow one to write a credential.`
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
if ((stats.mode & 63) !== 0) {
|
|
2078
|
+
throw new StoreError(
|
|
2079
|
+
"credential_store_unsafe",
|
|
2080
|
+
`${path} is readable by other users. Run: chmod ${kind === "directory" ? "700" : "600"} ${path}`
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function storeAccessError(error, path, operation) {
|
|
2085
|
+
if (error instanceof StoreError) return error;
|
|
2086
|
+
const code = error?.code;
|
|
2087
|
+
const permissionDenied = code === "EACCES" || code === "EPERM";
|
|
2088
|
+
return new StoreError(
|
|
2089
|
+
operation === "read" ? "credential_store_unreadable" : "credential_store_unwritable",
|
|
2090
|
+
permissionDenied ? `Balladeer cannot ${operation} its local credential store at ${path}. Allow this command to access that private folder in your terminal or coding agent, then rerun the same command. Existing saved credentials were not replaced.` : `Balladeer could not ${operation} its local credential store at ${path} (${code ?? "filesystem error"}). Check that this location is a private, accessible directory, then retry. Existing saved credentials were not replaced.`,
|
|
2091
|
+
{ cause: error }
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
function readCredentials(environment = process.env) {
|
|
2095
|
+
const path = credentialsPath(environment);
|
|
2096
|
+
let raw;
|
|
2097
|
+
try {
|
|
2098
|
+
assertPrivate(path, "file");
|
|
2099
|
+
raw = readFileSync(path, "utf8");
|
|
2100
|
+
} catch (error) {
|
|
2101
|
+
if (error?.code === "ENOENT") {
|
|
2102
|
+
return { ...EMPTY, pendingPairings: [], sessions: [], agents: [] };
|
|
2103
|
+
}
|
|
2104
|
+
throw storeAccessError(error, path, "read");
|
|
2105
|
+
}
|
|
2106
|
+
try {
|
|
2107
|
+
const parsed = JSON.parse(raw);
|
|
2108
|
+
return {
|
|
2109
|
+
version: 1,
|
|
2110
|
+
pendingPairings: Array.isArray(parsed.pendingPairings) ? parsed.pendingPairings : [],
|
|
2111
|
+
sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
|
|
2112
|
+
agents: Array.isArray(parsed.agents) ? parsed.agents : []
|
|
2113
|
+
};
|
|
2114
|
+
} catch {
|
|
2115
|
+
throw new StoreError(
|
|
2116
|
+
"credential_store_unreadable",
|
|
2117
|
+
`${path} is not valid JSON. Balladeer will not overwrite a file it cannot read.`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/guidance.ts
|
|
2123
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2124
|
+
import {
|
|
2125
|
+
constants,
|
|
2126
|
+
closeSync as closeSync2,
|
|
2127
|
+
fstatSync,
|
|
2128
|
+
lstatSync as lstatSync2,
|
|
2129
|
+
mkdirSync as mkdirSync2,
|
|
2130
|
+
openSync as openSync2,
|
|
2131
|
+
readFileSync as readFileSync2,
|
|
2132
|
+
renameSync as renameSync2,
|
|
2133
|
+
unlinkSync as unlinkSync2,
|
|
2134
|
+
writeFileSync
|
|
2135
|
+
} from "node:fs";
|
|
2136
|
+
import { join as join2 } from "node:path";
|
|
2137
|
+
var GUIDANCE_UNAVAILABLE = "Balladeer could not verify the current workspace guidance and capture mode. Continue the user's authorized work, but do not make unsolicited capture offers or file inferred promises. Do not reuse earlier Quiet/Thorough permission or cached instructions as current. An explicit request to record still requires current Balladeer tool checks and named-human agreement to meaning; never invent approval. Retry current guidance at the next hook boundary.";
|
|
2138
|
+
var HEX = /^[a-f0-9]{64}$/;
|
|
2139
|
+
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
2140
|
+
var MAX_BYTES = 4e4;
|
|
2141
|
+
var sha = (value) => createHash("sha256").update(value).digest("hex");
|
|
2142
|
+
function object(value) {
|
|
2143
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2144
|
+
}
|
|
2145
|
+
function parseGuidance(value, agent) {
|
|
2146
|
+
if (!object(value) || Object.keys(value).sort().join(",") !== "captureStyle,digest,instructions,repositoryId,revision,schemaVersion,workspaceId" || value.schemaVersion !== 1 || value.workspaceId !== agent.workspaceId || value.repositoryId !== agent.repositoryId || typeof value.revision !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/.test(value.revision) || typeof value.digest !== "string" || !HEX.test(value.digest) || !["quiet", "thorough", "off"].includes(String(value.captureStyle)) || typeof value.instructions !== "string" || value.instructions.length === 0 || value.instructions.length > 8e3 || sha(value.instructions) !== value.digest) {
|
|
2147
|
+
throw new Error("invalid_guidance");
|
|
2148
|
+
}
|
|
2149
|
+
return value;
|
|
2150
|
+
}
|
|
2151
|
+
function guidanceEndpoint(agent) {
|
|
2152
|
+
const url = new URL(agent.mcpUrl);
|
|
2153
|
+
const expected = new URL(agent.controlPlane);
|
|
2154
|
+
if (url.origin !== expected.origin || url.protocol !== "https:" || url.pathname !== "/api/mcp" || url.username || url.password || url.hash || !UUID.test(agent.workspaceId) || !UUID.test(agent.repositoryId))
|
|
2155
|
+
throw new Error("invalid_guidance_scope");
|
|
2156
|
+
for (const [key, value] of [
|
|
2157
|
+
["workspaceId", agent.workspaceId],
|
|
2158
|
+
["repositoryId", agent.repositoryId]
|
|
2159
|
+
]) {
|
|
2160
|
+
if (url.searchParams.getAll(key).length > 1 || url.searchParams.has(key) && url.searchParams.get(key) !== value)
|
|
2161
|
+
throw new Error("invalid_guidance_scope");
|
|
2162
|
+
url.searchParams.set(key, value);
|
|
2163
|
+
}
|
|
2164
|
+
url.pathname = "/api/agent-guidance";
|
|
2165
|
+
return url;
|
|
2166
|
+
}
|
|
2167
|
+
function readJson(path) {
|
|
2168
|
+
let fd;
|
|
2169
|
+
try {
|
|
2170
|
+
fd = openSync2(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
2171
|
+
const stat = fstatSync(fd);
|
|
2172
|
+
if (!stat.isFile() || stat.size > MAX_BYTES || (stat.mode & 63) !== 0) return void 0;
|
|
2173
|
+
return JSON.parse(readFileSync2(fd, "utf8"));
|
|
2174
|
+
} catch {
|
|
2175
|
+
return void 0;
|
|
2176
|
+
} finally {
|
|
2177
|
+
if (fd !== void 0) closeSync2(fd);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
function privateDirectory(path) {
|
|
2181
|
+
try {
|
|
2182
|
+
mkdirSync2(path, { mode: 448 });
|
|
2183
|
+
} catch {
|
|
2184
|
+
}
|
|
2185
|
+
const stat = lstatSync2(path);
|
|
2186
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 63) !== 0)
|
|
2187
|
+
throw new Error("cache_unavailable");
|
|
2188
|
+
}
|
|
2189
|
+
function atomicJson(path, value) {
|
|
2190
|
+
const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
2191
|
+
try {
|
|
2192
|
+
writeFileSync(temporary, `${JSON.stringify(value)}
|
|
2193
|
+
`, { flag: "wx", mode: 384 });
|
|
2194
|
+
renameSync2(temporary, path);
|
|
2195
|
+
} finally {
|
|
2196
|
+
try {
|
|
2197
|
+
unlinkSync2(temporary);
|
|
2198
|
+
} catch {
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function guidanceScopeKey(agent) {
|
|
2203
|
+
return sha(
|
|
2204
|
+
JSON.stringify([
|
|
2205
|
+
new URL(agent.controlPlane).origin,
|
|
2206
|
+
sha(agent.token),
|
|
2207
|
+
agent.workspaceId,
|
|
2208
|
+
agent.repositoryId,
|
|
2209
|
+
1
|
|
2210
|
+
])
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
function cacheDirectory(environment) {
|
|
2214
|
+
const home = configHome(environment);
|
|
2215
|
+
privateDirectory(home);
|
|
2216
|
+
const directory = join2(home, "guidance-v1");
|
|
2217
|
+
privateDirectory(directory);
|
|
2218
|
+
return directory;
|
|
2219
|
+
}
|
|
2220
|
+
function cached(directory, scopeKey, agent) {
|
|
2221
|
+
try {
|
|
2222
|
+
const pointer = readJson(join2(directory, `${scopeKey}.current.json`));
|
|
2223
|
+
if (!object(pointer) || typeof pointer.key !== "string" || !HEX.test(pointer.key))
|
|
2224
|
+
return void 0;
|
|
2225
|
+
const body = readJson(join2(directory, `${pointer.key}.body.json`));
|
|
2226
|
+
if (!object(body) || typeof body.etag !== "string" || !/^"[a-f0-9]{64}"$/.test(body.etag))
|
|
2227
|
+
return void 0;
|
|
2228
|
+
const document = parseGuidance(body.document, agent);
|
|
2229
|
+
if (pointer.key !== sha(JSON.stringify([scopeKey, document.revision]))) return void 0;
|
|
2230
|
+
return { document, etag: body.etag, key: pointer.key };
|
|
2231
|
+
} catch {
|
|
2232
|
+
return void 0;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
async function boundedBody(response) {
|
|
2236
|
+
const declared = Number(response.headers.get("content-length") ?? 0);
|
|
2237
|
+
if (declared > MAX_BYTES) throw new Error("guidance_oversized");
|
|
2238
|
+
const reader = response.body?.getReader();
|
|
2239
|
+
if (!reader) throw new Error("guidance_empty");
|
|
2240
|
+
const buffers = [];
|
|
2241
|
+
let bytes = 0;
|
|
2242
|
+
try {
|
|
2243
|
+
while (true) {
|
|
2244
|
+
const part = await reader.read();
|
|
2245
|
+
if (part.done) break;
|
|
2246
|
+
bytes += part.value.byteLength;
|
|
2247
|
+
if (bytes > MAX_BYTES) throw new Error("guidance_oversized");
|
|
2248
|
+
buffers.push(part.value);
|
|
2249
|
+
}
|
|
2250
|
+
return JSON.parse(Buffer.concat(buffers).toString("utf8"));
|
|
2251
|
+
} finally {
|
|
2252
|
+
await reader.cancel().catch(() => void 0);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
async function loadGuidance(input) {
|
|
2256
|
+
const scopeKey = guidanceScopeKey(input.agent);
|
|
2257
|
+
let directory;
|
|
2258
|
+
try {
|
|
2259
|
+
directory = cacheDirectory(input.environment);
|
|
2260
|
+
} catch {
|
|
2261
|
+
}
|
|
2262
|
+
const previous = directory ? cached(directory, scopeKey, input.agent) : void 0;
|
|
2263
|
+
const abort = new AbortController();
|
|
2264
|
+
let timer;
|
|
2265
|
+
try {
|
|
2266
|
+
const task = async () => {
|
|
2267
|
+
const response = await (input.fetchImpl ?? fetch)(guidanceEndpoint(input.agent), {
|
|
2268
|
+
method: "GET",
|
|
2269
|
+
redirect: "manual",
|
|
2270
|
+
signal: abort.signal,
|
|
2271
|
+
headers: {
|
|
2272
|
+
authorization: `Bearer ${input.agent.token}`,
|
|
2273
|
+
[CLIENT_HEADER]: CLIENT_HEADER_VALUE,
|
|
2274
|
+
accept: "application/json",
|
|
2275
|
+
...previous ? { "if-none-match": previous.etag } : {}
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
if (response.status === 304) {
|
|
2279
|
+
if (!previous || response.headers.get("etag") !== previous.etag || response.headers.get("x-balladeer-guidance-revision") !== previous.document.revision) {
|
|
2280
|
+
throw new Error("invalid_guidance_revalidation");
|
|
2281
|
+
}
|
|
2282
|
+
return { status: "revalidated", document: previous.document, scopeKey };
|
|
2283
|
+
}
|
|
2284
|
+
if (response.status !== 200) {
|
|
2285
|
+
await response.body?.cancel();
|
|
2286
|
+
throw new Error("guidance_unavailable");
|
|
2287
|
+
}
|
|
2288
|
+
const document = parseGuidance(await boundedBody(response), input.agent);
|
|
2289
|
+
const etag = response.headers.get("etag");
|
|
2290
|
+
if (!etag || !/^"[a-f0-9]{64}"$/.test(etag) || response.headers.get("x-balladeer-guidance-revision") !== document.revision)
|
|
2291
|
+
throw new Error("invalid_guidance_headers");
|
|
2292
|
+
if (abort.signal.aborted) throw new Error("guidance_deadline");
|
|
2293
|
+
if (directory) {
|
|
2294
|
+
try {
|
|
2295
|
+
const key = sha(JSON.stringify([scopeKey, document.revision]));
|
|
2296
|
+
atomicJson(join2(directory, `${key}.body.json`), { document, etag });
|
|
2297
|
+
atomicJson(join2(directory, `${scopeKey}.current.json`), { key });
|
|
2298
|
+
if (previous && previous.key !== key) {
|
|
2299
|
+
try {
|
|
2300
|
+
unlinkSync2(join2(directory, `${previous.key}.body.json`));
|
|
2301
|
+
} catch {
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
} catch {
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
return { status: "fresh", document, scopeKey };
|
|
2308
|
+
};
|
|
2309
|
+
return await Promise.race([
|
|
2310
|
+
task(),
|
|
2311
|
+
new Promise((_, reject) => {
|
|
2312
|
+
timer = setTimeout(
|
|
2313
|
+
() => {
|
|
2314
|
+
abort.abort();
|
|
2315
|
+
reject(new Error("guidance_deadline"));
|
|
2316
|
+
},
|
|
2317
|
+
Math.min(input.timeoutMs ?? 1e3, 1e3)
|
|
2318
|
+
);
|
|
2319
|
+
})
|
|
2320
|
+
]);
|
|
2321
|
+
} catch {
|
|
2322
|
+
abort.abort();
|
|
2323
|
+
return { status: "unavailable", scopeKey };
|
|
2324
|
+
} finally {
|
|
2325
|
+
if (timer) clearTimeout(timer);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function recordGuidanceContext(input, write) {
|
|
2329
|
+
const always = input.event !== "UserPromptSubmit" || input.load.status === "unavailable" || !input.sessionId;
|
|
2330
|
+
const contextKey = sha(
|
|
2331
|
+
JSON.stringify([
|
|
2332
|
+
input.scopeKey,
|
|
2333
|
+
input.hook,
|
|
2334
|
+
input.sessionId ?? randomBytes(16).toString("hex"),
|
|
2335
|
+
input.agentId ?? "root"
|
|
2336
|
+
])
|
|
2337
|
+
);
|
|
2338
|
+
const document = input.load.document;
|
|
2339
|
+
let path;
|
|
2340
|
+
let emitted = true;
|
|
2341
|
+
try {
|
|
2342
|
+
path = join2(cacheDirectory(input.environment), `${contextKey}.context.json`);
|
|
2343
|
+
const previous = readJson(path);
|
|
2344
|
+
emitted = always || !object(previous) || previous.status === "unavailable" || previous.digest !== document?.digest || previous.revision !== document?.revision || (previous.runtimeGeneration ?? null) !== (input.runtimeGeneration ?? null);
|
|
2345
|
+
} catch {
|
|
2346
|
+
}
|
|
2347
|
+
if (!emitted) return false;
|
|
2348
|
+
write();
|
|
2349
|
+
if (path) {
|
|
2350
|
+
try {
|
|
2351
|
+
atomicJson(path, {
|
|
2352
|
+
schemaVersion: 1,
|
|
2353
|
+
contextKey,
|
|
2354
|
+
event: input.event,
|
|
2355
|
+
status: input.load.status,
|
|
2356
|
+
digest: document?.digest ?? null,
|
|
2357
|
+
revision: document?.revision ?? null,
|
|
2358
|
+
runtimeGeneration: input.runtimeGeneration ?? null,
|
|
2359
|
+
emitted: true,
|
|
2360
|
+
emittedAt: new Date(input.now ?? Date.now()).toISOString()
|
|
2361
|
+
});
|
|
2362
|
+
} catch {
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return true;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// src/guidance-install.ts
|
|
2369
|
+
import { execFileSync } from "node:child_process";
|
|
2370
|
+
import {
|
|
2371
|
+
constants as constants2,
|
|
2372
|
+
closeSync as closeSync5,
|
|
2373
|
+
fstatSync as fstatSync2,
|
|
2374
|
+
openSync as openSync5,
|
|
2375
|
+
existsSync as existsSync2,
|
|
2376
|
+
lstatSync as lstatSync4,
|
|
2377
|
+
linkSync,
|
|
2378
|
+
mkdirSync as mkdirSync4,
|
|
2379
|
+
readFileSync as readFileSync5,
|
|
2380
|
+
renameSync as renameSync5,
|
|
2381
|
+
unlinkSync as unlinkSync5,
|
|
2382
|
+
writeFileSync as writeFileSync2
|
|
2383
|
+
} from "node:fs";
|
|
2384
|
+
var import_toml2 = __toESM(require_toml(), 1);
|
|
2385
|
+
import { dirname as dirname3, join as join5, relative } from "node:path";
|
|
2386
|
+
|
|
2387
|
+
// src/codex-config.ts
|
|
2388
|
+
var import_toml = __toESM(require_toml(), 1);
|
|
2389
|
+
import {
|
|
2390
|
+
closeSync as closeSync4,
|
|
2391
|
+
existsSync,
|
|
2392
|
+
fsyncSync as fsyncSync3,
|
|
2393
|
+
lstatSync as lstatSync3,
|
|
2394
|
+
mkdirSync as mkdirSync3,
|
|
2395
|
+
openSync as openSync4,
|
|
2396
|
+
readFileSync as readFileSync4,
|
|
2397
|
+
renameSync as renameSync4,
|
|
2398
|
+
unlinkSync as unlinkSync4,
|
|
2399
|
+
writeSync as writeSync3
|
|
2400
|
+
} from "node:fs";
|
|
2401
|
+
import { join as join4 } from "node:path";
|
|
2402
|
+
|
|
2403
|
+
// src/mcp-config.ts
|
|
2404
|
+
import {
|
|
2405
|
+
closeSync as closeSync3,
|
|
2406
|
+
fsyncSync as fsyncSync2,
|
|
2407
|
+
openSync as openSync3,
|
|
2408
|
+
readFileSync as readFileSync3,
|
|
2409
|
+
renameSync as renameSync3,
|
|
2410
|
+
unlinkSync as unlinkSync3,
|
|
2411
|
+
writeSync as writeSync2
|
|
2412
|
+
} from "node:fs";
|
|
2413
|
+
import { basename, dirname as dirname2, isAbsolute, join as join3 } from "node:path";
|
|
2414
|
+
|
|
2415
|
+
// src/release.ts
|
|
2416
|
+
var PUBLISHED_SPECIFIER = "balladeer@latest";
|
|
2417
|
+
|
|
2418
|
+
// src/mcp-config.ts
|
|
2419
|
+
var MCP_CONFIG_FILE = ".mcp.json";
|
|
2420
|
+
function sameOrigin(left, right) {
|
|
2421
|
+
try {
|
|
2422
|
+
return new URL(left).origin === new URL(right).origin;
|
|
2423
|
+
} catch {
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
function isNodeCommand(command) {
|
|
2428
|
+
const name = basename(command);
|
|
2429
|
+
return name === "node" || name === "node.exe";
|
|
2430
|
+
}
|
|
2431
|
+
function isOurEntry(entry, controlPlane) {
|
|
2432
|
+
if (entry === null || typeof entry !== "object") return false;
|
|
2433
|
+
const record = entry;
|
|
2434
|
+
if (typeof record.url === "string") return sameOrigin(record.url, controlPlane);
|
|
2435
|
+
if (typeof record.command !== "string" || !Array.isArray(record.args)) return false;
|
|
2436
|
+
const args = record.args;
|
|
2437
|
+
if (record.command === "npx") {
|
|
2438
|
+
const specifier = args[1];
|
|
2439
|
+
return typeof specifier === "string" && /^balladeer@(?:latest|\d+\.\d+\.\d+)$/.test(specifier) && args[2] === "mcp";
|
|
2440
|
+
}
|
|
2441
|
+
if (isNodeCommand(record.command)) {
|
|
2442
|
+
const script = args[0];
|
|
2443
|
+
return typeof script === "string" && isAbsolute(script) && basename(script) === "cli.js" && args[1] === "mcp";
|
|
2444
|
+
}
|
|
2445
|
+
return false;
|
|
2446
|
+
}
|
|
2447
|
+
function entryRepositoryId(entry) {
|
|
2448
|
+
const args = entry?.args;
|
|
2449
|
+
if (!Array.isArray(args)) return void 0;
|
|
2450
|
+
const at = args.indexOf("--repository");
|
|
2451
|
+
const value = at === -1 ? void 0 : args[at + 1];
|
|
2452
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2453
|
+
}
|
|
2454
|
+
function currentEntry(parsed) {
|
|
2455
|
+
const servers = parsed?.mcpServers;
|
|
2456
|
+
if (servers === null || typeof servers !== "object") return void 0;
|
|
2457
|
+
return servers.balladeer;
|
|
2458
|
+
}
|
|
2459
|
+
function readMcpConfig(repositoryRoot) {
|
|
2460
|
+
try {
|
|
2461
|
+
return JSON.parse(readFileSync3(join3(repositoryRoot, MCP_CONFIG_FILE), "utf8"));
|
|
2462
|
+
} catch {
|
|
2463
|
+
return void 0;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/codex-config.ts
|
|
2468
|
+
var CODEX_CONFIG_FILE = ".codex/config.toml";
|
|
2469
|
+
function readCodexEntry(root) {
|
|
2470
|
+
try {
|
|
2471
|
+
const config = (0, import_toml.parse)(readFileSync4(join4(root, CODEX_CONFIG_FILE), "utf8"));
|
|
2472
|
+
const servers = config.mcp_servers;
|
|
2473
|
+
return servers && typeof servers === "object" && !Array.isArray(servers) && !(servers instanceof Date) ? servers.balladeer : void 0;
|
|
2474
|
+
} catch {
|
|
2475
|
+
return void 0;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
function hasManagedCodexEntry(root, controlPlane) {
|
|
2479
|
+
try {
|
|
2480
|
+
const text = readFileSync4(join4(root, CODEX_CONFIG_FILE), "utf8");
|
|
2481
|
+
const starts = [...text.matchAll(/^# balladeer:mcp:start\r?$/gm)];
|
|
2482
|
+
const ends = [...text.matchAll(/^# balladeer:mcp:end\r?$/gm)];
|
|
2483
|
+
if (starts.length !== 1 || ends.length !== 1 || !starts[0] || !ends[0] || starts[0].index >= ends[0].index)
|
|
2484
|
+
return false;
|
|
2485
|
+
const block = (0, import_toml.parse)(text.slice(starts[0].index + starts[0][0].length, ends[0].index));
|
|
2486
|
+
const servers = block.mcp_servers;
|
|
2487
|
+
if (Object.keys(block).length !== 1 || !servers || typeof servers !== "object" || Array.isArray(servers) || servers instanceof Date || Object.keys(servers).length !== 1)
|
|
2488
|
+
return false;
|
|
2489
|
+
const own = readCodexEntry(root);
|
|
2490
|
+
return isOurEntry(own, controlPlane) && JSON.stringify(own) === JSON.stringify(servers.balladeer);
|
|
2491
|
+
} catch {
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/copy.ts
|
|
2497
|
+
var RETRIEVAL_SCOPE = `### Reading what is already agreed
|
|
2498
|
+
|
|
2499
|
+
Retrieve by promise, and only when this session has a reason to. One repository here can hold a
|
|
2500
|
+
thousand agreed promises, and a plan built from whatever survived a truncated catalog read is worse
|
|
2501
|
+
than a plan built from none of it, because nothing tells you which half went missing.
|
|
2502
|
+
|
|
2503
|
+
When a person gives you a promise id, expand exactly that one with get_promise and stop there. Every
|
|
2504
|
+
promise page carries a control that copies its id, so an id is what a person hands you when they
|
|
2505
|
+
mean a particular promise. Ask for one rather than searching for what they meant.
|
|
2506
|
+
|
|
2507
|
+
Consult list_promises in two situations and no others. The person asks what this repository has
|
|
2508
|
+
promised, in which case page the index they asked for. Or the change you are about to make touches
|
|
2509
|
+
paths that carry promises, in which case give those paths to list_promises: it answers with the
|
|
2510
|
+
promises whose scope overlaps them, closest first, and with the few that name no path and so cover
|
|
2511
|
+
the whole repository. That answer is a selection rather than a page and does not continue with a
|
|
2512
|
+
cursor, so when the total beside it is larger than what you were handed, narrow the paths rather
|
|
2513
|
+
than asking for more.
|
|
2514
|
+
|
|
2515
|
+
When you do not yet know which paths you are about to touch, do not call the index at all. Work from
|
|
2516
|
+
ids until you do, because a page you did not ask a question of is not about your change, and reading
|
|
2517
|
+
one as though it were is how a plan quietly misses the promise it breaks.
|
|
2518
|
+
|
|
2519
|
+
To learn which paths those are, read \`paths\` on a list_promises answer you asked for
|
|
2520
|
+
without paths of your own. It is the set of
|
|
2521
|
+
repository paths this repository's promises are scoped to, deduplicated and bounded, with
|
|
2522
|
+
\`pathsTruncated\` saying whether there were more than the answer carries. Compare the files you are
|
|
2523
|
+
about to change against it. Nothing matching means there is nothing here to read, and saying so is a
|
|
2524
|
+
better answer than a page of promises about somewhere else.
|
|
2525
|
+
|
|
2526
|
+
Never call get_promise_context at the start of a session. It answers the markers you give it, and
|
|
2527
|
+
before you know what you are changing there are no markers to give: what comes back is a slice of
|
|
2528
|
+
the catalog chosen by nothing. Call it once the work is in front of you, with that work's markers.
|
|
2529
|
+
|
|
2530
|
+
Every one of these reads is bounded and none of them returns the whole catalog. Read the total
|
|
2531
|
+
beside the rows and the sentence in \`scope\` that says what the total is a total of, and when the
|
|
2532
|
+
total is larger than what you were handed, page or narrow rather than planning as though you had
|
|
2533
|
+
seen everything.`;
|
|
2534
|
+
var SESSION_BEHAVIORS = `### Three things to offer without being asked
|
|
2535
|
+
|
|
2536
|
+
At the start of any session in this repository, before you plan anything, ask list_promises for the
|
|
2537
|
+
promises nothing is checking yet that belong to the person you are working with: \`unverified\` true
|
|
2538
|
+
and \`mine\` true, which is one bounded read rather than the catalog. If it returns any, say so in one
|
|
2539
|
+
line and offer to build their verifiers now. Name each one by its one-sentence claim rather than by
|
|
2540
|
+
an id, so the person can see which behavior is unguarded. Ask the same tool for
|
|
2541
|
+
\`brokenSinceLastSeen\` true as well, and where that returns any, say in one line that those promises
|
|
2542
|
+
broke since they last looked, name each by its claim, and offer to fix them. Then wait for their
|
|
2543
|
+
answer. The offer is the whole of it, and never start building or repairing one because nobody said
|
|
2544
|
+
no.
|
|
2545
|
+
|
|
2546
|
+
Theirs, and nobody else's. \`mine\` keeps the promises this person owns or agreed to, and a teammate's
|
|
2547
|
+
unguarded promise is that teammate's to hear about: a session that opens by reading out other
|
|
2548
|
+
people's unfinished work reads as an audit of them. \`brokenSinceLastSeen\` is that person's own by
|
|
2549
|
+
construction and needs no \`mine\` beside it. Drop \`mine\` when this person asks what the rest of the
|
|
2550
|
+
team has promised, and say whose promises you are showing them when you do.
|
|
2551
|
+
|
|
2552
|
+
When you have proposed promises, show them here as well as there. Put each proposal in the
|
|
2553
|
+
conversation in full: its one-sentence claim, who it is for, when it applies and what must then be
|
|
2554
|
+
true, and the numbered cases that must keep working and the ones that must be caught. Do not
|
|
2555
|
+
store or present an unanswered-question backlog. Then offer to finish the agreement from here, by asking for a sign-off, giving them
|
|
2556
|
+
the page it returns and taking the one-time code that page shows them, so the browser is needed only
|
|
2557
|
+
for signing. Give them the review link in the same message too, because some people would rather
|
|
2558
|
+
read it there and edit it before they agree.
|
|
2559
|
+
A promise whose meaning is agreed and which nothing is checking yet is one you can finish. When
|
|
2560
|
+
somebody gives you a promise id, run \`balladeer status <promise id>\`, or expand it with get_promise.
|
|
2561
|
+
If it comes back agreed with nothing checking it, say so in one line and offer to prepare and build
|
|
2562
|
+
its verifier now. Only if they say yes: \`prepare_qualification\` mints the one-time setup, or
|
|
2563
|
+
\`balladeer prepare <promise id>\` mints it and writes it where the sealed run reads it. You need no
|
|
2564
|
+
sign-off for that and there is no code to ask anybody for, because agreeing the meaning was their
|
|
2565
|
+
act and building the check that proves it is yours. Then build the verifier, seal it, push, and tell
|
|
2566
|
+
them protection starts by itself when that run qualifies. The offer is the whole of it: never
|
|
2567
|
+
prepare one because nobody said no.`;
|
|
2568
|
+
var CAPTURE_SEAM = `### When to offer, and what a yes is worth
|
|
2569
|
+
|
|
2570
|
+
Notice durable customer behavior during ordinary work. Offer to preserve a concrete behavior
|
|
2571
|
+
beside useful work, without blocking implementation. File nothing until the person explicitly
|
|
2572
|
+
asks you to record it or accepts a scoped capture offer. A factual answer or permission to build
|
|
2573
|
+
alone is not permission to record. A no ends that offer; do not raise it again in this conversation.
|
|
2574
|
+
|
|
2575
|
+
Do not offer while the user is comparing options or exploring without a decision. If they later
|
|
2576
|
+
choose a concrete behavior, you may offer to capture it even though the conversation began as
|
|
2577
|
+
exploration. Do not offer new promises for pure refactors or cosmetic changes. During an active
|
|
2578
|
+
production incident, prioritize recovery and defer capture until recovery. Ordinary bug fixing
|
|
2579
|
+
is not automatically an active production incident.
|
|
2580
|
+
|
|
2581
|
+
For clear feature intent, an offer before implementation can make the promise a target for the
|
|
2582
|
+
work. For a normal bug fix, offer alongside the completed fix and checks. Neither requires a
|
|
2583
|
+
merge or another human closing reply. If the user is away, one nonblocking offer can remain in
|
|
2584
|
+
your completion message for their return; do not file or wait for an answer.
|
|
2585
|
+
|
|
2586
|
+
Use available context to state a specific behavior. A precise capture offer can confirm your
|
|
2587
|
+
interpretation and obtain permission to record in one yes. Ask a separate work question only
|
|
2588
|
+
when a consequential choice remains that a clear, scoped offer cannot resolve. An unanswered
|
|
2589
|
+
earlier question is not itself a reason to ask again. The server supplies current detailed guidance.
|
|
2590
|
+
|
|
2591
|
+
### Two promises Balladeer cannot keep
|
|
2592
|
+
|
|
2593
|
+
A promise about speed needs three things before it is a promise at all: a number, a percentile, and
|
|
2594
|
+
where it is measured. "The quote page answers in under one second at p95, measured in production at
|
|
2595
|
+
peak load" is one Balladeer can keep. "The quote page has to be fast" is not. Say this, and ask for
|
|
2596
|
+
the part that is missing rather than filing it:
|
|
2597
|
+
|
|
2598
|
+
"I can keep that once it has a number, a percentile, and a place it is measured. Without those it is
|
|
2599
|
+
a wish, not a promise."
|
|
2600
|
+
|
|
2601
|
+
Balladeer refuses one without all three and names which of them is missing. When all three are
|
|
2602
|
+
there, write the measurement method into the promise, and tell them plainly that it reads agreed and
|
|
2603
|
+
unprotected until a test that actually measures it exists.
|
|
2604
|
+
|
|
2605
|
+
A promise about how the team works is not something the software does, so no check can ever catch
|
|
2606
|
+
it. "We must provide a low-friction capture experience" is one of these. File nothing and say:
|
|
2607
|
+
|
|
2608
|
+
"That is a promise about how we work, not something the software does that a check can fail.
|
|
2609
|
+
Balladeer only keeps promises a check can catch. If a customer would notice something when this
|
|
2610
|
+
slips, say that and I will keep that instead."
|
|
2611
|
+
|
|
2612
|
+
Then take the customer-visible half if they give you one, and file that instead.`;
|
|
2613
|
+
var SETTLING_QUESTIONS = `### When somebody says "tell me about" one
|
|
2614
|
+
|
|
2615
|
+
An id is how a person points at something here. Expand a promise id with get_promise and a proposal
|
|
2616
|
+
id with get_proposal. Describe who it is for, when it applies, the behavior and the concrete cases.
|
|
2617
|
+
A proposal awaits its named owner's agreement; a promise is agreed and may still be unprotected.
|
|
2618
|
+
Previously recorded answers are historical reasoning, not a new interview or an approval.
|
|
2619
|
+
If the person wants a change, clarify the changed commitment before writing it. Preserve all other
|
|
2620
|
+
meaning and cases. An agreed promise changes only through a revision its named owner approves.`;
|
|
2621
|
+
var FETCH_BUDGET = `Fetch by id, and only for an id the person gave you or an index row whose paths match the change in
|
|
2622
|
+
front of you. Never enumerate the catalog. Never chain one fetch into the next to see the whole of
|
|
2623
|
+
something: when the rows do not settle it, narrow the filter rather than expanding another promise.`;
|
|
2624
|
+
var SEALED_FILES = `### Files that are sealed, and the one reason to edit one
|
|
2625
|
+
|
|
2626
|
+
Every file under .continuity/promises/ is sealed. The promise that owns that directory records the
|
|
2627
|
+
exact bytes of each file in it, so editing one, adding one there, renaming one or deleting one
|
|
2628
|
+
breaks the seal. A promise whose seal is broken stops being checked, and it stays that way until a
|
|
2629
|
+
named person qualifies it again, which is their afternoon rather than your commit. Nothing in there
|
|
2630
|
+
is ordinary source: keep it out of refactors, formatting runs and dependency upgrades.
|
|
2631
|
+
|
|
2632
|
+
Find out which directories are sealed before you plan an edit, not after. balladeer status lists
|
|
2633
|
+
them, and list_promises names a promise's sealed directory on its row once a verifier is bound to
|
|
2634
|
+
it. Then, before you push, run balladeer check-seals. It prints nothing and exits zero when your
|
|
2635
|
+
change touches no seal, and names the promise, its owner and its page when your change would break
|
|
2636
|
+
one.
|
|
2637
|
+
|
|
2638
|
+
The one reason to edit a sealed file is to repair a verifier that can no longer run: something it
|
|
2639
|
+
imports moved, or the language it is written in changed under it. Never edit one to make a failing
|
|
2640
|
+
check pass. A check going red is the promise doing its job, and the repair for that belongs in the
|
|
2641
|
+
behavior it protects. A repair is not finished until the promise is sealed again with the runner
|
|
2642
|
+
this repository is pinned to. get_promise_setup carries that exact seal command, and
|
|
2643
|
+
balladeer check-seals prints it beside any promise it names.`;
|
|
2644
|
+
var TOUCH_MAP_GUIDANCE = `Prefer the measurement over the markers. Where this repository has a touch map, \`balladeer affected
|
|
2645
|
+
<paths...>\` answers which promises ran the files in front of you, out of what each verifier actually
|
|
2646
|
+
executed the last time \`balladeer touch-map\` measured it. Both commands run on this machine and
|
|
2647
|
+
send Balladeer nothing, so you may name any path in the change.
|
|
2648
|
+
|
|
2649
|
+
An answer marked stale was measured against a verifier that has changed since, so read it as the
|
|
2650
|
+
last thing anybody measured rather than as fact, and offer to run \`balladeer touch-map\` again.
|
|
2651
|
+
Where there is no map, where it does not name your paths, or where it reports a promise it could not
|
|
2652
|
+
measure, fall back to the paths on the index rows. The map narrows which promises are worth
|
|
2653
|
+
fetching. It never widens what you may read: the catalog is still never enumerated, and the fetch
|
|
2654
|
+
budget is unchanged.
|
|
2655
|
+
|
|
2656
|
+
When a retrieval receipt asks why you read a row, the answer for one the map named is \`touch\`.`;
|
|
2657
|
+
var SESSION_STAMP = `Use the installed \`balladeer\` command after setup reports CLI readiness. If it is missing from
|
|
2658
|
+
PATH (including an older 1.0.4 setup), use \`npx -y balladeer@latest <command>\` for that invocation
|
|
2659
|
+
and read the current /agent instructions for installation or repair. Do not claim that an older
|
|
2660
|
+
repository's instructions were refreshed. CLI availability does not prove that this coding host
|
|
2661
|
+
loaded the native MCP tools or that a credential is authorized. The optional session stamp below
|
|
2662
|
+
is separate from all three.
|
|
2663
|
+
|
|
2664
|
+
Run \`balladeer session\` when you start work here and pass the id it prints to every promise read
|
|
2665
|
+
you make, as \`session\`. It is the same id for the whole of one piece of work, and it costs nothing:
|
|
2666
|
+
a read without it is answered exactly the same way.
|
|
2667
|
+
|
|
2668
|
+
If local file permissions refuse that session write, no session stamp was saved. Your saved
|
|
2669
|
+
connection is unchanged; this does not prove its current server authorization. Continue
|
|
2670
|
+
already-authorized MCP reads, coding and explicitly requested capture without the optional
|
|
2671
|
+
\`session\` field. Do not invent an ID, add a session trailer, or record an unsaved stamp. Do not
|
|
2672
|
+
broaden filesystem access or move credentials to retry this write. A missing credential or server
|
|
2673
|
+
authorization refusal is different: follow that refusal. Capture still needs the person's request
|
|
2674
|
+
or accepted offer, and human meaning approval is unchanged.
|
|
2675
|
+
|
|
2676
|
+
If a session ID was saved, near the end of the work call \`mark_promise_used\` with the ids you actually cited
|
|
2677
|
+
or acted on. Naming none is a fine answer. It is the only way anybody finds out which rows were
|
|
2678
|
+
worth sending you, because reading a promise and then ignoring it is not the same as using it.
|
|
2679
|
+
|
|
2680
|
+
If a session ID was saved, write \`Balladeer-Session: <the id>\` into the commit message or pull-request body you produce,
|
|
2681
|
+
and run \`balladeer session --record\` once the commit exists. That is what lets a check that goes red
|
|
2682
|
+
next week be read back against what Balladeer told you before you started. Your commit message never
|
|
2683
|
+
leaves your machine: only the session id and the commit SHA are sent.`;
|
|
2684
|
+
var WORKFLOW_GUIDANCE = `## Balladeer promises
|
|
2685
|
+
|
|
2686
|
+
Before planning work in this repository, read the promises this team has already approved through
|
|
2687
|
+
the Balladeer MCP server. They are the behaviors a named person has agreed the software keeps, so
|
|
2688
|
+
your plan has to hold them, not just read them.
|
|
2689
|
+
|
|
2690
|
+
${RETRIEVAL_SCOPE}
|
|
2691
|
+
|
|
2692
|
+
Each index row carries what it takes to rule that promise out without fetching it: the repository,
|
|
2693
|
+
the paths it covers, its one-sentence claim, what protects it and why, and when anything last
|
|
2694
|
+
checked it.
|
|
2695
|
+
|
|
2696
|
+
${TOUCH_MAP_GUIDANCE}
|
|
2697
|
+
|
|
2698
|
+
${FETCH_BUDGET}
|
|
2699
|
+
|
|
2700
|
+
${CAPTURE_SEAM}
|
|
2701
|
+
|
|
2702
|
+
${SESSION_STAMP}
|
|
2703
|
+
|
|
2704
|
+
### When somebody asks you to protect a behavior
|
|
2705
|
+
|
|
2706
|
+
A request to build a behavior is not permission to record it. An explicit request to preserve it
|
|
2707
|
+
or acceptance of a specific capture offer permits one pending proposal for that behavior, not
|
|
2708
|
+
other behaviors you notice. A factual answer alone does not authorize recording.
|
|
2709
|
+
|
|
2710
|
+
Use available conversation and repository context before asking questions. A precise capture
|
|
2711
|
+
offer can state your interpretation and obtain permission to record it in one yes. Ask separately
|
|
2712
|
+
only when a consequential choice remains that the scoped offer cannot resolve. Do not ask for
|
|
2713
|
+
known facts, repeat a question resolved by later context or acceptance, or require a formal
|
|
2714
|
+
must-not sentence from the user. Preserve their actual words separately from your interpretation.
|
|
2715
|
+
If no specific commitment is possible yet, continue the authorized work without filing it.
|
|
2716
|
+
|
|
2717
|
+
Use relevant existing promises already retrieved, or the bounded repository/path lookup, to notice
|
|
2718
|
+
concrete conflicts before writing. If the same situation would require incompatible outcomes, show
|
|
2719
|
+
the two commitments and ask which behavior they intend. Revising or superseding agreed meaning
|
|
2720
|
+
still requires its named human owner. There is no automatic catalog-conflict gate or stored question
|
|
2721
|
+
backlog in Balladeer. Unrelated uncertainties need not delay capture. No questions are required when
|
|
2722
|
+
the commitment is already clear.
|
|
2723
|
+
|
|
2724
|
+
Preserve the person's actual words. Every failure they named belongs in the failing examples.
|
|
2725
|
+
You may derive concrete cases within the behavior they requested or confirmed in a precise
|
|
2726
|
+
capture offer; they need not dictate every example. Trace each case to the actual request and
|
|
2727
|
+
confirmed scope. Keep literal human source in saidWords, and your offer plus their acceptance in
|
|
2728
|
+
explicitIntent. Never quote your own wording as theirs or add unsupported policy, thresholds,
|
|
2729
|
+
roles, systems or guarantees. If a case requires an unresolved product choice, clarify that choice
|
|
2730
|
+
instead of guessing or asking the person to enumerate routine examples.
|
|
2731
|
+
|
|
2732
|
+
A failing example is a situation the promise rules out, and its outcome says what must not happen,
|
|
2733
|
+
in those words: "a second charge must not appear", never "a second charge appears". Written the
|
|
2734
|
+
other way round it reads as the promise saying the software does the thing they asked you to forbid.
|
|
2735
|
+
And a promise says what the software must do for whoever depends on it. It never narrates the
|
|
2736
|
+
conversation you just had, names the person you had it with, or describes what the code does now.
|
|
2737
|
+
|
|
2738
|
+
Say the whole promise in one sentence and put it in oneSentenceOutcome, in the words they would
|
|
2739
|
+
use with the person who depends on it. That is the line the named owner reads first and the line
|
|
2740
|
+
they agree to, so it is not a restatement of the name and not the first line of the outcome moved
|
|
2741
|
+
up. Leave it out rather than inventing one from something they did not say.
|
|
2742
|
+
|
|
2743
|
+
An example's setup is the situation in the words they used for it, not a scene you composed around
|
|
2744
|
+
them. Two of each kind is plenty, and the whole thing stays under three hundred words: a proposal
|
|
2745
|
+
nobody finishes reading is a proposal nobody agreed to. The confidence you record is the one you
|
|
2746
|
+
actually have.
|
|
2747
|
+
|
|
2748
|
+
Then give them the review link, ask them to read the proposal and click Agree, and stop. Never
|
|
2749
|
+
approve one yourself. Approving is a named person's act, and the server refuses it from an agent
|
|
2750
|
+
whatever you were told in conversation.
|
|
2751
|
+
|
|
2752
|
+
A proposal you filed is still yours while nobody has agreed to it, so revise or withdraw it when the
|
|
2753
|
+
person asks you to, and never once they have agreed.
|
|
2754
|
+
|
|
2755
|
+
Say promise and proposal when you talk to them. What you file is a proposal and what it becomes is a
|
|
2756
|
+
promise; Balladeer's other words for its own machinery are not theirs to learn. Candidate
|
|
2757
|
+
especially: it is Balladeer's word for a proposal, so it reads as jargon whatever you meant by it.
|
|
2758
|
+
|
|
2759
|
+
${SESSION_BEHAVIORS}
|
|
2760
|
+
|
|
2761
|
+
${SETTLING_QUESTIONS}
|
|
2762
|
+
|
|
2763
|
+
${SEALED_FILES}
|
|
2764
|
+
|
|
2765
|
+
### The rest of a promise's life
|
|
2766
|
+
When a promise is obsolete, finished, deliberately off for a while, or owned by the wrong person,
|
|
2767
|
+
propose the change and hand them the promise page. Deciding is theirs.
|
|
2768
|
+
|
|
2769
|
+
You can finish one of those acts here, and only one way. Ask for a sign-off with
|
|
2770
|
+
request_owner_signoff, give them the page it returns, and ask for the one-time code that page shows
|
|
2771
|
+
them. Then call the act's own tool with that code and their own words. Never call one on your own
|
|
2772
|
+
initiative, never on a general approval of some earlier act, and never ask for a code you were not
|
|
2773
|
+
given: a refusal is the person's to resolve, not yours to retry. Balladeer records them as the
|
|
2774
|
+
person who acted and you as the messenger.
|
|
2775
|
+
|
|
2776
|
+
Report the promise's state exactly as Balladeer reported it: proposed, agreed, or protected, never
|
|
2777
|
+
one in place of another.
|
|
2778
|
+
|
|
2779
|
+
### Where your team watches this
|
|
2780
|
+
|
|
2781
|
+
Balladeer is a web app as well as these tools, at the address setup printed. Its catalog lists every
|
|
2782
|
+
promise with who owns it and whether anything is checking it, and each promise has a page of its own
|
|
2783
|
+
showing what this team agreed the software must do and then every run that has checked it since,
|
|
2784
|
+
newest first, with the commit each one checked. Whenever there is a link to give, give the link
|
|
2785
|
+
rather than a summary of it: the page says what you would have said, and it stays true after this
|
|
2786
|
+
conversation has ended.
|
|
2787
|
+
|
|
2788
|
+
Balladeer also has a Slack app, which a workspace administrator installs from workspace settings.
|
|
2789
|
+
Once it is installed, whoever owns a promise gets a direct message when theirs goes live and when a
|
|
2790
|
+
run on the default branch breaks it. Until somebody installs it, nothing is sent anywhere, so say it
|
|
2791
|
+
is available rather than saying they will be told.
|
|
2792
|
+
|
|
2793
|
+
### Questions people ask
|
|
2794
|
+
|
|
2795
|
+
Answer these when they come up. Where you do not know, say so and point at the address setup
|
|
2796
|
+
printed: a confident wrong answer about what a vendor can see is worse than no answer.
|
|
2797
|
+
|
|
2798
|
+
What it does: it holds the behaviors this team has agreed the software must keep, and reports
|
|
2799
|
+
whether each one is still being kept, from this repository's own tests running in its own CI.
|
|
2800
|
+
|
|
2801
|
+
What it sees: the text of each promise somebody approves, this repository's numeric ids and the name
|
|
2802
|
+
of its default branch, and from CI the pass or fail outcome, the commit checked, and content hashes.
|
|
2803
|
+
Never the code, the tests, the fixtures, the logs, the prompts, or the transcripts.
|
|
2804
|
+
|
|
2805
|
+
What stopping costs: nothing that matters to their tests. The verifier package, its fixtures and the
|
|
2806
|
+
workflow file are theirs, in their repository, running in their CI, and disconnecting changes none
|
|
2807
|
+
of them. An administrator can download everything Balladeer holds at any time from workspace
|
|
2808
|
+
settings, and disconnecting hands them that same download in the response that ends access.
|
|
2809
|
+
|
|
2810
|
+
Who can approve one: the named person who owns it, in their own browser. Not an administrator on
|
|
2811
|
+
their behalf, and never you.
|
|
2812
|
+
|
|
2813
|
+
Whether it blocks a merge: no. The check is advisory on Balladeer's side, and their own branch
|
|
2814
|
+
protection is what decides whether a failing check stops anything.
|
|
2815
|
+
|
|
2816
|
+
Who can invite people and change setup: a workspace administrator. A contributor can read the
|
|
2817
|
+
workspace and propose promises, and a viewer can read it. If somebody asks you to add a teammate,
|
|
2818
|
+
invite_teammate returns the page an administrator sends the invitation from, with the address filled
|
|
2819
|
+
in for them to read. The tool sends nothing itself: an administrator presses Send.`;
|
|
2820
|
+
|
|
2821
|
+
// src/guidance-install.ts
|
|
2822
|
+
var MAX_FILE = 262144;
|
|
2823
|
+
function readRegular(path, allowMissing = false, writable = true) {
|
|
2824
|
+
if (!existsSync2(path)) {
|
|
2825
|
+
try {
|
|
2826
|
+
lstatSync4(path);
|
|
2827
|
+
} catch (e) {
|
|
2828
|
+
if (e.code === "ENOENT" && allowMissing) return void 0;
|
|
2829
|
+
}
|
|
2830
|
+
throw new Error("file_unavailable");
|
|
2831
|
+
}
|
|
2832
|
+
const stat = lstatSync4(path);
|
|
2833
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE || writable && (stat.mode & 146) === 0)
|
|
2834
|
+
throw new Error("file_not_safely_writable");
|
|
2835
|
+
return readFileSync5(path, "utf8");
|
|
2836
|
+
}
|
|
2837
|
+
function safeParent(root, path) {
|
|
2838
|
+
let at = dirname3(path);
|
|
2839
|
+
while (at !== root) {
|
|
2840
|
+
if (relative(root, at).startsWith("..")) throw new Error("outside_repository");
|
|
2841
|
+
try {
|
|
2842
|
+
const stat = lstatSync4(at);
|
|
2843
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("unsafe_directory");
|
|
2844
|
+
} catch (e) {
|
|
2845
|
+
if (e.code !== "ENOENT") throw e;
|
|
2846
|
+
}
|
|
2847
|
+
at = dirname3(at);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
function validateProjectGuidanceScope(options) {
|
|
2851
|
+
try {
|
|
2852
|
+
const root = execFileSync("git", ["-C", options.cwd, "rev-parse", "--show-toplevel"], {
|
|
2853
|
+
encoding: "utf8",
|
|
2854
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2855
|
+
timeout: 1e3
|
|
2856
|
+
}).trim();
|
|
2857
|
+
const path = join5(root, options.hook === "codex" ? ".codex/config.toml" : ".mcp.json");
|
|
2858
|
+
safeParent(root, path);
|
|
2859
|
+
readRegular(path, false, false);
|
|
2860
|
+
const entry = options.hook === "codex" ? readCodexEntry(root) : currentEntry(readMcpConfig(root));
|
|
2861
|
+
if (!isOurEntry(entry, options.controlPlane) || entryRepositoryId(entry) !== options.repositoryId)
|
|
2862
|
+
return false;
|
|
2863
|
+
if (options.hook === "codex" && !hasManagedCodexEntry(root, options.controlPlane)) return false;
|
|
2864
|
+
const args = entry.args;
|
|
2865
|
+
if (!Array.isArray(args)) return false;
|
|
2866
|
+
const at = args.indexOf("--control-plane");
|
|
2867
|
+
return at === -1 ? options.controlPlane === DEFAULT_CONTROL_PLANE : args[at + 1] === options.controlPlane;
|
|
2868
|
+
} catch {
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
// src/agent.ts
|
|
2874
|
+
var REPOSITORY_NAME = /^[A-Za-z0-9._-]{1,39}\/[A-Za-z0-9._-]{1,100}$/;
|
|
2875
|
+
function selectAgent(agents, controlPlane, repositoryId, currentRepository) {
|
|
2876
|
+
const here = agents.filter((agent) => agent.controlPlane === controlPlane);
|
|
2877
|
+
if (repositoryId !== void 0) {
|
|
2878
|
+
const named = here.find(
|
|
2879
|
+
(agent) => agent.repositoryId === repositoryId || agent.repository?.toLowerCase() === repositoryId.toLowerCase()
|
|
2880
|
+
);
|
|
2881
|
+
if (named === void 0) {
|
|
2882
|
+
return {
|
|
2883
|
+
kind: "refused",
|
|
2884
|
+
reason: `No Balladeer connection for repository ${repositoryId} is stored for ${controlPlane}.`,
|
|
2885
|
+
missingFor: repositoryId
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
return withSafeEndpoint(named);
|
|
2889
|
+
}
|
|
2890
|
+
if (!REPOSITORY_NAME.test(currentRepository) || currentRepository === "unknown/unknown") {
|
|
2891
|
+
return {
|
|
2892
|
+
kind: "refused",
|
|
2893
|
+
reason: "This directory has no GitHub origin remote I could read, so I cannot tell which repository's Balladeer connection to use. Pass --repository <id>."
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
const matches = here.filter(
|
|
2897
|
+
(agent) => agent.repository?.toLowerCase() === currentRepository.toLowerCase()
|
|
2898
|
+
);
|
|
2899
|
+
const chosen = matches[matches.length - 1];
|
|
2900
|
+
if (chosen === void 0) {
|
|
2901
|
+
return {
|
|
2902
|
+
kind: "refused",
|
|
2903
|
+
reason: `No Balladeer connection for ${currentRepository} is stored for ${controlPlane}.`,
|
|
2904
|
+
missingFor: currentRepository
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
return withSafeEndpoint(chosen);
|
|
2908
|
+
}
|
|
2909
|
+
function withSafeEndpoint(agent) {
|
|
2910
|
+
try {
|
|
2911
|
+
if (new URL(agent.mcpUrl).origin !== new URL(agent.controlPlane).origin) {
|
|
2912
|
+
return {
|
|
2913
|
+
kind: "refused",
|
|
2914
|
+
reason: "That connection's MCP endpoint is not on the control plane it belongs to, so I sent its credential nowhere."
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
} catch {
|
|
2918
|
+
return {
|
|
2919
|
+
kind: "refused",
|
|
2920
|
+
reason: "That connection's MCP endpoint is not a URL, so I did not use it."
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
return { kind: "agent", agent };
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// src/repository.ts
|
|
2927
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2928
|
+
var REPOSITORY_HINT_PATTERN = /^[A-Za-z0-9._-]{1,39}\/[A-Za-z0-9._-]{1,100}$/;
|
|
2929
|
+
function repositoryHint(cwd = process.cwd()) {
|
|
2930
|
+
let remote;
|
|
2931
|
+
try {
|
|
2932
|
+
remote = execFileSync2("git", ["-C", cwd, "remote", "get-url", "origin"], {
|
|
2933
|
+
encoding: "utf8",
|
|
2934
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2935
|
+
}).trim();
|
|
2936
|
+
} catch {
|
|
2937
|
+
return "unknown/unknown";
|
|
2938
|
+
}
|
|
2939
|
+
const match = /github\.com[:/]+([A-Za-z0-9._-]{1,39})\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?$/.exec(
|
|
2940
|
+
remote
|
|
2941
|
+
);
|
|
2942
|
+
if (!match) return "unknown/unknown";
|
|
2943
|
+
const hint = `${match[1]}/${match[2]}`;
|
|
2944
|
+
return REPOSITORY_HINT_PATTERN.test(hint) ? hint : "unknown/unknown";
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
// src/quiet.ts
|
|
2948
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2949
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, realpathSync } from "node:fs";
|
|
2950
|
+
import { join as join6 } from "node:path";
|
|
2951
|
+
var EMPTY2 = { schemaVersion: 1, folders: [], remotes: [] };
|
|
2952
|
+
function quietPath(environment = process.env) {
|
|
2953
|
+
return join6(configHome(environment), "quiet.json");
|
|
2954
|
+
}
|
|
2955
|
+
function readQuiet(environment = process.env) {
|
|
2956
|
+
const path = quietPath(environment);
|
|
2957
|
+
if (!existsSync3(path)) return EMPTY2;
|
|
2958
|
+
try {
|
|
2959
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
2960
|
+
if (parsed.schemaVersion !== 1) return EMPTY2;
|
|
2961
|
+
return {
|
|
2962
|
+
schemaVersion: 1,
|
|
2963
|
+
folders: Array.isArray(parsed.folders) ? parsed.folders.filter(isString) : [],
|
|
2964
|
+
remotes: Array.isArray(parsed.remotes) ? parsed.remotes.filter(isString) : []
|
|
2965
|
+
};
|
|
2966
|
+
} catch {
|
|
2967
|
+
return EMPTY2;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
var isString = (value) => typeof value === "string";
|
|
2971
|
+
function folderKey(cwd) {
|
|
2972
|
+
try {
|
|
2973
|
+
return realpathSync(
|
|
2974
|
+
execFileSync3("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
2975
|
+
encoding: "utf8",
|
|
2976
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2977
|
+
timeout: 1e3
|
|
2978
|
+
}).trim()
|
|
2979
|
+
);
|
|
2980
|
+
} catch {
|
|
2981
|
+
return realpathSync(cwd);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function isQuiet(cwd, environment = process.env) {
|
|
2985
|
+
const list = readQuiet(environment);
|
|
2986
|
+
const folder = folderKey(cwd);
|
|
2987
|
+
const remote = repositoryHint(cwd);
|
|
2988
|
+
return list.folders.includes(folder) || remote !== "unknown/unknown" && list.remotes.some((r) => r.toLowerCase() === remote.toLowerCase());
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/commands/guidance.ts
|
|
2992
|
+
function untrackedLine(remote) {
|
|
2993
|
+
const here = remote === "unknown/unknown" ? "This folder" : `This folder (${remote})`;
|
|
2994
|
+
return `${here} isn't tracked by Balladeer, so promises can't be tracked from here. To track it, run \`npx -y ${PUBLISHED_SPECIFIER} setup\` in this folder. To stop this message here, run \`npx -y ${PUBLISHED_SPECIFIER} quiet\` (or \`quiet --repo\` for the whole repository).`;
|
|
2995
|
+
}
|
|
2996
|
+
function readInput(stream) {
|
|
2997
|
+
return new Promise((resolve2, reject) => {
|
|
2998
|
+
let bytes = 0;
|
|
2999
|
+
const chunks = [];
|
|
3000
|
+
const finish = (error) => {
|
|
3001
|
+
clearTimeout(timer);
|
|
3002
|
+
stream.removeListener("data", data);
|
|
3003
|
+
stream.removeListener("end", end);
|
|
3004
|
+
stream.removeListener("error", failed);
|
|
3005
|
+
stream.pause();
|
|
3006
|
+
if (error) reject(error);
|
|
3007
|
+
else {
|
|
3008
|
+
try {
|
|
3009
|
+
resolve2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3010
|
+
} catch {
|
|
3011
|
+
reject(new Error("invalid_hook_input"));
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
const data = (chunk) => {
|
|
3016
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
3017
|
+
bytes += buffer.length;
|
|
3018
|
+
if (bytes > 65536) finish(new Error("hook_input_oversized"));
|
|
3019
|
+
else chunks.push(buffer);
|
|
3020
|
+
};
|
|
3021
|
+
const end = () => finish();
|
|
3022
|
+
const failed = () => finish(new Error("hook_input_unavailable"));
|
|
3023
|
+
const timer = setTimeout(() => finish(new Error("hook_input_deadline")), 250);
|
|
3024
|
+
stream.on("data", data);
|
|
3025
|
+
stream.once("end", end);
|
|
3026
|
+
stream.once("error", failed);
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
function eventFrom(input) {
|
|
3030
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
|
|
3031
|
+
const event = input.hook_event_name;
|
|
3032
|
+
return event === "SessionStart" || event === "SubagentStart" || event === "UserPromptSubmit" ? event : void 0;
|
|
3033
|
+
}
|
|
3034
|
+
function identifier(input) {
|
|
3035
|
+
return typeof input === "string" && input.length > 0 && input.length <= 256 ? input : void 0;
|
|
3036
|
+
}
|
|
3037
|
+
async function runGuidance(options) {
|
|
3038
|
+
const projectScoped = options.repositoryId !== void 0;
|
|
3039
|
+
if (projectScoped && !validateProjectGuidanceScope({
|
|
3040
|
+
cwd: options.cwd,
|
|
3041
|
+
hook: options.hook,
|
|
3042
|
+
repositoryId: options.repositoryId,
|
|
3043
|
+
controlPlane: options.controlPlane
|
|
3044
|
+
}))
|
|
3045
|
+
return 0;
|
|
3046
|
+
let event = options.event ?? "SessionStart";
|
|
3047
|
+
const emit = (context) => options.write(
|
|
3048
|
+
`${JSON.stringify({
|
|
3049
|
+
hookSpecificOutput: { hookEventName: event, additionalContext: context }
|
|
3050
|
+
})}
|
|
3051
|
+
`
|
|
3052
|
+
);
|
|
3053
|
+
try {
|
|
3054
|
+
const input = await readInput(options.stdin);
|
|
3055
|
+
const parsedEvent = eventFrom(input);
|
|
3056
|
+
if (!parsedEvent || options.event && parsedEvent !== options.event)
|
|
3057
|
+
throw new Error("invalid_hook_event");
|
|
3058
|
+
event = parsedEvent;
|
|
3059
|
+
const record = input;
|
|
3060
|
+
const credentials = readCredentials(options.environment).agents;
|
|
3061
|
+
const agents = projectScoped ? credentials.filter(
|
|
3062
|
+
(agent) => agent.controlPlane === options.controlPlane && agent.repositoryId === options.repositoryId
|
|
3063
|
+
) : (() => {
|
|
3064
|
+
const selection = selectAgent(
|
|
3065
|
+
credentials,
|
|
3066
|
+
options.controlPlane,
|
|
3067
|
+
void 0,
|
|
3068
|
+
repositoryHint(options.cwd)
|
|
3069
|
+
);
|
|
3070
|
+
return selection.kind === "refused" ? [] : [selection.agent];
|
|
3071
|
+
})();
|
|
3072
|
+
if (agents.length !== 1 || !agents[0]) {
|
|
3073
|
+
if (projectScoped) throw new Error("guidance_connection_unavailable");
|
|
3074
|
+
if (event === "SessionStart" && !isQuiet(options.cwd, options.environment))
|
|
3075
|
+
emit(untrackedLine(repositoryHint(options.cwd)));
|
|
3076
|
+
return 0;
|
|
3077
|
+
}
|
|
3078
|
+
const loaded = await loadGuidance({
|
|
3079
|
+
agent: agents[0],
|
|
3080
|
+
environment: options.environment,
|
|
3081
|
+
timeoutMs: 750,
|
|
3082
|
+
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
3083
|
+
});
|
|
3084
|
+
const sessionId = identifier(record.session_id);
|
|
3085
|
+
const agentId = identifier(record.agent_id);
|
|
3086
|
+
recordGuidanceContext(
|
|
3087
|
+
{
|
|
3088
|
+
environment: options.environment,
|
|
3089
|
+
scopeKey: loaded.scopeKey,
|
|
3090
|
+
hook: options.hook,
|
|
3091
|
+
event,
|
|
3092
|
+
...options.runtimeGeneration ? { runtimeGeneration: options.runtimeGeneration } : {},
|
|
3093
|
+
load: loaded,
|
|
3094
|
+
...sessionId ? { sessionId } : {},
|
|
3095
|
+
...agentId ? { agentId } : {},
|
|
3096
|
+
...options.now ? { now: options.now() } : {}
|
|
3097
|
+
},
|
|
3098
|
+
() => emit(
|
|
3099
|
+
loaded.document ? `Current Balladeer guidance (${loaded.document.revision}). This supersedes earlier Balladeer workflow and capture guidance in this context.
|
|
3100
|
+
|
|
3101
|
+
${loaded.document.instructions}` : GUIDANCE_UNAVAILABLE
|
|
3102
|
+
)
|
|
3103
|
+
);
|
|
3104
|
+
} catch {
|
|
3105
|
+
try {
|
|
3106
|
+
emit(GUIDANCE_UNAVAILABLE);
|
|
3107
|
+
} catch {
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
return 0;
|
|
3111
|
+
}
|
|
3112
|
+
export {
|
|
3113
|
+
runGuidance
|
|
3114
|
+
};
|