> ## Documentation Index
> Fetch the complete documentation index at: https://v3.selectfromuser.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AGENTS.md

> `sapp connect` 과정에서 [AGENTS.md](http://AGENTS.md) 가 자동으로 추가됩니다.

````markdown theme={null}
<!-- SELECT_CLI_AGENT_GUIDE_START -->
# Select CLI Agent Guide

This guide is for coding agents working inside a local project that uses the
Select CLI. Use it together with `README.md`.

## Authentication

Agents should connect with a project deploy token:

```sh
sapp connect --deployToken="<DEPLOY_TOKEN>"
```

`sapp connect` verifies the token with the server and writes `app.json` in the
current directory. The file includes:

- `version`
- `projectId`
- `projectName`
- `projectAlias`
- `projectPublicKey`
- `apiBaseUrl`
- `deployToken`
- `expiresAt`
- `env`

Keep `app.json` private. It contains credentials.

Project API calls use only the `Authorization` header. Public `/fn` calls use
the project public key. Private `/fn` calls use the member session token saved
by `sapp member login`.

Never expose `deployToken` or `memberSessionToken` in browser code. Local HTML
apps may expose `projectPublicKey` because it is the public `pk_project_...`
key for public `/fn` calls.

## Environment

The default CLI environment is `dev`.

```sh
sapp env
sapp env dev
sapp env prod
```

`sapp api call` and `sapp resource ... query` use the selected environment.

- `dev`: `apihub-dev.selectfromuser.com`
- `prod`: `apihub.selectfromuser.com`

For a local API base URL, the CLI maps to:

- `dev`: `apihub-dev.localhost:9502`
- `prod`: `apihub.localhost:9502`

## App Commands

```sh
sapp use <alias>
sapp apps
sapp app create <alias>
sapp deploy
sapp deploy <alias>
sapp deploy <alias> <path>
sapp deploy <alias> -m="message"
sapp deploy <alias> --message="message"
sapp deploy <alias> <path> -m="message"
sapp deployments
sapp deployments <alias>
sapp deployments <alias> files <deployment-id>
sapp deployments <alias> set <deployment-id>
sapp rollback <deployment-id>
sapp rollback <alias> <deployment-id>
sapp open
sapp open <alias>
```

Notes:

- `sapp use <alias>` saves the default app alias in `app.json`.
- `sapp apps` lists `GenProjectPortal` apps in the connected project.
- `sapp app create <alias>` creates a new `GenProjectPortal` app. If the alias
  is omitted, the CLI prompts for it.
- Deploy target must contain `index.html`.
- Deploy skips `.git`, `.gitignore`, `.sappignore`, `node_modules`, `app.json`,
  and files matched by `.gitignore` or `.sappignore`.
- Deployment requires a deploy token with the `app` scope.

## Local HTML Apps

Local single-file HTML apps should call project APIs through `/fn/<name>` with
the endpoint derived from `app.json`.

Use only these browser-safe values from `app.json`:

- `projectAlias`
- `projectPublicKey`
- `apiBaseUrl`
- `env`

Do not include these private values in HTML or client JavaScript:

- `deployToken`
- `memberSessionToken`
- `memberSession`

Endpoint mapping:

- `env: "dev"`: use `https://apihub-dev.selectfromuser.com/v3/fn/<name>`.
- `env: "prod"`: use `https://apihub.selectfromuser.com/v3/fn/<name>`.
- Local `apiBaseUrl` values map to `http://apihub-dev.localhost:9502/v3/fn/<name>`
  or `http://apihub.localhost:9502/v3/fn/<name>`.

When generating a local HTML app, prefer embedding a small public config block
from `app.json` so the app calls the right project and environment:

```html
<script>
  window.SELECT_APP = {
    apiBaseUrl: 'https://apihub-dev.selectfromuser.com/v3',
    projectPublicKey: 'pk_project_<project-alias>',
    env: 'dev',
  }
</script>
```

Public API fetch example:

```html
<script>
  const callApi = async (name, input = {}) => {
    const config = window.SELECT_APP
    const res = await fetch(`${config.apiBaseUrl}/fn/${encodeURIComponent(name)}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${config.projectPublicKey}`,
      },
      body: JSON.stringify({
        stage: config.env,
        ...input,
      }),
    })
    return await res.json()
  }
</script>
```

For private APIs, do not hard-code a member token into static HTML. Use
`sapp member login` only for CLI-side testing, or build a proper browser login
flow that obtains a session token at runtime.

## Local HTML Auth APIs

Browser login, signup, and session checks use `/api` endpoints. These requests
must include the project public key:

```js
Authorization: `Bearer ${window.SELECT_APP.projectPublicKey}`
```

Passwords for `/api/login` and `/api/signup` are base64-encoded before sending.
They are not sent as plain text:

```js
const passwordPayload = (value) => btoa(unescape(encodeURIComponent(value)))
```

The API payload field is `login_id`. In local JavaScript helpers, use `loginId`
for readability and map it to `login_id` in the request body.

Signup:

```js
const signup = async ({ loginId, password, email = '', phone = '', profile = {} }) => {
  const config = window.SELECT_APP
  const res = await fetch(`${config.apiBaseUrl}/api/signup`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${config.projectPublicKey}`,
    },
    body: JSON.stringify({
      login_id: loginId,
      login_pw: passwordPayload(password),
      email,
      phone,
      profile_json: profile,
    }),
  })
  return await res.json()
}
```

Login:

```js
const login = async ({ loginId, password }) => {
  const config = window.SELECT_APP
  const res = await fetch(`${config.apiBaseUrl}/api/login`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${config.projectPublicKey}`,
    },
    body: JSON.stringify({
      login_id: loginId,
      login_pw: passwordPayload(password),
    }),
  })
  const data = await res.json()
  if (data.access_token) localStorage.setItem('select_access_token', data.access_token)
  return data
}
```

Session check:

```js
const me = async () => {
  const config = window.SELECT_APP
  const accessToken = localStorage.getItem('select_access_token') || ''
  const res = await fetch(`${config.apiBaseUrl}/api/me`, {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${config.projectPublicKey}`,
      'X-Session-Token': accessToken,
    },
  })
  return await res.json()
}
```

Profile update:

```js
const updateProfile = async (profile_json) => {
  const config = window.SELECT_APP
  const accessToken = localStorage.getItem('select_access_token') || ''
  const res = await fetch(`${config.apiBaseUrl}/api/profile`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${config.projectPublicKey}`,
      'X-Session-Token': accessToken,
    },
    body: JSON.stringify({
      profile_json,
    }),
  })
  return await res.json()
}
```

Calling private `/fn` APIs from browser code uses the saved session token as the
bearer token instead of the public key:

```js
const callPrivateApi = async (name, input = {}) => {
  const config = window.SELECT_APP
  const accessToken = localStorage.getItem('select_access_token') || ''
  const res = await fetch(`${config.apiBaseUrl}/fn/${encodeURIComponent(name)}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      stage: config.env,
      ...input,
    }),
  })
  return await res.json()
}
```

Expected responses:

- `/api/signup` and `/api/login`: `{ message, session, access_token }`.
- `/api/me`: `{ message, session }`.
- `/api/profile`: `{ message, session }`.

`/api/login` response:

```json
{
  "message": "ok",
  "session": {
    "id": 7,
    "login_id": "test",
    "email": "test@test.com",
    "phone": "",
    "roles": [],
    "profile_json": {}
  },
  "access_token": "..."
}
```

`/api/signup` response:

```json
{
  "message": "ok",
  "session": {
    "id": 7,
    "login_id": "test",
    "email": "test@test.com",
    "phone": "",
    "roles": [],
    "profile_json": {}
  },
  "access_token": "..."
}
```

`/api/me` response:

```json
{
  "message": "ok",
  "session": {
    "id": 7,
    "login_id": "test",
    "email": "test@test.com",
    "phone": "",
    "roles": [],
    "profile_json": {}
  }
}
```

`/api/profile` response:

```json
{
  "message": "ok",
  "session": {
    "id": 7,
    "login_id": "test",
    "email": "test@test.com",
    "phone": "",
    "roles": [],
    "profile_json": {
      "name": "Test User"
    }
  }
}
```

There is no static logout endpoint requirement for local HTML apps. Log out by
removing the locally stored access token.

## Member Commands

```sh
sapp member
sapp member signup -loginid <id> --email <email> --phone <phone> --password <password>
sapp member <id>
sapp member <loginid>
sapp member <id|loginid> set --permission=Admin,User,Manager
sapp member <id|loginid> set --propertyKey=LEVEL --propertyValue="1"
sapp member <id|loginid> set --profile="{\"a\":1}"
sapp member login
sapp member login -loginid <id> --password <password>
```

Notes:

- Member management requires the `member` deploy-token scope.
- `sapp member login` calls `/api/login` and stores `memberSessionToken` in
  `app.json`.
- Use `sapp member login` before calling private APIs through
  `sapp api call`.

## API Commands

```sh
sapp api
sapp api list
sapp api describe <name>
sapp api call <name> --input '{"a":1}'
sapp api get <name>
sapp api validate <file>
sapp api plan <file>
sapp api apply <file>
sapp api delete <file>
```

Notes:

- API management requires the `api` deploy-token scope.
- `sapp api call` calls `/fn/<name>` using the selected `sapp env`.
- `sapp api get <name>` prints YAML.
- `sapp api apply <file>` supports multiple YAML documents separated by `---`.
- `sapp api delete <file>` deletes API paths listed in the YAML file.
- `sapp api plan <file>` shows changed lines with `-` and `+`.

SQL API template rules:

- `{{ name }}` and `${name}` bind body/session values as prepared-statement
  parameters.
- `{{ ENV_KEY }}` inserts environment values.
- Type hints are supported inside existing `{{ }}` and `${}` variables:
  `int:userId`, `bool:active`, `json:payload`, `identifier:table`.
- `int:` requires a safe integer and inserts it directly into SQL. Use it for
  numeric clauses such as `LIMIT` and `OFFSET` where MySQL prepared parameters
  can fail.
- `bool:` accepts booleans, `true`/`false`, or `1`/`0` and binds a boolean.
- `json:` requires valid JSON or serializes an object/array before binding.
- `identifier:` validates a SQL identifier such as `users` or `public.users`
  and inserts it directly into SQL. Do not use `identifier:` for user-controlled
  arbitrary SQL fragments.

Example API YAML:

```yaml
path: find-users
acl:
  public: false
  requiredRoles:
    - Admin
