@pega/sdk 0.0.8 → 0.0.10
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 +58 -0
- package/dist/index.js +248 -161
- package/package.json +1 -1
- package/templates/react-sdk-reference/.ai-common/agents/embedded-flow-expert.md +4 -0
- package/templates/react-sdk-reference/.ai-common/global/ai-agent-instructions-embedded.md +1 -1
- package/templates/react-sdk-reference/.ai-common/mcp/cursor.mcp.json +1 -1
- package/templates/react-sdk-reference/.ai-common/prompts/generate-all-components.md +19 -4
- package/templates/react-sdk-reference/.ai-common/skills/connect-pega-server.md +54 -0
- package/templates/react-sdk-reference/package.minimal.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,41 @@ fully configured project — build tooling, authentication, runtime bootstrap, a
|
|
|
5
5
|
optional starter components — so you can connect to your Pega server and start
|
|
6
6
|
building right away.
|
|
7
7
|
|
|
8
|
+
## For AI Coding Agents
|
|
9
|
+
|
|
10
|
+
Recognize these as requests to run this CLI, whether the folder is empty or already
|
|
11
|
+
has a project in it:
|
|
12
|
+
|
|
13
|
+
- "Scaffold a new Pega app / project using `@pega/sdk`" → run in an **empty folder**.
|
|
14
|
+
- "Scaffold `@pega/sdk` / add Pega embedded flow into this repo" (or similar,
|
|
15
|
+
said while working inside an existing host application) → run in that
|
|
16
|
+
**existing project's folder**.
|
|
17
|
+
|
|
18
|
+
In both cases, run `npx @pega/sdk init` non-interactively — see
|
|
19
|
+
[Non-Interactive / Scripted Usage](#non-interactive--scripted-usage) below for the
|
|
20
|
+
full flag reference. Gather answers conversationally, then invoke one command; do
|
|
21
|
+
not try to drive the interactive prompts through a terminal.
|
|
22
|
+
|
|
23
|
+
Mode is decided by the folder you run the command in, not something you need to ask
|
|
24
|
+
about or guess:
|
|
25
|
+
|
|
26
|
+
- **Empty folder (no `package.json`)** → new project / full portal mode. Omit
|
|
27
|
+
`--mode`; it defaults to `new`.
|
|
28
|
+
- **Folder already contains a project (`package.json` present)** → embedded mode.
|
|
29
|
+
Omit `--mode`; it defaults to `embedded`. (Pass `--mode` explicitly only to
|
|
30
|
+
override this auto-detection.)
|
|
31
|
+
|
|
32
|
+
| Decide yourself (don't ask the user) | Ask the user (can't be inferred) |
|
|
33
|
+
| ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
34
|
+
| `--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) | |
|
|
36
|
+
| `--framework`, `--name`, `--templates`, `--categories` (infer from the request, or ask only if genuinely ambiguous) | |
|
|
37
|
+
|
|
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)).
|
|
42
|
+
|
|
8
43
|
## Requirements
|
|
9
44
|
|
|
10
45
|
- **Node.js** 20 or later
|
|
@@ -58,6 +93,29 @@ Running `init` walks you through an interactive setup:
|
|
|
58
93
|
If you skip starter components in a React project, the scaffold uses a minimal
|
|
59
94
|
dependency manifest for a lighter baseline app.
|
|
60
95
|
|
|
96
|
+
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.
|
|
118
|
+
|
|
61
119
|
## What You Get
|
|
62
120
|
|
|
63
121
|
The generated project is ready to run and includes:
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import pc from "picocolors";
|
|
6
|
-
import
|
|
6
|
+
import path4 from "path";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
|
|
9
9
|
// src/scaffold.ts
|
|
@@ -1046,14 +1046,8 @@ function resolveEmbeddedSource() {
|
|
|
1046
1046
|
}
|
|
1047
1047
|
return { sampleDir, aiCommonSrc };
|
|
1048
1048
|
}
|
|
1049
|
-
var SDK_CONFIG_USER_KEYS = [
|
|
1050
|
-
|
|
1051
|
-
"serverConfig.appAlias",
|
|
1052
|
-
"authConfig.mashupClientId",
|
|
1053
|
-
"authConfig.mashupUserIdentifier",
|
|
1054
|
-
"authConfig.portalClientId"
|
|
1055
|
-
];
|
|
1056
|
-
var MCP_CONFIG_USER_KEYS = ["url", "clientId"];
|
|
1049
|
+
var SDK_CONFIG_USER_KEYS = [];
|
|
1050
|
+
var MCP_CONFIG_USER_KEYS = [];
|
|
1057
1051
|
var MCP_CONFIG_FILES = {
|
|
1058
1052
|
copilot: { source: "copilot.mcp.json", dest: path2.join(".vscode", "mcp.json") },
|
|
1059
1053
|
cursor: { source: "cursor.mcp.json", dest: path2.join(".cursor", "mcp.json") },
|
|
@@ -1065,11 +1059,10 @@ function resolveMcpConfig(aiCommonSrc, choices) {
|
|
|
1065
1059
|
if (!fs2.existsSync(srcPath)) {
|
|
1066
1060
|
throw new Error(`MCP config template not found at ${srcPath}. Ensure the template source is available.`);
|
|
1067
1061
|
}
|
|
1068
|
-
const tokens = {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
};
|
|
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;
|
|
1073
1066
|
if (choices.mcpRuleName) tokens["{MCPRuleName}"] = choices.mcpRuleName;
|
|
1074
1067
|
if (choices.mcpRuleInsKey) tokens["{MCPRuleInsKey}"] = choices.mcpRuleInsKey;
|
|
1075
1068
|
const content = applyTokens(fs2.readFileSync(srcPath, "utf-8"), tokens);
|
|
@@ -1085,8 +1078,8 @@ function ensureGitignoreEntry(targetDir, entry) {
|
|
|
1085
1078
|
function buildSdkConfig(choices) {
|
|
1086
1079
|
return {
|
|
1087
1080
|
serverConfig: {
|
|
1088
|
-
infinityRestServerUrl: choices.serverUrl,
|
|
1089
|
-
appAlias: choices.appAlias,
|
|
1081
|
+
infinityRestServerUrl: choices.serverUrl ?? "",
|
|
1082
|
+
appAlias: choices.appAlias ?? "",
|
|
1090
1083
|
sdkContentServerUrl: "",
|
|
1091
1084
|
appPortal: "",
|
|
1092
1085
|
appMashupCaseType: "",
|
|
@@ -1101,10 +1094,10 @@ function buildSdkConfig(choices) {
|
|
|
1101
1094
|
},
|
|
1102
1095
|
authConfig: {
|
|
1103
1096
|
authService: "pega",
|
|
1104
|
-
mashupClientId: choices.clientId,
|
|
1105
|
-
mashupUserIdentifier:
|
|
1097
|
+
mashupClientId: choices.clientId ?? "",
|
|
1098
|
+
mashupUserIdentifier: "",
|
|
1106
1099
|
mashupPassword: "",
|
|
1107
|
-
portalClientId: choices.clientId,
|
|
1100
|
+
portalClientId: choices.clientId ?? "",
|
|
1108
1101
|
portalClientSecret: "",
|
|
1109
1102
|
portalGrantType: ""
|
|
1110
1103
|
}
|
|
@@ -1247,22 +1240,250 @@ function copyDir(src, dest) {
|
|
|
1247
1240
|
}
|
|
1248
1241
|
}
|
|
1249
1242
|
|
|
1243
|
+
// src/cli-args.ts
|
|
1244
|
+
import { parseArgs } from "util";
|
|
1245
|
+
import fs3 from "fs";
|
|
1246
|
+
import path3 from "path";
|
|
1247
|
+
var ALL_CATEGORIES = ["fields", "templates", "widgets", "infra", "designSystemExtension"];
|
|
1248
|
+
var FRAMEWORKS = ["react", "angular", "web-components", "react-native"];
|
|
1249
|
+
var AI_TOOLS = ["copilot", "cursor", "claude-code"];
|
|
1250
|
+
var PROJECT_NAME_RE = /^[a-z0-9@][a-z0-9._/-]*$/;
|
|
1251
|
+
var CliValidationError = class extends Error {
|
|
1252
|
+
};
|
|
1253
|
+
var HELP_TEXT = `
|
|
1254
|
+
Usage: npx @pega/sdk init [flags]
|
|
1255
|
+
|
|
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.
|
|
1258
|
+
|
|
1259
|
+
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.
|
|
1266
|
+
|
|
1267
|
+
New project:
|
|
1268
|
+
--name <name> Project directory name (required).
|
|
1269
|
+
--framework <fw> react | angular | web-components | react-native (required).
|
|
1270
|
+
--templates <bool> Include starter components. Default: true.
|
|
1271
|
+
--categories <list> Comma-separated: fields,templates,widgets,infra,designSystemExtension.
|
|
1272
|
+
Default: all categories when --templates is true.
|
|
1273
|
+
|
|
1274
|
+
Embedded:
|
|
1275
|
+
--conflict-policy <p> merge | override. Default: merge.
|
|
1276
|
+
|
|
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
|
+
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
|
|
1290
|
+
`.trim();
|
|
1291
|
+
function parseCliArgs(argv) {
|
|
1292
|
+
let values;
|
|
1293
|
+
try {
|
|
1294
|
+
({ values } = parseArgs({
|
|
1295
|
+
args: argv,
|
|
1296
|
+
options: {
|
|
1297
|
+
help: { type: "boolean" },
|
|
1298
|
+
json: { type: "string" },
|
|
1299
|
+
yes: { type: "boolean" },
|
|
1300
|
+
mode: { type: "string" },
|
|
1301
|
+
name: { type: "string" },
|
|
1302
|
+
framework: { type: "string" },
|
|
1303
|
+
"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
|
+
templates: { type: "string" },
|
|
1310
|
+
categories: { type: "string" },
|
|
1311
|
+
"conflict-policy": { type: "string" }
|
|
1312
|
+
},
|
|
1313
|
+
strict: true,
|
|
1314
|
+
allowPositionals: true
|
|
1315
|
+
}));
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
throw new CliValidationError(err instanceof Error ? err.message : String(err));
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
help: Boolean(values.help),
|
|
1321
|
+
json: values.json,
|
|
1322
|
+
yes: Boolean(values.yes),
|
|
1323
|
+
mode: values.mode,
|
|
1324
|
+
name: values.name,
|
|
1325
|
+
framework: values.framework,
|
|
1326
|
+
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
|
+
templates: values.templates,
|
|
1333
|
+
categories: values.categories,
|
|
1334
|
+
conflictPolicy: values["conflict-policy"]
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function isNonInteractive(args) {
|
|
1338
|
+
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
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
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
|
+
function assertFramework(value) {
|
|
1354
|
+
if (typeof value === "string" && FRAMEWORKS.includes(value)) return value;
|
|
1355
|
+
throw new CliValidationError(`--framework is required and must be one of: ${FRAMEWORKS.join(", ")}`);
|
|
1356
|
+
}
|
|
1357
|
+
function parseAiTool(value) {
|
|
1358
|
+
if (value === void 0 || value === "none") return null;
|
|
1359
|
+
if (typeof value === "string" && AI_TOOLS.includes(value)) return value;
|
|
1360
|
+
throw new CliValidationError(`--ai-tool must be one of: ${AI_TOOLS.join(", ")}, or "none"`);
|
|
1361
|
+
}
|
|
1362
|
+
function parseRequiredAiTool(value) {
|
|
1363
|
+
if (typeof value === "string" && AI_TOOLS.includes(value)) return value;
|
|
1364
|
+
throw new CliValidationError(
|
|
1365
|
+
`--ai-tool is required for the embedded flow and must be one of: ${AI_TOOLS.join(", ")}`
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
function parseBoolean(value, fallback) {
|
|
1369
|
+
if (value === void 0) return fallback;
|
|
1370
|
+
if (value === true || value === "true") return true;
|
|
1371
|
+
if (value === false || value === "false") return false;
|
|
1372
|
+
throw new CliValidationError(`Expected "true" or "false", got "${String(value)}"`);
|
|
1373
|
+
}
|
|
1374
|
+
function parseCategories(value) {
|
|
1375
|
+
if (value === void 0) return ALL_CATEGORIES;
|
|
1376
|
+
if (Array.isArray(value)) return value;
|
|
1377
|
+
const entries = String(value).split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1378
|
+
const valid = new Set(ALL_CATEGORIES);
|
|
1379
|
+
for (const entry of entries) {
|
|
1380
|
+
if (!valid.has(entry)) {
|
|
1381
|
+
throw new CliValidationError(`--categories entry "${entry}" is not one of: ${ALL_CATEGORIES.join(", ")}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return entries;
|
|
1385
|
+
}
|
|
1386
|
+
function resolveNonInteractiveUserChoices(args) {
|
|
1387
|
+
const answers = args.json ? readJsonAnswers(args.json) : {};
|
|
1388
|
+
const projectName = pickFrom(answers, "projectName", args.name);
|
|
1389
|
+
if (typeof projectName !== "string" || !projectName) {
|
|
1390
|
+
throw new CliValidationError('Project name is required (--name, or "projectName" in --json).');
|
|
1391
|
+
}
|
|
1392
|
+
if (!PROJECT_NAME_RE.test(projectName)) {
|
|
1393
|
+
throw new CliValidationError("Invalid project name. Use lowercase letters, numbers, hyphens, and dots.");
|
|
1394
|
+
}
|
|
1395
|
+
const framework = assertFramework(pickFrom(answers, "framework", args.framework));
|
|
1396
|
+
const aiTool = parseAiTool(pickFrom(answers, "aiTool", args.aiTool));
|
|
1397
|
+
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)) : [];
|
|
1400
|
+
return {
|
|
1401
|
+
projectName,
|
|
1402
|
+
framework,
|
|
1403
|
+
aiTool,
|
|
1404
|
+
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)
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
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);
|
|
1417
|
+
let conflictPolicy;
|
|
1418
|
+
if (conflictPolicyRaw !== void 0) {
|
|
1419
|
+
if (conflictPolicyRaw !== "merge" && conflictPolicyRaw !== "override") {
|
|
1420
|
+
throw new CliValidationError('--conflict-policy must be "merge" or "override"');
|
|
1421
|
+
}
|
|
1422
|
+
conflictPolicy = conflictPolicyRaw;
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
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
|
+
conflictPolicy
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1250
1435
|
// src/index.ts
|
|
1251
1436
|
var require2 = createRequire(import.meta.url);
|
|
1252
1437
|
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
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
const mode = flags.mode ?? (detectExistingProject(cwd) ? "embedded" : "new");
|
|
1446
|
+
if (mode === "embedded") {
|
|
1447
|
+
const choices = resolveNonInteractiveEmbeddedChoices(flags);
|
|
1448
|
+
const conflicts = collectEmbeddedConflicts(cwd, choices);
|
|
1449
|
+
if (conflicts.length > 0) {
|
|
1450
|
+
console.log(
|
|
1451
|
+
pc.dim(
|
|
1452
|
+
`Found ${conflicts.length} existing file(s); using conflict policy "${choices.conflictPolicy ?? "merge"}".`
|
|
1453
|
+
)
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
await scaffoldEmbedded(choices, cwd, (message) => console.log(pc.dim(message)));
|
|
1457
|
+
console.log(pc.green("Embedded flow ready!"));
|
|
1458
|
+
} else {
|
|
1459
|
+
const choices = resolveNonInteractiveUserChoices(flags);
|
|
1460
|
+
const targetDir = path4.resolve(cwd, choices.projectName);
|
|
1461
|
+
await scaffold(choices, targetDir, (message) => console.log(pc.dim(message)));
|
|
1462
|
+
console.log(pc.green("Project scaffolded successfully!"));
|
|
1463
|
+
}
|
|
1464
|
+
} catch (err) {
|
|
1465
|
+
console.error(pc.red(err instanceof CliValidationError || err instanceof Error ? err.message : String(err)));
|
|
1466
|
+
process.exit(1);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1253
1469
|
async function main() {
|
|
1254
|
-
console.clear();
|
|
1255
|
-
p.intro(pc.bgCyan(pc.black(" @pega/sdk ")));
|
|
1256
|
-
p.log.info(`Using @pega/sdk v${sdkVersion}`);
|
|
1257
1470
|
const args = process.argv.slice(2);
|
|
1258
1471
|
const command = args[0];
|
|
1259
1472
|
if (command !== "init") {
|
|
1260
|
-
|
|
1473
|
+
console.error(`Unknown command "${command ?? ""}".
|
|
1261
1474
|
|
|
1262
|
-
Usage:
|
|
1475
|
+
Usage: npx @pega/sdk init`);
|
|
1263
1476
|
process.exit(1);
|
|
1264
1477
|
}
|
|
1265
1478
|
const cwd = process.cwd();
|
|
1479
|
+
const rest = args.slice(1);
|
|
1480
|
+
if (isNonInteractive(parseCliArgs(rest))) {
|
|
1481
|
+
await runNonInteractive(cwd, rest);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
console.clear();
|
|
1485
|
+
p.intro(pc.bgCyan(pc.black(" @pega/sdk ")));
|
|
1486
|
+
p.log.info(`Using @pega/sdk v${sdkVersion}`);
|
|
1266
1487
|
const projectMode = detectExistingProject(cwd) ? "embedded" : "new";
|
|
1267
1488
|
if (projectMode === "embedded") {
|
|
1268
1489
|
const continueEmbedded = await p.confirm({
|
|
@@ -1303,84 +1524,12 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1303
1524
|
p.cancel("Operation cancelled.");
|
|
1304
1525
|
process.exit(0);
|
|
1305
1526
|
}
|
|
1306
|
-
p.log.info(pc.dim("\nNow let's connect to your Pega server."));
|
|
1307
|
-
const serverUrlResult = await p.text({
|
|
1308
|
-
message: "What is your Pega server URL?",
|
|
1309
|
-
placeholder: "https://your-pega-server.com/prweb",
|
|
1310
|
-
validate(value) {
|
|
1311
|
-
if (!value) return "Server URL is required.";
|
|
1312
|
-
if (!value.startsWith("http")) return "Must be a valid URL starting with http:// or https://";
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
if (p.isCancel(serverUrlResult)) {
|
|
1316
|
-
p.cancel("Operation cancelled.");
|
|
1317
|
-
process.exit(0);
|
|
1318
|
-
}
|
|
1319
|
-
const serverUrl2 = serverUrlResult.replace(/\/+$/, "");
|
|
1320
|
-
const appAlias2 = await p.text({
|
|
1321
|
-
message: "What is your Pega application alias?",
|
|
1322
|
-
placeholder: "MyApp",
|
|
1323
|
-
validate(value) {
|
|
1324
|
-
if (!value) return "Application alias is required.";
|
|
1325
|
-
}
|
|
1326
|
-
});
|
|
1327
|
-
if (p.isCancel(appAlias2)) {
|
|
1328
|
-
p.cancel("Operation cancelled.");
|
|
1329
|
-
process.exit(0);
|
|
1330
|
-
}
|
|
1331
|
-
const clientId2 = await p.text({
|
|
1332
|
-
message: "What is the OAuth Client ID for your app?",
|
|
1333
|
-
placeholder: "Your OAuth 2.0 client identifier",
|
|
1334
|
-
validate(value) {
|
|
1335
|
-
if (!value) return "Client ID is required for authentication.";
|
|
1336
|
-
}
|
|
1337
|
-
});
|
|
1338
|
-
if (p.isCancel(clientId2)) {
|
|
1339
|
-
p.cancel("Operation cancelled.");
|
|
1340
|
-
process.exit(0);
|
|
1341
|
-
}
|
|
1342
|
-
const userIdentifier = await p.text({
|
|
1343
|
-
message: "What is the operator ID for the embedded session?",
|
|
1344
|
-
placeholder: "user@yourapp",
|
|
1345
|
-
validate(value) {
|
|
1346
|
-
if (!value) return "Operator ID is required.";
|
|
1347
|
-
}
|
|
1348
|
-
});
|
|
1349
|
-
if (p.isCancel(userIdentifier)) {
|
|
1350
|
-
p.cancel("Operation cancelled.");
|
|
1351
|
-
process.exit(0);
|
|
1352
|
-
}
|
|
1353
|
-
p.log.info(pc.dim("\nIf you've configured an MCP server for this app, we can wire it up too (optional)."));
|
|
1354
|
-
const mcpRuleNameResult = await p.text({
|
|
1355
|
-
message: "What is your Pega MCP rule name? (press Enter to skip)",
|
|
1356
|
-
placeholder: "SDKMCP"
|
|
1357
|
-
});
|
|
1358
|
-
if (p.isCancel(mcpRuleNameResult)) {
|
|
1359
|
-
p.cancel("Operation cancelled.");
|
|
1360
|
-
process.exit(0);
|
|
1361
|
-
}
|
|
1362
|
-
const mcpRuleInsKeyResult = await p.text({
|
|
1363
|
-
message: "What is your Pega MCP rule instance key? (press Enter to skip)",
|
|
1364
|
-
placeholder: "@baseclass!SDKMCP"
|
|
1365
|
-
});
|
|
1366
|
-
if (p.isCancel(mcpRuleInsKeyResult)) {
|
|
1367
|
-
p.cancel("Operation cancelled.");
|
|
1368
|
-
process.exit(0);
|
|
1369
|
-
}
|
|
1370
|
-
const mcpRuleName2 = mcpRuleNameResult || void 0;
|
|
1371
|
-
const mcpRuleInsKey2 = mcpRuleInsKeyResult || void 0;
|
|
1372
1527
|
const embeddedTargetDir = cwd;
|
|
1373
1528
|
p.note(
|
|
1374
1529
|
[
|
|
1375
1530
|
`${pc.bold("Mode")}: Embed Pega flow`,
|
|
1376
1531
|
`${pc.bold("Directory")}: ${embeddedTargetDir}`,
|
|
1377
|
-
`${pc.bold("AI Tool")}: ${embeddedAiTool}
|
|
1378
|
-
`${pc.bold("Server")}: ${serverUrl2}`,
|
|
1379
|
-
`${pc.bold("App Alias")}: ${appAlias2}`,
|
|
1380
|
-
`${pc.bold("Client ID")}: ${clientId2}`,
|
|
1381
|
-
`${pc.bold("Operator")}: ${userIdentifier}`,
|
|
1382
|
-
`${pc.bold("MCP Rule")}: ${mcpRuleName2 ?? pc.dim("(not configured \u2014 markers left in generated file)")}`,
|
|
1383
|
-
`${pc.bold("MCP Ins Key")}: ${mcpRuleInsKey2 ?? pc.dim("(not configured \u2014 markers left in generated file)")}`
|
|
1532
|
+
`${pc.bold("AI Tool")}: ${embeddedAiTool}`
|
|
1384
1533
|
].join("\n"),
|
|
1385
1534
|
"Configuration"
|
|
1386
1535
|
);
|
|
@@ -1393,13 +1542,7 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1393
1542
|
process.exit(0);
|
|
1394
1543
|
}
|
|
1395
1544
|
const embeddedChoices = {
|
|
1396
|
-
aiTool: embeddedAiTool
|
|
1397
|
-
serverUrl: serverUrl2,
|
|
1398
|
-
appAlias: appAlias2,
|
|
1399
|
-
clientId: clientId2,
|
|
1400
|
-
userIdentifier,
|
|
1401
|
-
mcpRuleName: mcpRuleName2,
|
|
1402
|
-
mcpRuleInsKey: mcpRuleInsKey2
|
|
1545
|
+
aiTool: embeddedAiTool
|
|
1403
1546
|
};
|
|
1404
1547
|
let conflicts = [];
|
|
1405
1548
|
try {
|
|
@@ -1519,62 +1662,6 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1519
1662
|
process.exit(0);
|
|
1520
1663
|
}
|
|
1521
1664
|
aiTool = aiToolResult;
|
|
1522
|
-
p.log.info(pc.dim("\nIf you'd like the new project to include an MCP config, we need a few connection details."));
|
|
1523
|
-
const serverUrlResult = await p.text({
|
|
1524
|
-
message: "What is your Pega server URL?",
|
|
1525
|
-
placeholder: "https://your-pega-server.com/prweb",
|
|
1526
|
-
validate(value) {
|
|
1527
|
-
if (!value) return "Server URL is required.";
|
|
1528
|
-
if (!value.startsWith("http")) return "Must be a valid URL starting with http:// or https://";
|
|
1529
|
-
}
|
|
1530
|
-
});
|
|
1531
|
-
if (p.isCancel(serverUrlResult)) {
|
|
1532
|
-
p.cancel("Operation cancelled.");
|
|
1533
|
-
process.exit(0);
|
|
1534
|
-
}
|
|
1535
|
-
const appAliasResult = await p.text({
|
|
1536
|
-
message: "What is your Pega application alias?",
|
|
1537
|
-
placeholder: "MyApp",
|
|
1538
|
-
validate(value) {
|
|
1539
|
-
if (!value) return "Application alias is required.";
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
if (p.isCancel(appAliasResult)) {
|
|
1543
|
-
p.cancel("Operation cancelled.");
|
|
1544
|
-
process.exit(0);
|
|
1545
|
-
}
|
|
1546
|
-
const clientIdResult = await p.text({
|
|
1547
|
-
message: "What is the OAuth Client ID for your app?",
|
|
1548
|
-
placeholder: "Your OAuth 2.0 client identifier",
|
|
1549
|
-
validate(value) {
|
|
1550
|
-
if (!value) return "Client ID is required for authentication.";
|
|
1551
|
-
}
|
|
1552
|
-
});
|
|
1553
|
-
if (p.isCancel(clientIdResult)) {
|
|
1554
|
-
p.cancel("Operation cancelled.");
|
|
1555
|
-
process.exit(0);
|
|
1556
|
-
}
|
|
1557
|
-
const mcpRuleNameResult = await p.text({
|
|
1558
|
-
message: "What is your Pega MCP rule name? (press Enter to skip)",
|
|
1559
|
-
placeholder: "SDKMCP"
|
|
1560
|
-
});
|
|
1561
|
-
if (p.isCancel(mcpRuleNameResult)) {
|
|
1562
|
-
p.cancel("Operation cancelled.");
|
|
1563
|
-
process.exit(0);
|
|
1564
|
-
}
|
|
1565
|
-
const mcpRuleInsKeyResult = await p.text({
|
|
1566
|
-
message: "What is your Pega MCP rule instance key? (press Enter to skip)",
|
|
1567
|
-
placeholder: "@baseclass!SDKMCP"
|
|
1568
|
-
});
|
|
1569
|
-
if (p.isCancel(mcpRuleInsKeyResult)) {
|
|
1570
|
-
p.cancel("Operation cancelled.");
|
|
1571
|
-
process.exit(0);
|
|
1572
|
-
}
|
|
1573
|
-
serverUrl = String(serverUrlResult).replace(/\/+$/, "");
|
|
1574
|
-
appAlias = String(appAliasResult);
|
|
1575
|
-
clientId = String(clientIdResult);
|
|
1576
|
-
mcpRuleName = String(mcpRuleNameResult) || void 0;
|
|
1577
|
-
mcpRuleInsKey = String(mcpRuleInsKeyResult) || void 0;
|
|
1578
1665
|
}
|
|
1579
1666
|
const framework = await p.select({
|
|
1580
1667
|
message: "Select a UI framework for your Pega SDK project:",
|
|
@@ -1659,7 +1746,7 @@ Usage: ${pc.cyan("npx @pega/sdk init")}`);
|
|
|
1659
1746
|
componentCategories = categories;
|
|
1660
1747
|
}
|
|
1661
1748
|
}
|
|
1662
|
-
const targetDir =
|
|
1749
|
+
const targetDir = path4.resolve(process.cwd(), projectName);
|
|
1663
1750
|
p.note(
|
|
1664
1751
|
[
|
|
1665
1752
|
`${pc.bold("Project")}: ${projectName}`,
|
package/package.json
CHANGED
|
@@ -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.
|
|
@@ -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
|
|
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
|
|
|
@@ -55,6 +55,10 @@ 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.
|
|
@@ -70,6 +74,7 @@ The response's `pxResults` array holds one entry per portal. Then:
|
|
|
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
|
|
|
@@ -89,10 +94,20 @@ Present the returned pages as a **checkbox list** (label each with its `pyRuleNa
|
|
|
89
94
|
|
|
90
95
|
For **each selected page**, resolve its components by its `pxObjClass`:
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
Apply this dispatch as an exact, mutually exclusive decision for every page. Do not choose a data source based on the page label, rule name, or a guessed similarity:
|
|
98
|
+
|
|
99
|
+
1. If `pxObjClass` is exactly `Rule-UI-View`, execute **only** `D_pyViewDependencies` with `ViewName = pyRuleName` and `ViewClass = pyClassName`. Read component names from `pxDependencies`.
|
|
100
|
+
2. Else if `pxObjClass` is exactly `Rule-UI-View-LandingPage`, execute **only** `D_pyLandingPageComponentsByType` with `RuleName = pyRuleName` and `ClassName = pyClassName`. Read component names from `pxResults`.
|
|
101
|
+
3. Else, do not execute either data source. Report the unsupported `pxObjClass`, preserve the page in the selection summary, and ask how to proceed. Never route an unknown class through the landing-page data source as a default.
|
|
102
|
+
|
|
103
|
+
The required mapping is:
|
|
104
|
+
|
|
105
|
+
| Exact `pxObjClass` | Data source | Parameters | Components are in |
|
|
106
|
+
| -------------------------- | --------------------------------- | -------------------------------------------------- | ----------------- |
|
|
107
|
+
| `Rule-UI-View` | `D_pyViewDependencies` | `ViewName = pyRuleName`, `ViewClass = pyClassName` | `pxDependencies` |
|
|
108
|
+
| `Rule-UI-View-LandingPage` | `D_pyLandingPageComponentsByType` | `RuleName = pyRuleName`, `ClassName = pyClassName` | `pxResults` |
|
|
109
|
+
|
|
110
|
+
For a page whose `pxObjClass` is `Rule-UI-View`, it is a workflow error to call `D_pyLandingPageComponentsByType`; stop and correct the dispatch before continuing. Likewise, do not call `D_pyViewDependencies` for `Rule-UI-View-LandingPage`.
|
|
96
111
|
|
|
97
112
|
Do not supply `ApplicationName` or `ApplicationVersion` to `D_pyLandingPageComponentsByType` — ignore those parameters. Make one call per selected page, even when two pages resolve to the same data source. Discard malformed entries (anything that is not a plain component-name identifier) from each response, then union the results across all selected pages and drop duplicates. Keep a note of which page(s) contributed each component — Step 0.5.5 reports it.
|
|
98
113
|
|
|
@@ -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.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@pega/auth": "file:pega-auth-0.2.
|
|
12
|
+
"@pega/auth": "file:pega-auth-0.2.44.tgz",
|
|
13
13
|
"@pega/constellationjs": "file:pega-constellationjs-0.26.3-local.tgz",
|
|
14
14
|
"@pega/sdk-react": "file:pega-sdk-react-0.1.0.tgz",
|
|
15
15
|
"@pega/sdk-types": "file:pega-sdk-types-0.1.0.tgz",
|