8 Commits
Author SHA1 Message Date
Daniele d84ce13dab ssh: fix sudo failing with "password required" (v6 / 1.1.0)
The connector asked for a sudo password on every privileged call and
failed whenever nobody answered it, which is every unattended run.

- Always probe `sudo -n` first, even for aliases set to sudo="prompt".
  `_sudo_prefix` used to elicit unconditionally, so a host granting this
  user NOPASSWD still opened an Agent Inbox prompt; with no human there
  it hit the client's 300s ELICITATION_DEADLINE, got back `cancel`, and
  surfaced as "sudo password required (user declined or timed out)".
  sudo refuses before running anything when it wants a password, so the
  probe is side-effect free.
- Strip a leading `sudo` from `command` and turn it into sudo=true.
  Agents write `exec(command="sudo systemctl restart x")`: with
  sudo=false that ran a tty-less sudo, with sudo=true it nested
  `sudo -S ... sudo ...` whose inner prompt had no tty either. Handles
  -u/-n/-S/-E/-H/-i/-k/-p/--; an unknown flag leaves the command alone.
  sudo_user now implies sudo=true.
- Run privileged commands as `sh -c '<command>'`, so `&&`, pipes and
  redirections are elevated too instead of only the first word.
- Add SSH_MCP_SUDO_PASSWORD (optional, secret) for unattended runs. It
  is consulted only after `sudo -n` proved a password is needed, so on a
  NOPASSWD host it never lands in the command's own stdin.
- Actionable errors for every sudo failure mode, and a `hint` on a
  nested sudo we could not peel off.

General review of the same server:

- Drain stdout and stderr together and make timeout_sec a real
  wall-clock deadline. Both streams share one SSH channel window, so
  reading stdout to EOF first stalled once a chatty stderr filled it.
  Command stdin is now closed after the optional password.
- Queue messages that arrive while awaiting an elicitation reply instead
  of discarding them, so a concurrent tools/call is not lost.
- Record the client's `elicitation` capability at initialize and fail
  fast when it is absent rather than blocking on a prompt nobody can
  answer.
- Tolerate null/string integer arguments (depth, max_results,
  context_lines, timeout_sec).
- Realign the version across both manifests: fragment.json said 5/1.0.4
  while connector.json said 2/1.0.1, so the feed was permanently ahead
  of the installed version and offered an update forever.

Also lands the pending docs work: CONNECTOR_MANIFEST_GUIDE.md as the
single source of truth, docs/connector.manifest_guide.md retired to a
pointer, CLAUDE.md audited against the repo, compile.py docstring fixed,
and an opencode.json config.
2026-09-03 23:48:00 +01:00
Daniele 78c78d6039 docs: merge SKALD.md into CLAUDE.md and remove it
CLAUDE.md and SKALD.md had drifted apart. CLAUDE.md still described a
hand-maintained connectors.json (pre-compile.py), an rsync deploy, 5
connectors, and a files[] that excluded connector.json.

CLAUDE.md is now the single working document, carrying every SKALD.md
section — full schemas for connectors.json / fragment.json /
connector.json, reserved enums, the auth/deliver/env/verify fields,
placeholder syntax, friendly tool names, icon conventions, file
integrity, local workflow and deploy — corrected against the actual
repo state: 18 connectors, the scripts/compile.py pipeline, and
connector.json included in the hashed files[].

It also names CONNECTOR_MANIFEST_GUIDE.md (repo root) as the
authoritative connector-authoring spec.
2026-08-24 17:37:20 +01:00
Daniele 5e4c41183c google-trends: fix broken MCP handshake and RSS articles (v2 / 1.1.0)
The server never implemented `initialize`, so every MCP client got
-32601 to its opening request and aborted before listing a tool. Also
missing: `notifications/initialized` and `ping`; `tools/list` returned a
bare array instead of {"tools": [...]}; notifications (no `id`) got a
full response written to stdout.

Rewritten to the shape the other local connectors already use (TOOLS
manifest + TOOL_DISPATCH, `_text_result` with isError, handle_request
returning None for notifications).

Also fixed:
- include_articles always returned nothing: trendspyg emits
  `news_articles`, the mapper read `articles`. explore_link kept too.
- fn(**arguments) turned a bad argument into -32603; handlers now take
  an args dict and coerce/clamp.
- errors were returned as successful results; now isError: true, with
  trendspyg's typed exceptions translated into actionable messages.
- browser calls could run ~100s (10 retries x 8s); capped at ~25s.
- verify.py shipped but connector.json declared no `verify` block; wired
  it and upgraded the script to a real RSS fetch.
- explore mutated trendspyg's ExploreEnvelope; dropped the no-op
  output_format param; trendspyg pinned >=1.6.0 and imported defensively.
2026-08-24 17:29:36 +01:00
Daniele 0c04ba2e4c whatsapp: persist chats and messages across restarts (v8 / 2.2.0)
WhatsApp delivers a history sync at login, not on reconnect, so an in-memory
store left the connector blind after every process restart (23 in two weeks).
The store is now mirrored to store/, next to auth/ and bind-mounted the same
way.

