winter-super-cli 2026.5.27 → 2026.5.29
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/bin/winter.js +2 -1
- package/package.json +1 -1
- package/src/ai/benchmark.js +352 -0
- package/src/ai/model-capabilities.js +185 -0
- package/src/ai/prompts/system-prompt.js +106 -29
- package/src/ai/prompts/task-classifier.js +5 -1
- package/src/ai/providers.js +145 -8
- package/src/ai/reasoning.js +190 -0
- package/src/cli/commands.js +62 -0
- package/src/cli/context-loader.js +64 -1
- package/src/cli/conversation-format.js +90 -12
- package/src/cli/prompt-builder.js +43 -17
- package/src/cli/repl-commands.js +15 -3
- package/src/cli/repl.js +327 -209
- package/src/context/resource-loader.js +136 -0
- package/src/context/router.js +77 -20
- package/src/tools/executor.js +78 -9
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Loader - Auto-discovers and indexes local resources
|
|
3
|
+
* (design systems, agent instructions, skills) for contextual injection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
13
|
+
const LOCAL_ROOT = path.join(PROJECT_ROOT, 'resources', 'local');
|
|
14
|
+
|
|
15
|
+
// ── Design Systems ──────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const DESIGN_MD_DIR = path.join(LOCAL_ROOT, 'awesome-design-md', 'design-md');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Discover all available design system brands.
|
|
21
|
+
*/
|
|
22
|
+
export async function discoverDesignBrands() {
|
|
23
|
+
try {
|
|
24
|
+
const entries = await fs.readdir(DESIGN_MD_DIR, { withFileTypes: true });
|
|
25
|
+
return entries
|
|
26
|
+
.filter(e => e.isDirectory())
|
|
27
|
+
.map(e => e.name)
|
|
28
|
+
.sort();
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load a DESIGN.md file for a specific brand.
|
|
36
|
+
*/
|
|
37
|
+
export async function loadDesignMd(brand) {
|
|
38
|
+
try {
|
|
39
|
+
const dir = path.join(DESIGN_MD_DIR, brand);
|
|
40
|
+
const files = ['DESIGN.md', 'README.md'];
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const filePath = path.join(dir, file);
|
|
43
|
+
await fs.access(filePath);
|
|
44
|
+
return { brand, file, content: await fs.readFile(filePath, 'utf8') };
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Search design systems by keyword (brand name or description).
|
|
52
|
+
*/
|
|
53
|
+
export async function searchDesignSystems(query) {
|
|
54
|
+
const brands = await discoverDesignBrands();
|
|
55
|
+
const q = query.toLowerCase();
|
|
56
|
+
const matched = brands.filter(b => b.includes(q));
|
|
57
|
+
return matched.slice(0, 10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Resource Manifest ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
const MANIFEST_PATH = path.join(LOCAL_ROOT, 'manifest.json');
|
|
63
|
+
|
|
64
|
+
export async function loadResourceManifest() {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await fs.readFile(MANIFEST_PATH, 'utf8');
|
|
67
|
+
return JSON.parse(raw);
|
|
68
|
+
} catch {
|
|
69
|
+
return { localResources: [] };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Context Builder ─────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build a concise context summary of all available local resources.
|
|
77
|
+
* Used for automatic injection into system prompts.
|
|
78
|
+
*/
|
|
79
|
+
export async function buildResourceContext() {
|
|
80
|
+
const manifest = await loadResourceManifest();
|
|
81
|
+
const resources = manifest.localResources || [];
|
|
82
|
+
const designBrands = await discoverDesignBrands();
|
|
83
|
+
|
|
84
|
+
const parts = [];
|
|
85
|
+
|
|
86
|
+
if (resources.length > 0) {
|
|
87
|
+
parts.push('## Local Resources');
|
|
88
|
+
resources.forEach(r => {
|
|
89
|
+
parts.push(` - ${r.name}: ${r.fileCount} files, ${(r.size / 1024).toFixed(0)}KB`);
|
|
90
|
+
});
|
|
91
|
+
parts.push('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (designBrands.length > 0) {
|
|
95
|
+
const brandsStr = designBrands.slice(0, 40).join(', ');
|
|
96
|
+
const leftover = designBrands.length - 40;
|
|
97
|
+
parts.push(`## Design Systems (${designBrands.length} available)`);
|
|
98
|
+
parts.push(` ${brandsStr}${leftover > 0 ? `, +${leftover} more` : ''}`);
|
|
99
|
+
parts.push('');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return parts.join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a relevant DESIGN.md content based on the task description.
|
|
107
|
+
* Uses keyword matching between the task text and design system brand names.
|
|
108
|
+
*/
|
|
109
|
+
export async function getRelevantDesignGuide(taskText) {
|
|
110
|
+
if (!taskText) return null;
|
|
111
|
+
|
|
112
|
+
const brands = await discoverDesignBrands();
|
|
113
|
+
const text = taskText.toLowerCase();
|
|
114
|
+
|
|
115
|
+
// Match by brand name in task text
|
|
116
|
+
for (const brand of brands) {
|
|
117
|
+
if (text.includes(brand)) {
|
|
118
|
+
const design = await loadDesignMd(brand);
|
|
119
|
+
if (design) return design;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Match by context clues (e.g., "design", "ui", "looks like", "brand")
|
|
124
|
+
const designHint = /\b(design|ui|looks? like|brand guide|style guide|make it look)\b/i.test(text);
|
|
125
|
+
if (designHint && brands.length > 0) {
|
|
126
|
+
// Return the first few brands as options
|
|
127
|
+
return {
|
|
128
|
+
brand: null,
|
|
129
|
+
type: 'design_hint',
|
|
130
|
+
brands: brands.slice(0, 5),
|
|
131
|
+
note: 'Design-related task detected. Available design systems listed above.',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
package/src/context/router.js
CHANGED
|
@@ -11,26 +11,83 @@ function flattenMessageText(messages) {
|
|
|
11
11
|
: String(messages || '').toLowerCase();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const providerNames = Object.keys(providers).filter(name => providers[name]?.ready || providers[name]?.model);
|
|
17
|
-
const hasProvider = name => providerNames.includes(name);
|
|
14
|
+
import { ReasoningConfig, REASONING_LEVELS } from '../ai/reasoning.js';
|
|
15
|
+
import { classifyModelTier, isSmallModel, getReasoningBump, MODEL_TIERS } from '../ai/model-capabilities.js';
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Bump reasoning level by N steps.
|
|
19
|
+
*/
|
|
20
|
+
function bumpReasoningLevel(level, steps) {
|
|
21
|
+
const order = [REASONING_LEVELS.NONE, REASONING_LEVELS.LOW, REASONING_LEVELS.MEDIUM, REASONING_LEVELS.HIGH, REASONING_LEVELS.MAX];
|
|
22
|
+
const idx = order.indexOf(level);
|
|
23
|
+
if (idx === -1) return level;
|
|
24
|
+
const newIdx = Math.min(idx + steps, order.length - 1);
|
|
25
|
+
return order[newIdx];
|
|
26
|
+
} export function selectExecutionProfile({ messages = [], activeProvider = null, providers = {}, options = {} } = {}) {
|
|
27
|
+
const text = flattenMessageText(messages);
|
|
28
|
+
const providerNames = Object.keys(providers).filter(name => providers[name]?.ready || providers[name]?.model);
|
|
29
|
+
const hasProvider = name => providerNames.includes(name);
|
|
30
|
+
|
|
31
|
+
const explicitProvider = options.provider && hasProvider(options.provider) ? options.provider : null;
|
|
32
|
+
let provider = explicitProvider || (activeProvider && hasProvider(activeProvider) ? activeProvider : providerNames[0] || null);
|
|
33
|
+
|
|
34
|
+
if (explicitProvider) {
|
|
35
|
+
provider = explicitProvider;
|
|
36
|
+
} else if (/\b(review|refactor|debug|fix|bug|error|stack trace|test|tool|patch|code)\b/.test(text) && hasProvider('claude')) {
|
|
37
|
+
provider = 'claude';
|
|
38
|
+
} else if (/\b(summary|summarize|commit message|changelog|docs|explain|rewrite)\b/.test(text) && hasProvider('openai')) {
|
|
39
|
+
provider = 'openai';
|
|
40
|
+
} else if (/\b(local|offline|privacy|private|on-device)\b/.test(text) && hasProvider('ollama')) {
|
|
41
|
+
provider = 'ollama';
|
|
42
|
+
} else if (/\b(quick|brief|short|fast)\b/.test(text) && hasProvider('groq')) {
|
|
43
|
+
provider = 'groq';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const providerConfig = providers[provider] || providers[activeProvider] || {};
|
|
47
|
+
const model = options.model || providerConfig.model || providers[activeProvider]?.model || null;
|
|
48
|
+
|
|
49
|
+
// Detect model capability tier
|
|
50
|
+
const modelTier = classifyModelTier(model, provider);
|
|
51
|
+
const isSmall = isSmallModel(modelTier);
|
|
52
|
+
const reasoningBump = getReasoningBump(modelTier);
|
|
53
|
+
|
|
54
|
+
// Determine reasoning level based on task complexity signals
|
|
55
|
+
// Default: HIGH for coding — all models must think deeply
|
|
56
|
+
let reasoningLevel = options.reasoningLevel || REASONING_LEVELS.HIGH;
|
|
57
|
+
if (!options.reasoningLevel) {
|
|
58
|
+
const hasDeepSignals = /\b(refactor|architecture|redesign|migrate|complex|full stack|e2e|end to end|security|optimize|performance|implement|build|create)\b/.test(text);
|
|
59
|
+
const hasComplexSignals = /\b(debug|fix|test|multiple|integrate|design|plan|review|analyze)\b/.test(text);
|
|
60
|
+
|
|
61
|
+
if (hasDeepSignals && text.length > 30) {
|
|
62
|
+
reasoningLevel = REASONING_LEVELS.MAX;
|
|
63
|
+
} else if (hasComplexSignals && text.length > 20) {
|
|
64
|
+
reasoningLevel = REASONING_LEVELS.MAX;
|
|
65
|
+
} else if (text.split(/\s+/).length > 10) {
|
|
66
|
+
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
67
|
+
} else if (text.split(/\s+/).length < 3) {
|
|
68
|
+
reasoningLevel = REASONING_LEVELS.MEDIUM;
|
|
69
|
+
} else {
|
|
70
|
+
reasoningLevel = REASONING_LEVELS.HIGH;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If small model, bump reasoning level even more to compensate
|
|
74
|
+
if (isSmall && reasoningBump > 0) {
|
|
75
|
+
reasoningLevel = bumpReasoningLevel(reasoningLevel, reasoningBump);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
33
78
|
|
|
34
|
-
const
|
|
35
|
-
|
|
79
|
+
const reasoning = new ReasoningConfig({
|
|
80
|
+
level: reasoningLevel,
|
|
81
|
+
provider: provider || activeProvider,
|
|
82
|
+
modelTier,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
provider,
|
|
87
|
+
model,
|
|
88
|
+
modelTier,
|
|
89
|
+
reasoningLevel,
|
|
90
|
+
reasoningParam: reasoning.getApiReasoningParam(),
|
|
91
|
+
reasoningPrompt: reasoning.getPromptInstructions(),
|
|
92
|
+
};
|
|
36
93
|
}
|
package/src/tools/executor.js
CHANGED
|
@@ -470,26 +470,26 @@ export class ToolExecutor {
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
async executeInternal(toolName, input, context = {}) {
|
|
473
|
-
input = input && typeof input === 'object' ? input : {};
|
|
474
473
|
toolName = this.normalizeToolName(toolName);
|
|
474
|
+
input = this.normalizeToolInput(toolName, input);
|
|
475
475
|
const cwd = context.cwd || this.projectPath;
|
|
476
476
|
const resolvedPath = (p) => this.resolveInputPath(p, cwd);
|
|
477
477
|
|
|
478
478
|
switch (toolName) {
|
|
479
479
|
case 'Read':
|
|
480
|
-
return await this.readFile(this.resolveInputPath(input.file_path ?? input.path ?? input.file, cwd));
|
|
480
|
+
return await this.readFile(this.resolveInputPath(input.file_path ?? input.filePath ?? input.filepath ?? input.path ?? input.file ?? input.filename ?? input.target_file ?? input.targetFile, cwd));
|
|
481
481
|
case 'Write':
|
|
482
|
-
return await this.writeFile(this.resolveInputPath(input.file_path ?? input.path ?? input.file, cwd), input.content);
|
|
482
|
+
return await this.writeFile(this.resolveInputPath(input.file_path ?? input.filePath ?? input.filepath ?? input.path ?? input.file ?? input.filename ?? input.target_file ?? input.targetFile, cwd), input.content ?? input.text ?? input.data ?? input.value ?? input.body);
|
|
483
483
|
case 'Edit':
|
|
484
484
|
return await this.executeEdit(input, cwd);
|
|
485
485
|
case 'Bash':
|
|
486
|
-
return await this.bash(input.command ?? input.cmd, input.cwd || cwd, input.timeout, input.shell);
|
|
486
|
+
return await this.bash(input.command ?? input.cmd ?? input.script ?? input.code ?? input.input, input.cwd || input.path || cwd, input.timeout, input.shell);
|
|
487
487
|
case 'Glob':
|
|
488
488
|
return await this.glob(input.pattern ?? input.glob ?? '**/*', input.cwd || input.path || cwd);
|
|
489
489
|
case 'Grep':
|
|
490
490
|
return await this.grep(
|
|
491
491
|
input.pattern ?? input.query ?? input.q,
|
|
492
|
-
input.path || cwd,
|
|
492
|
+
input.path || input.cwd || cwd,
|
|
493
493
|
input.glob,
|
|
494
494
|
input.output_mode,
|
|
495
495
|
{
|
|
@@ -519,9 +519,9 @@ export class ToolExecutor {
|
|
|
519
519
|
case 'BrowserDebug':
|
|
520
520
|
return await this.browserDebug(input.url ?? input.uri, input.action);
|
|
521
521
|
case 'WebFetch':
|
|
522
|
-
return await this.webFetch(input.url ?? input.uri, input.prompt);
|
|
522
|
+
return await this.webFetch(input.url ?? input.uri ?? input.href, input.prompt ?? input.query ?? input.extract);
|
|
523
523
|
case 'WebSearch':
|
|
524
|
-
return await this.webSearch(input.query ?? input.q);
|
|
524
|
+
return await this.webSearch(input.query ?? input.q ?? input.search ?? input.search_query ?? input.searchQuery);
|
|
525
525
|
case 'NotebookRead':
|
|
526
526
|
return await this.notebookTool.read(this.resolveInputPath(input.notebook_path ?? input.path ?? input.file, cwd));
|
|
527
527
|
case 'NotebookEdit':
|
|
@@ -651,41 +651,67 @@ export class ToolExecutor {
|
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
normalizeToolName(toolName) {
|
|
654
|
-
const
|
|
654
|
+
const raw = String(toolName || '').trim();
|
|
655
|
+
const normalized = raw
|
|
656
|
+
.replace(/^functions[._-]/i, '')
|
|
657
|
+
.replace(/^tools?[._-]/i, '')
|
|
658
|
+
.replace(/^winter[._-]/i, '')
|
|
659
|
+
.replace(/^[\w-]+[.:/](?=[A-Za-z])/i, '')
|
|
660
|
+
.replace(/[-_\s]/g, '')
|
|
661
|
+
.toLowerCase();
|
|
655
662
|
const aliases = {
|
|
656
663
|
read: 'Read',
|
|
657
664
|
readfile: 'Read',
|
|
665
|
+
fileread: 'Read',
|
|
658
666
|
openfile: 'Read',
|
|
659
667
|
viewfile: 'Read',
|
|
668
|
+
view: 'Read',
|
|
660
669
|
cat: 'Read',
|
|
670
|
+
getfile: 'Read',
|
|
671
|
+
readfilecontent: 'Read',
|
|
661
672
|
write: 'Write',
|
|
662
673
|
writefile: 'Write',
|
|
674
|
+
filewrite: 'Write',
|
|
663
675
|
writetofile: 'Write',
|
|
664
676
|
createfile: 'Write',
|
|
665
677
|
savefile: 'Write',
|
|
678
|
+
create: 'Write',
|
|
679
|
+
overwritefile: 'Write',
|
|
666
680
|
edit: 'Edit',
|
|
667
681
|
editfile: 'Edit',
|
|
682
|
+
fileedit: 'Edit',
|
|
668
683
|
replaceinfile: 'Edit',
|
|
669
684
|
strreplace: 'Edit',
|
|
670
685
|
strreplaceeditor: 'Edit',
|
|
686
|
+
strreplaceedit: 'Edit',
|
|
671
687
|
applydiff: 'Edit',
|
|
688
|
+
applypatch: 'Edit',
|
|
672
689
|
patch: 'Edit',
|
|
673
690
|
bash: 'Bash',
|
|
674
691
|
shell: 'Bash',
|
|
675
692
|
command: 'Bash',
|
|
676
693
|
commandexecutor: 'Bash',
|
|
677
694
|
executecommand: 'Bash',
|
|
695
|
+
runterminalcmd: 'Bash',
|
|
696
|
+
runterminalcommand: 'Bash',
|
|
678
697
|
runcommand: 'Bash',
|
|
698
|
+
runcmd: 'Bash',
|
|
699
|
+
exec: 'Bash',
|
|
679
700
|
terminal: 'Bash',
|
|
680
701
|
powershell: 'Bash',
|
|
702
|
+
cmd: 'Bash',
|
|
681
703
|
glob: 'Glob',
|
|
682
704
|
listfiles: 'Glob',
|
|
705
|
+
list: 'Glob',
|
|
683
706
|
ls: 'Glob',
|
|
684
707
|
findfiles: 'Glob',
|
|
708
|
+
find: 'Glob',
|
|
685
709
|
grep: 'Grep',
|
|
686
710
|
search: 'Grep',
|
|
687
711
|
searchfiles: 'Grep',
|
|
712
|
+
grepsearch: 'Grep',
|
|
688
713
|
searchtext: 'Grep',
|
|
714
|
+
searchcode: 'Grep',
|
|
689
715
|
rg: 'Grep',
|
|
690
716
|
rgfull: 'Grep',
|
|
691
717
|
searchadvanced: 'Grep',
|
|
@@ -707,12 +733,17 @@ export class ToolExecutor {
|
|
|
707
733
|
webfetch: 'WebFetch',
|
|
708
734
|
fetch: 'WebFetch',
|
|
709
735
|
fetchurl: 'WebFetch',
|
|
736
|
+
geturl: 'WebFetch',
|
|
710
737
|
websearch: 'WebSearch',
|
|
711
738
|
searchweb: 'WebSearch',
|
|
739
|
+
internetsearch: 'WebSearch',
|
|
740
|
+
googlesearch: 'WebSearch',
|
|
712
741
|
browserdebug: 'BrowserDebug',
|
|
713
742
|
browser: 'BrowserDebug',
|
|
743
|
+
browserinspect: 'BrowserDebug',
|
|
714
744
|
parallel: 'Parallel',
|
|
715
745
|
parallelexecute: 'Parallel',
|
|
746
|
+
paralleltools: 'Parallel',
|
|
716
747
|
batch: 'Parallel',
|
|
717
748
|
// New tools
|
|
718
749
|
webarchive: 'WebArchive',
|
|
@@ -746,7 +777,45 @@ export class ToolExecutor {
|
|
|
746
777
|
subagent: 'Agent',
|
|
747
778
|
agentrun: 'Agent',
|
|
748
779
|
};
|
|
749
|
-
return aliases[normalized] ||
|
|
780
|
+
return aliases[normalized] || raw;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
normalizeToolInput(toolName, input) {
|
|
784
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
785
|
+
return this.unwrapToolInput(input);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (typeof input !== 'string') return {};
|
|
789
|
+
const value = input.trim();
|
|
790
|
+
if (!value) return {};
|
|
791
|
+
|
|
792
|
+
switch (toolName) {
|
|
793
|
+
case 'Read':
|
|
794
|
+
case 'NotebookRead':
|
|
795
|
+
return { file_path: value };
|
|
796
|
+
case 'Write':
|
|
797
|
+
return { content: value };
|
|
798
|
+
case 'Bash':
|
|
799
|
+
return { command: value };
|
|
800
|
+
case 'Glob':
|
|
801
|
+
return { pattern: value };
|
|
802
|
+
case 'Grep':
|
|
803
|
+
case 'WebSearch':
|
|
804
|
+
return { query: value };
|
|
805
|
+
case 'WebFetch':
|
|
806
|
+
case 'WebArchive':
|
|
807
|
+
case 'BrowserDebug':
|
|
808
|
+
return { url: value };
|
|
809
|
+
case 'TaskCreate':
|
|
810
|
+
case 'TodoWrite':
|
|
811
|
+
return { title: value };
|
|
812
|
+
case 'ScheduleWakeup':
|
|
813
|
+
return { prompt: value };
|
|
814
|
+
case 'Agent':
|
|
815
|
+
return { task: value };
|
|
816
|
+
default:
|
|
817
|
+
return { input: value };
|
|
818
|
+
}
|
|
750
819
|
}
|
|
751
820
|
|
|
752
821
|
resolveInputPath(filePath, cwd) {
|