Metadata-Version: 2.4
Name: cueapi-worker
Version: 0.3.0
Summary: Local pull-based worker daemon for CueAPI — executes scheduled tasks without a public URL
Author-email: "Vector Apps Inc." <hello@cueapi.ai>
License: MIT
Project-URL: Homepage, https://cueapi.ai
Project-URL: Documentation, https://docs.cueapi.ai
Project-URL: Repository, https://github.com/cueapi/cueapi-worker
Keywords: cueapi,worker,daemon,scheduling,ai-agents
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: click>=8.0
Requires-Dist: httpx>=0.24
Requires-Dist: pyyaml>=6.0

# cueapi-worker

Local pull-based worker daemon for [CueAPI](https://cueapi.ai). Polls for
worker-transport executions, runs the matched shell handler, and
auto-reports the outcome. No public URL required.

## Install

```bash
pip install cueapi-worker
cueapi-worker --help
```

## Configure

Copy `example/cueapi-worker.yaml` to somewhere writable and customize:

```bash
cp example/cueapi-worker.yaml ~/.config/cueapi/cueapi-worker.yaml
cueapi-worker start --config ~/.config/cueapi/cueapi-worker.yaml
```

API key resolution order:

1. `api_key:` in the YAML config
2. `CUEAPI_API_KEY` environment variable
3. `~/.config/cueapi/credentials.json` (populated by `cueapi login`)

## Install as a background service

```bash
# macOS (launchd)
cueapi-worker install-service --config ~/.config/cueapi/cueapi-worker.yaml

# Linux (systemd user unit)
cueapi-worker install-service --config ~/.config/cueapi/cueapi-worker.yaml
```

## Handler environment

Every handler command runs as a subprocess with these variables injected:

| Variable | Source |
|---|---|
| `CUEAPI_EXECUTION_ID` | the execution being handled |
| `CUEAPI_CUE_ID` | the parent cue |
| `CUEAPI_CUE_NAME` | human-readable cue name |
| `CUEAPI_WORKER_ID` | this worker's id |
| `CUEAPI_PAYLOAD` | the full cue payload as a JSON string |
| `CUEAPI_API_KEY` | the same API key the worker is authenticated with |
| `CUEAPI_BASE_URL` | the CueAPI base URL (defaults to `https://api.cueapi.ai`) |
| `CUEAPI_OUTCOME_FILE` | path to a per-run temp file the handler may write JSON to (see "Reporting evidence from handlers" below) |

Handler-level `env:` entries from the YAML are layered on top and support
`{{ payload.field }}` template resolution (nested dot notation works).

## Chain pattern

A handler can fire a follow-up cue without duplicating the API key in
its own config. The worker's key is already on the handler's environment
as `$CUEAPI_API_KEY`:

```yaml
handlers:
  ingest_then_summarize:
    cmd: |
      set -e
      commit_sha=$(ingest_and_commit)
      FIRE_AT=$(date -u -v+5M '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || \
                date -u -d '+5 minutes' '+%Y-%m-%dT%H:%M:%SZ')
      curl -sf -X POST "$CUEAPI_BASE_URL/v1/cues" \
        -H "Authorization: Bearer $CUEAPI_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{
          \"name\": \"summarize-$commit_sha\",
          \"schedule\": {\"type\": \"once\", \"at\": \"$FIRE_AT\"},
          \"transport\": \"worker\",
          \"payload\": {\"task\": \"summarize\", \"commit_sha\": \"$commit_sha\"}
        }"
```

## Outcome reporting

The daemon reports the outcome automatically based on the handler's exit
code — success on exit 0, failure otherwise. Handler stdout+stderr
(capped at 4 KB) is recorded as `result` on success or `error` on
failure. Handlers do **not** need to call the outcome endpoint
themselves.

### Reporting evidence from handlers

A handler MAY write a JSON object to the `CUEAPI_OUTCOME_FILE` path
before exiting to:

- attach evidence fields (`external_id`, `result_url`, `result_type`,
  `summary`, `artifacts`, `result_ref`, `metadata`), which lets
  worker-transport cues satisfy verification modes like
  `require_external_id` and `require_result_url`;
- or override the exit-code-derived `success` value when the handler
  knows more than the shell (e.g., the subprocess exited 0 but the
  downstream API returned 503, so the logical outcome is failure).

The file is optional. If a handler does nothing with it, behavior is
exactly as before: exit code decides.

**Example — git commit handler that proves the commit landed:**

```yaml
handlers:
  git_commit_summary:
    cmd: |
      set -e
      cd /path/to/repo
      git add summaries/yesterday.md
      git commit -m "daily summary $(date +%Y-%m-%d)"
      SHA=$(git rev-parse HEAD)
      git push
      cat > "$CUEAPI_OUTCOME_FILE" <<EOF
      {
        "success": true,
        "external_id": "$SHA",
        "result_url": "https://github.com/you/repo/commit/$SHA",
        "result_type": "git_commit",
        "summary": "Committed daily summary"
      }
      EOF
    timeout: 120
```

### Schema

All fields are optional. The file is parsed as UTF-8 JSON; anything
that doesn't match the schema below is dropped individually (the rest
of the file still takes effect).

