@affectively/slash-commands 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/dist/index.mjs ADDED
@@ -0,0 +1,955 @@
1
+ // src/generator.ts
2
+ import { z } from "zod";
3
+ function extractCategory(toolName) {
4
+ const parts = toolName.split("_");
5
+ if (parts.length > 1) {
6
+ return parts[0];
7
+ }
8
+ return toolName;
9
+ }
10
+ function extractParametersFromZod(schema) {
11
+ const parameters = [];
12
+ if (schema instanceof z.ZodObject) {
13
+ const shape = schema.shape;
14
+ for (const [name, fieldSchema] of Object.entries(shape)) {
15
+ const param = extractSingleParameter(
16
+ name,
17
+ fieldSchema,
18
+ schema
19
+ );
20
+ if (param) {
21
+ parameters.push(param);
22
+ }
23
+ }
24
+ }
25
+ return parameters;
26
+ }
27
+ function extractSingleParameter(name, fieldSchema, _parentSchema) {
28
+ let innerSchema = fieldSchema;
29
+ let isOptional = false;
30
+ let defaultValue = void 0;
31
+ if (innerSchema instanceof z.ZodOptional) {
32
+ isOptional = true;
33
+ innerSchema = innerSchema._def.innerType;
34
+ }
35
+ if (innerSchema instanceof z.ZodNullable) {
36
+ isOptional = true;
37
+ innerSchema = innerSchema._def.innerType;
38
+ }
39
+ if (innerSchema instanceof z.ZodDefault) {
40
+ defaultValue = innerSchema._def.defaultValue();
41
+ innerSchema = innerSchema._def.innerType;
42
+ }
43
+ const description = innerSchema._def.description || fieldSchema._def.description || "";
44
+ const typeInfo = getZodType(innerSchema);
45
+ return {
46
+ name,
47
+ type: typeInfo.type,
48
+ description,
49
+ required: !isOptional && defaultValue === void 0,
50
+ defaultValue,
51
+ enumValues: typeInfo.enumValues
52
+ };
53
+ }
54
+ function getZodType(schema) {
55
+ if (schema instanceof z.ZodString) {
56
+ return { type: "string" };
57
+ }
58
+ if (schema instanceof z.ZodNumber) {
59
+ return { type: "number" };
60
+ }
61
+ if (schema instanceof z.ZodBoolean) {
62
+ return { type: "boolean" };
63
+ }
64
+ if (schema instanceof z.ZodEnum) {
65
+ return {
66
+ type: "enum",
67
+ enumValues: schema._def.values
68
+ };
69
+ }
70
+ if (schema instanceof z.ZodArray) {
71
+ return { type: "array" };
72
+ }
73
+ if (schema instanceof z.ZodObject) {
74
+ return { type: "object" };
75
+ }
76
+ if (schema instanceof z.ZodNativeEnum) {
77
+ const values = Object.values(schema._def.values).filter(
78
+ (v) => typeof v === "string"
79
+ );
80
+ return { type: "enum", enumValues: values };
81
+ }
82
+ return { type: "string" };
83
+ }
84
+ function generateHelpText(name, description, parameters) {
85
+ const lines = [];
86
+ lines.push(`/${name}`);
87
+ lines.push("");
88
+ lines.push(description);
89
+ if (parameters.length > 0) {
90
+ lines.push("");
91
+ lines.push("Parameters:");
92
+ for (const param of parameters) {
93
+ const required = param.required ? "(required)" : "(optional)";
94
+ const type = param.enumValues ? `[${param.enumValues.join("|")}]` : `<${param.type}>`;
95
+ const defaultStr = param.defaultValue !== void 0 ? ` (default: ${JSON.stringify(param.defaultValue)})` : "";
96
+ lines.push(` ${param.name} ${type} ${required}${defaultStr}`);
97
+ if (param.description) {
98
+ lines.push(` ${param.description}`);
99
+ }
100
+ }
101
+ }
102
+ return lines.join("\n");
103
+ }
104
+ function generateExamples(name, parameters) {
105
+ const examples = [];
106
+ examples.push(`/${name}`);
107
+ const stringParams = parameters.filter(
108
+ (p) => p.type === "string" && !p.enumValues
109
+ );
110
+ const enumParams = parameters.filter(
111
+ (p) => p.type === "enum" && p.enumValues
112
+ );
113
+ const numberParams = parameters.filter((p) => p.type === "number");
114
+ if (enumParams.length > 0) {
115
+ const param = enumParams[0];
116
+ const value = param.enumValues?.[0] || "value";
117
+ examples.push(`/${name} ${param.name}=${value}`);
118
+ }
119
+ if (stringParams.length > 0) {
120
+ const param = stringParams[0];
121
+ examples.push(`/${name} ${param.name}="example"`);
122
+ }
123
+ const limitParam = numberParams.find((p) => p.name === "limit");
124
+ if (limitParam) {
125
+ examples.push(`/${name} limit=10`);
126
+ }
127
+ return examples.slice(0, 3);
128
+ }
129
+ function generateSlashCommand(tool, tierOverride) {
130
+ const { name, description, inputSchema } = tool;
131
+ const isZodSchema = inputSchema instanceof z.ZodType;
132
+ const parameters = isZodSchema ? extractParametersFromZod(inputSchema) : extractParametersFromJsonSchema(inputSchema);
133
+ const category = extractCategory(name);
134
+ const requiredTier = tierOverride || tool.requiredTier || "free";
135
+ const helpText = generateHelpText(name, description, parameters);
136
+ const examples = generateExamples(name, parameters);
137
+ return {
138
+ name,
139
+ description,
140
+ schema: isZodSchema ? inputSchema : createSchemaFromJsonSchema(inputSchema),
141
+ category,
142
+ requiredTier,
143
+ parameters,
144
+ helpText,
145
+ examples
146
+ };
147
+ }
148
+ function extractParametersFromJsonSchema(jsonSchema) {
149
+ const parameters = [];
150
+ if (jsonSchema.type !== "object" || !jsonSchema.properties) {
151
+ return parameters;
152
+ }
153
+ const properties = jsonSchema.properties;
154
+ const required = jsonSchema.required || [];
155
+ for (const [name, propSchema] of Object.entries(properties)) {
156
+ const param = {
157
+ name,
158
+ type: mapJsonSchemaType(propSchema.type),
159
+ description: propSchema.description || "",
160
+ required: required.includes(name),
161
+ defaultValue: propSchema.default,
162
+ enumValues: propSchema.enum
163
+ };
164
+ parameters.push(param);
165
+ }
166
+ return parameters;
167
+ }
168
+ function mapJsonSchemaType(jsonType) {
169
+ switch (jsonType) {
170
+ case "string":
171
+ return "string";
172
+ case "number":
173
+ case "integer":
174
+ return "number";
175
+ case "boolean":
176
+ return "boolean";
177
+ case "array":
178
+ return "array";
179
+ case "object":
180
+ return "object";
181
+ default:
182
+ return "string";
183
+ }
184
+ }
185
+ function createSchemaFromJsonSchema(jsonSchema) {
186
+ if (jsonSchema.type !== "object" || !jsonSchema.properties) {
187
+ return z.object({});
188
+ }
189
+ const properties = jsonSchema.properties;
190
+ const required = new Set(jsonSchema.required || []);
191
+ const shape = {};
192
+ for (const [name, propSchema] of Object.entries(properties)) {
193
+ let fieldSchema = createFieldSchema(propSchema);
194
+ if (!required.has(name)) {
195
+ fieldSchema = fieldSchema.optional();
196
+ }
197
+ if (propSchema.description) {
198
+ fieldSchema = fieldSchema.describe(propSchema.description);
199
+ }
200
+ shape[name] = fieldSchema;
201
+ }
202
+ return z.object(shape);
203
+ }
204
+ function createFieldSchema(propSchema) {
205
+ const type = propSchema.type;
206
+ if (propSchema.enum && Array.isArray(propSchema.enum)) {
207
+ return z.enum(propSchema.enum);
208
+ }
209
+ switch (type) {
210
+ case "string":
211
+ return z.string();
212
+ case "number":
213
+ case "integer":
214
+ return z.number();
215
+ case "boolean":
216
+ return z.boolean();
217
+ case "array":
218
+ return z.array(z.unknown());
219
+ case "object":
220
+ return z.record(z.unknown());
221
+ default:
222
+ return z.unknown();
223
+ }
224
+ }
225
+ function generateSlashCommands(tools) {
226
+ return tools.map((tool) => generateSlashCommand(tool));
227
+ }
228
+ function generateAliases(name) {
229
+ const aliases = [];
230
+ const parts = name.split("_");
231
+ if (parts.length > 1) {
232
+ const acronym = parts.map((p) => p[0]).join("");
233
+ aliases.push(acronym);
234
+ }
235
+ const kebab = name.replace(/_/g, "-");
236
+ if (kebab !== name) {
237
+ aliases.push(kebab);
238
+ }
239
+ return aliases;
240
+ }
241
+
242
+ // src/parser.ts
243
+ var DEFAULT_OPTIONS = {
244
+ allowUnknownArgs: false,
245
+ coerceTypes: true,
246
+ argSeparator: " "
247
+ };
248
+ function isSlashCommand(input) {
249
+ return input.trim().startsWith("/");
250
+ }
251
+ function extractCommandName(input) {
252
+ const trimmed = input.trim();
253
+ if (!trimmed.startsWith("/")) {
254
+ return "";
255
+ }
256
+ const withoutSlash = trimmed.slice(1);
257
+ const match = withoutSlash.match(/^([\w-]+)/);
258
+ return match ? match[1] : "";
259
+ }
260
+ function parseSlashCommand(input, registry, options = {}) {
261
+ const opts = { ...DEFAULT_OPTIONS, ...options };
262
+ const trimmed = input.trim();
263
+ if (!trimmed.startsWith("/")) {
264
+ return {
265
+ command: "",
266
+ args: {},
267
+ raw: input,
268
+ found: false,
269
+ errors: ["Input must start with /"]
270
+ };
271
+ }
272
+ const withoutSlash = trimmed.slice(1);
273
+ const tokens = tokenize(withoutSlash);
274
+ if (tokens.length === 0) {
275
+ return {
276
+ command: "",
277
+ args: {},
278
+ raw: input,
279
+ found: false,
280
+ errors: ["No command specified"]
281
+ };
282
+ }
283
+ const { command, argTokens } = resolveCommand(tokens, registry);
284
+ const { args, errors } = parseArguments(argTokens, opts);
285
+ const definition = registry?.get(command);
286
+ const found = registry ? !!definition : true;
287
+ const validationErrors = definition ? validateArgs(args, definition, opts) : [];
288
+ return {
289
+ command,
290
+ args,
291
+ raw: input,
292
+ found,
293
+ errors: [...errors, ...validationErrors].length > 0 ? [...errors, ...validationErrors] : void 0,
294
+ definition
295
+ };
296
+ }
297
+ function tokenize(input) {
298
+ const tokens = [];
299
+ let current = "";
300
+ let inQuote = false;
301
+ let quoteChar = "";
302
+ for (let i = 0; i < input.length; i++) {
303
+ const char = input[i];
304
+ if (inQuote) {
305
+ if (char === quoteChar) {
306
+ inQuote = false;
307
+ quoteChar = "";
308
+ } else {
309
+ current += char;
310
+ }
311
+ } else if (char === '"' || char === "'") {
312
+ inQuote = true;
313
+ quoteChar = char;
314
+ } else if (char === " " || char === " ") {
315
+ if (current) {
316
+ tokens.push(current);
317
+ current = "";
318
+ }
319
+ } else {
320
+ current += char;
321
+ }
322
+ }
323
+ if (current) {
324
+ tokens.push(current);
325
+ }
326
+ return tokens;
327
+ }
328
+ function resolveCommand(tokens, registry) {
329
+ const firstToken = tokens[0];
330
+ if (registry?.has(firstToken)) {
331
+ return { command: firstToken, argTokens: tokens.slice(1) };
332
+ }
333
+ if (tokens.length > 1) {
334
+ const combined = `${firstToken}_${tokens[1]}`;
335
+ if (registry?.has(combined)) {
336
+ return { command: combined, argTokens: tokens.slice(2) };
337
+ }
338
+ }
339
+ const snakeCase = firstToken.replace(/-/g, "_");
340
+ if (registry?.has(snakeCase)) {
341
+ return { command: snakeCase, argTokens: tokens.slice(1) };
342
+ }
343
+ return { command: firstToken, argTokens: tokens.slice(1) };
344
+ }
345
+ function parseArguments(tokens, opts) {
346
+ const args = {};
347
+ const errors = [];
348
+ let i = 0;
349
+ while (i < tokens.length) {
350
+ const token = tokens[i];
351
+ if (token.startsWith("-")) {
352
+ const eqIndex2 = token.indexOf("=");
353
+ if (eqIndex2 > 0) {
354
+ const key2 = token.slice(token.startsWith("--") ? 2 : 1, eqIndex2);
355
+ const value = token.slice(eqIndex2 + 1);
356
+ args[key2] = opts.coerceTypes ? coerceValue(value) : value;
357
+ i++;
358
+ continue;
359
+ }
360
+ const key = token.slice(token.startsWith("--") ? 2 : 1);
361
+ if (i + 1 < tokens.length && !tokens[i + 1].startsWith("-")) {
362
+ const value = tokens[i + 1];
363
+ args[key] = opts.coerceTypes ? coerceValue(value) : value;
364
+ i += 2;
365
+ } else {
366
+ args[key] = true;
367
+ i++;
368
+ }
369
+ continue;
370
+ }
371
+ const eqIndex = token.indexOf("=");
372
+ if (eqIndex > 0) {
373
+ const key = token.slice(0, eqIndex);
374
+ const value = token.slice(eqIndex + 1);
375
+ args[key] = opts.coerceTypes ? coerceValue(value) : value;
376
+ i++;
377
+ continue;
378
+ }
379
+ errors.push(`Unknown argument format: ${token}`);
380
+ i++;
381
+ }
382
+ return { args, errors };
383
+ }
384
+ function coerceValue(value) {
385
+ if (value.toLowerCase() === "true") return true;
386
+ if (value.toLowerCase() === "false") return false;
387
+ if (value.toLowerCase() === "null") return null;
388
+ if (value.toLowerCase() === "undefined") return void 0;
389
+ if (/^-?\d+$/.test(value)) {
390
+ return parseInt(value, 10);
391
+ }
392
+ if (/^-?\d+\.\d+$/.test(value)) {
393
+ return parseFloat(value);
394
+ }
395
+ if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}")) {
396
+ try {
397
+ return JSON.parse(value);
398
+ } catch {
399
+ }
400
+ }
401
+ return value;
402
+ }
403
+ function validateArgs(args, definition, opts) {
404
+ const errors = [];
405
+ const paramNames = new Set(definition.parameters.map((p) => p.name));
406
+ if (!opts.allowUnknownArgs) {
407
+ for (const key of Object.keys(args)) {
408
+ if (!paramNames.has(key)) {
409
+ errors.push(`Unknown argument: ${key}`);
410
+ }
411
+ }
412
+ }
413
+ for (const param of definition.parameters) {
414
+ if (param.required && !(param.name in args)) {
415
+ errors.push(`Missing required argument: ${param.name}`);
416
+ }
417
+ }
418
+ for (const param of definition.parameters) {
419
+ if (param.enumValues && param.name in args) {
420
+ const value = args[param.name];
421
+ if (typeof value === "string" && !param.enumValues.includes(value)) {
422
+ errors.push(
423
+ `Invalid value for ${param.name}: '${value}'. Expected one of: ${param.enumValues.join(", ")}`
424
+ );
425
+ }
426
+ }
427
+ }
428
+ for (const param of definition.parameters) {
429
+ if (param.name in args) {
430
+ const value = args[param.name];
431
+ const typeError = validateType(value, param.type);
432
+ if (typeError) {
433
+ errors.push(`${param.name}: ${typeError}`);
434
+ }
435
+ }
436
+ }
437
+ return errors;
438
+ }
439
+ function validateType(value, expectedType) {
440
+ const actualType = typeof value;
441
+ switch (expectedType) {
442
+ case "string":
443
+ if (actualType !== "string") {
444
+ return `expected string, got ${actualType}`;
445
+ }
446
+ break;
447
+ case "number":
448
+ if (actualType !== "number" || isNaN(value)) {
449
+ return `expected number, got ${actualType}`;
450
+ }
451
+ break;
452
+ case "boolean":
453
+ if (actualType !== "boolean") {
454
+ return `expected boolean, got ${actualType}`;
455
+ }
456
+ break;
457
+ case "array":
458
+ if (!Array.isArray(value)) {
459
+ return `expected array, got ${actualType}`;
460
+ }
461
+ break;
462
+ case "object":
463
+ if (actualType !== "object" || value === null || Array.isArray(value)) {
464
+ return `expected object, got ${actualType}`;
465
+ }
466
+ break;
467
+ }
468
+ return null;
469
+ }
470
+ function formatCommand(command, args) {
471
+ const parts = [`/${command}`];
472
+ for (const [key, value] of Object.entries(args)) {
473
+ if (typeof value === "string" && value.includes(" ")) {
474
+ parts.push(`${key}="${value}"`);
475
+ } else if (typeof value === "object") {
476
+ parts.push(`${key}=${JSON.stringify(value)}`);
477
+ } else {
478
+ parts.push(`${key}=${value}`);
479
+ }
480
+ }
481
+ return parts.join(" ");
482
+ }
483
+ function getPartialCommand(input) {
484
+ const trimmed = input.trim();
485
+ if (!trimmed.startsWith("/")) {
486
+ return { command: "", isComplete: false };
487
+ }
488
+ const withoutSlash = trimmed.slice(1);
489
+ const spaceIndex = withoutSlash.indexOf(" ");
490
+ if (spaceIndex === -1) {
491
+ return { command: withoutSlash, isComplete: false };
492
+ }
493
+ const command = withoutSlash.slice(0, spaceIndex);
494
+ const rest = withoutSlash.slice(spaceIndex + 1).trim();
495
+ const lastToken = rest.split(" ").pop() || "";
496
+ const isTypingArg = !lastToken.includes("=") || lastToken.endsWith("=");
497
+ return {
498
+ command,
499
+ isComplete: true,
500
+ partialArg: isTypingArg ? lastToken : void 0
501
+ };
502
+ }
503
+
504
+ // src/registry.ts
505
+ var SlashCommandRegistry = class {
506
+ constructor() {
507
+ this.commands = /* @__PURE__ */ new Map();
508
+ this.aliases = /* @__PURE__ */ new Map();
509
+ // alias -> command name
510
+ this.categories = /* @__PURE__ */ new Set();
511
+ }
512
+ /**
513
+ * Register a single slash command
514
+ */
515
+ register(command) {
516
+ this.commands.set(command.name, command);
517
+ this.categories.add(command.category);
518
+ if (command.aliases) {
519
+ for (const alias of command.aliases) {
520
+ this.aliases.set(alias, command.name);
521
+ }
522
+ }
523
+ const autoAliases = generateAliases(command.name);
524
+ for (const alias of autoAliases) {
525
+ if (!this.aliases.has(alias) && !this.commands.has(alias)) {
526
+ this.aliases.set(alias, command.name);
527
+ }
528
+ }
529
+ }
530
+ /**
531
+ * Register multiple commands at once
532
+ */
533
+ registerAll(commands) {
534
+ for (const command of commands) {
535
+ this.register(command);
536
+ }
537
+ }
538
+ /**
539
+ * Register commands from tool definitions
540
+ */
541
+ registerFromTools(tools) {
542
+ for (const tool of tools) {
543
+ const command = generateSlashCommand(tool);
544
+ this.register(command);
545
+ }
546
+ }
547
+ /**
548
+ * Get a command by name or alias
549
+ */
550
+ get(nameOrAlias) {
551
+ const direct = this.commands.get(nameOrAlias);
552
+ if (direct) return direct;
553
+ const resolvedName = this.aliases.get(nameOrAlias);
554
+ if (resolvedName) {
555
+ return this.commands.get(resolvedName);
556
+ }
557
+ const snakeCase = nameOrAlias.replace(/-/g, "_");
558
+ return this.commands.get(snakeCase);
559
+ }
560
+ /**
561
+ * Check if a command exists
562
+ */
563
+ has(nameOrAlias) {
564
+ return this.get(nameOrAlias) !== void 0;
565
+ }
566
+ /**
567
+ * Get all registered commands
568
+ */
569
+ getAll() {
570
+ return Array.from(this.commands.values());
571
+ }
572
+ /**
573
+ * Get commands filtered by category
574
+ */
575
+ getByCategory(category) {
576
+ return this.getAll().filter((cmd) => cmd.category === category);
577
+ }
578
+ /**
579
+ * Get commands available for a subscription tier
580
+ */
581
+ getForTier(tier) {
582
+ const tierOrder = [
583
+ "free",
584
+ "premium",
585
+ "ultra-premium",
586
+ "enterprise"
587
+ ];
588
+ const tierIndex = tierOrder.indexOf(tier);
589
+ return this.getAll().filter((cmd) => {
590
+ const cmdTierIndex = tierOrder.indexOf(cmd.requiredTier);
591
+ return cmdTierIndex <= tierIndex;
592
+ });
593
+ }
594
+ /**
595
+ * Get all categories
596
+ */
597
+ getCategories() {
598
+ return Array.from(this.categories).sort();
599
+ }
600
+ /**
601
+ * Get command count by category
602
+ */
603
+ getCountByCategory() {
604
+ const counts = {};
605
+ for (const category of this.categories) {
606
+ counts[category] = this.getByCategory(category).length;
607
+ }
608
+ return counts;
609
+ }
610
+ /**
611
+ * Resolve an alias to the canonical command name
612
+ */
613
+ resolveAlias(alias) {
614
+ return this.aliases.get(alias);
615
+ }
616
+ /**
617
+ * Get the underlying Map for direct access
618
+ */
619
+ getMap() {
620
+ return this.commands;
621
+ }
622
+ /**
623
+ * Clear all registered commands
624
+ */
625
+ clear() {
626
+ this.commands.clear();
627
+ this.aliases.clear();
628
+ this.categories.clear();
629
+ }
630
+ /**
631
+ * Get total command count
632
+ */
633
+ get size() {
634
+ return this.commands.size;
635
+ }
636
+ };
637
+ var slashCommandRegistry = new SlashCommandRegistry();
638
+ async function executeSlashCommand(input, context, executor) {
639
+ const startTime = Date.now();
640
+ const parsed = parseSlashCommand(input, slashCommandRegistry.getMap());
641
+ if (!parsed.found) {
642
+ return {
643
+ success: false,
644
+ error: `Unknown command: ${parsed.command}`,
645
+ executionTimeMs: Date.now() - startTime,
646
+ command: parsed.command,
647
+ args: parsed.args
648
+ };
649
+ }
650
+ if (parsed.errors && parsed.errors.length > 0) {
651
+ return {
652
+ success: false,
653
+ error: parsed.errors.join("; "),
654
+ executionTimeMs: Date.now() - startTime,
655
+ command: parsed.command,
656
+ args: parsed.args
657
+ };
658
+ }
659
+ const definition = parsed.definition;
660
+ const tierOrder = [
661
+ "free",
662
+ "premium",
663
+ "ultra-premium",
664
+ "enterprise"
665
+ ];
666
+ const userTierIndex = tierOrder.indexOf(context.userTier);
667
+ const requiredTierIndex = tierOrder.indexOf(definition.requiredTier);
668
+ if (requiredTierIndex > userTierIndex) {
669
+ return {
670
+ success: false,
671
+ error: `This command requires ${definition.requiredTier} subscription`,
672
+ executionTimeMs: Date.now() - startTime,
673
+ command: parsed.command,
674
+ args: parsed.args
675
+ };
676
+ }
677
+ try {
678
+ const data = await executor(parsed.command, parsed.args, context);
679
+ return {
680
+ success: true,
681
+ data,
682
+ executionTimeMs: Date.now() - startTime,
683
+ command: parsed.command,
684
+ args: parsed.args
685
+ };
686
+ } catch (error) {
687
+ return {
688
+ success: false,
689
+ error: error instanceof Error ? error.message : String(error),
690
+ executionTimeMs: Date.now() - startTime,
691
+ command: parsed.command,
692
+ args: parsed.args
693
+ };
694
+ }
695
+ }
696
+ function createAPIExecutor(apiBaseUrl, authToken) {
697
+ return async (command, args, context) => {
698
+ const url = `${apiBaseUrl}/api/slash-command`;
699
+ const token = authToken || context.authToken;
700
+ const response = await fetch(url, {
701
+ method: "POST",
702
+ headers: {
703
+ "Content-Type": "application/json",
704
+ ...token ? { Authorization: `Bearer ${token}` } : {}
705
+ },
706
+ body: JSON.stringify({
707
+ command,
708
+ args,
709
+ conversationId: context.conversationId
710
+ })
711
+ });
712
+ if (!response.ok) {
713
+ const errorText = await response.text();
714
+ throw new Error(`API error: ${response.status} - ${errorText}`);
715
+ }
716
+ return response.json();
717
+ };
718
+ }
719
+ function getAllSlashCommands() {
720
+ return slashCommandRegistry.getAll();
721
+ }
722
+ function getSlashCommand(name) {
723
+ return slashCommandRegistry.get(name);
724
+ }
725
+ function hasSlashCommand(name) {
726
+ return slashCommandRegistry.has(name);
727
+ }
728
+
729
+ // src/autocomplete.ts
730
+ var DEFAULT_OPTIONS2 = {
731
+ maxSuggestions: 10,
732
+ userTier: "free",
733
+ includeHidden: false,
734
+ minScore: 0
735
+ };
736
+ function fuzzyMatch(query, target) {
737
+ if (!query) return 50;
738
+ if (query === target) return 100;
739
+ const q = query.toLowerCase();
740
+ const t = target.toLowerCase();
741
+ if (t.startsWith(q)) {
742
+ return 90 + q.length / t.length * 10;
743
+ }
744
+ const words = t.split(/[_\-\s]/);
745
+ const initials = words.map((w) => w[0]).join("");
746
+ if (initials.startsWith(q)) {
747
+ return 80 + q.length / initials.length * 10;
748
+ }
749
+ if (t.includes(q)) {
750
+ const position = t.indexOf(q);
751
+ const positionScore = Math.max(0, 70 - position * 2);
752
+ return positionScore;
753
+ }
754
+ let qIndex = 0;
755
+ let matchCount = 0;
756
+ let consecutiveBonus = 0;
757
+ let lastMatchIndex = -2;
758
+ for (let i = 0; i < t.length && qIndex < q.length; i++) {
759
+ if (t[i] === q[qIndex]) {
760
+ matchCount++;
761
+ if (i === lastMatchIndex + 1) {
762
+ consecutiveBonus += 5;
763
+ }
764
+ lastMatchIndex = i;
765
+ qIndex++;
766
+ }
767
+ }
768
+ if (qIndex === q.length) {
769
+ const matchRatio = matchCount / q.length;
770
+ const lengthPenalty = Math.max(0, 1 - (t.length - q.length) / 20);
771
+ return Math.min(
772
+ 60,
773
+ 30 + matchRatio * 20 + consecutiveBonus + lengthPenalty * 10
774
+ );
775
+ }
776
+ return 0;
777
+ }
778
+ function getAutocompleteSuggestions(input, options = {}, registry = slashCommandRegistry) {
779
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
780
+ const partial = getPartialCommand(input);
781
+ const tierOrder = [
782
+ "free",
783
+ "premium",
784
+ "ultra-premium",
785
+ "enterprise"
786
+ ];
787
+ const userTierIndex = tierOrder.indexOf(opts.userTier);
788
+ const allCommands = registry.getAll().filter((cmd) => {
789
+ if (cmd.hidden && !opts.includeHidden) return false;
790
+ return true;
791
+ });
792
+ if (!partial.isComplete) {
793
+ return suggestCommands(
794
+ partial.command,
795
+ allCommands,
796
+ opts,
797
+ userTierIndex,
798
+ tierOrder
799
+ );
800
+ }
801
+ const command = registry.get(partial.command);
802
+ if (command && partial.partialArg !== void 0) {
803
+ return suggestArguments(
804
+ command,
805
+ partial.partialArg,
806
+ opts,
807
+ userTierIndex,
808
+ tierOrder
809
+ );
810
+ }
811
+ if (command) {
812
+ return suggestArguments(command, "", opts, userTierIndex, tierOrder);
813
+ }
814
+ return [];
815
+ }
816
+ function suggestCommands(query, commands, opts, userTierIndex, tierOrder) {
817
+ const suggestions = [];
818
+ for (const cmd of commands) {
819
+ const score = fuzzyMatch(query, cmd.name);
820
+ if (score < opts.minScore) continue;
821
+ const cmdTierIndex = tierOrder.indexOf(cmd.requiredTier);
822
+ const hasAccess = cmdTierIndex <= userTierIndex;
823
+ suggestions.push({
824
+ command: cmd.name,
825
+ displayText: `/${cmd.name}`,
826
+ description: truncateDescription(cmd.description),
827
+ matchScore: score,
828
+ category: cmd.category,
829
+ hasAccess
830
+ });
831
+ }
832
+ suggestions.sort((a, b) => {
833
+ if (a.hasAccess !== b.hasAccess) {
834
+ return a.hasAccess ? -1 : 1;
835
+ }
836
+ if ((b.matchScore ?? 0) !== (a.matchScore ?? 0)) {
837
+ return (b.matchScore ?? 0) - (a.matchScore ?? 0);
838
+ }
839
+ return a.command.localeCompare(b.command);
840
+ });
841
+ return suggestions.slice(0, opts.maxSuggestions);
842
+ }
843
+ function suggestArguments(command, query, opts, userTierIndex, tierOrder) {
844
+ const suggestions = [];
845
+ const cmdTierIndex = tierOrder.indexOf(command.requiredTier);
846
+ const hasAccess = cmdTierIndex <= userTierIndex;
847
+ const eqIndex = query.indexOf("=");
848
+ if (eqIndex > 0) {
849
+ const argName = query.slice(0, eqIndex).replace(/^-+/, "");
850
+ const valuePrefix = query.slice(eqIndex + 1);
851
+ const param = command.parameters.find((p) => p.name === argName);
852
+ if (param?.enumValues) {
853
+ for (const value of param.enumValues) {
854
+ const score = fuzzyMatch(valuePrefix, value);
855
+ if (score < opts.minScore) continue;
856
+ suggestions.push({
857
+ command: `${argName}=${value}`,
858
+ displayText: `${argName}=${value}`,
859
+ description: `Set ${argName} to ${value}`,
860
+ matchScore: score,
861
+ category: "value",
862
+ hasAccess
863
+ });
864
+ }
865
+ }
866
+ } else {
867
+ const argQuery = query.replace(/^-+/, "");
868
+ for (const param of command.parameters) {
869
+ const score = fuzzyMatch(argQuery, param.name);
870
+ if (score < opts.minScore) continue;
871
+ const requiredTag = param.required ? " (required)" : "";
872
+ const typeTag = param.enumValues ? ` [${param.enumValues.slice(0, 3).join("|")}${param.enumValues.length > 3 ? "..." : ""}]` : ` <${param.type}>`;
873
+ suggestions.push({
874
+ command: param.name,
875
+ displayText: `${param.name}${typeTag}`,
876
+ description: param.description || `${param.name}${requiredTag}`,
877
+ matchScore: score,
878
+ category: "argument",
879
+ hasAccess
880
+ });
881
+ }
882
+ }
883
+ suggestions.sort((a, b) => {
884
+ if ((b.matchScore ?? 0) !== (a.matchScore ?? 0)) {
885
+ return (b.matchScore ?? 0) - (a.matchScore ?? 0);
886
+ }
887
+ return a.command.localeCompare(b.command);
888
+ });
889
+ return suggestions.slice(0, opts.maxSuggestions);
890
+ }
891
+ function truncateDescription(description, maxLength = 80) {
892
+ const firstLine = description.split("\n")[0];
893
+ const firstSentence = firstLine.split(/[.!?]/)[0];
894
+ const text = firstSentence || firstLine;
895
+ if (text.length <= maxLength) {
896
+ return text;
897
+ }
898
+ return text.slice(0, maxLength - 3) + "...";
899
+ }
900
+ function getSuggestionsGroupedByCategory(input, options = {}, registry = slashCommandRegistry) {
901
+ const suggestions = getAutocompleteSuggestions(input, options, registry);
902
+ const grouped = {};
903
+ for (const suggestion of suggestions) {
904
+ if (!grouped[suggestion.category]) {
905
+ grouped[suggestion.category] = [];
906
+ }
907
+ grouped[suggestion.category].push(suggestion);
908
+ }
909
+ return grouped;
910
+ }
911
+ function shouldShowAutocomplete(input) {
912
+ const trimmed = input.trim();
913
+ if (!trimmed.startsWith("/")) {
914
+ return false;
915
+ }
916
+ return true;
917
+ }
918
+ function getReplacementRange(input) {
919
+ const partial = getPartialCommand(input);
920
+ if (!partial.isComplete) {
921
+ return { start: 0, end: input.length };
922
+ }
923
+ if (partial.partialArg !== void 0) {
924
+ const start = input.lastIndexOf(partial.partialArg);
925
+ return { start, end: input.length };
926
+ }
927
+ return { start: input.length, end: input.length };
928
+ }
929
+ export {
930
+ SlashCommandRegistry,
931
+ createAPIExecutor,
932
+ executeSlashCommand,
933
+ extractCategory,
934
+ extractCommandName,
935
+ extractParametersFromJsonSchema,
936
+ extractParametersFromZod,
937
+ formatCommand,
938
+ fuzzyMatch,
939
+ generateAliases,
940
+ generateExamples,
941
+ generateHelpText,
942
+ generateSlashCommand,
943
+ generateSlashCommands,
944
+ getAllSlashCommands,
945
+ getAutocompleteSuggestions,
946
+ getPartialCommand,
947
+ getReplacementRange,
948
+ getSlashCommand,
949
+ getSuggestionsGroupedByCategory,
950
+ hasSlashCommand,
951
+ isSlashCommand,
952
+ parseSlashCommand,
953
+ shouldShowAutocomplete,
954
+ slashCommandRegistry
955
+ };