karrio-cli 2025.5rc3__py3-none-any.whl

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 (68) hide show
  1. karrio_cli/__init__.py +0 -0
  2. karrio_cli/__main__.py +105 -0
  3. karrio_cli/ai/README.md +335 -0
  4. karrio_cli/ai/__init__.py +0 -0
  5. karrio_cli/ai/commands.py +102 -0
  6. karrio_cli/ai/karrio_ai/__init__.py +1 -0
  7. karrio_cli/ai/karrio_ai/agent.py +972 -0
  8. karrio_cli/ai/karrio_ai/architecture/INTEGRATION_AGENT_PROMPT.md +497 -0
  9. karrio_cli/ai/karrio_ai/architecture/MAPPING_AGENT_PROMPT.md +355 -0
  10. karrio_cli/ai/karrio_ai/architecture/REAL_WORLD_TESTING.md +305 -0
  11. karrio_cli/ai/karrio_ai/architecture/SCHEMA_AGENT_PROMPT.md +183 -0
  12. karrio_cli/ai/karrio_ai/architecture/TESTING_AGENT_PROMPT.md +448 -0
  13. karrio_cli/ai/karrio_ai/architecture/TESTING_GUIDE.md +271 -0
  14. karrio_cli/ai/karrio_ai/enhanced_tools.py +943 -0
  15. karrio_cli/ai/karrio_ai/rag_system.py +503 -0
  16. karrio_cli/ai/karrio_ai/tests/test_agent.py +350 -0
  17. karrio_cli/ai/karrio_ai/tests/test_real_integration.py +360 -0
  18. karrio_cli/ai/karrio_ai/tests/test_real_world_scenarios.py +513 -0
  19. karrio_cli/commands/__init__.py +0 -0
  20. karrio_cli/commands/codegen.py +336 -0
  21. karrio_cli/commands/login.py +139 -0
  22. karrio_cli/commands/plugins.py +168 -0
  23. karrio_cli/commands/sdk.py +870 -0
  24. karrio_cli/common/queries.py +101 -0
  25. karrio_cli/common/utils.py +368 -0
  26. karrio_cli/resources/__init__.py +0 -0
  27. karrio_cli/resources/carriers.py +91 -0
  28. karrio_cli/resources/connections.py +207 -0
  29. karrio_cli/resources/events.py +151 -0
  30. karrio_cli/resources/logs.py +151 -0
  31. karrio_cli/resources/orders.py +144 -0
  32. karrio_cli/resources/shipments.py +210 -0
  33. karrio_cli/resources/trackers.py +287 -0
  34. karrio_cli/templates/__init__.py +9 -0
  35. karrio_cli/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  36. karrio_cli/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  37. karrio_cli/templates/__pycache__/address.cpython-311.pyc +0 -0
  38. karrio_cli/templates/__pycache__/address.cpython-312.pyc +0 -0
  39. karrio_cli/templates/__pycache__/docs.cpython-311.pyc +0 -0
  40. karrio_cli/templates/__pycache__/docs.cpython-312.pyc +0 -0
  41. karrio_cli/templates/__pycache__/documents.cpython-311.pyc +0 -0
  42. karrio_cli/templates/__pycache__/documents.cpython-312.pyc +0 -0
  43. karrio_cli/templates/__pycache__/manifest.cpython-311.pyc +0 -0
  44. karrio_cli/templates/__pycache__/manifest.cpython-312.pyc +0 -0
  45. karrio_cli/templates/__pycache__/pickup.cpython-311.pyc +0 -0
  46. karrio_cli/templates/__pycache__/pickup.cpython-312.pyc +0 -0
  47. karrio_cli/templates/__pycache__/rates.cpython-311.pyc +0 -0
  48. karrio_cli/templates/__pycache__/rates.cpython-312.pyc +0 -0
  49. karrio_cli/templates/__pycache__/sdk.cpython-311.pyc +0 -0
  50. karrio_cli/templates/__pycache__/sdk.cpython-312.pyc +0 -0
  51. karrio_cli/templates/__pycache__/shipments.cpython-311.pyc +0 -0
  52. karrio_cli/templates/__pycache__/shipments.cpython-312.pyc +0 -0
  53. karrio_cli/templates/__pycache__/tracking.cpython-311.pyc +0 -0
  54. karrio_cli/templates/__pycache__/tracking.cpython-312.pyc +0 -0
  55. karrio_cli/templates/address.py +308 -0
  56. karrio_cli/templates/docs.py +150 -0
  57. karrio_cli/templates/documents.py +428 -0
  58. karrio_cli/templates/manifest.py +396 -0
  59. karrio_cli/templates/pickup.py +839 -0
  60. karrio_cli/templates/rates.py +638 -0
  61. karrio_cli/templates/sdk.py +947 -0
  62. karrio_cli/templates/shipments.py +892 -0
  63. karrio_cli/templates/tracking.py +437 -0
  64. karrio_cli-2025.5rc3.dist-info/METADATA +165 -0
  65. karrio_cli-2025.5rc3.dist-info/RECORD +68 -0
  66. karrio_cli-2025.5rc3.dist-info/WHEEL +5 -0
  67. karrio_cli-2025.5rc3.dist-info/entry_points.txt +2 -0
  68. karrio_cli-2025.5rc3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,503 @@
