@koi-language/koi 1.0.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/QUICKSTART.md +89 -0
- package/README.md +545 -0
- package/examples/actions-demo.koi +177 -0
- package/examples/cache-test.koi +29 -0
- package/examples/calculator.koi +61 -0
- package/examples/clear-registry.js +33 -0
- package/examples/clear-registry.koi +30 -0
- package/examples/code-introspection-test.koi +149 -0
- package/examples/counter.koi +132 -0
- package/examples/delegation-test.koi +52 -0
- package/examples/directory-import-test.koi +84 -0
- package/examples/hello-world-claude.koi +52 -0
- package/examples/hello-world.koi +52 -0
- package/examples/hello.koi +24 -0
- package/examples/mcp-example.koi +70 -0
- package/examples/multi-event-handler-test.koi +144 -0
- package/examples/new-import-test.koi +89 -0
- package/examples/pipeline.koi +162 -0
- package/examples/registry-demo.koi +184 -0
- package/examples/registry-playbook-demo.koi +162 -0
- package/examples/registry-playbook-email-compositor-2.koi +140 -0
- package/examples/registry-playbook-email-compositor.koi +140 -0
- package/examples/sentiment.koi +90 -0
- package/examples/simple.koi +48 -0
- package/examples/skill-import-test.koi +76 -0
- package/examples/skills/advanced/index.koi +95 -0
- package/examples/skills/math-operations.koi +69 -0
- package/examples/skills/string-operations.koi +56 -0
- package/examples/task-chaining-demo.koi +244 -0
- package/examples/test-await.koi +22 -0
- package/examples/test-crypto-sha256.koi +196 -0
- package/examples/test-delegation.koi +41 -0
- package/examples/test-multi-team-routing.koi +258 -0
- package/examples/test-no-handler.koi +35 -0
- package/examples/test-npm-import.koi +67 -0
- package/examples/test-parse.koi +10 -0
- package/examples/test-peers-with-team.koi +59 -0
- package/examples/test-permissions-fail.koi +20 -0
- package/examples/test-permissions.koi +36 -0
- package/examples/test-simple-registry.koi +31 -0
- package/examples/test-typescript-import.koi +64 -0
- package/examples/test-uses-team-syntax.koi +25 -0
- package/examples/test-uses-team.koi +31 -0
- package/examples/utils/calculator.test.ts +144 -0
- package/examples/utils/calculator.ts +56 -0
- package/examples/utils/math-helpers.js +50 -0
- package/examples/utils/math-helpers.ts +55 -0
- package/examples/web-delegation-demo.koi +165 -0
- package/package.json +78 -0
- package/src/cli/koi.js +793 -0
- package/src/compiler/build-optimizer.js +447 -0
- package/src/compiler/cache-manager.js +274 -0
- package/src/compiler/import-resolver.js +369 -0
- package/src/compiler/parser.js +7542 -0
- package/src/compiler/transpiler.js +1105 -0
- package/src/compiler/typescript-transpiler.js +148 -0
- package/src/grammar/koi.pegjs +767 -0
- package/src/runtime/action-registry.js +172 -0
- package/src/runtime/actions/call-skill.js +45 -0
- package/src/runtime/actions/format.js +115 -0
- package/src/runtime/actions/print.js +42 -0
- package/src/runtime/actions/registry-delete.js +37 -0
- package/src/runtime/actions/registry-get.js +37 -0
- package/src/runtime/actions/registry-keys.js +33 -0
- package/src/runtime/actions/registry-search.js +34 -0
- package/src/runtime/actions/registry-set.js +50 -0
- package/src/runtime/actions/return.js +31 -0
- package/src/runtime/actions/send-message.js +58 -0
- package/src/runtime/actions/update-state.js +36 -0
- package/src/runtime/agent.js +1368 -0
- package/src/runtime/cli-logger.js +205 -0
- package/src/runtime/incremental-json-parser.js +201 -0
- package/src/runtime/index.js +33 -0
- package/src/runtime/llm-provider.js +1372 -0
- package/src/runtime/mcp-client.js +1171 -0
- package/src/runtime/planner.js +273 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
- package/src/runtime/registry-backends/local.js +260 -0
- package/src/runtime/registry.js +162 -0
- package/src/runtime/role.js +14 -0
- package/src/runtime/router.js +395 -0
- package/src/runtime/runtime.js +113 -0
- package/src/runtime/skill-selector.js +173 -0
- package/src/runtime/skill.js +25 -0
- package/src/runtime/team.js +162 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Logger - Single-line progress updates like Claude Code CLI
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* cliLogger.progress('Processing...') - Updates same line
|
|
6
|
+
* cliLogger.success('Done!') - New line with result
|
|
7
|
+
* cliLogger.error('Failed!') - New line with error
|
|
8
|
+
* cliLogger.clear() - Clear current line
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class CLILogger {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.currentLine = '';
|
|
14
|
+
this.isProgress = false;
|
|
15
|
+
this.animationInterval = null;
|
|
16
|
+
this.animationDots = 0;
|
|
17
|
+
this.isAnimating = false; // Track if we're in animation mode
|
|
18
|
+
this.indentStack = []; // Stack of messages showing delegation hierarchy
|
|
19
|
+
this.indentLevel = 0;
|
|
20
|
+
|
|
21
|
+
// Intercept console methods to auto-clear progress
|
|
22
|
+
this.setupConsoleIntercept();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setupConsoleIntercept() {
|
|
26
|
+
const originalLog = console.log;
|
|
27
|
+
const originalError = console.error;
|
|
28
|
+
const originalWarn = console.warn;
|
|
29
|
+
const originalInfo = console.info;
|
|
30
|
+
const self = this;
|
|
31
|
+
|
|
32
|
+
console.log = function(...args) {
|
|
33
|
+
self.clearProgress();
|
|
34
|
+
originalLog.apply(console, args);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
console.error = function(...args) {
|
|
38
|
+
self.clearProgress();
|
|
39
|
+
originalError.apply(console, args);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
console.warn = function(...args) {
|
|
43
|
+
self.clearProgress();
|
|
44
|
+
originalWarn.apply(console, args);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
console.info = function(...args) {
|
|
48
|
+
self.clearProgress();
|
|
49
|
+
originalInfo.apply(console, args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Push a message to the delegation stack (indented)
|
|
55
|
+
*/
|
|
56
|
+
pushIndent(message) {
|
|
57
|
+
this.indentLevel++;
|
|
58
|
+
const indent = ' '.repeat(this.indentLevel);
|
|
59
|
+
this.indentStack.push({ message, indent, level: this.indentLevel });
|
|
60
|
+
this.progress(`${indent}→ ${message}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pop the last delegation from stack and restore parent context
|
|
65
|
+
*/
|
|
66
|
+
popIndent() {
|
|
67
|
+
if (this.indentStack.length > 0) {
|
|
68
|
+
this.indentStack.pop();
|
|
69
|
+
this.indentLevel = Math.max(0, this.indentLevel - 1);
|
|
70
|
+
|
|
71
|
+
// Clear and restore parent context
|
|
72
|
+
this.clear();
|
|
73
|
+
|
|
74
|
+
// Re-render parent if exists
|
|
75
|
+
if (this.indentStack.length > 0) {
|
|
76
|
+
const parent = this.indentStack[this.indentStack.length - 1];
|
|
77
|
+
this.progress(`${parent.indent}→ ${parent.message}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
this.indentLevel = 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Clear all indentation stack
|
|
86
|
+
*/
|
|
87
|
+
clearStack() {
|
|
88
|
+
this.indentStack = [];
|
|
89
|
+
this.indentLevel = 0;
|
|
90
|
+
this.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get current indent string
|
|
95
|
+
*/
|
|
96
|
+
getIndent() {
|
|
97
|
+
return ' '.repeat(this.indentLevel);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Show planning state with animated spinning stick
|
|
102
|
+
*/
|
|
103
|
+
planning(prefix) {
|
|
104
|
+
// Stop any existing animation
|
|
105
|
+
this.stopAnimation();
|
|
106
|
+
|
|
107
|
+
const baseMessage = prefix || 'Thinking';
|
|
108
|
+
this.animationDots = 0;
|
|
109
|
+
this.isAnimating = true;
|
|
110
|
+
const spinnerChars = ['|', '/', '-', '\\'];
|
|
111
|
+
|
|
112
|
+
// Initial render
|
|
113
|
+
this.progress(`${baseMessage} ${spinnerChars[0]}`);
|
|
114
|
+
|
|
115
|
+
// Start animation
|
|
116
|
+
this.animationInterval = setInterval(() => {
|
|
117
|
+
this.animationDots = (this.animationDots + 1) % spinnerChars.length;
|
|
118
|
+
const spinner = spinnerChars[this.animationDots];
|
|
119
|
+
this.progress(`${baseMessage} ${spinner}`);
|
|
120
|
+
}, 150);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Stop animation if running
|
|
125
|
+
*/
|
|
126
|
+
stopAnimation() {
|
|
127
|
+
if (this.animationInterval) {
|
|
128
|
+
clearInterval(this.animationInterval);
|
|
129
|
+
this.animationInterval = null;
|
|
130
|
+
this.animationDots = 0;
|
|
131
|
+
this.isAnimating = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Update the same line with progress (no newline)
|
|
137
|
+
*/
|
|
138
|
+
progress(message) {
|
|
139
|
+
// Stop animation if it's running and we're not in animation mode
|
|
140
|
+
if (this.animationInterval && !this.isAnimating && !message.includes('...')) {
|
|
141
|
+
this.stopAnimation();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Clear previous line
|
|
145
|
+
if (this.isProgress) {
|
|
146
|
+
process.stdout.write('\r\x1b[K');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Write new message
|
|
150
|
+
process.stdout.write(message);
|
|
151
|
+
this.currentLine = message;
|
|
152
|
+
this.isProgress = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Complete progress and print result on new line
|
|
157
|
+
*/
|
|
158
|
+
success(message) {
|
|
159
|
+
this.clearProgress();
|
|
160
|
+
console.log(message);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Print error on new line
|
|
165
|
+
*/
|
|
166
|
+
error(message) {
|
|
167
|
+
this.clearProgress();
|
|
168
|
+
console.error(message);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Print info on new line
|
|
173
|
+
*/
|
|
174
|
+
info(message) {
|
|
175
|
+
this.clearProgress();
|
|
176
|
+
console.log(message);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clear current progress line
|
|
181
|
+
*/
|
|
182
|
+
clearProgress() {
|
|
183
|
+
this.stopAnimation();
|
|
184
|
+
if (this.isProgress) {
|
|
185
|
+
process.stdout.write('\r\x1b[K');
|
|
186
|
+
this.isProgress = false;
|
|
187
|
+
this.currentLine = '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Just clear, no new line
|
|
193
|
+
*/
|
|
194
|
+
clear() {
|
|
195
|
+
this.stopAnimation();
|
|
196
|
+
if (this.isProgress) {
|
|
197
|
+
process.stdout.write('\r\x1b[K');
|
|
198
|
+
this.isProgress = false;
|
|
199
|
+
this.currentLine = '';
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Singleton instance
|
|
205
|
+
export const cliLogger = new CLILogger();
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental JSON Parser for Streaming LLM Responses
|
|
3
|
+
*
|
|
4
|
+
* Parses JSON stream incrementally and yields complete action objects
|
|
5
|
+
* as soon as they're fully received, without waiting for the entire response.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class IncrementalJSONParser {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.buffer = '';
|
|
11
|
+
this.actionsStartIndex = -1; // Position where "actions":[ was found
|
|
12
|
+
this.lastParsedIndex = 0; // Last position we successfully parsed up to
|
|
13
|
+
this.parsedActions = 0; // Count of actions we've parsed
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Feed more content from the stream
|
|
18
|
+
* Returns array of complete actions found in this chunk
|
|
19
|
+
*/
|
|
20
|
+
feed(chunk) {
|
|
21
|
+
const actions = [];
|
|
22
|
+
const oldBufferLength = this.buffer.length;
|
|
23
|
+
this.buffer += chunk;
|
|
24
|
+
|
|
25
|
+
// First, find where the actions array starts (if we haven't found it yet)
|
|
26
|
+
if (this.actionsStartIndex === -1) {
|
|
27
|
+
// Look for "actions" : [ with flexible whitespace
|
|
28
|
+
const match = this.buffer.match(/"actions"\s*:\s*\[/);
|
|
29
|
+
if (match) {
|
|
30
|
+
const actionsIndex = match.index;
|
|
31
|
+
this.actionsStartIndex = actionsIndex + match[0].length; // Position right after "actions": [
|
|
32
|
+
this.lastParsedIndex = this.actionsStartIndex;
|
|
33
|
+
|
|
34
|
+
if (process.env.KOI_DEBUG_LLM) {
|
|
35
|
+
console.error(`[IncrementalParser] 📍 Found "actions" array at position ${actionsIndex}, starting to parse from ${this.actionsStartIndex}`);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// Haven't found actions array yet, wait for more data
|
|
39
|
+
if (process.env.KOI_DEBUG_LLM && this.buffer.length > 50) {
|
|
40
|
+
// Show first 100 chars of buffer to debug what LLM is generating
|
|
41
|
+
const preview = this.buffer.substring(0, 100).replace(/\n/g, '\\n');
|
|
42
|
+
console.error(`[IncrementalParser] ⏳ Waiting for "actions" array (buffer: ${this.buffer.length} chars, preview: "${preview}...")`);
|
|
43
|
+
}
|
|
44
|
+
return actions;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try to parse actions from the buffer
|
|
49
|
+
// We'll try to extract complete JSON objects from the actions array
|
|
50
|
+
let currentPos = this.lastParsedIndex;
|
|
51
|
+
let depth = 0;
|
|
52
|
+
let inString = false;
|
|
53
|
+
let escapeNext = false;
|
|
54
|
+
let objectStart = -1;
|
|
55
|
+
|
|
56
|
+
for (let i = currentPos; i < this.buffer.length; i++) {
|
|
57
|
+
const char = this.buffer[i];
|
|
58
|
+
|
|
59
|
+
// Handle string escaping
|
|
60
|
+
if (escapeNext) {
|
|
61
|
+
escapeNext = false;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (char === '\\' && inString) {
|
|
66
|
+
escapeNext = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Track if we're inside a string
|
|
71
|
+
if (char === '"') {
|
|
72
|
+
inString = !inString;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Skip processing inside strings
|
|
77
|
+
if (inString) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Track object depth
|
|
82
|
+
if (char === '{') {
|
|
83
|
+
if (depth === 0) {
|
|
84
|
+
objectStart = i; // Start of new action object
|
|
85
|
+
}
|
|
86
|
+
depth++;
|
|
87
|
+
} else if (char === '}') {
|
|
88
|
+
depth--;
|
|
89
|
+
if (depth === 0 && objectStart !== -1) {
|
|
90
|
+
// Complete action object found!
|
|
91
|
+
const actionJSON = this.buffer.substring(objectStart, i + 1);
|
|
92
|
+
try {
|
|
93
|
+
const action = JSON.parse(actionJSON);
|
|
94
|
+
actions.push(action);
|
|
95
|
+
this.parsedActions++;
|
|
96
|
+
this.lastParsedIndex = i + 1;
|
|
97
|
+
|
|
98
|
+
if (process.env.KOI_DEBUG_LLM) {
|
|
99
|
+
console.error(`[IncrementalParser] ✅ Parsed action #${this.parsedActions}: ${action.intent || action.type || 'unknown'}`);
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Failed to parse - might be incomplete, wait for more data
|
|
103
|
+
if (process.env.KOI_DEBUG_LLM) {
|
|
104
|
+
console.error(`[IncrementalParser] ⚠️ Failed to parse object at ${objectStart}: ${e.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
objectStart = -1;
|
|
108
|
+
}
|
|
109
|
+
} else if (char === ']' && depth === 0) {
|
|
110
|
+
// End of actions array
|
|
111
|
+
if (process.env.KOI_DEBUG_LLM) {
|
|
112
|
+
console.error(`[IncrementalParser] 🏁 End of actions array reached`);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return actions;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Signal end of stream - parse any remaining content
|
|
123
|
+
*/
|
|
124
|
+
finalize() {
|
|
125
|
+
const actions = [];
|
|
126
|
+
|
|
127
|
+
// If we have unparsed content, try one final parse
|
|
128
|
+
if (this.lastParsedIndex < this.buffer.length) {
|
|
129
|
+
if (process.env.KOI_DEBUG_LLM) {
|
|
130
|
+
console.error(`[IncrementalParser] Finalizing - ${this.buffer.length - this.lastParsedIndex} chars remaining`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Try to find any remaining complete objects
|
|
134
|
+
const remaining = this.buffer.substring(this.lastParsedIndex);
|
|
135
|
+
const objects = this.extractObjects(remaining);
|
|
136
|
+
actions.push(...objects);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return actions;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Extract complete JSON objects from a string
|
|
144
|
+
*/
|
|
145
|
+
extractObjects(str) {
|
|
146
|
+
const objects = [];
|
|
147
|
+
let depth = 0;
|
|
148
|
+
let inString = false;
|
|
149
|
+
let escapeNext = false;
|
|
150
|
+
let objectStart = -1;
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < str.length; i++) {
|
|
153
|
+
const char = str[i];
|
|
154
|
+
|
|
155
|
+
if (escapeNext) {
|
|
156
|
+
escapeNext = false;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (char === '\\' && inString) {
|
|
161
|
+
escapeNext = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (char === '"') {
|
|
166
|
+
inString = !inString;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (inString) continue;
|
|
171
|
+
|
|
172
|
+
if (char === '{') {
|
|
173
|
+
if (depth === 0) objectStart = i;
|
|
174
|
+
depth++;
|
|
175
|
+
} else if (char === '}') {
|
|
176
|
+
depth--;
|
|
177
|
+
if (depth === 0 && objectStart !== -1) {
|
|
178
|
+
try {
|
|
179
|
+
const obj = JSON.parse(str.substring(objectStart, i + 1));
|
|
180
|
+
objects.push(obj);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Ignore parse errors in finalize
|
|
183
|
+
}
|
|
184
|
+
objectStart = -1;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return objects;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Reset parser state for new stream
|
|
194
|
+
*/
|
|
195
|
+
reset() {
|
|
196
|
+
this.buffer = '';
|
|
197
|
+
this.actionsStartIndex = -1;
|
|
198
|
+
this.lastParsedIndex = 0;
|
|
199
|
+
this.parsedActions = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { Agent } from './agent.js';
|
|
2
|
+
export { Team } from './team.js';
|
|
3
|
+
export { Skill } from './skill.js';
|
|
4
|
+
export { Role } from './role.js';
|
|
5
|
+
export { Runtime } from './runtime.js';
|
|
6
|
+
export { MCPClient, mcpClient } from './mcp-client.js';
|
|
7
|
+
export { Planner, PlanningAgent } from './planner.js';
|
|
8
|
+
export { SkillSelector, skillSelector } from './skill-selector.js';
|
|
9
|
+
export { registry, getRegistry } from './registry.js';
|
|
10
|
+
|
|
11
|
+
// Global registry for skill functions (for tool calling)
|
|
12
|
+
export const SkillRegistry = {
|
|
13
|
+
_functions: {},
|
|
14
|
+
|
|
15
|
+
register(skillName, functionName, fn, metadata = {}) {
|
|
16
|
+
if (!this._functions[skillName]) {
|
|
17
|
+
this._functions[skillName] = {};
|
|
18
|
+
}
|
|
19
|
+
this._functions[skillName][functionName] = { fn, metadata };
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
get(skillName, functionName) {
|
|
23
|
+
return this._functions[skillName]?.[functionName];
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
getAll(skillName) {
|
|
27
|
+
return this._functions[skillName] || {};
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
getAllSkills() {
|
|
31
|
+
return this._functions;
|
|
32
|
+
}
|
|
33
|
+
};
|