Not SQLite: node:sqlite needs Node 22 (unflagged only from 24) and the runtime
image ships Debian trixie's nodejs = 20.19.2; better-sqlite3 is native and the
slim image has no toolchain. So an append-only JSONL log for messages plus a
debounced JSON snapshot for chats/contacts, both written tmp+rename so a crash
cannot truncate them. Compaction on load and every 500 appends keeps the log
from creeping upward.

Also fixes a pre-existing duplication bug: pushMessage appended unconditionally,
re-adding every message a history re-sync redelivered. It now returns false on a
known id and the message is skipped in both the transcript and the log.

MAX_MSGS_PER_CHAT 200 -> 500. logout deletes store/ with auth/, so re-linking a
different phone cannot inherit the previous account's history.

Data at rest: message text is now written to the user's bind-mounted home.
Nothing but session keys was persisted before.

Verified on skald-runtime:v4 with a seeded store: 751 lines with 50 duplicates,
a 700-message chat and a torn trailing line -> 701 loaded, compacted to 501, cap
applied, second run 501 -> 501 unchanged.
2026-08-23 18:22:50 +01:00
Daniele a2bbedad40 whatsapp: fix history sync + session corruption, Baileys 7.0.0-rc14 (v7 / 2.1.0)
Diagnosed from the live server (two users, two accounts, one shared log).

- history sync was gated off entirely on Baileys 6.7.x: the library derives
  shouldSyncHistoryMessage from syncFullHistory when unset, and the connector
  shipped syncFullHistory: false. Now both are passed explicitly, so behaviour
  no longer depends on the resolved Baileys version. browser -> ['Mac OS', ...]
  because PLATFORM_MAP only grants a desktop-grade sync to Mac OS / Windows.
- reconnects leaked their socket, leaving two writers over auth/: no end(), no
  removeAllListeners(). ~15 reconnects/day produced 1422 Bad MAC lines, all on
  the account's own LID device 0. Added teardownSock(), per-socket generation
  guards, and a single-slot reconnect timer with exponential backoff.
- undecryptable messages (stubType CIPHERTEXT) were stored with empty text and
  rendered as blank transcript lines; now labelled and counted. Text-less
  protocol frames are dropped.
- fetchLatestBaileysVersion() ran on every reconnect; now cached for 6h.
- libsignal's direct console.error spam collapsed into one counted line, log
  lines tagged with the linked account, console.log redirected off stdout.

Baileys pinned ^6.7.9 -> 7.0.0-rc14 (the range resolved to 6.7.24 for one user
and 6.17.16 for the other). RC is ESM-only, so index.js is now ESM.

Verified on skald-runtime:v4 (Node 20.19.2): initialize, tools/list, status,
list_chats, version fetch, QR, and logout -> teardown -> single reconnect.

Remaining gap: the store is still in-memory only, so synced history is lost on
the next process restart.
2026-08-23 18:15:56 +01:00
Daniele 040a0b2320 exa: fix verify.py rejecting every API key (v5 / 1.0.4)
Activating Exa always failed with "Exa API key is invalid or
unauthorized (HTTP 403)" — the key was never actually tested.

Two bugs stacked:

1. mcp.exa.ai is behind Cloudflare, which bans urllib's default
   Python-urllib/3.x agent with 403 / "error code: 1010" before Exa
   sees the request. verify.py sent no User-Agent and mapped any 403
   to "API key is invalid". Reproduced on the server with no key set
   at all — same "invalid key" message.

2. The probed endpoint cannot validate a key anyway: JSON-RPC
   initialize against the MCP endpoint returns 200 no matter what
   ?exaApiKey= carries (checked with a real key, a bogus key, and no
   key). Fixing only the headers would have flipped the bug to
   accepting every key, including garbage.

With a key, the probe is now a minimal POST to api.exa.ai/search with
the key in the x-api-key header — the only call that exercises the
credential (200 valid, 401/403 + Exa JSON error invalid, 402 out of
credits, 429 valid but throttled). With no key it probes MCP
initialize and reports reachability only, never validity. Both
requests send a User-Agent; the MCP one also sends
Accept: application/json, text/event-stream (else HTTP 406).

An opaque 401/403 with no Exa JSON error is now reported as "blocked
before reaching the API — the key was not tested", instead of blaming
the credential.

Also re-aligns manifest/fragment versions to 5 / 1.0.4 (were 2/1.0.1
vs 4/1.0.3; skald reads installed_version from the manifest, so the
update badge would never have appeared) and raises verify.timeout_secs
15 -> 20.
2026-08-20 23:02:30 +01:00
Daniele f02bb78ebd playwright: force --browser chromium (v2 / 1.0.1)
default 'chrome' channel requires system Google Chrome, missing on slim
Linux hosts; use the postinstall-downloaded Chromium build instead
2026-08-19 18:20:13 +01:00
Daniele fa8bbcb808 add playwright connector (v1 / 1.0.0) + commit pending docs reorganization
- new global mcp_local connector wrapping @playwright/mcp@0.0.79:
  index.js argv wrapper (--headless --isolated --no-sandbox) importing
  the package cli.js, postinstall downloads chromium-only browser,
  verify.js headless-launch probe, 24 tools with display names
- also carries the pending docs work: changelog extracted to
  CHANGELOG.md, manifest guide, SKALD.md/CLAUDE.md updates
2026-08-19 17:37:13 +01:00