@cspeach/cli 1.1.49 → 1.1.51

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 (64) hide show
  1. package/dist/agent/cold-prune.js +1 -1
  2. package/dist/agent/conversation-prompt.js +52 -0
  3. package/dist/agent/loop.js +189 -5
  4. package/dist/agent/oversized-results.js +204 -0
  5. package/dist/agent/turn-error-ux.js +14 -0
  6. package/dist/cli.js +1 -1
  7. package/dist/commands/compact.js +6 -0
  8. package/dist/commands/config-set.js +1 -1
  9. package/dist/commands/export-audit.js +26 -3
  10. package/dist/commands/export-conversation.js +343 -0
  11. package/dist/commands/help.js +3 -0
  12. package/dist/commands/plan-boundary.js +5 -0
  13. package/dist/commands/plan-continue-gate.js +11 -0
  14. package/dist/commands/plan-continue.js +11 -0
  15. package/dist/commands/plan-startup.js +5 -1
  16. package/dist/cost/cost-log.js +4 -0
  17. package/dist/cost/system-block.js +25 -0
  18. package/dist/docx/doc-meta.js +9 -1
  19. package/dist/docx/markdown-to-docx.js +14 -1
  20. package/dist/lane/app-chain.js +24 -1
  21. package/dist/models/server-config.js +13 -0
  22. package/dist/projects/document-save.js +65 -8
  23. package/dist/projects/document-skills.js +32 -1
  24. package/dist/projects/workspace.js +6 -3
  25. package/dist/repl/builtin-commands.js +1 -1
  26. package/dist/repl/slash-completer.js +4 -0
  27. package/dist/repl.js +50 -16
  28. package/dist/session/cli-record.js +165 -0
  29. package/dist/session/fresh-start.js +19 -0
  30. package/dist/tools/_filesystem-shared.js +7 -0
  31. package/dist/tools/cut-markers.js +47 -0
  32. package/dist/tools/filesystem/extract-document.js +50 -2
  33. package/dist/tools/filesystem/file-read.js +2 -2
  34. package/dist/tools/filesystem/read-document.js +20 -4
  35. package/dist/tools/tool-output-read.js +5 -1
  36. package/dist/tools/write-pipeline.js +5 -0
  37. package/dist/ui/card-slot.js +18 -1
  38. package/package.json +121 -121
  39. package/dist/commands/project.js +0 -301
  40. package/dist/doctor/checks/zcspeach.js +0 -96
  41. package/dist/projects/email-template.js +0 -56
  42. package/dist/projects/merge-cca.js +0 -292
  43. package/dist/tools/local-files.js +0 -31
  44. package/dist/tools/project/cca_merge.js +0 -164
  45. package/dist/tools/subagent/schedule_create.js +0 -131
  46. package/dist/ui/error-emitter.js +0 -21
  47. package/dist/ui/header.js +0 -41
  48. package/dist/ui/ink-classifier-route.js +0 -19
  49. package/dist/ui/rich-error-box.js +0 -9
  50. package/dist/ui/sidebar.js +0 -12
  51. package/dist/ui/status-emitter.js +0 -19
  52. package/dist/ui/status-line.js +0 -62
  53. package/dist/ui/status-row.js +0 -38
  54. package/dist/ui/todo-panel.js +0 -85
  55. package/dist/ui/widgets/ask-form.js +0 -250
  56. package/dist/ui/widgets/ask-question-modal.js +0 -178
  57. package/fiori/zlas-inspection-lr/package.json +0 -26
  58. package/fiori/zlas-inspection-lr/ui5-local.yaml +0 -45
  59. package/fiori/zlas-inspection-lr/ui5.yaml +0 -41
  60. package/fiori/zlas-inspection-lr/webapp/Component.js +0 -12
  61. package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +0 -9
  62. package/fiori/zlas-inspection-lr/webapp/index.html +0 -35
  63. package/fiori/zlas-inspection-lr/webapp/manifest.json +0 -145
  64. package/live-check-2026-10-17-1343.txt +0 -41
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Cut-note guard for SAP writes (2026-10-22, owner go).
3
+ *
4
+ * Large tool results reach the model shortened, each with a note of ours:
5
+ * - agent/oversized-results.ts — "[result cut: N more chars …]" and
6
+ * "[removed: this result was too large to send …]";
7
+ * - agent/cold-prune.ts — "[result elided at cold start — N chars]";
8
+ * - tools/result-spill.ts — the spill preview: the pointer line ("… full
9
+ * output saved to …; read slices with tool_output_read(…)"), the omitted
10
+ * line ("[… lines A–B omitted …]" / "[… N lines omitted — tool_output_read
11
+ * offset=K …]") and a clipped line ("… [+N chars]");
12
+ * - the paging trailers (tool_output_read, file_read, read_document) and the
13
+ * @file attachment note "... [truncated, file exceeds 64 KB]".
14
+ * A model that writes back what it saw would write those notes into SAP in
15
+ * place of the real code. The write pipeline refuses any write whose
16
+ * arguments carry one of them, before the approval is spent or SAP is
17
+ * called. Only our own notes: "[image N]" and other bracket text can be
18
+ * legitimate ABAP and are never refused. A write without them is untouched.
19
+ */
20
+ export const CUT_NOTE_REFUSAL = 'This source still contains a note that part of an earlier read was cut or moved to a file. Read the full source first (in parts if needed), then write the complete text.';
21
+ const CUT_NOTE_RES = [
22
+ /\[result cut: \d+ more chars\./,
23
+ /\[removed: this result was too large to send — \d+ chars\./,
24
+ /\[result elided at cold start — \d+ chars\]/,
25
+ /full output saved to .*; read slices with tool_output_read\(path, offset, limit\)\]/,
26
+ /\[… lines \d+–\d+ omitted …\]/,
27
+ /\[… \d+ lines omitted — tool_output_read offset=\d+ …\]/,
28
+ /… \[\+\d+ chars\]/,
29
+ // tool-output-read.ts sliceTrailer; _filesystem-shared.ts rereadTrailer
30
+ // (file_read, read_document); workspace.ts ATTACHMENT_TRUNCATED_NOTE (@file).
31
+ /\.\.\. \(\d+ more lines — next offset=\d+\)/,
32
+ /\.\.\. \(truncated, \d+ more lines — re-read with offset=\d+\)/,
33
+ /\.\.\. \[truncated, file exceeds 64 KB\]/,
34
+ ];
35
+ /** True when `text` carries one of our cut / removed / spill notes. */
36
+ export function hasCutNote(text) {
37
+ return CUT_NOTE_RES.some((re) => re.test(text));
38
+ }
39
+ /** True when any string anywhere in `value` (objects, arrays) carries a cut note. */
40
+ export function argsHaveCutNote(value, depth = 0) {
41
+ if (typeof value === 'string')
42
+ return hasCutNote(value);
43
+ if (depth > 8 || value === null || typeof value !== 'object')
44
+ return false;
45
+ const items = Array.isArray(value) ? value : Object.values(value);
46
+ return items.some((v) => argsHaveCutNote(v, depth + 1));
47
+ }
@@ -50,11 +50,59 @@ async function extractPdf(buffer, maxPages) {
50
50
  }).join('\n\n');
51
51
  return { text: body, pages: total, warnings: hasText ? [] : [NO_TEXT_WARNING] };
52
52
  }
53
+ /**
54
+ * Longest line a document read hands to the model. A longer one is split
55
+ * into consecutive lines, so paging by line reaches every character; a real
56
+ * line (a dense PDF page is a few thousand chars) fits well inside it.
57
+ */
58
+ export const DOC_MAX_LINE_CHARS = 10_000;
59
+ /** Longest picture description kept in an `[image N: …]` marker. */
60
+ const IMAGE_ALT_MAX_CHARS = 120;
61
+ // 2026-10-22 customer bug — mammoth's default inlines every picture as a
62
+ // base64 data: URI (a 1.5 MB picture ≈ 670k tokens on ONE line), and the
63
+ // prompt was then too long for every later turn. Pictures never become text:
64
+ // mammoth gets an img converter that never reads the bytes, and turndown
65
+ // writes each <img> as a short numbered marker.
66
+ const NO_IMAGE_DATA = mammoth.images.imgElement(async () => ({ src: '' }));
67
+ /** Remove any data: URI left in the text (a linked or pasted picture). */
68
+ export function stripDataUris(text) {
69
+ return text.replace(/data:[a-z0-9.+\/-]*(?:;[a-z0-9=._-]+)*;base64,[a-z0-9+\/=]*/gi, '');
70
+ }
71
+ /**
72
+ * Split every line longer than `maxLineChars` into consecutive lines of at
73
+ * most `maxLineChars`, so nothing is lost. Text whose lines all fit is
74
+ * returned unchanged (the same string).
75
+ */
76
+ export function splitLongLines(text, maxLineChars) {
77
+ const lines = text.split('\n');
78
+ if (!lines.some((l) => l.length > maxLineChars))
79
+ return text;
80
+ const out = [];
81
+ for (const l of lines) {
82
+ if (l.length <= maxLineChars) {
83
+ out.push(l);
84
+ continue;
85
+ }
86
+ for (let i = 0; i < l.length; i += maxLineChars)
87
+ out.push(l.slice(i, i + maxLineChars));
88
+ }
89
+ return out.join('\n');
90
+ }
53
91
  async function extractDocx(buffer) {
54
92
  // Fix 2: esModuleInterop:true makes the default import correct — no interop guard needed.
55
- const { value: html } = await mammoth.convertToHtml({ buffer });
93
+ const { value: html } = await mammoth.convertToHtml({ buffer }, { convertImage: NO_IMAGE_DATA });
56
94
  const td = new TurndownService({ headingStyle: 'atx', bulletListMarker: '-' });
57
- const raw = td.turndown(html).trim();
95
+ let images = 0;
96
+ td.addRule('pictureMarker', {
97
+ filter: 'img',
98
+ replacement: (_content, node) => {
99
+ images++;
100
+ const alt = (node.getAttribute('alt') ?? '')
101
+ .replace(/\s+/g, ' ').trim().slice(0, IMAGE_ALT_MAX_CHARS).trim();
102
+ return alt ? `[image ${images}: ${alt}]` : `[image ${images}]`;
103
+ },
104
+ });
105
+ const raw = stripDataUris(td.turndown(html)).trim();
58
106
  // Turndown emits "- text" (3 spaces) for bullet items; collapse to "- text".
59
107
  // The regex anchors on a line-leading "-" so mid-line double spaces (e.g. "Col A Col B")
60
108
  // and indented nested bullets (leading whitespace is captured in $1) are both preserved.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { promises as fs } from 'node:fs';
12
12
  import { registerTool } from '../index.js';
13
- import { resolveSafePath, assertRealPathContained, PathOutsideRootError, DENYLIST_DESCRIPTION, isDenylistedPath, isReadOnlyProjectFile } from '../_filesystem-shared.js';
13
+ import { resolveSafePath, assertRealPathContained, PathOutsideRootError, DENYLIST_DESCRIPTION, isDenylistedPath, isReadOnlyProjectFile, rereadTrailer } from '../_filesystem-shared.js';
14
14
  import { teamReadTarget } from '../_team-work-path.js';
15
15
  import { spillToolResult, isUnderSpillRoot } from '../result-spill.js';
16
16
  import { basename } from 'node:path';
@@ -74,7 +74,7 @@ export async function fileReadHandler(args, ctx) {
74
74
  const numbered = window.map((line, i) => `${offset + i + 1}\t${line}`).join('\n');
75
75
  const truncated = allLines.length > offset + window.length;
76
76
  const trailer = truncated
77
- ? `\n... (truncated, ${allLines.length - (offset + window.length)} more lines — re-read with offset=${offset + window.length})`
77
+ ? `\n${rereadTrailer(allLines.length - (offset + window.length), offset + window.length)}`
78
78
  : '';
79
79
  // Task 17 (spec D12) — a window above 8 000 chars is saved to the session
80
80
  // spill file and previewed; today's trailer stays last, byte-identical. A
@@ -7,10 +7,15 @@
7
7
  import { promises as fs } from 'node:fs';
8
8
  import * as path from 'node:path';
9
9
  import { registerTool } from '../index.js';
10
- import { resolveSafePath, assertRealPathContained, PathOutsideRootError, DENYLIST_DESCRIPTION, isDenylistedPath, } from '../_filesystem-shared.js';
10
+ import { resolveSafePath, assertRealPathContained, PathOutsideRootError, DENYLIST_DESCRIPTION, isDenylistedPath, rereadTrailer, } from '../_filesystem-shared.js';
11
11
  import { teamWorkTarget } from '../_team-work-path.js';
12
- import { extractDocument, DocumentTooLargeError } from './extract-document.js';
12
+ import { extractDocument, DocumentTooLargeError, splitLongLines, DOC_MAX_LINE_CHARS, } from './extract-document.js';
13
13
  const MAX_LINES = 2000;
14
+ // 2026-10-22 customer bug — one read returned ~2.3M tokens and every later
15
+ // turn of the session failed. A read now has a hard size: long lines are split
16
+ // (DOC_MAX_LINE_CHARS) and one call returns at most DOC_MAX_CHARS_PER_READ
17
+ // chars (about 60k tokens); the offset trailer says where to read on.
18
+ export const DOC_MAX_CHARS_PER_READ = 200_000;
14
19
  const MAX_BYTES = 50 * 1024 * 1024; // 50 MB
15
20
  const MAX_PAGES = 150;
16
21
  const SUPPORTED = new Set(['.pdf', '.docx', '.xlsx']);
@@ -89,14 +94,25 @@ export async function readDocumentHandler(args, ctx) {
89
94
  : 'document';
90
95
  const header = `${args.path} — ${scale}`;
91
96
  const warnLines = result.warnings.map((w) => `[warning] ${w}`);
92
- const allLines = result.text.replace(/\n$/, '').split('\n');
97
+ const allLines = splitLongLines(result.text.replace(/\n$/, ''), DOC_MAX_LINE_CHARS).split('\n');
93
98
  const offset = Math.max(0, args.offset ?? 0);
94
99
  const limit = Math.min(args.limit ?? MAX_LINES, MAX_LINES);
95
100
  const window = allLines.slice(offset, offset + limit);
101
+ // Total cap: stop before the line that would pass it (always keep one line).
102
+ let used = 0;
103
+ let fit = 0;
104
+ for (const line of window) {
105
+ const cost = `${offset + fit + 1}\t${line}`.length + 1;
106
+ if (fit > 0 && used + cost > DOC_MAX_CHARS_PER_READ)
107
+ break;
108
+ used += cost;
109
+ fit++;
110
+ }
111
+ window.length = fit;
96
112
  const numbered = window.map((line, i) => `${offset + i + 1}\t${line}`).join('\n');
97
113
  const truncated = allLines.length > offset + window.length;
98
114
  const trailer = truncated
99
- ? `\n... (truncated, ${allLines.length - (offset + window.length)} more lines — re-read with offset=${offset + window.length})`
115
+ ? `\n${rereadTrailer(allLines.length - (offset + window.length), offset + window.length)}`
100
116
  : '';
101
117
  return { content: [header, ...warnLines, numbered].join('\n') + trailer };
102
118
  }
@@ -95,7 +95,11 @@ export async function toolOutputReadHandler(args, ctx) {
95
95
  next = i + 1;
96
96
  }
97
97
  const rest = all.length - next;
98
- return { content: out.join('\n') + (rest > 0 ? `\n... (${rest} more lines — next offset=${next})` : '') };
98
+ return { content: out.join('\n') + (rest > 0 ? `\n${sliceTrailer(rest, next)}` : '') };
99
+ }
100
+ /** The slice trailer (also refused in SAP writes: tools/cut-markers.ts). */
101
+ export function sliceTrailer(rest, next) {
102
+ return `... (${rest} more lines — next offset=${next})`;
99
103
  }
100
104
  registerTool({
101
105
  name: 'tool_output_read',
@@ -44,6 +44,7 @@ import { resolveWriteTransport } from './transport-resolution.js';
44
44
  import { noteWriteTransport } from '../repl/current-transport.js';
45
45
  import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
46
46
  import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
47
+ import { argsHaveCutNote, CUT_NOTE_REFUSAL } from './cut-markers.js';
47
48
  // ── Transcript visibility helpers (D16-display + tour slide-7, 2026-06-12) ──
48
49
  //
49
50
  // Rule 7 auto-snapshots always fired but the transcript never said so
@@ -334,6 +335,10 @@ function auditOutcome(syntax, stillExists, postCheckError) {
334
335
  */
335
336
  export async function runWritePipeline(args, ctx, spec) {
336
337
  const raw = args;
338
+ // ── 2026-10-22 — never write our own cut / spill notes into SAP ────────────
339
+ // First, before the approval is spent or SAP is called (cut-markers.ts).
340
+ if (argsHaveCutNote(args))
341
+ return refuse({ error: 'source_has_cut_note', detail: CUT_NOTE_REFUSAL });
337
342
  // ── Before the approval is spent (input / capability checks) ──────────────
338
343
  if (spec.beforeApproval) {
339
344
  const refusal = spec.beforeApproval(args, ctx);
@@ -9,6 +9,7 @@
9
9
  import { EventEmitter } from 'node:events';
10
10
  import { suspendActiveSpinner } from '../renderer/tool-widget.js';
11
11
  import { turnStatusEmitter } from './turn-status-emitter.js';
12
+ import { isRecordedCard, recordCliCard } from '../session/cli-record.js';
12
13
  /**
13
14
  * Claim exclusive input focus for the duration of `fn`: pause the in-place
14
15
  * tool spinner and freeze the ticking status row so only the card repaints.
@@ -81,9 +82,25 @@ class CardSlotEmitter extends EventEmitter {
81
82
  if (this.listenerCount('confirm') === 0) {
82
83
  return Promise.resolve({ value: null, cancelled: true });
83
84
  }
84
- return withFocusClaim(() => new Promise((resolve) => {
85
+ const shown = withFocusClaim(() => new Promise((resolve) => {
85
86
  this.emit('confirm', { ...req, resolve });
86
87
  }));
88
+ // /export docx conversation (2026-10-23): the plan gate, plan startup, lane
89
+ // and journey cards are kept on the session for the conversation export.
90
+ // Never the save or Word cards. Nothing here reaches the model.
91
+ if (!isRecordedCard(req.id))
92
+ return shown;
93
+ return shown.then((res) => {
94
+ const picked = res.value == null ? undefined : req.choices.find((c) => c.value === res.value);
95
+ recordCliCard({
96
+ question: req.title,
97
+ options: req.choices.map((c) => c.label),
98
+ ...(req.facts?.length ? { facts: [...req.facts] } : {}),
99
+ answer: picked?.label ?? (res.value == null ? 'Cancelled' : String(res.value)),
100
+ by: 'user',
101
+ });
102
+ return res;
103
+ });
87
104
  }
88
105
  on(event, listener) {
89
106
  return super.on(event, listener);
package/package.json CHANGED
@@ -1,121 +1,121 @@
1
- {
2
- "name": "@cspeach/cli",
3
- "version": "1.1.49",
4
- "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, signed skills, safety gates.",
5
- "keywords": [
6
- "abap",
7
- "sap",
8
- "ai",
9
- "cli",
10
- "ai-assisted-development",
11
- "s4hana",
12
- "abap-development",
13
- "claude",
14
- "anthropic"
15
- ],
16
- "homepage": "https://cspeach.dev",
17
- "bugs": {
18
- "email": "laeeq.siddique@cremencing.com"
19
- },
20
- "author": "Laeeq Siddique (Cremencing Solutions)",
21
- "license": "SEE LICENSE IN LICENSE",
22
- "private": false,
23
- "publishConfig": {
24
- "access": "public"
25
- },
26
- "type": "module",
27
- "bin": {
28
- "cspeach": "dist/index.js"
29
- },
30
- "scripts": {
31
- "dev": "tsx src/index.ts",
32
- "prebuild": "tsx scripts/prebuild.ts",
33
- "build": "tsc && node scripts/make-bin-executable.js",
34
- "install:global": "tsx scripts/install-global.ts",
35
- "pretest": "tsx scripts/sync-signing-key.ts || node -e \"console.warn('WARN: sync-signing-key skipped — using stub')\"",
36
- "start": "node dist/index.js",
37
- "typecheck": "tsc --noEmit",
38
- "test": "vitest run",
39
- "test:watch": "vitest",
40
- "test:smoke-e2e": "vitest run --config vitest.smoke-e2e.config.ts",
41
- "adt:smoke": "tsx scripts/adt-smoke.ts",
42
- "smoke:all": "pnpm adt:smoke",
43
- "bench:models": "tsx scripts/bench-models.ts",
44
- "bench:audit": "tsx scripts/bench-audit-task.ts",
45
- "bench:replay": "tsx bench/replay/run.ts",
46
- "fiori": "tsx src/tools/fiori/bin.ts",
47
- "prepare": "husky",
48
- "prepublishOnly": "node -e \"const deps=require('./package.json').dependencies||{}; for (const n of ['@cspeach/sap-client','@cspeach/register-core']) { const d=deps[n]||''; if(d.startsWith('file:')||d.startsWith('link:')){console.error('publish blocked: '+n+' is a local link ('+d+') — point it at the published version before npm publish'); process.exit(1)} }\""
49
- },
50
- "engines": {
51
- "node": ">=20.0.0"
52
- },
53
- "dependencies": {
54
- "@anthropic-ai/sdk": "^0.90.0",
55
- "@azure/msal-node": "6.0.1",
56
- "@cspeach/register-core": "0.4.2",
57
- "@cspeach/sap-client": "0.1.29",
58
- "@iarna/toml": "^2.2.5",
59
- "@inkjs/ui": "^2.0.0",
60
- "@inquirer/prompts": "^7.0.0",
61
- "@napi-rs/keyring": "2.1.0",
62
- "@noble/ed25519": "2.1.0",
63
- "@noble/hashes": "1.4.0",
64
- "@sap-ux/annotation-converter": "0.10.21",
65
- "@sap-ux/edmx-parser": "0.10.0",
66
- "@sap-ux/fe-fpm-writer": "0.43.7",
67
- "@sap-ux/fiori-elements-writer": "^3.0.39",
68
- "@sap-ux/fiori-freestyle-writer": "3.0.12",
69
- "@sap-ux/vocabularies-types": "0.15.0",
70
- "adm-zip": "0.5.17",
71
- "chalk": "^5.3.0",
72
- "cli-highlight": "^2.1.11",
73
- "cli-table3": "^0.6.5",
74
- "clipboardy": "^5.3.1",
75
- "cross-spawn": "^7.0.6",
76
- "diff": "^9.0.0",
77
- "docx": "^9.0.2",
78
- "ejs": "3.1.10",
79
- "fuse.js": "^7.0.0",
80
- "ink": "^5.0.0",
81
- "ink-text-input": "^6.0.0",
82
- "jose": "^5.9.0",
83
- "mammoth": "^1.8.0",
84
- "marked": "^11.2.0",
85
- "marked-terminal": "^6.2.0",
86
- "mem-fs": "2.1.0",
87
- "mem-fs-editor": "9.4.0",
88
- "node-machine-id": "^1.1.12",
89
- "openai": "^6.49.0",
90
- "puppeteer-core": "24.10.2",
91
- "react": "^18.3.0",
92
- "string-width": "^7.2.0",
93
- "turndown": "^7.2.0",
94
- "unpdf": "1.7.0",
95
- "zod": "^3.23.0"
96
- },
97
- "devDependencies": {
98
- "@sap-ux/ui5-middleware-fe-mockserver": "2.4.17",
99
- "@types/adm-zip": "0.5.8",
100
- "@types/cross-spawn": "^6.0.6",
101
- "@types/diff": "^7.0.2",
102
- "@types/ejs": "^3.1.5",
103
- "@types/node": "^20.17.0",
104
- "@types/react": "^18.3.0",
105
- "@types/turndown": "^5.0.5",
106
- "@ui5/builder": "4.3.2",
107
- "@ui5/fs": "4.0.6",
108
- "husky": "^9.1.7",
109
- "ink-testing-library": "^4.0.0",
110
- "pdf-lib": "^1.17.1",
111
- "strip-ansi": "6.0.1",
112
- "tsx": "^4.19.0",
113
- "typescript": "^5.7.0",
114
- "vitest": "^2.1.0"
115
- },
116
- "pnpm": {
117
- "onlyBuiltDependencies": [
118
- "esbuild"
119
- ]
120
- }
121
- }
1
+ {
2
+ "name": "@cspeach/cli",
3
+ "version": "1.1.51",
4
+ "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, signed skills, safety gates.",
5
+ "keywords": [
6
+ "abap",
7
+ "sap",
8
+ "ai",
9
+ "cli",
10
+ "ai-assisted-development",
11
+ "s4hana",
12
+ "abap-development",
13
+ "claude",
14
+ "anthropic"
15
+ ],
16
+ "homepage": "https://cspeach.dev",
17
+ "bugs": {
18
+ "email": "laeeq.siddique@cremencing.com"
19
+ },
20
+ "author": "Laeeq Siddique (Cremencing Solutions)",
21
+ "license": "SEE LICENSE IN LICENSE",
22
+ "private": false,
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "bin": {
28
+ "cspeach": "dist/index.js"
29
+ },
30
+ "scripts": {
31
+ "dev": "tsx src/index.ts",
32
+ "prebuild": "tsx scripts/prebuild.ts",
33
+ "build": "tsc && node scripts/make-bin-executable.js",
34
+ "install:global": "tsx scripts/install-global.ts",
35
+ "pretest": "tsx scripts/sync-signing-key.ts || node -e \"console.warn('WARN: sync-signing-key skipped — using stub')\"",
36
+ "start": "node dist/index.js",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "test:smoke-e2e": "vitest run --config vitest.smoke-e2e.config.ts",
41
+ "adt:smoke": "tsx scripts/adt-smoke.ts",
42
+ "smoke:all": "pnpm adt:smoke",
43
+ "bench:models": "tsx scripts/bench-models.ts",
44
+ "bench:audit": "tsx scripts/bench-audit-task.ts",
45
+ "bench:replay": "tsx bench/replay/run.ts",
46
+ "fiori": "tsx src/tools/fiori/bin.ts",
47
+ "prepare": "husky",
48
+ "prepublishOnly": "node -e \"const deps=require('./package.json').dependencies||{}; for (const n of ['@cspeach/sap-client','@cspeach/register-core']) { const d=deps[n]||''; if(d.startsWith('file:')||d.startsWith('link:')){console.error('publish blocked: '+n+' is a local link ('+d+') — point it at the published version before npm publish'); process.exit(1)} }\""
49
+ },
50
+ "engines": {
51
+ "node": ">=20.0.0"
52
+ },
53
+ "dependencies": {
54
+ "@anthropic-ai/sdk": "^0.90.0",
55
+ "@azure/msal-node": "6.0.1",
56
+ "@cspeach/register-core": "0.4.2",
57
+ "@cspeach/sap-client": "0.1.30",
58
+ "@iarna/toml": "^2.2.5",
59
+ "@inkjs/ui": "^2.0.0",
60
+ "@inquirer/prompts": "^7.0.0",
61
+ "@napi-rs/keyring": "2.1.0",
62
+ "@noble/ed25519": "2.1.0",
63
+ "@noble/hashes": "1.4.0",
64
+ "@sap-ux/annotation-converter": "0.10.21",
65
+ "@sap-ux/edmx-parser": "0.10.0",
66
+ "@sap-ux/fe-fpm-writer": "0.43.7",
67
+ "@sap-ux/fiori-elements-writer": "^3.0.39",
68
+ "@sap-ux/fiori-freestyle-writer": "3.0.12",
69
+ "@sap-ux/vocabularies-types": "0.15.0",
70
+ "adm-zip": "0.5.17",
71
+ "chalk": "^5.3.0",
72
+ "cli-highlight": "^2.1.11",
73
+ "cli-table3": "^0.6.5",
74
+ "clipboardy": "^5.3.1",
75
+ "cross-spawn": "^7.0.6",
76
+ "diff": "^9.0.0",
77
+ "docx": "^9.0.2",
78
+ "ejs": "3.1.10",
79
+ "fuse.js": "^7.0.0",
80
+ "ink": "^5.0.0",
81
+ "ink-text-input": "^6.0.0",
82
+ "jose": "^5.9.0",
83
+ "mammoth": "^1.8.0",
84
+ "marked": "^11.2.0",
85
+ "marked-terminal": "^6.2.0",
86
+ "mem-fs": "2.1.0",
87
+ "mem-fs-editor": "9.4.0",
88
+ "node-machine-id": "^1.1.12",
89
+ "openai": "^6.49.0",
90
+ "puppeteer-core": "24.10.2",
91
+ "react": "^18.3.0",
92
+ "string-width": "^7.2.0",
93
+ "turndown": "^7.2.0",
94
+ "unpdf": "1.7.0",
95
+ "zod": "^3.23.0"
96
+ },
97
+ "devDependencies": {
98
+ "@sap-ux/ui5-middleware-fe-mockserver": "2.4.17",
99
+ "@types/adm-zip": "0.5.8",
100
+ "@types/cross-spawn": "^6.0.6",
101
+ "@types/diff": "^7.0.2",
102
+ "@types/ejs": "^3.1.5",
103
+ "@types/node": "^20.17.0",
104
+ "@types/react": "^18.3.0",
105
+ "@types/turndown": "^5.0.5",
106
+ "@ui5/builder": "4.3.2",
107
+ "@ui5/fs": "4.0.6",
108
+ "husky": "^9.1.7",
109
+ "ink-testing-library": "^4.0.0",
110
+ "pdf-lib": "^1.17.1",
111
+ "strip-ansi": "6.0.1",
112
+ "tsx": "^4.19.0",
113
+ "typescript": "^5.7.0",
114
+ "vitest": "^2.1.0"
115
+ },
116
+ "pnpm": {
117
+ "onlyBuiltDependencies": [
118
+ "esbuild"
119
+ ]
120
+ }
121
+ }