Skip to main content

luge-cli

CI PyPI Python

A command-line client for the Luge AI-employee platform — built to let an agent (Codex or 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), todo (your personal todo list) and plan (compose a day plan), schedule (recurring agent tasks), workflow + activity (run and inspect agent workflows and their runs), notification (your notification-center inbox), channel / dm / agent / colleagues (group chats, direct messages, talking to an AI, and the people directory), artifact (conversation deliverables), skill (authored Luge skills), webhook (inbound webhook endpoints), settings (tenant config), auth (local credentials), and profile (switching between saved backends). No card deletion, no column management.

luge-cli mcp serves that same surface to an MCP client over stdio, with the tools generated from the CLI itself — see MCP server.

Install

From PyPI:

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 skill for the agent that will drive the CLI:

luge-cli claude skill install   # ~/.claude/skills/luge-platform
luge-cli codex skill install    # ${CODEX_HOME:-~/.codex}/skills/luge-platform
luge-cli hermes skill install   # ${HERMES_HOME:-~/.hermes}/skills/luge-platform
luge-cli kimi skill install     # ${KIMI_CODE_HOME:-~/.kimi-code}/skills/luge-platform
luge-cli openclaw skill install # ${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/luge-platform
luge-cli qwen skill install     # ~/.qwen/skills/luge-platform
luge-cli cursor skill install   # ~/.cursor/skills/luge-platform
luge-cli opencode skill install # ${XDG_CONFIG_HOME:-~/.config}/opencode/skills/luge-platform
luge-cli cline skill install    # ~/.cline/skills/luge-platform
luge-cli kilo skill install     # ~/.kilo/skills/luge-platform (or ~/.kilocode, if that is where yours live)

With uv tool, make sure uv's bin dir is on your PATH (once): uv tool update-shell.

Then upgrade with luge-cli upgrade: it runs your own installer's upgrade (uv tool, pipx or pip — read off the receipt beside the interpreter, not guessed) and then rewrites every copy of the skill that already exists on the machine, which uv tool upgrade luge-cli alone does not. luge-cli upgrade --check reports what is installed and which copies are out of step; --skill-only refreshes the copies without fetching anything.

You do not have to remember to check. Every request carries the CLI's version (User-Agent: luge-cli/X.Y.Z … and X-Luge-Cli-Version), so a deployment that knows a newer release exists answers X-Luge-Cli-Latest on whatever call was being made — and the CLI prints one line on stderr, at most once a day, JSON under LUGE_CLI_JSON so an agent parses it. No index is polled and no third party is contacted; LUGE_CLI_UPGRADE_NOTICE=0 turns the line off.

Installed editable (make install-cli), the code is live but the version is not: luge-cli --version reads the package metadata written at install time, so it keeps reporting the old number until you re-run make install-cli (which make release now does for you). Only the reported version is stale — the commands themselves are whatever is on disk.

From source (development)

cd luge-cli
make install        # editable uv tool install + the Codex and Claude Code skills

--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).

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

Without a key to paste, pair the machine in a browser instead:

luge-cli auth login --url https://luge.example.com/api

It asks the server for a pairing and prints a link plus a short code — approve it in a browser (or enter the code from a phone) and the key the server mints for that approval is written into the profile. The key is scoped to a snapshot of your capabilities, shows up in auth key list under this machine's name, and revoking it there ends this CLI's access. Nothing is written if the pairing is refused or expires. A machine with no human at a browser — CI, an unattended agent — still uses auth init.

No Luge account yet is not a different command. Signed out, the same link asks you to sign in or to create an account, and the approval screen is where you land once you are through — a new account means signing up and going through the setup first, all in that one tab. The command keeps waiting for as long as the pairing lives, so there is nothing to come back and re-run.

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.

A third variable, LUGE_EXTERNAL_TOKEN, carries an external collaborator's session instead of a member's API key — see Being the guest. It replaces the key rather than joining it, and it is sticky: while it is set, every command speaks as that guest. unset LUGE_EXTERNAL_TOKEN goes back to your own key, and auth show names whichever of the two will actually be sent.

Profiles

Credentials are stored under named profiles, so a dev and a prod backend can coexist and you switch instead of retyping:

luge-cli profile add dev  --url http://localhost:8000/api --token luge_dev_xxx
luge-cli profile add prod --url https://luge.example.com/api --token luge_prod_xxx

luge-cli profile list                     # all profiles, `*` marks the active one
luge-cli profile use prod                 # switch the persistent default
luge-cli --profile dev board list         # one-off override (flag goes before the subcommand)
LUGE_CLI_PROFILE=dev luge-cli board list  # per-shell/session override