| Field | Type | Limit |
|---|---|---|
| `success` | bool | — |
| `result` | string | ≤ 2000 chars |
| `error` | string | ≤ 2000 chars |
| `external_id` | string | ≤ 500 chars |
| `result_url` | string | ≤ 2000 chars, must start with `http://` or `https://` |
| `result_ref` | string | ≤ 500 chars |
| `result_type` | string | ≤ 100 chars |
| `summary` | string | ≤ 500 chars |
| `artifacts` | list | — |
| `metadata` | dict | — |

**File size cap:** 10 KB. Files over that are rejected and the run
falls back to exit-code-only behavior (with a diagnostic breadcrumb
in reported metadata).

### Conflict resolution

| Exit code | File | Decision |
|---|---|---|
| 0 | absent / empty | **success**, no evidence |
| ≠ 0 | absent / empty | **failure**, error = stdout/stderr |
| 0 | `success: true` (or omitted) | **success**, evidence merged |
| 0 | `success: false` | **failure** — FILE WINS (handler knows more than the shell), error from file if given |
| ≠ 0 | `success: true` | **failure** — EXIT CODE WINS (the process crashed; the file record is stale). Evidence is still attached — a crashing process may legitimately have produced real side effects worth recording. |
| any | malformed JSON / too large / bad encoding | fall back to exit-code-only, attach `_cueapi_worker.outcome_file_parse_error` to metadata |

Dropped fields are surfaced under
`metadata._cueapi_worker.outcome_file_dropped_fields` for debugging
without breaking the run.

The temp file is **always deleted** after the handler returns — even
on timeout, exception, or crash.

## Security / trust model

**Handlers run with the same privileges the worker has on the CueAPI
account.** `CUEAPI_API_KEY` is exposed to the handler subprocess so it
can fire follow-up cues, list executions, or otherwise call the API
without re-reading config. If a handler command — or a binary or
dependency it shells out to — is compromised, the API key is
compromised.

There are no per-handler scoped tokens today. Decide which handlers to
run on what machine with that in mind; don't run untrusted handlers
under a worker wired to a production key.

## Troubleshooting

- **`cueapi-worker start` warns "No launchd service detected"** — you
  ran the daemon from a terminal. It will stop when the terminal
  closes. Run `cueapi-worker install-service` to register it as a
  launchd (macOS) or systemd user unit (Linux).
- **401s in logs** — the API key has been rotated or revoked. If your
  config has `email: you@example.com`, the daemon will auto-recover
  via magic-link flow. Otherwise run
  `cueapi-worker login --email <email> --config <path>` or
  `cueapi-worker regenerate-key --config <path>`.

## License

Apache 2.0 © Vector Apps Inc.
