copillm 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agentconfig/render.js +169 -25
- package/dist/cli/configCommands.js +15 -2
- package/dist/cli/launchAgent.js +8 -0
- package/dist/cli/processSafetyNet.js +52 -0
- package/dist/cli/resolveAgent.js +4 -2
- package/dist/cli.js +139 -18
- package/dist/config/home.js +3 -0
- package/dist/config/logging.js +27 -5
- package/dist/models/anthropicDefaults.js +1 -0
- package/dist/server/proxy.js +292 -26
- package/dist/server/requestLifecycle.js +115 -0
- package/dist/translation/streamingOpenAIToAnthropic.js +77 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { acquireLock, inspectLock, LockAlreadyRunningError, releaseLock } from "
|
|
|
16
16
|
import { startProxyServer } from "./server/proxy.js";
|
|
17
17
|
import { defaultOutputDir, generateCodexHome } from "./codex/init.js";
|
|
18
18
|
import { defaultOutputDir as defaultPiOutputDir, generatePiHome } from "./pi/init.js";
|
|
19
|
-
import { getCopillmHome } from "./config/home.js";
|
|
19
|
+
import { debugLogPath, getCopillmHome } from "./config/home.js";
|
|
20
20
|
import { clearClaudeGatewayCache } from "./claude/cache.js";
|
|
21
21
|
import { detectClaudeSettingsConflicts, formatSettingsConflictWarning } from "./claude/settingsConflict.js";
|
|
22
22
|
import { buildClaudeExportCommand as buildClaudeExport, computeAnthropicDefaults, readModelIdsFromCache } from "./models/anthropicDefaults.js";
|
|
@@ -25,10 +25,12 @@ import { buildClaudeEnvBundle, buildCodexEnvBundle, buildPiEnvBundle } from "./c
|
|
|
25
25
|
import { launchAgent } from "./cli/launchAgent.js";
|
|
26
26
|
import { applyAgentConfig, formatApplyNotes } from "./agentconfig/apply.js";
|
|
27
27
|
import { registerConfigCommands } from "./cli/configCommands.js";
|
|
28
|
+
import { installProcessSafetyNet } from "./cli/processSafetyNet.js";
|
|
28
29
|
const logger = createLogger();
|
|
29
30
|
const program = new Command();
|
|
30
31
|
program.name("copillm").description("Local Copilot proxy").version("0.1.0");
|
|
31
32
|
program.enablePositionalOptions();
|
|
33
|
+
program.option("--debug", "Enable copillm debug mode (debug endpoint plus verbose daemon diagnostics)");
|
|
32
34
|
program
|
|
33
35
|
.command("login")
|
|
34
36
|
.description("[deprecated] Use `copillm auth login`")
|
|
@@ -128,6 +130,8 @@ program
|
|
|
128
130
|
.option("--no-pi", "Skip generating ~/.pi/agent/models.json for pi coding agent")
|
|
129
131
|
.option("--json", "JSON output")
|
|
130
132
|
.action(async (opts) => {
|
|
133
|
+
const debug = resolveCopillmDebug(opts.debug);
|
|
134
|
+
enableRuntimeDebug(debug);
|
|
131
135
|
if (opts.detach) {
|
|
132
136
|
// Fail fast on missing credentials rather than letting the detached
|
|
133
137
|
// child die silently and surface as a generic "start timed out" error.
|
|
@@ -137,6 +141,7 @@ program
|
|
|
137
141
|
}
|
|
138
142
|
const existingLock = await readLiveLock();
|
|
139
143
|
if (existingLock) {
|
|
144
|
+
const activeDebug = await warnIfDebugRequestedButInactive(debug, existingLock.port);
|
|
140
145
|
const codex = opts.codex === false ? null : await refreshCodexHome(existingLock.port, opts.codexModel ?? null);
|
|
141
146
|
const pi = opts.pi === false ? null : await refreshPiHome(existingLock.port);
|
|
142
147
|
const claude = buildClaudeExportCommand(existingLock.port, null);
|
|
@@ -144,7 +149,8 @@ program
|
|
|
144
149
|
port: existingLock.port,
|
|
145
150
|
pid: existingLock.pid,
|
|
146
151
|
mode: "already_running",
|
|
147
|
-
debug:
|
|
152
|
+
debug: activeDebug,
|
|
153
|
+
debugLogPath: null,
|
|
148
154
|
codex,
|
|
149
155
|
pi
|
|
150
156
|
});
|
|
@@ -152,6 +158,7 @@ program
|
|
|
152
158
|
status: "already_running",
|
|
153
159
|
pid: existingLock.pid,
|
|
154
160
|
port: existingLock.port,
|
|
161
|
+
debug: activeDebug,
|
|
155
162
|
url: `http://127.0.0.1:${existingLock.port}`,
|
|
156
163
|
codex_home: codex?.outDir ?? null,
|
|
157
164
|
codex_export_command: codex?.exportCommand ?? null,
|
|
@@ -168,12 +175,13 @@ program
|
|
|
168
175
|
return;
|
|
169
176
|
}
|
|
170
177
|
const daemonArgs = [process.argv[1], "daemon"];
|
|
171
|
-
if (
|
|
178
|
+
if (debug) {
|
|
172
179
|
daemonArgs.push("--debug");
|
|
173
180
|
}
|
|
174
181
|
const child = spawn(process.execPath, daemonArgs, {
|
|
175
182
|
detached: true,
|
|
176
|
-
stdio: "ignore"
|
|
183
|
+
stdio: "ignore",
|
|
184
|
+
env: daemonSpawnEnv(debug)
|
|
177
185
|
});
|
|
178
186
|
child.unref();
|
|
179
187
|
const started = await waitForDaemonReady(child.pid ?? null, 8_000);
|
|
@@ -187,7 +195,8 @@ program
|
|
|
187
195
|
port: started.port,
|
|
188
196
|
pid: started.pid,
|
|
189
197
|
mode: "detached",
|
|
190
|
-
debug
|
|
198
|
+
debug,
|
|
199
|
+
debugLogPath: currentDebugLogPath(debug),
|
|
191
200
|
codex,
|
|
192
201
|
pi
|
|
193
202
|
});
|
|
@@ -196,7 +205,8 @@ program
|
|
|
196
205
|
mode: "detached",
|
|
197
206
|
pid: started.pid,
|
|
198
207
|
port: started.port,
|
|
199
|
-
debug
|
|
208
|
+
debug,
|
|
209
|
+
debug_log_path: currentDebugLogPath(debug),
|
|
200
210
|
url: `http://127.0.0.1:${started.port}`,
|
|
201
211
|
codex_home: codex?.outDir ?? null,
|
|
202
212
|
codex_export_command: codex?.exportCommand ?? null,
|
|
@@ -216,8 +226,9 @@ program
|
|
|
216
226
|
}
|
|
217
227
|
// Foreground path: interactively prompt for login if needed.
|
|
218
228
|
await ensureAuthenticatedInteractive();
|
|
219
|
-
const started = await runDaemon({ debug
|
|
229
|
+
const started = await runDaemon({ debug });
|
|
220
230
|
if (started.kind === "already_running") {
|
|
231
|
+
const activeDebug = await warnIfDebugRequestedButInactive(debug, started.lock.port);
|
|
221
232
|
const codex = opts.codex === false ? null : await refreshCodexHome(started.lock.port, opts.codexModel ?? null);
|
|
222
233
|
const pi = opts.pi === false ? null : await refreshPiHome(started.lock.port);
|
|
223
234
|
const claude = buildClaudeExportCommand(started.lock.port, null);
|
|
@@ -225,7 +236,8 @@ program
|
|
|
225
236
|
port: started.lock.port,
|
|
226
237
|
pid: started.lock.pid,
|
|
227
238
|
mode: "already_running",
|
|
228
|
-
debug:
|
|
239
|
+
debug: activeDebug,
|
|
240
|
+
debugLogPath: null,
|
|
229
241
|
codex,
|
|
230
242
|
pi
|
|
231
243
|
});
|
|
@@ -233,6 +245,7 @@ program
|
|
|
233
245
|
status: "already_running",
|
|
234
246
|
pid: started.lock.pid,
|
|
235
247
|
port: started.lock.port,
|
|
248
|
+
debug: activeDebug,
|
|
236
249
|
url: `http://127.0.0.1:${started.lock.port}`,
|
|
237
250
|
codex_home: codex?.outDir ?? null,
|
|
238
251
|
codex_export_command: codex?.exportCommand ?? null,
|
|
@@ -255,7 +268,8 @@ program
|
|
|
255
268
|
port: started.port,
|
|
256
269
|
pid: process.pid,
|
|
257
270
|
mode: "foreground",
|
|
258
|
-
debug
|
|
271
|
+
debug,
|
|
272
|
+
debugLogPath: currentDebugLogPath(debug),
|
|
259
273
|
codex,
|
|
260
274
|
pi
|
|
261
275
|
});
|
|
@@ -264,7 +278,8 @@ program
|
|
|
264
278
|
mode: "foreground",
|
|
265
279
|
pid: process.pid,
|
|
266
280
|
port: started.port,
|
|
267
|
-
debug
|
|
281
|
+
debug,
|
|
282
|
+
debug_log_path: currentDebugLogPath(debug),
|
|
268
283
|
url: `http://127.0.0.1:${started.port}`,
|
|
269
284
|
caller_secret: started.callerSecret,
|
|
270
285
|
codex_home: codex?.outDir ?? null,
|
|
@@ -287,11 +302,13 @@ program
|
|
|
287
302
|
.description("Internal background command")
|
|
288
303
|
.option("--debug", "Enable debug endpoints")
|
|
289
304
|
.action(async (opts) => {
|
|
290
|
-
const
|
|
305
|
+
const debug = resolveCopillmDebug(opts.debug);
|
|
306
|
+
enableRuntimeDebug(debug);
|
|
307
|
+
const started = await runDaemon({ debug });
|
|
291
308
|
if (started.kind === "already_running") {
|
|
292
309
|
process.exit(0);
|
|
293
310
|
}
|
|
294
|
-
process.stdout.write(`copillm listening on http://127.0.0.1:${started.port}${
|
|
311
|
+
process.stdout.write(`copillm listening on http://127.0.0.1:${started.port}${debug ? " [debug]" : ""}\n`);
|
|
295
312
|
});
|
|
296
313
|
program
|
|
297
314
|
.command("stop")
|
|
@@ -654,7 +671,9 @@ program
|
|
|
654
671
|
.helpOption(false)
|
|
655
672
|
.argument("[args...]", "Args forwarded to codex")
|
|
656
673
|
.action(async (forwardedArgs, opts) => {
|
|
657
|
-
const
|
|
674
|
+
const debug = resolveCopillmDebug(opts.copillmDebug);
|
|
675
|
+
enableRuntimeDebug(debug);
|
|
676
|
+
const lock = await ensureDaemonRunningForLauncher({ debug });
|
|
658
677
|
const codex = await refreshCodexHome(lock.port, null);
|
|
659
678
|
if (!codex) {
|
|
660
679
|
throw new Error("Failed to prepare Codex home (see warning above).");
|
|
@@ -692,7 +711,9 @@ program
|
|
|
692
711
|
.helpOption(false)
|
|
693
712
|
.argument("[args...]", "Args forwarded to claude")
|
|
694
713
|
.action(async (forwardedArgs, opts) => {
|
|
695
|
-
const
|
|
714
|
+
const debug = resolveCopillmDebug(opts.copillmDebug);
|
|
715
|
+
enableRuntimeDebug(debug);
|
|
716
|
+
const lock = await ensureDaemonRunningForLauncher({ debug });
|
|
696
717
|
const claude = buildClaudeExportCommand(lock.port, null);
|
|
697
718
|
const pinnedSpec = opts.copillmUse ?? process.env.COPILLM_CLAUDE_VERSION ?? undefined;
|
|
698
719
|
const conflicts = detectClaudeSettingsConflicts(claude.bundle.env);
|
|
@@ -729,7 +750,9 @@ program
|
|
|
729
750
|
.helpOption(false)
|
|
730
751
|
.argument("[args...]", "Args forwarded to pi")
|
|
731
752
|
.action(async (forwardedArgs, opts) => {
|
|
732
|
-
const
|
|
753
|
+
const debug = resolveCopillmDebug(opts.copillmDebug);
|
|
754
|
+
enableRuntimeDebug(debug);
|
|
755
|
+
const lock = await ensureDaemonRunningForLauncher({ debug });
|
|
733
756
|
const pi = await refreshPiHome(lock.port);
|
|
734
757
|
if (!pi) {
|
|
735
758
|
throw new Error("Failed to prepare pi models.json (see warning above).");
|
|
@@ -754,6 +777,49 @@ program
|
|
|
754
777
|
});
|
|
755
778
|
process.exit(exitCode);
|
|
756
779
|
});
|
|
780
|
+
program
|
|
781
|
+
.command("copilot")
|
|
782
|
+
.description("Launch GitHub Copilot CLI reusing copillm's stored GitHub token (no second device flow)")
|
|
783
|
+
.option("--copillm-use <spec>", "Pin copilot package version (e.g. 1.0.52 or @github/copilot@1.0.52)")
|
|
784
|
+
.option("--copillm-profile <name>", "Override active profile from ~/.copillm/agent.toml for this launch")
|
|
785
|
+
.option("--copillm-no-config", "Skip agent.toml fan-out for this launch", false)
|
|
786
|
+
.allowUnknownOption(true)
|
|
787
|
+
.passThroughOptions()
|
|
788
|
+
.helpOption(false)
|
|
789
|
+
.argument("[args...]", "Args forwarded to copilot")
|
|
790
|
+
.action(async (forwardedArgs, opts) => {
|
|
791
|
+
const credential = await loadStoredCredential();
|
|
792
|
+
if (!credential) {
|
|
793
|
+
process.stderr.write("copillm: no stored GitHub credential — run `copillm auth login` first.\n");
|
|
794
|
+
process.exit(1);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const pinnedSpec = opts.copillmUse ?? process.env.COPILLM_COPILOT_VERSION ?? undefined;
|
|
798
|
+
const applyResult = applyAgentConfig({
|
|
799
|
+
agent: "copilot",
|
|
800
|
+
cwd: process.cwd(),
|
|
801
|
+
profileOverride: opts.copillmProfile ?? process.env.COPILLM_PROFILE ?? null,
|
|
802
|
+
skip: Boolean(opts.copillmNoConfig)
|
|
803
|
+
});
|
|
804
|
+
for (const line of formatApplyNotes(applyResult, "copilot")) {
|
|
805
|
+
process.stderr.write(`${line}\n`);
|
|
806
|
+
}
|
|
807
|
+
// Inject the stored GitHub OAuth token into the child env only — never
|
|
808
|
+
// export to the parent shell and never persist. Copilot CLI honours
|
|
809
|
+
// COPILOT_GITHUB_TOKEN ahead of its own stored credentials, so this
|
|
810
|
+
// short-circuits its device-flow login when copillm already has a token.
|
|
811
|
+
const env = {
|
|
812
|
+
...applyResult.envOverlay,
|
|
813
|
+
COPILOT_GITHUB_TOKEN: credential.token
|
|
814
|
+
};
|
|
815
|
+
const exitCode = await launchAgent({
|
|
816
|
+
agent: "copilot",
|
|
817
|
+
args: [...(forwardedArgs ?? []), ...applyResult.cliArgs],
|
|
818
|
+
env,
|
|
819
|
+
pinnedSpec
|
|
820
|
+
});
|
|
821
|
+
process.exit(exitCode);
|
|
822
|
+
});
|
|
757
823
|
registerConfigCommands(program);
|
|
758
824
|
program.parseAsync(process.argv).catch((error) => {
|
|
759
825
|
if (error instanceof Error) {
|
|
@@ -814,6 +880,7 @@ async function runDaemon(options) {
|
|
|
814
880
|
tokenManager.clear();
|
|
815
881
|
throw new Error(`No available port in configured range (${ports[0]}-${ports[ports.length - 1]}).`);
|
|
816
882
|
}
|
|
883
|
+
installProcessSafetyNet(logger);
|
|
817
884
|
let shuttingDown = false;
|
|
818
885
|
const shutdown = async () => {
|
|
819
886
|
if (shuttingDown) {
|
|
@@ -940,6 +1007,32 @@ function writeCommandOutput(opts, humanLine, payload) {
|
|
|
940
1007
|
}
|
|
941
1008
|
process.stdout.write(`${humanLine}\n`);
|
|
942
1009
|
}
|
|
1010
|
+
function resolveCopillmDebug(commandDebug) {
|
|
1011
|
+
return Boolean(commandDebug) || Boolean(program.opts().debug);
|
|
1012
|
+
}
|
|
1013
|
+
function enableRuntimeDebug(debug) {
|
|
1014
|
+
if (!debug) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
process.env.COPILLM_LOG_LEVEL = "debug";
|
|
1018
|
+
logger.level = "debug";
|
|
1019
|
+
}
|
|
1020
|
+
function currentDebugLogPath(debug) {
|
|
1021
|
+
if (!debug) {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
return process.env.COPILLM_LOG_FILE ?? debugLogPath();
|
|
1025
|
+
}
|
|
1026
|
+
function daemonSpawnEnv(debug) {
|
|
1027
|
+
if (!debug) {
|
|
1028
|
+
return process.env;
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
...process.env,
|
|
1032
|
+
COPILLM_LOG_LEVEL: "debug",
|
|
1033
|
+
COPILLM_LOG_FILE: currentDebugLogPath(true) ?? debugLogPath()
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
943
1036
|
function formatStopHumanLine(primary, cache) {
|
|
944
1037
|
if (cache.cleared) {
|
|
945
1038
|
return `${primary} Cleared Claude Code gateway cache.`;
|
|
@@ -1012,6 +1105,9 @@ function formatStartBanner(input) {
|
|
|
1012
1105
|
if (input.codex) {
|
|
1013
1106
|
lines.push(` ${input.codex.modelCount} Copilot models discovered \u00B7 default: ${input.codex.defaultModel}`);
|
|
1014
1107
|
}
|
|
1108
|
+
if (input.debugLogPath) {
|
|
1109
|
+
lines.push(` debug log: ${displayHomePath(input.debugLogPath)}`);
|
|
1110
|
+
}
|
|
1015
1111
|
if (input.pi) {
|
|
1016
1112
|
lines.push(` pi: wrote ${input.pi.modelCount} models to ${displayHomePath(input.pi.configPath)}${input.pi.backupPath ? ` (backed up prior config to ${displayHomePath(input.pi.backupPath)})` : ""}`);
|
|
1017
1113
|
}
|
|
@@ -1051,6 +1147,25 @@ async function probeLivez(port) {
|
|
|
1051
1147
|
return false;
|
|
1052
1148
|
}
|
|
1053
1149
|
}
|
|
1150
|
+
async function warnIfDebugRequestedButInactive(debugRequested, port) {
|
|
1151
|
+
if (!debugRequested) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
const active = await probeDebugEndpoint(port);
|
|
1155
|
+
if (!active) {
|
|
1156
|
+
process.stderr.write(`warning: copillm is already running without debug mode; run \`copillm stop\` then \`copillm --debug start --detach\` to enable daemon diagnostics.\n`);
|
|
1157
|
+
}
|
|
1158
|
+
return active;
|
|
1159
|
+
}
|
|
1160
|
+
async function probeDebugEndpoint(port) {
|
|
1161
|
+
try {
|
|
1162
|
+
const response = await fetch(`http://127.0.0.1:${port}/_debug`, { signal: AbortSignal.timeout(1_200) });
|
|
1163
|
+
return response.ok;
|
|
1164
|
+
}
|
|
1165
|
+
catch {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1054
1169
|
async function probeHealth(port) {
|
|
1055
1170
|
try {
|
|
1056
1171
|
const response = await fetch(`http://127.0.0.1:${port}/healthz`, { signal: AbortSignal.timeout(1_500) });
|
|
@@ -1155,15 +1270,21 @@ function parseAgentName(raw) {
|
|
|
1155
1270
|
}
|
|
1156
1271
|
async function ensureDaemonRunningForLauncher(opts) {
|
|
1157
1272
|
const live = await readLiveLock();
|
|
1158
|
-
if (live)
|
|
1273
|
+
if (live) {
|
|
1274
|
+
await warnIfDebugRequestedButInactive(opts.debug, live.port);
|
|
1159
1275
|
return live;
|
|
1160
|
-
|
|
1276
|
+
}
|
|
1277
|
+
const debugLog = currentDebugLogPath(opts.debug);
|
|
1278
|
+
process.stderr.write(opts.debug && debugLog
|
|
1279
|
+
? `Starting copillm in background with debug logging at ${displayHomePath(debugLog)}...\n`
|
|
1280
|
+
: `Starting copillm in background...\n`);
|
|
1161
1281
|
const daemonArgs = [process.argv[1], "daemon"];
|
|
1162
1282
|
if (opts.debug)
|
|
1163
1283
|
daemonArgs.push("--debug");
|
|
1164
1284
|
const child = spawn(process.execPath, daemonArgs, {
|
|
1165
1285
|
detached: true,
|
|
1166
|
-
stdio: "ignore"
|
|
1286
|
+
stdio: "ignore",
|
|
1287
|
+
env: daemonSpawnEnv(opts.debug)
|
|
1167
1288
|
});
|
|
1168
1289
|
child.unref();
|
|
1169
1290
|
const started = await waitForDaemonReady(child.pid ?? null, 10_000);
|
package/dist/config/home.js
CHANGED
|
@@ -32,6 +32,9 @@ export function modelsCachePath() {
|
|
|
32
32
|
export function modelsCacheReadPath() {
|
|
33
33
|
return resolveReadablePath("models.cache.json");
|
|
34
34
|
}
|
|
35
|
+
export function debugLogPath() {
|
|
36
|
+
return path.join(getCopillmHome(), "debug.log");
|
|
37
|
+
}
|
|
35
38
|
function resolveReadablePath(fileName) {
|
|
36
39
|
const canonical = path.join(getCopillmHome(), fileName);
|
|
37
40
|
if (fs.existsSync(canonical)) {
|
package/dist/config/logging.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import pino from "pino";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { applyModeIfSupported } from "./fsSecurity.js";
|
|
5
|
+
export function createLogger(input) {
|
|
6
|
+
const destinationPath = input?.destinationPath ?? process.env.COPILLM_LOG_FILE;
|
|
7
|
+
const options = {
|
|
8
|
+
level: input?.level ?? process.env.COPILLM_LOG_LEVEL ?? "info",
|
|
5
9
|
redact: {
|
|
6
10
|
paths: [
|
|
7
11
|
"req.headers.authorization",
|
|
@@ -27,7 +31,25 @@ export function createLogger() {
|
|
|
27
31
|
remove: true
|
|
28
32
|
},
|
|
29
33
|
transport: process.env.COPILLM_LOG_PRETTY === "1"
|
|
30
|
-
? { target: "pino-pretty", options: { colorize: true } }
|
|
34
|
+
? { target: "pino-pretty", options: { colorize: destinationPath ? false : true, destination: destinationPath ?? 2 } }
|
|
31
35
|
: undefined
|
|
32
|
-
}
|
|
36
|
+
};
|
|
37
|
+
if (process.env.COPILLM_LOG_PRETTY === "1") {
|
|
38
|
+
if (destinationPath) {
|
|
39
|
+
prepareLogFile(destinationPath);
|
|
40
|
+
}
|
|
41
|
+
return pino(options);
|
|
42
|
+
}
|
|
43
|
+
if (destinationPath) {
|
|
44
|
+
prepareLogFile(destinationPath);
|
|
45
|
+
return pino(options, pino.destination(destinationPath));
|
|
46
|
+
}
|
|
47
|
+
return pino(options, pino.destination(2));
|
|
48
|
+
}
|
|
49
|
+
function prepareLogFile(filePath) {
|
|
50
|
+
const resolvedPath = path.resolve(filePath);
|
|
51
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true, mode: 0o700 });
|
|
52
|
+
const fd = fs.openSync(resolvedPath, "a", 0o600);
|
|
53
|
+
fs.closeSync(fd);
|
|
54
|
+
applyModeIfSupported(resolvedPath, 0o600);
|
|
33
55
|
}
|