Metadata-Version: 2.5
Name: algorise
Version: 0.8.0
Summary: Unified CLI and MCP server for your Algorise workspace (read knowledge + publish agent output)
License-Expression: MIT
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp[cli]<2,>=1.0.0
Description-Content-Type: text/markdown

# Algorise

**Publish agent output into Algorise** — from Claude Code, Cursor, CI, or any script.

Algorise is a CLI and MCP server that connects external coding agents to your
Algorise workspace. Push files, notes, and links; choose where they land; collaborate
with your team and digital employees; pull results back into your local workflow.

- **Publish** — files, URLs, or inline markdown land as reviewable artifacts
- **Route** — send work to your inbox, a Collection, Dataset, or Report
- **Collaborate** — post updates, request review, poll for replies from digital employees

Requires Python 3.12+ and an Algorise account.

## Quick start

```bash
# Install (Python 3.12+; see INSTALL.md for all options)
uv tool install algorise

# Authorize via browser (OAuth — no API keys to paste)
algorise login

# Publish a file into your workspace inbox
algorise publish ./draft.md --title "Draft report" --agent claude-code

# See what you have published
algorise inbox
```

Published work appears in your Algorise dashboard, inbox, and notifications.

## Install

This package installs two commands:

| Command | Purpose |
|---|---|
| `algorise` | CLI for publishing and collaboration |
| `algorise-mcp` | MCP server (stdio) for in-editor agents |

```bash
# Recommended — uv tool (persistent install, no system Python required)
uv tool install algorise

# Run without installing
uvx algorise login

# pip / pipx (needs Python 3.12+)
pipx install algorise
```

Full install notes: [INSTALL.md](./INSTALL.md).

## Authenticate