Selection precedence: --profile > LUGE_CLI_PROFILE > profile use > the sole stored profile. LUGE_URL / LUGE_TOKEN still override whatever profile was selected, field by field. auth init stays the first-run bootstrap: it writes whichever profile the run targets (default on a fresh machine). A config file from before profiles existed keeps working — it reads as a profile named default and is rewritten in the new shape on the next write.

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:

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.)

Saying an AI wrote it

A second switch belongs in the same export block when an agent is driving: LUGE_CLI_AS_AGENT=1 marks every message it sends as written by an AI. The message keeps the key holder's identity — same sender, same name — and the conversation records who composed it, so a colleague reading a DM or a channel sees an AI tag instead of assuming a person typed it.

export LUGE_CLI_AS_AGENT=1                        # a whole agent session
luge-cli dm send "Alex" "deployed, all green"     # marked AI-written
luge-cli dm send "Alex" "on my way" --as-human    # relaying Alex's colleague verbatim
luge-cli channel post general "build is red" --as-agent   # one message, no export

It is declared, never inferred: the platform does not read an API key as an AI (a key is as likely to belong to a deploy script), so a message carries no marker unless the sender asks for one. Nothing sent before this existed changes — no marker reads as human, which is what it always was. Reading back, dm show / channel messages tag those messages [AI], and --json carries author_kind.

luge-cli mcp turns it on by itself: only a model calls a tool on that transport. A host that drives the server from a script can say LUGE_CLI_AS_AGENT=0 and keep its own answer.

A second nicety is on by default: after any command that talked to the API, one line on stderr flags unread notifications (🔔 3 unread notification(s) — luge-cli notification list). In JSON mode — --json on the command or LUGE_CLI_JSON=1 — the bell matches the form: {"unread_notifications": 3, "hint": "luge-cli notification list --unread --json"}. The streams stay separate on purpose: stdout is the command's JSON document, stderr is the bell's — each parses on its own; don't merge them with 2>&1 before parsing. It rings only after successful, networked commands, never for the notification group itself (you are looking at the inbox), and any failure of the probe is silent — it can never break a command or pollute piped/--json stdout. Acknowledging (notification ack) is what stops the re-alert.

Turning it off, most specific wins: --no-notify (root flag, one run) > LUGE_CLI_NOTIFY=0 (shell/session) > luge-cli notification disable (persisted in the config file; notification enable restores the default).

Use

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

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 board create Roadmap -d "Product roadmap"   # new board, seeded with default columns
luge-cli board create Feuille -l fr                  # seed the columns in French (À faire…)
luge-cli board update Roadmap --name "Q3 Roadmap"    # rename, describe, or set the card prefix
luge-cli board update Roadmap --prefix RDM           # only renumbers cards created from then on
luge-cli board history Roadmap                       # audit trail: the board's own facts + its cards'
luge-cli board archive Roadmap                       # freeze it read-only; the cards stay
luge-cli board list --archived                       # what is frozen (the default list hides it)
luge-cli board unarchive Roadmap                     # writable again, back in the active list
luge-cli board delete Roadmap                        # destroys the board and every card on it (asks first)
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 create Roadmap "To do" "Sub-task" --parent ROL-17  # grouped at creation
luge-cli card comment ROL-17 "Picking this up"
luge-cli card move ROL-17 "In progress"
luge-cli card move-board ROL-17 "Roadmap"            # transfer to another board (card is renumbered)
luge-cli card set-parent ROL-18 ROL-17               # group ROL-18 under ROL-17 (same board)
luge-cli card remove-parent ROL-18                   # detach it again
luge-cli card reorder-children ROL-17 ROL-20 ROL-18  # set the sub-cards' manual order
luge-cli card done ROL-17                            # sets the completed flag (not the column)
luge-cli card reopen ROL-17
luge-cli card archive ROL-17                         # off the board, nothing deleted (not the done flag)
luge-cli card list Roadmap --archived                # what is put away (the default list hides it)
luge-cli card unarchive ROL-17                       # back in the column and position it left
luge-cli card delete ROL-17                          # destroys it (sub-cards survive, detached); --yes skips the prompt

luge-cli card attach ROL-17 ./report.pdf             # upload a local file and link it to the card
luge-cli card attach ROL-17 --document loi25          # link a document already in the corpus (id or name)
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.

A board is retired in one of two ways, both owner-or-manager only. board archive freezes it read-only and takes it out of the listings, cards kept and board unarchive undoing it. board delete is final: every card goes with the board — its comments, tags and attachment links included. Because a board is named by a substring, the board is read first so the prompt can name it and count its cards before you answer; --yes skips that prompt for scripts.

An archived board is out of board list, card mine and card search until it comes back — board list --archived is the view that shows it. unarchive and delete resolve a name against the archived boards too; every other command reads the active listing.

