에이블캠퍼스 Apps의 화면 디자인, 쉬운 시각적 설명과 AI 기능 개발을 지원합니다. 배포는 명시적으로 요청받았을 때만 수행합니다.

웹앱의 화면 디자인, 쉬운 시각적 설명, AI 기능 개발을 돕고 사용자가 요청하면 에이블캠퍼스 Apps에 배포한다. 배포는 명시적으로 요청받았을 때만 한다.
| 경로 | 대상 | Claude 로그인 | Apps 연결 |
|---|---|---|---|
| 공개 마켓플레이스(이 저장소) | Claude Code | Claude 구독(Pro, Max), API 키, 조직 게이트웨이 중 아무것 | 기기 로그인(start_apps_login) |
| 허브 마켓플레이스 | 교육 참가자의 Claude 데스크톱 | 에이블캠퍼스 로그인 | 자동 연결 |
apps_plugin_status의 distribution이 public이면 공개 마켓플레이스, hub이면 허브 마켓플레이스 설치다. 설치와 갱신 방법은 저장소 안내에 있다.
에이블캠퍼스 계정이 없어도 아래는 동작한다.
| 구성 요소 | 내용 |
|---|---|
frontend-design 스킬 | 화면 제작과 디자인 개선 |
eli5 스킬 | 구조와 데이터 흐름을 그림으로 쉽게 설명 |
ask-questions-if-underspecified 스킬 | 요구가 모호할 때 먼저 되묻기 |
build-ai-feature 스킬 | AI 기능 구현. 에이블캠퍼스 계정이 없으면 사용자가 가진 AI 제공자 API 키를 .env.local에 두고 구현한다(get_ai_gateway가 이 길을 안내한다) |
manage-apps-plugin 스킬 | 설치, 갱신, 제거 안내 |
apps_plugin_status | 실행 중인 판, API 주소, 인증 방식, 설치 경로 |
get_plugin_skill | 게시된 최신 스킬 지침. 로그인이 없으면 동봉 지침을 쓰라고 답한다 |
preview_sqlite_transition, preview_local_files_transition | SQLite와 로컬 파일 이전의 읽기 전용 미리보기 |
Claude 구독은 Claude Code 안에서만 쓰는 로그인이다. 만드는 앱의 서버가 부를 API 키로 쓸 수 없다.
아래 도구는 api.aible-campus.com을 부르며 에이블캠퍼스 계정과 기기 로그인이 필요하다. 로그인이 없으면 start_apps_login으로 로그인하라는 오류를 돌려준다.
| 묶음 | 도구 |
|---|---|
| 로그인 | start_apps_login, complete_apps_login |
| 계정과 공간 | apps_whoami, list_educations, list_deployment_organizations, list_deployments |
| 검증과 배포 | validate_project, deploy_project, deployment_status, deployment_logs, checkout_app_source |
| 설정과 관리 | deployment_config, set_deployment_config, stop_deployment, move_deployment, delete_deployment |
| 데이터 이전 | migrate_sqlite_data, migrate_local_files |
| 앱용 AI 환경 | get_ai_gateway, configure_ai_gateway |
verify_ai_gateway는 에이블캠퍼스에 로그인하지 않고 프로젝트의 .env.apps-ai로 AI 게이트웨이에 짧은 요청을 보낸다. 그 파일은 configure_ai_gateway가 만들므로 실제로는 로그인한 뒤에 쓴다.
start_apps_login이 승인 주소를 기본 브라우저에서 열고 complete_apps_login이 승인을 기다린다. 브라우저를 열 수 없으면 주소와 코드를 직접 안내한다.aiblecampus-apps/device.json(권한 0600)에만 저장한다. 원문을 프로젝트 파일, 커밋, 대화에 남기지 않는다.설정하지 않아도 된다. 개발용 서버에 붙일 때만 쓴다.
| 이름 | 기본값 |
|---|---|
APPS_API_URL | https://api.aible-campus.com |
APPS_IDENTITY_URL | 운영 API면 https://login.aible-campus.com, 그 밖에는 API 주소의 api.를 login.으로 바꾼 값 |
APPS_DEVICE_CLIENT_ID | 에이블캠퍼스 기기 로그인 클라이언트 |
APPS_OPEN_BROWSER | 1. 0, false, off면 승인 주소를 자동으로 열지 않는다 |
APPS_DEVICE_LABEL | 이 컴퓨터의 이름(기기 목록에 보이는 이름) |
APPS_CREDENTIAL_FILE | 사용자 설정 폴더의 aiblecampus-apps/device.json |
APPS_DEPLOYMENT_ATTEMPT_FILE, APPS_DELETE_CONFIRMATION_FILE | 사용자 설정 폴더 안 |
Claude Code의 ANTHROPIC_* 설정은 플러그인과 관계없다. 구독 로그인이든 게이트웨이든 플러그인은 같게 동작한다.
hooks/는 Claude Code mod다. Auto 권한 모드에서 배포 흐름 도구(apps_plugin_status, apps_whoami, list_educations, list_deployment_organizations, list_deployments, validate_project, deploy_project, deployment_status, deployment_logs, checkout_app_source)에 따로 정한 규칙이 없을 때만 허용으로 바꾼다. deny 규칙과 훅의 거절은 그대로다. 삭제, 중지, 이동, 데이터 이전, 설정, AI 키, 로그인 도구는 넣지 않았다.
dist/mcp.js: MCP 서버 번들. mcp/ 소스를 npm ci && npm run build로 다시 만들 수 있다.skills/: 스킬. frontend-design, eli5, ask-questions-if-underspecified는 원본 저장소의 파일을 고치지 않고 넣었다. 출처, 고정 커밋, 라이선스는 각 폴더의 NOTICE.md와 라이선스 파일에 있다.hooks/: Auto 권한 모드 mod.hooks/register.js 39 lines1/**
2 * 앱 도구 자동 허용 mod(Claude Code mods, 클로드 앱 Code 탭과 CLI).
3 *
4 * Auto 권한 모드에서 배포 흐름의 앱 도구 호출을 Auto 판별기에 넘기지 않고 허용한다. 판별기는 deploy_project를 "외부 서비스로 소스 업로드"로
5 * 막을 수 있고 같은 요청도 기기와 회차마다 결과가 달랐다(2026-10-13 소유자 화면). 사용자가 배포를 부탁한 것이므로 판별기 대신 허용한다.
6 * - tool.check는 권한 규칙과 PreToolUse 훅이 정한 뒤에 온다. 아무 규칙도 없으면 `ask`(Auto 모드에서는 이 뒤에 판별기가 본다)이고,
7 * 그때만 `allow`로 바꾼다. 문서: mod가 허용한 호출은 Auto 모드에서 판별기 확인 없이 실행된다(code.claude.com/docs/en/permissions "Extend permissions with hooks")
8 * - deny 규칙과 훅의 거절, 이미 허용한 것은 그대로 둔다. 삭제, 중지, 이동, 데이터 전환, 설정과 AI 키, 로그인 도구는 넣지 않는다
9 * - 이 파일은 플러그인의 MCP 서버 구성 요소와 함께 간다. 앱 기능을 끈 교육의 플러그인에는 없다(packages/domain/src/gateway/plugin-marketplace.ts)
10 */
11const PREFIX = "mcp__plugin_aiblecampus-apps_apps__";
12
13/** 배포 흐름에서 쓰는 도구. 조회, 검증, 배포, 기존 앱 소스 받기 */
14export const AUTO_ALLOWED_TOOLS = [
15 "apps_plugin_status",
16 "apps_whoami",
17 "list_educations",
18 "list_deployment_organizations",
19 "list_deployments",
20 "validate_project",
21 "deploy_project",
22 "deployment_status",
23 "deployment_logs",
24 "checkout_app_source",
25].map((name) => PREFIX + name);
26
27/**
28 * @typedef {{ decision: "allow" | "ask" | "deny"; reason?: string }} ToolDecision
29 * @typedef {(event: unknown) => Promise<ToolDecision>} Next
30 * @typedef {(api: unknown, event: unknown, next: Next) => Promise<ToolDecision>} ToolCheckHook
31 * @param {(event: "tool.check", matcher: { tool: string[] }, hook: ToolCheckHook) => void} on
32 */
33export function register(on) {
34 on("tool.check", { tool: AUTO_ALLOWED_TOOLS }, async (_api, e, next) => {
35 const decided = await next(e);
36 return decided.decision === "ask" ? { decision: "allow" } : decided;
37 });
38}
39