Metadata-Version: 2.4
Name: sigaa-tools
Version: 0.2.0
Summary: User-friendly SIGAA and public SIPAC UFPB client (CLI + MCP)
Author: Puca Vaz
License-Expression: MIT
Project-URL: Homepage, https://github.com/PucaVaz/sigaa-tools
Project-URL: Repository, https://github.com/PucaVaz/sigaa-tools
Project-URL: Issues, https://github.com/PucaVaz/sigaa-tools/issues
Keywords: sigaa,sipac,ufpb,mcp,cli,academic
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: lxml>=5.0
Requires-Dist: keyring>=24
Requires-Dist: pypdf<7,>=5.0
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1.27; extra == "mcp"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: ruff>=0.6.0; extra == "dev"
Dynamic: license-file

# sigaa-tools

> [!IMPORTANT]
> This is an unofficial personal project and is not affiliated with, endorsed
> by, or supported by UFPB, SIGAA, or SIPAC. Use it responsibly, keep request rates low,
> and follow the rules that apply to your SIGAA account.
> See [Privacidade e segurança](docs/privacidade.mdx) for what is stored and what is never stored.

Friendly CLI and MCP server for **SIGAA UFPB** and **SIPAC** public process lookup. Fetch your classes, grades, deadlines, and curriculum all at once without opening the web portal.

## Quickstart

