@questpie/probe 0.1.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.
Files changed (43) hide show
  1. package/dist/agent-browser-Cxuu-Zz0.js +203 -0
  2. package/dist/assert-BLP5_JwC.js +212 -0
  3. package/dist/browser-DoCXU5Bs.js +736 -0
  4. package/dist/check-Cny-3lkZ.js +41 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +30 -0
  7. package/dist/codegen-BH3cUNuf.js +61 -0
  8. package/dist/compose-D5a8qHkg.js +233 -0
  9. package/dist/config-BUEMgFYN.js +89 -0
  10. package/dist/duration-D1ya1zLn.js +3 -0
  11. package/dist/duration-DUrbfMLK.js +30 -0
  12. package/dist/health-B36ufFzJ.js +62 -0
  13. package/dist/http-BZouO1Cj.js +187 -0
  14. package/dist/index.d.ts +119 -0
  15. package/dist/index.js +4 -0
  16. package/dist/init-BjTfn_-A.js +92 -0
  17. package/dist/logs-BCgur07G.js +191 -0
  18. package/dist/output-CHUjdVDf.js +38 -0
  19. package/dist/process-manager-CzexpFO4.js +229 -0
  20. package/dist/process-manager-zzltWvZ0.js +4 -0
  21. package/dist/ps-DuHF7vmE.js +39 -0
  22. package/dist/record-C4SmoPsT.js +140 -0
  23. package/dist/recordings-Cb31alos.js +158 -0
  24. package/dist/replay-Dg9PHNrg.js +171 -0
  25. package/dist/reporter-CqWc26OP.js +25 -0
  26. package/dist/restart-By3Edj5X.js +44 -0
  27. package/dist/snapshot-diff-CqXEVTAZ.js +51 -0
  28. package/dist/start-BClY6oJq.js +79 -0
  29. package/dist/state-DRTSIt_r.js +62 -0
  30. package/dist/stop-QAP6gbDe.js +47 -0
  31. package/package.json +72 -0
  32. package/skills/qprobe/SKILL.md +103 -0
  33. package/skills/qprobe/references/browser.md +201 -0
  34. package/skills/qprobe/references/compose.md +128 -0
  35. package/skills/qprobe/references/http.md +151 -0
  36. package/skills/qprobe/references/process.md +114 -0
  37. package/skills/qprobe/references/recording.md +194 -0
  38. package/skills/qprobe-browser/SKILL.md +87 -0
  39. package/skills/qprobe-compose/SKILL.md +81 -0
  40. package/skills/qprobe-http/SKILL.md +67 -0
  41. package/skills/qprobe-process/SKILL.md +58 -0
  42. package/skills/qprobe-recording/SKILL.md +63 -0
  43. package/skills/qprobe-ux/SKILL.md +250 -0