Auth uses **browser OAuth** (OAuth 2.0 device-authorization grant). Tokens are
short-lived, refreshable, centrally revocable, and act as you. A token starts in
the organization that was active when you approved it; see
[Selecting an organization](#selecting-an-organization) to act in another one you
belong to.

```bash
algorise login
```

This prints a verification URL and user code, opens your browser, and waits for
approval (you must already be signed in to Algorise). The session is saved to
`~/.algorise/config` (mode 600).

```bash
algorise whoami   # profile, URL, org/user/scopes
algorise logout   # clear the active profile's session
```

Active sessions can be listed and revoked in the product under
**Settings → Connected CLIs**.

## Typical workflow

```bash
# 1. Publish work from your repo or agent session
algorise publish ./analysis.md --title "Competitor scan" --agent cursor

# 2. Route directly into a knowledge base (optional)
algorise publish ./policy.pdf --to collection --collection <collection-id>

# 3. Request a human review from the CLI
algorise review <artifact-id> "Please verify the sources"

# 4. After a digital-employee handoff in the UI, poll for replies
algorise pull <agent-session-id>
```

Review, approve, and hand off to digital employees in the Algorise UI. Bridge
delivers work into the workspace; it does not auto-publish on your behalf outside
Algorise.

## Profiles

One installed CLI can target multiple Algorise workspaces or accounts with
per-profile saved tokens — similar to `aws --profile`.

```bash
algorise login --profile work      # sign in to one workspace
algorise login --profile personal  # sign in to another

algorise profiles                  # list profiles (* = active)
algorise use work                  # switch active profile
algorise --profile personal whoami # one-off override

algorise logout --profile personal # clear one profile
algorise logout --all              # clear everything
```

Profiles are stored in `~/.algorise/config`. A legacy flat config is migrated
automatically into a `default` profile on first use.

## Selecting an organization

A session token is minted against **one** organization — whichever was active in
the browser when you approved the CLI. If you belong to several (an operator
provisioning tenants usually does), select the one you want to act in:

```bash
algorise org list                  # * marks the org this profile is set to
algorise org use beta-foods        # persist a selection for this profile
algorise whoami                    # confirms the effective org, by name

algorise --org acme-retail agents  # one-off: does NOT change the saved selection
algorise --org 9a1b44d0-... collection create --name "Ops KB"
```

`--org` accepts a **uuid or a slug**; a slug is resolved against your
memberships before anything is written, so a typo fails immediately rather than
at write time.

Order of precedence: `--org` > `ALGORISE_BRIDGE_ORG` > the profile's saved org >
no header at all (the token's own org).

The selection travels as an `X-Algorise-Org` request header. It is a request,
not an assertion: the server re-verifies that you are a live member of that org
on every call and returns **403** if you are not — it never silently falls back
to the token's org. `algorise org list` deliberately sends no org header, since
it is how you discover which orgs are valid.

Every command that writes prints its target first, so a cross-tenant write is
visible in scrollback and in CI logs:

```
-> Beta Foods (9a1b44d0...) on profile 'try'
```

Add `--output json` to any command to get the raw API body and no banner, for
scripting:

```bash
COL=$(algorise --output json collection create --name "Ops KB" | jq -r .id)
algorise upload ./ops/*.pdf --collection "$COL" --wait
```

### Resolution order

For each invocation, API URL and token resolve in this order (highest first):

1. `--api-url URL` (one-off; token from the selected profile or env)
2. `--profile NAME`
3. `ALGORISE_BRIDGE_PROFILE` env
4. `ALGORISE_BRIDGE_API_URL` / `ALGORISE_API_URL` (+ token env) — ad-hoc, no profile (CI)
5. Active profile in `~/.algorise/config`
6. Local dev fallback (`http://localhost:7100`)

## Selecting a project

A second axis, independent of the org: the org decides which tenant you write
into, the project decides where inside it the new content lands. Commands that
support it take `--project UUID` — a **uuid**, never a name — and a pin persists
one per profile so a whole provisioning run inherits it:

```bash
algorise projects                       # ids, kind, and your role in each
algorise project create --name "Marketing"   # make one (needs projects:write)
algorise project use "Marketing"        # pin it on this profile (name or uuid)
algorise --no-project agent create …    # one-off: ignore the pin for this call
algorise project clear                  # drop the pin
```

Order of precedence: `--no-project` > `--project` > `ALGORISE_BRIDGE_PROJECT` >
the profile's saved project > no project at all.

Unlike the org this never travels as a header — there is no project header in the
API — so each command sends it as a `project_id` body field or query param.
`project use` resolves the name against your accessible projects and refuses a
view-only one, because a pin you cannot write to would 403 every later authoring
command with nothing in the message tying the failure back to this selection.
`org use` clears the pin: a project id only exists inside one org, so carrying it
across a tenant switch would silently redirect every subsequent write. A one-off
`--org` (or `ALGORISE_BRIDGE_ORG`) leaves the pin on disk but suspends it for
that invocation for the same reason — unless the org it *resolves to* is the one
the pin was saved in, which is compared by id, never by the name or slug you
typed — and `project use` under one refuses to persist rather than pin another
org's project onto this profile. `whoami`
prints the effective project and where it came from, and the target banner gains
a `[project …]` suffix naming the project the request will actually carry — the
pin by name, or a `--project` / `ALGORISE_BRIDGE_PROJECT` value by id, since
those name no project the CLI has a name for:

```
-> Beta Foods (9a1b44d0...) on profile 'try' [project Marketing]
```

Under `--no-project` both report the suppression instead of the pin — the write
is landing at org level, and a banner naming a project it is not writing into is
the one lie this line exists to prevent:

```
-> Beta Foods (9a1b44d0...) on profile 'try' [no project, from --no-project]
```

The pin covers exactly two things: placing **new** content, and filtering a
**listing**. Everything else wants `--project` spelled out. Destinations that
name their own scope: `publish` (its default `--to inbox` target rejects a
project id), `library browse` and `library upload` (every scope but `--scope
project` rejects one), and `apps APP_KEY` (a defaulted project would turn an
org-shared app that resolves today into a 404). Updates of an existing resource —
`agent update`, `skill update`, `workflow update` — because an inherited pin
would *move* a resource the command never mentioned, and a skill moved into a
project drops out of every org-level listing with no flag to move it back.
And detail lookups by id or name — `tools ID`, `instructions ID`,
`datasets NAME` — because they find their target inside the listing, and a
defaulted filter there hides every org-global item, so a resource you can plainly
see reports as not visible to this session. The `app` **write** commands
(`add-block`, `update-block`, `delete-block`, `reorder-blocks`, `add-tab`,
`rename-tab`, `delete-tab`) are explicit-only for the same reason as
`apps APP_KEY`: they resolve an app that already exists, the server's project
branch is exact, and a miss stays a miss — so an inherited pin would 404 every
edit of a personal or org-shared app. Passing `--project` there is worth doing
when the same app key exists in more than one place; without it the server falls
back to its own-namespace-then-scan resolution, which can land on the wrong one.
Two commands take the flag only where it means something: `routines TASK_ID` and
`tools --available` reject an explicit `--project` rather than accept and ignore
it.

`app create` and `workflow import` DO inherit the pin — both place new content,
which is exactly what the pin covers. `app create --project` works for a blank
app and for `--template` alike, and the app is created inside the project's vault
rather than created personally and moved afterwards.

These commands have **no** project support at all, because the backend has no
field or filter for one — the flag is absent by design rather than accepted and
dropped: `note`, `collection create`, `collections`, and `doc upload` / `upload`.
A collection is org-scoped and has no `project_id` column at all (it is the
legacy surface the Library replaces), so the supported alternative is:

- project-scoped documents go into the Library, not a collection —
  `algorise library upload PATH --scope project --project ID`.

## Environment variables

Most users only run `algorise login`. These exist for CI and headless runners:

| Variable | Purpose |
|---|---|
| `ALGORISE_BRIDGE_API_URL` | API base URL (falls back through `ALGORISE_API_URL`, saved config, release default, then localhost) |
| `ALGORISE_BRIDGE_DEFAULT_URL` | Release-time public API base, baked into published builds |
| `ALGORISE_BRIDGE_TOKEN` | Session token override (falls back to `ALGORISE_TOKEN`, then saved config) |
| `ALGORISE_BRIDGE_PROFILE` | Named profile (same as `--profile`; used by the MCP server, which has no CLI flags) |
| `ALGORISE_BRIDGE_ORG` | Organization uuid to act in when no profile selection applies (CI / ad-hoc); `--org` overrides it |
| `ALGORISE_BRIDGE_PROJECT` | Project uuid new content is placed in; beats the profile's saved project, loses to `--project` and `--no-project` |
| `ALGORISE_TOOL_SECRET` | Credential for `tool create` (never a flag — a flag would land in shell history and `ps`) |

When no URL is configured, commands that need the API fail with a clear message
instead of silently hitting localhost.

## CLI reference

| Command | Description |
|---|---|
| `algorise [--profile NAME] [--api-url URL] [--org SLUG_OR_UUID] [--no-project] [--output text\|json] <command>` | Global selectors: backend, organization, project pin, output format |
| `algorise login [--profile] [--api-url] [--name] [--scopes] [--write] [--no-browser]` | Browser OAuth; saves token under profile (`--write` also requests the authoring scopes) |
| `algorise logout [--profile] [--all]` | Clear session(s) |
| `algorise whoami` | Show resolved profile, URL, **effective org** (by name), effective project, user, scopes |
| `algorise org list` | List the organizations you belong to (`*` = selected for this profile) |
| `algorise org use SLUG_OR_UUID` | Persist the organization this profile acts in (clears any pinned project) |
| `algorise projects [PROJECT_ID] [--kind personal\|shared]` | List accessible projects, or show one with your role and capabilities (`read`) |
| `algorise project create [--name] [--description] [--audience restricted\|organization] [--instructions] [--icon] [--color] [--mascot] [--json]` | Create a shared project; `--json` is the only way to set `seed_team_id` / nested `members` (`projects:write`) |
| `algorise project use NAME_OR_UUID` | Pin the project this profile places new content in; refuses a view-only project (`read`) |
| `algorise project clear` | Remove the pinned project from this profile (offline) |
| `algorise profiles` (alias `envs`) | List configured profiles |
| `algorise use NAME` | Set the active profile |
| `algorise publish PATH [--title] [--summary] [--type] [--agent] [--to …] [--collection ID] [--dataset ID] [--project ID] [--folder ID]` | Publish a file; `--project`/`--folder` are the Library and report homes and are never defaulted from the pin |
| `algorise publish --url URL [--title]` | Publish a URL as a link |
| `algorise publish --text "…" [--title]` | Publish inline markdown as a note |
| `algorise note TEXT [--title] [--summary] [--agent]` | Publish an inline note |
| `algorise artifacts [--target] [--limit]` | List published artifacts |
| `algorise inbox [--limit]` | List artifacts with compiled previews |
| `algorise update ARTIFACT_ID BODY [--review]` | Post a progress update |
| `algorise notify TITLE MESSAGE [--url]` | Send a standalone notification |
| `algorise review ARTIFACT_ID [BODY]` | Request review and show the thread |
| `algorise pull SESSION [--since ISO]` | Poll a collaboration session for replies |
| `algorise quota` | Show remaining ingest quota |
| `algorise search QUERY [--types T1,T2] [--limit N]` | Search the knowledge base, skills, agents, conversations (`read`) |
| `algorise library search --query Q [--scope auto\|all\|PROJECT_UUID] [--top-k N]` | Semantic Library search over one scope; a project uuid narrows it to that project (`read`) |
| `algorise library read ITEM_ID [--offset N] [--section S] [--limit N]` | Read one authorized Library item's text (`read`) |
| `algorise library browse --scope personal\|project\|org\|shared [--project ID] [--folder ID] [--kind K]… [--query Q]` | Browse one explicit Library home; project scope requires `--project`, folders are personal-only (`read`) |
| `algorise library upload PATH --scope personal\|project [--project ID] [--folder ID]` | Upload into an explicit Library home; project scope requires `--project`, folders are personal-only (`collections:write`) |
| `algorise library folder list [--parent ID \| --all]` | List personal Library folders, optionally below one parent (`read`) |
| `algorise library folder create NAME [--parent ID]` | Create a root or nested personal Library folder (`collections:write`) |
| `algorise library share ITEM_ID [--team UUID[:LEVEL]]… [--user UUID[:LEVEL]]…` | Add grants without replacing existing permissions; dataset projections cannot be mutated (`collections:write`) |
| `algorise collections [COLLECTION_ID]` | List accessible collections, or documents in one (`read`) |
| `algorise skills [SKILL_ID] [--all] [--limit N] [--project ID]` | List org skills, or show one's full instructions (`read`) |
| `algorise agents [AGENT_ID] [--status S] [--limit N] [--project ID]` | List custom agents, or show one's configuration (`read`) |
| `algorise routines [TASK_ID] [--status S] [--limit N] [--project ID]` | List scheduled routines, or show one in full (`read`) |
| `algorise tools [TOOL_ID] [--all] [--project ID]` | List custom tools, or show one in full (`read`) |
| `algorise instructions [INSTRUCTION_ID] [--limit N] [--project ID]` | List instructions, or show one in full (`read`) |
| `algorise workflows [WORKFLOW_ID] [--status S] [--limit N] [--project ID]` | List workflows, or show one's graph (`read`) |
| `algorise agent create --name N --display-name D [--description] [--system-prompt \| --system-prompt-file] [--model] [--temperature] [--skill ID]… [--collection ID]… [--project ID] [--json FILE]` | Create a custom agent as a personal draft (`agents:write`) |
| `algorise skill create --name N --description D (--file PATH \| --instructions TEXT) [--project ID]` | Create an org skill with private visibility (`skills:write`) |
| `algorise skill update SKILL_ID [--name] [--description] [--file \| --instructions] [--project ID]` | Update an org skill; sends only the fields you pass. `--project` moves it into a project — a one-way move, there is no flag that takes it back out (`skills:write`) |
| `algorise routine create (--file routine.json \| --title T --prompt P --schedule S [--at] [--day-of-week] [--day-of-month] [--cron] [--run-at] [--tz] [--agent-id] [--timeout] [--project ID])` | Create a scheduled routine (`routines:write`) |
| `algorise collection create --name N [--description D] [--json FILE]` | Create a knowledge collection in DRAFT state (`collections:write`) |
| `algorise upload PATH [PATH …] --collection ID [--folder ID] [--title T] [--replace ID] [--wait] [--timeout S] [--continue-on-error]` | Upload documents into a collection; also `algorise doc upload` (`collections:write`) |
| `algorise tool create (--file tool.json \| --display-name X [--name slug] [--description] [--base-url URL] [--auth-type …] [--auth-header NAME] [--project ID])` | Create a custom HTTP tool, private visibility (`tools:write`) |
| `algorise instruction create --name N --description D --trigger T (--content C \| --content-file PATH) [--project ID]` | Create an @-trigger instruction, personal visibility; a trigger is unique per layer, so the same trigger can exist once per project (`instructions:write`) |
| `algorise workflow create (--file wf.json \| --name N --display-name D [--description] [--icon]) [--project ID]` | Create a workflow and set its graph (`workflows:write`) |
| `algorise workflow import --file PATH [--display-name] [--description] [--tidy] [--project ID]` | Create a workflow from an export/graph JSON in one validated call; prefer over `create --file` (`workflows:write`) |
| `algorise workflow update WORKFLOW_ID [--file] [--display-name] [--description] [--project ID] [--tidy]` | Update a workflow's graph, names, or project placement (`workflows:write`) |
| `algorise agent update AGENT_ID [--name] [--display-name] [--description] [--system-prompt \| --system-prompt-file] [--model] [--temperature] [--skill ID]… [--collection ID]… [--add-tool SPEC]… [--remove-tool SPEC]… [--project ID] [--json FILE]` | Update a custom agent, including its tool assignment (`agents:write`) |
| `algorise agent delete AGENT_ID` | Delete a custom agent (`agents:write`) |
| `algorise tools --available` | List the assignable tool catalogue: builtin, custom, Composio, workflow (`read`) |
| `algorise datasets [NAME_OR_ID] [--limit N] [--sample N] [--project ID]` | List datasets, or show one's columns and types; `--project` also scopes a name lookup; `--sample` adds real rows (`read`) |
| `algorise dataset create PATH [--name N] [--table T] [--description D] [--header-row N] [--exclude COL]… [--type COL=TYPE]… [--team UUID]… [--org-wide] [--highlight] [--project ID] [--dry-run]` | Import a CSV/Excel file as one dataset (`datasets:write`) |
| `algorise dataset create-empty --name N --column NAME=TYPE… [--table T] [--description D] [--team UUID]… [--org-wide] [--project ID] [--dry-run]` | Create a dataset from typed columns with no rows; fill it later with append (`datasets:write`) |
| `algorise dataset append DATASET PATH [--duplicates append_all\|skip\|reject\|replace] [--key COL]… [--project ID]` | Append a CSV/Excel file's rows to an existing dataset (id or exact name); `--duplicates replace --key COL` upserts by key (`datasets:write`) |
| `algorise dataset replace DATASET PATH [--project ID]` | Replace every row of an existing dataset with a CSV/Excel file, in one transaction (`datasets:write`) |
| `algorise integrations accounts [--integration TYPE]` | List your connected integration accounts; `IN USE` marks the one actions run through (`read`) |
| `algorise integrations actions QUERY [--integration TYPE] [--limit N]` | Search actions on your integrations, e.g. `"read latest emails"` (`read`) |
| `algorise integrations action SLUG` | Show an action's input schema, the account it uses and whether it is allowed (`read`) |
| `algorise integrations run SLUG [--input JSON \| --input-file PATH] [--account ID]` | Run an allowed action as you through your existing connection (`integrations:execute`) |
| `algorise dataset create-multi PATH [--sheet NAME]… [--all-sheets] [--sheet-header SHEET=ROW]… [--prefix P] [--exclude SHEET:COL]… [--type SHEET:COL=TYPE]… [--team UUID]… [--org-wide] [--highlight] [--project ID] [--dry-run]` | Import an Excel workbook as one dataset per sheet, all-or-nothing (`datasets:write`) |
| `algorise apps [APP_KEY] [--project ID]` | List mini apps, or show one's tabs and numbered blocks; `--project` disambiguates the same app key across projects and needs an `APP_KEY` (`read`) |
| `algorise app templates` | List the gallery templates `app create --template` can clone (`read`) |
| `algorise app create [--title T] [--description D] [--icon I] [--template KEY] [--dataset-binding TEMPLATE_NAME=YOUR_DATASET_NAME]… [--project ID]` | Create a mini app, blank or cloned from a gallery template; `--project` creates it inside that project (`apps:write`) |
| `algorise app add-block APP_KEY --type T (--json FILE \| --content TEXT) [--tab-id ID] [--project ID]` | Add a panel to an app, optionally into a specific tab (`apps:write`) |
| `algorise app update-block APP_KEY BLOCK_ID (--json FILE \| --content TEXT) [--type T] [--project ID]` | Replace a panel's data/content/type (`apps:write`) |
| `algorise app delete-block APP_KEY BLOCK_ID [--project ID]` | Delete a panel (`apps:write`) |
| `algorise app reorder-blocks APP_KEY --order REF[,REF…] [--tab-id ID] [--project ID]` | Reorder panels by block id or 1-based position; partial orders front-load (`apps:write`) |
| `algorise app add-tab APP_KEY --label L [--project ID]` | Add a tab; the first tab folds existing panels into "Overview" (`apps:write`) |
| `algorise app rename-tab APP_KEY TAB_ID --label L [--project ID]` | Rename a tab (`apps:write`) |
| `algorise app delete-tab APP_KEY TAB_ID [--project ID]` | Delete a tab and its panels (`apps:write`) |

Apps hold at most 10 tabs of 10 blocks each. Panels bind to a dataset by **name**,
not id — `algorise datasets NAME --sample 3` is the way to get the exact column
names and value shapes a `group_by` / `value_column` needs before writing one.
That applies to `--dataset-binding` too: **both sides are names**. A dataset uuid
there is substituted in as a literal `dataset_name`, resolves to nothing, and every
panel fails at refresh. `algorise app templates` prints each template's slots.

**Only `--type dataset` is live.** It re-queries on every open, so the panel tracks
the dataset. `chart`, `table`, `kpi_card` and `metric_row` are static: whatever
values you write are baked in and never change. For a chart that follows a dataset,
use a dataset block with `"view": "chart"` — not `--type chart`:

```bash
cat > block.json <<'JSON'
{"title": "Revenue by store", "view": "chart", "dataset_name": "Parity Sales",
 "group_by": "store", "value_column": "revenue", "agg": "sum"}
JSON
algorise app add-block /apps/dash.json --type dataset --json block.json
```

`--json` takes a **file path**, not inline JSON. General-purpose views: `table`, `kpi`,
`metric_row`, `chart`, `heatmap`, `scatter`, `board`, `schedule`, `download` (a few more
exist for specific templates). A client-supplied `source_query` is refused with a 422
pointing here — raw SQL is agent-only.

Panels refresh when someone **opens the app**; there is no dataset-change hook. A
dashboard authored from the CLI renders its first numbers on first open.

### Write scopes

Authoring commands need a session minted with the matching write scope. Existing
sessions carry `publish,read` only and keep working unchanged for everything else:

```bash
# requests agents:write, skills:write, routines:write, collections:write,
# tools:write, instructions:write, workflows:write, apps:write, datasets:write,
# projects:write (plus read)
algorise login --write
```

`projects:write` is newer than the rest, so a session minted before it cannot run
`project create` — that one command answers 403 until you re-run `algorise login
--write`. Every other command keeps working on the older token.

### Connected integrations

`algorise integrations ...` (and the matching MCP tools) reach the integrations
you already connected in Algorise (Gmail, Slack, ...). OAuth credentials stay on
the backend; the CLI only names the action and its arguments. Running an action
needs its own scope, which `--write` does not include:

```bash
algorise login --integrations          # read + integrations:execute
algorise integrations accounts --integration gmail
algorise integrations actions "read latest emails" --integration gmail
algorise integrations action GMAIL_FETCH_EMAILS
algorise integrations run GMAIL_FETCH_EMAILS --input '{"max_results": 1, "query": "subject:test"}'
```

The backend applies the same checks as chat: your role, the tenant, team and
project integration permissions, the connected tier and your tool approval
settings. An action set to "ask" is refused (`approval_required`) because there
is no approval prompt outside the app; run it from chat or change its setting.
Refusals carry a stable code: `not_connected`, `connection_expired`,
`ambiguous_account` (pass `--account`), `account_mismatch`, `permission_denied`,
`approval_required`, `not_in_tier`, `invalid_arguments`, `unknown_action`,
`action_failed`, `backend_error`.

`--run-at` is sent as **local wall-clock** time together with `--tz`; the server
converts it. Do not pre-convert to UTC.

### Provisioning notes

None of these create routes is idempotent and none takes an idempotency key:

- collection names are unique per org, so re-running `collection create` returns **409**;
- documents auto-rename on a title collision, so a retried `upload` **silently duplicates**;
- tool names collide as **422** — the CLI always sends an explicit `name` so the
  collision is deterministic rather than silently suffixed with `-2`;
- a duplicate instruction trigger surfaces as **400** (not 409).

`upload` uploads **serially** and, by default, aborts on the first failure;
`--continue-on-error` uploads the rest and exits 1 with a summary. Unsupported
file types are rejected locally, before the request, because the server accepts
the upload and only discovers the problem in a background task. `--wait` polls
each job's status until it is `completed` or `failed`.

`algorise quota` reports the Bridge ingest byte caps only. The per-plan
collection and document **count** caps — the ones that actually return **402**
mid-run — have no read endpoint, so a bulk run can still fail partway.

`workflow create` is **two calls**: `POST /workflows` then a `PATCH` carrying
`graph_definition` (the create route has no graph field). There is no rollback —
if the PATCH fails, the CLI says so explicitly and the workflow exists with an
empty graph. Workflows are created in `draft`; validate and activate them in the
Workflow Builder.

`tool create` never takes the credential as a flag. Set `ALGORISE_TOOL_SECRET`
or put `auth_secret` in the `--file` body.

### Importing datasets

`dataset create` is **two calls**: the file is uploaded for a preview (nothing is
written, the preview expires) and then confirmed into a real PostgreSQL table.
`--dry-run` stops after the first call and prints the inferred schema, which is
how you check the types before committing to a table:

```bash
algorise dataset create sales.xlsx --dry-run
algorise dataset create sales.xlsx --exclude notes --type amount=NUMERIC --team <uuid>
```

Datasets are **private to you** by default. `--team` shares with specific teams;
`--org-wide` is organization-admins only. Table names are unique per org, so a
name collision is a **409** — pass `--table` (or `--prefix`, below) to resolve it.

`dataset create-multi` imports an Excel workbook as one dataset per sheet. It is
all-or-nothing server-side: if any sheet fails, every table created by that
request is rolled back. Start with a dry run, since only it can tell you the
sheet names and detected header rows:

```bash
algorise dataset create-multi 2026-budget.xlsx --dry-run
algorise dataset create-multi 2026-budget.xlsx --sheet Q1 --sheet Q2 \
  --sheet-header Q1=2 --prefix "Budget 2026"
```

By default only sheets the server flags as likely tables are imported — a notes
or keyword tab would otherwise become a one-column dataset. `--all-sheets` takes
them anyway; `--sheet` names them explicitly. `--sheet-header SHEET=ROW` forces a
0-indexed header row for one sheet (`-1` = headerless, synthetic column names),
which is what messy workbooks with title rows above the real header need.
`--prefix` prepends to every dataset **and** table name, so two workbooks that
both contain a `Sheet1` do not collide.

Because per-sheet flags need to say which sheet they mean, `--exclude` and
`--type` take a `SHEET:COLUMN` form here rather than the bare column name the
single-file command uses. A selector matching no column — or naming a sheet that
is not being imported — is an error, not a silent no-op.

### Destinations (`--to`)

Type is inferred from the file extension; `--type` overrides. Default destination
is the reviewable inbox.

| Extension | Type | Valid `--to` targets |
|---|---|---|
| pdf, docx, pptx, md, mdx, txt | `document` | inbox, collection, report |
| html, htm, xml | `page` | inbox, report |
| png, jpg, jpeg, svg, mp4 | `media` | inbox, collection |
| csv, json | `data` | inbox, dataset |
| (`--url`) | `link` | inbox |
| (`--text`) | `note` | inbox, collection |

`--collection ID` / `--dataset ID` imply the matching `--to`. Collection and
dataset targets embed content and are metered — check `algorise quota` first.
Over-cap ingests return **HTTP 402**; saturated org queues return **HTTP 429**.

## MCP server

The bundled MCP server (`algorise-mcp`, stdio transport) exposes the same
capabilities to in-editor agents. It reuses the CLI session in `~/.algorise/config`.

Run `algorise login` once, then register the server.

**Claude Code / Cursor** (`.mcp.json`):

```json
{
  "mcpServers": {
    "algorise": {
      "command": "uvx",
      "args": ["--from", "algorise", "algorise-mcp"]
    }
  }
}
```

If installed in a virtualenv, use `"command": "algorise-mcp"` instead.

Override API URL or token per server via `env` (`ALGORISE_BRIDGE_API_URL`,
`ALGORISE_BRIDGE_TOKEN`). For multiple backends, register separate server entries
with `ALGORISE_BRIDGE_PROFILE` in each `env` block.

### MCP tools

| Tool | Description |
|---|---|
| `publish_note(text, title?, summary?, agent?)` | Publish an inline markdown/MDX note |
| `publish_artifact(..., to?, collection_id?, dataset_id?, project_id?, folder_id?)` | Publish content to a destination; `project_id`/`folder_id` are the Library and report homes |
| `post_update(artifact_id, body, review_requested?)` | Post a progress update or request review |
| `notify(title, message, action_url?)` | Send a standalone notification |
| `get_inbox(limit?)` | List recently published artifacts (with previews) |
| `get_quota()` | Remaining ingest quota headroom |
| `request_review(artifact_id, body)` | Request a human review |
| `get_artifact(artifact_id)` | Fetch one artifact and its review state |
| `list_artifact_replies(artifact_id, since?)` | Replay an artifact's collaboration thread |
| `pull_updates(agent_session_id, since?)` | Poll a session for new replies |
| `browse_library(scope, project_id?, folder_id?, kinds?, query?, source?, favorites_only?, cursor?, limit?)` | Browse one explicit Library home; project scope requires `project_id` (`read`) |
| `list_library_folders(parent_id?, all_folders?)` | List root, nested, or all personal folders (`read`) |
| `upload_library_file(path, scope, project_id?, folder_id?)` | Upload a local file into an explicit personal/project home (`collections:write`) |
| `create_library_folder(name, parent_id?)` | Create a root or nested personal folder (`collections:write`) |
| `share_library_item(item_id, team_grants?, user_grants?)` | Add grants to a concrete item; dataset projections are read-only (`collections:write`) |
| `list_datasets(limit?, offset?, project_id?)` | List datasets, optionally scoped to one project (`read`) |
| `list_integration_accounts(integration?)` | List your connected integration accounts (`read`) |
| `search_integration_actions(query, integration?, limit?)` | Search actions on your integrations (`read`) |
| `get_integration_action(slug)` | An action's input schema, account and approval state (`read`) |
| `run_integration_action(slug, arguments?, account_id?)` | Run an allowed action as you; mounted only with `integrations:execute` |
| `create_skill(name, description, instructions, project_id?)` | Create an org skill with private visibility, optionally placed in a project (`skills:write`) |

## License

MIT
