Packages

Packages are ZIP archives containing Scriptling libraries that can be loaded from local files or URLs. They enable easy distribution and reuse of code.

Overview

A package is a ZIP file containing:

mylib.zip
├── manifest.toml    # Required - package metadata
├── lib/             # Required - Python modules
│   ├── __init__.py
│   └── utils.py
└── docs/            # Optional - documentation
    └── guide.md

Package Manifest

The manifest.toml file describes the package:

name = "mylib"
version = "1.0.0"
description = "A useful library"
main = "app.main"    # Optional: entry point for running

Fields:

Field Required Description
name Yes Package name
version Yes Version string
description No Brief description
main No Entry point as module.function for running

Loading Packages

Use the --package (or -p) flag to load packages:

# Load from local file
scriptling --package ./libs/mylib.zip script.py

# Load from URL
scriptling --package https://example.com/libs/mylib.zip script.py

# Load multiple packages
scriptling --package core.zip --package utils.zip script.py

Package Priority

When loading multiple packages, the last one has highest priority. Later packages can override modules from earlier ones:

# override.zip can shadow modules from core.zip
scriptling --package core.zip --package override.zip script.py

Self-Signed Certificates

Use --insecure (or -k) to allow self-signed HTTPS certificates:

scriptling --insecure --package https://self-signed.local/lib.zip script.py

Hash Verification

Verify package integrity by specifying an expected SHA256 hash:

# Verify package hash before loading
scriptling --package mylib.zip#sha256=abc123... script.py

# With URL (download and verify)
scriptling --package https://example.com/lib.zip#sha256=abc123... script.py

How it works:

  • Append #sha256=<hash> to the package path or URL
  • Scriptling computes the SHA256 hash after fetching
  • If the hash doesn’t match, loading fails with an error
  • For local files, the hash is optional (no hash = no verification)
  • For remote URLs, this ensures the package hasn’t been tampered with

Getting the hash:

When you create a package, the hash is printed:

scriptling pack ./mylib -o mylib.zip
# Output includes: sha256=abc123def456...

Or use the manifest command to print a package’s metadata:

scriptling pack manifest mylib.zip
# Shows: Name, Version, Description, and Main fields from the manifest

Custom Cache Directory

Remote packages are cached locally. Override the cache location with --cache-dir:

scriptling --cache-dir ./cache --package https://example.com/lib.zip script.py

Or set the SCRIPTLING_CACHE_DIR environment variable.

Running Packages

If a package defines a main entry point in its manifest, you can run it directly:

# Run the package's main function
scriptling --package mylib.zip

# With arguments
scriptling --package mylib.zip -- arg1 arg2

Execution order:

  1. If -c given → execute inline code
  2. Else if --interactive → start REPL
  3. Else if script file or stdin → execute script
  4. Else if packages with main → run entry point from last package
  5. Else → error

Inline Code

Use -c to execute inline code with packages loaded:

scriptling --package mylib.zip -c "import utils; print(utils.hello('World'))"

Creating Packages

Package Structure

Create a directory with your code:

mylib/
├── manifest.toml
├── lib/
│   ├── __init__.py
│   ├── utils.py
│   └── submodule/
│       ├── __init__.py
│       └── helpers.py
└── docs/
    └── guide.md

Pack Command

Create a package from a directory:

# Create package
scriptling pack ./mylib -o mylib.zip

# Overwrite existing
scriptling pack ./mylib -o mylib.zip -f

The SHA256 hash is printed on success: use it with #sha256=... to verify integrity on load.

Unpack Command

Extract a package for development:

# Unpack to current directory
scriptling unpack mylib.zip

# Unpack to specific directory
scriptling unpack mylib.zip -d ./mylib-dev

# List contents without extracting
scriptling unpack mylib.zip --list

# From URL
scriptling unpack https://example.com/lib.zip -d ./lib

Viewing Package Information

Manifest Command

View package metadata:

# From local package
scriptling pack manifest mylib.zip

# From URL
scriptling pack manifest https://example.com/lib.zip

# From source directory
scriptling pack manifest ./mylib

# JSON output
scriptling pack manifest mylib.zip --json

Docs Command

Browse package documentation interactively:

# Launch TUI browser
scriptling pack docs mylib.zip

# From URL
scriptling pack docs https://example.com/lib.zip

# From unpacked directory
scriptling pack docs ./mylib-dev

# List docs without TUI
scriptling pack docs mylib.zip --list

Cache Management

