| name | generate-claude-remote-build-script |
| description | Generate the setup/build script (and env-var template) to paste into a Claude Code remote routine environment so this repo runs in the cloud. Runs /lisa:analyze-claude-remote to inventory needs, then writes an idempotent, detect-before-install bash script that installs the required CLIs/binaries and package manager for both Lisa and the host project, plus a commented environment-variable template (names only, never real secrets) and a list of custom domains to allowlist. The script is fast (fits the ~5-minute environment-cache budget), re-runnable, and cloud-proxy aware. |
| allowed-tools | ["Skill","Bash","Read","Write","Glob","Grep"] |
Generate Claude Remote Build Script: $ARGUMENTS
Produce the artifacts a user pastes into a Claude Code remote routine environment so this repo
runs in the cloud: a setup/build script that installs everything the environment needs, plus an
environment-variable template and a network-allowlist list.
Purpose
A routine's cloud environment lets you configure a setup script (runs once, cached) and
environment variables. This skill turns the read-only inventory from
/lisa:analyze-claude-remote into those concrete artifacts so the user doesn't hand-assemble them.
This skill ships in the base Lisa plugin and is distributed to every host project, so the generated
script must reflect what this repo actually needs — Lisa's startup hooks and configured
tracker/source, plus the host project's own package manager and tooling — not a hardcoded list.
Inputs
- Optional flags in
$ARGUMENTS:
--out=<path> — where to write the script. Default scripts/claude-remote-setup.sh.
--include-optional — also install OPTIONAL (dormant-stack) tools. Default: required only.
--print — print the script to stdout instead of writing a file.
Procedure
-
Inventory. Invoke /lisa:analyze-claude-remote --json and parse its machine-readable
inventory block (packageManager, tools, env, mcp, gaps, platform,
networkAccess, allowlistDomains, awsProfiles). If the analysis cannot run, stop and report why — never
emit a script from guesses.
-
Compose the setup script from the inventory. The script must be:
- Idempotent & detect-before-install — every install guarded by a
command -v <tool> check
so re-runs are no-ops and already-present tools are skipped.
- Fast — fits the ~5-minute environment-cache budget; avoid heavyweight installs unless
REQUIRED. Long/optional installs (docker images, chromium, ruby) go in a clearly-marked
optional section gated by --include-optional.
- PATH-correct — export the package manager's bin dir (e.g.
$HOME/.bun/bin) after install
so subsequent steps and the cached environment resolve it.
- Cloud-proxy aware — when the package manager is flagged
RISK (bun), add a comment
documenting the known proxy package-fetch issue and, where safe, run the install with retries
so a transient proxy failure doesn't poison the cache. Do not silently swap package managers if
engines forbids it; surface the risk as a comment instead.
- Non-fatal on optional tools —
REQUIRED tool failures should exit non-zero (so the env
build fails loudly); OPTIONAL tool failures should warn and continue.
-
Emit the environment-variable template. Write a commented block listing every env entry
from the inventory grouped by integration, marked REQUIRED/OPTIONAL and secret/plain,
with the reason. Never write real secret values — only names and placeholders, because the
environment config is visible to anyone who can edit it. Entries flagged providedBy: settings.json
(the committed .claude/settings.json env flags, e.g. CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS) are
already applied from the committed file — list them under an # Already provided by committed .claude/settings.json — no UI entry needed heading, not as values to set. The "set in the
environment UI" template is for secrets only. For every secret entry that carries
acquireUrl/accessScope/headlessSubstrate (the
active tracker/source credentials from the analysis's group 4a), render those as comment lines
directly above the name — # Acquire: <url> and # Access: <scope> — so the user knows exactly
where to get the token and what permissions it needs. Emit only the env-var form of the name
that the analysis reported (including any per-account suffixed form like LINEAR_API_KEY_<slug>);
never emit a keychain instruction — keychain does not exist in a cloud routine.
When the entry is GH_TOKEN, add a comment from platform.githubProxy clarifying that the token
is for gh CLI commands against the project/Lisa repos, not for raw git clone/fetch/push or
sibling repos reachable through the routine's GitHub proxy.
For OPTIONAL non-tracker MCP recovery entries discovered by
/lisa:analyze-claude-remote, preserve the same names-only behavior:
include JAM_PAT, SONAR_TOKEN, or similar documented substrate env vars
only as optional secrets, with their acquire/scope comments when the analysis
supplied them. Never invent values or promote dormant substrates to required.
When AWS entries are present, list AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional
AWS_SESSION_TOKEN, and AWS_DEFAULT_REGION exactly as the analysis reported. State that these
bootstrap credentials are for STS assume-role only; do not emit or recommend aws sso login.
3a. Emit substrate setup snippets. When the inventory marks an MCP
headlessUsable: true through a documented substrate, render the matching
wiring guidance as commented, opt-in setup:
- CLI substrates: emit the detected install/login commands gated on the
optional env var. For Jam, this means
curl -fsSL https://native.jam.dev/install | bash,
export PATH="$HOME/.local/bin:$PATH", printf '%s' "$JAM_PAT" | jam auth login --token,
and jam skills install, all inside [ -n "${JAM_PAT:-}" ] && ... guards so missing
optional secrets do not fail the environment build.
- REST substitute substrates: do not install an MCP. Emit comments naming the
REST host and env var (for example
SONAR_TOKEN with https://sonarcloud.io/api/) and
rely on the access skill or generated consumer to call the API.
- PAT-bearer MCP substrates: print a commented
.mcp.json headers snippet from the
inventory's mcpHeaders. Use this only when the analysis explicitly says the same MCP
transport supports static-token auth. Do not print a Jam .mcp.json header snippet because
Jam's preferred headless substrate is its PAT-authenticated CLI.
3b. Emit AWS assume-role profile setup. When the inventory includes awsProfiles, write an
idempotent ~/.aws/config block gated on [ -n "${AWS_ACCESS_KEY_ID:-}" ]. The generated block
must:
mkdir -p "$HOME/.aws" and create or append profile stanzas without writing secrets.
- Emit one
[profile <name>] stanza per inventory profile with role_arn,
credential_source = Environment, region when present, and external_id when present.
- Keep regions and ExternalIds as non-secret project metadata from the inventory; never invent
account IDs, role names, profile names, regions, or ExternalIds.
- Warn and skip profile writing when AWS profile metadata is absent, while still listing the
required
AWS_* env var names in the secret template.
- Include a comment that agents should use
aws --profile <name> ... and must not run
aws sso login in headless routines.
-
Emit the allowlist + gaps notice. List any custom domains the setup or runtime reaches
(from networkAccess.allowlistDomains, falling back to legacy allowlistDomains) that the user
must add when the environment needs Custom network access. Do not include default Trusted domains
such as GitHub, npm/PyPI registries, or Docker Hub. Echo the gaps from the analysis
(auto-memory not synced, interactive-auth/stdio-MCP unavailable, etc.) and the
platform.secretsVisibility warning as a header comment so the user knows what the script
cannot fix.
-
Write and report. Write the script to --out (default scripts/claude-remote-setup.sh),
chmod +x it, and print: the path, a one-line summary of what it installs and which env vars to
set, and the exact next step (paste its contents — or a bash scripts/claude-remote-setup.sh
invocation — into the routine environment's setup script, and add the env vars in the
environment config). When --print is passed, print to stdout and do not write a file.
Generated script shape
The emitted script should follow this skeleton (populated from the live inventory — this is the
shape, not a fixed payload):
#!/usr/bin/env bash
set -uo pipefail
need() { command -v "$1" >/dev/null 2>&1; }
require() { need "$1" || { echo "FATAL: required tool '$1' missing and install failed" >&2; exit 1; }; }
detect_package_manager() {
_field="" _forced="" _forbidden=""
if [ -f package.json ] && command -v jq >/dev/null 2>&1; then
_field=$(jq -r '(.packageManager // "") | sub("@.*$";"")' package.json 2>/dev/null)
_forced=$(jq -r 'first((.engines // {})[] | strings | capture("please-use-(?<pm>bun|npm|yarn|pnpm)")?.pm) // ""' package.json 2>/dev/null)
_forbidden=$(jq -r '[(.engines // {}) | to_entries[] | select(((.value|strings) // "") | test("please-use|do-not-use";"i")) | .key] | join(" ")' package.json 2>/dev/null)
fi
case "$_field" in bun | npm | yarn | pnpm) printf '%s\n' "$_field"; return 0 ;; esac
case "$_forced" in bun | npm | yarn | pnpm) printf '%s\n' "$_forced"; return 0 ;; esac
_pm_allowed() { case " $_forbidden " in *" $1 "*) return 1 ;; *) return 0 ;; esac; }
{ [ -f bun.lockb ] || [ -f bun.lock ]; } && _pm_allowed bun && { printf 'bun\n'; return 0; }
[ -f pnpm-lock.yaml ] && _pm_allowed pnpm && { printf 'pnpm\n'; return 0; }
[ -f yarn.lock ] && _pm_allowed yarn && { printf 'yarn\n'; return 0; }
[ -f package-lock.json ] && _pm_allowed npm && { printf 'npm\n'; return 0; }
printf 'npm\n'
}
PM="$(detect_package_manager)"
if [ "$PM" = "bun" ] && ! need bun; then
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
fi
for i in 1 2 3; do "$PM" install && break || sleep 5; done
need gh || (sudo apt-get update -y && sudo apt-get install -y gh)
need jq || sudo apt-get install -y jq
require gh; require jq
if [ -n "${AWS_ACCESS_KEY_ID:-}" ]; then
mkdir -p "$HOME/.aws"
fi
Confirmation policy
Writing the script file is the deliverable — do not ask whether to proceed. Default to writing
scripts/claude-remote-setup.sh, then report the path and next steps. The only legitimate reasons
to stop are: the analysis could not run, or the --out path is not writable.
Rules
- Always derive the script from a fresh
/lisa:analyze-claude-remote run — never from a stale or
assumed inventory.
- Never write real secret values into the script or template — names and placeholders only.
- For active tracker/source credentials, carry the analysis's
Acquire: URL and Access: scope into
the template as comments, and emit only the env-var form of the name — never a keychain command.
- For
GH_TOKEN, preserve the analysis's GitHub proxy split: it is for gh CLI commands only, and
raw git/cross-repo clone guidance must not expand the token scope to sibling repositories.
- Never emit an install for a tool the analysis did not surface, and never install
OPTIONAL tools
unless --include-optional is set.
- When AWS is in the inventory, generate environment-backed assume-role profile stanzas from
awsProfiles; never emit aws sso login as a remote setup step.
- Prefer
networkAccess.allowlistDomains over legacy top-level allowlistDomains; never emit
domains already covered by the routine environment's default Trusted list.
- Keep the script idempotent and detect-before-install so it is safe to re-run and cache.
- Preserve the inventory's
REQUIRED vs OPTIONAL distinction in both fail-behavior (fatal vs
warn) and section placement.
- Surface, never hide, the
gaps — a generated script must not imply it makes a repo fully
cloud-ready when known constraints remain.