A single card retires the same way: card archive ROL-17 takes it off the board without deleting it, keeping the column and position card unarchive ROL-17 puts it back into. card list <board> --archived is where it can still be read — the two views select rather than overlap, so a card is in one or the other and never both — and a listed archived card is marked (archived). Its display id keeps working throughout: card show ROL-17 answers for an archived card rather than denying it exists. Archiving is not card done: done leaves the card on the board with its completed flag set, archiving takes it off the board.

card delete ROL-17 is the irreversible one: the card, its comments and its tag links go, while its sub-cards survive detached — the prompt names them before you answer, and --yes skips it. It reaches an archived card too, which with card unarchive is all the archives accept.

Todos and day plans

todo is your personal todo list — the standalone kind, the same rows kanban cards are made of but not placed on a board (cards stay in the card group; todo list --cards is the one read that crosses over). todo tag curates the tenant-wide tag vocabulary those cards and todos share.

luge-cli todo list --open --mine              # your open todos (--done, --cards, --limit)
luge-cli todo show "bank"                     # id or title substring
luge-cli todo create "Call the bank" --priority high --tag admin --due 2026-08-05
luge-cli todo update "bank" --notes "ask for Mr Roy" --due ""    # "" clears the due date
luge-cli todo done "bank" ; luge-cli todo reopen "bank"
luge-cli todo delete "bank"                   # (asks to confirm; -y to skip)
luge-cli todo tag list                        # the tenant vocabulary, most-used first
luge-cli todo tag create bug --color red      # idempotent on the name
luge-cli todo tag update bug --color sky      # recolours it on every card that links it

plan composes a day plan — the Planification surface. A plan holds schedulable entries (todos and free --note blocks) plus materials (documents and tables, which carry no time slot). Days read as today / tomorrow / yesterday / YYYY-MM-DD; times read as HH:MM local wall clock on that day (the CLI converts to a timezone-aware timestamp for the API — a full ISO datetime also works).

luge-cli plan show                            # today's plan (or a day, or a shared plan's id)
luge-cli plan days --from today --to 2026-08-31   # the days that have a plan
luge-cli plan add --todo "bank" --start 09:00 --end 09:30    # schedule a todo today
luge-cli plan add tomorrow --note "Deep work" --notes "spec review" --start 13:00 --end 15:00
luge-cli plan add --todo ROL-17               # a kanban card is a todo too
luge-cli plan add --document loi25            # material: alongside the day, no slot
luge-cli plan update <entry-id> --start 14:00 --day tomorrow  # reschedule (HH:MM needs its day)
luge-cli plan update <entry-id> --unschedule  # clear the slot, keep the entry
luge-cli plan remove <entry-id>               # the todo/document itself is untouched

The first plan add on a day creates its plan; re-adding the same resource returns the existing entry instead of duplicating it. plan show prints each entry's id — that is the handle update / remove take.

The daily-planning automation materializes a personal copy of the planning skill and a daily scheduled task that composes your day (LUG-303 in the app):

luge-cli plan automation status
luge-cli plan automation enable --time 07:30 --mode propose   # propose = you validate each plan
luge-cli plan automation enable --mode direct                 # write the plan without asking
luge-cli plan automation disable                              # task kept, never deleted

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.)

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

# Relational columns — pass the whole spec as JSON (link / lookup / rollup)
luge-cli table create Affaires --columns-json '[
  {"key":"titre","label":"Titre","type":"text","required":true},
  {"key":"entreprise","label":"Entreprise","type":"link","target_dataset_id":"<uuid>","multiple":false},
  {"key":"dom","label":"Domaine","type":"lookup","via":"entreprise","target_field":"domaine"}
]'
luge-cli table row create Affaires -s 'titre=Renouvellement' --link-by 'entreprise=nom:Acme'
luge-cli table row create Affaires -s 'titre=Onboarding' -s 'entreprise=["<row-uuid>"]'

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.

Relational columnslink, lookup, rollup — carry three or four fields each, more than the compact grammar holds, so pass the whole spec as JSON with --columns-json '[…]' or --columns-file spec.json (the array the API validates, sent through untouched — so a column type added later works with no CLI change). The three column sources are mutually exclusive; each JSON form is the whole spec.

type JSON fields (beyond key / label / type)
link target_dataset_id (uuid), multiple (bool, default true)
lookup via (a link column's key), target_field (a key on the linked table)
rollup via, aggcount|sum|avg|min|max|concat, target_field (unless agg=count)

A link cell is a list of target row ids. Write it with a bare id (-s 'entreprise=<uuid>') or a JSON array (-s 'entreprise=["<uuid>","<uuid>"]'), or name the target by a business value with --link-by 'entreprise=nom:Acme' — Luge matches that against the linked table (field == value) and errors if nothing matches. --link-by is one per link column; for several explicit targets use -s 'entreprise=["<uuid>","<uuid>"]'. lookup and rollup cells are computed by Luge, so --set never writes them. table row list / show print a link cell by the linked row's name (from the response's link_labels), and table show summarizes each relational column's wiring.

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:

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:

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.

