Features: - Remote script execution with version control (name@ref) - Background daemon for fast completions (< 15ms) - HTTP caching with ETag/Last-Modified support - Context-aware completions with async support - Comprehensive documentation and type definitions Changes: - Add loader.ts with daemon mode - Add completions.d.ts type definitions - Add COMPLETIONS.md guide for developers - Add README.md with usage examples - Update configure_ssh_key.ts with async completions - Use JSR imports for remote execution compatibility
149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
/**
|
|
* Type definitions for script completion functions
|
|
*
|
|
* Scripts can export a `getCompletions` function to provide intelligent
|
|
* shell completion suggestions for their arguments.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* export function getCompletions(ctx: CompletionContext): string[] {
|
|
* const { current, args } = ctx;
|
|
*
|
|
* // Suggest values for --key flag
|
|
* if (current.startsWith("--key=")) {
|
|
* return ["--key=~/.ssh/id_rsa", "--key=~/.ssh/id_ed25519"];
|
|
* }
|
|
*
|
|
* // Check what's already provided
|
|
* const hasForce = args.includes("--force");
|
|
*
|
|
* // Return available flags
|
|
* return ["--force", "--dry-run", "--help"].filter(
|
|
* flag => flag !== "--force" || !hasForce
|
|
* );
|
|
* }
|
|
* ```
|
|
*/
|
|
|
|
/**
|
|
* Context provided to the completion function
|
|
*/
|
|
export interface CompletionContext {
|
|
/**
|
|
* All arguments provided so far (excluding the script name)
|
|
*
|
|
* @example
|
|
* If user typed: `scripts my-script --flag1 value1 --flag2`
|
|
* Then args would be: ["--flag1", "value1", "--flag2"]
|
|
*/
|
|
args: string[];
|
|
|
|
/**
|
|
* The current word being completed
|
|
*
|
|
* @example
|
|
* - User typed `--k` and pressed TAB → current = "--k"
|
|
* - User typed `--key=` and pressed TAB → current = "--key="
|
|
* - User typed `--port=22` and pressed TAB → current = "--port=22"
|
|
*/
|
|
current: string;
|
|
|
|
/**
|
|
* Position of the current word in the args array (0-indexed)
|
|
*
|
|
* @example
|
|
* If user typed: `scripts my-script arg1 arg2 <TAB>`
|
|
* Then position would be 2 (0=arg1, 1=arg2, 2=current)
|
|
*/
|
|
position: number;
|
|
}
|
|
|
|
/**
|
|
* Completion function that scripts should export
|
|
*
|
|
* This function is called by the loader daemon when the user requests
|
|
* shell completions. It should return an array of completion suggestions
|
|
* based on the provided context.
|
|
*
|
|
* Can be synchronous or asynchronous (return Promise<string[]>).
|
|
*
|
|
* @param ctx - Completion context with current state
|
|
* @returns Array of completion suggestions (can be empty), or Promise resolving to array
|
|
*
|
|
* @remarks
|
|
* - Return empty array if no completions are available
|
|
* - Suggestions are filtered by the shell, so return all matches
|
|
* - Can include partial matches (e.g., "--key=" to prompt for value)
|
|
* - Keep execution fast (< 50ms recommended) for responsive UX
|
|
* - Use Deno APIs to discover filesystem, environment, etc.
|
|
* - Async functions enable API calls, but keep them fast
|
|
*
|
|
* @example Simple flag completions
|
|
* ```typescript
|
|
* export function getCompletions(ctx: CompletionContext): string[] {
|
|
* return ["--verbose", "--quiet", "--help"];
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Async completions with API call
|
|
* ```typescript
|
|
* export async function getCompletions(ctx: CompletionContext): Promise<string[]> {
|
|
* const { current } = ctx;
|
|
*
|
|
* if (current.startsWith("--branch=")) {
|
|
* try {
|
|
* const response = await fetch("https://api.github.com/repos/user/repo/branches");
|
|
* const branches = await response.json();
|
|
* return branches.map(b => `--branch=${b.name}`);
|
|
* } catch {
|
|
* return ["--branch=main"];
|
|
* }
|
|
* }
|
|
*
|
|
* return ["--branch=", "--help"];
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Context-aware completions
|
|
* ```typescript
|
|
* export function getCompletions(ctx: CompletionContext): string[] {
|
|
* const { current, args } = ctx;
|
|
*
|
|
* // Complete values for flags
|
|
* if (current.startsWith("--output=")) {
|
|
* return ["--output=json", "--output=yaml", "--output=text"];
|
|
* }
|
|
*
|
|
* // Avoid duplicate flags
|
|
* const usedFlags = new Set(args.map(a => a.split("=")[0]));
|
|
*
|
|
* const allFlags = ["--output=", "--verbose", "--format="];
|
|
* return allFlags.filter(f => !usedFlags.has(f.replace("=", "")));
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Dynamic completions with filesystem
|
|
* ```typescript
|
|
* export function getCompletions(ctx: CompletionContext): string[] {
|
|
* const { current } = ctx;
|
|
*
|
|
* if (current.startsWith("--config=")) {
|
|
* try {
|
|
* // Find config files in current directory
|
|
* const files: string[] = [];
|
|
* for (const entry of Deno.readDirSync(".")) {
|
|
* if (entry.isFile && entry.name.endsWith(".json")) {
|
|
* files.push(`--config=${entry.name}`);
|
|
* }
|
|
* }
|
|
* return files;
|
|
* } catch {
|
|
* return ["--config=config.json"];
|
|
* }
|
|
* }
|
|
*
|
|
* return ["--config=", "--help"];
|
|
* }
|
|
* ```
|
|
*/
|
|
export type CompletionFunction = (ctx: CompletionContext) => string[] | Promise<string[]>;
|