Remote packages (http:// and https://) are cached locally to avoid redundant downloads. Scriptling uses HTTP conditional requests to check for updates efficiently.

How Caching Works

When loading a remote package:

  1. First download - Package is cached to disk along with its ETag and Last-Modified headers
  2. Subsequent loads - Scriptling sends a conditional GET request with:
    • If-None-Match: <etag> - if the server provided an ETag
    • If-Modified-Since: <last-modified> - if the server provided Last-Modified
  3. If server responds 304 Not Modified - Uses cached copy (no body transferred)
  4. If server responds 200 OK - Downloads and caches the updated package

This means:

  • Single request - One GET request, whether cached or not
  • Automatic updates - New versions are fetched immediately when available
  • Bandwidth efficient - No body transferred when unchanged

Cache Location

Default cache directory:

Platform Location
macOS ~/Library/Caches/scriptling/packages/
Linux ~/.cache/scriptling/packages/
Windows %LOCALAPPDATA%\scriptling\packages\

Override with --cache-dir or SCRIPTLING_CACHE_DIR environment variable.

Cache Commands

# Clear all cached packages
scriptling cache clear

Cache TTL

Cached packages are automatically pruned after 7 days of non-use. Each access resets the TTL, so frequently used packages stay cached indefinitely.

Using Packages in Code

Once loaded, packages work like any other module:

# Import from package
import utils
from submodule import helpers

# Use functions
result = utils.process("data")
helpers.format(result)

App Bundles

A package with a manifest.toml is a self-contained unit — code, data, and metadata shipped as one folder or zip. Three types exist:

  • App bundle (serve declared): starts an HTTP, MCP, or JSON-RPC server.
  • Script package (main declared, no serve): runs the entry-point script and exits — a standalone tool packaged with its libraries and data.
  • Library pack (no main, no serve): provides importable modules only.

Manifest

name = "myapp"                             # REQUIRED — unique across loaded packages
version = "1.0.0"                          # REQUIRED
main = "setup.py"                          # optional: .py file or "module.function"
libs = ["lib", "vendor"]                   # optional: module search dirs (default ["lib"])
serve = ["http", "mcp"]                    # optional: protocols to serve
additional_files = ["data/", "LICENSE"]    # optional: extra dirs/files to include
Field Required Description
name yes Package name. Used by scriptling.package for file access. Must be unique across loaded packages.
version yes Version string (e.g., "1.0.0").
main no Entry point: a .py file path (runs top-level) or module.function. Without serve, the script runs and exits.
libs no Module search dirs inside the package, searched in order. Default ["lib"].
serve no Protocols to serve (e.g. ["http", "mcp"]). When present, the package starts a server instead of running and exiting.
additional_files no Extra files or directories to include. A trailing / includes the entire directory tree; a bare path includes a single file.

Transport

The serve list declares what the app provides, not how it’s reached. The CLI flags decide the transport:

CLI flags What happens
--package . (no --server) MCP or JSON-RPC over stdio (whichever is in serve)
--server :8000 --package . All declared protocols over HTTP: MCP at /mcp, JSON-RPC at /json-rpc, HTTP routes at their registered paths

So serve = ["mcp"] works for both scriptling --package . (stdio) and scriptling --server :8000 --package . (HTTP at /mcp).

Convention Dirs

These top-level dirs are auto-discovered when present:

Dir Protocol Contents
tools/ mcp .py + .toml pairs (MCP tools)
resources/ mcp Resource tree (static files and {var} templates)
prompts/ mcp .toml + .py pairs or .md/.txt (MCP prompts)
webroot/ http Static assets served at the HTTP root
docs/ Documentation viewer

Additional Files

Declare extra files or directories in the manifest to ship data, specs, or configuration alongside your code:

additional_files = ["data/", "LICENSE", "templates/"]

A trailing / includes the entire directory tree; a bare path includes a single file. These are packed into the zip alongside the libs and convention dirs.

At runtime, files inside a package — including those from additional_files — are accessible via the scriptling.package library. This works identically in directory mode and zip mode:

import scriptling.package as package

# Read a file shipped via additional_files
spec = package.read_file("myapp", "data/spec.md")

# List all loaded packages
for name in package.names():
    print(name)

# Glob for files
for f in package.glob("myapp", "**/*.md"):
    print(f)

Every function takes the package name (from the manifest’s name field) as its first argument, so there’s no ambiguity when multiple packages are loaded. Use package.exists("name") to check if a package is loaded, and package.file_exists("name", "path") to check for a specific file.

Note: os.read_file reads from the real filesystem only — it cannot read files inside a zip package. Use scriptling.package for that.

Running

# Development — run from a folder (hot-reloadable)
scriptling --server :8000 --package ./myapp          # HTTP (all serve protocols)
scriptling --package ./myapp                           # stdio (MCP or JSON-RPC)

# Production — run from a zip
scriptling pack ./myapp myapp.zip
scriptling --server :8000 --package myapp.zip
scriptling --server :8000 --package https://host/myapp.zip#sha256=...

In app-bundle mode the CLI rejects path/registration flags (-L, --script, --mcp-tools, --mcp-resources, --mcp-prompts, --web-root, --code, --interactive) because the manifest owns them. Deployment flags (--server, --tls-*, --bearer-token, secrets) remain valid.

Extra positional arguments after -- are available to tools and handlers via sys.argv — useful for conditional tool registration (e.g. gating write tools behind -- --allow-write). See Conditional Tool Registration for details.

main Resolution

main accepts two forms, resolved at boot by lookup order:

  1. Ends in .py and the file exists → run the file top-level (the bundle analogue of --script).
  2. Otherwise → module.function (eval import mod + mod.fn()).
  3. Neither resolves → boot error.

So main = "setup.py" runs the file; main = "demo.run" calls the function. main = "foo.py" with no such file falls back to module foo, function py.

Library Packs (without serve)

A package without serve is a library pack — it provides importable modules only, exactly as before. The --package flag accepts multiple library packs alongside one app bundle:

scriptling --server :8000 --package ./myapp --package ./vendor-deps.zip

Build Inclusion

pack build includes exactly: manifest.toml, every libs dir, the main script file, and the convention dirs when present. Dotfiles are excluded silently; anything else at the top level produces a warning. Missing declared libs dirs or main scripts are build errors.

Examples

  • examples/app-bundle/ — reference HTTP + MCP app with routes, tools and webroot.
  • examples/jsonrpc-package/ — JSON-RPC server shipped as a package (stdio + HTTP).
  • examples/sample-package/ — classic library pack (no serve, proves backward compatibility).

Distribution

Share packages via any HTTP server:

# Create and upload
scriptling pack ./mylib -o mylib.zip
scp mylib.zip server:/var/www/libs/

# Others can use directly
scriptling --package https://yourserver.com/libs/mylib.zip app.py

See Also