luge-cli workflow node addresses a graph node by node, so changing one node's config no longer means round-tripping the whole graph:

luge-cli workflow node list "test table"                       # id, type, config keys of each node
luge-cli workflow node show "test table" function-1784         # one node whole (id or unique prefix)
luge-cli workflow node update "test table" function-1784 --set code=@snippet.js
luge-cli workflow node update "test table" function-1784 --set timeout_ms=5000  # typed by the node schema

node update patches only the keys you --set (repeatable): it fetches the graph, merges the change into the one node's config, and PUTs the graph back (the server validates it as a whole — there is no node endpoint). Each --set is typed against the node type's config schema the way a row's cells are typed against its column spec, so timeout_ms=5000 leaves as a number, not "5000". Use key=@file to read a value from a file — the way to carry a function node's multi-line code; an undeclared key travels as a plain string.

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:

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:

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.

Notifications

luge-cli notification reads your notification-center inbox — mentions, card assignments, run failures, meeting summaries, membership changes. The API key sees the inbox of the user who owns it.

luge-cli notification list                 # newest first; ● marks unread
luge-cli notification list --unread --json # what an agent should consume
luge-cli notification ack <id>             # mark one read (id from `list`)
luge-cli notification ack --all            # clear the whole inbox

The --unread filter applies within the fetched page (the API has no server-side unread filter) — raise --limit (max 100) or page with --offset to see more. The verb is ack, not read: card read already means display content, and acknowledging is a write. enable / disable persist the after-command bell switch (see above). The agent loop: bell on stderr → list --unread --json → handle → ack — acknowledging is what stops the re-alert.

Plan and usage

billing answers what the plan allows; usage answers what was actually spent.

luge-cli billing status            # plan, seats, and each gauge as used / limit + state
luge-cli billing plans             # every tier with its real limits, current one marked
luge-cli billing storage           # what fills your share of the quota, largest first
luge-cli usage summary --days 7    # requests, tokens, cache hit rate, cost
luge-cli usage daily --days 30     # the day-by-day curve
luge-cli usage by-model            # split by provider and model
luge-cli usage by-user             # split by the person it was spent for
luge-cli usage budget              # monthly limit, spend so far, alert state

A limit of null means the plan does not bound that axis — printed as unlimited, and its state is none. Only storage is enforced; the daily request rates warn without blocking. --days is bounded 1-365 by the API and refused outside that, never clamped.

billing status / billing plans need tenant.manage; usage needs token_usage.view.

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.

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

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

External-collaboration governance

external-collaboration is a slice like the others, gated on external_collaborator.manage rather than on the admin role — governing who from outside may collaborate is grantable without handing over the tenant configuration. It carries enabled and the two domain lists.

luge-cli settings get external-collaboration
luge-cli settings set external-collaboration enabled=false          # freeze the surface
luge-cli settings set external-collaboration blocked_domains=json:'["blocked.example"]'
luge-cli settings set external-collaboration blocked_domains=json:null   # lift the restriction

Absent and null are different answers: a key you do not pass is left alone, while json:null clears the list. allowed_domains=json:[] is stricter still — an empty allow-list admits nobody, and is kept meaningful on purpose. Disabling freezes rather than deletes: invitations are refused, entry pages 404 and live sessions are cut, but every grant stays and re-enabling restores them.

Credentials