1
+ """
2
+ RAG (Retrieval-Augmented Generation) System for Karrio Carrier Integration
3
+
4
+ This module provides semantic search and pattern extraction capabilities
5
+ for analyzing existing Karrio connectors and SDK components.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import typing
11
+ from pathlib import Path
12
+ from dataclasses import dataclass
13
+ from typing import Dict, List, Any, Optional, Tuple
14
+ import hashlib
15
+
16
+
17
+ @dataclass
18
+ class CodeFragment:
19
+ """Represents a fragment of code with metadata."""
20
+ file_path: str
21
+ content: str
22
+ start_line: int
23
+ end_line: int
24
+ fragment_type: str # 'class', 'function', 'import', 'comment'
25
+ context: str
26
+ embedding_hash: Optional[str] = None
27
+
28
+
29
+ @dataclass
30
+ class ConnectorPattern:
31
+ """Extracted pattern from existing connectors."""
32
+ pattern_type: str # 'auth', 'mapping', 'schema', 'error_handling'
33
+ carrier_name: str
34
+ description: str
35
+ code_example: str
36
+ usage_context: str
37
+ related_files: List[str]
38
+ confidence_score: float = 0.0
39
+
40
+
41
+ class KarrioRAGSystem:
42
+ """RAG system for Karrio connector analysis and pattern extraction."""
43
+
44
+ def __init__(self, workspace_root: Path):
45
+ self.workspace_root = workspace_root.resolve() # Ensure we have absolute path
46
+ self.connectors_dir = self.workspace_root / "modules" / "connectors"
47
+ self.sdk_dir = self.workspace_root / "modules" / "sdk"
48
+ self.templates_dir = self.workspace_root / "modules" / "cli" / "karrio_cli" / "templates"
49
+
50
+ # In-memory knowledge base
51
+ self.code_fragments: List[CodeFragment] = []
52
+ self.patterns: List[ConnectorPattern] = []
53
+ self.indexed_files: Dict[str, str] = {} # file_path -> content_hash
54
+
55
+ # Initialize the knowledge base
56
+ self._build_knowledge_base()
57
+
58
+ def _build_knowledge_base(self):
59
+ """Build the knowledge base by indexing existing connectors and SDK."""
60
+ print("Building RAG knowledge base...")
61
+ print(f"Workspace root: {self.workspace_root}")
62
+ print(f"Connectors dir: {self.connectors_dir} (exists: {self.connectors_dir.exists()})")
63
+ print(f"SDK dir: {self.sdk_dir} (exists: {self.sdk_dir.exists()})")
64
+
65
+ # Index connector implementations
66
+ self._index_connectors()
67
+
68
+ # Index SDK patterns
69
+ self._index_sdk()
70
+
71
+ # Index CLI templates
72
+ self._index_templates()
73
+
74
+ # Extract common patterns
75
+ self._extract_patterns()
76
+
77
+ print(f"Knowledge base built with {len(self.code_fragments)} fragments and {len(self.patterns)} patterns")
78
+
79
+ def _index_connectors(self):
80
+ """Index all existing connector implementations."""
81
+ if not self.connectors_dir.exists():
82
+ return
83
+
84
+ for carrier_dir in self.connectors_dir.iterdir():
85
+ if carrier_dir.is_dir() and not carrier_dir.name.startswith('.'):
86
+ self._index_connector(carrier_dir)
87
+
88
+ def _index_connector(self, carrier_dir: Path):
89
+ """Index a specific connector directory."""
90
+ carrier_name = carrier_dir.name
91
+
92
+ # Index Python files
93
+ for py_file in carrier_dir.rglob("*.py"):
94
+ if py_file.is_file():
95
+ self._index_python_file(py_file, carrier_name)
96
+
97
+ # Index configuration files
98
+ for config_file in carrier_dir.glob("*.toml"):
99
+ if config_file.is_file():
100
+ self._index_config_file(config_file, carrier_name)
101
+
102
+ def _index_python_file(self, file_path: Path, context: str):
103
+ """Index a Python file and extract code fragments."""
104
+ try:
105
+ content = file_path.read_text(encoding='utf-8')
106
+ content_hash = hashlib.md5(content.encode()).hexdigest()
107
+
108
+ # Skip if already indexed and unchanged
109
+ file_key = str(file_path)
110
+ if file_key in self.indexed_files and self.indexed_files[file_key] == content_hash:
111
+ return
112
+
113
+ self.indexed_files[file_key] = content_hash
114
+
115
+ # Extract different types of code fragments
116
+ fragments = self._extract_code_fragments(file_path, content, context)
117
+ self.code_fragments.extend(fragments)
118
+
119
+ except Exception as e:
120
+ print(f"Error indexing {file_path}: {e}")
121
+
122
+ def _extract_code_fragments(self, file_path: Path, content: str, context: str) -> List[CodeFragment]:
123
+ """Extract meaningful code fragments from Python content."""
124
+ fragments = []
125
+ lines = content.split('\n')
126
+
127
+ # Extract imports
128
+ for i, line in enumerate(lines):
129
+ if line.strip().startswith(('import ', 'from ')):
130
+ fragments.append(CodeFragment(
131
+ file_path=str(file_path),
132
+ content=line.strip(),
133
+ start_line=i + 1,
134
+ end_line=i + 1,
135
+ fragment_type='import',
136
+ context=context
137
+ ))
138
+
139
+ # Extract class definitions
140
+ fragments.extend(self._extract_classes(file_path, content, context))
141
+
142
+ # Extract function definitions
143
+ fragments.extend(self._extract_functions(file_path, content, context))
144
+
145
+ return fragments
146
+
147
+ def _extract_classes(self, file_path: Path, content: str, context: str) -> List[CodeFragment]:
148
+ """Extract class definitions and their methods."""
149
+ fragments = []
150
+ lines = content.split('\n')
151
+
152
+ i = 0
153
+ while i < len(lines):
154
+ line = lines[i]
155
+ if line.strip().startswith('class '):
156
+ # Find the end of the class
157
+ class_start = i
158
+ indent_level = len(line) - len(line.lstrip())
159
+
160
+ j = i + 1
161
+ while j < len(lines):
162
+ if lines[j].strip() and (len(lines[j]) - len(lines[j].lstrip())) <= indent_level:
163
+ if not lines[j].strip().startswith(('"""', "'''")):
164
+ break
165
+ j += 1
166
+
167
+ class_content = '\n'.join(lines[class_start:j])
168
+ fragments.append(CodeFragment(
169
+ file_path=str(file_path),
170
+ content=class_content,
171
+ start_line=class_start + 1,
172
+ end_line=j,
173
+ fragment_type='class',
174
+ context=context
175
+ ))
176
+
177
+ i = j
178
+ else:
179
+ i += 1
180
+
181
+ return fragments
182
+
183
+ def _extract_functions(self, file_path: Path, content: str, context: str) -> List[CodeFragment]:
184
+ """Extract function definitions."""
185
+ fragments = []
186
+ lines = content.split('\n')
187
+
188
+ i = 0
189
+ while i < len(lines):
190
+ line = lines[i]
191
+ if line.strip().startswith('def '):
192
+ # Find the end of the function
193
+ func_start = i
194
+ indent_level = len(line) - len(line.lstrip())
195
+
196
+ j = i + 1
197
+ while j < len(lines):
198
+ if lines[j].strip() and (len(lines[j]) - len(lines[j].lstrip())) <= indent_level:
199
+ break
200
+ j += 1
201
+
202
+ func_content = '\n'.join(lines[func_start:j])
203
+ fragments.append(CodeFragment(
204
+ file_path=str(file_path),
205
+ content=func_content,
206
+ start_line=func_start + 1,
207
+ end_line=j,
208
+ fragment_type='function',
209
+ context=context
210
+ ))
211
+
212
+ i = j
213
+ else:
214
+ i += 1
215
+
216
+ return fragments
217
+
218
+ def _index_config_file(self, file_path: Path, context: str):
219
+ """Index configuration files."""
220
+ try:
221
+ content = file_path.read_text(encoding='utf-8')
222
+ self.code_fragments.append(CodeFragment(
223
+ file_path=str(file_path),
224
+ content=content,
225
+ start_line=1,
226
+ end_line=len(content.split('\n')),
227
+ fragment_type='config',
228
+ context=context
229
+ ))
230
+ except Exception as e:
231
+ print(f"Error indexing config {file_path}: {e}")
232
+
233
+ def _index_sdk(self):
234
+ """Index SDK patterns and utilities."""
235
+ if not self.sdk_dir.exists():
236
+ return
237
+
238
+ for py_file in self.sdk_dir.rglob("*.py"):
239
+ if py_file.is_file():
240
+ self._index_python_file(py_file, "sdk")
241
+
242
+ def _index_templates(self):
243
+ """Index CLI templates."""
244
+ if not self.templates_dir.exists():
245
+ return
246
+
247
+ for template_file in self.templates_dir.glob("*.py"):
248
+ if template_file.is_file():
249
+ self._index_python_file(template_file, "template")
250
+
251
+ def _extract_patterns(self):
252
+ """Extract common patterns from indexed code fragments."""
253
+ # Group fragments by functionality
254
+ auth_patterns = self._find_auth_patterns()
255
+ mapping_patterns = self._find_mapping_patterns()
256
+ schema_patterns = self._find_schema_patterns()
257
+ error_patterns = self._find_error_patterns()
258
+
259
+ self.patterns.extend(auth_patterns)
260
+ self.patterns.extend(mapping_patterns)
261
+ self.patterns.extend(schema_patterns)
262
+ self.patterns.extend(error_patterns)
263
+
264
+ def _find_auth_patterns(self) -> List[ConnectorPattern]:
265
+ """Find authentication patterns across connectors."""
266
+ patterns = []
267
+
268
+ # Look for authentication-related code
269
+ auth_keywords = ['auth', 'token', 'api_key', 'credential', 'login', 'bearer', 'oauth']
270
+
271
+ for fragment in self.code_fragments:
272
+ if any(keyword in fragment.content.lower() for keyword in auth_keywords):
273
+ if fragment.fragment_type in ['function', 'class']:
274
+ patterns.append(ConnectorPattern(
275
+ pattern_type='auth',
276
+ carrier_name=fragment.context,
277
+ description=f"Authentication pattern from {fragment.context}",
278
+ code_example=fragment.content,
279
+ usage_context=f"Lines {fragment.start_line}-{fragment.end_line} in {fragment.file_path}",
280
+ related_files=[fragment.file_path],
281
+ confidence_score=0.8
282
+ ))
283
+
284
+ return patterns
285
+
286
+ def _find_mapping_patterns(self) -> List[ConnectorPattern]:
287
+ """Find mapping patterns across connectors."""
288
+ patterns = []
289
+
290
+ # Look for mapping-related functions
291
+ mapping_keywords = ['parse_', 'request', 'response', 'transform', 'map_', 'convert']
292
+
293
+ for fragment in self.code_fragments:
294
+ if fragment.fragment_type == 'function':
295
+ if any(keyword in fragment.content.lower() for keyword in mapping_keywords):
296
+ patterns.append(ConnectorPattern(
297
+ pattern_type='mapping',
298
+ carrier_name=fragment.context,
299
+ description=f"Mapping pattern from {fragment.context}",
300
+ code_example=fragment.content,
301
+ usage_context=f"Lines {fragment.start_line}-{fragment.end_line} in {fragment.file_path}",
302
+ related_files=[fragment.file_path],
303
+ confidence_score=0.7
304
+ ))
305
+
306
+ return patterns
307
+
308
+ def _find_schema_patterns(self) -> List[ConnectorPattern]:
309
+ """Find schema patterns across connectors."""
310
+ patterns = []
311
+
312
+ # Look for schema-related classes
313
+ schema_keywords = ['@attr.s', 'class.*Type:', 'dataclass', 'jstruct']
314
+
315
+ for fragment in self.code_fragments:
316
+ if fragment.fragment_type == 'class':
317
+ if any(keyword.lower() in fragment.content.lower() for keyword in schema_keywords):
318
+ patterns.append(ConnectorPattern(
319
+ pattern_type='schema',
320
+ carrier_name=fragment.context,
321
+ description=f"Schema pattern from {fragment.context}",
322
+ code_example=fragment.content,
323
+ usage_context=f"Lines {fragment.start_line}-{fragment.end_line} in {fragment.file_path}",
324
+ related_files=[fragment.file_path],
325
+ confidence_score=0.9
326
+ ))
327
+
328
+ return patterns
329
+
330
+ def _find_error_patterns(self) -> List[ConnectorPattern]:
331
+ """Find error handling patterns across connectors."""
332
+ patterns = []
333
+
334
+ # Look for error handling code
335
+ error_keywords = ['error', 'exception', 'message', 'parse_error', 'handle_error']
336
+
337
+ for fragment in self.code_fragments:
338
+ if any(keyword in fragment.content.lower() for keyword in error_keywords):
339
+ if fragment.fragment_type in ['function', 'class']:
340
+ patterns.append(ConnectorPattern(
341
+ pattern_type='error_handling',
342
+ carrier_name=fragment.context,
343
+ description=f"Error handling pattern from {fragment.context}",
344
+ code_example=fragment.content,
345
+ usage_context=f"Lines {fragment.start_line}-{fragment.end_line} in {fragment.file_path}",
346
+ related_files=[fragment.file_path],
347
+ confidence_score=0.6
348
+ ))
349
+
350
+ return patterns
351
+
352
+ def search_patterns(self,
353
+ pattern_type: Optional[str] = None,
354
+ carrier_name: Optional[str] = None,
355
+ keywords: Optional[List[str]] = None,
356
+ limit: int = 10) -> List[ConnectorPattern]:
357
+ """Search for patterns based on criteria."""
358
+ filtered_patterns = self.patterns
359
+
360
+ # Filter by pattern type
361
+ if pattern_type:
362
+ filtered_patterns = [p for p in filtered_patterns if p.pattern_type == pattern_type]
363
+
364
+ # Filter by carrier name
365
+ if carrier_name:
366
+ filtered_patterns = [p for p in filtered_patterns if carrier_name.lower() in p.carrier_name.lower()]
367
+
368
+ # Filter by keywords
369
+ if keywords:
370
+ def matches_keywords(pattern: ConnectorPattern) -> bool:
371
+ text = f"{pattern.description} {pattern.code_example}".lower()
372
+ return any(keyword.lower() in text for keyword in keywords)
373
+
374
+ filtered_patterns = [p for p in filtered_patterns if matches_keywords(p)]
375
+
376
+ # Sort by confidence score
377
+ filtered_patterns.sort(key=lambda p: p.confidence_score, reverse=True)
378
+
379
+ return filtered_patterns[:limit]
380
+
381
+ def get_similar_carriers(self, carrier_characteristics: Dict[str, Any]) -> List[str]:
382
+ """Find carriers with similar characteristics."""
383
+ # Simple similarity based on common patterns
384
+ api_type = carrier_characteristics.get('api_type', 'REST')
385
+ auth_type = carrier_characteristics.get('auth_type', 'API_KEY')
386
+ operations = set(carrier_characteristics.get('operations', []))
387
+
388
+ similar_carriers = []
389
+ carrier_scores = {}
390
+
391
+ for pattern in self.patterns:
392
+ carrier = pattern.carrier_name
393
+ if carrier not in carrier_scores:
394
+ carrier_scores[carrier] = 0
395
+
396
+ # Score based on pattern matches
397
+ if api_type.lower() in pattern.code_example.lower():
398
+ carrier_scores[carrier] += 1
399
+
400
+ if auth_type.lower() in pattern.code_example.lower():
401
+ carrier_scores[carrier] += 2
402
+
403
+ # Check for operation matches
404
+ for op in operations:
405
+ if op.lower() in pattern.code_example.lower():
406
+ carrier_scores[carrier] += 1
407
+
408
+ # Sort by score and return top carriers
409
+ sorted_carriers = sorted(carrier_scores.items(), key=lambda x: x[1], reverse=True)
410
+ return [carrier for carrier, score in sorted_carriers[:5] if score > 0]
411
+
412
+ def get_implementation_examples(self,
413
+ operation: str,
414
+ carrier_names: List[str] = None) -> List[CodeFragment]:
415
+ """Get implementation examples for specific operations."""
416
+ examples = []
417
+
418
+ for fragment in self.code_fragments:
419
+ if operation.lower() in fragment.content.lower():
420
+ if carrier_names is None or fragment.context in carrier_names:
421
+ examples.append(fragment)
422
+
423
+ return examples[:10] # Limit results
424
+
425
+ def analyze_carrier_structure(self, carrier_name: str) -> Dict[str, Any]:
426
+ """Analyze the structure of an existing carrier implementation."""
427
+ analysis = {
428
+ 'carrier_name': carrier_name,
429
+ 'files': [],
430
+ 'patterns': [],
431
+ 'structure': {
432
+ 'has_schemas': False,
433
+ 'has_mappings': False,
434
+ 'has_tests': False,
435
+ 'has_providers': False
436
+ }
437
+ }
438
+
439
+ # Find all fragments for this carrier
440
+ carrier_fragments = [f for f in self.code_fragments if f.context == carrier_name]
441
+
442
+ for fragment in carrier_fragments:
443
+ analysis['files'].append({
444
+ 'path': fragment.file_path,
445
+ 'type': fragment.fragment_type,
446
+ 'lines': f"{fragment.start_line}-{fragment.end_line}"
447
+ })
448
+
449
+ # Determine structure components
450
+ if 'schemas' in fragment.file_path:
451
+ analysis['structure']['has_schemas'] = True
452
+ elif 'mappers' in fragment.file_path:
453
+ analysis['structure']['has_mappings'] = True
454
+ elif 'test' in fragment.file_path:
455
+ analysis['structure']['has_tests'] = True
456
+ elif 'providers' in fragment.file_path:
457
+ analysis['structure']['has_providers'] = True
458
+
459
+ # Find patterns for this carrier
460
+ analysis['patterns'] = [p for p in self.patterns if p.carrier_name == carrier_name]
461
+
462
+ return analysis
463
+
464
+
465
+ # Global RAG system instance
466
+ _rag_system: Optional[KarrioRAGSystem] = None
467
+
468
+
469
+ def get_rag_system(workspace_root: Path) -> KarrioRAGSystem:
470
+ """Get or create the global RAG system instance."""
471
+ global _rag_system
472
+ if _rag_system is None:
473
+ _rag_system = KarrioRAGSystem(workspace_root)
474
+ return _rag_system
475
+
476
+
477
+ def search_implementation_patterns(pattern_type: str,
478
+ carrier_name: str = None,
479
+ keywords: List[str] = None) -> List[Dict[str, Any]]:
480
+ """Search for implementation patterns using the RAG system."""
481
+ from pathlib import Path
482
+
483
+ # Get workspace root - resolve relative to this file's location
484
+ workspace_root = Path(__file__).resolve().parents[5] # Use resolve() for consistent path handling
485
+ rag = get_rag_system(workspace_root)
486
+
487
+ patterns = rag.search_patterns(
488
+ pattern_type=pattern_type,
489
+ carrier_name=carrier_name,
490
+ keywords=keywords
491
+ )
492
+
493
+ return [
494
+ {
495
+ 'pattern_type': p.pattern_type,
496
+ 'carrier_name': p.carrier_name,
497
+ 'description': p.description,
498
+ 'code_example': p.code_example,
499
+ 'usage_context': p.usage_context,
500
+ 'confidence_score': p.confidence_score
501
+ }
502
+ for p in patterns
503
+ ]