diff --git a/.github/ci-paths.toml b/.github/ci-paths.toml
index e6b355ae31..e777a04bb4 100644
--- a/.github/ci-paths.toml
+++ b/.github/ci-paths.toml
@@ -94,6 +94,8 @@ observability = [
"tests/e2e/*observability*", "tests/builtin/runtime/*worker*", "tests/builtin/runtime/*model_usage*",
]
packaging = [
+ "website/public/install.sh", "website/public/install.ps1", ".github/workflows/installation.yml",
+ "tests/test_installation_contract.py", "tests/native/test_installation.py", "tests/native/test_install_sources.py",
"scripts/build_ops_bundle.py",
"tests/test_installation.py", "tests/test_upgrade_publication.py", "scripts/ci_role_smoke.py",
"scripts/ci_installation_roles.py", "tests/test_ci_installation_roles.py", "release/**",
@@ -197,7 +199,7 @@ zcode = ["zcode-acceptance"]
kimi = ["kimi-acceptance"]
upgrade = ["seekdb-managed-startup", "upgrade-acceptance"]
observability = ["worker-observability"]
-packaging = ["isolated-installations"]
+packaging = ["isolated-installations", "script-installation"]
desktop = ["desktop-validation"]
managed_resources = ["managed-resource-candidates"]
candidate_replay = ["candidate-replay"]
diff --git a/.github/workflows/installation.yml b/.github/workflows/installation.yml
new file mode 100644
index 0000000000..f5deb4e392
--- /dev/null
+++ b/.github/workflows/installation.yml
@@ -0,0 +1,77 @@
+name: Script installation
+
+on:
+ workflow_call:
+ inputs:
+ suites:
+ description: Scope already selected by the Main orchestrator
+ required: true
+ type: string
+ workflow_dispatch:
+
+permissions:
+ actions: read
+ contents: read
+
+concurrency:
+ group: installation-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
+ cancel-in-progress: true
+
+jobs:
+ changes:
+ if: ${{ inputs.suites == '' }}
+ uses: ./.github/workflows/ci-changes.yml
+ with:
+ baseline-workflow: installation.yml
+
+ installation:
+ needs: changes
+ if: ${{ !cancelled() && ((inputs.suites == '' && needs.changes.result != 'success') || fromJSON(inputs.suites || needs.changes.outputs.suites || '{}').packaging) }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 20
+ steps:
+ - name: Check out
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+ with:
+ persist-credentials: false
+
+ - name: Set up the environment
+ uses: ./.github/actions/setup-python-env
+
+ - name: Build the installation fixture
+ shell: bash
+ env:
+ # Qualify this commit using release-shaped metadata, without publishing it.
+ SETUPTOOLS_SCM_PRETEND_VERSION: "1.2.0"
+ run: uv build --wheel --out-dir dist/installation
+
+ - name: Check shared installer behavior and source resolution
+ shell: bash
+ run: >-
+ uv run --no-sync pytest -q tests/test_installation_contract.py tests/native/test_install_sources.py
+ --junitxml "$RUNNER_TEMP/installation-contract.xml"
+ --basetemp "$RUNNER_TEMP/installation-contract-tests"
+
+ - name: Exercise bootstrap, version selection, profiles, and persistent memory
+ shell: bash
+ env:
+ POWERCONTEXT_INSTALL_WHEEL: ${{ github.workspace }}/dist/installation/powercontext-1.2.0-py3-none-any.whl
+ run: >-
+ uv run --no-sync pytest -q tests/native/test_installation.py
+ --junitxml "$RUNNER_TEMP/installation.xml"
+ --basetemp "$RUNNER_TEMP/installation-tests"
+
+ - name: Upload installation diagnostics after failure
+ if: failure()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: installation-${{ matrix.os }}
+ path: |
+ ${{ runner.temp }}/installation.xml
+ ${{ runner.temp }}/installation-contract.xml
+ ${{ runner.temp }}/installation-tests/**/*.log
+ if-no-files-found: ignore
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 80607cd91f..910a24e2b8 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -1153,6 +1153,16 @@ jobs:
with:
suites: ${{ needs.quality.outputs.suites }}
+ script-installation:
+ needs: [quality, tests]
+ if: >-
+ ${{ !cancelled() && needs.quality.result == 'success' &&
+ contains(fromJSON('["success","skipped"]'), needs.tests.result) &&
+ (fromJSON(needs.quality.outputs.suites || '{}').packaging) }}
+ uses: ./.github/workflows/installation.yml
+ with:
+ suites: ${{ needs.quality.outputs.suites }}
+
native-service:
needs: [quality, tests]
if: >-
@@ -1254,6 +1264,7 @@ jobs:
- desktop-validation
- managed-resource-candidates
- helm
+ - script-installation
- native-service
- kimi-acceptance
- zcode-acceptance
diff --git a/README.md b/README.md
index 6c6fafbf4a..0cacd77f6d 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,7 @@
## What is PowerContext?
-**PowerContext is an open-source context service for AI agents.** It keeps the durable part of your work — decisions,
-constraints, evidence, progress — outside any single conversation, so the next session, the next agent, or the next
-person can pick up from the current state instead of rereading the transcript.
+**PowerContext is an open-source context service for AI agents.** It keeps the durable part of your work — decisions, constraints, evidence, progress — outside any single conversation, so the next session, the next agent, or the next person can pick up from the current state instead of rereading the transcript.
You can use it as a ready-to-run Server or embed the same contracts in another application:
@@ -25,14 +23,11 @@ You can use it as a ready-to-run Server or embed the same contracts in another a
| Builtin runtime | Memory, Handoff, Experience, and Skill services, scheduling, inference, and supported storage backends | Running the standard local Server |
| Server and integrations | HTTP/MCP, Python Client, CLI, Dashboard, and Agent plugins | Connecting an Agent or application |
-Work rarely ends with whoever starts it. You hand a task to an agent, the agent gets part of the way, and later you or
-someone else takes over. The reasoning and current state often stay behind in that conversation. PowerContext moves them
-out of the conversation and into revisioned artifacts you can inspect:
+Work rarely ends with whoever starts it. You hand a task to an agent, the agent gets part of the way, and later you or someone else takes over. The reasoning and current state often stay behind in that conversation. PowerContext moves them out of the conversation and into revisioned artifacts you can inspect:

-Everything belongs to a **Scope** — one isolated Source journal, Memory lifecycle, review inbox, and Handoff history.
-A Scope ID selects data; it does not prove user identity, grant tool access, or authorize execution.
+Everything belongs to a **Scope** — one isolated Source journal, Memory lifecycle, review inbox, and Handoff history. A Scope ID selects data; it does not prove user identity, grant tool access, or authorize execution.
```text
Scope
@@ -49,10 +44,7 @@ For Kubernetes deployments with external OceanBase, see the [Helm chart and depl
### Memory — decisions and constraints that outlive a session
-Memory is a revisioned Artifact family for reusable decisions, constraints, facts, state, and next steps. An entry is
-`active`, `forgotten`, `merged`, or `retired`. Forgetting one through `POST /v1/atomic-memory/lifecycle` takes it out of
-active recall while its content and history stay available and restorable, without advancing the content revision.
-Explicit Memory writes need no model at all; extraction from captured Sources needs a configured generation pipeline.
+Memory is a revisioned Artifact family for reusable decisions, constraints, facts, state, and next steps. An entry is `active`, `forgotten`, `merged`, or `retired`. Forgetting one through `POST /v1/atomic-memory/lifecycle` takes it out of active recall while its content and history stay available and restorable, without advancing the content revision. Explicit Memory writes need no model at all; extraction from captured Sources needs a configured generation pipeline.
> "Don't refactor the old auth module — mobile is still using it."
> Context this expensive shouldn't depend on someone repeating it in every session.
@@ -67,32 +59,21 @@ Handoff records work continuity as a four-step loop:
Work Contract → Prepared Handoff → Acknowledgement → Task Outcome
```
-`handoff_current_work` captures an inspected boundary and returns a temporary Prepared Handoff. Committing it creates a
-durable Revision only when the user wants a milestone. The receiver resolves the Handoff and records an Acknowledgement;
-a Task Outcome preserves the final status and checks as Source evidence. The
-[Handoff Report](https://powercontext.oceanbase.io/en/docs/workflows/use-handoff-report/) projects the latest Revision in
-each selected Scope — read-only, and it never rewrites Memory.
+`handoff_current_work` captures an inspected boundary and returns a temporary Prepared Handoff. Committing it creates a durable Revision only when the user wants a milestone. The receiver resolves the Handoff and records an Acknowledgement; a Task Outcome preserves the final status and checks as Source evidence. The [Handoff Report](https://powercontext.oceanbase.io/en/docs/workflows/use-handoff-report/) projects the latest Revision in each selected Scope — read-only, and it never rewrites Memory.

### Experience — what worked, reviewed before it is reused
-An Experience records a situation, an action, an observed outcome, and a reusable lesson. It enters a scope-local Review
-Inbox as a pending Candidate. Review writes use `expected_version`, so a decision cannot silently apply after another
-writer changes the proposal. Approval writes an immutable Revision; rejection records a reason. Both are terminal, and
-only the approved current head takes part in recall — pending, rejected, and historical Revisions are excluded.
+An Experience records a situation, an action, an observed outcome, and a reusable lesson. It enters a scope-local Review Inbox as a pending Candidate. Review writes use `expected_version`, so a decision cannot silently apply after another writer changes the proposal. Approval writes an immutable Revision; rejection records a reason. Both are terminal, and only the approved current head takes part in recall — pending, rejected, and historical Revisions are excluded.
### Skill — a reviewed procedure, exported on purpose
-A managed Skill holds a name, a discovery description, instructions, validation checks, and lineage. Approval does not
-install or execute it: an exact approved Revision must be exported explicitly before an Agent host can discover the
-host-local projection.
+A managed Skill holds a name, a discovery description, instructions, validation checks, and lineage. Approval does not install or execute it: an exact approved Revision must be exported explicitly before an Agent host can discover the host-local projection.
### Desktop — inspect project memory from a connected workspace
-Connect to a Server, select an exact Scope, and search its memories. Open a result to read its saved revision,
-or save a note for the next session. The [Desktop preview](desktop/README.md) is intended for internal validation;
-signed distribution and platform release qualification remain separate work.
+Connect to a Server, select an exact Scope, and search its memories. Open a result to read its saved revision, or save a note for the next session. The [Desktop preview](desktop/README.md) is intended for internal validation; signed distribution and platform release qualification remain separate work.

@@ -100,27 +81,12 @@ signed distribution and platform release qualification remain separate work.
## Why PowerContext?
-- **Context lives outside the conversation.** Memory, Handoff, Experience, and Skill are server-side artifacts, not
- files inside one agent's home directory. Switch from Codex to Claude Code — or hand the work to a teammate — and the
- work travels with you.
-- **Everything is revisioned and traceable.** Each Artifact is addressed as `FAMILY/ARTIFACT_ID@REVISION`. The Artifact
- ID stays stable while approved replacements create later Revisions, and reading an exact reference returns that
- historical snapshot even after the family head advances.
-- **Generation, review, and execution are separate steps.** A model proposes content, review approves a Revision, and
- export creates a host-local copy. None of those steps grants an Agent permission to execute instructions. See
- [Interfaces](https://powercontext.oceanbase.io/en/docs/develop/interfaces/) for what each surface exposes today.
-- **Recall is bounded, not dumped.** `PreparedContext` selects configured sections from active Memory and approved
- Experience, with optional Profile snapshots and Topic Memory, then applies a shared byte budget and returns `ready`
- content or `empty`. Recalled content is history, not an instruction authority: the receiving Agent still follows
- current instructions and checks live workspace state.
-- **Review before reuse.** Experience and Skill pass through a review decision that is terminal either way, and
- approval writes an immutable Revision. With access control enforced, approval requires `scope.review`. An
- authorized Agent can approve its own Candidate;
- [separate submission and review](https://powercontext.oceanbase.io/en/docs/workflows/review-candidates/#separate-agent-submission-from-review)
- with distinct identities and permissions when a person must decide.
-- **One Server, several views of the same contract.** HTTP is the complete remote application contract. The Python Client
- gives typed access, MCP is a curated Agent-facing projection, and Core protocols let an application assemble its own
- Source, Artifact, and Trigger implementations.
+- **Context lives outside the conversation.** Memory, Handoff, Experience, and Skill are server-side artifacts, not files inside one agent's home directory. Switch from Codex to Claude Code — or hand the work to a teammate — and the work travels with you.
+- **Everything is revisioned and traceable.** Each Artifact is addressed as `FAMILY/ARTIFACT_ID@REVISION`. The Artifact ID stays stable while approved replacements create later Revisions, and reading an exact reference returns that historical snapshot even after the family head advances.
+- **Generation, review, and execution are separate steps.** A model proposes content, review approves a Revision, and export creates a host-local copy. None of those steps grants an Agent permission to execute instructions. See [Interfaces](https://powercontext.oceanbase.io/en/docs/develop/interfaces/) for what each surface exposes today.
+- **Recall is bounded, not dumped.** `PreparedContext` selects configured sections from active Memory and approved Experience, with optional Profile snapshots and Topic Memory, then applies a shared byte budget and returns `ready` content or `empty`. Recalled content is history, not an instruction authority: the receiving Agent still follows current instructions and checks live workspace state.
+- **Review before reuse.** Experience and Skill pass through a review decision that is terminal either way, and approval writes an immutable Revision. With access control enforced, approval requires `scope.review`. An authorized Agent can approve its own Candidate; [separate submission and review](https://powercontext.oceanbase.io/en/docs/workflows/review-candidates/#separate-agent-submission-from-review) with distinct identities and permissions when a person must decide.
+- **One Server, several views of the same contract.** HTTP is the complete remote application contract. The Python Client gives typed access, MCP is a curated Agent-facing projection, and Core protocols let an application assemble its own Source, Artifact, and Trigger implementations.
### How it compares
@@ -141,13 +107,9 @@ The comparison assumes typical single-agent or host-local setups; exact behavior

-- **Long-conversation memory (LoCoMo; 1,540 scored questions across categories 1–4): 90.78% accuracy**, against 87.79%
- for PowerMem and 52.9% for full-context replay — **+37.88 points** over full context.
-- **Coding tasks (SWE-bench Pro; a paired project run on all 731 public v2 tasks, not an official submission): 86.73%
- task resolution with PowerContext on, 82.35% with it off** — **+4.38 points** and 32 more tasks resolved.
-- **Retrieval quality (LoCoMo Plus)**, native PowerContext against PowerContext with Jev candidate filtering after
- retrieval. Each model is used for both Memory extraction and answering; embeddings use `qwen3.7-text-embedding`
- (1024 dimensions):
+- **Long-conversation memory (LoCoMo; 1,540 scored questions across categories 1–4): 90.78% accuracy**, against 87.79% for PowerMem and 52.9% for full-context replay — **+37.88 points** over full context.
+- **Coding tasks (SWE-bench Pro; a paired project run on all 731 public v2 tasks, not an official submission): 86.73% task resolution with PowerContext on, 82.35% with it off** — **+4.38 points** and 32 more tasks resolved.
+- **Retrieval quality (LoCoMo Plus)**, native PowerContext against PowerContext with Jev candidate filtering after retrieval. Each model is used for both Memory extraction and answering; embeddings use `qwen3.7-text-embedding` (1024 dimensions):
| Model | PowerContext | PowerContext + Jev | Difference |
|---|---|---|---|
@@ -155,10 +117,7 @@ The comparison assumes typical single-agent or host-local setups; exact behavior
| Qwen3.7-plus | 60.37% | 68.947% | +8.577 points |
| GPT-4o | 65.082% | 69.28% | +4.198 points |
-Full methods, results, and limitations: [benchmarks](https://powercontext.oceanbase.io/en/benchmarks/). Two caveats we
-would rather state than bury: the six LoCoMo Plus scores above were supplied by the project owner, and their run
-artifacts are not publicly available for verification — the published instructions reproduce the harness, not those
-scores. Windows support is `experimental`.
+Full methods, results, and limitations: [benchmarks](https://powercontext.oceanbase.io/en/benchmarks/). Two caveats we would rather state than bury: the six LoCoMo Plus scores above were supplied by the project owner, and their run artifacts are not publicly available for verification — the published instructions reproduce the harness, not those scores. Windows support is `experimental`.
## Quick start
@@ -170,12 +129,7 @@ PowerContext keeps durable project context outside any single chat. A first run
Agent input → Source evidence → Memory / Topic Memory → PreparedContext → next session or Agent
```
-PowerContext 1.5.0 includes an interactive setup wizard. You need Python 3.11+, Git, and
-[uv](https://docs.astral.sh/uv/getting-started/installation/); uv can provision Python when needed. To connect an Agent,
-also install its CLI. In the wizard, choose Basic memory for explicit saves and recall without model APIs; Full memory
-requires separate Generation and Embedding API credentials. For a local Server without model APIs, see
-[Install and run](docs/en/docs/get-started/install-and-run.md).
-macOS and Linux are supported; Windows support is `experimental`.
+PowerContext 1.5.0 includes an interactive setup wizard. You need Python 3.11+, Git, and [uv](https://docs.astral.sh/uv/getting-started/installation/); uv can provision Python when needed. To connect an Agent, also install its CLI. In the wizard, choose Basic memory for explicit saves and recall without model APIs; Full memory requires separate Generation and Embedding API credentials. For a local Server without model APIs, see [Install and run](docs/en/docs/get-started/install-and-run.md). macOS and Linux are supported; Windows support is `experimental`. To provision Python and uv automatically, use the [installation script](docs/en/docs/get-started/install-and-run.md#install-with-the-recommended-script).
Choose the path that matches your goal:
@@ -191,15 +145,21 @@ Choose the path that matches your goal:
uv tool install --force "powercontext[cli,server]==1.5.0"
mkdir -p powercontext-config && cd powercontext-config
powercontext config init --language en --output .env
-powercontext config validate --env-file .env
-powercontext server run --env-file .env # keep this terminal open
```
The wizard asks about storage, local or remote usage, memory capabilities, Dashboard/access, and Agent connections. SQLite is the simplest first storage choice; choose embedded seekdb only when you need it and can approve its dependency install.
-The wizard writes one `.env` environment file and `.env.next-steps.md`. These files can contain credentials; do not
-commit them. Keep Server running; in another terminal, return to `powercontext-config`, reload the generated environment
-file, and check the connection:
+The wizard writes `.env` and `.env.next-steps.md`. These files can contain credentials; do not commit them. Linux needs an available `systemd --user` manager for the personal service.
+
+After saving `.env`, validate it and register the personal service:
+
+```bash
+powercontext config validate --env-file .env
+powercontext service install --env-file .env
+powercontext service status
+```
+
+The service keeps the Server running after you close the terminal. Load the generated environment file and check the connection:
```bash
set -a
@@ -209,28 +169,18 @@ powercontext ready
powercontext capabilities
```
-Continue with `.env.next-steps.md` to create and bind the selected Scopes. Use the real `scope_id` returned by the
-creation request; a planned `codex-...` title is not an ID. Changing directories does not create isolation; use the
-same Server and Scope in the Dashboard and Agent. Then install the matching plugins and launch a new Agent session.
-The [complete walkthrough](https://powercontext.oceanbase.io/en/docs/get-started/quickstart/) covers Codex and Claude
-Code, Dashboard login, SSH forwarding, HTTPS prerequisites, and observable acceptance checks. For example, the matching
-Codex installation is:
+Continue with `.env.next-steps.md` to create and bind the selected Scopes. Use the real `scope_id` returned by the creation request; a planned `codex-...` title is not an ID. Changing directories does not create isolation; use the same Server and Scope in the Dashboard and Agent. After editing `.env`, rerun `powercontext service install --env-file .env` to apply the updated settings. Then install the matching plugins and launch a new Agent session. The [complete walkthrough](https://powercontext.oceanbase.io/en/docs/get-started/quickstart/) covers Codex and Claude Code, Dashboard login, SSH forwarding, HTTPS prerequisites, and observable acceptance checks. For example, the matching Codex installation is:
```bash
powercontext setup codex --ref powercontext-v1.5.0
powercontext doctor codex
```
-Before sending the first message in a new Codex session, open `/hooks` and, when prompted, trust the PowerContext
-hooks. Without an enabled `UserPromptSubmit` hook, automatic prompt capture and recall do not run, even if MCP connects.
-See [first-session checks and recovery](docs/en/docs/integrations/codex.md#first-session-hook-trust).
-After the first check below, use the [capability and trigger guide](docs/en/docs/get-started/quickstart.md#5-choose-your-next-workflow)
-to try all seven Artifact families, Source, and Tag. Basic memory needs no separate Server model API; the Agent still uses its own model.
+Before sending the first message in a new Codex session, open `/hooks` and, when prompted, trust the PowerContext hooks. Without an enabled `UserPromptSubmit` hook, automatic prompt capture and recall do not run, even if MCP connects. See [first-session checks and recovery](docs/en/docs/integrations/codex.md#first-session-hook-trust). After the first check below, use the [capability and trigger guide](docs/en/docs/get-started/quickstart.md#5-choose-your-next-workflow) to try all seven Artifact families, Source, and Tag. Basic memory needs no separate Server model API; the Agent still uses its own model.
### 2. Verify the first end-to-end result
-This checks the path that makes PowerContext useful: Source capture → background processing → cross-session recall. In
-the new Agent session bound to the same Scope, send a concrete decision such as:
+This checks the path that makes PowerContext useful: Source capture → background processing → cross-session recall. In the new Agent session bound to the same Scope, send a concrete decision such as:
```text
We are designing the Aurora acceptance project. Use uv for Python dependencies and .env for configuration.
@@ -245,49 +195,31 @@ If you enabled Dashboard, open the URL printed by the wizard; an empty Dashboard
3. Send a related update and confirm that the new Source arrives and the topic content or revision history reflects it.
4. Start a new Agent session with the same Scope and ask for the Aurora decisions with citations.
-`doctor codex` checks installation and connectivity only. Source capture does not by itself create Memory or Topic
-Memory, and processing can wait for configured schedules and model work. In Basic memory mode, explicitly ask the Agent
-to save a decision, then search for it in a new session; that does not test automatic extraction.
+`doctor codex` checks installation and connectivity only. Source capture does not by itself create Memory or Topic Memory, and processing can wait for configured schedules and model work. In Basic memory mode, explicitly ask the Agent to save a decision, then search for it in a new session; that does not test automatic extraction.
### 3. Explore Python first — no API key
-The tutorials run from a repository checkout and take their dependencies from a `uv` dependency group, so they are not
-part of the `powercontext` package. From the repository root:
+The tutorials run from a repository checkout and take their dependencies from a `uv` dependency group, so they are not part of the `powercontext` package. From the repository root:
```bash
uv sync --locked --group notebooks
uv run --locked --group notebooks jupyter lab --notebook-dir=examples/jupyter
```
-Start with [01 · Memory across sessions](examples/jupyter/01_memory_across_sessions.ipynb), then work through the
-[22 Chinese Jupyter tutorials and a complete team workflow](examples/jupyter/README.md) to run Memory, context
-preparation, Handoff, Experience, Skill, and a real Agent step by step. The first seven tutorials need no model or API
-key.
+Start with [01 · Memory across sessions](examples/jupyter/01_memory_across_sessions.ipynb), then work through the [22 Chinese Jupyter tutorials and a complete team workflow](examples/jupyter/README.md) to run Memory, context preparation, Handoff, Experience, Skill, and a real Agent step by step. The first seven tutorials need no model or API key.
### Common issues
-- `doctor codex` verifies Codex installation and connectivity. To verify automatic memory, check that a real prompt
- becomes a Source, produces a Topic, evolves after a related prompt, and can be recalled in a new session using the
- same Scope.
-- If Python dependency downloads are slow or fail, configure a package index for
- [uv](https://docs.astral.sh/uv/configuration/indexes/) and retry.
-- Choose **Full memory capabilities** to test automatic Memory and Topic Memory; this requires separate Generation and
- Embedding API credentials. An Agent subscription does not provide those Server credentials. Choose **Basic memory** to
- save and retrieve memories explicitly without additional model APIs.
+- `doctor codex` verifies Codex installation and connectivity. To verify automatic memory, check that a real prompt becomes a Source, produces a Topic, evolves after a related prompt, and can be recalled in a new session using the same Scope.
+- If Python dependency downloads are slow or fail, configure a package index for [uv](https://docs.astral.sh/uv/configuration/indexes/) and retry.
+- Choose **Full memory capabilities** to test automatic Memory and Topic Memory; this requires separate Generation and Embedding API credentials. An Agent subscription does not provide those Server credentials. Choose **Basic memory** to save and retrieve memories explicitly without additional model APIs.
- If seekdb needs installing, the wizard asks once and installs the dependency in the background.
-- The optional recall-sufficiency gate records scope-local daily search effort and budget omissions for later policy
- evaluation. See the [configuration reference](docs/en/docs/operate/configuration.md) for its recording and failure
- behavior.
-- For a Server on another machine, use HTTPS or follow the
- [remote connection guide](docs/en/docs/operate/connect-remote-server.md). Setup recognizes remote HTTP URLs from flags
- or environment variables and asks for explicit consent; automated setup uses `--allow-insecure-http`.
+- The optional recall-sufficiency gate records scope-local daily search effort and budget omissions for later policy evaluation. See the [configuration reference](docs/en/docs/operate/configuration.md) for its recording and failure behavior.
+- For a Server on another machine, use HTTPS or follow the [remote connection guide](docs/en/docs/operate/connect-remote-server.md). Setup recognizes remote HTTP URLs from flags or environment variables and asks for explicit consent; automated setup uses `--allow-insecure-http`.
## Connect your Agent
-Codex is `official`; other maintained hosts and Python Agent frameworks are `community`; Bub is `evaluation` only.
-These tags describe PowerContext integration maintenance and use. The
-[capability matrix](https://powercontext.oceanbase.io/en/docs/integrations/capabilities/) records which hosts are
-qualified, along with their supported features and availability.
+Codex is `official`; other maintained hosts and Python Agent frameworks are `community`; Bub is `evaluation` only. These tags describe PowerContext integration maintenance and use. The [capability matrix](https://powercontext.oceanbase.io/en/docs/integrations/capabilities/) records which hosts are qualified, along with their supported features and availability.
@@ -311,8 +243,7 @@ qualified, along with their supported features and availability.
-Applications can use PowerContext through the async Python client, HTTP API, MCP, or the in-process Core SDK. See the
-[interface reference](https://powercontext.oceanbase.io/en/docs/develop/interfaces/) to choose an entry point.
+Applications can use PowerContext through the async Python client, HTTP API, MCP, or the in-process Core SDK. See the [interface reference](https://powercontext.oceanbase.io/en/docs/develop/interfaces/) to choose an entry point.
## Build PowerContext
@@ -322,9 +253,7 @@ make check
make test
```
-See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow.
-Coding and memory evaluations, performance benchmarks, and Skill regressions live under
-[`evaluation/`](evaluation/README.md).
+See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow. Coding and memory evaluations, performance benchmarks, and Skill regressions live under [`evaluation/`](evaluation/README.md).
## Learn more
@@ -344,8 +273,7 @@ Thank you to everyone who contributes to PowerContext. ❤️
-[See all contributors](https://github.com/oceanbase/powercontext/graphs/contributors) ·
-[Start contributing](CONTRIBUTING.md)
+[See all contributors](https://github.com/oceanbase/powercontext/graphs/contributors) · [Start contributing](CONTRIBUTING.md)
## License
diff --git a/README_CN.md b/README_CN.md
index 12bf31abfe..a8e549099c 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -42,10 +42,7 @@ Scope
### Memory —— 活得比会话更久的决定与约束
-Memory 是一个带版本的 Artifact 家族,存放可以复用的决定、约束、事实、状态和下一步。条目状态为 `active`、
-`forgotten`、`merged` 或 `retired`。通过 `POST /v1/atomic-memory/lifecycle` 遗忘一条,会把它移出主动召回,但内容
-和历史仍然可用、可恢复,且不会推进内容 revision。显式写入 Memory 完全不需要模型;从已捕获的 Source 中提取,
-才需要配置生成流水线。
+Memory 是一个带版本的 Artifact 家族,存放可以复用的决定、约束、事实、状态和下一步。条目状态为 `active`、`forgotten`、`merged` 或 `retired`。通过 `POST /v1/atomic-memory/lifecycle` 遗忘一条,会把它移出主动召回,但内容和历史仍然可用、可恢复,且不会推进内容 revision。显式写入 Memory 完全不需要模型;从已捕获的 Source 中提取,才需要配置生成流水线。
> "别重构老的 auth 模块 —— 移动端还在用。"
> 这么贵的上下文,不应该依赖有人每个会话都重讲一遍。
@@ -74,8 +71,7 @@ Experience 记录一个场景、一次动作、观察到的结果,以及可复
### 桌面端 —— 在连接的工作区里查看项目记忆
-连接 Server、选择精确的 Scope 后,即可搜索其中的记忆,打开结果读取已保存的具体版本,或为下次会话记录便签。
-[桌面预览](desktop/README.zh.md)用于内部验证;签名分发和各平台的发行验收仍需单独完成。
+连接 Server、选择精确的 Scope 后,即可搜索其中的记忆,打开结果读取已保存的具体版本,或为下次会话记录便签。[桌面预览](desktop/README.zh.md)用于内部验证;签名分发和各平台的发行验收仍需单独完成。

@@ -109,10 +105,8 @@ Experience 记录一个场景、一次动作、观察到的结果,以及可复

-- **长对话记忆(LoCoMo;类别 1–4 的 1,540 个计分问题):准确率 90.78%**,PowerMem 为 87.79%,全量上下文
- 重放为 52.9% —— 相对后者 **+37.88 个百分点**。
-- **编码任务(SWE-bench Pro;731 个公开 v2 任务的配对项目运行,非官方提交):开启 PowerContext 后任务解决率
- 86.73%,关闭时 82.35%** —— **+4.38 个百分点**,多解出 32 题。
+- **长对话记忆(LoCoMo;类别 1–4 的 1,540 个计分问题):准确率 90.78%**,PowerMem 为 87.79%,全量上下文重放为 52.9% —— 相对后者 **+37.88 个百分点**。
+- **编码任务(SWE-bench Pro;731 个公开 v2 任务的配对项目运行,非官方提交):开启 PowerContext 后任务解决率 86.73%,关闭时 82.35%** —— **+4.38 个百分点**,多解出 32 题。
- **召回质量(LoCoMo Plus)**:原生 PowerContext 对比检索后用 Jev 做候选筛选。每组模型同时用于 Memory 抽取与回答,向量模型为 `qwen3.7-text-embedding`(1024 维):
| 模型 | PowerContext | PowerContext + Jev | 差值 |
@@ -133,12 +127,7 @@ PowerContext 把可长期复用的项目上下文放在单次对话之外。一
Agent 输入 → Source 证据 → Memory / Topic Memory → PreparedContext → 新会话或新 Agent
```
-PowerContext 1.5.0 包含交互式配置向导。需要 Python 3.11+、Git 和
-[uv](https://docs.astral.sh/uv/getting-started/installation/);需要时 uv 可以按需提供 Python。
-要接入 Agent,还需要安装对应的 CLI。向导中选择“基础记忆”即可在没有模型 API 的情况下显式保存与召回;
-选择“完整记忆能力”则需要独立的 Generation、Embedding API 凭据。只想启动不依赖模型 API 的本地 Server 时,
-请参考[安装与运行](docs/zh/docs/get-started/install-and-run.md)。
-支持 macOS 和 Linux;Windows 支持为 `experimental`。
+PowerContext 1.5.0 包含交互式配置向导。需要 Python 3.11+、Git 和 [uv](https://docs.astral.sh/uv/getting-started/installation/);需要时 uv 可以按需提供 Python。要接入 Agent,还需要安装对应的 CLI。向导中选择“基础记忆”即可在没有模型 API 的情况下显式保存与召回;选择“完整记忆能力”则需要独立的 Generation、Embedding API 凭据。只想启动不依赖模型 API 的本地 Server 时,请参考[安装与运行](docs/zh/docs/get-started/install-and-run.md)。支持 macOS 和 Linux;Windows 支持为 `experimental`。需要自动安装 Python 和 uv 时,使用[安装脚本](docs/zh/docs/get-started/install-and-run.md#使用推荐安装脚本)。
请选择与你目标匹配的路径:
@@ -154,13 +143,21 @@ PowerContext 1.5.0 包含交互式配置向导。需要 Python 3.11+、Git 和
uv tool install --force "powercontext[cli,server]==1.5.0"
mkdir -p powercontext-config && cd powercontext-config
powercontext config init --language zh --output .env
-powercontext config validate --env-file .env
-powercontext server run --env-file .env # 保持这个终端不关
```
向导会询问存储、本机或远程使用场景、记忆能力、Dashboard/访问控制和 Agent 连接。首次体验可以使用 SQLite;只有确实需要时再选择嵌入式 seekdb,并同意依赖安装。
-向导生成一个 `.env` 环境文件和 `.env.next-steps.md`。这些文件可能包含凭据,请勿提交到 Git。保持 Server 运行,在另一个终端回到 `powercontext-config` 目录,重新加载生成的环境文件并检查服务:
+向导生成 `.env` 和 `.env.next-steps.md`。这些文件可能包含凭据,请勿提交到 Git。Linux 上的个人服务需要可用的 `systemd --user` 管理器。
+
+保存 `.env` 后,验证配置并注册个人服务:
+
+```bash
+powercontext config validate --env-file .env
+powercontext service install --env-file .env
+powercontext service status
+```
+
+服务会在关闭终端后继续运行 Server。加载生成的环境文件并检查服务:
```bash
set -a
@@ -170,25 +167,18 @@ powercontext ready
powercontext capabilities
```
-接着按 `.env.next-steps.md` 创建并绑定 Scope。使用创建请求返回的真实 `scope_id`;向导规划的 `codex-...` 标题不是 ID。
-切换目录不会创建隔离;Dashboard 和 Agent 要使用同一个 Server 和 Scope。然后安装相同版本的插件,再打开新 Agent 会话。
-[完整安装流程](https://powercontext.oceanbase.io/zh/docs/get-started/quickstart/)包含 Codex、Claude Code、Dashboard 登录、SSH 隧道、HTTPS 前提及逐项验收。例如,匹配本版本的 Codex 安装命令是:
+接着按 `.env.next-steps.md` 创建并绑定 Scope。使用创建请求返回的真实 `scope_id`;向导规划的 `codex-...` 标题不是 ID。切换目录不会创建隔离;Dashboard 和 Agent 要使用同一个 Server 和 Scope。修改 `.env` 后,重新执行 `powercontext service install --env-file .env` 使修改后的配置生效。然后安装相同版本的插件,再打开新 Agent 会话。[完整安装流程](https://powercontext.oceanbase.io/zh/docs/get-started/quickstart/)包含 Codex、Claude Code、Dashboard 登录、SSH 隧道、HTTPS 前提及逐项验收。例如,匹配本版本的 Codex 安装命令是:
```bash
powercontext setup codex --ref powercontext-v1.5.0
powercontext doctor codex
```
-在新 Codex 会话发送第一条消息前,打开 `/hooks` 检查 PowerContext Hook,并在收到提示时授予信任。
-`UserPromptSubmit` Hook 未启用时,即使 MCP 连通,也不会自动采集输入或召回上下文。
-检查与恢复步骤见[首次会话 Hook 信任](docs/zh/docs/integrations/codex.md#首次会话-hook-信任)。
-完成下面的首次验收后,可按[能力与触发指南](docs/zh/docs/get-started/quickstart.md#5-选择下一项工作流)
-体验七类 Artifact、Source 和 Tag。基础记忆无需独立的 Server 模型 API;Agent 仍使用自己的模型。
+在新 Codex 会话发送第一条消息前,打开 `/hooks` 检查 PowerContext Hook,并在收到提示时授予信任。`UserPromptSubmit` Hook 未启用时,即使 MCP 连通,也不会自动采集输入或召回上下文。检查与恢复步骤见[首次会话 Hook 信任](docs/zh/docs/integrations/codex.md#首次会话-hook-信任)。完成下面的首次验收后,可按[能力与触发指南](docs/zh/docs/get-started/quickstart.md#5-选择下一项工作流) 体验七类 Artifact、Source 和 Tag。基础记忆无需独立的 Server 模型 API;Agent 仍使用自己的模型。
### 2. 验收第一条端到端结果
-这一步检查 PowerContext 真正有用的链路:Source 采集 → 后台处理 → 跨会话召回。
-在绑定了同一 Scope 的新 Agent 会话中,发送一条具体决定,例如:
+这一步检查 PowerContext 真正有用的链路:Source 采集 → 后台处理 → 跨会话召回。在绑定了同一 Scope 的新 Agent 会话中,发送一条具体决定,例如:
```text
我们正在设计 Aurora 验收项目。决定用 uv 管理 Python 依赖,配置文件使用 .env。
@@ -202,8 +192,7 @@ powercontext doctor codex
3. 发送一条相关更新,确认新的 Source 入库,且主题内容或修订记录反映了更新。
4. 使用同一 Scope 开启新的 Agent 会话,要求它带引用找回 Aurora 的决定。
-`doctor codex` 只检查安装与连接。Source 采集本身不会创建 Memory 或 Topic Memory,后台处理还要等待配置的调度和模型运行。
-基础记忆模式下,可以显式要求 Agent 保存一条决定,再在新会话中搜索;这不能验收自动提取。
+`doctor codex` 只检查安装与连接。Source 采集本身不会创建 Memory 或 Topic Memory,后台处理还要等待配置的调度和模型运行。基础记忆模式下,可以显式要求 Agent 保存一条决定,再在新会话中搜索;这不能验收自动提取。
### 3. 先用 Python 体验
@@ -214,9 +203,7 @@ uv sync --locked --group notebooks
uv run --locked --group notebooks jupyter lab --notebook-dir=examples/jupyter
```
-先从 [01 · 让新会话用上项目约定](examples/jupyter/01_memory_across_sessions.ipynb)开始,再通过
-[22 篇中文 Jupyter 教程与完整团队工作流](examples/jupyter/README.md)亲手运行 Memory、上下文、交接、Experience、Skill
-和真实 Agent。前七篇不需要模型或 API Key。
+先从 [01 · 让新会话用上项目约定](examples/jupyter/01_memory_across_sessions.ipynb)开始,再通过 [22 篇中文 Jupyter 教程与完整团队工作流](examples/jupyter/README.md)亲手运行 Memory、上下文、交接、Experience、Skill 和真实 Agent。前七篇不需要模型或 API Key。
### 常见问题
@@ -263,8 +250,7 @@ make check
make test
```
-完整开发流程请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。
-编程能力评测、记忆质量评测、性能压测和 Skill 回归统一放在 [`evaluation/`](evaluation/README.md),按用途选择对应目录。
+完整开发流程请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。编程能力评测、记忆质量评测、性能压测和 Skill 回归统一放在 [`evaluation/`](evaluation/README.md),按用途选择对应目录。
## 进一步了解
@@ -284,8 +270,7 @@ PowerContext 是 [PowerMem](https://www.powermem.ai/) 的后续项目。
-[查看全部贡献者](https://github.com/oceanbase/powercontext/graphs/contributors) ·
-[参与贡献](CONTRIBUTING.md)
+[查看全部贡献者](https://github.com/oceanbase/powercontext/graphs/contributors) · [参与贡献](CONTRIBUTING.md)
## 许可证
diff --git a/README_JP.md b/README_JP.md
index 89e59cc8a4..67369c893a 100644
--- a/README_JP.md
+++ b/README_JP.md
@@ -15,8 +15,7 @@
**PowerContext は、AI Agent 向けのオープンソース・コンテキストサービスです。** 作業のうち長く使う価値のある部分 —— 確定した判断、制約、根拠、進捗 —— を単一の会話の外に置きます。これにより、次のセッション、次の Agent、あるいは次の担当者が、記録をすべて読み返すのではなく現在の状態から続けられます。
-PowerContext は、そのまま使える Server としても、同じ契約を別のアプリケーションに組み込む形でも利用できます。
-まずは次の 3 層で理解すると分かりやすくなります。
+PowerContext は、そのまま使える Server としても、同じ契約を別のアプリケーションに組み込む形でも利用できます。まずは次の 3 層で理解すると分かりやすくなります。
| レイヤー | 担当 | 使う場面 |
| --- | --- | --- |
@@ -72,8 +71,7 @@ Experience は、状況・行動・観察された結果・再利用可能な教
### デスクトップ — 接続したワークスペースでプロジェクトの記憶を確認する
-Server に接続して正確な Scope を選択すると、その記憶を検索できます。検索結果を開いて保存済みの特定リビジョンを読み、次のセッションに向けたメモも保存できます。
-[デスクトッププレビュー](desktop/README.md)は内部検証用です。署名付き配布と各プラットフォームのリリース認定は、別途検証が必要です。
+Server に接続して正確な Scope を選択すると、その記憶を検索できます。検索結果を開いて保存済みの特定リビジョンを読み、次のセッションに向けたメモも保存できます。[デスクトッププレビュー](desktop/README.md)は内部検証用です。署名付き配布と各プラットフォームのリリース認定は、別途検証が必要です。

@@ -127,7 +125,7 @@ PowerContext は、長く使うプロジェクトのコンテキストを 1 つ
Agent の入力 → Source の証拠 → Memory / Topic Memory → PreparedContext → 新しいセッションまたは Agent
```
-PowerContext 1.5.0 には対話式セットアップウィザードが含まれています。Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) が必要です。必要に応じて uv が Python を用意できます。Agent に接続するには、その CLI もインストールしてください。ウィザードでは、モデル API なしで明示的な保存と想起を試す場合は Basic memory、自動抽出を有効にする場合は Full memory を選択してください。Full memory には Generation と Embedding API の個別の認証情報が必要です。モデル API なしでローカル Server だけを起動する場合は[インストールと実行](docs/en/docs/get-started/install-and-run.md)を参照してください。macOS と Linux をサポートし、Windows のサポートは `experimental` です。
+PowerContext 1.5.0 には対話式セットアップウィザードが含まれています。Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) が必要です。必要に応じて uv が Python を用意できます。Agent に接続するには、その CLI もインストールしてください。ウィザードでは、モデル API なしで明示的な保存と想起を試す場合は Basic memory、自動抽出を有効にする場合は Full memory を選択してください。Full memory には Generation と Embedding API の個別の認証情報が必要です。モデル API なしでローカル Server だけを起動する場合は[インストールと実行](docs/en/docs/get-started/install-and-run.md)を参照してください。macOS と Linux をサポートし、Windows のサポートは `experimental` です。Python と uv を自動で用意する場合は、[インストールスクリプト](docs/en/docs/get-started/install-and-run.md#install-with-the-recommended-script)を使ってください。
目的に合う経路を選択してください:
@@ -143,13 +141,21 @@ PowerContext 1.5.0 には対話式セットアップウィザードが含まれ
uv tool install --force "powercontext[cli,server]==1.5.0"
mkdir -p powercontext-config && cd powercontext-config
powercontext config init --language en --output .env
-powercontext config validate --env-file .env
-powercontext server run --env-file .env # このターミナルは開いたままにする
```
ウィザードでは、ストレージ、ローカルまたはリモート利用、メモリ機能、Dashboard/アクセス制御、Agent 接続を選択します。最初は SQLite が簡単です。組み込み seekdb は必要な場合だけ選び、依存関係のインストールを許可してください。
-ウィザードは `.env` と `.env.next-steps.md` を生成します。これらのファイルには認証情報が含まれることがあるため、コミットしないでください。Server を起動したまま、別のターミナルで `powercontext-config` に戻り、生成された環境ファイルを読み込んで接続を確認します。
+ウィザードは `.env` と `.env.next-steps.md` を生成します。これらのファイルには認証情報が含まれることがあるため、コミットしないでください。Linux のユーザーサービスには利用可能な `systemd --user` マネージャーが必要です。
+
+`.env` を保存してから、設定を検証してユーザーサービスを登録します。
+
+```bash
+powercontext config validate --env-file .env
+powercontext service install --env-file .env
+powercontext service status
+```
+
+サービスはターミナルを閉じた後も Server を実行します。生成された環境ファイルを読み込んで接続を確認します。
```bash
set -a
@@ -159,25 +165,18 @@ powercontext ready
powercontext capabilities
```
-続いて `.env.next-steps.md` の手順に従い、Scope を作成して紐付けます。作成リクエストが返す実際の `scope_id` を使ってください。
-計画された `codex-...` タイトルは ID ではありません。ディレクトリを変えても分離は作られないため、Dashboard と Agent で同じ Server と
-Scope を使います。その後、対応するプラグインをインストールして新しい Agent セッションを起動します。[完全な手順](https://powercontext.oceanbase.io/en/docs/get-started/quickstart/)では、Codex と Claude Code、Dashboard へのログイン、SSH 転送、HTTPS の前提条件、確認可能な受け入れチェックを扱っています。対応する Codex のインストールは次のとおりです。
+続いて `.env.next-steps.md` の手順に従い、Scope を作成して紐付けます。作成リクエストが返す実際の `scope_id` を使ってください。計画された `codex-...` タイトルは ID ではありません。ディレクトリを変えても分離は作られないため、Dashboard と Agent で同じ Server と Scope を使います。`.env` を編集したら、`powercontext service install --env-file .env` を再実行してサービスの設定を反映します。その後、対応するプラグインをインストールして新しい Agent セッションを起動します。[完全な手順](https://powercontext.oceanbase.io/en/docs/get-started/quickstart/)では、Codex と Claude Code、Dashboard へのログイン、SSH 転送、HTTPS の前提条件、確認可能な受け入れチェックを扱っています。対応する Codex のインストールは次のとおりです。
```bash
powercontext setup codex --ref powercontext-v1.5.0
powercontext doctor codex
```
-新しい Codex セッションで最初のメッセージを送る前に、`/hooks` で PowerContext Hook を確認し、求められたら信頼を許可してください。
-`UserPromptSubmit` Hook が無効な場合、MCP に接続できても入力の自動取得と想起は動作しません。
-[初回確認と復旧](docs/en/docs/integrations/codex.md#first-session-hook-trust)を参照してください。
-以下の確認後は、[機能と操作のガイド](docs/en/docs/get-started/quickstart.md#5-choose-your-next-workflow)から
-7 種類の Artifact、Source、Tag を試せます。Basic memory は Server 用の独立したモデル API が不要ですが、Agent 自身のモデルは使用します。
+新しい Codex セッションで最初のメッセージを送る前に、`/hooks` で PowerContext Hook を確認し、求められたら信頼を許可してください。`UserPromptSubmit` Hook が無効な場合、MCP に接続できても入力の自動取得と想起は動作しません。[初回確認と復旧](docs/en/docs/integrations/codex.md#first-session-hook-trust)を参照してください。以下の確認後は、[機能と操作のガイド](docs/en/docs/get-started/quickstart.md#5-choose-your-next-workflow)から 7 種類の Artifact、Source、Tag を試せます。Basic memory は Server 用の独立したモデル API が不要ですが、Agent 自身のモデルは使用します。
### 2. 最初のエンドツーエンド結果を確認する
-この確認では、PowerContext が役立つ流れを検証します:Source の取得 → バックグラウンド処理 → セッションをまたぐ想起。
-同じ Scope に紐付けた新しい Agent セッションで、次のような具体的な決定を送ります。
+この確認では、PowerContext が役立つ流れを検証します:Source の取得 → バックグラウンド処理 → セッションをまたぐ想起。同じ Scope に紐付けた新しい Agent セッションで、次のような具体的な決定を送ります。
```text
Aurora の受け入れプロジェクトを設計しています。Python の依存関係には uv、設定には .env を使います。
@@ -185,16 +184,14 @@ Aurora の受け入れプロジェクトを設計しています。Python の依
これらの制約を変えずに言い直してください。コードは変更しないでください。
```
-Dashboard を有効にした場合は、ウィザードが表示した URL を開いてください。取得前に Dashboard が空なのは正常です。
-その後、次を確認します。
+Dashboard を有効にした場合は、ウィザードが表示した URL を開いてください。取得前に Dashboard が空なのは正常です。その後、次を確認します。
1. 入力が Source として表示されること。
2. 設定した検査間隔とモデル処理の後、関連する Topic Memory が現れること。
3. 関連する更新を送り、新しい Source が入り、トピックの内容またはリビジョン履歴に反映されること。
4. 同じ Scope で新しい Agent セッションを開始し、Aurora の決定を引用付きで探すこと。
-`doctor codex` はインストールと接続だけを確認します。Source の取得だけでは Memory や Topic Memory は作成されず、バックグラウンド処理には設定したスケジュールとモデルの実行が必要です。
-Basic memory では Agent に決定を明示的に保存させ、新しいセッションで検索してください。ただし、これは自動抽出の検証ではありません。
+`doctor codex` はインストールと接続だけを確認します。Source の取得だけでは Memory や Topic Memory は作成されず、バックグラウンド処理には設定したスケジュールとモデルの実行が必要です。Basic memory では Agent に決定を明示的に保存させ、新しいセッションで検索してください。ただし、これは自動抽出の検証ではありません。
### 3. Python で最初に試す
@@ -205,8 +202,7 @@ uv sync --locked --group notebooks
uv run --locked --group notebooks jupyter lab --notebook-dir=examples/jupyter
```
-まず [01 · Memory across sessions](examples/jupyter/01_memory_across_sessions.ipynb) を実行し、その後 [22 本の Jupyter チュートリアル(中国語)](examples/jupyter/README.md)で、Memory、コンテキストの準備、Handoff、Experience、Skill、実際の Agent を動かしながら学べます。
-最初の 7 本はモデルや API キーなしで実行できます。
+まず [01 · Memory across sessions](examples/jupyter/01_memory_across_sessions.ipynb) を実行し、その後 [22 本の Jupyter チュートリアル(中国語)](examples/jupyter/README.md)で、Memory、コンテキストの準備、Handoff、Experience、Skill、実際の Agent を動かしながら学べます。最初の 7 本はモデルや API キーなしで実行できます。
### よくある問題
@@ -253,8 +249,7 @@ make check
make test
```
-開発ワークフロー全体については [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
-コーディング評価、メモリ品質評価、性能ベンチマーク、Skill 回帰テストは [`evaluation/`](evaluation/README.md) にまとめています。
+開発ワークフロー全体については [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。コーディング評価、メモリ品質評価、性能ベンチマーク、Skill 回帰テストは [`evaluation/`](evaluation/README.md) にまとめています。
## さらに詳しく
@@ -274,8 +269,7 @@ PowerContext に貢献してくださる皆さまに感謝します。❤️
-[すべてのコントリビューターを見る](https://github.com/oceanbase/powercontext/graphs/contributors) ·
-[開発に参加する](CONTRIBUTING.md)
+[すべてのコントリビューターを見る](https://github.com/oceanbase/powercontext/graphs/contributors) · [開発に参加する](CONTRIBUTING.md)
## ライセンス
diff --git a/docs/en/docs/get-started/install-and-run.md b/docs/en/docs/get-started/install-and-run.md
index e4bb7e0ffd..f7306ea37a 100644
--- a/docs/en/docs/get-started/install-and-run.md
+++ b/docs/en/docs/get-started/install-and-run.md
@@ -5,11 +5,7 @@ description: Install PowerContext 1.5.0 and run the local Server.
# Install and run
-For an Agent connecting from another machine, see [Connect to a remote Server](../operate/connect-remote-server.md)
-for guided URL confirmation, unattended setup, and endpoint-bound HTTP consent.
-
-Start with the [Quick Start](quickstart.md) for your first session. This page covers version selection,
-platforms, installation roles, startup, diagnostics, and updates.
+Use this guide to install PowerContext and run a personal Server. For your first Agent session, follow the [Quick Start](quickstart.md). To connect to an existing Server, use the [remote connection guide](../operate/connect-remote-server.md).
## Platform support
@@ -18,27 +14,148 @@ platforms, installation roles, startup, diagnostics, and updates.
| macOS, Linux | Supported |
| Windows | `experimental` |
-Windows CLI, Server, and personal-service support is experimental. Each Agent Host still has its own platform
-requirements. Examples using Bash syntax require a Bash environment and cannot be pasted directly into PowerShell.
-Embedded seekDB is unavailable on Windows.
+Windows CLI, Server, and personal-service support is experimental. Each Agent host has its own requirements. Embedded seekDB is unavailable on Windows. Bash examples below require a Bash shell.
+
+## Install with the recommended script
+
+The script installs the CLI and local Server in an isolated uv tool environment. It reuses compatible Python and uv, or installs the missing prerequisites in your user directory. You do not need administrator privileges.
+
+macOS or Linux needs Bash and `curl` or `wget`; Windows needs PowerShell 5.1 or newer.
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash
+```
+
+```powershell tab="Windows"
+powershell -ExecutionPolicy Bypass -c "& ([scriptblock]::Create((irm https://powercontext.oceanbase.io/install.ps1)))"
+```
+
+With `wget`, use `wget -qO- https://powercontext.oceanbase.io/install.sh | bash`.
-## Choose a version
+Run the PATH command printed at completion before using `powercontext` in your terminal. The script prints how to make the installed commands available; it leaves shell startup files and the persistent Windows PATH unchanged.
-These instructions use PowerContext 1.5.0. Keep the package and Agent integration on
-the same version: package `1.5.0` and Git tag `powercontext-v1.5.0`.
+The default installs the latest stable release and local Server dependencies. After installation, continue with the personal-service steps below. For a remote Server, use `--profile client` and follow the [remote connection guide](../operate/connect-remote-server.md).
+
+## Configure and install a personal service
+
+On macOS or Linux, configure a local Server:
+
+```bash
+powercontext config init --output .env
+```
+
+Choose local usage and loopback binding in the wizard. Save `.env` before continuing. If you cancel, stop here. Linux needs an available `systemd --user` manager. For Windows service setup and login startup, follow [Deploy the Server](../operate/deploy-server.md).
+
+Validate the saved file and register the service:
+
+```bash
+powercontext config validate --env-file .env
+powercontext service install --env-file .env
+powercontext service status
+```
+
+The service keeps the Server running after you close the terminal. Load the client settings and check the connection:
+
+```bash
+set -a
+. ./.env
+set +a
+powercontext ready
+powercontext capabilities
+```
+
+If you edit `.env`, including adding Scope IDs, rerun `powercontext service install --env-file .env` and reload the client settings. Use the original file path if you saved it elsewhere. The [Quick Start](quickstart.md) explains Scope creation and how to verify memory from an Agent session.
+
+For development, debugging, or systems without a native user-service manager, use the [foreground Server instructions](#run-the-local-server).
+
+## Install Agent integrations
+
+Install each Agent's CLI first, then add its PowerContext integration. For Codex:
+
+```bash
+powercontext setup codex --ref "powercontext-v$(powercontext --version)"
+```
+
+This uses the tag matching your installed PowerContext version. Run `powercontext setup --ref "powercontext-v$(powercontext --version)"` to choose hosts interactively. Follow the selected [integration guide](../integrations/index.md) to check the connection and enable its hooks.
+
+To install an integration with the package, pass `--host codex` to the installer. Repeat `--host` for multiple hosts; this needs Git and each host's prerequisites. If you already have a connection file, pass `--env-file PATH` with the selected hosts. The script passes that file to `setup`. Without `--host`, installation skips integrations; `--no-hosts` states this explicitly and cannot be combined with `--host`. See the [capability matrix](../integrations/capabilities.md) for supported hosts.
+
+## Installer version selection
+
+The default `--version latest` installs or upgrades to the newest compatible stable release on the selected index. To install PowerContext 1.5.0, choose the command for your platform:
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --version 1.5.0
+```
+
+```powershell tab="Windows"
+irm https://powercontext.oceanbase.io/install.ps1 -OutFile install.ps1
+powershell -ExecutionPolicy Bypass -File .\install.ps1 --version 1.5.0
+```
+
+You can also request an explicit prerelease, such as `1.3.0rc1`. If the selected index lacks that version, installation fails and preserves the installed tool. `latest` excludes prereleases. A mirror may lag PyPI, so its latest available release may differ.
+
+## Retry dependency downloads with a mirror
+
+Choose the China mirrors with `--region cn`:
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --region cn
+```
+
+```powershell tab="Windows"
+irm https://powercontext.oceanbase.io/install.ps1 -OutFile install.ps1
+powershell -ExecutionPolicy Bypass -File .\install.ps1 --region cn
+```
+
+`--region auto|cn|global` overrides `POWERCONTEXT_INSTALL_REGION`. Without an explicit region, the script uses your named timezone, then locale territory, then global sources. Use `--region global` to select global defaults.
+
+For a specific download problem, set the corresponding source. A package index setting does not change where Python or uv is downloaded:
+
+| Download | Explicit setting | Automatic China source | Global source |
+| --- | --- | --- | --- |
+| PowerContext and Python packages | `--index-url URL`, uv index variables or `uv.toml` | Tsinghua PyPI mirror | PyPI |
+| uv installer | `POWERCONTEXT_UV_INSTALLER_URL` | USTC uv release mirror | Astral installer |
+| uv binaries | `UV_DOWNLOAD_URL` or `UV_INSTALLER_GITHUB_BASE_URL` | USTC uv release mirror | Astral release channels |
+| Python distributions | `UV_PYTHON_INSTALL_MIRROR` | NJU python-build-standalone mirror | uv default channels |
+
+Existing uv settings take precedence over automatic mirrors. `--index-url` changes the default package index; additional uv indexes keep their priority. Store private-index credentials in uv's authentication configuration. uv does not read `PIP_INDEX_URL` or `PIP_EXTRA_INDEX_URL`. Existing Python mirror settings and `uv.toml` also suppress automatic Python mirrors. `UV_ASTRAL_MIRROR_URL` is passed to uv versions that support it.
+
+An unavailable automatic mirror can fall back to an official source. A reachable package mirror that lacks the requested version returns an installation error. Select `--region global` or an explicit index to retry elsewhere. Explicit sources stay selected when they fail.
+
+## Recover a failed installation
+
+Use the error reported by the installer to choose the next step:
+
+| Failure | What to do |
+| --- | --- |
+| Python or uv download failed | Check the download setting above and rerun the installer |
+| Package resolution or download failed | Check the selected index and requested version, then retry |
+| Package installed, command verification failed | Review the named command error and rerun with a known working `--version`; the previous executable may already have been replaced |
+| Agent integration setup failed | Keep the installed PowerContext and retry `powercontext setup` with the release tag printed in the error |
+
+For cached reinstallation, set `UV_OFFLINE=1` only when uv, compatible Python, and all required packages are available. Missing downloads fail in offline mode.
+
+To choose installation locations, use `UV_INSTALL_DIR` for a missing uv and `UV_TOOL_BIN_DIR` for the PowerContext commands. Reused uv versions and your persistent package-manager settings remain unchanged.
+
+## Install manually with uv
+
+If you already manage Python and uv, you can install a fixed release directly. Choose a version, then install the required package extras.
+
+### Choose a version
+
+These instructions use PowerContext 1.5.0. Keep the package and Agent integration on the same version: package `1.5.0` and Git tag `powercontext-v1.5.0`.
```bash
uv tool install --force "powercontext[cli,server]==1.5.0"
powercontext setup codex --ref powercontext-v1.5.0
```
-Check the [capability matrix](../integrations/capabilities.md) for host support and maintenance status.
-Capabilities marked `experimental` remain experimental in this release.
+Check the [capability matrix](../integrations/capabilities.md) for host support and maintenance status. Capabilities marked `experimental` remain experimental in this release.
-## Install the application
+### Install the application
-You need Python 3.11 or newer, Git, and [`uv`](https://docs.astral.sh/uv/) on macOS, Linux, or Windows. Then install
-PowerContext from PyPI:
+You need Python 3.11 or newer, Git, and [`uv`](https://docs.astral.sh/uv/) on macOS, Linux, or Windows. Then install PowerContext from PyPI:
```bash
uv tool install --force "powercontext[cli,server]==1.5.0"
@@ -50,20 +167,16 @@ For a source installation of the same version:
uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.5.0"
```
-For a machine that only connects to a remote Server, the `cli` extra includes `setup`, `doctor`, and remote content
-commands. Add `server` to run a local Server or use the `service` commands. If a command provider has a missing
-dependency, the CLI reports the provider and missing module so the installation can be repaired.
+For a machine that only connects to a remote Server, the `cli` extra includes `setup`, `doctor`, and remote content commands. Add `server` to run a local Server or use the `service` commands. If a command provider has a missing dependency, the CLI reports the provider and missing module so the installation can be repaired.
-The Git command does not leave a repository checkout for you to manage. Git uses its normal credential configuration,
-including credential helpers and SSH settings. For an SSH-based install, replace the HTTPS URL with the Git URL
-approved for your environment. `--force` also refreshes an existing tool from the current commit behind the selected
-Git ref; without it, `uv` may report the same requirement as already installed without fetching a newer `master`.
+The Git command does not leave a repository checkout for you to manage. Git uses its normal credential configuration, including credential helpers and SSH settings. For an SSH-based install, replace the HTTPS URL with the Git URL approved for your environment. `--force` also refreshes an existing tool from the current commit behind the selected Git ref; without it, `uv` may report the same requirement as already installed without fetching a newer `master`.
-To install another branch or tag, replace the ref after the final `@`. The `master` branch can include unreleased changes.
-Follow the [guide for each integration](../integrations/index.md) for Agent installation, connection options, and verification, using the same ref as the Server.
+To install another branch or tag, replace the ref after the final `@`. The `master` branch can include unreleased changes. Follow the [guide for each integration](../integrations/index.md) for Agent installation, connection options, and verification, using the same ref as the Server.
## Run the local Server
+Use this foreground mode for development, debugging, or temporary use. Stop an existing personal service before starting another Server on the same address.
+
```bash
powercontext server run
```
@@ -78,9 +191,7 @@ Without environment variables or an environment file, the Server:
`Ctrl-C` performs a clean shutdown. Restarting the command reopens the same database.
-The Dashboard is an optional content viewer for personal use and demonstrations. It is disabled by default and needs
-no separate frontend installation or model configuration. To enable it locally without a token, save these settings
-in an environment file:
+The Dashboard is an optional content viewer for personal use and demonstrations. It is disabled by default and needs no separate frontend installation or model configuration. To enable it locally without a token, save these settings in an environment file:
```dotenv
POWERCONTEXT_SERVER_HTTP_HOST=127.0.0.1
@@ -88,9 +199,7 @@ POWERCONTEXT_SERVER_DASHBOARD_ENABLED=true
POWERCONTEXT_SERVER_ACCESS_MODE=disabled
```
-To require authentication, set `POWERCONTEXT_SERVER_ACCESS_MODE=enforced` and set `POWERCONTEXT_SERVER_AUTH_TOKEN` to
-your own long random credential. The [configuration wizard](configure-server-environment.md#local-dashboard-and-optional-authentication)
-also offers this choice when enabling Dashboard locally.
+To require authentication, set `POWERCONTEXT_SERVER_ACCESS_MODE=enforced` and set `POWERCONTEXT_SERVER_AUTH_TOKEN` to your own long random credential. The [configuration wizard](configure-server-environment.md#local-dashboard-and-optional-authentication) also offers this choice when enabling Dashboard locally.
```bash
chmod 600 /path/to/powercontext.env
@@ -98,39 +207,25 @@ powercontext config validate --env-file /path/to/powercontext.env
powercontext server run --env-file /path/to/powercontext.env
```
-Open `http://127.0.0.1:8000/dashboard/home`, using the actual port if you change it. With authentication disabled, the
-page opens directly. When enabled, sign in with the Server token and configure connected Agents to use it for API
-and MCP requests. The CLI does not automatically load a directory's `.env` file.
+Open `http://127.0.0.1:8000/dashboard/home`, using the actual port if you change it. With authentication disabled, the page opens directly. When enabled, sign in with the Server token and configure connected Agents to use it for API and MCP requests. The CLI does not automatically load a directory's `.env` file.
-The first visit selects the Server default Scope. Pages are empty until content is saved. Save a Memory through an
-Agent or public API, then refresh Memories in the same Scope. Experiences, skills, handoffs, and usage also come from
-saved records. The Dashboard does not capture sessions, run generation, or approve candidates. The Dashboard and Agent
-must use the same Server and Scope.
+The first visit selects the Server default Scope. Pages are empty until content is saved. Save a Memory through an Agent or public API, then refresh Memories in the same Scope. Experiences, skills, handoffs, and usage also come from saved records. The Dashboard does not capture sessions, run generation, or approve candidates. The Dashboard and Agent must use the same Server and Scope.
-Open **Profile** to read the saved profile, inspect **Version history**, or verify its sources. Selecting a historical
-revision does not change the current profile. In **Handoff**, use **Export Markdown** on a collection entry or its
-detail page to download that exact revision, including its full text, omissions, and citations. If sign-in expires,
-sign in again to return to the selected detail, then repeat the download.
+Open **Profile** to read the saved profile, inspect **Version history**, or verify its sources. Selecting a historical revision does not change the current profile. In **Handoff**, use **Export Markdown** on a collection entry or its detail page to download that exact revision, including its full text, omissions, and citations. If sign-in expires, sign in again to return to the selected detail, then repeat the download.
-When authentication is enabled, all token holders use one identity. Multi-user RBAC deployments should leave the
-Dashboard disabled and use the API, MCP, or host integrations. See [Deploy the Server](../operate/deploy-server.md)
-for network and credential configuration.
+When authentication is enabled, all token holders use one identity. Multi-user RBAC deployments should leave the Dashboard disabled and use the API, MCP, or host integrations. See [Deploy the Server](../operate/deploy-server.md) for network and credential configuration.
-This minimal launch does not enable model-backed extraction or vector search. To generate and validate one explicit
-environment file for those capabilities, continue with the
-[Enable extraction and vector search](configure-models.md).
+This minimal launch does not enable model-backed extraction or vector search. To generate and validate one explicit environment file for those capabilities, continue with the [Enable extraction and vector search](configure-models.md).
## Use embedded seekDB
-Embedded seekDB is available on Linux and macOS when a compatible `pylibseekdb` wheel is available. Windows does not
-support this embedded backend. Install or replace the tool with the optional seekDB extra:
+Embedded seekDB is available on Linux and macOS when a compatible `pylibseekdb` wheel is available. Windows does not support this embedded backend. Install or replace the tool with the optional seekDB extra:
```bash
uv tool install --force "powercontext[cli,server,seekdb]==1.5.0"
```
-When switching from SQLite, remove `POWERCONTEXT_SERVER_DATABASE_URL` from the Server process environment. An explicit
-SQLAlchemy database URL is not valid for seekDB. Then select the backend and start the Server:
+When switching from SQLite, remove `POWERCONTEXT_SERVER_DATABASE_URL` from the Server process environment. An explicit SQLAlchemy database URL is not valid for seekDB. Then select the backend and start the Server:
```bash
unset POWERCONTEXT_SERVER_DATABASE_URL
@@ -138,17 +233,9 @@ export POWERCONTEXT_SERVER_DATABASE_KIND=seekdb
powercontext server run
```
-`server run` loads `.env` from the current directory when present. Export values in the shell to override that file,
-pass `--env-file ` to select another file, or pass `--no-env-file` to ignore environment files. Process managers
-and containers should normally provide an explicit environment instead of relying on their working directory.
+`server run` loads `.env` from the current directory when present. Export values in the shell to override that file, pass `--env-file ` to select another file, or pass `--no-env-file` to ignore environment files. Process managers and containers should normally provide an explicit environment instead of relying on their working directory.
-PowerContext defaults to a dedicated `powercontext` database and creates it before initializing application tables.
-To select another database, set `POWERCONTEXT_SERVER_DATABASE_DATABASE=my_context`; a missing database is created,
-and an existing one is reused. Names containing backticks are rejected before initialization.
-Leave `POWERCONTEXT_SERVER_DATABASE_PATH` unset to store
-the instance in the `seekdb` subdirectory of the PowerContext user data directory. If `POWERCONTEXT_HOME` is set, the
-default is `$POWERCONTEXT_HOME/seekdb`; set `POWERCONTEXT_SERVER_DATABASE_PATH` only when a different location is
-required.
+PowerContext defaults to a dedicated `powercontext` database and creates it before initializing application tables. To select another database, set `POWERCONTEXT_SERVER_DATABASE_DATABASE=my_context`; a missing database is created, and an existing one is reused. Names containing backticks are rejected before initialization. Leave `POWERCONTEXT_SERVER_DATABASE_PATH` unset to store the instance in the `seekdb` subdirectory of the PowerContext user data directory. If `POWERCONTEXT_HOME` is set, the default is `$POWERCONTEXT_HOME/seekdb`; set `POWERCONTEXT_SERVER_DATABASE_PATH` only when a different location is required.
In another terminal, verify that the Server and database are ready:
@@ -168,34 +255,17 @@ export POWERCONTEXT_SERVER_DATABASE_DATABASE=test
powercontext server run
```
-Keep the original `POWERCONTEXT_SERVER_DATABASE_PATH` or `POWERCONTEXT_HOME`. The instance directory and database name
-are separate settings: changing either can select different data. Configure services and containers with the same values.
+Keep the original `POWERCONTEXT_SERVER_DATABASE_PATH` or `POWERCONTEXT_HOME`. The instance directory and database name are separate settings: changing either can select different data. Configure services and containers with the same values.
-If no database name was explicitly configured and `test` contains tables named with PowerContext's `pc_` prefix,
-startup stops before creating application tables and asks you to choose a database. It never automatically selects,
-copies, or deletes the old database. Explicitly choose `test` to retain old data, or `powercontext` to use the new database.
-The configuration wizard also asks for the database name; existing users should enter `test`.
+If no database name was explicitly configured and `test` contains tables named with PowerContext's `pc_` prefix, startup stops before creating application tables and asks you to choose a database. It never automatically selects, copies, or deletes the old database. Explicitly choose `test` to retain old data, or `powercontext` to use the new database. The configuration wizard also asks for the database name; existing users should enter `test`.
-To move data, keep using `test` until you have a backup and a validated export/import procedure for your engine version.
-Stop all source writers, provision the target database, export/import the schema and data using that procedure, verify
-identities and relationships, and read the migrated data through PowerContext before switching the configuration.
-Automatic database creation does not perform this migration. Do not assume an arbitrary SQL dump is compatible across
-engine or schema versions; existing schemas remain subject to compatibility checks and explicit schema maintenance.
-Code graph references identify their instance and database: copying relational rows alone does not migrate graph storage.
-Include target Code index rebuilding and queries in your validation; explicitly retaining `test` preserves legacy graph identities.
+To move data, keep using `test` until you have a backup and a validated export/import procedure for your engine version. Stop all source writers, provision the target database, export/import the schema and data using that procedure, verify identities and relationships, and read the migrated data through PowerContext before switching the configuration. Automatic database creation does not perform this migration. Do not assume an arbitrary SQL dump is compatible across engine or schema versions; existing schemas remain subject to compatibility checks and explicit schema maintenance. Code graph references identify their instance and database: copying relational rows alone does not migrate graph storage. Include target Code index rebuilding and queries in your validation; explicitly retaining `test` preserves legacy graph identities.
-After switching, keep the old database without writers for a retention period you choose. Record the source and target
-instance paths and names, backup location, stop-write and cutover times, validation results, and a review date. Before
-manually deleting the source, confirm that no deployment uses it and that a usable backup remains. New writes to the
-target are not copied back to the source, so keeping the source does not provide lossless rollback. PowerContext does
-not automatically migrate data, send retention reminders, compare databases, or delete old databases.
+After switching, keep the old database without writers for a retention period you choose. Record the source and target instance paths and names, backup location, stop-write and cutover times, validation results, and a review date. Before manually deleting the source, confirm that no deployment uses it and that a usable backup remains. New writes to the target are not copied back to the source, so keeping the source does not provide lossless rollback. PowerContext does not automatically migrate data, send retention reminders, compare databases, or delete old databases.
## Use OceanBase
-Connect to an existing OceanBase **MySQL-compatible user tenant**. Prepare the selected database and initialize its
-full Server schema through explicit maintenance before starting the Server. PowerContext does not provision an
-OceanBase instance, tenant, or account. Ensure network access and complete the Server
-installation steps above, then configure the connection:
+Connect to an existing OceanBase **MySQL-compatible user tenant**. Prepare the selected database and initialize its full Server schema through explicit maintenance before starting the Server. PowerContext does not provision an OceanBase instance, tenant, or account. Ensure network access and complete the Server installation steps above, then configure the connection:
```bash
export POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
@@ -203,52 +273,23 @@ export POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
export POWERCONTEXT_SERVER_DATABASE_URL='mysql+aoceanbase://user%40tenant:encoded-password@host:2881/?charset=utf8mb4'
```
-Use the tenant account and the actual direct-connection or OBProxy endpoint and port. Percent-encode reserved characters
-in the URL username and password; for example, `user@tenant` becomes `user%40tenant`. The URL requires the
-`mysql+aoceanbase` driver, an explicit port, and `charset=utf8mb4`.
+Use the tenant account and the actual direct-connection or OBProxy endpoint and port. Percent-encode reserved characters in the URL username and password; for example, `user@tenant` becomes `user%40tenant`. The URL requires the `mysql+aoceanbase` driver, an explicit port, and `charset=utf8mb4`.
-The example omits the database name and therefore selects `powercontext`. To select a custom database, put its name in
-the URL path, for example `/team_context?charset=utf8mb4`. Existing URLs with an explicit database continue to select
-that database. Select the database only in the URL path; `db` and `database` query parameters are rejected.
-Database identifiers support 1–64 BMP characters, excluding control characters, slash, backslash, dot, backticks,
-leading/trailing whitespace, and system database names; names are never silently trimmed or renamed by PowerContext. The server's identifier case rules still apply.
-Names containing backticks are rejected during configuration validation, before connecting or creating a database.
-Choose a name such as `powercontext`, `team_context`, or a Chinese name without backticks.
+The example omits the database name and therefore selects `powercontext`. To select a custom database, put its name in the URL path, for example `/team_context?charset=utf8mb4`. Existing URLs with an explicit database continue to select that database. Select the database only in the URL path; `db` and `database` query parameters are rejected. Database identifiers support 1–64 BMP characters, excluding control characters, slash, backslash, dot, backticks, leading/trailing whitespace, and system database names; names are never silently trimmed or renamed by PowerContext. The server's identifier case rules still apply. Names containing backticks are rejected during configuration validation, before connecting or creating a database. Choose a name such as `powercontext`, `team_context`, or a Chinese name without backticks.
-Python SDK initialization first connects to the selected database. If it is usable, no database-creation privileges are required. Only a
-confirmed unknown-database error triggers an instance/tenant connection and `CREATE DATABASE IF NOT EXISTS`, followed
-by a new connection to the selected database and application-table initialization. Authentication and network errors
-do not trigger creation or fallback. If access is denied without proving whether the database exists, the error asks
-an administrator to check both existence and privileges.
+Python SDK initialization first connects to the selected database. If it is usable, no database-creation privileges are required. Only a confirmed unknown-database error triggers an instance/tenant connection and `CREATE DATABASE IF NOT EXISTS`, followed by a new connection to the selected database and application-table initialization. Authentication and network errors do not trigger creation or fallback. If access is denied without proving whether the database exists, the error asks an administrator to check both existence and privileges.
-For a new full Server deployment, create the selected database first. If SDK initialization cannot create a missing
-database, the same administrator step applies. Run the following in the **same tenant**, using your configured name,
-then grant the application account the required database privileges:
+For a new full Server deployment, create the selected database first. If SDK initialization cannot create a missing database, the same administrator step applies. Run the following in the **same tenant**, using your configured name, then grant the application account the required database privileges:
```sql
CREATE DATABASE IF NOT EXISTS `powercontext` CHARACTER SET utf8mb4;
```
-For the full Server, follow [database migrations](../operate/database-migrations.md) to plan, apply, and verify the
-registered Server bundle with an explicit evidence directory and coordination mode. Start `powercontext server run`
-only after the database is ready. Server startup checks readiness and does not initialize OceanBase schemas or
-automatically migrate existing databases. SDK callers can retry initialization using the same URL after provisioning.
-Creating missing tables requires table-creation privileges; an existing fully initialized database does not acquire a
-new database-creation privilege requirement. Initialization never drops or
-recreates an existing database, never switches to another database after a failure, and does not authorize schema upgrades.
-Concurrent creation uses idempotent database/table DDL and handles duplicate-index creation races. This does not provide
-coordination for existing-schema migrations or change embedded seekDB's process-ownership restrictions.
-
-For Python callers, `OceanBaseProfile.open()` resolves an omitted URL database and provisions it when necessary.
-`OceanBaseProfile.open_existing()` connects only to an already provisioned database, and `attach()` requires a
-caller-owned engine that already selects an existing database; neither replaces that engine or provisions a different
-connection target. Read-only inspection (`plan`, `verify`, and the `db-migrate` inspection commands) never creates a
-missing OceanBase or embedded seekDB database; explicit writable seekDB maintenance can provision its configured database
-before applying its bundle.
-
-Read-only seekDB inspection requires an initialized instance directory. A missing or empty path is rejected before
-starting the native engine, without creating parent directories or storage files. A file or a nonempty directory without
-recognized native storage is also rejected without changing its contents. Ordinary startup still initializes new instances.
+For the full Server, follow [database migrations](../operate/database-migrations.md) to plan, apply, and verify the registered Server bundle with an explicit evidence directory and coordination mode. Start `powercontext server run` only after the database is ready. Server startup checks readiness and does not initialize OceanBase schemas or automatically migrate existing databases. SDK callers can retry initialization using the same URL after provisioning. Creating missing tables requires table-creation privileges; an existing fully initialized database does not acquire a new database-creation privilege requirement. Initialization never drops or recreates an existing database, never switches to another database after a failure, and does not authorize schema upgrades. Concurrent creation uses idempotent database/table DDL and handles duplicate-index creation races. This does not provide coordination for existing-schema migrations or change embedded seekDB's process-ownership restrictions.
+
+For Python callers, `OceanBaseProfile.open()` resolves an omitted URL database and provisions it when necessary. `OceanBaseProfile.open_existing()` connects only to an already provisioned database, and `attach()` requires a caller-owned engine that already selects an existing database; neither replaces that engine or provisions a different connection target. Read-only inspection (`plan`, `verify`, and the `db-migrate` inspection commands) never creates a missing OceanBase or embedded seekDB database; explicit writable seekDB maintenance can provision its configured database before applying its bundle.
+
+Read-only seekDB inspection requires an initialized instance directory. A missing or empty path is rejected before starting the native engine, without creating parent directories or storage files. A file or a nonempty directory without recognized native storage is also rejected without changing its contents. Ordinary startup still initializes new instances.
## Verify the installation
@@ -258,14 +299,9 @@ powercontext ready
powercontext capabilities
```
-`doctor` checks the installed package, Server liveness, and Server readiness without requiring an integration. Server
-readiness covers the database and each configured inference provider. Runtime or database failures return
-`not_ready`; an inference failure returns `degraded` without removing database-backed operations from traffic.
-`ready` and `capabilities` show the readiness and enabled capabilities of the running service.
-For Agent diagnostics, use the [guide for each integration](../integrations/index.md). For Server status definitions and recovery steps, see [Troubleshoot](../operate/troubleshoot.md).
+`doctor` checks the installed package, Server liveness, and Server readiness without requiring an integration. Server readiness covers the database and each configured inference provider. Runtime or database failures return `not_ready`; an inference failure returns `degraded` without removing database-backed operations from traffic. `ready` and `capabilities` show the readiness and enabled capabilities of the running service. For Agent diagnostics, use the [guide for each integration](../integrations/index.md). For Server status definitions and recovery steps, see [Troubleshoot](../operate/troubleshoot.md).
-For a long-running process, Docker, authentication, or remote access, continue with
-[Deploy the Server](../operate/deploy-server.md).
+For a long-running process, Docker, authentication, or remote access, continue with [Deploy the Server](../operate/deploy-server.md).
## Update or replace an installation
@@ -273,21 +309,11 @@ For a long-running process, Docker, authentication, or remote access, continue w
Upgrades from 1.3.0 and 1.4.0 require passing full Server acceptance on SQLite, seekDB and OceanBase. These migration paths are not yet verified; do not upgrade existing databases.
-Keep existing deployments on their working version until their source version and backend have an accepted upgrade
-path. To evaluate a fresh installation, use a separate program environment, database location, and port. Replacing
-the installed package does not migrate its database. Backups do not make an unsupported migration supported.
+Keep existing deployments on their working version until their source version and backend have an accepted upgrade path. To evaluate a fresh installation, use a separate program environment, database location, and port. Replacing the installed package does not migrate its database. Backups do not make an unsupported migration supported.
-The support declaration is checked against the release matrix and installed-artifact acceptance; see
-[Preparing a release](../../development/releasing.md). Domain-specific
-[Processing](../operate/artifact-processing-migration.md) and
-[Atomic Memory](../operate/atomic-memory-migration.md) maintenance guides do not establish full Server readiness.
+The support declaration is checked against the release matrix and installed-artifact acceptance; see [Preparing a release](../../development/releasing.md). Domain-specific [Processing](../operate/artifact-processing-migration.md) and [Atomic Memory](../operate/atomic-memory-migration.md) maintenance guides do not establish full Server readiness.
-Review the [Dream coordinated upgrade requirements](../workflows/review-dream-revisions.md#coordinated-upgrade)
-before upgrading an existing database. The unified Candidate routes replace `/v1/artifact-candidates/*` with
-`/v1/candidates/*` without aliases, and Dream Clients require contract 2. Existing Dream storage needs an explicit
-migration after unfinished runs have drained. [Full Server migration](../operate/database-migrations.md) requires
-`state=ready` and `server_ready=true`; partial Artifact or Atomic Memory verification cannot replace it. Do not bypass
-the migration gate.
+Review the [Dream coordinated upgrade requirements](../workflows/review-dream-revisions.md#coordinated-upgrade) before upgrading an existing database. The unified Candidate routes replace `/v1/artifact-candidates/*` with `/v1/candidates/*` without aliases, and Dream Clients require contract 2. Existing Dream storage needs an explicit migration after unfinished runs have drained. [Full Server migration](../operate/database-migrations.md) requires `state=ready` and `server_ready=true`; partial Artifact or Atomic Memory verification cannot replace it. Do not bypass the migration gate.
Server builds with [context reuse and final admission](../operate/context-protection.md) always include
`context_protection` in `/v1/capabilities`, even when protection is disabled. Older Python SDKs reject this unknown
@@ -310,9 +336,7 @@ To install another Git ref in the separate environment:
uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@["
```
-Use the matching ref for clients and Agent integrations connected to the fresh deployment. Preserve the original
-deployment's database location and configuration; do not point the new Server at it as an installation check.
-On Windows, the virtual environment interpreter is at `Scripts/python.exe`.
+Use the matching ref for clients and Agent integrations connected to the fresh deployment. Preserve the original deployment's database location and configuration; do not point the new Server at it as an installation check. On Windows, the virtual environment interpreter is at `Scripts/python.exe`.
## Install a Python role
@@ -322,6 +346,4 @@ An application that imports the async Client SDK should add it to that applicati
uv add "powercontext[client]==1.5.0"
```
-Use `builtin` for in-process Python composition, `server` for the service, `client` for the Python SDK, or `cli` for
-the Server-backed command line. An extra that is only present in the isolated `uv tool` environment is not importable
-by an unrelated Python project.
+Use `builtin` for in-process Python composition, `server` for the service, `client` for the Python SDK, or `cli` for the Server-backed command line. An extra that is only present in the isolated `uv tool` environment is not importable by an unrelated Python project.
diff --git a/docs/en/docs/get-started/quickstart.md b/docs/en/docs/get-started/quickstart.md
index a819d5d449..f7e7700ca9 100644
--- a/docs/en/docs/get-started/quickstart.md
+++ b/docs/en/docs/get-started/quickstart.md
@@ -5,15 +5,9 @@ description: Configure full memory, connect Codex, and verify Source capture, To
# Quick Start
-Start with installation, discuss a project in Codex, watch its input become Source evidence and an evolving topic,
-then recover the decisions in a new session. These instructions use PowerContext 1.5.0,
-with the Agent plugin from the matching `powercontext-v1.5.0` tag.
+Start with installation, discuss a project in Codex, watch its input become Source evidence and an evolving topic, then recover the decisions in a new session. These instructions use PowerContext 1.5.0, with the Agent plugin from the matching `powercontext-v1.5.0` tag.
-You need macOS or Linux, Python 3.11+, Git, [uv](https://docs.astral.sh/uv/getting-started/installation/),
-and an installed Codex CLI. Full memory also needs working Generation and Embedding model APIs:
-prepare their base URLs, model names, and API keys. Signing into a Codex or Claude subscription does not automatically
-provide these APIs to the PowerContext Server. Without separate model APIs, select basic memory to test explicit saves
-and recall; that does not enable automatic Topic Memory.
+You need macOS or Linux, Python 3.11+, Git, [uv](https://docs.astral.sh/uv/getting-started/installation/), and an installed Codex CLI. Full memory also needs working Generation and Embedding model APIs: prepare their base URLs, model names, and API keys. Signing into a Codex or Claude subscription does not automatically provide these APIs to the PowerContext Server. Without separate model APIs, select basic memory to test explicit saves and recall; that does not enable automatic Topic Memory. To provision Python and uv automatically, use the [installation script](install-and-run.md#install-with-the-recommended-script).
## 1. Install and open the wizard
@@ -41,29 +35,21 @@ The wizard writes:
| `.env` | Server, client, Agent, database, and model settings, including any configured credentials |
| `.env.next-steps.md` | Startup, Scope creation, plugin connection, and checks for your choices |
-The final screen shows the Dashboard URL and the SSH command when selected. If authentication is enabled, it also
-shows a newly generated Server token once; later, look up `POWERCONTEXT_SERVER_AUTH_TOKEN` in `.env`.
-These files can contain credentials; do not commit them.
-If seekdb is still installing, the wizard waits with an activity indicator. Complete any reported dependency recovery
-before starting the Server. Saving files or installing dependencies does not start the Server.
+The final screen shows the Dashboard URL and the SSH command when selected. If authentication is enabled, it also shows a newly generated Server token once; later, look up `POWERCONTEXT_SERVER_AUTH_TOKEN` in `.env`. These files can contain credentials; do not commit them. If seekdb is still installing, the wizard waits with an activity indicator. Complete any reported dependency recovery before starting the Server. Saving files or installing dependencies does not start the Server.
## 2. Start the Server
-In this terminal, run:
+Register a personal service so the Server keeps running after you close the terminal. Linux needs an available `systemd --user` manager. Run:
```bash
powercontext config validate --env-file .env
-powercontext server run --env-file .env
+powercontext service install --env-file .env
+powercontext service status
```
-Keep the terminal running. Open the Dashboard URL printed by the wizard, using the port saved as
-`POWERCONTEXT_SERVER_HTTP_PORT` in `.env`. With authentication disabled, the page opens directly. Otherwise, sign in
-with the **Server token**, not a model API key.
-An empty Dashboard is expected before you capture data. For operation after closing the terminal, stop the foreground
-Server and install a [persistent personal service](../operate/deploy-server.md#run-a-persistent-personal-server)
-with `powercontext service install --env-file .env` to reuse the same configuration.
+Open the Dashboard URL printed by the wizard, using the port saved as `POWERCONTEXT_SERVER_HTTP_PORT` in `.env`. With authentication disabled, the page opens directly. Otherwise, sign in with the **Server token**, not a model API key. An empty Dashboard is expected before you capture data. For development, debugging, temporary use, or systems without a native user-service manager, use `powercontext server run --env-file .env`; stop the personal service first if it is running. See [persistent personal services](../operate/deploy-server.md#run-a-persistent-personal-server).
-Open another terminal, load the client settings, and check the running service:
+Load the client settings and check the running service:
```bash
cd ~/powercontext-demo
@@ -74,25 +60,23 @@ powercontext ready
powercontext capabilities
```
-Confirm readiness and the capabilities you selected. A `degraded` result means some dependencies still need attention;
-it is not a successful full-memory check. `config validate` checks configuration, while these commands check the running service.
+Confirm readiness and the capabilities you selected. A `degraded` result means some dependencies still need attention; it is not a successful full-memory check. `config validate` checks configuration, while these commands check the running service.
## 3. Create a Scope and install the Codex plugin
-Open `.env.next-steps.md` and run the request under “Create the planned isolated Scopes”. The Server returns the real
-`scope_id`. Add it to `.env`:
+Open `.env.next-steps.md` and run the request under “Create the planned isolated Scopes”. The Server returns the real `scope_id`. Add it to `.env`:
```dotenv
POWERCONTEXT_CODEX_SCOPE_ID=replace-with-returned-scope-id
```
-The planned `codex-xxxxxxxx` value is a title, not an ID. For Claude Code, put the ID returned by its creation request
-in `POWERCONTEXT_CLAUDE_SCOPE_ID`. Agents can have separate Scopes or explicitly share an existing one.
-Changing directories does not create isolation. Use the same Scope in the Dashboard and Agent during this check.
+The planned `codex-xxxxxxxx` value is a title, not an ID. For Claude Code, put the ID returned by its creation request in `POWERCONTEXT_CLAUDE_SCOPE_ID`. Agents can have separate Scopes or explicitly share an existing one. Changing directories does not create isolation. Use the same Scope in the Dashboard and Agent during this check.
-Reload the client settings and install the matching plugin:
+After editing `.env`, apply the updated service settings, reload the client settings, and install the matching plugin:
```bash
+powercontext service install --env-file .env
+powercontext service status
set -a
. ./.env
set +a
@@ -103,29 +87,19 @@ codex
Before sending the first message in the new session:
-1. Open `/hooks`, inspect the PowerContext hooks, and grant trust when prompted. `SessionStart` resolves the Session Scope;
- `UserPromptSubmit` automatically prepares relevant context and captures your input as a Source.
+1. Open `/hooks`, inspect the PowerContext hooks, and grant trust when prompted. `SessionStart` resolves the Session Scope; `UserPromptSubmit` automatically prepares relevant context and captures your input as a Source.
2. Confirm that PowerContext MCP tools are available. Tool discovery and Hook execution are separate checks.
-3. If you skipped or denied trust, use the [Hook recovery steps](../integrations/codex.md#first-session-hook-trust)
- before testing capture. Without an enabled `UserPromptSubmit` hook, automatic capture and recall do not run.
+3. If you skipped or denied trust, use the [Hook recovery steps](../integrations/codex.md#first-session-hook-trust) before testing capture. Without an enabled `UserPromptSubmit` hook, automatic capture and recall do not run.
-For a non-default address, follow the Codex connection instructions in `.env.next-steps.md`: the installed plugin's
-`.mcp.json` must use the same Server as the Hook, with credentials configured when authentication is enabled.
-Installing the plugin does not start the Server. `doctor codex` checks installation, connection, and tool coverage;
-it does not establish Hook trust, execution, or successful memory processing.
+For a non-default address, follow the Codex connection instructions in `.env.next-steps.md`: the installed plugin's `.mcp.json` must use the same Server as the Hook, with credentials configured when authentication is enabled. Installing the plugin does not start the Server. `doctor codex` checks installation, connection, and tool coverage; it does not establish Hook trust, execution, or successful memory processing.
-These commands use Codex CLI. A desktop app may not inherit terminal environment variables. Before testing in the
-desktop app, verify that both its Hook and MCP receive the same URL and Scope, and credentials when authentication
-is enabled. See [Codex](../integrations/codex.md) and [Claude Code](../integrations/claude-code.md) for host-specific behavior.
+These commands use Codex CLI. A desktop app may not inherit terminal environment variables. Before testing in the desktop app, verify that both its Hook and MCP receive the same URL and Scope, and credentials when authentication is enabled. See [Codex](../integrations/codex.md) and [Claude Code](../integrations/claude-code.md) for host-specific behavior.
-Full memory also needs a generation policy for this real Scope to use Profile. Follow the
-[Profile policy steps](configure-models.md#enable-a-profile-policy-for-the-scope) to read its current version and update it;
-saving the configuration does not perform this operation.
+Full memory also needs a generation policy for this real Scope to use Profile. Follow the [Profile policy steps](configure-models.md#enable-a-profile-policy-for-the-scope) to read its current version and update it; saving the configuration does not perform this operation.
## 4. Verify memory in your selected mode
-Both modes can capture Source evidence when the Hook is enabled. Basic memory needs no separate Server model API;
-the Agent still uses its own model. Choose the check matching your configuration.
+Both modes can capture Source evidence when the Hook is enabled. Basic memory needs no separate Server model API; the Agent still uses its own model. Choose the check matching your configuration.
### Basic memory: explicitly save and retrieve
@@ -142,8 +116,7 @@ Check the actual write result, then open a new session with the same Scope and a
Search PowerContext for Aurora's Python dependency-management decision, with its citation.
```
-Verify the saved content and citation. A conversational acknowledgement is not proof of a write; this check verifies
-explicit save and retrieval, not automatic extraction or Topic Memory generation.
+Verify the saved content and citation. A conversational acknowledgement is not proof of a write; this check verifies explicit save and retrieval, not automatic extraction or Topic Memory generation.
### Full memory: Source processing and Topic Memory
@@ -163,12 +136,9 @@ Select the bound Scope in the Dashboard and check the following:
4. Confirm that the second Source arrived and that topic content or revision history reflects the update. Waiting one minute is not itself a check.
5. Open a new Codex session with the same Scope and ask: “Find Aurora's dependency-management and deployment decisions in PowerContext, with citations.”
-Correct content and its citation complete the capture → evolution → cross-session recall check. A successful
-`doctor codex` only checks installation and connectivity; it does not prove this workflow. The model may merge evidence
-or decide that no update is needed, so not every message creates a new memory.
+Correct content and its citation complete the capture → evolution → cross-session recall check. A successful `doctor codex` only checks installation and connectivity; it does not prove this workflow. The model may merge evidence or decide that no update is needed, so not every message creates a new memory.
-Full memory does not generate every Artifact from every message. Profile, Experience, and Skill have additional
-triggers or review requirements; see [Capability behavior](configure-models.md#full-capabilities-do-not-generate-every-artifact-from-every-message).
+Full memory does not generate every Artifact from every message. Profile, Experience, and Skill have additional triggers or review requirements; see [Capability behavior](configure-models.md#full-capabilities-do-not-generate-every-artifact-from-every-message).
| Check | Evidence of success |
| --- | --- |
@@ -180,10 +150,7 @@ triggers or review requirements; see [Capability behavior](configure-models.md#f
## 5. Choose your next workflow
-The seven Artifact families have different inputs and consumption paths. Source is evidence and Tag is metadata;
-neither is an Artifact family. The examples below are requests, not guaranteed keyword triggers. Use the actual tools
-available in your connection; follow the linked HTTP/CLI workflow for operations without a dedicated MCP tool.
-Read the [Artifact overview](faq.md#artifact-families-at-a-glance) for conceptual differences.
+The seven Artifact families have different inputs and consumption paths. Source is evidence and Tag is metadata; neither is an Artifact family. The examples below are requests, not guaranteed keyword triggers. Use the actual tools available in your connection; follow the linked HTTP/CLI workflow for operations without a dedicated MCP tool. Read the [Artifact overview](faq.md#artifact-families-at-a-glance) for conceptual differences.
| Capability and purpose | Request or starting point | Requirements and result |
| --- | --- | --- |
@@ -199,16 +166,9 @@ Read the [Artifact overview](faq.md#artifact-families-at-a-glance) for conceptua
### Plugin guidance and managed Skills
-The installed plugin includes `powercontext-project-context`, which routes requests to the relevant workflow. It is
-separate from a managed Skill stored in PowerContext. If the routing Skill is absent from the host catalog, check plugin
-installation; a natural-language example does not add missing tools or grant permission.
+The installed plugin includes `powercontext-project-context`, which routes requests to the relevant workflow. It is separate from a managed Skill stored in PowerContext. If the routing Skill is absent from the host catalog, check plugin installation; a natural-language example does not add missing tools or grant permission.
-For a generated managed Skill, inspect the Candidate and obtain an authorized review decision. Approval creates an
-immutable Revision; it does not install or execute it. Follow [Create and export a Skill](../workflows/create-and-export-skill.md)
-to export the exact approved Revision with `powercontext skill export --target codex` to the intended host-local
-`.agents/skills/` directory. Confirm that Codex discovers the exported Skill; restart if it does not appear.
-Direct Artifact writes have their own contract; the Candidate sequence here describes generation and proposal workflows.
-Review authority depends on permissions, not on whether the reviewer is human. Discovery does not grant execution authority.
+For a generated managed Skill, inspect the Candidate and obtain an authorized review decision. Approval creates an immutable Revision; it does not install or execute it. Follow [Create and export a Skill](../workflows/create-and-export-skill.md) to export the exact approved Revision with `powercontext skill export --target codex` to the intended host-local `.agents/skills/` directory. Confirm that Codex discovers the exported Skill; restart if it does not appear. Direct Artifact writes have their own contract; the Candidate sequence here describes generation and proposal workflows. Review authority depends on permissions, not on whether the reviewer is human. Discovery does not grant execution authority.
## Continue
diff --git a/docs/en/rfcs/1892-script-installation.md b/docs/en/rfcs/1892-script-installation.md
new file mode 100644
index 0000000000..8b918dfdcc
--- /dev/null
+++ b/docs/en/rfcs/1892-script-installation.md
@@ -0,0 +1,174 @@
+---
+title: Script installation
+description: Bootstrap Python and uv, install a PowerContext release, and select matching Agent integrations.
+---
+
+- Proposal Name: `script_installation`
+- Start Date: 2026-10-09
+- RFC PR: [oceanbase/powercontext#1892](https://github.com/oceanbase/powercontext/pull/1892)
+- Tracking Issue: [oceanbase/powercontext#1900](https://github.com/oceanbase/powercontext/issues/1900)
+- Related RFCs: [RFC 1733](1733-usability-and-agent-workflows.md), [RFC 1299](1299_local_server_availability_and_service_installation.md)
+
+# Summary
+
+Provide Bash and PowerShell scripts that install PowerContext through uv. The scripts reuse or install Python and uv, select a Runtime profile, and optionally install Agent integrations at the matching release tag. The default package version is the latest stable release on the selected index; users can request an exact version.
+
+Configuration and personal-service registration remain separate CLI operations. The scripts finish with instructions for those steps. User instructions belong in the [installation guide](../docs/get-started/install-and-run.md).
+
+# Motivation
+
+Installing PowerContext requires a compatible Python, uv, the right package extras, and a matching integration ref. Users also need separate source settings for Python packages, uv binaries, and Python distributions. Changing a package index alone cannot fix a failed Python download.
+
+The installer should handle these prerequisites and release selection while reusing the existing configuration, integration, and service commands. This keeps package installation usable without starting a Server or selecting an Agent host.
+
+# Guide-level explanation
+
+## Runtime profiles and prerequisites
+
+The default `local` profile installs the CLI, Client, and local Server dependencies. `--profile client` installs the CLI and Client dependencies for a remote Server. Profile selection and Agent selection are independent.
+
+The installer reuses existing uv and compatible system or uv-managed Python. If either is missing, it installs the prerequisite in a user directory. It runs unattended by default, requires no administrator privileges, and prints the PATH command needed in a new terminal.
+
+## Releases and Agent integrations
+
+`--version latest` selects the newest compatible stable release on the configured index. An exact version gives a repeatable package selection. For example:
+
+```bash
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --version 1.2.0 --host codex
+```
+
+Only explicit `--host` arguments run integration setup. Each selected host requires Git and its own prerequisites. Multiple hosts can be selected. `--no-hosts` explicitly skips integrations and conflicts with `--host`.
+
+The installer reads the installed CLI version and passes its `powercontext-vVERSION` tag to `powercontext setup`. All hosts use that same release ref. `--env-file PATH` passes an existing connection file to host setup; the installer does not parse or source it.
+
+## Configuration and personal services
+
+After installation, users configure a local Server and register a personal service through the existing CLI. The native user-service manager runs the Server after the terminal closes. Client-only installations use the remote connection guide. The [installation guide](../docs/get-started/install-and-run.md#configure-and-install-a-personal-service) documents configuration, service registration, and connection checks.
+
+The installer leaves configuration, Server startup, and service lifecycle to their existing commands. Personal-service constraints, including loopback binding and supported managers, follow [RFC 1299](1299_local_server_availability_and_service_installation.md). Windows support remains experimental.
+
+## Download sources
+
+Package indexes, uv binaries, and Python distributions have separate source settings. Users can select a region or specify sources independently. Explicit settings take precedence over automatic mirrors; an automatic mirror can fall back to an official source as described below.
+
+# Reference-level explanation
+
+## Responsibilities and execution order
+
+The entry points are `website/public/install.sh` and `website/public/install.ps1`, served as static website assets. Both implement the same public options and failure behavior.
+
+| Responsibility | Owner |
+| --- | --- |
+| Validate installer arguments, prepare prerequisites, and sequence installation | Shell script |
+| Resolve packages, select compatible artifacts, and maintain the tool environment | uv |
+| Install selected Agent integrations | Existing `powercontext setup` adapters |
+| Configure and run a Server, diagnose it, and manage a personal service | Existing CLI and service layer |
+
+The scripts perform these steps in order:
+
+1. Validate arguments, including conflicting host options and the existence of a supplied connection file.
+2. Reuse or install uv and Python, then select the package index.
+3. Install the selected package requirement in a uv tool environment.
+4. Locate the installed launcher through `uv tool dir --bin` and verify its version and command availability.
+5. Print the installed version and PATH instructions, then run any selected host setup at that version's tag.
+6. Print local-service or remote-connection guidance. Return a nonzero status if host setup failed.
+
+## Prerequisite reuse
+
+A missing uv is installed at a version maintained by the installer, independently of the PowerContext version. An existing uv is reused without an upgrade.
+
+Python discovery accepts Python 3.11 or newer below 4.0 and excludes virtual environments. If no compatible interpreter is found, uv installs Python 3.12. uv selects the platform, architecture, and artifact. `UV_INSTALL_DIR`, `UV_TOOL_DIR`, and `UV_TOOL_BIN_DIR` retain their location controls.
+
+## Version and profile semantics
+
+| Selection | Package requirement and behavior |
+| --- | --- |
+| `--profile local` (default) | `powercontext[cli,server]` |
+| `--profile client` | `powercontext[cli]`, including Client dependencies |
+| `--version latest` (default) | Unpinned profile requirement with `uv tool install --upgrade --force --prerelease disallow` |
+| Exact version | Profile requirement with `==VERSION` |
+
+`latest` means the newest stable version compatible with the selected interpreter and configured index. `--force` makes older uv versions resolve a fresh tool environment, so an installed prerelease cannot bypass stable selection. Resolution failure is reported with the requested requirement unchanged.
+
+Exact versions use `X.Y.Z`, optionally followed by `aN`, `bN`, or `rcN`. Releases before 0.1.0, version ranges, source refs, and URLs are outside this option's contract. Changing the profile replaces the tool's dependency set; users who manage additional extras use the manual installation path.
+
+The installed launcher must report a release version, and an exact request must match it. All profiles must pass CLI help and `capabilities --help`; `local` must also pass `config init --help` and `server run --help`. These checks verify imports and command availability. Server readiness is checked after the user configures and starts the Server.
+
+## Source selection and fallback
+
+Region selection follows `--region`, `POWERCONTEXT_INSTALL_REGION`, local named timezone, locale territory, then global sources. Detection uses local settings without a network location service.
+
+| Component | Global source | Automatic China source | Explicit control |
+| --- | --- | --- | --- |
+| PowerContext and dependencies | PyPI | Tsinghua PyPI mirror | `--index-url`, uv index settings and configuration |
+| uv installer and binaries | Astral channels | USTC release mirror | `POWERCONTEXT_UV_INSTALLER_URL`, `UV_DOWNLOAD_URL`, uv installer mirror variables |
+| Python | uv default channels | NJU python-build-standalone mirror | `UV_PYTHON_INSTALL_MIRROR`, uv Python download configuration |
+
+Existing uv configuration suppresses automatic package mirrors. `--index-url` replaces the default index; additional indexes retain uv's priority rules. uv handles authentication and the package index protocol. Script URL arguments require HTTPS without embedded credentials. pip index variables do not configure uv.
+
+Explicit uv and Python sources are preserved without fallback. For automatic selection, an unavailable uv mirror installer falls back to Astral; mirrored uv artifacts can fall back to official sources. The Python build URL comes from uv. The script checks its mirrored artifact and retries with uv's default channels if the automatic mirror fails.
+
+An automatic package mirror receives a bounded availability request before tool installation. An unreachable mirror falls back to PyPI. A reachable mirror stays selected even if the requested version is missing. uv reports resolution and artifact failures; the script does not switch indexes after a tool installation attempt. This avoids retrying a package replacement against a different source after files may have changed. uv owns index parsing and compatibility checks.
+
+## Failure and recovery
+
+Each completed component remains available when a later step fails:
+
+| Failure | Result and recovery |
+| --- | --- |
+| A prerequisite cannot be installed | Stop and report the failed download; retain prerequisites already installed |
+| Package resolution fails, including an unavailable exact version | Preserve the installed tool and report the uv error |
+| The installed launcher fails verification | Report that the package was installed but verification failed; allow an explicit version retry |
+| Host setup fails | Preserve the verified Runtime, return nonzero, and identify the matching release ref for a setup retry |
+
+Verification can fail after replacing the previous executable. The installer does not roll package files back or undo completed host operations. It cleans only its temporary downloads. Source and PATH changes remain local to the installation process; PowerShell restores temporary source variables in `finally`.
+
+## Persistence and compatibility
+
+Package installation leaves `.env`, credentials, data directories, and database schemas unchanged. It does not stop or restart a service. Users follow the existing migration and service instructions when starting an updated Server. Runtime APIs and the existing configuration, diagnostic, and service commands retain their contracts.
+
+`UV_OFFLINE=1` disables installer probes and uv networking. Cached reinstallation requires an existing uv, compatible Python, and all required packages. Missing prerequisites or artifacts fail explicitly.
+
+## Executable contract and acceptance
+
+Both shell adapters must pass the shared behavior cases in `tests/fixtures/installation/*.json`. These cases check public arguments, installed capabilities, matching host refs, and exit results through real uv and small fixture wheels.
+
+Acceptance covers:
+
+- Bootstrap with missing uv or Python, reuse of existing tools and configuration, and paths containing spaces or Unicode.
+- Latest stable and exact releases, explicit prereleases, and preservation of an installed tool when resolution fails.
+- Client/local profile replacement, host opt-in and partial failure, and passing an existing connection file.
+- Unattended and piped Bash execution, plus PowerShell file and downloaded-scriptblock entry points.
+- Installation of the actual wheel, configuration validation, HTTP readiness, Memory persistence, cached offline reinstall, and restart readback.
+
+The native CI matrix runs installation acceptance on Linux, macOS, and Windows. Controlled-index source tests verify mirror selection and failure handling on Linux. Platform qualification requires native execution; syntax checks and fixture hosts do not establish service-manager or Agent workflow support. Release-reference checks keep explicit version examples current while preserving the `latest` default.
+
+# Drawbacks
+
+Bash and PowerShell require separate implementations of prerequisite and source selection. Automatic mirrors add availability dependencies. `latest` changes as the selected index publishes releases, so repeatable deployments need an exact version. A matching integration tag coordinates versions; each host still needs its own compatibility checks.
+
+# Rationale and alternatives
+
+Direct uv commands remain suitable for users who already manage Python and uv. The scripts cover machines without those prerequisites. Source installation remains available for development. The script defaults to `latest` so its package version does not become stale between website releases.
+
+A Python installer engine would still need a shell bootstrap. Generated shell adapters would add a generator while retaining the need for native tests. Two small adapters with shared behavior cases keep the initial implementation simpler.
+
+uv already maintains the package environment and installation records. A second installation ledger would duplicate that state. Separate manifests and records would be needed if PowerContext later distributed independently versioned raw artifacts.
+
+# Prior art
+
+[RFC 1408](https://github.com/oceanbase/powercontext/pull/1408) separates installation, configuration, and diagnostics, and defines independent profiles, host selection, and component recovery. [RFC 1299](1299_local_server_availability_and_service_installation.md) defines the personal-service lifecycle used here.
+
+[Magpie's installer](https://github.com/yetone/magpie/blob/023f5aaad2ecd41ae04390166b9cac9a0b300d81/site/public/install.sh) separates release sources from artifact mirrors and checks binary checksums before replacement. Its manifest serves direct binary distribution; this design delegates package distribution to uv.
+
+[Lody's daemon installer](https://github.com/LodyAI/Lody/blob/811b573329716b23e1144e5d66211ea4ddfb0dfd/specs/daemon-upgrade-installation.md) resolves the actual npm installation destination and verifies the installed executable. That check also applies here. Its [managed Agent runtime](https://github.com/LodyAI/Lody/blob/811b573329716b23e1144e5d66211ea4ddfb0dfd/apps/cli/src/agent/managed-agent-runtime.ts) separately manages raw archives and their records.
+
+[Bub](https://github.com/bubbuild/bub/tree/b4a61bf1326729a024161d22ba20019b8500f907/website/public) bootstraps uv before a Python preset resolver. This proposal also uses a shell bootstrap and tests both shell adapters on their native platforms. [uv tools](https://docs.astral.sh/uv/concepts/tools/), [uv configuration](https://docs.astral.sh/uv/concepts/configuration-files/), and the [Python Simple API](https://packaging.python.org/en/latest/specifications/simple-repository-api/) define the environment and package-source behavior delegated to uv.
+
+# Unresolved questions
+
+Complete offline bundles, cryptographic installation manifests, independently packaged host artifacts, and changes to configuration storage need separate designs. This RFC retains Windows' experimental status.
+
+# Future possibilities
+
+An installer engine may become useful for independent component versions, immutable manifests, or persistent repair records. An offline bundle could carry the required artifacts. Such extensions would retain explicit Server startup and service registration.
diff --git a/docs/zh/docs/get-started/install-and-run.md b/docs/zh/docs/get-started/install-and-run.md
index 91cf2cdc16..ba384a1b96 100644
--- a/docs/zh/docs/get-started/install-and-run.md
+++ b/docs/zh/docs/get-started/install-and-run.md
@@ -5,10 +5,7 @@ description: 安装 PowerContext 1.5.0,并运行本地 Server。
# 安装和运行
-跨机器连接 Agent 时,请阅读[连接远程 Server](../operate/connect-remote-server.md),了解地址引导确认、
-非交互安装及绑定地址的明文 HTTP 同意设置。
-
-首次使用请从 [Quick Start](quickstart.md)开始。本页说明版本选择、平台要求、安装角色、启动、诊断和更新。
+本页说明如何安装 PowerContext 并运行个人 Server。首次连接 Agent 时,按[快速开始](quickstart.md)操作;连接已有 Server 时,使用[远程连接指南](../operate/connect-remote-server.md)。
## 平台支持
@@ -17,26 +14,148 @@ description: 安装 PowerContext 1.5.0,并运行本地 Server。
| macOS、Linux | 支持 |
| Windows | `experimental` |
-Windows 的 CLI、Server 和个人服务支持为试验性;各 Agent Host 仍需满足自身的平台要求。
-使用 Bash 语法的示例需要 Bash 环境,不可直接粘贴到 PowerShell。嵌入式 seekDB 不支持 Windows。
+Windows 的 CLI、Server 和个人服务支持为试验性;各 Agent 宿主仍需满足自身要求。嵌入式 seekDB 不支持 Windows。下文 Bash 示例需要在 Bash 中执行。
+
+## 使用推荐安装脚本
+
+脚本在独立的 uv tool 环境中安装 CLI 和本地 Server,复用兼容的 Python 和 uv,缺少时在用户目录补齐,无需管理员权限。
+
+macOS 或 Linux 需要 Bash 和 `curl` 或 `wget`;Windows 需要 PowerShell 5.1 或更新版本。
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash
+```
+
+```powershell tab="Windows"
+powershell -ExecutionPolicy Bypass -c "& ([scriptblock]::Create((irm https://powercontext.oceanbase.io/install.ps1)))"
+```
+
+使用 `wget` 时,执行 `wget -qO- https://powercontext.oceanbase.io/install.sh | bash`。
+
+先执行安装完成时打印的 PATH 命令,再在终端使用 `powercontext`。脚本会提示如何让终端找到已安装命令;shell 启动文件和 Windows 的持久 PATH 保持不变。
+
+默认安装最新稳定版及本地 Server 依赖。完成后继续配置个人服务。连接远程 Server 时,使用 `--profile client`,再按[远程连接指南](../operate/connect-remote-server.md)设置。
+
+## 配置并安装个人服务
+
+在 macOS 或 Linux 上配置本地 Server:
+
+```bash
+powercontext config init --output .env
+```
+
+在向导中选择本机使用和 loopback 绑定,保存 `.env` 后再继续;如果取消,到这里停止。Linux 需要可用的 `systemd --user` 管理器。Windows 的服务安装及登录启动按[部署 Server](../operate/deploy-server.md)操作。
+
+验证已保存的文件并注册服务:
+
+```bash
+powercontext config validate --env-file .env
+powercontext service install --env-file .env
+powercontext service status
+```
+
+服务会在关闭终端后继续运行 Server。加载客户端配置,检查连接:
+
+```bash
+set -a
+. ./.env
+set +a
+powercontext ready
+powercontext capabilities
+```
+
+修改 `.env`(包括写入 Scope ID)后,重新执行 `powercontext service install --env-file .env`,并重新加载客户端配置。文件保存在其他位置时,使用原路径。[快速开始](quickstart.md)说明 Scope 创建及 Agent 会话中的记忆验证步骤。
-## 选择版本
+开发、排障或缺少原生用户服务管理器时,按[前台运行说明](#运行本地-server)启动 Server。
-本页使用 PowerContext 1.5.0。包与 Agent 集成保持版本一致:
-Python 包版本为 `1.5.0`,对应 Git tag 为 `powercontext-v1.5.0`。
+## 安装 Agent 集成
+
+先安装 Agent 自身的 CLI,再安装它的 PowerContext 集成。以 Codex 为例:
+
+```bash
+powercontext setup codex --ref "powercontext-v$(powercontext --version)"
+```
+
+该命令使用与已安装 PowerContext 版本匹配的 tag。需要交互选择宿主时,运行 `powercontext setup --ref "powercontext-v$(powercontext --version)"`。然后按所选[集成指南](../integrations/index.md)检查连接并启用 Hook。
+
+如需在安装包时同时安装集成,向脚本传入 `--host codex`;多个宿主可重复传入 `--host`,这时需要 Git 及各宿主的前置依赖。已有连接文件时,为所选宿主传入 `--env-file PATH`,脚本会把文件交给 `setup`。没有 `--host` 时跳过集成;`--no-hosts` 显式表达这一行为,不能与 `--host` 同时使用。支持范围见[能力矩阵](../integrations/capabilities.md)。
+
+## 选择脚本安装版本
+
+默认的 `--version latest` 安装或升级到所选索引中兼容的最新稳定版。安装 PowerContext 1.5.0 时,选择对应平台的命令:
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --version 1.5.0
+```
+
+```powershell tab="Windows"
+irm https://powercontext.oceanbase.io/install.ps1 -OutFile install.ps1
+powershell -ExecutionPolicy Bypass -File .\install.ps1 --version 1.5.0
+```
+
+也可以指定预发布版,例如 `1.3.0rc1`。所选索引缺少该版本时,安装失败并保留原工具;`latest` 则排除预发布版。镜像同步可能滞后,其最新版本可能与 PyPI 不同。
+
+## 下载失败时使用镜像
+
+使用 `--region cn` 选择中国区域镜像:
+
+```bash tab="macOS / Linux"
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --region cn
+```
+
+```powershell tab="Windows"
+irm https://powercontext.oceanbase.io/install.ps1 -OutFile install.ps1
+powershell -ExecutionPolicy Bypass -File .\install.ps1 --region cn
+```
+
+`--region auto|cn|global` 优先于 `POWERCONTEXT_INSTALL_REGION`。未显式选择区域时,脚本依次参考命名时区、locale 地区,最后采用全球源。使用 `--region global` 选择全球默认源。
+
+根据下载失败的组件设置对应来源。更换包索引不会改变 Python 或 uv 的下载位置:
+
+| 下载内容 | 显式设置 | 中国区域自动源 | 全球源 |
+| --- | --- | --- | --- |
+| PowerContext 和 Python 包 | `--index-url URL`、uv 索引变量或 `uv.toml` | 清华 PyPI 镜像 | PyPI |
+| uv 安装器 | `POWERCONTEXT_UV_INSTALLER_URL` | USTC uv release 镜像 | Astral 安装器 |
+| uv 二进制 | `UV_DOWNLOAD_URL` 或 `UV_INSTALLER_GITHUB_BASE_URL` | USTC uv release 镜像 | Astral release 渠道 |
+| Python 发行版 | `UV_PYTHON_INSTALL_MIRROR` | NJU python-build-standalone 镜像 | uv 默认渠道 |
+
+已有 uv 设置优先于自动镜像。`--index-url` 更改默认包索引,额外 uv 索引保持原有优先级。私有索引凭据使用 uv 的认证配置。uv 不读取 `PIP_INDEX_URL` 或 `PIP_EXTRA_INDEX_URL`。已有 Python 镜像设置和 `uv.toml` 也会阻止自动选择 Python 镜像。支持 `UV_ASTRAL_MIRROR_URL` 的 uv 版本会接收该设置。
+
+自动镜像不可用时,可以回退官方源;可达包镜像缺少请求版本时,会返回安装错误。使用 `--region global` 或显式索引切换来源后重试。显式来源失败时仍保持选中。
+
+## 安装失败后恢复
+
+根据安装器报告的错误选择下一步:
+
+| 失败情况 | 处理方式 |
+| --- | --- |
+| Python 或 uv 下载失败 | 检查上表中的下载设置,重新运行安装器 |
+| 包解析或下载失败 | 检查所选索引和请求版本后重试 |
+| 包已安装,但命令验证失败 | 检查报错命令,使用已知可用的 `--version` 重试;旧可执行文件可能已经被替换 |
+| Agent 集成安装失败 | 保留已安装的 PowerContext,使用错误提示中的发行 tag 重试 `powercontext setup` |
+
+只有在已有 uv、兼容 Python 和全部所需包时,才设置 `UV_OFFLINE=1` 进行缓存重装;离线模式下缺少下载内容会失败。
+
+需要更改安装位置时,使用 `UV_INSTALL_DIR` 指定缺少 uv 时的安装目录,使用 `UV_TOOL_BIN_DIR` 指定 PowerContext 命令目录。复用的 uv 版本及持久包管理器设置保持不变。
+
+## 使用 uv 手动安装
+
+已有 Python 和 uv 时,可以直接安装固定发行版。先选择版本,再安装所需的包 extras。
+
+### 选择版本
+
+本页使用 PowerContext 1.5.0。包与 Agent 集成保持版本一致:Python 包版本为 `1.5.0`,对应 Git tag 为 `powercontext-v1.5.0`。
```bash
uv tool install --force "powercontext[cli,server]==1.5.0"
powercontext setup codex --ref powercontext-v1.5.0
```
-宿主支持范围和维护状态见[能力矩阵](../integrations/capabilities.md)。
-标为 `experimental` 的能力在此版本中仍属于试验性能力。
+宿主支持范围和维护状态见[能力矩阵](../integrations/capabilities.md)。标为 `experimental` 的能力在此版本中仍属于试验性能力。
-## 安装应用
+### 安装应用
-需要在 macOS、Linux 或 Windows 上准备 Python 3.11 或更新版本、Git 和
-[`uv`](https://docs.astral.sh/uv/),然后从 PyPI 安装 PowerContext:
+需要在 macOS、Linux 或 Windows 上准备 Python 3.11 或更新版本、Git 和 [`uv`](https://docs.astral.sh/uv/),然后从 PyPI 安装 PowerContext:
```bash
uv tool install --force "powercontext[cli,server]==1.5.0"
@@ -48,18 +167,16 @@ uv tool install --force "powercontext[cli,server]==1.5.0"
uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@powercontext-v1.5.0"
```
-只连接远程 Server 的机器安装 `cli` extra 即可使用 `setup`、`doctor` 和远程内容命令。运行本地 Server 或使用
-`service` 命令时,还需安装 `server`。如果命令 provider 缺少依赖,CLI 会报告 provider 和缺失模块,便于修复安装。
+只连接远程 Server 的机器安装 `cli` extra 即可使用 `setup`、`doctor` 和远程内容命令。运行本地 Server 或使用 `service` 命令时,还需安装 `server`。如果命令 provider 缺少依赖,CLI 会报告 provider 和缺失模块,便于修复安装。
-Git 安装命令不会留下需要自行管理的仓库工作副本。Git 会沿用本机的凭据配置,包括 credential helper 和 SSH 设置。
-如需使用 SSH,请把 HTTPS URL 换成当前环境允许的 Git URL。`--force` 还会从所选 Git ref 当前指向的 commit
-刷新已安装工具;如果不加该参数,`uv` 可能只提示相同 requirement 已安装,而不会获取更新后的 `master`。
+Git 安装命令不会留下需要自行管理的仓库工作副本。Git 会沿用本机的凭据配置,包括 credential helper 和 SSH 设置。如需使用 SSH,请把 HTTPS URL 换成当前环境允许的 Git URL。`--force` 还会从所选 Git ref 当前指向的 commit 刷新已安装工具;如果不加该参数,`uv` 可能只提示相同 requirement 已安装,而不会获取更新后的 `master`。
-安装其他分支或 tag 时,替换最后一个 `@` 后的 ref。`master` 分支可能包含尚未发布的改动。
-Agent 的安装、连接参数和验证步骤见[各自的集成文档](../integrations/index.md),并使用与 Server 相同的 ref。
+安装其他分支或 tag 时,替换最后一个 `@` 后的 ref。`master` 分支可能包含尚未发布的改动。Agent 的安装、连接参数和验证步骤见[各自的集成文档](../integrations/index.md),并使用与 Server 相同的 ref。
## 运行本地 Server
+开发、排障或临时使用时,可以采用前台运行方式。在同一地址启动另一个 Server 前,先停止已有个人服务。
+
```bash
powercontext server run
```
@@ -74,8 +191,7 @@ powercontext server run
按 `Ctrl-C` 可正常关闭。再次运行该命令会打开同一个数据库。
-Dashboard 是个人使用和演示的可选内容查看器,默认关闭。它不需要单独安装前端或配置模型。
-本地免 token 启用时,在环境文件中设置以下值:
+Dashboard 是个人使用和演示的可选内容查看器,默认关闭。它不需要单独安装前端或配置模型。本地免 token 启用时,在环境文件中设置以下值:
```dotenv
POWERCONTEXT_SERVER_HTTP_HOST=127.0.0.1
@@ -83,8 +199,7 @@ POWERCONTEXT_SERVER_DASHBOARD_ENABLED=true
POWERCONTEXT_SERVER_ACCESS_MODE=disabled
```
-需要认证时,将 `POWERCONTEXT_SERVER_ACCESS_MODE` 设为 `enforced`,并将 `POWERCONTEXT_SERVER_AUTH_TOKEN` 设为自己的长随机凭据。
-[配置向导](configure-server-environment.md#本地-dashboard-与可选认证)在本地开启 Dashboard 时也提供这一选择。
+需要认证时,将 `POWERCONTEXT_SERVER_ACCESS_MODE` 设为 `enforced`,并将 `POWERCONTEXT_SERVER_AUTH_TOKEN` 设为自己的长随机凭据。[配置向导](configure-server-environment.md#本地-dashboard-与可选认证)在本地开启 Dashboard 时也提供这一选择。
```bash
chmod 600 /path/to/powercontext.env
@@ -92,34 +207,25 @@ powercontext config validate --env-file /path/to/powercontext.env
powercontext server run --env-file /path/to/powercontext.env
```
-打开 `http://127.0.0.1:8000/dashboard/home`,更改端口后使用实际端口。未启用认证时可直接进入页面;
-启用后使用 Server Token 登录,已连接的 Agent 也需配置该 token 来访问 API 和 MCP。CLI 不会自动读取目录中的 `.env` 文件。
+打开 `http://127.0.0.1:8000/dashboard/home`,更改端口后使用实际端口。未启用认证时可直接进入页面;启用后使用 Server Token 登录,已连接的 Agent 也需配置该 token 来访问 API 和 MCP。CLI 不会自动读取目录中的 `.env` 文件。
-首次访问选择 Server 默认 Scope,未保存内容时显示空状态。通过 Agent 或公开 API 保存一条 Memory,
-再刷新同一 Scope 的记忆页即可查看。经验、技能、交接和用量也来自实际保存记录;页面不采集会话、不运行生成,
-也不批准候选。Dashboard 和 Agent 必须连接同一个 Server、使用同一个 Scope。
+首次访问选择 Server 默认 Scope,未保存内容时显示空状态。通过 Agent 或公开 API 保存一条 Memory,再刷新同一 Scope 的记忆页即可查看。经验、技能、交接和用量也来自实际保存记录;页面不采集会话、不运行生成,也不批准候选。Dashboard 和 Agent 必须连接同一个 Server、使用同一个 Scope。
-打开**画像**查看已保存内容,通过**版本历史**阅读历史修订或核对来源;阅读旧版本不会改变当前画像。
-在**交接**目录条目或详情页点击**导出 Markdown**,可下载该精确版本的完整正文、遗漏和引用。
-若登录失效,重新登录后会返回所选详情,再次点击导出即可。
+打开**画像**查看已保存内容,通过**版本历史**阅读历史修订或核对来源;阅读旧版本不会改变当前画像。在**交接**目录条目或详情页点击**导出 Markdown**,可下载该精确版本的完整正文、遗漏和引用。若登录失效,重新登录后会返回所选详情,再次点击导出即可。
-启用认证时,所有 token 持有者使用同一个身份。多成员 RBAC 部署应保持 Dashboard 关闭,通过 API、MCP 或宿主集成访问内容。
-网络与凭据配置见[部署 Server](../operate/deploy-server.md)。
+启用认证时,所有 token 持有者使用同一个身份。多成员 RBAC 部署应保持 Dashboard 关闭,通过 API、MCP 或宿主集成访问内容。网络与凭据配置见[部署 Server](../operate/deploy-server.md)。
-这种最小启动方式不会启用依赖模型的抽取或向量搜索。如需生成并校验一份显式环境文件以启用这些能力,请继续阅读
-[启用提取与向量搜索](configure-models.md)。
+这种最小启动方式不会启用依赖模型的抽取或向量搜索。如需生成并校验一份显式环境文件以启用这些能力,请继续阅读 [启用提取与向量搜索](configure-models.md)。
## 使用嵌入式 seekDB
-在有兼容 `pylibseekdb` wheel 的 Linux 和 macOS 系统上可以使用嵌入式 seekDB;Windows 不支持该嵌入式
-后端。安装或替换工具时加入可选的 seekDB extra:
+在有兼容 `pylibseekdb` wheel 的 Linux 和 macOS 系统上可以使用嵌入式 seekDB;Windows 不支持该嵌入式后端。安装或替换工具时加入可选的 seekDB extra:
```bash
uv tool install --force "powercontext[cli,server,seekdb]==1.5.0"
```
-从 SQLite 切换时,需要从 Server 进程环境中删除 `POWERCONTEXT_SERVER_DATABASE_URL`;seekDB 不接受显式的
-SQLAlchemy 数据库 URL。然后选择 seekDB 后端并启动 Server:
+从 SQLite 切换时,需要从 Server 进程环境中删除 `POWERCONTEXT_SERVER_DATABASE_URL`;seekDB 不接受显式的 SQLAlchemy 数据库 URL。然后选择 seekDB 后端并启动 Server:
```bash
unset POWERCONTEXT_SERVER_DATABASE_URL
@@ -127,16 +233,9 @@ export POWERCONTEXT_SERVER_DATABASE_KIND=seekdb
powercontext server run
```
-当前目录存在 `.env` 时,`server run` 会自动加载该文件。可以在 shell 中导出变量来覆盖文件值,使用
-`--env-file ` 选择其他文件,或使用 `--no-env-file` 忽略环境文件。进程管理器和容器通常应提供显式环境,
-不要依赖其工作目录。
+当前目录存在 `.env` 时,`server run` 会自动加载该文件。可以在 shell 中导出变量来覆盖文件值,使用 `--env-file ` 选择其他文件,或使用 `--no-env-file` 忽略环境文件。进程管理器和容器通常应提供显式环境,不要依赖其工作目录。
-PowerContext 默认使用专用数据库 `powercontext`,先创建缺失的数据库,再初始化应用表。
-设置 `POWERCONTEXT_SERVER_DATABASE_DATABASE=my_context` 可以指定其他名称;缺失时创建,已有时复用。
-包含反引号的库名会在初始化之前被拒绝。
-未设置 `POWERCONTEXT_SERVER_DATABASE_PATH` 时,实例保存在
-PowerContext 用户数据目录的 `seekdb` 子目录中;如果设置了 `POWERCONTEXT_HOME`,默认路径为
-`$POWERCONTEXT_HOME/seekdb`。只有需要其他位置时才设置 `POWERCONTEXT_SERVER_DATABASE_PATH`。
+PowerContext 默认使用专用数据库 `powercontext`,先创建缺失的数据库,再初始化应用表。设置 `POWERCONTEXT_SERVER_DATABASE_DATABASE=my_context` 可以指定其他名称;缺失时创建,已有时复用。包含反引号的库名会在初始化之前被拒绝。未设置 `POWERCONTEXT_SERVER_DATABASE_PATH` 时,实例保存在 PowerContext 用户数据目录的 `seekdb` 子目录中;如果设置了 `POWERCONTEXT_HOME`,默认路径为 `$POWERCONTEXT_HOME/seekdb`。只有需要其他位置时才设置 `POWERCONTEXT_SERVER_DATABASE_PATH`。
在另一个终端确认 Server 和数据库已经就绪:
@@ -156,29 +255,17 @@ export POWERCONTEXT_SERVER_DATABASE_DATABASE=test
powercontext server run
```
-保持原来的 `POWERCONTEXT_SERVER_DATABASE_PATH` 或 `POWERCONTEXT_HOME`。实例目录和数据库名称是两个独立配置,
-修改其中任何一个都可能连接到不同的数据。服务和容器也需要使用相同配置。
+保持原来的 `POWERCONTEXT_SERVER_DATABASE_PATH` 或 `POWERCONTEXT_HOME`。实例目录和数据库名称是两个独立配置,修改其中任何一个都可能连接到不同的数据。服务和容器也需要使用相同配置。
-如果没有显式配置库名,而 `test` 中存在以 PowerContext 的 `pc_` 为前缀的表,启动会在创建应用表前停止,
-要求选择数据库。系统不会自动选择、复制或删除旧库。显式选择 `test` 可以继续使用旧数据;显式选择
-`powercontext` 则使用新库。配置向导也会询问数据库名称,已有用户应填写 `test`。
+如果没有显式配置库名,而 `test` 中存在以 PowerContext 的 `pc_` 为前缀的表,启动会在创建应用表前停止,要求选择数据库。系统不会自动选择、复制或删除旧库。显式选择 `test` 可以继续使用旧数据;显式选择 `powercontext` 则使用新库。配置向导也会询问数据库名称,已有用户应填写 `test`。
-需要迁移时,先继续使用 `test`,直到完成备份并准备好针对当前引擎版本验证过的导出、导入流程。
-停止所有源库写入者,准备目标库,按该流程导出、导入结构和数据,校验身份、关联关系,并通过 PowerContext
-读取迁移结果后再切换配置。自动建库不执行数据迁移。不能假定任意 SQL 转储都能跨引擎或表结构版本使用;
-已有表仍需通过兼容性检查,表结构升级需要显式维护。
-代码图引用绑定实例和数据库,仅复制关系表数据不等于迁移代码图存储;校验流程应包含目标库的 Code 索引重建和查询。
-显式继续使用 `test` 会保留旧代码图身份。
+需要迁移时,先继续使用 `test`,直到完成备份并准备好针对当前引擎版本验证过的导出、导入流程。停止所有源库写入者,准备目标库,按该流程导出、导入结构和数据,校验身份、关联关系,并通过 PowerContext 读取迁移结果后再切换配置。自动建库不执行数据迁移。不能假定任意 SQL 转储都能跨引擎或表结构版本使用;已有表仍需通过兼容性检查,表结构升级需要显式维护。代码图引用绑定实例和数据库,仅复制关系表数据不等于迁移代码图存储;校验流程应包含目标库的 Code 索引重建和查询。显式继续使用 `test` 会保留旧代码图身份。
-切换后由用户选择保留期,旧库保持停写。记录源和目标实例路径、库名、备份位置、停写及切换时间、校验结果和复核日期。
-手动删除旧库前,确认所有部署均已停止使用它,并且仍有可用备份。目标库的新写入不会同步回源库,保留源库不代表
-可以无损回退。PowerContext 不会自动迁移、发送到期提醒、比较新旧库或删除旧库。
+切换后由用户选择保留期,旧库保持停写。记录源和目标实例路径、库名、备份位置、停写及切换时间、校验结果和复核日期。手动删除旧库前,确认所有部署均已停止使用它,并且仍有可用备份。目标库的新写入不会同步回源库,保留源库不代表可以无损回退。PowerContext 不会自动迁移、发送到期提醒、比较新旧库或删除旧库。
## 使用 OceanBase
-连接已有 OceanBase **MySQL 兼容模式的用户租户**。完整 Server 需要先准备所选数据库,并通过显式维护初始化
-Server 表结构,再启动服务。PowerContext 不负责创建 OceanBase 实例、租户或账户。
-确认网络可达,按上文步骤安装 Server,然后配置连接:
+连接已有 OceanBase **MySQL 兼容模式的用户租户**。完整 Server 需要先准备所选数据库,并通过显式维护初始化 Server 表结构,再启动服务。PowerContext 不负责创建 OceanBase 实例、租户或账户。确认网络可达,按上文步骤安装 Server,然后配置连接:
```bash
export POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
@@ -186,42 +273,23 @@ export POWERCONTEXT_SERVER_DATABASE_KIND=oceanbase
export POWERCONTEXT_SERVER_DATABASE_URL='mysql+aoceanbase://user%40tenant:encoded-password@host:2881/?charset=utf8mb4'
```
-使用租户账户和实际的直连或 OBProxy 地址、端口。URL 用户名和密码中的保留字符需要百分号编码,例如
-`user@tenant` 编码为 `user%40tenant`。URL 必须使用 `mysql+aoceanbase` 驱动、显式端口和 `charset=utf8mb4`。
+使用租户账户和实际的直连或 OBProxy 地址、端口。URL 用户名和密码中的保留字符需要百分号编码,例如 `user@tenant` 编码为 `user%40tenant`。URL 必须使用 `mysql+aoceanbase` 驱动、显式端口和 `charset=utf8mb4`。
-示例省略库名,因此使用 `powercontext`。自定义库名放在 URL 路径中,例如 `/team_context?charset=utf8mb4`。
-已有 URL 中显式指定的库名保持有效。库名仅通过 URL 路径指定,不接受 `db` 或 `database` 查询参数。
-数据库名称支持 1~64 个 BMP 字符,不允许控制字符、斜杠、反斜杠、点、反引号、
-首尾空白或系统数据库名称;PowerContext 不会静默裁剪或改名,服务器自身的标识符大小写规则仍然适用。
-包含反引号的名称会在配置校验阶段被拒绝,不会连接或创建数据库。请使用 `powercontext`、`team_context`
-或不含反引号的中文名称。
+示例省略库名,因此使用 `powercontext`。自定义库名放在 URL 路径中,例如 `/team_context?charset=utf8mb4`。已有 URL 中显式指定的库名保持有效。库名仅通过 URL 路径指定,不接受 `db` 或 `database` 查询参数。数据库名称支持 1~64 个 BMP 字符,不允许控制字符、斜杠、反斜杠、点、反引号、首尾空白或系统数据库名称;PowerContext 不会静默裁剪或改名,服务器自身的标识符大小写规则仍然适用。包含反引号的名称会在配置校验阶段被拒绝,不会连接或创建数据库。请使用 `powercontext`、`team_context` 或不含反引号的中文名称。
-Python SDK 初始化时先尝试连接目标库,已有可用库不要求额外建库权限。只有明确的“数据库不存在”错误才触发实例或租户级连接,
-执行 `CREATE DATABASE IF NOT EXISTS`,随后重新连接目标库并初始化应用表。认证、网络错误不会触发建库或切库。
-如果服务器拒绝访问但无法确认库是否存在,错误会要求管理员同时检查数据库和权限。
+Python SDK 初始化时先尝试连接目标库,已有可用库不要求额外建库权限。只有明确的“数据库不存在”错误才触发实例或租户级连接,执行 `CREATE DATABASE IF NOT EXISTS`,随后重新连接目标库并初始化应用表。认证、网络错误不会触发建库或切库。如果服务器拒绝访问但无法确认库是否存在,错误会要求管理员同时检查数据库和权限。
-首次部署完整 Server 时,需要先创建所选数据库。SDK 自动初始化因权限不足无法建库时,也可由管理员执行同一步骤。
-请在**同一租户**中执行以下 SQL;自定义部署应替换为实际库名,然后为应用账户授予所需数据库权限:
+首次部署完整 Server 时,需要先创建所选数据库。SDK 自动初始化因权限不足无法建库时,也可由管理员执行同一步骤。请在**同一租户**中执行以下 SQL;自定义部署应替换为实际库名,然后为应用账户授予所需数据库权限:
```sql
CREATE DATABASE IF NOT EXISTS `powercontext` CHARACTER SET utf8mb4;
```
-完整 Server 接下来应按[数据库迁移](../operate/database-migrations.md)指南,显式指定证据目录和协调模式,
-对登记的 Server bundle 执行 plan、apply 和 verify。数据库就绪后再运行 `powercontext server run`。
-Server 启动检查就绪状态,不初始化 OceanBase 表结构,也不会自动升级已有库。SDK 调用方完成建库后可以使用原 URL 重试初始化。
-创建缺失表仍然需要建表权限;已有完整可用库不会新增建库权限要求。
-初始化不会删除或重建已有数据库,不会在失败后切换其他数据库,也不授权升级已有表结构。
-并发创建使用幂等建库、建表 DDL,并处理索引重名竞争;这不提供已有表结构迁移的协调能力,也不改变嵌入式 seekDB
-对实例进程所有权的限制。
+完整 Server 接下来应按[数据库迁移](../operate/database-migrations.md)指南,显式指定证据目录和协调模式,对登记的 Server bundle 执行 plan、apply 和 verify。数据库就绪后再运行 `powercontext server run`。Server 启动检查就绪状态,不初始化 OceanBase 表结构,也不会自动升级已有库。SDK 调用方完成建库后可以使用原 URL 重试初始化。创建缺失表仍然需要建表权限;已有完整可用库不会新增建库权限要求。初始化不会删除或重建已有数据库,不会在失败后切换其他数据库,也不授权升级已有表结构。并发创建使用幂等建库、建表 DDL,并处理索引重名竞争;这不提供已有表结构迁移的协调能力,也不改变嵌入式 seekDB 对实例进程所有权的限制。
-Python 调用方使用 `OceanBaseProfile.open()` 时可省略 URL 中的库名,并按需建库。
-`OceanBaseProfile.open_existing()` 只连接已准备好的数据库;`attach()` 要求调用方的 engine 已显式选择一个已有数据库。
-两者都不替换 engine 或改变连接目标。只读巡检(`plan`、`verify` 与 `db-migrate` 的检查类命令)不会创建缺失的
-OceanBase 或内嵌 seekDB 库;显式的 seekDB 写入维护可以在应用迁移包前准备配置指定的数据库。
+Python 调用方使用 `OceanBaseProfile.open()` 时可省略 URL 中的库名,并按需建库。`OceanBaseProfile.open_existing()` 只连接已准备好的数据库;`attach()` 要求调用方的 engine 已显式选择一个已有数据库。两者都不替换 engine 或改变连接目标。只读巡检(`plan`、`verify` 与 `db-migrate` 的检查类命令)不会创建缺失的 OceanBase 或内嵌 seekDB 库;显式的 seekDB 写入维护可以在应用迁移包前准备配置指定的数据库。
-只读 seekDB 巡检要求实例目录已初始化。路径缺失或目录为空时,会在启动 native 引擎前报错,不创建父目录或存储文件。
-路径为文件,或非空目录中没有可识别的 native 存储时,也会拒绝操作并保留原有内容。普通启动仍可初始化新实例。
+只读 seekDB 巡检要求实例目录已初始化。路径缺失或目录为空时,会在启动 native 引擎前报错,不创建父目录或存储文件。路径为文件,或非空目录中没有可识别的 native 存储时,也会拒绝操作并保留原有内容。普通启动仍可初始化新实例。
## 验证安装
@@ -231,10 +299,7 @@ powercontext ready
powercontext capabilities
```
-`doctor` 检查已安装的包、Server 存活状态和 Server 就绪状态,不要求安装集成。Server 就绪检查涵盖数据库和
-每个已配置的推理服务。Runtime 或数据库故障返回 `not_ready`;推理服务故障返回 `degraded`,不会使数据库
-操作退出流量。`ready` 和 `capabilities` 用于查看运行中服务的就绪状态和已启用能力。
-Agent 诊断见[各自的集成文档](../integrations/index.md);Server 状态解释和恢复步骤见[排查问题](../operate/troubleshoot.md)。
+`doctor` 检查已安装的包、Server 存活状态和 Server 就绪状态,不要求安装集成。Server 就绪检查涵盖数据库和每个已配置的推理服务。Runtime 或数据库故障返回 `not_ready`;推理服务故障返回 `degraded`,不会使数据库操作退出流量。`ready` 和 `capabilities` 用于查看运行中服务的就绪状态和已启用能力。Agent 诊断见[各自的集成文档](../integrations/index.md);Server 状态解释和恢复步骤见[排查问题](../operate/troubleshoot.md)。
需要长期运行进程、使用 Docker、启用鉴权或允许远程访问时,请继续阅读[部署 Server](../operate/deploy-server.md)。
@@ -244,17 +309,11 @@ Agent 诊断见[各自的集成文档](../integrations/index.md);Server 状态
1.3.0 和 1.4.0 升级必须通过 SQLite、seekDB 和 OceanBase 的完整 Server 验收。当前迁移路径尚未验证,请勿升级已有数据库。
-已有部署应保持当前可用版本,等待来源版本及后端具备已验收的升级路径。试用新建部署时,请使用独立程序环境、
-数据库位置和端口。替换程序包不会完成数据库迁移;备份也不能使不受支持的迁移变为可用。
+已有部署应保持当前可用版本,等待来源版本及后端具备已验收的升级路径。试用新建部署时,请使用独立程序环境、数据库位置和端口。替换程序包不会完成数据库迁移;备份也不能使不受支持的迁移变为可用。
-支持声明由发布矩阵和安装包验收共同检查,详见[准备发布](../../development/releasing.md)。
-[Artifact 处理迁移](../operate/artifact-processing-migration.md)和
-[Atomic Memory 迁移](../operate/atomic-memory-migration.md)各自的验证结果不代表完整 Server 就绪。
+支持声明由发布矩阵和安装包验收共同检查,详见[准备发布](../../development/releasing.md)。[Artifact 处理迁移](../operate/artifact-processing-migration.md)和 [Atomic Memory 迁移](../operate/atomic-memory-migration.md)各自的验证结果不代表完整 Server 就绪。
-升级已有数据库前,请先检查 [Dream 协调升级要求](../workflows/review-dream-revisions.md#协调升级)。
-统一 Candidate 路由将 `/v1/artifact-candidates/*` 替换为 `/v1/candidates/*`,不提供别名,Dream 客户端要求契约 2。
-已有 Dream 存储需排空未完成任务后显式迁移。[完整 Server 迁移](../operate/database-migrations.md)要求
-`state=ready` 和 `server_ready=true`;局部 Artifact 或 Atomic Memory 验证不能替代它。不要绕过迁移门禁。
+升级已有数据库前,请先检查 [Dream 协调升级要求](../workflows/review-dream-revisions.md#协调升级)。统一 Candidate 路由将 `/v1/artifact-candidates/*` 替换为 `/v1/candidates/*`,不提供别名,Dream 客户端要求契约 2。已有 Dream 存储需排空未完成任务后显式迁移。[完整 Server 迁移](../operate/database-migrations.md)要求 `state=ready` 和 `server_ready=true`;局部 Artifact 或 Atomic Memory 验证不能替代它。不要绕过迁移门禁。
带有[上下文复用与最终准入](../operate/context-protection.md)的 Server 构建始终在 `/v1/capabilities` 中返回
`context_protection`,关闭保护后也一样。旧版 Python SDK 会拒绝这个未知字段:`get_capabilities()` 抛出
@@ -275,8 +334,7 @@ uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[c
uv pip install --python /path/to/powercontext-preview/bin/python "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@]["
```
-连接新建部署的客户端及 Agent 集成应使用匹配的 ref。保留原部署的数据库位置和配置,不要用原数据库测试新版启动。
-Windows 的虚拟环境解释器位于 `Scripts/python.exe`。
+连接新建部署的客户端及 Agent 集成应使用匹配的 ref。保留原部署的数据库位置和配置,不要用原数据库测试新版启动。Windows 的虚拟环境解释器位于 `Scripts/python.exe`。
## 为 Python 项目安装角色
@@ -286,5 +344,4 @@ Windows 的虚拟环境解释器位于 `Scripts/python.exe`。
uv add "powercontext[client]==1.5.0"
```
-进程内 Python 组合使用 `builtin`,服务使用 `server`,Python SDK 使用 `client`,基于 Server 的命令行使用
-`cli`。只安装在 `uv tool` 隔离环境中的 extra 不能被另一个 Python 项目直接导入。
+进程内 Python 组合使用 `builtin`,服务使用 `server`,Python SDK 使用 `client`,基于 Server 的命令行使用 `cli`。只安装在 `uv tool` 隔离环境中的 extra 不能被另一个 Python 项目直接导入。
diff --git a/docs/zh/docs/get-started/quickstart.md b/docs/zh/docs/get-started/quickstart.md
index d87d1c6df0..5211667469 100644
--- a/docs/zh/docs/get-started/quickstart.md
+++ b/docs/zh/docs/get-started/quickstart.md
@@ -5,14 +5,9 @@ description: 通过配置向导安装完整记忆能力,接入 Codex,并验
# 快速开始
-本页从安装开始,带你完成一次真实的记忆体验:在 Codex 中讨论项目,看到原始输入进入 Source、主题记忆生成并演进,
-再在新会话中找回决策。以下命令使用 PowerContext 1.5.0,Agent 插件使用对应的
-`powercontext-v1.5.0` tag。
+本页从安装开始,带你完成一次真实的记忆体验:在 Codex 中讨论项目,看到原始输入进入 Source、主题记忆生成并演进,再在新会话中找回决策。以下命令使用 PowerContext 1.5.0,Agent 插件使用对应的 `powercontext-v1.5.0` tag。
-需要 macOS 或 Linux、Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) 和已安装的 Codex CLI。
-完整记忆还需要可用的 Generation 和 Embedding 模型 API;准备好各自的地址、模型名和 API key。
-Codex 或 Claude 的订阅登录不会自动为 PowerContext Server 提供这些 API。只有 Agent 登录、没有模型 API 时,
-仍可选择基础记忆,验收显式保存与召回,但不能据此期待自动 Topic Memory。
+需要 macOS 或 Linux、Python 3.11+、Git、[uv](https://docs.astral.sh/uv/getting-started/installation/) 和已安装的 Codex CLI。完整记忆还需要可用的 Generation 和 Embedding 模型 API;准备好各自的地址、模型名和 API key。Codex 或 Claude 的订阅登录不会自动为 PowerContext Server 提供这些 API。只有 Agent 登录、没有模型 API 时,仍可选择基础记忆,验收显式保存与召回,但不能据此期待自动 Topic Memory。需要自动安装 Python 和 uv 时,使用[安装脚本](install-and-run.md#使用推荐安装脚本)。
## 1. 安装并进入配置向导
@@ -40,26 +35,21 @@ powercontext config init --language zh --output .env
| `.env` | 本次安装使用的 Server、客户端、Agent、数据库和模型配置,包括已配置的凭据 |
| `.env.next-steps.md` | 与本次选择对应的启动、Scope 创建、插件连接和验收说明 |
-向导会打印 Dashboard 地址和所选 SSH 转发命令。启用认证时,还会显示一次新生成的 Server Token,
-以后可在 `.env` 中查看 `POWERCONTEXT_SERVER_AUTH_TOKEN`。文件可能包含凭据,不要提交到 Git。
-若 seekdb 仍在安装,向导会在当前界面显示活动进度并等待;安装失败时先按提示完成依赖安装。
-保存配置或装好依赖,都不代表 Server 已经启动。
+向导会打印 Dashboard 地址和所选 SSH 转发命令。启用认证时,还会显示一次新生成的 Server Token,以后可在 `.env` 中查看 `POWERCONTEXT_SERVER_AUTH_TOKEN`。文件可能包含凭据,不要提交到 Git。若 seekdb 仍在安装,向导会在当前界面显示活动进度并等待;安装失败时先按提示完成依赖安装。保存配置或装好依赖,都不代表 Server 已经启动。
## 2. 启动 Server
-在当前终端执行:
+注册个人服务,让 Server 在关闭终端后继续运行。Linux 需要可用的 `systemd --user` 管理器。执行:
```bash
powercontext config validate --env-file .env
-powercontext server run --env-file .env
+powercontext service install --env-file .env
+powercontext service status
```
-保持终端运行。在浏览器打开向导输出的 Dashboard 地址,端口以 `.env` 中保存的
-`POWERCONTEXT_SERVER_HTTP_PORT` 为准。未启用认证时可直接进入页面;启用后使用 **Server Token** 登录,不是模型 API key。
-首次没有数据是正常现象。需要关闭终端后继续运行时,先停止前台 Server,再通过
-`powercontext service install --env-file .env` 安装[个人后台服务](../operate/deploy-server.md#运行持久个人-server),复用同一份配置。
+在浏览器打开向导输出的 Dashboard 地址,端口以 `.env` 中保存的 `POWERCONTEXT_SERVER_HTTP_PORT` 为准。未启用认证时可直接进入页面;启用后使用 **Server Token** 登录,不是模型 API key。首次没有数据是正常现象。开发、排障、临时使用或没有原生用户服务管理器时,可使用 `powercontext server run --env-file .env`;如个人服务正在运行,先将其停止。服务管理详见[个人后台服务](../operate/deploy-server.md#运行持久个人-server)。
-另开终端,加载客户端连接配置并检查服务:
+加载客户端连接配置并检查服务:
```bash
cd ~/powercontext-demo
@@ -70,25 +60,23 @@ powercontext ready
powercontext capabilities
```
-确认服务就绪且所选处理能力可用;`degraded` 表示部分依赖仍有问题,不能算完整记忆验收通过。
-`config validate` 只检查配置,在线结果以启动后的检查为准。
+确认服务就绪且所选处理能力可用;`degraded` 表示部分依赖仍有问题,不能算完整记忆验收通过。`config validate` 只检查配置,在线结果以启动后的检查为准。
## 3. 创建 Scope 并安装 Codex 插件
-打开 `.env.next-steps.md`,执行其中“创建计划中的独立 Scope”的请求。Server 响应会返回真正的 `scope_id`。
-将它写入 `.env`:
+打开 `.env.next-steps.md`,执行其中“创建计划中的独立 Scope”的请求。Server 响应会返回真正的 `scope_id`。将它写入 `.env`:
```dotenv
POWERCONTEXT_CODEX_SCOPE_ID=替换为返回的scope_id
```
-`codex-xxxxxxxx` 是向导规划的标题,不是 ID。配置 Claude Code 时,将其创建请求返回的 ID 写入
-`POWERCONTEXT_CLAUDE_SCOPE_ID`。不同 Agent 可以各自隔离,也可以显式绑定同一已有 Scope。
-切换目录本身不会创建隔离。验收期间,Dashboard 和 Agent 必须使用相同的 Scope。
+`codex-xxxxxxxx` 是向导规划的标题,不是 ID。配置 Claude Code 时,将其创建请求返回的 ID 写入 `POWERCONTEXT_CLAUDE_SCOPE_ID`。不同 Agent 可以各自隔离,也可以显式绑定同一已有 Scope。切换目录本身不会创建隔离。验收期间,Dashboard 和 Agent 必须使用相同的 Scope。
-重新加载客户端文件并安装匹配的插件:
+修改 `.env` 后,更新服务设置,重新加载客户端配置并安装匹配的插件:
```bash
+powercontext service install --env-file .env
+powercontext service status
set -a
. ./.env
set +a
@@ -99,26 +87,19 @@ codex
在新会话中发送第一条消息前:
-1. 打开 `/hooks` 检查 PowerContext Hook,在收到提示时授予信任。`SessionStart` 解析会话 Scope;
- `UserPromptSubmit` 自动准备相关上下文,并将用户输入采集为 Source。
+1. 打开 `/hooks` 检查 PowerContext Hook,在收到提示时授予信任。`SessionStart` 解析会话 Scope;`UserPromptSubmit` 自动准备相关上下文,并将用户输入采集为 Source。
2. 确认 PowerContext MCP 工具可用。工具发现和 Hook 执行是不同的检查。
-3. 如果跳过或拒绝了信任,先按[Hook 恢复步骤](../integrations/codex.md#首次会话-hook-信任)处理,再验收采集。
- `UserPromptSubmit` Hook 未启用时,不会自动采集或召回。
+3. 如果跳过或拒绝了信任,先按[Hook 恢复步骤](../integrations/codex.md#首次会话-hook-信任)处理,再验收采集。`UserPromptSubmit` Hook 未启用时,不会自动采集或召回。
-非默认地址还需按 `.env.next-steps.md` 的 Codex 连接说明检查已安装插件的 `.mcp.json`:
-MCP 的 URL 必须和 Hook 使用同一 Server,启用认证时还需配置凭据。仅安装插件不会启动 Server。
-`doctor codex` 检查安装、连接和工具覆盖,不能证明 Hook 已获信任、已执行或记忆处理成功。
+非默认地址还需按 `.env.next-steps.md` 的 Codex 连接说明检查已安装插件的 `.mcp.json`:MCP 的 URL 必须和 Hook 使用同一 Server,启用认证时还需配置凭据。仅安装插件不会启动 Server。`doctor codex` 检查安装、连接和工具覆盖,不能证明 Hook 已获信任、已执行或记忆处理成功。
-这些命令以 Codex CLI 为例。桌面 App 不一定继承终端的环境变量;在桌面中验收前,要确认 App 的 Hook 和 MCP
-都拿到了相同的地址和 Scope,启用认证时还需取得对应凭据。更多宿主行为见[Codex](../integrations/codex.md)和[Claude Code](../integrations/claude-code.md)。
+这些命令以 Codex CLI 为例。桌面 App 不一定继承终端的环境变量;在桌面中验收前,要确认 App 的 Hook 和 MCP 都拿到了相同的地址和 Scope,启用认证时还需取得对应凭据。更多宿主行为见[Codex](../integrations/codex.md)和[Claude Code](../integrations/claude-code.md)。
-完整能力中的 Profile 还需要为这个真实 Scope 启用生成策略。按[Profile 策略步骤](configure-models.md#为-scope-启用-profile-策略)
-读取当前版本并更新;只生成配置文件不会自动完成它。
+完整能力中的 Profile 还需要为这个真实 Scope 启用生成策略。按[Profile 策略步骤](configure-models.md#为-scope-启用-profile-策略) 读取当前版本并更新;只生成配置文件不会自动完成它。
## 4. 按所选模式验收记忆
-Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立的 Server 模型 API;Agent 仍使用自己的模型。
-按实际配置选择下面的验收路径。
+Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立的 Server 模型 API;Agent 仍使用自己的模型。按实际配置选择下面的验收路径。
### 基础记忆:显式保存与检索
@@ -134,8 +115,7 @@ Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立
从 PowerContext 搜索 Aurora 的 Python 依赖管理决定,带上 citation。
```
-核对已保存内容与引用。对话中的“记住了”不能证明写入成功;这条路径验证显式保存与检索,
-不验证自动提取或 Topic Memory 生成。
+核对已保存内容与引用。对话中的“记住了”不能证明写入成功;这条路径验证显式保存与检索,不验证自动提取或 Topic Memory 生成。
### 完整记忆:Source 处理与 Topic Memory
@@ -154,11 +134,9 @@ Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立
4. 确认第二条 Source 入库,主题内容或修订记录反映这次更新。不要以“等了一分钟”代替检查。
5. 开启新的 Codex 会话,保留同一 Scope,询问:“从 PowerContext 找回 Aurora 的依赖管理和部署决定,带上引用。”
-找到正确内容及其 citation,才完成从采集到演进再到跨会话召回的验收。`doctor codex` 成功仅说明安装与连接检查通过,
-不能替代上述业务检查。模型可能决定合并或不更新主题,不保证每条消息都创建一条记忆。
+找到正确内容及其 citation,才完成从采集到演进再到跨会话召回的验收。`doctor codex` 成功仅说明安装与连接检查通过,不能替代上述业务检查。模型可能决定合并或不更新主题,不保证每条消息都创建一条记忆。
-完整能力不代表每条对话都会生成所有制品。Profile、Experience 和 Skill 还有各自的触发或审核条件,
-见[模型与能力配置](configure-models.md#完整能力不等于每条对话都会生成所有制品)。
+完整能力不代表每条对话都会生成所有制品。Profile、Experience 和 Skill 还有各自的触发或审核条件,见[模型与能力配置](configure-models.md#完整能力不等于每条对话都会生成所有制品)。
| 检查 | 成功依据 |
| --- | --- |
@@ -170,9 +148,7 @@ Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立
## 5. 选择下一项工作流
-七类 Artifact 的输入和消费路径不同。Source 是证据,Tag 是元数据,两者都不是 Artifact family。
-下面的例句是请求示例,不是保证生效的关键词触发器。以当前连接实际提供的工具为准;没有专用 MCP 工具的操作,
-按链接进入 HTTP/CLI 工作流。概念区别见[Artifact 速览](faq.md#artifact-速览)。
+七类 Artifact 的输入和消费路径不同。Source 是证据,Tag 是元数据,两者都不是 Artifact family。下面的例句是请求示例,不是保证生效的关键词触发器。以当前连接实际提供的工具为准;没有专用 MCP 工具的操作,按链接进入 HTTP/CLI 工作流。概念区别见[Artifact 速览](faq.md#artifact-速览)。
| 能力与用途 | 请求示例或入口 | 前提与结果 |
| --- | --- | --- |
@@ -188,14 +164,9 @@ Hook 启用后,两种模式都可以采集 Source。基础记忆无需独立
### 插件引导 Skill 与受管 Skill
-插件自带的 `powercontext-project-context` 将请求路由到对应工作流,和保存在 PowerContext 的 Managed Skill 是两个对象。
-宿主目录中找不到引导 Skill 时,先检查插件安装;自然语言示例不会增加缺失工具,也不会授予权限。
+插件自带的 `powercontext-project-context` 将请求路由到对应工作流,和保存在 PowerContext 的 Managed Skill 是两个对象。宿主目录中找不到引导 Skill 时,先检查插件安装;自然语言示例不会增加缺失工具,也不会授予权限。
-生成受管 Skill 后,先检查 Candidate,再由有权限的主体作出审核决定。批准创建不可变 Revision,不代表安装或执行。
-按[创建与导出 Skill](../workflows/create-and-export-skill.md),使用 `powercontext skill export --target codex`
-将精确的 approved Revision 导出到目标宿主的 `.agents/skills/` 目录。
-确认 Codex 能发现导出的 Skill;没有出现时再重启。直接 Artifact 写入有自己的契约,这里的 Candidate 流程描述生成和提案路径。
-谁能审核取决于权限,不要求一定由人审核;宿主发现 Skill 也不会授予执行权限。
+生成受管 Skill 后,先检查 Candidate,再由有权限的主体作出审核决定。批准创建不可变 Revision,不代表安装或执行。按[创建与导出 Skill](../workflows/create-and-export-skill.md),使用 `powercontext skill export --target codex` 将精确的 approved Revision 导出到目标宿主的 `.agents/skills/` 目录。确认 Codex 能发现导出的 Skill;没有出现时再重启。直接 Artifact 写入有自己的契约,这里的 Candidate 流程描述生成和提案路径。谁能审核取决于权限,不要求一定由人审核;宿主发现 Skill 也不会授予执行权限。
## 继续使用
diff --git a/docs/zh/rfcs/1892-script-installation.md b/docs/zh/rfcs/1892-script-installation.md
new file mode 100644
index 0000000000..298b507b8a
--- /dev/null
+++ b/docs/zh/rfcs/1892-script-installation.md
@@ -0,0 +1,174 @@
+---
+title: 脚本安装
+description: 引导安装 Python 和 uv,选择 PowerContext 版本,并安装匹配的 Agent 集成。
+---
+
+- Proposal Name: `script_installation`
+- Start Date: 2026-10-09
+- RFC PR: [oceanbase/powercontext#1892](https://github.com/oceanbase/powercontext/pull/1892)
+- Tracking Issue: [oceanbase/powercontext#1900](https://github.com/oceanbase/powercontext/issues/1900)
+- Related RFCs: [RFC 1733](1733-usability-and-agent-workflows.md)、[RFC 1299](1299_local_server_availability_and_service_installation.md)
+
+# Summary
+
+提供通过 uv 安装 PowerContext 的 Bash 和 PowerShell 脚本。脚本复用或安装 Python 和 uv,选择 Runtime profile,并按需安装版本匹配的 Agent 集成。默认包版本为所选索引中的最新稳定版,用户也可以指定准确版本。
+
+配置和个人服务注册仍由用户分步执行现有 CLI 命令。脚本完成后给出这些步骤的指引,具体操作见[安装指南](../docs/get-started/install-and-run.md)。
+
+# Motivation
+
+安装 PowerContext 需要兼容的 Python、uv、正确的包 extras,以及匹配的集成 ref。Python 包、uv 二进制和 Python 发行版还需要分别配置下载来源;仅更换包索引无法解决 Python 下载失败的问题。
+
+安装器负责前置依赖和发行版本选择,复用现有配置、集成和服务命令。用户可以先安装软件,再决定何时启动 Server、使用哪个 Agent 宿主。
+
+# Guide-level explanation
+
+## Runtime profile 与前置依赖
+
+默认的 `local` profile 安装 CLI、Client 和本地 Server 依赖。`--profile client` 安装连接远程 Server 所需的 CLI 和 Client 依赖。Profile 与 Agent 选择相互独立。
+
+安装器复用已有 uv,以及兼容的系统 Python 或 uv 管理的 Python;缺少时在用户目录补齐。默认安装可以无人值守执行,无需管理员权限,完成后打印新终端所需的 PATH 命令。
+
+## 发行版本与 Agent 集成
+
+`--version latest` 选择已配置索引中兼容的最新稳定版。指定准确版本可以固定包版本。例如:
+
+```bash
+curl -fsSL https://powercontext.oceanbase.io/install.sh | bash -s -- --version 1.2.0 --host codex
+```
+
+只有显式传入 `--host` 才运行集成安装。所选宿主需要 Git 和各自的前置依赖;可以选择多个宿主。`--no-hosts` 显式跳过集成,与 `--host` 冲突。
+
+安装器读取已安装的 CLI 版本,把对应的 `powercontext-vVERSION` tag 传给 `powercontext setup`,全部宿主使用同一发行 ref。`--env-file PATH` 把已有连接文件传给宿主安装;安装器不解析或执行该文件。
+
+## 配置与个人服务
+
+安装完成后,用户通过现有 CLI 配置本地 Server、注册个人服务,由原生用户服务管理器在终端关闭后继续运行 Server。Client-only 安装按远程连接指南设置。[安装指南](../docs/get-started/install-and-run.md#配置并安装个人服务)说明配置、服务注册和连接检查的具体步骤。
+
+配置、Server 启动和服务生命周期由各自的现有命令负责。个人服务的 loopback 绑定、管理器支持等约束沿用 [RFC 1299](1299_local_server_availability_and_service_installation.md)。Windows 支持保持试验性。
+
+## 下载来源
+
+包索引、uv 二进制和 Python 发行版分别配置来源。用户可以选择区域或分别指定来源。显式设置优先于自动镜像;自动镜像可以按下文规则回退到官方源。
+
+# Reference-level explanation
+
+## 职责与执行顺序
+
+入口为 `website/public/install.sh` 和 `website/public/install.ps1`,作为网站静态资源发布。两个脚本提供相同的公开选项和失败行为。
+
+| 职责 | 负责方 |
+| --- | --- |
+| 校验安装参数、准备前置依赖、编排安装步骤 | Shell 脚本 |
+| 解析包、选择兼容产物、维护工具环境 | uv |
+| 安装选定的 Agent 集成 | 现有 `powercontext setup` 适配器 |
+| 配置和运行 Server、诊断状态、管理个人服务 | 现有 CLI 和服务层 |
+
+脚本按以下顺序执行:
+
+1. 校验参数,包括宿主选项冲突及所指定连接文件是否存在。
+2. 复用或安装 uv 和 Python,再选择包索引。
+3. 在 uv tool 环境中安装所选包 requirement。
+4. 通过 `uv tool dir --bin` 找到已安装入口,检查版本和命令可用性。
+5. 打印已安装版本和 PATH 指引,再按该版本的 tag 安装选定宿主。
+6. 打印本地服务或远程连接指引;宿主安装失败时返回非零状态。
+
+## 前置依赖复用
+
+缺少 uv 时,安装器使用自己维护的固定版本安装,与 PowerContext 版本无关。已有 uv 直接复用,不执行升级。
+
+Python 发现接受 3.11 及以上、低于 4.0 的版本,并排除虚拟环境。没有兼容解释器时,通过 uv 安装 Python 3.12。平台、架构和产物由 uv 选择。`UV_INSTALL_DIR`、`UV_TOOL_DIR` 和 `UV_TOOL_BIN_DIR` 继续控制安装位置。
+
+## 版本和 profile 语义
+
+| 选择 | 包 requirement 与行为 |
+| --- | --- |
+| `--profile local`(默认) | `powercontext[cli,server]` |
+| `--profile client` | `powercontext[cli]`,包含 Client 依赖 |
+| `--version latest`(默认) | 未固定版本的 profile requirement,使用 `uv tool install --upgrade --force --prerelease disallow` |
+| 准确版本 | 在 profile requirement 后追加 `==VERSION` |
+
+`latest` 指所选解释器及已配置索引兼容的最新稳定版。`--force` 让旧版 uv 重新解析工具环境,避免已有预发布版本绕过稳定版选择。解析失败时保留请求的 requirement 并报告错误。
+
+准确版本采用 `X.Y.Z`,可以追加 `aN`、`bN` 或 `rcN`。该选项不接受 0.1.0 之前的版本、版本范围、源码 ref 或 URL。更换 profile 会替换工具的依赖集合;需要管理其他 extras 的用户使用手动安装路径。
+
+已安装入口必须报告发行版本,准确版本请求必须与其一致。所有 profile 都需要通过 CLI help 和 `capabilities --help`;`local` 还需要通过 `config init --help` 和 `server run --help`。这些检查验证导入和命令可用性;Server 就绪状态在用户完成配置并启动后检查。
+
+## 来源选择与回退
+
+区域选择依次使用 `--region`、`POWERCONTEXT_INSTALL_REGION`、本地命名时区、locale 地区,最后采用全球源。检测只读取本地设置,不调用网络定位服务。
+
+| 组件 | 全球默认源 | 中国区域自动源 | 显式控制 |
+| --- | --- | --- | --- |
+| PowerContext 及依赖 | PyPI | 清华 PyPI 镜像 | `--index-url`、uv 索引设置及配置文件 |
+| uv 安装器及二进制 | Astral 渠道 | USTC release 镜像 | `POWERCONTEXT_UV_INSTALLER_URL`、`UV_DOWNLOAD_URL`、uv 安装器镜像变量 |
+| Python | uv 默认渠道 | NJU python-build-standalone 镜像 | `UV_PYTHON_INSTALL_MIRROR`、uv Python 下载配置 |
+
+已有 uv 配置时不自动选择包镜像。`--index-url` 替换默认索引,额外索引沿用 uv 的优先级。认证和包索引协议由 uv 处理。脚本 URL 参数必须使用 HTTPS,且不含凭据;pip 索引变量不会配置 uv。
+
+显式 uv 和 Python 来源不做回退。自动选择时,uv 镜像安装器不可用则回退 Astral;uv 镜像产物可回退官方源。Python 构建 URL 由 uv 提供;脚本检查对应镜像产物,自动镜像失败时使用 uv 默认渠道重试。
+
+自动包镜像在工具安装前接受有超时限制的可用性请求。镜像不可达时回退 PyPI;可达时保持选中,即使缺少请求版本。解析和产物下载错误由 uv 报告,脚本不在工具安装尝试后切换索引,以免文件已改变时又使用另一来源重试替换。索引解析和兼容性检查仍由 uv 负责。
+
+## 失败与恢复
+
+后续步骤失败时,保留已经完成的组件:
+
+| 失败阶段 | 结果与恢复方式 |
+| --- | --- |
+| 前置依赖安装失败 | 停止并报告下载错误,保留已经安装的前置依赖 |
+| 包解析失败,包括准确版本不存在 | 保留已安装工具,报告 uv 错误 |
+| 已安装入口验证失败 | 报告包已安装但验证失败,允许指定准确版本重试 |
+| 宿主安装失败 | 保留已验证的 Runtime,返回非零状态,并提示使用匹配的发行 ref 重试 setup |
+
+入口验证失败时,旧可执行文件可能已经被替换。安装器不回滚包文件,也不撤销已经完成的宿主操作,只清理自己的临时下载。来源和 PATH 变化局限于安装进程;PowerShell 在 `finally` 恢复临时来源变量。
+
+## 持久化与兼容性
+
+包安装保持 `.env`、凭据、数据目录和数据库 schema 不变,也不停止或重启服务。启动升级后的 Server 时,用户按现有迁移和服务指南操作。Runtime API 及现有配置、诊断、服务命令保持原有契约。
+
+`UV_OFFLINE=1` 禁用安装器探测和 uv 网络访问。缓存重装需要已有 uv、兼容 Python 和全部所需包;缺少前置依赖或产物时明确失败。
+
+## 可执行契约与验收
+
+两个 shell 适配器都必须通过 `tests/fixtures/installation/*.json` 中的共享行为用例。用例通过真实 uv 和小型 fixture wheel,检查公开参数、安装后的能力、匹配的宿主 ref 和退出结果。
+
+验收覆盖:
+
+- 缺少 uv 或 Python 时的引导、复用已有工具与配置,以及包含空格或非 ASCII 字符的路径。
+- 最新稳定版、准确版本、显式预发布版,以及解析失败时保留已安装工具。
+- Client/local profile 替换、显式宿主选择、宿主部分失败及已有连接文件传递。
+- 无人值守和 Bash 管道执行,以及 PowerShell 文件和下载后 scriptblock 入口。
+- 安装实际 wheel、配置验证、HTTP 就绪、Memory 持久化、离线缓存重装和重启回读。
+
+原生 CI 矩阵在 Linux、macOS 和 Windows 上运行安装验收;受控索引测试在 Linux 上验证镜像选择和失败处理。平台验收需要原生执行,语法检查和模拟宿主不代表服务管理器或 Agent 工作流可用。发布引用检查保持显式版本示例同步,同时保留 `latest` 默认值。
+
+# Drawbacks
+
+Bash 和 PowerShell 需要分别实现前置依赖与来源选择。自动镜像增加可用性依赖。`latest` 会随所选索引发布新版本而变化,可重复部署需要准确版本。匹配的集成 tag 用于协调版本,各宿主仍需单独验证兼容性。
+
+# Rationale and alternatives
+
+已有 Python 和 uv 的用户可以直接使用 uv 命令;脚本负责补齐这些前置依赖。源码安装仍供开发使用。脚本默认使用 `latest`,避免两次网站发布之间固定的包版本逐渐过时。
+
+Python 安装引擎仍需要 shell 引导。生成 shell 适配器会增加生成器,同时仍需原生测试。初期采用两个较小的适配器,并共享行为用例,可以减少维护环节。
+
+uv 已经维护包环境和安装记录,额外安装账本会重复保存这些状态。如果以后分发独立版本的原始产物,再引入对应的清单和记录。
+
+# Prior art
+
+[RFC 1408](https://github.com/oceanbase/powercontext/pull/1408) 区分安装、配置和诊断,定义独立的 profile、宿主选择及组件恢复。[RFC 1299](1299_local_server_availability_and_service_installation.md) 定义本方案复用的个人服务生命周期。
+
+[Magpie 安装器](https://github.com/yetone/magpie/blob/023f5aaad2ecd41ae04390166b9cac9a0b300d81/site/public/install.sh) 区分发行源与产物镜像,在替换前验证二进制校验和。它的清单用于直接分发二进制,本方案则把包分发交给 uv。
+
+[Lody daemon 安装器](https://github.com/LodyAI/Lody/blob/811b573329716b23e1144e5d66211ea4ddfb0dfd/specs/daemon-upgrade-installation.md) 解析实际 npm 安装位置并验证安装后的可执行文件,本方案同样检查实际入口。它的 [Agent runtime](https://github.com/LodyAI/Lody/blob/811b573329716b23e1144e5d66211ea4ddfb0dfd/apps/cli/src/agent/managed-agent-runtime.ts) 另行管理原始归档及其记录。
+
+[Bub](https://github.com/bubbuild/bub/tree/b4a61bf1326729a024161d22ba20019b8500f907/website/public) 先引导 uv,再运行 Python preset 解析器。本方案同样使用 shell 引导,并在各原生平台测试两个适配器。[uv 工具环境](https://docs.astral.sh/uv/concepts/tools/)、[uv 配置](https://docs.astral.sh/uv/concepts/configuration-files/)和 [Python Simple API](https://packaging.python.org/en/latest/specifications/simple-repository-api/)定义交给 uv 处理的环境与包来源规则。
+
+# Unresolved questions
+
+完整离线包、密码学校验安装清单、独立打包的宿主产物,以及配置存储变化,需要单独设计。本 RFC 保持 Windows 的试验性支持状态。
+
+# Future possibilities
+
+当需要独立组件版本、不可变清单或持久修复记录时,可以考虑安装引擎。离线包可以携带全部所需产物。这些扩展仍保留显式的 Server 启动和服务注册。
diff --git a/scripts/release_version.py b/scripts/release_version.py
index 538f094d95..90f35dd31b 100644
--- a/scripts/release_version.py
+++ b/scripts/release_version.py
@@ -46,12 +46,14 @@
*INSTALL_REFERENCES,
r"PowerContext ",
r"package `",
+ r"--version ",
r"^To install ",
),
"docs/zh/docs/get-started/install-and-run.md": (
*INSTALL_REFERENCES,
r"PowerContext ",
r"Python 包版本为 `",
+ r"--version ",
r"^安装 ",
),
"integrations/dsh/plugins/powercontext/README.md": INSTALL_REFERENCES,
diff --git a/tests/fixtures/installation/README.md b/tests/fixtures/installation/README.md
new file mode 100644
index 0000000000..4e1c56b4db
--- /dev/null
+++ b/tests/fixtures/installation/README.md
@@ -0,0 +1,47 @@
+# Installer behavior contract
+
+Both shell installers run against the JSON catalogs in this directory through `tests/test_installation_contract.py`. Add a case here when changing a public argument or observable installer behavior.
+
+## Run the contract tests
+
+With Python and uv available, run from the repository root:
+
+```bash
+uv run pytest tests/test_installation_contract.py -q
+```
+
+The harness builds small fixture wheels, installs them with real uv in temporary directories without network access, and runs the installed launcher. Profile cases check the installed dependency set. Host cases use a fixture CLI to record the setup arguments and simulate success or failure.
+
+## Add a case
+
+Each catalog has `schema_version: 1` and a `cases` array. Give every case an ID unique across all catalogs. Describe the public arguments and the resulting installation state or error. Avoid assertions about private function names or call order.
+
+| Field | Meaning |
+| --- | --- |
+| `id` | Stable scenario name shown in test results |
+| `args` | Arguments passed to the native installer |
+| `git` | Whether Git is discoverable; defaults to false |
+| `setup_exit` | Exit status of the fixture host operation; defaults to success |
+| `reported_version` | Fixture CLI version; defaults to the wheel's `1.2.0` |
+| `environment_file` | Create a protected existing configuration file for the case |
+| `missing_commands` | Public command help paths omitted by the fixture |
+| `expected.exit_code` | Expected installer process exit code |
+| `expected.runtime_installed` | Whether uv installed the tool launcher, including verification failures |
+| `expected.server_extra` | Whether the selected profile contains the fixture Server dependency |
+| `expected.selected_hosts` | Hosts actually submitted to setup; defaults to none |
+| `expected.setup_ref` | Release tag actually submitted to setup |
+| `expected.setup_env_file` | Whether host setup received the selected configuration file |
+| `expected.verification_failed` | Whether the Runtime installed message must be absent |
+| `expected.error_contains` | Required diagnostic fragment |
+
+## Choose the right test suite
+
+| Suite | Coverage |
+| --- | --- |
+| `tests/test_installation_contract.py` | Shared installer arguments, profiles, command verification, host selection, and partial host failures using fixture wheels |
+| `tests/native/test_install_sources.py` | Source selection, fallback, and version resolution with real uv and a controlled loopback index on Linux |
+| `tests/native/test_installation.py` | Actual PowerContext wheel, prerequisite bootstrap, HTTP readiness, Memory persistence, offline reinstall, profiles, and versions |
+
+The installation CI workflow builds the PowerContext wheel and runs the shared contract and native acceptance on Linux, macOS, and Windows. Set `POWERCONTEXT_INSTALL_WHEEL` to a built wheel to run actual-wheel acceptance locally; see `.github/workflows/installation.yml` for the build and invocation.
+
+Fixture hosts do not exercise a real Agent or service manager. Controlled sources do not check public mirror availability. Personal-service acceptance runs separately in `.github/workflows/native-service.yml`.
diff --git a/tests/fixtures/installation/capabilities.json b/tests/fixtures/installation/capabilities.json
new file mode 100644
index 0000000000..9c9fe5dca2
--- /dev/null
+++ b/tests/fixtures/installation/capabilities.json
@@ -0,0 +1,116 @@
+{
+ "schema_version": 1,
+ "cases": [
+ {
+ "id": "missing-root-help",
+ "args": [
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ ""
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "server_extra": true,
+ "verification_failed": true,
+ "error_contains": "Package installed, but"
+ }
+ },
+ {
+ "id": "missing-capabilities",
+ "args": [
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ "capabilities"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "server_extra": true,
+ "verification_failed": true,
+ "error_contains": "Package installed, but"
+ }
+ },
+ {
+ "id": "runtime-without-optional-setup",
+ "args": [
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ "setup"
+ ],
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "server_extra": true
+ }
+ },
+ {
+ "id": "missing-config-init",
+ "args": [
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ "config init"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "server_extra": true,
+ "verification_failed": true,
+ "error_contains": "Package installed, but"
+ }
+ },
+ {
+ "id": "missing-server-run",
+ "args": [
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ "server run"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "server_extra": true,
+ "verification_failed": true,
+ "error_contains": "Package installed, but"
+ }
+ },
+ {
+ "id": "client-does-not-require-local-commands",
+ "args": [
+ "--profile",
+ "client",
+ "--no-hosts"
+ ],
+ "missing_commands": [
+ "config init",
+ "server run"
+ ],
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "server_extra": false
+ }
+ },
+ {
+ "id": "exact-version-report-mismatch",
+ "args": [
+ "--version",
+ "1.2.0",
+ "--no-hosts"
+ ],
+ "reported_version": "9.9.9",
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "server_extra": true,
+ "verification_failed": true,
+ "error_contains": "differs from the requested release"
+ }
+ }
+ ]
+}
diff --git a/tests/fixtures/installation/hosts.json b/tests/fixtures/installation/hosts.json
new file mode 100644
index 0000000000..fc650a6fe0
--- /dev/null
+++ b/tests/fixtures/installation/hosts.json
@@ -0,0 +1,168 @@
+{
+ "schema_version": 1,
+ "cases": [
+ {
+ "id": "unattended-default",
+ "args": [],
+ "git": false,
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "selected_hosts": [],
+ "server_extra": true
+ }
+ },
+ {
+ "id": "compatibility-no-hosts",
+ "args": [
+ "--no-hosts"
+ ],
+ "git": false,
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "selected_hosts": [],
+ "server_extra": true
+ }
+ },
+ {
+ "id": "client-profile",
+ "args": [
+ "--profile",
+ "client"
+ ],
+ "git": false,
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "selected_hosts": [],
+ "server_extra": false
+ }
+ },
+ {
+ "id": "explicit-hosts",
+ "args": [
+ "--host",
+ "codex",
+ "--host",
+ "claude"
+ ],
+ "git": true,
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "selected_hosts": [
+ "codex",
+ "claude"
+ ],
+ "setup_ref": "powercontext-v1.2.0",
+ "server_extra": true
+ }
+ },
+ {
+ "id": "host-partial-failure",
+ "args": [
+ "--host",
+ "codex"
+ ],
+ "git": true,
+ "setup_exit": 17,
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": true,
+ "selected_hosts": [
+ "codex"
+ ],
+ "setup_ref": "powercontext-v1.2.0",
+ "server_extra": true,
+ "error_contains": "Runtime installed, but integration setup did not complete"
+ }
+ },
+ {
+ "id": "host-choice-conflict",
+ "args": [
+ "--host",
+ "codex",
+ "--no-hosts"
+ ],
+ "git": true,
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": false,
+ "selected_hosts": [],
+ "error_contains": "--host and --no-hosts cannot be combined"
+ }
+ },
+ {
+ "id": "explicit-host-needs-git",
+ "args": [
+ "--host",
+ "codex"
+ ],
+ "git": false,
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": false,
+ "selected_hosts": [],
+ "error_contains": "requires Git"
+ }
+ },
+ {
+ "id": "host-uses-existing-file",
+ "environment_file": true,
+ "git": true,
+ "args": [
+ "--host",
+ "codex",
+ "--env-file",
+ "{env_file}"
+ ],
+ "expected": {
+ "exit_code": 0,
+ "runtime_installed": true,
+ "selected_hosts": [
+ "codex"
+ ],
+ "setup_ref": "powercontext-v1.2.0",
+ "setup_env_file": true
+ }
+ },
+ {
+ "id": "host-requires-existing-file",
+ "git": true,
+ "args": [
+ "--host",
+ "codex",
+ "--env-file",
+ "{env_file}"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": false,
+ "error_contains": "existing file"
+ }
+ },
+ {
+ "id": "unsupported-configure",
+ "args": [
+ "--configure"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": false,
+ "error_contains": "Unknown option"
+ }
+ },
+ {
+ "id": "unsupported-service",
+ "args": [
+ "--service"
+ ],
+ "expected": {
+ "exit_code": 1,
+ "runtime_installed": false,
+ "error_contains": "Unknown option"
+ }
+ }
+ ]
+}
diff --git a/tests/native/test_install_sources.py b/tests/native/test_install_sources.py
new file mode 100644
index 0000000000..1ab71a9e09
--- /dev/null
+++ b/tests/native/test_install_sources.py
@@ -0,0 +1,358 @@
+# Copyright (c) 2026 OceanBase.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Linux installer source acceptance with real uv and a loopback package index."""
+
+from __future__ import annotations
+
+import hashlib
+import http.server
+import io
+import os
+import shutil
+import ssl
+import subprocess
+import sys
+import urllib.parse
+import zipfile
+from pathlib import Path
+from threading import Thread
+from typing import ClassVar
+
+import pytest
+
+ROOT = Path(__file__).resolve().parents[2]
+BASH = shutil.which("bash")
+UV = os.environ.get("POWERCONTEXT_INSTALL_UV") or shutil.which("uv")
+pytestmark = pytest.mark.skipif(sys.platform != "linux" or not UV, reason="requires Linux and real uv")
+
+
+def build_wheel(version: str) -> bytes:
+ data = io.BytesIO()
+ metadata = f"powercontext-{version}.dist-info"
+ with zipfile.ZipFile(data, "w") as archive:
+ archive.writestr(
+ "fixture_cli.py",
+ "from importlib.metadata import version\ndef main():\n print(version('powercontext'))\n",
+ )
+ archive.writestr(
+ f"{metadata}/METADATA",
+ f"Metadata-Version: 2.1\nName: powercontext\nVersion: {version}\nProvides-Extra: cli\nProvides-Extra: server\n",
+ )
+ archive.writestr(f"{metadata}/WHEEL", "Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n")
+ archive.writestr(f"{metadata}/entry_points.txt", "[console_scripts]\npowercontext = fixture_cli:main\n")
+ archive.writestr(f"{metadata}/RECORD", "")
+ return data.getvalue()
+
+
+@pytest.fixture
+def tls_certificate(tmp_path):
+ openssl = shutil.which("openssl")
+ if not openssl:
+ pytest.skip("HTTPS download acceptance requires openssl")
+ cert, key = tmp_path / "cert.pem", tmp_path / "key.pem"
+ subprocess.run(
+ [
+ openssl,
+ "req",
+ "-x509",
+ "-newkey",
+ "rsa:2048",
+ "-nodes",
+ "-keyout",
+ str(key),
+ "-out",
+ str(cert),
+ "-days",
+ "1",
+ "-subj",
+ "/CN=localhost",
+ "-addext",
+ "subjectAltName=DNS:localhost",
+ "-addext",
+ "basicConstraints=critical,CA:FALSE",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ return cert, key
+
+
+@pytest.fixture
+def installation(tmp_path, tls_certificate): # noqa: C901 - one fixture owns the server and subprocess environment
+ assert BASH
+ bash_command: str = BASH
+ wheels = {version: build_wheel(version) for version in ("1.0.0", "1.1.0", "1.2.0rc1")}
+
+ class Index(http.server.BaseHTTPRequestHandler):
+ mirror_status = 200
+ mirror_versions: ClassVar[list[str]] = ["1.0.0", "1.1.0", "1.2.0rc1"]
+ requests: ClassVar[list[str]] = []
+
+ # Keep this controlled fixture compatible with standalone Python 3.11.
+ def log_message(self, format: str, *args: object) -> None: # noqa: A002
+ pass
+
+ def do_HEAD(self):
+ self.do_GET()
+
+ def do_GET(self):
+ self.requests.append(self.path)
+ path = urllib.parse.unquote(self.path)
+ if path.startswith("/cn/") and self.mirror_status != 200:
+ self.send_error(self.mirror_status)
+ return
+ if path.endswith("/powercontext/"):
+ versions = self.mirror_versions if path.startswith("/cn/") else list(wheels)
+ source = path.split("/")[1]
+ links = []
+ for version in versions:
+ name = f"powercontext-{version}-py3-none-any.whl"
+ # Valid URL encoding and opaque anchor text reproduce the filename-probe defect.
+ href = f"/{source}/files/{name}".replace(".", "%2E")
+ digest = hashlib.sha256(wheels[version]).hexdigest()
+ links.append(f'download')
+ payload = ("" + "\n".join(links)).encode()
+ content_type = "text/html"
+ elif path.endswith(".whl"):
+ payload = wheels[path.rsplit("/", 1)[-1].split("-")[1]]
+ content_type = "application/octet-stream"
+ else:
+ self.send_error(404)
+ return
+ self.send_response(200)
+ self.send_header("Content-Type", content_type)
+ self.send_header("Content-Length", str(len(payload)))
+ self.end_headers()
+ if self.command != "HEAD":
+ self.wfile.write(payload)
+
+ server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Index)
+ cert, key = tls_certificate
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain(cert, key)
+ server.socket = context.wrap_socket(server.socket, server_side=True)
+ worker = Thread(target=server.serve_forever, daemon=True)
+ worker.start()
+ base = f"https://localhost:{server.server_port}"
+ utilities = tmp_path / "utilities"
+ utilities.mkdir()
+ # Translate only distribution endpoints to fixtures; run the real downloader and uv.
+ mappings = {
+ "https://pypi.tuna.tsinghua.edu.cn/simple": base + "/cn/simple",
+ "https://pypi.org/simple": base + "/global/simple",
+ "https://explicit.invalid/simple": base + "/cn/simple",
+ }
+ for name, executable in (("uv", UV), ("curl", shutil.which("curl"))):
+ assert executable
+ wrapper = utilities / name
+ wrapper.write_text(
+ f"#!{sys.executable}\nimport os, sys\nmapping={mappings!r}\n"
+ "args=[mapping.get(value, value) for value in sys.argv[1:]]\n"
+ "args=[next((target + value[len(source):] for source,target in mapping.items() "
+ "if value.startswith(source)), value) for value in args]\n"
+ "for key in ('UV_DEFAULT_INDEX','UV_INDEX','UV_INDEX_URL'):\n"
+ " if key in os.environ: os.environ[key]=mapping.get(os.environ[key],os.environ[key])\n"
+ f"os.execv({executable!r}, [{executable!r}, *args])\n",
+ encoding="utf-8",
+ )
+ wrapper.chmod(0o755)
+ env = {
+ "PATH": str(utilities) + os.pathsep + os.environ["PATH"],
+ "HOME": str(tmp_path / "home"),
+ "TMPDIR": str(tmp_path),
+ "LANG": "C.UTF-8",
+ "XDG_CONFIG_HOME": str(tmp_path / "config"),
+ "XDG_CONFIG_DIRS": str(tmp_path / "system-config"),
+ "UV_TOOL_DIR": str(tmp_path / "tools"),
+ "UV_TOOL_BIN_DIR": str(tmp_path / "bin"),
+ "UV_CACHE_DIR": str(tmp_path / "cache"),
+ "UV_PYTHON_INSTALL_DIR": str(tmp_path / "python"),
+ "UV_HTTP_RETRIES": "0",
+ "NO_PROXY": "localhost,127.0.0.1",
+ "CURL_CA_BUNDLE": str(cert),
+ "SSL_CERT_FILE": str(cert),
+ "UV_NATIVE_TLS": "true",
+ }
+ Path(env["HOME"]).mkdir()
+
+ def install(*arguments, success=True, extra=None):
+ completed = subprocess.run(
+ [bash_command, str(ROOT / "website/public/install.sh"), "--no-hosts", *arguments],
+ cwd=tmp_path,
+ env=env | (extra or {}),
+ text=True,
+ capture_output=True,
+ timeout=30,
+ )
+ assert (completed.returncode == 0) == success, completed.stdout + completed.stderr
+ return completed.stdout + completed.stderr
+
+ def version():
+ return subprocess.check_output([str(tmp_path / "bin/powercontext"), "--version"], env=env, text=True).strip()
+
+ try:
+ yield install, version, Index, env
+ finally:
+ server.shutdown()
+ server.server_close()
+ worker.join()
+
+
+@pytest.mark.parametrize("region", ["global", "cn"])
+def test_encoded_release_installs_through_public_script(installation, region):
+ install, version, _index, _env = installation
+ output = install("--region", region, "--version", "1.0.0")
+ assert version() == "1.0.0"
+ assert "Runtime installed: 1.0.0" in output
+
+
+@pytest.mark.parametrize("region", ["global", "cn"])
+def test_latest_stable_and_explicit_prerelease(installation, region):
+ install, version, _index, _env = installation
+ install("--region", region)
+ assert version() == "1.1.0"
+ install("--region", region, "--version", "1.2.0rc1")
+ assert version() == "1.2.0rc1"
+ install("--region", region)
+ assert version() == "1.1.0"
+
+
+def test_latest_without_a_stable_release_preserves_installed_prerelease(installation):
+ install, version, index, _env = installation
+ install("--region", "cn", "--version", "1.2.0rc1")
+ index.mirror_versions = ["1.2.0rc1"]
+
+ output = install("--region", "cn", success=False)
+
+ assert "Installation failed" in output
+ assert version() == "1.2.0rc1"
+
+
+def test_unavailable_automatic_mirror_falls_back_before_tool_install(installation):
+ install, version, index, _env = installation
+ index.mirror_status = 503
+ output = install("--region", "cn", "--version", "1.0.0")
+ assert version() == "1.0.0"
+ assert "Automatic package mirror unavailable; using PyPI." in output
+ assert any(path.startswith("/global/") for path in index.requests)
+
+
+def test_reachable_stale_mirror_preserves_request_and_installed_runtime(installation):
+ install, version, index, _env = installation
+ install("--region", "global", "--version", "1.1.0")
+ index.requests.clear()
+ index.mirror_versions = ["1.0.0"]
+ output = install("--region", "cn", "--version", "1.2.0rc1", success=False)
+ assert "Installation failed" in output
+ assert version() == "1.1.0"
+ assert not any(path.startswith("/global/") for path in index.requests)
+
+
+def test_explicit_failed_index_has_no_automatic_fallback(installation):
+ install, _version, index, _env = installation
+ index.mirror_status = 403
+ install("--region", "cn", "--index-url", "https://explicit.invalid/simple", "--version", "1.0.0", success=False)
+ assert not any(path.startswith("/global/") for path in index.requests)
+
+
+def test_unrelated_config_preserved_and_region_does_not_override_index(installation):
+ install, version, index, env = installation
+ config = Path(env["XDG_CONFIG_HOME"]) / "uv/uv.toml"
+ config.parent.mkdir(parents=True)
+ config.write_text("compile-bytecode = false\n", encoding="utf-8")
+ original = config.read_bytes()
+ # Existing uv settings provide the local source while conservative suppression preserves config.
+ output = install("--region", "cn", "--version", "1.0.0", extra={"UV_DEFAULT_INDEX": "https://pypi.org/simple"})
+ assert version() == "1.0.0"
+ assert "Using existing uv configuration" in output
+ assert config.read_bytes() == original
+ assert not any(path.startswith("/cn/") for path in index.requests)
+
+
+@pytest.mark.parametrize("downloader", ["curl", "wget"])
+@pytest.mark.parametrize("route", ["direct", "https", "http"])
+def test_bootstrap_download_transport(tmp_path, tls_certificate, downloader, route):
+ executable = shutil.which(downloader)
+ if not executable:
+ pytest.skip(f"download acceptance requires {downloader}")
+ assert BASH
+ requests = []
+
+ class Installer(http.server.BaseHTTPRequestHandler):
+ def log_message(self, format: str, *args: object) -> None: # noqa: A002
+ pass
+
+ def do_GET(self):
+ scheme = "https" if isinstance(self.connection, ssl.SSLSocket) else "http"
+ requests.append((scheme, self.path))
+ if self.path != "/direct":
+ target = self.path.removeprefix("/")
+ port = secure.server_port if target == "https" else plain.server_port
+ self.send_response(302)
+ self.send_header("Location", f"{target}://localhost:{port}/direct")
+ self.end_headers()
+ return
+ # Stop before real installation; the marker proves the bootstrap executed the download.
+ payload = b'#!/bin/sh\nprintf executed > "$HOME/download-executed"\nexit 1\n'
+ self.send_response(200)
+ self.send_header("Content-Length", str(len(payload)))
+ self.end_headers()
+ self.wfile.write(payload)
+
+ cert, key = tls_certificate
+ plain = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Installer)
+ secure = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Installer)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain(cert, key)
+ secure.socket = context.wrap_socket(secure.socket, server_side=True)
+ workers = [Thread(target=server.serve_forever, daemon=True) for server in (plain, secure)]
+ utilities = tmp_path / "utilities"
+ utilities.mkdir()
+ for name in (downloader, "uname", "mktemp", "rm", "dirname", "cat", "sh", "readlink"):
+ tool = shutil.which(name)
+ assert tool
+ (utilities / name).symlink_to(tool)
+ wgetrc = tmp_path / "wgetrc"
+ wgetrc.write_text(f"ca_certificate = {cert}\n", encoding="utf-8")
+ for worker in workers:
+ worker.start()
+ try:
+ completed = subprocess.run(
+ [BASH, str(ROOT / "website/public/install.sh"), "--region", "global", "--no-hosts"],
+ env={
+ "PATH": str(utilities),
+ "HOME": str(tmp_path),
+ "TMPDIR": str(tmp_path),
+ "UV_INSTALL_DIR": str(tmp_path / "bin"),
+ "CURL_CA_BUNDLE": str(cert),
+ "WGETRC": str(wgetrc),
+ "POWERCONTEXT_UV_INSTALLER_URL": f"https://localhost:{secure.server_port}/{route}",
+ },
+ text=True,
+ capture_output=True,
+ timeout=30,
+ )
+ output = completed.stdout + completed.stderr
+ assert completed.returncode != 0, output
+ expected_execution = route == "direct" or (route == "https" and downloader == "curl")
+ assert (tmp_path / "download-executed").exists() == expected_execution, output
+ assert all(scheme == "https" for scheme, _path in requests), requests
+ if downloader == "wget" and route != "direct":
+ assert "Install curl" in output
+ finally:
+ for server, worker in zip((plain, secure), workers, strict=True):
+ server.shutdown()
+ server.server_close()
+ worker.join()
diff --git a/tests/native/test_installation.py b/tests/native/test_installation.py
new file mode 100644
index 0000000000..ea78e9b39a
--- /dev/null
+++ b/tests/native/test_installation.py
@@ -0,0 +1,382 @@
+# Copyright (c) 2026 OceanBase.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Acceptance through the installed CLI and a real HTTP Server, without model services."""
+
+from __future__ import annotations
+
+import email
+import os
+import shutil
+import socket
+import subprocess
+import sys
+import time
+import zipfile
+from collections.abc import Iterator
+from contextlib import contextmanager
+from functools import partial
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from pathlib import Path
+from threading import Thread
+from typing import Any
+
+import httpx
+import pytest
+from dotenv import dotenv_values
+
+ROOT = Path(__file__).parents[2]
+WHEEL = os.environ.get("POWERCONTEXT_INSTALL_WHEEL")
+pytestmark = pytest.mark.skipif(not WHEEL, reason="set POWERCONTEXT_INSTALL_WHEEL to run installation acceptance")
+
+
+def run(
+ command: list[str], root: Path, env: dict[str, str], name: str, *, stdin: str = "", success: bool = True
+) -> str:
+ with (root / f"{name}.log").open("w", encoding="utf-8") as log:
+ result = subprocess.run(
+ command,
+ cwd=root,
+ env=env,
+ input=stdin,
+ text=True,
+ stdout=log,
+ stderr=subprocess.STDOUT,
+ timeout=300,
+ check=False,
+ )
+ output = (root / f"{name}.log").read_text(encoding="utf-8", errors="replace")
+ assert (result.returncode == 0) == success, output
+ return output
+
+
+def request(url: str, token: str, body: dict[str, Any] | None = None) -> dict[str, Any]:
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
+ with httpx.Client(trust_env=False, timeout=5, headers=headers) as client:
+ response = client.get(url) if body is None else client.post(url, json=body)
+ response.raise_for_status()
+ return response.json()
+
+
+@contextmanager
+def running_server(cli: str, root: Path, env: dict[str, str], token: str, name: str) -> Iterator[str]:
+ with socket.socket() as listener:
+ listener.bind(("127.0.0.1", 0))
+ port = listener.getsockname()[1]
+ url = f"http://127.0.0.1:{port}"
+ log_path = root / f"{name}.log"
+ with log_path.open("w", encoding="utf-8") as log:
+ process = subprocess.Popen(
+ [cli, "server", "run", "--env-file", str(root / ".env"), "--host", "127.0.0.1", "--port", str(port)],
+ cwd=root,
+ env=env,
+ stdin=subprocess.DEVNULL,
+ stdout=log,
+ stderr=subprocess.STDOUT,
+ )
+ try:
+ deadline = time.monotonic() + 90
+ while time.monotonic() < deadline and process.poll() is None:
+ try:
+ request(url + "/health/ready", token)
+ break
+ except (httpx.TransportError, httpx.HTTPStatusError):
+ time.sleep(0.2)
+ else:
+ pytest.fail(log_path.read_text(encoding="utf-8", errors="replace"))
+ yield url
+ finally:
+ process.terminate()
+ try:
+ process.wait(timeout=15)
+ except subprocess.TimeoutExpired:
+ process.kill()
+ process.wait()
+
+
+def reinstall_offline(
+ installer: list[str], root: Path, env: dict[str, str], version: str, *, interactive: bool
+) -> None:
+ if not interactive:
+ run(installer, root, env, "reinstall-offline")
+ return
+ # A piped default install succeeds without implicitly opening Agent selection.
+ controller = """import errno, os, pty, sys
+pid, terminal = pty.fork()
+if pid == 0:
+ os.execv('/bin/bash', ['/bin/bash', '-o', 'pipefail', '-c',
+ 'cat "$1" | /bin/bash -s -- --version "$2"', 'installer-pipeline', *sys.argv[1:]])
+try:
+ os.write(terminal, b'\\n')
+ while True:
+ try:
+ data = os.read(terminal, 4096)
+ except OSError as error:
+ if error.errno == errno.EIO:
+ break
+ raise
+ if not data:
+ break
+ os.write(1, data)
+finally:
+ os.close(terminal)
+_, status = os.waitpid(pid, 0)
+sys.exit(os.waitstatus_to_exitcode(status))
+"""
+ output = run(
+ [sys.executable, "-c", controller, str(ROOT / "website/public/install.sh"), str(version)],
+ root,
+ env,
+ "reinstall-offline",
+ )
+ assert "Runtime installed:" in output
+ assert "Select hosts" not in output
+
+
+def utilities_without_python_or_uv(root: Path, system_path: str) -> Path:
+ """Keep OS utilities available while removing Python and uv from PATH discovery."""
+ utilities = root / "utilities"
+ utilities.mkdir()
+ for directory in system_path.split(os.pathsep):
+ for executable in Path(directory).iterdir():
+ if executable.name.startswith(("python", "pypy", "uv")) or not executable.is_file():
+ continue
+ target = utilities / executable.name
+ if not target.exists():
+ target.symlink_to(executable)
+ return utilities
+
+
+@pytest.mark.parametrize("environment", ["bootstrap-global", "bootstrap-cn", "existing"])
+def test_install_configure_remember_and_reinstall(tmp_path: Path, environment: str) -> None:
+ assert WHEEL is not None
+ wheel = Path(WHEEL).resolve()
+ with zipfile.ZipFile(wheel) as archive:
+ metadata = next(name for name in archive.namelist() if name.endswith(".dist-info/METADATA"))
+ version = email.message_from_bytes(archive.read(metadata))["Version"]
+ # The direct URL selects this build; a file URL hash fragment is not an integrity check.
+ constraints = tmp_path / "constraints.txt"
+ constraints.write_text(f"powercontext @ {wheel.as_uri()}\n", encoding="utf-8")
+ home_dir = tmp_path / "home with spaces 测试"
+ home_dir.mkdir()
+ env = {
+ key: value
+ for key, value in os.environ.items()
+ if not key.startswith(("UV_", "PIP_", "PYTHON", "POWERCONTEXT_")) and key != "VIRTUAL_ENV"
+ }
+ env.update(
+ HOME=str(home_dir),
+ USERPROFILE=str(home_dir),
+ # These subprocesses write UTF-8 log files, rather than a Windows console.
+ PYTHONIOENCODING="utf-8",
+ UV_TOOL_DIR=str(tmp_path / "tools"),
+ UV_TOOL_BIN_DIR=str(tmp_path / "bin"),
+ UV_CACHE_DIR=str(tmp_path / "cache"),
+ UV_PYTHON_INSTALL_DIR=str(tmp_path / "python"),
+ UV_NO_CONFIG="1",
+ UV_CONSTRAINT=str(constraints),
+ UV_NO_PROGRESS="1",
+ POWERCONTEXT_HOME=str(tmp_path / "state"),
+ )
+ if sys.platform == "win32":
+ # Python inherits PowerShell 7 module paths, which Windows PowerShell cannot load.
+ env.pop("PSMODULEPATH", None)
+ shell = shutil.which("powershell.exe")
+ assert shell is not None
+ installer = [
+ shell,
+ "-NoProfile",
+ "-ExecutionPolicy",
+ "Bypass",
+ "-File",
+ str(ROOT / "website/public/install.ps1"),
+ ]
+ if environment == "bootstrap-cn":
+ # Exercise the documented downloaded-scriptblock entry point as well as -File.
+ wrapper = tmp_path / "downloaded-installer.ps1"
+ script_path = str(ROOT / "website/public/install.ps1").replace("'", "''")
+ wrapper.write_text(
+ f"& ([scriptblock]::Create([IO.File]::ReadAllText('{script_path}'))) @args\n", encoding="utf-8"
+ )
+ installer[-1] = str(wrapper)
+ cli = str(tmp_path / "bin/powercontext.exe")
+ system_path = os.pathsep.join([str(Path(os.environ["SYSTEMROOT"]) / "System32"), os.environ["SYSTEMROOT"]])
+ else:
+ installer = ["/bin/bash", str(ROOT / "website/public/install.sh")]
+ cli = str(tmp_path / "bin/powercontext")
+ system_path = os.defpath
+ installer += ["--no-hosts", "--version", str(version)]
+ if environment.startswith("bootstrap"):
+ if sys.platform == "win32":
+ env["PATH"] = system_path
+ env["UV_PYTHON_NO_REGISTRY"] = "1"
+ else:
+ env["PATH"] = str(utilities_without_python_or_uv(tmp_path, system_path))
+ env.update(TZ="Asia/Shanghai", LC_ALL="C", LANG="en_US.UTF-8")
+ if environment == "bootstrap-global":
+ env["POWERCONTEXT_INSTALL_REGION"] = "cn"
+ installer += ["--region", "global"]
+ else:
+ env.pop("UV_NO_CONFIG")
+ uv_config = tmp_path / "uv.toml"
+ uv_config.write_text('[[index]]\nurl = "https://pypi.org/simple"\ndefault = true\n', encoding="utf-8")
+ env.update(
+ TZ="UTC",
+ LC_ALL="zh_CN.UTF-8",
+ UV_CONFIG_FILE=str(uv_config),
+ UV_PYTHON_DOWNLOADS="never",
+ UV_PYTHON_INSTALL_MIRROR="https://127.0.0.1:9/unavailable",
+ POWERCONTEXT_UV_INSTALLER_URL="https://127.0.0.1:9/unavailable",
+ )
+ run(installer, tmp_path, env, "install")
+ if environment == "existing":
+ # A fresh tool/cache forces resolution; exact reinstallation may legitimately reuse the existing tool.
+ failed_source_env = {
+ **env,
+ "UV_TOOL_DIR": str(tmp_path / "failed-source-tools"),
+ "UV_TOOL_BIN_DIR": str(tmp_path / "failed-source-bin"),
+ "UV_CACHE_DIR": str(tmp_path / "failed-source-cache"),
+ }
+ run(
+ [*installer, "--index-url", "https://127.0.0.1:9/simple"],
+ tmp_path,
+ failed_source_env,
+ "explicit-index",
+ success=False,
+ )
+ assert run([cli, "--version"], tmp_path, env, "version").strip() == version
+ run([cli, "config", "init", "--template", "--output", ".env"], tmp_path, env, "configure", stdin="\n")
+ config = (tmp_path / ".env").read_bytes()
+ run([cli, "config", "validate", "--env-file", ".env"], tmp_path, env, "validate")
+ token = dotenv_values(tmp_path / ".env").get("POWERCONTEXT_SERVER_AUTH_TOKEN") or ""
+ text = "Installation acceptance preserves the release decision."
+ with running_server(cli, tmp_path, env, token, "server") as url:
+ scope = request(url + "/v1/scopes/default", token)["scope_id"]
+ request(url + "/v1/memory/remember", token, {"scope_id": scope, "kind": "decision", "text": text})
+ query = {"scope_id": scope, "query": "release decision", "mode": "fts"}
+ found = request(url + "/v1/memory/search", token, query)
+ assert text in [hit["memory"]["text"] for hit in found["hits"]]
+ env.update(
+ UV_OFFLINE="1", UV_PYTHON_DOWNLOADS="never", POWERCONTEXT_UV_INSTALLER_URL="https://127.0.0.1:9/unavailable"
+ )
+ reinstall_offline(
+ installer, tmp_path, env, str(version), interactive=environment == "existing" and sys.platform != "win32"
+ )
+ assert (tmp_path / ".env").read_bytes() == config
+ with running_server(cli, tmp_path, env, token, "server-restarted") as url:
+ found = request(url + "/v1/memory/search", token, query)
+ assert text in [hit["memory"]["text"] for hit in found["hits"]]
+
+
+def test_latest_exact_versions_and_client_profile(tmp_path: Path) -> None:
+ """Resolve real wheels from a controlled index with stable and prerelease candidates."""
+ assert WHEEL is not None
+ uv = shutil.which("uv")
+ assert uv is not None
+ repository = tmp_path / "index"
+ packages = repository / "powercontext"
+ packages.mkdir(parents=True)
+ shutil.copyfile(WHEEL, packages / Path(WHEEL).name)
+ for version in ("1.2.1", "1.2.2rc1"):
+ run(
+ [uv, "build", "--project", str(ROOT), "--wheel", "--out-dir", str(packages)],
+ tmp_path,
+ {**os.environ, "SETUPTOOLS_SCM_PRETEND_VERSION": version},
+ f"build-{version}",
+ )
+ (packages / "index.html").write_text(
+ "\n".join(f'{wheel.name}' for wheel in sorted(packages.glob("*.whl"))),
+ encoding="utf-8",
+ )
+ env = {
+ key: value
+ for key, value in os.environ.items()
+ if not key.startswith(("UV_", "PIP_", "PYTHON", "POWERCONTEXT_")) and key != "VIRTUAL_ENV"
+ }
+ env.update(
+ PYTHONIOENCODING="utf-8",
+ UV_NO_CONFIG="1",
+ UV_TOOL_DIR=str(tmp_path / "tools"),
+ UV_TOOL_BIN_DIR=str(tmp_path / "bin with spaces"),
+ UV_CACHE_DIR=str(tmp_path / "cache"),
+ UV_NO_PROGRESS="1",
+ POWERCONTEXT_HOME=str(tmp_path / "state"),
+ )
+ if sys.platform == "win32":
+ shell = shutil.which("powershell.exe")
+ assert shell is not None
+ installer = [
+ shell,
+ "-NoProfile",
+ "-ExecutionPolicy",
+ "Bypass",
+ "-File",
+ str(ROOT / "website/public/install.ps1"),
+ ]
+ cli = str(tmp_path / "bin with spaces/powercontext.exe")
+ python = str(tmp_path / "tools/powercontext/Scripts/python.exe")
+ else:
+ installer = ["/bin/bash", str(ROOT / "website/public/install.sh")]
+ cli = str(tmp_path / "bin with spaces/powercontext")
+ python = str(tmp_path / "tools/powercontext/bin/python")
+ server = ThreadingHTTPServer(("127.0.0.1", 0), partial(SimpleHTTPRequestHandler, directory=str(repository)))
+ worker = Thread(target=server.serve_forever, daemon=True)
+ worker.start()
+ env["UV_INDEX"] = f"http://127.0.0.1:{server.server_port}"
+ try:
+ # Contradictory host intent must fail before any tool installation.
+ run([*installer, "--host", "codex", "--no-hosts"], tmp_path, env, "host-choice-conflict", success=False)
+ assert not Path(cli).exists()
+ client_install = [*installer, "--no-hosts", "--profile", "client"]
+ run([*client_install, "--version", "1.2.0"], tmp_path, env, "install-old")
+ assert run([cli, "--version"], tmp_path, env, "old-version").strip() == "1.2.0"
+ run(client_install, tmp_path, env, "install-latest")
+ assert run([cli, "--version"], tmp_path, env, "latest-version").strip() == "1.2.1"
+ if shutil.which("git"):
+ output = run(
+ [*installer, "--profile", "client", "--host", "unknown-host"],
+ tmp_path,
+ env,
+ "integration-failure",
+ success=False,
+ )
+ assert "Runtime installed" in output
+ assert "powercontext-v1.2.1" in output
+ assert run([cli, "--version"], tmp_path, env, "retained-runtime").strip() == "1.2.1"
+ run(
+ [python, "-c", "from importlib.util import find_spec; assert find_spec('fastapi') is None"],
+ tmp_path,
+ env,
+ "client-without-server",
+ )
+ assert not (tmp_path / "state").exists()
+ run([*client_install, "--version", "1.2.2rc1"], tmp_path, env, "install-prerelease")
+ assert run([cli, "--version"], tmp_path, env, "prerelease-version").strip() == "1.2.2rc1"
+ run([*client_install, "--version", "99.0.0"], tmp_path, env, "missing-version", success=False)
+ assert run([cli, "--version"], tmp_path, env, "preserved-version").strip() == "1.2.2rc1"
+ run(client_install, tmp_path, env, "stable-after-prerelease")
+ assert run([cli, "--version"], tmp_path, env, "restored-stable-version").strip() == "1.2.1"
+ run([*installer, "--no-hosts", "--version", "1.2.0"], tmp_path, env, "switch-to-local")
+ run([python, "-c", "import fastapi"], tmp_path, env, "local-includes-server")
+ run([*client_install, "--version", "1.2.0"], tmp_path, env, "switch-to-client")
+ run(
+ [python, "-c", "from importlib.util import find_spec; assert find_spec('fastapi') is None"],
+ tmp_path,
+ env,
+ "server-dependencies-removed",
+ )
+ finally:
+ server.shutdown()
+ worker.join(timeout=5)
+ server.server_close()
diff --git a/tests/test_ci_changes.py b/tests/test_ci_changes.py
index f63ccc3e7a..67d7a5208c 100644
--- a/tests/test_ci_changes.py
+++ b/tests/test_ci_changes.py
@@ -36,6 +36,12 @@
(["docs/zh/docs/quickstart.md"], {"website", "docs_tests"}),
(["docs/en/rfcs/0001_design.md", "README_CN.md"], {"website", "docs_tests"}),
(["website/src/app/page.tsx"], {"website"}),
+ (["website/public/install.sh"], {"website", "packaging"}),
+ (["website/public/install.ps1"], {"website", "packaging"}),
+ ([".github/workflows/installation.yml"], {"packaging"}),
+ (["tests/test_installation_contract.py"], {"tests", "packaging"}),
+ (["tests/native/test_installation.py"], {"tests", "native", "packaging"}),
+ (["tests/native/test_install_sources.py"], {"tests", "native", "packaging"}),
(["tests/e2e/test_blog_site.py"], {"website", "tests"}),
(["AGENTS.md", "CONTRIBUTING.md"], set()),
(["deploy/helm/powercontext/templates/deployments.yaml"], {"helm"}),
diff --git a/tests/test_ci_pipeline.py b/tests/test_ci_pipeline.py
index ea808a474c..1fe657ceef 100644
--- a/tests/test_ci_pipeline.py
+++ b/tests/test_ci_pipeline.py
@@ -169,7 +169,7 @@ def test_cancelled_or_draft_runs_cannot_start_terminal_runner_jobs():
assert "converted_to_draft" in workflow("master.yml")["on"]["pull_request"]["types"]
-@pytest.mark.parametrize("selected_job", ["quality", "harness-validation"])
+@pytest.mark.parametrize("selected_job", ["quality", "harness-validation", "script-installation"])
@pytest.mark.parametrize(
("selected_result", "unselected_result", "passes"),
[
@@ -185,7 +185,7 @@ def test_actual_ci_result_script_rejects_missing_selected_acceptance(
):
script = workflow("master.yml")["jobs"]["ci-result"]["steps"][0]["run"]
script = script.split("<<'PYTHON'\n", 1)[1].rsplit("PYTHON", 1)[0]
- required = ["quality", "upgrade-release-gate", "harness-validation"]
+ required = ["quality", "upgrade-release-gate", "harness-validation", "script-installation"]
results = {job: {"result": "success"} for job in required}
results[selected_job]["result"] = selected_result
results["desktop-validation"] = {"result": unselected_result}
diff --git a/tests/test_installation_contract.py b/tests/test_installation_contract.py
new file mode 100644
index 0000000000..81b54e135b
--- /dev/null
+++ b/tests/test_installation_contract.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2026 OceanBase.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Native installer conformance with real uv and offline synthetic distributions."""
+
+import csv
+import io
+import json
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+from pathlib import Path
+from typing import Any
+
+import pytest
+
+ROOT = Path(__file__).resolve().parents[1]
+CASES = [
+ case
+ for path in sorted((ROOT / "tests/fixtures/installation").glob("*.json"))
+ for case in json.loads(path.read_text())["cases"]
+]
+CLI = """import importlib.util
+import json
+import os
+from pathlib import Path
+import sys
+
+def main():
+ args = sys.argv[1:]
+ if args == ["--version"]:
+ print(os.environ.get("CONTRACT_REPORTED_VERSION", "1.2.0"))
+ elif not args or args[-1] == "--help":
+ command = " ".join(args[:-1]) if args else ""
+ missing = json.loads(os.environ.get("CONTRACT_MISSING_COMMANDS", "[]"))
+ if command in missing:
+ return 2
+ if command == "server run":
+ import contract_server
+ print("Synthetic installation contract CLI")
+ elif args and args[0] == "setup":
+ hosts = []
+ index = 1
+ while index < len(args):
+ if args[index].startswith("--"):
+ index += 2
+ else:
+ hosts.append(args[index])
+ index += 1
+ ref = args[args.index("--ref") + 1]
+ selected = {"hosts": hosts, "ref": ref}
+ if "--env-file" in args:
+ selected["env_file"] = args[args.index("--env-file") + 1]
+ Path(os.environ["CONTRACT_SETUP_RESULT"]).write_text(json.dumps(selected))
+ return int(os.environ.get("CONTRACT_SETUP_EXIT", "0"))
+ elif args == ["contract-capabilities"]:
+ print(json.dumps({"server_extra": importlib.util.find_spec("contract_server") is not None}))
+ else:
+ return 2
+ return 0
+"""
+
+
+def wheel(directory: Path, name: str, files: dict[str, str], extra_metadata: str = "") -> None:
+ """Build a tiny standards-shaped wheel using only stdlib ZIP/CSV."""
+ info = f"{name}-1.2.0.dist-info"
+ entries = files | {
+ f"{info}/METADATA": f"Metadata-Version: 2.3\nName: {name}\nVersion: 1.2.0\n{extra_metadata}\n",
+ f"{info}/WHEEL": "Wheel-Version: 1.0\nGenerator: installation-contract\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
+ }
+ record = io.StringIO()
+ writer = csv.writer(record)
+ writer.writerows((path, "", "") for path in entries)
+ writer.writerow((f"{info}/RECORD", "", ""))
+ entries[f"{info}/RECORD"] = record.getvalue()
+ with zipfile.ZipFile(directory / f"{name}-1.2.0-py3-none-any.whl", "w") as archive:
+ for path, content in entries.items():
+ archive.writestr(path, content)
+
+
+def executable_path(directory: Path, uv: str, windows: bool, include_git: bool) -> str:
+ """Expose real bootstrap prerequisites, optionally excluding Git."""
+ tools = directory / "tools"
+ tools.mkdir()
+ # Real executables only; uv creates the platform-native console launcher.
+ executables = {"uv": uv} if windows else {"uv": uv, "python": sys.executable}
+ for name, executable in executables.items():
+ destination = tools / (name + ".exe" if windows else name)
+ if windows:
+ shutil.copy2(executable, destination)
+ else:
+ destination.symlink_to(executable)
+ git = shutil.which("git")
+ if include_git and not git:
+ pytest.skip("explicit-host contract requires Git discovery")
+ if not windows:
+ for name in ("curl", "wget", "uname", "mktemp", "rm", "dirname", "cat", "sh", "readlink"):
+ executable = shutil.which(name)
+ if executable:
+ (tools / name).symlink_to(executable)
+ if include_git and git:
+ (tools / "git").symlink_to(git)
+ elif include_git and git:
+ # Presence check only: synthetic host setup never executes Git.
+ shutil.copy2(git, tools / "git.exe")
+ if windows:
+ return os.pathsep.join((
+ str(tools),
+ str(Path(sys.executable).parent),
+ str(Path(os.environ["SYSTEMROOT"]) / "System32"),
+ ))
+ return str(tools)
+
+
+@pytest.mark.parametrize("case", CASES, ids=lambda case: case["id"])
+def test_installation_contract(case: dict[str, Any], tmp_path: Path) -> None:
+ uv = shutil.which("uv")
+ if not uv:
+ pytest.skip("native installation contract requires real uv")
+ windows = sys.platform == "win32"
+ shell = shutil.which("powershell") or shutil.which("pwsh") if windows else shutil.which("bash")
+ if not shell:
+ pytest.skip("native installer shell is unavailable")
+ path = executable_path(tmp_path, uv, windows, case.get("git", False))
+ packages = tmp_path / "packages"
+ packages.mkdir()
+ wheel(packages, "contract_server", {"contract_server.py": ""})
+ wheel(
+ packages,
+ "powercontext",
+ {
+ "powercontext_contract.py": CLI,
+ "powercontext-1.2.0.dist-info/entry_points.txt": "[console_scripts]\npowercontext = powercontext_contract:main\n",
+ },
+ "Provides-Extra: cli\nProvides-Extra: server\nRequires-Dist: contract-server==1.2.0; extra == 'server'\n",
+ )
+ environment = {
+ key: value
+ for key, value in os.environ.items()
+ if not key.startswith(("UV_", "POWERCONTEXT_", "PIP_", "PYTHON", "CONTRACT_")) and key != "VIRTUAL_ENV"
+ }
+ environment.update({
+ "PYTHONIOENCODING": "utf-8",
+ "HOME": str(tmp_path),
+ "USERPROFILE": str(tmp_path),
+ "TMPDIR": str(tmp_path),
+ "PATH": path,
+ "UV_TOOL_DIR": str(tmp_path / "tool"),
+ "UV_TOOL_BIN_DIR": str(tmp_path / "bin"),
+ "UV_CACHE_DIR": str(tmp_path / "cache"),
+ "UV_NO_CONFIG": "1",
+ "UV_NO_INDEX": "1",
+ "UV_FIND_LINKS": str(packages),
+ "UV_PYTHON_DOWNLOADS": "never",
+ "UV_OFFLINE": "1",
+ "CONTRACT_SETUP_RESULT": str(tmp_path / "setup.json"),
+ "CONTRACT_SETUP_EXIT": str(case.get("setup_exit", 0)),
+ "CONTRACT_REPORTED_VERSION": case.get("reported_version", "1.2.0"),
+ "CONTRACT_MISSING_COMMANDS": json.dumps(case.get("missing_commands", [])),
+ })
+ env_file = tmp_path / "existing configuration.env"
+ if case.get("environment_file"):
+ env_file.write_text("POWERCONTEXT_SERVER_HTTP_PORT=18000\n", encoding="utf-8")
+ env_file.chmod(0o600)
+ arguments = [str(env_file) if value == "{env_file}" else value for value in case["args"]]
+ script = ROOT / "website/public" / ("install.ps1" if windows else "install.sh")
+ command = (
+ [shell, "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(script)] if windows else [shell, str(script)]
+ )
+ result = subprocess.run(
+ [*command, "--region", "global", *arguments], env=environment, capture_output=True, text=True, timeout=60
+ )
+ assert_installation_outcome(case, tmp_path, result, environment)
+
+
+def assert_installation_outcome(case, tmp_path, result, environment) -> None:
+ expected = case["expected"]
+ windows = sys.platform == "win32"
+ env_file = tmp_path / "existing configuration.env"
+ assert result.returncode == expected["exit_code"], result.stdout + result.stderr
+ executable = tmp_path / "bin" / ("powercontext.exe" if windows else "powercontext")
+ assert executable.exists() == expected["runtime_installed"]
+ setup = tmp_path / "setup.json"
+ observed = json.loads(setup.read_text()) if setup.exists() else {"hosts": []}
+ assert observed["hosts"] == expected.get("selected_hosts", [])
+ if "setup_ref" in expected:
+ assert observed["ref"] == expected["setup_ref"]
+ if expected.get("setup_env_file"):
+ assert observed["env_file"] == str(env_file)
+ if case.get("environment_file"):
+ assert env_file.read_text() == "POWERCONTEXT_SERVER_HTTP_PORT=18000\n"
+ if expected.get("verification_failed"):
+ assert "Runtime installed:" not in result.stdout
+ if "error_contains" in expected:
+ assert expected["error_contains"] in result.stdout + result.stderr
+ if expected["runtime_installed"] and "server_extra" in expected:
+ capabilities = subprocess.check_output([str(executable), "contract-capabilities"], env=environment, text=True)
+ assert json.loads(capabilities)["server_extra"] == expected["server_extra"]
diff --git a/tests/test_release_version.py b/tests/test_release_version.py
index 5af0f99c3c..1067c8c51c 100644
--- a/tests/test_release_version.py
+++ b/tests/test_release_version.py
@@ -87,11 +87,10 @@ def test_release_updates_installation_references_and_preserves_history(release_r
for relative in INSTALL_PAGES:
content = (release_repo / relative).read_text(encoding="utf-8")
assert set(re.findall(r"powercontext\[[^\]\n]+\]==([^\s\"`]+)", content)) == {version}, relative
- assert set(re.findall(r"powercontext-v([^\s\"`]+)", content)) == {version}, relative
+ assert set(re.findall(r"powercontext-v([0-9][^\s\"`]+)", content)) == {version}, relative
if relative.startswith(("README", "docs/")) and not relative.endswith("/manual-start.md"):
assert f"PowerContext {version}" in content, relative
if re.search(r"(?:a|b|rc)[0-9]+$", version):
- assert "stable release" not in content, relative
assert f"stable PowerContext {version}" not in content, relative
assert "正式版本" not in content, relative
assert "正式リリース" not in content, relative
@@ -99,6 +98,8 @@ def test_release_updates_installation_references_and_preserves_history(release_r
english = (release_repo / "docs/en/docs/get-started/install-and-run.md").read_text(encoding="utf-8")
chinese = (release_repo / "docs/zh/docs/get-started/install-and-run.md").read_text(encoding="utf-8")
assert f"package `{version}`" in english
+ assert f"--version {version}" in english
+ assert f"--version {version}" in chinese
assert f"To install {version} in a separate environment" in english
assert f"Python 包版本为 `{version}`" in chinese
assert f"安装 {version} 到独立环境" in chinese
diff --git a/website/public/install.ps1 b/website/public/install.ps1
new file mode 100644
index 0000000000..9bedf0f6f9
--- /dev/null
+++ b/website/public/install.ps1
@@ -0,0 +1,366 @@
+# Copyright (c) 2026 OceanBase.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version Latest
+
+$Version = 'latest'
+$UvVersion = '0.12.23'
+$RuntimeProfile = 'local'
+$Region = 'auto'
+if ($env:POWERCONTEXT_INSTALL_REGION) { $Region = $env:POWERCONTEXT_INSTALL_REGION }
+$IndexUrl = ''
+$Hosts = @()
+$NoHosts = $false
+$EnvFile = ''
+$Uv = ''
+$TempDir = $null
+
+function Show-Help {
+ Write-Host @'
+Install PowerContext on Windows. Python and uv need not be installed.
+
+Usage: powershell -ExecutionPolicy Bypass -File install.ps1 [options]
+
+ --version VERSION Exact release version, or latest (default: latest stable).
+ --profile PROFILE local (CLI and Server, default) or client (CLI and Client).
+ --region REGION auto, cn, or global (default: POWERCONTEXT_INSTALL_REGION or auto).
+ --index-url URL HTTPS default package index for this installation.
+ --host HOST Install an Agent integration; repeat for multiple hosts.
+ --no-hosts Compatibility option; integration setup is skipped by default.
+ --env-file PATH Existing configuration file for selected Agent integrations.
+ -h, --help Show this help.
+
+Agent integration setup runs only for explicitly selected --host values.
+After installation, run powercontext setup --ref "powercontext-v$(powercontext --version)".
+Configure and register a personal service separately after installation.
+Windows personal services remain experimental.
+
+Region priority: --region, POWERCONTEXT_INSTALL_REGION, named timezone, locale
+territory, then global. No network location service is queried.
+CN defaults: Tsinghua for PyPI, USTC for uv, NJU for Python. Global defaults use
+PyPI and Astral's download channels. Unavailable automatic mirrors fall back to
+official sources; explicit download settings are preserved without fallback.
+
+Download configuration (independent of the package index):
+ POWERCONTEXT_UV_INSTALLER_URL HTTPS uv PowerShell installer URL.
+ UV_DOWNLOAD_URL uv release artifact directory.
+ UV_INSTALLER_GITHUB_BASE_URL GitHub mirror base URL for uv binaries.
+ UV_PYTHON_INSTALL_MIRROR Mirror of Python distribution downloads.
+ UV_ASTRAL_MIRROR_URL Astral mirror for uv versions supporting it.
+Existing additional uv indexes still take precedence over the default index.
+'@
+}
+
+function Assert-HttpsUrl([string]$Value, [string]$Option) {
+ $Uri = $null
+ if (-not [Uri]::TryCreate($Value, [UriKind]::Absolute, [ref]$Uri) -or
+ $Uri.Scheme -ne 'https' -or -not $Uri.Host -or $Value -match '[@?#\s]') {
+ throw "$Option must be an HTTPS URL without credentials, query parameters, or fragments."
+ }
+}
+
+function Save-Download([string]$Url, [string]$Path, [int]$Timeout = 30) {
+ Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec $Timeout -OutFile $Path
+}
+
+function Select-Region {
+ $Source = 'explicit'
+ if ($Region -eq 'auto') {
+ $Source = 'timezone'
+ $Timezone = [TimeZoneInfo]::Local.Id
+ if ($env:TZ) { $Timezone = $env:TZ.TrimStart(':') }
+ if ($Timezone -in @('Asia/Shanghai', 'Asia/Chongqing', 'Asia/Chungking', 'Asia/Harbin', 'Asia/Urumqi', 'PRC', 'China Standard Time')) {
+ $Region = 'cn'
+ }
+ elseif ($Timezone -match '^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/' -or
+ ($Timezone -ne 'UTC' -and -not $env:TZ -and $Timezone -match 'Standard Time$')) {
+ $Region = 'global'
+ }
+ else {
+ $Source = 'locale'
+ $LocaleName = ''
+ foreach ($Name in @('LC_ALL', 'LC_MESSAGES', 'LANG')) {
+ $LocaleName = [Environment]::GetEnvironmentVariable($Name)
+ if ($LocaleName) { break }
+ }
+ if (-not $LocaleName -or $LocaleName -match '^(C($|\.)|POSIX$)') { $LocaleName = [Globalization.CultureInfo]::CurrentCulture.Name }
+ $Region = 'global'
+ if ($LocaleName -match '[_-]CN($|[.@])') { $Region = 'cn' }
+ }
+ }
+ Write-Host "Download region: $Region ($Source)."
+ return $Region
+}
+
+function Test-UvConfigFile {
+ if ($env:UV_CONFIG_FILE) { return $true }
+ if ($env:UV_NO_CONFIG -in @('1', 'true')) { return $false }
+ foreach ($Directory in @($env:APPDATA, $env:PROGRAMDATA)) {
+ if ($Directory -and (Test-Path -LiteralPath (Join-Path $Directory 'uv\uv.toml'))) { return $true }
+ }
+ return $false
+}
+
+function Test-UvConfiguration {
+ foreach ($Name in @('UV_DEFAULT_INDEX', 'UV_INDEX', 'UV_INDEX_URL', 'UV_EXTRA_INDEX_URL',
+ 'UV_OFFLINE', 'UV_NO_INDEX', 'UV_FIND_LINKS')) {
+ if ([Environment]::GetEnvironmentVariable($Name)) { return $true }
+ }
+ return (Test-UvConfigFile)
+}
+
+function Test-DownloadUrl([string]$Url) {
+ try {
+ Invoke-WebRequest -Uri $Url -UseBasicParsing -Method Head -TimeoutSec 10 | Out-Null
+ return $true
+ }
+ catch { return $false }
+}
+
+function Test-IndexAvailable([string]$Url) {
+ $IndexFile = Join-Path $TempDir 'index.html'
+ # Probe transport availability only; uv interprets the Simple API and versions.
+ try { Save-Download ($Url.TrimEnd('/') + '/powercontext/') $IndexFile }
+ catch {
+ Write-Host "Package mirror is unreachable: $Url"
+ return $false
+ }
+ return $true
+}
+
+function Select-Index {
+ if (-not $IndexUrl -and (Test-UvConfiguration)) {
+ Write-Host 'Using existing uv configuration; uv will check the configured indexes.'
+ return
+ }
+ if ($env:PIP_INDEX_URL -or $env:PIP_EXTRA_INDEX_URL) {
+ Write-Host 'uv does not read pip index settings. Use --index-url or uv configuration.'
+ }
+ if (-not $IndexUrl) {
+ $IndexUrl = 'https://pypi.org/simple'
+ if ($Region -eq 'cn') {
+ $Mirror = 'https://pypi.tuna.tsinghua.edu.cn/simple'
+ Write-Host "Checking package mirror availability: $Mirror"
+ if (Test-IndexAvailable $Mirror) { $IndexUrl = $Mirror }
+ else { Write-Host 'Automatic package mirror unavailable; using PyPI.' }
+ }
+ }
+ # A reachable mirror may be stale or incompatible. Let uv report that failure
+ # without retrying a tool installation or changing the requested requirement.
+ Write-Host "Package index: $IndexUrl"
+ $env:UV_DEFAULT_INDEX = $IndexUrl
+ return $IndexUrl
+}
+
+function Install-UvIfMissing {
+ $Command = Get-Command uv -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
+ $UserUv = Join-Path $HOME '.local\bin\uv.exe'
+ if ($env:UV_INSTALL_DIR) { $UserUv = Join-Path $env:UV_INSTALL_DIR 'uv.exe' }
+ if ($Command) { $Uv = $Command.Source }
+ elseif (Test-Path -LiteralPath $UserUv -PathType Leaf) { $Uv = $UserUv }
+ else {
+ if ($env:UV_OFFLINE -in @('1', 'true')) { throw 'uv is not installed and offline mode disables downloads.' }
+ Write-Host "Installing uv $UvVersion in the user executable directory."
+ $Url = "https://astral.sh/uv/$UvVersion/install.ps1"
+ if ($env:POWERCONTEXT_UV_INSTALLER_URL) { $Url = $env:POWERCONTEXT_UV_INSTALLER_URL }
+ $Installer = Join-Path $TempDir 'uv-install.ps1'
+ $CustomDownload = $env:POWERCONTEXT_UV_INSTALLER_URL -or $env:UV_DOWNLOAD_URL -or $env:INSTALLER_DOWNLOAD_URL -or
+ $env:UV_INSTALLER_GITHUB_BASE_URL -or $env:UV_INSTALLER_GHE_BASE_URL -or $env:UV_ASTRAL_MIRROR_URL
+ $Downloaded = $false
+ if ($Region -eq 'cn' -and -not $CustomDownload) {
+ $Mirror = "https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/$UvVersion"
+ try {
+ Write-Host "uv mirror: $Mirror"
+ Save-Download "$Mirror/uv-installer.ps1" $Installer
+ $Downloaded = $true
+ $env:UV_DOWNLOAD_URL = $Mirror
+ }
+ catch { Write-Host 'uv mirror installer unavailable; using the official installer.' }
+ }
+ if (-not $Downloaded) {
+ try { Save-Download $Url $Installer }
+ catch { throw 'Could not download uv. Check POWERCONTEXT_UV_INSTALLER_URL.' }
+ }
+ $env:UV_INSTALL_DIR = Split-Path -Parent $UserUv
+ $env:UV_NO_MODIFY_PATH = '1'
+ $PowerShell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
+ & $PowerShell -NoProfile -ExecutionPolicy Bypass -File $Installer | Out-Host
+ if ($LASTEXITCODE -ne 0 -and $Downloaded) {
+ # The official PowerShell installer accepts only one UV_DOWNLOAD_URL.
+ Write-Host 'uv mirror installation failed; retrying uv with official sources.'
+ Remove-Item Env:UV_DOWNLOAD_URL
+ & $PowerShell -NoProfile -ExecutionPolicy Bypass -File $Installer | Out-Host
+ }
+ if ($LASTEXITCODE -ne 0) { throw 'uv installation failed. Check the installer output and configured download source.' }
+ $Uv = $UserUv
+ }
+ if (-not (Test-Path -LiteralPath $Uv -PathType Leaf)) { throw 'uv executable was not found after installation.' }
+ Write-Host "Using uv: $Uv"
+ $env:PATH = "$(Split-Path -Parent $Uv);$env:PATH"
+ return $Uv
+}
+
+$SavedEnvironment = @{}
+foreach ($Name in @('UV_DEFAULT_INDEX', 'UV_INSTALL_DIR', 'UV_NO_MODIFY_PATH', 'UV_DOWNLOAD_URL')) {
+ $SavedEnvironment[$Name] = [Environment]::GetEnvironmentVariable($Name)
+}
+try {
+ for ($Index = 0; $Index -lt $args.Count; $Index++) {
+ $Option = $args[$Index]
+ switch ($Option) {
+ { $_ -in '--version', '--profile', '--region', '--index-url', '--host', '--env-file' } {
+ $Index++
+ if ($Index -ge $args.Count -or -not $args[$Index] -or $args[$Index].StartsWith('--')) {
+ throw "$Option requires a value."
+ }
+ switch ($Option) {
+ '--version' { $Version = $args[$Index] }
+ '--profile' { $RuntimeProfile = $args[$Index] }
+ '--region' { $Region = $args[$Index] }
+ '--index-url' { $IndexUrl = $args[$Index] }
+ '--host' { $Hosts += @($args[$Index]) }
+ '--env-file' { $EnvFile = $args[$Index] }
+ }
+ }
+ '--no-hosts' { $NoHosts = $true }
+ { $_ -in '-h', '--help' } { Show-Help; exit 0 }
+ default { throw 'Unknown option. Run install.ps1 --help.' }
+ }
+ }
+ if ($Region -cnotin @('auto', 'cn', 'global')) { throw 'Use --region auto, cn, or global.' }
+ if ($RuntimeProfile -cnotin @('local', 'client')) { throw 'Use --profile local or client.' }
+ if (($Version -cne 'latest' -and $Version -notmatch '^\d+\.\d+\.\d+((a|b|rc)\d+)?$') -or $Version.StartsWith('0.0.')) {
+ throw 'Use latest or an exact package version starting at 0.1.0.'
+ }
+ if ($IndexUrl) { Assert-HttpsUrl $IndexUrl '--index-url' }
+ if ($env:POWERCONTEXT_UV_INSTALLER_URL) { Assert-HttpsUrl $env:POWERCONTEXT_UV_INSTALLER_URL 'POWERCONTEXT_UV_INSTALLER_URL' }
+ if ($NoHosts -and $Hosts.Count) { throw '--host and --no-hosts cannot be combined.' }
+ if ($EnvFile -and -not (Test-Path -LiteralPath $EnvFile -PathType Leaf)) {
+ throw '--env-file must name an existing file.'
+ }
+ if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) { throw 'Use install.sh on macOS and Linux.' }
+ if ($Hosts.Count -and -not (Get-Command git -CommandType Application -ErrorAction SilentlyContinue)) {
+ throw 'Agent integration setup requires Git. Install Git or use --no-hosts.'
+ }
+ $TempDir = Join-Path ([IO.Path]::GetTempPath()) ('powercontext-install-' + [Guid]::NewGuid())
+ [IO.Directory]::CreateDirectory($TempDir) | Out-Null
+ $Region = Select-Region
+ $Uv = Install-UvIfMissing
+
+ $Python = $null
+ $FoundPython = $false
+ try {
+ $Python = & $Uv python find --system --no-project --no-python-downloads '>=3.11,<4' 2>$null
+ $FoundPython = $LASTEXITCODE -eq 0 -and $Python
+ }
+ catch { $FoundPython = $false }
+ if ($FoundPython) {
+ Write-Host "Using local Python: $Python"
+ }
+ else {
+ if ($env:UV_PYTHON_DOWNLOADS -in @('never', 'false', '0')) { throw 'No compatible local Python; UV_PYTHON_DOWNLOADS disables downloads.' }
+ if ($env:UV_OFFLINE -in @('1', 'true')) { throw 'No compatible local Python in offline mode.' }
+ $PythonArgs = @('python', 'install', '3.12')
+ $AutomaticMirror = $false
+ if ($Region -eq 'cn' -and -not ($env:UV_PYTHON_INSTALL_MIRROR -or $env:UV_ASTRAL_MIRROR_URL -or
+ $env:UV_PYTHON_DOWNLOADS_JSON_URL -or (Test-UvConfigFile))) {
+ $Downloads = & $Uv python list 'cpython@3.12' --only-downloads --show-urls --color never
+ if ($LASTEXITCODE -ne 0) { throw 'Could not resolve a Python download.' }
+ $Url = (@($Downloads)[0] -split '\s+')[-1]
+ if ($Url -match '^https://[^/]+/(?:github/|astral-sh/)?python-build-standalone/releases/download/(.+)$') {
+ $Mirror = 'https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone'
+ if (Test-DownloadUrl "$Mirror/$($Matches[1])") {
+ Write-Host "Python mirror: $Mirror"
+ $PythonArgs += @('--mirror', $Mirror)
+ $AutomaticMirror = $true
+ }
+ else { Write-Host 'Python mirror does not provide the requested build; using uv default sources.' }
+ }
+ }
+ Write-Host 'No compatible local Python found. Installing Python 3.12.'
+ & $Uv @PythonArgs
+ if ($LASTEXITCODE -ne 0 -and $AutomaticMirror) {
+ Write-Host 'Python mirror installation failed; retrying with uv default sources.'
+ & $Uv python install 3.12
+ }
+ if ($LASTEXITCODE -ne 0) { throw 'Python installation failed. Check uv output and UV_PYTHON_INSTALL_MIRROR.' }
+ $Python = & $Uv python find --system --no-project --no-python-downloads 3.12
+ if ($LASTEXITCODE -ne 0 -or -not $Python) { throw 'Installed Python was not found.' }
+ }
+ $IndexUrl = Select-Index
+ $Requirement = 'powercontext[cli,server]'
+ if ($RuntimeProfile -eq 'client') { $Requirement = 'powercontext[cli]' }
+ $InstallArgs = @('tool', 'install', '--python', $Python.Trim(), '--no-python-downloads')
+ # Older uv retains installed prereleases unless it resolves a fresh tool environment.
+ if ($Version -eq 'latest') { $InstallArgs += @('--upgrade', '--force', '--prerelease', 'disallow') }
+ else { $Requirement += "==$Version" }
+ $InstallArgs += $Requirement
+ if ($IndexUrl) { $InstallArgs += @('--default-index', $IndexUrl) }
+ & $Uv @InstallArgs
+ if ($LASTEXITCODE -ne 0) { throw 'Installation failed. Check uv indexes for packages and UV_PYTHON_INSTALL_MIRROR for Python.' }
+ $ToolBin = & $Uv tool dir --bin
+ if ($LASTEXITCODE -ne 0) { throw 'Could not locate the installed CLI.' }
+ $Cli = Join-Path $ToolBin.Trim() 'powercontext.exe'
+ $env:PATH = "$($ToolBin.Trim());$env:PATH"
+ $InstalledVersion = & $Cli --version
+ if ($LASTEXITCODE -ne 0) { throw 'Package installed, but CLI version verification failed. Check uv output and retry installation.' }
+ if ($InstalledVersion -notmatch '^\d+\.\d+\.\d+((a|b|rc)\d+)?$') { throw 'Package installed, but CLI release version verification failed. Check uv output and retry installation.' }
+ if ($Version -ne 'latest' -and $Version -ne $InstalledVersion) { throw 'Package installed, but the CLI version differs from the requested release. Retry installation with the intended version.' }
+ # Help checks import and command availability without configuring or starting a service.
+ & $Cli --help | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw 'Package installed, but CLI help verification failed. Retry installation after reviewing the command error.' }
+ & $Cli capabilities --help | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw 'Package installed, but Client command verification failed (capabilities --help). Retry installation after reviewing the command error.' }
+ if ($RuntimeProfile -eq 'local') {
+ & $Cli config init --help | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw 'Package installed, but configuration command verification failed (config init --help). Retry installation after reviewing the command error.' }
+ & $Cli server run --help | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw 'Package installed, but Server command verification failed (server run --help). Retry installation after reviewing the command error.' }
+ }
+ $Version = $InstalledVersion.Trim()
+ Write-Host "Runtime installed: $Version ($RuntimeProfile)"
+ $PathPrefix = ("$($ToolBin.Trim());$(Split-Path -Parent $Uv)").Replace("'", "''")
+ Write-Host ('For a new terminal: $env:Path = ''' + $PathPrefix + ';'' + $env:Path')
+ $SetupStatus = 0
+ if ($Hosts.Count) {
+ $SetupArgs = @('setup')
+ if ($EnvFile) { $SetupArgs += @('--env-file', $EnvFile) }
+ & $Cli @SetupArgs --source oceanbase/powercontext --ref "powercontext-v$Version" @Hosts
+ $SetupStatus = $LASTEXITCODE
+ }
+ if ($RuntimeProfile -eq 'local') {
+ Write-Host "`nConfigure a local Server: powercontext config init --output .env"
+ Write-Host 'After saving, validate the file and register the personal service:'
+ Write-Host ' powercontext config validate --env-file .env'
+ Write-Host ' powercontext service install --env-file .env'
+ Write-Host ' powercontext service status'
+ Write-Host 'Load the generated Client settings, then run powercontext ready and powercontext capabilities.'
+ Write-Host 'Windows personal services are experimental. For development or temporary use, run powercontext server run.'
+ Write-Host ' https://powercontext.oceanbase.io/en/docs/get-started/quickstart/'
+ }
+ else {
+ Write-Host "`nConnect to your existing Server:"
+ Write-Host ' https://powercontext.oceanbase.io/en/docs/operate/connect-remote-server/'
+ }
+ if ($SetupStatus -ne 0) { throw "Runtime installed, but integration setup did not complete. Retry setup with powercontext-v$Version." }
+}
+catch {
+ [Console]::Error.WriteLine("error: $($_.Exception.Message)")
+ exit 1
+}
+finally {
+ foreach ($Name in $SavedEnvironment.Keys) { [Environment]::SetEnvironmentVariable($Name, $SavedEnvironment[$Name]) }
+ if ($TempDir) { Remove-Item -LiteralPath $TempDir -Recurse -Force -ErrorAction SilentlyContinue }
+}
diff --git a/website/public/install.sh b/website/public/install.sh
new file mode 100644
index 0000000000..c8987c55ef
--- /dev/null
+++ b/website/public/install.sh
@@ -0,0 +1,387 @@
+#!/usr/bin/env bash
+# Copyright (c) 2026 OceanBase.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -euo pipefail
+
+VERSION=latest
+UV_VERSION=0.12.23
+PROFILE=local
+REGION=${POWERCONTEXT_INSTALL_REGION:-auto}
+PYTHON_BIN=""
+INDEX_URL=""
+HOSTS=()
+NO_HOSTS=false
+ENV_FILE=""
+TEMP_DIR=""
+UV_BIN=""
+GUIDE=https://powercontext.oceanbase.io/en/docs/get-started/quickstart/
+
+fail() {
+ printf 'error: %s\n' "$*" >&2
+ exit 1
+}
+
+cleanup() {
+ if [[ -n "$TEMP_DIR" ]]; then
+ rm -rf -- "$TEMP_DIR"
+ fi
+}
+
+usage() {
+ cat <<'EOF'
+Install PowerContext on macOS or Linux. Python and uv need not be installed.
+
+Usage: bash install.sh [--version VERSION] [--index-url URL] [--host HOST]... [--no-hosts]
+
+ --version VERSION Exact package version, or latest (default: latest stable).
+ Integrations use the matching powercontext-vVERSION tag.
+ --profile PROFILE local (CLI and Server, default) or client (CLI and Client).
+ --region REGION auto, cn, or global (default: POWERCONTEXT_INSTALL_REGION or auto).
+ --index-url URL HTTPS default package index for this installation.
+ Existing additional uv indexes still take precedence.
+ --host HOST Install this Agent integration; repeat for multiple hosts.
+ --no-hosts Compatibility option; integration setup is skipped by default.
+ --env-file PATH Existing configuration file for selected Agent integrations.
+ -h, --help Show this help.
+
+Agent integration setup runs only for explicitly selected --host values.
+After installation, run powercontext setup --ref "powercontext-v$(powercontext --version)".
+Configure and register a personal service separately after installation.
+
+Region priority: --region, POWERCONTEXT_INSTALL_REGION, named timezone, locale
+territory, then global. No network location service is queried.
+CN defaults: Tsinghua for PyPI, USTC for uv, NJU for Python. Global defaults use
+PyPI and Astral's download channels. Unavailable automatic mirrors fall back to
+official sources; explicit download settings are preserved without fallback.
+
+Download configuration (independent of the package index):
+ POWERCONTEXT_UV_INSTALLER_URL HTTPS uv installer script URL.
+ UV_DOWNLOAD_URL uv release artifact directory.
+ UV_INSTALLER_GITHUB_BASE_URL GitHub mirror base URL for uv binaries.
+ UV_PYTHON_INSTALL_MIRROR Mirror of python-build-standalone downloads.
+ UV_ASTRAL_MIRROR_URL Astral mirror for uv versions supporting it.
+These settings are passed to the official installer and uv without modification.
+Existing uv and compatible Python installations are reused.
+EOF
+}
+
+parse_args() {
+ while (($#)); do
+ case "$1" in
+ --version|--profile|--region|--index-url|--host|--env-file)
+ (($# >= 2)) && [[ -n "$2" && "$2" != --* ]] || fail "$1 requires a value"
+ case "$1" in
+ --version) VERSION=$2 ;;
+ --profile) PROFILE=$2 ;;
+ --region) REGION=$2 ;;
+ --index-url) INDEX_URL=$2 ;;
+ --host) HOSTS+=("$2") ;;
+ --env-file) ENV_FILE=$2 ;;
+ esac
+ shift 2
+ ;;
+ --no-hosts) NO_HOSTS=true; shift ;;
+ -h|--help) usage; exit 0 ;;
+ *) fail "Unknown option. Run bash install.sh --help." ;;
+ esac
+ done
+ case "$REGION" in auto|cn|global) ;; *) fail "Use --region auto, cn, or global." ;; esac
+ case "$PROFILE" in local|client) ;; *) fail "Use --profile local or client." ;; esac
+ [[ "$VERSION" == latest || "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+)?$ ]] || fail "Use latest or an exact package version."
+ [[ "$VERSION" != 0.0.* ]] || fail "This installer supports releases starting at 0.1.0."
+ if [[ -n "$INDEX_URL" ]]; then
+ [[ "$INDEX_URL" == https://?* && "$INDEX_URL" != *[@\?#[:space:]]* ]] ||
+ fail "Use an HTTPS index URL without credentials, query parameters, or fragments."
+ fi
+ if [[ -n "${POWERCONTEXT_UV_INSTALLER_URL:-}" ]]; then
+ [[ "$POWERCONTEXT_UV_INSTALLER_URL" == https://?* && "$POWERCONTEXT_UV_INSTALLER_URL" != *[@\?#[:space:]]* ]] ||
+ fail "POWERCONTEXT_UV_INSTALLER_URL must be an HTTPS URL without credentials, query parameters, or fragments."
+ fi
+ if [[ "$NO_HOSTS" == true && ${#HOSTS[@]} -gt 0 ]]; then
+ fail "--host and --no-hosts cannot be combined."
+ fi
+ if [[ -n "$ENV_FILE" ]]; then
+ [[ -f "$ENV_FILE" ]] || fail "--env-file must name an existing file."
+ fi
+}
+
+download() {
+ local timeout=${3:-30}
+ if command -v curl >/dev/null 2>&1; then
+ curl --proto '=https' --proto-redir '=https' --fail --location --silent --show-error --connect-timeout 5 --max-time "$timeout" "$1" -o "$2"
+ else
+ timeout=${3:-15}
+ # wget cannot restrict redirect protocols; reject redirects rather than allow a downgrade.
+ wget --quiet --max-redirect=0 --timeout="$timeout" --tries=1 "$1" -O "$2" || {
+ printf '%s\n' 'wget download failed. Install curl if the HTTPS source requires redirects.' >&2
+ return 1
+ }
+ fi
+}
+
+detect_region() {
+ local timezone=${TZ:-} locale_name source=explicit
+ if [[ "$REGION" == auto ]]; then
+ source=timezone
+ if [[ -z "$timezone" ]]; then
+ timezone=$(readlink /etc/localtime 2>/dev/null || true)
+ if [[ "$timezone" != */zoneinfo/* && -f /etc/timezone ]]; then
+ IFS= read -r timezone /dev/null || true)
+ fi
+ fi
+ case "$locale_name" in
+ *_CN|*_CN.*|*_CN@*|*-CN|*-CN.*|*-CN@*) REGION=cn ;;
+ *) REGION=global ;;
+ esac
+ ;;
+ esac
+ fi
+ printf 'Download region: %s (%s).\n' "$REGION" "$source"
+}
+
+has_uv_config_file() {
+ [[ -n "${UV_CONFIG_FILE:-}" ]] && return 0
+ case "${UV_NO_CONFIG:-}" in 1|true) return 1 ;; esac
+ [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/uv/uv.toml" || -f /etc/uv/uv.toml ]] && return 0
+ local directory
+ local config_dirs=()
+ IFS=: read -r -a config_dirs <<< "${XDG_CONFIG_DIRS:-/etc/xdg}"
+ for directory in "${config_dirs[@]}"; do
+ [[ -f "$directory/uv/uv.toml" ]] && return 0
+ done
+ return 1
+}
+
+has_uv_configuration() {
+ # Let uv resolve its configuration and authentication; do not parse TOML in shell.
+ [[ -n "${UV_DEFAULT_INDEX:-}${UV_INDEX:-}${UV_INDEX_URL:-}${UV_EXTRA_INDEX_URL:-}${UV_OFFLINE:-}${UV_NO_INDEX:-}${UV_FIND_LINKS:-}" ]] && return 0
+ has_uv_config_file
+}
+
+url_available() {
+ if command -v curl >/dev/null 2>&1; then
+ curl --proto '=https' --proto-redir '=https' --fail --location --silent --head --connect-timeout 5 --max-time 10 "$1" >/dev/null
+ else
+ wget --quiet --max-redirect=0 --spider --timeout=10 --tries=1 "$1"
+ fi
+}
+
+index_available() {
+ # Probe transport availability only; uv interprets the Simple API and versions.
+ if ! download "${1%/}/powercontext/" "$TEMP_DIR/index.html"; then
+ printf 'Package mirror is unreachable: %s\n' "$1" >&2
+ return 1
+ fi
+}
+
+check_index() {
+ if [[ -z "$INDEX_URL" ]] && has_uv_configuration; then
+ printf '%s\n' 'Using existing uv configuration; uv will check the configured indexes.'
+ return
+ fi
+ if [[ -n "${PIP_INDEX_URL:-}${PIP_EXTRA_INDEX_URL:-}" ]]; then
+ printf '%s\n' 'uv does not read PIP_INDEX_URL or PIP_EXTRA_INDEX_URL. Use --index-url or uv configuration.'
+ fi
+ if [[ -n "$INDEX_URL" ]]; then
+ # uv owns index authentication and package resolution, including private indexes.
+ printf 'Package index: %s\n' "$INDEX_URL"
+ return
+ fi
+
+ INDEX_URL=https://pypi.org/simple
+ if [[ "$REGION" == cn ]]; then
+ local mirror=https://pypi.tuna.tsinghua.edu.cn/simple
+ printf 'Checking package mirror availability: %s\n' "$mirror"
+ if index_available "$mirror"; then
+ INDEX_URL=$mirror
+ else
+ printf '%s\n' 'Automatic package mirror unavailable; using PyPI.'
+ fi
+ fi
+ # A reachable mirror may be stale or incompatible. Let uv report that failure
+ # without retrying a tool installation or changing the requested requirement.
+ printf 'Package index: %s\n' "$INDEX_URL"
+}
+
+ensure_uv() {
+ local uv_install_dir=${UV_INSTALL_DIR:-$HOME/.local/bin}
+ if command -v uv >/dev/null 2>&1; then
+ UV_BIN=$(command -v uv)
+ elif [[ -x "$uv_install_dir/uv" ]]; then
+ UV_BIN="$uv_install_dir/uv"
+ else
+ case "${UV_OFFLINE:-}" in 1|true) fail "uv is not installed and offline mode disables downloads." ;; esac
+ printf 'Installing uv %s in the user executable directory.\n' "$UV_VERSION"
+ local installer_url=${POWERCONTEXT_UV_INSTALLER_URL:-https://astral.sh/uv/$UV_VERSION/install.sh}
+ local mirror=https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/$UV_VERSION
+ local official=https://releases.astral.sh/github/uv/releases/download/$UV_VERSION
+ # A custom installer may install another version; never pair it with our pinned mirror.
+ if [[ "$REGION" == cn && -z "${POWERCONTEXT_UV_INSTALLER_URL:-}${UV_DOWNLOAD_URL:-}${INSTALLER_DOWNLOAD_URL:-}${UV_INSTALLER_GITHUB_BASE_URL:-}${UV_INSTALLER_GHE_BASE_URL:-}${UV_ASTRAL_MIRROR_URL:-}" ]]; then
+ printf 'uv mirror: %s\n' "$mirror"
+ if download "$mirror/uv-installer.sh" "$TEMP_DIR/uv-install.sh"; then
+ # The official shell installer supports an ordered list of artifact sources.
+ UV_DOWNLOAD_URL="$mirror $official https://github.com/astral-sh/uv/releases/download/$UV_VERSION" \
+ UV_INSTALL_DIR="$uv_install_dir" UV_NO_MODIFY_PATH=1 sh "$TEMP_DIR/uv-install.sh" || fail "uv installation failed."
+ else
+ printf '%s\n' 'uv mirror unavailable; using the official installer.'
+ download "$installer_url" "$TEMP_DIR/uv-install.sh" || fail "Could not download the uv installer."
+ UV_INSTALL_DIR="$uv_install_dir" UV_NO_MODIFY_PATH=1 sh "$TEMP_DIR/uv-install.sh" || fail "uv installation failed."
+ fi
+ else
+ download "$installer_url" "$TEMP_DIR/uv-install.sh" || fail "Could not download the uv installer. Check POWERCONTEXT_UV_INSTALLER_URL."
+ UV_INSTALL_DIR="$uv_install_dir" UV_NO_MODIFY_PATH=1 sh "$TEMP_DIR/uv-install.sh" || fail "uv installation failed. Check the configured download source."
+ fi
+ UV_BIN="$uv_install_dir/uv"
+ fi
+ [[ -x "$UV_BIN" ]] || fail "uv executable was not found after installation."
+ printf 'Using uv: %s\n' "$UV_BIN"
+ PATH="$(dirname "$UV_BIN"):$PATH"
+ export PATH
+}
+
+ensure_python() {
+ if PYTHON_BIN=$("$UV_BIN" python find --system --no-project --no-python-downloads '>=3.11,<4' 2>/dev/null); then
+ printf 'Using local Python: %s\n' "$PYTHON_BIN"
+ return
+ fi
+ case "${UV_PYTHON_DOWNLOADS:-}" in never|false|0) fail "No compatible local Python; UV_PYTHON_DOWNLOADS disables downloads." ;; esac
+ case "${UV_OFFLINE:-}" in 1|true) fail "No compatible local Python in offline mode." ;; esac
+ local mirror=https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone
+ local key url suffix automatic_mirror=false
+ local python_args=(python install 3.12)
+ if [[ "$REGION" == cn && -z "${UV_PYTHON_INSTALL_MIRROR:-}${UV_ASTRAL_MIRROR_URL:-}${UV_PYTHON_DOWNLOADS_JSON_URL:-}" ]] && ! has_uv_config_file; then
+ # Ask uv for the exact build URL; platform and archive selection stay with uv.
+ "$UV_BIN" python list 'cpython@3.12' --only-downloads --show-urls --color never >"$TEMP_DIR/python-downloads.txt" || fail "Could not resolve a Python download."
+ read -r key url <"$TEMP_DIR/python-downloads.txt" || true
+ case "$url" in
+ https://*/python-build-standalone/releases/download/*)
+ suffix=${url#*/python-build-standalone/releases/download/}
+ if url_available "$mirror/$suffix"; then
+ printf 'Python mirror: %s\n' "$mirror"
+ python_args+=(--mirror "$mirror")
+ automatic_mirror=true
+ else
+ printf '%s\n' 'Python mirror does not provide the requested build; using uv default sources.'
+ fi
+ ;;
+ esac
+ fi
+ printf '%s\n' 'No compatible local Python found. Installing Python 3.12.'
+ if ! "$UV_BIN" "${python_args[@]}"; then
+ if [[ "$automatic_mirror" == true ]]; then
+ printf '%s\n' 'Python mirror installation failed; retrying with uv default sources.'
+ "$UV_BIN" python install 3.12 || fail "Python installation failed. Check UV_PYTHON_INSTALL_MIRROR."
+ else
+ fail "Python installation failed. Check uv output and UV_PYTHON_INSTALL_MIRROR."
+ fi
+ fi
+ PYTHON_BIN=$("$UV_BIN" python find --system --no-project --no-python-downloads 3.12) || fail "Installed Python was not found."
+}
+
+main() {
+ parse_args "$@"
+ case "$(uname -s)" in
+ Darwin|Linux) ;;
+ *) fail "Use the Windows instructions in the installation guide. This script supports macOS and Linux." ;;
+ esac
+ [[ -n "${HOME:-}" ]] || fail "HOME is not set."
+ command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || fail "Install curl or wget first."
+ if [[ ${#HOSTS[@]} -gt 0 ]]; then
+ command -v git >/dev/null 2>&1 || fail "Agent integration setup requires Git. Install Git or use --no-hosts."
+ fi
+ TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/powercontext-install.XXXXXX")
+ trap cleanup EXIT
+ detect_region
+ ensure_uv
+ ensure_python
+ check_index
+ [[ -z "$INDEX_URL" ]] || export UV_DEFAULT_INDEX="$INDEX_URL"
+
+ local requirement='powercontext[cli,server]'
+ [[ "$PROFILE" != client ]] || requirement='powercontext[cli]'
+ local install_args=(tool install --python "$PYTHON_BIN" --no-python-downloads)
+ if [[ "$VERSION" == latest ]]; then
+ # Older uv retains installed prereleases unless it resolves a fresh tool environment.
+ install_args+=(--upgrade --force --prerelease disallow)
+ else
+ requirement="$requirement==$VERSION"
+ fi
+ install_args+=("$requirement")
+ [[ -z "$INDEX_URL" ]] || install_args+=(--default-index "$INDEX_URL")
+ printf 'Installing PowerContext %s.\n' "$VERSION"
+ "$UV_BIN" "${install_args[@]}" ||
+ fail "Installation failed. Check the uv error: Python downloads use UV_PYTHON_INSTALL_MIRROR or UV_ASTRAL_MIRROR_URL; Python packages use uv indexes."
+
+ local tool_bin
+ tool_bin=$("$UV_BIN" tool dir --bin)
+ export PATH="$tool_bin:$PATH"
+ local installed_version
+ installed_version=$("$tool_bin/powercontext" --version) || fail "Package installed, but CLI version verification failed. Check uv output and retry installation."
+ [[ "$installed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+)?$ ]] || fail "Package installed, but CLI release version verification failed. Check uv output and retry installation."
+ [[ "$VERSION" == latest || "$VERSION" == "$installed_version" ]] || fail "Package installed, but the CLI version differs from the requested release. Retry installation with the intended version."
+ # Help checks import and command availability without configuring or starting a service.
+ "$tool_bin/powercontext" --help >/dev/null ||
+ fail "Package installed, but CLI help verification failed. Retry installation after reviewing the command error."
+ "$tool_bin/powercontext" capabilities --help >/dev/null ||
+ fail "Package installed, but Client command verification failed (capabilities --help). Retry installation after reviewing the command error."
+ if [[ "$PROFILE" == local ]]; then
+ "$tool_bin/powercontext" config init --help >/dev/null ||
+ fail "Package installed, but configuration command verification failed (config init --help). Retry installation after reviewing the command error."
+ "$tool_bin/powercontext" server run --help >/dev/null ||
+ fail "Package installed, but Server command verification failed (server run --help). Retry installation after reviewing the command error."
+ fi
+ VERSION=$installed_version
+ printf 'Runtime installed: %s (%s)\n' "$VERSION" "$PROFILE"
+ printf 'For a new terminal, add these directories to PATH if needed:\n'
+ printf ' export PATH=%q:%q:"%s"\n' "$(dirname "$UV_BIN")" "$tool_bin" "\$PATH"
+
+ local setup_status=0
+ if [[ ${#HOSTS[@]} -gt 0 ]]; then
+ local setup_args=(setup)
+ [[ -z "$ENV_FILE" ]] || setup_args+=(--env-file "$ENV_FILE")
+ "$tool_bin/powercontext" "${setup_args[@]}" --source oceanbase/powercontext \
+ --ref "powercontext-v$VERSION" ${HOSTS[@]+"${HOSTS[@]}"} || setup_status=$?
+ fi
+ if [[ "$PROFILE" == local ]]; then
+ printf '\nConfigure a local Server: powercontext config init --output .env\n'
+ printf 'After saving, validate the file and register the personal service:\n'
+ printf ' powercontext config validate --env-file .env\n'
+ printf ' powercontext service install --env-file .env\n powercontext service status\n'
+ printf '%s\n' 'Load the generated Client settings, then run powercontext ready and powercontext capabilities.'
+ printf '%s\n' 'A native user-service manager and loopback Server bind are required. For development or temporary use, run powercontext server run.'
+ printf ' %s\n' "$GUIDE"
+ else
+ printf '\nConnect to your existing Server:\n %s\n' \
+ 'https://powercontext.oceanbase.io/en/docs/operate/connect-remote-server/'
+ fi
+ if ((setup_status != 0)); then
+ fail "Runtime installed, but integration setup did not complete. Review its results and retry setup with powercontext-v$VERSION."
+ fi
+}
+
+main "$@"
]