Deploy Engine

SkillCloud & infra

Deploy or share generated HQ artifacts through hq-deploy.

Available today. Use it from your connected AI after setup.

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.

ModeWhen to pick itWhat it does
publicDefault. Casual handoff, anyone with the link.No gate. App serves immediately.
passwordSensitive 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.
companyInternal/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.
selectedSpecific 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.
privateLegacy 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 leaving private so 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 for company, selected, and policy-versioned password. 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-emails
  • POST /api/apps/:id/allowed-emails {email} — accepts an exact address ([EMAIL]) or a @domain.tld pattern; idempotent; lowercased server-side.
  • DELETE /api/apps/:id/allowed-emails/{patternKey}patternKey URL-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 commentsEnabled stays 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).

PhaseWhat runsParallelism
Step 1Preferences + exclusions (gate)inline, sequential
Phase AFramework 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 BLocalhost preview (inline-bg) ‖ Guardrails (script)2-way parallel via & + wait
Phase CPassword gen → upload → wire password → announce → present linksequential, 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 appId which 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):

ScriptPurposeReturns
.claude/skills/deploy/scripts/identity-resolve.shResolves 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.shMakes a checked Phase C API/S3 requestvalidated 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.shgen / announce / persist / lookuppassword 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) or company
  • .deploy.access.internalDefault: company (default)
  • .deploy.access.orgRestrictedByDefault: true makes 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 → continue
  • vercel, netlify, custom → silently stop (user has their own pipeline)
  • none → silently stop, skip even localhost preview

1d. Exclusions

  • Vercel-managed: manifest.yaml vercel_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

ThingHow
Deploy contextCompany 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.endpointhttps://api.indigo-hq.com (always-on public default) — via resolve-deploy-api.sh
App namepackage.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.

PrioritySourceNotes
1--org=<slug> arg or HQ_ORG envExplicit one-off override
2Agent-supplied via session contextAgent sets HQ_ORG before invoking when conversation clearly implies a company
3cwd → companies/{slug}/… segmentRunning inside HQ tree
4~/.hq/deploy-prefs.json defaultOrg fieldPersisted choice (legacy ~/.hq/config.json read as fallback for backwards-compat)
5Single active vault membershipAuto-resolved + auto-written to defaultOrg (runs in A.5)
5bNo active membership → personal scopeSigned 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
6State-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:

  1. 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.
  2. Restyle $OUTPUT_DIR/index.html to 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).
  3. 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 .html sub-pages (the static host SPA-fallbacks them — keep one self-contained index.html).
  4. 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 set BUILD_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 require jq even 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"), set ACCESS_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"), set ACCESS_MODE=private and parse the recipient list into ALLOW_PATTERNS (newline-separated, each either [EMAIL] or @domain.tld).
    • Else if DEPLOY_ORG_RESTRICTED_BY_DEFAULT=true or DEPLOY_ACCESS_SENSITIVE_DEFAULT=company, set ACCESS_MODE=company.
    • Otherwise set ACCESS_MODE=password — the historical default for sensitive auto-deploy.

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