Where a provider is pointed at — an ollama's base_url, an Anthropic api_key — and where an integration's token lives. One field at a time (credential.manage, i.e. an admin key's integrations scope).

luge-cli settings credential list [--type ollama]   # which fields are set; values never returned
luge-cli settings credential set ollama base_url http://localhost:11434
luge-cli settings credential set anthropic api_key -    # read the secret from stdin
luge-cli settings credential set anthropic api_key      # or be prompted, without echo
luge-cli settings credential delete ollama base_url --yes
luge-cli settings credential delete ollama --yes        # every field of the type

Prefer - or the prompt over typing a secret as an argument: an argument lands in the shell history and the process list. A secret is write-only — the API returns the value of descriptive fields (base_url, model) and null for anything secret, so a key can be replaced but never read back.

Nothing pings the provider: the value is stored as sent, and the first agent turn is what proves it. Deleting the last field an agent's provider needs takes that agent off the air at its next turn — these are read live, nothing is restarted for the breakage to happen.

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 chatluge-cli channel participates in team channels. Reads and posts are plain; the AI answers only when a message @luge-mentions it.

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 post general "build is red" --as-agent   # an AI wrote it, not you
luge-cli channel join general ; luge-cli channel leave general
luge-cli channel create team --display "Team" --public --agent <id>

Direct messageluge-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.

luge-cli dm send "Alex" "got a sec?"     # colleague by id, email, or name
luge-cli dm send "Alex" "deployed" --as-agent   # an AI wrote it on your behalf
luge-cli dm list                          # your DMs
luge-cli dm show "Alex" --limit 50        # read the thread

--as-agent on either send (or LUGE_CLI_AS_AGENT=1 for a whole session) keeps your identity on the message and marks it AI-written — see Saying an AI wrote it.

Agent chatluge-cli agent talks to an AI agent.

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.

Why an agent answered that wayluge-cli conversation context <id> prints what the model actually received on its last turn: the verbatim system prompt, the tool schemas, the skills manifest, the injected blocks (workspace graph, document context, retrieved memories, running summary) and the final message list. It is the command-line twin of the app's chat debug panel.

luge-cli conversation context <id>                  # the shape of the turn + the skill states
luge-cli conversation context <id> --show prompt    # the system prompt, verbatim
luge-cli conversation context <id> --show skills    # catalogue / proposed / activated
luge-cli conversation context <id> --show blocks    # every injected block, in injection order
luge-cli conversation context <id> --json           # the whole snapshot, lossless

Only on a recent conversation: the snapshot is written per agent turn into Redis with a one-hour TTL and overwritten by the next turn. Past that you get "no snapshot" plus the DB-backed injection counts, which survive.

--show skills is the one that earns the command. A skill can be in the catalogue, proposed to the model (a <skill_recommendation> block, which sits inside the messages) or activated by it — and "the skill didn't fire" covers two opposite causes, triggers that never matched and a description the model ignored, with opposite fixes. Needs token_usage.view (an admin key's compliance scope) plus access to the conversation itself.

Reactluge-cli message acts on a single message by its id (the chat "Copy ID" action). Emoji reactions work the same in a channel or a DM:

luge-cli message react <messageId> 👍       # add a reaction (safe to repeat)
luge-cli message unreact <messageId> 👍     # remove yours
luge-cli message reactions <messageId>      # who reacted, with what

Read an attached filemessage attachment opens what a message carries, in a channel or a DM alike. Text prints on stdout; anything binary reports its type and size rather than dumping bytes, and -o writes it out (a directory keeps the original filename). The download is streamed, so a meeting recording never lands in memory.

luge-cli message attachment list <messageId>              # number, filename, type, size
luge-cli message attachment get <messageId>               # the first file's text
luge-cli message attachment get <messageId> 2 -o .        # write it here, original name

Colleaguesluge-cli colleagues is the platform people directory.

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).

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.

Semantic search

luge-cli search runs the platform's unified semantic (vector) search across notes and documents — it finds meaning, not substrings (note search stays the lexical counterpart). Each hit shows its similarity, its workspace-graph node_id, and a connected: line naming what links to it in the graph (depth-1 neighbourhood).

luge-cli search "how do we handle auth tokens"       # notes + documents, by meaning
luge-cli search "auth" --type note                   # one type only (repeatable)
luge-cli search "auth" --limit 5 --min-similarity 0.3   # cast wider than the 0.5 default
luge-cli search "auth" --json                        # raw server response (diagnostics)

Documents join the results only when your token carries document read access — a restricted API key simply sees fewer types. The search needs a default embeddings agent configured on the platform; without one the server's 400 is reported as-is.

Documents — the RAG corpus

luge-cli document manages the tenant's document corpus: the knowledge layer agents retrieve from. A document is a file (or a pointer to one); rag decides whether it is chunked and embedded for retrieval, and folders are a single flat level it can be filed under.

luge-cli document list --status ready                # the corpus (filter by folder/status)
luge-cli document show "loi25"                       # type, size, RAG, folder — and why it failed
luge-cli document add ./prd.pdf --scope tenant --rag # upload and index it
luge-cli document rag "loi25" --off                  # stop indexing it (the file stays)
luge-cli document move "loi25" Specs                 # file it under a folder
luge-cli document folder create Specs                # folders are one flat level

document reference records a file that lives on Google Drive or SharePoint without copying it — the provider stays the source of truth and the corpus stores a pointer the agent resolves on demand:

luge-cli document reference "Q3 deck" --source microsoft --external-id 01ABC \
    --file-type pptx --drive-id 'b!…' --url https://sharepoint.example/x --rag

Reading what the corpus holds:

luge-cli document chunks "loi25"                     # the pieces it was split into
luge-cli document chunks "loi25" --indexes 0,3       # specific chunk indexes
luge-cli document download "loi25"                   # text prints; binary reports type + size
luge-cli document download "loi25" -o ./out.pdf      # streamed; --force to overwrite

Embedding coherence and re-indexing

Switching the tenant's embeddings agent to a model of a different vector width invalidates every vector already stored, and nothing announces it: the writes keep going, and search silently drops the rows of the old width. document embeddings is how you find out.

luge-cli document embeddings                  # the coherence report + its verdict
luge-cli document reindex                     # the cure — asks first (--yes for scripts)
luge-cli document reprocess "loi25"           # the same, for one document

The report probes the model live — one network round-trip to Ollama/OpenAI, so it is not a free read — and prints the effective vector width against the width pinned on the agent, the stored widths per corpus (document_chunks, note_chunks, memories) with their stale-row counts, any ANN index standing on an embedding column, and whether a re-index is required. An unreachable provider degrades the report instead of failing it: the widths and the indexes that need dropping still print.

document reindex drops any ANN index and re-embeds all three corpora, so it confirms before acting. It queues the work and returns — what it prints is what was accepted, not what has finished; document embeddings reads the outcome back. These four commands need document.manage; a token without it gets a plain refusal.

Sharing — who can access a resource

luge-cli share is one generic surface over every ownable object: a board, a todo, a day plan, a document or its folder, a table, a workflow, a scheduled task, a webhook, a channel, a meeting, a notetaker capture, a note. The reference is the same id-or-name used everywhere else — a plan, which has no name, is referenced by its day (share grant plan today Alice). Managing a resource's shares is owner-only (a channel also lets a channel manager do it).

luge-cli share show board Roadmap                    # visibility + who it is shared with
luge-cli share readers document "PRD loi25"          # concrete readers: members + externals
luge-cli share grant board Roadmap Alice             # read access for a colleague
luge-cli share grant table Budget Alice --write      # write (editor) instead of read
luge-cli share grant workflow Nightly --tenant       # visible to the whole tenant
luge-cli share revoke board Roadmap Alice            # take it back (--team / --tenant too)
luge-cli share request document <id>                 # ask the owner for read access

request is the other direction — the verb for a resource you cannot read. It files an approval task in the owner's inbox; approving shares the resource with you and you hear about it as a notification. Pass the id: a resource you cannot read is in no listing, so no name resolves it. For a document, that id comes from the conflict document add / card attach raises when the corpus already holds those bytes — the one place an unreadable document's id surfaces.

The API answers a request the same way whether one was filed, you could already read the resource, or a request was already pending. The CLI reports that limit instead of claiming a new ask each time.

External collaborators

luge-cli external manages collaborators from outside the tenant. They are not colleagues: no seat, no capability, never in the people directory — they can open exactly the resources shared to them by name, until the collaboration expires or is revoked. Only the kinds the guest surface serves reach them (note, note-folder, canvas, board today — boards read-only); a grant on another kind is accepted by the API and has no effect.

luge-cli external invite jeanne@corp.com --name Jeanne --expires-in-days 30
                                                     # prints the entry link to hand over
luge-cli external list                               # the roster: status, expiration
luge-cli share grant board Roadmap --external jeanne@corp.com   # nominative read grant
luge-cli share show board Roadmap                    # names her: email, right, expiration
luge-cli external grants jeanne@corp.com             # everything she can reach
luge-cli share revoke board Roadmap --external jeanne@corp.com  # one resource back
luge-cli external revoke jeanne@corp.com             # the collaboration; grants are purged

Inviting, listing and revoking need the external_collaborator.invite capability; the grants review needs external_collaborator.manage. The entry link identifies the invitation and never authenticates anyone — entering still costs proving the invited address. Re-inviting an address re-opens the same collaborator, with no grants.

Being the guest

The other half of the same group walks the invitee's side, so a collaboration can be proved end to end without leaving the CLI.

luge-cli external entry "$ENTRY_LINK"       # the entry screen: org, masked address, proofs
luge-cli external code "$ENTRY_LINK"        # send the one-time code to the invited address
luge-cli external verify "$ENTRY_LINK" --code 123456     # exchange it for a session

export LUGE_EXTERNAL_TOKEN=$(luge-cli external verify "$ENTRY_LINK" --code 123456 --json | jq -r .token)
luge-cli external whoami                    # who the carried session belongs to
luge-cli note show "Shared note"            # …and read what was granted, as the guest

entry, code and verify carry no credential at all — not even a profile's API key — because they are reached by someone holding a link and nothing else. They name the deployment with --url, else LUGE_URL, else the stored profile's URL, so an invitee who has never run auth init can use them. Every way an invitation can fail (unknown, revoked, expired, domain-refused) is the same 404 on purpose; being rate-limited is its own 429. All three take the whole entry link or the bare token, query string and all.

LUGE_EXTERNAL_TOKEN replaces the API key rather than joining it: with it set, the CLI sends X-Luge-External-Token and no Authorization at all, so every command answers with exactly what the collaboration was granted. Sending both would be worse than useless — the API reads the external header first and refuses any route not opened to a guest on sight of it. Nothing is written to config.toml: an external session has a TTL and a reverify_after, which is nothing like a profile.

Which is also why it is sticky in a way worth knowing: while that variable is set, a member command like board list speaks as the collaboration too and is refused. unset LUGE_EXTERNAL_TOKEN is how you stop being the guest; the refusals name the external session rather than your API key, so you can tell which credential the door closed on.

The six-digit code is not a CLI surface, by design. It is a proof of the invited address, and the platform sends it to that address and nowhere else — verify takes it as input. Read it in the invited mailbox, or, on a deployment with no mail transport configured, in the API log — the API writes it there when it cannot mail it (a WARNING line, and only when the deployment runs at DEBUG level).

Skills (personal & local)

luge-cli skill manages authored Luge skills — instruction sets (name, description, instructions body, required tools, trigger phrases) that agents can use. By default commands target your personal skills (visible only to you); --scope local targets the tenant's shared local skills. Reading works with any key; create/update/delete need the skill-manage capability (an admin/owner key).

luge-cli skill list                        # your personal skills, with created/updated
luge-cli skill list --scope local          # the tenant's shared local skills
luge-cli skill show <name>                  # description, instructions, requires, triggers, dates
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 update my-skill --instructions "…" --if-match "<updated_at from show>"
luge-cli skill delete my-skill              # (asks to confirm; -y to skip)

--if-match makes the update conditional: pass the updated_at you read (from show or list) and the API answers 412 instead of overwriting a version someone else changed since — re-read and retry. Without it, last write wins.

(Not to be confused with luge-cli claude skill and its siblings — codex, hermes, kimi, openclaw, qwen, cursor, opencode, cline, kilo — which install this CLI's own agent skill — see Agent skill below.)

Self-improvement loop

luge-cli improvement is the platform's self-improvement loop — what the app's self-improvement dashboard renders. The loop observes friction in agent exchanges, writes proposals, auto-applies them into a journaled changelog, and learns from human verdicts. Every command requires tenant admin (tenant.manage); other keys get a clean 403.

luge-cli improvement summary [--days 7]     # metrics, verdicts, pipeline, per-agent
luge-cli improvement proposals              # the proposer's output, newest first
luge-cli improvement proposals --status failed   # only failed applies, with the why
luge-cli improvement proposals --offset 100 # read past the first page
luge-cli improvement changes                # the auto-apply changelog + impact signals
luge-cli improvement synthesize             # queue a proposer pass (reads friction)
luge-cli improvement apply                  # queue an apply pass over pending proposals
luge-cli improvement approve <proposal-id>  # apply a parked fork proposal now
luge-cli improvement reject <proposal-id> --reason "not the right direction"
luge-cli improvement revert <change-id> --reason "regressed the tone"
luge-cli improvement comment <change-id> "good catch, keep this direction"

proposals and changes page at up to 100 rows — --limit/--offset walk the listing and the reply's total says when to stop; a failed row's Error column carries the apply's own words — a semantic-gate refusal reads review refused: …. synthesize and apply queue a worker pass and return immediately (queued): the outcome lands on proposals / changes moments later, never in the trigger's output. revert restores the change's before-snapshot, and a proposal-sourced change's target sits out re-proposal for the dedup window; its --reason (and any comment) feeds back into the proposer's next pass. The changelog's impact columns say what happened since each change — skill activations and friction recurrence — and --json adds the before/after snapshots.

One kind of proposal is never auto-applied: a patch_skill aimed at a synced skill parks as pendingparked in the proposals view — and waits for a human. approve applies it on the spot (a 409 means the row is not parked or already decided; a 422 carries the apply's own failure motive). reject requires a --reason (3-600 chars, checked before the call): the row lands reverted with a rejected by human: … motive and, like a revert, its target sits out re-proposal for the dedup window — the "no" holds for a while, it is not a skip.

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.

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.

Agent skill

The package bundles an agent skill (luge-platform) for Claude Code, Codex, Hermes, Kimi Code, OpenClaw, Qwen Code, Cursor, opencode, Cline and Kilo Code — every agent that reads a skills/<name>/SKILL.md. It teaches 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:

luge-cli claude skill install --force   # copies it to ~/.claude/skills/luge-platform
luge-cli codex skill install --force    # ${CODEX_HOME:-~/.codex}/skills/luge-platform
luge-cli hermes skill install --force   # ${HERMES_HOME:-~/.hermes}/skills/luge-platform
luge-cli kimi skill install --force     # ${KIMI_CODE_HOME:-~/.kimi-code}/skills/luge-platform
luge-cli openclaw skill install --force # ${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/luge-platform
luge-cli qwen skill install --force     # and likewise: cursor, opencode, cline, kilo

Unlike the editable CLI, the skill is a copy — it does not update on git pull, and skill install without --force refuses to overwrite one. That is what luge-cli upgrade exists for: it rewrites every copy already on the machine (and only those — it never creates a home for an agent that is not installed here), so a release cannot leave an agent reading the previous one's instructions. luge-cli upgrade --check lists each copy and whether it still matches the installed package.

MCP server

For a host that speaks MCP but has no shell, luge-cli mcp serves the whole CLI over stdio:

// Claude Desktop / Claude Code / any MCP client
{
  "mcpServers": {
    "luge": { "command": "luge-cli", "args": ["mcp"] }
  }
}

The tools are generated from the CLI itself at startup — the Click command tree is walked and turned into one tool per command group, with each group's verbs as an enum and every option and argument as a typed field. Nothing is declared by hand, so a command added to the CLI is a tool the server offers on its next start, and two tests (tests/test_mcp.py) fail the build if anything in the tree ever becomes unreachable.

Call a tool by naming the verb in command and its arguments beside it:

{ "name": "luge_card",
  "arguments": { "command": "create", "board": "Roadmap",
                 "column": "To do", "title": "Ship the MCP server" } }

Results are this CLI's --json output verbatim — it is passed automatically, so nothing comes back truncated. profile picks the connection per call, and credentials come from the usual config (luge-cli auth show).

Narrow the surface when a client does not need all of it:

luge-cli mcp --read-only                  # reads only — no create/update/delete/send
luge-cli mcp --group card,note,board      # these groups (repeatable, or comma-separated)
luge-cli mcp --exclude 'auth *' --exclude '* delete'
luge-cli mcp --list-tools                 # print the generated definitions, do not serve

--read-only classifies by verb and treats anything it does not recognise as a write, so a newly added command stays out until someone says otherwise. A message sent through the server is marked AI-written by default — nothing but a model calls a tool over that transport — unless the host sets LUGE_CLI_AS_AGENT=0.

Develop

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

make bump V=0.48.0   # close CHANGELOG's [Unreleased] under that number + set pyproject
make release         # tag vX.Y.Z + create a GitHub release from CHANGELOG.md
make publish         # upload to PyPI via twine + ~/.pypirc  (REPO=testpypi to test)

Merged work does not touch the version. Add your entry under ## [Unreleased] in CHANGELOG.md and stop there. make bump is what turns that section into a number, and it is run at the moment of releasing — the only moment the number is knowable, since it has to be the one after what PyPI already holds. It refuses a version that is not ahead of the current one and an [Unreleased] section with nothing in it, then reopens an empty one for the next round.

Bumping per merge is how this repo once reached 0.50.0 while PyPI served 0.47.0: three versions announced in a changelog nobody could install.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

luge_cli-0.49.0.tar.gz (486.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

luge_cli-0.49.0-py3-none-any.whl (342.5 kB view details)

Uploaded Python 3

File details

Details for the file luge_cli-0.49.0.tar.gz.

File metadata

  • Download URL: luge_cli-0.49.0.tar.gz
  • Upload date:
  • Size: 486.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.11

File hashes

Hashes for luge_cli-0.49.0.tar.gz
Algorithm Hash digest
SHA256 b30b2d2cb1b974abde4085fcf31e0ee1f726f1b537ee64f7fa3af537ce2ffb14
MD5 71b0e2e46dbda567166d0a8261be865c
BLAKE2b-256 33f926a94225f1d5cf803438f0d08abd1d6c47351091915820b08460137fd418

See more details on using hashes here.

File details

Details for the file luge_cli-0.49.0-py3-none-any.whl.

File metadata

  • Download URL: luge_cli-0.49.0-py3-none-any.whl
  • Upload date:
  • Size: 342.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.11

File hashes

Hashes for luge_cli-0.49.0-py3-none-any.whl
Algorithm Hash digest
SHA256 05f0fc97905afca45d8efa8f25d1a5d88cb6df3207e0a790e0feef85714d92de
MD5 04d7ec2038c111f1cd903e9effbb3fe2
BLAKE2b-256 a90aec17e94a4951b5066c017607c7b5e33886de77aceb42dda4c2720a19ff58

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page