@pega/sdk 0.0.9 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,10 +15,12 @@ has a project in it:
15
15
  said while working inside an existing host application) → run in that
16
16
  **existing project's folder**.
17
17
 
18
- In both cases, run `npx @pega/sdk init` non-interactively — see
18
+ In both cases, run `npx --yes @pega/sdk init` non-interactively — see
19
19
  [Non-Interactive / Scripted Usage](#non-interactive--scripted-usage) below for the
20
20
  full flag reference. Gather answers conversationally, then invoke one command; do
21
- not try to drive the interactive prompts through a terminal.
21
+ not try to drive the interactive prompts through a terminal. The leading `npx
22
+ --yes` matters: without it, npx's own install confirmation blocks before this CLI
23
+ even starts.
22
24
 
23
25
  Mode is decided by the folder you run the command in, not something you need to ask
24
26
  about or guess:
@@ -32,13 +34,15 @@ about or guess:
32
34
  | Decide yourself (don't ask the user) | Ask the user (can't be inferred) |
33
35
  | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
34
36
  | `--mode` (auto-detected from whether `package.json` exists in the cwd) | none — these are all optional and not needed to scaffold |
35
- | `--ai-tool` (you know which tool you are) | |
37
+ | `--ai-tool` (set it to yourself: GitHub Copilot → `copilot`, Cursor → `cursor`, Claude Code → `claude-code`) | |
36
38
  | `--framework`, `--name`, `--templates`, `--categories` (infer from the request, or ask only if genuinely ambiguous) | |
37
39
 
38
- `--server-url` / `--app-alias` / `--client-id` / `--mcp-rule-name` / `--mcp-rule-ins-key`
39
- are optional, CI-only flags. Do not prompt the user for them at scaffold time —
40
- they're configured afterward by editing `sdk-config.json` and the generated MCP
41
- config file directly (see [Configure Your Pega Server](#configure-your-pega-server)).
40
+ Server connection details (Pega server URL, app alias, OAuth client ID, MCP rule
41
+ name/instance key) are never asked at scaffold time, in either mode — the scaffold
42
+ always leaves `sdk-config.json` blank and the generated MCP config file with its
43
+ placeholder markers. An AI coding assistant gathers these from the user in chat
44
+ and fills both files in automatically the first time a workflow needs them (see
45
+ [Configure Your Pega Server](#configure-your-pega-server)).
42
46
 
43
47
  ## Requirements
44
48
 
@@ -46,6 +50,22 @@ config file directly (see [Configure Your Pega Server](#configure-your-pega-serv
46
50
  - **npm** 9 or later
47
51
  - Access to a Pega Infinity server (for running the generated app)
48
52
 
53
+ ## Non-Interactive / Scripted Usage
54
+
55
+ Pass any flag to `init` to scaffold in one shot, with no prompts:
56
+
57
+ ```bash
58
+ # New project
59
+ npx --yes @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false
60
+
61
+ # Embedded flow, inside an existing project
62
+ npx --yes @pega/sdk init --mode embedded --ai-tool cursor --conflict-policy merge
63
+ ```
64
+
65
+ Run `npx --yes @pega/sdk init --help` for the full flag reference (`--help` also
66
+ works without the `init` subcommand). On invalid or missing required input, the
67
+ CLI prints an error to stderr and exits with a non-zero code — no hanging prompt.
68
+
49
69
  ## Quick Start
50
70
 
51
71
  ```bash
@@ -94,27 +114,10 @@ If you skip starter components in a React project, the scaffold uses a minimal
94
114
  dependency manifest for a lighter baseline app.
95
115
 
96
116
  Server connection details (Pega server URL, app alias, OAuth client ID) and MCP
97
- rule details are no longer asked interactively — edit `sdk-config.json` and the
98
- generated MCP config file after scaffolding (see below).
99
-
100
- ## Non-Interactive / Scripted Usage
101
-
102
- Pass any flag to `init` (or `--json`) to scaffold in one shot, with no prompts:
103
-
104
- ```bash
105
- # New project
106
- npx @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false --yes
107
-
108
- # Embedded flow, inside an existing project
109
- npx @pega/sdk init --mode embedded --ai-tool copilot --conflict-policy merge
110
-
111
- # Or supply everything via a JSON answers file (values here win over individual flags)
112
- npx @pega/sdk init --json ./sdk-answers.json
113
- ```
114
-
115
- Run `npx @pega/sdk init --help` for the full flag reference. On invalid or
116
- missing required input, the CLI prints an error to stderr and exits with a
117
- non-zero code — no hanging prompt.
117
+ rule details are never asked — neither interactively nor as CLI flags. Configure
118
+ them after scaffolding, either by asking your AI coding assistant (it gathers them
119
+ in chat and fills in both files automatically) or by editing `sdk-config.json` and
120
+ the generated MCP config file yourself (see below).
118
121
 
119
122
  ## What You Get
120
123
 
@@ -129,8 +132,15 @@ The generated project is ready to run and includes:
129
132
 
130
133
  ## Configure Your Pega Server
131
134
 
132
- Before running the app, edit `sdk-config.json` in the project root with your
133
- environment details:
135
+ The scaffolded project ships with `sdk-config.json` and a generated MCP config file
136
+ both intentionally blank/placeholder — connection details are never collected at
137
+ scaffold time. There are two ways to fill them in:
138
+
139
+ - **AI coding assistant (recommended)** — the first time you ask it to do something
140
+ that needs a live Pega server (e.g. generating components from your app's real
141
+ portals/case types), it asks you for the server URL, application alias, OAuth
142
+ client ID, and MCP rule details in chat, then writes them into both files itself.
143
+ - **Manual** — edit `sdk-config.json` in the project root yourself:
134
144
 
135
145
  ```jsonc
136
146
  {
@@ -153,6 +163,11 @@ environment details:
153
163
  - `appAlias` — the application alias to load
154
164
  - `mashupClientId` / `portalClientId` — OAuth 2.0 client IDs registered in Pega
155
165
 
166
+ You'll also need to fill in the generated MCP config file (`.vscode/mcp.json` for
167
+ Copilot, `.cursor/mcp.json` for Cursor, or `.mcp.json` for Claude Code) — replace
168
+ its `{InfinityURL}`/`{AppAlias}`/`{MCPRuleName}`/`{MCPRuleInsKey}` markers the same
169
+ way if editing manually.
170
+
156
171
  ## Available Scripts
157
172
 
158
173
  Inside the generated project:
package/dist/index.js CHANGED
@@ -1020,16 +1020,9 @@ function generateAiConfigs(targetDir, choices) {
1020
1020
  if (choices.aiTool) {
1021
1021
  applyPlannedFiles(planAiConfigs(aiCommonDir, targetDir, choices.aiTool), "override", { markers: false });
1022
1022
  }
1023
- if (choices.aiTool && choices.serverUrl && choices.appAlias && choices.clientId) {
1024
- const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonDir, {
1025
- aiTool: choices.aiTool,
1026
- serverUrl: choices.serverUrl,
1027
- appAlias: choices.appAlias,
1028
- clientId: choices.clientId,
1029
- mcpRuleName: choices.mcpRuleName,
1030
- mcpRuleInsKey: choices.mcpRuleInsKey
1031
- });
1032
- writeManagedJson(path2.join(targetDir, mcpDestRelPath), mcpConfig, "override", MCP_CONFIG_USER_KEYS);
1023
+ if (choices.aiTool) {
1024
+ const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonDir, choices.aiTool);
1025
+ writeManagedJson(path2.join(targetDir, mcpDestRelPath), mcpConfig, "override");
1033
1026
  if (choices.aiTool === "cursor") {
1034
1027
  ensureGitignoreEntry(targetDir, mcpDestRelPath);
1035
1028
  }
@@ -1046,27 +1039,18 @@ function resolveEmbeddedSource() {
1046
1039
  }
1047
1040
  return { sampleDir, aiCommonSrc };
1048
1041
  }
1049
- var SDK_CONFIG_USER_KEYS = [];
1050
- var MCP_CONFIG_USER_KEYS = [];
1051
1042
  var MCP_CONFIG_FILES = {
1052
1043
  copilot: { source: "copilot.mcp.json", dest: path2.join(".vscode", "mcp.json") },
1053
1044
  cursor: { source: "cursor.mcp.json", dest: path2.join(".cursor", "mcp.json") },
1054
1045
  "claude-code": { source: "claude-code.mcp.json", dest: ".mcp.json" }
1055
1046
  };
1056
- function resolveMcpConfig(aiCommonSrc, choices) {
1057
- const { source, dest } = MCP_CONFIG_FILES[choices.aiTool];
1047
+ function resolveMcpConfig(aiCommonSrc, aiTool) {
1048
+ const { source, dest } = MCP_CONFIG_FILES[aiTool];
1058
1049
  const srcPath = path2.join(aiCommonSrc, "mcp", source);
1059
1050
  if (!fs2.existsSync(srcPath)) {
1060
1051
  throw new Error(`MCP config template not found at ${srcPath}. Ensure the template source is available.`);
1061
1052
  }
1062
- const tokens = {};
1063
- if (choices.serverUrl) tokens["{InfinityURL}"] = choices.serverUrl;
1064
- if (choices.appAlias) tokens["{AppAlias}"] = choices.appAlias;
1065
- if (choices.clientId) tokens["{ClientId}"] = choices.clientId;
1066
- if (choices.mcpRuleName) tokens["{MCPRuleName}"] = choices.mcpRuleName;
1067
- if (choices.mcpRuleInsKey) tokens["{MCPRuleInsKey}"] = choices.mcpRuleInsKey;
1068
- const content = applyTokens(fs2.readFileSync(srcPath, "utf-8"), tokens);
1069
- return { config: JSON.parse(content), destRelPath: dest };
1053
+ return { config: JSON.parse(fs2.readFileSync(srcPath, "utf-8")), destRelPath: dest };
1070
1054
  }
1071
1055
  function ensureGitignoreEntry(targetDir, entry) {
1072
1056
  const gitignorePath = path2.join(targetDir, ".gitignore");
@@ -1075,11 +1059,11 @@ function ensureGitignoreEntry(targetDir, entry) {
1075
1059
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
1076
1060
  fs2.writeFileSync(gitignorePath, existing + separator + entry + "\n");
1077
1061
  }
1078
- function buildSdkConfig(choices) {
1062
+ function buildSdkConfig() {
1079
1063
  return {
1080
1064
  serverConfig: {
1081
- infinityRestServerUrl: choices.serverUrl ?? "",
1082
- appAlias: choices.appAlias ?? "",
1065
+ infinityRestServerUrl: "",
1066
+ appAlias: "",
1083
1067
  sdkContentServerUrl: "",
1084
1068
  appPortal: "",
1085
1069
  appMashupCaseType: "",
@@ -1094,10 +1078,10 @@ function buildSdkConfig(choices) {
1094
1078
  },
1095
1079
  authConfig: {
1096
1080
  authService: "pega",
1097
- mashupClientId: choices.clientId ?? "",
1081
+ mashupClientId: "",
1098
1082
  mashupUserIdentifier: "",
1099
1083
  mashupPassword: "",
1100
- portalClientId: choices.clientId ?? "",
1084
+ portalClientId: "",
1101
1085
  portalClientSecret: "",
1102
1086
  portalGrantType: ""
1103
1087
  }
@@ -1115,12 +1099,12 @@ function collectEmbeddedConflicts(targetDir, choices) {
1115
1099
  }
1116
1100
  }).map((file) => file.destPath);
1117
1101
  const sdkConfigPath = path2.join(resolveStaticDir(targetDir), "sdk-config.json");
1118
- if (isConflict(sdkConfigPath, buildSdkConfig(choices), SDK_CONFIG_USER_KEYS)) {
1102
+ if (isConflict(sdkConfigPath, buildSdkConfig())) {
1119
1103
  conflicts.push(sdkConfigPath);
1120
1104
  }
1121
- const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonSrc, choices);
1105
+ const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonSrc, choices.aiTool);
1122
1106
  const mcpConfigPath = path2.join(targetDir, mcpDestRelPath);
1123
- if (isConflict(mcpConfigPath, mcpConfig, MCP_CONFIG_USER_KEYS)) {
1107
+ if (isConflict(mcpConfigPath, mcpConfig)) {
1124
1108
  conflicts.push(mcpConfigPath);
1125
1109
  }
1126
1110
  return conflicts.map((filePath) => path2.relative(targetDir, filePath));
@@ -1169,13 +1153,13 @@ async function scaffoldEmbedded(choices, targetDir, onProgress) {
1169
1153
  report("Creating SDK configuration\u2026");
1170
1154
  const staticDir = ensureStaticDir(targetDir);
1171
1155
  const sdkConfigPath = path2.join(staticDir, "sdk-config.json");
1172
- writeManagedJson(sdkConfigPath, buildSdkConfig(choices), policy, SDK_CONFIG_USER_KEYS);
1156
+ writeManagedJson(sdkConfigPath, buildSdkConfig(), policy);
1173
1157
  if (staticDir !== targetDir && fs2.existsSync(path2.join(targetDir, "sdk-config.json"))) {
1174
1158
  report(`\u26A0\uFE0F A sdk-config.json also exists at the project root. The active one is ${sdkConfigPath}.`);
1175
1159
  }
1176
1160
  report("Creating MCP server configuration\u2026");
1177
- const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonSrc, choices);
1178
- writeManagedJson(path2.join(targetDir, mcpDestRelPath), mcpConfig, policy, MCP_CONFIG_USER_KEYS);
1161
+ const { config: mcpConfig, destRelPath: mcpDestRelPath } = resolveMcpConfig(aiCommonSrc, aiTool);
1162
+ writeManagedJson(path2.join(targetDir, mcpDestRelPath), mcpConfig, policy);
1179
1163
  if (aiTool === "cursor") {
1180
1164
  ensureGitignoreEntry(targetDir, mcpDestRelPath);
1181
1165
  }
@@ -1250,23 +1234,46 @@ var AI_TOOLS = ["copilot", "cursor", "claude-code"];
1250
1234
  var PROJECT_NAME_RE = /^[a-z0-9@][a-z0-9._/-]*$/;
1251
1235
  var CliValidationError = class extends Error {
1252
1236
  };
1237
+ var AI_TOOL_CHOICES = AI_TOOLS.join("|");
1238
+ var FRAMEWORK_CHOICES = FRAMEWORKS.join("|");
1239
+ var AGENT_QUICKSTART = `
1240
+ AI coding agents (GitHub Copilot, Cursor, Claude Code, or any other): do not answer
1241
+ this CLI's interactive prompts. Run it non-interactively instead \u2014 one command, no
1242
+ prompts, no keystrokes to drive:
1243
+
1244
+ # Inside an existing project (package.json present) \u2014 embedded flow
1245
+ npx --yes @pega/sdk init --ai-tool <${AI_TOOL_CHOICES}>
1246
+
1247
+ # In an empty folder \u2014 new project
1248
+ npx --yes @pega/sdk init --name <project-name> --framework <${FRAMEWORK_CHOICES}> --ai-tool <${AI_TOOL_CHOICES}>
1249
+
1250
+ Mode is auto-detected from whether package.json exists in the current folder \u2014 you
1251
+ do not need to pass --mode.
1252
+
1253
+ --ai-tool is you, the agent running this command \u2014 not something to detect or ask
1254
+ about. GitHub Copilot -> copilot, Cursor -> cursor, Claude Code -> claude-code.
1255
+ Always pass it: it decides which agent config files get written, and a repo can
1256
+ contain another assistant's config left behind by someone else.
1257
+ `.trim();
1253
1258
  var HELP_TEXT = `
1254
- Usage: npx @pega/sdk init [flags]
1259
+ Usage: npx --yes @pega/sdk init [flags]
1255
1260
 
1256
- Run with no flags for the interactive setup. Pass any flag below (or --json)
1257
- to scaffold non-interactively in one shot \u2014 no prompts are shown.
1261
+ Scaffolds a Pega SDK application. Run with no flags for the interactive setup,
1262
+ or pass any flag below to scaffold non-interactively in one shot.
1263
+
1264
+ Mode is auto-detected from the folder you run in: an empty folder creates a new
1265
+ project, a folder that already has a package.json integrates the embedded flow.
1258
1266
 
1259
1267
  Common:
1260
- --json <path|-> Answers file (JSON), matching the shapes documented in
1261
- README.md. Use "-" to read from stdin. Values here win
1262
- over the individual flags below when both are given.
1263
- --mode <new|embedded> Override auto-detection of new-project vs embedded.
1264
- --ai-tool <tool> copilot | cursor | claude-code (embedded: required; new: also accepts "none")
1265
- --help Show this help.
1268
+ --mode <new|embedded> Override the auto-detection described above.
1269
+ --ai-tool <tool> ${AI_TOOLS.join(" | ")} (new: also accepts "none").
1270
+ Adds that assistant's config and skill files.
1271
+ --yes Accept all defaults; enough on its own to skip every prompt.
1272
+ --help, -h Show this help.
1266
1273
 
1267
1274
  New project:
1268
1275
  --name <name> Project directory name (required).
1269
- --framework <fw> react | angular | web-components | react-native (required).
1276
+ --framework <fw> ${FRAMEWORKS.join(" | ")}. Default: react.
1270
1277
  --templates <bool> Include starter components. Default: true.
1271
1278
  --categories <list> Comma-separated: fields,templates,widgets,infra,designSystemExtension.
1272
1279
  Default: all categories when --templates is true.
@@ -1274,20 +1281,17 @@ New project:
1274
1281
  Embedded:
1275
1282
  --conflict-policy <p> merge | override. Default: merge.
1276
1283
 
1277
- Optional, either mode (CI/scripted use only \u2014 a chat agent should not ask a
1278
- user for these; they're configured post-scaffold by editing sdk-config.json /
1279
- the generated MCP config file):
1280
- --server-url <url>
1281
- --app-alias <alias>
1282
- --client-id <id>
1283
- --mcp-rule-name <name>
1284
- --mcp-rule-ins-key <key>
1285
-
1286
1284
  Examples:
1287
- npx @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false --yes
1288
- npx @pega/sdk init --mode embedded --ai-tool copilot --conflict-policy merge
1289
- npx @pega/sdk init --json ./sdk-answers.json
1285
+ npx --yes @pega/sdk init --name my-app --framework react --ai-tool copilot --templates false
1286
+ npx --yes @pega/sdk init --mode embedded --ai-tool cursor --conflict-policy merge
1287
+
1288
+ ------------------------------------------------------------------------------
1289
+ ${AGENT_QUICKSTART}
1290
1290
  `.trim();
1291
+ var HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "-help", "--h", "-?"]);
1292
+ function isHelpRequest(argv) {
1293
+ return argv[0] === "help" || argv.some((arg) => HELP_FLAGS.has(arg));
1294
+ }
1291
1295
  function parseCliArgs(argv) {
1292
1296
  let values;
1293
1297
  try {
@@ -1295,17 +1299,11 @@ function parseCliArgs(argv) {
1295
1299
  args: argv,
1296
1300
  options: {
1297
1301
  help: { type: "boolean" },
1298
- json: { type: "string" },
1299
1302
  yes: { type: "boolean" },
1300
1303
  mode: { type: "string" },
1301
1304
  name: { type: "string" },
1302
1305
  framework: { type: "string" },
1303
1306
  "ai-tool": { type: "string" },
1304
- "server-url": { type: "string" },
1305
- "app-alias": { type: "string" },
1306
- "client-id": { type: "string" },
1307
- "mcp-rule-name": { type: "string" },
1308
- "mcp-rule-ins-key": { type: "string" },
1309
1307
  templates: { type: "string" },
1310
1308
  categories: { type: "string" },
1311
1309
  "conflict-policy": { type: "string" }
@@ -1318,17 +1316,11 @@ function parseCliArgs(argv) {
1318
1316
  }
1319
1317
  return {
1320
1318
  help: Boolean(values.help),
1321
- json: values.json,
1322
1319
  yes: Boolean(values.yes),
1323
1320
  mode: values.mode,
1324
1321
  name: values.name,
1325
1322
  framework: values.framework,
1326
1323
  aiTool: values["ai-tool"],
1327
- serverUrl: values["server-url"],
1328
- appAlias: values["app-alias"],
1329
- clientId: values["client-id"],
1330
- mcpRuleName: values["mcp-rule-name"],
1331
- mcpRuleInsKey: values["mcp-rule-ins-key"],
1332
1324
  templates: values.templates,
1333
1325
  categories: values.categories,
1334
1326
  conflictPolicy: values["conflict-policy"]
@@ -1336,23 +1328,13 @@ function parseCliArgs(argv) {
1336
1328
  }
1337
1329
  function isNonInteractive(args) {
1338
1330
  return Boolean(
1339
- args.help || args.json || args.mode || args.name || args.framework || args.aiTool || args.serverUrl || args.appAlias || args.clientId || args.mcpRuleName || args.mcpRuleInsKey || args.templates || args.categories || args.conflictPolicy || args.yes
1331
+ args.help || args.mode || args.name || args.framework || args.aiTool || args.templates || args.categories || args.conflictPolicy || args.yes
1340
1332
  );
1341
1333
  }
1342
- function readJsonAnswers(source) {
1343
- const raw = source === "-" ? fs3.readFileSync(0, "utf-8") : fs3.readFileSync(path3.resolve(source), "utf-8");
1344
- try {
1345
- return JSON.parse(raw);
1346
- } catch {
1347
- throw new CliValidationError(`--json source "${source}" is not valid JSON.`);
1348
- }
1349
- }
1350
- function pickFrom(answers, key, flagValue) {
1351
- return answers[key] ?? flagValue;
1352
- }
1353
1334
  function assertFramework(value) {
1335
+ if (value === void 0) return "react";
1354
1336
  if (typeof value === "string" && FRAMEWORKS.includes(value)) return value;
1355
- throw new CliValidationError(`--framework is required and must be one of: ${FRAMEWORKS.join(", ")}`);
1337
+ throw new CliValidationError(`--framework must be one of: ${FRAMEWORKS.join(", ")}`);
1356
1338
  }
1357
1339
  function parseAiTool(value) {
1358
1340
  if (value === void 0 || value === "none") return null;
@@ -1362,8 +1344,19 @@ function parseAiTool(value) {
1362
1344
  function parseRequiredAiTool(value) {
1363
1345
  if (typeof value === "string" && AI_TOOLS.includes(value)) return value;
1364
1346
  throw new CliValidationError(
1365
- `--ai-tool is required for the embedded flow and must be one of: ${AI_TOOLS.join(", ")}`
1347
+ `--ai-tool must be one of: ${AI_TOOLS.join(", ")}. Pass the assistant you are running as.`
1348
+ );
1349
+ }
1350
+ var AI_TOOL_MARKERS = {
1351
+ copilot: [".github/copilot-instructions.md", ".github/instructions", ".github/prompts", ".github/skills"],
1352
+ cursor: [".cursor", ".cursorrules"],
1353
+ "claude-code": [".claude", "CLAUDE.md"]
1354
+ };
1355
+ function detectRepoAiTool(cwd) {
1356
+ const matches = Object.keys(AI_TOOL_MARKERS).filter(
1357
+ (tool) => AI_TOOL_MARKERS[tool].some((marker) => fs3.existsSync(path3.join(cwd, marker)))
1366
1358
  );
1359
+ return matches.length === 1 ? matches[0] : void 0;
1367
1360
  }
1368
1361
  function parseBoolean(value, fallback) {
1369
1362
  if (value === void 0) return fallback;
@@ -1384,36 +1377,36 @@ function parseCategories(value) {
1384
1377
  return entries;
1385
1378
  }
1386
1379
  function resolveNonInteractiveUserChoices(args) {
1387
- const answers = args.json ? readJsonAnswers(args.json) : {};
1388
- const projectName = pickFrom(answers, "projectName", args.name);
1380
+ const projectName = args.name;
1389
1381
  if (typeof projectName !== "string" || !projectName) {
1390
- throw new CliValidationError('Project name is required (--name, or "projectName" in --json).');
1382
+ throw new CliValidationError("Project name is required (--name).");
1391
1383
  }
1392
1384
  if (!PROJECT_NAME_RE.test(projectName)) {
1393
1385
  throw new CliValidationError("Invalid project name. Use lowercase letters, numbers, hyphens, and dots.");
1394
1386
  }
1395
- const framework = assertFramework(pickFrom(answers, "framework", args.framework));
1396
- const aiTool = parseAiTool(pickFrom(answers, "aiTool", args.aiTool));
1387
+ const framework = assertFramework(args.framework);
1388
+ const aiTool = parseAiTool(args.aiTool);
1397
1389
  const isReactNative = framework === "react-native";
1398
- const includeTemplates = isReactNative ? true : parseBoolean(pickFrom(answers, "includeTemplates", args.templates), true);
1399
- const componentCategories = isReactNative ? ["fields", "templates", "infra"] : includeTemplates ? parseCategories(pickFrom(answers, "componentCategories", args.categories)) : [];
1390
+ const includeTemplates = isReactNative ? true : parseBoolean(args.templates, true);
1391
+ const componentCategories = isReactNative ? ["fields", "templates", "infra"] : includeTemplates ? parseCategories(args.categories) : [];
1400
1392
  return {
1401
1393
  projectName,
1402
1394
  framework,
1403
1395
  aiTool,
1404
1396
  includeTemplates,
1405
- componentCategories,
1406
- serverUrl: pickFrom(answers, "serverUrl", args.serverUrl),
1407
- appAlias: pickFrom(answers, "appAlias", args.appAlias),
1408
- clientId: pickFrom(answers, "clientId", args.clientId),
1409
- mcpRuleName: pickFrom(answers, "mcpRuleName", args.mcpRuleName),
1410
- mcpRuleInsKey: pickFrom(answers, "mcpRuleInsKey", args.mcpRuleInsKey)
1397
+ componentCategories
1411
1398
  };
1412
1399
  }
1413
- function resolveNonInteractiveEmbeddedChoices(args) {
1414
- const answers = args.json ? readJsonAnswers(args.json) : {};
1415
- const aiTool = parseRequiredAiTool(pickFrom(answers, "aiTool", args.aiTool));
1416
- const conflictPolicyRaw = pickFrom(answers, "conflictPolicy", args.conflictPolicy);
1400
+ function resolveNonInteractiveEmbeddedChoices(args, cwd) {
1401
+ const requestedAiTool = args.aiTool;
1402
+ const inferredAiTool = requestedAiTool === void 0 ? detectRepoAiTool(cwd) : void 0;
1403
+ if (inferredAiTool) {
1404
+ console.warn(
1405
+ `Warning: --ai-tool was not passed; guessed "${inferredAiTool}" from this repo's existing config. If you are a different assistant, re-run with --ai-tool set to yourself.`
1406
+ );
1407
+ }
1408
+ const aiTool = parseRequiredAiTool(requestedAiTool ?? inferredAiTool);
1409
+ const conflictPolicyRaw = args.conflictPolicy;
1417
1410
  let conflictPolicy;
1418
1411
  if (conflictPolicyRaw !== void 0) {
1419
1412
  if (conflictPolicyRaw !== "merge" && conflictPolicyRaw !== "override") {
@@ -1423,11 +1416,6 @@ function resolveNonInteractiveEmbeddedChoices(args) {
1423
1416
  }
1424
1417
  return {
1425
1418
  aiTool,
1426
- serverUrl: pickFrom(answers, "serverUrl", args.serverUrl),
1427
- appAlias: pickFrom(answers, "appAlias", args.appAlias),
1428
- clientId: pickFrom(answers, "clientId", args.clientId),
1429
- mcpRuleName: pickFrom(answers, "mcpRuleName", args.mcpRuleName),
1430
- mcpRuleInsKey: pickFrom(answers, "mcpRuleInsKey", args.mcpRuleInsKey),
1431
1419
  conflictPolicy
1432
1420
  };
1433
1421
  }
@@ -1435,16 +1423,11 @@ function resolveNonInteractiveEmbeddedChoices(args) {
1435
1423
  // src/index.ts
1436
1424
  var require2 = createRequire(import.meta.url);
1437
1425
  var { version: sdkVersion } = require2("../package.json");
1438
- async function runNonInteractive(cwd, rawArgs) {
1439
- const flags = parseCliArgs(rawArgs);
1440
- if (flags.help) {
1441
- console.log(HELP_TEXT);
1442
- return;
1443
- }
1426
+ async function runNonInteractive(cwd, flags) {
1444
1427
  try {
1445
1428
  const mode = flags.mode ?? (detectExistingProject(cwd) ? "embedded" : "new");
1446
1429
  if (mode === "embedded") {
1447
- const choices = resolveNonInteractiveEmbeddedChoices(flags);
1430
+ const choices = resolveNonInteractiveEmbeddedChoices(flags, cwd);
1448
1431
  const conflicts = collectEmbeddedConflicts(cwd, choices);
1449
1432
  if (conflicts.length > 0) {
1450
1433
  console.log(
@@ -1463,22 +1446,40 @@ async function runNonInteractive(cwd, rawArgs) {
1463
1446
  }
1464
1447
  } catch (err) {
1465
1448
  console.error(pc.red(err instanceof CliValidationError || err instanceof Error ? err.message : String(err)));
1449
+ console.error(`
1450
+ ${AGENT_QUICKSTART}
1451
+
1452
+ Full flag reference: npx --yes @pega/sdk init --help`);
1466
1453
  process.exit(1);
1467
1454
  }
1468
1455
  }
1469
1456
  async function main() {
1470
1457
  const args = process.argv.slice(2);
1458
+ if (isHelpRequest(args)) {
1459
+ console.log(HELP_TEXT);
1460
+ return;
1461
+ }
1471
1462
  const command = args[0];
1472
1463
  if (command !== "init") {
1473
1464
  console.error(`Unknown command "${command ?? ""}".
1474
-
1475
- Usage: npx @pega/sdk init`);
1465
+ `);
1466
+ console.error(HELP_TEXT);
1476
1467
  process.exit(1);
1477
1468
  }
1478
1469
  const cwd = process.cwd();
1479
1470
  const rest = args.slice(1);
1480
- if (isNonInteractive(parseCliArgs(rest))) {
1481
- await runNonInteractive(cwd, rest);
1471
+ let flags;
1472
+ try {
1473
+ flags = parseCliArgs(rest);
1474
+ } catch (err) {
1475
+ console.error(pc.red(err instanceof Error ? err.message : String(err)));
1476
+ console.error(`
1477
+ ${HELP_TEXT}`);
1478
+ process.exit(1);
1479
+ return;
1480
+ }
1481
+ if (isNonInteractive(flags)) {
1482
+ await runNonInteractive(cwd, flags);
1482
1483
  return;
1483
1484
  }
1484
1485
  console.clear();
@@ -1631,11 +1632,6 @@ Usage: npx @pega/sdk init`);
1631
1632
  process.exit(0);
1632
1633
  }
1633
1634
  let aiTool = null;
1634
- let serverUrl;
1635
- let appAlias;
1636
- let clientId;
1637
- let mcpRuleName;
1638
- let mcpRuleInsKey;
1639
1635
  if (aiIntegration) {
1640
1636
  const aiToolResult = await p.select({
1641
1637
  message: "Which AI coding tool will you use?",
@@ -1770,12 +1766,7 @@ Usage: npx @pega/sdk init`);
1770
1766
  framework,
1771
1767
  aiTool,
1772
1768
  includeTemplates,
1773
- componentCategories,
1774
- serverUrl,
1775
- appAlias,
1776
- clientId,
1777
- mcpRuleName,
1778
- mcpRuleInsKey
1769
+ componentCategories
1779
1770
  };
1780
1771
  const spinner2 = p.spinner();
1781
1772
  spinner2.start("Scaffolding your project\u2026");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pega/sdk",
3
- "version": "0.0.9",
3
+ "version": "0.0.12",
4
4
  "description": "Scaffold a Pega SDK application project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,6 +53,10 @@ Determine which workflow governs the request, then execute it by reading the ref
53
53
 
54
54
  ⛔ **This step MUST complete before any component generation begins.** Do NOT use hardcoded component lists. Both the case type and the component list MUST come from the Pega server.
55
55
 
56
+ ### Step 0.0 — Ensure the Pega server connection is configured
57
+
58
+ Before executing any data source below, read and execute `{{SKILL_DIR}}/connect-pega-server.md`. It is a no-op when the connection is already configured, and otherwise gathers the missing values from the user and writes them into the MCP config and `sdk-config.json` before this step continues.
59
+
56
60
  ### Step 0.1 — Check for a case type in the prompt
57
61
 
58
62
  Scan the user's message for a **Pega case type class name** (hyphen-separated segments like `DIXL-MediaCo-Work-NewService`). Note whether one was found — it will be needed in Step 0.2.
@@ -61,11 +65,11 @@ Scan the user's message for a **Pega case type class name** (hyphen-separated se
61
65
 
62
66
  Execute the data source **`D_pzGetCaseTypesInApp`**, which returns the case types available in the application. Pass the name exactly as written — this data page is part of the Pega platform and does not vary between installations.
63
67
 
64
- Execute it directly. Do not survey the session's capabilities first — the attempt is itself the check, and a better one: a capability survey can come back empty while the capability is present, stopping a flow that would have worked. Resolve the means of executing a Pega data source by what it does, not by what it is called; tool names and the server's label differ between editors and AI tools. Never look for a configuration file, never assume a path or a server name, and never tell the user which file to edit.
68
+ Execute it directly, by name. An execution tool may instruct you to look up the available data pages before calling it; you may run that lookup to satisfy the tool, but **its output is not authoritative**. The advertised list omits platform data pages that execute perfectly well, so a data page missing from it is not evidence that it is unavailable — and never a reason to stop, to ask the user to expose it through the MCP rule, or to abandon the flow. Only a failed execution attempt counts as a failure. Resolve the means of executing a Pega data source by what it does, not by what it is called; tool names and the server's label differ between editors and AI tools. Never look for a configuration file, never assume a path or a server name, and never tell the user which file to edit.
65
69
 
66
70
  If nothing in this session can execute a Pega data source, stop. Tell the user the Pega MCP server is not available to this conversation, and name both common causes: the server is not running, or it was started after this conversation began — in which case its tools are not bound to this session and a new session is needed. Do not guess at its configuration, and do not fall back to a hardcoded component list.
67
71
 
68
- If the execution runs but fails, report the failure, and if the server's data sources can be listed, list them so the user can see whether the data page is offered at all. Do not substitute a different data page.
72
+ If the execution runs but fails, report the failure and the error the server returned. You may list the server's advertised data sources alongside it as context, but never present that list as evidence the data page is unavailable. Do not substitute a different data page.
69
73
 
70
74
  1. Check `applicationIsConstellationCompatible` in the response. If it is `false`, stop and tell the user the application is not Constellation-compatible and cannot be embedded.
71
75
  2. If the user's message did NOT contain a case type class name, present the returned case types as a numbered pick-list of `name — ID` pairs and ask which one to embed.
@@ -77,7 +81,7 @@ If the execution runs but fails, report the failure, and if the server's data so
77
81
 
78
82
  Execute the data source **`D_pyCaseViewComponentsByType`** for the confirmed case type. Pass the name exactly as written — like the data page above, it is part of the platform. It returns the components the case type uses, each mapped to the views it appears in.
79
83
 
80
- Pass the confirmed case type **ID** — the fully qualified class name, not the display label. Supply only the parameters the execution tool declares, taking their names from the tool itself; do not add parameters it does not declare, and do not invent values for ones you cannot resolve.
84
+ Pass the confirmed case type **ID** — the fully qualified class name, not the display label — as the **`CaseType`** parameter. That is the only parameter this data page takes: do not add others, and do not invent values for ones you cannot resolve.
81
85
 
82
86
  Reduce that response to a generatable list by applying the following in order. Points 2 and 3 are independent rules that remove components for unrelated reasons — keep those reasons distinct when reporting the outcome rather than presenting one merged list of removals.
83
87
 
@@ -153,7 +157,7 @@ If the user named a launch point but no render target, the skill infers it from
153
157
  | Situation | Response |
154
158
  | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
155
159
  | A Pega data source cannot be executed in this session | Stop. Report that the Pega MCP server is not available to this conversation — either it is not running, or it was started after the conversation began and its tools are not bound to this session, which a new session fixes. Do not name a configuration file, a path, or a server label, and do not fall back to a hardcoded component list |
156
- | A required data page is not among the data sources offered | Report which data sources the server did offer, then stop. Do not substitute a different data page and do not fall back to a hardcoded list |
160
+ | A required data page is not among the data sources offered | Not a failure state. The advertised list omits platform data pages that execute correctly — execute it by name anyway. Only an actual execution error is a failure, and only then report what the server offered |
157
161
  | `D_pzGetCaseTypesInApp` returns no case types | The operator has no creatable case types. Ask the user to verify the application alias and the operator's permissions |
158
162
  | `D_pyCaseViewComponentsByType` returns no components | Report the case type that was queried and ask the user to confirm it has views configured in its lifecycle. Do not fall back to a hardcoded list |
159
163
  | The starting-fields capability is unavailable or returns nothing | Record that no property names were confirmed and continue. The wiring step seeds nothing and reports the gap — it must not infer names from the customer's domain model |
@@ -47,7 +47,7 @@ Pega SDK packages (`.tgz` files) have been added to the project root and referen
47
47
 
48
48
  ### SDK Configuration
49
49
 
50
- `sdk-config.json` contains connection details for the Pega server. The customer must fill in their server URL, app alias, OAuth client ID, and case type.
50
+ `sdk-config.json` contains connection details for the Pega server. The AI assistant gathers the server URL, app alias, OAuth client ID, and (when the MCP configuration requires one) a client secret from the user in chat the first time a workflow needs them, and fills them in automatically — see `{{SKILL_DIR}}/connect-pega-server.md`. The case type is resolved from the server during the embedded flow itself.
51
51
 
52
52
  ---
53
53
 
@@ -8,6 +8,10 @@ You are assisting developers in a reference application that demonstrates the Pe
8
8
 
9
9
  This is a **reference implementation** — not a framework. The UI components (fields, templates, widgets) are intentionally simple sample implementations meant to be swapped out. The SDK wiring (bootstrap, PConnect bridge, component registry) is reusable infrastructure that should NOT be modified.
10
10
 
11
+ ### SDK Configuration
12
+
13
+ `sdk-config.json` and the generated MCP config file are scaffolded blank/placeholder — connection details are never collected at scaffold time. The AI assistant gathers the server URL, app alias, OAuth client ID, and MCP rule details from the user in chat the first time a workflow needs a live Pega server (e.g. resolving portals/components in Section 15), and fills them into both files automatically — see `{{SKILL_DIR}}/connect-pega-server.md`.
14
+
11
15
  ---
12
16
 
13
17
  ## 2. What Is Replaceable vs. Infrastructure
@@ -4,7 +4,7 @@
4
4
  "url": "{InfinityURL}/app/{AppAlias}/api/service/v1/mcp/{MCPRuleInsKey}",
5
5
  "auth": {
6
6
  "clientId": "{ClientId}",
7
- "clientSecret": "<YOUR_CLIENT_SECRET>"
7
+ "clientSecret": "{ClientSecret}"
8
8
  }
9
9
  }
10
10
  }
@@ -55,21 +55,26 @@ Once design system, base folder, and type scope are resolved, continue to Step 0
55
55
 
56
56
  ⛔ **This step MUST complete before anything is read, installed, or written.** A portal renders a specific set of components, and generating the full catalogue produces components the portal will never mount. Do NOT use a hardcoded component list, and do NOT infer one from the project's existing folders.
57
57
 
58
+ ### 0.5.0 — Ensure the Pega server connection is configured
59
+
60
+ Before executing any data source below, read and execute `{{SKILL_DIR}}/connect-pega-server.md`. It is a no-op when the connection is already configured, and otherwise gathers the missing values from the user and writes them into the MCP config and `sdk-config.json` before this step continues.
61
+
58
62
  ### 0.5.1 — Resolve the portal
59
63
 
60
64
  Execute the data source **`D_pyAvailablePortals`**, which returns the portals available in the application. Pass the name exactly as written — this data page is part of the Pega platform and does not vary between installations.
61
65
 
62
- Execute it directly. Do not survey the session's capabilities first — the attempt is itself the check, and a better one: a capability survey can come back empty while the capability is present, stopping a flow that would have worked. Resolve the means of executing a Pega data source by what it does, not by what it is called; tool names and the server's label differ between editors and AI tools. Never look for a configuration file, never assume a path or a server name, and never tell the user which file to edit.
66
+ Execute it directly, by name. An execution tool may instruct you to look up the available data pages before calling it; you may run that lookup to satisfy the tool, but **its output is not authoritative**. The advertised list omits platform data pages that execute perfectly well, so a data page missing from it is not evidence that it is unavailable — and never a reason to stop, to ask the user to expose it through the MCP rule, or to abandon the flow. Only a failed execution attempt counts as a failure. Resolve the means of executing a Pega data source by what it does, not by what it is called; tool names and the server's label differ between editors and AI tools. Never look for a configuration file, never assume a path or a server name, and never tell the user which file to edit.
63
67
 
64
68
  If nothing in this session can execute a Pega data source, stop. Tell the user the Pega MCP server is not available to this conversation, and name both common causes: the server is not running, or it was started after this conversation began — in which case its tools are not bound to this session and a new session is needed. Do not guess at its configuration, and do not fall back to generating the full component catalogue.
65
69
 
66
- If the execution runs but fails, report the failure, and if the server's data sources can be listed, list them so the user can see whether the data page is offered at all. Do not substitute a different data page.
70
+ If the execution runs but fails, report the failure and the error the server returned. You may list the server's advertised data sources alongside it as context, but never present that list as evidence the data page is unavailable. Do not substitute a different data page.
67
71
 
68
72
  The response's `pxResults` array holds one entry per portal. Then:
69
73
 
70
74
  1. If the user's message did NOT name a portal, present the returned portals as a numbered pick-list and ask which one to generate for.
71
75
  2. If the user's message DID name one, confirm it appears in the response. If it does not, show what the server returned and ask the user to choose from that list. Never proceed on an unverified portal name.
72
76
  3. Record the confirmed entry's **`pyPortalLayout`** value. This — not the display label shown to the user — is what Steps 0.5.3 and 0.5.4 pass to the server as `PortalName`; carry it forward exactly as the data page returned it.
77
+ 4. Write the confirmed `pyPortalLayout` value into `sdk-config.json` field `serverConfig.appPortal`.
73
78
 
74
79
  ### 0.5.2 — Ask what to convert
75
80
 
@@ -83,6 +88,8 @@ Ask the user which parts of the portal to convert: **Landing pages**, **Cases**,
83
88
 
84
89
  Execute the data source **`D_pyPortalPages`** with `PortalName` set to the `pyPortalLayout` value recorded in Step 0.5.1. It returns the landing pages configured in that portal, in a `pxResults` array whose entries carry `pxObjClass`, `pyRuleName`, and `pyClassName`.
85
90
 
91
+ Execute it — and every data page this step dispatches to — by name. Step 0.5.1's rule still applies: absence from an availability lookup is not evidence a data page is unavailable, and never a reason to stop.
92
+
86
93
  If `pxResults` is empty, tell the user this portal has no landing pages and, if Cases was not already selected, offer to fall back to it. If Cases was also not selected or also comes back empty, stop — there is nothing to generate.
87
94
 
88
95
  Present the returned pages as a **checkbox list** (label each with its `pyRuleName`, and show its `pxObjClass` alongside so the user can tell a view from a landing page) and let the user select one or more. This is a gate.
@@ -110,17 +117,59 @@ Do not supply `ApplicationName` or `ApplicationVersion` to `D_pyLandingPageCompo
110
117
 
111
118
  Execute the data source **`D_pyPortalCaseTypes`** with `PortalName` set to the `pyPortalLayout` value recorded in Step 0.5.1. It returns the case types configured in that portal, in a `pxResults` array whose entries carry `pyClassName`.
112
119
 
120
+ Execute it — and `D_pyCaseViewComponentsByType` below — by name. Step 0.5.1's rule still applies: absence from an availability lookup is not evidence a data page is unavailable, and never a reason to stop.
121
+
113
122
  If `pxResults` is empty, tell the user this portal has no case types and, if Landing pages was not already selected, offer to fall back to it. If Landing pages was also not selected or also comes back empty, stop — there is nothing to generate.
114
123
 
115
124
  Present the returned case types as a **checkbox list** and let the user select one or more. This is a gate.
116
125
 
117
126
  For **each selected case type**, execute **`D_pyCaseViewComponentsByType`** with `CaseType` set to its `pyClassName`. Make one call per selected case type. Discard malformed entries from each response, then union the results across all selected case types and drop duplicates. Keep a note of which case type(s) contributed each component.
118
127
 
119
- ### 0.5.5 — Merge, present, and ask which components to generate
128
+ ### 0.5.5 — Resolve names, present, and ask which components to generate
120
129
 
121
130
  Union whatever Step 0.5.3 and Step 0.5.4 produced (only one ran unless the user chose Both) and drop duplicates across the two.
122
131
 
123
- Read `{{PROMPT_DIR}}/component-manifest.json` and use its `components` and `aliases` entries to group every resulting name under its type — `field`, `template`, `widget`, `designSystemExtension`, or `infra`. List any name that matches nothing in the manifest under **Unrecognized**, carried through exactly as the server returned it, rather than dropping it or guessing a canonical name for it.
132
+ Every component you generate must correspond to one canonical component listed in `{{PROMPT_DIR}}/component-manifest.json`. Pega defines a fixed set of components, and the manifest is the complete list of them. Read it once. It contains three things you need here:
133
+
134
+ - `components` — the canonical names, grouped by type (`field`, `template`, `widget`, `designSystemExtension`, `infra`)
135
+ - `aliases` — known non-canonical names and the canonical name each one maps to
136
+ - `behaviorClasses` — a fixed list of behaviour descriptions, each mapping to one or more canonical names
137
+
138
+ For each name the server returned, work through the following stages in order. Stop at the first stage that produces an answer. These names come from Pega's own view metadata, so most are already canonical and settle at Stage 1 — the later stages exist for the ones that do not.
139
+
140
+ #### Stage 1 — Normalized name match
141
+
142
+ 1. Take the returned name and make a normalized form: convert it to lowercase, then remove all spaces, hyphens, and underscores, then remove a trailing `field`, `component`, `input`, or `control` if one is present.
143
+ 2. Build the same normalized form for every canonical name in `components`.
144
+ 3. If exactly one canonical name has the same normalized form, that is the answer.
145
+
146
+ #### Stage 2 — Alias lookup
147
+
148
+ 1. Look up the returned name in the `aliases` object. Compare using the normalized form from Stage 1 so that casing and spacing do not matter.
149
+ 2. If the name is present, the value it maps to is the answer.
150
+
151
+ #### Stage 3 — Behaviour class match
152
+
153
+ Do not search for a "similar sounding" name here. Follow these three sub-steps exactly.
154
+
155
+ 1. Decide what the component does for the end user, and choose **exactly one** entry from the `behaviorClasses` list that describes that behaviour. You must pick one of the listed classes. If you cannot honestly place the component in any of them, go to Stage 4.
156
+ 2. Read the canonical names that class maps to. If the class maps to exactly one name, that is the candidate.
157
+ 3. Read the candidate's skill file — `{{SKILL_DIR}}/{type}-skills/<CanonicalName>.md`, where `{type}` is `field`, `template`, `widget`, `design-system-extension`, or `infra` — and confirm its behavioural contract covers what the component does. If it clearly does not, go to Stage 4.
158
+
159
+ If the class maps to more than one canonical name, choose between them using the page or case type the name came from and the surrounding context. If the context does not make the choice obvious, go to Stage 4.
160
+
161
+ #### Stage 4 — Ask the user
162
+
163
+ Tell the user the name could not be resolved to a Pega component, list the two or three canonical names you considered most likely, and ask which one they want — or whether to leave it out. Do not guess, and do not proceed with that component until they answer. Collect every unresolved name into a **single** question rather than asking once per name; a portal can return a long list, and one question per name is not a usable gate.
164
+
165
+ #### Rules for this step
166
+
167
+ - This process always ends in one of two ways: a single canonical name, or a question to the user. It never ends with you inventing a component.
168
+ - Once a canonical name is chosen, use it everywhere afterwards: the type grouping, the skill file you read, the generated folder name, and the registry key. The name the server returned is only used when reporting the substitution back to the user.
169
+ - Record the pairing of returned name and canonical name. You will report it in the Step 9 summary.
170
+ - If two different returned names resolve to the same canonical name, generate that component once.
171
+
172
+ Group the resolved canonical names by type — `field`, `template`, `widget`, `designSystemExtension`, or `infra`.
124
173
 
125
174
  Present the grouped list — noting, for any component contributed by more than one page or case type, which ones — and ask which components to generate as a **checkbox list**, offering selecting all of them as an explicit option. Where a returned component is one the SDK provides and this prompt never generates — anything named in Step 8's never-generate list, or a structural wrapper such as `View`, `Region`, or `FlowContainer` — keep it visible in the list but mark it as not generatable and say why, so the user can see it's used without expecting a folder for it.
126
175
 
@@ -539,3 +588,4 @@ Report a final summary:
539
588
  - `FieldValueList`, if it was added automatically under Step 0.5.6
540
589
  - All entries added to the component map
541
590
  - Any components skipped (if "skip existing" was chosen in Step 3, or because they are SDK-provided infrastructure)
591
+ - Any name the server returned that resolved to a **different** canonical name, with the stage that produced the match (normalized name, alias, behaviour class, or the user's choice). Say it in your reply as well — the user must never discover a substitution only by reading the generated files
@@ -0,0 +1,54 @@
1
+ ---
2
+ ai-description: Ensure the Pega server connection (MCP config + sdk-config.json) is configured before any workflow executes a live Pega data source. Not a standalone flow — invoked as a prerequisite step by other skills/prompts.
3
+ ---
4
+
5
+ # Pega Server Connection Gate
6
+
7
+ Run this **before** the first Pega data source is executed in a chat session. It ensures the MCP server this session talks to, and `sdk-config.json`, both carry real connection values instead of the placeholder markers the project scaffolding step leaves behind. Scaffolding never asks for these — this gate is where they are actually collected, the first time a workflow needs them.
8
+
9
+ ## 1. Locate the MCP config file
10
+
11
+ Find the file this session's AI tool reads its Pega MCP server list from. Do not assume a fixed name or path, and do not ask the user which file to edit — discover it by what it contains: a server entry whose key starts with `pega-`, holding a `url` that points at an `/api/service/v1/mcp/` endpoint. Search the project for a file matching that shape.
12
+
13
+ ## 2. Check whether it is already configured
14
+
15
+ Read that file and `sdk-config.json`. Treat the connection as **already configured** only when none of the following markers remain, literally, in either file: `{InfinityURL}`, `{AppAlias}`, `{MCPRuleName}`, `{MCPRuleInsKey}`, `{ClientId}`, `{ClientSecret}`, and `sdk-config.json`'s `serverConfig.infinityRestServerUrl` / `serverConfig.appAlias` / `authConfig.mashupClientId` are non-empty.
16
+
17
+ If already configured, skip the rest of this gate and continue with the workflow that invoked it.
18
+
19
+ ## 3. Ask only for what is still missing
20
+
21
+ Check each value independently — ask only for the ones still a marker or blank, never re-ask for a value already present:
22
+
23
+ - Pega server URL
24
+ - Application alias
25
+ - OAuth client ID
26
+ - MCP rule name
27
+ - MCP rule instance key
28
+ - **Client secret — only if the MCP config's server entry has an `auth` block containing a `clientSecret` field.** Some MCP configurations authenticate this way and some do not; let the file's own structure decide whether to ask, rather than deciding by which AI tool is running. If there is no `auth` block, do not ask for a client secret at all.
29
+
30
+ Ask these as a single batch of questions, not one at a time.
31
+
32
+ ## 4. Write the values
33
+
34
+ **MCP config file** — replace each marker with the value collected for it, and rename the `pega-{MCPRuleName}` server key using the real rule name:
35
+
36
+ - `{InfinityURL}` → server URL
37
+ - `{AppAlias}` → application alias
38
+ - `{MCPRuleName}` → MCP rule name (in both the server key and the URL path)
39
+ - `{MCPRuleInsKey}` → MCP rule instance key
40
+ - `{ClientId}` → client ID, if the entry has an `auth` block
41
+ - `{ClientSecret}` → client secret, if the `auth` block has that field
42
+
43
+ **`sdk-config.json`** — write into the existing structure, do not create new fields:
44
+
45
+ - `serverConfig.infinityRestServerUrl` → server URL
46
+ - `serverConfig.appAlias` → application alias
47
+ - `authConfig.mashupClientId` → client ID
48
+ - `authConfig.portalClientId` → client ID (same value as `mashupClientId`)
49
+
50
+ Never write the client secret into `sdk-config.json` — it belongs only in the MCP config file's `auth.clientSecret`.
51
+
52
+ ## 5. Tell the user to reconnect
53
+
54
+ Most AI tools read their MCP config once at startup. After writing these values, tell the user the MCP server configuration changed and they need to reload or reconnect it (tool-dependent — e.g. restarting the MCP connection or reloading the window) before their request can proceed, and ask them to retry once reconnected.