dotcms 0.2.0 → 0.2.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 +397 -243
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -45,9 +45,6 @@ var confirmExclude = (files) => ask(
|
|
|
45
45
|
`Add ${files.length === 1 ? "this file" : `these ${files.length} files`} to .gitignore? They contain an access token.`
|
|
46
46
|
);
|
|
47
47
|
|
|
48
|
-
// libs/sdk/cli/src/commands/agent/setup.ts
|
|
49
|
-
import { parse as parseToml } from "smol-toml";
|
|
50
|
-
|
|
51
48
|
// libs/sdk/cli/src/commands/agent/connect.ts
|
|
52
49
|
import * as childProcess from "node:child_process";
|
|
53
50
|
|
|
@@ -62,13 +59,13 @@ var SKILLS_SOURCE = "dotCMS/agent-toolkit";
|
|
|
62
59
|
|
|
63
60
|
// libs/sdk/cli/src/commands/agent/connect.ts
|
|
64
61
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
65
|
-
function classify(stderr
|
|
62
|
+
function classify(stderr) {
|
|
66
63
|
if (/404|E404|not found|ETARGET|ENOTFOUND|registry/i.test(stderr))
|
|
67
64
|
return "fetch-failed";
|
|
68
65
|
if (/Unsupported engine|requires Node|SyntaxError|Unexpected token/i.test(stderr)) {
|
|
69
66
|
return "runtime-unsupported";
|
|
70
67
|
}
|
|
71
|
-
return
|
|
68
|
+
return "exited";
|
|
72
69
|
}
|
|
73
70
|
async function confirmConnection(args) {
|
|
74
71
|
const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -98,13 +95,19 @@ async function confirmConnection(args) {
|
|
|
98
95
|
resolve2(result);
|
|
99
96
|
};
|
|
100
97
|
const timer = setTimeout(
|
|
101
|
-
() => finish({
|
|
98
|
+
() => finish({
|
|
99
|
+
ok: false,
|
|
100
|
+
cause: "timeout",
|
|
101
|
+
detail: `No response within ${timeoutMs}ms.`
|
|
102
|
+
}),
|
|
102
103
|
timeoutMs
|
|
103
104
|
);
|
|
104
105
|
let buffer = "";
|
|
105
106
|
child.stdout?.on("data", (chunk) => {
|
|
106
107
|
buffer += String(chunk);
|
|
107
|
-
|
|
108
|
+
const frames = buffer.split("\n");
|
|
109
|
+
buffer = frames.pop() ?? "";
|
|
110
|
+
for (const line of frames) {
|
|
108
111
|
if (!line.trim())
|
|
109
112
|
continue;
|
|
110
113
|
try {
|
|
@@ -124,7 +127,7 @@ async function confirmConnection(args) {
|
|
|
124
127
|
"exit",
|
|
125
128
|
(code) => finish({
|
|
126
129
|
ok: false,
|
|
127
|
-
cause: classify(stderr
|
|
130
|
+
cause: classify(stderr),
|
|
128
131
|
detail: stderr.trim().split("\n").slice(-1)[0] || `Server exited with code ${code}.`
|
|
129
132
|
})
|
|
130
133
|
);
|
|
@@ -133,7 +136,11 @@ async function confirmConnection(args) {
|
|
|
133
136
|
jsonrpc: "2.0",
|
|
134
137
|
id: 1,
|
|
135
138
|
method: "initialize",
|
|
136
|
-
params: {
|
|
139
|
+
params: {
|
|
140
|
+
protocolVersion: "2024-11-05",
|
|
141
|
+
capabilities: {},
|
|
142
|
+
clientInfo: { name: "dotcms", version: "0" }
|
|
143
|
+
}
|
|
137
144
|
})}
|
|
138
145
|
`
|
|
139
146
|
);
|
|
@@ -146,7 +153,6 @@ async function confirmConnection(args) {
|
|
|
146
153
|
import { existsSync } from "node:fs";
|
|
147
154
|
import * as fs from "node:fs/promises";
|
|
148
155
|
import * as path from "node:path";
|
|
149
|
-
var CONVENTIONALLY_COMMITTED = /* @__PURE__ */ new Set([".mcp.json"]);
|
|
150
156
|
function findRepositoryRoot(from) {
|
|
151
157
|
let dir = path.resolve(from);
|
|
152
158
|
for (; ; ) {
|
|
@@ -161,8 +167,9 @@ function findRepositoryRoot(from) {
|
|
|
161
167
|
async function protectFromVersionControl(args) {
|
|
162
168
|
const warnings = [];
|
|
163
169
|
const root = findRepositoryRoot(args.cwd);
|
|
170
|
+
const committed = new Set(args.committedByConvention ?? []);
|
|
164
171
|
for (const file of args.files) {
|
|
165
|
-
if (
|
|
172
|
+
if (committed.has(file)) {
|
|
166
173
|
warnings.push(
|
|
167
174
|
`${path.basename(file)} is normally committed to version control \u2014 it now holds a token, so committing it would publish that token.`
|
|
168
175
|
);
|
|
@@ -223,10 +230,23 @@ async function installSkills(args) {
|
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
// libs/sdk/cli/src/
|
|
227
|
-
import
|
|
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";
|
|
228
236
|
import * as path2 from "node:path";
|
|
229
237
|
|
|
238
|
+
// libs/sdk/cli/src/shared/env.ts
|
|
239
|
+
var ENV_KEYS = {
|
|
240
|
+
url: "DOTCMS_URL",
|
|
241
|
+
password: "DOTCMS_PASSWORD",
|
|
242
|
+
authToken: "DOTCMS_AUTH_TOKEN",
|
|
243
|
+
codexHome: "CODEX_HOME"
|
|
244
|
+
};
|
|
245
|
+
function readEnv(key) {
|
|
246
|
+
const value = process.env[key];
|
|
247
|
+
return value && value.trim() !== "" ? value : void 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
230
250
|
// libs/sdk/cli/src/shared/errors.ts
|
|
231
251
|
var CliError = class extends Error {
|
|
232
252
|
constructor(message) {
|
|
@@ -246,7 +266,9 @@ var InvalidUrlError = class extends CliError {
|
|
|
246
266
|
};
|
|
247
267
|
var InstanceUnreachableError = class extends CliError {
|
|
248
268
|
constructor(url, reason) {
|
|
249
|
-
super(
|
|
269
|
+
super(
|
|
270
|
+
`Could not reach ${url} \u2014 ${reason}. Check the address and that the instance is running.`
|
|
271
|
+
);
|
|
250
272
|
}
|
|
251
273
|
};
|
|
252
274
|
var NotADotCmsInstanceError = class extends CliError {
|
|
@@ -284,137 +306,39 @@ var ConflictingAuthError = class extends UsageError {
|
|
|
284
306
|
};
|
|
285
307
|
var MissingInputError = class extends UsageError {
|
|
286
308
|
constructor(what) {
|
|
287
|
-
super(
|
|
309
|
+
super(
|
|
310
|
+
`${what} is required and there is no terminal to prompt on. Pass it as an option or set its environment variable.`
|
|
311
|
+
);
|
|
288
312
|
}
|
|
289
313
|
};
|
|
290
314
|
var NoConfigPathError = class extends CliError {
|
|
291
315
|
constructor(displayName, scope) {
|
|
292
316
|
super(
|
|
293
|
-
`${displayName} has no configuration file at ${scope} scope. Re-run
|
|
317
|
+
`${displayName} has no configuration file at ${scope} scope. Re-run ${scope === "folder" ? "with -g/--global" : "without -g/--global"}, or drop it from --agent.`
|
|
294
318
|
);
|
|
295
319
|
}
|
|
296
320
|
};
|
|
297
321
|
var MalformedConfigError = class extends CliError {
|
|
298
|
-
|
|
322
|
+
/** The format is passed, not sniffed from the extension: both callers know exactly which
|
|
323
|
+
* parser just refused the file. */
|
|
324
|
+
constructor(file, format = "JSON") {
|
|
299
325
|
super(
|
|
300
|
-
`${file} is not valid ${
|
|
326
|
+
`${file} is not valid ${format} \u2014 fix it or re-run with --skip-mcp. It has been left untouched.`
|
|
301
327
|
);
|
|
302
328
|
}
|
|
303
329
|
};
|
|
304
330
|
|
|
305
|
-
// libs/sdk/cli/src/shared/config-file.ts
|
|
306
|
-
var CAN_RESTRICT = process.platform !== "win32";
|
|
307
|
-
var FILE_MODE = 384;
|
|
308
|
-
var DIR_MODE = 448;
|
|
309
|
-
async function readJsonDocument(file) {
|
|
310
|
-
let raw;
|
|
311
|
-
try {
|
|
312
|
-
raw = await fs2.readFile(file, "utf8");
|
|
313
|
-
} catch {
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
if (raw.trim() === "")
|
|
317
|
-
return {};
|
|
318
|
-
try {
|
|
319
|
-
return JSON.parse(raw);
|
|
320
|
-
} catch {
|
|
321
|
-
throw new MalformedConfigError(file);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
async function hasEntry(args) {
|
|
325
|
-
let doc;
|
|
326
|
-
if (args.parse) {
|
|
327
|
-
try {
|
|
328
|
-
const raw = await fs2.readFile(args.file, "utf8");
|
|
329
|
-
doc = raw.trim() === "" ? {} : args.parse(raw);
|
|
330
|
-
} catch {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
doc = await readJsonDocument(args.file);
|
|
335
|
-
}
|
|
336
|
-
const container = doc?.[args.containerKey] ?? {};
|
|
337
|
-
return Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
338
|
-
}
|
|
339
|
-
async function restrictFile(file, canRestrict = CAN_RESTRICT) {
|
|
340
|
-
if (!canRestrict)
|
|
341
|
-
return false;
|
|
342
|
-
await fs2.chmod(file, FILE_MODE);
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
async function ensureDir(dir) {
|
|
346
|
-
const created = await fs2.mkdir(dir, { recursive: true });
|
|
347
|
-
if (created && CAN_RESTRICT)
|
|
348
|
-
await fs2.chmod(dir, DIR_MODE).catch(() => void 0);
|
|
349
|
-
}
|
|
350
|
-
async function writeMerged(args) {
|
|
351
|
-
const existing = await readJsonDocument(args.file) ?? {};
|
|
352
|
-
const container = existing[args.containerKey] ?? {};
|
|
353
|
-
const replacedExisting = Object.prototype.hasOwnProperty.call(container, args.entryKey);
|
|
354
|
-
const next = {
|
|
355
|
-
...existing,
|
|
356
|
-
[args.containerKey]: { ...container, [args.entryKey]: args.entry }
|
|
357
|
-
};
|
|
358
|
-
await ensureDir(path2.dirname(args.file));
|
|
359
|
-
await fs2.writeFile(args.file, `${JSON.stringify(next, null, 2)}
|
|
360
|
-
`, "utf8");
|
|
361
|
-
const permissionsApplied = await restrictFile(args.file, args.canRestrict ?? CAN_RESTRICT);
|
|
362
|
-
return { path: args.file, permissionsApplied, replacedExisting };
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// libs/sdk/cli/src/commands/agent/targets/json-target.ts
|
|
366
|
-
function buildEntry(target, url, token) {
|
|
367
|
-
const env = { [SERVER_ENV.url]: url, [SERVER_ENV.token]: token };
|
|
368
|
-
if (target.entryShape === "opencode-local") {
|
|
369
|
-
return {
|
|
370
|
-
type: "local",
|
|
371
|
-
command: ["npx", "-y", MCP_SERVER_PACKAGE],
|
|
372
|
-
enabled: true,
|
|
373
|
-
environment: env
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
return { type: "stdio", command: "npx", args: ["-y", MCP_SERVER_PACKAGE], env };
|
|
377
|
-
}
|
|
378
|
-
async function writeJsonTargetDetailed(args) {
|
|
379
|
-
const file = args.target.configPath(args.scope, args.cwd);
|
|
380
|
-
if (!file)
|
|
381
|
-
throw new NoConfigPathError(args.target.displayName, args.scope);
|
|
382
|
-
return writeMerged({
|
|
383
|
-
file,
|
|
384
|
-
containerKey: args.target.containerKey,
|
|
385
|
-
entryKey: ENTRY_KEY,
|
|
386
|
-
entry: buildEntry(args.target, args.url, args.token)
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// libs/sdk/cli/src/commands/agent/targets/registry.ts
|
|
391
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
392
|
-
import * as os from "node:os";
|
|
393
|
-
import * as path3 from "node:path";
|
|
394
|
-
|
|
395
|
-
// libs/sdk/cli/src/shared/env.ts
|
|
396
|
-
var ENV_KEYS = {
|
|
397
|
-
url: "DOTCMS_URL",
|
|
398
|
-
password: "DOTCMS_PASSWORD",
|
|
399
|
-
authToken: "DOTCMS_AUTH_TOKEN",
|
|
400
|
-
codexHome: "CODEX_HOME"
|
|
401
|
-
};
|
|
402
|
-
function readEnv(key) {
|
|
403
|
-
const value = process.env[key];
|
|
404
|
-
return value && value.trim() !== "" ? value : void 0;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
331
|
// libs/sdk/cli/src/commands/agent/targets/registry.ts
|
|
408
332
|
var home = () => os.homedir();
|
|
409
|
-
var inHome = (...parts) =>
|
|
410
|
-
var inFolder = (cwd, ...parts) =>
|
|
333
|
+
var inHome = (...parts) => path2.join(home(), ...parts);
|
|
334
|
+
var inFolder = (cwd, ...parts) => path2.join(cwd ?? process.cwd(), ...parts);
|
|
411
335
|
var probe = (...parts) => async () => existsSync2(inHome(...parts));
|
|
412
336
|
function vscodeUserDir() {
|
|
413
337
|
if (process.platform === "darwin")
|
|
414
338
|
return inHome("Library", "Application Support", "Code", "User");
|
|
415
339
|
if (process.platform === "win32") {
|
|
416
340
|
const appData = process.env["APPDATA"] ?? inHome("AppData", "Roaming");
|
|
417
|
-
return
|
|
341
|
+
return path2.join(appData, "Code", "User");
|
|
418
342
|
}
|
|
419
343
|
return inHome(".config", "Code", "User");
|
|
420
344
|
}
|
|
@@ -431,6 +355,7 @@ var TARGETS = [
|
|
|
431
355
|
containerKey: "mcpServers",
|
|
432
356
|
entryShape: "stdio",
|
|
433
357
|
detect: probe(".claude"),
|
|
358
|
+
folderConfigIsCommitted: true,
|
|
434
359
|
configPath: (scope, cwd) => scope === "global" ? inHome(".claude.json") : inFolder(cwd, ".mcp.json")
|
|
435
360
|
},
|
|
436
361
|
{
|
|
@@ -459,7 +384,7 @@ var TARGETS = [
|
|
|
459
384
|
containerKey: "servers",
|
|
460
385
|
entryShape: "stdio",
|
|
461
386
|
detect: async () => existsSync2(vscodeUserDir()),
|
|
462
|
-
configPath: (scope, cwd) => scope === "global" ?
|
|
387
|
+
configPath: (scope, cwd) => scope === "global" ? path2.join(vscodeUserDir(), "mcp.json") : inFolder(cwd, ".vscode", "mcp.json")
|
|
463
388
|
},
|
|
464
389
|
{
|
|
465
390
|
id: "codex",
|
|
@@ -470,7 +395,7 @@ var TARGETS = [
|
|
|
470
395
|
containerKey: "mcp_servers",
|
|
471
396
|
entryShape: "stdio",
|
|
472
397
|
detect: probe(".codex"),
|
|
473
|
-
configPath: (scope, cwd) => scope === "global" ?
|
|
398
|
+
configPath: (scope, cwd) => scope === "global" ? path2.join(codexHome(), "config.toml") : inFolder(cwd, ".codex", "config.toml")
|
|
474
399
|
},
|
|
475
400
|
{
|
|
476
401
|
id: "antigravity",
|
|
@@ -519,10 +444,160 @@ async function detectTargets() {
|
|
|
519
444
|
return results.filter((t) => t !== null);
|
|
520
445
|
}
|
|
521
446
|
|
|
447
|
+
// libs/sdk/cli/src/commands/agent/targets/entry.ts
|
|
448
|
+
function buildEntry(target, url, token) {
|
|
449
|
+
const env = { [SERVER_ENV.url]: url, [SERVER_ENV.token]: token };
|
|
450
|
+
if (target.entryShape === "opencode-local") {
|
|
451
|
+
return {
|
|
452
|
+
type: "local",
|
|
453
|
+
command: ["npx", "-y", MCP_SERVER_PACKAGE],
|
|
454
|
+
enabled: true,
|
|
455
|
+
environment: env
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return { type: "stdio", command: "npx", args: ["-y", MCP_SERVER_PACKAGE], env };
|
|
459
|
+
}
|
|
460
|
+
|
|
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
|
+
// libs/sdk/cli/src/commands/agent/targets/json-target.ts
|
|
573
|
+
function hasJsonEntry(file, target) {
|
|
574
|
+
return hasEntry({ file, containerKey: target.containerKey, entryKey: ENTRY_KEY });
|
|
575
|
+
}
|
|
576
|
+
async function writeJsonTarget(args) {
|
|
577
|
+
const file = args.target.configPath(args.scope, args.cwd);
|
|
578
|
+
if (!file)
|
|
579
|
+
throw new NoConfigPathError(args.target.displayName, args.scope);
|
|
580
|
+
return writeMerged({
|
|
581
|
+
file,
|
|
582
|
+
containerKey: args.target.containerKey,
|
|
583
|
+
entryKey: ENTRY_KEY,
|
|
584
|
+
entry: buildEntry(args.target, args.url, args.token)
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
522
588
|
// libs/sdk/cli/src/commands/agent/targets/toml-target.ts
|
|
523
589
|
import { parse, stringify } from "smol-toml";
|
|
524
590
|
import * as fs3 from "node:fs/promises";
|
|
525
591
|
import * as path4 from "node:path";
|
|
592
|
+
async function hasTomlEntry(file, target) {
|
|
593
|
+
let raw;
|
|
594
|
+
try {
|
|
595
|
+
raw = await fs3.readFile(file, "utf8");
|
|
596
|
+
} catch {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return findEntrySpan(raw.split("\n"), target.containerKey) !== null;
|
|
600
|
+
}
|
|
526
601
|
function findEntrySpan(lines, containerKey) {
|
|
527
602
|
const ours = new RegExp(`^\\s*\\[\\s*${containerKey}\\.${ENTRY_KEY}\\s*(\\.[^\\]]+)?\\]`);
|
|
528
603
|
const anyHeader = /^\s*\[/;
|
|
@@ -560,17 +635,19 @@ async function writeTomlTarget(args) {
|
|
|
560
635
|
try {
|
|
561
636
|
parse(original);
|
|
562
637
|
} catch {
|
|
563
|
-
throw new MalformedConfigError(file);
|
|
638
|
+
throw new MalformedConfigError(file, "TOML");
|
|
564
639
|
}
|
|
565
640
|
}
|
|
566
641
|
const block = renderEntry(args.target, args.url, args.token, args.target.containerKey);
|
|
567
642
|
let next;
|
|
643
|
+
let replacedExisting = false;
|
|
568
644
|
if (original.trim() === "") {
|
|
569
645
|
next = block;
|
|
570
646
|
} else {
|
|
571
647
|
const lines = original.split("\n");
|
|
572
648
|
const span = findEntrySpan(lines, args.target.containerKey);
|
|
573
649
|
if (span) {
|
|
650
|
+
replacedExisting = true;
|
|
574
651
|
lines.splice(span.start, span.end - span.start, ...block.trimEnd().split("\n"));
|
|
575
652
|
next = lines.join("\n");
|
|
576
653
|
} else {
|
|
@@ -581,8 +658,48 @@ async function writeTomlTarget(args) {
|
|
|
581
658
|
await ensureDir(path4.dirname(file));
|
|
582
659
|
await fs3.writeFile(file, next.endsWith("\n") ? next : `${next}
|
|
583
660
|
`, "utf8");
|
|
584
|
-
await restrictFile(file);
|
|
585
|
-
|
|
661
|
+
return { path: file, permissionsApplied: await restrictFile(file), replacedExisting };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// libs/sdk/cli/src/commands/agent/targets/writers.ts
|
|
665
|
+
var WRITERS = {
|
|
666
|
+
json: { hasEntry: hasJsonEntry, write: writeJsonTarget },
|
|
667
|
+
toml: { hasEntry: hasTomlEntry, write: writeTomlTarget }
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// libs/http/src/lib/fetch-retry.ts
|
|
671
|
+
function isSuccessStatus(status) {
|
|
672
|
+
return status >= 200 && status < 300;
|
|
673
|
+
}
|
|
674
|
+
function describeRequestFailure(error) {
|
|
675
|
+
if (isHttpError(error)) {
|
|
676
|
+
if (error.code === "ECONNREFUSED") {
|
|
677
|
+
return "Connection refused - service not accepting connections yet";
|
|
678
|
+
}
|
|
679
|
+
if (error.code === "ETIMEDOUT") {
|
|
680
|
+
return "Connection timeout - service too slow or not responding";
|
|
681
|
+
}
|
|
682
|
+
if (error.code === "ENOTFOUND") {
|
|
683
|
+
return "Host not found (DNS lookup failed)";
|
|
684
|
+
}
|
|
685
|
+
if (error.code === "ECONNRESET") {
|
|
686
|
+
return "Connection reset by the server";
|
|
687
|
+
}
|
|
688
|
+
if (error.code === "CERT_HAS_EXPIRED") {
|
|
689
|
+
return "TLS certificate has expired";
|
|
690
|
+
}
|
|
691
|
+
if (error.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || error.code === "SELF_SIGNED_CERT_IN_CHAIN") {
|
|
692
|
+
return "TLS certificate is self-signed and not trusted";
|
|
693
|
+
}
|
|
694
|
+
if (error.response) {
|
|
695
|
+
return `HTTP ${error.response.status}: ${error.response.statusText}`;
|
|
696
|
+
}
|
|
697
|
+
return error.code || error.message;
|
|
698
|
+
}
|
|
699
|
+
if (error instanceof Error) {
|
|
700
|
+
return error.message;
|
|
701
|
+
}
|
|
702
|
+
return String(error);
|
|
586
703
|
}
|
|
587
704
|
|
|
588
705
|
// libs/http/src/lib/http.ts
|
|
@@ -601,9 +718,6 @@ function isHttpError(error) {
|
|
|
601
718
|
return error instanceof HttpError;
|
|
602
719
|
}
|
|
603
720
|
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
604
|
-
function isSuccess(status) {
|
|
605
|
-
return status >= 200 && status < 300;
|
|
606
|
-
}
|
|
607
721
|
async function readBody(response) {
|
|
608
722
|
const text = await response.text().catch(() => "");
|
|
609
723
|
if (!text) {
|
|
@@ -636,7 +750,7 @@ async function request(url, init, { token, timeoutMs = DEFAULT_TIMEOUT_MS2, acce
|
|
|
636
750
|
clearTimeout(timer);
|
|
637
751
|
}
|
|
638
752
|
const data = await readBody(response);
|
|
639
|
-
if (!
|
|
753
|
+
if (!isSuccessStatus(response.status) && !acceptAnyStatus) {
|
|
640
754
|
throw new HttpError(`Request failed with status code ${response.status}`, {
|
|
641
755
|
status: response.status,
|
|
642
756
|
statusText: response.statusText
|
|
@@ -659,38 +773,6 @@ function httpPost(url, body, options = {}) {
|
|
|
659
773
|
);
|
|
660
774
|
}
|
|
661
775
|
|
|
662
|
-
// libs/http/src/lib/fetch-retry.ts
|
|
663
|
-
function describeRequestFailure(error) {
|
|
664
|
-
if (isHttpError(error)) {
|
|
665
|
-
if (error.code === "ECONNREFUSED") {
|
|
666
|
-
return "Connection refused - service not accepting connections yet";
|
|
667
|
-
}
|
|
668
|
-
if (error.code === "ETIMEDOUT") {
|
|
669
|
-
return "Connection timeout - service too slow or not responding";
|
|
670
|
-
}
|
|
671
|
-
if (error.code === "ENOTFOUND") {
|
|
672
|
-
return "Host not found (DNS lookup failed)";
|
|
673
|
-
}
|
|
674
|
-
if (error.code === "ECONNRESET") {
|
|
675
|
-
return "Connection reset by the server";
|
|
676
|
-
}
|
|
677
|
-
if (error.code === "CERT_HAS_EXPIRED") {
|
|
678
|
-
return "TLS certificate has expired";
|
|
679
|
-
}
|
|
680
|
-
if (error.code === "DEPTH_ZERO_SELF_SIGNED_CERT" || error.code === "SELF_SIGNED_CERT_IN_CHAIN") {
|
|
681
|
-
return "TLS certificate is self-signed and not trusted";
|
|
682
|
-
}
|
|
683
|
-
if (error.response) {
|
|
684
|
-
return `HTTP ${error.response.status}: ${error.response.statusText}`;
|
|
685
|
-
}
|
|
686
|
-
return error.code || error.message;
|
|
687
|
-
}
|
|
688
|
-
if (error instanceof Error) {
|
|
689
|
-
return error.message;
|
|
690
|
-
}
|
|
691
|
-
return String(error);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
776
|
// libs/http/src/lib/endpoints.ts
|
|
695
777
|
var DOTCMS_API = {
|
|
696
778
|
/** Reachability probe, and the source of the instance version. */
|
|
@@ -773,6 +855,26 @@ function readVersion(data) {
|
|
|
773
855
|
const candidate = info?.version;
|
|
774
856
|
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
775
857
|
}
|
|
858
|
+
function parseVersion(value) {
|
|
859
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value.trim());
|
|
860
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
861
|
+
}
|
|
862
|
+
function compatibilityWarning(instanceVersion, toolVersion) {
|
|
863
|
+
if (!instanceVersion)
|
|
864
|
+
return null;
|
|
865
|
+
const instance = parseVersion(instanceVersion);
|
|
866
|
+
const tool = parseVersion(toolVersion);
|
|
867
|
+
if (!instance || !tool)
|
|
868
|
+
return null;
|
|
869
|
+
for (let i = 0; i < 3; i++) {
|
|
870
|
+
if (instance[i] > tool[i])
|
|
871
|
+
return null;
|
|
872
|
+
if (instance[i] < tool[i]) {
|
|
873
|
+
return `This tool targets dotCMS ${toolVersion}, but the instance reports ${instanceVersion}. Install dotcms@${instanceVersion} to match your instance.`;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
776
878
|
|
|
777
879
|
// libs/sdk/cli/src/shared/prompts.ts
|
|
778
880
|
var DEFAULT_URL = "http://localhost:8082";
|
|
@@ -791,50 +893,52 @@ async function resolveInstanceUrl(opts, port, interactive = Boolean(port)) {
|
|
|
791
893
|
return url;
|
|
792
894
|
}
|
|
793
895
|
async function resolveRequiredInputs(opts, port, interactive = Boolean(port)) {
|
|
794
|
-
let prompted = false;
|
|
795
896
|
const ask2 = async (what, run) => {
|
|
796
897
|
if (!interactive || !port)
|
|
797
898
|
throw new MissingInputError(what);
|
|
798
|
-
prompted = true;
|
|
799
899
|
return run();
|
|
800
900
|
};
|
|
801
901
|
const url = await resolveInstanceUrl(opts, port, interactive);
|
|
802
|
-
if (!opts.url && !readEnv(ENV_KEYS.url))
|
|
803
|
-
prompted = true;
|
|
804
902
|
const authToken = opts.authToken ?? readEnv(ENV_KEYS.authToken);
|
|
805
903
|
const user = opts.user;
|
|
806
904
|
const password = opts.password ?? readEnv(ENV_KEYS.password);
|
|
807
905
|
if (authToken)
|
|
808
|
-
return { url, authToken
|
|
906
|
+
return { url, authToken };
|
|
809
907
|
if (user && password)
|
|
810
|
-
return { url, user, password
|
|
908
|
+
return { url, user, password };
|
|
811
909
|
if (user) {
|
|
812
910
|
const typed = await ask2("A password", () => port.password("Password"));
|
|
813
|
-
return { url, user, password: typed
|
|
911
|
+
return { url, user, password: typed };
|
|
814
912
|
}
|
|
815
913
|
if (password) {
|
|
816
914
|
const typed = await ask2("A username", () => port.text("Username"));
|
|
817
|
-
return { url, user: typed, password
|
|
915
|
+
return { url, user: typed, password };
|
|
818
916
|
}
|
|
819
917
|
if (!interactive || !port) {
|
|
820
918
|
throw new MissingInputError("A username and password, or an authentication token,");
|
|
821
919
|
}
|
|
822
|
-
|
|
920
|
+
return { url, ...await promptForAuth(port) };
|
|
921
|
+
}
|
|
922
|
+
async function promptForAuth(port) {
|
|
823
923
|
const mode = await port.select("How should we authenticate?", [
|
|
824
924
|
{ name: "Sign in with a username and password", value: "signin" },
|
|
825
925
|
{ name: "Paste an existing authentication token", value: "token" }
|
|
826
926
|
]);
|
|
827
927
|
if (mode === "token") {
|
|
828
|
-
return {
|
|
928
|
+
return { authToken: await port.password("Authentication token") };
|
|
829
929
|
}
|
|
830
930
|
return {
|
|
831
|
-
url,
|
|
832
931
|
user: await port.text("Username"),
|
|
833
|
-
password: await port.password("Password")
|
|
834
|
-
prompted
|
|
932
|
+
password: await port.password("Password")
|
|
835
933
|
};
|
|
836
934
|
}
|
|
837
935
|
|
|
936
|
+
// libs/sdk/cli/package.json
|
|
937
|
+
var version = "0.2.0";
|
|
938
|
+
|
|
939
|
+
// libs/sdk/cli/src/shared/version.ts
|
|
940
|
+
var TOOL_VERSION = version;
|
|
941
|
+
|
|
838
942
|
// libs/sdk/cli/src/commands/agent/setup.ts
|
|
839
943
|
var MAX_AUTH_ATTEMPTS = 3;
|
|
840
944
|
function resolveAuthMode(opts) {
|
|
@@ -861,10 +965,26 @@ async function runSetup(opts) {
|
|
|
861
965
|
const auth = resolveAuthMode(opts);
|
|
862
966
|
const explicitTargets = resolveTargets(opts);
|
|
863
967
|
const scope = opts.scope ?? "folder";
|
|
968
|
+
const outcome = (targetId, path5, result, reason = null, extra = {}) => ({
|
|
969
|
+
targetId,
|
|
970
|
+
scope,
|
|
971
|
+
path: path5,
|
|
972
|
+
result,
|
|
973
|
+
reason,
|
|
974
|
+
permissionsApplied: false,
|
|
975
|
+
skillsInstalled: "no",
|
|
976
|
+
...extra
|
|
977
|
+
});
|
|
864
978
|
const step = opts.onProgress ?? (() => void 0);
|
|
979
|
+
const warnings = [];
|
|
865
980
|
const url = await resolveInstanceUrl(opts, opts.promptPort);
|
|
866
981
|
step(`Checking ${url}`);
|
|
867
|
-
await checkReachable(url);
|
|
982
|
+
const instance = await checkReachable(url);
|
|
983
|
+
const warning = compatibilityWarning(instance.version, TOOL_VERSION);
|
|
984
|
+
if (warning) {
|
|
985
|
+
warnings.push(warning);
|
|
986
|
+
opts.onWarning?.(warning);
|
|
987
|
+
}
|
|
868
988
|
let inputs = await resolveRequiredInputs(
|
|
869
989
|
{ ...opts, url, authToken: auth.token, user: auth.user, password: auth.password },
|
|
870
990
|
opts.promptPort
|
|
@@ -890,7 +1010,8 @@ async function runSetup(opts) {
|
|
|
890
1010
|
if (!rejected || !opts.promptPort || attempt >= MAX_AUTH_ATTEMPTS)
|
|
891
1011
|
throw error;
|
|
892
1012
|
opts.onAuthRetry?.(error.message, attempt, MAX_AUTH_ATTEMPTS);
|
|
893
|
-
|
|
1013
|
+
const fresh = await promptForAuth(opts.promptPort);
|
|
1014
|
+
inputs = { url, ...fresh };
|
|
894
1015
|
}
|
|
895
1016
|
}
|
|
896
1017
|
let targets;
|
|
@@ -926,68 +1047,44 @@ async function runSetup(opts) {
|
|
|
926
1047
|
const outcomes = [];
|
|
927
1048
|
if (opts.skipMcp) {
|
|
928
1049
|
for (const { target, file } of plan) {
|
|
929
|
-
outcomes.push(
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
path: file,
|
|
933
|
-
result: "skipped",
|
|
934
|
-
reason: "configuration writing skipped (--skip-mcp)",
|
|
935
|
-
permissionsApplied: false,
|
|
936
|
-
skillsInstalled: "no"
|
|
937
|
-
});
|
|
1050
|
+
outcomes.push(
|
|
1051
|
+
outcome(target.id, file, "skipped", "configuration writing skipped (--skip-mcp)")
|
|
1052
|
+
);
|
|
938
1053
|
}
|
|
939
|
-
}
|
|
940
|
-
if (!opts.skipMcp && plan.length) {
|
|
1054
|
+
} else if (plan.length) {
|
|
941
1055
|
step(`Writing configuration for ${plan.length} editor${plan.length === 1 ? "" : "s"}`);
|
|
942
1056
|
}
|
|
943
1057
|
for (const { target, file } of opts.skipMcp ? [] : plan) {
|
|
944
1058
|
try {
|
|
945
|
-
const
|
|
946
|
-
const existing = await hasEntry(
|
|
947
|
-
file,
|
|
948
|
-
containerKey: target.containerKey,
|
|
949
|
-
entryKey: ENTRY_KEY,
|
|
950
|
-
parse: isToml ? (raw) => parseToml(raw) : void 0
|
|
951
|
-
});
|
|
1059
|
+
const writer = WRITERS[target.format];
|
|
1060
|
+
const existing = await writer.hasEntry(file, target);
|
|
952
1061
|
if (existing && !opts.force && !opts.yes && opts.confirmOverwrite) {
|
|
953
1062
|
const proceed = await opts.confirmOverwrite(file);
|
|
954
1063
|
if (!proceed) {
|
|
955
|
-
outcomes.push(
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
path: file,
|
|
959
|
-
result: "skipped",
|
|
960
|
-
reason: "left the existing entry in place",
|
|
961
|
-
permissionsApplied: false,
|
|
962
|
-
skillsInstalled: "no"
|
|
963
|
-
});
|
|
1064
|
+
outcomes.push(
|
|
1065
|
+
outcome(target.id, file, "skipped", "left the existing entry in place")
|
|
1066
|
+
);
|
|
964
1067
|
continue;
|
|
965
1068
|
}
|
|
966
1069
|
}
|
|
967
|
-
const written2 =
|
|
968
|
-
|
|
969
|
-
permissionsApplied: CAN_RESTRICT,
|
|
970
|
-
replacedExisting: existing
|
|
971
|
-
} : await writeJsonTargetDetailed({ target, scope, url, token: token.value, cwd: opts.cwd });
|
|
972
|
-
outcomes.push({
|
|
973
|
-
targetId: target.id,
|
|
1070
|
+
const written2 = await writer.write({
|
|
1071
|
+
target,
|
|
974
1072
|
scope,
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
permissionsApplied: written2.permissionsApplied,
|
|
979
|
-
skillsInstalled: "no"
|
|
1073
|
+
url,
|
|
1074
|
+
token: token.value,
|
|
1075
|
+
cwd: opts.cwd
|
|
980
1076
|
});
|
|
1077
|
+
outcomes.push(
|
|
1078
|
+
outcome(
|
|
1079
|
+
target.id,
|
|
1080
|
+
written2.path,
|
|
1081
|
+
written2.replacedExisting ? "replaced" : "written",
|
|
1082
|
+
null,
|
|
1083
|
+
{ permissionsApplied: written2.permissionsApplied }
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
981
1086
|
} catch (error) {
|
|
982
|
-
outcomes.push(
|
|
983
|
-
targetId: target.id,
|
|
984
|
-
scope,
|
|
985
|
-
path: file,
|
|
986
|
-
result: "failed",
|
|
987
|
-
reason: error.message,
|
|
988
|
-
permissionsApplied: false,
|
|
989
|
-
skillsInstalled: "no"
|
|
990
|
-
});
|
|
1087
|
+
outcomes.push(outcome(target.id, file, "failed", error.message));
|
|
991
1088
|
}
|
|
992
1089
|
}
|
|
993
1090
|
let versionControl;
|
|
@@ -995,6 +1092,9 @@ async function runSetup(opts) {
|
|
|
995
1092
|
if (scope === "folder" && written.length) {
|
|
996
1093
|
versionControl = await protectFromVersionControl({
|
|
997
1094
|
files: written,
|
|
1095
|
+
// Which of them a project would normally commit is the registry's to say, not a
|
|
1096
|
+
// basename set inside the gitignore module.
|
|
1097
|
+
committedByConvention: plan.filter(({ target }) => target.folderConfigIsCommitted).map(({ file }) => file),
|
|
998
1098
|
cwd: opts.cwd ?? process.cwd(),
|
|
999
1099
|
confirmExclude: opts.yes ? async () => true : opts.confirmExclude
|
|
1000
1100
|
});
|
|
@@ -1016,14 +1116,23 @@ async function runSetup(opts) {
|
|
|
1016
1116
|
let connection = "skipped";
|
|
1017
1117
|
let connectionReason;
|
|
1018
1118
|
if (!opts.skipVerify && !opts.skipMcp) {
|
|
1019
|
-
step(
|
|
1119
|
+
step(
|
|
1120
|
+
"Starting the server to confirm it responds (this can take a minute on a cold npx cache)"
|
|
1121
|
+
);
|
|
1020
1122
|
const result = await confirmConnection({ url, token: token.value });
|
|
1021
1123
|
connection = result.ok ? "ok" : "failed";
|
|
1022
1124
|
if (!result.ok)
|
|
1023
1125
|
connectionReason = `${result.cause}: ${result.detail}`;
|
|
1024
1126
|
}
|
|
1025
1127
|
const anyFailed = outcomes.some((o) => o.result === "failed") || connection === "failed";
|
|
1026
|
-
return {
|
|
1128
|
+
return {
|
|
1129
|
+
outcomes,
|
|
1130
|
+
versionControl,
|
|
1131
|
+
warnings,
|
|
1132
|
+
connection,
|
|
1133
|
+
connectionReason,
|
|
1134
|
+
exitCode: anyFailed ? 1 : 0
|
|
1135
|
+
};
|
|
1027
1136
|
}
|
|
1028
1137
|
|
|
1029
1138
|
// libs/sdk/cli/src/shared/ui.ts
|
|
@@ -1041,10 +1150,11 @@ var RESULT_MARK = {
|
|
|
1041
1150
|
};
|
|
1042
1151
|
function renderSummary(input) {
|
|
1043
1152
|
const lines = [];
|
|
1153
|
+
const idWidth = Math.max(...input.outcomes.map((o) => o.targetId.length), 0);
|
|
1044
1154
|
for (const o of input.outcomes) {
|
|
1045
1155
|
const bits = [
|
|
1046
1156
|
` ${RESULT_MARK[o.result]}`,
|
|
1047
|
-
o.targetId.padEnd(
|
|
1157
|
+
o.targetId.padEnd(idWidth),
|
|
1048
1158
|
o.scope.padEnd(7),
|
|
1049
1159
|
o.path ?? "\u2014"
|
|
1050
1160
|
];
|
|
@@ -1055,9 +1165,14 @@ function renderSummary(input) {
|
|
|
1055
1165
|
lines.push(" could not restrict file permissions on this platform");
|
|
1056
1166
|
}
|
|
1057
1167
|
if (o.skillsInstalled === "unverified") {
|
|
1058
|
-
lines.push(
|
|
1168
|
+
lines.push(
|
|
1169
|
+
" skills location unverified for this editor \u2014 not confirmed installed"
|
|
1170
|
+
);
|
|
1059
1171
|
}
|
|
1060
1172
|
}
|
|
1173
|
+
for (const w of input.warnings ?? []) {
|
|
1174
|
+
lines.push(chalk.yellow(` ! ${w}`));
|
|
1175
|
+
}
|
|
1061
1176
|
if (input.versionControl?.files.length) {
|
|
1062
1177
|
const vc = input.versionControl;
|
|
1063
1178
|
lines.push("");
|
|
@@ -1106,8 +1221,24 @@ function makeProgress(interactive) {
|
|
|
1106
1221
|
},
|
|
1107
1222
|
done() {
|
|
1108
1223
|
spinner?.stop();
|
|
1224
|
+
spinner = null;
|
|
1109
1225
|
},
|
|
1110
|
-
/**
|
|
1226
|
+
/**
|
|
1227
|
+
* Hand the terminal over to a question.
|
|
1228
|
+
*
|
|
1229
|
+
* Keeps the finished step visible, then stops repainting. `done()` erases the line;
|
|
1230
|
+
* here the step really did complete, so it should stay on screen above the prompt.
|
|
1231
|
+
*/
|
|
1232
|
+
pause() {
|
|
1233
|
+
spinner?.succeed();
|
|
1234
|
+
spinner = null;
|
|
1235
|
+
},
|
|
1236
|
+
/**
|
|
1237
|
+
* Leave the failed step visible instead of a spinner that never stops.
|
|
1238
|
+
*
|
|
1239
|
+
* `warn` was a byte-identical second copy of this: a retry notice and a failure render
|
|
1240
|
+
* the same way — mark the attempt failed, then carry on.
|
|
1241
|
+
*/
|
|
1111
1242
|
fail(text) {
|
|
1112
1243
|
if (spinner)
|
|
1113
1244
|
spinner.fail(text);
|
|
@@ -1115,16 +1246,37 @@ function makeProgress(interactive) {
|
|
|
1115
1246
|
writeOut(` \u2717 ${text}`);
|
|
1116
1247
|
spinner = null;
|
|
1117
1248
|
},
|
|
1118
|
-
/** A retry notice: mark the attempt failed, then carry on. */
|
|
1119
1249
|
warn(text) {
|
|
1120
|
-
|
|
1121
|
-
spinner.fail(text);
|
|
1122
|
-
else
|
|
1123
|
-
writeOut(` \u2717 ${text}`);
|
|
1124
|
-
spinner = null;
|
|
1250
|
+
this.fail(text);
|
|
1125
1251
|
}
|
|
1126
1252
|
};
|
|
1127
1253
|
}
|
|
1254
|
+
function pausingPort(port, progress) {
|
|
1255
|
+
return {
|
|
1256
|
+
text(message, defaultValue) {
|
|
1257
|
+
progress.pause();
|
|
1258
|
+
return port.text(message, defaultValue);
|
|
1259
|
+
},
|
|
1260
|
+
password(message) {
|
|
1261
|
+
progress.pause();
|
|
1262
|
+
return port.password(message);
|
|
1263
|
+
},
|
|
1264
|
+
select(message, choices) {
|
|
1265
|
+
progress.pause();
|
|
1266
|
+
return port.select(message, choices);
|
|
1267
|
+
},
|
|
1268
|
+
multiSelect(message, choices) {
|
|
1269
|
+
progress.pause();
|
|
1270
|
+
return port.multiSelect(message, choices);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function pausingConfirm(confirm, progress) {
|
|
1275
|
+
return (...args) => {
|
|
1276
|
+
progress.pause();
|
|
1277
|
+
return confirm(...args);
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1128
1280
|
function registerAgentCommand(program2) {
|
|
1129
1281
|
const agent = program2.command("agent").description("Connect an AI coding agent to dotCMS");
|
|
1130
1282
|
agent.command("setup").description("Configure your editors to talk to a dotCMS instance").option("--url <url>", "dotCMS instance address (or set DOTCMS_URL)").option("--user <user>", "username, to mint a token").option(
|
|
@@ -1138,17 +1290,18 @@ function registerAgentCommand(program2) {
|
|
|
1138
1290
|
`editor to configure, repeatable (${TARGET_IDS.join(", ")})`,
|
|
1139
1291
|
(value, previous = []) => [...previous, value]
|
|
1140
1292
|
).option("-g, --global", "write to your user account instead of this folder").option("--skip-mcp", "do not write configuration").option("--skip-skills", "do not install the dotCMS skills").option("--skip-verify", "do not launch the server to confirm it responds").option("-y, --yes", "accept confirmations (never skips a required input)").option("--force", "replace an existing dotcms entry without asking").action(async (options) => {
|
|
1141
|
-
if (canPrompt())
|
|
1142
|
-
printBanner();
|
|
1143
1293
|
const interactive = canPrompt();
|
|
1294
|
+
if (interactive)
|
|
1295
|
+
printBanner();
|
|
1144
1296
|
const progress = makeProgress(interactive);
|
|
1145
1297
|
try {
|
|
1146
1298
|
const result = await runSetup({
|
|
1147
1299
|
onProgress: progress.step,
|
|
1148
1300
|
onAuthRetry: (message, attempt, max) => progress.warn(`${message} (attempt ${attempt} of ${max})`),
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1301
|
+
onWarning: (message) => progress.warn(message),
|
|
1302
|
+
promptPort: interactive ? pausingPort(inquirerPort, progress) : void 0,
|
|
1303
|
+
confirmOverwrite: interactive ? pausingConfirm(confirmOverwrite, progress) : void 0,
|
|
1304
|
+
confirmExclude: interactive ? pausingConfirm(confirmExclude, progress) : void 0,
|
|
1152
1305
|
url: options["url"],
|
|
1153
1306
|
user: options["user"],
|
|
1154
1307
|
password: options["password"],
|
|
@@ -1166,6 +1319,7 @@ function registerAgentCommand(program2) {
|
|
|
1166
1319
|
renderSummary({
|
|
1167
1320
|
outcomes: result.outcomes,
|
|
1168
1321
|
versionControl: result.versionControl,
|
|
1322
|
+
warnings: result.warnings,
|
|
1169
1323
|
connection: result.connection,
|
|
1170
1324
|
connectionReason: result.connectionReason
|
|
1171
1325
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotcms",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"chalk": "^5.6.2",
|
|
19
19
|
"commander": "^14.0.2",
|
|
20
20
|
"inquirer": "^13.0.1",
|
|
21
|
+
"jsonc-parser": "^3.3.1",
|
|
21
22
|
"ora": "^9.0.0",
|
|
22
23
|
"smol-toml": "^1.8.0"
|
|
23
24
|
}
|