Skip to main content

lightning-artifacts

Publish a local file (HTML report, PDF, image, dataset sample, build output) to Lightning AI and get a durable, public lightning.ai/artifacts/<id> link that never expires and renders inline in the browser. Also lists the artifacts drive, unpublishes (revokes) links and deletes the files behind them, all through the `lightning` CLI (`lightning-sdk` package) with `lightning login` or an API key, no code. Use when the user wants to share a file, a generated one-pager, or an agent-made artifact as a permanent URL, hand a file to a teammate or CI job, see or revoke existing shared links, or asks to "get a public / shareable link for this file".

소스 정보

저장소
Lightning-AI/skills
최근 소스 활동
2026년 9월 28일 09:41
감지된 SKILL.md 언어
영어
스타
7
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
lightning-artifacts
description
Publish a local file (HTML report, PDF, image, dataset sample, build output) to Lightning AI and get a durable, public lightning.ai/artifacts/<id> link that never expires and renders inline in the browser. Also lists the artifacts drive, unpublishes (revokes) links and deletes the files behind them, all through the `lightning` CLI (`lightning-sdk` package) with `lightning login` or an API key, no code. Use when the user wants to share a file, a generated one-pager, or an agent-made artifact as a permanent URL, hand a file to a teammate or CI job, see or revoke existing shared links, or asks to "get a public / shareable link for this file".
# Lightning AI Artifacts (durable shareable file links) Publish a file to a teamspace and get a `https://lightning.ai/artifacts/<id>` URL that anyone can open without a Lightning login. The control plane streams the bytes from storage on every request, so unlike a presigned S3 URL the link never expires (no ~1h cap). **The whole flow is CLI-only.** `lightning cp` / `ls` / `rm` handle the files, and `lightning api` makes the publish and unpublish calls. No Python or `curl`. ## Setup & auth ```bash # Use the Lightning AI CLI from the current env; install or upgrade it there if it's missing or older than 2026.9.18 v=$(lightning --version 2>/dev/null | sed -n 's/^Lightning CLI version //p') [ -n "$v" ] && [ "$(printf '%s\n' 2026.9.18 "$v" | sort -V | head -1)" = 2026.9.18 ] \ || uv pip install -U lightning-sdk || python3 -m pip install -U lightning-sdk lightning --version # must print "Lightning CLI version …"; if not, see the table below lightning login # interactive browser sign-in — enough for everything here # or: export LIGHTNING_API_KEY=... # non-interactive alternative (CI, agents) ``` Either credential works for **every call in this skill** — no token minting or extra auth steps. Get a key from lightning.ai → user/org settings, or `lightning api-key create --org <org> --name artifacts`. **Never hardcode the key** — read it from the environment. To target a non-prod control plane, set `LIGHTNING_CLOUD_URL` (default `https://lightning.ai`). This uses, and if needed installs into, the agent's current environment (project venv, conda env, …); then call plain `lightning …` everywhere. If setup doesn't go cleanly: | Symptom | Fix | |---|---| | No active env, `externally-managed-environment`, or a conflict with the project's own pins | Install it on its own: `uv tool install lightning-sdk` (or `pipx install lightning-sdk`), and call `"$(uv tool dir --bin)/lightning"` if that isn't on `PATH` | | Still no `Lightning CLI version` line after installing | Another `lightning` (PyTorch Lightning's) or an inactive env comes first: check `command -v lightning`, then activate the env or call its `bin/lightning` | | `No such command`, `No such option` or `unexpected extra argument` | The CLI is too old: upgrade it where `command -v lightning` points (`uv pip install -U lightning-sdk`, or `uv tool upgrade lightning-sdk`) | | Installing is blocked (read-only env or home, agent sandbox) | Run each command as `UV_CACHE_DIR="${TMPDIR:-/tmp}/uv" UV_TOOL_DIR="${TMPDIR:-/tmp}/uv-tools" uvx lightning-sdk …` | | SSL/CA errors on every call, even `lightning --version`, only inside an agent sandbox | A `**/*.pem` read-deny rule hides certifi's `cacert.pem`: allow that one path; don't disable the sandbox | | `NameResolutionError`, only inside an agent sandbox | If `lightning api` fails too, ask the user to allow `lightning.ai` and `*.lightning.ai` (`/sandbox`). If only `studio`/`job` commands or the SDK fail, they bypass the sandbox proxy: run just those outside it. Background pollers fail the same way, silently | `lightning api` flags: `-X` method, `-f key=val` string field, `-F key=val` typed field, `-H` header, `--input <file>` request body (`--input -` to pipe one), `-q` jq filter (needs the `jq` binary for `-q`), `-i` include response headers, `--silent` no output. Fields with no `-X` make it a POST. Fields are JSON body for POST/PUT-with-body and **query params** when the request also has `--input` or is a GET. ## Resolve the teamspace (do this first) Artifacts live in a teamspace (a "project" in the REST API). You need the **owner name** and **teamspace name** (for `lit://` paths) and the **project id** (for REST calls). **Never guess.** If more than one membership fits and none is configured, **ask the user which to use**: ```bash lightning api /v1/memberships -q '.memberships[] | [.name, .projectId, .ownerType, .ownerId] | @tsv' ``` The membership carries only the owner's id. Resolve the name by `ownerType`: ```bash PID=<projectId-from-above> TSNAME=<name-from-above> OWNER_ID=<ownerId-from-above> if [ "<ownerType-from-above>" = organization ]; then OWNER=$(lightning api "/v1/orgs/$OWNER_ID" | jq -r .name) else # a personal teamspace: yours, or another user's you were added to OWNER=$(lightning api /v1/users/search -X GET -f "query=$OWNER_ID" \ | jq -r --arg id "$OWNER_ID" '.users[] | select(.id==$id) | .username') fi [ -n "$OWNER" ] && [ "$OWNER" != null ] || echo "could not resolve the owner of $TSNAME — ask the user" >&2 ``` The user search is fuzzy, so match `.id` exactly (the SDK does the same). Never fall back to your own username: a wrong owner in the `lit://` path uploads into a different teamspace. Teamspaces reached through org-level permissions don't appear in `/v1/memberships`. If the user names one you can't find, ask them for the `<owner>/<teamspace>` pair and the project id. Don't try to look it up by name: `/v1/projects?name=…` doesn't exist. ## Publish a durable link (the CLI flow) **A public link (`private=false`, the default below) needs the user's say-so in this request.** "Publish this" or "get me a public link" is authorization — don't ask twice. "Send this to the team" is not: say what the link exposes and get a yes, or use `-F private=true`. Three calls: upload into the `artifacts/` drive, read back the storage cluster it landed on, then register the object as a shared artifact. `share` and `shares` (below) need the `jq` binary; without it, parse the JSON yourself. ```bash # share <local-file> [remote-name] [content-type] # Pretty status goes to stderr; the bare URL is the only thing on stdout, so # URL=$(share file.html) captures cleanly while interactive use looks like: # ✅ Published report.html (text/html; charset=utf-8) # 🔗 https://lightning.ai/artifacts/art_... share() { local FILE="$1" NAME="${2:-$(basename "$1")}" CT="${3:-$(file -b --mime-type "$1")}" local KEY="artifacts/${NAME#artifacts/}" # publish only finds objects under artifacts/ # 1. upload; the server picks where to store it (see Gotchas) lightning cp "$FILE" "lit://$OWNER/$TSNAME/$KEY" >&2 || return 1 # 2. publish needs the blob's storage cluster, read from the listing (see Gotchas) local CLUSTER; CLUSTER=$(lightning ls --json "lit://$OWNER/$TSNAME/$KEY" | jq -r '.[0].clusterId') # 3. register it -> durable, no-expiry lightning.ai/artifacts/<id> local LINK; LINK=$(lightning api "/v1/projects/$PID/shared-artifacts" -X POST \ -f clusterId="$CLUSTER" -f filename="$KEY" -f contentType="$CT" -F private=false -q .url | tr -d '"') echo "✅ Published $NAME ($CT)" >&2 printf '🔗 ' >&2 echo "$LINK" } share report.html # -> https://lightning.ai/artifacts/art_... share dashboard.html reports/dash.html text/html # custom remote name + explicit type ``` **Always show the user the full URL on its own line**, never just "done". Terminals make it clickable. **Shares are confined to the `artifacts/` folder.** Publish only finds objects under `lit://<owner>/<teamspace>/artifacts/...`. Files under `uploads/` or `lightning_storage/` can be copied with `lightning cp`, but publish won't find them. The `KEY` variable keeps the upload path and the publish `filename` identical. A bare `filename` gets `artifacts/` prepended by the server, but matching the two explicitly is clearest. **The default `private=false` link is open to anyone who has it, with no auth.** Set `-F private=true` so only authorized project readers can open it. ### Content types that render inline **The publish call's `contentType` decides whether HTML/PDF render or download.** It is set at publish time, not upload time, and overrides the stored object's type on every request. `file -b --mime-type` guesses most cases; pass an explicit type when the extension is ambiguous. | File | Content-Type | | --- | --- | | `.html` | `text/html; charset=utf-8` | | `.pdf` | `application/pdf` | | `.svg` | `image/svg+xml` | | `.png` / `.jpg` | `image/png` / `image/jpeg` | | `.json` / `.txt` / `.csv` | `application/json` / `text/plain` / `text/csv` | | anything to force-download | `application/octet-stream` | ## List, see what's published, unpublish, delete **`lightning ls` lists the drive completely, even for folders with many thousands of files.** It follows the server's pages. It shows one level by default, `-r` for every file underneath, and `--json` for entries with `size` and `clusterId`: ```bash lightning ls "lit://$OWNER/$TSNAME/artifacts/" # one level; folders end in / lightning ls -r "lit://$OWNER/$TSNAME/artifacts/reports" # full relative file paths lightning ls --json "lit://$OWNER/$TSNAME/artifacts/reports" ``` **`shares` lists every published link in the project, newest first.** It calls `GET /v1/projects/{pid}/shared-artifacts`, which returns id, filename, content type, public/private, download count and URL: ```bash # shares -> one block per published artifact shares() { lightning api "/v1/projects/$PID/shared-artifacts" | jq -r '.artifacts // [] | .[] | "\(if .private then "🔒" else "🌐" end) \(.filename) · ⬇ \(.downloads // 0) · \(.createdAt // "") id: \(.id) 🔗 \(.url)"' } ``` **`unshare` revokes a link but keeps the file in the drive:** ```bash # unshare <artifact-id> unshare() { lightning api "/v1/projects/$PID/shared-artifacts/$1" -X DELETE --silent \ && echo "🗑️ Unpublished $1 — link is dead, file kept in the drive" >&2 } unshare art_01kxgaep54zzs84arfns1j21wd # illustrative id; take yours from shares ``` **`lightning rm` deletes the file itself, with no cluster id.** The server removes it wherever it is stored. Use it after unpublishing, or to clean up an abandoned upload: ```bash lightning rm "lit://$OWNER/$TSNAME/artifacts/report.html" lightning rm -r "lit://$OWNER/$TSNAME/artifacts/reports" # a folder and everything under it ``` `rm` fails on a path that doesn't exist (pass `-f` to ignore) and refuses a folder without `-r`. **Re-running `lightning cp` to the same `artifacts/<name>` updates the bytes behind existing links.** Their ids keep serving the new contents. Repeating the publish call with the same `filename` gives a new id and URL; do that to change the served Content-Type. ## Example workflows Prompts this skill handles: *"share this HTML report as a permanent link"*, *"give me a public URL for output.pdf"*, *"publish this one-pager so I can send it"*, *"drop this file somewhere CI can curl it"*. **An agent-generated HTML one-pager needs only `share`; anyone can open the printed URL:** ```bash share summary.html # hand the printed URL to anyone ``` **A loop over `share` publishes a folder of reports, one link each:** ```bash for f in out/*.html; do share "$f" "reports/$(basename "$f")" "text/html; charset=utf-8"; done ``` **`share` prints only the URL on stdout, so a CI job can capture and curl it:** ```bash URL=$(share model-metrics.json) # elsewhere: curl -sL "$URL" -o metrics.json ``` ## Gotchas - **Publishing with a compute cluster's id fails with HTTP 500; publish needs the blob's storage cluster.** A teamspace can be bound to compute clusters that store files under a parent cluster's bucket, so the upload cluster is not always the storage cluster. Read the real `clusterId` from the listing, as `share` does. - **`lightning cp` needs no cluster flag, because the server picks the storage.** Only if the server rejects the upload for a missing cluster does `cp` choose one itself (`LIGHTNING_CLUSTER_ID` first, which is set inside a Studio, then the teamspace's only or default cloud account) and warn `No cloud account specified. Using cloud account: <id>.` Pass `--cloud-account <id>` only to steer placement deliberately, and pick a cluster whose `status.phase` is `CLUSTER_STATE_RUNNING` (`/v1/projects/$PID/clusters`). A bound but unusable cluster makes the upload fail with a drive error that says nothing about cluster health. - **Unpublish reports success for links that don't exist.** `DELETE /v1/projects/{pid}/shared-artifacts/{id}` returns `HTTP 200` with `{}` for an id that was already revoked or mistyped. Exit code 0 is not proof; check that the public URL 404s. (`lightning rm`, by contrast, fails on a missing path unless you pass `-f`.) - **Served HTML is not byte-identical to the upload, so don't checksum it against the source.** Cloudflare injects a Browser-Insights RUM beacon (`static.cloudflareinsights.com/beacon.min.js`) into HTML responses. It is harmless for viewing, but don't promise a bit-exact document. - **`shares` fails with HTTP 501 "Method Not Allowed" on control planes built before mid-July 2026.** The list endpoint `GET /v1/projects/{pid}/shared-artifacts` is newer than the rest. Publish and unpublish still work there. - **A public link is open to anyone who has it, and revoking it doesn't recall a copy already fetched.** Use `-F private=true` for anything that shouldn't be world-readable.
GitHub에서 보기