feat(cli): ceki contract group — port ceki-agent.js (task 423)#6
Merged
Conversation
added 10 commits
June 17, 2026 07:11
ContractClient (httpx) + `ceki contract …` subparser covering all
ceki-agent.js commands: list/members/tasks/my-jobs/task/children/history,
create/comment/propose/vote, poll/watch, tools/raw.
- /mcp/agent endpoint derived from CEKI_API_URL; CEKI_AGENT_MCP_ENDPOINT
override preserved for backward compat
- Token: CEKI_AGENT_TOKEN primary, falls back to CEKI_API_KEY
- CEKI_CONTRACT_IDS default for tasks/create (csv / bracketed / json)
- benefitable "agent:N" → {type,value}; undefined fields stripped before send
- poll handles 429 explicitly (rate-limit 10/min/token), watch min 6s
- MCP unwrap: result.content[].text (json-parsed) or structuredContent
Tests: 33 new in tests/test_contract.py (benefitable, clean, env resolve,
MCP unwrap, tool name mapping, payload shape, polling, parser).
Version 2.18.0 → 2.19.0.
- ceki_sdk/timelog.py: TimelogClient wraps ContractClient (same /mcp/agent transport, env, auth) — start/stop/check by event_id - ceki_sdk/cli.py: new top-level group `ceki timelog` (NOT under contract) - timelog start <event_id> → MCP timelog-start - timelog stop <event_id> [--label] → MCP timelog-stop (duration server-side) - timelog check <event_id> → MCP timelog-check - tests/test_timelog.py: 13 tests (tool name mapping, --label payload, unwrap, error propagation, CLI parser, ensures it is NOT under contract) - README: ceki timelog section - pyproject: bump to 2.20.0
…t, sessions --json datetime (task 425) — v2.21.0 BUG-1 — Browser.type now accepts an optional CSS selector (-- 'ceki type $SID --selector input[type=email] <text>'). The SDK focuses the element via Runtime.evaluate before sending Ceki.typeText so native- flow signups (signup.live.com) land keystrokes on the intended input even when no prior click established focus. BUG-3 — screenshot()/snapshot() pass 'optimizeForSpeed: true' to Page.captureScreenshot and default the CDP timeout to 120s (up from 60s). Heavy pages routinely take 60+ seconds via the high-level wrapper; the bumped budget plus the speed flag bring captures back to sub-second on the same pages. BUG-4 — 'ceki sessions --json' / 'ceki my-browsers' / 'ceki search' now serialise datetimes via 'model_dump(mode="json")' so the JSON output is valid (started_at/ended_at as ISO strings, not Python datetime objects). Versions: 2.20.0 → 2.21.0 in pyproject.toml and __init__.__version__ (the latter was stale at 2.16.0).
…art/end/date/limit Audit of `tools/list` against live /mcp/agent revealed fields present in server schema but missing from the ceki-agent.js shim we ported. Adding them so we don't lose info clients could legitimately want to send: - create-contract-event: + `timezone`, `data` (arbitrary extra payload) - comment: + `start`, `end`, `date` - propose-correction: + `start`, `end`, `date` - get-event-history: + `limit` CLI mirrors with new flags: `--timezone`, `--data` (JSON), `--start`, `--end`, `--date`, `--limit`. Existing flags untouched, backward compatible. Tests: +9 (payload shape for new fields, parser coverage). 78 contract+cli tests pass. Verified smoke against clawapi.ittribe.org/mcp/agent: server accepts the new fields (403 / validation errors only when expected for our token). Version 2.21.0 → 2.22.0.
…task 425 BUG-1) — v2.23.0
Joe reproduced BUG-1 on prod 2026-06-18 (schedule 11722, session 2256,
signup.live.com): `ceki type ... --selector` still failed with
"ReferenceError: document is not defined" despite the v2.21.0 fix.
Root cause: the SDK ran `Runtime.evaluate(document.querySelector(...))`
before the keystrokes. On pages that register a service worker (login/
signup.live.com et al.) Chrome's CDP routes the bare Runtime.evaluate
to the service-worker execution context where `document` is undefined,
so the eval throws before focus ever runs and the typing never starts.
Drop the SDK-side Runtime.evaluate entirely. Forward `selector` inside
the existing Ceki.typeText params instead — the extension intercepts
that custom CDP method and focuses the matching element via
chrome.scripting.executeScript({target: {tabId, allFrames: true}, ...}),
which always runs in a page frame's isolated world (has `document`),
not in any SW. Cross-frame: scripting.executeScript fans out to every
frame and we accept a match in any of them, so iframe-hosted signup
forms (signup.live.com loads its email field inside an iframe) get
focused without the agent having to know which frame holds it.
Compat: needs extension ≥ 0.6.236 (handleTypeText accepts `selector`).
Older extensions ignore the new param and behave as before — selector
silently no-ops, keystrokes still dispatch but may not land. No-selector
path is unchanged on every extension version.
Tests:
- tests/test_type_keyboard_events.py: two new tests asserting
(a) selector path forwards inside Ceki.typeText with no Runtime.evaluate,
(b) no-selector path omits the `selector` param.
- full suite: 241 passed.
….24.0 Humanization is the default in both main and incognito sessions. Three ways to disable it: 1. CEKI_HUMAN_DISABLE=1 — global env, kills the humanizer SDK-wide 2. Browser.click(x, y, human=False) — per call (also navigate/type/scroll) 3. ceki click ... --no-human / --raw — per CLI invocation human=False bypasses the SDK-side humanizer timings AND sends `_ceki_raw` in the mousePressed CDP params so the extension skips mouse-jitter for that single command. typeText receives `human: None` and runs at raw keyboard cadence. CLI: --no-human / --raw available on root parser and on click/type/scroll/ navigate subparsers. Old `ceki type --natural` is now silent no-op (humanization is on by default; keep the flag for backwards compat with existing scripts but no longer document it).
…BUG-B) QA local-humanizer-toggle-typing was FAILing in both main and incognito with "OFF leg has jitter stddev=39.6ms / 224.9ms — humanizer leaked into raw call". Root cause: after task 427 the SDK made humanization default ON, but the CLI `type` parser kept `--natural` as a silent no-op while also no longer threading a per-call OFF for the default code path. Result: `ceki type` always humanized, and QA's contract for `type ... --natural` (ON) vs no flag (OFF) silently broke. Restored CLI `type` semantics: - default → flat keystrokes (human=False) - --natural → humanizer ON (human=None → SDK default profile) - --no-human / --raw → explicit OFF (symmetry with click/scroll/navigate) - --no-human wins over --natural Other commands (click, scroll, navigate) keep task 427 semantics (default ON, --no-human → OFF) — only `type` is opt-in because typing cadence noticeably slows scripted flows. Tests: tests/test_cli.py +4 (default / --natural / --no-human / override order). 41 CLI tests pass. Version 2.24.0 → 2.25.0.
Konstantin's decision: typing must be humanized by default in both modes. Task 428 BUG-B's real fix was the leak — `--no-human`/`human=False` not flattening — not flipping the default. 429 keeps the leak fix but restores default ON. Semantics post-429: - default (`ceki type ...`) → humanized (SDK natural profile) - `--no-human` / `--raw` / human=False → flat keystrokes for THIS call - `--natural` → no-op alias (default is already ON) - env `CEKI_HUMAN_DISABLE=1` → flat everywhere (unchanged) `_cmd_type` now delegates to `_human_flag` (same as click/scroll/navigate); no per-command override. Help text for `--natural` is SUPPRESSed. Tests: tests/test_cli.py updated to lock in new semantics (default → human=None, --natural → no-op, --no-human → human=False, --no-human wins over --natural). 41 CLI tests pass. Version 2.25.0 → 2.26.0.
README aligned with the post-429/430 final semantics: - typing AND mouse humanization are ON by default in both incognito and main. - per-call disable: SDK `human=False` / CLI `--no-human` / `--raw`. - `--natural` is a no-op alias, NOT how you turn humanization on. - env `CEKI_HUMAN_DISABLE=1` is the global kill-switch. - Fingerprint Tier-2 (UA/timezone/WebGL) stays OFF in main — separate from behavioral humanization, called out so users don't conflate them. Updated: - "Human Mode" section: rewrote intro, added per-call disable subsection. - CLI command table: `type` row no longer shows `[--natural]`; `click`, `navigate`, `scroll` rows now show `[--no-human|--raw]`.
Lint-only changes to make ruff check green: - ceki_sdk/_browser.py: split type() signature across multiple lines - ceki_sdk/cli.py: wrap two long writer/argparse lines - tests/test_contract.py: wrap ContractClient() ctor on the 4 poll cases - tests/test_browser_screenshot_format.py, tests/test_timelog.py: drop unused imports, sort import block ruff check . → All checks passed; pytest tests/ → 246 passed, 1 skipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ceki contractcommand group inceki_sdk(Python CLI) covering everythingagent-contract-tasks/scripts/ceki-agent.jsdoes: list / members / tasks / my-jobs / task / children / history / create / comment / propose / vote / poll / watch / tools / raw.ContractClient(httpx) overPOST /mcp/agent+ REST/api/agent/polling. MCP unwrap:result.content[].text(JSON-parsed) → fallbackresult.structuredContent.CEKI_API_URLderives both endpoints;CEKI_AGENT_MCP_ENDPOINT/CEKI_API_BASEoverrides preserved. Token:CEKI_AGENT_TOKENprimary, fallback toCEKI_API_KEY.CEKI_CONTRACT_IDS(csv / bracketed / json) for default contract(s).--benefitable agent:N→{type,value}; undefined fields stripped before send; polling handles 429 (rate-limit 10/min/token),watchenforces ≥6s interval.Test plan
tests/test_contract.py— 33 unit tests (benefitable / clean / env resolve, MCP unwrap, tool-name mapping, payload shape, polling 200 / list / dict / 429 / error, CLI parser).tests/test_cli.py— existing 36 tests still green (no regression in rental commands).Notes
agent-contract-tasks; removal is a separate task after this lands and works in production.CEKI_AGENT_TOKENseparate, fallback toCEKI_API_KEY) chosen because rental SDK usesCEKI_API_KEYwhile agent contract MCP uses Bearerag_*viaauth.agent.keymiddleware — likely different tokens for the same agent, so keep them separable, but fall back so a single-token setup keeps working.