Metadata-Version: 2.4
Name: luge-cli
Version: 0.23.0
Summary: Drive the Luge AI-employee platform from the command line — boards, agents, chat, workflows, and more, built for agents like Claude Code.
Project-URL: Homepage, https://github.com/Tchat-N-Sign/luge-cli
Project-URL: Repository, https://github.com/Tchat-N-Sign/luge-cli
Project-URL: Issues, https://github.com/Tchat-N-Sign/luge-cli/issues
Author-email: Sylvain Boily <sylvainboilydroid@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai,claude-code,cli,kanban,luge
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27
Requires-Dist: rich>=13.7
Requires-Dist: tomli-w>=1.0
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# luge-cli

[![CI](https://github.com/Tchat-N-Sign/luge-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Tchat-N-Sign/luge-cli/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/luge-cli.svg)](https://pypi.org/project/luge-cli/)
[![Python](https://img.shields.io/pypi/pyversions/luge-cli.svg)](https://pypi.org/project/luge-cli/)

A command-line client for the **Luge AI-employee platform** — built to let an
agent (Claude Code) drive Luge: work kanban boards, run scheduled tasks and
workflows, chat in channels / DMs / with AI agents, manage artifacts, personal
skills, and inbound webhooks, and read/update tenant settings.

Commands are grouped by noun: `board` and `card` (read + work a board),
`schedule` (recurring agent tasks), `workflow` + `activity` (run and inspect
agent workflows and their runs), `channel` / `dm` / `agent` / `colleagues` (group
chats, direct messages, talking to an AI, and the people directory), `artifact`
(conversation deliverables), `skill` (your personal Luge skills), `webhook`
(inbound webhook endpoints), `settings` (tenant config), and `auth` (local
credentials). No card deletion, no board/column management.

## Install

From [PyPI](https://pypi.org/project/luge-cli/):

```bash
uv tool install luge-cli     # recommended — installs the `luge-cli` command globally
# or: pipx install luge-cli
# or: pip install luge-cli
```

Then install the bundled Claude Code skill so an agent knows how to drive the CLI:

```bash
luge-cli claude skill install
```

With `uv tool`, make sure uv's bin dir is on your `PATH` (once): `uv tool update-shell`.
Upgrade with `uv tool upgrade luge-cli` (or `pip install -U luge-cli`).

### From source (development)

```bash
cd luge-cli
make install        # editable uv tool install + the skill
```

`--editable` means `git pull` updates the command with no reinstall. `make help`
lists every target.

## Configure

Needs the Luge API base URL and a tenant API key (`luge_…`). The base URL is the
full API base, including any deployment prefix (usually `/api`).

```bash
luge-cli auth init --url https://luge.example.com/api --token luge_xxx
luge-cli auth show    # verify (token is masked)
```

`LUGE_URL` / `LUGE_TOKEN` environment variables override the stored file. The
config lives at `${XDG_CONFIG_HOME:-~/.config}/luge-cli/config.toml`, written
`0600` — never commit it.

### Output for agents and scripts

Every command that prints data takes `--json`. `LUGE_CLI_JSON=1` turns it on for
all of them at once, so an agent or a CI job sets it once instead of remembering
a flag on every call:

```bash
export LUGE_CLI_JSON=1
luge-cli table row list Expenses      # JSON, no flag
luge-cli table row list Expenses --json   # same, explicit
LUGE_CLI_JSON=0 luge-cli table list # human view, whatever the environment says
```

This matters more than it looks: the human views are grids that fit themselves
to the terminal and clip what does not fit — and outside a terminal that width
is 80 columns, so a piped `table row list` silently ellipsizes long cells. `--json` is
never truncated. If something reads the output rather than a person, give it
JSON.

(It is `LUGE_CLI_JSON`, not `LUGE_JSON`: `LUGE_*` is the platform's own
namespace — this only concerns the CLI's output.)

## Use

Commands are grouped by noun (`board`, `card`, `auth`, `skill`):

```bash
luge-cli board list                                  # boards you can access
luge-cli board show Roadmap                          # columns + cards (id or name substring)
luge-cli board show Roadmap --limit 50               # show more of each column
luge-cli card list Roadmap --status open --tag bug   # filter by tag/color/column/assignee/priority
luge-cli card show ROL-17                            # one card + comment thread + artifacts
luge-cli card history ROL-17                         # who did what to the card, newest first
luge-cli card mine                                   # cards assigned to you
luge-cli card search "login" --status open           # free-text across your boards

luge-cli card create Roadmap "In progress" "Fix login bug" --priority high --tag bug
luge-cli card comment ROL-17 "Picking this up"
luge-cli card move ROL-17 "In progress"
luge-cli card done ROL-17                            # sets the completed flag (not the column)
luge-cli card reopen ROL-17

luge-cli card attach ROL-17 ./report.pdf             # upload a local file and link it to the card
luge-cli card detach ROL-17 3                        # detach attachment #3 (or its link id)
luge-cli card read ROL-17 1                          # read attachment #1's content

luge-cli <group> <command> --json                    # structured output for scripts/agents
```

A card is named by the `display_id` every listing prints (`ROL-17`) or its uuid —
its board is worked out for you, so you can paste back what you just read. Add
`--board` to name a card by a title substring instead (`card show "login" --board
Roadmap`), or to settle the rare case of two boards issuing the same id.

`card list` and `card create` still take a board first: they act on a board, not
on a card.

Boards, columns and (with `--board`) card titles are referenced by id **or** a
unique name substring; an ambiguous reference is a loud error, never a silent
guess.

## Tables

A table is a small typed data store — the "Tables" surface in the app. `table`
manages the table and its columns; `table row` reads and writes the data. The
rows sit *under* the table rather than beside it because a row has no id you can
paste on its own — the API addresses one as `/datasets/{id}/rows/{row_id}`, so
every row command names a table. (A card earns a group of its own by naming
itself: `card show ROL-17`. A row never can.)

```bash
luge-cli table list                                  # tables you can see
luge-cli table show Expenses                         # column spec + row count
luge-cli table create Expenses \
        --column 'label:text!' \
        --column 'amount:number|Amount (CAD)' \
        --column 'status:select!=open,paid' \
        --column 'due:date' \
        --column 'who:user=$user'
luge-cli table update Expenses --name Costs -d "Team costs"
luge-cli table delete Expenses                       # rows included (-y to skip the prompt)

luge-cli table row list Expenses                           # every row, paginated
luge-cli table row list Expenses -w 'status=open' -w 'amount>20' --sort amount --desc
luge-cli table row show Expenses 48b3e75d                  # one row in full, a cell per line
luge-cli table row create Expenses -s 'label=Taxi' -s 'amount=42.50' -s 'status=open'
luge-cli table row update Expenses 48b3e75d -s 'status=paid'   # short id, as printed
luge-cli table row update Expenses 48b3e75d --clear due        # empty an optional cell
luge-cli table row delete Expenses 48b3e75d
```

A **column** is `key:type`, plus `!` to make it required, `=a,b,c` for a
select's options, `=$variable` to have Luge fill it in, and `|Label` to name it
(the label defaults to a readable form of the key — `unit_price` → "Unit
price"). Types: `text`, `number`, `date`, `boolean`, `select`, `user`.

| spec | means |
| --- | --- |
| `title:text!` | required text |
| `amount:number\|Amount (CAD)` | optional number, explicit label |
| `status:select!=open,paid` | required select (the `!` rides on the type) |
| `who:user=$user` | filled with whoever inserts the row |
| `logged:date=$datetime` | filled with the insert time |

Variable columns (`$user`, `$display_name`, `$datetime`, `$email`) are filled in
by Luge, so `table row create` never asks for them.

Cells are typed by that spec: `--set amount=42` sends the number `42`, and a
value the column cannot hold is refused before the request goes out. `--where`
takes `key=value`, `key!=value`, `key~text` (contains), and `key>n` / `key>=n` /
`key<n` / `key<=n` on numbers and dates; filters AND together. Quote any filter
using `<` or `>` so the shell does not read it as a redirection.

`table row list` is a grid, so it fits its cells to the terminal; `table row
show` reads one row whole, a cell per line, without reaching for `--json`. A row is named by the
short id the grid prints (a unique prefix is enough) and never by its position
in a listing — a rank depends on the filters that produced it.

`table update --column` replaces the **whole** spec — pass every column you
keep. A kept key keeps its type, and dropping a column hides its data (it
returns if you re-add the key), so the CLI names what would be dropped and asks
first. `table show` prints the current spec.

Reads cover your own tables plus tenant-visible and shared ones; writes act on
tables you own. Sharing is managed in the app.

## Scheduled tasks

A scheduled task is a recurring prompt that fires an agent run. Manage them under
`luge-cli schedule`:

```bash
luge-cli schedule list [--enabled]                         # tasks you can see
luge-cli schedule show "Daily digest"                       # one task (id or name)
luge-cli schedule create "Daily digest" --prompt "Summarise open cards" \
        --every daily --at 09:00                            # see --every below
luge-cli schedule update "Daily digest" --every weekly:mon,fri --at 08:30
luge-cli schedule toggle "Daily digest"                     # pause / resume
luge-cli schedule run "Daily digest"                        # trigger a one-off run now
luge-cli schedule delete "Daily digest"                     # (asks to confirm; -y to skip)
```

`--every` is a compact recurrence spec (the API uses structured recurrence, not
cron): `daily`, `hourly`, `weekly:mon,wed,fri`, `monthly:15`, `interval:4h`,
`interval:2d`, `once:2026-08-01T18:00`. `--at HH:MM` sets the time(s) of day
(repeatable; defaults to `09:00`). A run's history/result isn't on the task — a
triggered run surfaces under `luge-cli activity` (see below).

## Workflows and activity

`luge-cli workflow` inspects, authors and runs multi-step agent workflows:

```bash
luge-cli workflow list [--trigger-type event] [--enabled]   # workflow definitions
luge-cli workflow show "veille-techno"                        # one definition (id or name)
luge-cli workflow create "Greeter" --agent "General Agent" --prompt "Say hi and the time"
luge-cli workflow create "Complex" --graph ./graph.json --trigger-type event
luge-cli workflow update "Complex" --graph ./graph.json      # only passed fields change
luge-cli workflow update "Relances" --enable                 # or --disable, --name, ...
luge-cli workflow delete "Greeter"                            # (asks to confirm; -y to skip)
luge-cli workflow trigger "veille-techno" --data topic=rust   # start a run (repeatable --data)
luge-cli workflow runs "veille-techno"                        # a workflow's run history
luge-cli workflow run "veille-techno" <run-id>                # one run in full
luge-cli workflow cancel "veille-techno" <run-id>             # cancel a running run
luge-cli workflow retry  "veille-techno" <run-id>             # retry a failed/cancelled run
```

`create --agent <id|name> --prompt "..."` builds a linear single-agent workflow
(start → agent → end) for you; `create --graph <file.json>` posts an arbitrary
roomkit-graph you supply (for branches, human-review, notifications, etc.).
`update` patches only the fields you pass; `--graph` replaces the graph as a
whole — `workflow show <ref> --json`, edit the `graph` object, pass it back.

An **event** trigger fires the workflow off another surface. Pass `--trigger-type
event` with a `--trigger-config` JSON that names the `source_type` (`workflow`,
`scheduled_task`, `webhook`, `board`, or `data_table`) and, optionally, filters —
e.g. fire on a new/updated row in a Luge table:

```bash
luge-cli workflow create "On new order" --agent "Ops" --prompt "Handle it" \
  --trigger-type event \
  --trigger-config '{"source_type":"data_table","source_data_table_id":"<table id>","events":["row_created"]}'
```

`luge-cli activity` is the cross-surface run inbox — every run (schedule,
workflow, webhook, notetaker, …), for diagnosing what fired and what failed:

```bash
luge-cli activity list [--status failed] [--kind schedule] [--search "..."]
luge-cli activity show <id>       # a run's message thread + human-review steps
luge-cli activity retry <id>      # re-run a failed/cancelled run
luge-cli activity stats [--days 7]
```

`show` / `retry` take either id a run carries: the `id` from `activity list` (its
chat room) or the `execution_id` — the one `schedule run` prints — so you can
paste back whichever id you were given. Workflow runs have no chat room; inspect
those with `workflow runs` instead.

## Settings

`luge-cli settings` reads and updates tenant configuration. **Most of it needs an
admin-role API key** — a member key gets 403 on the memory/pii/compliance/
web-search slices and on credentials; `agents`, `providers` and the `tenant` read
are member-safe.

```bash
luge-cli settings get memory                      # memory | pii | compliance | web-search | tenant
luge-cli settings set web-search web_search_provider=brave   # partial patch (only keys you pass)
luge-cli settings set memory rag_max_chunks=8 summarization_enabled=true
luge-cli settings agents                          # the tenant's agents (provider/model live here)
luge-cli settings agent "General Agent"           # one agent's config (id or name)
luge-cli settings providers                       # AI provider/model catalogue
luge-cli settings credentials                     # configured credentials (secrets never returned)
```

`set` values are typed: `true`/`false` → bool, numbers → int/float, `json:[...]`
or `json:{...}` for lists/objects, anything else a string.

## Conversations: channels, DMs, agents

Sending a message has three distinct surfaces — one command group each — plus a
`colleagues` directory to find who to DM.

**Group chat** — `luge-cli channel` participates in **team channels**. Reads and
posts are plain; the AI answers only when a message `@luge`-mentions it.

```bash
luge-cli channel list ; luge-cli channel browse       # channels you can see / can join
luge-cli channel show general                          # one channel (id or name)
luge-cli channel messages general --limit 50           # message history (oldest-first)
luge-cli channel post general "ship it @luge"          # @luge to invoke the agent
luge-cli channel join general ; luge-cli channel leave general
luge-cli channel create team --display "Team" --public --agent <id>
```

**Direct message** — `luge-cli dm` sends a 1:1 message to a colleague (human↔human,
never triggers the AI). `dm send` opens the DM (get-or-create) and posts in one go.

```bash
luge-cli dm send "Alex" "got a sec?"     # colleague by id, email, or name
luge-cli dm list                          # your DMs
luge-cli dm show "Alex" --limit 50        # read the thread
```

**Agent chat** — `luge-cli agent` talks to an AI agent.

```bash
luge-cli agent send "Summarise the open cards" [--agent <id|name>] [--conversation <id>]
luge-cli agent send "Any blockers?" --wait --json   # wait for the reply → {conversation_id, reply}
luge-cli agent list                                  # your agent conversations
luge-cli agent show <id> --limit 50                  # a conversation + its messages
```

By default `agent send` is **fire-and-forget**: it invokes an agent and returns the
conversation id immediately (read the reply later with `agent show <id>`). With
`--wait` it polls until the agent's text reply arrives and prints it (intermediate
tool-call steps are skipped; tune with `--timeout` / `--interval`) — the
agent-friendly "ask → answer" form, especially with `--json`. `--agent` accepts an
id or a name.

**Colleagues** — `luge-cli colleagues` is the platform people directory.

```bash
luge-cli colleagues list [--type human]   # the roster
luge-cli colleagues get "Alex"            # a colleague's profile (id, role, skills)
```

## Artifacts

`luge-cli artifact` works with conversation artifacts (markdown/html/code
deliverables). Every artifact lives in a room — the `--room` id is a
conversation/channel id (shown by `chat show` / `channel messages`).

```bash
luge-cli artifact mine                              # your artifacts (summaries)
luge-cli artifact list --room <room-id>             # a room's artifacts (with content)
luge-cli artifact show <id>                          # one artifact + its content
luge-cli artifact create --room <room-id> "Notes" ./notes.md --type markdown
echo "# Draft" | luge-cli artifact create --room <room-id> "Draft" -    # from stdin
luge-cli artifact delete <id>                        # (asks to confirm; -y to skip)
```

There is no update-in-place — `create` always inserts a new artifact.

## Personal skills

`luge-cli skill` manages your **personal Luge skills** — authored instruction sets
(name, description, instructions body, required tools, trigger phrases) that agents
can use, visible only to you. Reading works with any key; create/update/delete need
the skill-manage capability (an admin/owner key).

```bash
luge-cli skill list                        # your personal skills
luge-cli skill show <name>                  # description, instructions, requires, triggers
luge-cli skill create my-skill --description "..." --instructions "..." \
        --require terminal_run --trigger "do the thing"
luge-cli skill create my-skill --instructions-file ./SKILL.md   # body from a file
luge-cli skill update my-skill --description "..." [--instructions … --require … --trigger …]
luge-cli skill delete my-skill              # (asks to confirm; -y to skip)
```

(Not to be confused with `luge-cli claude skill`, which installs *this CLI's own*
Claude Code skill — see [Claude Code skill](#claude-code-skill) below.)

## Inbound webhooks

`luge-cli webhook` manages inbound webhook endpoints — receivers Luge hosts so an
external system can drive automation. Each endpoint POSTs arriving at its
`inbound_url` are routed to an **agent**, a **workflow**, or a **data table** (or
just logged). The CLI creates/inspects endpoints and reads their deliveries;
Luge's server receives the webhooks.

```bash
luge-cli webhook list
luge-cli webhook create "GitHub CI" --to agent --target "General Agent" --source github
        # prints the inbound_url + signing secret (secret is shown ONCE — save it)
luge-cli webhook create "Stripe" --to workflow --target "veille-techno" --source stripe
luge-cli webhook create "Orders" --to data_table --target "Orders" \
        --map customer=data.customer.name --map amount=data.total   # one row per POST
luge-cli webhook create "Debug" --to log_only          # just log, triggers nothing
luge-cli webhook show <id|name>
luge-cli webhook deliveries <id|name>                   # what has arrived (the journal)
luge-cli webhook delivery <id|name> <delivery-id>       # one delivery in full
luge-cli webhook update <id|name> --disable | --regenerate-secret
luge-cli webhook delete <id|name>
```

`--to agent|workflow` resolves `--target` (an agent or workflow, by id or name)
into the endpoint's destination. `--to data_table` resolves `--target` to a table
and maps the payload into a row: each `--map <column>=<payload.path>` fills a
column from a dot-path into the delivery body, and every required column must be
mapped. The signing `secret` is returned only on create and `--regenerate-secret`
— capture it then, it is not shown again.

## Claude Code skill

The package bundles a Claude Code skill (`luge-platform`) that teaches an agent the
whole CLI — the card-work protocol (read the thread, announce, deliver, mark done
only when asked) plus the schedule / workflow / activity / channel / dm / agent /
artifact / webhook / settings surfaces and their non-obvious semantics. `make
install` installs it; to (re)install it on its own:

```bash
luge-cli claude skill install --force   # copies it to ~/.claude/skills/luge-platform
```

Unlike the editable CLI, the skill is a **copy** — it does not update on
`git pull`. Re-run `luge-cli claude skill install --force` after the bundled skill
changes.

## Develop

```bash
make sync            # create/refresh the dev environment
make hooks           # install the pre-commit git hooks (once)
make check           # lint (ruff) + type-check (mypy) + test (pytest) — what CI runs
make format          # auto-fix lint issues and format
```

`make test` runs the suite (filters, resolution, HTTP client mocked, config, CLI
wiring). CI (GitHub Actions) runs lint, type-check, and test on every push and PR.

### Release

```bash
make build           # sdist + wheel (runs `check` first)
make publish         # upload to PyPI via twine + ~/.pypirc  (REPO=testpypi to test)
make release         # tag vX.Y.Z + create a GitHub release from CHANGELOG.md
```

Bump `version` in `pyproject.toml` and add a `CHANGELOG.md` entry before releasing.
