# Smartlinks agent guide

Use this document as the operational contract when helping someone create a Smartlink.

## What Smartlinks is

[Smartlinks](https://github.com/jonaslsaa/smartlinks) turns a JavaScript or TypeScript function body into a self-contained executable URL.
The CLI strictly type-checks TypeScript against the Smartlinks runtime contract when the input
filename ends in `.ts`, transpiles it, optionally minifies the emitted JavaScript, validates the
exact stored wrapper with QuickJS, seals requested secrets, compresses the payload, and returns
an execution URL. No script or per-link record is stored by the service.

The execution URL carries the script and encrypted secret blobs. A Cloudflare Worker decodes the
payload, decrypts secrets, creates a fresh QuickJS runtime, supplies a small request context, and
maps the script's return value to an HTTP response.

## Authoring contract

Input files contain an async function body, not a module or complete function. Top-level `await`
and `return` are valid. Imports and Node APIs are unavailable inside QuickJS.

The script receives `ctx` with:

- `params`: query parameters excluding names beginning with `__`.
- `paramValues`: every value for repeated query parameters, excluding reserved names.
- `method`: the incoming HTTP method.
- `headers`: incoming headers with lowercase names.
- `body`: the request body as a string or `null`.
- `secrets`: decrypted values keyed by the names supplied during build.
- `requestId`: an opaque per-execution correlation ID.
- `crypto`: SHA-256, HMAC-SHA256, and constant-time HMAC verification helpers.

`ctx.crypto.sha256(message, encoding?)`, `hmacSha256(key, message, encoding?)`, and
`verifyHmacSha256(key, message, signature, encoding?)` accept strings. Encoding defaults to
lowercase `hex`; `base64` is also supported. An execution may perform at most 16 cryptographic
operations, with at most 1 MiB of string input per operation.

Global `fetch(url, options)` accepts a string URL, method, plain headers, and a string body. It
returns a Response-like value with `status`, `statusText`, `ok`, `url`, `redirected`, `headers`,
`bodyUsed`, `text()`, and `json()`. The response body can be consumed once. Streams, `Request`,
`Blob`, `FormData`, cloning, custom redirect modes, and guest abort signals are not supported.

Return an absolute HTTP(S) URL for a redirect, `{ status?, headers?, body? }` for a literal
response, or `undefined` for the default completion page.

TypeScript is checked in isolation with strict compiler settings and built-in types for `ctx`,
global `fetch`, and valid script results. Smartlinks does not load a project `tsconfig` or resolve
imports. The execution link and decoder contain emitted JavaScript. `--no-type-check` skips
semantic checking but still transpiles TypeScript; `--no-minify` still transpiles it as well.

## CLI discovery

Smartlinks requires Node.js 18.18 or newer. In a fresh environment, check the runtime and CLI;
install the CLI from npm if it is unavailable:

- `node --version`
- `npm install --global @jonaslsa/smartlinks`
- `smartlinks --version`

Treat installed CLI help as authoritative:

- `smartlinks --help`
- `smartlinks help build`
- `smartlinks help run`
- `smartlinks help decode`

There is no separate `compile` command. `build` performs TypeScript checking and transpilation,
minification, compile-only QuickJS validation, secret sealing, compression, and link creation
without executing the guest script. There is no command named `dry-run`; `run` is the local
execution and validation path.

## `smartlinks build <script.js|script.ts>`

- `--interstitial`: require a browser confirmation before execution.
- `--secret NAME[=value]`: seal a secret; repeatable. Prefer environment values over inline values.
- `--copy`: copy the execution URL.
- `--json`: emit machine-readable output only.
- `--no-type-check`: skip strict semantic checking for TypeScript; syntax must still transpile.
- `--no-minify`: skip JavaScript minification; TypeScript is still transpiled.

The command produces an execution URL; `--json` also includes its non-executing decoder URL. Pass
either URL or the raw payload to `smartlinks decode` for inspection. `build` validates with
QuickJS's compile-only mode and never runs the script.

## `smartlinks run <script.js|script.ts>`

Use this as the local dry-run before building a final link.

- `--param NAME=value`: supply a query parameter; repeatable.
- `--secret NAME[=value]`: supply a local secret; repeatable.
- `--header NAME=value`: supply a request header; repeatable.
- `--method METHOD`: set the request method; defaults to `GET`.
- `--body TEXT`: set a request body; invalid for `GET` and `HEAD`.
- `--allow-network`: enable guarded `fetch`; networking is disabled by default locally.
- `--json`: emit the mapped response as machine-readable output.
- `--no-type-check`: skip strict semantic checking for TypeScript; syntax must still transpile.
- `--no-minify`: skip JavaScript minification; TypeScript is still transpiled.

Local execution uses the same wrapper, QuickJS engine, request-context normalization, general
URL/method/header/body/count/redirect policy, and response mapping as production.

## Other commands

`smartlinks decode <link-or-payload> [--json]` inspects the emitted script and metadata without
executing it or decrypting secrets.

## Secrets and authority

The CLI fetches the runtime's public key and encrypts each requested secret locally. Ciphertext is
bound to the active key ID and the exact emitted script, so it cannot be moved to a modified
script. The private key remains a Worker secret.

Encryption hides values from URL inspection; it does not make the execution URL private. Anyone
with the complete URL can invoke the script with its sealed authority. Prefer narrowly scoped,
revocable credentials. Avoid inline `NAME=value` secrets because shell history can retain them.

## Runtime and link limits

- Execution links are immutable and have no authentication, revocation list, or per-link
  analytics. The hosted runtime is rate-limited for fair-use of this service; excess executions return HTTP 429.
- Encoded payloads are limited to 7,800 characters. Raw and emitted source have a one-million-
  character wrong-file safety guard; minification and compression determine whether the URL fits.
- Each request gets a fresh QuickJS runtime with a 16 MiB heap, 512 KiB stack, deterministic
  1,500-interrupt-poll budget, and 15-second host-wait deadline. Interrupt polls are not CPU-time
  measurements.
- `fetch` permits HTTP(S), blocks local hostnames and private/local/reserved IP literals,
  limits same-origin redirects to three, total requests to five, request and response bodies to
  1 MiB, and each fetch to ten seconds. Cross-origin redirects are rejected. Local
  `smartlinks run --allow-network` additionally resolves and pins DNS connections to validated
  public addresses.
- Known crawler, preview, prefetch, and `HEAD` requests do not execute scripts. Detection is
  intentionally best-effort.
- Browser and intermediary URL limits vary; shorter links are preferable even below the hard cap.

Keep the script small, explicit, and least-privileged. Use `run` first, inspect the decoder output,
then use `build` for the final immutable link.
