dotcms 0.2.1 → 26.9.9-1
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/index.js +473 -266
- package/package.json +23 -23
package/index.js
CHANGED
|
@@ -57,6 +57,24 @@ var SERVER_ENV = {
|
|
|
57
57
|
};
|
|
58
58
|
var SKILLS_SOURCE = "dotCMS/agent-toolkit";
|
|
59
59
|
|
|
60
|
+
// libs/sdk/cli/src/shared/env.ts
|
|
61
|
+
var ENV_KEYS = {
|
|
62
|
+
url: "DOTCMS_URL",
|
|
63
|
+
password: "DOTCMS_PASSWORD",
|
|
64
|
+
authToken: "DOTCMS_AUTH_TOKEN",
|
|
65
|
+
codexHome: "CODEX_HOME"
|
|
66
|
+
};
|
|
67
|
+
function readEnv(key) {
|
|
68
|
+
const value = process.env[key];
|
|
69
|
+
return value && value.trim() !== "" ? value : void 0;
|
|
70
|
+
}
|
|
71
|
+
function envWithoutSecrets(extra = {}) {
|
|
72
|
+
const clean = { ...process.env };
|
|
73
|
+
delete clean[ENV_KEYS.authToken];
|
|
74
|
+
delete clean[ENV_KEYS.password];
|
|
75
|
+
return { ...clean, ...extra };
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
// libs/sdk/cli/src/commands/agent/connect.ts
|
|
61
79
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
62
80
|
function classify(stderr) {
|
|
@@ -71,11 +89,17 @@ async function confirmConnection(args) {
|
|
|
71
89
|
const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
72
90
|
const child = childProcess.spawn("npx", ["-y", MCP_SERVER_PACKAGE], {
|
|
73
91
|
stdio: ["pipe", "pipe", "pipe"],
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
// `npx` is `npx.cmd` on Windows and Node refuses `.cmd` without a shell since the
|
|
93
|
+
// CVE-2024-27980 fix — so FR-024a failed on every Windows run, reporting a broken
|
|
94
|
+
// server for a configuration that was written correctly.
|
|
95
|
+
shell: process.platform === "win32",
|
|
96
|
+
// This child needs the URL and the token; it has no use for the developer's
|
|
97
|
+
// DOTCMS_PASSWORD, and it is an unpinned `@latest` package by design — so the smaller
|
|
98
|
+
// the environment it sees, the better.
|
|
99
|
+
env: envWithoutSecrets({
|
|
76
100
|
[SERVER_ENV.url]: args.url,
|
|
77
101
|
[SERVER_ENV.token]: args.token
|
|
78
|
-
}
|
|
102
|
+
})
|
|
79
103
|
});
|
|
80
104
|
let stderr = "";
|
|
81
105
|
child.stderr?.on("data", (chunk) => {
|
|
@@ -89,7 +113,15 @@ async function confirmConnection(args) {
|
|
|
89
113
|
settled = true;
|
|
90
114
|
clearTimeout(timer);
|
|
91
115
|
try {
|
|
92
|
-
child.
|
|
116
|
+
child.stdin?.end();
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (process.platform === "win32" && child.pid) {
|
|
121
|
+
childProcess.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"]);
|
|
122
|
+
} else {
|
|
123
|
+
child.kill();
|
|
124
|
+
}
|
|
93
125
|
} catch {
|
|
94
126
|
}
|
|
95
127
|
resolve2(result);
|
|
@@ -112,6 +144,17 @@ async function confirmConnection(args) {
|
|
|
112
144
|
continue;
|
|
113
145
|
try {
|
|
114
146
|
const message = JSON.parse(line);
|
|
147
|
+
if (message.id === 1) {
|
|
148
|
+
child.stdin?.write(
|
|
149
|
+
`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}
|
|
150
|
+
`
|
|
151
|
+
);
|
|
152
|
+
child.stdin?.write(
|
|
153
|
+
`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" })}
|
|
154
|
+
`
|
|
155
|
+
);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
115
158
|
const tools = message.result?.tools;
|
|
116
159
|
if (Array.isArray(tools))
|
|
117
160
|
finish({ ok: true, toolCount: tools.length });
|
|
@@ -144,108 +187,23 @@ async function confirmConnection(args) {
|
|
|
144
187
|
})}
|
|
145
188
|
`
|
|
146
189
|
);
|
|
147
|
-
child.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" })}
|
|
148
|
-
`);
|
|
149
190
|
});
|
|
150
191
|
}
|
|
151
192
|
|
|
152
193
|
// libs/sdk/cli/src/commands/agent/gitignore.ts
|
|
194
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
153
195
|
import { existsSync } from "node:fs";
|
|
154
|
-
import * as
|
|
155
|
-
import * as path from "node:path";
|
|
156
|
-
function findRepositoryRoot(from) {
|
|
157
|
-
let dir = path.resolve(from);
|
|
158
|
-
for (; ; ) {
|
|
159
|
-
if (existsSync(path.join(dir, ".git")))
|
|
160
|
-
return dir;
|
|
161
|
-
const parent = path.dirname(dir);
|
|
162
|
-
if (parent === dir)
|
|
163
|
-
return null;
|
|
164
|
-
dir = parent;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function protectFromVersionControl(args) {
|
|
168
|
-
const warnings = [];
|
|
169
|
-
const root = findRepositoryRoot(args.cwd);
|
|
170
|
-
const committed = new Set(args.committedByConvention ?? []);
|
|
171
|
-
for (const file of args.files) {
|
|
172
|
-
if (committed.has(file)) {
|
|
173
|
-
warnings.push(
|
|
174
|
-
`${path.basename(file)} is normally committed to version control \u2014 it now holds a token, so committing it would publish that token.`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (!root) {
|
|
179
|
-
warnings.push(
|
|
180
|
-
"This directory is not under version control, so these files are unprotected \u2014 nothing here can exclude them for you."
|
|
181
|
-
);
|
|
182
|
-
return { files: args.files, inRepository: false, excluded: false, warnings };
|
|
183
|
-
}
|
|
184
|
-
const proceed = args.confirmExclude ? await args.confirmExclude(args.files) : false;
|
|
185
|
-
if (!proceed) {
|
|
186
|
-
return { files: args.files, inRepository: true, excluded: false, warnings };
|
|
187
|
-
}
|
|
188
|
-
const gitignorePath = path.join(root, ".gitignore");
|
|
189
|
-
let current = "";
|
|
190
|
-
try {
|
|
191
|
-
current = await fs.readFile(gitignorePath, "utf8");
|
|
192
|
-
} catch {
|
|
193
|
-
}
|
|
194
|
-
const already = new Set(current.split("\n").map((line) => line.trim()));
|
|
195
|
-
const toAdd = args.files.map((file) => path.relative(root, file).split(path.sep).join("/")).filter((entry) => !already.has(entry));
|
|
196
|
-
if (toAdd.length) {
|
|
197
|
-
const prefix = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
198
|
-
const block = `${prefix}
|
|
199
|
-
# dotCMS agent configuration \u2014 contains an access token
|
|
200
|
-
${toAdd.join("\n")}
|
|
201
|
-
`;
|
|
202
|
-
await fs.writeFile(gitignorePath, current + block, "utf8");
|
|
203
|
-
}
|
|
204
|
-
return { files: args.files, inRepository: true, excluded: true, warnings };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// libs/sdk/cli/src/commands/agent/skills.ts
|
|
208
|
-
import * as childProcess2 from "node:child_process";
|
|
209
|
-
function buildSkillsArgs(agentIds, global) {
|
|
210
|
-
return [
|
|
211
|
-
"-y",
|
|
212
|
-
"skills",
|
|
213
|
-
"add",
|
|
214
|
-
SKILLS_SOURCE,
|
|
215
|
-
...agentIds.flatMap((id) => ["-a", id]),
|
|
216
|
-
...global ? ["-g"] : [],
|
|
217
|
-
"-y"
|
|
218
|
-
];
|
|
219
|
-
}
|
|
220
|
-
async function installSkills(args) {
|
|
221
|
-
const argv = buildSkillsArgs(args.agentIds, args.global);
|
|
222
|
-
const command = `npx ${argv.join(" ")}`;
|
|
223
|
-
try {
|
|
224
|
-
const result = childProcess2.spawnSync("npx", argv, { stdio: "inherit" });
|
|
225
|
-
if (result.status === 0)
|
|
226
|
-
return { ok: true, command };
|
|
227
|
-
return { ok: false, command, reason: `skills exited with code ${result.status}` };
|
|
228
|
-
} catch (error) {
|
|
229
|
-
return { ok: false, command, reason: error.message };
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// libs/sdk/cli/src/commands/agent/targets/registry.ts
|
|
234
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
235
|
-
import * as os from "node:os";
|
|
196
|
+
import * as fs2 from "node:fs/promises";
|
|
236
197
|
import * as path2 from "node:path";
|
|
237
198
|
|
|
238
|
-
// libs/sdk/cli/src/shared/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const value = process.env[key];
|
|
247
|
-
return value && value.trim() !== "" ? value : void 0;
|
|
248
|
-
}
|
|
199
|
+
// libs/sdk/cli/src/shared/config-file.ts
|
|
200
|
+
import {
|
|
201
|
+
findNodeAtLocation,
|
|
202
|
+
parse as parseJsonc,
|
|
203
|
+
parseTree
|
|
204
|
+
} from "jsonc-parser";
|
|
205
|
+
import * as fs from "node:fs/promises";
|
|
206
|
+
import * as path from "node:path";
|
|
249
207
|
|
|
250
208
|
// libs/sdk/cli/src/shared/errors.ts
|
|
251
209
|
var CliError = class extends Error {
|
|
@@ -256,11 +214,31 @@ var CliError = class extends Error {
|
|
|
256
214
|
};
|
|
257
215
|
var UsageError = class extends CliError {
|
|
258
216
|
};
|
|
217
|
+
function withoutCredentials(value) {
|
|
218
|
+
try {
|
|
219
|
+
const u = new URL(value);
|
|
220
|
+
u.username = "";
|
|
221
|
+
u.password = "";
|
|
222
|
+
const path5 = u.pathname === "/" && !/\/$/.test(value) ? "" : u.pathname;
|
|
223
|
+
return `${u.protocol}//${u.host}${path5}${u.search}`;
|
|
224
|
+
} catch {
|
|
225
|
+
return value.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/]*@/i, "$1");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
259
228
|
var InvalidUrlError = class extends CliError {
|
|
260
229
|
constructor(value) {
|
|
261
|
-
const
|
|
230
|
+
const safe = withoutCredentials(value);
|
|
231
|
+
const host = safe.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/^\/+/, "");
|
|
232
|
+
super(
|
|
233
|
+
`"${safe}" is not a valid instance address. Pass it with an http:// or https:// scheme, e.g. https://${host || "demo.dotcms.com"}`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var CredentialInUrlError = class extends CliError {
|
|
238
|
+
constructor(url) {
|
|
239
|
+
const safe = withoutCredentials(url);
|
|
262
240
|
super(
|
|
263
|
-
`
|
|
241
|
+
`The instance address must not carry a username or password. Use ${safe} on its own \u2014 setup will ask you to sign in, or pass --authToken.`
|
|
264
242
|
);
|
|
265
243
|
}
|
|
266
244
|
};
|
|
@@ -273,7 +251,15 @@ var InstanceUnreachableError = class extends CliError {
|
|
|
273
251
|
};
|
|
274
252
|
var NotADotCmsInstanceError = class extends CliError {
|
|
275
253
|
constructor(url) {
|
|
276
|
-
|
|
254
|
+
let hint = " Check the address.";
|
|
255
|
+
try {
|
|
256
|
+
const parsed = new URL(url);
|
|
257
|
+
if (parsed.pathname !== "/" && parsed.pathname !== "") {
|
|
258
|
+
hint = ` That address includes a path; the instance itself is probably ${parsed.origin}.`;
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
super(`${url} is not a valid dotCMS instance.${hint}`);
|
|
277
263
|
}
|
|
278
264
|
};
|
|
279
265
|
var CredentialsRejectedError = class extends CliError {
|
|
@@ -318,6 +304,13 @@ var NoConfigPathError = class extends CliError {
|
|
|
318
304
|
);
|
|
319
305
|
}
|
|
320
306
|
};
|
|
307
|
+
var UnreadableConfigError = class extends CliError {
|
|
308
|
+
constructor(file, reason) {
|
|
309
|
+
super(
|
|
310
|
+
`${file} could not be read \u2014 ${reason}. Nothing was changed. Check the file's permissions, or re-run with --skip-mcp.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
321
314
|
var MalformedConfigError = class extends CliError {
|
|
322
315
|
/** The format is passed, not sniffed from the extension: both callers know exactly which
|
|
323
316
|
* parser just refused the file. */
|
|
@@ -328,17 +321,257 @@ var MalformedConfigError = class extends CliError {
|
|
|
328
321
|
}
|
|
329
322
|
};
|
|
330
323
|
|
|
324
|
+
// libs/sdk/cli/src/shared/config-file.ts
|
|
325
|
+
var CAN_RESTRICT = process.platform !== "win32";
|
|
326
|
+
var FILE_MODE = 384;
|
|
327
|
+
var DIR_MODE = 448;
|
|
328
|
+
async function readFileIfPresent(file) {
|
|
329
|
+
try {
|
|
330
|
+
return await fs.readFile(file, "utf8");
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (error.code === "ENOENT")
|
|
333
|
+
return null;
|
|
334
|
+
throw new UnreadableConfigError(file, error.message);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function writeFileAtomic(file, contents) {
|
|
338
|
+
const temp = `${file}.dotcms-${process.pid}.tmp`;
|
|
339
|
+
try {
|
|
340
|
+
await fs.writeFile(temp, contents, { encoding: "utf8", mode: FILE_MODE });
|
|
341
|
+
} catch {
|
|
342
|
+
await fs.writeFile(file, contents, { encoding: "utf8", mode: FILE_MODE });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
await fs.rename(temp, file);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
await fs.rm(temp, { force: true });
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
var PARSE_OPTIONS = { allowTrailingComma: true, allowEmptyContent: true };
|
|
353
|
+
function parseOrThrow(raw, file) {
|
|
354
|
+
if (raw.trim() === "")
|
|
355
|
+
return {};
|
|
356
|
+
const errors = [];
|
|
357
|
+
const doc = parseJsonc(raw, errors, PARSE_OPTIONS);
|
|
358
|
+
if (errors.length > 0 || doc === void 0)
|
|
359
|
+
throw new MalformedConfigError(file);
|
|
360
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
361
|
+
throw new MalformedConfigError(file);
|
|
362
|
+
}
|
|
363
|
+
return doc;
|
|
364
|
+
}
|
|
365
|
+
async function readJsonDocument(file) {
|
|
366
|
+
const raw = await readFileIfPresent(file);
|
|
367
|
+
return raw === null ? null : parseOrThrow(raw, file);
|
|
368
|
+
}
|
|
369
|
+
function detectIndent(raw) {
|
|
370
|
+
const match = raw.match(/\n([ \t]+)\S/);
|
|
371
|
+
if (!match)
|
|
372
|
+
return { insertSpaces: true, tabSize: 2 };
|
|
373
|
+
const indent = match[1];
|
|
374
|
+
return indent.startsWith(" ") ? { insertSpaces: false, tabSize: 1 } : { insertSpaces: true, tabSize: indent.length };
|
|
375
|
+
}
|
|
376
|
+
async function hasEntry(args) {
|
|
377
|
+
const doc = await readJsonDocument(args.file).catch(() => null);
|
|
378
|
+
const container = doc?.[args.containerKey] ?? {};
|
|
379
|
+
return Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
380
|
+
}
|
|
381
|
+
async function restrictFile(file, canRestrict = CAN_RESTRICT) {
|
|
382
|
+
if (!canRestrict)
|
|
383
|
+
return false;
|
|
384
|
+
await fs.chmod(file, FILE_MODE);
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
async function ensureDir(dir) {
|
|
388
|
+
const created = await fs.mkdir(dir, { recursive: true });
|
|
389
|
+
if (created && CAN_RESTRICT)
|
|
390
|
+
await fs.chmod(dir, DIR_MODE).catch(() => void 0);
|
|
391
|
+
}
|
|
392
|
+
function renderAt(value, unit, depth) {
|
|
393
|
+
const base = unit.repeat(depth);
|
|
394
|
+
return JSON.stringify(value, null, unit).split("\n").map((line, i) => i === 0 ? line : base + line).join("\n");
|
|
395
|
+
}
|
|
396
|
+
function setProperty(raw, object, key, value, unit, depth) {
|
|
397
|
+
const rendered = renderAt(value, unit, depth);
|
|
398
|
+
const properties = object.children ?? [];
|
|
399
|
+
const existing = properties.find((p) => p.children?.[0]?.value === key);
|
|
400
|
+
if (existing?.children?.[1]) {
|
|
401
|
+
const node = existing.children[1];
|
|
402
|
+
return raw.slice(0, node.offset) + rendered + raw.slice(node.offset + node.length);
|
|
403
|
+
}
|
|
404
|
+
const insertion = `"${key}": ${rendered}`;
|
|
405
|
+
const last = properties[properties.length - 1];
|
|
406
|
+
if (last) {
|
|
407
|
+
const end = last.offset + last.length;
|
|
408
|
+
return `${raw.slice(0, end)},
|
|
409
|
+
${unit.repeat(depth)}${insertion}${raw.slice(end)}`;
|
|
410
|
+
}
|
|
411
|
+
const close = raw.lastIndexOf("}", object.offset + object.length);
|
|
412
|
+
return `${raw.slice(0, object.offset + 1)}
|
|
413
|
+
${unit.repeat(depth)}${insertion}
|
|
414
|
+
${unit.repeat(depth - 1)}${raw.slice(close)}`;
|
|
415
|
+
}
|
|
416
|
+
async function writeMerged(args) {
|
|
417
|
+
const raw = await readFileIfPresent(args.file);
|
|
418
|
+
const existing = raw === null ? {} : parseOrThrow(raw, args.file);
|
|
419
|
+
const container = existing[args.containerKey] ?? {};
|
|
420
|
+
const replacedExisting = Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
421
|
+
let next;
|
|
422
|
+
const tree = raw === null ? void 0 : parseTree(raw, [], PARSE_OPTIONS);
|
|
423
|
+
if (raw === null || raw.trim() === "" || tree?.type !== "object") {
|
|
424
|
+
next = `${JSON.stringify({ [args.containerKey]: { [args.entryKey]: args.entry } }, null, 2)}
|
|
425
|
+
`;
|
|
426
|
+
} else {
|
|
427
|
+
const { insertSpaces, tabSize } = detectIndent(raw);
|
|
428
|
+
const unit = insertSpaces ? " ".repeat(tabSize) : " ";
|
|
429
|
+
const containerNode = findNodeAtLocation(tree, [args.containerKey]);
|
|
430
|
+
next = containerNode?.type === "object" ? setProperty(raw, containerNode, args.entryKey, args.entry, unit, 2) : setProperty(
|
|
431
|
+
raw,
|
|
432
|
+
tree,
|
|
433
|
+
args.containerKey,
|
|
434
|
+
{ [args.entryKey]: args.entry },
|
|
435
|
+
unit,
|
|
436
|
+
1
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
await ensureDir(path.dirname(args.file));
|
|
440
|
+
await writeFileAtomic(args.file, next);
|
|
441
|
+
const permissionsApplied = await restrictFile(args.file, args.canRestrict ?? CAN_RESTRICT);
|
|
442
|
+
return { path: args.file, permissionsApplied, replacedExisting };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// libs/sdk/cli/src/commands/agent/gitignore.ts
|
|
446
|
+
function asPattern(relative2) {
|
|
447
|
+
const escaped = relative2.replace(/([\\*?[\]])/g, "\\$1").replace(/ $/, "\\ ");
|
|
448
|
+
return `/${escaped}`;
|
|
449
|
+
}
|
|
450
|
+
function confirmIgnored(root, files) {
|
|
451
|
+
const missed = [];
|
|
452
|
+
for (const file of files) {
|
|
453
|
+
const result = spawnSync2("git", ["check-ignore", "-q", file], { cwd: root });
|
|
454
|
+
if (result.error || result.status === null || result.status > 1)
|
|
455
|
+
return null;
|
|
456
|
+
if (result.status === 1)
|
|
457
|
+
missed.push(path2.basename(file));
|
|
458
|
+
}
|
|
459
|
+
return missed;
|
|
460
|
+
}
|
|
461
|
+
function findRepositoryRoot(from) {
|
|
462
|
+
let dir = path2.resolve(from);
|
|
463
|
+
for (; ; ) {
|
|
464
|
+
if (existsSync(path2.join(dir, ".git")))
|
|
465
|
+
return dir;
|
|
466
|
+
const parent = path2.dirname(dir);
|
|
467
|
+
if (parent === dir)
|
|
468
|
+
return null;
|
|
469
|
+
dir = parent;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function protectFromVersionControl(args) {
|
|
473
|
+
const warnings = [];
|
|
474
|
+
const root = findRepositoryRoot(args.cwd);
|
|
475
|
+
const committed = new Set(args.committedByConvention ?? []);
|
|
476
|
+
for (const file of args.files) {
|
|
477
|
+
if (committed.has(file)) {
|
|
478
|
+
warnings.push(
|
|
479
|
+
`${path2.basename(file)} is normally committed to version control \u2014 it now holds a token, so committing it would publish that token.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!root) {
|
|
484
|
+
warnings.push(
|
|
485
|
+
"This directory is not under version control, so these files are unprotected \u2014 nothing here can exclude them for you."
|
|
486
|
+
);
|
|
487
|
+
return { files: args.files, inRepository: false, excluded: false, warnings };
|
|
488
|
+
}
|
|
489
|
+
const proceed = args.confirmExclude ? await args.confirmExclude(args.files) : false;
|
|
490
|
+
if (!proceed) {
|
|
491
|
+
return { files: args.files, inRepository: true, excluded: false, warnings };
|
|
492
|
+
}
|
|
493
|
+
const gitignorePath = path2.join(root, ".gitignore");
|
|
494
|
+
const current = await readFileIfPresent(gitignorePath) ?? "";
|
|
495
|
+
const already = new Set(current.split("\n").map((line) => line.trim()));
|
|
496
|
+
const relatives = args.files.map((file) => path2.relative(root, file).split(path2.sep).join("/"));
|
|
497
|
+
const toAdd = relatives.filter((rel) => !already.has(rel) && !already.has(asPattern(rel)));
|
|
498
|
+
if (toAdd.length) {
|
|
499
|
+
const prefix = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
500
|
+
const block = `${prefix}
|
|
501
|
+
# dotCMS agent configuration \u2014 contains an access token
|
|
502
|
+
${toAdd.map(asPattern).join("\n")}
|
|
503
|
+
`;
|
|
504
|
+
await fs2.writeFile(gitignorePath, current + block, "utf8");
|
|
505
|
+
}
|
|
506
|
+
const unverified = confirmIgnored(root, args.files);
|
|
507
|
+
if (unverified === null) {
|
|
508
|
+
warnings.push(
|
|
509
|
+
"Could not confirm with git that these files are excluded \u2014 check `git status` before committing."
|
|
510
|
+
);
|
|
511
|
+
} else if (unverified.length) {
|
|
512
|
+
warnings.push(
|
|
513
|
+
`.gitignore was written but git still tracks: ${unverified.join(", ")}. Exclude them by hand before committing.`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
files: args.files,
|
|
518
|
+
inRepository: true,
|
|
519
|
+
excluded: unverified !== null && unverified.length === 0,
|
|
520
|
+
warnings
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// libs/sdk/cli/src/commands/agent/skills.ts
|
|
525
|
+
import * as childProcess2 from "node:child_process";
|
|
526
|
+
function buildSkillsArgs(agentIds, global) {
|
|
527
|
+
return [
|
|
528
|
+
"-y",
|
|
529
|
+
"skills",
|
|
530
|
+
"add",
|
|
531
|
+
SKILLS_SOURCE,
|
|
532
|
+
...agentIds.flatMap((id) => ["-a", id]),
|
|
533
|
+
...global ? ["-g"] : [],
|
|
534
|
+
"-y"
|
|
535
|
+
];
|
|
536
|
+
}
|
|
537
|
+
async function installSkills(args) {
|
|
538
|
+
const argv = buildSkillsArgs(args.agentIds, args.global);
|
|
539
|
+
const command = `npx ${argv.join(" ")}`;
|
|
540
|
+
try {
|
|
541
|
+
const result = childProcess2.spawnSync("npx", argv, {
|
|
542
|
+
stdio: "inherit",
|
|
543
|
+
// The doc comment above says no secret is passed; `spawnSync` defaults to the whole
|
|
544
|
+
// environment, so it was not true until this line.
|
|
545
|
+
env: envWithoutSecrets(),
|
|
546
|
+
// On Windows `npx` is `npx.cmd`, and since the CVE-2024-27980 fix Node refuses to
|
|
547
|
+
// execute `.cmd`/`.bat` without a shell — so this spawn failed on every Windows run
|
|
548
|
+
// and FR-025 never installed anything there.
|
|
549
|
+
shell: process.platform === "win32"
|
|
550
|
+
});
|
|
551
|
+
if (result.error)
|
|
552
|
+
return { ok: false, command, reason: result.error.message };
|
|
553
|
+
if (result.status === 0)
|
|
554
|
+
return { ok: true, command };
|
|
555
|
+
return { ok: false, command, reason: `skills exited with code ${result.status}` };
|
|
556
|
+
} catch (error) {
|
|
557
|
+
return { ok: false, command, reason: error.message };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
331
561
|
// libs/sdk/cli/src/commands/agent/targets/registry.ts
|
|
562
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
563
|
+
import * as os from "node:os";
|
|
564
|
+
import * as path3 from "node:path";
|
|
332
565
|
var home = () => os.homedir();
|
|
333
|
-
var inHome = (...parts) =>
|
|
334
|
-
var inFolder = (cwd, ...parts) =>
|
|
566
|
+
var inHome = (...parts) => path3.join(home(), ...parts);
|
|
567
|
+
var inFolder = (cwd, ...parts) => path3.join(cwd ?? process.cwd(), ...parts);
|
|
335
568
|
var probe = (...parts) => async () => existsSync2(inHome(...parts));
|
|
336
569
|
function vscodeUserDir() {
|
|
337
570
|
if (process.platform === "darwin")
|
|
338
571
|
return inHome("Library", "Application Support", "Code", "User");
|
|
339
572
|
if (process.platform === "win32") {
|
|
340
573
|
const appData = process.env["APPDATA"] ?? inHome("AppData", "Roaming");
|
|
341
|
-
return
|
|
574
|
+
return path3.join(appData, "Code", "User");
|
|
342
575
|
}
|
|
343
576
|
return inHome(".config", "Code", "User");
|
|
344
577
|
}
|
|
@@ -384,7 +617,7 @@ var TARGETS = [
|
|
|
384
617
|
containerKey: "servers",
|
|
385
618
|
entryShape: "stdio",
|
|
386
619
|
detect: async () => existsSync2(vscodeUserDir()),
|
|
387
|
-
configPath: (scope, cwd) => scope === "global" ?
|
|
620
|
+
configPath: (scope, cwd) => scope === "global" ? path3.join(vscodeUserDir(), "mcp.json") : inFolder(cwd, ".vscode", "mcp.json")
|
|
388
621
|
},
|
|
389
622
|
{
|
|
390
623
|
id: "codex",
|
|
@@ -395,7 +628,7 @@ var TARGETS = [
|
|
|
395
628
|
containerKey: "mcp_servers",
|
|
396
629
|
entryShape: "stdio",
|
|
397
630
|
detect: probe(".codex"),
|
|
398
|
-
configPath: (scope, cwd) => scope === "global" ?
|
|
631
|
+
configPath: (scope, cwd) => scope === "global" ? path3.join(codexHome(), "config.toml") : inFolder(cwd, ".codex", "config.toml")
|
|
399
632
|
},
|
|
400
633
|
{
|
|
401
634
|
id: "antigravity",
|
|
@@ -458,117 +691,6 @@ function buildEntry(target, url, token) {
|
|
|
458
691
|
return { type: "stdio", command: "npx", args: ["-y", MCP_SERVER_PACKAGE], env };
|
|
459
692
|
}
|
|
460
693
|
|
|
461
|
-
// libs/sdk/cli/src/shared/config-file.ts
|
|
462
|
-
import {
|
|
463
|
-
findNodeAtLocation,
|
|
464
|
-
parse as parseJsonc,
|
|
465
|
-
parseTree
|
|
466
|
-
} from "jsonc-parser";
|
|
467
|
-
import * as fs2 from "node:fs/promises";
|
|
468
|
-
import * as path3 from "node:path";
|
|
469
|
-
var CAN_RESTRICT = process.platform !== "win32";
|
|
470
|
-
var FILE_MODE = 384;
|
|
471
|
-
var DIR_MODE = 448;
|
|
472
|
-
var PARSE_OPTIONS = { allowTrailingComma: true, allowEmptyContent: true };
|
|
473
|
-
function parseOrThrow(raw, file) {
|
|
474
|
-
if (raw.trim() === "")
|
|
475
|
-
return {};
|
|
476
|
-
const errors = [];
|
|
477
|
-
const doc = parseJsonc(raw, errors, PARSE_OPTIONS);
|
|
478
|
-
if (errors.length > 0 || doc === void 0)
|
|
479
|
-
throw new MalformedConfigError(file);
|
|
480
|
-
return doc;
|
|
481
|
-
}
|
|
482
|
-
async function readJsonDocument(file) {
|
|
483
|
-
let raw;
|
|
484
|
-
try {
|
|
485
|
-
raw = await fs2.readFile(file, "utf8");
|
|
486
|
-
} catch {
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
return parseOrThrow(raw, file);
|
|
490
|
-
}
|
|
491
|
-
function detectIndent(raw) {
|
|
492
|
-
const match = raw.match(/\n([ \t]+)\S/);
|
|
493
|
-
if (!match)
|
|
494
|
-
return { insertSpaces: true, tabSize: 2 };
|
|
495
|
-
const indent = match[1];
|
|
496
|
-
return indent.startsWith(" ") ? { insertSpaces: false, tabSize: 1 } : { insertSpaces: true, tabSize: indent.length };
|
|
497
|
-
}
|
|
498
|
-
async function hasEntry(args) {
|
|
499
|
-
const doc = await readJsonDocument(args.file).catch(() => null);
|
|
500
|
-
const container = doc?.[args.containerKey] ?? {};
|
|
501
|
-
return Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
502
|
-
}
|
|
503
|
-
async function restrictFile(file, canRestrict = CAN_RESTRICT) {
|
|
504
|
-
if (!canRestrict)
|
|
505
|
-
return false;
|
|
506
|
-
await fs2.chmod(file, FILE_MODE);
|
|
507
|
-
return true;
|
|
508
|
-
}
|
|
509
|
-
async function ensureDir(dir) {
|
|
510
|
-
const created = await fs2.mkdir(dir, { recursive: true });
|
|
511
|
-
if (created && CAN_RESTRICT)
|
|
512
|
-
await fs2.chmod(dir, DIR_MODE).catch(() => void 0);
|
|
513
|
-
}
|
|
514
|
-
function renderAt(value, unit, depth) {
|
|
515
|
-
const base = unit.repeat(depth);
|
|
516
|
-
return JSON.stringify(value, null, unit).split("\n").map((line, i) => i === 0 ? line : base + line).join("\n");
|
|
517
|
-
}
|
|
518
|
-
function setProperty(raw, object, key, value, unit, depth) {
|
|
519
|
-
const rendered = renderAt(value, unit, depth);
|
|
520
|
-
const properties = object.children ?? [];
|
|
521
|
-
const existing = properties.find((p) => p.children?.[0]?.value === key);
|
|
522
|
-
if (existing?.children?.[1]) {
|
|
523
|
-
const node = existing.children[1];
|
|
524
|
-
return raw.slice(0, node.offset) + rendered + raw.slice(node.offset + node.length);
|
|
525
|
-
}
|
|
526
|
-
const insertion = `"${key}": ${rendered}`;
|
|
527
|
-
const last = properties[properties.length - 1];
|
|
528
|
-
if (last) {
|
|
529
|
-
const end = last.offset + last.length;
|
|
530
|
-
return `${raw.slice(0, end)},
|
|
531
|
-
${unit.repeat(depth)}${insertion}${raw.slice(end)}`;
|
|
532
|
-
}
|
|
533
|
-
const close = raw.lastIndexOf("}", object.offset + object.length);
|
|
534
|
-
return `${raw.slice(0, object.offset + 1)}
|
|
535
|
-
${unit.repeat(depth)}${insertion}
|
|
536
|
-
${unit.repeat(depth - 1)}${raw.slice(close)}`;
|
|
537
|
-
}
|
|
538
|
-
async function writeMerged(args) {
|
|
539
|
-
let raw;
|
|
540
|
-
try {
|
|
541
|
-
raw = await fs2.readFile(args.file, "utf8");
|
|
542
|
-
} catch {
|
|
543
|
-
raw = null;
|
|
544
|
-
}
|
|
545
|
-
const existing = raw === null ? {} : parseOrThrow(raw, args.file);
|
|
546
|
-
const container = existing[args.containerKey] ?? {};
|
|
547
|
-
const replacedExisting = Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
548
|
-
let next;
|
|
549
|
-
const tree = raw === null ? void 0 : parseTree(raw, [], PARSE_OPTIONS);
|
|
550
|
-
if (raw === null || raw.trim() === "" || tree?.type !== "object") {
|
|
551
|
-
next = `${JSON.stringify({ [args.containerKey]: { [args.entryKey]: args.entry } }, null, 2)}
|
|
552
|
-
`;
|
|
553
|
-
} else {
|
|
554
|
-
const { insertSpaces, tabSize } = detectIndent(raw);
|
|
555
|
-
const unit = insertSpaces ? " ".repeat(tabSize) : " ";
|
|
556
|
-
const containerNode = findNodeAtLocation(tree, [args.containerKey]);
|
|
557
|
-
next = containerNode?.type === "object" ? setProperty(raw, containerNode, args.entryKey, args.entry, unit, 2) : setProperty(
|
|
558
|
-
raw,
|
|
559
|
-
tree,
|
|
560
|
-
args.containerKey,
|
|
561
|
-
{ [args.entryKey]: args.entry },
|
|
562
|
-
unit,
|
|
563
|
-
1
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
await ensureDir(path3.dirname(args.file));
|
|
567
|
-
await fs2.writeFile(args.file, next, "utf8");
|
|
568
|
-
const permissionsApplied = await restrictFile(args.file, args.canRestrict ?? CAN_RESTRICT);
|
|
569
|
-
return { path: args.file, permissionsApplied, replacedExisting };
|
|
570
|
-
}
|
|
571
|
-
|
|
572
694
|
// libs/sdk/cli/src/commands/agent/targets/json-target.ts
|
|
573
695
|
function hasJsonEntry(file, target) {
|
|
574
696
|
return hasEntry({ file, containerKey: target.containerKey, entryKey: ENTRY_KEY });
|
|
@@ -587,19 +709,16 @@ async function writeJsonTarget(args) {
|
|
|
587
709
|
|
|
588
710
|
// libs/sdk/cli/src/commands/agent/targets/toml-target.ts
|
|
589
711
|
import { parse, stringify } from "smol-toml";
|
|
590
|
-
import * as fs3 from "node:fs/promises";
|
|
591
712
|
import * as path4 from "node:path";
|
|
592
713
|
async function hasTomlEntry(file, target) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
raw = await fs3.readFile(file, "utf8");
|
|
596
|
-
} catch {
|
|
597
|
-
return false;
|
|
598
|
-
}
|
|
599
|
-
return findEntrySpan(raw.split("\n"), target.containerKey) !== null;
|
|
714
|
+
const raw = await readFileIfPresent(file);
|
|
715
|
+
return raw === null ? false : findEntrySpan(raw.split("\n"), target.containerKey) !== null;
|
|
600
716
|
}
|
|
601
717
|
function findEntrySpan(lines, containerKey) {
|
|
602
|
-
const
|
|
718
|
+
const spelt = (key) => `(?:${key}|"${key}"|'${key}')`;
|
|
719
|
+
const ours = new RegExp(
|
|
720
|
+
`^\\s*\\[\\s*${spelt(containerKey)}\\s*\\.\\s*${spelt(ENTRY_KEY)}\\s*(\\.[^\\]]*)?\\]`
|
|
721
|
+
);
|
|
603
722
|
const anyHeader = /^\s*\[/;
|
|
604
723
|
const start = lines.findIndex((line) => ours.test(line));
|
|
605
724
|
if (start === -1)
|
|
@@ -626,11 +745,7 @@ async function writeTomlTarget(args) {
|
|
|
626
745
|
if (!file) {
|
|
627
746
|
throw new NoConfigPathError(args.target.displayName, args.scope);
|
|
628
747
|
}
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
original = await fs3.readFile(file, "utf8");
|
|
632
|
-
} catch {
|
|
633
|
-
}
|
|
748
|
+
const original = await readFileIfPresent(file) ?? "";
|
|
634
749
|
if (original.trim() !== "") {
|
|
635
750
|
try {
|
|
636
751
|
parse(original);
|
|
@@ -656,8 +771,8 @@ async function writeTomlTarget(args) {
|
|
|
656
771
|
}
|
|
657
772
|
}
|
|
658
773
|
await ensureDir(path4.dirname(file));
|
|
659
|
-
await
|
|
660
|
-
|
|
774
|
+
await writeFileAtomic(file, next.endsWith("\n") ? next : `${next}
|
|
775
|
+
`);
|
|
661
776
|
return { path: file, permissionsApplied: await restrictFile(file), replacedExisting };
|
|
662
777
|
}
|
|
663
778
|
|
|
@@ -718,8 +833,37 @@ function isHttpError(error) {
|
|
|
718
833
|
return error instanceof HttpError;
|
|
719
834
|
}
|
|
720
835
|
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
721
|
-
|
|
722
|
-
|
|
836
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
837
|
+
async function readBody(response, url) {
|
|
838
|
+
const tooLarge = () => new HttpError(`Response from ${url} is too large (over ${MAX_BODY_BYTES} bytes)`, {
|
|
839
|
+
status: response.status,
|
|
840
|
+
code: "EBODYTOOLARGE"
|
|
841
|
+
});
|
|
842
|
+
const declared = Number(response.headers.get("content-length"));
|
|
843
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES)
|
|
844
|
+
throw tooLarge();
|
|
845
|
+
let text = "";
|
|
846
|
+
if (!response.body) {
|
|
847
|
+
text = await response.text().catch(() => "");
|
|
848
|
+
} else {
|
|
849
|
+
const reader = response.body.getReader();
|
|
850
|
+
const decoder = new TextDecoder();
|
|
851
|
+
let size = 0;
|
|
852
|
+
for (; ; ) {
|
|
853
|
+
const { done, value } = await reader.read();
|
|
854
|
+
if (done)
|
|
855
|
+
break;
|
|
856
|
+
if (!value)
|
|
857
|
+
continue;
|
|
858
|
+
size += value.byteLength;
|
|
859
|
+
if (size > MAX_BODY_BYTES) {
|
|
860
|
+
await reader.cancel().catch(() => void 0);
|
|
861
|
+
throw tooLarge();
|
|
862
|
+
}
|
|
863
|
+
text += decoder.decode(value, { stream: true });
|
|
864
|
+
}
|
|
865
|
+
text += decoder.decode();
|
|
866
|
+
}
|
|
723
867
|
if (!text) {
|
|
724
868
|
return void 0;
|
|
725
869
|
}
|
|
@@ -738,25 +882,38 @@ async function request(url, init, { token, timeoutMs = DEFAULT_TIMEOUT_MS2, acce
|
|
|
738
882
|
}
|
|
739
883
|
let response;
|
|
740
884
|
try {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
885
|
+
try {
|
|
886
|
+
response = await fetch(url, { ...init, headers, signal: controller.signal });
|
|
887
|
+
} catch (error) {
|
|
888
|
+
const aborted = error?.name === "AbortError";
|
|
889
|
+
const cause = error?.cause;
|
|
890
|
+
throw new HttpError(
|
|
891
|
+
aborted ? `Request to ${url} timed out after ${timeoutMs}ms` : `Request to ${url} failed: ${error?.message ?? String(error)}`,
|
|
892
|
+
{ status: null, code: aborted ? "ETIMEDOUT" : cause?.code }
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
let data;
|
|
896
|
+
try {
|
|
897
|
+
data = await readBody(response, url);
|
|
898
|
+
} catch (error) {
|
|
899
|
+
if (isHttpError(error))
|
|
900
|
+
throw error;
|
|
901
|
+
const aborted = error?.name === "AbortError";
|
|
902
|
+
throw new HttpError(
|
|
903
|
+
aborted ? `Request to ${url} timed out after ${timeoutMs}ms while reading the response` : `Reading the response from ${url} failed: ${error?.message ?? String(error)}`,
|
|
904
|
+
{ status: response.status, code: aborted ? "ETIMEDOUT" : void 0 }
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (!isSuccessStatus(response.status) && !acceptAnyStatus) {
|
|
908
|
+
throw new HttpError(`Request failed with status code ${response.status}`, {
|
|
909
|
+
status: response.status,
|
|
910
|
+
statusText: response.statusText
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
return { status: response.status, data };
|
|
749
914
|
} finally {
|
|
750
915
|
clearTimeout(timer);
|
|
751
916
|
}
|
|
752
|
-
const data = await readBody(response);
|
|
753
|
-
if (!isSuccessStatus(response.status) && !acceptAnyStatus) {
|
|
754
|
-
throw new HttpError(`Request failed with status code ${response.status}`, {
|
|
755
|
-
status: response.status,
|
|
756
|
-
statusText: response.statusText
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
return { status: response.status, data };
|
|
760
917
|
}
|
|
761
918
|
function httpGet(url, options = {}) {
|
|
762
919
|
return request(url, { method: "GET" }, options);
|
|
@@ -819,14 +976,29 @@ async function verifyToken(url, token) {
|
|
|
819
976
|
function normalizeUrl(url) {
|
|
820
977
|
return url.trim().replace(/\/+$/, "");
|
|
821
978
|
}
|
|
979
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
980
|
+
function insecureTransportWarning(url) {
|
|
981
|
+
let parsed;
|
|
982
|
+
try {
|
|
983
|
+
parsed = new URL(url);
|
|
984
|
+
} catch {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
if (parsed.protocol !== "http:" || LOOPBACK.has(parsed.hostname))
|
|
988
|
+
return null;
|
|
989
|
+
return `${parsed.host} is plain http, so your password and the access token cross the network in the clear. Use https:// if the instance supports it.`;
|
|
990
|
+
}
|
|
822
991
|
function validateUrl(url) {
|
|
823
992
|
if (!/^https?:\/\//i.test(url))
|
|
824
993
|
throw new InvalidUrlError(url);
|
|
994
|
+
let parsed;
|
|
825
995
|
try {
|
|
826
|
-
new URL(url);
|
|
996
|
+
parsed = new URL(url);
|
|
827
997
|
} catch {
|
|
828
998
|
throw new InvalidUrlError(url);
|
|
829
999
|
}
|
|
1000
|
+
if (parsed.username || parsed.password)
|
|
1001
|
+
throw new CredentialInUrlError(url);
|
|
830
1002
|
}
|
|
831
1003
|
async function checkReachable(url) {
|
|
832
1004
|
let response;
|
|
@@ -934,7 +1106,7 @@ async function promptForAuth(port) {
|
|
|
934
1106
|
}
|
|
935
1107
|
|
|
936
1108
|
// libs/sdk/cli/package.json
|
|
937
|
-
var version = "
|
|
1109
|
+
var version = "26.09.09-01";
|
|
938
1110
|
|
|
939
1111
|
// libs/sdk/cli/src/shared/version.ts
|
|
940
1112
|
var TOOL_VERSION = version;
|
|
@@ -985,6 +1157,11 @@ async function runSetup(opts) {
|
|
|
985
1157
|
warnings.push(warning);
|
|
986
1158
|
opts.onWarning?.(warning);
|
|
987
1159
|
}
|
|
1160
|
+
const insecure = insecureTransportWarning(url);
|
|
1161
|
+
if (insecure) {
|
|
1162
|
+
warnings.push(insecure);
|
|
1163
|
+
opts.onWarning?.(insecure);
|
|
1164
|
+
}
|
|
988
1165
|
let inputs = await resolveRequiredInputs(
|
|
989
1166
|
{ ...opts, url, authToken: auth.token, user: auth.user, password: auth.password },
|
|
990
1167
|
opts.promptPort
|
|
@@ -1044,6 +1221,12 @@ async function runSetup(opts) {
|
|
|
1044
1221
|
seen.add(file);
|
|
1045
1222
|
plan.push({ target, file });
|
|
1046
1223
|
}
|
|
1224
|
+
const configuredNothing = !plan.length && !opts.skipMcp;
|
|
1225
|
+
if (configuredNothing) {
|
|
1226
|
+
const noEditors = `No editor was configured. None of the supported editors was detected, and none was named. Re-run with --agent <id>, choosing from: ${TARGET_IDS.join(", ")}.`;
|
|
1227
|
+
warnings.push(noEditors);
|
|
1228
|
+
opts.onWarning?.(noEditors);
|
|
1229
|
+
}
|
|
1047
1230
|
const outcomes = [];
|
|
1048
1231
|
if (opts.skipMcp) {
|
|
1049
1232
|
for (const { target, file } of plan) {
|
|
@@ -1090,14 +1273,21 @@ async function runSetup(opts) {
|
|
|
1090
1273
|
let versionControl;
|
|
1091
1274
|
const written = outcomes.filter((o) => (o.result === "written" || o.result === "replaced") && o.path).map((o) => o.path);
|
|
1092
1275
|
if (scope === "folder" && written.length) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1276
|
+
try {
|
|
1277
|
+
versionControl = await protectFromVersionControl({
|
|
1278
|
+
files: written,
|
|
1279
|
+
// Which of them a project would normally commit is the registry's to say, not a
|
|
1280
|
+
// basename set inside the gitignore module.
|
|
1281
|
+
committedByConvention: plan.filter(({ target }) => target.folderConfigIsCommitted).map(({ file }) => file),
|
|
1282
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
1283
|
+
confirmExclude: opts.yes ? async () => true : opts.confirmExclude
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
const vcFailed = `Could not update .gitignore \u2014 ${error.message}. These files hold an access token and are NOT excluded from version control:
|
|
1287
|
+
${written.join("\n ")}`;
|
|
1288
|
+
warnings.push(vcFailed);
|
|
1289
|
+
opts.onWarning?.(vcFailed);
|
|
1290
|
+
}
|
|
1101
1291
|
}
|
|
1102
1292
|
if (!opts.skipSkills) {
|
|
1103
1293
|
const eligible = opts.skipMcp ? plan.map((p) => p.target) : outcomes.filter((o) => o.result !== "failed").map((o) => byId.get(o.targetId)).filter((t) => Boolean(t));
|
|
@@ -1105,6 +1295,12 @@ async function runSetup(opts) {
|
|
|
1105
1295
|
if (ids.length) {
|
|
1106
1296
|
step("Installing the dotCMS skills");
|
|
1107
1297
|
const skills = await installSkills({ agentIds: ids, global: scope === "global" });
|
|
1298
|
+
if (!skills.ok) {
|
|
1299
|
+
const failed = `Skills were not installed${skills.reason ? ` \u2014 ${skills.reason}` : ""}. Run this when the problem is fixed:
|
|
1300
|
+
${skills.command}`;
|
|
1301
|
+
warnings.push(failed);
|
|
1302
|
+
opts.onWarning?.(failed);
|
|
1303
|
+
}
|
|
1108
1304
|
for (const o of outcomes) {
|
|
1109
1305
|
if (o.result === "failed")
|
|
1110
1306
|
continue;
|
|
@@ -1131,7 +1327,8 @@ async function runSetup(opts) {
|
|
|
1131
1327
|
warnings,
|
|
1132
1328
|
connection,
|
|
1133
1329
|
connectionReason,
|
|
1134
|
-
|
|
1330
|
+
skillsSkipped: Boolean(opts.skipSkills),
|
|
1331
|
+
exitCode: anyFailed || configuredNothing ? 1 : 0
|
|
1135
1332
|
};
|
|
1136
1333
|
}
|
|
1137
1334
|
|
|
@@ -1186,6 +1383,9 @@ function renderSummary(input) {
|
|
|
1186
1383
|
for (const w of vc.warnings)
|
|
1187
1384
|
lines.push(chalk.yellow(` ! ${w}`));
|
|
1188
1385
|
}
|
|
1386
|
+
if (input.skillsSkipped) {
|
|
1387
|
+
lines.push(" \xB7 skills installation skipped (--skip-skills)");
|
|
1388
|
+
}
|
|
1189
1389
|
lines.push("");
|
|
1190
1390
|
if (input.connection === "ok") {
|
|
1191
1391
|
lines.push(chalk.green(" \u2713 server responded"));
|
|
@@ -1195,10 +1395,16 @@ function renderSummary(input) {
|
|
|
1195
1395
|
lines.push(chalk.red(` \u2717 ${input.connectionReason ?? "the server did not start"}`));
|
|
1196
1396
|
lines.push(" Configuration was written and left in place; the server did not come up.");
|
|
1197
1397
|
}
|
|
1198
|
-
const
|
|
1398
|
+
const done = (o) => o.result === "written" || o.result === "replaced";
|
|
1399
|
+
const left = input.outcomes.filter((o) => !done(o) && o.result !== "failed");
|
|
1400
|
+
const allGood = input.connection === "ok" && input.outcomes.length > 0 && input.outcomes.every(done);
|
|
1401
|
+
lines.push("");
|
|
1199
1402
|
if (allGood) {
|
|
1200
|
-
lines.push("");
|
|
1201
1403
|
lines.push(` Ready \u2014 ${input.nextStep ?? "open your editor and start using dotCMS."}`);
|
|
1404
|
+
} else if (left.length && input.connection === "ok") {
|
|
1405
|
+
lines.push(
|
|
1406
|
+
` ${left.length} editor${left.length === 1 ? " was" : "s were"} left unchanged; what they already point at was not verified.`
|
|
1407
|
+
);
|
|
1202
1408
|
}
|
|
1203
1409
|
return lines.join("\n");
|
|
1204
1410
|
}
|
|
@@ -1321,7 +1527,8 @@ function registerAgentCommand(program2) {
|
|
|
1321
1527
|
versionControl: result.versionControl,
|
|
1322
1528
|
warnings: result.warnings,
|
|
1323
1529
|
connection: result.connection,
|
|
1324
|
-
connectionReason: result.connectionReason
|
|
1530
|
+
connectionReason: result.connectionReason,
|
|
1531
|
+
skillsSkipped: result.skillsSkipped
|
|
1325
1532
|
})
|
|
1326
1533
|
);
|
|
1327
1534
|
process.exitCode = result.exitCode;
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
"name": "dotcms",
|
|
3
|
+
"version": "26.09.09-01",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dotcms": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"*.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"cfonts": "^3.3.1",
|
|
18
|
+
"chalk": "^5.6.2",
|
|
19
|
+
"commander": "^14.0.2",
|
|
20
|
+
"inquirer": "^13.0.1",
|
|
21
|
+
"jsonc-parser": "^3.3.1",
|
|
22
|
+
"ora": "^9.0.0",
|
|
23
|
+
"smol-toml": "^1.8.0"
|
|
24
|
+
}
|
|
25
25
|
}
|