(cwd: string)
| 482 | * auth changes (e.g. /login) take effect immediately. |
| 483 | */ |
| 484 | export async function getCommands(cwd: string): Promise<Command[]> { |
| 485 | const allCommands = await loadAllCommands(cwd) |
| 486 | |
| 487 | // Get dynamic skills discovered during file operations |
| 488 | const dynamicSkills = getDynamicSkills() |
| 489 | |
| 490 | const exposeInNoumenaMode = (cmd: Command): Command => |
| 491 | isNoumenaMode() && cmd.isHidden ? { ...cmd, isHidden: false } : cmd |
| 492 | |
| 493 | // Build base commands without dynamic skills |
| 494 | const baseCommands = allCommands.filter( |
| 495 | _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_), |
| 496 | ).map(exposeInNoumenaMode) |
| 497 | |
| 498 | if (dynamicSkills.length === 0) { |
| 499 | return baseCommands |
| 500 | } |
| 501 | |
| 502 | // Dedupe dynamic skills - only add if not already present |
| 503 | const baseCommandNames = new Set(baseCommands.map(c => c.name)) |
| 504 | const uniqueDynamicSkills = dynamicSkills.filter( |
| 505 | s => |
| 506 | !baseCommandNames.has(s.name) && |
| 507 | meetsAvailabilityRequirement(s) && |
| 508 | isCommandEnabled(s), |
| 509 | ).map(exposeInNoumenaMode) |
| 510 | |
| 511 | if (uniqueDynamicSkills.length === 0) { |
| 512 | return baseCommands |
| 513 | } |
| 514 | |
| 515 | // Insert dynamic skills after plugin skills but before built-in commands |
| 516 | const builtInNames = new Set(COMMANDS().map(c => c.name)) |
| 517 | const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name)) |
| 518 | |
| 519 | if (insertIndex === -1) { |
| 520 | return [...baseCommands, ...uniqueDynamicSkills] |
| 521 | } |
| 522 | |
| 523 | return [ |
| 524 | ...baseCommands.slice(0, insertIndex), |
| 525 | ...uniqueDynamicSkills, |
| 526 | ...baseCommands.slice(insertIndex), |
| 527 | ] |
| 528 | } |
| 529 | |
| 530 | /** |
| 531 | * Clears only the memoization caches for commands, WITHOUT clearing skill caches. |
no test coverage detected