@darkhunt-security/endpoint-codex 0.9.7 → 0.9.9
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/.codex-plugin/plugin.json +1 -1
- package/dist/bin/backfill.mjs +72 -14
- package/dist/bin/forwarder.mjs +72 -14
- package/dist/bin/guard.mjs +8 -0
- package/dist/bin/spool.mjs +8 -0
- package/dist/bin/status.mjs +8 -0
- package/package.json +1 -1
package/dist/bin/backfill.mjs
CHANGED
|
@@ -18135,6 +18135,11 @@ function readSecure(path) {
|
|
|
18135
18135
|
throw new ConfigError(`${path} is mode ${mode.toString(8)}; must be 0600`);
|
|
18136
18136
|
return readFileSync(path, "utf8");
|
|
18137
18137
|
}
|
|
18138
|
+
function isBlank(path) {
|
|
18139
|
+
if (statSync(path).size === 0)
|
|
18140
|
+
return true;
|
|
18141
|
+
return readFileSync(path, "utf8").trim() === "";
|
|
18142
|
+
}
|
|
18138
18143
|
function readJson(path) {
|
|
18139
18144
|
try {
|
|
18140
18145
|
return JSON.parse(readSecure(path));
|
|
@@ -18159,6 +18164,9 @@ function readConfigFile(vendor) {
|
|
|
18159
18164
|
if (!existsSync(configPath())) {
|
|
18160
18165
|
throw new ConfigMissingError(`${configPath()} does not exist \u2014 run init.mjs to create it`);
|
|
18161
18166
|
}
|
|
18167
|
+
if (isBlank(configPath())) {
|
|
18168
|
+
throw new ConfigMissingError(`${configPath()} is empty \u2014 run init.mjs to create it`);
|
|
18169
|
+
}
|
|
18162
18170
|
const file = readJson(configPath());
|
|
18163
18171
|
if (!file) {
|
|
18164
18172
|
throw new ConfigError(`cannot read ${configPath()} \u2014 it exists but did not parse`);
|
|
@@ -18295,6 +18303,10 @@ function summarize(output) {
|
|
|
18295
18303
|
return text;
|
|
18296
18304
|
return `${text.slice(0, TOOL_SUMMARY_LIMIT)}\u2026 [truncated, ${text.length} chars]`;
|
|
18297
18305
|
}
|
|
18306
|
+
function attachmentLabel(record) {
|
|
18307
|
+
const size = record.bytes ? `, ${Math.round(record.bytes / 1024)} KB` : "";
|
|
18308
|
+
return `[${record.mediaType ?? "attachment"}${size}]`;
|
|
18309
|
+
}
|
|
18298
18310
|
var RESUMED_USER_TEXT_LIMIT = 4e3;
|
|
18299
18311
|
var SessionEmitter = class {
|
|
18300
18312
|
client;
|
|
@@ -18316,8 +18328,12 @@ var SessionEmitter = class {
|
|
|
18316
18328
|
/** True while consecutive user records still belong to the same turn. */
|
|
18317
18329
|
userTurnOpen = false;
|
|
18318
18330
|
pendingAttachments = [];
|
|
18331
|
+
/** Attachments seen before the trace could be opened knowing the prompt. */
|
|
18332
|
+
deferred = [];
|
|
18319
18333
|
pendingUsage;
|
|
18320
18334
|
outputText = [];
|
|
18335
|
+
/** The last thing the assistant said in this pass — the trace's result. */
|
|
18336
|
+
lastAssistantText;
|
|
18321
18337
|
count = 0;
|
|
18322
18338
|
constructor(client, options) {
|
|
18323
18339
|
this.client = client;
|
|
@@ -18368,6 +18384,28 @@ var SessionEmitter = class {
|
|
|
18368
18384
|
return;
|
|
18369
18385
|
}
|
|
18370
18386
|
this.count++;
|
|
18387
|
+
if (record.kind === "attachment")
|
|
18388
|
+
this.pendingAttachments.push(attachmentLabel(record));
|
|
18389
|
+
if (record.kind === "user_message")
|
|
18390
|
+
this.absorbUserTurn(record);
|
|
18391
|
+
if (!this.trace && record.kind === "attachment") {
|
|
18392
|
+
this.deferred.push(record);
|
|
18393
|
+
return;
|
|
18394
|
+
}
|
|
18395
|
+
this.flushDeferred();
|
|
18396
|
+
this.place(record);
|
|
18397
|
+
}
|
|
18398
|
+
/** Open the trace on the turn held back, then replay it. No-op once the trace exists. */
|
|
18399
|
+
flushDeferred() {
|
|
18400
|
+
if (this.deferred.length === 0)
|
|
18401
|
+
return;
|
|
18402
|
+
const held = this.deferred.splice(0);
|
|
18403
|
+
this.ensureTrace(held[0].ts);
|
|
18404
|
+
for (const record of held)
|
|
18405
|
+
this.place(record);
|
|
18406
|
+
}
|
|
18407
|
+
/** Route one record onto its owning trace and emit whatever it is worth. */
|
|
18408
|
+
place(record) {
|
|
18371
18409
|
const root = this.ensureTrace(record.ts);
|
|
18372
18410
|
const parentTrace = record.parentUuid ? this.owners.get(record.parentUuid) : void 0;
|
|
18373
18411
|
let owner = parentTrace ?? root;
|
|
@@ -18377,28 +18415,21 @@ var SessionEmitter = class {
|
|
|
18377
18415
|
sessionId: this.options.sessionId,
|
|
18378
18416
|
handoffFrom: [root.handoffToken()],
|
|
18379
18417
|
startTime: record.ts,
|
|
18418
|
+
// A subagent's first record is the task it was given, and it has already been
|
|
18419
|
+
// absorbed by the time this fires — so the child trace opens with its brief
|
|
18420
|
+
// rather than empty.
|
|
18421
|
+
...this.lastUserText ? { input: this.lastUserText } : {},
|
|
18380
18422
|
...this.options.userId !== void 0 ? { userId: this.options.userId } : {}
|
|
18381
18423
|
}));
|
|
18382
18424
|
this.sidechains.push(owner);
|
|
18383
18425
|
}
|
|
18384
18426
|
this.owners.set(record.uuid, owner);
|
|
18385
18427
|
switch (record.kind) {
|
|
18386
|
-
case "user_message":
|
|
18428
|
+
case "user_message":
|
|
18387
18429
|
this.endGeneration();
|
|
18388
|
-
const part = [this.pendingAttachments.join(" "), record.text].filter(Boolean).join(" ");
|
|
18389
|
-
this.pendingAttachments = [];
|
|
18390
|
-
if (this.userTurnOpen) {
|
|
18391
|
-
this.lastUserText = [this.lastUserText, part].filter(Boolean).join(" ");
|
|
18392
|
-
} else {
|
|
18393
|
-
this.lastUserText = part;
|
|
18394
|
-
this.userTurnOpen = true;
|
|
18395
|
-
this.pendingToolResults = [];
|
|
18396
|
-
}
|
|
18397
18430
|
break;
|
|
18398
|
-
}
|
|
18399
18431
|
case "attachment": {
|
|
18400
|
-
const label =
|
|
18401
|
-
this.pendingAttachments.push(label);
|
|
18432
|
+
const label = attachmentLabel(record);
|
|
18402
18433
|
this.seeded(["attachment", record.uuid], () => owner.span("attachment", {
|
|
18403
18434
|
startTime: record.ts,
|
|
18404
18435
|
metadata: {
|
|
@@ -18455,8 +18486,26 @@ var SessionEmitter = class {
|
|
|
18455
18486
|
break;
|
|
18456
18487
|
}
|
|
18457
18488
|
}
|
|
18489
|
+
/**
|
|
18490
|
+
* Fold a user record into the turn in flight.
|
|
18491
|
+
*
|
|
18492
|
+
* Split out of the `user_message` case so it can run before the trace is opened —
|
|
18493
|
+
* see the call site in {@link ingest}.
|
|
18494
|
+
*/
|
|
18495
|
+
absorbUserTurn(record) {
|
|
18496
|
+
const part = [this.pendingAttachments.join(" "), record.text].filter(Boolean).join(" ");
|
|
18497
|
+
this.pendingAttachments = [];
|
|
18498
|
+
if (this.userTurnOpen) {
|
|
18499
|
+
this.lastUserText = [this.lastUserText, part].filter(Boolean).join(" ");
|
|
18500
|
+
} else {
|
|
18501
|
+
this.lastUserText = part;
|
|
18502
|
+
this.userTurnOpen = true;
|
|
18503
|
+
this.pendingToolResults = [];
|
|
18504
|
+
}
|
|
18505
|
+
}
|
|
18458
18506
|
/** Ends everything still open and flushes. Safe to call twice. */
|
|
18459
18507
|
async finish() {
|
|
18508
|
+
this.flushDeferred();
|
|
18460
18509
|
this.endGeneration();
|
|
18461
18510
|
for (const span of this.toolSpans.values())
|
|
18462
18511
|
span.end();
|
|
@@ -18467,7 +18516,10 @@ var SessionEmitter = class {
|
|
|
18467
18516
|
if (this.trace) {
|
|
18468
18517
|
this.trace.update({
|
|
18469
18518
|
metadata: this.meta,
|
|
18470
|
-
...this.title !== void 0 ? { name: this.title } : {}
|
|
18519
|
+
...this.title !== void 0 ? { name: this.title } : {},
|
|
18520
|
+
// The pass's result. Paired with the `input` set when the trace opened, this is
|
|
18521
|
+
// what stops the consumer inferring the trace's I/O from a descendant.
|
|
18522
|
+
...this.lastAssistantText !== void 0 ? { output: this.lastAssistantText } : {}
|
|
18471
18523
|
});
|
|
18472
18524
|
this.trace.end();
|
|
18473
18525
|
this.trace = void 0;
|
|
@@ -18523,6 +18575,10 @@ var SessionEmitter = class {
|
|
|
18523
18575
|
name: this.title ?? this.defaultTraceName(),
|
|
18524
18576
|
sessionId: this.grouping ?? this.options.sessionId,
|
|
18525
18577
|
startTime: ts,
|
|
18578
|
+
// What the agent was asked. `TraceArgs.input` is construction-only — the SDK's
|
|
18579
|
+
// `update()` carries output alone — which is why `ingest` absorbs the user turn
|
|
18580
|
+
// before it calls this.
|
|
18581
|
+
...this.lastUserText ? { input: this.lastUserText } : {},
|
|
18526
18582
|
tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
|
|
18527
18583
|
metadata: {
|
|
18528
18584
|
...this.meta,
|
|
@@ -18588,6 +18644,8 @@ var SessionEmitter = class {
|
|
|
18588
18644
|
if (!this.generation)
|
|
18589
18645
|
return;
|
|
18590
18646
|
const output = this.outputText.join("\n");
|
|
18647
|
+
if (output)
|
|
18648
|
+
this.lastAssistantText = output;
|
|
18591
18649
|
this.generation.end({
|
|
18592
18650
|
...output ? { outputMessages: [{ role: "assistant", content: output }] } : {},
|
|
18593
18651
|
...this.model !== void 0 ? { model: this.model } : {},
|
package/dist/bin/forwarder.mjs
CHANGED
|
@@ -18135,6 +18135,11 @@ function readSecure(path) {
|
|
|
18135
18135
|
throw new ConfigError(`${path} is mode ${mode.toString(8)}; must be 0600`);
|
|
18136
18136
|
return readFileSync(path, "utf8");
|
|
18137
18137
|
}
|
|
18138
|
+
function isBlank(path) {
|
|
18139
|
+
if (statSync(path).size === 0)
|
|
18140
|
+
return true;
|
|
18141
|
+
return readFileSync(path, "utf8").trim() === "";
|
|
18142
|
+
}
|
|
18138
18143
|
function readJson(path) {
|
|
18139
18144
|
try {
|
|
18140
18145
|
return JSON.parse(readSecure(path));
|
|
@@ -18159,6 +18164,9 @@ function readConfigFile(vendor) {
|
|
|
18159
18164
|
if (!existsSync(configPath())) {
|
|
18160
18165
|
throw new ConfigMissingError(`${configPath()} does not exist \u2014 run init.mjs to create it`);
|
|
18161
18166
|
}
|
|
18167
|
+
if (isBlank(configPath())) {
|
|
18168
|
+
throw new ConfigMissingError(`${configPath()} is empty \u2014 run init.mjs to create it`);
|
|
18169
|
+
}
|
|
18162
18170
|
const file = readJson(configPath());
|
|
18163
18171
|
if (!file) {
|
|
18164
18172
|
throw new ConfigError(`cannot read ${configPath()} \u2014 it exists but did not parse`);
|
|
@@ -18295,6 +18303,10 @@ function summarize(output) {
|
|
|
18295
18303
|
return text;
|
|
18296
18304
|
return `${text.slice(0, TOOL_SUMMARY_LIMIT)}\u2026 [truncated, ${text.length} chars]`;
|
|
18297
18305
|
}
|
|
18306
|
+
function attachmentLabel(record) {
|
|
18307
|
+
const size = record.bytes ? `, ${Math.round(record.bytes / 1024)} KB` : "";
|
|
18308
|
+
return `[${record.mediaType ?? "attachment"}${size}]`;
|
|
18309
|
+
}
|
|
18298
18310
|
var RESUMED_USER_TEXT_LIMIT = 4e3;
|
|
18299
18311
|
var SessionEmitter = class {
|
|
18300
18312
|
client;
|
|
@@ -18316,8 +18328,12 @@ var SessionEmitter = class {
|
|
|
18316
18328
|
/** True while consecutive user records still belong to the same turn. */
|
|
18317
18329
|
userTurnOpen = false;
|
|
18318
18330
|
pendingAttachments = [];
|
|
18331
|
+
/** Attachments seen before the trace could be opened knowing the prompt. */
|
|
18332
|
+
deferred = [];
|
|
18319
18333
|
pendingUsage;
|
|
18320
18334
|
outputText = [];
|
|
18335
|
+
/** The last thing the assistant said in this pass — the trace's result. */
|
|
18336
|
+
lastAssistantText;
|
|
18321
18337
|
count = 0;
|
|
18322
18338
|
constructor(client, options) {
|
|
18323
18339
|
this.client = client;
|
|
@@ -18368,6 +18384,28 @@ var SessionEmitter = class {
|
|
|
18368
18384
|
return;
|
|
18369
18385
|
}
|
|
18370
18386
|
this.count++;
|
|
18387
|
+
if (record.kind === "attachment")
|
|
18388
|
+
this.pendingAttachments.push(attachmentLabel(record));
|
|
18389
|
+
if (record.kind === "user_message")
|
|
18390
|
+
this.absorbUserTurn(record);
|
|
18391
|
+
if (!this.trace && record.kind === "attachment") {
|
|
18392
|
+
this.deferred.push(record);
|
|
18393
|
+
return;
|
|
18394
|
+
}
|
|
18395
|
+
this.flushDeferred();
|
|
18396
|
+
this.place(record);
|
|
18397
|
+
}
|
|
18398
|
+
/** Open the trace on the turn held back, then replay it. No-op once the trace exists. */
|
|
18399
|
+
flushDeferred() {
|
|
18400
|
+
if (this.deferred.length === 0)
|
|
18401
|
+
return;
|
|
18402
|
+
const held = this.deferred.splice(0);
|
|
18403
|
+
this.ensureTrace(held[0].ts);
|
|
18404
|
+
for (const record of held)
|
|
18405
|
+
this.place(record);
|
|
18406
|
+
}
|
|
18407
|
+
/** Route one record onto its owning trace and emit whatever it is worth. */
|
|
18408
|
+
place(record) {
|
|
18371
18409
|
const root = this.ensureTrace(record.ts);
|
|
18372
18410
|
const parentTrace = record.parentUuid ? this.owners.get(record.parentUuid) : void 0;
|
|
18373
18411
|
let owner = parentTrace ?? root;
|
|
@@ -18377,28 +18415,21 @@ var SessionEmitter = class {
|
|
|
18377
18415
|
sessionId: this.options.sessionId,
|
|
18378
18416
|
handoffFrom: [root.handoffToken()],
|
|
18379
18417
|
startTime: record.ts,
|
|
18418
|
+
// A subagent's first record is the task it was given, and it has already been
|
|
18419
|
+
// absorbed by the time this fires — so the child trace opens with its brief
|
|
18420
|
+
// rather than empty.
|
|
18421
|
+
...this.lastUserText ? { input: this.lastUserText } : {},
|
|
18380
18422
|
...this.options.userId !== void 0 ? { userId: this.options.userId } : {}
|
|
18381
18423
|
}));
|
|
18382
18424
|
this.sidechains.push(owner);
|
|
18383
18425
|
}
|
|
18384
18426
|
this.owners.set(record.uuid, owner);
|
|
18385
18427
|
switch (record.kind) {
|
|
18386
|
-
case "user_message":
|
|
18428
|
+
case "user_message":
|
|
18387
18429
|
this.endGeneration();
|
|
18388
|
-
const part = [this.pendingAttachments.join(" "), record.text].filter(Boolean).join(" ");
|
|
18389
|
-
this.pendingAttachments = [];
|
|
18390
|
-
if (this.userTurnOpen) {
|
|
18391
|
-
this.lastUserText = [this.lastUserText, part].filter(Boolean).join(" ");
|
|
18392
|
-
} else {
|
|
18393
|
-
this.lastUserText = part;
|
|
18394
|
-
this.userTurnOpen = true;
|
|
18395
|
-
this.pendingToolResults = [];
|
|
18396
|
-
}
|
|
18397
18430
|
break;
|
|
18398
|
-
}
|
|
18399
18431
|
case "attachment": {
|
|
18400
|
-
const label =
|
|
18401
|
-
this.pendingAttachments.push(label);
|
|
18432
|
+
const label = attachmentLabel(record);
|
|
18402
18433
|
this.seeded(["attachment", record.uuid], () => owner.span("attachment", {
|
|
18403
18434
|
startTime: record.ts,
|
|
18404
18435
|
metadata: {
|
|
@@ -18455,8 +18486,26 @@ var SessionEmitter = class {
|
|
|
18455
18486
|
break;
|
|
18456
18487
|
}
|
|
18457
18488
|
}
|
|
18489
|
+
/**
|
|
18490
|
+
* Fold a user record into the turn in flight.
|
|
18491
|
+
*
|
|
18492
|
+
* Split out of the `user_message` case so it can run before the trace is opened —
|
|
18493
|
+
* see the call site in {@link ingest}.
|
|
18494
|
+
*/
|
|
18495
|
+
absorbUserTurn(record) {
|
|
18496
|
+
const part = [this.pendingAttachments.join(" "), record.text].filter(Boolean).join(" ");
|
|
18497
|
+
this.pendingAttachments = [];
|
|
18498
|
+
if (this.userTurnOpen) {
|
|
18499
|
+
this.lastUserText = [this.lastUserText, part].filter(Boolean).join(" ");
|
|
18500
|
+
} else {
|
|
18501
|
+
this.lastUserText = part;
|
|
18502
|
+
this.userTurnOpen = true;
|
|
18503
|
+
this.pendingToolResults = [];
|
|
18504
|
+
}
|
|
18505
|
+
}
|
|
18458
18506
|
/** Ends everything still open and flushes. Safe to call twice. */
|
|
18459
18507
|
async finish() {
|
|
18508
|
+
this.flushDeferred();
|
|
18460
18509
|
this.endGeneration();
|
|
18461
18510
|
for (const span of this.toolSpans.values())
|
|
18462
18511
|
span.end();
|
|
@@ -18467,7 +18516,10 @@ var SessionEmitter = class {
|
|
|
18467
18516
|
if (this.trace) {
|
|
18468
18517
|
this.trace.update({
|
|
18469
18518
|
metadata: this.meta,
|
|
18470
|
-
...this.title !== void 0 ? { name: this.title } : {}
|
|
18519
|
+
...this.title !== void 0 ? { name: this.title } : {},
|
|
18520
|
+
// The pass's result. Paired with the `input` set when the trace opened, this is
|
|
18521
|
+
// what stops the consumer inferring the trace's I/O from a descendant.
|
|
18522
|
+
...this.lastAssistantText !== void 0 ? { output: this.lastAssistantText } : {}
|
|
18471
18523
|
});
|
|
18472
18524
|
this.trace.end();
|
|
18473
18525
|
this.trace = void 0;
|
|
@@ -18523,6 +18575,10 @@ var SessionEmitter = class {
|
|
|
18523
18575
|
name: this.title ?? this.defaultTraceName(),
|
|
18524
18576
|
sessionId: this.grouping ?? this.options.sessionId,
|
|
18525
18577
|
startTime: ts,
|
|
18578
|
+
// What the agent was asked. `TraceArgs.input` is construction-only — the SDK's
|
|
18579
|
+
// `update()` carries output alone — which is why `ingest` absorbs the user turn
|
|
18580
|
+
// before it calls this.
|
|
18581
|
+
...this.lastUserText ? { input: this.lastUserText } : {},
|
|
18526
18582
|
tags: [this.options.vendor, ...this.options.agentId ? ["subagent"] : []],
|
|
18527
18583
|
metadata: {
|
|
18528
18584
|
...this.meta,
|
|
@@ -18588,6 +18644,8 @@ var SessionEmitter = class {
|
|
|
18588
18644
|
if (!this.generation)
|
|
18589
18645
|
return;
|
|
18590
18646
|
const output = this.outputText.join("\n");
|
|
18647
|
+
if (output)
|
|
18648
|
+
this.lastAssistantText = output;
|
|
18591
18649
|
this.generation.end({
|
|
18592
18650
|
...output ? { outputMessages: [{ role: "assistant", content: output }] } : {},
|
|
18593
18651
|
...this.model !== void 0 ? { model: this.model } : {},
|
package/dist/bin/guard.mjs
CHANGED
|
@@ -196,6 +196,11 @@ function readSecure(path) {
|
|
|
196
196
|
throw new ConfigError(`${path} is mode ${mode.toString(8)}; must be 0600`);
|
|
197
197
|
return readFileSync(path, "utf8");
|
|
198
198
|
}
|
|
199
|
+
function isBlank(path) {
|
|
200
|
+
if (statSync(path).size === 0)
|
|
201
|
+
return true;
|
|
202
|
+
return readFileSync(path, "utf8").trim() === "";
|
|
203
|
+
}
|
|
199
204
|
function readJson(path) {
|
|
200
205
|
try {
|
|
201
206
|
return JSON.parse(readSecure(path));
|
|
@@ -234,6 +239,9 @@ function readConfigFile(vendor) {
|
|
|
234
239
|
if (!existsSync(configPath())) {
|
|
235
240
|
throw new ConfigMissingError(`${configPath()} does not exist \u2014 run init.mjs to create it`);
|
|
236
241
|
}
|
|
242
|
+
if (isBlank(configPath())) {
|
|
243
|
+
throw new ConfigMissingError(`${configPath()} is empty \u2014 run init.mjs to create it`);
|
|
244
|
+
}
|
|
237
245
|
const file = readJson(configPath());
|
|
238
246
|
if (!file) {
|
|
239
247
|
throw new ConfigError(`cannot read ${configPath()} \u2014 it exists but did not parse`);
|
package/dist/bin/spool.mjs
CHANGED
|
@@ -42,6 +42,11 @@ function readSecure(path) {
|
|
|
42
42
|
throw new ConfigError(`${path} is mode ${mode.toString(8)}; must be 0600`);
|
|
43
43
|
return readFileSync(path, "utf8");
|
|
44
44
|
}
|
|
45
|
+
function isBlank(path) {
|
|
46
|
+
if (statSync(path).size === 0)
|
|
47
|
+
return true;
|
|
48
|
+
return readFileSync(path, "utf8").trim() === "";
|
|
49
|
+
}
|
|
45
50
|
function readJson(path) {
|
|
46
51
|
try {
|
|
47
52
|
return JSON.parse(readSecure(path));
|
|
@@ -80,6 +85,9 @@ function readConfigFile(vendor) {
|
|
|
80
85
|
if (!existsSync(configPath())) {
|
|
81
86
|
throw new ConfigMissingError(`${configPath()} does not exist \u2014 run init.mjs to create it`);
|
|
82
87
|
}
|
|
88
|
+
if (isBlank(configPath())) {
|
|
89
|
+
throw new ConfigMissingError(`${configPath()} is empty \u2014 run init.mjs to create it`);
|
|
90
|
+
}
|
|
83
91
|
const file = readJson(configPath());
|
|
84
92
|
if (!file) {
|
|
85
93
|
throw new ConfigError(`cannot read ${configPath()} \u2014 it exists but did not parse`);
|
package/dist/bin/status.mjs
CHANGED
|
@@ -62,6 +62,11 @@ function readSecure(path) {
|
|
|
62
62
|
throw new ConfigError(`${path} is mode ${mode.toString(8)}; must be 0600`);
|
|
63
63
|
return readFileSync(path, "utf8");
|
|
64
64
|
}
|
|
65
|
+
function isBlank(path) {
|
|
66
|
+
if (statSync(path).size === 0)
|
|
67
|
+
return true;
|
|
68
|
+
return readFileSync(path, "utf8").trim() === "";
|
|
69
|
+
}
|
|
65
70
|
function readJson(path) {
|
|
66
71
|
try {
|
|
67
72
|
return JSON.parse(readSecure(path));
|
|
@@ -86,6 +91,9 @@ function readConfigFile(vendor) {
|
|
|
86
91
|
if (!existsSync(configPath())) {
|
|
87
92
|
throw new ConfigMissingError(`${configPath()} does not exist \u2014 run init.mjs to create it`);
|
|
88
93
|
}
|
|
94
|
+
if (isBlank(configPath())) {
|
|
95
|
+
throw new ConfigMissingError(`${configPath()} is empty \u2014 run init.mjs to create it`);
|
|
96
|
+
}
|
|
89
97
|
const file = readJson(configPath());
|
|
90
98
|
if (!file) {
|
|
91
99
|
throw new ConfigError(`cannot read ${configPath()} \u2014 it exists but did not parse`);
|
package/package.json
CHANGED