Commit Graph
41 Commits
Author SHA1 Message Date
Latte 945e40b5d8 fix(ci): smoke-test the image from inside its own container via docker exec
CI / lint (pull_request) Successful in 56s
docker / lint (pull_request) Successful in 54s
CI / test (pull_request) Successful in 1m2s
CI / publish (pull_request) Has been skipped
docker / test (pull_request) Successful in 57s
docker / docker (pull_request) Successful in 41s
The smoke test probed the server over the network, which fails on the
self-hosted runner: the job step runs in a container that shares the host
Docker socket but sits on a different network, so it can reach neither a
published host port (-p lands on the runner host) nor the server
container's bridge IP (different network → curl connect hangs, so the job
looked stuck rather than failing).

Probe from inside the server container with `docker exec` instead: it goes
through the Docker socket and runs in the server's own netns, where
127.0.0.1:8080 always works regardless of runner topology. The image ships
python + httpx, so no curl or extra image pull is needed. Drop the now
unnecessary -p publish.
2026-07-05 13:42:40 +02:00
Latte 0fdba707fe Merge pull request 'feat: production-ready remote MCP — Docker image, CI publish, per-client docs, OAuth mode' (#64) from feat/docker-serve into dev
CI / lint (push) Successful in 54s
docker / lint (push) Successful in 56s
CI / test (push) Successful in 1m5s
docker / test (push) Successful in 1m3s
CI / publish (push) Successful in 52s
docker / docker (push) Failing after 1m9s
CI / lint (pull_request) Successful in 52s
docker / lint (pull_request) Successful in 56s
CI / test (pull_request) Successful in 1m5s
docker / test (pull_request) Successful in 1m5s
CI / publish (pull_request) Has been skipped
docker / docker (pull_request) Failing after 1m8s
Reviewed-on: #64
2026-07-05 09:54:00 +00:00
Latte df9e06a3e7 docs: deployment guide + per-client setup + connect matrix
CI / lint (pull_request) Successful in 55s
docker / lint (pull_request) Successful in 56s
CI / test (pull_request) Successful in 1m6s
docker / test (pull_request) Successful in 1m4s
CI / publish (pull_request) Has been skipped
docker / docker (pull_request) Failing after 1m11s
- doc/deployment.md: run familiar as a remote MCP server — secure defaults,
  bearer vs OAuth modes, the full env-var reference, one-time login / token-store
  provisioning, the published images, docker compose, and TLS reverse-proxy
  blocks for nginx (proxy_buffering off + long read timeout), Caddy, and Traefik.
- doc/clients/: one page per client with exact working steps — Claude Code,
  VS Code (.vscode/mcp.json with a masked token input, and the workspace
  .mcp.json header gotcha), Mistral Le Chat, Claude Desktop + Cowork (mcp-remote
  bridge, per-OS config paths), and Claude web (oauth mode + external IdP, with
  "use Desktop + bearer" as the simpler alternative). Plus a clients/ index.
- README: a "Connect a client" matrix (client -> auth -> guide) and a compact
  "Deploy with Docker" section linking to doc/deployment.md; oauth mode noted.
- doc/README.md index: rows for Deployment and Client setup.
- doc/getting-started.md: pointer from the HTTPS quickstart to the full docs.
- CLAUDE.md: FAMILIAR_AUTH_MODE / FAMILIAR_OAUTH_* / FAMILIAR_ALLOW_INSECURE_BIND
  and /healthz in the transport note; the split ci.yml / docker.yml workflows.

Deployment docs live under the existing doc/ tree (the repo uses doc/, not a
separate docs/) to keep a single documentation directory.
2026-07-04 19:26:33 +02:00
Latte eb2922f63c ci(docker): build, smoke-test, and publish the remote-HTTP image
New .gitea/workflows/docker.yml (alongside the untouched ci.yml wheel publish).
Runs lint + test, then builds the image and smoke-tests it — non-root uid,
/healthz 200, MCP 401 without a token and 200 with a valid bearer. On a merge to
main/dev it pushes to git.hiddenden.cafe/hiddenden/familiar (main -> :latest +
:main, dev -> :dev, plus :sha-<12>) via the existing REGISTRY_TOKEN, then does a
scoped cleanup. The two workflows are independent and don't interfere.
2026-07-04 19:26:17 +02:00
Latte 39ce8005c0 feat(docker): image, compose, and env for remote-HTTP deployment
- docker/Dockerfile: multi-stage uv wheel build; runtime runs `familiar serve`
  as a non-root user (uid 1000) on port 8080, /data volume for the encrypted
  token store, and a HEALTHCHECK against /healthz. No token is baked in — serve
  refuses to start without FAMILIAR_HTTP_TOKEN, so it's supplied at run time.
- docker-compose.yml: joins an external `proxy` network (Traefik labels for TLS,
  nginx/Caddy documented as alternatives), read-only rootfs, tmpfs /tmp,
  no-new-privileges, cap_drop ALL, non-root, /healthz healthcheck. Pulls the
  published FAMILIAR_IMAGE with a local build as fallback; writes default off.
- .env.example: every FAMILIAR_* knob incl. the bearer token, auth mode, the
  OAuth resource-server settings, and the insecure-bind escape hatch.
- .dockerignore: keep local state (data/, *.enc, key) and dev files out.
2026-07-04 19:26:17 +02:00
Latte 7bb99d94bf feat(serve): /healthz, fail-closed bind, and OAuth resource-server mode
Extend the HTTP transport (from the remote-MCP work) so it's production-ready.

- /healthz: an unauthenticated GET liveness route that returns only
  {status, server, version} — no account data, no token state. For Docker and
  reverse-proxy probes; on stdio the route is never served.
- Fail-closed bind: refuse to start without authentication, and make the
  message distinguish a loopback bind from a public (0.0.0.0) one. The only
  override is the explicit FAMILIAR_ALLOW_INSECURE_BIND=true escape hatch, which
  permits an unauthenticated bind (with a loud warning).
- FAMILIAR_AUTH_MODE=oauth: run as an OAuth 2.1 resource server. A JWTVerifier
  validates external-issuer JWTs (JWKS from FAMILIAR_OAUTH_JWKS_URI, else
  discovered from FAMILIAR_OAUTH_ISSUER's OIDC metadata) against issuer +
  audience, wrapped in a RemoteAuthProvider that serves
  /.well-known/oauth-protected-resource so OAuth-only clients (Claude web) can
  discover the authorization server. familiar is a resource server only — no
  authorization server, no DCR. bearer mode is unchanged and stays the default.

Tests cover healthz (200, no leak), fail-closed vs escape hatch, and oauth
(valid JWT 200; missing/expired/wrong-audience 401 + WWW-Authenticate; the
protected-resource metadata), plus the new config readers.
2026-07-04 19:25:52 +02:00
Latte 3b2c04cde3 Merge pull request 'feat: serve familiar as a remote HTTP MCP server (bearer auth)' (#63) from feat/remote-http into dev
CI / lint (push) Successful in 50s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
Reviewed-on: #63
2026-07-04 16:46:13 +00:00
Latte ceb907b2fe docs: document the remote-HTTP serve mode, env vars, nginx and Mistral setup
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 51s
- README + getting-started: a "Serve over HTTPS (remote MCP)" section with the
  `familiar serve` command, the FAMILIAR_HTTP_* env vars, the write-off-by-
  default note, an nginx location block (proxy_buffering off, long read
  timeout), the headless-login / copy-the-store options, and the steps to add
  it as a Mistral custom connector.
- architecture: describe the stdio/HTTP transport split, the bearer-auth gate,
  and the HTTP write default.
- CLAUDE.md: add the new commands and env vars to Commands + Architecture.
2026-07-04 18:22:09 +02:00
Latte 44b2dbac0c test(serve): cover HTTP bearer auth, start refusal, config, write default, headless login
- 401 without/with a bad token (incl. WWW-Authenticate: Bearer), 200 with a
  valid one, driven through the ASGI app in-process.
- configure_http refuses to start when the token is unset or blank.
- FAMILIAR_HTTP_* config defaults and overrides; http_token() strip/empty.
- HTTP write default disabled by default, env still wins, stdio stays enabled.
- parse_callback (URL / bare code / empty) and the manual_login exchange flow.
2026-07-04 18:22:01 +02:00
Latte 458e31685f feat(serve): opt-in HTTP transport with bearer auth + headless login
Add a `familiar serve` subcommand that runs the FastMCP streamable-HTTP
transport for use as a remote MCP server (e.g. a Mistral custom connector),
while stdio stays the default and is completely unchanged.

- config: FAMILIAR_HTTP_HOST/PORT/PATH (default 127.0.0.1:8080/mcp) and a
  live http_token() reader for FAMILIAR_HTTP_TOKEN.
- server: configure_http()/serve_http() attach a StaticTokenVerifier so a
  missing/wrong bearer token gets 401 + WWW-Authenticate: Bearer and a valid
  one passes the handshake. Refuses to start (SystemExit) without a token.
- writes: in HTTP mode the write default flips to disabled unless
  FAMILIAR_ENABLE_WRITES is explicitly truthy; guard() logic is untouched.
- auth: manual_login() + parse_callback() power `login --no-browser`, printing
  the SSO URL and reading the pasted callback from stdin (no local browser).
- bump fastmcp pin to >=3.0.0 for the streamable-HTTP + verifier API.
2026-07-04 18:20:59 +02:00
Latte d65e9be5ff Merge pull request 'Feat/documentations' (#59) from feat/documentations into dev
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / lint (pull_request) Successful in 49s
CI / publish (push) Successful in 52s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
Reviewed-on: #59
2026-06-27 19:33:39 +00:00
Latte a611b6f3f1 Merge branch 'dev' of https://git.hiddenden.cafe/Hiddenden/familiar into dev
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
2026-06-27 21:30:11 +02:00
Latte 650439be27 added documentations 2026-06-27 21:30:08 +02:00
LatteandClaude Opus 4.8 463ea84360 feat(sde): bulk-load downloader + offline system/station lookups
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 59s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
Implements issue #51. Adds a Fuzzwork SDE downloader/importer and
expands the offline lookup layer beyond item types.

- sde.py: SDE_URL constant (FAMILIAR_SDE_URL override) and
  async download_sde() that streams + bz2-decompresses the dump
  incrementally to sde_path(), resetting the cached connection.
- sde.py: system_by_id/system_by_name/station_by_id offline lookups,
  all degrading to None when the table/file is missing.
- universe.py: get_system/get_station prefer the SDE when available,
  falling back to ESI otherwise (signatures unchanged).
- __main__.py: `familiar sde download|status|path` subcommands.
- tests: download_sde via injected MockTransport, new lookups + None
  fallbacks, and SDE-vs-ESI fast-path/fallback for get_system/station.
- README: short offline-SDE setup note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:25:09 +02:00
LatteandClaude Opus 4.8 7bae47d994 docs: structured Claude Desktop config; bump to 0.0.2
CI / lint (pull_request) Successful in 53s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 58s
CI / lint (push) Successful in 50s
CI / publish (push) Successful in 50s
CI / test (pull_request) Successful in 1m0s
The Claude Desktop JSON in the README rendered as one cramped line on the
package page — format it as a proper multi-line block. Bump version to
0.0.2 so the published package "About" picks up the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:10:49 +02:00
LatteandClaude Opus 4.8 2f12a03257 feat(sde): optional local SDE store for offline name/type lookups
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 59s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 50s
Add src/familiar/sde.py backing type lookups from a local SQLite SDE with
graceful fallback to ESI when absent. lookup.resolve_id prefers the SDE for
inventory_types; universe.get_item serves SDE-backed fields with no network
call. Register a read-only sde_status() MCP tool. Tested against an in-memory
SQLite fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:44:18 +02:00
LatteandClaude Opus 4.8 0dfd6d2350 feat(writes): enable write actions by default (opt-out)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 51s
Invert the global write toggle so writes are ENABLED by default and the
user opts OUT, while keeping all other safety intact (per-call confirm=true
dry-run preview and the on-disk audit log are unchanged).

- writes.py: use a `writes_disabled` flag file instead of `writes_enabled`;
  `writes_enabled()` returns True unless the flag exists (env var still
  honored either way); `set_writes(False)` creates the flag, `set_writes(True)`
  removes it. Update user-facing notes.
- tests: default is now ENABLED; adjust toggle/disabled tests (set disabled
  explicitly via set_writes(False) or FAMILIAR_ENABLE_WRITES=0).
- README.md / CLAUDE.md: document writes are on by default, opt out via
  `familiar writes disable`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:39:25 +02:00
LatteandClaude Opus 4.8 0f7a861fc4 Phase 5: ESI Expires caching, error-limit back-off, setup docs (#16)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
EsiClient now caches GET responses honoring the Expires header and backs off
when the rolling ESI error window is nearly exhausted (X-Esi-Error-Limit-*).
README gains Setup / Connect-to-Claude / enabling-writes sections.

Closes #16. SDE bulk-load split out to its own follow-up (#51).
135 tests, coverage 90%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:31:42 +02:00
LatteandClaude Opus 4.8 02a009f111 docs: add CLAUDE.md for future Claude Code instances
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 51s
Document common commands, the register(mcp) tool-module pattern, the ESI/
auth/charutil/write-safety architecture, test conventions, and the dev-only
git/CI workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:26:03 +02:00
LatteandClaude Opus 4.8 318ca17889 Phase 4: planetary interaction reads (#29)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
Add read-only PI tools: get_planets (colonies), get_planet_layout
(pins/links/routes), get_corp_customs_offices. ESI exposes no PI write
endpoint despite the manage_planets scope name, so these are reads.

Closes #29. 132 tests, coverage 90%, 84 tools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:22:35 +02:00
LatteandClaude Opus 4.8 0a828af4df Phase 4: fleet & UI commands — read + guarded writes (#27, #28)
CI / lint (pull_request) Successful in 51s
CI / publish (pull_request) Has been skipped
CI / test (pull_request) Successful in 57s
CI / lint (push) Successful in 51s
CI / test (push) Successful in 59s
CI / publish (push) Successful in 51s
Fleet: get_fleet (read, handles not-in-fleet); guarded invite_to_fleet and
update_fleet. UI: guarded set_waypoint and open_window (market/info/contract).
All writes go through write-safety.

Closes #27, #28. 128 tests, coverage 89%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:18:59 +02:00
LatteandClaude Opus 4.8 b3f06af8a4 Phase 4: contacts & fittings — reads + guarded writes (#25, #26)
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 50s
Contacts: read character/corp/alliance contacts; guarded add/remove of
character contacts. Fittings: read saved fits; guarded create/delete.
EsiClient.delete now takes params. All writes go through write-safety.

Closes #25, #26. 120 tests, coverage 89%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:14:57 +02:00
LatteandClaude Opus 4.8 3bd170e8ab Phase 4: mail & calendar — reads + guarded writes (#23, #24)
CI / test (push) Successful in 56s
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / publish (push) Successful in 50s
Add read tools (get_mail, read_mail, get_calendar) and guarded write tools
(send_mail, set_mail_read, respond_calendar_event). Writes resolve an
explicit character, return a dry-run preview unless confirm=true, and only
execute when writes are enabled. Add EsiClient put/delete.

Closes #23, #24. 112 tests, coverage 90%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:11:07 +02:00
LatteandClaude Opus 4.8 f4f6ddede8 Phase 4: remaining reads — FW, projects, freelance, activities, access (#34,#35,#36)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 51s
Add read-only tools: faction-warfare stats (char + corp), freelance jobs
(char + corp), corp projects, character activities, and access lists.
FW endpoints are stable; the newer projects/freelance/activities/access
paths are best-effort and flagged for live confirmation.

Closes #34, #35, #36. 106 tests, coverage 91%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:07:02 +02:00
LatteandClaude Opus 4.8 20761d8644 Phase 4: corp economy & facilities reads (#33, #20)
CI / publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / lint (push) Successful in 51s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 52s
Add read-only corp tools: wallets, assets, mining observers, facilities,
starbases — all corp-level (need corp roles). Closes #33 and completes the
remaining corp items of #20.

100 tests, coverage 91%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:03:42 +02:00
LatteandClaude Opus 4.8 754c80485a Phase 4: corp organization reads (#32)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 56s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
Add read-only corp tools: members, member tracking, divisions, titles,
medals, standings (corp-level, need corp roles), plus the character's own
corp roles. Closes #32.

95 tests, coverage 91%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 20:00:36 +02:00
LatteandClaude Opus 4.8 ca393e3f86 Phase 4: character profile/social reads (#31)
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 50s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 50s
Add read-only tools: loyalty points, standings, medals, titles, agents
research, jump fatigue, notifications, chat channels — per character or
aggregated. Closes #31.

90 tests, coverage 92%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:53:23 +02:00
LatteandClaude Opus 4.8 4203a907d5 Phase 4: killmails + zKillboard (read)
CI / lint (pull_request) Successful in 50s
CI / test (pull_request) Successful in 56s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 57s
CI / publish (push) Successful in 50s
Add read-only killmail tools: recent character and corp killmails (refs
enriched with system/victim/attacker detail) plus zKillboard PvP stats via
a tiny injectable zkill client. New charutil.character_targets helper for
token-less per-character iteration.

85 tests, coverage 93%. Closes #30.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:42:07 +02:00
LatteandClaude Opus 4.8 07738f2237 Phase 4: write-action safety & guardrails (#22)
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 57s
CI / lint (push) Successful in 51s
CI / publish (push) Successful in 50s
Add writes.py: the single gate every write tool must pass. A write happens
only when confirm=true AND writes are enabled (off by default); otherwise a
dry-run preview is returned. Every executed write is appended to an audit
log. New `familiar writes [enable|disable|status]` CLI and a read-only
write_status MCP tool.

81 tests, coverage 92%. Closes #22.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:37:37 +02:00
LatteandClaude Opus 4.8 692c8e245a Phase 3: structure tools (search, info, markets, container logs)
CI / lint (pull_request) Successful in 52s
CI / lint (push) Successful in 50s
CI / test (pull_request) Successful in 59s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 56s
CI / publish (push) Successful in 51s
Add read-only structure tools: search_structures (per character),
get_structure and get_structure_market (single structure via one token),
and get_corp container logs. New charutil.one_token helper picks a single
authenticated token (named character or first).

31 tools total, 75 tests, coverage 92%. Closes #21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:32:02 +02:00
LatteandClaude Opus 4.8 eeebfaa684 Phase 3: industry & trade tools (character + corp)
CI / lint (pull_request) Successful in 51s
CI / lint (push) Successful in 53s
CI / test (pull_request) Successful in 1m0s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 58s
CI / publish (push) Successful in 51s
Add read-only industry tools (industry jobs, blueprints, mining ledger;
corp jobs + blueprints) and trade tools (contracts, own market orders;
corp contracts + orders). New charutil helper runs a fetcher over one or
all characters, with a corp variant that resolves each character's corp id.
account._run now reuses it.

27 tools total, 70 tests, coverage 91%. Closes #18 and #19; covers most of #20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:28:21 +02:00
LatteandClaude Opus 4.8 77e47e75aa Foundational: full scope set + read+write direction
CI / lint (pull_request) Successful in 50s
CI / lint (push) Successful in 50s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 56s
CI / publish (push) Successful in 51s
Expand config.SCOPES to the complete character + corp scope set (64 read,
9 write), split into READ_SCOPES / WRITE_SCOPES so a caller can opt into a
read-only subset via FAMILIAR_SCOPES. Update the README: familiar is no
longer "read-only by design" — reads are free, writes are opt-in, previewed,
and audited.

Part of #17. Note: changing scopes requires re-running `familiar login` per
character to re-consent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 19:04:01 +02:00
LatteandClaude Opus 4.8 61837d4783 Multi-character support across accounts
CI / lint (pull_request) Successful in 49s
CI / test (pull_request) Successful in 56s
CI / lint (push) Successful in 50s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 55s
CI / publish (push) Successful in 50s
Rework the token store to hold many characters keyed by character_id,
migrating the previous single-character file transparently. `familiar login`
now adds a character; `familiar status` lists all; `familiar logout [name|id]`
removes one or all.

Every account tool gains an optional `character` (name or id): omitted means
all logged-in characters, given means just that one. Results are uniform
{"characters": [...]}. get_auth refreshes per character.

58 tests, coverage 89%. Closes #9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:47:34 +02:00
LatteandClaude Opus 4.8 eb6440db40 Phase 2: EVE SSO auth + read-only account tools
CI / lint (pull_request) Successful in 51s
CI / lint (push) Successful in 50s
CI / test (pull_request) Successful in 57s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 57s
CI / publish (push) Successful in 50s
Add PKCE OAuth2 login against EVE SSO with an encrypted, auto-refreshing
token store, driven by `familiar login` / `status` / `logout` CLI commands.
The MCP server gains authenticated account tools:

- account_status, get_wallet, get_skills, get_skill_queue
- get_location, get_clones, get_assets (paginated)

ESI client extended with Bearer auth and X-Pages pagination. 51 tests
(auth + account mocked), coverage 88%. Client id/callback/scopes are
env-overridable; refresh token Fernet-encrypted on disk.

Closes #7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:32:47 +02:00
LatteandClaude Opus 4.8 edf2298e20 ci: authenticate twine with REGISTRY_TOKEN secret
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 59s
CI / lint (push) Successful in 53s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 1m0s
CI / publish (push) Successful in 51s
The repo already has a REGISTRY_TOKEN secret (Gitea access token with
package scope); the workflow referenced a non-existent PACKAGE_REGISTRY_TOKEN
and fell back to the auto GITHUB_TOKEN, which is not granted package-write,
so every upload was rejected. Point twine at REGISTRY_TOKEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:01:22 +02:00
LatteandClaude Opus 4.8 d76c8f87a8 ci: drop unsupported --skip-existing from twine upload
CI / lint (pull_request) Successful in 51s
CI / test (pull_request) Successful in 59s
CI / lint (push) Successful in 52s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 58s
CI / publish (push) Failing after 30s
Gitea's PyPI registry rejects twine's --skip-existing flag
(UnsupportedConfiguration), which failed every publish run. Remove it and
instead tolerate an "already exists" conflict in the shell, so dev (unique
.devN versions) always publishes and a main re-run of an existing release
is treated as success rather than a hard failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:57:50 +02:00
LatteandClaude Opus 4.8 a136853126 ci: grant contents:read so publish can checkout private repo
CI / lint (pull_request) Successful in 52s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 51s
CI / test (push) Successful in 56s
CI / publish (push) Failing after 29s
Setting `permissions: packages: write` alone dropped every other scope to
none, so actions/checkout could not clone the private repo and the publish
job failed before building. Add contents:read, and use github.actor (more
reliably populated than gitea.actor) as the twine username.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:52:14 +02:00
LatteandClaude Opus 4.8 4223141cf7 Phase 1: public market + universe lookups via ESI
CI / test (pull_request) Successful in 56s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 52s
CI / test (push) Successful in 56s
CI / publish (push) Failing after 34s
CI / lint (pull_request) Successful in 52s
Add a thin async ESI client (httpx, descriptive User-Agent, uniform errors,
mockable transport) plus name<->id resolution. New read-only tools:

- universe: resolve_names, get_item, get_system, get_station, get_region, get_route
- market:   list_hubs, get_market_price, get_market_orders, compare_hubs

Trade-hub IDs (Jita/Amarr/Dodixie/Rens/Hek) are hardcoded constants. Market
aggregation (best buy/sell, spread, order book) lives in pure helpers. 30 unit
tests with mocked ESI; coverage 99%. SDE bulk-load deferred to a later phase.

Closes #2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:49:40 +02:00
LatteandClaude Opus 4.8 585acc766c ci: add Gitea Actions workflow (lint, test, publish)
CI / lint (pull_request) Successful in 53s
CI / test (pull_request) Successful in 1m0s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 53s
CI / test (push) Successful in 59s
CI / publish (push) Failing after 39s
Lint (ruff) and test (pytest, fail under 80% coverage) on every PR and
push to main/dev. On a merge to main/dev, build the sdist+wheel and
publish to the Gitea PyPI registry — main as a clean release, dev as a
unique .devN pre-release. No Docker; runs on uv.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:34:59 +02:00
LatteandClaude Opus 4.8 ef27cd85f6 Phase 0: FastMCP scaffold with ping tool
Set up the familiar package (src layout, uv/hatchling) with a FastMCP
server exposing a read-only ping health tool. Add pytest smoke tests with
80% coverage gate. Flesh out the README with the project vision and roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 17:34:51 +02:00
Latte f99b8a7884 Initial commit 2026-06-27 15:06:02 +00:00