@avsbhq/cli 1.0.0 → 3.7.0

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @avsbhq/cli
2
2
 
3
- CLI tool for local A/B experiment and project script development with the [A vs B](https://app.avsb.cloud) platform.
3
+ Command line tool for the [A vs B](https://app.avsb.cloud) platform.
4
4
 
5
- Clone experiments, edit variation code locally in your preferred editor, manage project-level scripts, and push changes back to the platform.
5
+ Clone experiments and edit variation code in your own editor, manage project scripts, create metrics and metric bindings, push datasets, and generate TypeScript types for your feature flags.
6
6
 
7
7
  ## Install
8
8
 
@@ -16,154 +16,303 @@ Verify the installation:
16
16
  avsb --version
17
17
  ```
18
18
 
19
+ Node.js 18 or later is required.
20
+
19
21
  ## Authentication
20
22
 
21
- Before using the CLI, you need to generate a personal API token from the platform.
23
+ The CLI authenticates with a **personal access token**.
22
24
 
23
- ### Step 1: Generate a token
25
+ ### Step 1: Create a token
24
26
 
25
- 1. Log in to the [A vs B platform](https://app.avsb.cloud)
27
+ 1. Log in to the [A vs B dashboard](https://app.avsb.cloud)
26
28
  2. Go to **Account Settings** (click your avatar in the top bar)
27
- 3. Open the **Personal Auth Tokens** tab
28
- 4. Click **Generate Token**, give it a name (e.g. "CLI access"), and click **Generate**
29
- 5. Copy the token immediately -- it won't be shown again
29
+ 3. Open the **Personal Access Tokens** tab
30
+ 4. Click **Create token**, give it a name (for example "CLI access"), and confirm
31
+ 5. Copy the token immediately. It is shown once and never again.
30
32
 
31
- > You need the **Developer** role or higher in your organization to generate tokens.
33
+ > You need the Owner, Admin, or Developer role in the organization to create a token.
32
34
 
33
- ### Step 2: Log in with the CLI
35
+ ### Step 2a: Save the token on your machine
34
36
 
35
37
  ```bash
36
38
  avsb login
37
39
  ```
38
40
 
39
- You'll be prompted for:
41
+ You are prompted to paste the token. That is the only question: the CLI reads the script and style languages for each experiment from the platform, so it never asks you to choose them.
42
+
43
+ ### Step 2b: Or skip the prompt (scripts, CI, containers)
44
+
45
+ ```bash
46
+ # save it without a prompt
47
+ avsb login --token "$AVSB_TOKEN" --no-input
48
+
49
+ # or set the variable and skip `avsb login` altogether
50
+ export AVSB_TOKEN=avsb_pat_...
51
+ avsb codegen --project 42
52
+ ```
53
+
54
+ | Variable | Effect |
55
+ |----------|--------|
56
+ | `AVSB_TOKEN` | Used as the token for every command, in place of the saved login |
57
+ | `AVSB_API_URL` | Platform base URL. Defaults to `https://app.avsb.cloud` |
58
+
59
+ Precedence for the token: `--token` (on `avsb login` only), then `AVSB_TOKEN`, then the saved login, then the prompt. `--no-input` turns a missing token into an error instead of a prompt, so a CI job never hangs.
40
60
 
41
- - **API token** -- paste the token you copied
42
- - **Default script language** -- JavaScript or TypeScript
43
- - **Default style language** -- CSS or SCSS
61
+ ### Where the token is stored
44
62
 
45
- ### Logout
63
+ `avsb login` writes `~/.avsb/config.json` with file mode 0600. It is **plain-text JSON on every operating system**: no macOS Keychain, no Windows Credential Manager, no encryption. Anything that can read your user account's files can read the token. The file holds one entry per organization you have logged in to, the active organization, and the platform base URL.
46
64
 
47
- To remove stored credentials:
65
+ Treat it like a password, and prefer `AVSB_TOKEN` where you would not want a token written to disk.
48
66
 
49
67
  ```bash
50
- avsb logout
68
+ avsb whoami # which account and organization does this token reach?
69
+ avsb logout # delete every saved login from this machine
51
70
  ```
52
71
 
72
+ `avsb logout` deletes the file. If `AVSB_TOKEN` is still set in your shell, the CLI says so, because commands would keep working.
73
+
53
74
  ## Quick start
54
75
 
55
- ### Experiments
76
+ ### Set up a project (one command)
56
77
 
57
78
  ```bash
58
- # 1. Log in (see Authentication above)
59
- avsb login
60
-
61
- # 2. Clone an experiment
62
- avsb clone EXP-42
79
+ avsb init # in your own application's folder
80
+ ```
63
81
 
64
- # 3. Edit the variation code in your editor
65
- cd exp-42
82
+ Detects your framework from `package.json`, links or creates a project, writes
83
+ the SDK key into the environment file that framework reads, writes a working
84
+ example, and waits for your app's first check-in so the terminal tells you it
85
+ worked. Nothing it writes overwrites anything, and every prompt has a flag.
66
86
 
67
- # 4. Start dev server with live reload
68
- avsb dev
87
+ ### Experiments
69
88
 
70
- # 5. Push changes back to the platform
71
- avsb push
89
+ ```bash
90
+ avsb login # 1. authenticate
91
+ avsb clone EXP-42 # 2. clone an experiment
92
+ cd homepage-hero-test # 3. the folder is named after the experiment
93
+ avsb dev # 4. live reload through the Chrome extension
94
+ avsb push # 5. push your changes back
72
95
  ```
73
96
 
74
97
  ### Project scripts
75
98
 
76
99
  ```bash
77
- # 1. Pull a project script
78
100
  avsb project pull PRJ-10
79
-
80
- # 2. Edit the project script in your editor
81
101
  cd project-10
82
-
83
- # 3. Start dev server with live reload
84
102
  avsb project dev
85
-
86
- # 4. Push changes back to the platform
87
103
  avsb project push
88
104
  ```
89
105
 
106
+ ### Feature flags
107
+
108
+ ```bash
109
+ avsb flags list --project PRJ-10
110
+ avsb flags toggle checkout_v2 --env production --off
111
+ ```
112
+
113
+ ### Typed feature flags
114
+
115
+ ```bash
116
+ avsb codegen --project 42 --env production
117
+ avsb codegen # inside a cloned or pulled folder
118
+ ```
119
+
90
120
  ## Commands
91
121
 
92
- ### General
122
+ Every command group the CLI ships. All of them need a token already in place (see Authentication) except `avsb login`, which supplies one, plus `avsb logout`, `avsb status` and `avsb agents-md`, which only touch local files.
123
+
124
+ ### Setup
93
125
 
94
- | Command | Description |
95
- | -------------- | ----------------------------------------------------- |
96
- | `avsb login` | Authenticate with the A vs B platform |
97
- | `avsb logout` | Log out and remove stored credentials |
98
- | `avsb org` | Show current organization |
99
- | `avsb org list` | List all organizations accessible with your token |
100
- | `avsb org switch` | Switch to a different organization |
126
+ | Command | Description |
127
+ | ------- | ----------- |
128
+ | `avsb init` | Set this project up and prove it works. `-p, --project`, `--create <name>`, `-e, --env`, `--framework`, `--flag`, `-d, --dir`, `--install`, `--no-env-file`, `--no-example`, `--example-path`, `--codegen`, `--codegen-output`, `--wait <seconds>`, `--no-wait`, `--require-heartbeat`, `--no-input`, `--json` |
129
+ | `avsb agents-md install` | Write an A vs B section into `CLAUDE.md` and `AGENTS.md` so coding agents know the commands, the credentials, and how a flag is read. `--claude`, `--agents`, `--print`, `-d, --dir`, `-p, --project`, `-e, --env`, `--json` |
130
+
131
+ The `agents-md` section sits between markers, so re-running the command replaces
132
+ it instead of adding a second copy.
133
+
134
+ ### Account and organization
135
+
136
+ | Command | Description |
137
+ | ------- | ----------- |
138
+ | `avsb login` | Save a personal access token. `--token <t>`, `--no-input`, `-u, --url <url>` |
139
+ | `avsb logout` | Delete the saved logins on this machine |
140
+ | `avsb whoami` | Show the account email, organization, role, API URL, and where the token came from |
141
+ | `avsb org` | Show the active organization and the token source |
142
+ | `avsb org list` | List the organizations this token can reach |
143
+ | `avsb org switch [org]` | Choose which organization commands use. Accepts a short id, an id, or a name; prompts when given nothing. `--no-input` |
144
+
145
+ Personal access tokens belong to a user, not to one organization, so a single token usually reaches every organization your account belongs to. `avsb org switch` records your choice in `~/.avsb/config.json`, and keeps one token per organization, so logging in with another account's token adds a login rather than replacing one.
101
146
 
102
147
  ### Experiments
103
148
 
104
- | Command | Description |
105
- | ------------------------- | ----------------------------------------------------------------- |
106
- | `avsb clone <experiment>` | Clone an experiment locally (accepts URL, EXP-123, or numeric ID) |
107
- | `avsb status` | Show the status of the current experiment |
108
- | `avsb dev` | Start dev server with live reload for the browser extension |
109
- | `avsb pull` | Pull latest experiment data from the platform |
110
- | `avsb push` | Compile and push local changes to the platform |
149
+ | Command | Description |
150
+ | ------- | ----------- |
151
+ | `avsb clone <experiment>` | Clone an experiment locally. Accepts a dashboard URL, `EXP-42`, or `42` |
152
+ | `avsb status` | Show what the local manifest says about this experiment |
153
+ | `avsb dev` | Watch local files and hot reload through the Chrome extension |
154
+ | `avsb pull` | Overwrite local files with the platform's current code. `-f, --force` |
155
+ | `avsb push` | Compile and upload local code. `-f, --force` |
111
156
 
112
157
  ### Project scripts
113
158
 
114
- | Command | Description |
115
- | ---------------------------- | -------------------------------------------------------------- |
116
- | `avsb project pull <project>` | Pull a project script (accepts PRJ-42 or numeric ID) |
117
- | `avsb project push` | Compile and push project script to the platform |
118
- | `avsb project dev` | Watch project script and hot-reload via browser extension |
159
+ | Command | Description |
160
+ | ------- | ----------- |
161
+ | `avsb project pull <project>` | Pull a project script. Accepts `PRJ-42` or `42`. `-f, --force` |
162
+ | `avsb project push` | Compile and push the project script. `-f, --force` |
163
+ | `avsb project dev` | Watch the project script and hot reload through the extension |
164
+
165
+ ### Feature flags
166
+
167
+ Flag commands call the public API with the same personal access token, so nothing extra is needed.
168
+
169
+ | Command | Description |
170
+ | ------- | ----------- |
171
+ | `avsb flags list` | List flags: id, key, type, status, and which environments serve them. `--limit <n>`, `--cursor <c>` |
172
+ | `avsb flags get <flag>` | One flag with its variations and environments. Accepts the key, `12`, or the id |
173
+ | `avsb flags create <key>` | Create a flag. `--type`, `--variation <key>=<value>` (repeatable), `--default <key>`, `--name`, `--description` |
174
+ | `avsb flags update <flag>` | Rename or re-describe a flag. `--name`, `--description` (`""` clears it), `--key`, `--if-match <version>`, `--force` |
175
+ | `avsb flags toggle <flag>` | Start or stop serving one environment. `--env <key>` plus `--on` or `--off` |
176
+
177
+ `toggle` is idempotent: asking for the state it is already in succeeds and reports that nothing changed, so an incident script can fire it blind. `update` reads the flag first and sends its version back, so a concurrent change is refused (exit code 4) instead of overwritten.
178
+
179
+ The API addresses a flag by short id or id, not by key, so a key is matched inside the project first and the match is printed (`Matched key checkout_v2 to FLG-12`). Passing the short id skips that lookup.
180
+
181
+ ### Experiments (read)
182
+
183
+ | Command | Description |
184
+ | ------- | ----------- |
185
+ | `avsb experiments list` | List experiments: id, status, name, last change. `--limit <n>`, `--cursor <c>` |
186
+
187
+ ### Getting around
188
+
189
+ | Command | Description |
190
+ | ------- | ----------- |
191
+ | `avsb open [target]` | Open a dashboard page. Targets: `dashboard`, `experiments`, `flags`, `environments`, `metrics`, `datasets`, `settings`, `snippet`, `docs`, `tokens`, `EXP-<id>`, `PRJ-<id>`. `--url` prints instead of opening |
192
+ | `avsb completion <shell>` | Print a completion script for `bash`, `zsh`, or `fish` |
193
+
194
+ ### Metrics
195
+
196
+ Tracking definitions: what counts as a click, a pageview, or a custom event.
197
+
198
+ | Command | Description |
199
+ | ------- | ----------- |
200
+ | `avsb metrics list` | List metrics. `--type <CLICK\|PAGEVIEW\|CUSTOM>`, `--include-archived` |
201
+ | `avsb metrics create` | Create a metric. `--type`, `--name`, `--description`, `--selector`, `--page-url`, `--url-match`, `--match-mode`, `--key`, `--has-value` |
202
+ | `avsb metrics update <metricId>` | Update a metric. Same field flags, plus `--no-has-value` |
203
+ | `avsb metrics archive <metricId>` | Archive a metric, keeping its history |
204
+ | `avsb metrics unarchive <metricId>` | Restore an archived metric |
205
+ | `avsb metrics delete <metricId>` | Delete a metric. Refused while metric bindings reference it. `-f, --force` |
206
+
207
+ Every metrics command accepts `--project <PRJ-42 or 42>`, and falls back to the project in the local manifest when you are inside a cloned folder.
208
+
209
+ ### Metric bindings
210
+
211
+ Analysis-side wrappers: how a metric is measured in an experiment.
212
+
213
+ | Command | Description |
214
+ | ------- | ----------- |
215
+ | `avsb metric-bindings list` | List bindings. `--library`, `--search <text>`, `--include-archived`, `--limit <n>` |
216
+ | `avsb metric-bindings create` | Create a binding. `--measure`, `--direction`, `--name`, `--description`, `--metric`, `--numerator-metric`, `--denominator-metric`, `--composition <json>`, `--component <metricId:weight>`, `--percentile`, `--value-source`, `--winsorize-upper`, `--winsorize-lower` |
217
+ | `avsb metric-bindings update <id>` | Update a binding. Same flags, plus `--library` / `--no-library` |
218
+ | `avsb metric-bindings archive <id>` | Archive a binding |
219
+ | `avsb metric-bindings unarchive <id>` | Restore an archived binding |
220
+ | `avsb metric-bindings delete <id>` | Delete a binding. Refused while experiments attach it. `-f, --force` |
221
+
222
+ Measures: `UNIQUE_CONVERSIONS_PER_VISITOR`, `TOTAL_EVENTS`, `UNIQUE_VISITORS_WHO_FIRED`, `TOTAL_VALUE_PER_VISITOR`, `TOTAL_VALUE`, `PERCENTILE`, `RATE`, `COMPOSITE`. Directions: `INCREASE`, `DECREASE`.
223
+
224
+ ### Datasets
225
+
226
+ | Command | Description |
227
+ | ------- | ----------- |
228
+ | `avsb datasets list` | List the project's datasets |
229
+ | `avsb datasets push <slug> <file.ndjson>` | Create a version, upload, and commit. `--label <text>`, `--activate`, `--timeout <seconds>` |
230
+ | `avsb datasets activate <slug> <version>` | Activate a committed version (`12` or `v12`) |
231
+ | `avsb datasets rollback <slug>` | Re-activate the previous live version. `-f, --force` |
232
+
233
+ ### Typed flags
234
+
235
+ | Command | Description |
236
+ | ------- | ----------- |
237
+ | `avsb codegen` | Generate TypeScript types from a published datafile. `-p, --project <project>` (defaults to the local manifest), `-e, --env <key>` (default `production`), `-o, --output <path>` (default `./src/generated/flags.ts`) |
238
+
239
+ The generated file declares `FlagKey` (a union of your flag keys), `FlagValues` (booleans, numbers, string-literal unions, and `unknown` for JSON flags, which you narrow yourself), and `ContextSchema` (one entry per context kind, each with `key: string` plus your registered attributes).
240
+
241
+ `--project` accepts `PRJ-42`, `42`, or a dashboard URL. Inside a folder created by `avsb project pull` or `avsb clone`, leave it out: the project comes from `.avsb-project.json`, then `.avsb.json`, and the run says which file it read.
242
+
243
+ ## Output for scripts
244
+
245
+ Every read command takes `--json` and prints exactly one document on stdout:
246
+
247
+ ```json
248
+ { "ok": true, "command": "flags list", "data": [], "meta": { "page": { "nextCursor": null, "hasMore": false } } }
249
+ ```
250
+
251
+ `data` is the platform's own response body, unchanged; anything about the response rather than the resource (a page cursor, a resource version) rides in `meta`.
252
+
253
+ A failure prints the same envelope with the platform's own error inside it:
254
+
255
+ ```json
256
+ { "ok": false, "command": "flags toggle", "error": { "code": "not_found", "message": "Flag not found", "status": 404, "docUrl": "..." } }
257
+ ```
258
+
259
+ Codes starting with `cli_` mean the CLI stopped before asking the platform. Results always go to stdout and everything else to stderr, so redirecting stdout leaves a valid document and still shows you the problem. `--quiet` prints the result only: rows, a completion script or a URL still print, progress notes and headings do not. `AVSB_JSON=1` and `AVSB_QUIET=1` do the same for a whole script.
260
+
261
+ Exit codes:
262
+
263
+ | Code | Meaning |
264
+ | ---- | ------- |
265
+ | `0` | Success |
266
+ | `1` | The command ran and failed |
267
+ | `2` | The command was called wrongly (unknown option, missing argument, bad value) |
268
+ | `3` | Not logged in, or the platform rejected the token |
269
+ | `4` | The resource changed or refuses the change: re-read it and retry |
119
270
 
120
271
  ## Experiment workflow
121
272
 
122
273
  ### Clone
123
274
 
124
- Fetches experiment data and scaffolds a local directory with editable files.
275
+ Fetches the experiment and scaffolds a local folder. Script and style languages come from the experiment's own settings on the platform.
125
276
 
126
277
  ```bash
127
- avsb clone EXP-42 # by experiment ID
128
- avsb clone 42 # by numeric ID
129
- avsb clone <platform-url> # by URL
278
+ avsb clone EXP-42
279
+ avsb clone 42
280
+ avsb clone https://app.avsb.cloud/projects/42/experiments/300015
130
281
  ```
131
282
 
132
- ### Push
133
-
134
- Compiles your local TypeScript/SCSS and pushes the built output to the platform.
283
+ ### Dev
135
284
 
136
- Before pushing, the CLI checks if the remote experiment has changed since your last sync. If changes are detected, you'll be prompted before overwriting.
285
+ Starts a file watcher and a local WebSocket server for the A vs B Chrome extension. It takes the first free port between 4400 and 4409 and prints the one it took.
137
286
 
138
287
  ```bash
139
- avsb push # push with conflict check
140
- avsb push --force # skip conflict check
288
+ avsb dev
141
289
  ```
142
290
 
143
- After a successful push, if the experiment is running, you'll be offered the option to publish changes immediately.
291
+ Press `Ctrl+C` to stop.
144
292
 
145
- ### Pull
293
+ ### Push
146
294
 
147
- Fetches the latest experiment data and updates your local files.
295
+ Compiles your TypeScript and SCSS locally, then uploads the source and the compiled output. Compile errors are reported with file, line, and column, and nothing is uploaded until they are fixed.
148
296
 
149
- If you have local edits that would be overwritten, you'll be prompted before proceeding.
297
+ Before uploading, the CLI hashes the platform's copy and warns you if it changed since your last sync. When the experiment is running, it offers to publish the change immediately.
150
298
 
151
299
  ```bash
152
- avsb pull # pull with local change check
153
- avsb pull --force # skip local change check
300
+ avsb push
301
+ avsb push --force # skip the remote-change check
154
302
  ```
155
303
 
156
- ### Dev
304
+ ### Pull
157
305
 
158
- Starts a WebSocket dev server that watches your local files for changes and hot-reloads them in the browser via the A vs B Chrome extension.
306
+ Replaces your local variation files with the platform's current code, after warning you about local edits it would overwrite.
159
307
 
160
308
  ```bash
161
- avsb dev
309
+ avsb pull
310
+ avsb pull --force
162
311
  ```
163
312
 
164
313
  ### Status
165
314
 
166
- Shows the current experiment's metadata: name, ID, status, variations, languages, last sync time, and platform URL.
315
+ Prints what the local manifest holds: experiment name, `EXP-` id, status, variation count, script and style languages, when you last synced, and the platform URL. It does not contact the platform, so it does not report whether the platform copy has moved on. `avsb push` is what detects that.
167
316
 
168
317
  ```bash
169
318
  avsb status
@@ -173,61 +322,53 @@ avsb status
173
322
 
174
323
  Project scripts run on every page where your snippet is active, independent of any experiment.
175
324
 
176
- ### Pull
177
-
178
- Fetches the project script and scaffolds a local directory. Supports both JavaScript and TypeScript projects.
179
-
180
325
  ```bash
181
- avsb project pull PRJ-10 # by project ID
182
- avsb project pull 10 # by numeric ID
326
+ avsb project pull PRJ-10 # or: avsb project pull 10
327
+ avsb project push # --force to skip the conflict check
328
+ avsb project dev
183
329
  ```
184
330
 
185
- The CLI creates a `project-<id>/` directory containing:
331
+ `project pull` creates `project-<id>/`:
186
332
 
187
- ```
333
+ ```text
188
334
  project-10/
189
- project.ts # or project.js (based on project language)
335
+ project.ts # or project.js, matching the project's language
190
336
  .avsb-project.json # manifest (project metadata, do not delete)
191
- tsconfig.json # TypeScript config (TS projects only)
192
- avsb.d.ts # type definitions for avsb APIs (TS projects only)
337
+ tsconfig.json # TypeScript projects only
338
+ avsb.d.ts # type definitions for the avsb API, TypeScript projects only
193
339
  ```
194
340
 
195
- Re-running `project pull` in an existing directory updates the local file from the platform.
341
+ Re-running `project pull` in an existing folder updates the local file.
196
342
 
197
- ### Push
198
-
199
- Compiles (if TypeScript) and pushes the project script to the platform. The script is pushed to the correct field based on the project's language setting -- JavaScript code goes to `projectJs`, TypeScript code goes to `projectTs`.
343
+ ## Cloned experiment structure
200
344
 
201
- ```bash
202
- avsb project push # push with conflict check
203
- avsb project push --force # skip conflict check
345
+ ```text
346
+ homepage-hero-test/
347
+ .avsb.json # manifest (experiment metadata, do not delete)
348
+ tsconfig.json # TypeScript experiments only
349
+ avsb.d.ts # TypeScript experiments only: the window.avsb API
350
+ variation-code.d.ts # TypeScript experiments only: options, activate, track
351
+ trigger.ts # trigger code: when the experiment activates. Editable, and pushed by `avsb push`
352
+ control/
353
+ control.ts # control variation script
354
+ control.scss # control variation styles
355
+ variant-a/
356
+ variant-a.ts # variant script
357
+ variant-a.scss # variant styles
204
358
  ```
205
359
 
206
- ### Dev
207
-
208
- Watches the project script for changes and hot-reloads via the browser extension.
360
+ File extensions follow the experiment's language settings (`.js` or `.ts`, `.css` or `.scss`).
209
361
 
210
- ```bash
211
- avsb project dev
212
- ```
362
+ The two `.d.ts` files are copies of the published `@avsbhq/snippet-types` declarations, so editor hints match what the snippet actually ships. `avsb pull` rewrites them, so upgrading the CLI upgrades the types. Do not edit them.
213
363
 
214
- ## Experiment project structure
364
+ ## When a command is refused
215
365
 
216
- After cloning an experiment, your directory looks like this:
366
+ A valid token is not always enough:
217
367
 
218
- ```
219
- exp-42/
220
- .avsb.manifest.json # manifest (experiment metadata, do not delete)
221
- trigger.ts # trigger code (when to activate the experiment)
222
- control/
223
- control.ts # control variation script
224
- control.scss # control variation styles
225
- my-variant/
226
- my-variant.ts # variant script
227
- my-variant.scss # variant styles
228
- ```
368
+ - **The organization is suspended.** Commands that read or change data are refused until the suspension is lifted. `avsb login`, `avsb whoami`, and `avsb org` keep working so you can see where you are pointed.
369
+ - **The feature is switched off for the organization.** Experiment commands are refused while web experiments are off.
229
370
 
230
- File extensions depend on the experiment's script/style language setting (`.js`/`.ts`, `.css`/`.scss`).
371
+ Both cases fail with a short message saying why.
231
372
 
232
373
  ## Uninstall
233
374