Add remote script loader with intelligent completions
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
This commit is contained in:
parent
616a2d9c96
commit
df5df0ea92
7 changed files with 1448 additions and 3 deletions
322
README.md
Normal file
322
README.md
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
# Remote Script Loader
|
||||
|
||||
A lightweight, intelligent script loader for Deno that enables remote script execution with shell completions, version control, and caching.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Remote Execution** - Run scripts directly from Git without cloning
|
||||
- 🔄 **Version Control** - Execute scripts from specific branches or tags (`script@v1.0`)
|
||||
- ⚡ **Smart Completions** - Context-aware shell completions with filesystem discovery
|
||||
- 💾 **Intelligent Caching** - HTTP caching with ETag/Last-Modified support
|
||||
- 🔒 **Fast Daemon** - Background daemon for instant completions (< 15ms)
|
||||
- 📦 **Zero Config** - Works out of the box with bash and zsh
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
Add to your `~/.bashrc` or `~/.zshrc`:
|
||||
|
||||
```bash
|
||||
# Load remote script loader from main branch
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.kkarolis.lt/Karolis/scripts/raw/branch/main/loader.ts)"
|
||||
```
|
||||
|
||||
Or pin to a specific version:
|
||||
|
||||
```bash
|
||||
# Load from a specific tag
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.kkarolis.lt/Karolis/scripts/raw/tag/v1.0.0/loader.ts)"
|
||||
```
|
||||
|
||||
Or use a local copy:
|
||||
|
||||
```bash
|
||||
# Load from local file (for development)
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet /path/to/scripts/loader.ts)"
|
||||
```
|
||||
|
||||
### Using Custom Refs
|
||||
|
||||
Set the `LOADER_REF` environment variable to use a different default branch:
|
||||
|
||||
```bash
|
||||
# Use a development branch by default
|
||||
export LOADER_REF=develop
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.kkarolis.lt/Karolis/scripts/raw/branch/develop/loader.ts)"
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# List available scripts
|
||||
scripts
|
||||
|
||||
# Run a script
|
||||
scripts configure_ssh_key user@host --key=~/.ssh/id_rsa
|
||||
|
||||
# Run script from a specific branch
|
||||
scripts configure_ssh_key@develop user@host
|
||||
|
||||
# Run script from a specific tag
|
||||
scripts configure_ssh_key@v1.0.0 user@host
|
||||
```
|
||||
|
||||
### Shell Completions
|
||||
|
||||
Press `TAB` to get intelligent completions:
|
||||
|
||||
```bash
|
||||
scripts <TAB> # Lists all available scripts
|
||||
scripts conf<TAB> # Completes to configure_ssh_key
|
||||
scripts configure_ssh_key@<TAB> # Lists available branches and tags
|
||||
scripts configure_ssh_key --<TAB> # Shows script-specific flags
|
||||
scripts configure_ssh_key --key=<TAB> # Suggests SSH keys from ~/.ssh/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- **`LOADER_REF`** - Default branch/tag to use (default: `main`)
|
||||
- **`SCRIPTS_DAEMON_PORT`** - Force specific port for daemon (default: auto-assign)
|
||||
- **`SCRIPTS_DAEMON_PORT_FILE`** - Port file location (default: `/tmp/scripts-daemon.port`)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Use staging branch by default
|
||||
export LOADER_REF=staging
|
||||
|
||||
# Force daemon to use specific port
|
||||
export SCRIPTS_DAEMON_PORT=54321
|
||||
|
||||
# Custom port file location
|
||||
export SCRIPTS_DAEMON_PORT_FILE=~/.cache/scripts-daemon.port
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Initial Load**: When sourced, the loader:
|
||||
- Spawns a background daemon for completions
|
||||
- Fetches the list of available scripts
|
||||
- Defines the `scripts` shell function
|
||||
|
||||
2. **Daemon**: A persistent HTTP server that:
|
||||
- Caches script lists and branch/tag information (60s TTL)
|
||||
- Caches script modules with HTTP ETag/Last-Modified (300s TTL)
|
||||
- Provides fast completions (< 15ms after first request)
|
||||
|
||||
3. **Execution**: When you run a script:
|
||||
- Validates the script exists on the remote
|
||||
- Downloads and executes it with Deno
|
||||
- All script arguments are passed through
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Shell (bash/zsh) │
|
||||
│ scripts configure_ssh_key --key=~/.ssh/id_rsa <TAB> │
|
||||
└──────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
├─ Execution: Direct to Deno
|
||||
│ │
|
||||
│ └─> Deno runs remote script
|
||||
│
|
||||
└─ Completion: Query daemon
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Daemon (port 54xxx) │
|
||||
│ ─────────────────── │
|
||||
│ • Caches metadata │
|
||||
│ • Caches modules │
|
||||
│ • HTTP conditional │
|
||||
│ requests (304) │
|
||||
└──────────────────────┘
|
||||
│
|
||||
├─> Script List API
|
||||
├─> Branches/Tags API
|
||||
└─> Script Module Import
|
||||
```
|
||||
|
||||
## Writing Scripts
|
||||
|
||||
See [COMPLETIONS.md](./COMPLETIONS.md) for a comprehensive guide on adding intelligent completions to your scripts.
|
||||
|
||||
### Minimal Example
|
||||
|
||||
```typescript
|
||||
#!/usr/bin/env -S deno run --allow-all
|
||||
import type { CompletionContext } from "./completions.d.ts";
|
||||
|
||||
// Export completion function (optional)
|
||||
export function getCompletions(ctx: CompletionContext): string[] {
|
||||
return ["--verbose", "--help"];
|
||||
}
|
||||
|
||||
// Your script logic
|
||||
console.log("Hello from remote script!");
|
||||
```
|
||||
|
||||
### Using Dependencies
|
||||
|
||||
For remote scripts to work, use explicit JSR imports instead of bare specifiers:
|
||||
|
||||
```typescript
|
||||
// ❌ Won't work for remote scripts
|
||||
import { parseArgs } from "@std/cli/parse-args";
|
||||
|
||||
// ✅ Use JSR imports for remote execution
|
||||
import { parseArgs } from "jsr:@std/cli@^1.0.0/parse-args";
|
||||
```
|
||||
|
||||
This ensures dependencies are resolved when the script is executed remotely via URL.
|
||||
|
||||
### Async Completions
|
||||
|
||||
```typescript
|
||||
export async function getCompletions(ctx: CompletionContext): Promise<string[]> {
|
||||
const { current } = ctx;
|
||||
|
||||
if (current.startsWith("--key=")) {
|
||||
// Discover SSH keys asynchronously
|
||||
const keys: string[] = [];
|
||||
for await (const entry of Deno.readDir(`${Deno.env.get("HOME")}/.ssh`)) {
|
||||
if (entry.isFile && entry.name.startsWith("id_")) {
|
||||
keys.push(`--key=~/.ssh/${entry.name}`);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
return ["--key=", "--help"];
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
| Resource | Cache Duration | Strategy |
|
||||
|----------|----------------|----------|
|
||||
| Script List | 60 seconds | In-memory |
|
||||
| Branches/Tags | 60 seconds | In-memory |
|
||||
| Script Modules | 300 seconds | HTTP ETag/Last-Modified |
|
||||
|
||||
### Benchmarks
|
||||
|
||||
- **First completion**: ~120ms (network + parse)
|
||||
- **Cached completion**: ~14ms (8x faster)
|
||||
- **Daemon startup**: ~100ms
|
||||
- **Loader script**: Exits immediately after spawning daemon
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Daemon Not Starting
|
||||
|
||||
Check if the daemon is running:
|
||||
|
||||
```bash
|
||||
ps aux | grep "loader.ts.*daemon"
|
||||
cat /tmp/scripts-daemon.port
|
||||
curl http://127.0.0.1:$(cat /tmp/scripts-daemon.port)/health
|
||||
```
|
||||
|
||||
Restart the daemon:
|
||||
|
||||
```bash
|
||||
pkill -f "loader.ts.*daemon"
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet loader.ts)"
|
||||
```
|
||||
|
||||
### Completions Not Working
|
||||
|
||||
1. Verify daemon is running (see above)
|
||||
2. Check completion function is exported:
|
||||
```bash
|
||||
deno eval 'const m = await import("file:///path/to/script.ts"); console.log(typeof m.getCompletions)'
|
||||
```
|
||||
3. Test completion function directly:
|
||||
```bash
|
||||
deno eval 'const m = await import("file:///path/to/script.ts"); console.log(await m.getCompletions({args: [], current: "--", position: 0}))'
|
||||
```
|
||||
|
||||
### Slow Completions
|
||||
|
||||
- Check network latency to Git server
|
||||
- Verify caching is working (second request should be faster)
|
||||
- Consider increasing cache TTL in daemon code
|
||||
- Avoid expensive operations in completion functions
|
||||
|
||||
### Scripts Not Found
|
||||
|
||||
```bash
|
||||
# Check script exists in repository
|
||||
scripts
|
||||
|
||||
# Verify URL is accessible
|
||||
curl -I https://git.kkarolis.lt/Karolis/scripts/raw/branch/main/script-name.ts
|
||||
|
||||
# Try with explicit ref
|
||||
scripts script-name@main
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Script Repositories
|
||||
|
||||
You can have multiple loader instances with different configurations:
|
||||
|
||||
```bash
|
||||
# Personal scripts
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.example.com/me/scripts/raw/branch/main/loader.ts)"
|
||||
alias myscripts='scripts'
|
||||
|
||||
# Work scripts
|
||||
export LOADER_REF=production
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.company.com/team/scripts/raw/branch/production/loader.ts)"
|
||||
alias workscripts='scripts'
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Use local loader for testing
|
||||
cd ~/scripts
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet ./loader.ts)"
|
||||
|
||||
# Test your script
|
||||
scripts my-new-script --help
|
||||
|
||||
# Push and use remote version
|
||||
git push
|
||||
export LOADER_REF=main
|
||||
eval "$(deno run --allow-net --allow-read --allow-env --allow-run --quiet https://git.kkarolis.lt/Karolis/scripts/raw/branch/main/loader.ts)"
|
||||
scripts my-new-script@main --help
|
||||
```
|
||||
|
||||
## Files in This Repository
|
||||
|
||||
- **`loader.ts`** - Main loader script with daemon
|
||||
- **`completions.d.ts`** - TypeScript definitions for completion functions
|
||||
- **`COMPLETIONS.md`** - Comprehensive guide for script developers
|
||||
- **`configure_ssh_key.ts`** - Example script with advanced completions
|
||||
- **`README.md`** - This file
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Deno](https://deno.land/) 1.30+ installed
|
||||
- Bash or Zsh shell
|
||||
- Network access to Git server
|
||||
- `curl` for health checks (in completion functions)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Loading…
Add table
Add a link
Reference in a new issue