@@ -0,0 +1,201 @@
1
+ # Browser Control Reference
2
+
3
+ Powered by `agent-browser` (default) or `playwright-cli` (optional). Uses accessibility tree snapshots with compact `@e` refs.
4
+
5
+ ## Navigation
6
+
7
+ ```bash
8
+ qprobe browser open <url> # navigate (absolute or relative to baseUrl)
9
+ qprobe browser open /login # relative
10
+ qprobe browser back # browser back
11
+ qprobe browser forward # browser forward
12
+ qprobe browser reload # reload current page
13
+ qprobe browser url # print current URL
14
+ qprobe browser title # print page title
15
+ qprobe browser close # close browser
16
+ ```
17
+
18
+ ## Snapshot — How the Agent Sees the Page
19
+
20
+ ```bash
21
+ qprobe browser snapshot [flags]
22
+ ```
23
+
24
+ | Flag | Short | Description |
25
+ |------|-------|-------------|
26
+ | `--interactive` | `-i` | Only interactive elements (buttons, inputs, links) |
27
+ | `--compact` | `-c` | Remove empty structural elements |
28
+ | `--depth <n>` | `-d` | Limit tree depth |
29
+ | `--selector <css>` | `-s` | Scope to CSS selector |
30
+ | `--diff` | | Show only changes since last snapshot |
31
+
32
+ ### Output Example (-i -c)
33
+ ```
34
+ - heading "Login" [@e1]
35
+ - textbox "Email" [@e2]
36
+ - textbox "Password" [@e3]
37
+ - button "Sign In" [@e4]
38
+ - link "Forgot password?" [@e5]
39
+ ```
40
+
41
+ ~200-400 tokens for a typical page. Each element gets a stable ref like `@e1`.
42
+
43
+ ### Diff Output (--diff)
44
+ ```
45
+ URL: /login → /dashboard
46
+ REMOVED: textbox "Email" [@e2]
47
+ REMOVED: textbox "Password" [@e3]
48
+ REMOVED: button "Sign In" [@e4]
49
+ ADDED: heading "Dashboard" [@e6]
50
+ ADDED: button "Logout" [@e7]
51
+ ADDED: table (12 rows) [@e8]
52
+ ```
53
+
54
+ **Use `-i` by default.** Full snapshots include decorative elements that waste tokens. Add `-c` for even more compression. Use `--diff` after actions to see only what changed.
55
+
56
+ ## Interaction
57
+
58
+ All interaction commands accept `@e` refs (from snapshot) or CSS selectors.
59
+
60
+ ```bash
61
+ qprobe browser click @e4 # click
62
+ qprobe browser dblclick @e4 # double-click
63
+ qprobe browser fill @e2 "admin@test.com" # clear + fill
64
+ qprobe browser type "hello" # type at current focus
65
+ qprobe browser select @e3 "option-value" # select dropdown
66
+ qprobe browser check @e5 # check checkbox
67
+ qprobe browser uncheck @e5 # uncheck
68
+ qprobe browser press Enter # keyboard key
69
+ qprobe browser press Tab
70
+ qprobe browser press Control+a
71
+ qprobe browser hover @e1 # hover
72
+ qprobe browser scroll down 500 # scroll pixels
73
+ qprobe browser scroll up
74
+ qprobe browser focus @e2 # focus element
75
+ qprobe browser upload @e6 ./file.pdf # file upload
76
+ ```
77
+
78
+ ## Console & Errors
79
+
80
+ ```bash
81
+ qprobe browser console # all console messages
82
+ qprobe browser console --level error # only errors
83
+ qprobe browser console --level warn # warnings and above
84
+ qprobe browser console --clear # clear buffer
85
+ qprobe browser console --json # machine-readable
86
+
87
+ qprobe browser errors # uncaught JS exceptions only
88
+ qprobe browser errors --clear
89
+ ```
90
+
91
+ Output:
92
+ ```
93
+ [error] TypeError: Cannot read property 'map' of undefined
94
+ at UserList (src/components/UserList.tsx:23:15)
95
+ [warn] Each child in a list should have a unique "key" prop
96
+ [log] API response: 200 OK
97
+ ```
98
+
99
+ ## Network
100
+
101
+ ```bash
102
+ qprobe browser network # all HTTP requests
103
+ qprobe browser network --failed # only 4xx/5xx
104
+ qprobe browser network --method POST # filter by method
105
+ qprobe browser network --grep "api/" # filter by URL pattern
106
+ qprobe browser network --json # machine-readable
107
+ ```
108
+
109
+ Output:
110
+ ```
111
+ 200 GET /api/health 12ms
112
+ 200 GET /api/users 89ms
113
+ 201 POST /api/users 123ms
114
+ 500 POST /api/orders 234ms ← FAILED
115
+ 404 GET /api/nonexistent 8ms ← FAILED
116
+ ```
117
+
118
+ ## Screenshots
119
+
120
+ ```bash
121
+ qprobe browser screenshot # save to tmp/qprobe/shots/
122
+ qprobe browser screenshot ./my-shot.png # custom path
123
+ qprobe browser screenshot --annotate # overlay @e ref labels
124
+ qprobe browser screenshot --full # full page scroll capture
125
+ qprobe browser screenshot --selector "#main" # capture specific element
126
+ ```
127
+
128
+ Annotated screenshots overlay numbered labels `[1] @e1`, `[2] @e2` on interactive elements — useful for multimodal AI models that can reason about visual layout.
129
+
130
+ ## Waiting
131
+
132
+ ```bash
133
+ qprobe browser wait @e1 # wait for element to exist
134
+ qprobe browser wait "#loading" --hidden # wait for element to disappear
135
+ qprobe browser wait --url "/dashboard" # wait for URL change
136
+ qprobe browser wait --text "Welcome" # wait for text to appear
137
+ qprobe browser wait --network idle # wait for no pending requests
138
+ qprobe browser wait --timeout 10s # custom timeout (default 30s)
139
+ ```
140
+
141
+ ## JavaScript Execution
142
+
143
+ ```bash
144
+ qprobe browser eval "document.title"
145
+ # "My App - Dashboard"
146
+
147
+ qprobe browser eval "document.querySelectorAll('tr').length"
148
+ # 12
149
+
150
+ qprobe browser eval "localStorage.getItem('token')"
151
+ # "eyJhbG..."
152
+
153
+ qprobe browser text # extract all text content
154
+ qprobe browser text "#main" # text from specific element
155
+ ```
156
+
157
+ ## Common Patterns
158
+
159
+ ### Login Flow
160
+ ```bash
161
+ qprobe browser open /login
162
+ qprobe browser snapshot -i
163
+ qprobe browser fill @e1 "admin@test.com"
164
+ qprobe browser fill @e2 "password"
165
+ qprobe browser click @e3
166
+ qprobe browser wait --url "/dashboard"
167
+ qprobe browser snapshot --diff
168
+ ```
169
+
170
+ ### Check for Errors After Action
171
+ ```bash
172
+ qprobe browser click @e5 # submit form
173
+ qprobe browser wait --network idle
174
+ qprobe browser console --level error # any JS errors?
175
+ qprobe browser network --failed # any failed requests?
176
+ qprobe browser snapshot --diff # what changed?
177
+ ```
178
+
179
+ ### Debug Why Page Looks Wrong
180
+ ```bash
181
+ qprobe browser screenshot --annotate # see the visual state
182
+ qprobe browser console --level error # JS errors?
183
+ qprobe browser network --failed # failed API calls?
184
+ qprobe browser eval "document.querySelector('.error')?.textContent"
185
+ ```
186
+
187
+ ## Driver Selection
188
+
189
+ ```bash
190
+ # Default: agent-browser (token-efficient, Rust CLI, persistent daemon)
191
+ qprobe browser open /login
192
+
193
+ # Playwright: cross-browser, network interception, test codegen
194
+ qprobe browser open /login --driver playwright
195
+ qprobe browser open /login --driver playwright --browser firefox
196
+ ```
197
+
198
+ Set default in `qprobe.config.ts`:
199
+ ```typescript
200
+ browser: { driver: 'agent-browser' } // or 'playwright'
201
+ ```
@@ -0,0 +1,128 @@
1
+ # Compose Reference
2
+
3
+ ## Commands
4
+
5
+ ```bash
6
+ qprobe compose up [flags] # start all services in dependency order
7
+ qprobe compose down # stop all in reverse order
8
+ qprobe compose restart [name] # restart one or all
9
+ qprobe compose status # show all service states
10
+ ```
11
+
12
+ | Flag | Description |
13
+ |------|-------------|
14
+ | `--only <name,...>` | Start only these services (+ their dependencies) |
15
+ | `--skip <name,...>` | Skip these services |
16
+ | `--no-health` | Don't wait for health checks |
17
+ | `--config <path>` | Custom config file path |
18
+
19
+ ## Config File
20
+
21
+ `qprobe.config.ts` in project root:
22
+
23
+ ```typescript
24
+ import { defineConfig } from '@questpie/probe'
25
+
26
+ export default defineConfig({
27
+ services: {
28
+ db: {
29
+ cmd: 'docker compose up postgres',
30
+ ready: 'ready to accept connections',
31
+ health: 'http://localhost:5432',
32
+ stop: 'docker compose down postgres', // custom stop command
33
+ },
34
+ server: {
35
+ cmd: 'bun dev',
36
+ ready: 'ready on http://localhost:3000',
37
+ port: 3000,
38
+ health: '/api/health', // relative to http://localhost:<port>
39
+ depends: ['db'],
40
+ env: {
41
+ DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/dev',
42
+ },
43
+ },
44
+ admin: {
45
+ cmd: 'bun run admin:dev',
46
+ ready: 'ready on http://localhost:3001',
47
+ port: 3001,
48
+ depends: ['server'],
49
+ },
50
+ worker: {
51
+ cmd: 'bun run jobs',
52
+ ready: 'worker started',
53
+ depends: ['db'],
54
+ },
55
+ },
56
+ browser: {
57
+ driver: 'agent-browser',
58
+ baseUrl: 'http://localhost:3000',
59
+ },
60
+ http: {
61
+ baseUrl: 'http://localhost:3000',
62
+ },
63
+ })
64
+ ```
65
+
66
+ ## Dependency Resolution
67
+
68
+ `qprobe compose up` resolves the dependency graph and starts in order:
69
+
70
+ ```
71
+ Given: server depends on db, admin depends on server, worker depends on db
72
+
73
+ Start order:
74
+ 1. db (no dependencies)
75
+ 2. server + worker (both depend on db, start in parallel)
76
+ 3. admin (depends on server)
77
+
78
+ Stop order (reverse):
79
+ 1. admin
80
+ 2. server + worker
81
+ 3. db
82
+ ```
83
+
84
+ ## Examples
85
+
86
+ ```bash
87
+ # Start everything
88
+ qprobe compose up
89
+ # ⏳ Starting db... ready (2.3s)
90
+ # ⏳ Starting server... ready (4.1s)
91
+ # ⏳ Starting worker... ready (1.2s)
92
+ # ⏳ Starting admin... ready (3.5s)
93
+ # ✅ All 4 services ready (11.1s)
94
+
95
+ # Start only server (auto-starts db dependency)
96
+ qprobe compose up --only server
97
+ # ⏳ Starting db... ready (2.3s)
98
+ # ⏳ Starting server... ready (4.1s)
99
+ # ✅ 2 services ready (6.4s)
100
+
101
+ # Skip admin
102
+ qprobe compose up --skip admin
103
+
104
+ # Stop everything
105
+ qprobe compose down
106
+ # ⏳ Stopping admin... stopped
107
+ # ⏳ Stopping server... stopped
108
+ # ⏳ Stopping worker... stopped
109
+ # ⏳ Stopping db... stopped
110
+ # ✅ All services stopped
111
+
112
+ # Restart just the server
113
+ qprobe compose restart server
114
+
115
+ # Check status
116
+ qprobe compose status
117
+ ```
118
+
119
+ ## Inline Compose (No Config File)
120
+
121
+ ```bash
122
+ qprobe compose up \
123
+ --service "db: docker compose up postgres | ready to accept" \
124
+ --service "server: bun dev | ready on http://localhost:3000" \
125
+ --depends "server:db"
126
+ ```
127
+
128
+ Format: `--service "name: command | ready-pattern"`
@@ -0,0 +1,151 @@
1
+ # HTTP Requests Reference
2
+
3
+ ## Basic Usage
4
+
5
+ ```bash
6
+ qprobe http <METHOD> <path> [flags]
7
+ ```
8
+
9
+ Path is relative to baseUrl (from config, `--base`, or auto-detected from running services).
10
+
11
+ | Flag | Short | Description |
12
+ |------|-------|-------------|
13
+ | `--base <url>` | | Override base URL |
14
+ | `--data <json>` | `-d` | Request body (JSON string) |
15
+ | `--header <k:v>` | `-H` | Header (repeatable) |
16
+ | `--token <jwt>` | | Bearer token shortcut |
17
+ | `--status <code>` | | Assert expected status (exit 1 if different) |
18
+ | `--jq <expr>` | | JQ-style filter on response body |
19
+ | `--raw` | | Raw output (no pretty-print, no status line) |
20
+ | `--timing` | | Show request timing breakdown |
21
+ | `--verbose` | `-v` | Show full request + response headers |
22
+ | `--form` | | Send as form-urlencoded instead of JSON |
23
+ | `--file <path>` | | Upload file as multipart |
24
+
25
+ ## BaseUrl Resolution
26
+
27
+ Order of precedence:
28
+ 1. `--base` flag
29
+ 2. `QPROBE_BASE_URL` env variable
30
+ 3. `http.baseUrl` in `qprobe.config.ts`
31
+ 4. `browser.baseUrl` in config
32
+ 5. First running service with a port → `http://localhost:<port>`
33
+ 6. `http://localhost:3000` (fallback)
34
+
35
+ ## Examples
36
+
37
+ ### GET
38
+ ```bash
39
+ qprobe http GET /api/users
40
+ # 200 OK (45ms)
41
+ # [
42
+ # { "id": 1, "name": "Admin", "email": "admin@test.com" },
43
+ # { "id": 2, "name": "User", "email": "user@test.com" }
44
+ # ]
45
+ ```
46
+
47
+ ### POST with Body
48
+ ```bash
49
+ qprobe http POST /api/users -d '{"name":"New User","email":"new@test.com"}'
50
+ # 201 Created (123ms)
51
+ # { "id": 3, "name": "New User", "email": "new@test.com" }
52
+ ```
53
+
54
+ ### Auth
55
+ ```bash
56
+ # Bearer token
57
+ qprobe http GET /api/admin/stats --token "eyJhbGci..."
58
+
59
+ # Custom header
60
+ qprobe http GET /api/data -H "X-API-Key: abc123"
61
+
62
+ # Cookie
63
+ qprobe http GET /api/me -H "Cookie: session=abc123"
64
+ ```
65
+
66
+ ### Assert Status
67
+ ```bash
68
+ qprobe http GET /api/health --status 200
69
+ # ✅ 200 OK (12ms)
70
+
71
+ qprobe http DELETE /api/users/1 --status 204
72
+ # ✅ 204 No Content (89ms)
73
+
74
+ qprobe http GET /api/nonexistent --status 404
75
+ # ✅ 404 Not Found (8ms)
76
+
77
+ qprobe http GET /api/broken --status 200
78
+ # ❌ Expected 200, got 500 Internal Server Error (234ms)
79
+ # { "error": "Database connection failed" }
80
+ # Exit code: 1
81
+ ```
82
+
83
+ ### JQ Filter
84
+ ```bash
85
+ qprobe http GET /api/users --jq ".[0].name"
86
+ # "Admin"
87
+
88
+ qprobe http GET /api/users --jq "length"
89
+ # 5
90
+
91
+ qprobe http GET /api/stats --jq ".revenue.total"
92
+ # 125000
93
+ ```
94
+
95
+ ### Verbose
96
+ ```bash
97
+ qprobe http POST /api/login -d '{"email":"a@b.com","password":"123"}' -v
98
+ # → POST http://localhost:3000/api/login
99
+ # → Content-Type: application/json
100
+ # → Body: {"email":"a@b.com","password":"123"}
101
+ # ← 200 OK (89ms)
102
+ # ← Set-Cookie: session=abc123; HttpOnly; Path=/
103
+ # ← Content-Type: application/json
104
+ # { "token": "eyJ...", "user": { "id": 1, "name": "Admin" } }
105
+ ```
106
+
107
+ ### File Upload
108
+ ```bash
109
+ qprobe http POST /api/upload --file ./avatar.png -H "Authorization: Bearer eyJ..."
110
+ ```
111
+
112
+ ### Raw Output (for piping)
113
+ ```bash
114
+ # Pipe JSON to jq
115
+ qprobe http GET /api/users --raw | jq '.[].email'
116
+
117
+ # Save response to file
118
+ qprobe http GET /api/export --raw > export.json
119
+ ```
120
+
121
+ ## Common Patterns
122
+
123
+ ### Login → Use Token
124
+ ```bash
125
+ # Login and extract token
126
+ TOKEN=$(qprobe http POST /api/login -d '{"email":"admin@test.com","password":"pass"}' --raw | jq -r '.token')
127
+
128
+ # Use token in subsequent requests
129
+ qprobe http GET /api/admin/users --token "$TOKEN"
130
+ ```
131
+
132
+ ### Test CRUD Sequence
133
+ ```bash
134
+ # Create
135
+ qprobe http POST /api/users -d '{"name":"Test"}' --status 201
136
+ # Read
137
+ qprobe http GET /api/users/3 --status 200
138
+ # Update
139
+ qprobe http PUT /api/users/3 -d '{"name":"Updated"}' --status 200
140
+ # Delete
141
+ qprobe http DELETE /api/users/3 --status 204
142
+ # Verify deleted
143
+ qprobe http GET /api/users/3 --status 404
144
+ ```
145
+
146
+ ### Health Check with Retry
147
+ ```bash
148
+ # Wait for server, then test
149
+ qprobe health http://localhost:3000/api/health
150
+ qprobe http GET /api/users --status 200
151
+ ```
@@ -0,0 +1,114 @@
1
+ # Process Management Reference
2
+
3
+ ## Start a Process
4
+
5
+ ```bash
6
+ qprobe start <name> "<command>" [flags]
7
+ ```
8
+
9
+ | Flag | Default | Description |
10
+ |------|---------|-------------|
11
+ | `--ready "<pattern>"` | — | String/regex in stdout that signals ready |
12
+ | `--timeout <dur>` | `60s` | Max wait for ready pattern |
13
+ | `--port <n>` | — | Port for health checks and baseUrl |
14
+ | `--env KEY=VAL` | — | Environment variables (repeatable) |
15
+ | `--cwd <path>` | `.` | Working directory |
16
+
17
+ ### Examples
18
+
19
+ ```bash
20
+ # Database
21
+ qprobe start db "docker compose up postgres" --ready "ready to accept" --timeout 30s
22
+
23
+ # Dev server
24
+ qprobe start server "bun dev" --ready "ready on" --port 3000
25
+
26
+ # Worker with env
27
+ qprobe start worker "bun run jobs" --ready "worker started" --env QUEUE=default
28
+
29
+ # No ready pattern — just start and return immediately
30
+ qprobe start redis "redis-server"
31
+ ```
32
+
33
+ ### What Happens
34
+
35
+ 1. Spawns child process (detached, survives between CLI calls)
36
+ 2. Pipes stdout/stderr → `tmp/qprobe/logs/<name>.log` (timestamped)
37
+ 3. Monitors stdout for `--ready` pattern
38
+ 4. Returns when ready OR exits with code 2 on timeout
39
+ 5. Saves PID to `tmp/qprobe/pids/<name>.pid`
40
+ 6. Saves config to `tmp/qprobe/state/<name>.json` (for restart)
41
+
42
+ ## Stop
43
+
44
+ ```bash
45
+ qprobe stop <name> # stop one service
46
+ qprobe stop --all # stop everything
47
+ ```
48
+
49
+ Sends SIGTERM → waits 5s → SIGKILL if still alive.
50
+
51
+ ## Restart
52
+
53
+ ```bash
54
+ qprobe restart <name> # stop + start with original config
55
+ ```
56
+
57
+ ## Process Status
58
+
59
+ ```bash
60
+ qprobe ps # table view
61
+ qprobe ps --json # machine-readable
62
+ ```
63
+
64
+ Output:
65
+ ```
66
+ NAME PID PORT STATUS UPTIME MEM
67
+ db 12345 5432 ready 5m 23s 128MB
68
+ server 12346 3000 ready 5m 20s 256MB
69
+ ```
70
+
71
+ ## Health Check
72
+
73
+ ```bash
74
+ qprobe health <url> [flags]
75
+ ```
76
+
77
+ | Flag | Default | Description |
78
+ |------|---------|-------------|
79
+ | `--interval <dur>` | `1s` | Polling interval |
80
+ | `--timeout <dur>` | `30s` | Max total wait |
81
+ | `--status <code>` | `200` | Expected HTTP status |
82
+
83
+ ```bash
84
+ qprobe health http://localhost:3000/api/health
85
+ # ✅ Responding (200 OK, 45ms) after 3.2s
86
+
87
+ qprobe health http://localhost:5432 --timeout 60s
88
+ # ✅ Responding after 8.1s
89
+ ```
90
+
91
+ ## Common Patterns
92
+
93
+ ### Start server, wait, then test
94
+ ```bash
95
+ qprobe start server "bun dev" --ready "ready on" --port 3000
96
+ qprobe health http://localhost:3000/api/health
97
+ qprobe http GET /api/users
98
+ ```
99
+
100
+ ### Restart after code change
101
+ ```bash
102
+ qprobe restart server
103
+ qprobe health http://localhost:3000/api/health
104
+ qprobe check
105
+ ```
106
+
107
+ ### Check if something crashed
108
+ ```bash
109
+ qprobe ps
110
+ # If server shows "stopped" or is missing:
111
+ qprobe logs server --lines 50
112
+ # Look for the crash reason, then:
113
+ qprobe restart server
114
+ ```