aibos-design-system 1.0.0 → 1.0.1
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 +21 -0
- package/README.md +335 -333
- package/dist/headless-map.json +44 -41
- package/dist/tokens/index.d.ts +66 -66
- package/dist/tokens.json +34 -34
- package/{API_REFERENCE.md → docs/API_REFERENCE.md} +379 -379
- package/{EXTERNAL_USAGE.md → docs/EXTERNAL_USAGE.md} +372 -370
- package/docs/INTEGRATION_GUIDE.md +433 -0
- package/docs/QUICK_REFERENCE.md +303 -0
- package/input.css +4056 -4050
- package/lib/cli-autocomplete.ts +231 -0
- package/lib/cli-commands.ts +364 -0
- package/lib/cli-filter-engine.ts +271 -0
- package/lib/cli-parser.ts +197 -0
- package/lib/utils.ts +18 -0
- package/package.json +11 -4
- package/style.css +683 -237
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Autocomplete Engine
|
|
3
|
+
*
|
|
4
|
+
* Context-aware suggestion engine that understands whether the user is:
|
|
5
|
+
* - Typing a Command Key (e.g., "sta" -> "status:")
|
|
6
|
+
* - Choosing a Value (e.g., "status:ac" -> "active ")
|
|
7
|
+
* - Entering an Operator (e.g., "score:>" or "score:<")
|
|
8
|
+
*
|
|
9
|
+
* See docs/CLI_FILTER_COMMANDS.md for the complete command specification.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { COMMAND_SCHEMA, type ValidCommand } from './cli-commands';
|
|
13
|
+
|
|
14
|
+
export interface Suggestion {
|
|
15
|
+
label: string;
|
|
16
|
+
type: 'key' | 'value' | 'operator';
|
|
17
|
+
insertText: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ContextInfo {
|
|
22
|
+
word: string;
|
|
23
|
+
isKey: boolean;
|
|
24
|
+
keyContext: ValidCommand | null;
|
|
25
|
+
colonIndex: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AutocompleteEngine
|
|
30
|
+
*
|
|
31
|
+
* Analyzes cursor position and input text to generate context-aware suggestions.
|
|
32
|
+
* The "Policeman" enforces valid commands and values.
|
|
33
|
+
*/
|
|
34
|
+
export class AutocompleteEngine {
|
|
35
|
+
/**
|
|
36
|
+
* Get suggestions based on current input and cursor position
|
|
37
|
+
*
|
|
38
|
+
* @param fullText - Complete input text
|
|
39
|
+
* @param cursorIndex - Current cursor position (0-based)
|
|
40
|
+
* @returns Array of suggestions to display
|
|
41
|
+
*/
|
|
42
|
+
getSuggestions(fullText: string, cursorIndex: number): Suggestion[] {
|
|
43
|
+
const context = this.parseContext(fullText, cursorIndex);
|
|
44
|
+
|
|
45
|
+
if (context.isKey) {
|
|
46
|
+
// MODE A: Suggesting Keys
|
|
47
|
+
return this.getSuggestionsForKeys(context.word);
|
|
48
|
+
} else if (context.keyContext) {
|
|
49
|
+
// MODE B: Suggesting Values or Operators
|
|
50
|
+
return this.getSuggestionsForValues(context.keyContext, context.word);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get suggestions for command keys
|
|
58
|
+
*
|
|
59
|
+
* When user types "sta", suggest "status:", "stage:"
|
|
60
|
+
*/
|
|
61
|
+
private getSuggestionsForKeys(prefix: string): Suggestion[] {
|
|
62
|
+
const validKeys = Object.keys(COMMAND_SCHEMA) as ValidCommand[];
|
|
63
|
+
const lower = prefix.toLowerCase();
|
|
64
|
+
|
|
65
|
+
return validKeys
|
|
66
|
+
.filter(key => key.toLowerCase().startsWith(lower))
|
|
67
|
+
.map((key): Suggestion => {
|
|
68
|
+
const schema = COMMAND_SCHEMA[key];
|
|
69
|
+
return {
|
|
70
|
+
label: key,
|
|
71
|
+
type: 'key' as const,
|
|
72
|
+
insertText: key + ':', // Auto-append colon
|
|
73
|
+
description: schema.description,
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get suggestions for values or operators
|
|
81
|
+
*
|
|
82
|
+
* When user types "status:ac", suggest "active "
|
|
83
|
+
* When user types "score:>", suggest "1", "100", etc.
|
|
84
|
+
*/
|
|
85
|
+
private getSuggestionsForValues(
|
|
86
|
+
key: ValidCommand,
|
|
87
|
+
prefix: string
|
|
88
|
+
): Suggestion[] {
|
|
89
|
+
const schema = COMMAND_SCHEMA[key];
|
|
90
|
+
if (!schema) return [];
|
|
91
|
+
|
|
92
|
+
// For ENUM types: Suggest matching values
|
|
93
|
+
if (schema.type === 'enum' && schema.values) {
|
|
94
|
+
const lower = prefix.toLowerCase();
|
|
95
|
+
return schema.values
|
|
96
|
+
.filter(val => val.toLowerCase().startsWith(lower))
|
|
97
|
+
.map(val => ({
|
|
98
|
+
label: val,
|
|
99
|
+
type: 'value',
|
|
100
|
+
insertText: val + ' ', // Auto-append space for next command
|
|
101
|
+
description: `${key}: ${val}`,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// For NUMERIC types: Suggest operators first
|
|
106
|
+
if (schema.type === 'numeric' && schema.supportsOperators) {
|
|
107
|
+
const operators = ['>', '<', '=', '!=', '>=', '<='];
|
|
108
|
+
const lower = prefix.toLowerCase();
|
|
109
|
+
|
|
110
|
+
// If prefix is empty or starts with operator, suggest operators
|
|
111
|
+
if (!prefix || operators.some(op => op.startsWith(lower))) {
|
|
112
|
+
return operators
|
|
113
|
+
.filter(op => op.startsWith(lower))
|
|
114
|
+
.map(op => ({
|
|
115
|
+
label: `Operator: ${op}`,
|
|
116
|
+
type: 'operator',
|
|
117
|
+
insertText: op,
|
|
118
|
+
description: `${key}: ${op} [value]`,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// If already has operator, suggest example values
|
|
123
|
+
return [
|
|
124
|
+
{ label: '0', type: 'value', insertText: '0 ', description: 'Number value' },
|
|
125
|
+
{ label: '10', type: 'value', insertText: '10 ', description: 'Number value' },
|
|
126
|
+
{ label: '100', type: 'value', insertText: '100 ', description: 'Number value' },
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// For DATE types: Similar to numeric
|
|
131
|
+
if (schema.type === 'date' && schema.supportsOperators) {
|
|
132
|
+
const operators = ['>', '<', '='];
|
|
133
|
+
const lower = prefix.toLowerCase();
|
|
134
|
+
|
|
135
|
+
if (!prefix || operators.some(op => op.startsWith(lower))) {
|
|
136
|
+
return operators
|
|
137
|
+
.filter(op => op.startsWith(lower))
|
|
138
|
+
.map(op => ({
|
|
139
|
+
label: `Operator: ${op}`,
|
|
140
|
+
type: 'operator',
|
|
141
|
+
insertText: op,
|
|
142
|
+
description: `${key}: ${op} YYYY-MM-DD`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Suggest date format
|
|
147
|
+
const today = new Date();
|
|
148
|
+
const isoToday = today.toISOString().split('T')[0];
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
label: isoToday,
|
|
152
|
+
type: 'value',
|
|
153
|
+
insertText: isoToday + ' ',
|
|
154
|
+
description: 'Today',
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// For STRING types: No enum, accept any value
|
|
160
|
+
// Return empty to avoid spam, user can type freely
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parse context from full text and cursor position
|
|
166
|
+
*
|
|
167
|
+
* Determines:
|
|
168
|
+
* - Are we typing a Key or Value?
|
|
169
|
+
* - What's the current word?
|
|
170
|
+
* - If Value, which Key is it for?
|
|
171
|
+
*/
|
|
172
|
+
parseContext(fullText: string, cursorIndex: number): ContextInfo {
|
|
173
|
+
// Get text up to cursor
|
|
174
|
+
const leftText = fullText.slice(0, cursorIndex);
|
|
175
|
+
|
|
176
|
+
// Find the start of the current token (after the last space)
|
|
177
|
+
const lastSpaceIndex = leftText.lastIndexOf(' ');
|
|
178
|
+
const currentToken = leftText.slice(lastSpaceIndex + 1);
|
|
179
|
+
|
|
180
|
+
// Check if token contains a colon
|
|
181
|
+
const colonIndex = currentToken.indexOf(':');
|
|
182
|
+
|
|
183
|
+
if (colonIndex === -1) {
|
|
184
|
+
// No colon found: we're typing a KEY
|
|
185
|
+
return {
|
|
186
|
+
word: currentToken,
|
|
187
|
+
isKey: true,
|
|
188
|
+
keyContext: null,
|
|
189
|
+
colonIndex: -1,
|
|
190
|
+
};
|
|
191
|
+
} else {
|
|
192
|
+
// Colon found: we're typing a VALUE
|
|
193
|
+
const key = currentToken.slice(0, colonIndex);
|
|
194
|
+
const valuePart = currentToken.slice(colonIndex + 1);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
word: valuePart,
|
|
198
|
+
isKey: false,
|
|
199
|
+
keyContext: key as ValidCommand, // Assume it's valid; schema will filter
|
|
200
|
+
colonIndex,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get the text to insert and the resulting cursor position
|
|
207
|
+
*
|
|
208
|
+
* This handles inserting the suggestion and positioning the cursor
|
|
209
|
+
* for the next edit.
|
|
210
|
+
*/
|
|
211
|
+
getInsertionInfo(
|
|
212
|
+
fullText: string,
|
|
213
|
+
cursorIndex: number,
|
|
214
|
+
insertText: string
|
|
215
|
+
): { newText: string; newCursorPos: number } {
|
|
216
|
+
// Find the start of the current token
|
|
217
|
+
const leftText = fullText.slice(0, cursorIndex);
|
|
218
|
+
const lastSpaceIndex = leftText.lastIndexOf(' ');
|
|
219
|
+
const tokenStart = lastSpaceIndex + 1;
|
|
220
|
+
|
|
221
|
+
// Build new text by replacing current token with insertText
|
|
222
|
+
const newText =
|
|
223
|
+
fullText.slice(0, tokenStart) +
|
|
224
|
+
insertText +
|
|
225
|
+
fullText.slice(cursorIndex);
|
|
226
|
+
|
|
227
|
+
const newCursorPos = tokenStart + insertText.length;
|
|
228
|
+
|
|
229
|
+
return { newText, newCursorPos };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Filter Command Registry
|
|
3
|
+
*
|
|
4
|
+
* Centralized source of truth for valid filter keys, values, and behavior.
|
|
5
|
+
* Used by parser, autocomplete, and validation systems.
|
|
6
|
+
*
|
|
7
|
+
* See docs/CLI_FILTER_COMMANDS.md for the full specification.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type CommandType = 'enum' | 'string' | 'numeric' | 'date' | 'boolean';
|
|
11
|
+
|
|
12
|
+
export interface CommandSchema {
|
|
13
|
+
type: CommandType;
|
|
14
|
+
description: string;
|
|
15
|
+
values?: string[]; // For enum types
|
|
16
|
+
supportsOperators?: boolean; // For numeric/date types
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ValidCommand =
|
|
20
|
+
// Status & Lifecycle
|
|
21
|
+
| 'status'
|
|
22
|
+
| 'stage'
|
|
23
|
+
| 'priority'
|
|
24
|
+
| 'severity'
|
|
25
|
+
// People & Teams
|
|
26
|
+
| 'owner'
|
|
27
|
+
| 'assignee'
|
|
28
|
+
| 'reviewer'
|
|
29
|
+
| 'created-by'
|
|
30
|
+
| 'team'
|
|
31
|
+
// Classification
|
|
32
|
+
| 'type'
|
|
33
|
+
| 'component'
|
|
34
|
+
| 'area'
|
|
35
|
+
| 'tag'
|
|
36
|
+
// Metrics & Comparisons
|
|
37
|
+
| 'score'
|
|
38
|
+
| 'effort'
|
|
39
|
+
| 'duration'
|
|
40
|
+
| 'age'
|
|
41
|
+
| 'count'
|
|
42
|
+
// Time-Based
|
|
43
|
+
| 'created'
|
|
44
|
+
| 'updated'
|
|
45
|
+
| 'due'
|
|
46
|
+
| 'week'
|
|
47
|
+
| 'month'
|
|
48
|
+
// Flags & Booleans
|
|
49
|
+
| 'is'
|
|
50
|
+
| 'has'
|
|
51
|
+
| 'no';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Full command schema registry
|
|
55
|
+
*
|
|
56
|
+
* Defines the type, valid values, and behavior of each filter key.
|
|
57
|
+
* Update this when adding new filter commands to the system.
|
|
58
|
+
*/
|
|
59
|
+
export const COMMAND_SCHEMA: Record<ValidCommand, CommandSchema> = {
|
|
60
|
+
// ============== Status & Lifecycle ==============
|
|
61
|
+
status: {
|
|
62
|
+
type: 'enum',
|
|
63
|
+
description: 'Item lifecycle status',
|
|
64
|
+
values: ['active', 'pending', 'completed', 'archived', 'paused'],
|
|
65
|
+
},
|
|
66
|
+
stage: {
|
|
67
|
+
type: 'enum',
|
|
68
|
+
description: 'Workflow stage',
|
|
69
|
+
values: ['backlog', 'todo', 'in-progress', 'in-review', 'done'],
|
|
70
|
+
},
|
|
71
|
+
priority: {
|
|
72
|
+
type: 'enum',
|
|
73
|
+
description: 'Priority level',
|
|
74
|
+
values: ['critical', 'high', 'medium', 'low', 'none'],
|
|
75
|
+
},
|
|
76
|
+
severity: {
|
|
77
|
+
type: 'enum',
|
|
78
|
+
description: 'Issue severity',
|
|
79
|
+
values: ['blocker', 'major', 'minor', 'trivial', 'info'],
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// ============== People & Teams ==============
|
|
83
|
+
owner: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'Person who owns the item',
|
|
86
|
+
},
|
|
87
|
+
assignee: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
description: 'Person assigned to work on item',
|
|
90
|
+
},
|
|
91
|
+
reviewer: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Person reviewing the item',
|
|
94
|
+
},
|
|
95
|
+
'created-by': {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'Person who created the item',
|
|
98
|
+
},
|
|
99
|
+
team: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
description: 'Team responsible for item',
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// ============== Classification ==============
|
|
105
|
+
type: {
|
|
106
|
+
type: 'enum',
|
|
107
|
+
description: 'Item type or category',
|
|
108
|
+
values: ['bug', 'feature', 'enhancement', 'task', 'spike', 'doc', 'refactor'],
|
|
109
|
+
},
|
|
110
|
+
component: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Component or module name',
|
|
113
|
+
},
|
|
114
|
+
area: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Area of codebase',
|
|
117
|
+
},
|
|
118
|
+
tag: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'Custom tag or label',
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// ============== Metrics & Comparisons ==============
|
|
124
|
+
score: {
|
|
125
|
+
type: 'numeric',
|
|
126
|
+
description: 'Numeric score (supports comparison operators)',
|
|
127
|
+
supportsOperators: true,
|
|
128
|
+
},
|
|
129
|
+
effort: {
|
|
130
|
+
type: 'numeric',
|
|
131
|
+
description: 'Effort estimate (1-10 scale)',
|
|
132
|
+
supportsOperators: true,
|
|
133
|
+
},
|
|
134
|
+
duration: {
|
|
135
|
+
type: 'numeric',
|
|
136
|
+
description: 'Duration in days/hours',
|
|
137
|
+
supportsOperators: true,
|
|
138
|
+
},
|
|
139
|
+
age: {
|
|
140
|
+
type: 'numeric',
|
|
141
|
+
description: 'Age in days',
|
|
142
|
+
supportsOperators: true,
|
|
143
|
+
},
|
|
144
|
+
count: {
|
|
145
|
+
type: 'numeric',
|
|
146
|
+
description: 'Count of items',
|
|
147
|
+
supportsOperators: true,
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// ============== Time-Based ==============
|
|
151
|
+
created: {
|
|
152
|
+
type: 'date',
|
|
153
|
+
description: 'Creation date (YYYY-MM-DD format)',
|
|
154
|
+
supportsOperators: true,
|
|
155
|
+
},
|
|
156
|
+
updated: {
|
|
157
|
+
type: 'date',
|
|
158
|
+
description: 'Last update date',
|
|
159
|
+
supportsOperators: true,
|
|
160
|
+
},
|
|
161
|
+
due: {
|
|
162
|
+
type: 'date',
|
|
163
|
+
description: 'Due date',
|
|
164
|
+
},
|
|
165
|
+
week: {
|
|
166
|
+
type: 'enum',
|
|
167
|
+
description: 'Week relative to current',
|
|
168
|
+
values: ['current', 'next', 'last'],
|
|
169
|
+
},
|
|
170
|
+
month: {
|
|
171
|
+
type: 'enum',
|
|
172
|
+
description: 'Month relative to current',
|
|
173
|
+
values: ['current', 'next', 'last'],
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// ============== Flags & Booleans ==============
|
|
177
|
+
is: {
|
|
178
|
+
type: 'enum',
|
|
179
|
+
description: 'State flags',
|
|
180
|
+
values: ['blocked', 'duplicate', 'blocker', 'breaking', 'hotfix'],
|
|
181
|
+
},
|
|
182
|
+
has: {
|
|
183
|
+
type: 'enum',
|
|
184
|
+
description: 'Presence check',
|
|
185
|
+
values: ['label', 'assignee', 'reviewer', 'dependencies'],
|
|
186
|
+
},
|
|
187
|
+
no: {
|
|
188
|
+
type: 'enum',
|
|
189
|
+
description: 'Absence check',
|
|
190
|
+
values: ['owner', 'reviewer', 'description'],
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get all valid command keys for autocomplete
|
|
196
|
+
*/
|
|
197
|
+
export function getValidCommands(): ValidCommand[] {
|
|
198
|
+
return Object.keys(COMMAND_SCHEMA) as ValidCommand[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if a key is a valid command
|
|
203
|
+
*/
|
|
204
|
+
export function isValidCommand(key: string): key is ValidCommand {
|
|
205
|
+
return key in COMMAND_SCHEMA;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the schema for a specific command
|
|
210
|
+
*/
|
|
211
|
+
export function getCommandSchema(key: string): CommandSchema | null {
|
|
212
|
+
if (!isValidCommand(key)) return null;
|
|
213
|
+
return COMMAND_SCHEMA[key];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get valid values for an enum command
|
|
218
|
+
*/
|
|
219
|
+
export function getCommandValues(key: string): string[] {
|
|
220
|
+
const schema = getCommandSchema(key);
|
|
221
|
+
if (schema?.type === 'enum' && schema.values) {
|
|
222
|
+
return schema.values;
|
|
223
|
+
}
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if a command supports comparison operators
|
|
229
|
+
*/
|
|
230
|
+
export function supportsOperators(key: string): boolean {
|
|
231
|
+
const schema = getCommandSchema(key);
|
|
232
|
+
return schema?.supportsOperators ?? false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Autocomplete suggestions
|
|
237
|
+
*
|
|
238
|
+
* Returns matching keys based on partial input
|
|
239
|
+
* Used by autocomplete menu to suggest valid commands
|
|
240
|
+
*/
|
|
241
|
+
export function autocompleteKeys(prefix: string): ValidCommand[] {
|
|
242
|
+
const lower = prefix.toLowerCase();
|
|
243
|
+
return getValidCommands().filter(key =>
|
|
244
|
+
key.toLowerCase().startsWith(lower)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Autocomplete values for a given key
|
|
250
|
+
*
|
|
251
|
+
* For enum commands, returns predefined values.
|
|
252
|
+
* For string commands, should be populated from data source (usernames, components, etc.)
|
|
253
|
+
* For numeric/date commands, returns example syntax.
|
|
254
|
+
*/
|
|
255
|
+
export function autocompleteValues(key: string, context?: Record<string, any>): string[] {
|
|
256
|
+
const schema = getCommandSchema(key);
|
|
257
|
+
|
|
258
|
+
if (!schema) return [];
|
|
259
|
+
|
|
260
|
+
// Enum types: return predefined values
|
|
261
|
+
if (schema.type === 'enum' && schema.values) {
|
|
262
|
+
return schema.values;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// String types: return from context (usernames, components, etc.)
|
|
266
|
+
if (schema.type === 'string') {
|
|
267
|
+
const field = key === 'created-by' ? 'createdBy' : key;
|
|
268
|
+
if (context?.[field]) {
|
|
269
|
+
return Array.isArray(context[field])
|
|
270
|
+
? context[field]
|
|
271
|
+
: [String(context[field])];
|
|
272
|
+
}
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Numeric types: return operator hints
|
|
277
|
+
if (schema.type === 'numeric') {
|
|
278
|
+
return ['>', '<', '=', '!=', '>=', '<='];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Date types: return format hint
|
|
282
|
+
if (schema.type === 'date') {
|
|
283
|
+
return ['YYYY-MM-DD', '>', '<'];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Validation: Check if a value is valid for a command
|
|
291
|
+
*
|
|
292
|
+
* For enum types, checks against predefined list.
|
|
293
|
+
* For string types, always accepts (open-ended).
|
|
294
|
+
* For numeric types, validates number format.
|
|
295
|
+
* For date types, validates YYYY-MM-DD format.
|
|
296
|
+
*/
|
|
297
|
+
export function isValidValue(key: string, value: string): boolean {
|
|
298
|
+
const schema = getCommandSchema(key);
|
|
299
|
+
if (!schema) return false;
|
|
300
|
+
|
|
301
|
+
switch (schema.type) {
|
|
302
|
+
case 'enum':
|
|
303
|
+
return schema.values?.includes(value) ?? false;
|
|
304
|
+
|
|
305
|
+
case 'string':
|
|
306
|
+
// All strings are valid
|
|
307
|
+
return true;
|
|
308
|
+
|
|
309
|
+
case 'numeric':
|
|
310
|
+
// Check if value (after stripping operators) is a valid number
|
|
311
|
+
const numMatch = value.match(/[><=!]+(\d+)/);
|
|
312
|
+
return numMatch ? !isNaN(Number(numMatch[1])) : !isNaN(Number(value));
|
|
313
|
+
|
|
314
|
+
case 'date':
|
|
315
|
+
// Validate YYYY-MM-DD format
|
|
316
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value) || /^[><=!]+\d{4}-\d{2}-\d{2}$/.test(value);
|
|
317
|
+
|
|
318
|
+
case 'boolean':
|
|
319
|
+
return value === 'true' || value === 'false';
|
|
320
|
+
|
|
321
|
+
default:
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get autocomplete suggestions for next word
|
|
328
|
+
*
|
|
329
|
+
* Analyzes current input and suggests:
|
|
330
|
+
* - Keys if none selected yet
|
|
331
|
+
* - Values if a key is selected
|
|
332
|
+
* - Operators if key supports them
|
|
333
|
+
*/
|
|
334
|
+
export function getContextualSuggestions(
|
|
335
|
+
input: string,
|
|
336
|
+
position: number,
|
|
337
|
+
context?: Record<string, any>
|
|
338
|
+
): { type: 'key' | 'value' | 'operator'; suggestions: string[] } {
|
|
339
|
+
// Simple heuristic: if input ends with ':', suggest values
|
|
340
|
+
if (input.slice(0, position).endsWith(':')) {
|
|
341
|
+
const keyMatch = input.slice(0, position).match(/([a-z-]+):$/);
|
|
342
|
+
if (keyMatch) {
|
|
343
|
+
const key = keyMatch[1];
|
|
344
|
+
return {
|
|
345
|
+
type: supportsOperators(key) ? 'operator' : 'value',
|
|
346
|
+
suggestions: autocompleteValues(key, context),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Otherwise suggest keys
|
|
352
|
+
const partialKey = input.slice(0, position).split(/\s+/).pop() || '';
|
|
353
|
+
return {
|
|
354
|
+
type: 'key',
|
|
355
|
+
suggestions: autocompleteKeys(partialKey),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Export schema for testing/debugging
|
|
361
|
+
*/
|
|
362
|
+
export function dumpSchema(): object {
|
|
363
|
+
return COMMAND_SCHEMA;
|
|
364
|
+
}
|