0
0
Fork 0
Commit graph

41 commits

Author SHA1 Message Date
steel
320646b8fe fixup
Some checks failed
CI / build-and-test (18.x) (push) Has been cancelled
CI / build-and-test (20.x) (push) Has been cancelled
CI / build-and-test (22.x) (push) Has been cancelled
2026-08-19 18:39:47 +08:00
steel
729ba03593 fixup msp-con
Some checks failed
CI / build-and-test (18.x) (push) Has been cancelled
CI / build-and-test (20.x) (push) Has been cancelled
CI / build-and-test (22.x) (push) Has been cancelled
2026-08-03 17:45:08 +08: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
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
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
7d4eef801e chore: bump to 0.12.1 (clean tarball + review fixes) 2026-06-12 14:02:24 +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
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
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
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
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
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
Karstein Phobic Nyvold Kvistad
46b9c2967c fix(approve-gate): auto-approve when no TTY (stdio transport)
When the MCP server runs under Claude Code (stdio JSON-RPC), there is
no TTY attached -- the parent process owns stdin/stdout for the protocol
channel. phobiCS-tui spawned with stdio=inherit then has nothing to
render to and exits immediately, which gateOpForTool was interpreting
as "user rejected the change" -- silently blocking every modifying
operation (download_to_device, set_pou_code, create_*, etc.) with no
visible cause.

Detect non-TTY at gate entry. When phobiCS-tui isn't viable, log a
clear warning to stderr (visible in MCP server logs) and let the
operation proceed instead of falsely rejecting it. Applies to both
the generic gateOpForTool path and the set_pou_code runApproveGate
path.

User can pass --no-approve-edits to silence the warning when they
explicitly don't want the gate. The right long-term fix is to route
approval through the MCP elicitation protocol so the user gets the
prompt in their chat UI; that's a separate change.
2026-05-11 18:41:57 +02:00
Karstein Phobic Nyvold Kvistad
41db7d86e9 fix(device): non-blocking pre-flight, always-force rebind, ensure connect()
End-to-end download via MCP was failing for three reasons; all three
fixed here so the agent can drive the whole flow without manual IDE
clicks.

1. verify_device_reachable did a live gateway.perform_network_scan()
   on every call. That freezes the CODESYS UI thread for the full
   scan duration, so the user couldn't interact with the IDE during
   pre-flight (and a 60s tool timeout was easy to hit). Now reads
   gateway.get_cached_network_scan_result() first (instant, no UI
   block), only falls back to a live scan when no cache exists.
   Adds scan_source ("cache" | "live") to the JSON so callers know
   which path was taken.

2. rebind_device_to_scan short-circuited with "already-bound" when
   the resolved address matched the cached one. But the IDE's
   Select-Device + OK flow re-applies set_gateway_and_address even
   when the address didn't change -- that's what refreshes the
   device's scanned_* properties and re-establishes a session.
   Skipping it left the binding stale even after a successful
   rebind call. Always apply now.

3. ensure_online_connection created an online_application but never
   opened a device session. login() then waited on (or popped a
   dialog for) a session nobody had opened -- which is what the
   user was doing manually as "right-click device -> Connect".
   Adds _ensure_device_connected() helper that locates the project's
   PLC device, creates a ScriptOnlineDevice, and calls connect() if
   not already connected. Idempotent; best-effort (logs and falls
   through on failure so login() can still try its own path).
2026-05-11 18:09:34 +02:00
Karstein Phobic Nyvold Kvistad
4200b15b40 fix(device-tools): surface JSON output in tool responses
scan_network_devices, verify_device_reachable, and
rebind_device_to_scan_result all emit JSON between marker pairs in their
script stdout. The tool handlers wrapped that output with
formatToolResponse which returns only the success line on success --
hiding the actual data so the agent had nothing to act on.

Adds extractMarkerJson() helper that pulls the block between the start
and end markers and pretty-prints it, then returns the JSON in the tool
response. Now the agent sees the full scan results / reachability
candidates / rebind outcome.
2026-05-11 17:45:30 +02:00
Karstein Phobic Nyvold Kvistad
1feb76d009 fix(scripts): raw-string PROJECT_FILE_PATH in new device tools
The scan_network_devices, verify_device_reachable, and
rebind_device_to_scan templates used plain "..." strings for
PROJECT_FILE_PATH. With a UNC path like \\files\... the rendered Python
source had "\\files\..." which Python's source parser collapses to
"\files\..." (one backslash). ensure_project_open then saw the mangled
path as different from the (correctly-opened) primary, CLOSED the
primary project, and failed to reopen the corrupted path.

Other scripts use r"..." to bypass source-level escaping. Applying the
same to the three new ones.

