@cspeach/cli 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +108 -0
  3. package/dist/agent/anthropic-provider.js +59 -0
  4. package/dist/agent/llm-provider.js +1 -0
  5. package/dist/agent/loop.js +709 -0
  6. package/dist/agent/maybe-build-project-context.js +126 -0
  7. package/dist/agent/providers/ai-hub-provider.js +58 -0
  8. package/dist/agent/providers/byok-provider.js +53 -0
  9. package/dist/agent/providers/factory.js +13 -0
  10. package/dist/agent/providers/local-provider.js +125 -0
  11. package/dist/agent/repair-partial.js +31 -0
  12. package/dist/agent/retry-key.js +58 -0
  13. package/dist/agent/retry.js +17 -0
  14. package/dist/agent/sap-connection-adapter.js +82 -0
  15. package/dist/agent/skill-checkpoint.js +119 -0
  16. package/dist/agent/tool-dispatch.js +47 -0
  17. package/dist/agent/turn-assistant-text.js +49 -0
  18. package/dist/agent/turn-error-ux.js +126 -0
  19. package/dist/agent/turn-stream.js +79 -0
  20. package/dist/agent/turn-watchdog.js +71 -0
  21. package/dist/approvals/advisory-prompt.js +40 -0
  22. package/dist/approvals/advisory-render.js +38 -0
  23. package/dist/approvals/approval-prompt.js +100 -0
  24. package/dist/approvals/jwt.js +33 -0
  25. package/dist/approvals/render.js +211 -0
  26. package/dist/approvals/risk-floor.js +26 -0
  27. package/dist/auth/api-key.js +40 -0
  28. package/dist/auth/auth-file.js +59 -0
  29. package/dist/auth/device.js +8 -0
  30. package/dist/auth/me.js +19 -0
  31. package/dist/classifier/client.js +58 -0
  32. package/dist/cli-args.js +38 -0
  33. package/dist/cli.js +148 -0
  34. package/dist/commands/config-set.js +245 -0
  35. package/dist/commands/config-show.js +159 -0
  36. package/dist/commands/help.js +93 -0
  37. package/dist/commands/login.js +122 -0
  38. package/dist/commands/logout.js +17 -0
  39. package/dist/commands/project-context-impact.js +215 -0
  40. package/dist/commands/reroute.js +60 -0
  41. package/dist/commands/spec-gap-status.js +52 -0
  42. package/dist/commands/whoami.js +35 -0
  43. package/dist/config/loader.js +67 -0
  44. package/dist/config/paths.js +20 -0
  45. package/dist/doctor/checks/_http-probe.js +56 -0
  46. package/dist/doctor/checks/auth.js +15 -0
  47. package/dist/doctor/checks/cert.js +24 -0
  48. package/dist/doctor/checks/forge-rules.js +102 -0
  49. package/dist/doctor/checks/keychain-fallback.js +14 -0
  50. package/dist/doctor/checks/keychain.js +23 -0
  51. package/dist/doctor/checks/llm-mode.js +27 -0
  52. package/dist/doctor/checks/proxy.js +13 -0
  53. package/dist/doctor/checks/sap.js +33 -0
  54. package/dist/doctor/checks/skill.js +24 -0
  55. package/dist/doctor/checks/write-mode.js +34 -0
  56. package/dist/doctor/checks/zcspeach.js +76 -0
  57. package/dist/doctor/run.js +46 -0
  58. package/dist/errors/codes.js +12 -0
  59. package/dist/index.js +6 -0
  60. package/dist/lock-contention.js +22 -0
  61. package/dist/one-shot.js +104 -0
  62. package/dist/project-context/conventions.js +309 -0
  63. package/dist/project-context/detect.js +250 -0
  64. package/dist/project-context/domain/abap-cloud.js +26 -0
  65. package/dist/project-context/domain/abapgit.js +177 -0
  66. package/dist/project-context/domain/cap.js +164 -0
  67. package/dist/project-context/domain/fiori.js +326 -0
  68. package/dist/project-context/git.js +115 -0
  69. package/dist/project-context/index-files.js +235 -0
  70. package/dist/project-context/index.js +117 -0
  71. package/dist/project-context/render.js +308 -0
  72. package/dist/project-context/types.js +14 -0
  73. package/dist/projects/build.js +20 -0
  74. package/dist/projects/canonicalize.js +39 -0
  75. package/dist/projects/email-template.js +54 -0
  76. package/dist/projects/extract-cca.js +139 -0
  77. package/dist/projects/extract-design.js +107 -0
  78. package/dist/projects/extract-estimate.js +93 -0
  79. package/dist/projects/extract-modernize.js +130 -0
  80. package/dist/projects/extract-spec-gap.js +101 -0
  81. package/dist/projects/extract-test-coverage.js +137 -0
  82. package/dist/projects/extract-upgrade.js +230 -0
  83. package/dist/projects/filename.js +18 -0
  84. package/dist/projects/index.js +8 -0
  85. package/dist/projects/migration.js +111 -0
  86. package/dist/projects/promote-command.js +96 -0
  87. package/dist/projects/promote.js +107 -0
  88. package/dist/projects/save-command.js +124 -0
  89. package/dist/projects/save.js +21 -0
  90. package/dist/projects/status.js +170 -0
  91. package/dist/projects/types.js +1 -0
  92. package/dist/projects/validate.js +146 -0
  93. package/dist/projects/workspace.js +478 -0
  94. package/dist/renderer/abap-inline.js +121 -0
  95. package/dist/renderer/banners.js +39 -0
  96. package/dist/renderer/highlighters/abap.js +126 -0
  97. package/dist/renderer/highlighters/bdef.js +81 -0
  98. package/dist/renderer/highlighters/cds.js +91 -0
  99. package/dist/renderer/markdown.js +291 -0
  100. package/dist/renderer/pipeline.js +201 -0
  101. package/dist/renderer/progress-chatter.js +237 -0
  102. package/dist/renderer/question-normalizer.js +306 -0
  103. package/dist/renderer/severity.js +61 -0
  104. package/dist/renderer/status-footer.js +50 -0
  105. package/dist/renderer/syntax.js +58 -0
  106. package/dist/renderer/tables.js +55 -0
  107. package/dist/renderer/thinking-heartbeat.js +70 -0
  108. package/dist/renderer/tool-widget.js +199 -0
  109. package/dist/renderer/tty.js +66 -0
  110. package/dist/renderer/widget-extractor.js +87 -0
  111. package/dist/renderer/widget-fallback.js +78 -0
  112. package/dist/renderer/widget-schemas.js +43 -0
  113. package/dist/repl/at-completer.js +64 -0
  114. package/dist/repl/at-picker.js +122 -0
  115. package/dist/repl/bracketed-paste.js +284 -0
  116. package/dist/repl/current-transport.js +46 -0
  117. package/dist/repl/diff-display.js +41 -0
  118. package/dist/repl/file-picker.js +219 -0
  119. package/dist/repl/inquirer-guard.js +130 -0
  120. package/dist/repl/inquirer-theme.js +41 -0
  121. package/dist/repl/rule8-detector.js +99 -0
  122. package/dist/repl/safety-confirm.js +106 -0
  123. package/dist/repl/safety-mode-state.js +36 -0
  124. package/dist/repl/slash-completer.js +59 -0
  125. package/dist/repl/slash-picker.js +124 -0
  126. package/dist/repl/update-method-preview-hook.js +45 -0
  127. package/dist/repl.js +1383 -0
  128. package/dist/router/classifier.js +38 -0
  129. package/dist/router/intent-extractor.js +140 -0
  130. package/dist/router/routing-decision.js +19 -0
  131. package/dist/sap/connection-manager.js +52 -0
  132. package/dist/sap/onboarding.js +178 -0
  133. package/dist/sap/system-info.js +515 -0
  134. package/dist/session/awaiting-answer.js +73 -0
  135. package/dist/session/gc.js +28 -0
  136. package/dist/session/pending.js +37 -0
  137. package/dist/session/resume.js +77 -0
  138. package/dist/session/schema.js +20 -0
  139. package/dist/session/store.js +147 -0
  140. package/dist/session/time-ago.js +41 -0
  141. package/dist/skill-catalog.js +222 -0
  142. package/dist/skills/bundled-skills.js +1 -0
  143. package/dist/skills/canonical.js +12 -0
  144. package/dist/skills/manifest-client.js +93 -0
  145. package/dist/skills/promotion-dispatch.js +24 -0
  146. package/dist/skills/signing-public-key.js +4 -0
  147. package/dist/skills/source-bundled.js +20 -0
  148. package/dist/skills/source-managed.js +26 -0
  149. package/dist/skills/source-manifest.js +26 -0
  150. package/dist/tools/_command-shared.js +110 -0
  151. package/dist/tools/_filesystem-shared.js +81 -0
  152. package/dist/tools/_flag.js +39 -0
  153. package/dist/tools/approval.js +228 -0
  154. package/dist/tools/ask-question.js +205 -0
  155. package/dist/tools/dispatch-skill.js +81 -0
  156. package/dist/tools/filesystem/file-edit.js +140 -0
  157. package/dist/tools/filesystem/file-read.js +89 -0
  158. package/dist/tools/filesystem/file-write.js +128 -0
  159. package/dist/tools/filesystem/glob.js +177 -0
  160. package/dist/tools/filesystem/grep.js +163 -0
  161. package/dist/tools/index.js +32 -0
  162. package/dist/tools/project/convention_get.js +91 -0
  163. package/dist/tools/project/playbook_get.js +132 -0
  164. package/dist/tools/project/project_context_get.js +101 -0
  165. package/dist/tools/sap-read.js +454 -0
  166. package/dist/tools/sap-write.js +746 -0
  167. package/dist/tools/shell/shell_exec.js +209 -0
  168. package/dist/tools/snapshot.js +107 -0
  169. package/dist/tools/subagent/_background-shared.js +133 -0
  170. package/dist/tools/subagent/agent_run.js +186 -0
  171. package/dist/tools/subagent/background_run.js +143 -0
  172. package/dist/tools/subagent/monitor_emit.js +65 -0
  173. package/dist/tools/subagent/schedule_create.js +131 -0
  174. package/dist/tools/transport.js +233 -0
  175. package/dist/tools/update-method-intercept.js +119 -0
  176. package/dist/tools/verify.js +39 -0
  177. package/dist/tools/web/_web-shared.js +251 -0
  178. package/dist/tools/web/web_fetch.js +257 -0
  179. package/dist/tools/web/web_search.js +195 -0
  180. package/dist/tools/write-mode.js +22 -0
  181. package/dist/ui/app.js +95 -0
  182. package/dist/ui/approval-emitter.js +10 -0
  183. package/dist/ui/approval-modal.js +53 -0
  184. package/dist/ui/ascii-chars.js +6 -0
  185. package/dist/ui/body.js +102 -0
  186. package/dist/ui/coaching-picker-classic.js +36 -0
  187. package/dist/ui/coaching-picker-emitter.js +27 -0
  188. package/dist/ui/command-palette.js +34 -0
  189. package/dist/ui/error-emitter.js +21 -0
  190. package/dist/ui/footer.js +103 -0
  191. package/dist/ui/header.js +17 -0
  192. package/dist/ui/ink-classifier-route.js +19 -0
  193. package/dist/ui/login-banner.js +72 -0
  194. package/dist/ui/rich-error-box.js +9 -0
  195. package/dist/ui/sap-state-store.js +65 -0
  196. package/dist/ui/session-timeline.js +31 -0
  197. package/dist/ui/sidebar.js +10 -0
  198. package/dist/ui/skill-picker.js +50 -0
  199. package/dist/ui/status-row.js +12 -0
  200. package/dist/ui/widget-control.js +4 -0
  201. package/dist/ui/widgets/bar-chart.js +15 -0
  202. package/dist/ui/widgets/coaching-picker.js +41 -0
  203. package/dist/ui/widgets/component-registry.js +12 -0
  204. package/dist/ui/widgets/dep-graph.js +9 -0
  205. package/dist/ui/widgets/diff-viewer.js +11 -0
  206. package/dist/ui/widgets/question-card.js +11 -0
  207. package/dist/ui/widgets/stack-frames.js +5 -0
  208. package/dist/upgrade-check.js +28 -0
  209. package/dist/upgrade.js +13 -0
  210. package/package.json +83 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * cli-highlight language definition for ABAP.
3
+ *
4
+ * Registered as 'abap'. Matches fenced code blocks with ```abap.
5
+ * 150+ keywords covering SELECT/FROM/WHERE, OOP class/interface/method,
6
+ * structured data (types/data/field-symbols), control flow, RAP/EML,
7
+ * CDS annotations are handled separately in cds.ts.
8
+ */
9
+ // Keywords are unioned into a single Set during grammar build; duplicates
10
+ // are harmless at runtime but noisy in source. Kept alphabetical within
11
+ // each section for diff-friendliness. Keywords that appear in multiple
12
+ // conceptual sections (e.g. READ — both DML and EML, TABLES — declaration
13
+ // and selection-screen) are listed ONCE in the most-DML-like section with a
14
+ // trailing comment noting the other usage.
15
+ const KEYWORDS = [
16
+ // DML / Open SQL (READ/MODIFY/DELETE/CREATE also used in EML — listed here)
17
+ 'ALL', 'APPENDING', 'AS', 'BY', 'BYPASSING', 'BUFFER', 'CLIENT',
18
+ 'COMMIT', 'CORRESPONDING', 'CREATE', 'DELETE', 'DISTINCT', 'ENTRIES',
19
+ 'FIELDS', 'FOR', 'FROM', 'GROUP', 'HAVING', 'IN', 'INNER', 'INSERT',
20
+ 'INTO', 'JOIN', 'LEFT', 'MODIFY', 'OF', 'ON', 'ORDER', 'OUTER',
21
+ 'READ', 'ROLLBACK', 'ROWS', 'SELECT', 'SINGLE', 'SPECIFIED', 'TABLE',
22
+ 'TO', 'UNION', 'UP', 'UPDATE', 'WHERE', 'WORK',
23
+ // Data declaration (TABLES also used in selection-screen — listed here)
24
+ 'ALIASES', 'BEGIN', 'CLASS-DATA', 'CLASS-METHODS', 'CONSTANTS', 'DATA',
25
+ 'DEFAULT', 'END', 'EVENTS', 'HASHED', 'HEADER', 'INCLUDE', 'INTERFACES',
26
+ 'KEY', 'LIKE', 'LINE', 'METHODS', 'NEW', 'NON-UNIQUE', 'OCCURS',
27
+ 'REF', 'SORTED', 'STANDARD', 'STRUCTURE', 'TABLES', 'TYPE', 'TYPES',
28
+ 'UNIQUE', 'WITH',
29
+ // OOP (ABSTRACT/FINAL also in 'misc' — listed here)
30
+ 'ABSTRACT', 'CHANGING', 'CLASS', 'DEFERRED', 'DEFINITION', 'ENDCLASS',
31
+ 'ENDINTERFACE', 'ENDMETHOD', 'EXCEPTIONS', 'EXPORTING', 'FINAL',
32
+ 'FRIENDS', 'IMPLEMENTATION', 'IMPORTING', 'INHERITING', 'INTERFACE',
33
+ 'METHOD', 'OBJECT', 'OPTIONAL', 'OVERRIDING', 'PARAMETER', 'PARAMETERS',
34
+ 'PREFERRED', 'PRIVATE', 'PROTECTED', 'PUBLIC', 'RAISING', 'REDEFINITION',
35
+ 'RETURNING', 'SECTION',
36
+ // Exception / flow
37
+ 'ASSERT', 'CATCH', 'CLEANUP', 'ENDTRY', 'EXCEPTION', 'MESSAGE',
38
+ 'RAISE', 'RESUME', 'TRY',
39
+ // Control
40
+ 'AT', 'CASE', 'CHECK', 'CONTINUE', 'DO', 'ELSE', 'ELSEIF', 'ENDAT',
41
+ 'ENDCASE', 'ENDDO', 'ENDIF', 'ENDLOOP', 'ENDWHILE', 'EXIT', 'IF',
42
+ 'LOOP', 'OTHERS', 'RETURN', 'STOP', 'WHEN', 'WHILE',
43
+ // Field symbols
44
+ 'ASSIGN', 'FIELD-SYMBOLS', 'UNASSIGN',
45
+ // String / table ops
46
+ 'APPEND', 'BINARY', 'CLEAR', 'COLLECT', 'CONCATENATE', 'CONDENSE',
47
+ 'DESCRIBE', 'FILTER', 'FIND', 'FREE', 'MOVE', 'REDUCE', 'REPLACE',
48
+ 'SEARCH', 'SHIFT', 'SORT', 'SPLIT', 'TRANSLATE', 'VALUE', 'WRITE',
49
+ // Report / selection screen
50
+ 'CLASS-POOL', 'END-OF-SELECTION', 'FUNCTION-POOL', 'INITIALIZATION',
51
+ 'PROGRAM', 'REPORT', 'SELECT-OPTIONS', 'SELECTION-SCREEN',
52
+ 'START-OF-SELECTION',
53
+ // FORM / PERFORM (legacy)
54
+ 'CALL', 'ENDFORM', 'FORM', 'FUNCTION', 'PERFORM', 'USING',
55
+ // Misc
56
+ 'ACTVT', 'AUTHORITY-CHECK', 'CAST', 'COND', 'CONV', 'DEQUEUE',
57
+ 'ENQUEUE', 'EXACT', 'FAIL', 'FIELD', 'ID', 'LET', 'LOCK', 'NOTICE',
58
+ 'STATIC', 'SWITCH', 'UNLOCK',
59
+ // EML / RAP (only terms NOT already covered above — READ/MODIFY/DELETE/
60
+ // CREATE/IN/FIELDS/WITH/BY are in the DML section)
61
+ 'ASSIGNING', 'AUTHORITY', 'ENTITIES', 'ENTITY', 'EXECUTE', 'FAILED',
62
+ 'LOCAL', 'MAPPED', 'MODE', 'REPORTED', 'RESPONSE', 'RESULT',
63
+ 'TRANSPORTING',
64
+ ];
65
+ const grammar = {
66
+ case_insensitive: true,
67
+ keywords: {
68
+ keyword: KEYWORDS.join(' '),
69
+ },
70
+ contains: [
71
+ // Line comments: * at start of line
72
+ {
73
+ className: 'comment',
74
+ begin: /^\*/,
75
+ end: /$/,
76
+ relevance: 5,
77
+ },
78
+ // Inline comments: " anywhere mid-line
79
+ {
80
+ className: 'comment',
81
+ begin: /"/,
82
+ end: /$/,
83
+ relevance: 3,
84
+ },
85
+ // String literals: single-quoted
86
+ {
87
+ className: 'string',
88
+ begin: /'/,
89
+ end: /'/,
90
+ illegal: /\n/,
91
+ contains: [{ begin: /''/ }], // escaped single quote
92
+ relevance: 4,
93
+ },
94
+ // String templates: |...|
95
+ // Classical begin/end pair with `illegal: /\n/` to prevent the string
96
+ // from consuming across line boundaries. Using zero-width lookahead as
97
+ // `end` is non-standard in highlight.js mode syntax and was rejected in
98
+ // review (H9) — the begin/end/illegal triple is the idiomatic form.
99
+ {
100
+ className: 'string',
101
+ begin: /\|/,
102
+ end: /\|/,
103
+ illegal: /\n/,
104
+ relevance: 0,
105
+ },
106
+ // Z* / Y* identifiers — custom namespace (highlight distinctly as 'title')
107
+ {
108
+ className: 'title',
109
+ begin: /\b[ZY][A-Z0-9_]{1,30}\b/,
110
+ relevance: 3,
111
+ },
112
+ // Numeric literals
113
+ {
114
+ className: 'number',
115
+ begin: /\b\d+(\.\d+)?\b/,
116
+ relevance: 2,
117
+ },
118
+ // Built-in type keywords used after TYPE (treated as type keyword)
119
+ {
120
+ className: 'type',
121
+ begin: /\b(string|xstring|i|int1|int2|int4|int8|f|d|t|c|n|x|p|decfloat16|decfloat34|char|numc|dats|tims|clnt|lang|syst|boolean)\b/i,
122
+ relevance: 3,
123
+ },
124
+ ],
125
+ };
126
+ export default grammar;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * cli-highlight language definition for BDEF (Behavior Definition).
3
+ *
4
+ * Registered as 'bdef'. Matches fenced code blocks with ```bdef.
5
+ * Covers managed/unmanaged syntax, persistent/draft table, operations,
6
+ * validations, determinations, actions, feature control, authorization.
7
+ */
8
+ const BDEF_KEYWORDS = [
9
+ // Top-level
10
+ 'managed', 'unmanaged', 'abstract', 'projection',
11
+ 'implementation', 'in', 'class', 'unique',
12
+ 'define', 'behavior', 'for', 'alias',
13
+ // Persistence
14
+ 'persistent', 'table', 'draft', 'lock', 'master', 'dependent',
15
+ 'total', 'etag',
16
+ // Authorization
17
+ 'authorization', 'instance', 'global',
18
+ // Operations
19
+ 'create', 'update', 'delete', 'read',
20
+ 'use', 'action', 'function', 'static', 'internal',
21
+ 'validation', 'determination', 'on', 'save', 'modify',
22
+ 'association', 'composition', 'to', 'parent',
23
+ // Field control
24
+ 'field', 'readonly', 'mandatory', 'features',
25
+ // RAP features
26
+ 'with', 'draft', 'side', 'effects', 'target',
27
+ 'mapping', 'from', 'numbering', 'managed', 'early', 'late',
28
+ 'result', 'parameter', 'uses', 'augment',
29
+ // Feature modes
30
+ 'instance', 'global', 'static',
31
+ // Triggers
32
+ 'create', 'update', 'delete', 'activate', 'discard', 'edit', 'resume',
33
+ ];
34
+ const grammar = {
35
+ case_insensitive: true,
36
+ keywords: {
37
+ keyword: BDEF_KEYWORDS.join(' '),
38
+ },
39
+ contains: [
40
+ // Line comments: //
41
+ {
42
+ className: 'comment',
43
+ begin: /\/\//,
44
+ end: /$/,
45
+ relevance: 5,
46
+ },
47
+ // Block comments: /* ... */
48
+ {
49
+ className: 'comment',
50
+ begin: /\/\*/,
51
+ end: /\*\//,
52
+ relevance: 5,
53
+ },
54
+ // Class/table names in double-quotes (e.g. implementation in class "zbp_sales_order")
55
+ {
56
+ className: 'string',
57
+ begin: /"/,
58
+ end: /"/,
59
+ relevance: 3,
60
+ },
61
+ // Z* / Y* identifiers (behavior entity names, class names, table names)
62
+ {
63
+ className: 'title',
64
+ begin: /\b[ZY][A-Za-z0-9_]{1,30}\b/,
65
+ relevance: 4,
66
+ },
67
+ // ZBP_ class references (behavior pool class naming convention)
68
+ {
69
+ className: 'title',
70
+ begin: /\bZBP_[A-Za-z0-9_]+\b/i,
71
+ relevance: 5,
72
+ },
73
+ // Numeric literals
74
+ {
75
+ className: 'number',
76
+ begin: /\b\d+\b/,
77
+ relevance: 2,
78
+ },
79
+ ],
80
+ };
81
+ export default grammar;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * cli-highlight language definition for CDS (Core Data Services).
3
+ *
4
+ * Registered as 'cds'. Matches fenced code blocks with ```cds.
5
+ * Covers: define view/table syntax, @Annotations, association/composition,
6
+ * SQL-like keywords used in CDS projections.
7
+ */
8
+ const CDS_KEYWORDS = [
9
+ 'define', 'view', 'entity', 'as', 'select', 'from', 'projection', 'on',
10
+ 'association', 'composition', 'to', 'parent', 'redirected',
11
+ 'join', 'inner', 'left', 'outer', 'right', 'cross',
12
+ 'where', 'group', 'by', 'having', 'union', 'all', 'distinct',
13
+ 'extend', 'annotate', 'with', 'abstract',
14
+ 'key', 'virtual', 'literal', 'cast', 'case', 'when', 'then', 'else', 'end',
15
+ 'not', 'null', 'is', 'and', 'or', 'in', 'between', 'like',
16
+ 'aspect', 'type', 'element', 'action', 'function', 'returns',
17
+ 'service', 'namespace', 'using', 'context',
18
+ 'managed', 'unmanaged', 'implementation', 'class', 'unique',
19
+ 'draft', 'table', 'lock', 'authorization', 'master', 'instance',
20
+ 'persistent', 'field', 'validation', 'determination',
21
+ 'create', 'update', 'delete',
22
+ ];
23
+ const grammar = {
24
+ case_insensitive: true,
25
+ keywords: {
26
+ keyword: CDS_KEYWORDS.join(' '),
27
+ },
28
+ contains: [
29
+ // Line comments: //
30
+ {
31
+ className: 'comment',
32
+ begin: /\/\//,
33
+ end: /$/,
34
+ relevance: 5,
35
+ },
36
+ // Block comments: /* ... */
37
+ {
38
+ className: 'comment',
39
+ begin: /\/\*/,
40
+ end: /\*\//,
41
+ relevance: 5,
42
+ },
43
+ // Annotations: @Something or @Something.nested or @Something.nested: value
44
+ // Annotation value strings handled below; here we color the annotation name.
45
+ {
46
+ className: 'meta',
47
+ begin: /@[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*/,
48
+ relevance: 8,
49
+ contains: [
50
+ // Annotation value strings inside annotations e.g. @EndUserText.label: 'Sales Order'
51
+ {
52
+ className: 'string',
53
+ begin: /'/,
54
+ end: /'/,
55
+ },
56
+ ],
57
+ },
58
+ // String literals
59
+ {
60
+ className: 'string',
61
+ begin: /'/,
62
+ end: /'/,
63
+ relevance: 3,
64
+ },
65
+ // Z* / Y* / C* identifiers (custom CDS view names)
66
+ {
67
+ className: 'title',
68
+ begin: /\b[ZYC][A-Za-z_][A-Za-z0-9_]{0,29}\b/,
69
+ relevance: 4,
70
+ },
71
+ // $projection, $session, $parameters pseudo-elements
72
+ {
73
+ className: 'variable',
74
+ begin: /\$[a-z_]+/,
75
+ relevance: 5,
76
+ },
77
+ // Numeric literals
78
+ {
79
+ className: 'number',
80
+ begin: /\b\d+(\.\d+)?\b/,
81
+ relevance: 2,
82
+ },
83
+ // Cardinality annotations like [0..1], [1..*]
84
+ {
85
+ className: 'number',
86
+ begin: /\[\d+\.\.[*\d]+\]/,
87
+ relevance: 3,
88
+ },
89
+ ],
90
+ };
91
+ export default grammar;
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Markdown → ANSI via marked + marked-terminal.
3
+ *
4
+ * marked-terminal@6 TerminalRenderer options accept CHALK STYLE FUNCTIONS (e.g.,
5
+ * chalk.cyan.bold) — NOT arg-taking callbacks. See
6
+ * https://github.com/mikaelbr/marked-terminal/blob/v6.2.0/README.md for the full
7
+ * option table. Arg-taking callbacks are allowed only for `list(body, ordered)`
8
+ * and `image(href, title, text)`; every other key is a wrapper applied to the
9
+ * already-rendered text.
10
+ *
11
+ * We cannot distinguish h1/h2/h3/h4 with a single chalk function, so we pick one
12
+ * styling for all heading levels — terminal output rarely needs a level hierarchy.
13
+ */
14
+ import chalk from 'chalk';
15
+ import { marked } from 'marked';
16
+ import { markedTerminal } from 'marked-terminal';
17
+ import Table from 'cli-table3';
18
+ import { applySeverityToCell } from './severity.js';
19
+ const PEACH = '#FFB88C';
20
+ // marked-terminal uses these sentinels internally. We parse them back out to
21
+ // rebuild a width-capped cli-table3 in our table renderer override below.
22
+ // Source: node_modules/marked-terminal/index.js lines 10-15.
23
+ const MT_CELL_SPLIT = '^*||*^';
24
+ const MT_ROW_WRAP_RE = new RegExp('\\*\\|\\*\\|\\*\\|\\*', 'g');
25
+ // marked-terminal replaces `:` in codespans with `*#COLON|*` before markdown
26
+ // parses the table (otherwise `:` would be read as column alignment syntax).
27
+ // It reverses this via `undoColon` inside its own transform pipeline; we must
28
+ // do the same in our custom table renderer or raw sentinels leak into cells.
29
+ const MT_COLON_RE = /\*#COLON\|\*/g;
30
+ const ANSI_RE = /\x1B\[[0-9;]*[mGKHFABCDsuJSTfrl]|\x1B\][^\x07]*\x07/g;
31
+ function stripAnsi(s) {
32
+ return s.replace(ANSI_RE, '');
33
+ }
34
+ /** Apply marked-terminal's transform pipeline (undoColon + HTML entity unescape). */
35
+ function mtTransform(s) {
36
+ return s
37
+ .replace(MT_COLON_RE, ':')
38
+ .replace(/&/g, '&')
39
+ .replace(/&lt;/g, '<')
40
+ .replace(/&gt;/g, '>')
41
+ .replace(/&quot;/g, '"')
42
+ .replace(/&#39;/g, "'");
43
+ }
44
+ function parseMtTableRows(text, applyTransform = true) {
45
+ if (!text)
46
+ return [];
47
+ const rows = [];
48
+ const normalized = applyTransform ? mtTransform(text) : text;
49
+ for (const line of normalized.split('\n')) {
50
+ if (!line)
51
+ continue;
52
+ const cells = line.replace(MT_ROW_WRAP_RE, '').split(MT_CELL_SPLIT);
53
+ // marked-terminal's tablecell emits `content + TABLE_CELL_SPLIT` — split leaves
54
+ // one trailing empty element we drop.
55
+ rows.push(cells.slice(0, -1));
56
+ }
57
+ return rows;
58
+ }
59
+ /**
60
+ * Compute per-column widths that fit within the terminal.
61
+ *
62
+ * Strategy:
63
+ * 1. Natural: widest cell in each column (no wrapping).
64
+ * 2. Min: longest single word in each column (prevents mid-word truncation).
65
+ * 3. If natural + padding + borders ≤ termWidth → use natural.
66
+ * 4. Else → each column gets at least its min, remaining budget scales
67
+ * proportionally from (natural - min).
68
+ *
69
+ * cli-table3 layout: `│ <content> │ <content> │`
70
+ * - Each `│` separator is 1 char → borders = colCount + 1
71
+ * - Each cell has 1-char L-pad + content + 1-char R-pad
72
+ * - `colWidths[i]` in cli-table3 = content width + 2 (padding)
73
+ * - totalTableWidth = sum(colWidths) + colCount + 1
74
+ */
75
+ function computeColWidths(naturalContentWidths, minContentWidths, termWidth) {
76
+ const colCount = naturalContentWidths.length;
77
+ const borders = colCount + 1;
78
+ const paddingTotal = colCount * 2;
79
+ const SAFETY = 2; // leave 2 cols headroom at the terminal right edge
80
+ const contentAvailable = Math.max(minContentWidths.reduce((a, b) => a + b, 0), termWidth - borders - paddingTotal - SAFETY);
81
+ const naturalSum = naturalContentWidths.reduce((a, b) => a + b, 0);
82
+ if (naturalSum <= contentAvailable) {
83
+ // Fits — use natural widths.
84
+ return naturalContentWidths.map((w) => w + 2);
85
+ }
86
+ // Overflow — each col gets its min, remainder distributed proportionally to
87
+ // the (natural - min) "stretch" of each column. Short columns (fixed-width
88
+ // severity labels, short IDs) stay at their min; long columns absorb the shrink.
89
+ const minSum = minContentWidths.reduce((a, b) => a + b, 0);
90
+ const stretchAvailable = Math.max(0, contentAvailable - minSum);
91
+ const stretchNatural = naturalSum - minSum;
92
+ const scale = stretchNatural > 0 ? stretchAvailable / stretchNatural : 0;
93
+ const widths = naturalContentWidths.map((nat, i) => {
94
+ const min = minContentWidths[i];
95
+ const extra = Math.floor((nat - min) * scale);
96
+ return min + Math.max(0, extra);
97
+ });
98
+ // Distribute rounding leftover to the widest natural column (so long-content
99
+ // columns like Detail get the extra space).
100
+ let leftover = contentAvailable - widths.reduce((a, b) => a + b, 0);
101
+ if (leftover > 0) {
102
+ const widestIdx = naturalContentWidths.indexOf(Math.max(...naturalContentWidths));
103
+ widths[widestIdx] += leftover;
104
+ }
105
+ return widths.map((w) => w + 2);
106
+ }
107
+ // Width for wrapping prose + list items. Use the terminal width minus a small
108
+ // safety margin so we don't render right up to the last column and get
109
+ // double-wrapped by the terminal. Snapshot at module-load — if the terminal
110
+ // is resized mid-session the wrap will be slightly off but still word-safe.
111
+ const PROSE_WIDTH = Math.max(60, (process.stdout.columns || 100) - 2);
112
+ marked.use(markedTerminal({
113
+ heading: chalk.cyan.bold,
114
+ firstHeading: chalk.hex(PEACH).bold,
115
+ // `strong` (markdown bold via `**...**`) was previously plain `chalk.bold`,
116
+ // which emits only `[1m`. On many terminals — and inside Ink's default
117
+ // <Text> wrap — bold weight on white text is visually indistinguishable
118
+ // from normal-weight white. Pair bold with the peach brand colour so
119
+ // labels like `**Headline:**` stand out even when bold weight isn't
120
+ // rendered strongly. Cheap, safe, and consistent with firstHeading.
121
+ strong: chalk.bold.hex(PEACH),
122
+ em: chalk.italic,
123
+ codespan: chalk.bgGray.white,
124
+ code: chalk.gray,
125
+ blockquote: chalk.gray.italic,
126
+ link: chalk.blue.underline,
127
+ href: chalk.blue.underline,
128
+ listitem: chalk.white,
129
+ hr: chalk.gray,
130
+ paragraph: chalk.white,
131
+ // Enable reflow so paragraphs and list items wrap at WORD boundaries at
132
+ // the configured width, and continuation lines of list items align under
133
+ // the bullet content (not at column 0). Without this, marked-terminal
134
+ // emits unwrapped text and the terminal hard-wraps at its column limit,
135
+ // producing mid-word breaks like "busine\nss".
136
+ reflowText: true,
137
+ width: PROSE_WIDTH,
138
+ // tableOptions is consumed by our override below, not marked-terminal's default
139
+ // renderer (we replace it in the next marked.use call).
140
+ }));
141
+ // Override marked-terminal's table renderer with one that caps column widths to the
142
+ // terminal width + word-wraps long cell content. marked-terminal's default auto-sizes
143
+ // each column to fit its widest cell without constraint, so a single long Detail cell
144
+ // (e.g. a radar finding with a 200-char explanation) produces a 235-column table that
145
+ // the terminal then wraps visually, destroying the box layout.
146
+ marked.use({
147
+ renderer: {
148
+ table(header, body) {
149
+ const headerRow = parseMtTableRows(header)[0] ?? [];
150
+ const rawBodyRows = parseMtTableRows(body);
151
+ if (headerRow.length === 0)
152
+ return `${header}${body}\n\n`;
153
+ // Apply severity pills to EACH CELL before cli-table3 sees them. If we
154
+ // left severity for the outer pipeline (applySeverity after markdown),
155
+ // it would wrap "HIGH" (4 chars) with " HIGH " (6 visible chars) AFTER
156
+ // cli-table3 had already allocated a 4-char column — the extra 2 chars
157
+ // then push the next `│` border right and break every following row's
158
+ // alignment. Applying here keeps visible widths correct via string-width.
159
+ const bodyRows = rawBodyRows.map((row) => row.map((c) => applySeverityToCell(c)));
160
+ // Natural content width per column (stripped of ANSI escapes) AND
161
+ // minimum word width per column (so wrap points never cut mid-word).
162
+ const colCount = headerRow.length;
163
+ const naturalWidths = new Array(colCount).fill(0);
164
+ const minWidths = new Array(colCount).fill(1);
165
+ for (const row of [headerRow, ...bodyRows]) {
166
+ for (let i = 0; i < colCount; i++) {
167
+ const content = row[i] ?? '';
168
+ const visible = stripAnsi(content);
169
+ if (visible.length > naturalWidths[i])
170
+ naturalWidths[i] = visible.length;
171
+ // Longest whitespace-separated token — the minimum column width that
172
+ // wordWrap can respect without mid-word breakage.
173
+ for (const word of visible.split(/\s+/)) {
174
+ if (word.length > minWidths[i])
175
+ minWidths[i] = word.length;
176
+ }
177
+ }
178
+ }
179
+ const termWidth = process.stdout.columns || 120;
180
+ const colWidths = computeColWidths(naturalWidths, minWidths, termWidth);
181
+ // Decide whether row separators are needed. When every row fits on one
182
+ // visual line we suppress them (avoids stairstep on small tables, see
183
+ // earlier comment). When ANY row will wrap (some cell content exceeds
184
+ // its allocated column width), the wrap line otherwise looks like a
185
+ // separate empty row to the reader — so re-enable thin separators
186
+ // between body rows. The header/body separator is suppressed in both
187
+ // cases (the cyan-bold header colour already separates them visually).
188
+ let anyRowWraps = false;
189
+ for (const row of bodyRows) {
190
+ for (let i = 0; i < colCount; i++) {
191
+ const visible = stripAnsi(row[i] ?? '');
192
+ // colWidths[i] = content width + 2 padding. Wrap happens when the
193
+ // visible content exceeds the content width (colWidths[i] - 2).
194
+ if (visible.length > (colWidths[i] - 2)) {
195
+ anyRowWraps = true;
196
+ break;
197
+ }
198
+ }
199
+ if (anyRowWraps)
200
+ break;
201
+ }
202
+ const tableChars = anyRowWraps
203
+ ? {} // default chars — keeps `mid: ─` so wrapped rows have separators
204
+ : { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' };
205
+ const table = new Table({
206
+ head: headerRow,
207
+ colWidths,
208
+ wordWrap: true,
209
+ wrapOnWordBoundary: true,
210
+ style: { head: ['cyan', 'bold'], border: ['gray'] },
211
+ chars: tableChars,
212
+ });
213
+ for (const row of bodyRows)
214
+ table.push(row);
215
+ return zebraStripe(table.toString()) + '\n\n';
216
+ },
217
+ },
218
+ });
219
+ /**
220
+ * Apply subtle dark-gray background to every other body row.
221
+ *
222
+ * Since we suppressed the `├─┼─┤` separators (avoids stairstep on variable-height
223
+ * rows), the rows would otherwise visually blend together. Zebra striping
224
+ * distinguishes them without adding horizontal rules.
225
+ *
226
+ * Approach — post-process the rendered cli-table3 string:
227
+ * - First `│…│` line after the `┌─┐` top border = header (pass through).
228
+ * - Subsequent `│…│` lines up to the `└─┘` bottom = body.
229
+ * - A body line is a NEW row start when the first column (between the first
230
+ * two `│` chars) contains non-whitespace (cli-table3 pads continuation
231
+ * lines of multi-line cells with whitespace in non-wrapping columns).
232
+ * - Odd row indices get wrapped with `\x1b[48;5;235m … \x1b[49m`. Mid-line
233
+ * bg-resets (e.g. when a severity pill's `\x1b[49m` ends) are rewritten
234
+ * to re-apply the zebra bg so the reset doesn't punch a hole after the pill.
235
+ */
236
+ const ZEBRA_BG = '\x1b[48;5;235m'; // dark gray — subtle but visible
237
+ const ZEBRA_RESET = '\x1b[49m';
238
+ function applyZebraBg(line) {
239
+ // Re-apply zebra bg after any in-line bg-reset or full-reset so the stripe
240
+ // survives through severity pills and inline-code spans.
241
+ const reZebraed = line
242
+ .replace(/\x1B\[49m/g, ZEBRA_BG)
243
+ .replace(/\x1B\[0m/g, '\x1B[0m' + ZEBRA_BG);
244
+ return ZEBRA_BG + reZebraed + ZEBRA_RESET;
245
+ }
246
+ function zebraStripe(tableText) {
247
+ const lines = tableText.split('\n');
248
+ const result = [];
249
+ let sawTop = false;
250
+ let sawHeader = false;
251
+ let rowIndex = -1;
252
+ for (const line of lines) {
253
+ const clean = stripAnsi(line);
254
+ if (clean.startsWith('┌')) {
255
+ sawTop = true;
256
+ result.push(line);
257
+ continue;
258
+ }
259
+ if (clean.startsWith('└')) {
260
+ result.push(line);
261
+ continue;
262
+ }
263
+ if (sawTop && !sawHeader && clean.startsWith('│')) {
264
+ // First `│…│` line after top border = header. Pass through (cyan+bold already applied).
265
+ result.push(line);
266
+ sawHeader = true;
267
+ continue;
268
+ }
269
+ if (sawHeader && clean.startsWith('│')) {
270
+ // Detect new row: first cell (between first and second `│`) has non-whitespace.
271
+ const m = clean.match(/^│([^│]*)│/);
272
+ const firstCellTrimmed = m ? m[1].trim() : '';
273
+ if (firstCellTrimmed !== '')
274
+ rowIndex++;
275
+ if (rowIndex >= 0 && rowIndex % 2 === 1) {
276
+ result.push(applyZebraBg(line));
277
+ }
278
+ else {
279
+ result.push(line);
280
+ }
281
+ continue;
282
+ }
283
+ // Pre-table blank lines, etc. — pass through.
284
+ result.push(line);
285
+ }
286
+ return result.join('\n');
287
+ }
288
+ export function renderMarkdown(input) {
289
+ const result = marked.parse(input);
290
+ return typeof result === 'string' ? result : input;
291
+ }