@cspeach/cli 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/agent/loop.js +33 -4
  2. package/dist/agent/session-context.js +24 -0
  3. package/dist/agent/tool-dispatch.js +17 -1
  4. package/dist/approvals/jwt.js +12 -1
  5. package/dist/cli-args.js +24 -0
  6. package/dist/cli.js +33 -4
  7. package/dist/commands/config-set.js +139 -4
  8. package/dist/config/loader.js +34 -0
  9. package/dist/cost/pricing.js +4 -0
  10. package/dist/cost/session-cost.js +28 -0
  11. package/dist/doctor/checks/_standalone-skip.js +37 -0
  12. package/dist/doctor/checks/cert.js +5 -0
  13. package/dist/doctor/checks/forge-rules.js +6 -0
  14. package/dist/doctor/checks/sap.js +5 -0
  15. package/dist/doctor/checks/standards.js +27 -0
  16. package/dist/doctor/checks/system-roles.js +5 -0
  17. package/dist/doctor/checks/zcspeach.js +5 -0
  18. package/dist/doctor/run.js +2 -0
  19. package/dist/one-shot.js +36 -11
  20. package/dist/renderer/abap-inline.js +2 -1
  21. package/dist/renderer/syntax.js +7 -1
  22. package/dist/repl/current-transport.js +23 -1
  23. package/dist/repl/post-turn-status.js +9 -6
  24. package/dist/repl.js +40 -7
  25. package/dist/sap/offline-adt-client.js +58 -0
  26. package/dist/sap/standalone-onboarding.js +118 -0
  27. package/dist/sap/standalone-profile.js +237 -0
  28. package/dist/skills/bundled-skills.js +41 -41
  29. package/dist/standards/standards-file.js +107 -0
  30. package/dist/standards/standards-init.js +194 -0
  31. package/dist/tools/capability/tool.js +2 -0
  32. package/dist/tools/fiori/fe-scaffold.js +25 -0
  33. package/dist/tools/fiori/smoke/assertions.js +8 -2
  34. package/dist/tools/fiori/smoke/driver.js +6 -0
  35. package/dist/tools/fiori/smoke/run-smoke.js +69 -2
  36. package/dist/tools/fiori/tools.js +4 -2
  37. package/dist/tools/index.js +25 -0
  38. package/dist/tools/sap-read.js +18 -0
  39. package/dist/tools/sap-write.js +32 -0
  40. package/dist/tools/snapshot.js +3 -0
  41. package/dist/tools/todo.js +11 -3
  42. package/dist/tools/transport.js +4 -0
  43. package/dist/ui/app.js +1 -1
  44. package/dist/ui/sap-state-store.js +1 -1
  45. package/dist/ui/status-line.js +2 -2
  46. package/dist/ui/todo-panel.js +27 -6
  47. package/fiori/zlas-inspection-lr/package.json +26 -0
  48. package/fiori/zlas-inspection-lr/ui5-local.yaml +45 -0
  49. package/fiori/zlas-inspection-lr/ui5.yaml +41 -0
  50. package/fiori/zlas-inspection-lr/webapp/Component.js +12 -0
  51. package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +9 -0
  52. package/fiori/zlas-inspection-lr/webapp/index.html +35 -0
  53. package/fiori/zlas-inspection-lr/webapp/manifest.json +145 -0
  54. package/package.json +109 -109
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Standalone-mode SAP profile — the "what system would this land on?" facts a
3
+ * prospect gives us ONCE when they run CSPeach without a SAP connection
4
+ * (`--no-sap` / `no_sap = true`).
5
+ *
6
+ * This module is PURE: types, whitelists, derivations and rendering only. The
7
+ * interactive wizard lives in `standalone-onboarding.ts` so tests never pull in
8
+ * `@inquirer/prompts`, and `config/loader.ts` can import the shape + sanitiser
9
+ * without a prompt dependency.
10
+ *
11
+ * The rendered element deliberately reuses the CONNECTED `<sap_system …/>`
12
+ * shape (see sap/system-info.ts `renderSapSystemBlock`) so skills read one
13
+ * element in both modes and need no change.
14
+ */
15
+ export const STANDALONE_PLATFORMS = [
16
+ 'ECC',
17
+ 'S/4HANA',
18
+ 'S/4HANA Cloud Private',
19
+ 'S/4HANA Cloud Public',
20
+ 'BTP ABAP Environment',
21
+ ];
22
+ export const STANDALONE_DATABASES = ['HANA', 'other'];
23
+ export const STANDALONE_DEPLOYMENTS = ['on-prem', 'private-cloud', 'public-cloud'];
24
+ export const STANDALONE_IDES = ['ADT', 'SE80', 'both'];
25
+ /** The three platforms that are "cloud" for defaulting purposes. */
26
+ const CLOUD_PLATFORMS = new Set([
27
+ 'S/4HANA Cloud Private',
28
+ 'S/4HANA Cloud Public',
29
+ 'BTP ABAP Environment',
30
+ ]);
31
+ export function isStandalonePlatform(v) {
32
+ return typeof v === 'string' && STANDALONE_PLATFORMS.includes(v);
33
+ }
34
+ /**
35
+ * Deployment implied by the platform. `null` means "the platform doesn't decide
36
+ * it — ask the user" (ECC and on-prem S/4HANA can both be on-prem or hosted in
37
+ * a private cloud).
38
+ */
39
+ export function deriveDeployment(platform) {
40
+ switch (platform) {
41
+ case 'S/4HANA Cloud Public':
42
+ case 'BTP ABAP Environment':
43
+ return 'public-cloud';
44
+ case 'S/4HANA Cloud Private':
45
+ return 'private-cloud';
46
+ default:
47
+ return null;
48
+ }
49
+ }
50
+ /**
51
+ * Database implied by the platform. S/4HANA (any edition) and BTP ABAP are
52
+ * HANA by definition; only ECC can sit on something else. `null` = ask.
53
+ */
54
+ export function deriveDatabase(platform) {
55
+ return platform === 'ECC' ? null : 'HANA';
56
+ }
57
+ /** Default answer to "must new code be ABAP Cloud / clean-core compliant?". */
58
+ export function defaultAbapCloudOnly(platform) {
59
+ return CLOUD_PLATFORMS.has(platform);
60
+ }
61
+ /** Release examples shown in the wizard prompt, per platform. */
62
+ export function releaseExamples(platform) {
63
+ switch (platform) {
64
+ case 'ECC':
65
+ return '6.0 EhP8 / NW 7.50';
66
+ case 'S/4HANA':
67
+ return '2023 FPS01 / 2022 / 2021 / 1909';
68
+ case 'S/4HANA Cloud Private':
69
+ return '2023 FPS01 / 2022 / current';
70
+ default:
71
+ return 'current';
72
+ }
73
+ }
74
+ /** Pre-filled answer for the release prompt, per platform. */
75
+ export function defaultRelease(platform) {
76
+ switch (platform) {
77
+ case 'ECC':
78
+ return '6.0 EhP8';
79
+ case 'S/4HANA':
80
+ case 'S/4HANA Cloud Private':
81
+ return '2023';
82
+ default:
83
+ return 'current';
84
+ }
85
+ }
86
+ /** S/4HANA release year → SAP_BASIS / ABAP language version. */
87
+ const S4_YEAR_TO_ABAP = {
88
+ '1909': '7.54',
89
+ '2020': '7.55',
90
+ '2021': '7.56',
91
+ '2022': '7.57',
92
+ '2023': '7.58',
93
+ '2025': '7.59',
94
+ };
95
+ function releaseYear(release) {
96
+ for (const year of Object.keys(S4_YEAR_TO_ABAP)) {
97
+ // Word-boundary match so "2023 FPS01" and "S/4HANA 2022 FPS02" both land.
98
+ if (new RegExp(`(^|\\D)${year}(\\D|$)`).test(release))
99
+ return year;
100
+ }
101
+ return undefined;
102
+ }
103
+ /**
104
+ * Map platform + release to the `abap_version` attribute skills already read
105
+ * from the connected `<sap_system>` element.
106
+ *
107
+ * Note on S/4HANA Cloud Private: it runs the SAME on-prem code line, so when
108
+ * the user states a release year we report the real ABAP version (2023 → 7.58)
109
+ * instead of the generic "ABAP Cloud" marker. Only a year-less answer
110
+ * ("current") falls back to "ABAP Cloud".
111
+ */
112
+ export function mapAbapVersion(platform, release) {
113
+ switch (platform) {
114
+ case 'ECC':
115
+ return '7.50 (classic ABAP, no ABAP Cloud)';
116
+ case 'S/4HANA': {
117
+ const y = releaseYear(release);
118
+ return y ? S4_YEAR_TO_ABAP[y] : 'unknown';
119
+ }
120
+ case 'S/4HANA Cloud Private': {
121
+ const y = releaseYear(release);
122
+ return y ? S4_YEAR_TO_ABAP[y] : 'ABAP Cloud';
123
+ }
124
+ default:
125
+ return 'ABAP Cloud';
126
+ }
127
+ }
128
+ /** Map the profile's deployment onto the existing `SapDeployment` values. */
129
+ export function mapSapDeployment(deployment) {
130
+ return deployment === 'public-cloud' ? 'public-cloud' : 'on-prem-or-private-cloud';
131
+ }
132
+ /**
133
+ * Coerce a possibly-hand-edited `[standalone]` TOML table into a valid profile.
134
+ *
135
+ * The platform is load-bearing (every other default derives from it), so an
136
+ * unrecognised platform drops the WHOLE table — better a clean "profile not
137
+ * set" than a confident wrong release rendered into every prompt. Individual
138
+ * bad sub-fields are repaired from the platform defaults.
139
+ */
140
+ export function sanitiseStandaloneProfile(raw) {
141
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
142
+ return undefined;
143
+ const r = raw;
144
+ if (!isStandalonePlatform(r.platform))
145
+ return undefined;
146
+ const platform = r.platform;
147
+ const release = typeof r.release === 'string' && r.release.trim().length > 0
148
+ ? r.release.trim()
149
+ : defaultRelease(platform);
150
+ const database = typeof r.database === 'string' && STANDALONE_DATABASES.includes(r.database)
151
+ ? r.database
152
+ : (deriveDatabase(platform) ?? 'HANA');
153
+ const deployment = typeof r.deployment === 'string' && STANDALONE_DEPLOYMENTS.includes(r.deployment)
154
+ ? r.deployment
155
+ : (deriveDeployment(platform) ?? 'on-prem');
156
+ const ide = typeof r.ide === 'string' && STANDALONE_IDES.includes(r.ide)
157
+ ? r.ide
158
+ : 'ADT';
159
+ const profile = {
160
+ platform,
161
+ release,
162
+ database,
163
+ deployment,
164
+ ide,
165
+ abap_cloud_only: typeof r.abap_cloud_only === 'boolean' ? r.abap_cloud_only : defaultAbapCloudOnly(platform),
166
+ };
167
+ if (typeof r.notes === 'string' && r.notes.trim().length > 0)
168
+ profile.notes = r.notes.trim();
169
+ return profile;
170
+ }
171
+ function escapeAttr(s) {
172
+ return s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
173
+ }
174
+ /** Hard cap on the free-text `notes` attribute, so one long line can't bloat every turn. */
175
+ export const STANDALONE_NOTES_MAX_CHARS = 200;
176
+ /**
177
+ * Render the standalone `<sap_system …/>` element — the SAME element name the
178
+ * connected path emits, with `connected="false" mode="standalone"` plus the
179
+ * three extra facts the profile knows (database / ide / abap_cloud_only).
180
+ *
181
+ * With no profile every fact renders as "unknown" rather than being omitted:
182
+ * a uniform attribute set is easier for a skill to read than a shape that
183
+ * changes depending on what the user answered.
184
+ */
185
+ export function renderStandaloneSapSystemBlock(profile) {
186
+ const platform = profile ? profile.platform : 'unknown';
187
+ const release = profile ? profile.release : 'unknown';
188
+ const abapVersion = profile ? mapAbapVersion(profile.platform, profile.release) : 'unknown';
189
+ const deployment = profile ? mapSapDeployment(profile.deployment) : 'unknown';
190
+ const database = profile ? profile.database : 'unknown';
191
+ const ide = profile ? profile.ide : 'unknown';
192
+ const cloudOnly = profile ? String(profile.abap_cloud_only ?? defaultAbapCloudOnly(profile.platform)) : 'unknown';
193
+ const attrs = [
194
+ `platform="${escapeAttr(platform)}"`,
195
+ `release="${escapeAttr(release)}"`,
196
+ `abap_version="${escapeAttr(abapVersion)}"`,
197
+ `deployment="${escapeAttr(deployment)}"`,
198
+ `connected="false"`,
199
+ `mode="standalone"`,
200
+ `database="${escapeAttr(database)}"`,
201
+ `ide="${escapeAttr(ide)}"`,
202
+ `abap_cloud_only="${escapeAttr(cloudOnly)}"`,
203
+ ];
204
+ // `notes` is the user's own free-text constraint ("no BTP", "transports via
205
+ // ChaRM") — worth giving the model, but only when they actually answered:
206
+ // the attribute is OMITTED entirely rather than rendered empty/"unknown".
207
+ const notes = profile?.notes?.trim();
208
+ if (notes) {
209
+ attrs.push(`notes="${escapeAttr(notes.slice(0, STANDALONE_NOTES_MAX_CHARS))}"`);
210
+ }
211
+ return `<sap_system ${attrs.join(' ')}/>`;
212
+ }
213
+ /**
214
+ * The manual-implementation hand-off rule. Standalone cannot create or activate
215
+ * anything, so every would-be write has to end as instructions the developer
216
+ * can follow by hand — on disk when the file tools are available, otherwise in
217
+ * the reply — plus the rule for repairing a single failed step.
218
+ */
219
+ export const STANDALONE_NOTE = '<standalone_note>No SAP system is connected (standalone mode). Do not call SAP tools. ' +
220
+ 'Work from code the user pastes or describes; when a task needs system data (source, table definitions, ATC output, dumps), ask the user to paste it. ' +
221
+ 'Target the platform and release in sap_system exactly — use only syntax and APIs available on that release. ' +
222
+ 'Because nothing can be created or activated here, every task that would normally write to SAP must instead end with a "Manual Implementation Guide": ' +
223
+ 'numbered steps in dependency order, one object per step, each stating the object type and name, where to create it (ADT wizard path, or SE80/SE11/SE24/SE38 transaction for SE80 teams), ' +
224
+ 'the exact source to paste, the activation step, and any prerequisite (package, transport, number range, message class). ' +
225
+ 'Finish with a short verification checklist (syntax check, activation, ATC, unit test) the user can run themselves.\n' +
226
+ 'File outputs: when file tools are available (local build is on), also write every produced object to the working folder as ./cspeach-out/<yyyy-mm-dd>-<short-task-name>/NN-<OBJECT_NAME>.<abap|ddls|bdef|srvd|srvb|ddlx|dcls|txt> in dependency order, and write the Manual Implementation Guide to GUIDE.md in the same folder, with each step pointing at its file. Tell the user the folder path once. If file tools are not available, say so once and keep everything in the reply.\n' +
227
+ 'Step failures: when the user reports a failure at a step ("step 3 failed" plus an error message or screenshot), ask only for what is missing to diagnose it, fix only the affected object, rewrite its file, re-issue that step and any step that depends on it, and state which steps are unchanged. Never restart the whole guide.</standalone_note>';
228
+ /**
229
+ * The full standalone contribution to `<session_context>`: the sap_system
230
+ * element followed by the hand-off note.
231
+ *
232
+ * `formatFactBlock` (router/intent-extractor.ts) indents only the FIRST line of
233
+ * the block it is handed, so every following line carries its own two spaces.
234
+ */
235
+ export function renderStandaloneContextBlock(profile) {
236
+ return `${renderStandaloneSapSystemBlock(profile)}\n ${STANDALONE_NOTE}`;
237
+ }