Also bumps to 0.9.5.
2026-05-11 17:27:15 +02:00
Karstein Phobic Nyvold Kvistad
50b8b5763b chore: bump version to 0.9.4 2026-05-11 17:18:39 +02:00
Karstein Phobic Nyvold Kvistad
da49dc16fe chore: bump version to 0.9.3 2026-05-11 16:52:00 +02:00
phobicdotno
403a4336ae
fix: no headless fallback by default + clear stale lastError (v0.9.2) (#19)
* fix: disable headless fallback by default

Persistent mode silently fell back to running CODESYS with --noUI
whenever the launch refused (e.g. another GUI instance was already
running). That left the user staring at no window while the MCP
spawned hidden processes per command. Flip the default of
--fallback-headless to false so the MCP errors out loudly instead;
opt in explicitly if silent --noUI is actually wanted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: clear stale lastError on launch retry and successful ready

get_codesys_status kept reporting historical "Last Error: Refusing
to launch..." messages forever because lastError was set on failure
but never reset. Reset to null at the top of launch() so a retry
starts clean, and again when the watcher signals ready so a
successful re-launch wipes prior errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* release: v0.9.2 -- no headless fallback by default + clear stale lastError

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Karstein Phobic Nyvold Kvistad <karstein.kvistad@maritimerobotics.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:16:01 +02:00
Karstein Phobic Nyvold Kvistad
6ed62f1536 fix(deps): move 'diff' from devDependencies to dependencies (v0.9.1)
The TUI subpackage imports 'diff' at runtime (computeHunks in
src/tui/shared/diff.ts), so a global install via 'npm install -g'
was breaking with:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'diff'
    imported from .../dist/tui/shared/diff.js

devDependencies are not installed by 'npm install -g <pkg>', only
'dependencies' are. Move 'diff' to the right section.

This bug shipped silently in 0.5.0 onwards because the source-build
flow during dev install everything, masking it. First reproduces
when a user does a fresh global install -- which is the only flow
the published package should support.

Bumps to 0.9.1; tag + republish required to fix the live install.
2026-04-29 17:53:11 +02:00
Karstein Phobic Nyvold Kvistad
c157267b20 release: v0.9.0 -- live-values: sub-property descent + configurable interval, plus SP22 librarymanager fix
Bumps codesys-mcp-sp21-plus from 0.8.0 to 0.9.0.

Headline changes since v0.8.0:

  Live values:
    - Pump now descends one level into user-defined types. A var
      whose declared type resolves to another mirror .st file (a
      POU/FB/STRUCT/DUT) gets each of its members read as
      <var>.<member>. Caller-supplied resolveTypeMirror dep so the
      pump stays decoupled from the mirror layout.
    - --live-values-interval <ms> CLI flag (default 500, clamped
      [100, 60000]).

  add_library:
    - SP22 librarymanager attribute name fix. The SP22 stub
      documents 'library_manager' (underscore) but the actual
      injected attribute is 'librarymanager' (one word). Now
      probes both with SP-version-aware preference + adds an
      SP22-specific find_library dispatcher that walks
      lm.repositories when the documented signature is rejected.
2026-04-29 14:23:11 +02:00
Karstein Phobic Nyvold Kvistad
0962f93118 release: v0.8.0 -- phobiCS-tui v0.3 (inline live values)
Bumps codesys-mcp-sp21-plus from 0.7.1 to 0.8.0 and phobiCS-tui's
--version output from v0.2.0 to v0.3.0.

Headline changes since v0.7.1:

  TUI:
    - Viewer overlays each declared variable's live runtime value
      inline next to its declaration when the runtime is online.
    - Viewer joins multi-line (* ... *) comments across line
      boundaries (lines fully inside a block no longer get
      false-highlighted keywords).

  MCP server:
    - new --live-values CLI flag starts a 500 ms pump that reads
      runtime values for the variables of the user's current POU
      selection and writes them to tui-live-values.json next to the
      existing tui-state.json.
    - --approve-edits now also gates 4 runtime tools:
      write_variable, download_to_device, start_stop_application,
      restart_runtime_ssh (in addition to the 9 modifying project
      tools landed in v0.7.0).

No tag created here; npm publish has to be run from your terminal
because the npm 2FA passkey can't be driven through the bash tool.
2026-04-29 10:10:40 +02:00
Karstein Phobic Nyvold Kvistad
58a1573b80 release: v0.7.1 -- add_library wrapper-message fix + mock_watcher PermissionError retry
Two bug fixes since v0.7.0:
  e948922 fix(add_library): wrapper message reflects dedup vs add branch
  d01f6ed fix(test): retry on PermissionError in mock_watcher (Win race)
2026-04-29 09:07:52 +02:00
Karstein Phobic Nyvold Kvistad
7e427e93c5 release: v0.7.0 -- phobiCS-tui v0.2 (TUI keybinds + 9-tool approve-gate)
Bumps codesys-mcp-sp21-plus to 0.7.0 (main was at 0.6.4 from
intermediate releases) and phobiCS-tui's --version output to v0.2.0.

Headline changes since v0.6.4:

  TUI:
    - browser keybinds: / (filter), o (open in editor), d (cross-
      device diff), r (rescan), ? (help overlay)
    - approve mode: v toggles unified <-> side-by-side diff
    - viewer: ST syntax highlighting (keywords/types/comments/strings)
    - statusbar: stale-mirror indicator + small-terminal resize warn

  MCP server:
    - --approve-edits now gates ALL 9 modifying tools, not just
      set_pou_code: create_pou, create_property, create_method,
      create_dut, create_gvl, create_folder, delete_object,
      rename_object, add_library

No tag created here; npm publish has to be run from your terminal
because the npm 2FA passkey can't be driven through the bash tool.
2026-04-29 08:23:57 +02:00
Karstein Phobic Nyvold Kvistad
2ea39cb9a6 tui: scaffold phobiCS-tui ESM subpackage with ink 2026-04-28 20:52:02 +02:00
Karstein Phobic Nyvold Kvistad
722c09ed70 feat(ssh): restart_runtime_ssh -- restart codesyscontrol over SSH (password auth + sudo -S + port-listen liveness probe)
WHY: an unlicensed CODESYS Control runtime drops out of demo mode every 2
hours. systemctl is-active reports "active" even after the binary has
died, so a TCP probe on the runtime port (default 11740) is the only
honest liveness signal. The new tool gives MCP a one-call path to bring
the runtime back without dropping into a terminal.

Implementation choices:

- ssh2 (npm) instead of spawning ssh/sshpass: sshpass is not on the
  default Windows path, and the target Pi's sshd 10.x rejects pubkey
  signatures from this client environment in practice. ssh2 handles
  password auth + remote stdin + exit-code capture cross-platform.
- sudo -S with the password fed on remote stdin -- avoids a NOPASSWD
  sudoers entry on the PLC.
- After issuing the restart, polls 'ss -tln | grep :<port>' once per
  second until the listen port is up or livenessWaitSeconds expires.
  This is what catches a half-dead runtime that systemctl reports as
  fine.

Defaults match the only Pi we currently target (codesys-pi.local /
karstein / codesys123 / codesyscontrol / port 11740) but every field
is overridable.

Smoke-tested against codesys-pi.local: restart exit 0, port back up
after ~3s.
2026-04-28 16:50:19 +02:00
Karstein Phobic Nyvold Kvistad
4b54fbbd80 release: v0.4.9 -- --inspect, --ssh-version, --for-project, open_project pre-flight, per-project mirror dir, auto-mirror tool wraps, drop CODESYS Git 2026-04-27 22:33:45 +02:00
Karstein Phobic Nyvold Kvistad
f94e63ea8a release: v0.4.8 -- launcher allows multi-install (filter spawn-guard by exe path) 2026-04-27 20:58:00 +02:00
Karstein Phobic Nyvold Kvistad
1145fdc32e release: v0.4.7 -- drop broken postinstall, document the on-demand --print-config flow 2026-04-27 20:46:46 +02:00
Karstein Phobic Nyvold Kvistad
c1eabf08a9 release: v0.4.6 -- postinstall actually prints when installing from a repo cwd 2026-04-27 20:40:03 +02:00
Karstein Phobic Nyvold Kvistad
5c756147c1 release: v0.4.5 -- postinstall banner actually prints (npm 11+) 2026-04-27 20:37:29 +02:00
Karstein Phobic Nyvold Kvistad
7f7abaf996 release: v0.4.4 -- postinstall now tells you WHERE to paste the snippet 2026-04-27 20:32:31 +02:00
Karstein Phobic Nyvold Kvistad
3d75548ba9 release: v0.4.3 -- postinstall banner with .mcp.json snippet 2026-04-27 20:31:22 +02:00
Karstein Phobic Nyvold Kvistad
a11c58062f release: v0.4.2 -- --print-config CLI for auto-generated .mcp.json snippets 2026-04-27 20:23:59 +02:00
Karstein Phobic Nyvold Kvistad
fda997e546 release: v0.4.1 -- README restructure (fork notice, what's-new, npm install) 2026-04-26 22:48:21 +02:00
Karstein Phobic Nyvold Kvistad
0352b6c15e chore: sync lockfile to current name + drop stale branch reference from README 2026-04-26 22:48:11 +02:00
Luke
e374519fe1 Initial release: MCP server for CODESYS with persistent UI instance
v0.3.0 returning-watcher architecture — background thread polls for commands
and marshals execution onto the CODESYS UI thread, keeping the IDE fully
responsive between operations. File-based IPC with atomic writes, async mutex
command serialization, headless fallback, and 35 passing tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:59:16 +10:00