Releases: emaxe/open-artifacts
Releases · emaxe/open-artifacts
Release list
v0.11.0 - redesigned share-page viewer panel
Changed
- Redesigned the top panel of the shared-artifact viewer (
/s/:token): the panel is now a single, always one-line row (logo, title, kind badge, version/date/author/team) with the actions on the right. Size, view count, expiry, the version note and the description moved into a details block that starts collapsed and is opened with a chevron (managers only; the choice is remembered). "Download source" and "Open in workspace" are grouped under a "⋯" menu when both are present. The version picker and the visibility control are now compact chips with icons, and the layout stays on one line down to phone width. - The visibility control applies on click. It is now a list of "Team only / Password protected / Public" rows with a one-line explanation each: picking one saves it immediately (a spinner, then a checkmark), updates the panel in place and no longer reloads the page, so the artifact keeps its scroll position and state. "Password protected" reveals a password field with a "Set password" button (Enter works too). A mode the team forbids is shown disabled with the reason.
- Popovers close on Escape and on an outside click, return focus to their trigger, support arrow keys / Home / End in the visibility list, and announce their result to screen readers.
- The panel's details block is collapsed by default, and its remembered state uses a new
oa.viewer.detailskey (the oldoa.viewer.panelCollapsedis ignored).
Fixed
- Visibility control appeared to do nothing. The panel had
max-height: 40vh; overflow-y: auto, which clipped the popover so the "Apply" button was out of reach, and picking an option in the<select>only revealed the password field without saving anything. The version picker was clipped the same way. Popovers are now anchored to their own trigger and are never clipped by the panel.
Full changelog: https://github.com/emaxe/open-artifacts/blob/main/CHANGELOG.md
npm: https://www.npmjs.com/package/@emaxe/oa
v0.8.0 - bilingual landing page and named share links
Added
- A bilingual (RU/EN) marketing landing page at
/, with an inline sign-in/sign-up card so a visitor can register or log in without leaving it. Signed-in users are redirected to their workspace as before; the same landing stays reachable at/welcome. A new unauthenticatedGET /api/v1/instance/configendpoint tells the page (and, later, other pages) whether self-service registration is open, invite-only, or closed, so it never advertises a form that can only 403. - The "Sharing" table on an artifact's detail page can now name a link: an optional, creator-facing-only
label(never shown to a visitor of/s/:token) onPOST /artifacts/:id/shares(and thecreate_shareMCP tool), so several links on the same artifact can be told apart at a glance. - Any active link in the "Sharing" table can be copied again at any time via a "Ссылка" button, not just right after creation — the list endpoint now returns each share's full URL.
- The three separate "create link" buttons are now a single "Создать ссылку" dialog (access mode, optional label, password when applicable) whose submit button shows a loading spinner while the link is being created, instead of the button appearing to do nothing for a moment.
Fixed
incrementShareViewCountupdated a share's view count via a read-then-write instead of an atomic SQL increment, silently losing counts under concurrent views. It's now a single atomicUPDATE ... SET view_count = view_count + 1.- The web dev server's proxy rule for share pages (
"/s") was a bare prefix match, so it silently intercepted every request whose path merely started with "s" — including all of/src/*, which madepnpm dev:webserve a stale production bundle instead of live modules. Narrowed to"/s/".
Changed
- A manager's (owner/team admin/superadmin) own view of their link is tracked again, in a separate
managerViewCount— the "Sharing" table now shows both "Просмотры" (audience only, unchanged since 0.6.0) and "Всего" (audience + manager), instead of a manager's own check of the link looking like it wasn't counted at all.
Full changelog: https://github.com/emaxe/open-artifacts/blob/main/CHANGELOG.md#080---2026-09-13
v0.7.1 - fix attachment loading inside sandboxed preview/embed
Fixed
- Attached files (images, text, and any other type) embedded inside an artifact's own content (
<img src>, links, etc.) failed to load or open when viewed through the sandboxed preview/embed iframe (/api/v1/artifacts/:id/preview,/embed/:token), even though the exact same/af/:tokenURL worked fine opened directly in a browser tab. Two causes: theassetOriginthe artifact's Content-Security-Policy needs to allowmedia-src/font-srcfrom the file-serving origin was never actually passed by either route, and the sandboxed iframes were missing theallow-downloadstoken, which browsers require before letting a sandboxed frame initiate any file download (every non-image/video/audio/font attachment is always served asContent-Disposition: attachment).
Full changelog: https://github.com/emaxe/open-artifacts/blob/main/CHANGELOG.md#071---2026-09-13
v0.7.0 - brand identity, S3 file storage, storage quotas
Added
- Brand identity: the Open Artifacts logo now appears on the login/register pages (with a dark-theme wordmark variant), the sidebar rail and mobile header, and as the browser favicon/apple-touch-icon. Generated from a single source image via
scripts/gen-brand-assets.sh(ImageMagick), checked in alongside it for reproducibility. - S3-compatible file storage for artifacts: images and other files can now be uploaded and attached to an artifact —
POST /api/v1/artifacts/:id/files(multipart/form-data),GET .../filesto list,DELETE .../files/:fileIdto remove, served back through the app atGET /af/:token(never a public bucket, no presigned URLs).oa files upload|ls|rmon the CLI;list_artifact_files/delete_artifact_fileas MCP tools (uploading itself is CLI/REST-only — MCP tool calls are JSON, not multipart). A file is always attached to exactly one artifact and is deleted with it (explicit delete, expiry, or an org being deleted all queue its object for removal); a file with no artifact cannot exist.docker-compose.ymlnow bundles a MinIO service as the default storage backend; pointS3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEYat an external S3/R2/Spaces bucket instead, or leaveS3_BUCKETunset to disable the feature entirely (upload endpoints answer501, everything else is unaffected). - Storage quotas, instance- and team-wide: two new limits — total bytes per team and bytes per single artifact (both counting artifact source text plus uploaded files) — configurable instance-wide in
/admin/settingsand, per team, in its own team settings (the per-artifact one by an owner/admin, the team-wide one by a superadmin, matching the existing permission split for other org-level knobs). A team override can only ever be equal-or-stricter than the instance ceiling, never looser. Default: unlimited at both levels (see Changed below). GET /api/v1/quota(and theget_storage_quotaMCP tool /oa quotaCLI command): lets an agent check remaining quota — team-wide, or narrowed to one artifact — before deciding whether to upload a file, rather than uploading blindly and handling the failure after the fact.quota_exceededis now a structured error (scope,limitBytes,usedBytes) instead of a flat message, on both the REST API and MCP tools, so a caller can act on it instead of just displaying it.
Changed
- Breaking: a team's storage quota now defaults to unlimited instead of 1 GiB. Any team whose quota was never explicitly changed by a superadmin (i.e. still sitting at exactly the old 1 GiB default) is migrated to "inherit the instance quota" (itself unlimited by default); a team a superadmin deliberately set to a specific number keeps that number.
orgs.storageQuotaBytesalso changed from a required column to nullable (null= inherit) and fromintegertobigint, since it now also counts uploaded file bytes, not just artifact source text. - Fixed a latent bug in the instance's own "maximum artifact size" setting (
InstanceSettings.maxArtifactSizeBytes): it was editable in the admin UI and accepted byPATCH /admin/settings, but nothing ever read it back — artifact-size enforcement used a hardcoded 5 MiB constant regardless. It's now a required, resolved parameter on every artifact-creating/updating code path, the same pattern already used for artifact lifetime.
Fixed
- Every "copy" button silently did nothing on insecure (plain HTTP) origins, or wherever the browser/embedder denied
navigator.clipboard— that API is undefined or rejects in those cases without surfacing an error. Added a sharedcopyToClipboard()helper with anexecCommand('copy')fallback, used byCopyButtonand the public share page's own inline copy-link script.
v0.6.1 - Fix org slug resolution
Fixed
--org <team>(CLI) andorgId(REST/MCP) accept either a team's real id or its slug, per their own documentation — but passing the slug on an explicit request (anyone in more than one team, using--org/orgIdto pick a non-default one) crashed with a500:resolveOrgScopeforwarded it straight into a UUID-typed database column instead of resolving it first. AffectedPOST/GET /artifactsand the org-scoped MCP tools; the single-team auto-selected case was never affected.
Found via an end-to-end check of the published npm package against a live server with more than one team.
v0.6.0 - Public Share Page Viewer Panel & Design Templates
Added
- Viewer panel on the public share page (
/s/:token): a header above the embedded artifact shows its title, type, displayed version number, and last-updated date to every visitor. A logged-in member of the artifact's team additionally sees the author and team name; the artifact's owner, a team owner/admin, or a superadmin gets the full panel — description, size, expiry, the share's view count, the version's commit message, and a version picker to browse older versions (view-only, via?v=N; never writes to the database, and never affects what any other visitor of the same link sees). The panel is collapsible (state remembered per-browser) and hidden when printing. "Copy link" (always copies the canonical URL, without?v=) and "Download source" are available to everyone, including anonymous visitors; "Open in workspace" is shown once the viewer has read access to the artifact. - New
GET /s/:token/downloadendpoint — the artifact's raw source, served astext/plain(never astext/htmlorimage/svg+xml, regardless of the artifact's ownkind) withContent-Disposition: attachment, so anhtml/svgartifact's own script can never execute in this app's origin. - Design templates for
htmlartifacts: four theme-specific design systems (DESIGN-data.md,DESIGN-document.md,DESIGN-promo.md,DESIGN-diagram.md) plus a sharedDESIGN-core.mdfoundation (tokens, light/dark theming, responsive/print/accessibility rules, self-review checklist), shipped insideskills/open-artifacts/.SKILL.mdnow routes an agent to the matching template before it writes any markup.
Changed
- Breaking: a view of
/embed/:tokenby someone who can manage the artifact (its owner, a team owner/admin, or a superadmin) no longer increments the share's view count — browsing your own version history no longer inflates the metric that's supposed to measure your audience. Anonymous and other logged-in visitors are counted exactly as before. /s/:tokennow sendsCache-Control: private, no-storeandVary: Cookie— its response now depends on who's asking (the viewer panel differs by audience), so it must never be served from a shared cache to the wrong visitor.- The public share page now discloses the artifact's title, type, currently-shown version number, and that version's date to anyone with the link — previously the page carried no metadata of any kind, just the embedded content itself.
- Server-rendered
markdown/mermaid/svgartifacts now use a real stylesheet instead of a five-line placeholder: full typography scale, tables, code blocks, blockquotes, light/dark theming viaprefers-color-scheme, and print styles.mermaiddiagrams now pick their theme (dark/default) from the same signal instead of always rendering light.
Docs
@emaxe/oa's README/CHANGELOG didn't document the--team/--publicflags added in v0.5.0, and its README still listed the old--expiresflag foroa push— both fixed.
v0.5.0 - Team-Only Share Links & Link Policy
Added
- Team-Only Share Links (
teammode): A third share mode alongsidepublicandpassword— a/s/:tokenlink that requires the viewer to be logged in and a member of the artifact's team (any role). No password needed; safe to paste into a team chat. An anonymous visitor is redirected to/login?next=...; a logged-in non-member gets a 403 page with a link to log in as someone else. - Team Link Policy: Team owners/admins (including for their personal main workspace) can now set, in
/t/:orgId/settings, the default link mode (teamorpublic) used whenever a share is created without naming one, and whether the team allowspubliclinks at all. Disabling public links only blocks new ones — existing public shares keep working until revoked, either one at a time from the artifact page or in bulk (with a confirmation dialog showing the affected count) from team settings. - Instance-Wide Link Policy: A superadmin sets the same two knobs instance-wide in
/admin/settings. A team can only be equal-or-stricter than the instance, never looser: it cannot re-enable public links the instance disabled, nor default topublicwhile public links are forbidden. - New endpoints
GET /orgs/:id/share-policy(effective policy plus a count of active public shares) andPOST /orgs/:id/shares/revoke-public(bulk-revoke, idempotent). - New CLI flags
--teamand--publiconoa pushandoa share, alongside the existing--password; bothoa push/oa sharenow print the resolved share mode alongside the URL.
Changed
- Breaking:
oa push --shareandoa share <id>with no mode flag now create a link in the team's configured default mode instead of alwayspublic. Use--publicto get the old behavior explicitly. - Breaking: MCP
create_share'smodeargument no longer defaults to"public"— omitting it uses the team's configured default, same as the CLI. The tool result always echoes back the mode actually used. - Breaking:
POST /artifacts/:id/shares'smodefield is now optional (omitting it uses the team default) instead of required; requesting"public"when it's disallowed now returns403 public_shares_forbiddenwithallowedModesin the body. - Breaking:
POST /s/:token/unlockon a non-passwordshare now returns400 not_password_modeinstead of silently{ok:true}. - The agent skill (
skills/open-artifacts/SKILL.md) no longer instructs the agent to reason about link privacy (defaulting to no share, generating passwords, asking before going public) — that decision now belongs entirely to the server-side team/instance policy above; the skill only documents the three modes and the new error.
Fixed
- A
password-mode share whosepasswordHashwasNULL(a state that should never occur, but wasn't guarded against) was treated as fully public by the shared access-resolution matrix; it now correctly still requires a password. - An unvalidated
?next=parameter on the login page was an open-redirect risk.
Docs
@emaxe/oa's own README/CHANGELOG never documented the new--team/--publicflags, and its README still listed--expiresforoa pushafter that flag was renamed to--lifetimein v0.4.0 — both fixed.
v0.4.0 - Artifact Lifetime (TTL) & Retention
Added
- Artifact Lifetime (TTL): Artifacts can now be set to expire. A superadmin sets an instance-wide maximum artifact lifetime in
/admin/settings(minutes; unlimited by default) — it also serves as the default lifetime for new artifacts. Team owners/admins may set their own stricter maximum for their team, but never looser than the instance one. Callers may choose any lifetime up to the effective limit at creation time (CLIoa push --lifetime, the web UI, or thelifetimefield in the REST/MCP API), and change it later viaPATCH. On expiry the artifact's content is hard-deleted (all versions removed irreversibly) — only a tombstone row remains for audit/analytics. A background sweeper (ARTIFACT_PURGE_INTERVAL_MINUTES, default 5) performs the actual deletion; reads treat an expired artifact as gone immediately either way.
Changed
- Lowering the instance or team maximum lifetime re-clamps every affected artifact's expiry from its own creation date (
min(current deadline, created_at + new max)) and can make already-existing artifacts expire immediately; raising a maximum never extends anything already created. GET /artifactsandGET /artifacts/:idnow omit/404 an artifact once its lifetime has passed, even before the background sweeper has run. Artifact payloads gained anexpiresAtfield.
Full changelog: CHANGELOG.md · История изменений (RU)
v0.3.0 - Teams, Invites & Personal API Keys
Added
- Personal (User-Scoped) API Keys:
oa loginnow issues a personal key by default — one login, valid across every team you belong to, with your real role in each, instead of one agent key per team.oa orgslists your teams and the currently selected one;oa use <team>sets a default for the current project (written to a secret-free.oa.jsonyou can commit) or--globalfor the whole machine. The MCP server gained a matchingorgIdparameter and alist_orgstool, and a request with no team selected and more than one candidate gets a structuredorg_requirederror listing them instead of guessing. Agent keys (oa login --agent) are unchanged and remain the right choice for CI and narrowly-scoped access. NewSettings > Личные API-ключиpage to issue and revoke personal keys from the web UI. - Auto-Provisioned Main Workspace: Every user now gets a personal "main" workspace on registration — including when registering through an invite link, alongside the inviting team — instead of landing with zero teams.
- Full Invite Lifecycle: Invites are now a real state machine (
pending/accepted/declined/revoked) with list, revoke, and reissue endpoints; a public link preview at/invite/:token; and an in-app "Приглашения" (/invites) inbox so an already-registered invitee can discover and accept or decline an invite without any email delivery. - Team Identification for Same-Named Teams: A single
OrgIdentitycomponent (id-derived color monogram, "Основное" badge, owner/slug secondary line) used everywhere a team is rendered, plus owner email and member/artifact counts in every team listing — so two teams (or two personal workspaces) sharing a display name stay distinguishable. - Searchable Team Switcher: The sidebar's team switcher is now a searchable popover grouped by "Основное" / "Мои команды" / (superadmin) "Все команды инстанса", backed by server-side search.
- Restructured Admin Area:
/adminis now real routed tabs (Overview, Users, Teams, Audit, Instance Settings) instead of one 387-line component; user search by name as well as email with realtotal-based pagination and team-membership chips; keyset-paginated audit log with resolved actor names/emails and Russian action labels; all five instance settings are editable (previously only registration mode had a UI). - Agent Instructions for Everyone: The agent-connection cheatsheet moved from
/admin/instructionsto/help/agents, available to any signed-in user — connecting an agent is a team task, not a superadmin one. Old links still redirect. - Tailwind v4 Design System: The whole web app moved off hand-rolled CSS classes onto Tailwind v4 and a small set of accessible primitives (
Button,Dialog,ConfirmDialog,DropdownMenu,Toast,Table,EmptyState, and others), with a light/dark/system theme toggle and a responsive mobile nav drawer — with zero added runtime dependencies. - Team Management: Renaming a team, adding an existing user directly to a team (bypassing the invite round-trip), and a superadmin-only storage quota editor.
Changed
GET /auth/meNo Longer Lists Every Org for Superadmins: It now returns only the caller's own memberships, plusmainOrgIdand a pending-invite count — a superadmin reaches teams they don't belong to via search instead. (Breaking API change for any external client relying on the old shape.)- Native
confirm()/alert()/prompt()dialogs — the last ones left in the app — replaced with proper modal components.
Fixed
- Invite Email Spoofing: Registering through an invite link no longer trusts a caller-supplied email that doesn't match the invite — closing a hole where anyone holding a link could join under an arbitrary address.
- A user could previously be removed as the last remaining owner of the org they created via
DELETE /orgs/:id/members/:userId(onlyPATCHhad the guard); both now refuse it, and a team's creator can never leave or be removed from their own main workspace. - Login and registration no longer redirect to a nonexistent
/artifactsroute.
v0.2.0 - MCP Server, Agent Skill & Multi-tenancy
Open Artifacts v0.2.0
See CHANGELOG.md for full release details.
- MCP Server: Native Model Context Protocol support at
/mcp - AI Agent Skill: Ready-to-use skill via
npx skills add emaxe/open-artifacts - CLI @emaxe/oa v0.2.0: OAuth Device Flow login, password/expiring shares, auto-format detection
- Multi-tenancy & Teams: Full organization management and role permissions
- Bilingual Documentation: English and Russian READMEs & Changelogs