# Publish, install, update, pin, remove — and the audit log

Apps are shared through a **team server**: members + a store. Publishing and installing need a
signed-in Cowork account (sign in inside Cowork; `cowork status` shows the account). Pushing
needs an admin role on that team server.

```bash
cowork team-server list                 # your team servers (slug, your role)
cowork team-server store <team-server>  # what it publishes
```

## Push (publish a version)

```bash
cowork push                         # the current folder: an app folder, or a workspace (every .cowork/apps/<name>/)
cowork push .cowork/apps/gmail      # one app
cowork push .cowork/apps/gmail --to acme  # an explicit team server
cowork push --to acme --remember    # …and make it this workspace's default (workspace.json#publishTo)
cowork push --dry-run               # what would be published / bumped; nothing uploaded or written
cowork push --json                  # machine-readable rows
```

Each app prints one row: `published vX`, `bumped vA → vB`, `up to date vX`, or `failed: …`
(exit 1; the other apps still push).

- **Target**: `--to <team-server>` > the store the folder came from (its install receipt) >
  `.cowork/workspace.json#publishTo` > an error listing your team servers. `--to <ts> --remember`
  stores that team server as `publishTo` (machine-local, never packaged) once it resolves —
  even if the push itself then fails; an unknown team server is never stored and `--dry-run`
  writes nothing.
- **Version** comes from `cowork.json`. Unchanged files → "up to date" (nothing published).
  Changed files but that version already published → the patch is bumped (`1.2.3` → `1.2.4`)
  and written back to `cowork.json`. Store newer than your copy → refused: update first.
  Versions only move forward — never lower `version` by hand.
- After a push the folder is tracked like an install, so a collaborator's later push reaches
  your copy automatically while you haven't edited it.
- The team server keeps the newest 10 versions of each app. An admin can withdraw a bad older
  one (`cowork app unpublish <ts>/<app>@<ver>`); the latest can't be withdrawn — push a fixed
  version first. Copies already installed (even pinned to it) keep working.

## Install and manage (in a workspace)

```bash
cowork app list [--json]                 # every app here: version, local | <ts>@<ver> (pinned or auto), on/off, newer version out
cowork app install acme/gmail            # the store's latest → .cowork/apps/gmail/, follows the store
cowork app install acme/gmail@1.2.0      # a version the store still keeps, PINNED (auto-update leaves it)
cowork app versions acme/gmail           # the versions the store keeps (yours marked)
cowork app update                        # every clean, unpinned update that verifies
cowork app update gmail                  # take the latest now (unpins)
cowork app update gmail --keep-both      # an EDITED app needs one choice (below)
cowork app disable gmail                 # switch the whole app folder off (workspace.json#disabled); files kept
cowork app enable gmail
cowork app remove gmail                  # uninstall: files still exactly as installed are deleted, edited ones kept
cowork app unpublish acme/gmail --yes    # delete it from the store (admins)
cowork app unpublish acme/gmail@1.2.0 --yes   # withdraw ONE older version (admins; never the latest)
                                               #   `app versions` then notes a copy pinned here to a withdrawn version
cowork app open .cowork/apps/gmail/mail-list.jsx --as tab   # open a widget in the running workbench (tab|dialog|sidemenu)
```

