{"id":"14095547-5db0-4c7e-a8ab-04904e8056ad","entityType":"agent","slug":"clawhub-olares-olares-market","name":"Olares Market (olares-cli market)","canonicalUrl":"https://www.xpersona.co/agent/clawhub-olares-olares-market","canonicalPath":"/agent/clawhub-olares-olares-market","generatedAt":"2026-10-12T00:18:30.923Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":null},"description":"Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the...","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-market","sourceUrl":"https://clawhub.ai/olares/olares-market","homepage":"https://clawhub.ai/olares/skills/olares-market","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/olares/olares-market","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/olares/skills/olares-market","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Olares Market (olares-cli market) technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":null},"stars":null,"forks":null,"downloads":1002,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"4.0.1","tractionLabel":"1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:40:26.582Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T19:40:26.598Z","lastCrawledAt":"2026-10-11T19:40:26.582Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T19:40:26.582Z","lastVerifiedAt":null,"highlights":[{"version":"4.0.1","createdAt":"2026-05-29T05:54:40.654Z","changelog":"Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0)","fileCount":6,"zipByteSize":14740},{"version":"4.0.0","createdAt":"2026-05-29T04:53:46.218Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":6,"zipByteSize":14668},{"version":"1.3.0","createdAt":"2026-05-28T09:28:06.311Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":3,"zipByteSize":23923},{"version":"1.1.0","createdAt":"2026-04-30T13:23:17.179Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":2,"zipByteSize":7889}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-market","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-market` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/olares/olares-market before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-12T00:18:30.918Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-market/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":null},"readme":"Skill: Olares Market (olares-cli market)\n\nOwner: olares\n\nSummary: Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the...\n\nTags: latest:4.0.1\n\nVersion history:\n\nv4.0.1 | 2026-05-29T05:54:40.654Z | user\n\nMigrate ownership from @pengpeng to @olares (no content change vs 4.0.0)\n\nv4.0.0 | 2026-05-29T04:53:46.218Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv1.3.0 | 2026-05-28T09:28:06.311Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv1.1.0 | 2026-04-30T13:23:17.179Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nArchive index:\n\nArchive v4.0.1: 6 files, 14740 bytes\n\nFiles: references/olares-market-charts.md (5182b), references/olares-market-lifecycle.md (8417b), references/olares-market-list.md (6692b), skill-card.md (2645b), SKILL.md (10819b), _meta.json (132b)\n\nFile v4.0.1:SKILL.md\n\n---\r\nname: olares-market\r\nversion: 4.0.1\r\ndescription: \"Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the Olares Market UI, scoped to the active Olares ID. Covers catalog browsing (list, get, categories), the full Olares app lifecycle (install, uninstall, upgrade, clone, cancel, stop, resume), runtime status, --mine view of the active Olares ID's apps (same set the Market UI's My Terminus tab shows, including in-flight installs / upgrades / failures), upload / delete for local Helm chart packages, and --watch flags that block until the app reaches a terminal state. Use when the user mentions Olares, Olares ID, Olares Market, olares-cli market, Olares app store, installing / upgrading / uninstalling / cloning / stopping / resuming / cancelling an Olares app, 'my Olares apps', '我的应用', upload chart, --watch, or asks 'is <app> installed yet on my Olares' / 'show me my Olares apps'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli market --help\"\r\n---\r\n\r\n# market (App-store v2)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli market <verb> --help`.** This file only carries what `--help` cannot give: source resolution, the lifecycle state machine, OpType-vs-State race safety, the verb index, the `-s`/`-a` matrix, and the \"what apps do I have\" routing.\r\n\r\n## Routing\r\n\r\n| User intent | Use this skill? |\r\n|---|---|\r\n| \"Install / upgrade / uninstall an Olares app\" | ✅ yes |\r\n| \"What apps do I have?\" / \"我的应用\" | ✅ yes — `market list --mine` |\r\n| \"Is `<app>` installed yet?\" / \"Wait for `<app>` to be running\" | ✅ yes — `market status <app> --watch` |\r\n| \"Upload my custom helm chart\" | ✅ yes — `market upload` |\r\n| \"What pods is `<app>` running?\" | use [`olares-cluster`](../olares-cluster/SKILL.md) (`cluster application status <ns>`) |\r\n| \"Edit my app's entrance / domain / env / ACL\" | use [`olares-settings`](../olares-settings/SKILL.md) (`settings apps`) |\r\n| \"What's eating CPU on my cluster?\" | use [`olares-dashboard`](../olares-dashboard/SKILL.md) (`dashboard overview ranking`) |\r\n\r\n> **Mental model:** `market` is **lifecycle and inventory** at the app-store level (install / upgrade / chart push). For runtime K8s objects, settings, or metrics, route to a sibling.\r\n\r\n## Verb families\r\n\r\n| Family | Verbs | Mutating? |\r\n|---|---|---|\r\n| **catalog** | `list`, `get`, `categories` | no |\r\n| **runtime** | `status` | no |\r\n| **lifecycle** | `install`, `upgrade`, `uninstall`, `clone`, `stop`, `resume`, `cancel` | yes |\r\n| **charts** | `upload`, `delete` | yes |\r\n\r\nFor verb-specific behavior, **always start with `olares-cli market <verb> --help`**. Then drill into a reference if listed:\r\n\r\n| Family | Reference |\r\n|---|---|\r\n| catalog + runtime | [references/olares-market-list.md](references/olares-market-list.md) (`list` / `--mine` / `categories` / `get` / `status`) |\r\n| lifecycle | [references/olares-market-lifecycle.md](references/olares-market-lifecycle.md) (`install` / `upgrade` / `uninstall` / `clone` / `stop` / `resume` / `cancel`) |\r\n| charts | [references/olares-market-charts.md](references/olares-market-charts.md) (`upload` / `delete`) |\r\n\r\n## Source resolution (cross-cutting)\r\n\r\nThe market backend serves multiple \"sources\" of charts. The CLI resolves which one to talk to from `-s / --source`, falling back to a default that depends on the verb:\r\n\r\n| Source id | What it is | Used by |\r\n|---|---|---|\r\n| `market.olares` | Public catalog (read-only browse) | default for `list`, `get`, `categories`, `install`, `upgrade`, `clone`, `status` |\r\n| `upload` | SPA \"Local Sources → Upload\" bucket | **hard-coded for `upload` / `delete`** — `-s` is intentionally NOT exposed on those two verbs |\r\n| `cli` | Legacy CLI-upload bucket | read-only (`list`, `status`) |\r\n| `studio` | Devbox / Studio bucket | read-only (`list`, `status`) |\r\n\r\n- When `-s` is omitted, every verb that accepts it prints `Using source: <id>` to stderr so the agent can confirm which backend was hit.\r\n- `-a / --all-sources` bypasses single-source resolution and spans every source the user has — read-only verbs only.\r\n- **Unknown source ids silently produce an empty result** with a `no apps in source 'X'` stderr hint. Run `market list -a` to enumerate the configured sources.\r\n\r\n### `-s` / `-a` matrix\r\n\r\n| Flag | Read-only browse | Lifecycle (mutating) | Chart management |\r\n|---|---|---|---|\r\n| `-s / --source` | `list`, `categories`, `status`, `get` | `install`, `upgrade`, `clone` | — (hard-coded `upload`) |\r\n| `-a / --all-sources` | `list`, `categories`, `status` | — | — |\r\n\r\n> **`-s` is NOT on `uninstall` / `stop` / `resume` / `cancel`:** they act on whichever per-user state row matches the app name, regardless of source.\r\n\r\n## App lifecycle / state machine\r\n\r\nThe backend tracks two orthogonal axes per app: **`State`** (where the row currently is) and **`OpType`** (which mutation is in flight). The CLI groups the full enum into four buckets:\r\n\r\n| Bucket | Examples | Meaning |\r\n|---|---|---|\r\n| **Progressing** | `pending`, `installing`, `upgrading`, `uninstalling`, `stopping`, `resuming`, `*Canceling` | Backend is actively working — keep polling |\r\n| **Terminal success** | `running`, `stopped`, `uninstalled` | Mutation finished cleanly |\r\n| **Terminal failure** | `installFailed`, `upgradeFailed`, `uninstallFailed`, `stopFailed`, `resumeFailed` | Mutation finished with a hard error |\r\n| **Canceled / cancel-failed** | `*Canceled`, `*CancelFailed` | A `cancel` request landed (or itself failed) |\r\n\r\nEach lifecycle verb maps to its own subset of terminal-success buckets — see the lifecycle reference.\r\n\r\n## OpType vs State (race-safety)\r\n\r\nThe same `State` can mean different things depending on which mutation is in flight. Concrete example: an `upgrade` issued against an app already in `running` will return `state=running, opType=running` for one or two ticks before the backend flips to `state=upgrading, opType=upgrade`. A naive watcher would declare success at tick zero.\r\n\r\n**The CLI's mutating-verb watchers refuse to accept any \"success\" classification until either:**\r\n\r\n1. the row's `OpType` matches the op the CLI just issued, **or**\r\n2. the row disappears entirely (only legal for `uninstall` / `status`).\r\n\r\n`cancel` and `status` deliberately set `matchOpType=false` because they are op-agnostic by design — `cancel` declares success on any \"row stopped moving\" state.\r\n\r\n## `--watch` semantics (lifecycle verbs)\r\n\r\n- **Polling, not streaming.** Tick on `--watch-interval` (default tuned to the backend's progress cadence).\r\n- `--watch-timeout D` caps total wall-clock time.\r\n- One-shot (no `--watch`) returns as soon as the backend ACKs the mutation request — the row may still be `progressing` for minutes.\r\n- With `--watch`, the CLI blocks until the row reaches a terminal bucket (success OR failure) matching the OpType safety rules above.\r\n- **Idempotent**: `resume` against an already-`running` row returns immediately with success; `install` against an already-installed row returns immediately with `state=running`. Watcher never hangs on \"no-op\" mutations.\r\n- `--watch-iterations` / `--watch-interval` / `--watch-timeout` are **rejected without `--watch`**.\r\n\r\n## \"What apps do I have?\" routing\r\n\r\n| Question | Right verb | Why |\r\n|---|---|---|\r\n| \"Show me my apps\" / \"我的应用\" | `market list --mine` (alias `-m`) | Matches the Market UI's \"My Terminus\" tab exactly — includes in-flight installs, failed rows, transitional states. Wider than \"completed installs only\" |\r\n| \"Runtime status of `<app>`\" / \"is it installed yet\" | `market status <app>` or `market status <app> --watch` | Focused view: `STATE / OPERATION / PROGRESS / SOURCE`. The single-app form does cross-source fallback if `-s` doesn't find the row |\r\n| \"Which apps are running right now\" | `market status` (no app) | All installed-app rows in the resolved source. Add `-a` for every source. **Cannot `--watch`** — use `status <app> --watch` instead |\r\n| \"Browse the catalog\" | `market list` (no `--mine`) | Hits `/market/data`, not `/market/state`. Browse-and-discover, not inventory |\r\n\r\n> **`list --mine` is NOT the same as \"completed installs only\".** It hides only the 6 SPA-hidden `uninstalledAppStates` (`pendingCanceled`, `downloadingCanceled`, `downloadFailed`, `installFailed`, `installingCanceled`, `uninstalled`). In-flight installs and post-install failures stay visible because the user clicked something and expects to monitor / retry / cancel them.\r\n\r\n## Output conventions\r\n\r\n- `-o table` (default) or `-o json` — present on EVERY market verb.\r\n- `-q / --quiet` suppresses all stdout / stderr; exit code carries the signal.\r\n- `--no-headers` — only `list` and `categories` (row-oriented browse). NOT on `get` (key:value detail, no headers separable) or `status` (runtime probe, always headered). No-op silently on mutating verbs.\r\n- Mutating verbs emit a stable `OperationResult` JSON shape with `finalState` / `finalOpType` for scripted parsing.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `no apps in source 'X'` (stderr) | Unknown source id, or empty result | Run `market list -a` to enumerate configured sources |\r\n| `app 'X' is not installed (run 'olares-cli market install X' to install it)` | `status <app>` could not find the row anywhere | Install first, or verify the app name spelling |\r\n| `App is installed under source 'Y' (not 'X')` (stderr) | `-s X` but the row is actually in source Y | Drop `-s`, or pass the correct source. The CLI still renders the row |\r\n| `missing required env var(s): KEY1, KEY2 ...` | `install` for an app that declares required envs | Re-run with `--env KEY=VALUE` (repeatable) for each missing var |\r\n| Lifecycle watcher hangs near `*Failed` state | Backend failed but kept the failure row visible | Inspect `market status <app>` for the failure detail; cancel with `market cancel <app>` if applicable |\r\n| `cannot --watch 'status' (no app argument)` | `market status` without an app + `--watch` | Use `status <app> --watch` |\r\n| 401 / 403 from any verb after refresh | Token rotation / consistent server-side rejection | See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) |\r\n| `--cascade NOT passed: auto-decided ...` (stderr) | C/S v2 multi-chart app on single-user cluster | Informational — the CLI picked `--cascade=true`; pass `--cascade=false` explicitly to override |\r\n\r\nFor the full auth-error matrix see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\n\nFile v4.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-market\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034080654\n}\n\nFile v4.0.1:references/olares-market-charts.md\n\n# market upload / delete (local chart management)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & examples:** `olares-cli market upload --help`, `olares-cli market delete --help`.\r\n\r\nManage helm chart packages in the SPA's \"Local Sources → Upload\" bucket.\r\n\r\n## Hard-coded source (`upload`)\r\n\r\nBoth verbs **hard-code the target source to `upload`** — the same bucket the SPA's \"Local Sources → Upload\" tab writes to. **`-s / --source` is intentionally NOT exposed.**\r\n\r\nThe history: an earlier revision exposed `-s` on these verbs, and users pushed charts to `cli` or `studio` that were then invisible to the SPA's Local Sources tab (different buckets, same backend). Pinning the source eliminates that footgun. To install/delete from a different bucket, use `market install -s <id>` separately.\r\n\r\n## `upload`\r\n\r\n```bash\r\nolares-cli market upload ./mychart.tgz                 # single file\r\nolares-cli market upload ./charts/                     # every chart in a directory (no recursion)\r\nolares-cli market upload ./a.tgz ./b.tar.gz            # multiple files\r\nolares-cli market upload ./charts/ -o json             # structured per-file report\r\nolares-cli market upload ./mychart.tgz -q              # exit code only\r\n```\r\n\r\n- Accepts `.tgz` or `.tar.gz` files.\r\n- Directory mode uploads every `.tgz` / `.tar.gz` directly under the directory. **Subdirectories are NOT recursed.**\r\n- **Per-file results are summarized at the end.** `-o json` emits a structured report with one entry per file (`status` / `message`).\r\n- **Exit code is the OR of per-file results** — any single failure flips the overall exit non-zero.\r\n- Multipart upload through a dedicated `uploadClient` with no timeout (chart pushes can be slow over large WAN links). The same `refreshingTransport` is shared with the JSON client, so a token refresh on one is immediately visible on the other.\r\n\r\n### After upload\r\n\r\nMatch the chart with an install / delete:\r\n\r\n```bash\r\n# Install (must pass -s upload):\r\nolares-cli market install mychart -s upload --version 1.0.0\r\n\r\n# Browse what's in the upload bucket:\r\nolares-cli market list -s upload\r\n\r\n# Detail of one uploaded chart:\r\nolares-cli market get mychart -s upload -o json\r\n```\r\n\r\n## `delete`\r\n\r\n```bash\r\nolares-cli market delete mychart                       # every uploaded version\r\nolares-cli market delete mychart --version 1.0.0       # one version\r\nolares-cli market delete mychart -o json\r\nolares-cli market delete mychart -q\r\n```\r\n\r\n- **Does NOT uninstall the app if it is running.** Use `market uninstall <app>` first, then `market delete` to also remove the chart from local sources.\r\n- `--version` omitted → every uploaded version of the chart in the `upload` bucket is removed.\r\n\r\n> The \"delete the chart\" and \"uninstall the running app\" are deliberately separate verbs. A chart can be uploaded without ever being installed; an installed app can keep running after the source chart is deleted from the bucket.\r\n\r\n## Agent workflows\r\n\r\n```bash\r\n# Full custom-chart cycle.\r\nolares-cli market upload ./mychart-1.0.0.tgz                       # land it in source=upload\r\nolares-cli market install mychart -s upload --version 1.0.0 --watch\r\n# ... use the app ...\r\nolares-cli market uninstall mychart --watch                        # tear down the deployment\r\nolares-cli market delete mychart --version 1.0.0                   # remove the chart from local sources\r\n```\r\n\r\n```bash\r\n# Bulk-upload every chart in a release directory.\r\nolares-cli market upload ./dist/ -o json | jq '.[] | select(.status != \"uploaded\")'\r\n# JSON exit code is the OR of all per-file results; the jq filter surfaces the failures\r\n```\r\n\r\n```bash\r\n# Spring cleaning: remove every version of a chart from the upload bucket.\r\nolares-cli market delete mychart                                   # all versions\r\nolares-cli market list -s upload                                   # confirm\r\n```\r\n\r\n## Safety constraints\r\n\r\n- **`delete` is destructive** — it removes the chart from the bucket. If the app is still running, the deployment continues to work but you can no longer reinstall from the local bucket.\r\n- **`upload` overwrites by `(name, version)`** — uploading `mychart-1.0.0.tgz` twice replaces the previous bytes. To bump, change the version inside `Chart.yaml` and re-upload.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `unsupported file extension: must be .tgz or .tar.gz` | Wrong file type | Repackage with `helm package` |\r\n| `failed to upload: HTTP 413 (Payload Too Large)` | Chart exceeds the server's upload size limit | Slim the chart's contents; ask the operator about the limit |\r\n| `chart not found in source 'upload'` (delete) | The chart was never uploaded, or was uploaded to a different bucket | `market list -s upload` to confirm |\r\n| `chart still in use by running app 'X'` | `delete` while the app is running | `market uninstall X` first, then re-run `delete` |\r\n| Exit non-zero on directory upload despite some files succeeding | Partial failure | Inspect the per-file JSON report for which files failed |\n\nFile v4.0.1:references/olares-market-lifecycle.md\n\n# market lifecycle (install / upgrade / uninstall / clone / stop / resume / cancel)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"App lifecycle / state machine\", \"OpType vs State\", and \"`--watch` semantics\") first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb.\r\n\r\nThe mutating verb family. Every verb here returns an `OperationResult` JSON shape on `-o json`:\r\n\r\n```json\r\n{\r\n  \"name\": \"firefox\",\r\n  \"op\": \"install\",\r\n  \"accepted\": true,\r\n  \"finalState\": \"running\",\r\n  \"finalOpType\": \"\",\r\n  \"message\": \"\",\r\n  \"watched\": true,\r\n  \"cloneTarget\": \"\"          // only set for `clone`\r\n}\r\n```\r\n\r\n## Source-aware vs source-implicit verbs\r\n\r\n| Verb | `-s / --source` | Why |\r\n|---|---|---|\r\n| `install`, `upgrade`, `clone` | accepts; defaults to auto-selected source | The chart can live in different sources |\r\n| `uninstall`, `stop`, `resume`, `cancel` | **NOT exposed** | Acts on whichever per-user state row matches the app name, regardless of source |\r\n\r\n## `install`\r\n\r\n```bash\r\nolares-cli market install firefox                      # auto-selected source, latest version\r\nolares-cli market install firefox --version 1.2.3      # pin version (strict semver)\r\nolares-cli market install firefox -s upload            # install a locally-uploaded chart\r\nolares-cli market install gitea --env GITEA_TOKEN=...  # required envs\r\nolares-cli market install firefox --watch              # block until terminal\r\nolares-cli market install firefox --watch -o json\r\n```\r\n\r\n- `--version` defaults to the latest catalog version. Strict semver validated client-side before send.\r\n- `--env KEY=VALUE` (repeatable) for required env vars. Missing required envs surface as `missing required env var(s): KEY1, KEY2 ...` (server returns HTTP 422 / `type=appenv`).\r\n- **To install a locally-uploaded chart, pass `-s upload`** (the bucket `market upload` writes to).\r\n\r\n## `upgrade`\r\n\r\n```bash\r\nolares-cli market upgrade firefox                      # latest catalog\r\nolares-cli market upgrade firefox --version 1.5.0\r\nolares-cli market upgrade firefox --watch\r\n```\r\n\r\n### Pre-flight gates (run BEFORE the PUT request)\r\n\r\nMirrors the SPA's `canUpgrade()`. Bails locally with a self-contained error (formatted via `failOp`, so `-o json` carries it in `.message` and `-q` still surfaces the exit code):\r\n\r\n1. **Row exists** — state row found via `Name` or `RawName` (clones included)\r\n2. **State is upgradable** — `running` / `stopped` / `stopFailed` / `upgradeFailed` / `applyEnvFailed`\r\n3. **Newer chart available** — `targetVersion > installedVersion` (semver compare)\r\n4. **App labels don't forbid upgrade** — `disabled-upgrade`, `suspend`, `remove` labels\r\n\r\n## `uninstall`\r\n\r\n```bash\r\nolares-cli market uninstall firefox                    # implicit source\r\nolares-cli market uninstall firefox --cascade=true     # tear down shared sub-charts (C/S v2 multi-chart)\r\nolares-cli market uninstall firefox --watch\r\n```\r\n\r\n### `--cascade` auto-decision (C/S v2 multi-chart apps)\r\n\r\nThe JSON payload field is `all`. Default behavior mirrors the SPA's `csAppUninstall()` dialog:\r\n\r\n- `--cascade NOT passed`: **auto-decided**. When the cluster has a single user AND the target app is a v2 multi-chart bundle (`isCSV2`), default to `--cascade=true`; otherwise `--cascade=false`.\r\n- A short reason is printed on **stderr** when the auto-decision flips the default to true.\r\n- Probe errors (user count / app info) soft-fail to `--cascade=false`; the backend has the final say either way.\r\n\r\n## `clone`\r\n\r\n```bash\r\nolares-cli market clone firefox --title \"Work Browser\"\r\nolares-cli market clone firefox --title \"Work Browser\" --entrance-title web=WorkWeb\r\nolares-cli market clone firefox --title \"Work Browser\" --watch\r\n```\r\n\r\n- **Only apps that advertise `cloneable: true`** in `market get <app> -o json` support this. Pre-flight check the source app first if unsure.\r\n- `--title` is REQUIRED — feeds the cloned app's desktop shortcut title.\r\n- For apps with multiple entrances: `--entrance-title NAME=TITLE` (repeatable) overrides per-entrance titles. For single-entrance apps, the entrance title defaults to `--title`.\r\n- **The backend mints a per-instance app name** (e.g. `firefoxe992`). The CLI surfaces it as `cloneTarget` in the JSON output so scripted callers can chain follow-ups. **`--watch` tracks the new clone name, not the source app.**\r\n\r\n## `stop` / `resume`\r\n\r\n```bash\r\nolares-cli market stop firefox                         # suspend\r\nolares-cli market stop firefox --cascade=true          # C/S v2: shared sub-charts too\r\nolares-cli market stop firefox --watch                 # block until `stopped`\r\n\r\nolares-cli market resume firefox                       # un-suspend\r\nolares-cli market resume firefox --watch               # block until `running`\r\n```\r\n\r\n- Source is implicit on both.\r\n- `--cascade` on `stop` follows the same auto-decision rules as `uninstall`.\r\n- **`resume` is idempotent**: against an already-`running` row, returns immediately with success (`{state=running, opType=\"\"}`), instead of hanging until `--watch-timeout` fires.\r\n\r\n## `cancel`\r\n\r\n```bash\r\nolares-cli market cancel firefox                       # cancel current op\r\nolares-cli market cancel firefox --watch               # block until row stops moving\r\n```\r\n\r\n- Source is implicit.\r\n- **The widest watcher in the tree**: any \"row stopped moving\" state counts as success, including `*Canceled`, `*Failed` (the underlying op died, cancel \"won by default\"), and stable resting states `running` / `stopped` / `uninstalled` (cancel raced and lost, OR rollback landed).\r\n- Failure is ONLY surfaced for `*CancelFailed` (the cancel request itself was rejected).\r\n- The terminal row carries the **underlying op** (install / upgrade / ...) as its `opType`, not `cancel`. `matchOpType` is OFF — no race-tracking gate applies.\r\n\r\n## `--watch` interaction with each verb\r\n\r\n| Verb | Terminal-success buckets | Idempotent shortcut |\r\n|---|---|---|\r\n| `install` | `running` | App already installed → returns immediately |\r\n| `upgrade` | `running` (matchOpType=upgrade) | — (handled by pre-flight) |\r\n| `uninstall` | `uninstalled`, row disappears | App already uninstalled → returns immediately |\r\n| `clone` | `running` on the new clone name | — |\r\n| `stop` | `stopped` | Already stopped → returns immediately |\r\n| `resume` | `running` | Already running → returns immediately |\r\n| `cancel` | Any \"stopped moving\" state | — |\r\n\r\n## Agent best practices\r\n\r\n- **For \"install X and tell me when it's running\"** → `market install X --watch -o json`, then parse `.finalState`.\r\n- **For \"upgrade X if there's a newer version\"** → `market get X -o json` to check version, then `market upgrade X --watch`. The pre-flight will short-circuit if there's no newer chart.\r\n- **For \"stop everything for this user\"** → `market list --mine -o json | jq -r '.[].name'` + a shell loop calling `market stop`. The cluster doesn't expose a bulk-stop verb.\r\n- **For \"install a custom chart\"** → `market upload ./mychart.tgz` (always lands in source `upload`), then `market install <name> -s upload`.\r\n- **For ambiguous source rows on uninstall/stop/resume**: the verb already resolves automatically. Don't pass `-s` even when the SPA shows it under multiple sources.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `missing required env var(s): KEY1, KEY2 ...` (install) | App declares required envs | Re-run with `--env KEY=VALUE` per missing var |\r\n| `app 'X' is not in an upgradable state (current: Y)` | Pre-flight gate 2 | Wait for terminal state, or run `cancel` first |\r\n| `no newer version available (installed: 1.2.3, latest: 1.2.3)` | Pre-flight gate 3 | Nothing to upgrade |\r\n| `app labels forbid upgrade (suspend / remove / disabled-upgrade)` | Pre-flight gate 4 | App is marked non-upgradable in the catalog; contact app maintainer |\r\n| `app 'X' is not cloneable` | `clone` against non-cloneable app | Check `market get X -o json` for `cloneable` |\r\n| `--title is required` | `clone` without `--title` | Add `--title \"...\"` |\r\n| Watcher hangs near `*Failed` | Backend op failed | `market status <app>` to inspect; `market cancel <app>` if applicable |\r\n| Stderr hint about `--cascade` auto-decision | C/S v2 single-user cluster | Informational; override with `--cascade=false` if needed |\n\nFile v4.0.1:references/olares-market-list.md\n\n# market list / categories / get / status (catalog + runtime)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"What apps do I have? routing\" and the `-s` / `-a` matrix) first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb below.\r\n\r\nThe read-only family — catalog browsing, runtime status, and the `--mine` inventory view.\r\n\r\n## `list` — catalog OR inventory\r\n\r\nTwo modes, selected by `--mine` / `-m`:\r\n\r\n| Mode | Without `--mine` | With `--mine` / `-m` |\r\n|---|---|---|\r\n| Endpoint | `/market/data` | `/market/state` |\r\n| Scope | Catalog | Active profile's apps |\r\n| Default source scope | Auto-selected single source (usually `market.olares`) | **All sources** (matches Market UI \"My Terminus\" tab) |\r\n| Columns | `NAME / TITLE / VERSION / CATEGORIES` | `NAME / TITLE / VERSION / STATE / SOURCE / CATEGORIES` |\r\n\r\n```bash\r\nolares-cli market list                          # catalog (auto-selected source)\r\nolares-cli market list -s market.olares         # pin to a source\r\nolares-cli market list -s cli                   # browse a local source\r\nolares-cli market list -c AI                    # filter by category\r\nolares-cli market list -a                       # every source the user has\r\nolares-cli market list --mine                   # what apps does this user have (all sources)\r\nolares-cli market list -m -s cli                # mine, narrowed to one source\r\nolares-cli market list --no-headers             # table without column headers\r\nolares-cli market list -q                       # exit code only\r\n```\r\n\r\n### `--mine` filter (matches SPA \"My Terminus\" exactly)\r\n\r\nHides only **6 SPA-hidden states** (`pendingCanceled`, `downloadingCanceled`, `downloadFailed`, `installFailed`, `installingCanceled`, `uninstalled`). Everything else stays visible — including in-flight installs, transitional states (`upgrading` / `stopping` / `resuming` / `applyingEnv` / `uninstalling`), and post-install failures (`upgradeFailed` / `stopFailed` / `resumeFailed` / `applyEnvFailed` / `uninstallFailed`).\r\n\r\n> **\"My apps\" is intentionally broader than \"completed installs only\"** because the SPA's My Terminus tab is too. The user clicked something and wants to monitor / retry / cancel the row.\r\n\r\n### Version on `--mine` rows\r\n\r\nThe version column reflects the **chart the user picked for this row**, NOT the catalog latest. If the user installed 1.0.10 and the marketplace catalog has since moved to 1.2.3, this listing surfaces 1.0.10. During an in-flight upgrade, the row may show the target version while `STATE=upgrading`. **Both are intentional — don't \"correct\" to the catalog version.**\r\n\r\nClones look up the catalog by `rawAppName`, not their per-instance `name` (e.g. `windowsefe992` → catalog row `windows`), so clones still surface the source app's title and categories.\r\n\r\n## `categories`\r\n\r\n```bash\r\nolares-cli market categories                    # per-source counts\r\nolares-cli market categories -s market.olares\r\nolares-cli market categories -a -o json         # every source, JSON\r\nolares-cli market categories --no-headers\r\nolares-cli market categories -q\r\n```\r\n\r\nReturns category names with per-source app counts. Pure browsing — no state.\r\n\r\n## `get <app>`\r\n\r\n```bash\r\nolares-cli market get firefox                   # curated summary\r\nolares-cli market get firefox -o json           # full upstream payload\r\nolares-cli market get firefox -s upload         # local-uploaded chart\r\n```\r\n\r\n- Table mode = curated summary.\r\n- **JSON mode = full upstream payload.** Use it to fish out fields the table doesn't surface:\r\n  - `cloneable: true` — whether `market clone` is supported for this app\r\n  - `app_simple_info.app_labels` — the same suspend/remove labels `market upgrade` pre-flight checks\r\n  - Declared env-var spec (needed by `market install --env KEY=VALUE`)\r\n\r\n> `--no-headers` is deliberately NOT supported on `get` — the layout is key:value, not a row table. **For scripted access, use `-o json`.**\r\n\r\n## `status` — runtime probe\r\n\r\n```bash\r\nolares-cli market status                        # every installed-app row (resolved source)\r\nolares-cli market status -s market.olares       # pin to a source\r\nolares-cli market status firefox                # single app, with cross-source fallback hint\r\nolares-cli market status firefox -a             # search every source\r\nolares-cli market status firefox --watch        # block until terminal\r\nolares-cli market status firefox -q             # exit code only\r\n```\r\n\r\nColumns: `NAME / STATE / OPERATION / PROGRESS / SOURCE`.\r\n\r\n### `status <app>` cross-source fallback\r\n\r\n- If the row is missing in the resolved source AND in every other source the user has → `app 'X' is not installed (run 'olares-cli market install X' to install it)`\r\n- If the row exists in source `Y` but the user passed `-s X` → stderr hint `App is installed under source 'Y' (not 'X')`, then renders the row anyway. **Agents do not need to retry blindly.**\r\n\r\n### `status` (no app) does NOT support `--watch`\r\n\r\n`runStatusAll` explicitly rejects `--watch`. Use `status <app> --watch` for a single app, or run `status` in a shell loop with a sleep for the all-apps view.\r\n\r\n## \"What apps do I have?\" decision tree\r\n\r\n```\r\nuser asks: \"show me my apps\" / \"我的应用\" / \"list my Olares apps\"\r\n  → market list --mine          (broad inventory; SPA My Terminus parity)\r\n\r\nuser asks: \"is <app> installed yet?\" / \"wait for <app> to be running\"\r\n  → market status <app> --watch  (single-app runtime; cross-source fallback)\r\n\r\nuser asks: \"which apps are running right now\"\r\n  → market status                (all rows in resolved source; -a for every source)\r\n\r\nuser asks: \"browse the catalog\" / \"what apps are available\"\r\n  → market list                  (no --mine; /market/data, not /market/state)\r\n```\r\n\r\n`list --mine` and `status` overlap but are NOT interchangeable — see the parent SKILL.md \"What apps do I have?\" table for the full distinction.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `no apps in source 'X'` | Unknown source id, or genuinely empty source | `market list -a` to enumerate; check spelling |\r\n| `app 'X' is not installed` | `status <app>` couldn't find the row anywhere | Install first |\r\n| `cannot --watch 'status' (no app argument)` | `--watch` on multi-app `status` | Use `status <app> --watch` |\r\n| `--no-headers has no effect on this verb` (silent) | `--no-headers` on `get` / mutating verbs | Use `-o json` instead |\r\n| Cross-source mismatch hint on `status -s X firefox` | Row in source Y, not X | Drop `-s` or pass the correct source |\n\nFile v4.0.1:skill-card.md\n\n## Description:\n\nOlares Market (olares-cli market) helps agents browse, install, upgrade, uninstall, clone, stop, resume, monitor, upload, and delete Olares Market apps and local Helm chart packages from the command line.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[olares](https://clawhub.ai/user/olares)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and operators use this skill to guide Olares app-store workflows through `olares-cli market`, including catalog browsing, active app inventory, lifecycle changes, runtime status checks, and local Helm chart upload or deletion.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Lifecycle commands such as uninstall, stop, cancel, and cascade actions can interrupt or remove Olares apps.\n\nMitigation: Confirm the target app name, version, and requested operation before proposing destructive commands, and use status checks or `--watch` when the user needs completion confirmation.\n\nRisk: Deleting a local chart package removes it from the upload bucket and can affect future reinstall workflows.\n\nMitigation: Confirm the uploaded chart name and version, and distinguish chart deletion from app uninstallation before proposing `olares-cli market delete`.\n\nRisk: Using the wrong market source can produce empty results or target a different catalog than intended.\n\nMitigation: Verify the source with `market list -a`, source hints, or explicit `-s` values before install, upgrade, or local-chart workflows.\n\n## Reference(s):\n\n- [Olares Market Skill Page](https://clawhub.ai/olares/skills/olares-market)\n- [market list / categories / get / status](references/olares-market-list.md)\n- [market lifecycle (install / upgrade / uninstall / clone / stop / resume / cancel)](references/olares-market-lifecycle.md)\n- [market upload / delete (local chart management)](references/olares-market-charts.md)\n\n## Skill Output:\n\n**Output Type(s):** [guidance, markdown, shell commands, configuration]\n\n**Output Format:** [Markdown with inline `olares-cli market` commands and option guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May recommend table or JSON CLI output modes depending on whether the user needs human-readable results or scripted parsing.]\n\n## Skill Version(s):\n\n4.0.1 (source: frontmatter and server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v4.0.0: 6 files, 14668 bytes\n\nFiles: references/olares-market-charts.md (5182b), references/olares-market-lifecycle.md (8417b), references/olares-market-list.md (6692b), skill-card.md (2562b), SKILL.md (10819b), _meta.json (132b)\n\nFile v4.0.0:SKILL.md\n\n---\r\nname: olares-market\r\nversion: 4.0.0\r\ndescription: \"Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the Olares Market UI, scoped to the active Olares ID. Covers catalog browsing (list, get, categories), the full Olares app lifecycle (install, uninstall, upgrade, clone, cancel, stop, resume), runtime status, --mine view of the active Olares ID's apps (same set the Market UI's My Terminus tab shows, including in-flight installs / upgrades / failures), upload / delete for local Helm chart packages, and --watch flags that block until the app reaches a terminal state. Use when the user mentions Olares, Olares ID, Olares Market, olares-cli market, Olares app store, installing / upgrading / uninstalling / cloning / stopping / resuming / cancelling an Olares app, 'my Olares apps', '我的应用', upload chart, --watch, or asks 'is <app> installed yet on my Olares' / 'show me my Olares apps'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli market --help\"\r\n---\r\n\r\n# market (App-store v2)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli market <verb> --help`.** This file only carries what `--help` cannot give: source resolution, the lifecycle state machine, OpType-vs-State race safety, the verb index, the `-s`/`-a` matrix, and the \"what apps do I have\" routing.\r\n\r\n## Routing\r\n\r\n| User intent | Use this skill? |\r\n|---|---|\r\n| \"Install / upgrade / uninstall an Olares app\" | ✅ yes |\r\n| \"What apps do I have?\" / \"我的应用\" | ✅ yes — `market list --mine` |\r\n| \"Is `<app>` installed yet?\" / \"Wait for `<app>` to be running\" | ✅ yes — `market status <app> --watch` |\r\n| \"Upload my custom helm chart\" | ✅ yes — `market upload` |\r\n| \"What pods is `<app>` running?\" | use [`olares-cluster`](../olares-cluster/SKILL.md) (`cluster application status <ns>`) |\r\n| \"Edit my app's entrance / domain / env / ACL\" | use [`olares-settings`](../olares-settings/SKILL.md) (`settings apps`) |\r\n| \"What's eating CPU on my cluster?\" | use [`olares-dashboard`](../olares-dashboard/SKILL.md) (`dashboard overview ranking`) |\r\n\r\n> **Mental model:** `market` is **lifecycle and inventory** at the app-store level (install / upgrade / chart push). For runtime K8s objects, settings, or metrics, route to a sibling.\r\n\r\n## Verb families\r\n\r\n| Family | Verbs | Mutating? |\r\n|---|---|---|\r\n| **catalog** | `list`, `get`, `categories` | no |\r\n| **runtime** | `status` | no |\r\n| **lifecycle** | `install`, `upgrade`, `uninstall`, `clone`, `stop`, `resume`, `cancel` | yes |\r\n| **charts** | `upload`, `delete` | yes |\r\n\r\nFor verb-specific behavior, **always start with `olares-cli market <verb> --help`**. Then drill into a reference if listed:\r\n\r\n| Family | Reference |\r\n|---|---|\r\n| catalog + runtime | [references/olares-market-list.md](references/olares-market-list.md) (`list` / `--mine` / `categories` / `get` / `status`) |\r\n| lifecycle | [references/olares-market-lifecycle.md](references/olares-market-lifecycle.md) (`install` / `upgrade` / `uninstall` / `clone` / `stop` / `resume` / `cancel`) |\r\n| charts | [references/olares-market-charts.md](references/olares-market-charts.md) (`upload` / `delete`) |\r\n\r\n## Source resolution (cross-cutting)\r\n\r\nThe market backend serves multiple \"sources\" of charts. The CLI resolves which one to talk to from `-s / --source`, falling back to a default that depends on the verb:\r\n\r\n| Source id | What it is | Used by |\r\n|---|---|---|\r\n| `market.olares` | Public catalog (read-only browse) | default for `list`, `get`, `categories`, `install`, `upgrade`, `clone`, `status` |\r\n| `upload` | SPA \"Local Sources → Upload\" bucket | **hard-coded for `upload` / `delete`** — `-s` is intentionally NOT exposed on those two verbs |\r\n| `cli` | Legacy CLI-upload bucket | read-only (`list`, `status`) |\r\n| `studio` | Devbox / Studio bucket | read-only (`list`, `status`) |\r\n\r\n- When `-s` is omitted, every verb that accepts it prints `Using source: <id>` to stderr so the agent can confirm which backend was hit.\r\n- `-a / --all-sources` bypasses single-source resolution and spans every source the user has — read-only verbs only.\r\n- **Unknown source ids silently produce an empty result** with a `no apps in source 'X'` stderr hint. Run `market list -a` to enumerate the configured sources.\r\n\r\n### `-s` / `-a` matrix\r\n\r\n| Flag | Read-only browse | Lifecycle (mutating) | Chart management |\r\n|---|---|---|---|\r\n| `-s / --source` | `list`, `categories`, `status`, `get` | `install`, `upgrade`, `clone` | — (hard-coded `upload`) |\r\n| `-a / --all-sources` | `list`, `categories`, `status` | — | — |\r\n\r\n> **`-s` is NOT on `uninstall` / `stop` / `resume` / `cancel`:** they act on whichever per-user state row matches the app name, regardless of source.\r\n\r\n## App lifecycle / state machine\r\n\r\nThe backend tracks two orthogonal axes per app: **`State`** (where the row currently is) and **`OpType`** (which mutation is in flight). The CLI groups the full enum into four buckets:\r\n\r\n| Bucket | Examples | Meaning |\r\n|---|---|---|\r\n| **Progressing** | `pending`, `installing`, `upgrading`, `uninstalling`, `stopping`, `resuming`, `*Canceling` | Backend is actively working — keep polling |\r\n| **Terminal success** | `running`, `stopped`, `uninstalled` | Mutation finished cleanly |\r\n| **Terminal failure** | `installFailed`, `upgradeFailed`, `uninstallFailed`, `stopFailed`, `resumeFailed` | Mutation finished with a hard error |\r\n| **Canceled / cancel-failed** | `*Canceled`, `*CancelFailed` | A `cancel` request landed (or itself failed) |\r\n\r\nEach lifecycle verb maps to its own subset of terminal-success buckets — see the lifecycle reference.\r\n\r\n## OpType vs State (race-safety)\r\n\r\nThe same `State` can mean different things depending on which mutation is in flight. Concrete example: an `upgrade` issued against an app already in `running` will return `state=running, opType=running` for one or two ticks before the backend flips to `state=upgrading, opType=upgrade`. A naive watcher would declare success at tick zero.\r\n\r\n**The CLI's mutating-verb watchers refuse to accept any \"success\" classification until either:**\r\n\r\n1. the row's `OpType` matches the op the CLI just issued, **or**\r\n2. the row disappears entirely (only legal for `uninstall` / `status`).\r\n\r\n`cancel` and `status` deliberately set `matchOpType=false` because they are op-agnostic by design — `cancel` declares success on any \"row stopped moving\" state.\r\n\r\n## `--watch` semantics (lifecycle verbs)\r\n\r\n- **Polling, not streaming.** Tick on `--watch-interval` (default tuned to the backend's progress cadence).\r\n- `--watch-timeout D` caps total wall-clock time.\r\n- One-shot (no `--watch`) returns as soon as the backend ACKs the mutation request — the row may still be `progressing` for minutes.\r\n- With `--watch`, the CLI blocks until the row reaches a terminal bucket (success OR failure) matching the OpType safety rules above.\r\n- **Idempotent**: `resume` against an already-`running` row returns immediately with success; `install` against an already-installed row returns immediately with `state=running`. Watcher never hangs on \"no-op\" mutations.\r\n- `--watch-iterations` / `--watch-interval` / `--watch-timeout` are **rejected without `--watch`**.\r\n\r\n## \"What apps do I have?\" routing\r\n\r\n| Question | Right verb | Why |\r\n|---|---|---|\r\n| \"Show me my apps\" / \"我的应用\" | `market list --mine` (alias `-m`) | Matches the Market UI's \"My Terminus\" tab exactly — includes in-flight installs, failed rows, transitional states. Wider than \"completed installs only\" |\r\n| \"Runtime status of `<app>`\" / \"is it installed yet\" | `market status <app>` or `market status <app> --watch` | Focused view: `STATE / OPERATION / PROGRESS / SOURCE`. The single-app form does cross-source fallback if `-s` doesn't find the row |\r\n| \"Which apps are running right now\" | `market status` (no app) | All installed-app rows in the resolved source. Add `-a` for every source. **Cannot `--watch`** — use `status <app> --watch` instead |\r\n| \"Browse the catalog\" | `market list` (no `--mine`) | Hits `/market/data`, not `/market/state`. Browse-and-discover, not inventory |\r\n\r\n> **`list --mine` is NOT the same as \"completed installs only\".** It hides only the 6 SPA-hidden `uninstalledAppStates` (`pendingCanceled`, `downloadingCanceled`, `downloadFailed`, `installFailed`, `installingCanceled`, `uninstalled`). In-flight installs and post-install failures stay visible because the user clicked something and expects to monitor / retry / cancel them.\r\n\r\n## Output conventions\r\n\r\n- `-o table` (default) or `-o json` — present on EVERY market verb.\r\n- `-q / --quiet` suppresses all stdout / stderr; exit code carries the signal.\r\n- `--no-headers` — only `list` and `categories` (row-oriented browse). NOT on `get` (key:value detail, no headers separable) or `status` (runtime probe, always headered). No-op silently on mutating verbs.\r\n- Mutating verbs emit a stable `OperationResult` JSON shape with `finalState` / `finalOpType` for scripted parsing.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `no apps in source 'X'` (stderr) | Unknown source id, or empty result | Run `market list -a` to enumerate configured sources |\r\n| `app 'X' is not installed (run 'olares-cli market install X' to install it)` | `status <app>` could not find the row anywhere | Install first, or verify the app name spelling |\r\n| `App is installed under source 'Y' (not 'X')` (stderr) | `-s X` but the row is actually in source Y | Drop `-s`, or pass the correct source. The CLI still renders the row |\r\n| `missing required env var(s): KEY1, KEY2 ...` | `install` for an app that declares required envs | Re-run with `--env KEY=VALUE` (repeatable) for each missing var |\r\n| Lifecycle watcher hangs near `*Failed` state | Backend failed but kept the failure row visible | Inspect `market status <app>` for the failure detail; cancel with `market cancel <app>` if applicable |\r\n| `cannot --watch 'status' (no app argument)` | `market status` without an app + `--watch` | Use `status <app> --watch` |\r\n| 401 / 403 from any verb after refresh | Token rotation / consistent server-side rejection | See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) |\r\n| `--cascade NOT passed: auto-decided ...` (stderr) | C/S v2 multi-chart app on single-user cluster | Informational — the CLI picked `--cascade=true`; pass `--cascade=false` explicitly to override |\r\n\r\nFor the full auth-error matrix see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\n\nFile v4.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-market\",\n  \"version\": \"4.0.0\",\n  \"publishedAt\": 1780030426218\n}\n\nFile v4.0.0:references/olares-market-charts.md\n\n# market upload / delete (local chart management)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & examples:** `olares-cli market upload --help`, `olares-cli market delete --help`.\r\n\r\nManage helm chart packages in the SPA's \"Local Sources → Upload\" bucket.\r\n\r\n## Hard-coded source (`upload`)\r\n\r\nBoth verbs **hard-code the target source to `upload`** — the same bucket the SPA's \"Local Sources → Upload\" tab writes to. **`-s / --source` is intentionally NOT exposed.**\r\n\r\nThe history: an earlier revision exposed `-s` on these verbs, and users pushed charts to `cli` or `studio` that were then invisible to the SPA's Local Sources tab (different buckets, same backend). Pinning the source eliminates that footgun. To install/delete from a different bucket, use `market install -s <id>` separately.\r\n\r\n## `upload`\r\n\r\n```bash\r\nolares-cli market upload ./mychart.tgz                 # single file\r\nolares-cli market upload ./charts/                     # every chart in a directory (no recursion)\r\nolares-cli market upload ./a.tgz ./b.tar.gz            # multiple files\r\nolares-cli market upload ./charts/ -o json             # structured per-file report\r\nolares-cli market upload ./mychart.tgz -q              # exit code only\r\n```\r\n\r\n- Accepts `.tgz` or `.tar.gz` files.\r\n- Directory mode uploads every `.tgz` / `.tar.gz` directly under the directory. **Subdirectories are NOT recursed.**\r\n- **Per-file results are summarized at the end.** `-o json` emits a structured report with one entry per file (`status` / `message`).\r\n- **Exit code is the OR of per-file results** — any single failure flips the overall exit non-zero.\r\n- Multipart upload through a dedicated `uploadClient` with no timeout (chart pushes can be slow over large WAN links). The same `refreshingTransport` is shared with the JSON client, so a token refresh on one is immediately visible on the other.\r\n\r\n### After upload\r\n\r\nMatch the chart with an install / delete:\r\n\r\n```bash\r\n# Install (must pass -s upload):\r\nolares-cli market install mychart -s upload --version 1.0.0\r\n\r\n# Browse what's in the upload bucket:\r\nolares-cli market list -s upload\r\n\r\n# Detail of one uploaded chart:\r\nolares-cli market get mychart -s upload -o json\r\n```\r\n\r\n## `delete`\r\n\r\n```bash\r\nolares-cli market delete mychart                       # every uploaded version\r\nolares-cli market delete mychart --version 1.0.0       # one version\r\nolares-cli market delete mychart -o json\r\nolares-cli market delete mychart -q\r\n```\r\n\r\n- **Does NOT uninstall the app if it is running.** Use `market uninstall <app>` first, then `market delete` to also remove the chart from local sources.\r\n- `--version` omitted → every uploaded version of the chart in the `upload` bucket is removed.\r\n\r\n> The \"delete the chart\" and \"uninstall the running app\" are deliberately separate verbs. A chart can be uploaded without ever being installed; an installed app can keep running after the source chart is deleted from the bucket.\r\n\r\n## Agent workflows\r\n\r\n```bash\r\n# Full custom-chart cycle.\r\nolares-cli market upload ./mychart-1.0.0.tgz                       # land it in source=upload\r\nolares-cli market install mychart -s upload --version 1.0.0 --watch\r\n# ... use the app ...\r\nolares-cli market uninstall mychart --watch                        # tear down the deployment\r\nolares-cli market delete mychart --version 1.0.0                   # remove the chart from local sources\r\n```\r\n\r\n```bash\r\n# Bulk-upload every chart in a release directory.\r\nolares-cli market upload ./dist/ -o json | jq '.[] | select(.status != \"uploaded\")'\r\n# JSON exit code is the OR of all per-file results; the jq filter surfaces the failures\r\n```\r\n\r\n```bash\r\n# Spring cleaning: remove every version of a chart from the upload bucket.\r\nolares-cli market delete mychart                                   # all versions\r\nolares-cli market list -s upload                                   # confirm\r\n```\r\n\r\n## Safety constraints\r\n\r\n- **`delete` is destructive** — it removes the chart from the bucket. If the app is still running, the deployment continues to work but you can no longer reinstall from the local bucket.\r\n- **`upload` overwrites by `(name, version)`** — uploading `mychart-1.0.0.tgz` twice replaces the previous bytes. To bump, change the version inside `Chart.yaml` and re-upload.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `unsupported file extension: must be .tgz or .tar.gz` | Wrong file type | Repackage with `helm package` |\r\n| `failed to upload: HTTP 413 (Payload Too Large)` | Chart exceeds the server's upload size limit | Slim the chart's contents; ask the operator about the limit |\r\n| `chart not found in source 'upload'` (delete) | The chart was never uploaded, or was uploaded to a different bucket | `market list -s upload` to confirm |\r\n| `chart still in use by running app 'X'` | `delete` while the app is running | `market uninstall X` first, then re-run `delete` |\r\n| Exit non-zero on directory upload despite some files succeeding | Partial failure | Inspect the per-file JSON report for which files failed |\n\nFile v4.0.0:references/olares-market-lifecycle.md\n\n# market lifecycle (install / upgrade / uninstall / clone / stop / resume / cancel)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"App lifecycle / state machine\", \"OpType vs State\", and \"`--watch` semantics\") first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb.\r\n\r\nThe mutating verb family. Every verb here returns an `OperationResult` JSON shape on `-o json`:\r\n\r\n```json\r\n{\r\n  \"name\": \"firefox\",\r\n  \"op\": \"install\",\r\n  \"accepted\": true,\r\n  \"finalState\": \"running\",\r\n  \"finalOpType\": \"\",\r\n  \"message\": \"\",\r\n  \"watched\": true,\r\n  \"cloneTarget\": \"\"          // only set for `clone`\r\n}\r\n```\r\n\r\n## Source-aware vs source-implicit verbs\r\n\r\n| Verb | `-s / --source` | Why |\r\n|---|---|---|\r\n| `install`, `upgrade`, `clone` | accepts; defaults to auto-selected source | The chart can live in different sources |\r\n| `uninstall`, `stop`, `resume`, `cancel` | **NOT exposed** | Acts on whichever per-user state row matches the app name, regardless of source |\r\n\r\n## `install`\r\n\r\n```bash\r\nolares-cli market install firefox                      # auto-selected source, latest version\r\nolares-cli market install firefox --version 1.2.3      # pin version (strict semver)\r\nolares-cli market install firefox -s upload            # install a locally-uploaded chart\r\nolares-cli market install gitea --env GITEA_TOKEN=...  # required envs\r\nolares-cli market install firefox --watch              # block until terminal\r\nolares-cli market install firefox --watch -o json\r\n```\r\n\r\n- `--version` defaults to the latest catalog version. Strict semver validated client-side before send.\r\n- `--env KEY=VALUE` (repeatable) for required env vars. Missing required envs surface as `missing required env var(s): KEY1, KEY2 ...` (server returns HTTP 422 / `type=appenv`).\r\n- **To install a locally-uploaded chart, pass `-s upload`** (the bucket `market upload` writes to).\r\n\r\n## `upgrade`\r\n\r\n```bash\r\nolares-cli market upgrade firefox                      # latest catalog\r\nolares-cli market upgrade firefox --version 1.5.0\r\nolares-cli market upgrade firefox --watch\r\n```\r\n\r\n### Pre-flight gates (run BEFORE the PUT request)\r\n\r\nMirrors the SPA's `canUpgrade()`. Bails locally with a self-contained error (formatted via `failOp`, so `-o json` carries it in `.message` and `-q` still surfaces the exit code):\r\n\r\n1. **Row exists** — state row found via `Name` or `RawName` (clones included)\r\n2. **State is upgradable** — `running` / `stopped` / `stopFailed` / `upgradeFailed` / `applyEnvFailed`\r\n3. **Newer chart available** — `targetVersion > installedVersion` (semver compare)\r\n4. **App labels don't forbid upgrade** — `disabled-upgrade`, `suspend`, `remove` labels\r\n\r\n## `uninstall`\r\n\r\n```bash\r\nolares-cli market uninstall firefox                    # implicit source\r\nolares-cli market uninstall firefox --cascade=true     # tear down shared sub-charts (C/S v2 multi-chart)\r\nolares-cli market uninstall firefox --watch\r\n```\r\n\r\n### `--cascade` auto-decision (C/S v2 multi-chart apps)\r\n\r\nThe JSON payload field is `all`. Default behavior mirrors the SPA's `csAppUninstall()` dialog:\r\n\r\n- `--cascade NOT passed`: **auto-decided**. When the cluster has a single user AND the target app is a v2 multi-chart bundle (`isCSV2`), default to `--cascade=true`; otherwise `--cascade=false`.\r\n- A short reason is printed on **stderr** when the auto-decision flips the default to true.\r\n- Probe errors (user count / app info) soft-fail to `--cascade=false`; the backend has the final say either way.\r\n\r\n## `clone`\r\n\r\n```bash\r\nolares-cli market clone firefox --title \"Work Browser\"\r\nolares-cli market clone firefox --title \"Work Browser\" --entrance-title web=WorkWeb\r\nolares-cli market clone firefox --title \"Work Browser\" --watch\r\n```\r\n\r\n- **Only apps that advertise `cloneable: true`** in `market get <app> -o json` support this. Pre-flight check the source app first if unsure.\r\n- `--title` is REQUIRED — feeds the cloned app's desktop shortcut title.\r\n- For apps with multiple entrances: `--entrance-title NAME=TITLE` (repeatable) overrides per-entrance titles. For single-entrance apps, the entrance title defaults to `--title`.\r\n- **The backend mints a per-instance app name** (e.g. `firefoxe992`). The CLI surfaces it as `cloneTarget` in the JSON output so scripted callers can chain follow-ups. **`--watch` tracks the new clone name, not the source app.**\r\n\r\n## `stop` / `resume`\r\n\r\n```bash\r\nolares-cli market stop firefox                         # suspend\r\nolares-cli market stop firefox --cascade=true          # C/S v2: shared sub-charts too\r\nolares-cli market stop firefox --watch                 # block until `stopped`\r\n\r\nolares-cli market resume firefox                       # un-suspend\r\nolares-cli market resume firefox --watch               # block until `running`\r\n```\r\n\r\n- Source is implicit on both.\r\n- `--cascade` on `stop` follows the same auto-decision rules as `uninstall`.\r\n- **`resume` is idempotent**: against an already-`running` row, returns immediately with success (`{state=running, opType=\"\"}`), instead of hanging until `--watch-timeout` fires.\r\n\r\n## `cancel`\r\n\r\n```bash\r\nolares-cli market cancel firefox                       # cancel current op\r\nolares-cli market cancel firefox --watch               # block until row stops moving\r\n```\r\n\r\n- Source is implicit.\r\n- **The widest watcher in the tree**: any \"row stopped moving\" state counts as success, including `*Canceled`, `*Failed` (the underlying op died, cancel \"won by default\"), and stable resting states `running` / `stopped` / `uninstalled` (cancel raced and lost, OR rollback landed).\r\n- Failure is ONLY surfaced for `*CancelFailed` (the cancel request itself was rejected).\r\n- The terminal row carries the **underlying op** (install / upgrade / ...) as its `opType`, not `cancel`. `matchOpType` is OFF — no race-tracking gate applies.\r\n\r\n## `--watch` interaction with each verb\r\n\r\n| Verb | Terminal-success buckets | Idempotent shortcut |\r\n|---|---|---|\r\n| `install` | `running` | App already installed → returns immediately |\r\n| `upgrade` | `running` (matchOpType=upgrade) | — (handled by pre-flight) |\r\n| `uninstall` | `uninstalled`, row disappears | App already uninstalled → returns immediately |\r\n| `clone` | `running` on the new clone name | — |\r\n| `stop` | `stopped` | Already stopped → returns immediately |\r\n| `resume` | `running` | Already running → returns immediately |\r\n| `cancel` | Any \"stopped moving\" state | — |\r\n\r\n## Agent best practices\r\n\r\n- **For \"install X and tell me when it's running\"** → `market install X --watch -o json`, then parse `.finalState`.\r\n- **For \"upgrade X if there's a newer version\"** → `market get X -o json` to check version, then `market upgrade X --watch`. The pre-flight will short-circuit if there's no newer chart.\r\n- **For \"stop everything for this user\"** → `market list --mine -o json | jq -r '.[].name'` + a shell loop calling `market stop`. The cluster doesn't expose a bulk-stop verb.\r\n- **For \"install a custom chart\"** → `market upload ./mychart.tgz` (always lands in source `upload`), then `market install <name> -s upload`.\r\n- **For ambiguous source rows on uninstall/stop/resume**: the verb already resolves automatically. Don't pass `-s` even when the SPA shows it under multiple sources.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `missing required env var(s): KEY1, KEY2 ...` (install) | App declares required envs | Re-run with `--env KEY=VALUE` per missing var |\r\n| `app 'X' is not in an upgradable state (current: Y)` | Pre-flight gate 2 | Wait for terminal state, or run `cancel` first |\r\n| `no newer version available (installed: 1.2.3, latest: 1.2.3)` | Pre-flight gate 3 | Nothing to upgrade |\r\n| `app labels forbid upgrade (suspend / remove / disabled-upgrade)` | Pre-flight gate 4 | App is marked non-upgradable in the catalog; contact app maintainer |\r\n| `app 'X' is not cloneable` | `clone` against non-cloneable app | Check `market get X -o json` for `cloneable` |\r\n| `--title is required` | `clone` without `--title` | Add `--title \"...\"` |\r\n| Watcher hangs near `*Failed` | Backend op failed | `market status <app>` to inspect; `market cancel <app>` if applicable |\r\n| Stderr hint about `--cascade` auto-decision | C/S v2 single-user cluster | Informational; override with `--cascade=false` if needed |\n\nFile v4.0.0:references/olares-market-list.md\n\n# market list / categories / get / status (catalog + runtime)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"What apps do I have? routing\" and the `-s` / `-a` matrix) first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb below.\r\n\r\nThe read-only family — catalog browsing, runtime status, and the `--mine` inventory view.\r\n\r\n## `list` — catalog OR inventory\r\n\r\nTwo modes, selected by `--mine` / `-m`:\r\n\r\n| Mode | Without `--mine` | With `--mine` / `-m` |\r\n|---|---|---|\r\n| Endpoint | `/market/data` | `/market/state` |\r\n| Scope | Catalog | Active profile's apps |\r\n| Default source scope | Auto-selected single source (usually `market.olares`) | **All sources** (matches Market UI \"My Terminus\" tab) |\r\n| Columns | `NAME / TITLE / VERSION / CATEGORIES` | `NAME / TITLE / VERSION / STATE / SOURCE / CATEGORIES` |\r\n\r\n```bash\r\nolares-cli market list                          # catalog (auto-selected source)\r\nolares-cli market list -s market.olares         # pin to a source\r\nolares-cli market list -s cli                   # browse a local source\r\nolares-cli market list -c AI                    # filter by category\r\nolares-cli market list -a                       # every source the user has\r\nolares-cli market list --mine                   # what apps does this user have (all sources)\r\nolares-cli market list -m -s cli                # mine, narrowed to one source\r\nolares-cli market list --no-headers             # table without column headers\r\nolares-cli market list -q                       # exit code only\r\n```\r\n\r\n### `--mine` filter (matches SPA \"My Terminus\" exactly)\r\n\r\nHides only **6 SPA-hidden states** (`pendingCanceled`, `downloadingCanceled`, `downloadFailed`, `installFailed`, `installingCanceled`, `uninstalled`). Everything else stays visible — including in-flight installs, transitional states (`upgrading` / `stopping` / `resuming` / `applyingEnv` / `uninstalling`), and post-install failures (`upgradeFailed` / `stopFailed` / `resumeFailed` / `applyEnvFailed` / `uninstallFailed`).\r\n\r\n> **\"My apps\" is intentionally broader than \"completed installs only\"** because the SPA's My Terminus tab is too. The user clicked something and wants to monitor / retry / cancel the row.\r\n\r\n### Version on `--mine` rows\r\n\r\nThe version column reflects the **chart the user picked for this row**, NOT the catalog latest. If the user installed 1.0.10 and the marketplace catalog has since moved to 1.2.3, this listing surfaces 1.0.10. During an in-flight upgrade, the row may show the target version while `STATE=upgrading`. **Both are intentional — don't \"correct\" to the catalog version.**\r\n\r\nClones look up the catalog by `rawAppName`, not their per-instance `name` (e.g. `windowsefe992` → catalog row `windows`), so clones still surface the source app's title and categories.\r\n\r\n## `categories`\r\n\r\n```bash\r\nolares-cli market categories                    # per-source counts\r\nolares-cli market categories -s market.olares\r\nolares-cli market categories -a -o json         # every source, JSON\r\nolares-cli market categories --no-headers\r\nolares-cli market categories -q\r\n```\r\n\r\nReturns category names with per-source app counts. Pure browsing — no state.\r\n\r\n## `get <app>`\r\n\r\n```bash\r\nolares-cli market get firefox                   # curated summary\r\nolares-cli market get firefox -o json           # full upstream payload\r\nolares-cli market get firefox -s upload         # local-uploaded chart\r\n```\r\n\r\n- Table mode = curated summary.\r\n- **JSON mode = full upstream payload.** Use it to fish out fields the table doesn't surface:\r\n  - `cloneable: true` — whether `market clone` is supported for this app\r\n  - `app_simple_info.app_labels` — the same suspend/remove labels `market upgrade` pre-flight checks\r\n  - Declared env-var spec (needed by `market install --env KEY=VALUE`)\r\n\r\n> `--no-headers` is deliberately NOT supported on `get` — the layout is key:value, not a row table. **For scripted access, use `-o json`.**\r\n\r\n## `status` — runtime probe\r\n\r\n```bash\r\nolares-cli market status                        # every installed-app row (resolved source)\r\nolares-cli market status -s market.olares       # pin to a source\r\nolares-cli market status firefox                # single app, with cross-source fallback hint\r\nolares-cli market status firefox -a             # search every source\r\nolares-cli market status firefox --watch        # block until terminal\r\nolares-cli market status firefox -q             # exit code only\r\n```\r\n\r\nColumns: `NAME / STATE / OPERATION / PROGRESS / SOURCE`.\r\n\r\n### `status <app>` cross-source fallback\r\n\r\n- If the row is missing in the resolved source AND in every other source the user has → `app 'X' is not installed (run 'olares-cli market install X' to install it)`\r\n- If the row exists in source `Y` but the user passed `-s X` → stderr hint `App is installed under source 'Y' (not 'X')`, then renders the row anyway. **Agents do not need to retry blindly.**\r\n\r\n### `status` (no app) does NOT support `--watch`\r\n\r\n`runStatusAll` explicitly rejects `--watch`. Use `status <app> --watch` for a single app, or run `status` in a shell loop with a sleep for the all-apps view.\r\n\r\n## \"What apps do I have?\" decision tree\r\n\r\n```\r\nuser asks: \"show me my apps\" / \"我的应用\" / \"list my Olares apps\"\r\n  → market list --mine          (broad inventory; SPA My Terminus parity)\r\n\r\nuser asks: \"is <app> installed yet?\" / \"wait for <app> to be running\"\r\n  → market status <app> --watch  (single-app runtime; cross-source fallback)\r\n\r\nuser asks: \"which apps are running right now\"\r\n  → market status                (all rows in resolved source; -a for every source)\r\n\r\nuser asks: \"browse the catalog\" / \"what apps are available\"\r\n  → market list                  (no --mine; /market/data, not /market/state)\r\n```\r\n\r\n`list --mine` and `status` overlap but are NOT interchangeable — see the parent SKILL.md \"What apps do I have?\" table for the full distinction.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `no apps in source 'X'` | Unknown source id, or genuinely empty source | `market list -a` to enumerate; check spelling |\r\n| `app 'X' is not installed` | `status <app>` couldn't find the row anywhere | Install first |\r\n| `cannot --watch 'status' (no app argument)` | `--watch` on multi-app `status` | Use `status <app> --watch` |\r\n| `--no-headers has no effect on this verb` (silent) | `--no-headers` on `get` / mutating verbs | Use `-o json` instead |\r\n| Cross-source mismatch hint on `status -s X firefox` | Row in source Y, not X | Drop `-s` or pass the correct source |\n\nFile v4.0.0:skill-card.md\n\n## Description: <br>\nOlares Market (olares-cli market) helps agents install, upgrade, uninstall, clone, stop, resume, cancel, inspect, upload, and delete Olares apps through the Olares command-line market workflow. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[olares](https://clawhub.ai/user/olares) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers, operators, and Olares users use this skill to route app-store tasks to `olares-cli market`, including catalog browsing, per-user app inventory, lifecycle operations, runtime status checks, and local Helm chart upload or deletion. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill can guide destructive Olares Market actions such as uninstall, stop, cancel, bulk stop, and uploaded chart deletion. <br>\nMitigation: Confirm exact app names, target sources, and user intent before allowing destructive operations; explain that services may be interrupted or local uploaded chart versions removed. <br>\nRisk: Market operations depend on the active Olares profile and Olares ID, so commands can affect the wrong user's app inventory if context is stale. <br>\nMitigation: Use the shared Olares profile and authentication guidance before running market commands, and prefer read-only status or list checks before mutations. <br>\n\n\n## Reference(s): <br>\n- [Olares Market skill page](https://clawhub.ai/olares/olares-market) <br>\n- [Olares Market charts reference](references/olares-market-charts.md) <br>\n- [Olares Market lifecycle reference](references/olares-market-lifecycle.md) <br>\n- [Olares Market list, categories, get, and status reference](references/olares-market-list.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>\n**Output Format:** [Markdown with inline shell commands and command-output interpretation guidance] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [May propose `olares-cli market` commands, JSON output modes, watch flags, source selection, and confirmation steps for destructive operations.] <br>\n\n## Skill Version(s): <br>\n4.0.0 (source: frontmatter and server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.3.0: 3 files, 23923 bytes\n\nFiles: skill-card.md (2680b), SKILL.md (66461b), _meta.json (132b)\n\nFile v1.3.0:SKILL.md\n\n---\r\nname: olares-market\r\nversion: 1.3.0\r\ndescription: \"Install, upgrade, and manage Olares apps via olares-cli market — the per-user Market app-store v2 mirror of the Olares Market UI. Covers catalog browsing (list, get, categories), the full app lifecycle (install, uninstall, upgrade, clone, cancel, stop, resume), runtime status, --mine view of the active profile's apps (same set the Market UI's My Terminus tab shows, including in-flight installs / upgrades / failures), upload / delete for local Helm chart packages, and --watch flags that block until the app reaches a terminal state. Use when the user mentions Olares Market, olares-cli market, app store, installing / upgrading / uninstalling / cloning / stopping / resuming / cancelling an Olares app, 'my apps', '我的应用', upload chart, --watch, or asks 'is <app> installed yet' / 'show me my Olares apps'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli market --help\"\r\n---\r\n\r\n# market (App-store v2 + per-user market-backend)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for the profile selection, login, and HTTP 401/403 recovery rules that every command here depends on.**\r\n\r\n## Core concepts\r\n\r\n### Source resolution\r\n\r\nThe market backend serves multiple \"sources\" of charts. The CLI resolves which one to talk to from `-s / --source`, falling back to a default that depends on the verb:\r\n\r\n| Source id    | What it is                                              | Used by (default)              |\r\n|--------------|---------------------------------------------------------|--------------------------------|\r\n| `market.olares` | Public catalog (read-only browse)                    | `list`, `get`, `categories`, `install`, `upgrade`, `clone`, `status` |\r\n| `upload`     | Local source for charts pushed through the SPA's \"Local Sources → Upload\" UI **and** through `market upload` | `upload`, `delete` (hard-coded — see below) |\r\n| `cli`        | Legacy local source for charts uploaded via earlier CLI revisions; still readable via `-s cli` on browse verbs but no longer a write target | read-only (`list`, `status`, ...) |\r\n| `studio`     | Local source for charts produced by Devbox / Studio     | read-only (`list`, `status`, ...) |\r\n\r\nResolution is centralized in [`cli/cmd/ctl/market/common.go`](cli/cmd/ctl/market/common.go):\r\n\r\n- `resolveCatalogSource(opts)` → `opts.Source` if set, else `defaultCatalogSource = \"market.olares\"`.\r\n- `chartUploadSource = \"upload\"` constant — the **only** local source `market upload` and `market delete` ever write to. `-s` is intentionally NOT exposed on those two verbs: pinning the bucket avoids the historical foot-gun where a chart pushed to `cli` was invisible to the SPA's Local Sources tab despite using the same backend.\r\n\r\nWhen `-s` is omitted, every command that DOES accept it prints `Using source: <id>` to stderr so the agent can confirm which backend it hit. `-a / --all-sources` (where supported) bypasses the single-source resolver and asks the backend across every source the user has access to.\r\n\r\n### App lifecycle / state machine\r\n\r\nThe backend tracks two orthogonal axes per app: **`State`** (where the row currently is) and **`OpType`** (which mutation is in flight). The full enum lives in [`framework/app-service/api/app.bytetrade.io/v1alpha1/appmanager_states.go`](framework/app-service/api/app.bytetrade.io/v1alpha1/appmanager_states.go). The CLI groups them into four buckets in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go):\r\n\r\n| Bucket               | Examples                                                        | Meaning                                  |\r\n|----------------------|------------------------------------------------------------------|------------------------------------------|\r\n| Progressing          | `pending`, `installing`, `upgrading`, `uninstalling`, `stopping`, `resuming`, `installingCanceling`, …Canceling | Backend is actively working; keep polling |\r\n| Terminal success     | `running`, `stopped`, `uninstalled`                              | Mutation finished cleanly                |\r\n| Terminal failure     | `installFailed`, `upgradeFailed`, `uninstallFailed`, `stopFailed`, `resumeFailed` | Mutation finished with a hard error      |\r\n| Canceled / cancel-failed | `installingCanceled`, `upgradingCanceled`, `resumingCanceled`, `installingCancelFailed`, `upgradingCancelFailed`, `resumingCancelFailed` | A `cancel` request landed (or failed)    |\r\n\r\nThe CLI maps each verb to the subset of buckets it considers terminal — see the `--watch` section below.\r\n\r\n### `OpType` vs `State` (race-safety)\r\n\r\nThe same `State` can mean different things depending on which mutation is in flight. Concrete example: an `upgrade` issued against an app already in `running` will return `state=running, opType=running` for one or two ticks before the backend flips to `state=upgrading, opType=upgrade`. A naive watcher would declare success at tick zero.\r\n\r\nThe fix lives in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go) (`waitForTerminal` + `watchTarget.matchOpType`): for mutating verbs the watcher refuses to accept any \"success\" classification until either:\r\n\r\n1. the row's `OpType` matches the op the CLI just issued, **or**\r\n2. the row disappears entirely (only legal for `uninstall` / `status`).\r\n\r\n`cancel` and `status` deliberately set `matchOpType=false` because they are op-agnostic by design.\r\n\r\n## Authentication transport\r\n\r\nEvery request goes through a factory-injected `*http.Client` whose `RoundTripper` (a `refreshingTransport` — see [`cli/pkg/cmdutil/factory.go`](cli/pkg/cmdutil/factory.go)) **injects `X-Authorization` and auto-rotates expired tokens transparently**. The `MarketClient` itself is purely an HTTP wrapper — it never sees the access_token.\r\n\r\n- Base URL: `<rp.MarketURL>/app-store/api/v2` — built in [`cli/cmd/ctl/market/client.go`](cli/cmd/ctl/market/client.go) (`NewMarketClient` / `apiPrefix`).\r\n- Auth header: `X-Authorization: <access_token>` (NOT `Authorization: Bearer …`). The transport handles header injection; the client code does not call `req.Header.Set(\"X-Authorization\", …)` anywhere.\r\n- `MarketURL` is derived from the Olares ID (`https://market.<localPrefix><terminusName>`) and surfaced through [`cli/pkg/credential/types.go`](cli/pkg/credential/types.go) (`ResolvedProfile.MarketURL`).\r\n- Two clients in `MarketClient`: `httpClient` (30s timeout, JSON verbs) and `uploadClient` (no timeout, multipart chart pushes). Both share the SAME `refreshingTransport` instance via the Factory, so a refresh triggered on one is immediately visible on the other.\r\n- 401/403 reaches `executeRequest` only when the transport already auto-refreshed and STILL got rejected (consistent server-side rejection); it's reformatted via `reformatMarketAuthErr`.\r\n- When `/api/refresh` itself fails, `executeRequest` surfaces the typed `*credential.ErrTokenInvalidated` / `*credential.ErrNotLoggedIn` directly so the user sees the canonical \"run profile login\" CTA without `request failed: Get \"https://...\":` noise. **Recovery rules → [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) \"Automatic token refresh\".**\r\n\r\n> All `market` verbs use replayable request bodies (JSON in `*bytes.Reader`, multipart in `*bytes.Buffer`), so they all benefit from the transport's reactive 401-retry path. There is no streaming-upload edge case in the market tree — chart pushes are fully buffered before the request goes out.\r\n\r\n## Command cheatsheet\r\n\r\nAll verbs live under `olares-cli market <verb>`. Common flags:\r\n\r\n- `-s / --source <id>` — pin to a single source (`market.olares` for the\r\n  remote catalog; `cli` / `upload` / `studio` for local helm-chart\r\n  sources). Auto-selected when omitted.\r\n- `-a / --all-sources` — span every source the user has (where supported).\r\n- `-o / --output {table,json}` — output format. Default `table`. `json`\r\n  emits a parseable payload (and suppresses any informational stderr\r\n  hints — see `-q` for the related \"no output at all\" toggle).\r\n- `-q / --quiet` — suppress all stdout/stderr output; the exit code\r\n  still propagates the operation result. Use in scripts that only need\r\n  the success/failure signal (e.g. `olares-cli market list -q && ...`).\r\n- `--no-headers` — omit table column headers (and the trailing\r\n  \"Total: N …\" summary). **Exposed only on `list` and `categories`**\r\n  — the row-oriented browse verbs. Deliberately NOT on:\r\n  - `get` — renders a key:value detail layout (one record, fields\r\n    like `Name: …`, `Title: …`), closer to `kubectl describe` than\r\n    to a row-oriented table. There are no \"headers\" separable from\r\n    values; for machine-readable output of `get` use `-o json`.\r\n  - `status` — always prints headers in table mode (its rows are a\r\n    runtime probe, not a stable scrape target; pipe through `awk`\r\n    on column index, or use `-o json`).\r\n  - Mutating verbs (`install` / `upgrade` / `stop` / `resume` /\r\n    `uninstall` / `cancel` / `clone` / `upload` / `delete`) — they\r\n    don't render tables, so `--no-headers` on them is a no-op\r\n    footgun in scripts that pass it expecting it to apply.\r\n\r\n  Useful for piping a stable table format into `awk` / `cut` /\r\n  `column` etc. without needing JSON. Has no effect in JSON output.\r\n  (No short flag.) Wiring lives in `addNoHeadersFlag` in\r\n  [`cli/cmd/ctl/market/options.go`](cli/cmd/ctl/market/options.go)\r\n  — separate from `addOutputFlags` (which only carries `-o` / `-q`),\r\n  so adding a new browse verb means explicitly opting in.\r\n- Mutating verbs additionally accept `-w / --watch` plus the timing\r\n  knobs (see the [`--watch`](#watch-flag) section).\r\n\r\n**`-o` and `-q` are on EVERY market verb** — read-only and mutating\r\nalike — because every verb has something to print (a table, an\r\n`OperationResult`, a chart-management payload) and benefits from a\r\nquiet mode in scripts. Concretely:\r\n\r\n- `olares-cli market install firefox -o json` → emits a single\r\n  `OperationResult` JSON document (see\r\n  [Lifecycle output shape](#lifecycle-output-shape) below) and\r\n  suppresses the stderr `info` chatter (`Installing 'firefox' ...`).\r\n- `olares-cli market install firefox -q` → no stdout, no stderr,\r\n  exit code reflects backend acceptance (or, when combined with\r\n  `--watch`, terminal success / failure).\r\n- `olares-cli market uninstall firefox -o json -q` is contradictory\r\n  but tolerated — `-q` wins and nothing is printed.\r\n\r\n`-s` (source pin) and `-a` (span all sources) are narrower. Scope by\r\nverb family:\r\n\r\n| Flag             | Read-only browsing                                      | Lifecycle (mutating)                                          | Chart management   |\r\n|------------------|---------------------------------------------------------|---------------------------------------------------------------|--------------------|\r\n| `-s / --source`  | `list`, `categories`, `status`, `get`                   | `install`, `upgrade`, `clone`                                 | `upload`, `delete` |\r\n| `-a / --all-sources` | `list`, `categories`, `status`                      | —                                                             | —                  |\r\n\r\n`-s` is NOT on `uninstall` / `stop` / `resume` / `cancel`: these act\r\non whichever per-user state row matches the app name, regardless of\r\nsource. `-a` is read-only only — the inventory verbs use it to span\r\nsources; mutating an app implicitly targets the source the user is\r\nalready running it from.\r\n\r\n`--no-headers` is the narrowest — only `list` and `categories` (the\r\nrow-oriented browse verbs). `get` deliberately does NOT accept it\r\nbecause its output is a key:value detail layout, not a table — use\r\n`-o json` for scripted access to its fields.\r\n\r\nCombine freely — e.g. `olares-cli market list --mine --no-headers\r\n-o table` for a header-less table you can pipe into shell tools, or\r\n`olares-cli market categories -q` to just check exit status, or\r\n`olares-cli market install firefox --watch -o json | jq '.finalState'`\r\nto read the watcher's verdict.\r\n\r\n### Catalog (read-only)\r\n\r\n```bash\r\nolares-cli market list                          # auto-selected source (usually market.olares)\r\nolares-cli market list -s market.olares         # narrow to a specific source by id\r\nolares-cli market list -s cli                   # browse a local source (cli / upload / studio)\r\nolares-cli market list -c AI                    # filter by category\r\nolares-cli market list -a                       # query every source the user has\r\nolares-cli market list -o json\r\nolares-cli market list --no-headers             # table without column headers (scripting)\r\nolares-cli market list -q                       # no output; exit code only\r\n```\r\n\r\n`-s / --source` works for both catalog browse and `--mine`: it pins the\r\nlisting to a single source id. Valid ids are `market.olares` (the\r\nofficial remote catalog) and the three local-chart sources — `cli`,\r\n`upload`, `studio` — used for locally-uploaded helm charts. Omit `-s`\r\nto fall back to the auto-selected source (catalog browse) or to span\r\nevery source the user has (`--mine`). The id is matched verbatim and\r\nunknown ids silently produce an empty result (the listing prints a\r\n\"no apps in source 'X'\" hint on stderr rather than erroring out), so\r\nwhen you're unsure what's available run `olares-cli market list -a` and\r\nread the SOURCE column to enumerate the user's configured sources.\r\n\r\n```bash\r\nolares-cli market list --mine                   # what apps does this user have right now (all sources by default)\r\nolares-cli market list -m -s cli                # narrow to a single source\r\nolares-cli market list -m -c AI -o json         # category filter still works in mine mode\r\n```\r\n\r\n`list --mine` (alias `-m`) diverts the verb from `/market/data`\r\n(catalog browse) to `/market/state` (per-user state rows) and is the\r\ncanonical \"show me my apps\" listing — the exact same set the Market UI's\r\n\"My Terminus\" tab shows.\r\n\r\n> **\"My apps\" ≠ \"已安装应用 / completed installs only.\"** The Market UI\r\n> shows in-flight install rows (`pending` / `downloading` / `installing`\r\n> + their `*Canceling` / `*CancelFailed` variants), every post-install\r\n> transitional state (`upgrading` / `resuming` / `stopping` /\r\n> `applyingEnv` / `uninstalling`) and every post-install failure\r\n> (`upgradeFailed` / `stopFailed` / `resumeFailed` / `applyEnvFailed` /\r\n> `uninstallFailed`) on My Terminus as well, because they're all \"the\r\n> user's apps\" — the user clicked something and expects to monitor /\r\n> retry / cancel the row. `--mine` matches that mental model. Only the\r\n> 6 SPA-hidden states (see below) drop out.\r\n\r\nDifferences vs catalog browse:\r\n\r\n- **Source scope defaults to every source** — pass `-s` to narrow to one,\r\n  `-a` (or omitting `-s`) keeps the full cross-source view. This matches\r\n  the agent's mental model of \"show me my apps\" without forcing `-a`.\r\n- **State filter mirrors the SPA's \"My Terminus\" filter exactly.** The\r\n  denylist is the SPA's `uninstalledAppStates` set in\r\n  [`apps/packages/app/src/constant/config.ts`](apps/packages/app/src/constant/config.ts)\r\n  (around line 170), which `MarketRemotePage.vue` →\r\n  `appStore.getSourceInstalledApp(sourceId)` calls through\r\n  `uninstalledApp(status)` to decide what shows up under the Market's\r\n  \"My Terminus\" tab. The 6 hidden states are: `pendingCanceled`,\r\n  `downloadingCanceled`, `downloadFailed`, `installFailed`,\r\n  `installingCanceled`, `uninstalled`. Everything else stays — including\r\n  in-flight install rows (`pending`, `downloading`, `installing` plus\r\n  their `*Canceling` / `*CancelFailed` variants), `initializing` /\r\n  `initializingCanceling`, and every post-install transitional /\r\n  failure state (`running`, `stopped`, `stopping`, `stopFailed`,\r\n  `resuming`, `resumeFailed`, `upgrading`, `upgradeFailed`,\r\n  `uninstalling`, `uninstallFailed`, `applyingEnv`, `applyEnvFailed`,\r\n  `*Canceling` / `*Canceled` / `*CancelFailed` siblings).\r\n  - This deliberately does NOT mirror the backend state machine in\r\n    `framework/app-service/pkg/appstate/state_transition.go`: the SPA\r\n    keeps `pending` / `downloading` / `installing` rows visible on My\r\n    Terminus because the user just clicked install and wants to\r\n    see / monitor / cancel the in-progress row, so the CLI must show\r\n    them too for the two views to agree.\r\n  - Use `market status` if you specifically want a runtime-state view.\r\n  - If the SPA changes its `uninstalledAppStates` set, update the\r\n    `notInstalledStates` map in `cli/cmd/ctl/market/types.go` and the\r\n    `TestIsInstalledState` / `TestFetchInstalledAppsMirrorsSpaUninstalledFilter`\r\n    tables in `cli/cmd/ctl/market/list_test.go` together so the two\r\n    listings stay in sync.\r\n- **Output adds a STATE column** and (for JSON) a `state` field.\r\n- **Version is the version on the user's state row**, not the catalog\r\n  latest. It comes from the `version` field at the `AppStateLatest`\r\n  level of `/market/state` (the same field the SPA's `AppStatusLatest`\r\n  interface reads) — the chart the user picked for this row, regardless\r\n  of whether the install / upgrade has completed. If the user installed\r\n  1.0.10 and the marketplace catalog has since moved to 1.2.3, this\r\n  listing will surface 1.0.10 — that is the intended behavior; do not\r\n  \"correct\" it to the catalog version. During an upgrade in flight,\r\n  the row may show the target version while STATE is still `upgrading`,\r\n  which is also intentional. Title and categories ARE best-effort\r\n  enriched from `/market/data`; locally-uploaded charts that have since\r\n  been deleted from their source still surface but may render with\r\n  blank title / categories. Version is left blank only when the state\r\n  row genuinely lacks it (older backends, in-flight rows that haven't\r\n  been bound yet).\r\n- **Clones look up the catalog by `rawAppName`, not their unique\r\n  `name`.** A cloned multi-instance app gets its own per-instance\r\n  identifier (e.g. `windowsefe992`) but the catalog only knows the\r\n  source app (`windows`). The state row carries the source name as\r\n  `rawAppName`; the parser uses it as the catalog lookup key whenever\r\n  non-empty (and falls back to `name` for normal non-clone installs)\r\n  so clones still pick up the source app's title and categories. Source\r\n  of truth for the rule: `framework/app-service/pkg/utils/app/app.go`\r\n  `GetRawAppName`.\r\n\r\n```bash\r\nolares-cli market categories                    # category counts in the auto-selected source\r\nolares-cli market categories -s market.olares   # pin to a single source\r\nolares-cli market categories -a -o json         # every source, JSON output\r\nolares-cli market categories --no-headers       # table without the CATEGORY/APPS header row\r\nolares-cli market categories -q                 # no output; exit code only\r\n```\r\n\r\n```bash\r\nolares-cli market get firefox                   # detailed info, table view\r\nolares-cli market get firefox -o json           # full upstream payload\r\n```\r\n\r\n`get` answers questions like \"is this app cloneable?\" — look at the `cloneable` field in JSON output (see [`cli/cmd/ctl/market/get.go`](cli/cmd/ctl/market/get.go)).\r\n\r\n### Runtime (read-only)\r\n\r\n```bash\r\nolares-cli market status                        # all installed apps in the resolved source\r\nolares-cli market status -s market.olares       # pin the listing to a specific source\r\nolares-cli market status firefox                # one app, with the source-fallback hint\r\nolares-cli market status firefox -a             # search across every source\r\nolares-cli market status firefox --watch        # see the --watch section\r\nolares-cli market status firefox -q             # no output; exit code only\r\n```\r\n\r\n`status <app>` UX rules — implemented in [`cli/cmd/ctl/market/status.go`](cli/cmd/ctl/market/status.go) (`runStatusSingle`):\r\n\r\n- If the row is missing in the resolved source **and** in every other source the user has, the CLI prints `app 'X' is not installed (run 'olares-cli market install X' to install it)`.\r\n- If the row exists but under a different source than the one the user passed, the CLI prints an info hint `App is installed under source 'Y' (not 'X')` and continues to render the row, so the agent does not need to retry blindly.\r\n- `runStatusAll` (no app argument) explicitly rejects `--watch`. Use `status <app> --watch` instead.\r\n\r\n> `market status` (no app) and `market list --mine` overlap but are\r\n> not interchangeable: `status` is the runtime-state-focused view\r\n> (`STATE / OPERATION / PROGRESS`, filters by source by default), while\r\n> `list --mine` is the \"my apps\" inventory view\r\n> (`NAME / TITLE / VERSION / STATE / SOURCE / CATEGORIES`, defaults to\r\n> every source and hides exactly the same rows the Market SPA hides\r\n> from its \"My Terminus\" tab — the 6 `uninstalledAppStates` listed in\r\n> the State filter bullet above). Prefer `list --mine` when the\r\n> user asks \"what apps do I have\" / \"show me my apps\" / \"我的应用\";\r\n> prefer `status` when they want runtime / progress detail. Note that\r\n> \"my apps\" deliberately INCLUDES in-flight installs and failed rows\r\n> (not just `running` ones), since that is what the SPA shows.\r\n\r\n### Lifecycle (mutating, support `--watch`)\r\n\r\n```bash\r\nolares-cli market install firefox --watch\r\nolares-cli market install firefox --version 1.0.11 --env DEBUG=1 --watch\r\nolares-cli market install firefox -o json                # one OperationResult JSON doc; status=\"accepted\"\r\nolares-cli market install firefox --watch -o json        # JSON with finalState/finalOpType populated\r\nolares-cli market install firefox -q                     # silent; exit code only\r\nolares-cli market install firefox --watch -q             # silent + block until terminal\r\nolares-cli market install ollama-webui --watch --watch-timeout 30m       # bump deadline for image-pull-heavy installs\r\nolares-cli market install firefox --watch --watch-interval 1s            # tight polling for fast feedback\r\nolares-cli market install firefox --watch --watch-timeout 5m --watch-interval 1s   # tight CI bounds\r\n```\r\n\r\n```bash\r\nolares-cli market upgrade firefox --version 1.0.12 --watch\r\nolares-cli market upgrade firefox --version 1.0.12 -o json     # accepted payload only\r\nolares-cli market upgrade firefox --version 1.0.12 --watch -o json | jq -r '.finalState'\r\nolares-cli market upgrade firefox --version 1.0.12 --watch --watch-timeout 30m     # slow image pull\r\n```\r\n\r\n> **`upgrade` deliberately does NOT accept `--env`** — mirrors the Market SPA's `upgradeApp({app_name, source, version})` payload (see [`apps/.../components/appcard/InstallButton.vue`](apps/packages/app/src/components/appcard/InstallButton.vue) and [`useAppAction.ts`](apps/packages/app/src/components/appcard/useAppAction.ts)). Existing env values are preserved server-side from the prior install. To change env values, use `olares-cli market env --set KEY=value <app>` (out-of-band) — that's the same flow the SPA exposes via its env-editor dialog. The CLI's `UpgradeApp` wire payload uses a dedicated `UpgradeRequest` type that has no `envs` field, so passing envs accidentally is impossible.\r\n\r\n#### `upgrade` pre-flight gates (parity with SPA `canUpgrade`)\r\n\r\nBefore issuing `PUT /apps/{name}/upgrade`, `runUpgrade` runs a four-gate\r\npre-flight that mirrors the SPA's `canUpgrade(statusLatest, appId,\r\nsourceId)` predicate in [`apps/.../constant/config.ts`](apps/packages/app/src/constant/config.ts). All\r\nfour must pass or the CLI bails locally with a self-contained error\r\n(formatted via `failOp` so `-o json` carries it in the `message` field\r\nand `-q` still surfaces the exit code). The gates and their failure\r\nmodes:\r\n\r\n| Gate | Source of truth | Pass condition | CLI error on failure |\r\n|------|-----------------|----------------|----------------------|\r\n| 1. **Row exists** | `GET /market/state` | `appName` is found via Name **or** RawName (clones included) | `cannot upgrade '<X>': app is not installed (no per-user state row); use 'olares-cli market install <X>' first` |\r\n| 2. **State is upgradable** | `app_state_latest[].status.state` | state ∈ `{running, stopped, stopFailed, upgradeFailed, applyEnvFailed}` (verbatim mirror of SPA's `isUpgradableAppStates`) | `cannot upgrade '<X>' in state '<S>': upgrade is only allowed from running, stopped, stopFailed, upgradeFailed, applyEnvFailed; ...` |\r\n| 3. **Strict semver newer** | `app_state_latest[].version` vs `--version` (or `resolveVersionInSource` latest) | `target > installed` after `Masterminds/semver/v3` strict-parse of both sides | `cannot upgrade '<X>': target version '<T>' is already installed — nothing to do` / `... is older than installed version '<I>'; downgrade via upgrade is rejected` |\r\n| 4. **Not suspended** | `POST /apps` → `apps[0].app_simple_info.app_labels` | labels contain NEITHER `suspend` NOR `remove` (verbatim mirror of SPA's `suspendApp`) | `cannot upgrade '<X>': chart is marked 'suspend' or 'remove' in source '<S>' (the SPA hides the Upgrade button for the same reason); upstream has withdrawn this app` |\r\n\r\nGate behavior nuances worth knowing:\r\n\r\n- **Soft-fail on probe error for gate 4**. If `/apps` errors or returns\r\n  an empty `apps[]`, the preflight surfaces a one-line stderr warning\r\n  (`warning: preflight could not read catalog metadata ...; skipping\r\n  suspend-label check`) and **proceeds** with the upgrade. Same\r\n  philosophy as `shouldAutoCascade` in [`uninstall.go`](cli/cmd/ctl/market/uninstall.go) — the\r\n  backend has the final say, and we don't want a flaky catalog probe\r\n  to block the user when gates 1–3 already passed. Gates 1–3 never\r\n  soft-fail because their inputs come from the same `/market/state`\r\n  response the row lookup already has in hand.\r\n- **Source mismatch is a warning, not a failure**. If the installed\r\n  row's source is `market.test` and the user passes `-s market.olares`,\r\n  the preflight prints a stderr warning and continues — sometimes\r\n  legitimate (chart moved between sources), and the backend can\r\n  reject it cleanly if not.\r\n- **Clones (RawName != Name)** use `RawName` for the gate-4 catalog\r\n  lookup so a clone like `windowsefe992` reads its suspend label from\r\n  the source app `windows`, matching how the SPA renders the upgrade\r\n  button.\r\n- **Mid-flight rows** (state row exists but `version` is empty —\r\n  typical for `pending` / older backends) bail with `cannot upgrade\r\n  '<X>': no version recorded on the state row (mid-flight install or\r\n  older backend) — re-run 'olares-cli market status <X> --watch' until\r\n  the row stabilizes, then retry`.\r\n\r\nThe whole predicate lives in [`cli/cmd/ctl/market/preflight.go`](cli/cmd/ctl/market/preflight.go) and is\r\npinned by [`preflight_test.go`](cli/cmd/ctl/market/preflight_test.go) (`TestIsUpgradableState`,\r\n`TestIsAppSuspended`, `TestPreflightUpgrade`). If the SPA reshuffles\r\n`isUpgradableAppStates` or `suspendApp`, update both the predicate\r\nand its tests in lockstep so the CLI's bar tracks the dialog's bar.\r\n\r\n```bash\r\nolares-cli market uninstall firefox --watch\r\nolares-cli market uninstall firefox --cascade --delete-data --watch   # see Security rules\r\nolares-cli market uninstall ollamav2                                  # auto-cascade for single-user CS apps\r\nolares-cli market uninstall ollamav2 --cascade=false                  # force no cascade on a CS app\r\nolares-cli market uninstall firefox -o json                           # status=\"accepted\"\r\nolares-cli market uninstall firefox --watch -q                        # silent; exit code = 0 iff successfully uninstalled\r\nolares-cli market uninstall firefox --watch --watch-interval 1s       # snappy uninstall completion signal\r\nolares-cli market uninstall ollamav2 --cascade --watch --watch-timeout 30m   # cascade with large shared sub-charts\r\n```\r\n\r\n#### Auto-cascade for CS apps (`uninstall` / `stop`)\r\n\r\n`uninstall` and `stop` both **auto-decide** their `--cascade` default to mirror the Market SPA's `csAppUninstall()` and `csAppStop()` dialogs (both in `apps/.../stores/market/csAppOperation.ts`, dispatched from `appService.ts`). When the user does **not** pass `--cascade`, the CLI:\r\n\r\n1. Probes user count via `GET /api/users/v2` (`fetchUserTotals` in [`cli/cmd/ctl/market/users_helper.go`](cli/cmd/ctl/market/users_helper.go)).\r\n2. If single-user, looks up the app's state row via `/market/state` using `lookupInstalledApp` ([`cli/cmd/ctl/market/preflight.go`](cli/cmd/ctl/market/preflight.go)) — the same helper `preflightUpgrade` uses, so both gates agree on what \"the user's row\" is. The row carries both the canonical `Name` (e.g. `windowsefe992`) AND a `RawName` (the source app, e.g. `windows`). **Match rule is strict on `Name`, NOT `RawName`**: when both the primary `windows` row and clones like `windowsefe992` (which carry `RawName=windows`) are installed, a query for `windows` MUST return the primary, never a clone. The SPA enforces the same contract — clicking \"Upgrade\" / \"Uninstall\" / \"Stop\" on a clone card sends the per-instance name, not the source name. The only fallback is the legacy / malformed case where a row has `Name=\"\"` and `RawName=<appName>` — those still match by RawName because Name is empty (the only way the disambiguation rule could fire). See `TestLookupInstalledAppDisambiguatesPrimaryFromClones` for the pinned semantics.\r\n3. Fetches catalog metadata via `/apps` using **`RawName` when present, falling back to `Name`** for the lookup key. **Clones (where `RawName != Name`) MUST go through `RawName` here** — the catalog (`/apps`) is indexed by source app, NOT by per-instance clone name, and the SPA's `csAppUninstall()` / `csAppStop()` read `AppFullInfo` the same way (keyed by source app under the hood). Using the clone name instead would return an empty `/apps` response, make `isCSV2` answer false, and silently flip `--cascade` off for single-user CS clones — diverging from the SPA. Same RawName-preferred-catalog-key trick is used by `preflightUpgrade` (`preflight.go`) and `fetchInstalledApps` (`list.go`); the three sites must stay in lockstep.\r\n4. Runs `isCSV2(appInfo)` ([`cli/cmd/ctl/market/common.go`](cli/cmd/ctl/market/common.go)) on the result — TRUE only when `app_info.app_entry.apiVersion == \"v2\"` AND `subCharts` is non-empty (verbatim mirror of SPA's `isCSV2()` in `apps/.../constant/constants.ts`).\r\n5. **`--cascade` defaults to `true` iff both conditions hold** (`single-user && isCSV2`). Otherwise default stays `false`. Shared decision helper `shouldAutoCascade` ([`cli/cmd/ctl/market/uninstall.go`](cli/cmd/ctl/market/uninstall.go)) is reused unchanged by `stop` ([`cli/cmd/ctl/market/stop.go`](cli/cmd/ctl/market/stop.go)). The auto-decision's stderr explainer surfaces the catalog key when it differs from the user's app name (e.g. `--cascade auto-enabled: single-user instance + v2 multi-chart app (via source app \"windows\" in source \"market.olares\") ...`), making the decision auditable when a clone is involved.\r\n\r\n`--cascade` passed explicitly (`--cascade`, `--cascade=true`, `--cascade=false`) always wins — cobra's `cmd.Flags().Changed(\"cascade\")` gates the auto-default so the override is non-ambiguous. Any probe error (HTTP failure, malformed catalog) **soft-fails to `--cascade=false`** (the historical default) — the verb always proceeds, the backend's own validation has the final say.\r\n\r\n> **Why mirror SPA here instead of always defaulting `--cascade=false`?** The most common interactive uninstall / stop in Olares is a personal-cloud admin operating on a CS app like `ollamav2` (Ollama with shared GPU server sub-chart). The SPA dialog auto-checks \"also tear down / stop the shared server\" for the single-user-admin case and only surfaces the checkbox in multi-user setups so the admin can opt out. Without auto-cascade the CLI version of the same op leaves the shared sub-chart orphaned (uninstall) or running (stop), which breaks the next install or wastes the GPU. The auto-default puts `olares-cli market uninstall` / `stop` on parity with the dialog UX; explicit `--cascade=false` is the documented escape hatch.\r\n\r\n> **Subtle SPA difference, same CLI surface.** `csAppUninstall` always shows a dialog (it has a \"delete data\" checkbox to confirm) and the cascade checkbox visibility is gated by `csApp && isAdmin && users > 1`; `csAppStop` skips the dialog entirely for non-CS / non-admin (returns `all=false`) AND for single-user CS (returns `all=true`), only popping for the CS-admin-multi-user case. The CLI collapses both flows to the same `single-user && isCSV2 ⇒ default true` rule because it is non-interactive — there is no dialog to pop in the multi-user branch, so the CLI keeps the conservative `false` default there and asks the user to opt in with `--cascade`.\r\n\r\n> **\"CS\" here is the SPA's `isCSV2` — v2 multi-chart — NOT `clusterScoped`.** They are independent concepts: `clusterScoped` controls install permissions (admin-only); `isCSV2` controls cascade semantics. Updating only one when the SPA changes its predicate will silently desync the CLI from the dialog.\r\n\r\n> **The `--cascade` JSON wire field is `all`, not `cascade`** — same field the SPA's DELETE / `/apps/{name}/stop` payloads use. See `UninstallRequest` in [`cli/cmd/ctl/market/types.go`](cli/cmd/ctl/market/types.go) and `StopApp` in [`cli/cmd/ctl/market/client.go`](cli/cmd/ctl/market/client.go).\r\n\r\n```bash\r\nolares-cli market clone firefox --title \"Firefox (work)\" --watch\r\nolares-cli market clone firefox --title \"FF\" --entrance-title firefox=Work --watch\r\nolares-cli market clone firefox --title \"FF\" --watch -o json   # JSON includes targetApp (the backend-assigned clone name)\r\nolares-cli market clone firefox --title \"FF\" --watch -q        # silent; exit code only\r\nolares-cli market clone firefox --title \"FF\" --watch --watch-timeout 30m   # large chart with slow first-time pull\r\n```\r\n\r\n`clone` quirks (cite [`cli/cmd/ctl/market/clone.go`](cli/cmd/ctl/market/clone.go)):\r\n\r\n- `--title` is required and capped at 30 characters.\r\n- The clone target name is decided by the backend; the CLI tracks it in `OperationResult.TargetApp` and falls back to the source app name only if the backend never reports one. **Read `targetApp` from the JSON output** — it's the only way to discover the unique per-instance identifier (e.g. `windowsefe992`) the backend just minted.\r\n- Only multi-instance apps are cloneable — confirm with `market get <app>` (`cloneable: true`) before running.\r\n\r\n```bash\r\nolares-cli market stop firefox                          # fire-and-forget; returns once backend accepts (status=\"accepted\")\r\nolares-cli market resume firefox                        # fire-and-forget; returns once backend accepts\r\nolares-cli market stop firefox --watch\r\nolares-cli market stop firefox --cascade --watch        # force cascade (also stop dependents)\r\nolares-cli market stop ollamav2 --watch                 # auto-cascade for single-user CS apps (see SPA-parity note below)\r\nolares-cli market stop ollamav2 --cascade=false --watch # force NO cascade on a CS app\r\nolares-cli market resume firefox --watch\r\nolares-cli market stop firefox --watch -o json          # JSON; idempotent on already-stopped (see Idempotent shortcut)\r\nolares-cli market stop firefox --watch -q               # silent\r\nolares-cli market stop firefox --watch --watch-interval 1s --watch-timeout 2m   # snappy + tight cap\r\nolares-cli market resume firefox --watch --watch-interval 1s --watch-timeout 2m\r\n```\r\n\r\n> **Bare invocation semantics** (`market stop firefox` / `market resume firefox` with **no other flags**): the CLI fires one `POST /apps/{app}/stop` (or `/resume`) and returns the moment the backend accepts the request — the row may still be in `stopping` / `resuming` when the CLI exits. The exit code reflects acceptance only (HTTP 2xx, request well-formed), **not** terminal landing. Use `--watch` if you need the CLI to block until the row reaches `stopped` / `running`, or re-attach after the fact with `olares-cli market status firefox --watch`.\r\n\r\n`stop` shares the same auto-cascade rule as `uninstall` — see [Auto-cascade for CS apps](#auto-cascade-for-cs-apps-uninstall--stop) above. TL;DR: omit `--cascade` and the CLI checks `single-user && isCSV2`; if both hold, `--cascade` defaults to `true` and prints a one-line stderr explainer; otherwise stays `false`. The on-wire field is `all` (matches the SPA's stop payload), passed through `MarketClient.StopApp` ([`cli/cmd/ctl/market/client.go`](cli/cmd/ctl/market/client.go)).\r\n\r\n```bash\r\nolares-cli market cancel firefox --watch                 # cancel the in-flight op\r\nolares-cli market cancel firefox --watch -o json         # JSON; finalState is one of the *Canceled states\r\nolares-cli market cancel firefox --watch -q              # silent\r\nolares-cli market cancel firefox --watch --watch-interval 1s --watch-timeout 2m   # cancel is usually fast\r\n```\r\n\r\n`cancel` always sets `matchOpType=false` (it is itself op-agnostic) — see [`cli/cmd/ctl/market/cancel.go`](cli/cmd/ctl/market/cancel.go).\r\n\r\n### Local sources (chart push)\r\n\r\n`upload` and `delete` **always** target the SPA's \"Local Sources → Upload\" bucket (`chartUploadSource = \"upload\"` constant in [`cli/cmd/ctl/market/common.go`](cli/cmd/ctl/market/common.go)). Neither verb exposes `-s / --source` — passing `-s` to either is a flag-parse error from cobra. Rationale: the SPA's Local Sources tab manages a single `upload` bucket; offering the CLI's old `cli` / `studio` write targets meant a CLI-pushed chart could be invisible to the SPA, which broke every \"upload then install via SPA\" flow. Pinning the source eliminates that desync.\r\n\r\n```bash\r\nolares-cli market upload ./myapp-1.0.0.tgz                # writes to source 'upload' (the only valid target)\r\nolares-cli market upload ./charts/                        # all .tgz / .tar.gz under dir\r\nolares-cli market upload ./myapp-1.0.0.tgz -o json        # JSON: per-file status=success/failed, includes filename + message\r\nolares-cli market upload ./charts/ -q                     # silent; exit code aggregates per-file results\r\n```\r\n\r\n```bash\r\nolares-cli market delete myapp                            # latest version in source 'upload'\r\nolares-cli market delete myapp --version 1.0.0\r\nolares-cli market delete myapp -o json                    # OperationResult-shaped: status=success on accepted delete\r\nolares-cli market delete myapp -q                         # silent\r\n```\r\n\r\n`upload` does not run a chart — `install <app> -s upload` does (note the\r\nnew install source: previously `cli`, now `upload` to match the bucket\r\nthe CLI writes to). Unlike the lifecycle verbs, `upload` returns\r\n`status=\"success\"` immediately on accepted upload (there is no async\r\nchart-store reconciliation to wait for) and `delete` does the same on\r\naccepted delete. Neither supports `--watch`.\r\n\r\n> **Migration note for existing scripts.** Anything passing `-s cli` /\r\n> `-s studio` to `market upload` or `market delete` will now fail with\r\n> `unknown flag: -s`. Drop the flag — the target is implicit. Charts\r\n> that were previously uploaded to `cli` via older CLI revisions can\r\n> still be **read** with `market list -s cli` but must be re-uploaded\r\n> through the new path (which lands them in `upload`) for the SPA's\r\n> Local Sources tab to see them.\r\n\r\n<a id=\"lifecycle-output-shape\"></a>\r\n### Lifecycle output shape (`-o json` / `-q`)\r\n\r\nEvery mutating verb (`install`, `upgrade`, `uninstall`, `clone`, `stop`,\r\n`resume`, `cancel`, `upload`, `delete`) emits exactly **one** JSON\r\ndocument under `-o json` and **nothing** under `-q` (exit code carries\r\nthe signal). The struct is `OperationResult` in\r\n[`cli/cmd/ctl/market/types.go`](cli/cmd/ctl/market/types.go):\r\n\r\n```json\r\n{\r\n  \"app\": \"firefox\",\r\n  \"operation\": \"install\",\r\n  \"status\": \"accepted\",\r\n  \"message\": \"install requested for version 1.0.11\",\r\n  \"source\": \"market.olares\",\r\n  \"version\": \"1.0.11\",\r\n  \"user\": \"guotest458@olares.com\"\r\n}\r\n```\r\n\r\nThe `status` field transitions through:\r\n\r\n| Mode                         | `status` value                          | `finalState` / `finalOpType` | Exit code |\r\n|------------------------------|------------------------------------------|------------------------------|-----------|\r\n| Without `--watch` (accepted) | `\"accepted\"`                             | omitted                      | 0         |\r\n| `--watch` reaches success    | `\"success\"`                              | populated                    | 0         |\r\n| `--watch` reaches failure    | `\"failed\"`                               | populated (terminal state)   | non-zero  |\r\n| `--watch` times out          | `\"failed\"` (with timeout message)        | last-seen state              | non-zero  |\r\n| Request rejected pre-flight  | `\"failed\"` (HTTP error or validation)    | omitted                      | non-zero  |\r\n| `upload` / `delete`          | `\"success\"` (no async lifecycle to wait) | n/a                          | 0         |\r\n\r\n`finalState` / `finalOpType` carry the `omitempty` JSON tag so non-watch\r\ninvocations stay byte-identical to the pre-watch CLI release —\r\nexisting scripts that parse `OperationResult` continue to work\r\nunchanged.\r\n\r\nUseful one-liners:\r\n\r\n```bash\r\n# Read the final landing state (only present under --watch):\r\nolares-cli market install firefox --watch -o json | jq -r '.finalState'\r\n\r\n# Discover a clone's backend-assigned name:\r\nolares-cli market clone firefox --title \"FF\" --watch -o json | jq -r '.targetApp'\r\n\r\n# Quiet mode purely for CI gating:\r\nif olares-cli market install firefox --watch -q; then\r\n    echo \"install ok\"\r\nfi\r\n```\r\n\r\n`-q` ALSO swallows the stderr `info` lines that lifecycle verbs print\r\nbetween transitions (`Installing 'firefox' ...`, `[firefox] state=running\r\nop=install ...`); `-o json` swallows only `info` and reroutes the final\r\nresult to stdout as JSON.\r\n\r\n<a id=\"watch-flag\"></a>\r\n## `--watch` (block until terminal state)\r\n\r\nThe synchronous CLI experience: lifecycle verbs return immediately when the backend accepts the request, but the actual mutation is asynchronous. `--watch` polls the same backend the SPA polls and only returns once the row reaches a terminal state (or the watcher gives up). Implementation lives in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go) (`waitForTerminal`, `runWithWatch`, `newWatchTarget`).\r\n\r\n### Flags\r\n\r\nDefined in [`cli/cmd/ctl/market/options.go`](cli/cmd/ctl/market/options.go) (`addWatchFlags`):\r\n\r\n- **`-w / --watch`** — opt-in. Default **off**. When off, the verb prints\r\n  `OperationResult{status:\"accepted\"}` and returns immediately; the\r\n  backend lifecycle still runs asynchronously and the user must follow\r\n  up with `status --watch` (see [recovery](#status--watch-op-agnostic-recovery))\r\n  to confirm landing.\r\n- **`--watch-timeout`** *(duration; default `15m`)* — total deadline\r\n  for the watch loop. If the row hasn't reached a terminal state by\r\n  this point, the watcher exits with `<op> '<app>' watch timed out\r\n  (last state: <S>, op: <O>)` and the process returns non-zero. The\r\n  mutation **is not canceled** on timeout (re-attach with `status\r\n  --watch`). Accepts Go-style durations: `90s`, `5m`, `30m`, `1h`,\r\n  `2h30m`. **No effect without `--watch`.** Pick by op cost: install\r\n  / upgrade of image-pull-heavy charts on slow links → `30m`–`1h`;\r\n  stop / resume / cancel → `2m`–`5m` is plenty; uninstall → default\r\n  `15m` is usually fine.\r\n- **`--watch-interval`** *(duration; default `2s`)* — polling cadence\r\n  for `GET /market/state`. The watcher fetches one snapshot per\r\n  interval, classifies it (see [Per-op terminal sets](#per-op-terminal-sets))\r\n  and either decides terminal or sleeps another `--watch-interval`.\r\n  Lower values (`500ms`, `1s`) give snappier CI feedback at the cost\r\n  of more backend load; raise to `5s`–`10s` on slow / metered networks\r\n  or when running many parallel watchers. **No effect without\r\n  `--watch`.** Note `--watch-interval` is **wall-clock**, not \"tries\r\n  to reach terminal that many times\" — the watcher quits the moment\r\n  the deadline computed from `--watch-timeout` passes regardless of\r\n  how many polls fit.\r\n\r\n#### Tuning quick reference\r\n\r\n| Scenario                                                  | `--watch-interval` | `--watch-timeout` | Why                                                                                |\r\n|-----------------------------------------------------------|--------------------|-------------------|------------------------------------------------------------------------------------|\r\n| Default (interactive shell)                               | `2s`               | `15m`             | Built-in defaults; covers most installs on a normal home cluster.                  |\r\n| Tight CI / e2e feedback                                   | `1s`               | `5m`              | Faster pickup of terminal state; cap shorter so failed tests don't hang a job.     |\r\n| Image-pull-heavy install (Stable Diffusion, Ollama, ...)  | `2s`–`5s`          | `30m`–`1h`        | Pulls can dominate; default 15m may timeout right before `running`.                |\r\n| Slow / metered network                                    | `5s`–`10s`         | `30m`             | Cuts polling chatter, keeps deadline comfortable.                                  |\r\n| Stop / resume / cancel                                    | `1s`               | `2m`              | Terminal state arrives in seconds; tight bounds catch real failures fast.          |\r\n| Uninstall (cascade-heavy, sub-charts to tear down)        | `2s`               | `15m`             | Cascade work can serialize; default is OK, raise timeout if shared chart is large. |\r\n\r\n### Per-op terminal sets\r\n\r\n| Op           | Success                                      | Failure                                                  | `matchOpType` | `absentMeansSuccess` | `acceptInitialAbsent` | `idempotentSuccess` |\r\n|--------------|----------------------------------------------|----------------------------------------------------------|---------------|----------------------|-----------------------|---------------------|\r\n| `install`    | `running`                                    | `*Failed` ∪ `*Canceled`                                  | true          | false                | false                 | false               |\r\n| `clone`      | `running`                                    | `*Failed` ∪ `*Canceled`                                  | true          | false                | false                 | false               |\r\n| `upgrade`    | `running`                                    | `upgradeFailed` ∪ `*Canceled`                            | true          | false                | false                 | false               |\r\n| `uninstall`  | `uninstalled` (or row absent)                | `uninstallFailed`                                        | true          | true                 | **true**              | false               |\r\n| `stop`       | `stopped`                                    | `stopFailed`                                             | true          | false                | false                 | **true**            |\r\n| `resume`     | `running`                                    | `resumeFailed` / `resumingCanceled` / `resumingCancelFailed` | true       | false                | false                 | **true**            |\r\n| `cancel`     | `*Canceled` ∪ `*Failed` ∪ `running` / `stopped` / `uninstalled` (or row absent) | `*CancelFailed`                       | **false**     | **true**             | false                 | false               |\r\n| `status`     | `running` / `stopped` / `uninstalled` / `*Canceled` | `*Failed` / `*CancelFailed`                       | **false**     | true                 | false                 | n/a (op-agnostic)   |\r\n\r\nThe `*` columns expand to the matching state-set constants (`operationFailedStates`, `cancelFailedStates`, `canceledStates`) defined in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go).\r\n\r\n### Tick-zero absent shortcut (`uninstall`)\r\n\r\n`uninstall` is the only verb where the row being **absent from the very first poll** counts as terminal success. Other `absentMeansSuccess` verbs (currently just `status --watch`) require having seen the row at least once before treating its disappearance as success.\r\n\r\nThe driving scenario: multi-user CS app (e.g. `ollamav2` — v2 multi-chart with shared sub-charts).\r\n\r\n1. `olares-cli market uninstall ollamav2 --watch` (no `--cascade`, multi-user default) clears the per-user row; the watch sees `uninstalling → row gone` and exits cleanly via the `seen-then-absent` path.\r\n2. `olares-cli market uninstall ollamav2 --cascade --watch` re-runs to tear down the shared sub-charts. The backend accepts the DELETE, but the user's per-user row was already cleared in step 1 and does **not** re-appear in `/market/state` for the cascade-only pass. Without the tick-zero shortcut, the `seen` gate is never satisfied and the watcher would hang until `--watch-timeout` (~15m) and then exit with a timeout error.\r\n\r\n`uninstall` therefore sets `acceptInitialAbsent = true` in `newWatchTarget`. Inside `waitForTerminal` the absent branch accepts either:\r\n\r\n- `absentMeansSuccess ∧ seen` — canonical \"we saw the row, then the backend pruned it\" (covers both step-1 above and `status --watch` while an uninstall is in flight); **or**\r\n- `absentMeansSuccess ∧ acceptInitialAbsent` (uninstall only) — tick-zero absence, covering step-2 above plus `uninstall` on an already-uninstalled app.\r\n\r\n`status --watch` deliberately does **not** opt in: its production entry point (`runStatusSingle` in [`cli/cmd/ctl/market/status.go`](cli/cmd/ctl/market/status.go)) fetches the initial row before invoking `waitForTerminal`, so \"first-poll absent\" there would only ever mean \"row just disappeared between the prefetch and our first poll\" — already handled by the `seen` path. The classifier still independently refuses the tick-zero shortcut for `watchStatus` to keep that invariant intact even if a future cal\n\nFile v1.3.0:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-market\",\n  \"version\": \"1.3.0\",\n  \"publishedAt\": 1779960486311\n}\n\nFile v1.3.0:skill-card.md\n\n## Description: <br>\nHelps agents browse, install, upgrade, uninstall, clone, stop, resume, cancel, upload, delete, and monitor Olares Market apps through olares-cli market. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[pengpeng](https://clawhub.ai/user/pengpeng) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nOlares users and administrators use this skill when they want an agent to inspect the app catalog or manage their own Olares apps through olares-cli market, including long-running installs and upgrades. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: Mutating market commands can install, upgrade, stop, resume, uninstall, cancel, clone, upload, or delete apps and local charts. <br>\nMitigation: Confirm the target app, source, and expected impact before allowing the agent to run mutating commands; use read-only list, get, categories, and status commands when inspection is enough. <br>\nRisk: Destructive options such as uninstall --delete-data and cascade stop or uninstall can remove user data or affect dependent charts. <br>\nMitigation: Require explicit user confirmation for --delete-data, --cascade, and chart delete operations, and state the irreversible or fan-out impact before execution. <br>\nRisk: The skill depends on Olares profile credentials and sensitive access tokens managed by olares-cli. <br>\nMitigation: Do not print, paste, or script access tokens; rely on the CLI profile login and token refresh flow. <br>\nRisk: Cancel and watch outcomes can be misunderstood because cancel is only a request and watch timeout or interruption does not stop cluster-side work. <br>\nMitigation: Re-check landed state with status or status --watch before reporting a final outcome, especially after cancel, timeout, or Ctrl-C. <br>\n\n\n## Reference(s): <br>\n- [ClawHub release page](https://clawhub.ai/pengpeng/olares-market) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>\n**Output Format:** [Markdown guidance with shell command examples] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [May include JSON-output flags and status/watch recommendations for olares-cli market workflows.] <br>\n\n## Skill Version(s): <br>\n1.3.0 (source: frontmatter and server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.1.0: 2 files, 7889 bytes\n\nFiles: SKILL.md (22979b), _meta.json (132b)\n\nFile v1.1.0:SKILL.md\n\n---\r\nname: olares-market\r\nversion: 1.1.0\r\ndescription: \"olares-cli market command tree against the per-user Market app-store v2 API: list / get / categories for catalog browsing; install / uninstall / upgrade / clone / cancel / stop / resume for lifecycle; status for runtime state; upload / delete for local chart sources (cli / upload / studio); --watch / --watch-timeout / --watch-interval to block until terminal state. Covers source resolution (market.olares catalog vs local sources), the install/upgrade/uninstall/stop/resume/cancel state machine, OpType gating for race-safe watching, the op-agnostic status --watch recovery path, and JSON shape additions (finalState/finalOpType). Use whenever the user mentions market / app store / install / upgrade / uninstall / clone / stop / resume / cancel / status / upload chart / app state / running / installFailed / --watch, asks 'is firefox installed yet', or sees errors like 'app X is not installed', 'watch timed out', 'watch canceled by user', '--watch requires an app name'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli market --help\"\r\n---\r\n\r\n# market (App-store v2 + per-user market-backend)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for the profile selection, login, and HTTP 401/403 recovery rules that every command here depends on.**\r\n\r\n## Core concepts\r\n\r\n### Source resolution\r\n\r\nThe market backend serves multiple \"sources\" of charts. The CLI resolves which one to talk to from `-s / --source`, falling back to a default that depends on the verb:\r\n\r\n| Source id    | What it is                                              | Used by (default)              |\r\n|--------------|---------------------------------------------------------|--------------------------------|\r\n| `market.olares` | Public catalog (read-only browse)                    | `list`, `get`, `categories`, `install`, `upgrade`, `clone`, `status` |\r\n| `cli`        | Local source for charts uploaded via this CLI           | `upload`, `delete` (default)   |\r\n| `upload`     | Local source for charts pushed through the SPA's \"upload\" UI | `upload`, `delete`        |\r\n| `studio`     | Local source for charts produced by Devbox / Studio     | `upload`, `delete`             |\r\n\r\nResolution is centralized in [`cli/cmd/ctl/market/common.go`](cli/cmd/ctl/market/common.go):\r\n\r\n- `resolveCatalogSource(opts)` → `opts.Source` if set, else `defaultCatalogSource = \"market.olares\"`.\r\n- `resolveLocalSource(opts)` → `opts.Source` if set, else `defaultLocalSource = \"cli\"`.\r\n- `validateLocalSource(s)` rejects anything outside `localSources = {\"upload\", \"studio\", \"cli\"}`.\r\n\r\nWhen `-s` is omitted, every command prints `Using source: <id>` to stderr so the agent can confirm which backend it hit. `-a / --all-sources` (where supported) bypasses the single-source resolver and asks the backend across every source the user has access to.\r\n\r\n### App lifecycle / state machine\r\n\r\nThe backend tracks two orthogonal axes per app: **`State`** (where the row currently is) and **`OpType`** (which mutation is in flight). The full enum lives in [`framework/app-service/api/app.bytetrade.io/v1alpha1/appmanager_states.go`](framework/app-service/api/app.bytetrade.io/v1alpha1/appmanager_states.go). The CLI groups them into four buckets in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go):\r\n\r\n| Bucket               | Examples                                                        | Meaning                                  |\r\n|----------------------|------------------------------------------------------------------|------------------------------------------|\r\n| Progressing          | `pending`, `installing`, `upgrading`, `uninstalling`, `stopping`, `resuming`, `installingCanceling`, …Canceling | Backend is actively working; keep polling |\r\n| Terminal success     | `running`, `stopped`, `uninstalled`                              | Mutation finished cleanly                |\r\n| Terminal failure     | `installFailed`, `upgradeFailed`, `uninstallFailed`, `stopFailed`, `resumeFailed` | Mutation finished with a hard error      |\r\n| Canceled / cancel-failed | `installingCanceled`, `upgradingCanceled`, `resumingCanceled`, `installingCancelFailed`, `upgradingCancelFailed`, `resumingCancelFailed` | A `cancel` request landed (or failed)    |\r\n\r\nThe CLI maps each verb to the subset of buckets it considers terminal — see the `--watch` section below.\r\n\r\n### `OpType` vs `State` (race-safety)\r\n\r\nThe same `State` can mean different things depending on which mutation is in flight. Concrete example: an `upgrade` issued against an app already in `running` will return `state=running, opType=running` for one or two ticks before the backend flips to `state=upgrading, opType=upgrade`. A naive watcher would declare success at tick zero.\r\n\r\nThe fix lives in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go) (`waitForTerminal` + `watchTarget.matchOpType`): for mutating verbs the watcher refuses to accept any \"success\" classification until either:\r\n\r\n1. the row's `OpType` matches the op the CLI just issued, **or**\r\n2. the row disappears entirely (only legal for `uninstall` / `status`).\r\n\r\n`cancel` and `status` deliberately set `matchOpType=false` because they are op-agnostic by design.\r\n\r\n## Authentication transport\r\n\r\nEvery request goes through a factory-injected `*http.Client` whose `RoundTripper` (a `refreshingTransport` — see [`cli/pkg/cmdutil/factory.go`](cli/pkg/cmdutil/factory.go)) **injects `X-Authorization` and auto-rotates expired tokens transparently**. The `MarketClient` itself is purely an HTTP wrapper — it never sees the access_token.\r\n\r\n- Base URL: `<rp.MarketURL>/app-store/api/v2` — built in [`cli/cmd/ctl/market/client.go`](cli/cmd/ctl/market/client.go) (`NewMarketClient` / `apiPrefix`).\r\n- Auth header: `X-Authorization: <access_token>` (NOT `Authorization: Bearer …`). The transport handles header injection; the client code does not call `req.Header.Set(\"X-Authorization\", …)` anywhere.\r\n- `MarketURL` is derived from the Olares ID (`https://market.<localPrefix><terminusName>`) and surfaced through [`cli/pkg/credential/types.go`](cli/pkg/credential/types.go) (`ResolvedProfile.MarketURL`).\r\n- Two clients in `MarketClient`: `httpClient` (30s timeout, JSON verbs) and `uploadClient` (no timeout, multipart chart pushes). Both share the SAME `refreshingTransport` instance via the Factory, so a refresh triggered on one is immediately visible on the other.\r\n- 401/403 reaches `executeRequest` only when the transport already auto-refreshed and STILL got rejected (consistent server-side rejection); it's reformatted via `reformatMarketAuthErr`.\r\n- When `/api/refresh` itself fails, `executeRequest` surfaces the typed `*credential.ErrTokenInvalidated` / `*credential.ErrNotLoggedIn` directly so the user sees the canonical \"run profile login\" CTA without `request failed: Get \"https://...\":` noise. **Recovery rules → [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) \"Automatic token refresh\".**\r\n\r\n> All `market` verbs use replayable request bodies (JSON in `*bytes.Reader`, multipart in `*bytes.Buffer`), so they all benefit from the transport's reactive 401-retry path. There is no streaming-upload edge case in the market tree — chart pushes are fully buffered before the request goes out.\r\n\r\n## Command cheatsheet\r\n\r\nAll verbs live under `olares-cli market <verb>`. Common flags: `-s / --source` (single source), `-a / --all-sources` (where supported), `-o / --output {table,json}` (default `table`), `-q / --quiet` (suppress info on stderr; exit code still propagates). Mutating verbs additionally accept `-w / --watch` plus the timing knobs (see the [`--watch`](#watch-flag) section).\r\n\r\n### Catalog (read-only)\r\n\r\n```bash\r\nolares-cli market list                          # default source: market.olares\r\nolares-cli market list -c AI                    # filter by category\r\nolares-cli market list -a                       # query every source the user has\r\nolares-cli market list -o json\r\n```\r\n\r\n```bash\r\nolares-cli market categories                    # category counts\r\nolares-cli market categories -a -o json\r\n```\r\n\r\n```bash\r\nolares-cli market get firefox                   # detailed info, table view\r\nolares-cli market get firefox -o json           # full upstream payload\r\n```\r\n\r\n`get` answers questions like \"is this app cloneable?\" — look at the `cloneable` field in JSON output (see [`cli/cmd/ctl/market/get.go`](cli/cmd/ctl/market/get.go)).\r\n\r\n### Runtime (read-only)\r\n\r\n```bash\r\nolares-cli market status                        # all installed apps in the resolved source\r\nolares-cli market status firefox                # one app, with the source-fallback hint\r\nolares-cli market status firefox -a             # search across every source\r\nolares-cli market status firefox --watch        # see the --watch section\r\n```\r\n\r\n`status <app>` UX rules — implemented in [`cli/cmd/ctl/market/status.go`](cli/cmd/ctl/market/status.go) (`runStatusSingle`):\r\n\r\n- If the row is missing in the resolved source **and** in every other source the user has, the CLI prints `app 'X' is not installed (run 'olares-cli market install X' to install it)`.\r\n- If the row exists but under a different source than the one the user passed, the CLI prints an info hint `App is installed under source 'Y' (not 'X')` and continues to render the row, so the agent does not need to retry blindly.\r\n- `runStatusAll` (no app argument) explicitly rejects `--watch`. Use `status <app> --watch` instead.\r\n\r\n### Lifecycle (mutating, support `--watch`)\r\n\r\n```bash\r\nolares-cli market install firefox --watch\r\nolares-cli market install firefox --version 1.0.11 --env DEBUG=1 --watch\r\n```\r\n\r\n```bash\r\nolares-cli market upgrade firefox --version 1.0.12 --watch\r\n```\r\n\r\n```bash\r\nolares-cli market uninstall firefox --watch\r\nolares-cli market uninstall firefox --cascade --delete-data --watch   # see Security rules\r\n```\r\n\r\n```bash\r\nolares-cli market clone firefox --title \"Firefox (work)\" --watch\r\nolares-cli market clone firefox --title \"FF\" --entrance-title firefox=Work --watch\r\n```\r\n\r\n`clone` quirks (cite [`cli/cmd/ctl/market/clone.go`](cli/cmd/ctl/market/clone.go)):\r\n\r\n- `--title` is required and capped at 30 characters.\r\n- The clone target name is decided by the backend; the CLI tracks it in `OperationResult.TargetApp` and falls back to the source app name only if the backend never reports one.\r\n- Only multi-instance apps are cloneable — confirm with `market get <app>` (`cloneable: true`) before running.\r\n\r\n```bash\r\nolares-cli market stop firefox --watch\r\nolares-cli market stop firefox --cascade --watch        # also stop dependents\r\nolares-cli market resume firefox --watch\r\n```\r\n\r\n```bash\r\nolares-cli market cancel firefox --watch                 # cancel the in-flight op\r\n```\r\n\r\n`cancel` always sets `matchOpType=false` (it is itself op-agnostic) — see [`cli/cmd/ctl/market/cancel.go`](cli/cmd/ctl/market/cancel.go).\r\n\r\n### Local sources (chart push)\r\n\r\n`upload` and `delete` only accept `-s upload | studio | cli` — `validateLocalSource` rejects anything else.\r\n\r\n```bash\r\nolares-cli market upload ./myapp-1.0.0.tgz                # default: -s cli\r\nolares-cli market upload ./charts/                        # all .tgz / .tar.gz under dir\r\nolares-cli market upload ./myapp-1.0.0.tgz -s studio\r\n```\r\n\r\n```bash\r\nolares-cli market delete myapp                            # latest version in the source\r\nolares-cli market delete myapp --version 1.0.0\r\n```\r\n\r\n`upload` does not run a chart — `install -s cli <app>` does.\r\n\r\n<a id=\"watch-flag\"></a>\r\n## `--watch` (block until terminal state)\r\n\r\nThe synchronous CLI experience: lifecycle verbs return immediately when the backend accepts the request, but the actual mutation is asynchronous. `--watch` polls the same backend the SPA polls and only returns once the row reaches a terminal state (or the watcher gives up). Implementation lives in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go) (`waitForTerminal`, `runWithWatch`, `newWatchTarget`).\r\n\r\n### Flags\r\n\r\nDefined in [`cli/cmd/ctl/market/options.go`](cli/cmd/ctl/market/options.go) (`addWatchFlags`):\r\n\r\n- `-w / --watch` — opt-in. Default off.\r\n- `--watch-timeout` (default `15m`) — total deadline; surfaced as a typed timeout error including last-seen state.\r\n- `--watch-interval` (default `2s`) — polling interval. Lower values just hammer the backend; raise to `5s`–`10s` on slow networks.\r\n\r\n### Per-op terminal sets\r\n\r\n| Op           | Success                                      | Failure                                                  | `matchOpType` | `absentMeansSuccess` |\r\n|--------------|----------------------------------------------|----------------------------------------------------------|---------------|----------------------|\r\n| `install`    | `running`                                    | `*Failed` ∪ `*Canceled`                                  | true          | false                |\r\n| `clone`      | `running`                                    | `*Failed` ∪ `*Canceled`                                  | true          | false                |\r\n| `upgrade`    | `running`                                    | `upgradeFailed` ∪ `*Canceled`                            | true          | false                |\r\n| `uninstall`  | `uninstalled` (or row absent)                | `uninstallFailed`                                        | true          | true                 |\r\n| `stop`       | `stopped`                                    | `stopFailed`                                             | true          | false                |\r\n| `resume`     | `running`                                    | `resumeFailed` / `resumingCanceled` / `resumingCancelFailed` | true       | false                |\r\n| `cancel`     | `*Canceled`                                  | `*CancelFailed`                                          | **false**     | false                |\r\n| `status`     | `running` / `stopped` / `uninstalled` / `*Canceled` | `*Failed` / `*CancelFailed`                       | **false**     | true                 |\r\n\r\nThe `*` columns expand to the matching state-set constants (`operationFailedStates`, `cancelFailedStates`, `canceledStates`) defined in [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go).\r\n\r\n### `status --watch` (op-agnostic recovery)\r\n\r\nThe reason `status` got its own `--watch` even though it is read-only: a user runs `install firefox` *without* `--watch`, then five minutes later wants to know whether it landed. The recovery is:\r\n\r\n```bash\r\nolares-cli market status firefox --watch\r\n```\r\n\r\nThe watcher uses `case watchStatus` in `newWatchTarget` (see [`cli/cmd/ctl/market/watch.go`](cli/cmd/ctl/market/watch.go)): any **stable** terminal state is success (the user did not declare an op, so any quiescent state is fine), and a row that has disappeared between ticks is also treated as success. `matchOpType` is forced to `false` so the CLI does not get stuck waiting for a specific OpType that is not its own.\r\n\r\n`status --watch` requires an app name — see the errors table.\r\n\r\n### OpType gating in practice\r\n\r\nConcretely, `upgrade`'s watcher will not classify `state=running, opType=running` (or any leftover OpType from the previous mutation) as success. It waits until the backend reports `opType=upgrade` at least once, and only then accepts a `state=running` tick as terminal success. The same logic protects `install` after a previous `upgrade`, etc.\r\n\r\n### Output semantics\r\n\r\n| Mode                | What the user sees                                                                 |\r\n|---------------------|-------------------------------------------------------------------------------------|\r\n| TTY (`-o table`)    | Per-transition `info` lines on stderr (`installing`, `running`, …) and final OK/Fail line. |\r\n| `-o json`           | Exactly **one** final `OperationResult` JSON document with the new `finalState` / `finalOpType` fields populated (`omitempty` keeps non-watch JSON output unchanged). |\r\n| `-q / --quiet`      | No transition lines, no final summary; exit code still reflects success/failure.    |\r\n\r\n### Ctrl-C and timeout\r\n\r\n- The watch context is wrapped in `signal.NotifyContext` for `SIGINT` / `SIGTERM`. Pressing Ctrl-C exits cleanly with `<op> '<app>' watch canceled by user`. **The underlying mutation is NOT canceled** — re-attach with `status --watch` if needed.\r\n- Timeout exits with `<op> '<app>' watch timed out (last state: <S>, op: <O>)`. The mutation again may still be running on the cluster; `status --watch` is the canonical recovery.\r\n- After three consecutive transport errors the watcher gives up with `<op> '<app>' watch aborted after N consecutive errors`.\r\n\r\n### Watch flow (mermaid)\r\n\r\n```mermaid\r\nflowchart TD\r\n    A[verb returns 200] --> B{--watch set?}\r\n    B -- no --> Z[print summary / exit]\r\n    B -- yes --> C[waitForTerminal loop]\r\n    C --> D[GetMarketState every --watch-interval]\r\n    D --> E{matchOpType ok?}\r\n    E -- waiting --> C\r\n    E -- ok --> F{state in successSet?}\r\n    F -- yes --> G[exit 0 + finalState]\r\n    F -- no --> H{state in failureSet?}\r\n    H -- yes --> I[exit 1 + finalState]\r\n    H -- no --> J{ctx done?}\r\n    J -- timeout --> K[watch timed out]\r\n    J -- signal --> L[watch canceled by user]\r\n    J -- no --> C\r\n```\r\n\r\n## Common errors → fixes\r\n\r\n| Error message                                                                             | Cause                                                              | Fix                                                                    |\r\n|-------------------------------------------------------------------------------------------|---------------------------------------------------------------------|------------------------------------------------------------------------|\r\n| `server rejected the access token (HTTP 401/403)`                                         | Profile token is expired / wrong / missing                          | Defer to [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) (login + profile rules) |\r\n| `app 'X' is not installed (run 'olares-cli market install X' to install it)`              | Row missing in every source the user has                            | It really is not installed — install it, or check spelling             |\r\n| `App is installed under source 'Y' (not 'X')` (info, not error)                           | The user passed `-s X` but the row lives in source `Y`              | Re-run with `-s Y` for a clean filter, or `-a` to query every source   |\r\n| `--watch requires an app name (...)`                                                      | `status --watch` (or any lifecycle verb) was invoked without an app | Pass an app name, or drop `--watch` for the listing                    |\r\n| `<op> '<app>' watch timed out (last state: <S>, op: <O>)`                                 | `--watch-timeout` elapsed before terminal state                     | Bump `--watch-timeout`, or drill into the stuck state via `market status <app>` |\r\n| `<op> '<app>' watch canceled by user`                                                     | User pressed Ctrl-C                                                 | The mutation is likely still running on the cluster — re-attach with `market status <app> --watch` |\r\n| `<op> '<app>' watch aborted after N consecutive errors`                                   | Network / proxy flake during polling                                | Check connectivity, then re-attach with `status --watch`               |\r\n| `--title is required for cloning` / `--title cannot exceed 30 characters`                 | `clone` was invoked without a valid title                           | Pass `--title \"<= 30 chars>\"`","readmeExcerpt":"Skill: Olares Market (olares-cli market) Owner: olares Summary: Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the... Tags: latest:4.0.1 Version history: v4.0.1 | 2026-05-29T05:54:40.654Z | user Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0) v4.0.0 | 2026-05-29T04:53:46.218Z | user Auto","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: olares-market\r\nversion: 4.0.1\r\ndescription: \"Olares Market (olares-cli market) — install, upgrade, and manage Olares apps on Olares from the command line. Per-Olares-ID Market app-store v2 mirror of the Olares Market UI, scoped to the active Olares ID. Covers catalog browsing (list, get, categories), the full Olares app lifecycle (install, uninstall, upgrade, clone, cancel, stop, resume), runtime status, --mine view of the active Olares ID's apps (same set the Market UI's My Terminus tab shows, including in-flight installs / upgrades / failures), upload / delete for local Helm chart packages, and --watch flags that block until the app reaches a terminal state. Use when the user mentions Olares, Olares ID, Olares Market, olares-cli market, Olares app store, installing / upgrading / uninstalling / cloning / stopping / resuming / cancelling an Olares app, 'my Olares apps', '我的应用', upload chart, --watch, or asks 'is <app> installed yet on my Olares' / 'show me my Olares apps'.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli market --help\"\r\n---\r\n\r\n# market (App-store v2)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, automatic token refresh, and the auth-error recovery table.**\r\n\r\n> **Source of truth for flags is always `olares-cli market <verb> --help`.** This file only carries what `--help` cannot give: source resolution, the lifecycle state machine, OpType-vs-State race safety, the verb index, the `-s`/`-a` matrix, and the \"what apps do I have\" routing.\r\n\r\n## Routing\r\n\r\n| User intent | Use this skill? |\r\n|---|---|\r\n| \"Install / upgrade / uninstall an Olares app\" | ✅ yes |\r\n| \"What apps do I have?\" / \"我的应用\" | ✅ yes — `market list --mine` |\r\n| \"Is `<app>` installed yet?\" / \"Wait for `<app>` to be running\" | ✅ yes — `market status <app> --watch` |\r\n| \"Upload my custom helm chart\" | ✅ yes — `market upload` |\r\n| \"What pods is `<app>` running?\" | use [`olares-cluster`](../olares-cluster/SKILL.md) (`cluster application status <ns>`) |\r\n| \"Edit my app's entrance / domain / env / ACL\" | use [`olares-settings`](../olares-settings/SKILL.md) (`settings apps`) |\r\n| \"What's eating CPU on my cluster?\" | use [`olares-dashboard`](../olares-dashboard/SKILL.md) (`dashboard overview ranking`) |\r\n\r\n> **Mental model:** `market` is **lifecycle and inventory** at the app-store level (install / upgrade / chart push). For runtime K8s objects, settings, or metrics, route to a sibling.\r\n\r\n## Verb families\r\n\r\n| Family | Verbs | Mutating? |\r\n|---|---|---|\r\n| **catalog** | `list`, `get`, `categories` | no |\r\n| **runtime** | `status` | no |\r\n| **lifecycle** | `install`, `upgrade`, `uninstall`, `clone`, `stop`, `resume`, `cancel` | yes |\r\n| **charts** | `upload`, `delete` | yes |\r\n\r\nFor verb-specific behavior, **always start with `olares-cli market <verb> --help`**. Then drill into a reference if listed:\r\n\r\n| Family | Reference |\r\n|---"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-market\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034080654\n}"},{"path":"references/olares-market-charts.md","content":"# market upload / delete (local chart management)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & examples:** `olares-cli market upload --help`, `olares-cli market delete --help`.\r\n\r\nManage helm chart packages in the SPA's \"Local Sources → Upload\" bucket.\r\n\r\n## Hard-coded source (`upload`)\r\n\r\nBoth verbs **hard-code the target source to `upload`** — the same bucket the SPA's \"Local Sources → Upload\" tab writes to. **`-s / --source` is intentionally NOT exposed.**\r\n\r\nThe history: an earlier revision exposed `-s` on these verbs, and users pushed charts to `cli` or `studio` that were then invisible to the SPA's Local Sources tab (different buckets, same backend). Pinning the source eliminates that footgun. To install/delete from a different bucket, use `market install -s <id>` separately.\r\n\r\n## `upload`\r\n\r\n```bash\r\nolares-cli market upload ./mychart.tgz                 # single file\r\nolares-cli market upload ./charts/                     # every chart in a directory (no recursion)\r\nolares-cli market upload ./a.tgz ./b.tar.gz            # multiple files\r\nolares-cli market upload ./charts/ -o json             # structured per-file report\r\nolares-cli market upload ./mychart.tgz -q              # exit code only\r\n```\r\n\r\n- Accepts `.tgz` or `.tar.gz` files.\r\n- Directory mode uploads every `.tgz` / `.tar.gz` directly under the directory. **Subdirectories are NOT recursed.**\r\n- **Per-file results are summarized at the end.** `-o json` emits a structured report with one entry per file (`status` / `message`).\r\n- **Exit code is the OR of per-file results** — any single failure flips the overall exit non-zero.\r\n- Multipart upload through a dedicated `uploadClient` with no timeout (chart pushes can be slow over large WAN links). The same `refreshingTransport` is shared with the JSON client, so a token refresh on one is immediately visible on the other.\r\n\r\n### After upload\r\n\r\nMatch the chart with an install / delete:\r\n\r\n```bash\r\n# Install (must pass -s upload):\r\nolares-cli market install mychart -s upload --version 1.0.0\r\n\r\n# Browse what's in the upload bucket:\r\nolares-cli market list -s upload\r\n\r\n# Detail of one uploaded chart:\r\nolares-cli market get mychart -s upload -o json\r\n```\r\n\r\n## `delete`\r\n\r\n```bash\r\nolares-cli market delete mychart                       # every uploaded version\r\nolares-cli market delete mychart --version 1.0.0       # one version\r\nolares-cli market delete mychart -o json\r\nolares-cli market delete mychart -q\r\n```\r\n\r\n- **Does NOT uninstall the app if it is running.** Use `market uninstall <app>` first, then `market delete` to also remove the chart from local sources.\r\n- `--version` omitted → every uploaded version of the chart in the `upload` bucket is removed.\r\n\r\n> The \"delete the chart\" and \"uninstall the running app\" are deliberately separate verbs. A chart can be uploaded without ever being installed; an installed app can keep runn"},{"path":"references/olares-market-lifecycle.md","content":"# market lifecycle (install / upgrade / uninstall / clone / stop / resume / cancel)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"App lifecycle / state machine\", \"OpType vs State\", and \"`--watch` semantics\") first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb.\r\n\r\nThe mutating verb family. Every verb here returns an `OperationResult` JSON shape on `-o json`:\r\n\r\n```json\r\n{\r\n  \"name\": \"firefox\",\r\n  \"op\": \"install\",\r\n  \"accepted\": true,\r\n  \"finalState\": \"running\",\r\n  \"finalOpType\": \"\",\r\n  \"message\": \"\",\r\n  \"watched\": true,\r\n  \"cloneTarget\": \"\"          // only set for `clone`\r\n}\r\n```\r\n\r\n## Source-aware vs source-implicit verbs\r\n\r\n| Verb | `-s / --source` | Why |\r\n|---|---|---|\r\n| `install`, `upgrade`, `clone` | accepts; defaults to auto-selected source | The chart can live in different sources |\r\n| `uninstall`, `stop`, `resume`, `cancel` | **NOT exposed** | Acts on whichever per-user state row matches the app name, regardless of source |\r\n\r\n## `install`\r\n\r\n```bash\r\nolares-cli market install firefox                      # auto-selected source, latest version\r\nolares-cli market install firefox --version 1.2.3      # pin version (strict semver)\r\nolares-cli market install firefox -s upload            # install a locally-uploaded chart\r\nolares-cli market install gitea --env GITEA_TOKEN=...  # required envs\r\nolares-cli market install firefox --watch              # block until terminal\r\nolares-cli market install firefox --watch -o json\r\n```\r\n\r\n- `--version` defaults to the latest catalog version. Strict semver validated client-side before send.\r\n- `--env KEY=VALUE` (repeatable) for required env vars. Missing required envs surface as `missing required env var(s): KEY1, KEY2 ...` (server returns HTTP 422 / `type=appenv`).\r\n- **To install a locally-uploaded chart, pass `-s upload`** (the bucket `market upload` writes to).\r\n\r\n## `upgrade`\r\n\r\n```bash\r\nolares-cli market upgrade firefox                      # latest catalog\r\nolares-cli market upgrade firefox --version 1.5.0\r\nolares-cli market upgrade firefox --watch\r\n```\r\n\r\n### Pre-flight gates (run BEFORE the PUT request)\r\n\r\nMirrors the SPA's `canUpgrade()`. Bails locally with a self-contained error (formatted via `failOp`, so `-o json` carries it in `.message` and `-q` still surfaces the exit code):\r\n\r\n1. **Row exists** — state row found via `Name` or `RawName` (clones included)\r\n2. **State is upgradable** — `running` / `stopped` / `stopFailed` / `upgradeFailed` / `applyEnvFailed`\r\n3. **Newer chart available** — `targetVersion > installedVersion` (semver compare)\r\n4. **App labels don't forbid upgrade** — `disabled-upgrade`, `suspend`, `remove` labels\r\n\r\n## `uninstall`\r\n\r\n```bash\r\nolares-cli market uninstall firefox                    # implicit source\r\nolares-cli market uninstall firefox --cascade=true     # tear down shared sub-charts (C/S v2 multi-chart)\r\nolares-cli market uninstall"},{"path":"references/olares-market-list.md","content":"# market list / categories / get / status (catalog + runtime)\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) (especially \"What apps do I have? routing\" and the `-s` / `-a` matrix) first.\r\n> **Flags & examples:** `olares-cli market <verb> --help` for each verb below.\r\n\r\nThe read-only family — catalog browsing, runtime status, and the `--mine` inventory view.\r\n\r\n## `list` — catalog OR inventory\r\n\r\nTwo modes, selected by `--mine` / `-m`:\r\n\r\n| Mode | Without `--mine` | With `--mine` / `-m` |\r\n|---|---|---|\r\n| Endpoint | `/market/data` | `/market/state` |\r\n| Scope | Catalog | Active profile's apps |\r\n| Default source scope | Auto-selected single source (usually `market.olares`) | **All sources** (matches Market UI \"My Terminus\" tab) |\r\n| Columns | `NAME / TITLE / VERSION / CATEGORIES` | `NAME / TITLE / VERSION / STATE / SOURCE / CATEGORIES` |\r\n\r\n```bash\r\nolares-cli market list                          # catalog (auto-selected source)\r\nolares-cli market list -s market.olares         # pin to a source\r\nolares-cli market list -s cli                   # browse a local source\r\nolares-cli market list -c AI                    # filter by category\r\nolares-cli market list -a                       # every source the user has\r\nolares-cli market list --mine                   # what apps does this user have (all sources)\r\nolares-cli market list -m -s cli                # mine, narrowed to one source\r\nolares-cli market list --no-headers             # table without column headers\r\nolares-cli market list -q                       # exit code only\r\n```\r\n\r\n### `--mine` filter (matches SPA \"My Terminus\" exactly)\r\n\r\nHides only **6 SPA-hidden states** (`pendingCanceled`, `downloadingCanceled`, `downloadFailed`, `installFailed`, `installingCanceled`, `uninstalled`). Everything else stays visible — including in-flight installs, transitional states (`upgrading` / `stopping` / `resuming` / `applyingEnv` / `uninstalling`), and post-install failures (`upgradeFailed` / `stopFailed` / `resumeFailed` / `applyEnvFailed` / `uninstallFailed`).\r\n\r\n> **\"My apps\" is intentionally broader than \"completed installs only\"** because the SPA's My Terminus tab is too. The user clicked something and wants to monitor / retry / cancel the row.\r\n\r\n### Version on `--mine` rows\r\n\r\nThe version column reflects the **chart the user picked for this row**, NOT the catalog latest. If the user installed 1.0.10 and the marketplace catalog has since moved to 1.2.3, this listing surfaces 1.0.10. During an in-flight upgrade, the row may show the target version while `STATE=upgrading`. **Both are intentional — don't \"correct\" to the catalog version.**\r\n\r\nClones look up the catalog by `rawAppName`, not their per-instance `name` (e.g. `windowsefe992` → catalog row `windows`), so clones still surface the source app's title and categories.\r\n\r\n## `categories`\r\n\r\n```bash\r\nolares-cli market categories                    # per-source"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1460,"uniquenessScore":44,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T19:40:26.598Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-12T00:18:30.923Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}