Deploy Engine
SkillCloud & infraDeploy or share generated HQ artifacts through hq-deploy.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Deploy Engine skill
What this skill tells your AI
The instructions your AI receives, as published by indigoai-us/hq-core in .claude/skills/deploy/SKILL.md and read by ahel’s review.
Skill for deploying web artifacts to hq-deploy infrastructure. Invoked directly via /deploy, or auto-triggered by auto-deploy-on-create (silent post-build) and hq-deploy-reinforcement (intent-to-share, deliverable PRDs) policies. The two paths share this same engine.
Guiding principle: quick casual handoff — preview, upload, link. Sensitive artifacts get the lowest-friction appropriate gate: password, Cognito company access, or an email allowlist when the user names recipients.
Access modes (reference)
Every deployed app has exactly one edge access mode. New policy-aware deploys prefer the first-class access-policy endpoint for Cognito gates; the legacy access-mode endpoint remains the right path for password and email/domain allowlists.
| Mode | When to pick it | What it does |
|---|---|---|
public | Default. Casual handoff, anyone with the link. | No gate. App serves immediately. |
password | Sensitive content, casual share over Slack/email, recipients unknown ahead of time. | App owner sets a password (Argon2id-hashed). Visitors land on hq.{your-domain}.com/__access, enter the password, get a 24h hq-access JWT cookie scoped to .{your-domain}.com. |
company | Internal/company-only share; user says "restricted to org", "internal-only", "company-only"; or config prefers org-restricted deploys. | Visitors sign in with HQ Cognito on hq.{your-domain}.com/__access; the HQ access service checks active membership in the app's company before minting a policy-versioned hq-access cookie. |
selected | Specific HQ people/groups when the caller has resolvable HQ directory IDs. | Same Cognito flow as company, but only selected user/group IDs in the policy are accepted. Use only when IDs are known from the HQ directory, not from free-form names. |
private | Legacy sensitive sharing for known recipients by email/domain when Cognito company membership is not the desired gate. | Visitors must be signed in to hq-auth (auth.{your-domain}.com) AND their email must be on the app's allowlist. Lands on hq.{your-domain}.com/__private, which checks the session + allowlist and mints the same hq-access JWT. |
Pick company when the user asks for org/company/internal restriction. Pick private over password when the user gives concrete email/domain recipients ("share with [EMAIL] and the @example.com team") and did not ask for company-wide Cognito access. Pick password when sensitivity is detected but recipients are unspecified and config does not prefer org restriction.
Canonical mutation endpoint for switching between modes:
POST /api/apps/:id/access-mode {mode, password?}— atomic; clears the fields that don't belong to the chosen mode; wipes EmailGrant rows when leavingprivateso orphans can't silently re-activate on a future flip back.PUT /api/apps/:id/access-policy {mode, companyUid, users?, groups?, password?}— first-class policy endpoint forcompany,selected, and policy-versionedpassword. Use this for Cognito org gates.
Legacy path gotcha: PATCH /api/apps/:id {passwordProtected, password} is rejected with 409 ACCESS_MODE_CONFLICT when the app is currently in private mode. Always use /access-mode to change modes; reserve PATCH for in-mode password rotation.
Email allowlist CRUD (only relevant in private mode):
GET /api/apps/:id/allowed-emailsPOST /api/apps/:id/allowed-emails {email}— accepts an exact address ([EMAIL]) or a@domain.tldpattern; idempotent; lowercased server-side.DELETE /api/apps/:id/allowed-emails/{patternKey}—patternKeyURL-encoded.
Comments (opt-in) — --comments on|off: orthogonal to access mode. Turn it on when the user wants identity-verified, click-anywhere commenting on the deploy: viewers drop a point pin or drag a box/region, tagged to who they are, the owner reads/resolves/deletes from a side pane, and a "Sign in to comment" Cognito prompt turns viewers into HQ users. It sets the per-app commentsEnabled flag, which the deploy pipeline reads to inject the comment widget at deploy time. Access controls still hold: on a gated deploy the thread is only readable/writable by viewers who pass the gate, and access revocation applies to the comment surface too.
- Detect intent from the invocation:
--comments/--comments on(or "with comments", "turn comments on") → on;--comments off(or "turn comments off") → off; otherwise leave unset. - Wire it in Phase C after upload returns
appId(see C.2.6):PATCH /api/apps/:id {commentsEnabled: true|false}. - Off by default. Without the flag
commentsEnabledstays unset and the injector is a strict no-op — the served HTML is byte-identical to a pre-feature deploy (no widget markup, no script, no network calls). The flag takes effect on the next deploy.
Architecture: Three Phases, Inline Parallel Scripts
The engine is three phases, structured by data-dependency. Independent work runs in parallel via bash background jobs; I/O-heavy decisions live in inline scripts (no Task sub-agents — they cost 3–5s of spawn overhead each and the JWT/verdicts have to flow back to main anyway).
| Phase | What runs | Parallelism |
|---|---|---|
| Step 1 | Preferences + exclusions (gate) | inline, sequential |
| Phase A | Framework detect + design pass (A.1.5, generated static only) → Build (inline) ‖ Identity (script) ‖ Sensitivity (script) | detect + design sync, then 3-way parallel via & + wait |
| Phase B | Localhost preview (inline-bg) ‖ Guardrails (script) | 2-way parallel via & + wait |
| Phase C | Password gen → upload → wire password → announce → present link | sequential, hard-gated |
Hard ordering constraints (preserved from core/policies/hq-deploy-reinforcement.md):
- Identity (Phase A) MUST complete before Upload (Phase C)
- Guardrails (Phase B) MUST gate Upload (Phase C)
- Upload (Phase C) returns
appIdwhich MUST exist before password persist + announce - Localhost preview (Phase B) is NEVER gated by identity — always runs
- Design pass (A.1.5) MUST complete before Build/Guardrails package the artifact — it restyles the generated static source in place, so it runs synchronously right after framework detection and before the Phase A fan-out
Inline helper scripts (each is self-contained, returns one JSON line on stdout):
| Script | Purpose | Returns |
|---|---|---|
.claude/skills/deploy/scripts/identity-resolve.sh | Resolves Cognito JWT (cache → refresh → login); --force-refresh bypasses a rejected cache token; jq preferred, node via hook-lib.sh | {"status":"ok"|"login_required"|"missing_dependency",...} |
.claude/skills/deploy/scripts/sensitivity-check.sh <path> [user_msg] | Classifies artifact sensitivity (filename-list grep, no content surfaces) | {"sensitive":bool,"trigger":string|null} |
.claude/skills/deploy/scripts/guardrails-check.sh <output_dir> | Caps + builds tarball | {"pass":bool,"reason":string|null,"tarball_path":string,...} |
.claude/skills/deploy/scripts/deploy-api-request.sh | Makes a checked Phase C API/S3 request | validated body on stdout; safe failure diagnostic on stderr |
.claude/skills/deploy/scripts/og-inject.sh <output_dir> [base_url] [app_name] | Injects OG/Twitter preview tags; generates a 1200x630 card image when none exists | {"injected":int,"image":string,"changed":bool} |
.claude/skills/deploy/scripts/password-helper.sh | gen / announce / persist / lookup | password text, or persisted entry |
All scripts are deterministic, run in 0.3–0.5s, and never echo JWTs / artifact contents / matched PII.
Step 1 — Preferences and Exclusions
Auto-deploy is opt-out. Honor user preference and rule out projects that shouldn't deploy.
1a. Read user preference
$PREF_FILE is ~/.hq/deploy-prefs.json — a file owned exclusively by /deploy. The legacy ~/.hq/config.json is read-only here (backwards compat) and never written by this skill: that path is owned by the HQ Desktop App's strict HqConfig serde struct, and overlapping writers caused the HQ Desktop App to bail on every sync (see feedback_3ab4f113-2e7c-4e4e-a171-771b47a2b5fd).
PREF_FILE="$HOME/.hq/deploy-prefs.json"
LEGACY_PREF_FILE="$HOME/.hq/config.json"
# One-time migration: lift deploy-owned fields out of the legacy file so
# hq-sync can resume parsing ~/.hq/config.json as HqConfig. Read-only on the
# legacy path — never write back to it.
if [ ! -f "$PREF_FILE" ] && [ -f "$LEGACY_PREF_FILE" ]; then
LEGACY_DEFAULT=$(jq -r '.defaultOrg // empty' "$LEGACY_PREF_FILE" 2>/dev/null)
LEGACY_PREF=$(jq -r '.deploy.preference // empty' "$LEGACY_PREF_FILE" 2>/dev/null)
if [ -n "$LEGACY_DEFAULT" ] || [ -n "$LEGACY_PREF" ]; then
mkdir -p "$HOME/.hq"
jq -n --arg slug "$LEGACY_DEFAULT" --arg pref "$LEGACY_PREF" \
'{} | (if $slug != "" then .defaultOrg = $slug else . end)
| (if $pref != "" then .deploy.preference = $pref else . end)' \
> "$PREF_FILE"
fi
fi
if [ -f "$PREF_FILE" ]; then
DEPLOY_PREF=$(jq -r '.deploy.preference // "hq-deploy"' "$PREF_FILE" 2>/dev/null)
DEPLOY_ACCESS_SENSITIVE_DEFAULT=$(jq -r '.deploy.access.sensitiveDefault // "password"' "$PREF_FILE" 2>/dev/null)
DEPLOY_ACCESS_INTERNAL_DEFAULT=$(jq -r '.deploy.access.internalDefault // "company"' "$PREF_FILE" 2>/dev/null)
DEPLOY_ORG_RESTRICTED_BY_DEFAULT=$(jq -r '.deploy.access.orgRestrictedByDefault // false' "$PREF_FILE" 2>/dev/null)
else
DEPLOY_PREF="hq-deploy"
DEPLOY_ACCESS_SENSITIVE_DEFAULT="password"
DEPLOY_ACCESS_INTERNAL_DEFAULT="company"
DEPLOY_ORG_RESTRICTED_BY_DEFAULT="false"
fi
Valid values: hq-deploy (default), vercel, netlify, custom, none.
Access preference values:
.deploy.access.sensitiveDefault:password(default) orcompany.deploy.access.internalDefault:company(default).deploy.access.orgRestrictedByDefault:truemakes sensitive deploys company-restricted unless the user asks for public/password/email-recipient sharing
1b. Per-project override
if [ -f "prd.json" ]; then
PRD_DEPLOY=$(jq -r '.metadata.deploy // "unset"' prd.json 2>/dev/null)
if [ "$PRD_DEPLOY" = "false" ]; then DEPLOY_PREF="none"; fi
fi
1c. Honor the preference
hq-deploy→ continuevercel,netlify,custom→ silently stop (user has their own pipeline)none→ silently stop, skip even localhost preview
1d. Exclusions
- Vercel-managed:
manifest.yamlvercel_projects[]lists this project → skip - Backend service: Dockerfile / serverless.yml / sst.config.* at root → skip (Phase B guardrails will also catch these)
- Build is dirty: a recent test/typecheck failed → skip
1e. Resolve Context
| Thing | How |
|---|---|
| Deploy context | Company context via $ORG_SLUG, or explicit personal context when the signed-in user has no companies |
| API endpoint | $HQ_DEPLOY_API → manifest services.hq-deploy.endpoint → https://api.indigo-hq.com (always-on public default) — via resolve-deploy-api.sh |
| App name | package.json name → current directory name, slug-cased |
Resolve the deploy API base concretely, up front — this must produce a non-empty
$API or Phase C stalls on an empty upload host. The resolver applies the chain above
and always falls back to the public default, so a fresh install (no manifest, no
$HQ_DEPLOY_API) still deploys:
API="$(.claude/skills/deploy/scripts/resolve-deploy-api.sh)"
# $API is now guaranteed non-empty (public default https://api.indigo-hq.com when
# nothing else is configured). Every Phase C hq-deploy call uses "$API/api/...".
Org resolution chain
The org the deploy targets MUST be resolved — never fall back to a hardcoded slug. Walk these priorities in order until one produces $ORG_SLUG. Priorities 1–4 don't need a JWT and run here; Priority 5 needs $JWT and runs in A.5 after the Phase A barrier. When the signed-in user has no company at all, A.5 sets PERSONAL_SCOPE=true and the deploy ships to their personal scope (Priority 5b). Priority 6 is the state-aware CTA path, reached only when the org is genuinely ambiguous (multi-org) or vault is unreachable.
| Priority | Source | Notes |
|---|---|---|
| 1 | --org=<slug> arg or HQ_ORG env | Explicit one-off override |
| 2 | Agent-supplied via session context | Agent sets HQ_ORG before invoking when conversation clearly implies a company |
| 3 | cwd → companies/{slug}/… segment | Running inside HQ tree |
| 4 | ~/.hq/deploy-prefs.json defaultOrg field | Persisted choice (legacy ~/.hq/config.json read as fallback for backwards-compat) |
| 5 | Single active vault membership | Auto-resolved + auto-written to defaultOrg (runs in A.5) |
| 5b | No active membership → personal scope | Signed in but no company: deploy to the auto-provisioned personal-<sub> scope (PERSONAL_SCOPE=true, runs in A.5). Upload proceeds with X-HQ-Deploy-Scope: personal |
| 6 | State-aware CTA (multi-org / unreachable — see C.5) | Only when the org is genuinely ambiguous or vault is down; preview already shown; skip upload |
Pre-JWT block (Priorities 1–4):
ORG_SLUG="${HQ_ORG:-}"
# Priority 3: cwd → companies/{slug}/...
if [ -z "$ORG_SLUG" ]; then
PWD_REAL="$(pwd -P)"
HQ_ROOT=""
D="$PWD_REAL"
while [ "$D" != "/" ] && [ -n "$D" ]; do
if [ -f "$D/companies/manifest.yaml" ]; then HQ_ROOT="$D"; break; fi
D="$(dirname "$D")"
done
if [ -n "$HQ_ROOT" ]; then
REL="${PWD_REAL#$HQ_ROOT/companies/}"
if [ "$REL" != "$PWD_REAL" ]; then
CAND="${REL%%/*}"
# Reject non-company paths like _template or stray files
if [ -d "$HQ_ROOT/companies/$CAND" ] && [[ "$CAND" != _* ]]; then
ORG_SLUG="$CAND"
fi
fi
fi
fi
# Priority 4: ~/.hq/deploy-prefs.json defaultOrg (legacy ~/.hq/config.json read-only fallback)
if [ -z "$ORG_SLUG" ] && [ -f "$HOME/.hq/deploy-prefs.json" ]; then
ORG_SLUG=$(jq -r '.defaultOrg // empty' "$HOME/.hq/deploy-prefs.json" 2>/dev/null)
fi
if [ -z "$ORG_SLUG" ] && [ -f "$HOME/.hq/config.json" ]; then
ORG_SLUG=$(jq -r '.defaultOrg // empty' "$HOME/.hq/config.json" 2>/dev/null)
fi
Priorities 5 and 6 run in A.5 once $JWT is in scope.
1f. Writing a preference on request
When the user says "I use Vercel", "don't deploy my stuff":
mkdir -p "$HOME/.hq"
if [ -f "$PREF_FILE" ]; then
jq '.deploy.preference = "vercel"' "$PREF_FILE" > "$PREF_FILE.tmp" && mv "$PREF_FILE.tmp" "$PREF_FILE"
else
echo '{"deploy":{"preference":"vercel"}}' > "$PREF_FILE"
fi
Then say once:
Got it — I won't offer auto-deploy. You can change this in
~/.hq/deploy-prefs.json.
Phase A — Fan-out (3-way parallel)
After Step 1 resolves preferences, kick three workstreams off in the same shell command: Build inline, Identity script, Sensitivity script. Phase A completes when all three have returned.
A.1 — Framework detection (sync, fast)
# Skip rebuild if dist/index.html newer than newest source file
if [ -f "dist/index.html" ] || [ -f "out/index.html" ] || [ -f "build/client/index.html" ]; then
SKIP_BUILD=1
fi
# Detect framework + output dir + deploy type
if [ -f "next.config.js" ] || [ -f "next.config.mjs" ] || [ -f "next.config.ts" ]; then
FRAMEWORK="nextjs"; OUTPUT_DIR="out"; DEPLOY_TYPE="static"
elif [ -f "remix.config.js" ] || [ -f "remix.config.ts" ]; then
FRAMEWORK="remix"; OUTPUT_DIR="build/client"; DEPLOY_TYPE="ssr"
elif [ -f "astro.config.js" ] || [ -f "astro.config.mjs" ] || [ -f "astro.config.ts" ]; then
FRAMEWORK="astro"; OUTPUT_DIR="dist"; DEPLOY_TYPE="static"
elif [ -f "vite.config.js" ] || [ -f "vite.config.ts" ] || [ -f "vite.config.mjs" ]; then
FRAMEWORK="vite"; OUTPUT_DIR="dist"; DEPLOY_TYPE="static"
else
FRAMEWORK="static"; DEPLOY_TYPE="static"
for d in dist build out public .; do [ -f "$d/index.html" ] && OUTPUT_DIR="$d" && break; done
fi
# Backend API routes → upgrade to the `app` deploy type (per-app-function path).
# Orthogonal to the framework above: a root `api/` dir with >=1 handler file
# (api/**/*.{ts,js}) means the app ships backend routes, so it deploys as a
# static frontend PLUS an `api/*` per-app Lambda with keyless secret bindings —
# NOT Docker/ECR/ECS. Framework NAME is preserved (a Vite app with api/ stays
# framework=vite, type=app). No api/ dir (or empty) stays `static`. See the
# "App deploy type" section below and hq-deploy `src/deploy/function/`.
if [ "$DEPLOY_TYPE" != "ssr" ] && [ -n "$(find api -type f \( -name '*.ts' -o -name '*.js' \) 2>/dev/null | head -n1)" ]; then
DEPLOY_TYPE="app"
fi
# Package manager
if [ -f "bun.lockb" ] || [ -f "bun.lock" ]; then PM="bun"
elif [ -f "pnpm-lock.yaml" ]; then PM="pnpm"
elif [ -f "yarn.lock" ]; then PM="yarn"
else PM="npm"; fi
A.1.5 — Design pass (generated single-page artifacts only)
Default ON — this is the deploy-quality default. A plain, HQ-generated report/deck/summary should never ship looking like an unstyled document. Before the artifact is packaged, lift a self-authored single-page HTML to on-brand quality using the hq-design house system. This runs synchronously here (right after framework detection, before the Phase A fan-out) so the restyled file is what Phase B guardrails tars and Phase C uploads.
Gate — decide whether to run it:
DESIGN_PASS=1
[ "$FRAMEWORK" != "static" ] && DESIGN_PASS=0 # framework builds (Next/Vite/Astro/Remix) own their design — never touch them
[ -f "$OUTPUT_DIR/DESIGN.md" ] && DESIGN_PASS=0 # artifact already declares its own design system
[ "$(jq -r '.deploy.designPass // "true"' "$HOME/.hq/deploy-prefs.json" 2>/dev/null)" = "false" ] && DESIGN_PASS=0 # user disabled globally
case "$LATEST_USER_MSG" in # explicit opt-out in the latest message
*as-is*|*"as is"*|*"no design"*|*"skip design"*|*"no restyle"*|*"don't restyle"*|*"dont restyle"*|*"leave the styling"*|*"keep the design"*|*"keep the styling"*) DESIGN_PASS=0 ;;
esac
Scope is deliberately narrow: only FRAMEWORK=static single-page artifacts (reports, decks, summaries, briefs that HQ generated). Framework builds and already-designed artifacts pass through untouched.
When DESIGN_PASS=1, apply the pass — this is design work you do inline, not a script:
- Read the house system:
core/knowledge/public/hq-core/design-md-spec.md. If the design packs are installed (core/knowledge/public/design-styles/,core/knowledge/public/design-quality/), fold them in for a higher bar. - Restyle
$OUTPUT_DIR/index.htmlto that bar — deliberate type scale, spacing rhythm, color/token discipline, restraint, visual hierarchy; accessible (semantic HTML, aria) and responsive; wrap any animation in@media (prefers-reduced-motion: reduce). - Preserve exactly: every piece of content and every link, and self-containment (inline CSS, inline SVG, web fonts via CDN only — no new local asset dependencies, no external calls). Never invent facts, drop items, or add
.htmlsub-pages (the static host SPA-fallbacks them — keep one self-containedindex.html). - If the page is already at the hq-design bar, make it a no-op and move on — don't restyle good work.
Then continue to A.2 with the restyled artifact in place.
A.2 — Spawn three workstreams in parallel
Launch Build (if needed), Identity, and Sensitivity simultaneously — each writes to its own tmp file, then wait syncs the barrier.
T_IDENTITY=$(mktemp -t hq-deploy-identity.XXXXXX)
T_SENSITIVITY=$(mktemp -t hq-deploy-sensitivity.XXXXXX)
T_BUILD=$(mktemp -t hq-deploy-build.XXXXXX)
# A.2.1 — Identity in background (script self-resolves cache/refresh/login)
.claude/skills/deploy/scripts/identity-resolve.sh > "$T_IDENTITY" 2>/dev/null &
IDENTITY_PID=$!
# A.2.2 — Sensitivity in background ($LATEST_USER_MSG = excerpt of latest user message, ≤200 chars)
.claude/skills/deploy/scripts/sensitivity-check.sh "$PWD" "$LATEST_USER_MSG" > "$T_SENSITIVITY" 2>/dev/null &
SENSITIVITY_PID=$!
# A.2.3 — Build in background (skipped if SKIP_BUILD)
if [ -z "$SKIP_BUILD" ]; then
( $PM install >/dev/null 2>&1 && $PM run build >/dev/null 2>&1 \
&& echo '{"status":"ok"}' || echo '{"status":"fail"}' ) > "$T_BUILD" &
BUILD_PID=$!
else
echo '{"status":"ok","skipped":true}' > "$T_BUILD"
BUILD_PID=""
fi
# Barrier — wait for all three
wait $IDENTITY_PID $SENSITIVITY_PID $BUILD_PID 2>/dev/null
A.3 — Parse the three verdicts
IDENTITY_JSON=$(cat "$T_IDENTITY")
SENSITIVITY_JSON=$(cat "$T_SENSITIVITY")
BUILD_JSON=$(cat "$T_BUILD")
rm -f "$T_IDENTITY" "$T_SENSITIVITY" "$T_BUILD"
# Parse all Phase A verdicts through the shared jq-first, node-fallback engine.
# Do not use bare jq here: identity-resolve may have succeeded through node.
. core/scripts/hook-lib.sh
# hook-lib intentionally uses command -v for hot-path hooks, but /deploy must
# not trust a broken Windows app-execution alias or stale node shim.
if [ -n "$HQ_LIB_NODE" ] \
&& ! "$HQ_LIB_NODE" -e 'process.exit(0)' >/dev/null 2>&1; then
HQ_LIB_NODE=""
fi
if [ -z "$HQ_LIB_JQ" ] && [ -z "$HQ_LIB_NODE" ]; then
printf '%s\n' "Deploy requires jq or Node.js to parse its phase verdicts. Install jq: Windows: winget install jqlang.jq | choco install jq | scoop install jq; Linux: sudo apt install jq | sudo dnf install jq; macOS: brew install jq" >&2
IDENTITY_STATUS="missing_dependency"
JWT=""
HQ_PRO_JWT=""
LOGIN_REASON="missing_jq_and_node"
SENSITIVE="false"
SENSITIVITY_TRIGGER=""
BUILD_STATUS="fail"
else
IDENTITY_STATUS=$(printf '%s' "$IDENTITY_JSON" | hq_json_get status)
JWT=$(printf '%s' "$IDENTITY_JSON" | hq_json_get jwt)
# id_token is the HQ Pro / grantee-validation token used by C.3. Take it from
# the resolver output, never from a raw read of ~/.hq/cognito-tokens.json —
# only the resolver applies the expiry skew and the refresh path.
HQ_PRO_JWT=$(printf '%s' "$IDENTITY_JSON" | hq_json_get id_token)
LOGIN_REASON=$(printf '%s' "$IDENTITY_JSON" | hq_json_get reason)
SENSITIVE=$(printf '%s' "$SENSITIVITY_JSON" | hq_json_get sensitive)
SENSITIVITY_TRIGGER=$(printf '%s' "$SENSITIVITY_JSON" | hq_json_get trigger)
BUILD_STATUS=$(printf '%s' "$BUILD_JSON" | hq_json_get status)
fi
A.4 — Phase A barrier rules
BUILD_STATUS == "fail"→ abort the deploy entirely (silent skip). Localhost preview also skipped.IDENTITY_STATUS == "login_required"→ mark Phase C upload as no-op; Phase B preview still runs.IDENTITY_STATUS == "missing_dependency"→ not a sign-in problem. The A.3 parser has already printed per-OS jq guidance and setBUILD_STATUS=fail; abort the deploy without a login upsell or browser sign-in. When node exists, A.3 uses it and this hard-stop is not taken. Note: later Phase C steps still requirejqeven when identity itself used the node fallback.SENSITIVE == "true"→ choose an access mode for Phase C:- If the latest user message asks for org/company/internal restriction (
"restricted to org","company-only","internal-only","HQ members only"), setACCESS_MODE=${DEPLOY_ACCESS_INTERNAL_DEFAULT:-company}. - Else if the latest user message names specific recipients (
"share with alice@…","@example.com only","private to the design team"), setACCESS_MODE=privateand parse the recipient list intoALLOW_PATTERNS(newline-separated, each either[EMAIL]or@domain.tld). - Else if
DEPLOY_ORG_RESTRICTED_BY_DEFAULT=trueorDEPLOY_ACCESS_SENSITIVE_DEFAULT=company, setACCESS_MODE=company. - Otherwise set
ACCESS_MODE=password— the historical default for sensitive auto-deploy.
- If the latest user message asks for org/company/internal restriction (
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 84
- Forks
- 15
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
deploy-indigoai-us- Source
- github.com/indigoai-us/hq-core