query:
  resourceName: mysql.base
  resourceType: mysql
  type: sql
  sql: |
    SELECT *
    FROM {{ identifier:table }}
    WHERE user_id = ${int:userId}
      AND active = ${bool:active}
      AND name LIKE CONCAT('%', ${name}, '%')
    LIMIT ${int:limit}
    OFFSET ${int:offset}
```

## Resource Commands

```sh
sapp resource list
sapp resource <name> query --input="SELECT 1"
sapp resource <name> query
sapp resource <name> history
```

Notes:

- Resource commands require the `resource` deploy-token scope.
- Resource query uses the environment from `sapp env`.
- MySQL query runs are appended to `GenProjectResourcePad` with `agent_id`.
- CLI resource query does not store result JSON by default.

## Practical Agent Workflow

1. Run `sapp connect --deployToken="<DEPLOY_TOKEN>"`.
2. Run `sapp env dev` or `sapp env prod`.
3. Use `sapp apps`, `sapp resource list`, `sapp api list`, or `sapp member` to
   inspect the project.
4. For private API calls, run `sapp member login -loginid <id> --password <pw>`.
5. Use `sapp api call <name> --input '{"key":"value"}'` to test APIs.
6. Use `sapp deploy <alias> <path> -m="message"` to deploy app files.

## Practical Agent Workflow Details

### First connection

1. Ask the user for a deploy token when it is not already available.
2. Run `sapp connect --deployToken="<DEPLOY_TOKEN>"`.
3. Confirm `app.json` was created.
4. Confirm `projectPublicKey`, `projectAlias`, `apiBaseUrl`, and `env` exist in
   `app.json`; these values drive local HTML `/fn` calls.
5. Run `sapp env` to check the active environment.
6. Set the intended environment with `sapp env dev` or `sapp env prod`.

Use `dev` unless the user explicitly asks to operate on production.

### Inspect project apps

1. Run `sapp apps` to list project apps.
2. Run `sapp app create <alias>` when a new app is needed.
3. Run `sapp open` to open the project console when a browser is useful.
4. Run `sapp deployments <alias>` before changing an app.
5. Use `sapp deployments <alias> files <deployment-id>` when you need to inspect
   what was deployed.
6. Run `sapp use <alias>` when the user wants one app to be the default target.

### Deploy app files

1. Confirm the deploy directory contains `index.html`.
2. Confirm `.sappignore` excludes local-only files.
3. If the app already exists, run `sapp deploy <alias> <path> -m="short message"`.
4. If a new app is needed, run `sapp app create <alias>` first.
5. Run `sapp deployments <alias>` and confirm the new deployment is active.
6. Run `sapp open <alias>` if the user wants to inspect the deployed app.

If the user asks for rollback:

1. Run `sapp deployments <alias>`.
2. Pick the requested deployment id.
3. Run `sapp deployments <alias> set <deployment-id>`.
4. Run `sapp deployments <alias>` again to confirm the active marker changed.

### Inspect members

1. Run `sapp member` to list members.
2. Run `sapp member <id|loginid>` to inspect one member.
3. Use `sapp member signup ...` to create a member when requested.
4. Use `sapp member <id|loginid> set --permission=...` to replace roles.
5. Use `sapp member <id|loginid> set --propertyKey=... --propertyValue=...` to
   update one internal property.
6. Use `sapp member <id|loginid> set --profile='{"key":"value"}'` to replace the
   member profile.

For private API tests:

1. Run `sapp member login -loginid <id> --password <password>`.
2. Confirm `memberSessionToken` is saved in `app.json`.
3. Run `sapp api call <name> --input '{"key":"value"}'`.

### Inspect APIs

1. Run `sapp api list`.
2. Run `sapp api describe <name>` for JSON output.
3. Run `sapp api get <name>` for YAML output.
4. Save YAML changes to a local file.
5. Run `sapp api validate <file>`.
6. Run `sapp api plan <file>` and review the `-` and `+` lines.
7. Run `sapp api apply <file>` when the user approves the change.
8. Run `sapp api call <name> --input '{"key":"value"}'` to verify behavior.

Use multi-document YAML with `---` when applying several API definitions at
once.

### Inspect resource schemas

1. Run `sapp resource list` to find the resource name.
2. Run `sapp env dev` or `sapp env prod` to select the target database.
3. Run `sapp resource <name> history` to see recent schema and query work.
4. Run `sapp resource <name> query --input="SHOW TABLES"` to list tables.
5. Run `sapp resource <name> query --input="DESC TableName"` to inspect a table
   schema.
6. Run `sapp resource <name> query --input="SHOW CREATE TABLE TableName"` when
   the full schema is needed.

`sapp resource <name> history` is useful for checking which schema queries were
already run by agents or console users.

### Add or change resource schema

1. Select the correct environment with `sapp env dev` or `sapp env prod`.
2. Inspect the current schema first:

```sh
sapp resource <name> query --input="SHOW TABLES"
sapp resource <name> query --input="DESC TableName"
```

3. Prepare the SQL change locally.
4. Prefer idempotent SQL for creation:

```sh
sapp resource <name> query --input="CREATE TABLE IF NOT EXISTS SampleTask (id int unsigned NOT NULL AUTO_INCREMENT, title varchar(1000) DEFAULT NULL, status varchar(100) DEFAULT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id))"
```

5. For schema changes, use explicit `ALTER TABLE` statements:

```sh
sapp resource <name> query --input="ALTER TABLE SampleTask ADD COLUMN assignee varchar(255) DEFAULT NULL"
```

6. Re-run `DESC TableName` after the change.
7. Run `sapp resource <name> history` so the user can see what was executed.

Be careful with destructive SQL such as `DROP`, `TRUNCATE`, broad `DELETE`, or
column type changes. Get explicit user approval before running those.

### Call project APIs

1. Set the target environment with `sapp env dev` or `sapp env prod`.
2. For public APIs, ensure `projectPublicKey` exists in `app.json`.
3. For private APIs, run `sapp member login`.
4. Run:

```sh
sapp api call <name> --input '{"key":"value"}'
```

If a private call returns `session token required`, login again with
`sapp member login -loginid <id> --password <password>`.

<!-- SELECT_CLI_AGENT_GUIDE_END -->
````
