0
0
Fork 0
Commit graph

269 commits

Author SHA1 Message Date
Karstein Phobic Nyvold Kvistad
33363fbfd0 fix(release_project_version): stop README corruption and silent Changelog no-op
BUG 1: the README.md version replace was content.replace(/v\d+\.\d+\.\d+\.\d+/g,
newVersion) -- a blanket sweep that rewrote every version-looking string in
the file, corrupting historical fix landmarks, build-archive snapshots, and
even a different project's synced-library version (observed live on
SeaLeopard's README v1.3.0.0 -> v1.3.1.0). Replaced with updateReadmeVersion:
only touches the version token on the first "# " title heading, or a single
"**Version:**" line (Lib001 convention). If no anchor is found (or the
Version-line anchor is ambiguous), nothing is changed and the reason is
reported -- no global-replace fallback.

BUG 2: appendChangelogEntry's "ownership guard" checked for the literal text
'Auto-generated by `bump_project_version`', but the intro this same tool
seeds into new files says 'Auto-appended by `bump_project_version` on
release.' -- the strings never matched, so every one of this tool's own
previously-created Changelog.md files (SeaLeopard, since its v1.0.0.0 seed)
was treated as foreign and silently skipped. The skip only logged to
stderr while release_project_version's caller printed a fixed "Changelog.md:
appended vX" success line regardless. Separately, a genuinely hand-maintained
Keep-a-Changelog file (Lib001's CHANGELOG.md, "## [Unreleased]" / "## [x.y.z.w]
- date") was never understood at all. Confirmed via SeaLeopard commits
3c87d18 / c98c264, which hand-repaired both the missing changelog entries and
the corrupted README.

Replaced with buildChangelogUpdate (pure) + appendChangelogEntry (I/O
wrapper): detects the existing file's heading style from its own headings
(not intro wording) and emits a matching entry -- "ours" style
(## vX.Y.Z.W -- date) inserted before the first existing entry, or
Keep-a-Changelog style (## [X.Y.Z.W] - date) inserted after any
"## [Unreleased]" section (or before the first version heading if none).
An unrecognized format is left untouched with a clear skip reason. The write
is verified by re-reading the file before release_project_version reports
success; a write that didn't happen is now reported as NOT appended.

Rebuilt dist/ (tsc + scripts copy) since the MCP server runs from there.

Tests: 16 new (update-readme-version, build-changelog-update,
append-changelog-entry), full suite: npx vitest --run -- 229 passed, 1
pre-existing unrelated failure (script-manager.test.ts trips on a local
__pycache__ dir under src/scripts, present before this change too).
2026-07-23 17:20:02 +02:00
Karstein Phobic Nyvold Kvistad
e334d5318c bump_project_version: manifest variable NAME = library namespace, value = version
Per feedback: emit '<Namespace> : STRING := "<version>";' (self-describing,
name carries the library) instead of sLibNN with the name inside the value.
Namespace sanitized to a valid IEC identifier; deduped by name; sVersion /
uiLibraryCount reserved-name guarded.
2026-07-23 10:53:31 +02:00
Karstein Phobic Nyvold Kvistad
d0ea3843e7 bump_project_version: library manifest as scalar STRINGs, not an array
Per feedback: emit one 'sLibNN : STRING := "Namespace Version"' per reference
(same style as sVersion, individually online-readable) instead of
asLibraries ARRAY[1..N] OF STRING(79). uiLibraryCount unchanged.
2026-07-23 10:43:31 +02:00
Karstein Phobic Nyvold Kvistad
bffe5b80a5 bump_project_version: write library manifest into _MCP_PROJECT_VERSION GVL
Enumerate all library-manager references (name + resolved version) and write
them into the runtime-readable GVL as uiLibraryCount + asLibraries ARRAY OF
STRING(79), alongside sVersion. Refreshed on every version bump so the running
PLC reports its full library manifest. Best-effort enumeration (soft-fails to
an empty manifest so a bump never fails); ARRAY omitted when empty. Enumeration
reuses the verified list_project_libraries API (has_library_manager -> lm.references).

Not yet active in a running MCP: needs a server restart (+ rebuild/republish if
scripts are bundled) and a test bump to verify manifest population and that
read_running_version_online still parses sVersion.
2026-07-23 10:17:02 +02:00
Karstein Phobic Nyvold Kvistad
3866be231d import_native: optional parentObjectPath to import under an object
project.import_native lands at the PROJECT ROOT (POU pool, visible only in
the POUs view), and CODESYS refuses to move root-level objects into an
application afterwards ('Cannot move X from <root>') -- a wrong-level bulk
import is unrecoverable by script. ScriptObject.import_native (API 3.4.4.0)
imports under a node; expose it via parentObjectPath so library subtrees
can be imported straight into e.g. 'Application/MRLib'.
2026-07-22 09:05:23 +02:00
Karstein Phobic Nyvold Kvistad
7c9133d4ff flush_editor_views: delete per-user .opt sidecar between close and reopen
CODESYS persists the open-editor window layout in
<Project>-<user>-<machine>.opt and restores it on project open -- so a
plain close/reopen (or even a full IDE restart) brings every accumulated
editor view straight back, as seen live: a fresh IDE hit 'low on system
resources' immediately after reopening a project whose .opt held ~60
views (647 kB sidecar). Deleting the per-user .opt (pure UI state,
regenerates clean; AllUsers.opt kept) makes the flush actually stick.
2026-07-22 08:28:40 +02:00
Karstein Phobic Nyvold Kvistad
7887e20199 Add editor-view pressure guard (auto project close/reopen every N edits)
Every scripted textual_declaration/textual_implementation write and object
creation opens an editor view in the visible IDE. The ScriptEngine has no
API to close views (ScriptCommands is lookup-only per ScriptSystem.pyi and
helpme-codesys ScriptingEngine docs; the WinForms menus are also invisible
to UI Automation). After ~40-60 scripted edits the IDE exhausts UI
resources ('Please close some views to free up resources') and every
subsequent script call times out at the IPC layer.

Mitigation: count edit-tool calls (set_pou_code, create_pou/method/
property/dut/gvl); before the Nth edit since the last flush, run
flush_editor_views.py which saves, closes and reopens the primary project
-- disposing all editor views in seconds. Threshold configurable via
CODESYS_EDITOR_FLUSH_THRESHOLD (default 20, 0 disables). The flush result
is surfaced as a NOTE/WARN line in the tool response.
2026-07-22 07:56:58 +02:00
Karstein Phobic Nyvold Kvistad
327d639f2c create_pou/create_method: accept declarationCode/implementationCode; full IEC keyword guard (0.13.0)
Both tools silently DISCARDED declarationCode/implementationCode (zod strips
unknown keys): the object was created EMPTY, compiled clean, and did nothing.
Cost a full download/debug cycle on the fp-j1939 bench session 2026-07-17.
Now the params are real: applied after creation via ScriptTextualObject
textual_declaration/textual_implementation.replace() (same proven API as
set_pou_code), failing LOUDLY if provided code cannot be applied.

Also extend findReservedIecIdentifiers beyond time-suffix letters to the full
IEC 61131-3 ST reserved-keyword set, checked case-insensitively ('by'/'BY' are
both the FOR-loop step keyword -- 'by : BYTE;' failed on the bench 2026-07-16).
Sources: IEC 61131-3 (3rd ed.) keyword tables; CODESYS export-format keywords
(content.helpme-codesys.com/en/CODESYS%20Development%20System/_cds_keywords.html).
Standard-function names (MIN/MAX/ABS/...) deliberately excluded: not confirmed
compiler-rejected, and a false positive blocks legitimate code. Scanner now also
checks every name in comma-separated declaration lists and AT %address forms.
Guard wired into create_pou + create_method declarationCode, same as set_pou_code.

Tests: tests/unit/reserved-iec-identifiers.test.ts (8 cases); full suite 214 green.
2026-07-17 09:35:44 +02:00
Karstein Phobic Nyvold Kvistad
31f4bdf9ab rebind: accept IP-form matchAddress via set_gateway_and_ip_address (v0.12.5)
set_gateway_and_address raises 'Invalid address format' for 'ip[:port]'
strings. Detect IP form and route it to ScriptDeviceObject
.set_gateway_and_ip_address (helpme-codesys ScriptDeviceObject, since
3.5.8.0), which binds the block driver directly by IP -- needed for
SSH-tunnelled PLCs (e.g. 127.0.0.1:11740 -> lab PLC via jump host).
Live-verified against WAGO PFC200 10.0.0.202 through codesys1-prox.
2026-07-16 15:53:09 +02:00
Karstein Phobic Nyvold Kvistad
6bab7c6aa4 fix: bump_project_version -- never clobber a hand-maintained changelog, seed from latest v* tag; 0.12.4
Two field-observed defects in bump_project_version (hit on nmea2000-mr-library):

1. appendChangelogEntry wrote into 'Changelog.md', which on a case-insensitive
   Windows filesystem is the SAME file as a repo's hand-maintained
   CHANGELOG.md. With Keep-a-Changelog '## [x.y.z.w]' headings (no '## v'),
   the appender fell through to the append-at-end branch and dumped an auto
   entry at the bottom of the curated file. Now: if the existing file was not
   created by this tool (missing the auto-generated intro marker), skip the
   append with a warning instead of writing.

2. First-run seed ignored existing releases: with Project Information.Version
   unset but the repo tagged up to v0.8.0.0, a manual minor bump seeded
   1.0.0.0 -- out of series. Now the TS side derives a seed from the latest
   v* tag bumped at the requested level (new SEED_VERSION script param);
   the Python side falls back to the classic 1.0.0.0 only when no usable
   tag exists.

tsc clean, vitest 206/206.
2026-07-06 14:53:15 +02:00
Karstein Phobic Nyvold Kvistad
d29ebfcb8f docs+chore: online verification complete on PFC200 750-8216, bump to 0.12.3
All 12 online tools verified live; 8th drift fix (source_download temp/
stale-archive). Only import_text_list_file remains prep-test-only.
2026-06-12 16:03:23 +02:00
Karstein Phobic Nyvold Kvistad
22245b91af fix: source_download -- clean stale Archive.prj, app-level first unless compact
Live verification against the PFC200: device-level download_source on
SP21 writes its temp file into the CODESYS install dir (Program Files)
and dies with access-denied; the failed attempt leaves a locked
Archive.prj in the project dir that breaks the app-level fallback with
'file is being used by another process'. Now: remove stale Archive.prj
up front, use app-level source_download unless compact was explicitly
requested.
2026-06-12 16:02:42 +02:00
Karstein Phobic Nyvold Kvistad
fb76d89a8e docs+chore: live-verification complete for offline tools, bump to 0.12.2
28 of the 46 new tools verified against CODESYS V3.5 SP21 Patch 5; 7 SP21
stub-vs-runtime drift bugs found live and fixed. Remaining live checks:
import_text_list_file and the 12 online tools (need the WAGO PFC200).
2026-06-12 15:32:46 +02:00
Karstein Phobic Nyvold Kvistad
69394eabc1 fix: add_project_user -- idempotent create, probe password APIs, degrade loudly
SP21 removed IScriptUser.change_password ('no longer supported'). The
script now probes change_password/set_password/reset_password and, if
none works, saves the user WITH A WARNING instead of failing the whole
creation. Creation is idempotent so a half-failed prior run doesn't
block a retry on the duplicate name.
2026-06-12 15:32:24 +02:00
Karstein Phobic Nyvold Kvistad
c0e59165d2 docs: live verification results for SP21 coverage (26 tools verified, 6 drift fixes) 2026-06-12 15:26:09 +02:00
Karstein Phobic Nyvold Kvistad
29736e2963 fix: import_xml is reporter-first on SP21 -- all-keyword call
Same overload drift as export_xml: positional (path, None, folders) put
the path string in the reporter slot ('expected IImportReporter, got
str'). All args now passed by keyword with positional fallback.
2026-06-12 15:24:32 +02:00
Karstein Phobic Nyvold Kvistad
80ba96aae7 fix: project user tools log in to user management before modifying
Live verification on SP21: users.create raised "permission 'Modify' not
granted to user '(nobody)'". The scripts now log in as 'Owner' with empty
password (CODESYS default) when nobody is logged on; new optional
adminUser/adminPassword args override for protected projects.
2026-06-12 15:21:25 +02:00
Karstein Phobic Nyvold Kvistad
dbb0c2fb5a fix: exclude_from_build lives on build_properties on SP21, not flat on ScriptObject 2026-06-12 15:18:18 +02:00
Karstein Phobic Nyvold Kvistad
1342ead87f fix: compiled-library default extension is .compiled-library (hyphen) on SP21 2026-06-12 15:16:25 +02:00
Karstein Phobic Nyvold Kvistad
d777832aff fix: export_xml is reporter-first on SP21 -- all-keyword call + fail-loud existence check
Live verification: positional (objects, None, path, ...) bound 'objects'
into the reporter slot on SP21's overload, silently switching export_xml
into export-to-string mode -- tool reported success with no file written.
All args now passed by keyword; export_plcopen_xml and export_native
raise if the destination file does not exist after the call.
2026-06-12 15:14:21 +02:00
Karstein Phobic Nyvold Kvistad
52f8746268 fix: is_online_change_possible is a property on SP21, not a method
Live verification against CODESYS V3.5 SP21 Patch 5: calling it raised
'bool is not callable'. Handle both the property (SP21) and callable
(stub-documented) shapes.
2026-06-12 15:12:33 +02:00
Karstein Phobic Nyvold Kvistad
4c5ec53473 docs: README fork-changes narrative for SP21 coverage and v0.12.1 hardening 2026-06-12 14:27:56 +02:00
Karstein Phobic Nyvold Kvistad
7d4eef801e chore: bump to 0.12.1 (clean tarball + review fixes) 2026-06-12 14:02:24 +02:00
Karstein Phobic Nyvold Kvistad
b013c4ee53 fix: code-review findings -- dot-path name check, Python string-literal injection
1. find_object_by_path: final name verification used the original
   full_path instead of the dot->slash normalized segments, so
   dot-separated paths ('Application.MyPOU') traversed correctly but
   failed the final check and returned None.
2. User-arbitrary values (plcPath/plcDirectory, passwords, comments,
   project-info fields, device parameter name/value, task event,
   device credentials) were interpolated into r"..."/r"""...""" Python
   literals unescaped -- a quote or triple-quote in the value broke the
   generated script (or injected code). Templates now take pre-escaped
   literals via pyStringLiteral().

Reviewed-range: dead49a..e9aa714. Third reviewer finding (task.priority
must be int) was rejected: SP21 ScriptTaskConfigObject.pyi types the
priority/interval/interval_unit setters as str.
2026-06-12 14:02:22 +02:00
Karstein Phobic Nyvold Kvistad
e9aa71415e fix: ASCII-fy add_library.py comment, clean dist/scripts on build, global ASCII test
- add_library.py line 49 had UTF-8 box-drawing dashes in a comment with no
  coding declaration -- latent IronPython 2.7 source-encoding risk.
- build now removes dist/scripts before copying, so deleted/renamed
  templates (compile_project.py.bak, probe_app_error_state.py,
  set_library_namespace.py) no longer ship in the npm tarball.
- script-manager test now asserts EVERY template is ASCII-only instead of
  per-phase lists.
2026-06-12 13:43:27 +02:00
Karstein Phobic Nyvold Kvistad
5fbe5687de docs+chore: README sections for phases 2-5, plan status, bump to 0.12.0
102 tools total. SP21 ScriptEngine functional coverage complete per plan
(SVN/Composer/Cloud deliberately out of scope; plug_module and trace
tools deferred).
2026-06-12 13:15:26 +02:00
Karstein Phobic Nyvold Kvistad
83691c0547 feat: add 7 project-user & misc-object tools (SP21 coverage, phase 5)
list_project_users, add_project_user, remove_project_user,
create_text_list, import_text_list_file, create_image_pool,
add_external_file.

API per SP21 ScriptUserManagement.pyi / ScriptTextListObject.pyi /
ScriptImagePoolObject.pyi / ScriptExternalFileObject.pyi.
Plan: docs/superpowers/plans/2026-06-12-sp21-api-coverage.md (phase 5).
2026-06-12 13:14:28 +02:00
Karstein Phobic Nyvold Kvistad
0f3bebe839 feat: add 9 device-config & task-config tools (SP21 coverage, phase 4)
list_device_parameters, get_device_parameter, set_device_parameter,
export_io_mappings_csv, import_io_mappings_csv, set_device_state
(enable/disable/simulation), get_device_identification, create_task,
configure_task. New shared helper find_device_object.

plug_module/unplug_module deferred: needs a module-slot test device to
verify the connector/index call shape.

API per SP21 ScriptDeviceObject.pyi / ScriptDeviceParameters.pyi /
ScriptTaskConfigObject.pyi.
Plan: docs/superpowers/plans/2026-06-12-sp21-api-coverage.md (phase 4).
2026-06-12 13:11:26 +02:00
Karstein Phobic Nyvold Kvistad
44a58ff2bf feat: add 5 application-build & object tools (SP21 coverage, phase 3)
application_build (generate_code/rebuild/clean), check_online_change,
move_object, get_signature_crc, set_exclude_from_build.

API per SP21 ScriptApplication.pyi / ScriptObject.pyi.
Plan: docs/superpowers/plans/2026-06-12-sp21-api-coverage.md (phase 3).
2026-06-12 13:07:06 +02:00
Karstein Phobic Nyvold Kvistad
fb7d886a33 feat: add 13 project lifecycle/interop tools (SP21 ScriptProject coverage, phase 2)
close_project, save_project_as, save_project_archive,
save_as_compiled_library, export_plcopen_xml, import_plcopen_xml,
export_native, import_native, get_project_info, set_project_info,
get_compiler_version, set_compiler_version_to_newest, clean_all.

API per SP21 ScriptProject.pyi; semantics cross-checked against
helpme-codesys.com/en/ScriptingEngine/ScriptProjects.html.
Plan: docs/superpowers/plans/2026-06-12-sp21-api-coverage.md (phase 2).
2026-06-12 13:04:34 +02:00
Karstein Phobic Nyvold Kvistad
6d95c4698d docs+chore: README online-tools table for phase 1, bump to 0.11.0 2026-06-12 12:52:57 +02:00
Karstein Phobic Nyvold Kvistad
3a012a7149 feat: add 12 online/runtime tools (SP21 ScriptOnline coverage, phase 1)
reset_application (warm/cold/origin), read_variables, write_variables,
force_variables, unforce_variables, list_forced_variables,
create_boot_application (online/offline), source_download, source_upload,
plc_file_list, plc_file_transfer, plc_file_delete.

API per SP21 ScriptOnline.pyi (ScriptOnlineApplication/ScriptOnlineDevice);
semantics cross-checked against
helpme-codesys.com/en/ScriptingEngine/ScriptOnline.html.
Plan: docs/superpowers/plans/2026-06-12-sp21-api-coverage.md (phase 1).
2026-06-12 12:52:17 +02:00
Karstein Phobic Nyvold Kvistad
a990cf23ee fix: stop interpolate() mangling $-sequences in param values
String.replace with a string replacement interprets $$/$& as regex
replacement patterns, corrupting IEC string literals like '$R$N' passed
through tool params (set_pou_code code bodies, write_variable values).
Use a function replacement so values pass through verbatim.
2026-06-12 12:52:17 +02:00
Karstein Phobic Nyvold Kvistad
aeb1b67ee9 docs: master plan for SP21 ScriptEngine full functional coverage (phases 1-5) 2026-06-12 12:41:20 +02:00
Karstein Phobic Nyvold Kvistad
549e40ffab debug: log sibling names when find_object_by_path misses a direct child 2026-06-12 12:41:20 +02:00
Karstein Phobic Nyvold Kvistad
dead49ada0 fix: never fall back to headless --noUI in persistent mode (v0.10.3)
Persistent mode now uses a LazyPersistentExecutor everywhere the launcher
is not running: --no-auto-launch before first use, after shutdown_codesys,
and after a launch conflict. The first tool call launches the VISIBLE IDE
and delegates; nothing silently spawns --noUI processes anymore.

Why: headless spawns pop modal dialogs nobody can see (tool calls just
abort), hold .project locks (stale .~u files), and leave orphaned
CODESYS.exe processes that then block the next launch. Diagnosed live on
2026-06-11 while deploying TestN2k_v2_Fable: open_project/download ran
headless via the --no-auto-launch fallback and burned ~30 min on invisible
dialogs, zombie PIDs and lock-file cleanup.

Headless execution now requires explicit opt-in: --mode headless or
--fallback-headless.

Also lands the task-configuration tools from the 2026-06-09 session that
were complete but uncommitted: list_tasks, add_pou_to_task,
remove_pou_from_task (+ their IronPython scripts).
2026-06-11 14:15:50 +02:00
Karstein Phobic Nyvold Kvistad
a037025303 docs: add OpenAI Codex CLI (config.toml) setup instructions
The server is a standard stdio MCP server and works with any MCP client, not
just Claude Code. Added a "Use with OpenAI Codex CLI" section covering the
~/.codex/config.toml location, the [mcp_servers.<name>] TOML table (the
equivalent of the --print-config JSON block), env-based credential injection,
tool_timeout_sec for slow CODESYS launches, and the codex mcp add shortcut.
Linked it from Quick Start step 3.
2026-06-10 09:59:20 +02:00
Karstein Phobic Nyvold Kvistad
fb8b5b0c67 fix(ide-bridge): reap CodesysMCPBridge.exe so it never orphans
registerIdeBridgeTools() spawned CodesysMCPBridge.exe but the SIGINT/SIGTERM shutdown handler never closed the client, so every orchestrator exit left the shim running. They piled up across sessions (7 observed live with no IDE open).

- ide-bridge: add findOrphanedBridgePids()/killOrphanedBridges() using dead-parent detection (only reaps shims whose parent process is gone, so live sessions are untouched); harden close() with a taskkill /F fallback; expose .pid getter.
- server: sweep orphaned shims at startup, track the bridge client and close() it on shutdown, and add a process.on('exit') taskkill safety net for non-signal exits (stdin EOF / fatal).

Verified end-to-end against compiled dist: real orphan detected+reaped, live-parent shim left alone. Build clean, 156 tests pass. Bump 0.10.1 -> 0.10.2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:22:11 +02:00
Karstein Phobic Nyvold Kvistad
d801038dda feat(project-tools): reject UNC paths with a clear, actionable error
CODESYS opens/saves projects from UNC paths (\server\share\...) only
unreliably -- it tends to fail late and opaquely. Add src/path-guard.ts
(isUncPath + uncPathError) and gate open_project, create_project,
save_project, and launch_codesys_with_project on it: each now returns
isError early telling the user to map a drive (net use Z: \server\share)
or copy the project to a local drive. Mapped drive letters and local
drives (incl. \?\C:\) are not treated as UNC, so existing workflows are
unaffected.

Also bumps version to 0.10.1 (first published release of the 0.10.x line;
includes the prior unreleased phobiCS-tui removal and add_device tool).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:02:48 +02:00
Karstein Phobic Nyvold Kvistad
8d22242507 feat(add_device): script backing the new add_device MCP tool
Pairs with the add_device MCP tool that landed in e1f47a1 (folded in
during the approve-gate rip). Wraps ScriptDeviceObject.add(name, device_id)
to programmatically attach a child device under a parent device:
'Modbus TCP Server' under an Ethernet adapter, 'Ethernet' under the top-
level PLC, etc.

Behaviour:
- Required inputs: PROJECT_FILE_PATH, PARENT_PATH ('/'-separated, e.g.
  'MainPLC' or 'MainPLC/Ethernet1'), DEVICE_NAME, TARGET_NAME (substring
  of device repository display name).
- Optional: TARGET_VERSION (exact, e.g. '4.5.0.0'). Omit -> highest-version
  match wins, mirroring update_device_type's resolver.
- Suppresses CODESYS prompt dialogs via PromptHandling.NONE (with int=0
  fallback for SP21).
- Idempotent: if a child with DEVICE_NAME already exists under the parent,
  no-ops with SCRIPT_SUCCESS rather than creating a duplicate or erroring.
- Refuses when:
  - parentPath isn't found in the project,
  - the object at parentPath isn't a device (only attaches under device-
    typed ScriptObjects),
  - TARGET_NAME (+ optional version) has no match in the device repo --
    error suggests inspecting Tools > Device Repository.
- On ScriptDeviceObject.add() failure prints the full traceback plus the
  two common causes (parent doesn't allow this child type; device
  descriptor needs an uninstalled library).

After add, saves the project. Save failure is a WARN, not a hard error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:57:20 +02:00
Karstein Phobic Nyvold Kvistad
e1f47a1c1c chore: rip approve-gate (TUI follow-up); fold add_device tool in gate-free
Knock-on cleanup from the phobiCS-tui nuke in 4b82b7c. With the TUI gone
the gate had no UI to render -- it just printed a stderr warning and
auto-approved on every call. Ripping it out kills ~470 lines of dead code
and a redundant CLI flag.

Removed:
- src/approve-gate.ts (whole file; runApproveGate + gateOpForTool + helpers)
- tests/unit/approve-gate.test.ts
- bin.ts: --approve-edits CLI option, approveEdits config wiring, startup log
- types.ts: ServerConfig.approveEdits field + its doc comment
- server.ts: import of runApproveGate/gateOpForTool, the `Approve edits:` log,
  16 `gateOpForTool({...}); if (blocked) return blocked;` blocks across
  every modifying MCP tool handler, and the lone `runApproveGate({...})`
  block in set_pou_code.

Folded in (Karstein's prior WIP, gate-free now per his explicit choice
"Single rip commit, I edit your WIP too"):
- New `add_device` MCP tool in server.ts that wraps ScriptDeviceObject.add
  for attaching child devices (Modbus TCP Server under Ethernet, Ethernet
  under PLC, etc.). The supporting `src/scripts/add_device.py` remains
  UNTRACKED in Karstein's working tree -- still his to commit separately.
  Without that script the tool will fail at runtime; with it, fine.

Verify:
- `npx tsc --noEmit` clean
- `npm run build` clean
- `npx vitest --run tests/unit/` -> 14 files, 119 tests, all pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:55:55 +02:00
Karstein Phobic Nyvold Kvistad
4b82b7c569 chore: nuke phobiCS-tui front-end (v0.10.0)
The phobiCS-tui CLI/UI is retired in favour of driving the codesys-mcp-sp21-plus
MCP tools directly from Claude Code (see https://docs.anthropic.com/claude-code).
The MCP server itself is unchanged; only the TUI front-end goes.

Removed:
- src/tui/ (browser + approve + shared + entry; 14 files)
- dist/tui/ (compiled output)
- tests/tui/ (14 .test.* files + the mini-mirror fixture tree)
- tsconfig.tui.json
- 3 superpowers plans/specs docs (2026-04-28 phobics-tui v0.1-v0.2, 2026-04-29 v0.3-live-values, 2026-04-28 tui-design)
- package.json: phobiCS-tui bin entry, build:tui script, TUI compile step in build, TUI typecheck step
- package.json: dependencies ink + react + diff (TUI-only); devDependencies @types/diff + @types/react + ink-testing-library
- README.md: ## phobiCS-tui section + ### Inline live values subsection

Git history side:
- Worktree .worktrees/phobics-tui removed (was on feature/phobics-tui-followup @ 7e427e9)
- Local + origin branches deleted:
  - feature/phobics-tui              (was 08ee361, 0 unmerged vs origin/main)
  - feature/phobics-tui-followup     (was 7e427e9, 0 unmerged vs origin/main)
  - feature/phobics-tui-v0.3-live-values (was 9f4dc48, 0 unmerged vs origin/main)
- All three branches were merged into main, so deleting refs loses no history --
  the commits remain reachable through main.

Knock-on (deliberately deferred):
- src/approve-gate.ts and the --approve-edits flag in src/bin.ts / src/server.ts
  still exist. With the TUI gone, the gate auto-approves at every prompt and
  prints a `[approve-gate] No TTY available -- phobiCS-tui cannot render` warning
  to stderr. The infrastructure also still has callers in the uncommitted
  add_device work, so a clean rip-out is left for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:48:28 +02:00
Karstein Phobic Nyvold Kvistad
465718e93b fix(launcher): revalidate stale "Refusing to launch" cache on get_codesys_status
Once launch() refused due to a foreign CODESYS, the error string was
cached in lastError and getStatus() returned that frozen snapshot
forever. Closing the foreign CODESYS did not update the status -- only
an MCP restart cleared it. From the user's POV: "every time CODESYS
HAS been open, you get a problem".

Fix: getStatus() now calls revalidateLaunchRefusal() first. If the
launcher is parked in 'error' state with a "Refusing to launch:"
prefix and findConflictingInstances() now returns empty, we transition
back to 'stopped' and clear lastError so the next status call / launch
attempt sees a fresh state. Other 'error' states (process died,
watcher timeout, exe not found) are not auto-cleared -- only the
launch-refusal cache, since that's the one that goes stale on its own
when the user closes the foreign window.

Bump 0.9.13 -> 0.9.14.

Tests: 2 new launcher unit tests pin the auto-clear behaviour and
verify unrelated 'error' states are NOT auto-cleared. 22/22 launcher
tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:37:02 +02:00
Karstein Phobic Nyvold Kvistad
74a1d894a4 feat(device): update_device_type tool (in-place device-type swap)
Wraps ScriptObject.update(device_id) per the CODESYS Forge snippet so
a project can be retargeted between device families without destroying
the Application/POU/library subtree. Used for the WAGO PFC200 ->
CODESYS Control for Raspberry Pi MC SL workflow documented in the new
deploying-plc-project-to-rpi skill in mr-engineering-skill.

Intentionally NOT a remove+add fallback: if update() raises (cross-
family swap the IDE can't do in-place), surface the error rather than
silently destroying the subtree the user wants to keep.

Inputs:
  projectFilePath     -- target .project
  targetDeviceName    -- substring of repo display name
  devicePath          -- optional: device under project root; auto-picks
                         the first routed device, then any top-level
                         device, when omitted
  targetVersion       -- optional: exact version (else latest)
2026-05-12 19:21:42 +02:00
Karstein Phobic Nyvold Kvistad
7b0c5db125 feat(launch): launch_codesys_with_project tool
Detached spawn of an arbitrary CODESYS.exe with a .project as CLI arg
and optional --Profile= override. Useful when you want an SP22-saved
project opened in an SP21 IDE for SIM/inspection work, or when this
MCP is bound to install A but you want install B to handle the open
without registering a second server.

The launched IDE is not managed by this MCP: no IPC, no watcher, no
shutdown_codesys. Validates the exe + project paths up front and
returns the new PID.
2026-05-12 18:57:07 +02:00
Karstein Phobic Nyvold Kvistad
885ef84215 chore: bump version to 0.9.11 (ide-bridge passthrough) 2026-05-12 18:45:24 +02:00
Karstein Phobic Nyvold Kvistad
02bc1d001a feat(ide-bridge): passthrough for CODESYS-shipped MCP bridge (SP22.10+)
Adds an opt-in --ide-bridge auto|on|off flag. When the CODESYS install
ships the bridge shim (CodesysMCPBridge.exe alongside CODESYS.exe in
SP22 Patch 1 onward) and the in-IDE plugin is loaded, the sp21-plus
server spawns the shim as a child process, fetches its tools/list, and
re-registers each bridge tool under an 'ide_' prefix that forwards
JSON-RPC verbatim. Gives us the bridge's authoring tools (which mutate
the live project graph and pop the affected POU into the editor view
immediately) while keeping our 48 watcher tools as the home of online,
runtime, SSH, symbol-config, and release-pipeline work.

Backward-compatible by design: SP19/SP21 installs don't ship the
bridge, so defaultExePath() returns null and registration silently
skips under mode=auto. Under mode=on the server fails loudly.

The bridge's stdout speaks newline-delimited MCP JSON-RPC, exactly
like our own stdio transport, so the client is a small subprocess
wrapper plus a minimal JSON-Schema -> Zod-shape converter (handles
string/number/boolean/array/object/enum + optionality, falls back to
z.unknown() for anything else). Verified end-to-end against an open
MCPTest2 project on SP22.10:

  Bridge initialize OK (protocolVersion=2024-11-05)
  IDE bridge attached. Registering 19 passthrough tool(s) with 'ide_' prefix.
  ide_get_active_app_as_path -> 'CodesysRpi.Plc Logic.Application'

Tools exposed (prefixed): browse_project_tree, check_for_errors,
create_or_replace_structured_text_object, replace_text_in_structured_text,
get_structured_text_content, get_active_app_as_path,
get_active_path_and_selection, create_folder, remove_object,
add_library, add_program_call_to_task, get_available_libraries_list,
get_libraries_referenced_in_application, get_library_documentation,
get_detailed_library_documentation, get_device_and_io_configuration,
search_in_files_by_regex, search_libraries_for_type, search_path_by_glob.
2026-05-12 18:39:21 +02:00
Karstein Phobic Nyvold Kvistad
4236ab07ad docs(readme): document 7 missing tools (network scan/rebind, device user mgmt, remove_library, read_running_version_ssh); bump tool count 41 -> 48 2026-05-12 16:42:15 +02:00
Karstein Phobic Nyvold Kvistad
19fbaef9f4 feat(device-mgmt): add_device_user + grant_object_access tools
Two new MCP tools to drive previously IDE-only workflows:

- add_device_user: add (or update password of) a user in the PLC
  runtime's live User Management via
  ScriptOnlineDevice.create_live_user_management() + add_user(). Required
  for OPC UA authentication on CODESYS Control SP16+, where the OPC UA
  server reads its UserIdentityToken policies from the runtime UserMgr.
  Handles add-vs-update by calling live_um.upload() to sync local cache,
  and falls back to set_user_password if add_user reports "already
  existing" (the upload() race).

- grant_object_access: set Access Control permissions on a project
  object for a user group. Maps to the IDE's Properties -> Access
  Control matrix. Wraps ScriptUserManagement.get_object_permission() +
  ScriptObjectPermission.set_permission_state(). Common use case: grant
  Everyone View+Modify on the Symbol Configuration so the downloaded
  OPC UA server can advertise UserIdentityTokens for clients.

Both tools follow the existing pattern: JSON markers in the script,
extractMarkerJson() to surface the output, ensure_project_open helper.

Also bumps grant_object_access timeout to 120s after observing that
first-touch project ACL operations can be slow.
2026-05-11 20:34:21 +02:00
Karstein Phobic Nyvold Kvistad
7bd4155c8a fix(ensure_project_open): drop SilentMode so project is visible in IDE
ensure_project_open opened projects with VersionUpdateFlags.NoUpdates |
SilentMode. SilentMode loads the project into the scriptengine context
only -- the IDE's project explorer stays empty, so the user can't see
which project the MCP is operating on (or whether it actually loaded).
download_to_device, set_pou_code, etc. all still worked, but the IDE
window looked blank, which was confusing.

Drop SilentMode. NoUpdates stays (don't auto-update libraries on open;
that would silently change project content). With this change, opening
a project via the MCP behaves like a normal File -> Open: the tree
populates, the user can navigate it, and we can still drive
modifications from script.
2026-05-11 19:07:38 +02:00