Skip to content

Authenticated command gateway + CLI + MCP wrapper (#57) - #40

Open
pplupo wants to merge 55 commits into
Euro-Office:mainfrom
pplupo:feature/cdp-gateway-cli
Open

Authenticated command gateway + CLI + MCP wrapper (#57)#40
pplupo wants to merge 55 commits into
Euro-Office:mainfrom
pplupo:feature/cdp-gateway-cli

Conversation

@pplupo

@pplupo pplupo commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Authenticated command gateway + CLI + MCP wrapper

Implements #57 (Local API / Daemon Mode): an authenticated, allowlisted command
gateway for Word/Cell/Slide/PDF, a thin CLI (eo-ctl) around it, and a thin MCP
service (eo-mcp) around that. Depends on the companion desktop-sdk PR (the
in-process CDP bridge this gateway drives).

Architecture

  • Transport: a real AF_UNIX SOCK_STREAM socket at
    $XDG_RUNTIME_DIR/eo-gateway-<uid>.sock (QLocalServer), mode 0600. One JSON
    object per connection, newline-terminated, one-shot request/response.
  • Auth: a fresh random token per gateway startup, written to
    $XDG_RUNTIME_DIR/eo-gateway-<uid>.token (mode 0600).
  • Allowlist: every command has a hand-rolled per-field scope validator and a script
    template (%%SCOPE%% as the sole substitution point, filled via
    QJsonDocument::toJson, never raw string interpolation) — matches #57's explicit
    "parameters passed via JSON scope injection, never raw string interpolation"
    requirement.
  • Target resolution: CDP scripts run via CCefView::SendGatewayDevToolsMessage
    against a view resolved through the app's own CAscApplicationManager view map — no
    external CDP target-matching by URL/title.
  • Command set: curated directly from the real sdkjs/*/apiBuilder.js source (not
    guessed) — every scope field, return-shape conversion (unserializable Api* objects
    converted to indices/ids/class-type strings), and error-contract decision
    (SCRIPT_EXCEPTION vs SCHEMA_INVALID) is backed by reading the actual vendored
    API. A handful of commands (word.addCheckBoxForm, cell.addShape,
    slide.applyTheme, slide.setBackground, slide.createTable, pdf.addStamp) are
    deliberately left unimplemented where the required factory/enum couldn't be
    confirmed in the vendored source, rather than guessed.
  • eo-ctl: thin CLI — connect <file> (resolves a file to a stable
    targetViewId, opening it if needed), call <command> --scope '<json>',
    allowlist. No business logic beyond framing requests; the one non-trivial piece
    (the connect/poll algorithm) is factored into a dependency-injected, unit-tested
    module (tools/eo-ctl/tests/connectlogic_test.cpp).
  • eo-mcp: a standalone Node MCP server (tools/eo-mcp/, not part of the CMake
    build) exposing three tools — gateway_connect, gateway_call,
    gateway_list_commands — talking directly to the gateway socket.

Command coverage

Word (13 families): document properties, content enumeration, insert/edit text,
character/paragraph formatting, search & replace, tables, styles, images/shapes,
page setup, bookmarks/hyperlinks, form fields, comments/track changes.

Cell (16 families): sheet management, range read/write, number formats/merge/clear,
copy/find/replace, formatting, conditional formatting, validation/named ranges,
AutoFilter, PivotTable, freeze panes, images/OLE, comments, insert/delete rows/cols,
recalculate, charts, SmartArt.

Slide (11 families): slide management, content enumeration, layouts/masters,
transitions, shapes, text formatting, images, tables, speaker notes, comments,
document properties.

PDF (5 families): form fields, annotations, text search/selection/extraction,
redaction, page operations.

Plus gateway.connect and gateway.listCommands meta-commands for target resolution
and command discovery.

Testing

46 automated ctest targets (one per command family + eo_ctl_connectlogic),
schema-validation-level coverage per the established pattern in this suite (round-trip
verification against a real running document is the CI/build gate's job). All 46 pass
in a full server build against this branch's own base
(feature/wayland-migration-derived); this specific rebase onto main was
conflict-resolved by tracing each commit's original diff rather than blindly accepting
either side, and re-verified with standalone syntax checks + the full 11-test
eo-mcp suite, but has not yet been re-validated with a full CEF/Qt build against
main specifically — flagging that honestly for CI/review to catch anything the
rebase might have missed.

Docs

Full command reference (every scope field, return shape, error case, CLI/MCP usage
examples) is being maintained in the companion DesktopEditors superproject PR's
gateway-api-reference.md.


Companion PRs: desktop-sdk#12 · DesktopEditors#72

pplupo added 30 commits August 18, 2026 20:31
Implements the allowlist table, GatewayCommandRunner (CDP Runtime.evaluate
over QtWebSockets), GatewayServer (authenticated unix-socket transport),
and the eo-ctl CLI per cdp-gateway-cli-plan.md.

Word command family: word.getTitle/setTitle, word.getCustomProperty/
setCustomProperty (ApiCore/ApiCustomProperties), matching
gateway-test-case-designs.md section B1.

Known gaps, not silently papered over:
- GatewayCommandRunner::ResolveTargetWebSocketUrl is a stub that always
  returns TARGET_NOT_FOUND; correlating a CDP target to an app view id
  via CAscApplicationManager's m_mapViews needs a follow-up look at the
  vendored CEF sources (see the KNOWN GAP comment in
  gatewaycommandrunner.h).
- GatewayServer is not yet instantiated from the app's startup path.
- word_document_properties_test.cpp currently only exercises schema
  validation (blocked on the above two gaps for full round-trip cases).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
…rtup

Drops QtWebSockets and the external-CDP-port approach in favor of
CCefView::SendGatewayDevToolsMessage (desktop-sdk). GatewayCommandRunner
now resolves targets via CAscApplicationManager::GetViewById directly
-- no CDP target-correlation step needed, since we already hold the
CefBrowser through the resolved view. Registers the Word command
family and starts GatewayServer once from main.cpp, right after
AscAppManager::startApp(), per investigation of the app's startup
sequence.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.getAllParagraphs/getAllTables/getAllDrawingObjects/getAllCharts,
backed by ApiDocumentContent's GetAll* methods (ApiDocument inherits
ApiDocumentContent, apiBuilder.js:3112). Each returns an array of
0-based indices rather than the Api* object instances the underlying
method returns, since those aren't JSON-serializable over CDP's
returnByValue -- corrected gateway-test-case-designs.md §B2 to match.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.addText/getText, backed by ApiDocumentContent.GetElement +
ApiParagraph.AddText/GetElement + ApiRun.GetText (apiBuilder.js:6028,
10146, 10315, 12883). AddText always appends a new run rather than
editing one in place, per the underlying API's own documented
behavior -- not assumed, read directly.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.setBold/setItalic/setFontFamily/setColor, backed by ApiRun
methods (apiBuilder.js:12457,12550,12614; SetColor at 12507 forwards
to ApiTextPr.SetColor at 15918, whose non-ApiColor branch still
accepts plain r,g,b ints -- used directly rather than constructing an
ApiColor, since the scope's #RRGGBB hex already has to be decomposed
into r/g/b regardless). setColor's schema constrains scope.color to a

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
#RRGGBB pattern, matching gateway-test-case-designs.md §B4.6.
word.setJc/setSpacingBefore/setIndLeft, backed by ApiParagraph.GetParaPr
+ ApiParaPr methods (apiBuilder.js:10253,16580,16677,16863). All three
take twips (1/1440 inch), not points -- scope field renamed from the
plan's original 'points' to 'twips' to match the real unit, and
gateway-test-case-designs.md §B5 corrected accordingly. setIndLeft
allows negative twips (hanging indent), confirmed against SetIndLeft's
own lack of a sign check.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.search/searchAndReplace, backed by ApiDocument.Search/
SearchAndReplace (apiBuilder.js:7598,8253). Search returns the match
count rather than the underlying ApiRange[] (not JSON-serializable
over returnByValue, same issue as §B2) -- corrected
gateway-test-case-designs.md §B6 to match.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.addRow/addColumn/mergeCells/setStyle, backed by ApiTable methods
(apiBuilder.js:13589,13609,13666,13755,13811) and
ApiDocument.GetStyle (7091). tableIndex indexes GetAllTables() (same
space as word.getAllTables, §B2). AddRow/AddColumn take a cell to
insert relative to, not a row/column number -- resolved via
GetCell(rowIndex/colIndex, 0) then inserted after it. MergeCells
takes an ApiTableCell[] built from the fromRow/fromCol..toRow/toCol
rectangle. SetStyle takes an ApiStyle object, resolved via
ApiDocument.GetStyle(styleId).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.createStyle/getStyle/setStyleTextPr, backed by
ApiDocument.CreateStyle/GetStyle (apiBuilder.js:7091,7106),
ApiStyle.SetTextPr (15424), Api.CreateTextPr (27443). getStyle returns
a boolean (ApiStyle.Style is undefined for a nonexistent style) rather
than the unserializable ApiStyle handle. Resolved gateway-test-case-designs.md
§B8's two open decisions: duplicate createStyle is idempotent per
CreateStyle's own doc comment (confirmed in source), and getStyle's
wire shape is boolean not a handle.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.createImage/setWrappingStyle/setHorPosition, backed by
Api.CreateImage (apiBuilder.js:4674), ApiParagraph.AddDrawing (10486),
ApiDrawing.SetWrappingStyle/SetHorPosition (18651, 18771). Corrected
gateway-test-case-designs.md §B9: createImage's imageSrc is a URL or
base64 data URI per the method's own doc comment, not a local file
path as originally planned; added the missing paraIndex/target
paragraph the original design omitted.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.setHeaderText/setPageMargins/setPageSize, backed by
ApiDocument.GetFinalSection (apiBuilder.js:7191),
ApiSection.GetHeader/SetPageMargins/SetPageSize (13141,13181,13289),
Api.CreateParagraph (4575), ApiDocumentContent.AddElement (6052).
Dropped the originally-planned sectionIndex scope field -- ApiDocument
has no GetSection(index), only GetFinalSection() -- correct for every
single-section fixture in this document; corrected
gateway-test-case-designs.md §B10 accordingly.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.addBookmark/getBookmark/addHyperlink, backed by
ApiParagraph.GetRange (apiBuilder.js:10604) + ApiRange.AddBookmark
(1581), ApiDocument.GetBookmark (8907), ApiParagraph.AddText+
AddHyperlink (10578). Corrected gateway-test-case-designs.md §B11:
AddHyperlink takes no display-text parameter -- it wraps whatever text
is already in the paragraph -- so text is added first, then wrapped.
URL scheme allowlist (http/https/mailto) enforced at the gateway's own
schema layer as defense-in-depth.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.addTextForm/getAllForms/setFormsData, backed by
ApiDocument.InsertTextForm (sdkjs-forms/apiBuilder.js:452),
GetAllForms/SetFormsData (sdkjs/word/apiBuilder.js:8613,7963),
ApiFormBase.GetFormKey (25123). word.addCheckBoxForm intentionally
NOT implemented -- no confirmed insertion method exists in the
vendored source for a created ApiCheckBoxForm (AddInlineLvlSdt's own
type guard would reject it); flagged as an open item in
gateway-test-case-designs.md §B12 rather than guessed.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
word.addComment/getAllComments/setTrackRevisions/
acceptAllRevisionChanges, backed by ApiDocument.AddComment/
GetAllComments/AcceptAllRevisionChanges (apiBuilder.js:9583,8702,8856)
and ApiComment.GetText/GetAuthorName (27848,27877). AddComment adds
to the current selection, positioned via the same GetRange(0,0).Select()
pattern as §B12; getAllComments returns plain {text,author} objects
rather than the unserializable ApiComment handles.

This completes all 13 Word command families (§B1-§B13) per
cdp-gateway-cli-plan.md's build order, aside from the one deferred
word.addCheckBoxForm (§B12, flagged, not guessed). Next: the plan's
§6 per-editor build/deploy/regression gate before starting Cell.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Discovered as a real gap by the plan's §6 build/deploy gate: eo-ctl
built fine as a CMake target but had no install() rule, so it never
made it into the app's install/deploy bundle (cmake --install .),
unlike DesktopEditors/editors_helper. Added to the same
install(TARGETS ...) list at the app root, matching how those
targets are installed.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Gateway tests (gateway_word_*_test) are headless -- Qt Core only, no
CEF/display needed -- so they can run right after cmake --build in
the same containerized build step, gating cmake --install (and thus
packaging) on them passing, per cdp-gateway-cli-plan.md §6.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addSheet/getSheets/setActiveSheet/getActiveSheet/setVisible/
setName, backed by Api.AddSheet/GetSheets/GetSheet/GetActiveSheet
(sdkjs/cell/apiBuilder.js:777,799,867) and ApiWorksheet.SetActive/
SetVisible/SetName/GetName (8332,8314,8557,8546). getSheets/
getActiveSheet return sheet names rather than unserializable
ApiWorksheet handles, same pattern as the Word families. First Cell
family per cdp-gateway-cli-plan.md §4 build order -- Word is fully
implemented and passed its §6 build/deploy/test gate.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.setValue/getValue/getFormula, backed by ApiWorksheet.GetRange
(apiBuilder.js:8602) and ApiRange.SetValue/GetValue/GetFormula
(10161,10132,10241). Corrected gateway-test-case-designs.md §C2: there
is no separate SetFormula -- a string value starting with '=' becomes
a formula through the same SetValue call, so setValue's scope merges
down to one 'value' field. GetFormula returns '= ' + text with a
literal space, preserved rather than assumed away.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.setNumberFormat/merge/clearContents, backed by ApiRange methods
(apiBuilder.js:10828,10897,9759), all of which return null/undefined
on success. cell.merge's across field maps to Merge's isAcross param.
No read-back command exists yet for formatted display text (only
cell.getValue, which returns the raw value) -- C3.1's read-back
deferred to the §6 build/deploy gate rather than adding a speculative
command.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.copy/find/replace, backed by ApiRange.Copy/Find/Replace
(apiBuilder.js:11338,11616,11764) over ApiWorksheet.GetUsedRange()
(8524). Corrected gateway-test-case-designs.md §C4: Find/Replace are
per-range methods returning a single ApiRange|null (first match), not
a document-wide multi-match search -- the original 'returns 2
matches' expectation didn't correspond to any real capability of
these methods.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.setFontName/setFillColor/setBorders/setAlignHorizontal, backed by
ApiRange methods (apiBuilder.js:10513,10772,10575, SetBorders per §C3
investigation). SetFillColor/SetBorders need a real ApiColor built via
Api.CreateColorFromRGB (925), same hex decomposition as word.setColor.
setBorders' edge:"all" is handled by looping over the four outer
edges in script -- SetBorders itself has no such mode.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addColorScale/addDatabar/addIconSetCondition, backed by
ApiRange.GetFormatConditions (apiBuilder.js:12827) and
ApiFormatConditions.AddColorScale/AddDatabar/AddIconSetCondition
(21119,21229,21299), all returning a boolean rather than the
unserializable created-rule objects. AddIconSetCondition takes no
parameters -- dropped the originally-planned iconSet scope field
rather than guess at how icon-set type is actually configured.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addValidation/addDefName, backed by ApiRange.GetValidation().Add
(apiBuilder.js:12793,19898) and ApiWorksheet.AddDefName (8974).
Corrected gateway-test-case-designs.md §C7: validation type/operator
use the real internal enum strings (xlValidateWholeNumber, xlBetween),
not the originally-planned whole/between. AddDefName returns false
rather than throwing for an invalid name -- converted to a thrown
error in the command's script to keep this gateway's own error
contract consistent.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.applyFilter/getFilters. Corrected gateway-test-case-designs.md
§C8: ApiAutoFilter.ApplyFilter (apiBuilder.js:27379) only re-evaluates
an existing AutoFilter's criteria, it does not create one -- creating
one is ApiRange.SetAutoFilter() called with no args (12216), a toggle
(deletes if one already exists). getFilters returns GetFilterMode()'s
boolean rather than the unserializable ApiFilter[].

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addPivotTable/addPivotDataField/setPivotFieldFunction. Real
workflow is three distinct steps, not the single-call shape
originally planned: Api.InsertPivotExistingWorksheet (apiBuilder.js:
7676) creates + names the table (ApiPivotTable.SetName, 16782) so
ApiWorksheet.GetPivotByName (9412) can re-resolve it in later, separate
gateway calls -- there's no addressing by source range.
ApiPivotField.SetFunction (the field type AddFields works with) is a
hardcoded-error stub; the real setter is ApiPivotDataField.SetFunction
(17582), via AddDataField (16192) or GetDataFields (16633) re-resolve.
func takes capitalized enum strings (Sum, Average, ...), not
lowercase.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.freezeAt, backed by ApiWorksheet.GetFreezePanes().FreezeAt
(apiBuilder.js:9474,15780). Range resolved explicitly via
ws.GetRange() rather than FreezeAt's own string-overload, which
resolves against the active sheet, not necessarily this command's
target sheet.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addImage/addOleObject, backed by ApiWorksheet.AddImage/
AddOleObject (apiBuilder.js:9167,9228) -- placed by column/row + EMU
offset, not a range; imageSrc is URL/base64, matching word.createImage
(§B9). cell.addShape intentionally NOT implemented -- AddShape (9146)
needs real ApiFill/ApiStroke objects whose constructors weren't
confirmed in this file this pass; flagged in
gateway-test-case-designs.md §C11 rather than guessed.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addComment/addReply/setSolved, backed by ApiRange.AddComment
(apiBuilder.js:10969), ApiComment.AddReply/SetSolved/GetId
(14189,14019,13921). ApiComment has no public row/col accessor --
addReply/setSolved address a comment by the id addComment/GetId
returns, resolved via ws.GetComments().find(...), not by range as
originally planned (no real lookup for that exists).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.insertEntireRow/deleteEntireColumn, backed by
ApiWorksheet.GetRangeByNumber (apiBuilder.js:8642, 0-based, matching
this document's index convention) + ApiRange.GetEntireRow/
GetEntireColumn/Insert/Delete (12753,12775,11280,11241).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
pplupo added 25 commits August 18, 2026 20:32
cell.recalculateAllFormulas, backed by Api.RecalculateAllFormulas
(apiBuilder.js:7586) -- matches the plan's original design exactly,
no correction needed.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.addSeria/setSeriaName, backed by ApiChart.AddSeria/SetSeriaName
(apiBuilder.js:13641,13608), addressed via
ApiWorksheet.GetAllCharts()[chartIndex] (9359) -- the only real
addressing option, since ApiChart has no GetName/SetName at all.
AddSeria/SetSeriaName take range strings, not the single range/name
scalars originally planned for a couple of params.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
cell.getSmartArtClassType, backed by ApiSmartArt.GetClassType
(apiBuilder.js:13307/13294), addressed via
ApiWorksheet.GetAllDrawings()[index] (9271) -- SmartArt is one variant
of the generic Drawing typedef, there is no SmartArt-only collection.

This completes all 16 Cell command families (§C1-§C16) per
cdp-gateway-cli-plan.md §4's build order, aside from two deliberately
deferred commands (cell.addShape §C11, word.addCheckBoxForm §B12 from
the Word family) flagged rather than guessed. Next: the plan's §6
per-editor build/deploy/regression gate before starting Slide.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.addSlide/removeSlides/duplicate/moveTo, backed by
ApiPresentation.AddSlide/RemoveSlides/GetSlideByIndex
(sdkjs/slide/apiBuilder.js:1365,1564,1324), Api.CreateSlide (805),
ApiSlide.Duplicate/MoveTo (3853,3872). Corrected
gateway-test-case-designs.md §D1: RemoveSlides takes a contiguous
start+count range, not an arbitrary indices array, and returns false
rather than throwing for an out-of-range start -- converted to a
thrown error in the command's script. First Slide command family per
cdp-gateway-cli-plan.md §4 build order -- Word and Cell are both fully
implemented and passed their §6 gates.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.getAllShapes/getAllImages/getAllTables/getAllCharts, backed by
ApiSlide's matching GetAll* methods (apiBuilder.js:4140,4155,4197,
4169) -- matches the plan exactly. Returns index arrays rather than
the unserializable Api* object arrays, same pattern as word.getAllTables
(§B2).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.getLayout/applyLayout/addMaster, backed by
ApiSlide.GetLayout/ApplyLayout (apiBuilder.js:4092,3800),
ApiPresentation.AddMaster (1522), Api.CreateMaster (553).
applyLayout borrows an already-resolved layout from another slide
rather than a fabricated layoutId lookup that doesn't exist.
slide.applyTheme intentionally NOT implemented -- Api.CreateTheme
(640) needs three further factory-built scheme objects not confirmed
in this pass; flagged in gateway-test-case-designs.md §D3.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.setTransition, backed by Api.CreateSlideShowTransition
(apiBuilder.js:1075) + ApiSlideShowTransition.SetEntryEffect/
SetDuration (4848,4902) + ApiSlide.SetSlideShowTransition (4389).
Field renamed from type to entryEffect to match SetEntryEffect's real
param name; false return converted to a thrown error, same pattern as
elsewhere. slide.setBackground intentionally NOT implemented -- no
solid-fill factory (Api.CreateSolidFill or equivalent) was confirmed
in sdkjs/slide/apiBuilder.js; flagged in
gateway-test-case-designs.md §D4.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.createShape/setPosition/setRotation/setSize, backed by
Api.CreateShape (apiBuilder.js:870, has real fill/stroke defaults,
unlike Cell's AddShape), ApiSlide.AddObject (3621),
ApiDrawing.SetPosition/SetRotation/SetSize (6100,6511,6079). Corrected
gateway-test-case-designs.md §D5: CreateShape takes no position --
createShape's x/y map to a follow-up SetPosition call in the same
command script.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.setBold/setFontFamily, reusing ApiRun.SetBold/SetFontFamily
(shared classes with Word, confirmed absent from
sdkjs/slide/apiBuilder.js itself) with slide-side target resolution
via ApiShape.GetContent() (apiBuilder.js:6975) +
GetElement(paraIndex).GetElement(runIndex), same chain as word.setBold
(§B4). Added the missing paraIndex the original scope lacked.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.createImage, backed by Api.CreateImage (apiBuilder.js:825),
matching the Word/Cell precedent exactly (URL/base64, not a file
path). Same AddObject+SetPosition two-step as slide.createShape (§D5).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.addRow/mergeCells, backed by ApiTable.AddRow/MergeCells
(apiBuilder.js:7412,7314) on an existing table addressed via
slide.GetAllTables()[tableIndex] (§D2). Cell resolution via
GetRow(r).GetCell(c) (7295,7614) -- slide's ApiTable has no
GetCell(row,col) shortcut. slide.createTable intentionally NOT
implemented -- Api.CreateTable (947) places on whatever slide is
'current', with no public setter to target one by index; flagged in
gateway-test-case-designs.md §D8.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.addNotesText/getNotesText, backed by ApiSlide.AddNotesText
(apiBuilder.js:4331, calls ApiParagraph.AddText internally, same
append-only semantics as word.addText §B3) and
GetNotesPage().GetBodyShape().GetDocContent().GetElement(0).GetText()
for read-back. Added slide.getNotesText, not in the original design
at all -- without it D9.1's expectation had no allowlisted command to
verify with. Resolved D9.2's append-vs-replace question definitively:
appends, confirmed in source.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
slide.addComment/presentation.getAllComments, backed by
ApiSlide.AddComment (apiBuilder.js:3649) and
ApiPresentation.GetAllComments (1697, shared ApiComment class with
Word, §B13). Added missing x/y EMU position fields the original scope
lacked.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
presentation.getDocumentInfo/getCustomProperty, backed by
ApiPresentation.GetDocumentInfo (apiBuilder.js:1849, returns a plain
JS object -- already JSON-safe) and GetCustomProperties (1936, same
shared ApiCustomProperties class as Word §B1, no GetAll -- corrected
to singular getCustomProperty{name}).

This completes all 11 Slide command families (§D1-§D11) per
cdp-gateway-cli-plan.md §4's build order, aside from four deliberately
deferred commands (slide.applyTheme §D3, slide.setBackground §D4,
slide.createTable §D8) flagged rather than guessed, matching the same
discipline applied to word.addCheckBoxForm (§B12) and cell.addShape
(§C11). Next: the plan's §6 per-editor build/deploy/regression gate
before starting PDF.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
pdf.getAllFields/getFieldValue/setFieldValue, backed by
ApiDocument.GetAllFields/GetFieldByName (sdkjs/pdf/apiBuilder.js:1426,
1452) and the shared ApiBaseField.GetValue/SetValue (1921,1902) --
uniform across text/checkbox/combobox field types. getAllFields
returns field names, not the unserializable ApiField objects.
Corrected gateway-test-case-designs.md §E1: SetValue stringifies its
argument, so a checkbox's checked state is a string export value
(e.g. "Yes"/"Off"), not a JSON boolean; an unknown key surfaces as
SCRIPT_EXCEPTION from GetFieldByName's own unchecked .IsWidget()
access, no extra logic needed. First PDF command family per
cdp-gateway-cli-plan.md §4 build order -- Word, Cell, and Slide are
all fully implemented.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Adds pdf.addHighlight, pdf.addUnderline, pdf.addStrikeout, pdf.addFreeText,
pdf.addInk, and the read-back command pdf.getAllAnnots. Each creation command
maps to the matching Api.Create*Annot() factory plus ApiPage.AddObject();
ink paths are accepted as [x,y] pairs and converted to the {x,y} object shape
CreateInkAnnot requires. pdf.addStamp is deliberately not implemented --
AscPDF.STAMP_TYPES' real values were not located in the vendored source.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Adds pdf.searchText, pdf.setSelection, pdf.getSelectedText, and
pdf.recognizeContent. searchText's Quad results are already flat
JSON-safe arrays, unlike every other Api* read command in this file.
recognizeContent's ApiDrawing[] result is converted to class-type
strings, same read-back pattern used for annotations in §E2.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Adds pdf.addRedact (creates a pending redact annotation), pdf.searchAndRedact
(marks every doc-wide text match as pending), and pdf.applyRedact (strips
all pending redacts, document-wide -- there is no per-page apply).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Adds pdf.addPage, pdf.removePage, and pdf.getPageCount. addPage's ApiPage
result is converted to its index; removePage's false-on-out-of-range
return is converted to a thrown error for the consistent SCRIPT_EXCEPTION
contract. This completes the PDF command family (Word -> Cell -> Slide -> PDF).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Every gateway_*_test executable failed at runtime with 'libQt6Core.so.6:
cannot open shared object file' -- this is the first build that ever
actually exercised the ctest step (the Dockerfile's ctest line hadn't
reached the server checkout for any earlier gateway build), so this bug
was never caught before now. Unlike the app's real targets, these test
executables never go through set_default_options() (core/common.cmake)
or the app's install step, so they got no RPATH and no copy of Qt's
vcpkg-built .so files. Setting a directory-scoped CMAKE_BUILD_RPATH,
derived from Qt6::Core's actual imported location, fixes every target
in this file with one change.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
The compile step's verbose warning output was filling BuildKit's 2MiB
per-step log buffer before ctest's own results streamed, repeatedly
hiding per-test detail behind '[output clipped, log limit 2MiB reached]'.
Isolating ctest+install+package into a separate layer, reusing the same
build-cache-desktop and ccache mounts, keeps its log independent of the
compile step's noise.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
…'s LOCATION property

get_target_property(Qt::Core LOCATION) silently resolved to empty --
Qt's vcpkg-generated imported target only populates
IMPORTED_LOCATION_<CONFIG>, not the generic LOCATION, with no
CMAKE_BUILD_TYPE set. Deriving the lib dir directly from vcpkg's own
install layout instead (confirmed against the build log: every
vcpkg-built .so, Qt included, lands under
CMAKE_BINARY_DIR/vcpkg_installed/<triplet>/lib), plus a status message
so this is visible at configure time instead of failing silently again.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
CMAKE_BUILD_RPATH had no effect: core/common.cmake sets
CMAKE_BUILD_WITH_INSTALL_RPATH TRUE globally, which makes build-tree
binaries use each target's INSTALL_RPATH property instead of the
auto-computed build RPATH -- CMAKE_BUILD_RPATH is ignored entirely
under that setting. CMAKE_INSTALL_RPATH (seeding INSTALL_RPATH for
targets created afterward) is what actually takes effect without an
install step.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
…r RPATH

Ground truth from debugging: Qt6 here comes from aqtinstall
(third_party/install/qt/<version>/<arch>), not vcpkg -- vcpkg's install
tree has no Qt in it at all. QT_ROOT is already computed globally by
core/common.cmake for exactly this purpose; using it directly instead
of guessing vcpkg's layout. Also reverts the temporary debug find/readelf
commands added to the Dockerfile while diagnosing this.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
…lve a real id

Closes the gap found while designing the MCP wrapper (~/repos/eo-mcp-service-plan.md):
'eo-ctl connect <file>' previously only checked whether *a* gateway socket existed at
all, never whether the requested file was actually open, and returned an exit code
with no way for a caller to learn which view id corresponds to the file it just asked
to open.

- GatewayCommandRunner::ResolveViewIdByPath: pure resolver via
  CAscApplicationManager::GetViewByUrl, normalizing the path the same way
  CAscApplicationManagerWrapper::handleInputCmd does before a view's local-file URL is
  set.
- gateway.connect: new meta-command (same pattern as gateway.listCommands) exposing
  that resolver over the wire. Deliberately never opens anything itself.
- eo-ctl connect: now actually opens the file via SingleApplication (confirmed against
  main.cpp/cascapplicationmanagerwrapper.cpp: a second launch already forwards its file
  arg to a running instance, which opens it as a new tab via handleInputCmd -- cold
  start and already-running are handled by the same subprocess launch either way) and
  polls gateway.connect until it resolves, printing {"targetViewId": N} instead of
  just an exit code.
- The poll/launch-decision algorithm is factored into connectlogic.h/.cpp
  (EoCtl::ConnectAndResolveViewId), dependency-injected for unit testing without a live
  process -- see tools/eo-ctl/tests/connectlogic_test.cpp (6 cases).

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant