scripts/README.md
Karolis2011 df5df0ea92 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
2025-11-29 23:22:35 +02:00

322 lines
9.5 KiB
Markdown

# 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