**Step 1:** Install [uv](https://docs.astral.sh/uv/) (once only):
```bash
brew install uv
# or: curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Step 2:** Install sigaa:
```bash
uv tool install "sigaa-tools[mcp] @ git+https://github.com/PucaVaz/sigaa-tools"
```

**Step 3:** Run the setup wizard:
```bash
sigaa init
```

That's it. The wizard prompts for your SIGAA credentials, stores them securely, syncs your data, and optionally sets up MCP for Claude Code or background polling.

## What you get

- **CLI**: `sigaa classes`, `sigaa news`, `sigaa grades`, `sigaa deadlines`, etc. Fast, offline-first queries from a local SQLite database.
- **MCP server**: Wire into Claude Code (or any MCP client) to let AI agents fetch your SIGAA data on demand.
- **SIPAC lookup**: `sigaa sipac process 23074.056437/2026-26` — look up public UFPB administrative processes without a browser.

## Common commands

```bash
sigaa sync                 # fetch & persist new news, deadlines, grades
sigaa sync --bodies        # also fetch full news article text
sigaa classes --schedule   # enrolled classes with decoded weekly schedule
sigaa news --class DSCO00022   # news for one class
sigaa news --unread --mark-seen
sigaa grades --semester 2025.1 # grades by semester
sigaa deadlines            # assessment/task due dates
sigaa ics --out sigaa.ics  # export classes + deadlines as calendar
sigaa curriculum           # live progress & required courses
sigaa cra --json           # official CRA as JSON
sigaa watch --once --json  # sync and report changes as JSON (see Notifications)
```

Store-backed commands (`classes`, `news`, `grades`, `deadlines`) are fast and offline. Network commands (`sync`, `watch`, `curriculum`, `cra`, downloads) need internet.

## SIPAC public process lookup

Use this feature to follow UFPB administrative processes without opening each SIPAC page manually. Look up one process by its complete number or find processes by an interested party name or identifier. Process details include the current status, subject, interested parties, public documents, routing movements, status changes, and attached files.

```bash
sigaa sipac process 23074.056437/2026-26
sigaa sipac process 23074.056437/2026-26 --json
sigaa sipac search --name "Fulano de Tal"
sigaa sipac search --identifier "12345678901" --page 2 --json
```

Example summary:

```text
23074.056437/2026-26  [ATIVO]
  ANÁLISE DE PROPOSTA DE RESOLUÇÃO SOBRE DISTRIBUIÇÃO DE ENCARGOS DIDÁTICOS
  Origin: CI - DIREÇÃO DE CENTRO (11.01.45.01)
  Opened: 12/06/2026 17:26
  Interested parties: 1 | Documents: 14 | Movements: 6
```

Agents can call `sipac_get_public_process` with `{"number": "23074.056437/2026-26"}`. To search, call `sipac_search_public_processes` with either `name` or `identifier`. Both interfaces use the same `schema_version: 1` contracts as the CLI JSON output. Public SIPAC commands do not authenticate, bypass restricted documents, change processes, or persist results. See the [SIPAC process guide](docs/sipac-processes.mdx) for fields, use cases, privacy guidance, and error handling.

Headless fallback: `export SIGAA_USER=... SIGAA_PASS=...`.
Optional `SIGAA_DB=/path/to/sigaa.db` to override the store location.

## MCP server (for code agents)

Run `sigaa init`; it can detect or create `.mcp.json` and add the `sigaa` MCP
server without manual absolute-path editing.

Tools: `sigaa_list_classes`, `sigaa_list_news`, `sigaa_get_news_body`,
`sigaa_get_schedule`, `sigaa_list_grades`, `sigaa_list_deadlines`,
`sigaa_get_curriculum`, `sigaa_get_cra`, `sigaa_export_ics`,
`sipac_get_public_process`, `sipac_search_public_processes`,
`sigaa_download_historico`,
`sigaa_download_declaracao_vinculo`, `sigaa_download_atestado_matricula`,
`sigaa_sync`. Store-backed reads are offline; sync, live lookups, and downloads
touch the network.

`sipac_get_public_process(number)` returns the public process metadata exposed
by UFPB: general data, interested parties, documents and public download links,
movements, status changes, and attached files. It shares schema version 1 with
`sigaa sipac process --json` and does not require credentials.

`sipac_search_public_processes(name?, identifier?, page=1)` finds public
processes by one interested-party field. Pass exactly one of `name` or
`identifier`. Each response contains at most the 15 results exposed by one
portal page. Results are not retained after the request finishes.
Identifiers such as CPF, registration number, and CNPJ are personal data. Query
them only for a legitimate purpose and avoid copying them into logs.

`sigaa_get_curriculum` is networked and uses the same normalized contract and
filters as `sigaa curriculum`: `status`, `required_only`, `period`,
`include_requirements`, and `include_cra`. Its default `current` view contains
enrolled components plus required pending ones. Pending optional components are
choices toward the remaining optional workload, not courses that must all be
completed. `sigaa_get_cra` is also networked and reads the official CRA from the
academic transcript; a new student may receive `source: "unavailable"` until
SIGAA reports one. Neither response exposes SIGAA's internal student id.

Every download tool accepts a safe filename (not an arbitrary path), never
overwrites, and writes under the app's private `downloads` directory. This
covers the three academic documents plus `sigaa_download_material` and
`sigaa_download_tarefa_anexo`; on those two the parameter is named `filename`
and no longer accepts a path. Filenames SIGAA supplies in `Content-Disposition`
are sanitized rather than trusted. Set
`SIGAA_DOWNLOAD_DIR` on the MCP server to choose another directory. A successful
call returns both structured metadata (filename, MIME type, and size) and
an opaque MCP `ResourceLink`. Clients that support resource links can present the
document as an attachment or download; opening it reads the saved file through
MCP without putting its bytes in the original tool response. The resource link is
valid for the current server session, while the local file remains on disk. HTML
certificates are exposed as download-only binary resources so an MCP client does
not execute the report's active SIGAA markup inline.

### Tool surface: `SIGAA_MODE`

`SIGAA_MODE` picks which tools the MCP server exposes. It defaults to `local`,
which exposes all 24 tools — **nothing is ever removed from a local or
self-hosted install**.

`SIGAA_MODE=hosted` is for a shared, multi-tenant deployment. It withholds the
five tools that write files to the server's disk:

| Withheld in hosted | Why |
| --- | --- |
| `sigaa_download_material` | Unbounded file writes; a class can hold hundreds of MB |
| `sigaa_download_tarefa_anexo` | Same |
| `sigaa_download_historico` | Needs a per-tenant download directory, which does not exist yet |
| `sigaa_download_declaracao_vinculo` | Same |
| `sigaa_download_atestado_matricula` | Same |

Everything else stays, including `sigaa_sync` — without it a tenant's store is
empty and every store-backed tool returns nothing. Its writes are confined to
`SIGAA_DB`. No MCP tool mutates SIGAA state in any mode: enrollment
selection and confirmation are deliberately CLI-only.

The mode is applied at import, so it holds whether the server is started via
`sigaa-mcp`, `python -m sigaa.mcp_server`, or by importing `mcp` from
`sigaa.mcp_server` and mounting it. Withheld tools are removed from the tool
manager, so they are uncallable rather than merely hidden. An unrecognized
`SIGAA_MODE` raises at startup instead of falling back to `local`.

A hosted deployment must also set `SIGAA_DB` and `SIGAA_DOWNLOAD_DIR` per
tenant. Restoring the three academic-document tools in hosted mode depends on
that isolation existing first.

## Scheduled polling

Run `sigaa init`; it can write the launchd plist on macOS or print the cron
entry for other operating systems.

## Notifications

`sigaa watch` is the integration point for cron, systemd, and chat bridges. It
syncs, then prints what changed since its previous run as JSON events; no LLM is
involved in deciding whether something changed.

```bash
sigaa login                                          # password + account into the OS keyring
sigaa sync --bodies --json                           # expect "ok": true
sigaa watch --once --baseline --json                 # optional: skip what is already stored
sigaa watch --once --bodies --json --fail-on-error   # schedule this
```

- stdout carries only JSON; human messages go to stderr.
- A run with nothing new reports `"status": "no_changes"`.
- A failed run reports `"status": "failed"` plus `error` events tagged with a
  `stage` (`auth`, `network`, `parse`, `sync`) and, with `--fail-on-error`,
  exits 1. A failure is never reported as `no_changes`.
- A class page whose news panel cannot be read is a `parse` error, not an empty class.
- Unchanged state prints identical bytes, so hash-based monitors are safe.

`--jsonl` prints one event per line (for `--interval 30m` loops). Event schema
and a consumer example: [docs/notificacoes.mdx](docs/notificacoes.mdx).

## Credentials

Keyring-first, env-second. Never commit credentials, cookies, downloaded live HTML, SQLite databases, exported PDFs, or `.env` files.

`sigaa init` stores your SIGAA password securely in your OS keyring and verifies the login. Public SIPAC queries do not need credentials.

After `sigaa login`, no environment variables are needed. For headless
environments without a keyring: `export SIGAA_USER=username SIGAA_PASS=password`
(plaintext password files are not recommended). Optional: `SIGAA_DB=/path/to/sigaa.db` to override the store location.

## Advanced manual configuration

Usually you should run `sigaa init`. The examples below are for manual setups,
debugging, or custom automation.

### Manual MCP

Run with `sigaa-mcp` (stdio). Wire into Claude Code via `.mcp.json`:

```json
{
  "mcpServers": {
    "sigaa": {
      "command": "uvx",
      "args": [
        "--from",
        "sigaa-tools[mcp] @ git+https://github.com/PucaVaz/sigaa-tools",
        "sigaa-mcp"
      ]
    }
  }
}
```

This form carries no machine-specific path, so the file stays valid when
committed and shared with your team. `sigaa init` writes it for you. The server
reads the active account from your keyring; add
`"env": { "SIGAA_USER": "your_user" }` only if keyring is unavailable.

### Manual scheduled polling

**macOS (launchd)** - sync every 30 min. Save as
`~/Library/LaunchAgents/ai.sigaa.sync.plist` then
`launchctl load` it:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>ai.sigaa.sync</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/you/.local/bin/sigaa</string>
    <string>sync</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict><key>SIGAA_USER</key><string>your_user</string></dict>
  <key>StartInterval</key><integer>1800</integer>
</dict></plist>
```

`uv tool install` puts `sigaa` in `~/.local/bin`; launchd needs it spelled out in
full, since it does not expand `~`. Run `which sigaa` to confirm yours.

**Linux (cron)**: `*/30 * * * * $HOME/.local/bin/sigaa watch --once --bodies --jsonl --fail-on-error`
(account and password from `sigaa login`; `SIGAA_USER`/`SIGAA_PASS` only where no keyring is reachable).

## Architecture

```
sigaa/
  config.py     endpoints, JSF constants, slot-time table, creds resolution
  http.py       session: cookie jar, ViewState, re-login + retry
  auth.py       login flow
  client.py     SigaaClient -> domain models
  sipac.py      unauthenticated public SIPAC process client + JSON contract
  models.py     Student, Turma, NewsItem, Schedule, CurriculumStatus
  parsers/      portal, news, schedule, curriculum, transcript, SIPAC process
  store/        SQLite db + repository (dedup, queries)
  services/     sync (fetch -> diff -> persist)
  cli.py        command line
  mcp_server.py agent tools
```

Adding a feature (materials, attendance) = a parser + client method and a
CLI/MCP surface, plus store columns when it is persisted. HTML changes touch
only `parsers/`. Implemented so far: classes, news (+bodies), grades, deadlines,
curriculum progress, official CRA, academic documents, and ICS export.
Public SIPAC administrative-process consultation is also available live.

`exporters/` turns store data into interchange formats (currently `ics`).

## Schedule decoding

`6M2345` → Fri (day 6) morning slots 2–5. Days 2=Mon..7=Sat, shift M/T/N.
Clock times in `config.SLOT_TIMES_UNCONFIRMED` are an **unconfirmed** default —
verify against a turma's "Plano de Curso" before using them for calendar export.

## Tests

```bash
pytest        # offline: parsers run on fixtures, store on in-memory sqlite
```

## Contributing

Interested in hacking on sigaa-tools? See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and release instructions.

## Security

Single-user, local-first tool. `config.py` reads credentials from keyring first
and environment variables second. The SQLite db is local and may contain student
data copied from SIGAA. `.gitignore` excludes `*.db`, `.env`, and live HTML
dumps, but review generated files before sharing logs, issues, or screenshots.
The SIPAC process feature is read-only and public; it does not send stored
credentials, but its responses can contain names and identifiers already shown
by the public portal, so handle exported JSON responsibly.

## License

MIT. See [LICENSE](LICENSE).