Every verb takes `--workspace <dir>` (default: the current folder). `<app>` is the folder
(`.cowork/apps/gmail`), its basename, or the manifest `name` (the folder that owns it). An updated app you
edited needs one of: `--replace` (the store wins; files you ADDED are never touched),
`--keep-both` (yours stays exactly as it is; the new version lands WHOLE in `.cowork/apps/<app>-v<ver>/` (`gmail-v1-2-0`),
switched off — compare the two, then delete the one you don't want), `--ai-merge` (Claude merges;
needs the `claude` CLI; if it can't, it keeps both) or `--skip` (dismiss that version). Keep both
and skip dismiss the version: the next one asks again, and `--replace` can still take it later.

Two folders with one `name` (Keep both's copy beside yours): the name belongs to the first folder
that is switched ON, and the other is listed but registers nothing. To try the new version, switch
yours off and the copy on (`cowork app disable .cowork/apps/gmail` + `cowork app enable .cowork/apps/gmail-v1-2-0`)
— its routines, rail icon and skill take over; both share `.cowork/data/<name>/`.
`remove` only handles installed apps — a local app (one you made) is yours: delete its folder.
`install` / `update` refuse unverified content unless you pass `--allow-unverified` — don't,
unless the user explicitly asks. The same actions exist in the IDE (Manage Workspace and the
Team Servers store page).

## Auto-update

Cowork checks installed apps every few minutes (and at start, after sign-in, after a push).
A newer store version is applied automatically only when the installed app is **clean** (every
file still matches what was installed), **verified**, and **not pinned**. An edited app is never
overwritten — the Manage Workspace icon counts it and a toast names it; you choose in Manage
Workspace or on the store page. Kill switch: `COWORK_NO_AUTO_UPDATE=1`.

Consequences for authors and agents:
- Editing an installed app's files turns its auto-update off (until you `cowork app update
  \<app> --replace|--keep-both|--ai-merge|--skip`). To change someone else's app, prefer pushing
  a new version from the source.
- Keep user data outside the app folder, or the app is "edited" forever.

## The audit log

Every routine run (from a widget, the CLI or an agent, a webhook, a schedule) and every app
lifecycle action (push / install / update / remove) appends one JSON line to

```
<Cowork root>/workspaces/<workspace path, non-alphanumerics → "-">-<8-hex hash>/audit/<YYYY-MM-DD>.jsonl
```

(`<Cowork root>` is `~/.cowork-studio` on macOS/Linux and `%LOCALAPPDATA%\Cowork` on Windows; e.g.
`~/.cowork-studio/workspaces/-home-ana-work-fa27a359/audit/2026-09-26.jsonl`, dates in UTC; the hash keeps
paths like `/a/b-c` and `/a-b/c` apart — find the folder with `ls ~/.cowork-studio/workspaces/ | grep <name>`.)
A line:

```json
{"ts":"2026-09-26T14:03:11.412Z","capability":"gmail-fetch","input":{"folder":"INBOX"},
 "status":"ok","durationMs":812,
 "context":{"executionId":"…","userId":"…","organizationId":"…","deviceId":"…",
            "workspace":"/home/ana/work","source":"cli","agent":"claude-code",
            "appId":"gmail","appVersion":"1.2.3","kind":"capability"}}
```

- `capability` is the routine id for a run (`gmail-fetch`) and `"cowork.app.push"` / `"cowork.app.install"` /
  `"cowork.app.update"` / `"cowork.app.remove"` for a lifecycle action (an automatic update is
  `"cowork.app.update"` with source `system`).
- `source`: `app` (a widget), `cli` (the CLI — `agent` says if Claude Code / Codex ran it),
  `mcp` (an agent through Cowork's MCP door — `principal` names its login, `taskId` its task),
  `webhook`, `schedule`, `system`, `deploy`.
- `status` is `ok`, `error`, or `denied` (refused before it started: an agent the routine is not
  granted to — see [agents and grants](/docs/guides/permissions.md)).
- `input` is the routine's arguments (capped at 16 KB). Values under credential-looking keys
  (`authorization`, `cookie`, hook/capability tokens, `*password`, `*secret`, `*api_key`,
  `*token` fields) are replaced by `"[redacted]"` — but never pass secrets as args anyway.
- `context.caller` (when present) is what the caller CLAIMED (app / source / agent);
  `appId`/`appVersion`/`kind` (`app` | `capability`) are derived by Cowork from the routine file that actually
  ran; `context.caller.chain` lists the routines that called it. `cowork routine runs --app <name>` lists one
  package's runs; `show <run-id>` prints the routines that called it (`via daily → send-report`).
- `agent` is filled when the run came from Claude Code or Codex.
- Local only, files are private to the user (0600), deleted after 7 days. A run that failed or
  changed something also keeps a result preview (200 characters, credential-named keys redacted)
  and its last log lines (up to 2,000 characters); a read that called `ctx.unchanged()` keeps
  neither. Nothing else of the result or the log is recorded.
- To answer "what ran here recently?", read the day files with `jq`, e.g.
  `jq -c 'select(.status!="ok")' ~/.cowork-studio/workspaces/<encoded>/audit/*.jsonl`.
