dotcms 0.0.20 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +33 -74
  2. package/index.js +1197 -0
  3. package/package.json +22 -9
  4. package/src/index.d.ts +0 -30
  5. package/src/index.js +0 -40
  6. package/src/index.js.map +0 -1
  7. package/src/lib/api/DotApiAuthorization.d.ts +0 -11
  8. package/src/lib/api/DotApiAuthorization.js +0 -46
  9. package/src/lib/api/DotApiAuthorization.js.map +0 -1
  10. package/src/lib/api/DotApiConfiguration.d.ts +0 -11
  11. package/src/lib/api/DotApiConfiguration.js +0 -32
  12. package/src/lib/api/DotApiConfiguration.js.map +0 -1
  13. package/src/lib/api/DotApiContent.d.ts +0 -14
  14. package/src/lib/api/DotApiContent.js +0 -59
  15. package/src/lib/api/DotApiContent.js.map +0 -1
  16. package/src/lib/api/DotApiContentType.d.ts +0 -12
  17. package/src/lib/api/DotApiContentType.js +0 -35
  18. package/src/lib/api/DotApiContentType.js.map +0 -1
  19. package/src/lib/api/DotApiElasticSearch.d.ts +0 -15
  20. package/src/lib/api/DotApiElasticSearch.js +0 -37
  21. package/src/lib/api/DotApiElasticSearch.js.map +0 -1
  22. package/src/lib/api/DotApiEvent.d.ts +0 -8
  23. package/src/lib/api/DotApiEvent.js +0 -20
  24. package/src/lib/api/DotApiEvent.js.map +0 -1
  25. package/src/lib/api/DotApiForm.d.ts +0 -17
  26. package/src/lib/api/DotApiForm.js +0 -25
  27. package/src/lib/api/DotApiForm.js.map +0 -1
  28. package/src/lib/api/DotApiLanguage.d.ts +0 -13
  29. package/src/lib/api/DotApiLanguage.js +0 -33
  30. package/src/lib/api/DotApiLanguage.js.map +0 -1
  31. package/src/lib/api/DotApiNavigation.d.ts +0 -11
  32. package/src/lib/api/DotApiNavigation.js +0 -34
  33. package/src/lib/api/DotApiNavigation.js.map +0 -1
  34. package/src/lib/api/DotApiPage.d.ts +0 -18
  35. package/src/lib/api/DotApiPage.js +0 -51
  36. package/src/lib/api/DotApiPage.js.map +0 -1
  37. package/src/lib/api/DotApiSite.d.ts +0 -10
  38. package/src/lib/api/DotApiSite.js +0 -31
  39. package/src/lib/api/DotApiSite.js.map +0 -1
  40. package/src/lib/api/DotApiWidget.d.ts +0 -13
  41. package/src/lib/api/DotApiWidget.js +0 -33
  42. package/src/lib/api/DotApiWidget.js.map +0 -1
  43. package/src/lib/models/DotCMSAuthorization.model.d.ts +0 -6
  44. package/src/lib/models/DotCMSAuthorization.model.js +0 -3
  45. package/src/lib/models/DotCMSAuthorization.model.js.map +0 -1
  46. package/src/lib/models/DotCMSConfiguration.model.d.ts +0 -53
  47. package/src/lib/models/DotCMSConfiguration.model.js +0 -3
  48. package/src/lib/models/DotCMSConfiguration.model.js.map +0 -1
  49. package/src/lib/models/DotCMSContent.model.d.ts +0 -13
  50. package/src/lib/models/DotCMSContent.model.js +0 -3
  51. package/src/lib/models/DotCMSContent.model.js.map +0 -1
  52. package/src/lib/models/DotCMSElasticSearch.model.d.ts +0 -48
  53. package/src/lib/models/DotCMSElasticSearch.model.js +0 -3
  54. package/src/lib/models/DotCMSElasticSearch.model.js.map +0 -1
  55. package/src/lib/models/DotCMSError.model.d.ts +0 -4
  56. package/src/lib/models/DotCMSError.model.js +0 -3
  57. package/src/lib/models/DotCMSError.model.js.map +0 -1
  58. package/src/lib/models/DotCMSEvent.model.d.ts +0 -4
  59. package/src/lib/models/DotCMSEvent.model.js +0 -3
  60. package/src/lib/models/DotCMSEvent.model.js.map +0 -1
  61. package/src/lib/models/DotCMSForm.model.d.ts +0 -14
  62. package/src/lib/models/DotCMSForm.model.js +0 -3
  63. package/src/lib/models/DotCMSForm.model.js.map +0 -1
  64. package/src/lib/models/DotCMSHttpClient.model.d.ts +0 -10
  65. package/src/lib/models/DotCMSHttpClient.model.js +0 -3
  66. package/src/lib/models/DotCMSHttpClient.model.js.map +0 -1
  67. package/src/lib/models/DotCMSLanguage.model.d.ts +0 -7
  68. package/src/lib/models/DotCMSLanguage.model.js +0 -3
  69. package/src/lib/models/DotCMSLanguage.model.js.map +0 -1
  70. package/src/lib/models/DotCMSNavigation.model.d.ts +0 -13
  71. package/src/lib/models/DotCMSNavigation.model.js +0 -3
  72. package/src/lib/models/DotCMSNavigation.model.js.map +0 -1
  73. package/src/lib/models/DotCMSPage.model.d.ts +0 -245
  74. package/src/lib/models/DotCMSPage.model.js +0 -3
  75. package/src/lib/models/DotCMSPage.model.js.map +0 -1
  76. package/src/lib/models/DotCMSPageFormat.model.d.ts +0 -4
  77. package/src/lib/models/DotCMSPageFormat.model.js +0 -3
  78. package/src/lib/models/DotCMSPageFormat.model.js.map +0 -1
  79. package/src/lib/models/DotCMSSite.model.d.ts +0 -156
  80. package/src/lib/models/DotCMSSite.model.js +0 -3
  81. package/src/lib/models/DotCMSSite.model.js.map +0 -1
  82. package/src/lib/models/index.d.ts +0 -13
  83. package/src/lib/models/index.js +0 -17
  84. package/src/lib/models/index.js.map +0 -1
  85. package/src/lib/utils/DotCMSHttpClient.d.ts +0 -6
  86. package/src/lib/utils/DotCMSHttpClient.js +0 -14
  87. package/src/lib/utils/DotCMSHttpClient.js.map +0 -1
  88. package/src/lib/utils/getEsQuery.d.ts +0 -2
  89. package/src/lib/utils/getEsQuery.js +0 -39
  90. package/src/lib/utils/getEsQuery.js.map +0 -1
  91. package/src/lib/utils/request.d.ts +0 -2
  92. package/src/lib/utils/request.js +0 -47
  93. package/src/lib/utils/request.js.map +0 -1
