feat: 收录 inkreach-teable-plugin skill;install.sh 安装全部技能
skills/ 现包含 inkreach-teable + inkreach-teable-plugin, npx skills add 一并安装两个;install.sh 改为遍历安装 skills/*。
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# Plugin deployment
|
||||
|
||||
Uploading a bundle plugin, enabling it, and managing permissions.
|
||||
|
||||
## Upload — `POST /api/plugin/:pluginId/upload-bundle`
|
||||
|
||||
Upload the built bundle to the host. Uses `multipart/form-data`.
|
||||
The `pluginId` in the URL must match the `id` in `plugin.json`.
|
||||
|
||||
```bash
|
||||
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('plugin.json'))['id'])")
|
||||
curl -s -b "auth_session=$SESSION" \
|
||||
-F "js=@dist/main.js" \
|
||||
-F "css=@dist/styles.css" \
|
||||
-F "manifest=@plugin.json" \
|
||||
"http://localhost:3003/api/plugin/$PLUGIN_ID/upload-bundle"
|
||||
```
|
||||
|
||||
**Auth note**: Supports both session cookies (`auth_session`) and Personal Access
|
||||
Tokens (`Authorization: Bearer <PAT>`).
|
||||
|
||||
**Fields:**
|
||||
| Field | Required | Max size | Description |
|
||||
|---|---|---|---|
|
||||
| `js` | Yes | 5 MB | The IIFE bundle (`dist/main.js`) |
|
||||
| `css` | No | 1 MB | Optional stylesheet (`dist/styles.css`) |
|
||||
| `manifest` | Yes | 10 KB | `plugin.json` manifest |
|
||||
|
||||
**Returns** `201` with `{ id, name, version, ... }` — the plugin ID to use for
|
||||
enable and install.
|
||||
|
||||
**Security** (7-layer validation):
|
||||
1. Authentication — valid session cookie required (401 if missing)
|
||||
2. Developer whitelist — `plugin_developer` table lookup (403 if not whitelisted)
|
||||
3. Rate limit — 10 uploads/min per user (429 if exceeded)
|
||||
4. Size limits — JS ≤ 5 MB, CSS ≤ 1 MB, manifest ≤ 10 KB
|
||||
5. Manifest validation — required fields, `id` regex `[a-z0-9-]+`
|
||||
6. Content validation — JS must be IIFE format (prefix `!function`/`(()=>`),
|
||||
rejects `<script>`/`eval`/`document.write` injection; CSS strips `expression()`/
|
||||
`@import`/`javascript:` URIs
|
||||
7. Integrity — SHA-256 hash stored in DB, verified at load time
|
||||
|
||||
## Enable — `PUT /api/base/:baseId/plugin-config/:pluginId`
|
||||
|
||||
The plugin must be enabled before it can be installed to any position.
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X PUT \
|
||||
"http://localhost:3003/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"enabled":true,"tableIds":[]}'
|
||||
```
|
||||
|
||||
- `tableIds: []` — enabled base-wide (all tables)
|
||||
- `tableIds: ["tblX", "tblY"]` — enabled only for specific tables
|
||||
|
||||
## Install by position
|
||||
|
||||
### ContextMenu
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/table/$TABLE_ID/plugin-context-menu/install" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
|
||||
```
|
||||
|
||||
Body: `pluginId` (required), `name` (optional display name).
|
||||
|
||||
### Panel
|
||||
|
||||
Panel install requires an existing panel. First create one, then install to it:
|
||||
|
||||
```bash
|
||||
# Create a panel (if none exists)
|
||||
PANEL_ID=$(curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/table/$TABLE_ID/plugin-panel" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"My Panel"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
|
||||
# Install plugin to the panel
|
||||
curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/table/$TABLE_ID/plugin-panel/$PANEL_ID/install" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
|
||||
```
|
||||
|
||||
The `plg` prefixed ID is the panel's UUID, not a plugin ID. Create a panel first
|
||||
if you don't already have one.
|
||||
|
||||
### Dashboard
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/base/$BASE_ID/dashboard/$DASHBOARD_ID/plugin" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/table/$TABLE_ID/view/plugin" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
|
||||
```
|
||||
|
||||
Returns `{ pluginId, pluginInstallId, name, viewId }`. Creates a new view
|
||||
of type `plugin` in the table's view list. The plugin's `definePlugin.onLoad`
|
||||
must call `ctx.addView(id, component)` to render the component as a view.
|
||||
Authority Matrix entries are auto-created for the new install (default
|
||||
denied for non-owner roles).
|
||||
|
||||
### Route
|
||||
|
||||
Route-type plugins registered via `ctx.addRoute(path, component)` are
|
||||
automatically available at the registered path — no install API needed.
|
||||
Upload + enable is sufficient.
|
||||
|
||||
## Authority Matrix (plugin permissions)
|
||||
|
||||
Plugins integrate with the 4-layer permission system. Three control layers:
|
||||
|
||||
| Layer | What it controls | Mechanism |
|
||||
|---|---|---|
|
||||
| **Plugin visible** | User sees plugin in context menu / panel / dashboard | `AuthorityMatrixRolePluginInstall` per-role per-install |
|
||||
| **Plugin executable** | User can click and run the plugin | Checked at `PluginSlot` render |
|
||||
| **Data operations** | Plugin's CRUD respects user's permissions | **Already protected** — plugins call `@teable/openapi` which passes through existing AM enforcement |
|
||||
|
||||
Default behavior:
|
||||
- New installs default to disabled for non-owner roles
|
||||
- Base owner always has full access
|
||||
- Admin enables per role through the RoleEditor UI
|
||||
|
||||
## Developer whitelist
|
||||
|
||||
Only users in the `plugin_developer` table can upload bundles. To add a developer:
|
||||
|
||||
```sql
|
||||
INSERT INTO plugin_developer (id, user_id, email, created_by)
|
||||
VALUES (gen_random_uuid(), '<userId>', '<email>', '<adminUserId>');
|
||||
```
|
||||
|
||||
## Getting a session cookie
|
||||
|
||||
```bash
|
||||
curl -s -c /tmp/cookie_hr.txt http://localhost:3003/api/auth/signin \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}'
|
||||
|
||||
|
||||
> Account note (2026-08): `hr_admin@xingchen.tech` no longer exists on the
|
||||
> deployed instance. Create a throwaway user via `POST /api/auth/signup`
|
||||
> and add a `plugin_developer` row for it before uploading (see SKILL.md).
|
||||
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
|
||||
```
|
||||
|
||||
## Full deploy script (copy-paste)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
DIR="${1:-.}"
|
||||
UPLOAD_URL="${UPLOAD_URL:-http://localhost:3003}"
|
||||
BASE_ID="${BASE_ID:-bseAi6Hazmw7jPWZbfO}"
|
||||
|
||||
# Read plugin ID from manifest
|
||||
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('$DIR/plugin.json'))['id'])")
|
||||
|
||||
# Get session
|
||||
SESSION=$(grep auth_session /tmp/cookie_hr.txt 2>/dev/null | awk '{print $NF}')
|
||||
if [ -z "$SESSION" ]; then
|
||||
curl -s -c /tmp/cookie_hr.txt "$UPLOAD_URL/api/auth/signin" \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}' > /dev/null
|
||||
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
|
||||
fi
|
||||
|
||||
# Upload (URL includes pluginId from manifest)
|
||||
curl -s -b "auth_session=$SESSION" \
|
||||
-F "js=@$DIR/dist/main.js" \
|
||||
-F "css=@$DIR/dist/styles.css" \
|
||||
-F "manifest=@$DIR/plugin.json" \
|
||||
"$UPLOAD_URL/api/plugin/$PLUGIN_ID/upload-bundle"
|
||||
echo "Uploaded: $PLUGIN_ID"
|
||||
|
||||
# Enable
|
||||
curl -s -b "auth_session=$SESSION" -X PUT \
|
||||
"$UPLOAD_URL/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"enabled":true,"tableIds":[]}' > /dev/null
|
||||
echo "Enabled: $PLUGIN_ID"
|
||||
```
|
||||
Reference in New Issue
Block a user