gyst-ai 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stanley Yang
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 ADDED
@@ -0,0 +1,495 @@
1
+ <p align="center">
2
+ <br />
3
+ <img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
4
+ <img src="https://img.shields.io/badge/Node.js-%3E%3D20-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js" />
5
+ <img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="MIT License" />
6
+ <img src="https://img.shields.io/badge/AI%20Powered-Claude%20%7C%20GPT%20%7C%20Gemini-blueviolet?style=for-the-badge" alt="AI Powered" />
7
+ </p>
8
+
9
+ <pre align="center">
10
+ ██████╗ ██╗ ██╗███████╗████████╗
11
+ ██╔════╝ ╚██╗ ██╔╝██╔════╝╚══██╔══╝
12
+ ██║ ███╗ ╚████╔╝ ███████╗ ██║
13
+ ██║ ██║ ╚██╔╝ ╚════██║ ██║
14
+ ╚██████╔╝ ██║ ███████║ ██║
15
+ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝
16
+ </pre>
17
+
18
+ <h3 align="center">Get your sh*t together.</h3>
19
+ <p align="center">
20
+ <strong>AI-powered developer toolkit for your terminal.</strong><br />
21
+ 7 commands that save you hours every week.
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="#installation">Install</a> ·
26
+ <a href="#commands">Commands</a> ·
27
+ <a href="#providers">Providers</a> ·
28
+ <a href="#configuration">Configuration</a> ·
29
+ <a href="#contributing">Contributing</a>
30
+ </p>
31
+
32
+ ---
33
+
34
+ ## What is gyst?
35
+
36
+ **gyst** is an all-in-one AI developer CLI that lives in your terminal. It translates natural language to git commands, reviews your code with auto-fix, roasts bad code, explains cryptic errors, generates standups from git history, writes READMEs, and explains codebases — all powered by the AI provider of your choice.
37
+
38
+ ```bash
39
+ # Translate English to git
40
+ $ gyst git "undo my last commit but keep the changes"
41
+ 🔧 git reset --soft HEAD~1
42
+
43
+ # AI code review with one-click fixes
44
+ $ gyst review src/ --fix
45
+ 🔴 SQL Injection in auth.ts:42 → auto-fixed ✓
46
+
47
+ # Get roasted
48
+ $ gyst roast index.js --severity brutal
49
+ 💀 Using var in 2026? This code is a time capsule.
50
+
51
+ # Explain any error
52
+ $ npm run build 2>&1 | gyst wtf
53
+ 💡 Your PostCSS config references a plugin that isn't installed...
54
+
55
+ # Generate standup from git history
56
+ $ gyst standup --format slack
57
+ 📋 *What I did:* Implemented auth flow, fixed 3 bugs...
58
+ ```
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ # npm
64
+ npm install -g gyst-ai
65
+
66
+ # pnpm
67
+ pnpm add -g gyst-ai
68
+
69
+ # yarn
70
+ yarn global add gyst-ai
71
+ ```
72
+
73
+ **Requirements:** Node.js >= 20
74
+
75
+ ## Quick Start
76
+
77
+ ```bash
78
+ # 1. Set your API key (pick any provider)
79
+ export ANTHROPIC_API_KEY=sk-ant-...
80
+ # or
81
+ gyst config set key anthropic sk-ant-...
82
+
83
+ # 2. Start using it
84
+ gyst git "show me what changed this week"
85
+ gyst review src/
86
+ gyst roast app.js
87
+ gyst wtf "ECONNREFUSED 127.0.0.1:5432"
88
+ gyst standup
89
+ gyst readme --dry-run
90
+ gyst explain src/core/
91
+ ```
92
+
93
+ ## Commands
94
+
95
+ ### `gyst git` — Natural Language Git
96
+
97
+ Translates plain English instructions into the exact git command you need. Understands your repo context (current branch, status, remotes, stash) and warns you before running anything destructive.
98
+
99
+ ```bash
100
+ $ gyst git "squash the last 3 commits"
101
+ ┌─ 🔧 Git Command ───────────────────────┐
102
+ │ git rebase -i HEAD~3 │
103
+ │ Interactively squash the last 3 commits │
104
+ └─────────────────────────────────────────┘
105
+ Run this command? (Y/n):
106
+ ```
107
+
108
+ **Features:**
109
+ - Reads your repo state (branch, status, log, remotes, stash count)
110
+ - Warns about destructive commands (requires typing `yes` instead of `Y`)
111
+ - Shows alternative commands when applicable
112
+ - Strips markdown fences from AI response for reliable parsing
113
+
114
+ ---
115
+
116
+ ### `gyst review` — AI Code Review with Auto-Fix
117
+
118
+ Professional code review that finds real bugs, security issues, and anti-patterns — then provides exact code replacements you can auto-apply.
119
+
120
+ ```bash
121
+ # Review a file
122
+ $ gyst review src/api/auth.ts
123
+
124
+ # Review an entire directory
125
+ $ gyst review src/
126
+
127
+ # Auto-apply all fixes
128
+ $ gyst review src/ --fix
129
+
130
+ # Only show critical and warning severity
131
+ $ gyst review src/ --severity warning
132
+ ```
133
+
134
+ **Features:**
135
+ - 4 severity levels: `critical`, `warning`, `suggestion`, `nitpick`
136
+ - Exact `currentCode` → `suggestedCode` replacements for auto-fix
137
+ - Fixes applied in reverse line order to preserve positions
138
+ - Confirms before writing files, suggests `git diff` after
139
+ - `--json` output for CI integration
140
+
141
+ ---
142
+
143
+ ### `gyst roast` — Code Roasting
144
+
145
+ Get a brutally honest (and funny) review of your code. Three severity levels from gentle encouragement to full Gordon Ramsay.
146
+
147
+ ```bash
148
+ $ gyst roast src/utils.ts --severity brutal
149
+
150
+ 💀 Roast mode: BRUTAL
151
+
152
+ 🔥 Code Roast: utils.ts
153
+
154
+ Line 1: `var express = require('express')` — Using var in 2026?
155
+ This code is a time capsule. Someone call the archaeology department.
156
+
157
+ 📊 Overall: 3/10
158
+ 🎤 Final Verdict: This code doesn't just have bugs — it IS the bug.
159
+ ```
160
+
161
+ **Severity levels:**
162
+ - `gentle` — Constructive with light humor
163
+ - `medium` — Comedy roast meets code review (default)
164
+ - `brutal` — Full Gordon Ramsay, no mercy
165
+
166
+ ---
167
+
168
+ ### `gyst wtf` — Error Explanation
169
+
170
+ Explain any error message and get copy-paste-ready fix commands. Supports piping directly from your build tools.
171
+
172
+ ```bash
173
+ # Pass an error directly
174
+ $ gyst wtf "ENOENT: no such file or directory, open './config.json'"
175
+
176
+ # Pipe from any command
177
+ $ npm run build 2>&1 | gyst wtf
178
+ $ cargo build 2>&1 | gyst wtf
179
+ $ python app.py 2>&1 | gyst wtf
180
+ ```
181
+
182
+ **Features:**
183
+ - Detects your project type for framework-specific advice
184
+ - Streaming output — see the explanation as it generates
185
+ - Supports stdin piping (`!process.stdin.isTTY` detection)
186
+
187
+ ---
188
+
189
+ ### `gyst standup` — Standup Generation
190
+
191
+ Generate standup updates from your git history. Supports multiple output formats.
192
+
193
+ ```bash
194
+ # Default: beautiful terminal output
195
+ $ gyst standup
196
+
197
+ # Slack-formatted for pasting
198
+ $ gyst standup --format slack
199
+
200
+ # JSON for automation
201
+ $ gyst standup --format json
202
+
203
+ # Look back 3 days
204
+ $ gyst standup --days 3
205
+
206
+ # Custom time range
207
+ $ gyst standup --since "last monday"
208
+ ```
209
+
210
+ **Output includes:**
211
+ - What you did (grouped by logical work items)
212
+ - What's next (inferred from patterns)
213
+ - Blockers (if any)
214
+ - Stats: commits, files changed, insertions, deletions
215
+
216
+ ---
217
+
218
+ ### `gyst readme` — README Generation
219
+
220
+ Analyzes your entire codebase and generates a comprehensive README.
221
+
222
+ ```bash
223
+ # Generate (saves as README.generated.md if README.md exists)
224
+ $ gyst readme
225
+
226
+ # Preview without writing
227
+ $ gyst readme --dry-run
228
+
229
+ # Different styles
230
+ $ gyst readme --style minimal # Just the essentials
231
+ $ gyst readme --style detailed # Full documentation (default)
232
+ $ gyst readme --style startup # Pitch-style, sells the project
233
+
234
+ # Custom output path
235
+ $ gyst readme --output DOCS.md
236
+ ```
237
+
238
+ ---
239
+
240
+ ### `gyst explain` — Code Explanation
241
+
242
+ Explain what code does in plain English. Works with single files or entire directories.
243
+
244
+ ```bash
245
+ # Explain a file
246
+ $ gyst explain src/core/ai.ts
247
+
248
+ # Explain a directory (reads up to 8 files)
249
+ $ gyst explain src/core/
250
+ ```
251
+
252
+ **Covers:**
253
+ - Purpose and problem solved
254
+ - Key logic and data flow
255
+ - Design patterns recognized
256
+ - Dependencies and gotchas
257
+
258
+ ---
259
+
260
+ ### `gyst config` — Configuration
261
+
262
+ Manage API keys, default models, and preferences.
263
+
264
+ ```bash
265
+ # Show current config
266
+ $ gyst config show
267
+
268
+ # Set API keys
269
+ $ gyst config set key anthropic sk-ant-api03-...
270
+ $ gyst config set key openai sk-...
271
+ $ gyst config set key google AIza...
272
+ $ gyst config set key groq gsk-...
273
+
274
+ # Set default model
275
+ $ gyst config set model gpt-4o
276
+ $ gyst config set model claude-opus
277
+
278
+ # Set preferences
279
+ $ gyst config set preference roastSeverity brutal
280
+ $ gyst config set preference standupDays 3
281
+
282
+ # Reset everything
283
+ $ gyst config reset
284
+ ```
285
+
286
+ ## Providers
287
+
288
+ gyst supports 6 AI providers out of the box. Set your preferred provider's API key and go.
289
+
290
+ | Provider | Models | Env Variable | Best For |
291
+ |----------|--------|-------------|----------|
292
+ | **Anthropic** | `claude-sonnet`, `claude-haiku`, `claude-opus` | `ANTHROPIC_API_KEY` | Best overall quality |
293
+ | **OpenAI** | `gpt-4o`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4.1-mini` | `OPENAI_API_KEY` | Fastest responses |
294
+ | **Google** | `gemini-pro`, `gemini-flash` | `GOOGLE_GENERATIVE_AI_API_KEY` | Free tier available |
295
+ | **Groq** | `llama3` (Llama 3.1 70B) | `GROQ_API_KEY` | Fastest open model |
296
+ | **DeepSeek** | `deepseek` | `DEEPSEEK_API_KEY` | Budget-friendly |
297
+ | **Ollama** | `local:<model-name>` | — | Fully offline / private |
298
+
299
+ ### Using a Specific Provider
300
+
301
+ ```bash
302
+ # Use a specific model
303
+ $ gyst git "status" --model gpt-4o
304
+
305
+ # Use local Ollama
306
+ $ gyst git "status" --local
307
+
308
+ # Override provider
309
+ $ gyst git "status" --provider openai
310
+ ```
311
+
312
+ ### Provider Key Resolution
313
+
314
+ 1. Environment variable (e.g., `ANTHROPIC_API_KEY`)
315
+ 2. Config file (`~/.gyst/config.json` via `gyst config set key`)
316
+ 3. Helpful error message pointing to `gyst config set key`
317
+
318
+ ## Configuration
319
+
320
+ Config is stored at `~/.gyst/config.json`. You can edit it directly or use `gyst config set`.
321
+
322
+ ```json
323
+ {
324
+ "defaultModel": "claude-sonnet",
325
+ "defaultProvider": "anthropic",
326
+ "keys": {
327
+ "anthropic": "sk-ant-..."
328
+ },
329
+ "ollamaBaseUrl": "http://localhost:11434",
330
+ "preferences": {
331
+ "roastSeverity": "medium",
332
+ "standupDays": 1,
333
+ "gitAutoExecute": false
334
+ }
335
+ }
336
+ ```
337
+
338
+ ## Global Flags
339
+
340
+ These flags work with every command:
341
+
342
+ | Flag | Description |
343
+ |------|-------------|
344
+ | `-m, --model <model>` | AI model to use (e.g., `claude-sonnet`, `gpt-4o`, `local:llama3.2`) |
345
+ | `-p, --provider <provider>` | Override provider (`anthropic`, `openai`, `google`, `groq`, `ollama`, `deepseek`) |
346
+ | `--local` | Use local Ollama model |
347
+ | `--temperature <n>` | Override temperature (0-1) |
348
+ | `--max-tokens <n>` | Override max output tokens |
349
+ | `--json` | Output as JSON (where supported) |
350
+ | `--no-color` | Disable colored output |
351
+ | `-v, --verbose` | Verbose output |
352
+
353
+ ## Project Architecture
354
+
355
+ ```
356
+ src/
357
+ ├── cli/
358
+ │ ├── index.ts # Commander.js setup, all commands
359
+ │ └── commands/ # One file per command
360
+ │ ├── git.ts # Natural language → git
361
+ │ ├── review.ts # Code review + auto-fix
362
+ │ ├── roast.ts # Code roasting
363
+ │ ├── wtf.ts # Error explanation
364
+ │ ├── standup.ts # Standup generation
365
+ │ ├── readme.ts # README generation
366
+ │ ├── explain.ts # Code explanation
367
+ │ └── config.ts # Config management
368
+ ├── core/
369
+ │ ├── types.ts # All TypeScript interfaces
370
+ │ ├── ai.ts # Unified AI client (Vercel AI SDK)
371
+ │ ├── models.ts # Model registry + aliases
372
+ │ ├── context.ts # Project context detection
373
+ │ └── git-context.ts # Git repo state gathering
374
+ ├── prompts/ # System prompts for each command
375
+ ├── ui/
376
+ │ ├── theme.ts # Chalk color theme
377
+ │ ├── spinner.ts # Ora spinner wrapper
378
+ │ └── box.ts # Boxen box wrapper
379
+ ├── utils/
380
+ │ ├── exec.ts # Shell execution
381
+ │ ├── detect-project.ts # Framework detection
382
+ │ └── file-tree.ts # Directory tree generator
383
+ └── storage/
384
+ └── config.ts # ~/.gyst/config.json management
385
+ ```
386
+
387
+ ### Tech Stack
388
+
389
+ - **Runtime:** Node.js >= 20, TypeScript (strict mode)
390
+ - **Build:** tsup (ESM output, Node 20 target)
391
+ - **CLI Framework:** Commander.js
392
+ - **Terminal UI:** chalk 5 + boxen 8 + ora 8
393
+ - **AI:** [Vercel AI SDK](https://sdk.vercel.ai/) (`ai` + provider packages)
394
+ - **Testing:** Vitest with v8 coverage (182 tests, 96%+ coverage)
395
+ - **Linting:** Biome
396
+
397
+ ## Development
398
+
399
+ ```bash
400
+ # Clone
401
+ git clone https://github.com/stanleycyang/gyst.git
402
+ cd gyst
403
+
404
+ # Install dependencies
405
+ pnpm install
406
+
407
+ # Build
408
+ pnpm build
409
+
410
+ # Run locally
411
+ node dist/cli/index.js --help
412
+
413
+ # Development (watch mode)
414
+ pnpm dev
415
+
416
+ # Run tests
417
+ pnpm test
418
+
419
+ # Run tests with coverage
420
+ pnpm vitest run --coverage
421
+
422
+ # Lint
423
+ pnpm lint
424
+
425
+ # Type check
426
+ pnpm typecheck
427
+ ```
428
+
429
+ ### Landing Page
430
+
431
+ The landing page lives in `website/` and is a Next.js 16 app with Tailwind CSS.
432
+
433
+ ```bash
434
+ cd website
435
+ pnpm install
436
+ pnpm dev # http://localhost:3000
437
+ pnpm build # Production build
438
+ ```
439
+
440
+ ## Testing
441
+
442
+ 182 tests across 26 test suites with 96%+ statement coverage.
443
+
444
+ ```
445
+ Test Files 26 passed (26)
446
+ Tests 182 passed (182)
447
+
448
+ % Coverage report from v8
449
+ All files | 96.07% Stmts | 86.37% Branch | 100% Funcs | 96.07% Lines
450
+ ```
451
+
452
+ Tests cover:
453
+ - All 7 system prompts
454
+ - All 8 CLI commands (with mocked AI calls)
455
+ - Core modules: model resolution, AI client, project context, git context
456
+ - Utils: shell exec, project detection, file tree generation
457
+ - Storage: config read/write/update, API key management
458
+ - UI: theme, spinner, box rendering
459
+ - Error handling and edge cases
460
+
461
+ ```bash
462
+ # Run all tests
463
+ pnpm test
464
+
465
+ # Run with coverage report
466
+ pnpm vitest run --coverage
467
+
468
+ # Run specific test file
469
+ pnpm vitest run tests/core/models.test.ts
470
+ ```
471
+
472
+ ## FAQ
473
+
474
+ **Q: Which AI provider should I use?**
475
+ A: Claude Sonnet (default) offers the best balance of quality and speed. GPT-4o is faster. Gemini has a generous free tier. Llama via Groq is the fastest open model. For privacy, use Ollama locally.
476
+
477
+ **Q: Does it send my code to the cloud?**
478
+ A: Yes, unless you use `--local` with Ollama. Your code is sent to whichever AI provider you configure. Use Ollama for fully offline, private operation.
479
+
480
+ **Q: Can I use it in CI/CD?**
481
+ A: Yes. Use `--json` for machine-readable output and set your API key via environment variable. Example: `gyst review src/ --severity warning --json`
482
+
483
+ **Q: How does `--fix` work?**
484
+ A: The AI returns exact `currentCode` → `suggestedCode` pairs. gyst does a `String.replace(currentCode, suggestedCode)` on your source files. It always confirms before writing and suggests `git diff` after.
485
+
486
+ ## License
487
+
488
+ MIT
489
+
490
+ ---
491
+
492
+ <p align="center">
493
+ <strong>Built with TypeScript, Vercel AI SDK, and way too much caffeine.</strong><br />
494
+ <sub>If gyst saved you time, give it a star ⭐</sub>
495
+ </p>
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/exec.ts
4
+ import { execSync } from "child_process";
5
+ async function exec(command, options = {}) {
6
+ const execOpts = {
7
+ cwd: options.cwd || process.cwd(),
8
+ encoding: "utf-8",
9
+ stdio: options.inheritStdio ? "inherit" : "pipe",
10
+ maxBuffer: 10 * 1024 * 1024
11
+ // 10MB
12
+ };
13
+ const result = execSync(command, execOpts);
14
+ return (result || "").toString();
15
+ }
16
+
17
+ // src/core/git-context.ts
18
+ async function getGitContext() {
19
+ const isRepo = await exec("git rev-parse --is-inside-work-tree").then(() => true).catch(() => false);
20
+ if (!isRepo) {
21
+ return {
22
+ isRepo: false,
23
+ currentBranch: "",
24
+ status: "",
25
+ recentLog: "",
26
+ stagedFiles: [],
27
+ modifiedFiles: [],
28
+ untrackedFiles: [],
29
+ remotes: [],
30
+ stashCount: 0,
31
+ hasUncommittedChanges: false,
32
+ lastCommitMessage: "",
33
+ branchList: []
34
+ };
35
+ }
36
+ const [branch, status, log, remotes, stashList, lastCommit, branches] = await Promise.all([
37
+ exec("git branch --show-current").catch(() => "HEAD"),
38
+ exec("git status --short").catch(() => ""),
39
+ exec("git log --oneline -20 --no-decorate").catch(() => ""),
40
+ exec("git remote -v").catch(() => ""),
41
+ exec("git stash list").catch(() => ""),
42
+ exec("git log -1 --format=%s").catch(() => ""),
43
+ exec('git branch --list --format="%(refname:short)"').catch(() => "")
44
+ ]);
45
+ const statusLines = status.trim().split("\n").filter(Boolean);
46
+ return {
47
+ isRepo: true,
48
+ currentBranch: branch.trim(),
49
+ status: status.trim(),
50
+ recentLog: log.trim(),
51
+ stagedFiles: statusLines.filter((l) => /^[MADRC]/.test(l)).map((l) => l.slice(3)),
52
+ modifiedFiles: statusLines.filter((l) => /^.M/.test(l)).map((l) => l.slice(3)),
53
+ untrackedFiles: statusLines.filter((l) => l.startsWith("??")).map((l) => l.slice(3)),
54
+ remotes: remotes.trim().split("\n").filter(Boolean),
55
+ stashCount: stashList.trim() ? stashList.trim().split("\n").length : 0,
56
+ hasUncommittedChanges: statusLines.length > 0,
57
+ lastCommitMessage: lastCommit.trim(),
58
+ branchList: branches.trim().split("\n").filter(Boolean).map((b) => b.replace(/"/g, ""))
59
+ };
60
+ }
61
+ async function getDetailedGitLog(since) {
62
+ try {
63
+ return await exec(
64
+ `git log --since="${since}" --format="%h %s (%an, %ar)" --stat --no-merges`
65
+ );
66
+ } catch {
67
+ return "";
68
+ }
69
+ }
70
+ async function getGitStats(since) {
71
+ try {
72
+ const commitCount = await exec(
73
+ `git rev-list --count --since="${since}" HEAD`
74
+ );
75
+ const diffStat = await exec(`git diff --stat --since="${since}" HEAD`);
76
+ const summaryMatch = diffStat.match(
77
+ /(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/
78
+ );
79
+ return {
80
+ commits: parseInt(commitCount.trim(), 10) || 0,
81
+ filesChanged: summaryMatch ? parseInt(summaryMatch[1], 10) || 0 : 0,
82
+ insertions: summaryMatch ? parseInt(summaryMatch[2], 10) || 0 : 0,
83
+ deletions: summaryMatch ? parseInt(summaryMatch[3], 10) || 0 : 0
84
+ };
85
+ } catch {
86
+ return { commits: 0, filesChanged: 0, insertions: 0, deletions: 0 };
87
+ }
88
+ }
89
+
90
+ export {
91
+ exec,
92
+ getGitContext,
93
+ getDetailedGitLog,
94
+ getGitStats
95
+ };