<!-- 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 -->