u-foo 2.5.6 → 2.5.7
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/bin/ucode.js +9 -0
- package/package.json +1 -1
- package/src/agents/launch/notifier.js +6 -0
- package/src/agents/prompts/native/index.js +1 -1
- package/src/agents/prompts/native/toolDescriptions/edit.js +1 -0
- package/src/code/agent.js +53 -1076
- package/src/code/busConsumer.js +504 -0
- package/src/code/dispatch.js +1 -6
- package/src/code/launcher/ucode.js +3 -251
- package/src/code/launcher/ucodeBootstrap.js +18 -1
- package/src/code/launcher/ucodeBuild.js +0 -3
- package/src/code/launcher/ucodeDoctor.js +24 -9
- package/src/code/launcher/ucodeRuntimeConfig.js +12 -3
- package/src/code/nativeRunner.js +62 -109
- package/src/code/repl.js +610 -0
- package/src/code/sessionStore.js +5 -1
- package/src/code/skills/injection.js +17 -1
- package/src/code/taskDecomposer.js +36 -29
- package/src/code/tools/common.js +34 -0
- package/src/code/tools/edit.js +11 -3
- package/src/coordination/bus/inject.js +52 -7
- package/src/coordination/bus/subscriber.js +33 -6
- package/src/runtime/daemon/deliveryScheduler.js +102 -2
- package/src/runtime/daemon/index.js +8 -1
- package/src/runtime/daemon/ops.js +23 -0
|
@@ -2,55 +2,6 @@ const { loadConfig } = require("../../config");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
|
|
5
|
-
function bundledModuleRoots() {
|
|
6
|
-
return [
|
|
7
|
-
path.join(__dirname, ".."),
|
|
8
|
-
];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function resolveFirstExisting(paths = []) {
|
|
12
|
-
for (const candidate of paths) {
|
|
13
|
-
if (!candidate) continue;
|
|
14
|
-
try {
|
|
15
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
16
|
-
} catch {
|
|
17
|
-
// ignore
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function defaultBundledCoreRoot() {
|
|
24
|
-
const root = path.join(__dirname, "..");
|
|
25
|
-
const agentEntry = path.join(root, "agent.js");
|
|
26
|
-
if (resolveFirstExisting([agentEntry])) return root;
|
|
27
|
-
return root;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function defaultBundledPromptFile() {
|
|
31
|
-
const moduleRoots = bundledModuleRoots();
|
|
32
|
-
const candidates = moduleRoots.map((root) => path.join(root, "UCODE_PROMPT.md"));
|
|
33
|
-
return resolveFirstExisting(candidates) || candidates[0];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function isWindowsPlatform() {
|
|
37
|
-
return process.platform === "win32";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function canExecutePath(filePath = "") {
|
|
41
|
-
const target = String(filePath || "").trim();
|
|
42
|
-
if (!target) return false;
|
|
43
|
-
try {
|
|
44
|
-
const stat = fs.statSync(target);
|
|
45
|
-
if (!stat.isFile()) return false;
|
|
46
|
-
if (isWindowsPlatform()) return true;
|
|
47
|
-
fs.accessSync(target, fs.constants.X_OK);
|
|
48
|
-
return true;
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
5
|
function isReadableFile(filePath = "") {
|
|
55
6
|
const target = String(filePath || "").trim();
|
|
56
7
|
if (!target) return false;
|
|
@@ -61,113 +12,6 @@ function isReadableFile(filePath = "") {
|
|
|
61
12
|
}
|
|
62
13
|
}
|
|
63
14
|
|
|
64
|
-
function resolveExecutableFromPath(command = "", env = process.env) {
|
|
65
|
-
const text = String(command || "").trim();
|
|
66
|
-
if (!text) return "";
|
|
67
|
-
if (path.isAbsolute(text) || text.includes("/") || text.includes("\\")) {
|
|
68
|
-
return canExecutePath(text) ? path.resolve(text) : "";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const pathText = String((env && env.PATH) || process.env.PATH || "").trim();
|
|
72
|
-
if (!pathText) return "";
|
|
73
|
-
const dirs = pathText.split(path.delimiter).map((item) => String(item || "").trim()).filter(Boolean);
|
|
74
|
-
if (dirs.length === 0) return "";
|
|
75
|
-
|
|
76
|
-
const hasExplicitExt = /\.[a-zA-Z0-9]+$/.test(text);
|
|
77
|
-
const exts = isWindowsPlatform()
|
|
78
|
-
? String((env && env.PATHEXT) || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
|
|
79
|
-
.split(";")
|
|
80
|
-
.map((item) => item.trim())
|
|
81
|
-
.filter(Boolean)
|
|
82
|
-
: [""];
|
|
83
|
-
const suffixes = hasExplicitExt ? [""] : exts;
|
|
84
|
-
|
|
85
|
-
for (const dir of dirs) {
|
|
86
|
-
for (const ext of suffixes) {
|
|
87
|
-
const candidate = path.join(dir, `${text}${ext}`);
|
|
88
|
-
if (canExecutePath(candidate)) return candidate;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return "";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function tokenizeCommand(raw = "") {
|
|
95
|
-
const text = String(raw || "");
|
|
96
|
-
const tokens = [];
|
|
97
|
-
let current = "";
|
|
98
|
-
let quote = "";
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
101
|
-
const ch = text[i];
|
|
102
|
-
if (quote) {
|
|
103
|
-
if (quote === "\"") {
|
|
104
|
-
if (ch === "\\") {
|
|
105
|
-
if (i + 1 < text.length) {
|
|
106
|
-
const next = text[i + 1];
|
|
107
|
-
if (next === "\"" || next === "\\") {
|
|
108
|
-
current += next;
|
|
109
|
-
i += 1;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
current += "\\";
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
} else if (quote === "'" && ch === "\\") {
|
|
117
|
-
current += "\\";
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (ch === quote) {
|
|
121
|
-
quote = "";
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
current += ch;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (ch === "'" || ch === "\"") {
|
|
129
|
-
quote = ch;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (ch === "\\") {
|
|
133
|
-
if (i + 1 < text.length) {
|
|
134
|
-
const next = text[i + 1];
|
|
135
|
-
if (/\s/.test(next) || next === "'" || next === "\"" || next === "\\") {
|
|
136
|
-
current += next;
|
|
137
|
-
i += 1;
|
|
138
|
-
} else {
|
|
139
|
-
current += "\\";
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
current += "\\";
|
|
143
|
-
}
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (/\s/.test(ch)) {
|
|
147
|
-
if (current) {
|
|
148
|
-
tokens.push(current);
|
|
149
|
-
current = "";
|
|
150
|
-
}
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
current += ch;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (quote) {
|
|
157
|
-
return String(raw || "").trim().split(/\s+/).filter(Boolean);
|
|
158
|
-
}
|
|
159
|
-
if (current) tokens.push(current);
|
|
160
|
-
return tokens;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function splitCommand(raw, fallback = "pi") {
|
|
164
|
-
const text = String(raw || "").trim();
|
|
165
|
-
if (!text) return { command: fallback, args: [] };
|
|
166
|
-
const parts = tokenizeCommand(text);
|
|
167
|
-
if (parts.length === 0) return { command: fallback, args: [] };
|
|
168
|
-
return { command: parts[0], args: parts.slice(1) };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
15
|
function hasAnyArg(args = [], names = []) {
|
|
172
16
|
if (!Array.isArray(args) || args.length === 0) return false;
|
|
173
17
|
const flags = new Set((Array.isArray(names) ? names : []).filter(Boolean));
|
|
@@ -182,19 +26,6 @@ function hasAnyArg(args = [], names = []) {
|
|
|
182
26
|
});
|
|
183
27
|
}
|
|
184
28
|
|
|
185
|
-
function pickBinEntry(binField = {}) {
|
|
186
|
-
if (typeof binField === "string" && binField.trim()) {
|
|
187
|
-
return binField.trim();
|
|
188
|
-
}
|
|
189
|
-
if (!binField || typeof binField !== "object") return "";
|
|
190
|
-
const entries = Object.entries(binField)
|
|
191
|
-
.filter(([, value]) => typeof value === "string" && value.trim());
|
|
192
|
-
if (entries.length === 0) return "";
|
|
193
|
-
const preferred = entries.find(([name]) => /^(ucode|core|cli)$/i.test(String(name)));
|
|
194
|
-
if (preferred) return preferred[1].trim();
|
|
195
|
-
return entries[0][1].trim();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
29
|
function normalizeAppendSystemPromptMode(value = "") {
|
|
199
30
|
const text = String(value || "").trim().toLowerCase();
|
|
200
31
|
if (text === "always" || text === "force" || text === "on" || text === "1" || text === "true") return "always";
|
|
@@ -202,20 +33,6 @@ function normalizeAppendSystemPromptMode(value = "") {
|
|
|
202
33
|
return "auto";
|
|
203
34
|
}
|
|
204
35
|
|
|
205
|
-
function isLikelyPiCoreCommand(command = "", args = []) {
|
|
206
|
-
const cmdText = String(command || "").trim();
|
|
207
|
-
const cmdBase = path.basename(cmdText).toLowerCase();
|
|
208
|
-
if (cmdBase === "ucode" || cmdBase === "ucode.exe") return true;
|
|
209
|
-
|
|
210
|
-
const joined = [cmdText, ...(Array.isArray(args) ? args : [])]
|
|
211
|
-
.map((part) => String(part || "").toLowerCase())
|
|
212
|
-
.join(" ");
|
|
213
|
-
if (!joined) return false;
|
|
214
|
-
if (joined.includes("/src/code/agent.js")) return true;
|
|
215
|
-
if (joined.includes("\\src\\code\\agent.js")) return true;
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
36
|
function readLastArgValue(args = [], flag = "") {
|
|
220
37
|
if (!Array.isArray(args) || !flag) return "";
|
|
221
38
|
let value = "";
|
|
@@ -238,54 +55,6 @@ function readLastArgValue(args = [], flag = "") {
|
|
|
238
55
|
return value;
|
|
239
56
|
}
|
|
240
57
|
|
|
241
|
-
function resolveCoreFromPath(coreRoot = "") {
|
|
242
|
-
const requestedRoot = String(coreRoot || "").trim();
|
|
243
|
-
if (!requestedRoot) return null;
|
|
244
|
-
let stat;
|
|
245
|
-
try {
|
|
246
|
-
stat = fs.statSync(requestedRoot);
|
|
247
|
-
} catch {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
if (!stat.isDirectory()) return null;
|
|
251
|
-
|
|
252
|
-
const candidates = [
|
|
253
|
-
requestedRoot,
|
|
254
|
-
path.join(requestedRoot, "packages", "coding-agent"),
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
for (const root of candidates) {
|
|
258
|
-
const packageFile = path.join(root, "package.json");
|
|
259
|
-
let pkg = null;
|
|
260
|
-
try {
|
|
261
|
-
pkg = JSON.parse(fs.readFileSync(packageFile, "utf8"));
|
|
262
|
-
} catch {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
const binRel = pickBinEntry(pkg && pkg.bin ? pkg.bin : {});
|
|
266
|
-
if (!binRel) continue;
|
|
267
|
-
const binAbs = path.resolve(root, binRel);
|
|
268
|
-
if (!fs.existsSync(binAbs)) continue;
|
|
269
|
-
return {
|
|
270
|
-
command: process.execPath,
|
|
271
|
-
args: [binAbs],
|
|
272
|
-
root,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function resolveCandidateCoreRoot({
|
|
279
|
-
env = process.env,
|
|
280
|
-
config = {},
|
|
281
|
-
} = {}) {
|
|
282
|
-
// Native-only mode: external pi-mono path is no longer used as launch fallback.
|
|
283
|
-
// Keep function for compatibility with older diagnostic surfaces.
|
|
284
|
-
void env;
|
|
285
|
-
void config;
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
58
|
function resolveNativeFallbackCommand({ env = process.env } = {}) {
|
|
290
59
|
void env;
|
|
291
60
|
const entry = path.resolve(__dirname, "..", "agent.js");
|
|
@@ -361,16 +130,10 @@ function resolveUcodeLaunch({
|
|
|
361
130
|
|| config.ucodeAppendSystemPromptMode
|
|
362
131
|
|| "auto"
|
|
363
132
|
);
|
|
133
|
+
// Native-only mode: the bundled native core always supports
|
|
134
|
+
// --append-system-prompt, so only mode=never suppresses injection.
|
|
364
135
|
const hasSystemPromptArg = hasAnyArg(finalArgs, ["--system-prompt", "--append-system-prompt"]);
|
|
365
|
-
|
|
366
|
-
|| (
|
|
367
|
-
appendSystemPromptMode === "auto"
|
|
368
|
-
&& (
|
|
369
|
-
nativeCore.kind === "native"
|
|
370
|
-
|| isLikelyPiCoreCommand(command, finalArgs)
|
|
371
|
-
)
|
|
372
|
-
);
|
|
373
|
-
if (!hasSystemPromptArg && appendSystemPrompt && appendSystemPromptMode !== "never" && appendSupported) {
|
|
136
|
+
if (!hasSystemPromptArg && appendSystemPrompt && appendSystemPromptMode !== "never") {
|
|
374
137
|
finalArgs.push("--append-system-prompt", appendSystemPrompt);
|
|
375
138
|
}
|
|
376
139
|
const effectiveProvider = readLastArgValue(finalArgs, "--provider");
|
|
@@ -398,20 +161,9 @@ function resolveUcodeLaunch({
|
|
|
398
161
|
}
|
|
399
162
|
|
|
400
163
|
module.exports = {
|
|
401
|
-
bundledModuleRoots,
|
|
402
|
-
defaultBundledCoreRoot,
|
|
403
|
-
defaultBundledPromptFile,
|
|
404
|
-
tokenizeCommand,
|
|
405
|
-
splitCommand,
|
|
406
164
|
hasAnyArg,
|
|
407
|
-
pickBinEntry,
|
|
408
165
|
normalizeAppendSystemPromptMode,
|
|
409
|
-
isLikelyPiCoreCommand,
|
|
410
166
|
readLastArgValue,
|
|
411
|
-
resolveCoreFromPath,
|
|
412
|
-
resolveCandidateCoreRoot,
|
|
413
|
-
canExecutePath,
|
|
414
|
-
resolveExecutableFromPath,
|
|
415
167
|
resolveNativeFallbackCommand,
|
|
416
168
|
resolveUcodeLaunch,
|
|
417
169
|
};
|
|
@@ -86,16 +86,32 @@ function mergeDefaultUfooProtocolPrompt(projectRoot = "", promptText = "") {
|
|
|
86
86
|
return [defaultPrompt, currentPrompt].filter(Boolean).join("\n\n");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function isPathInsideRoot(root = "", target = "") {
|
|
90
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
91
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
function prepareUcodeBootstrap({
|
|
90
95
|
projectRoot = process.cwd(),
|
|
91
96
|
promptFile = "",
|
|
92
97
|
promptText = "",
|
|
93
98
|
targetFile = "",
|
|
94
99
|
includeDefaultProtocol = true,
|
|
100
|
+
allowOutsideProjectRoot = false,
|
|
95
101
|
} = {}) {
|
|
96
102
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
97
103
|
const resolvedPrompt = String(promptFile || "").trim();
|
|
98
|
-
const resolvedTarget =
|
|
104
|
+
const resolvedTarget = path.resolve(
|
|
105
|
+
resolvedProjectRoot,
|
|
106
|
+
String(targetFile || "").trim() || defaultBootstrapPath(resolvedProjectRoot)
|
|
107
|
+
);
|
|
108
|
+
// The target may come from the project config (.ufoo/config.json), which is
|
|
109
|
+
// attacker-controlled in a cloned repository. Refuse to write outside the
|
|
110
|
+
// project root unless the caller marked the target as user-trusted (e.g.
|
|
111
|
+
// the user's own UFOO_UCODE_BOOTSTRAP_FILE env var).
|
|
112
|
+
if (!allowOutsideProjectRoot && !isPathInsideRoot(resolvedProjectRoot, resolvedTarget)) {
|
|
113
|
+
throw new Error(`ucode bootstrap target must stay inside the project root: ${resolvedTarget}`);
|
|
114
|
+
}
|
|
99
115
|
|
|
100
116
|
const inlinePromptText = String(promptText || "").trim();
|
|
101
117
|
const resolvedPromptText = inlinePromptText || readFileSafe(resolvedPrompt);
|
|
@@ -129,5 +145,6 @@ module.exports = {
|
|
|
129
145
|
resolveProjectRules,
|
|
130
146
|
defaultBootstrapPath,
|
|
131
147
|
buildBootstrapContent,
|
|
148
|
+
isPathInsideRoot,
|
|
132
149
|
prepareUcodeBootstrap,
|
|
133
150
|
};
|
|
@@ -27,15 +27,12 @@ function inspectUcodeBuildSetup({
|
|
|
27
27
|
const workspaceRoot = root;
|
|
28
28
|
const distCliPath = Array.isArray(native.args) && native.args[0] ? path.resolve(native.args[0]) : "";
|
|
29
29
|
const distCliExists = Boolean(distCliPath && fs.existsSync(distCliPath));
|
|
30
|
-
const nodeModulesPath = "";
|
|
31
30
|
return {
|
|
32
31
|
projectRoot: root,
|
|
33
32
|
coreRoot,
|
|
34
33
|
workspaceRoot,
|
|
35
34
|
distCliPath,
|
|
36
35
|
distCliExists,
|
|
37
|
-
nodeModulesPath,
|
|
38
|
-
nodeModulesExists: Boolean(nodeModulesPath && fs.existsSync(nodeModulesPath)),
|
|
39
36
|
};
|
|
40
37
|
}
|
|
41
38
|
|
|
@@ -131,6 +131,9 @@ function formatUcodeDoctor(result = {}) {
|
|
|
131
131
|
}
|
|
132
132
|
lines.push(`prompt: ${result.promptFile || "(none)"}${result.promptFile && !result.promptExists ? " (missing)" : ""}`);
|
|
133
133
|
lines.push(`bootstrap: ${result.bootstrapFile || "(none)"}`);
|
|
134
|
+
if (result.bootstrapPrepared && result.bootstrapPrepared.ok === false) {
|
|
135
|
+
lines.push(` warning: bootstrap preparation failed: ${result.bootstrapPrepared.error || "unknown error"}`);
|
|
136
|
+
}
|
|
134
137
|
if (result.build && result.build.coreRoot) {
|
|
135
138
|
lines.push(`build: ${result.build.distCliExists ? "ready" : "missing dist"}`);
|
|
136
139
|
lines.push(` core root: ${result.build.coreRoot}`);
|
|
@@ -165,15 +168,27 @@ function prepareAndInspectUcode({
|
|
|
165
168
|
loadConfigImpl = loadConfig,
|
|
166
169
|
} = {}) {
|
|
167
170
|
const inspection = inspectUcodeSetup({ projectRoot, env, loadConfigImpl });
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
let prepared;
|
|
172
|
+
try {
|
|
173
|
+
prepared = prepareUcodeBootstrap({
|
|
174
|
+
projectRoot: inspection.projectRoot,
|
|
175
|
+
promptFile: inspection.promptFile,
|
|
176
|
+
targetFile: inspection.bootstrapFile,
|
|
177
|
+
// The user's own env var is trusted; a bootstrap path coming from the
|
|
178
|
+
// project config must stay inside the project root.
|
|
179
|
+
allowOutsideProjectRoot: Boolean(String(env.UFOO_UCODE_BOOTSTRAP_FILE || "").trim()),
|
|
180
|
+
// The native core already injects the ufoo protocol via the modular
|
|
181
|
+
// prompt (src/agents/prompts/native/ufoo.js); inlining it into the
|
|
182
|
+
// bootstrap file would append the same protocol text a second time.
|
|
183
|
+
includeDefaultProtocol: false,
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
prepared = {
|
|
187
|
+
ok: false,
|
|
188
|
+
file: inspection.bootstrapFile,
|
|
189
|
+
error: err && err.message ? err.message : "failed to prepare bootstrap",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
177
192
|
return {
|
|
178
193
|
...inspection,
|
|
179
194
|
bootstrapPrepared: prepared,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
+
const { randomUUID } = require("crypto");
|
|
3
4
|
const { loadGlobalUcodeConfig } = require("../../config");
|
|
4
5
|
|
|
5
6
|
function readJson(filePath = "", fallback = {}) {
|
|
@@ -14,10 +15,17 @@ function readJson(filePath = "", fallback = {}) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function writeJson(filePath = "", data = {}) {
|
|
18
|
+
function writeJson(filePath = "", data = {}, { mode = 0 } = {}) {
|
|
18
19
|
if (!filePath) return;
|
|
19
20
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
20
|
-
|
|
21
|
+
// Write to a temp file and rename so a crash mid-write cannot leave a
|
|
22
|
+
// truncated JSON file behind.
|
|
23
|
+
const tmpFile = `${filePath}.${process.pid}-${randomUUID()}.tmp`;
|
|
24
|
+
fs.writeFileSync(tmpFile, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
25
|
+
fs.renameSync(tmpFile, filePath);
|
|
26
|
+
if (mode) {
|
|
27
|
+
fs.chmodSync(filePath, mode);
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
function resolveRuntimeValues({
|
|
@@ -103,7 +111,8 @@ function prepareUcodeRuntimeConfig({
|
|
|
103
111
|
type: "api_key",
|
|
104
112
|
key: inspection.apiKey,
|
|
105
113
|
};
|
|
106
|
-
|
|
114
|
+
// auth.json carries API keys: keep it readable only by the owner.
|
|
115
|
+
writeJson(inspection.authFile, auth, { mode: 0o600 });
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
if (inspection.provider && inspection.baseUrl) {
|