ultra-dex 3.1.0 → 3.3.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 (52) hide show
  1. package/README.md +79 -74
  2. package/assets/code-patterns/clerk-middleware.ts +138 -0
  3. package/assets/code-patterns/prisma-schema.prisma +224 -0
  4. package/assets/code-patterns/rls-policies.sql +246 -0
  5. package/assets/code-patterns/server-actions.ts +191 -0
  6. package/assets/code-patterns/trpc-router.ts +258 -0
  7. package/assets/cursor-rules/13-ai-integration.mdc +155 -0
  8. package/assets/cursor-rules/14-server-components.mdc +81 -0
  9. package/assets/cursor-rules/15-server-actions.mdc +102 -0
  10. package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
  11. package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
  12. package/bin/ultra-dex.js +50 -1
  13. package/lib/commands/agents.js +16 -13
  14. package/lib/commands/banner.js +43 -21
  15. package/lib/commands/build.js +26 -17
  16. package/lib/commands/cloud.js +780 -0
  17. package/lib/commands/doctor.js +98 -79
  18. package/lib/commands/exec.js +434 -0
  19. package/lib/commands/generate.js +19 -16
  20. package/lib/commands/github.js +475 -0
  21. package/lib/commands/init.js +52 -56
  22. package/lib/commands/scaffold.js +151 -0
  23. package/lib/commands/search.js +477 -0
  24. package/lib/commands/serve.js +15 -13
  25. package/lib/commands/state.js +43 -70
  26. package/lib/commands/swarm.js +31 -9
  27. package/lib/config/theme.js +47 -0
  28. package/lib/mcp/client.js +502 -0
  29. package/lib/providers/agent-sdk.js +630 -0
  30. package/lib/providers/anthropic-agents.js +580 -0
  31. package/lib/templates/code/clerk-middleware.ts +138 -0
  32. package/lib/templates/code/prisma-schema.prisma +224 -0
  33. package/lib/templates/code/rls-policies.sql +246 -0
  34. package/lib/templates/code/server-actions.ts +191 -0
  35. package/lib/templates/code/trpc-router.ts +258 -0
  36. package/lib/themes/doomsday.js +229 -0
  37. package/lib/ui/index.js +5 -0
  38. package/lib/ui/interface.js +241 -0
  39. package/lib/ui/spinners.js +116 -0
  40. package/lib/ui/theme.js +183 -0
  41. package/lib/utils/agents.js +32 -0
  42. package/lib/utils/browser.js +373 -0
  43. package/lib/utils/help.js +64 -0
  44. package/lib/utils/messages.js +35 -0
  45. package/lib/utils/progress.js +24 -0
  46. package/lib/utils/prompts.js +47 -0
  47. package/lib/utils/spinners.js +46 -0
  48. package/lib/utils/status.js +31 -0
  49. package/lib/utils/tables.js +41 -0
  50. package/lib/utils/theme-state.js +9 -0
  51. package/lib/utils/version-display.js +32 -0
  52. package/package.json +19 -4
@@ -4,9 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from 'chalk';
7
- import ora from 'ora';
8
7
  import fs from 'fs/promises';
9
- import { watch as fsWatch } from 'fs';
10
8
  import path from 'path';
11
9
  import { validateSafePath } from '../utils/validation.js';
12
10
  import { buildGraph } from '../utils/graph.js';
@@ -34,19 +32,16 @@ export async function saveState(state) {
34
32
  }
35
33
 
