SLOPSHOPPER

aiblecampus-apps

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

guard
A shopper browsing a rack in a slop shop
README

에이블캠퍼스 Apps 플러그인

웹앱의 화면 디자인, 쉬운 시각적 설명, AI 기능 개발을 돕고 사용자가 요청하면 에이블캠퍼스 Apps에 배포한다. 배포는 명시적으로 요청받았을 때만 한다.

두 가지 설치 경로

경로대상Claude 로그인Apps 연결
공개 마켓플레이스(이 저장소)Claude CodeClaude 구독(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_transitionSQLite와 로컬 파일 이전의 읽기 전용 미리보기

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이 승인을 기다린다. 브라우저를 열 수 없으면 주소와 코드를 직접 안내한다.
  • 기기별 DPoP 키와 refresh token은 사용자 설정 폴더의 aiblecampus-apps/device.json(권한 0600)에만 저장한다. 원문을 프로젝트 파일, 커밋, 대화에 남기지 않는다.

환경변수(선택)

설정하지 않아도 된다. 개발용 서버에 붙일 때만 쓴다.

이름기본값
APPS_API_URLhttps://api.aible-campus.com
APPS_IDENTITY_URL운영 API면 https://login.aible-campus.com, 그 밖에는 API 주소의 api.를 login.으로 바꾼 값
APPS_DEVICE_CLIENT_ID에이블캠퍼스 기기 로그인 클라이언트
APPS_OPEN_BROWSER1. 0, false, off면 승인 주소를 자동으로 열지 않는다
APPS_DEVICE_LABEL이 컴퓨터의 이름(기기 목록에 보이는 이름)
APPS_CREDENTIAL_FILE사용자 설정 폴더의 aiblecampus-apps/device.json
APPS_DEPLOYMENT_ATTEMPT_FILE, APPS_DELETE_CONFIRMATION_FILE사용자 설정 폴더 안

Claude Code의 ANTHROPIC_* 설정은 플러그인과 관계없다. 구독 로그인이든 게이트웨이든 플러그인은 같게 동작한다.

Auto 권한 모드

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.
Source 1 files
hooks/register.js 39 lines
1/**
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