#!/bin/sh # Generated for one immutable SDK release. No sudo, shell-profile edits or login. set -eu umask 077 sdk_release='0.1.0-preview.20' sdk_platform='darwin-arm64' sdk_min_macos='27.0' sdk_archive='titan-sdk-0.1.0-preview.20-darwin-arm64.tar.gz.ugZowLNd.bin' sdk_hash='5c0b124371dae5bff6f028cf22cf11ae620ba56942605ec809bd3abde613bf4a' sdk_root='titan-sdk-0.1.0-preview.20-darwin-arm64' sdk_base='https://titan-dev.projectroom.io/_app/immutable/assets' sdk_local='' sdk_prefix="${HOME:?HOME is required}/.local/share/titan" sdk_scratch='' sdk_lock='' sdk_guide_temp='' sdk_guide_dir=$(pwd -P) || exit 1 fail() { printf '%s\n' "Titan SDK: $*" >&2; exit 1; } usage() { printf '%s\n' 'Usage: sh install.sh [--archive /absolute/sdk.tar.gz] [--prefix /absolute/install-root]' printf '%s\n' 'Installs SDK and leaves TITAN-START-HERE.md in the current directory. Does not login, create an app, or modify PATH.' } while [ "$#" -gt 0 ]; do case "$1" in --archive) [ "$#" -ge 2 ] || fail 'Missing archive path'; sdk_local=$2; shift 2 ;; --prefix) [ "$#" -ge 2 ] || fail 'Missing installation path'; sdk_prefix=$2; shift 2 ;; --help|-h) usage; exit 0 ;; *) fail 'Unsupported argument' ;; esac done [ "$(uname -s)" = Darwin ] && [ "$(uname -m)" = arm64 ] || fail 'This preview supports macOS Apple Silicon only.' sdk_macos=$(sw_vers -productVersion) awk -v actual="$sdk_macos" -v minimum="$sdk_min_macos" 'BEGIN { split(actual,a,"."); split(minimum,b,"."); for(i=1;i<=3;i++) {if(a[i]+0>b[i]+0) exit 0; if(a[i]+0/dev/null 2>&1 || fail "Required system tool is missing: $sdk_tool" done case "$sdk_prefix" in /*) ;; *) fail 'Installation path must be absolute.' ;; esac case "$sdk_prefix/" in *'/../'*|*'/./'*|*'//'*) fail 'Installation path must be normalized.' ;; esac case "$sdk_prefix" in /|/Users|/private|/private/tmp|/tmp|/var|/usr|/usr/local|"$HOME") fail 'Choose a dedicated SDK installation directory.' ;; esac sdk_at=$sdk_prefix while [ "$sdk_at" != / ]; do [ ! -L "$sdk_at" ] || fail 'Symbolic links in the installation path are not allowed.' [ ! -e "$sdk_at" ] || [ -d "$sdk_at" ] || fail 'Installation path contains a non-directory.' sdk_at=$(dirname "$sdk_at") done if [ -e "$sdk_prefix" ]; then [ "$(stat -f '%u' "$sdk_prefix")" = "$(id -u)" ] || fail 'Installation directory must belong to you.' sdk_mode=$(stat -f '%OLp' "$sdk_prefix") [ $((0$sdk_mode & 0022)) -eq 0 ] || fail 'Installation directory is writable by another user.' fi if [ -z "$sdk_local" ]; then case "$sdk_base" in https://*) ;; *) fail 'No public download URL is configured. Use --archive with the downloaded SDK.' ;; esac else case "$sdk_local" in /*) ;; *) fail 'Archive path must be absolute.' ;; esac [ -f "$sdk_local" ] && [ ! -L "$sdk_local" ] || fail 'Archive must be a regular file, not a symbolic link.' fi case "$sdk_guide_dir" in /|/Users|/usr|/usr/local|/bin|/System|/Library) fail 'Run the installer from your intended workspace directory.' ;; esac case "$sdk_guide_dir$sdk_prefix" in *' '*|*' '*) fail 'Newlines in workspace or installation paths are not supported.' ;; esac mkdir -p "$sdk_prefix" sdk_lock="$sdk_prefix/.install-lock" mkdir "$sdk_lock" 2>/dev/null || fail 'Another SDK installer owns the lock. Do not remove an active lock.' cleanup() { if [ -n "$sdk_guide_temp" ] && [ "$(dirname "$sdk_guide_temp")" = "$sdk_guide_dir" ]; then case "$(basename "$sdk_guide_temp")" in .titan-start.??????) rm -f -- "$sdk_guide_temp" ;; esac fi # Delete only the exact private directory created by mktemp below. if [ -n "$sdk_scratch" ] && [ "$(dirname "$sdk_scratch")" = "$sdk_prefix" ]; then case "$(basename "$sdk_scratch")" in .install.??????) rm -rf -- "$sdk_scratch" ;; esac fi [ -z "$sdk_lock" ] || rmdir "$sdk_lock" 2>/dev/null || true } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM sdk_scratch=$(mktemp -d "$sdk_prefix/.install.XXXXXX") # Reserve a private file on the workspace filesystem before changing an installation. sdk_guide_temp=$(mktemp "$sdk_guide_dir/.titan-start.XXXXXX") || fail 'Cannot write a start guide in this workspace.' if [ -n "$sdk_local" ]; then cp "$sdk_local" "$sdk_scratch/sdk.tar.gz" else curl -q --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ --connect-timeout 10 --max-time 120 --max-redirs 3 --max-filesize 33554432 \ --output "$sdk_scratch/sdk.tar.gz" "$sdk_base/$sdk_archive" fi [ "$(shasum -a 256 "$sdk_scratch/sdk.tar.gz" | awk '{print $1}')" = "$sdk_hash" ] || fail 'SDK checksum mismatch. Nothing was installed.' tar -tzf "$sdk_scratch/sdk.tar.gz" > "$sdk_scratch/entries" awk -v root="$sdk_root/" ' index($0,root)!=1 || $0 ~ /[^A-Za-z0-9_.\/-]/ || $0 ~ /(^|\/)\.\.?($|\/)/ || $0 ~ /\/\// {bad=1} END {if(NR==0 || NR>1000 || bad) exit 1} ' "$sdk_scratch/entries" || fail 'Unsafe SDK archive paths.' tar -tvzf "$sdk_scratch/sdk.tar.gz" > "$sdk_scratch/types" awk 'substr($0,1,1)!="-" && substr($0,1,1)!="d" {bad=1} END {if(bad) exit 1}' "$sdk_scratch/types" || fail 'SDK archive contains links or special files.' tar -xzf "$sdk_scratch/sdk.tar.gz" -C "$sdk_scratch" (cd "$sdk_scratch/$sdk_root" && shasum -a 256 -c SHA256SUMS >/dev/null) || fail 'SDK file verification failed.' # The entire release is one pinned archive, including CLI, executor, Kit and # guides. Never fetch those components independently from mutable latest URLs. [ -f "$sdk_scratch/$sdk_root/release-contract.json" ] || fail 'SDK release contract is missing.' grep -F '"release": "'"$sdk_release"'"' "$sdk_scratch/$sdk_root/release-contract.json" >/dev/null || fail 'SDK release contract does not match the installer.' "$sdk_scratch/$sdk_root/bin/titan" tooling install || fail 'Managed Node/Go installation failed. Existing SDK command was preserved.' sdk_dest="$sdk_prefix/versions/$sdk_release-$sdk_platform" for sdk_dir in "$sdk_prefix/versions" "$sdk_prefix/bin"; do [ ! -L "$sdk_dir" ] || fail 'Existing SDK paths must not be symbolic links.' mkdir -p "$sdk_dir" done sdk_link="../versions/$sdk_release-$sdk_platform/bin/titan" sdk_previous_link='' if [ -L "$sdk_prefix/bin/titan" ]; then sdk_previous_link=$(readlink "$sdk_prefix/bin/titan") if [ "$sdk_previous_link" != "$sdk_link" ]; then # Only upgrade a command owned by an intact earlier installer release. case "$sdk_previous_link" in ../versions/*-darwin-arm64/bin/titan) ;; *) fail 'Existing Titan command points to another installation. It was preserved.' ;; esac case "$sdk_previous_link" in *'/../'*'/../'*|*'//'*) fail 'Existing Titan command was preserved.' ;; esac sdk_previous_name=${sdk_previous_link#../versions/} sdk_previous_name=${sdk_previous_name%/bin/titan} printf '%s\n' "$sdk_previous_name" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-preview\.[0-9]+-darwin-arm64$' || fail 'Existing Titan command was preserved.' sdk_previous_dest="$sdk_prefix/versions/$sdk_previous_name" [ -d "$sdk_previous_dest" ] && [ ! -L "$sdk_previous_dest" ] || fail 'Previous SDK was preserved.' [ -f "$sdk_previous_dest/.sdk-receipt" ] && [ ! -L "$sdk_previous_dest/.sdk-receipt" ] || fail 'Previous SDK is not installer-owned.' grep -Eq '^[a-f0-9]{64}$' "$sdk_previous_dest/.sdk-receipt" || fail 'Previous SDK receipt is invalid.' [ -z "$(find "$sdk_previous_dest" -type l -print -quit)" ] || fail 'Previous SDK contains symbolic links.' (cd "$sdk_previous_dest" && shasum -a 256 -c SHA256SUMS >/dev/null) || fail 'Previous SDK changed; it was preserved.' fi elif [ -e "$sdk_prefix/bin/titan" ]; then fail 'Existing Titan command was preserved. Choose another --prefix.' fi if [ -e "$sdk_dest" ] || [ -L "$sdk_dest" ]; then [ -d "$sdk_dest" ] && [ ! -L "$sdk_dest" ] || fail 'Existing release path was preserved.' [ -f "$sdk_dest/.sdk-receipt" ] && [ ! -L "$sdk_dest/.sdk-receipt" ] || fail 'Existing release is not owned by this installer.' [ "$(cat "$sdk_dest/.sdk-receipt")" = "$sdk_hash" ] || fail 'Existing release does not match this SDK.' # Compare the trusted checksum inventory too; do not trust a locally edited inventory. cmp "$sdk_scratch/$sdk_root/SHA256SUMS" "$sdk_dest/SHA256SUMS" >/dev/null || fail 'Existing SDK inventory changed.' [ -z "$(find "$sdk_dest" -type l -print -quit)" ] || fail 'Existing SDK contains symbolic links.' (cd "$sdk_dest" && shasum -a 256 -c SHA256SUMS >/dev/null) || fail 'Existing SDK files changed; they were not overwritten.' else printf '%s\n' "$sdk_hash" > "$sdk_scratch/$sdk_root/.sdk-receipt" mv "$sdk_scratch/$sdk_root" "$sdk_dest" fi # Stable SDK guide follows the verified release, without modifying workspace guides. sdk_guide_link="versions/$sdk_release-$sdk_platform/START-HERE.md" sdk_previous_guide='' if [ -L "$sdk_prefix/START-HERE.md" ]; then sdk_previous_guide=$(readlink "$sdk_prefix/START-HERE.md") printf '%s\n' "$sdk_previous_guide" | grep -Eq '^versions/[0-9]+\.[0-9]+\.[0-9]+-preview\.[0-9]+-darwin-arm64/START-HERE\.md$' || fail 'Existing SDK guide was preserved.' [ -f "$sdk_prefix/${sdk_previous_guide%/START-HERE.md}/.sdk-receipt" ] || fail 'Existing SDK guide is not installer-owned.' elif [ -e "$sdk_prefix/START-HERE.md" ]; then fail 'Existing SDK guide was preserved. Choose another --prefix.' fi if [ -z "$sdk_previous_link" ]; then ln -s "$sdk_link" "$sdk_prefix/bin/titan" elif [ "$sdk_previous_link" != "$sdk_link" ]; then [ -L "$sdk_prefix/bin/titan" ] && [ "$(readlink "$sdk_prefix/bin/titan")" = "$sdk_previous_link" ] || fail 'Titan command changed during installation; it was preserved.' ln -s "$sdk_link" "$sdk_scratch/titan-command" mv -f "$sdk_scratch/titan-command" "$sdk_prefix/bin/titan" fi if [ -z "$sdk_previous_guide" ]; then ln -s "$sdk_guide_link" "$sdk_prefix/START-HERE.md" elif [ "$sdk_previous_guide" != "$sdk_guide_link" ]; then [ -L "$sdk_prefix/START-HERE.md" ] && [ "$(readlink "$sdk_prefix/START-HERE.md")" = "$sdk_previous_guide" ] || fail 'SDK guide changed during installation; it was preserved.' ln -s "$sdk_guide_link" "$sdk_scratch/sdk-guide" mv -f "$sdk_scratch/sdk-guide" "$sdk_prefix/START-HERE.md" fi # Profile is an internal name, fixed for this workspace + SDK prefix. No account # information is read or inferred, and no login is performed by the installer. sdk_profile="setup-$(printf '%s\000%s\000' "$sdk_guide_dir" "$sdk_prefix" | shasum -a 256 | awk '{print substr($1,1,20)}')" sdk_guide_request="$sdk_profile-create" sdk_shell_quote() { sdk_quote_value=$(printf '%s' "$1" | sed "s/'/'\\\\''/g") printf "'%s'" "$sdk_quote_value" } sdk_guide_cli=$(sdk_shell_quote "$sdk_prefix/bin/titan") sdk_guide_workspace=$(sdk_shell_quote "$sdk_guide_dir") sdk_guide_start=$(sdk_shell_quote "$sdk_prefix/START-HERE.md") { printf '%s\n' '# Titan 로그인과 새 앱 시작' printf '%s\n' '' printf '%s\n' 'Titan SDK 설치는 끝났지만 아직 로그인하거나 앱을 만든 것은 아니다.' printf '%s\n' 'SDK의 release-contract.json이 이번 릴리스의 CLI·실행기·KIT 버전 조합을 고정한다.' printf '%s\n' 'sdk-manifest.json과 실제 version/capabilities를 대조하고 다른 설치의 구성요소를 섞지 않는다.' printf '%s\n' '최신 공개 install.sh는 그 조합 전체를 한 압축파일로 설치한다. 저장해 둔 예전 설치 스크립트는' printf '%s\n' '예전 버전에 고정되어 있으므로 초기화할 때 공개 주소에서 다시 받는다.' printf '%s\n' '사용자가 이 폴더에서 Titan 앱을 만들자고 하면 AI는 아래 순서로 진행한다.' printf '%s\n' '' printf '%s\n' '이 안내와 공개 SDK만으로 시작한다. 이전 채팅·IDE·다른 앱 폴더·운영자가 넣어준 보조 파일이' printf '%s\n' '있다고 가정하지 않는다. 폴더가 비어 있어도 같은 컴퓨터의 보호된 생성 기록이나 서버 앱은' printf '%s\n' '남아 있을 수 있다. CLI 결과로 확인하고, 기존 앱을 복원할지 새로운 별도 앱을 만들지 사용자와 구분한다.' printf '%s\n' '' printf '%s\n' '## 앱 이름·폴더와 객관식 체크리스트' printf '%s\n' '' printf '%s\n' '이 SDK의 `applicationInitialization.schemaVersion=2` 계약을 확인한다. 앱 이름과 사용할 폴더 중' printf '%s\n' '아직 정하지 않은 것만 묻고 필요한 폴더 권한을 받는다. 이름은 자유 입력이나 제안 이름 선택이 가능하다.' printf '%s\n' '이후 `init application`이 반환한 앱 유형·언어·진행 확인을 객관식 UI로 제시한다.' printf '%s\n' 'UI가 없는 AI 도구는 번호 선택을 제공한다. 이미 받은 답은 채워서 한 번만 확인한다.' printf '%s\n' '' printf '%s\n' '- 앱 유형: 웹 서비스 / API 서버 / 배치·자동화 / 아직 모르겠어요' printf '%s\n' '- 서버·배치 언어: JavaScript(기본) / Go / Java / 아직 모르겠어요(JavaScript)' printf '%s\n' '- 진행 확인: 이 구성으로 다음 단계 / 수정 / 중단' printf '%s\n' '' printf '%s\n' '언어가 미선택이면 “JavaScript를 기본으로 사용합니다. 서버·배치는 Node.js, 웹 화면이 필요하면' printf '%s\n' 'SvelteKit의 JavaScript 구성을 사용합니다”라고 고지한다. 사용자 확인 뒤에만 반환된' printf '%s\n' '`--checklist-reviewed yes` 다음 행동으로 진행한다. 언어 기본값은 인증·생성의 기본 승인이 아니다.' printf '%s\n' '선택한 언어의 실제 템플릿·SDK 지원은 나중에 `module contracts`로 확인한다.' printf '%s\n' '‘화면은 JavaScript, 서버는 Go’처럼 역할별 언어 선택도 가능하다. 하나의 언어로 통일하지 않고' printf '%s\n' 'SvelteKit 화면 모듈과 Go 온라인 서버 모듈로 계획한다. 실제 생성은 모듈 계획 확인 뒤에 수행한다.' printf '%s\n' '' printf '%s\n' '## 별도의 Titan 로그인과 계정 확인' printf '%s\n' '' printf '%s\n' '프로필은 이 컴퓨터 안의 인증 연결을 구분하는 내부 이름이다. 사용자에게' printf '%s\n' '‘계정 연결 이름’이나 프로필 이름을 정해 달라고 묻지 않는다. 기존 IDE 로그인,' printf '%s\n' '다른 프로필이나 AWS SSO 연결을 찾거나 가져오지 않는다. 이 가이드에 지정된' printf '%s\n' '전용 프로필을 사용하고, 실제 사용할 계정은 브라우저에서 사용자가 확인한다.' printf '%s\n' '' printf '%s\n' '이 폴더에서 실행할 CLI와 지원 범위를 먼저 확인한다.' printf '%s\n' '' printf '%s\n' '```sh' printf '%s\n' "$sdk_guide_cli"' version' printf '%s\n' "$sdk_guide_cli"' capabilities --output json' printf '%s\n' "$sdk_guide_cli"' init application --name "확인한 앱 이름" --parent '"$sdk_guide_workspace"' --profile '"$sdk_profile"' --request-id '"$sdk_guide_request"' --output json' printf '%s\n' '```' printf '%s\n' '' printf '%s\n' '체크리스트 확인 뒤 CLI가 인증을 요구하면 사용자에게 ‘Titan 로그인 화면을 열겠습니다. 사용할 계정으로' printf '%s\n' '로그인하고 연결 승인을 눌러 주세요’라고 안내하고 다음 명령을 실행한다.' printf '%s\n' '브라우저 승인은 사용자가 직접 하며 AI는 비밀번호나 토큰을 받지 않는다.' printf '%s\n' '' printf '%s\n' '```sh' printf '%s\n' "$sdk_guide_cli"' auth login --profile '"$sdk_profile"' --output json' printf '%s\n' '```' printf '%s\n' '' printf '%s\n' 'CLI가 승인을 기다리는 동안 새 로그인 요청을 반복하지 않는다. 연결이 끝나면' printf '%s\n' '다음 명령으로 표시되는 계정을 확인한다. 이미 이 전용 프로필에 연결되어 있으면' printf '%s\n' '계정을 사용자와 확인한 뒤 이어가며, 다른 계정으로 자동 교체하지 않는다.' printf '%s\n' '사용자가 다른 계정의 연결을 명시적으로 요청하면 기존 연결을 덮어쓰지 않고 AI가' printf '%s\n' '새 내부 프로필 이름을 정해 별도 로그인한다. 이 이름을 사용자에게 과제로 묻지 않는다.' printf '%s\n' '' printf '%s\n' '```sh' printf '%s\n' "$sdk_guide_cli"' auth status --profile '"$sdk_profile"' --output json' printf '%s\n' '```' printf '%s\n' '' printf '%s\n' '## 로그인과 구분한 앱 생성 승인' printf '%s\n' '' printf '%s\n' '체크리스트에서 받은 선택과 request-id를 그대로 유지한다. CLI가 인증을 요청했을 때만' printf '%s\n' '브라우저 로그인을 진행한다. 계정 확인 후 `--confirm-user`로 계획을 요청한다.' printf '%s\n' '아래는 기본값을 선택했을 때의 계획 조회 예시이며, 사용자가 고른 언어·유형이 있다면 유지한다.' printf '%s\n' '' printf '%s\n' '```sh' printf '%s\n' "$sdk_guide_cli"' init application --name "확인한 앱 이름" --parent '"$sdk_guide_workspace"' --profile '"$sdk_profile"' --request-id '"$sdk_guide_request"' --app-kind undecided --language auto --checklist-reviewed yes --output json' printf '%s\n' '```' printf '%s\n' '' printf '%s\n' '결과의 questions와 nextActions를 읽고 계정·앱 이름·작업 폴더를 확인한다.' printf '%s\n' '계정 확인 뒤 `--confirm-user`를 전달하면 앱 생성 계획이 나온다. 이름·폴더·계정·구성·기본 사용' printf '%s\n' '안내를 보여주고 **별도로 앱 생성 승인을 받은 뒤에만** 반환된 `nextActions`의 `--confirm-create`' printf '%s\n' '계획 ID를 전달한다. ID를 추측·계산하거나 사용자에게 복사하게 하지 않는다. `--confirm-user`만으로는' printf '%s\n' '생성되지 않는다. 이름·폴더·언어·계정·세션이 바뀌어 새 계획이 나오면 다시 승인받는다.' printf '%s\n' '무응답, 기본 언어 선택, 브라우저 연결 승인을 앱 생성 승인으로 간주하지 않는다.' printf '%s\n' '인증이 필요하다는 응답이면 위 로그인 순서로 돌아가며 프로필 이름을 묻지 않는다.' printf '%s\n' '예시의 request-id는 이 첫 앱 생성에 고정한 내부 작업 ID다. 기존 앱 생성이 끝난 뒤' printf '%s\n' '사용자가 다른 새 앱을 명시적으로 요청하면 AI가 새 작업 ID를 정한다. 사용자에게' printf '%s\n' 'ID를 정해 달라고 묻거나, 실패한 요청을 우회하려고 ID를 바꾸지 않는다.' printf '%s\n' '' printf '%s\n' '## 만들어진 사용자별 앱 폴더에서 계속' printf '%s\n' '' printf '%s\n' '새 앱은 이 작업공간 아래 `userId/appId` 구조로 만든다. userId는 브라우저 로그인으로' printf '%s\n' '확인한 실제 사용자 ID이며 프로필 이름이나 이메일로 대체하지 않는다. 사용자 ID를' printf '%s\n' '다시 입력하게 하거나 폴더 이름을 임의로 추측하지 않는다.' printf '%s\n' '성공 결과의 data.root가 실제 앱 작업 폴더다. 그 폴더로 이동해 이 CLI의' printf '%s\n' 'workspace status --output json으로 계정·앱 연결을 확인하고, 앱의 AGENTS.md와' printf '%s\n' '.titan-docs/START-HERE.md를 읽는다. `.titan/project-preferences.json`의 appId/userId를' printf '%s\n' '대조해 선택한 언어도 읽는다. 이 파일은 개발 선호도이며 인증·모듈 생성·빌드·배포 승인이 아니다.' printf '%s\n' 'SDK나 부모 폴더에는 업무 코드를' printf '%s\n' '임의로 작성하지 않는다. 앱은 모듈 0개로 시작한다. 다른 계정으로 같은 앱에 참여해도' printf '%s\n' '별도 사용자 폴더를 사용한다. 예전 버전에서 만든 폴더는 자동 이동하거나 복제하지 않는다.' printf '%s\n' '' printf '%s\n' 'SDK의 상세 계약은 다음 파일에 있다. 프로필 선택은 이 가이드에 이미 지정되어' printf '%s\n' '있으므로 사용자에게 내부 연결 이름을 다시 선택하게 하지 않는다.' printf '%s\n' '' printf '%s\n' '```text' printf '%s\n' "$sdk_guide_start" printf '%s\n' '```' printf '%s\n' '' printf '%s\n' '## 실패와 지원 범위' printf '%s\n' '' printf '%s\n' '### 빈 폴더에 이전 생성 기록이 남아 있을 때' printf '%s\n' '' printf '%s\n' '`CORE_LOCAL_WORKSPACE_MISSING`은 새 앱 생성 실패가 아니다. 같은 요청으로 만든 앱이' printf '%s\n' '서버에 남아 있지만 로컬 앱 폴더가 없다는 뜻이다. CLI가 반환하는 data.app·root·userId와' printf '%s\n' 'questions를 보여주고 **기존 작업공간 복원 승인**을 받는다. 그 뒤에만 반환된 nextActions의' printf '%s\n' '`init application`과 인자 전체를 그대로 수행한다. `--restore-local yes`·기존 request-id·' printf '%s\n' '`--resume-app`·`--confirm-user`를 AI가 계산하거나 다른 값으로 대체하지 않는다.' printf '%s\n' '이 복구 기능은 공개 SDK에 포함되어 있으며 별도 운영자 파일이나 복구용 실행 파일은 필요 없다.' printf '%s\n' '' printf '%s\n' '복원은 기존 앱·작업공간 ID와 레이아웃을 유지한다. 검증된 캐시와 초기 문서만 복원하며' printf '%s\n' '새 서버 앱·모듈·빌드·배포를 만들지 않는다. 이전 사용자 코드 전체 복원이라고 설명하지 않는다.' printf '%s\n' '이미 모듈이 있으면 `CORE_RESTORE_MODULE_SOURCE_REQUIRED`로 중단한다. 보호된 캐시가 없거나' printf '%s\n' '부모 폴더 identity가 바뀐 경우, 또는 기존 파일·다른 계정이 있으면 자동 수정·덮어쓰기하지 않는다.' printf '%s\n' '실행 중 중단된 복원도 CLI가 돌려주는 같은 승인 대상·다음 행동으로 재개한다.' printf '%s\n' '' printf '%s\n' '사용자가 정말 별도의 새 앱을 원하면 기존 앱의 존재를 설명하고 새 생성 계획을 따로 승인받는다.' printf '%s\n' '실패를 감추려고 새로운 request-id나 앱 이름으로 바꾸지 않는다. 앱의 언어를 바꾸려는 목적만으로' printf '%s\n' '초기화를 반복하지 않는다. 복원 후 모듈 계획에서 화면/서버의 언어를 확인하면 된다.' printf '%s\n' '' printf '%s\n' '생성 결과가 불명확하거나 이미 생성된 appId가 있으면 새 앱을 다시 만들지 않는다.' printf '%s\n' '같은 이름·프로필·폴더·request-id를 유지한다. application list로 생성 여부를 확인하고' printf '%s\n' '사용자가 확인한 실제 appId로 --resume-app을 지정해 재개한다. 새 request-id로 우회하지 않는다.' printf '%s\n' '기존 파일·가이드·잠금·인증 정보를 삭제하거나 덮어쓰지 않는다.' printf '%s\n' '새 가이드의 CLI 버전과 `localWorkspaceRecovery` 계약이 없으면 구버전일 수 있으므로' printf '%s\n' '공개 설치 페이지에서 갱신하고 설치기가 출력한 새 안내 파일을 읽는다. 오래된 고정 경로를 재사용하지 않는다.' printf '%s\n' '' printf '%s\n' '앱을 준비한 뒤 SDK START-HERE.md와 그 옆 AI-END-TO-END-TEST.md의 전체 순서로 이어간다.' printf '%s\n' '웹 화면이 있으면 모듈 계획과 구현 전에 SDK의 development-kit/implementation/ui-guide.md를 반드시 읽는다.' printf '%s\n' 'UI 모듈에는 웹 페이지와 SSR만 구현하고, 화면에서 호출하는 API 기능은 별도의 서비스 모듈에서 개발하여 연동한다.' printf '%s\n' 'module contracts/list로' printf '%s\n' '공식 템플릿을 확인하고 module create로 소스 준비와 최초 빌드를 한다. 이후 module build,' printf '%s\n' 'module status, 사용자 확인 후 module deploy를 사용한다. 배포는 서버 승인 서명 ZIP 대상만' printf '%s\n' '허용하며 unsigned 우회는 없다. 공통 정책의 새 Node·Java·Go ZIP은 정상 CLI가 앱 권한으로' printf '%s\n' '고정 ZIP 승인·단기 STS를 받고 로컬에서 서명 요청·대기·배포를 수행한다. 개발자 AWS 로그인이나' printf '%s\n' '수동 앱 허용 목록을 요구하지 않는다. AWS Signer가 서명하며 로컬 개인 키를 준비하지 않는다.' printf '%s\n' '사용자가 배포까지 승인했다면 빌드 뒤 deploy·status·module invoke의 실제 결과와 도메인 확인까지' printf '%s\n' '이어가고, URL 검증에 브라우저가 필요하면 기존 플랫폼 인증 경로로 사용자와 협업한다.' printf '%s\n' '실패·불명확 결과는 기존 승인·ZIP을 보존해 중단·보고한다. CLI PR·병합·pull/rebase·이미지 배포는 아직 지원하지 않는다.' printf '%s\n' 'capabilities에 없는 기능을 가정하거나' printf '%s\n' '내부 API·AWS·SSO·다른 프로젝트로 우회하지 않고 필요한 미지원 단계를 알린다.' printf '%s\n' '설치기는 로그인·앱 생성·커밋·Push를 자동 실행하지 않는다.' printf '%s\n' '' printf '%s\n' '' printf '%s\n' 'CLI 0.5.45부터 IDE 없는 Git 저장을 지원한다. `titan git --help`와' printf '%s\n' '`development-kit/documentation/guidelines/cli-git-guide.md`를 읽고, 현재 앱에서' printf '%s\n' '`git status` → 필요한 경우 `git init` → 개인 브랜치 → 파일 선택 → 승인된 commit·push 순서로 진행한다.' printf '%s\n' '새 저장소 URL이나 AWS 로그인을 요구하지 않는다. 커밋·Push와 앱 배포 성공은 별도로 확인한다.' printf '%s\n' '' printf '%s\n' '## 문서로 해결되지 않는 문제: SDK 버전부터 확인' printf '%s\n' '' printf '%s\n' '현재 가이드와 제공된 정상 명령으로 원인을 해결하지 못했다면, 추가 우회나 재설치에 앞서' printf '%s\n' '**SDK 버전 확인을 먼저 수행한다.** 설치 안내 또는 이전 명령 결과에서 확인한 CLI 절대 경로를 사용한다.' printf '%s\n' '' printf '%s\n' '```sh' printf '%s\n' 'titan update --check --output json' printf '%s\n' '```' printf '%s\n' '' printf '%s\n' '1. 원래 오류의 단계·코드와 appId·moduleId·buildId·approvalId를 보존한다. 진행 중인 명령은 끝날 때까지 기다린다.' printf '%s\n' '2. 위 명령의 `data.installedRelease`, `data.latestRelease`, `data.updateAvailable`을 확인한다. 로그인이나 개발자 AWS 인증은 필요 없다.' printf '%s\n' '3. **`updateAvailable=true`이고 `selfUpdateSupported=true`일 때만** 사용자에게 버전 차이를 알리고, SDK 갱신이 허용된 작업 범위에서' printf '%s\n' ' `titan update --output json`을 실행한다. 최신 버전이면 갱신·재설치를 반복하지 않는다.' printf '%s\n' '4. 갱신에 성공하면 결과의 `data.executable`로 후속 CLI를 호출하고 **`data.guide`를 다시 읽는다.**' printf '%s\n' ' 새 SDK의 AI 개발 가이드와 `capabilities --output json`을 확인한다. 앱 안의 고정 KIT 사본·사용자 문서는 임의 덮어쓰지 않는다.' printf '%s\n' '5. 원래 앱·모듈과 승인·ZIP을 그대로 두고 정상 상태 조회로 진행 상황부터 확인한다.' printf '%s\n' ' **SDK 갱신 성공은 앱 생성·복구·서명·배포를 다시 실행할 승인이 아니다.** 원격 접수 여부가 불명확하거나' printf '%s\n' ' 이미 실패한 변경 작업은 해당 중단·재개 규칙을 따른다. 새 앱·모듈·승인·ZIP으로 우회하지 않는다.' printf '%s\n' '6. 새 버전이 없거나 확인·갱신에 실패했거나 새 가이드로도 해결되지 않으면, 설치·공개 버전과 안전한 오류 정보를 보고한다.' printf '%s\n' ' 같은 문제로 버전 확인·갱신을 반복하지 않고 AWS 접근·플랫폼 소스 수정·잠금 삭제로 우회하지 않는다.' printf '%s\n' '' printf '%s\n' '`update` 명령이 없는 옛 공개 SDK만 공식 설치 명령으로 한 번 갱신한 뒤 이 순서를 적용한다.' printf '%s\n' '플러그인 내장·직접 복사한 CLI도 `--check`는 가능하지만, `checkScope=cli-version`은 공개 SDK의 CLI 버전과 비교한 결과다.' printf '%s\n' '`selfUpdateSupported=false`이면 직접 `update`를 실행하지 않고 IDE 플러그인 또는 원본 배포 경로의 갱신 방법을 안내한다.' printf '%s\n' '이 결과만으로 플러그인이 최신이라고 판단하지 않는다. 플러그인 버전 확인은 IDE의 업데이트 기능을 사용한다.' printf '%s\n' '관리형 SDK 결과는 `checkScope=sdk-release`이며 SDK 묶음 자체를 비교한다.' printf '%s\n' '이미 저장된 옛 설치 스크립트나 버전별 실행 경로에 고정하지 말고 고정 진입 경로와 갱신 결과의 경로를 사용한다.' printf '%s\n' '이 SDK 진단 절차는 아래 플랫폼 실패 중단 규칙의 사전 점검이며, 원래 변경 작업을 재시도하는 예외가 아니다.' } > "$sdk_guide_temp" # Hard-link publication is atomic and never replaces a file or follows its link. # Candidate and destination are on the same filesystem. Edited guides are retained. sdk_guide_number=0 while :; do if [ "$sdk_guide_number" -eq 0 ]; then sdk_guide_file="$sdk_guide_dir/TITAN-START-HERE.md" else sdk_guide_file="$sdk_guide_dir/TITAN-START-HERE.sdk-$sdk_release-$sdk_guide_number.md"; fi if [ -f "$sdk_guide_file" ] && [ ! -L "$sdk_guide_file" ] && cmp -s "$sdk_guide_temp" "$sdk_guide_file"; then break; fi if /bin/link "$sdk_guide_temp" "$sdk_guide_file" 2>/dev/null; then break; fi sdk_guide_number=$((sdk_guide_number + 1)) [ "$sdk_guide_number" -le 99 ] || fail 'SDK installed, but existing guides were preserved. Choose a clean workspace for a new guide.' done printf '%s\n' "Titan SDK installed: $sdk_dest" printf '%s\n' "Command: $sdk_prefix/bin/titan" printf '%s\n' "AI start guide: $sdk_guide_file" printf '%s\n' '다음: AI에게 이 시작 가이드를 읽고 Titan 로그인을 진행해 달라고 요청하세요.' printf '%s\n' 'Existing files, PATH and login sessions were preserved.' printf '%s\n' 'Next: follow the guide for Titan login, source management, builds and approved signed ZIP deployment.'