agent-cloud-kit 0.0.0-stage → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +232 -2
- package/dist/ca.js +30 -0
- package/dist/cli.js +418 -0
- package/dist/config.js +197 -0
- package/dist/constants.js +77 -0
- package/dist/detect.js +265 -0
- package/dist/docker.js +95 -0
- package/dist/doctor.js +200 -0
- package/dist/init.js +136 -0
- package/dist/installers.js +463 -0
- package/dist/launcher.js +140 -0
- package/dist/manual.js +223 -0
- package/dist/prepare/detect.js +29 -0
- package/dist/prepare/index.js +489 -0
- package/dist/prepare/prompts.js +28 -0
- package/dist/prepare/questions.js +5 -0
- package/dist/run.js +113 -0
- package/dist/session.js +246 -0
- package/dist/setup.js +277 -0
- package/dist/skill.js +148 -0
- package/dist/state.js +147 -0
- package/dist/steps.js +46 -0
- package/package.json +46 -4
- package/schema.json +72 -0
- package/skills/cloud-ready/SKILL.md +202 -0
- package/skills/cloud-ready/reference/compat.md +110 -0
- package/skills/cloud-ready/reference/knowledge.md +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ramón Soto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,233 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agent-cloud-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A zero-dependency CLI that prepares [Claude Code on the web](https://claude.ai/code) cloud
|
|
4
|
+
sessions. It installs the tool versions a repo pins, starts Docker, completes the partial
|
|
5
|
+
clone, and tells you when the saved machine is not what the repo asks for, working around the
|
|
6
|
+
limits of the cloud sandbox that the platform does not document.
|
|
7
|
+
|
|
8
|
+
> Not affiliated with or endorsed by Anthropic. Works with Claude Code on the web.
|
|
9
|
+
>
|
|
10
|
+
> Continues `@ramon-sg/claude-cloud` (its 1.0.0), with no automatic migration: install this package
|
|
11
|
+
> and run `init` again.
|
|
12
|
+
|
|
13
|
+
- Written in TypeScript, published compiled, **no runtime dependencies**, Node `>=20` (the cloud
|
|
14
|
+
image ships Node 22, so it runs before any tool of your repo is installed).
|
|
15
|
+
- The **repo decides the version**: `.claude/cloud.json` pins the exact version of this package,
|
|
16
|
+
so updating it is a commit.
|
|
17
|
+
|
|
18
|
+
## What it solves
|
|
19
|
+
|
|
20
|
+
Installing Node and starting Docker takes a few lines of shell. The expensive part is what the
|
|
21
|
+
platform does not tell you:
|
|
22
|
+
|
|
23
|
+
- The egress proxy blocks GitHub releases and GitHub's GraphQL API, so tools that download from
|
|
24
|
+
GitHub fail. `agent-cloud-kit` installs Node from nodejs.org; Bun, Deno, pnpm and Yarn from npm;
|
|
25
|
+
and uv from PyPI.
|
|
26
|
+
- The setup script runs from `/home/user`, not from the repo, and a failure is silent.
|
|
27
|
+
- A setup that takes more than about 5 minutes does not leave a saved machine.
|
|
28
|
+
- Two `apt` runs at the same time collide on the dpkg lock, so apt work runs in series.
|
|
29
|
+
- `dockerd` is off and the clone is partial (shallow, without `origin/<default branch>`).
|
|
30
|
+
- There is no IPv6.
|
|
31
|
+
- The proxy replaces TLS, and `docker build` does not trust its CA.
|
|
32
|
+
- A resumed session does not run the setup again, and changing the setup does not rebuild the
|
|
33
|
+
saved machine.
|
|
34
|
+
|
|
35
|
+
## The flow
|
|
36
|
+
|
|
37
|
+
1. **Install your agent tooling** with agent-kitchen (config, hook and skill arrive in the repo).
|
|
38
|
+
2. **Run `npx agent-cloud-kit prepare`** in a local terminal. It asks nothing up front: it
|
|
39
|
+
creates a worktree on a dated branch, runs `init`, and opens
|
|
40
|
+
Claude locally with the `cloud-ready` skill so it can write the cloud preparation with you. Then
|
|
41
|
+
it writes the manual, commits, pushes and opens a draft PR.
|
|
42
|
+
3. **Configure the environment in claude.ai** following `docs/agent-cloud.md`, which `prepare`
|
|
43
|
+
generated from your `.claude/cloud.json`. The document keeps growing: when the skill, locally
|
|
44
|
+
or in the cloud, finds that a domain or an environment variable is missing, it adds it to
|
|
45
|
+
`.claude/cloud.json`, regenerates the document and tells you in the session what to add in
|
|
46
|
+
claude.ai.
|
|
47
|
+
4. **Verification in the cloud:** `prepare` launches `claude --cloud` on that branch, so a real
|
|
48
|
+
cloud session runs `cloud-ready verify`, fixes what fails and reports in the PR.
|
|
49
|
+
|
|
50
|
+
`claude --cloud` uses the environment chosen with `/remote-env` in `claude`, and the Claude GitHub
|
|
51
|
+
App must be installed on the repo (without it the session cannot push). Inside the cloud, `gh api`
|
|
52
|
+
(REST) works; GraphQL (`gh pr list`, `gh pr create`) is blocked. If `prepare` is interrupted,
|
|
53
|
+
`npx agent-cloud-kit prepare --resume` continues in the existing worktree.
|
|
54
|
+
|
|
55
|
+
## No opinion about tests
|
|
56
|
+
|
|
57
|
+
This package does not impose any way of testing, and a repo may have no tests at all (then the
|
|
58
|
+
cloud only needs a prepared machine). The `cloud-ready` skill finds the tests your repo already
|
|
59
|
+
has, tells you which would run in the cloud as they are, and proposes adapting the cloud
|
|
60
|
+
preparation or, only if needed, a cloud variant of them. Nothing changes without your approval.
|
|
61
|
+
|
|
62
|
+
### The `cloud-ready` skill
|
|
63
|
+
|
|
64
|
+
`init` copies it to `.agents/skills/cloud-ready` and links it from `.claude/skills/cloud-ready`,
|
|
65
|
+
because Claude Code does not discover `.agents/skills`. Commit both. `cloud-ready configure` runs
|
|
66
|
+
locally with you; `cloud-ready verify` runs inside the cloud session. The skill and the generated
|
|
67
|
+
manual are written in Spanish.
|
|
68
|
+
|
|
69
|
+
## Set up claude.ai
|
|
70
|
+
|
|
71
|
+
In the environment form at `https://claude.ai/code`:
|
|
72
|
+
|
|
73
|
+
1. **Network access:** choose *Custom* and keep **Also include default list of common package
|
|
74
|
+
managers** checked. Add any extra domains your `apt` repos or setup steps need, one per line.
|
|
75
|
+
Run `agent-cloud-kit doctor` in a session to see which hosts are blocked (it probes with `curl`,
|
|
76
|
+
so it goes through the proxy like the setup does). If the setup never ran, use
|
|
77
|
+
`npx agent-cloud-kit doctor`.
|
|
78
|
+
2. **Setup script:** the cloud runs it as root from `/home/user`, before Claude starts:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
#!/bin/bash
|
|
82
|
+
npx -y agent-cloud-kit@1 setup
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `@1` only pins the launcher. Each repo then relaunches the exact version it asks for, so a
|
|
86
|
+
new launcher major is adopted by editing this field on purpose. To force the saved machine to
|
|
87
|
+
be rebuilt, change anything in this field (a comment with the date works).
|
|
88
|
+
|
|
89
|
+
3. In each repo run `npx agent-cloud-kit init`, then commit `.claude/cloud.json`,
|
|
90
|
+
`.claude/settings.json`, `.agents/skills/cloud-ready` and the `.claude/skills/cloud-ready` link.
|
|
91
|
+
|
|
92
|
+
Environment changes are read when a session is created: start a new session to test them. A
|
|
93
|
+
resumed session does not run the setup again.
|
|
94
|
+
|
|
95
|
+
## `.claude/cloud.json`
|
|
96
|
+
|
|
97
|
+
It lives in `.claude/` rather than in `package.json` so repos without Node can use it. A JSON
|
|
98
|
+
schema ships with the package (`schema.json`). Unknown fields are reported and ignored, and a
|
|
99
|
+
broken file never stops a session.
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"$schema": "https://unpkg.com/agent-cloud-kit@1.0.0/schema.json",
|
|
104
|
+
"version": "1.0.0",
|
|
105
|
+
"aptRepos": ["https://example.com/setup.deb.sh"],
|
|
106
|
+
"apt": ["direnv", "jq"],
|
|
107
|
+
"domains": ["registry.example.com"],
|
|
108
|
+
"docker": { "pull": "auto" },
|
|
109
|
+
"setup": ["scripts/cloud-setup-extra.sh"],
|
|
110
|
+
"session": ["scripts/worktree-setup.sh"],
|
|
111
|
+
"requiredEnv": ["API_TOKEN"],
|
|
112
|
+
"notes": ".claude/cloud-notes.md"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Field | Required | Meaning |
|
|
117
|
+
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `version` | yes | Exact version of this package to run (no ranges). If `setup` runs a different one it relaunches with this one. |
|
|
119
|
+
| `aptRepos` | no | HTTPS URLs of setup scripts that add APT repositories; each is downloaded and piped to `bash` before installing packages. |
|
|
120
|
+
| `apt` | no | Debian packages to install. Names are validated and passed as arguments, never to a shell. |
|
|
121
|
+
| `domains` | no | Extra hosts the environment must allow; `doctor` probes them and the manual lists them. |
|
|
122
|
+
| `docker.pull` | no | `"auto"` (default) pulls the images found in compose files and in `FROM` lines pinned with `@sha256`; a list of image names pulls those; `false` leaves Docker alone during `setup`. |
|
|
123
|
+
| `setup` | no | Shell commands run once during `setup`, in the repo, with the pinned tools first on `PATH`. A failing command is recorded and the rest still run. |
|
|
124
|
+
| `session` | no | Shell commands run at the start of every session, in the repo. Their output goes to stderr (not into Claude's context), each one is stopped after 2 minutes, and a failing command is reported while the rest still run. |
|
|
125
|
+
| `requiredEnv` | no | Environment variables that must be set; the session warns about the missing ones. |
|
|
126
|
+
| `notes` | no | What does and does not run in the cloud: free text, or a path, relative to the repo, of a text file inside it (then its contents are used). Printed at the start of every session and shown in the manual. `prepare` expects the file the `cloud-ready` skill writes. |
|
|
127
|
+
|
|
128
|
+
Tool versions are deduced, in order, from `.tool-versions`, `mise.toml`, `.nvmrc` and
|
|
129
|
+
`package.json#packageManager`. The default branch comes from `origin/HEAD`. Each tool is installed
|
|
130
|
+
under `/opt/agent-cloud-kit/<tool>-<version>`, so versions coexist and the repos of one environment
|
|
131
|
+
share the folder. A tool with no installer produces a warning and is skipped.
|
|
132
|
+
|
|
133
|
+
A Node version that is not exact (`22`, `22.1`, `lts/iron`, `lts/*` or `node`) is resolved during
|
|
134
|
+
`setup` to the newest matching release in `https://nodejs.org/dist/index.json` and installed under
|
|
135
|
+
`/opt/agent-cloud-kit/node-<exact version>`. The resolved version is recorded in the machine state, so
|
|
136
|
+
`session` puts the same folder on `PATH`.
|
|
137
|
+
|
|
138
|
+
## Commands
|
|
139
|
+
|
|
140
|
+
| Command | What it does |
|
|
141
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `agent-cloud-kit setup [--repo <path>]` | Runs as root before Claude starts. For every git repo in the home dir (`/home/user`) with a `.claude/cloud.json` (or only `<path>`): installs tools, apt packages and images and copies itself (no npm, no network) to `/opt/agent-cloud-kit/bin/agent-cloud-kit`, then runs the `setup` steps and records the result. Repos without the file are skipped with a notice. It always exits 0: failures are recorded, never fatal. |
|
|
143
|
+
| `agent-cloud-kit session` | Runs from the `SessionStart` hook, in seconds and without network (except to complete the clone). Warns when setup did not run, failed steps, or the machine was prepared with another configuration; writes the `PATH` line (the pinned tools, then `/opt/agent-cloud-kit/bin`, so `agent-cloud-kit` itself resolves) to `$CLAUDE_ENV_FILE`; starts Docker; fetches the full history and `origin/<default branch>`; runs the `session` steps. It never fails the session start. |
|
|
144
|
+
| `agent-cloud-kit docker` | Starts `dockerd` detached and waits up to 60 seconds for it to answer. Exits 1 if it does not, at once when `dockerd` is not installed. |
|
|
145
|
+
| `agent-cloud-kit doctor` | Checks that the proxy lets the needed hosts through, that the pinned tools are installed, that Docker answers, and the recorded state (fingerprint and each step). Exits 1 if any check fails. |
|
|
146
|
+
| `agent-cloud-kit init` | Creates `.claude/cloud.json` if missing, adds the `SessionStart` hook to `.claude/settings.json` (keeping the hooks already there), copies the `cloud-ready` skill to `.agents/skills/cloud-ready` when its version differs, and links it from `.claude/skills/cloud-ready`. Safe to run again. |
|
|
147
|
+
| `agent-cloud-kit manual` | Writes `docs/agent-cloud.md` from `.claude/cloud.json`, deterministically: the exact setup script (`agent-cloud-kit@<major>`, or the exact version for a release candidate), domains, variable names, how to verify the environment. |
|
|
148
|
+
| `agent-cloud-kit prepare [--resume]` | Local only, needs an interactive terminal: sets the repo up for the cloud on a new branch in a worktree, opens the PR and verifies it in a real cloud session (see "The flow"). `--resume` continues in the existing worktree. |
|
|
149
|
+
|
|
150
|
+
Environment variables: `AGENT_CLOUD_KIT_DIR` overrides `/opt/agent-cloud-kit` and `AGENT_CLOUD_KIT_HOME`
|
|
151
|
+
overrides `/home/user`; both exist mainly for tests.
|
|
152
|
+
|
|
153
|
+
### The `SessionStart` hook
|
|
154
|
+
|
|
155
|
+
`init` adds this hook. Outside a cloud session it does nothing, and it calls the copy of the
|
|
156
|
+
package that `setup` left on the saved machine, so it needs no network and no `node_modules`:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"type": "command",
|
|
161
|
+
"command": "[ \"$CLAUDE_CODE_REMOTE\" = true ] || exit 0; /opt/agent-cloud-kit/bin/agent-cloud-kit session || echo 'agent-cloud-kit: the environment setup did not run; see npx agent-cloud-kit doctor'",
|
|
162
|
+
"timeout": 600
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
When the repo pins another version than the one that is running and that version is installed,
|
|
167
|
+
`session` hands over to it.
|
|
168
|
+
|
|
169
|
+
### Machine fingerprint
|
|
170
|
+
|
|
171
|
+
`setup` stores a fingerprint of the config, the pinned versions, the images and the package
|
|
172
|
+
version. If the repo changes afterwards, `session` warns that "the machine was prepared with
|
|
173
|
+
another configuration". The saved machine is rebuilt by claude.ai about every 7 days, or when you
|
|
174
|
+
edit the setup script field.
|
|
175
|
+
|
|
176
|
+
## The proxy CA and `docker build`
|
|
177
|
+
|
|
178
|
+
The cloud proxy replaces TLS, so `docker build` does not trust it. `session` finds the CA bundle
|
|
179
|
+
(`SSL_CERT_FILE`, then `NODE_EXTRA_CA_CERTS`, then `/root/.ccr/ca-bundle.crt`) and exports its
|
|
180
|
+
path as `CLOUD_PROXY_CA`. Pass it to the build as a secret, only when it is set:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
docker build ${CLOUD_PROXY_CA:+--secret id=proxy_ca,src="$CLOUD_PROXY_CA"} -t app .
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```dockerfile
|
|
187
|
+
RUN --mount=type=secret,id=proxy_ca \
|
|
188
|
+
if [ -f /run/secrets/proxy_ca ]; then \
|
|
189
|
+
export SSL_CERT_FILE=/run/secrets/proxy_ca NODE_EXTRA_CA_CERTS=/run/secrets/proxy_ca; \
|
|
190
|
+
fi; \
|
|
191
|
+
npm ci
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## What it does not do
|
|
195
|
+
|
|
196
|
+
- It does not make the platform's limits go away: no open ports or previews, and `gh pr` and
|
|
197
|
+
`gh issue` fail (GitHub's GraphQL is blocked; use `gh api`). `gh pr ready` fails too; the 403
|
|
198
|
+
names a REST route for it (`POST /repos/{owner}/{repo}/pulls/{n}/ccr/ready_for_review`), not
|
|
199
|
+
yet verified.
|
|
200
|
+
- It does not configure claude.ai for you: the environment is yours to set, following the manual.
|
|
201
|
+
- It does not download skills in the cloud; they live in the repo.
|
|
202
|
+
- It does not impose a way of testing.
|
|
203
|
+
- It does not install plugins, or run the hooks of plugins, from your machine.
|
|
204
|
+
- It does not install tools from GitHub releases; the proxy blocks them.
|
|
205
|
+
- It does not run Docker inside Docker, and it does not start a Docker daemon in local sessions
|
|
206
|
+
(the hook exits when `CLAUDE_CODE_REMOTE` is not `true`).
|
|
207
|
+
- It does not keep secrets safe: environment variables of a cloud environment are readable by
|
|
208
|
+
anyone who uses it.
|
|
209
|
+
- It does not run your `setup` steps on a resumed session; only a new session does.
|
|
210
|
+
- `setup` runs code fetched from npm as root. The version is pinned in the repo and the relaunch
|
|
211
|
+
uses exactly that version.
|
|
212
|
+
|
|
213
|
+
## Releasing
|
|
214
|
+
|
|
215
|
+
For maintainers. Releases are published by `.github/workflows/publish.yml` when a GitHub release is
|
|
216
|
+
published; the workflow fails if the release tag, without its leading `v`, differs from the
|
|
217
|
+
`version` in `package.json`.
|
|
218
|
+
|
|
219
|
+
The first publish of a new package cannot use npm trusted publishing, because trusted publishing
|
|
220
|
+
is configured on a package that already exists. Publish the first version by hand:
|
|
221
|
+
|
|
222
|
+
1. Either run `npm ci && npm run build && npm publish --provenance=false` from a machine logged in
|
|
223
|
+
to npm (`npm login`) with publish rights on the `agent-cloud-kit` package (provenance needs a CI, and
|
|
224
|
+
`package.json` asks for it), or run the workflow once with an `NPM_TOKEN` secret
|
|
225
|
+
(`NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` on the publish step).
|
|
226
|
+
2. On npmjs.com, open the package settings and add a trusted publisher: this GitHub repository and
|
|
227
|
+
the `publish.yml` workflow.
|
|
228
|
+
3. Remove the `NPM_TOKEN` secret if you created one. From then on, bump `version` in
|
|
229
|
+
`package.json`, update `CHANGELOG.md`, and publish a GitHub release tagged `v<version>`. A version with a prerelease suffix (`1.0.0-rc.1`) is published under the `next` tag once a stable version is `latest`; before that it also becomes `latest`, so tools that read `latest` (agent-kitchen) see it. Mark its GitHub release as a pre-release.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
package/dist/ca.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Discovery of the cloud proxy CA bundle and safe shell quoting for the lines written to the env file.
|
|
3
|
+
*/
|
|
4
|
+
import { PROXY_CA_DEFAULT } from "./constants.js";
|
|
5
|
+
/** Environment variables that may point at the CA bundle, in priority order. */
|
|
6
|
+
const CA_ENV_VARS = ["SSL_CERT_FILE", "NODE_EXTRA_CA_CERTS"];
|
|
7
|
+
/**
|
|
8
|
+
* Finds the proxy CA bundle: `SSL_CERT_FILE`, then `NODE_EXTRA_CA_CERTS`, then the default path.
|
|
9
|
+
*
|
|
10
|
+
* @param env - Process environment.
|
|
11
|
+
* @param exists - Tells whether a file exists.
|
|
12
|
+
* @returns The first candidate that exists, or undefined.
|
|
13
|
+
* @example
|
|
14
|
+
* findProxyCa({ SSL_CERT_FILE: "/etc/ca.crt" }, () => true); // "/etc/ca.crt"
|
|
15
|
+
*/
|
|
16
|
+
export function findProxyCa(env, exists) {
|
|
17
|
+
const candidates = [...CA_ENV_VARS.map((name) => env[name]), PROXY_CA_DEFAULT];
|
|
18
|
+
return candidates.find((path) => Boolean(path) && exists(path));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Quotes a value for a POSIX shell using single quotes, so nothing in it is interpreted.
|
|
22
|
+
*
|
|
23
|
+
* @param value - Any string.
|
|
24
|
+
* @returns The value wrapped in single quotes, with embedded quotes escaped as `'\''`.
|
|
25
|
+
* @example
|
|
26
|
+
* shellQuote("it's"); // 'it'\''s'
|
|
27
|
+
*/
|
|
28
|
+
export function shellQuote(value) {
|
|
29
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
30
|
+
}
|