package/index.js ADDED
@@ -0,0 +1,1197 @@
1
+ #!/usr/bin/env node
2
+
3
+ // libs/sdk/cli/src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // libs/sdk/cli/src/commands/agent/index.ts
7
+ import ora from "ora";
8
+
9
+ // libs/sdk/cli/src/commands/agent/prompts.ts
10
+ import inquirer from "inquirer";
11
+ var inquirerPort = {
12
+ async text(message, defaultValue) {
13
+ const { value } = await inquirer.prompt([
14
+ { type: "input", name: "value", message, default: defaultValue }
15
+ ]);
16
+ return value;
17
+ },
18
+ async password(message) {
19
+ const { value } = await inquirer.prompt([
20
+ { type: "password", name: "value", message, mask: "*" }
21
+ ]);
22
+ return value;
23
+ },
24
+ async select(message, choices) {
25
+ const { value } = await inquirer.prompt([
26
+ { type: "select", name: "value", message, choices }
27
+ ]);
28
+ return value;
29
+ },
30
+ async multiSelect(message, choices) {
31
+ const { value } = await inquirer.prompt([
32
+ { type: "checkbox", name: "value", message, choices }
33
+ ]);
34
+ return value;
35
+ }
36
+ };
37
+ async function ask(message) {
38
+ const { value } = await inquirer.prompt([
39
+ { type: "confirm", name: "value", message, default: true }
40
+ ]);
41
+ return value;
42
+ }
43
+ var confirmOverwrite = (file) => ask(`${file} already has a dotcms entry. Replace it?`);
44
+ var confirmExclude = (files) => ask(
45
+ `Add ${files.length === 1 ? "this file" : `these ${files.length} files`} to .gitignore? They contain an access token.`
46
+ );
47
+
48
+ // libs/sdk/cli/src/commands/agent/setup.ts
49
+ import { parse as parseToml } from "smol-toml";
50
+
51
+ // libs/sdk/cli/src/commands/agent/connect.ts
52
+ import * as childProcess from "node:child_process";
53
+
54
+ // libs/sdk/cli/src/commands/agent/constants.ts
55
+ var ENTRY_KEY = "dotcms";
56
+ var MCP_SERVER_PACKAGE = "@dotcms/mcp-server@latest";
57
+ var SERVER_ENV = {
58
+ url: "DOTCMS_URL",
59
+ token: "AUTH_TOKEN"
60
+ };
61
+ var SKILLS_SOURCE = "dotCMS/agent-toolkit";
62
+
63
+ // libs/sdk/cli/src/commands/agent/connect.ts
64
+ var DEFAULT_TIMEOUT_MS = 6e4;
65
+ function classify(stderr, code) {
66
+ if (/404|E404|not found|ETARGET|ENOTFOUND|registry/i.test(stderr))
67
+ return "fetch-failed";
68
+ if (/Unsupported engine|requires Node|SyntaxError|Unexpected token/i.test(stderr)) {
69
+ return "runtime-unsupported";
70
+ }
71
+ return code === null ? "exited" : "exited";
72
+ }
73
+ async function confirmConnection(args) {
74
+ const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT_MS;
75
+ const child = childProcess.spawn("npx", ["-y", MCP_SERVER_PACKAGE], {
76
+ stdio: ["pipe", "pipe", "pipe"],
77
+ env: {
78
+ ...process.env,
79
+ [SERVER_ENV.url]: args.url,
80
+ [SERVER_ENV.token]: args.token
81
+ }
82
+ });
83
+ let stderr = "";
84
+ child.stderr?.on("data", (chunk) => {
85
+ stderr += String(chunk);
86
+ });
87
+ return new Promise((resolve2) => {
88
+ let settled = false;
89
+ const finish = (result) => {
90
+ if (settled)
91
+ return;
92
+ settled = true;
93
+ clearTimeout(timer);
94
+ try {
95
+ child.kill();
96
+ } catch {
97
+ }
98
+ resolve2(result);
99
+ };
100
+ const timer = setTimeout(
101
+ () => finish({ ok: false, cause: "timeout", detail: `No response within ${timeoutMs}ms.` }),
102
+ timeoutMs
103
+ );
104
+ let buffer = "";
105
+ child.stdout?.on("data", (chunk) => {
106
+ buffer += String(chunk);
107
+ for (const line of buffer.split("\n")) {
108
+ if (!line.trim())
109
+ continue;
110
+ try {
111
+ const message = JSON.parse(line);
112
+ const tools = message.result?.tools;
113
+ if (Array.isArray(tools))
114
+ finish({ ok: true, toolCount: tools.length });
115
+ } catch {
116
+ }
117
+ }
118
+ });
119
+ child.on(
120
+ "error",
121
+ (error) => finish({ ok: false, cause: "exited", detail: error.message })
122
+ );
123
+ child.on(
124
+ "exit",
125
+ (code) => finish({
126
+ ok: false,
127
+ cause: classify(stderr, code),
128
+ detail: stderr.trim().split("\n").slice(-1)[0] || `Server exited with code ${code}.`
129
+ })
130
+ );
131
+ child.stdin?.write(
132
+ `${JSON.stringify({
133
+ jsonrpc: "2.0",
134
+ id: 1,
135
+ method: "initialize",
136
+ params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "dotcms", version: "0" } }
137
+ })}
138
+ `
139
+ );
140
+ child.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" })}
141
+ `);
142
+ });
143
+ }
144
+
145
+ // libs/sdk/cli/src/commands/agent/gitignore.ts
146
+ import { existsSync } from "node:fs";
147
+ import * as fs from "node:fs/promises";
148
+ import * as path from "node:path";
149
+ var CONVENTIONALLY_COMMITTED = /* @__PURE__ */ new Set([".mcp.json"]);
150
+ function findRepositoryRoot(from) {
151
+ let dir = path.resolve(from);
152
+ for (; ; ) {
153
+ if (existsSync(path.join(dir, ".git")))
154
+ return dir;
155
+ const parent = path.dirname(dir);
156
+ if (parent === dir)
157
+ return null;
158
+ dir = parent;
159
+ }
160
+ }
161
+ async function protectFromVersionControl(args) {
162
+ const warnings = [];
163
+ const root = findRepositoryRoot(args.cwd);
164
+ for (const file of args.files) {
165
+ if (CONVENTIONALLY_COMMITTED.has(path.basename(file))) {
166
+ warnings.push(
167
+ `${path.basename(file)} is normally committed to version control \u2014 it now holds a token, so committing it would publish that token.`
168
+ );
169
+ }
170
+ }
171
+ if (!root) {
172
+ warnings.push(
173
+ "This directory is not under version control, so these files are unprotected \u2014 nothing here can exclude them for you."
174
+ );
175
+ return { files: args.files, inRepository: false, excluded: false, warnings };
176
+ }
177
+ const proceed = args.confirmExclude ? await args.confirmExclude(args.files) : false;
178
+ if (!proceed) {
179
+ return { files: args.files, inRepository: true, excluded: false, warnings };
180
+ }
181
+ const gitignorePath = path.join(root, ".gitignore");
182
+ let current = "";
183
+ try {
184
+ current = await fs.readFile(gitignorePath, "utf8");
185
+ } catch {
186
+ }
187
+ const already = new Set(current.split("\n").map((line) => line.trim()));
188
+ const toAdd = args.files.map((file) => path.relative(root, file).split(path.sep).join("/")).filter((entry) => !already.has(entry));
189
+ if (toAdd.length) {
190
+ const prefix = current === "" || current.endsWith("\n") ? "" : "\n";
191
+ const block = `${prefix}
192
+ # dotCMS agent configuration \u2014 contains an access token
193
+ ${toAdd.join("\n")}
194
+ `;
195
+ await fs.writeFile(gitignorePath, current + block, "utf8");
196
+ }
197
+ return { files: args.files, inRepository: true, excluded: true, warnings };
198
+ }
199
+
200
+ // libs/sdk/cli/src/commands/agent/skills.ts
201
+ import * as childProcess2 from "node:child_process";
202
+ function buildSkillsArgs(agentIds, global) {
203
+ return [
204
+ "-y",
205
+ "skills",
206
+ "add",
207
+ SKILLS_SOURCE,
208
+ ...agentIds.flatMap((id) => ["-a", id]),
209
+ ...global ? ["-g"] : [],
210
+ "-y"
211
+ ];
212
+ }
213
+ async function installSkills(args) {
214
+ const argv = buildSkillsArgs(args.agentIds, args.global);
215
+ const command = `npx ${argv.join(" ")}`;
216
+ try {
217
+ const result = childProcess2.spawnSync("npx", argv, { stdio: "inherit" });
218
+ if (result.status === 0)
219
+ return { ok: true, command };
220
+ return { ok: false, command, reason: `skills exited with code ${result.status}` };
221
+ } catch (error) {
222
+ return { ok: false, command, reason: error.message };
223
+ }
224
+ }
225
+
226
+ // libs/sdk/cli/src/shared/config-file.ts
227
+ import * as fs2 from "node:fs/promises";
228
+ import * as path2 from "node:path";
229
+
230
+ // libs/sdk/cli/src/shared/errors.ts
231
+ var CliError = class extends Error {
232
+ constructor(message) {
233
+ super(message);
234
+ this.name = new.target.name;
235
+ }
236
+ };
237
+ var UsageError = class extends CliError {
238
+ };
239
+ var InvalidUrlError = class extends CliError {
240
+ constructor(value) {
241
+ const host = value.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/^\/+/, "");
242
+ super(
243
+ `"${value}" is not a valid instance address. Pass it with an http:// or https:// scheme, e.g. https://${host || "demo.dotcms.com"}`
244
+ );
245
+ }
246
+ };
247
+ var InstanceUnreachableError = class extends CliError {
248
+ constructor(url, reason) {
249
+ super(`Could not reach ${url} \u2014 ${reason}. Check the address and that the instance is running.`);
250
+ }
251
+ };
252
+ var NotADotCmsInstanceError = class extends CliError {
253
+ constructor(url) {
254
+ super(`${url} is not a valid dotCMS instance. Check the address.`);
255
+ }
256
+ };
257
+ var CredentialsRejectedError = class extends CliError {
258
+ constructor(url) {
259
+ super(`The username and password were rejected by ${url}. Check them and try again.`);
260
+ }
261
+ };
262
+ var TokenRejectedError = class extends CliError {
263
+ constructor(url) {
264
+ super(
265
+ `The token was rejected by ${url} \u2014 it is invalid, expired, or revoked. Nothing has been written. Supply a valid token, or sign in to mint a new one.`
266
+ );
267
+ }
268
+ };
269
+ var UnknownTargetError = class extends UsageError {
270
+ constructor(id, valid) {
271
+ super(`"${id}" is not a supported editor. Valid ids: ${valid.join(", ")}.`);
272
+ }
273
+ };
274
+ var ConflictingAuthError = class extends UsageError {
275
+ /**
276
+ * Names the sources the developer actually used. Reporting `--authToken` to someone who set
277
+ * `DOTCMS_AUTH_TOKEN` and never typed a flag sends them looking for a flag that is not there.
278
+ */
279
+ constructor(tokenSource, credentialSource) {
280
+ super(
281
+ `${tokenSource} cannot be combined with ${credentialSource}. They are alternative ways to authenticate, not a fallback chain \u2014 use one or the other. Nothing has been written.`
282
+ );
283
+ }
284
+ };
285
+ var MissingInputError = class extends UsageError {
286
+ constructor(what) {
287
+ super(`${what} is required and there is no terminal to prompt on. Pass it as an option or set its environment variable.`);
288
+ }
289
+ };
290
+ var NoConfigPathError = class extends CliError {
291
+ constructor(displayName, scope) {
292
+ super(
293
+ `${displayName} has no configuration file at ${scope} scope. Re-run with ${scope === "folder" ? "-g/--global" : "--project"}, or drop it from --agent.`
294
+ );
295
+ }
296
+ };
297
+ var MalformedConfigError = class extends CliError {
298
+ constructor(file) {
299
+ super(
300
+ `${file} is not valid ${file.endsWith(".toml") ? "TOML" : "JSON"} \u2014 fix it or re-run with --skip-mcp. It has been left untouched.`
301
+ );
302
+ }
303
+ };
304
+
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
+ // libs/sdk/cli/src/commands/agent/targets/registry.ts
408
+ var home = () => os.homedir();
409
+ var inHome = (...parts) => path3.join(home(), ...parts);
410
+ var inFolder = (cwd, ...parts) => path3.join(cwd ?? process.cwd(), ...parts);
411
+ var probe = (...parts) => async () => existsSync2(inHome(...parts));
412
+ function vscodeUserDir() {
413
+ if (process.platform === "darwin")
414
+ return inHome("Library", "Application Support", "Code", "User");
415
+ if (process.platform === "win32") {
416
+ const appData = process.env["APPDATA"] ?? inHome("AppData", "Roaming");
417
+ return path3.join(appData, "Code", "User");
418
+ }
419
+ return inHome(".config", "Code", "User");
420
+ }
421
+ function codexHome() {
422
+ return readEnv(ENV_KEYS.codexHome) ?? inHome(".codex");
423
+ }
424
+ var TARGETS = [
425
+ {
426
+ id: "claude-code",
427
+ displayName: "Claude Code",
428
+ skillsAgentId: "claude-code",
429
+ skillsLocationVerified: true,
430
+ format: "json",
431
+ containerKey: "mcpServers",
432
+ entryShape: "stdio",
433
+ detect: probe(".claude"),
434
+ configPath: (scope, cwd) => scope === "global" ? inHome(".claude.json") : inFolder(cwd, ".mcp.json")
435
+ },
436
+ {
437
+ id: "cursor",
438
+ displayName: "Cursor",
439
+ skillsAgentId: "cursor",
440
+ skillsLocationVerified: true,
441
+ format: "json",
442
+ containerKey: "mcpServers",
443
+ entryShape: "stdio",
444
+ detect: probe(".cursor"),
445
+ configPath: (scope, cwd) => scope === "global" ? inHome(".cursor", "mcp.json") : inFolder(cwd, ".cursor", "mcp.json")
446
+ },
447
+ {
448
+ id: "vscode",
449
+ displayName: "VS Code (Copilot)",
450
+ // Writes to ~/.copilot/skills — the Copilot CLI location. Not confirmed to be read by
451
+ // the in-editor agent, so the summary must not claim skills landed here (FR-027).
452
+ skillsAgentId: "github-copilot",
453
+ // Confirmed 2026-09-04 against VS Code's own docs: the in-editor agent reads Agent
454
+ // Skills from ~/.copilot/skills (alongside ~/.claude/skills and ~/.agents/skills), the
455
+ // same directory `skills -a github-copilot` writes to. The spec's caveat is retired.
456
+ skillsLocationVerified: true,
457
+ format: "json",
458
+ // NOT `mcpServers` — VS Code is the one that differs by key.
459
+ containerKey: "servers",
460
+ entryShape: "stdio",
461
+ detect: async () => existsSync2(vscodeUserDir()),
462
+ configPath: (scope, cwd) => scope === "global" ? path3.join(vscodeUserDir(), "mcp.json") : inFolder(cwd, ".vscode", "mcp.json")
463
+ },
464
+ {
465
+ id: "codex",
466
+ displayName: "Codex",
467
+ skillsAgentId: "codex",
468
+ skillsLocationVerified: true,
469
+ format: "toml",
470
+ containerKey: "mcp_servers",
471
+ entryShape: "stdio",
472
+ detect: probe(".codex"),
473
+ configPath: (scope, cwd) => scope === "global" ? path3.join(codexHome(), "config.toml") : inFolder(cwd, ".codex", "config.toml")
474
+ },
475
+ {
476
+ id: "antigravity",
477
+ displayName: "Antigravity",
478
+ skillsAgentId: "antigravity",
479
+ skillsLocationVerified: true,
480
+ format: "json",
481
+ containerKey: "mcpServers",
482
+ entryShape: "stdio",
483
+ detect: probe(".gemini"),
484
+ configPath: (scope, cwd) => scope === "global" ? inHome(".gemini", "config", "mcp_config.json") : inFolder(cwd, ".agents", "mcp_config.json")
485
+ },
486
+ {
487
+ id: "devin",
488
+ displayName: "Devin",
489
+ skillsAgentId: "devin",
490
+ skillsLocationVerified: true,
491
+ format: "json",
492
+ containerKey: "mcpServers",
493
+ entryShape: "stdio",
494
+ detect: probe(".config", "devin"),
495
+ configPath: (scope, cwd) => scope === "global" ? inHome(".config", "devin", "mcp_config.json") : inFolder(cwd, ".devin", "mcp_config.local.json")
496
+ },
497
+ {
498
+ id: "opencode",
499
+ displayName: "OpenCode",
500
+ skillsAgentId: "opencode",
501
+ skillsLocationVerified: true,
502
+ format: "json",
503
+ // Differs by key AND by entry shape — see `entryShape`.
504
+ containerKey: "mcp",
505
+ entryShape: "opencode-local",
506
+ detect: probe(".config", "opencode"),
507
+ configPath: (scope, cwd) => scope === "global" ? inHome(".config", "opencode", "opencode.json") : inFolder(cwd, "opencode.json")
508
+ }
509
+ ];
510
+ var TARGET_IDS = TARGETS.map((t) => t.id);
511
+ function getTarget(id) {
512
+ const found = TARGETS.find((t) => t.id === id);
513
+ if (!found)
514
+ throw new UnknownTargetError(id, TARGET_IDS);
515
+ return found;
516
+ }
517
+ async function detectTargets() {
518
+ const results = await Promise.all(TARGETS.map(async (t) => await t.detect() ? t : null));
519
+ return results.filter((t) => t !== null);
520
+ }
521
+
522
+ // libs/sdk/cli/src/commands/agent/targets/toml-target.ts
523
+ import { parse, stringify } from "smol-toml";
524
+ import * as fs3 from "node:fs/promises";
525
+ import * as path4 from "node:path";
526
+ function findEntrySpan(lines, containerKey) {
527
+ const ours = new RegExp(`^\\s*\\[\\s*${containerKey}\\.${ENTRY_KEY}\\s*(\\.[^\\]]+)?\\]`);
528
+ const anyHeader = /^\s*\[/;
529
+ const start = lines.findIndex((line) => ours.test(line));
530
+ if (start === -1)
531
+ return null;
532
+ let end = lines.length;
533
+ for (let i = start + 1; i < lines.length; i++) {
534
+ if (anyHeader.test(lines[i]) && !ours.test(lines[i])) {
535
+ end = i;
536
+ break;
537
+ }
538
+ }
539
+ while (end > start + 1 && (lines[end - 1].trim() === "" || lines[end - 1].trim().startsWith("#"))) {
540
+ end--;
541
+ }
542
+ return { start, end };
543
+ }
544
+ function renderEntry(target, url, token, containerKey) {
545
+ return stringify({
546
+ [containerKey]: { [ENTRY_KEY]: buildEntry(target, url, token) }
547
+ }).trimEnd();
548
+ }
549
+ async function writeTomlTarget(args) {
550
+ const file = args.target.configPath(args.scope, args.cwd);
551
+ if (!file) {
552
+ throw new NoConfigPathError(args.target.displayName, args.scope);
553
+ }
554
+ let original = "";
555
+ try {
556
+ original = await fs3.readFile(file, "utf8");
557
+ } catch {
558
+ }
559
+ if (original.trim() !== "") {
560
+ try {
561
+ parse(original);
562
+ } catch {
563
+ throw new MalformedConfigError(file);
564
+ }
565
+ }
566
+ const block = renderEntry(args.target, args.url, args.token, args.target.containerKey);
567
+ let next;
568
+ if (original.trim() === "") {
569
+ next = block;
570
+ } else {
571
+ const lines = original.split("\n");
572
+ const span = findEntrySpan(lines, args.target.containerKey);
573
+ if (span) {
574
+ lines.splice(span.start, span.end - span.start, ...block.trimEnd().split("\n"));
575
+ next = lines.join("\n");
576
+ } else {
577
+ const sep2 = original.endsWith("\n") ? "\n" : "\n\n";
578
+ next = `${original}${sep2}${block}`;
579
+ }
580
+ }
581
+ await ensureDir(path4.dirname(file));
582
+ await fs3.writeFile(file, next.endsWith("\n") ? next : `${next}
583
+ `, "utf8");
584
+ await restrictFile(file);
585
+ return file;
586
+ }
587
+
588
+ // libs/http/src/lib/http.ts
589
+ var HttpError = class extends Error {
590
+ constructor(message, init) {
591
+ super(message);
592
+ this.name = "HttpError";
593
+ this.status = init.status ?? null;
594
+ this.code = init.code;
595
+ if (typeof init.status === "number") {
596
+ this.response = { status: init.status, statusText: init.statusText ?? "" };
597
+ }
598
+ }
599
+ };
600
+ function isHttpError(error) {
601
+ return error instanceof HttpError;
602
+ }
603
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
604
+ function isSuccess(status) {
605
+ return status >= 200 && status < 300;
606
+ }
607
+ async function readBody(response) {
608
+ const text = await response.text().catch(() => "");
609
+ if (!text) {
610
+ return void 0;
611
+ }
612
+ try {
613
+ return JSON.parse(text);
614
+ } catch {
615
+ return text;
616
+ }
617
+ }
618
+ async function request(url, init, { token, timeoutMs = DEFAULT_TIMEOUT_MS2, acceptAnyStatus = false }) {
619
+ const controller = new AbortController();
620
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
621
+ const headers = new Headers(init.headers);
622
+ if (token) {
623
+ headers.set("Authorization", `Bearer ${token}`);
624
+ }
625
+ let response;
626
+ try {
627
+ response = await fetch(url, { ...init, headers, signal: controller.signal });
628
+ } catch (error) {
629
+ const aborted = error?.name === "AbortError";
630
+ const cause = error?.cause;
631
+ throw new HttpError(
632
+ aborted ? `Request to ${url} timed out after ${timeoutMs}ms` : `Request to ${url} failed: ${error?.message ?? String(error)}`,
633
+ { status: null, code: aborted ? "ETIMEDOUT" : cause?.code }
634
+ );
635
+ } finally {
636
+ clearTimeout(timer);
637
+ }
638
+ const data = await readBody(response);
639
+ if (!isSuccess(response.status) && !acceptAnyStatus) {
640
+ throw new HttpError(`Request failed with status code ${response.status}`, {
641
+ status: response.status,
642
+ statusText: response.statusText
643
+ });
644
+ }
645
+ return { status: response.status, data };
646
+ }
647
+ function httpGet(url, options = {}) {
648
+ return request(url, { method: "GET" }, options);
649
+ }
650
+ function httpPost(url, body, options = {}) {
651
+ return request(
652
+ url,
653
+ {
654
+ method: "POST",
655
+ body: JSON.stringify(body),
656
+ headers: { "Content-Type": "application/json" }
657
+ },
658
+ options
659
+ );
660
+ }
661
+
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
+ // libs/http/src/lib/endpoints.ts
695
+ var DOTCMS_API = {
696
+ /** Reachability probe, and the source of the instance version. */
697
+ appConfiguration: "/api/v1/appconfiguration",
698
+ /** Mints an API token. `expirationDays` is sent as a STRING. */
699
+ apiToken: "/api/v1/authentication/api-token",
700
+ /** Confirms a token actually authorizes, not merely that it was issued. */
701
+ currentUser: "/api/v1/users/current"
702
+ };
703
+ function endpoint(baseUrl, path5) {
704
+ return `${baseUrl}${path5}`;
705
+ }
706
+
707
+ // libs/sdk/cli/src/shared/auth.ts
708
+ async function mintToken(args) {
709
+ try {
710
+ const { data } = await httpPost(endpoint(args.url, DOTCMS_API.apiToken), {
711
+ user: args.user,
712
+ password: args.password,
713
+ expirationDays: "365",
714
+ label: "dotcms agent setup"
715
+ });
716
+ return { value: data.entity.token, origin: "minted", verified: false };
717
+ } catch (error) {
718
+ if (isHttpError(error) && error.response?.status === 401) {
719
+ throw new CredentialsRejectedError(args.url);
720
+ }
721
+ throw new InstanceUnreachableError(args.url, describeRequestFailure(error));
722
+ }
723
+ }
724
+ async function verifyToken(url, token) {
725
+ try {
726
+ await httpGet(endpoint(url, DOTCMS_API.currentUser), { token: token.value });
727
+ return { ...token, verified: true };
728
+ } catch (error) {
729
+ if (isHttpError(error) && (error.response?.status === 401 || error.response?.status === 403)) {
730
+ throw new TokenRejectedError(url);
731
+ }
732
+ throw new InstanceUnreachableError(url, describeRequestFailure(error));
733
+ }
734
+ }
735
+
736
+ // libs/sdk/cli/src/shared/instance.ts
737
+ function normalizeUrl(url) {
738
+ return url.trim().replace(/\/+$/, "");
739
+ }
740
+ function validateUrl(url) {
741
+ if (!/^https?:\/\//i.test(url))
742
+ throw new InvalidUrlError(url);
743
+ try {
744
+ new URL(url);
745
+ } catch {
746
+ throw new InvalidUrlError(url);
747
+ }
748
+ }
749
+ async function checkReachable(url) {
750
+ let response;
751
+ try {
752
+ response = await httpGet(endpoint(url, DOTCMS_API.appConfiguration), {
753
+ acceptAnyStatus: true
754
+ });
755
+ } catch (error) {
756
+ throw new InstanceUnreachableError(url, describeRequestFailure(error));
757
+ }
758
+ if (!isDotCmsConfiguration(response.data)) {
759
+ throw new NotADotCmsInstanceError(url);
760
+ }
761
+ return { url, version: readVersion(response.data) };
762
+ }
763
+ function isDotCmsConfiguration(data) {
764
+ const config = data?.entity?.config;
765
+ if (!config || typeof config !== "object")
766
+ return false;
767
+ return ["releaseInfo", "license", "cluster", "emailRegex", "languages"].some(
768
+ (key) => key in config
769
+ );
770
+ }
771
+ function readVersion(data) {
772
+ const info = data?.entity?.config?.releaseInfo;
773
+ const candidate = info?.version;
774
+ return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
775
+ }
776
+
777
+ // libs/sdk/cli/src/shared/prompts.ts
778
+ var DEFAULT_URL = "http://localhost:8082";
779
+ function canPrompt() {
780
+ return Boolean(process.stdin.isTTY);
781
+ }
782
+ async function resolveInstanceUrl(opts, port, interactive = Boolean(port)) {
783
+ let url = opts.url ?? readEnv(ENV_KEYS.url);
784
+ if (!url) {
785
+ if (!interactive || !port)
786
+ throw new MissingInputError("An instance address");
787
+ url = await port.text("dotCMS instance address", DEFAULT_URL);
788
+ }
789
+ url = normalizeUrl(url);
790
+ validateUrl(url);
791
+ return url;
792
+ }
793
+ async function resolveRequiredInputs(opts, port, interactive = Boolean(port)) {
794
+ let prompted = false;
795
+ const ask2 = async (what, run) => {
796
+ if (!interactive || !port)
797
+ throw new MissingInputError(what);
798
+ prompted = true;
799
+ return run();
800
+ };
801
+ const url = await resolveInstanceUrl(opts, port, interactive);
802
+ if (!opts.url && !readEnv(ENV_KEYS.url))
803
+ prompted = true;
804
+ const authToken = opts.authToken ?? readEnv(ENV_KEYS.authToken);
805
+ const user = opts.user;
806
+ const password = opts.password ?? readEnv(ENV_KEYS.password);
807
+ if (authToken)
808
+ return { url, authToken, prompted };
809
+ if (user && password)
810
+ return { url, user, password, prompted };
811
+ if (user) {
812
+ const typed = await ask2("A password", () => port.password("Password"));
813
+ return { url, user, password: typed, prompted };
814
+ }
815
+ if (password) {
816
+ const typed = await ask2("A username", () => port.text("Username"));
817
+ return { url, user: typed, password, prompted };
818
+ }
819
+ if (!interactive || !port) {
820
+ throw new MissingInputError("A username and password, or an authentication token,");
821
+ }
822
+ prompted = true;
823
+ const mode = await port.select("How should we authenticate?", [
824
+ { name: "Sign in with a username and password", value: "signin" },
825
+ { name: "Paste an existing authentication token", value: "token" }
826
+ ]);
827
+ if (mode === "token") {
828
+ return { url, authToken: await port.password("Authentication token"), prompted };
829
+ }
830
+ return {
831
+ url,
832
+ user: await port.text("Username"),
833
+ password: await port.password("Password"),
834
+ prompted
835
+ };
836
+ }
837
+
838
+ // libs/sdk/cli/src/commands/agent/setup.ts
839
+ var MAX_AUTH_ATTEMPTS = 3;
840
+ function resolveAuthMode(opts) {
841
+ const token = opts.authToken ?? readEnv(ENV_KEYS.authToken);
842
+ const password = opts.password ?? readEnv(ENV_KEYS.password);
843
+ const user = opts.user;
844
+ if (token && (user || password)) {
845
+ const tokenSource = opts.authToken ? "--authToken" : ENV_KEYS.authToken;
846
+ const credentialSource = opts.user ? "--user" : opts.password ? "--password" : ENV_KEYS.password;
847
+ throw new ConflictingAuthError(tokenSource, credentialSource);
848
+ }
849
+ return { token, user, password };
850
+ }
851
+ function resolveTargets(opts) {
852
+ if (!opts.agents?.length)
853
+ return [];
854
+ for (const id of opts.agents) {
855
+ if (!TARGET_IDS.includes(id))
856
+ throw new UnknownTargetError(id, TARGET_IDS);
857
+ }
858
+ return opts.agents;
859
+ }
860
+ async function runSetup(opts) {
861
+ const auth = resolveAuthMode(opts);
862
+ const explicitTargets = resolveTargets(opts);
863
+ const scope = opts.scope ?? "folder";
864
+ const step = opts.onProgress ?? (() => void 0);
865
+ const url = await resolveInstanceUrl(opts, opts.promptPort);
866
+ step(`Checking ${url}`);
867
+ await checkReachable(url);
868
+ let inputs = await resolveRequiredInputs(
869
+ { ...opts, url, authToken: auth.token, user: auth.user, password: auth.password },
870
+ opts.promptPort
871
+ );
872
+ let token;
873
+ for (let attempt = 1; ; attempt++) {
874
+ try {
875
+ if (inputs.authToken) {
876
+ token = { value: inputs.authToken, origin: "supplied", verified: false };
877
+ } else {
878
+ step("Minting an access token");
879
+ token = await mintToken({
880
+ url,
881
+ user: inputs.user,
882
+ password: inputs.password
883
+ });
884
+ }
885
+ step("Verifying the token");
886
+ token = await verifyToken(url, token);
887
+ break;
888
+ } catch (error) {
889
+ const rejected = error instanceof TokenRejectedError || error instanceof CredentialsRejectedError;
890
+ if (!rejected || !opts.promptPort || attempt >= MAX_AUTH_ATTEMPTS)
891
+ throw error;
892
+ opts.onAuthRetry?.(error.message, attempt, MAX_AUTH_ATTEMPTS);
893
+ inputs = await resolveRequiredInputs({ url, cwd: opts.cwd }, opts.promptPort);
894
+ }
895
+ }
896
+ let targets;
897
+ if (explicitTargets.length) {
898
+ targets = explicitTargets.map(getTarget);
899
+ } else {
900
+ const detected = await detectTargets();
901
+ const detectedById = new Map(detected.map((t) => [t.id, t]));
902
+ if (opts.promptPort) {
903
+ const chosen = await opts.promptPort.multiSelect(
904
+ "Which editors should we configure?",
905
+ TARGETS.map((t) => ({
906
+ name: t.displayName,
907
+ value: t.id,
908
+ checked: detectedById.has(t.id)
909
+ }))
910
+ );
911
+ targets = chosen.map((id) => detectedById.get(id) ?? getTarget(id));
912
+ } else {
913
+ targets = detected;
914
+ }
915
+ }
916
+ const byId = new Map(targets.map((t) => [t.id, t]));
917
+ const plan = [];
918
+ const seen = /* @__PURE__ */ new Set();
919
+ for (const target of targets) {
920
+ const file = target.configPath(scope, opts.cwd);
921
+ if (!file || seen.has(file))
922
+ continue;
923
+ seen.add(file);
924
+ plan.push({ target, file });
925
+ }
926
+ const outcomes = [];
927
+ if (opts.skipMcp) {
928
+ for (const { target, file } of plan) {
929
+ outcomes.push({
930
+ targetId: target.id,
931
+ scope,
932
+ path: file,
933
+ result: "skipped",
934
+ reason: "configuration writing skipped (--skip-mcp)",
935
+ permissionsApplied: false,
936
+ skillsInstalled: "no"
937
+ });
938
+ }
939
+ }
940
+ if (!opts.skipMcp && plan.length) {
941
+ step(`Writing configuration for ${plan.length} editor${plan.length === 1 ? "" : "s"}`);
942
+ }
943
+ for (const { target, file } of opts.skipMcp ? [] : plan) {
944
+ try {
945
+ const isToml = target.format === "toml";
946
+ const existing = await hasEntry({
947
+ file,
948
+ containerKey: target.containerKey,
949
+ entryKey: ENTRY_KEY,
950
+ parse: isToml ? (raw) => parseToml(raw) : void 0
951
+ });
952
+ if (existing && !opts.force && !opts.yes && opts.confirmOverwrite) {
953
+ const proceed = await opts.confirmOverwrite(file);
954
+ if (!proceed) {
955
+ outcomes.push({
956
+ targetId: target.id,
957
+ scope,
958
+ path: file,
959
+ result: "skipped",
960
+ reason: "left the existing entry in place",
961
+ permissionsApplied: false,
962
+ skillsInstalled: "no"
963
+ });
964
+ continue;
965
+ }
966
+ }
967
+ const written2 = isToml ? {
968
+ path: await writeTomlTarget({ target, scope, url, token: token.value, cwd: opts.cwd }),
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,
974
+ scope,
975
+ path: written2.path,
976
+ result: written2.replacedExisting ? "replaced" : "written",
977
+ reason: null,
978
+ permissionsApplied: written2.permissionsApplied,
979
+ skillsInstalled: "no"
980
+ });
981
+ } 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
+ });
991
+ }
992
+ }
993
+ let versionControl;
994
+ const written = outcomes.filter((o) => (o.result === "written" || o.result === "replaced") && o.path).map((o) => o.path);
995
+ if (scope === "folder" && written.length) {
996
+ versionControl = await protectFromVersionControl({
997
+ files: written,
998
+ cwd: opts.cwd ?? process.cwd(),
999
+ confirmExclude: opts.yes ? async () => true : opts.confirmExclude
1000
+ });
1001
+ }
1002
+ if (!opts.skipSkills) {
1003
+ 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));
1004
+ const ids = eligible.filter((t) => Boolean(t.skillsAgentId)).map((t) => t.skillsAgentId);
1005
+ if (ids.length) {
1006
+ step("Installing the dotCMS skills");
1007
+ const skills = await installSkills({ agentIds: ids, global: scope === "global" });
1008
+ for (const o of outcomes) {
1009
+ if (o.result === "failed")
1010
+ continue;
1011
+ const target = byId.get(o.targetId);
1012
+ o.skillsInstalled = !skills.ok ? "no" : target?.skillsLocationVerified ? "yes" : "unverified";
1013
+ }
1014
+ }
1015
+ }
1016
+ let connection = "skipped";
1017
+ let connectionReason;
1018
+ if (!opts.skipVerify && !opts.skipMcp) {
1019
+ step("Starting the server to confirm it responds (this can take a minute on a cold npx cache)");
1020
+ const result = await confirmConnection({ url, token: token.value });
1021
+ connection = result.ok ? "ok" : "failed";
1022
+ if (!result.ok)
1023
+ connectionReason = `${result.cause}: ${result.detail}`;
1024
+ }
1025
+ const anyFailed = outcomes.some((o) => o.result === "failed") || connection === "failed";
1026
+ return { outcomes, versionControl, connection, connectionReason, exitCode: anyFailed ? 1 : 0 };
1027
+ }
1028
+
1029
+ // libs/sdk/cli/src/shared/ui.ts
1030
+ import cfonts from "cfonts";
1031
+ import chalk from "chalk";
1032
+ function printBanner() {
1033
+ cfonts.say("dotCMS", { font: "block", align: "left", colors: ["red", "white"], space: false });
1034
+ process.stdout.write(" Connect your AI coding agent to a dotCMS instance\n\n");
1035
+ }
1036
+ var RESULT_MARK = {
1037
+ written: chalk.green("\u2713 written "),
1038
+ replaced: chalk.green("\u2713 replaced"),
1039
+ skipped: chalk.dim("\xB7 skipped "),
1040
+ failed: chalk.red("\u2717 failed ")
1041
+ };
1042
+ function renderSummary(input) {
1043
+ const lines = [];
1044
+ for (const o of input.outcomes) {
1045
+ const bits = [
1046
+ ` ${RESULT_MARK[o.result]}`,
1047
+ o.targetId.padEnd(13),
1048
+ o.scope.padEnd(7),
1049
+ o.path ?? "\u2014"
1050
+ ];
1051
+ lines.push(bits.join(" "));
1052
+ if (o.result === "failed" && o.reason)
1053
+ lines.push(` ${o.reason}`);
1054
+ if (!o.permissionsApplied && o.path && o.result !== "failed" && o.result !== "skipped") {
1055
+ lines.push(" could not restrict file permissions on this platform");
1056
+ }
1057
+ if (o.skillsInstalled === "unverified") {
1058
+ lines.push(" skills location unverified for this editor \u2014 not confirmed installed");
1059
+ }
1060
+ }
1061
+ if (input.versionControl?.files.length) {
1062
+ const vc = input.versionControl;
1063
+ lines.push("");
1064
+ lines.push(" These files now contain an access token:");
1065
+ for (const f of vc.files)
1066
+ lines.push(` ${f}`);
1067
+ if (vc.excluded)
1068
+ lines.push(chalk.green(" \u2713 added to .gitignore"));
1069
+ else if (vc.inRepository)
1070
+ lines.push(chalk.yellow(" ! not excluded from version control"));
1071
+ for (const w of vc.warnings)
1072
+ lines.push(chalk.yellow(` ! ${w}`));
1073
+ }
1074
+ lines.push("");
1075
+ if (input.connection === "ok") {
1076
+ lines.push(chalk.green(" \u2713 server responded"));
1077
+ } else if (input.connection === "skipped") {
1078
+ lines.push(" \xB7 connection check skipped");
1079
+ } else {
1080
+ lines.push(chalk.red(` \u2717 ${input.connectionReason ?? "the server did not start"}`));
1081
+ lines.push(" Configuration was written and left in place; the server did not come up.");
1082
+ }
1083
+ const allGood = input.connection === "ok" && input.outcomes.every((o) => o.result !== "failed");
1084
+ if (allGood) {
1085
+ lines.push("");
1086
+ lines.push(` Ready \u2014 ${input.nextStep ?? "open your editor and start using dotCMS."}`);
1087
+ }
1088
+ return lines.join("\n");
1089
+ }
1090
+ function writeOut(text) {
1091
+ process.stdout.write(`${text}
1092
+ `);
1093
+ }
1094
+
1095
+ // libs/sdk/cli/src/commands/agent/index.ts
1096
+ function makeProgress(interactive) {
1097
+ let spinner = null;
1098
+ return {
1099
+ step(text) {
1100
+ if (!interactive) {
1101
+ writeOut(` \xB7 ${text}`);
1102
+ return;
1103
+ }
1104
+ spinner?.succeed();
1105
+ spinner = ora().start(text);
1106
+ },
1107
+ done() {
1108
+ spinner?.stop();
1109
+ },
1110
+ /** Leave the failed step visible instead of a spinner that never stops. */
1111
+ fail(text) {
1112
+ if (spinner)
1113
+ spinner.fail(text);
1114
+ else if (text)
1115
+ writeOut(` \u2717 ${text}`);
1116
+ spinner = null;
1117
+ },
1118
+ /** A retry notice: mark the attempt failed, then carry on. */
1119
+ warn(text) {
1120
+ if (spinner)
1121
+ spinner.fail(text);
1122
+ else
1123
+ writeOut(` \u2717 ${text}`);
1124
+ spinner = null;
1125
+ }
1126
+ };
1127
+ }
1128
+ function registerAgentCommand(program2) {
1129
+ const agent = program2.command("agent").description("Connect an AI coding agent to dotCMS");
1130
+ 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(
1131
+ "--password <password>",
1132
+ "password. Visible in the process list and shell history \u2014 prefer DOTCMS_PASSWORD or the prompt"
1133
+ ).option(
1134
+ "--authToken <token>",
1135
+ "an existing token. Visible in the process list and shell history \u2014 prefer DOTCMS_AUTH_TOKEN or the prompt. Cannot be combined with --user/--password"
1136
+ ).option(
1137
+ "--agent <id>",
1138
+ `editor to configure, repeatable (${TARGET_IDS.join(", ")})`,
1139
+ (value, previous = []) => [...previous, value]
1140
+ ).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
+ const interactive = canPrompt();
1144
+ const progress = makeProgress(interactive);
1145
+ try {
1146
+ const result = await runSetup({
1147
+ onProgress: progress.step,
1148
+ onAuthRetry: (message, attempt, max) => progress.warn(`${message} (attempt ${attempt} of ${max})`),
1149
+ promptPort: interactive ? inquirerPort : void 0,
1150
+ confirmOverwrite: interactive ? confirmOverwrite : void 0,
1151
+ confirmExclude: interactive ? confirmExclude : void 0,
1152
+ url: options["url"],
1153
+ user: options["user"],
1154
+ password: options["password"],
1155
+ authToken: options["authToken"],
1156
+ agents: options["agent"],
1157
+ scope: options["global"] ? "global" : "folder",
1158
+ skipMcp: Boolean(options["skipMcp"]),
1159
+ skipSkills: Boolean(options["skipSkills"]),
1160
+ skipVerify: Boolean(options["skipVerify"]),
1161
+ yes: Boolean(options["yes"]),
1162
+ force: Boolean(options["force"])
1163
+ });
1164
+ progress.done();
1165
+ writeOut(
1166
+ renderSummary({
1167
+ outcomes: result.outcomes,
1168
+ versionControl: result.versionControl,
1169
+ connection: result.connection,
1170
+ connectionReason: result.connectionReason
1171
+ })
1172
+ );
1173
+ process.exitCode = result.exitCode;
1174
+ } catch (error) {
1175
+ progress.fail();
1176
+ throw error;
1177
+ }
1178
+ });
1179
+ }
1180
+
1181
+ // libs/sdk/cli/src/index.ts
1182
+ var program = new Command();
1183
+ program.name("dotcms").description("The dotCMS command line tool").showHelpAfterError();
1184
+ registerAgentCommand(program);
1185
+ program.parseAsync(process.argv).catch((error) => {
1186
+ if (error instanceof CliError) {
1187
+ console.error(`
1188
+ ${error.message}
1189
+ `);
1190
+ process.exitCode = error instanceof UsageError ? 2 : 1;
1191
+ return;
1192
+ }
1193
+ console.error(`
1194
+ Something went wrong: ${error?.message ?? String(error)}
1195
+ `);
1196
+ process.exitCode = 1;
1197
+ });