36
34
  export async function computeState() {
37
- // Try to load existing state first to check schema
38
35
  const existing = await loadState();
39
- if (existing && existing.project?.mode === 'GOD_MODE') {
40
- // In God Mode, we update the timestamp
36
+ if (existing && existing.project?.mode === 'ULTRA_MODE') {
41
37
  existing.updatedAt = new Date().toISOString();
42
38
  return existing;
43
39
  }
44
40
 
45
- // Legacy computation logic
46
41
  const state = {
47
- version: '2.4.0',
42
+ version: '3.2.0',
48
43
  updatedAt: new Date().toISOString(),
49
- project: { name: path.basename(process.cwd()) },
44
+ project: { name: path.basename(process.cwd()), mode: 'ULTRA_MODE' },
50
45
  files: {},
51
46
  sections: { total: 34, completed: 0, list: [] },
52
47
  score: 0
@@ -73,7 +68,7 @@ export async function computeState() {
73
68
  } catch { /* no plan */ }
74
69
 
75
70
  const fileScore = Object.values(state.files).filter(f => f.exists).length / coreFiles.length * 40;
76
- const sectionScore = state.sections.completed / state.sections.total * 60;
71
+ const sectionScore = Math.min(state.sections.completed / state.sections.total * 60, 60);
77
72
  state.score = Math.round(fileScore + sectionScore);
78
73
 
79
74
  return state;
@@ -92,10 +87,8 @@ export function registerAlignCommand(program) {
92
87
  .option('--strict', 'Exit with error if score < 70')
93
88
  .option('--json', 'Output as JSON')
94
89
  .action(async (options) => {
95
- // 1. Compute Base State
96
90
  const state = await computeState();
97
91
 
98
- // 2. Compute Graph Score (God Mode)
99
92
  let graphScore = 0;
100
93
  let graphStats = { nodes: 0, edges: 0 };
101
94
 
@@ -103,9 +96,6 @@ export function registerAlignCommand(program) {
103
96
  const graph = await buildGraph();
104
97
  graphStats = { nodes: graph.nodes.length, edges: graph.edges.length };
105
98
 
106
- // Simple heuristic: A healthy project has nodes and edges
107
- // 10+ nodes = 20 points
108
- // 10+ edges = 20 points
109
99
  const nodesPoints = Math.min(graph.nodes.length * 2, 20);
110
100
  const edgesPoints = Math.min(graph.edges.length * 2, 20);
111
101
  graphScore = nodesPoints + edgesPoints;
@@ -113,14 +103,12 @@ export function registerAlignCommand(program) {
113
103
  // Graph failed
114
104
  }
115
105
 
116
- // 3. Combine Scores
117
- // Legacy score (files/plan) is 60% weight, Graph is 40%
118
106
  const totalScore = Math.min(Math.round((state.score * 0.6) + graphScore), 100);
119
107
 
120
108
  if (options.json) {
121
- console.log(JSON.stringify({
109
+ console.log(JSON.stringify({
122
110
  score: totalScore,
123
- legacyScore: state.score,
111
+ documentationScore: state.score,
124
112
  graphScore,
125
113
  graphStats,
126
114
  files: Object.values(state.files).filter(f => f.exists).length,
@@ -129,7 +117,7 @@ export function registerAlignCommand(program) {
129
117
  } else {
130
118
  const icon = totalScore >= 80 ? '✅' : totalScore >= 50 ? '⚠️' : '❌';
131
119
  console.log(`${icon} Alignment: ${totalScore}/100`);
132
- console.log(chalk.gray(` • Plan/Docs: ${state.score}/100`));
120
+ console.log(chalk.gray(` • Documentation: ${state.score}/100`));
133
121
  console.log(chalk.gray(` • Code Graph: ${graphScore}/40 (Nodes: ${graphStats.nodes}, Edges: ${graphStats.edges})`));
134
122
  }
135
123
 
@@ -153,7 +141,7 @@ export function registerStatusCommand(program) {
153
141
 
154
142
  let state = await loadState();
155
143
  if (!state) {
156
- console.log(chalk.yellow('\n⚠️ No .ultra/state.json found. Generating...\n'));
144
+ console.log(chalk.yellow('\nℹ️ Initializing project state...\n'));
157
145
  state = await computeState();
158
146
  await saveState(state);
159
147
  }
@@ -166,13 +154,12 @@ export function registerStatusCommand(program) {
166
154
  console.log(chalk.bold('\n📊 Ultra-Dex Status\n'));
167
155
  console.log(chalk.gray('─'.repeat(50)));
168
156
 
169
- if (state.project?.mode === 'GOD_MODE') {
170
- // Render God Mode Status
171
- console.log(chalk.cyan(` MODE: ${state.project.mode}`));
172
- console.log(chalk.gray(` Version: ${state.project.version}`));
157
+ if (state.phases) {
158
+ console.log(chalk.cyan(` MODE: ${state.project?.mode || 'ULTRA_MODE'}`));
159
+ console.log(chalk.gray(` Version: ${state.project?.version || state.version}`));
173
160
  console.log(chalk.gray('─'.repeat(50)));
174
161
 
175
- console.log(chalk.bold('\n🚀 Phases:'));
162
+ console.log(chalk.bold('\n🚀 Implementation Phases:'));
176
163
  state.phases.forEach(phase => {
177
164
  const icon = phase.status === 'completed' ? '✅' : phase.status === 'in_progress' ? '🔄' : '⏳';
178
165
  console.log(` ${icon} ${chalk.bold(phase.name)}`);
@@ -183,31 +170,32 @@ export function registerStatusCommand(program) {
183
170
  console.log('');
184
171
  });
185
172
 
186
- console.log(chalk.bold('🤖 Agents:'));
187
- state.agents.registry.forEach(agent => {
188
- const active = state.agents.active.includes(agent) ? chalk.green('(Active)') : '';
189
- console.log(` • @${agent} ${active}`);
190
- });
173
+ if (state.agents) {
174
+ console.log(chalk.bold('🤖 Orchestration Agents:'));
175
+ state.agents.registry?.forEach(agent => {
176
+ const active = state.agents.active?.includes(agent) ? chalk.green('(Active)') : '';
177
+ console.log(` • @${agent} ${active}`);
178
+ });
179
+ }
191
180
 
192
181
  } else {
193
- // Render Legacy Status
194
182
  const scoreColor = state.score >= 80 ? 'green' : state.score >= 50 ? 'yellow' : 'red';
195
183
  console.log(chalk[scoreColor](` Score: ${state.score}/100`));
196
184
  console.log(chalk.gray(` Updated: ${state.updatedAt}`));
197
185
  console.log(chalk.gray('─'.repeat(50)));
198
186
 
199
- console.log(chalk.bold('\n📁 Files:'));
187
+ console.log(chalk.bold('\n📁 Documentation Files:'));
200
188
  if (state.files) {
201
189
  Object.entries(state.files).forEach(([name, info]) => {
202
- const icon = info.exists ? chalk.green('✓') : chalk.red('');
190
+ const icon = info.exists ? chalk.green('✓') : chalk.red('');
203
191
  const size = info.exists ? chalk.gray(` (${info.size} bytes)`) : '';
204
192
  console.log(` ${icon} ${name}${size}`);
205
193
  });
206
194
  }
207
195
 
208
- console.log(chalk.bold('\n📝 Sections:'));
209
- console.log(` ${state.sections.completed}/${state.sections.total} documented`);
210
- if (state.sections.list.length > 0) {
196
+ console.log(chalk.bold('\n📝 Implementation Sections:'));
197
+ console.log(` ${state.sections.completed}/${state.sections.total} completed`);
198
+ if (state.sections.list?.length > 0) {
211
199
  const recent = state.sections.list.slice(-3);
212
200
  recent.forEach(s => console.log(chalk.gray(` ${s.number}. ${s.title}`)));
213
201
  if (state.sections.list.length > 3) {
@@ -220,19 +208,15 @@ export function registerStatusCommand(program) {
220
208
  }
221
209
 
222
210
  export function registerWatchCommand(program) {
223
- // This is now handled by watch.js, but keeping here for legacy imports if any.
224
- // In God Mode, watch.js replaces this.
225
- // The bin/ultra-dex.js uses watch.js, so this might be dead code or overwritten.
226
- // I will leave it as is or update it to be safe.
227
211
  program
228
- .command('watch-legacy') // Rename to avoid conflict if both registered
212
+ .command('watch-legacy')
229
213
  .action(() => console.log("Use 'ultra-dex watch' instead."));
230
214
  }
231
215
 
232
216
  export function registerPreCommitCommand(program) {
233
217
  program
234
218
  .command('pre-commit')
235
- .description('Pre-commit hook - verify before commit')
219
+ .description('Pre-commit hook - verify standards before commit')
236
220
  .option('--install', 'Install git pre-commit hook')
237
221
  .option('--scan', 'Include deep code quality scan in hook')
238
222
  .option('--ai', 'Run AI-powered quality review on staged changes')
@@ -254,7 +238,7 @@ export function registerPreCommitCommand(program) {
254
238
  # Ultra-Dex pre-commit hook
255
239
  npx ultra-dex align --strict${scanCmd}${aiCmd}
256
240
  if [ $? -ne 0 ]; then
257
- echo " Ultra-Dex quality gate failed."
241
+ echo " Ultra-Dex quality gate failed."
258
242
  echo " Run 'ultra-dex review' or 'ultra-dex validate --scan' for details."
259
243
  exit 1
260
244
  fi
@@ -262,22 +246,20 @@ fi
262
246
  try {
263
247
  await fs.mkdir(path.dirname(hookPath), { recursive: true });
264
248
  await fs.writeFile(hookPath, hookScript, { mode: 0o755 });
265
- console.log(chalk.green(' Pre-commit hook installed!'));
249
+ console.log(chalk.green(' Pre-commit hook installed!'));
266
250
  console.log(chalk.gray(' Commits will be blocked if alignment score < 70 or AI review fails.'));
267
251
  } catch (e) {
268
- console.log(chalk.red(' Failed to install hook: ' + e.message));
252
+ console.log(chalk.red(' Failed to install hook: ' + e.message));
269
253
  }
270
254
  return;
271
255
  }
272
256
 
273
257
  const state = await loadState();
274
258
 
275
- // AI Quality Gate (God Mode)
276
259
  if (options.ai) {
277
- const spinner = (await import('ora')).default('🤖 AI Quality Gate: Reviewing staged changes...').start();
260
+ const spinner = (await import('ora')).default('🤖 AI Quality Review: Analyzing staged changes...').start();
278
261
  try {
279
262
  const { execSync } = await import('child_process');
280
- // Get staged changes
281
263
  const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' }).split('\n').filter(Boolean);
282
264
  if (staged.length === 0) {
283
265
  spinner.succeed('No staged changes to review.');
@@ -291,28 +273,23 @@ fi
291
273
  const reviewResult = await runAgentLoop('reviewer', `Review these staged files for architectural violations (e.g. missing validation, security risks):\n${staged.join('\n')}`, provider, { state });
292
274
 
293
275
  if (reviewResult.toLowerCase().includes('reject') || reviewResult.toLowerCase().includes('blocking violation')) {
294
- spinner.fail('AI Quality Gate: REJECTED');
295
- console.log(chalk.red('\nviolations found:'));
276
+ spinner.fail('Quality Gate: REJECTED');
277
+ console.log(chalk.red('\nViolations found:'));
296
278
  console.log(reviewResult);
297
279
  process.exit(1);
298
280
  }
299
- spinner.succeed('AI Quality Gate: PASSED');
281
+ spinner.succeed('Quality Gate: PASSED');
300
282
  } catch (e) {
301
- spinner.warn('AI Quality Gate skipped: ' + e.message);
283
+ spinner.warn('Quality Gate skipped: ' + e.message);
302
284
  }
303
285
  }
304
286
 
305
- if (state && state.project?.mode === 'GOD_MODE') {
306
- console.log(chalk.green(`✅ Alignment OK: GOD MODE ACTIVE`));
307
- return;
308
- }
309
-
310
287
  if (state && state.score < 70) {
311
- console.log(chalk.red(`❌ BLOCKED: Alignment score ${state.score}/100 (required: 70)`));
288
+ console.log(chalk.red(`✕ BLOCKED: Alignment score ${state.score}/100 (required: 70)`));
312
289
  console.log(chalk.yellow(' Run `ultra-dex review` for detailed analysis.'));
313
290
  process.exit(1);
314
291
  } else {
315
- console.log(chalk.green(`✅ Alignment OK: ${state ? state.score : 'N/A'}/100`));
292
+ console.log(chalk.green(`✓ Alignment verified: ${state ? state.score : 'N/A'}/100`));
316
293
  }
317
294
  });
318
295
  }
@@ -320,25 +297,21 @@ fi
320
297
  export function registerStateCommand(program) {
321
298
  program
322
299
  .command('state')
323
- .description('Manage .ultra/state.json')
324
- .option('--init', 'Initialize .ultra directory')
325
- .option('--refresh', 'Refresh state from files')
300
+ .description('Manage project state')
301
+ .option('--init', 'Initialize state directory')
302
+ .option('--refresh', 'Refresh state from documentation')
326
303
  .action(async (options) => {
327
304
  if (options.init || options.refresh) {
328
305
  const state = await computeState();
329
306
  await saveState(state);
330
- console.log(chalk.green(' State updated'));
331
- if (state.project?.mode === 'GOD_MODE') {
332
- console.log(chalk.gray(` Mode: GOD_MODE`));
333
- } else {
334
- console.log(chalk.gray(` Score: ${state.score}/100`));
335
- }
307
+ console.log(chalk.green(' Project state updated'));
308
+ console.log(chalk.gray(` Score: ${state.score}/100`));
336
309
  return;
337
310
  }
338
311
 
339
312
  const state = await loadState();
340
313
  if (!state) {
341
- console.log(chalk.yellow('No .ultra/state.json found. Run `ultra-dex state --init`'));
314
+ console.log(chalk.yellow('No state file found. Run `ultra-dex state --init`'));
342
315
  return;
343
316
  }
344
317
  console.log(JSON.stringify(state, null, 2));
@@ -351,4 +324,4 @@ export default {
351
324
  registerWatchCommand,
352
325
  registerPreCommitCommand,
353
326
  registerStateCommand,
354
- };
327
+ };
@@ -8,6 +8,9 @@ import { join } from 'path';
8
8
  import { glob } from 'glob';
9
9
  import { projectGraph } from '../mcp/graph.js';
10
10
  import { updateState, loadState, saveState } from './state.js';
11
+ import { agents } from '../utils/agents.js';
12
+ import { isDoomsdayMode } from '../utils/theme-state.js';
13
+ import { showSwarmAssemble as showDoomsdaySwarm } from '../themes/doomsday.js';
11
14
 
12
15
  const AGENT_PIPELINE = [
13
16
  { name: 'planner', description: 'Break down task into steps', tier: '1-planning' },
@@ -20,6 +23,25 @@ const AGENT_PIPELINE = [
20
23
  { name: 'reviewer', description: 'Code review', tier: '4-quality' }
21
24
  ];
22
25
 
26
+ export function showSwarmAssemble(activeAgents) {
27
+ if (isDoomsdayMode()) {
28
+ return showDoomsdaySwarm(activeAgents);
29
+ }
30
+
31
+ console.log('');
32
+ console.log(chalk.hex('#8b5cf6').bold(' ⚡ AGENT PIPELINE INITIALIZED'));
33
+ console.log('');
34
+
35
+ activeAgents.forEach((agentInfo) => {
36
+ const agent = agents[agentInfo.name];
37
+ if (agent) {
38
+ console.log(` ${agent.emoji} ${chalk.hex('#6366f1').bold(agent.name)}`);
39
+ console.log(` ${chalk.dim('"' + agent.tagline + '"')}`);
40
+ console.log('');
41
+ }
42
+ });
43
+ }
44
+
23
45
  async function runAgent(agent, task, context, previousOutput, provider) {
24
46
  const agentPrompt = await loadAgentPrompt(agent.name);
25
47
  const prompt = `
@@ -67,7 +89,7 @@ async function writeSwarmLog(logDir, task, results, stats) {
67
89
  }
68
90
 
69
91
  export async function swarmCommand(task, options) {
70
- console.log(chalk.cyan.bold('\n🐝 Ultra-Dex Swarm Mode v3.0\n'));
92
+ console.log(chalk.cyan.bold('\n🐝 Ultra-Dex Swarm Mode\n'));
71
93
  console.log(chalk.white(`Task: "${task}"\n`));
72
94
 
73
95
  const startTime = Date.now();
@@ -78,12 +100,12 @@ export async function swarmCommand(task, options) {
78
100
  console.log(` ${i + 1}. @${agent.name} - ${agent.description} [${agent.tier}]`);
79
101
  });
80
102
  if (options.parallel) {
81
- console.log(chalk.blue('\nℹ️ Parallel execution enabled for 2-implementation tier'));
103
+ console.log(chalk.blue('\nℹ️ Parallel execution enabled for implementation tier'));
82
104
  }
83
105
  return;
84
106
  }
85
107
 
86
- // Load context & Graph (God Mode)
108
+ // Load context & Graph
87
109
  const contextPath = join(process.cwd(), 'CONTEXT.md');
88
110
  const planPath = join(process.cwd(), 'IMPLEMENTATION-PLAN.md');
89
111
 
@@ -108,7 +130,7 @@ export async function swarmCommand(task, options) {
108
130
  // Get AI provider
109
131
  const provider = getProvider();
110
132
  if (!provider) {
111
- console.log(chalk.red('No AI provider configured. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_KEY'));
133
+ console.log(chalk.red('No AI provider configured. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY'));
112
134
  return;
113
135
  }
114
136
 
@@ -116,7 +138,7 @@ export async function swarmCommand(task, options) {
116
138
  const logDir = await ensureLogDirectory();
117
139
 
118
140
  // Update State to indicate Swarm is running
119
- const state = await loadState() || { project: { mode: 'GOD_MODE' }, agents: { active: [] } };
141
+ const state = await loadState() || { project: { mode: 'ULTRA_MODE' }, agents: { active: [] } };
120
142
  state.agents = state.agents || { active: [] };
121
143
  state.updatedAt = new Date().toISOString();
122
144
  await saveState(state);
@@ -126,7 +148,7 @@ export async function swarmCommand(task, options) {
126
148
  const agentResults = [];
127
149
  const agentTimings = {};
128
150
 
129
- // Define execution tiers (sorted by tier number)
151
+ // Define execution tiers
130
152
  const executionTiers = options.parallel
131
153
  ? [
132
154
  { name: '1-Planning', agents: AGENT_PIPELINE.filter(a => a.tier === '1-planning'), parallel: false },
@@ -144,7 +166,7 @@ export async function swarmCommand(task, options) {
144
166
  }
145
167
 
146
168
  if (tier.parallel) {
147
- // Parallel Execution for implementation tier
169
+ // Parallel Execution
148
170
  const tierStart = Date.now();
149
171
  const promises = tier.agents.map(async (agent) => {
150
172
  const agentStart = Date.now();
@@ -263,7 +285,7 @@ export async function swarmCommand(task, options) {
263
285
  });
264
286
  console.log(chalk.gray(`\n Log saved: ${logPath}`));
265
287
 
266
- console.log(chalk.green.bold('\n✅ Swarm complete!\n'));
288
+ console.log(chalk.green.bold('\n✅ Swarm execution complete!\n'));
267
289
  }
268
290
 
269
291
  async function loadAgentPrompt(name) {
@@ -281,4 +303,4 @@ async function loadAgentPrompt(name) {
281
303
  }
282
304
 
283
305
  return `You are the @${name} agent.`;
284
- }
306
+ }
@@ -0,0 +1,47 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const themes = {
4
+ default: {
5
+ primary: '#6366f1',
6
+ secondary: '#8b5cf6',
7
+ accent: '#d946ef',
8
+ success: '#22c55e',
9
+ warning: '#eab308',
10
+ error: '#ef4444',
11
+ dim: '#6b7280'
12
+ },
13
+ ocean: {
14
+ primary: '#0ea5e9',
15
+ secondary: '#06b6d4',
16
+ accent: '#14b8a6',
17
+ success: '#22c55e',
18
+ warning: '#f59e0b',
19
+ error: '#f43f5e',
20
+ dim: '#64748b'
21
+ },
22
+ forest: {
23
+ primary: '#22c55e',
24
+ secondary: '#10b981',
25
+ accent: '#14b8a6',
26
+ success: '#22c55e',
27
+ warning: '#eab308',
28
+ error: '#ef4444',
29
+ dim: '#6b7280'
30
+ }
31
+ };
32
+
33
+ let currentTheme = themes.default;
34
+
35
+ export function getTheme() {
36
+ return currentTheme;
37
+ }
38
+
39
+ export function setTheme(name) {
40
+ if (themes[name]) {
41
+ currentTheme = themes[name];
42
+ }
43
+ }
44
+
45
+ export function styled(type, text) {
46
+ return chalk.hex(currentTheme[type])(text);
47
+ }