@meistrari/tela-skills 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -12
- package/cli.ts +28 -20
- package/package.json +1 -1
- package/skill/TELA_LOCALHOST_SKILL.md +8 -6
- package/skill/TELA_SKILL.md +56 -28
- package/skill/canvas.test.ts +149 -0
- package/skill/canvas.ts +49 -23
- package/skill/common.ts +41 -22
- package/skill/create-canvas.md +9 -20
- package/skill/create-project.md +1 -3
- package/skill/index.ts +53 -24
- package/skill/localhost-utils.test.ts +98 -2
- package/skill/localhost-utils.ts +245 -16
- package/skill/setup-session.md +46 -10
- package/skill/test-cases.ts +21 -6
- package/skill/update-canvas.md +10 -20
- package/skill/workflow-builder-types.ts +1 -0
- package/skill/workflow-builders.ts +19 -2
- package/skill/workflow-graph-builder.test.ts +438 -0
- package/skill/workflow-graph-builder.ts +17 -2
- package/skill/workflow-module-rules.test.ts +340 -0
- package/skill/workflow-module-rules.ts +226 -0
- package/skill/workflow-node-builders.ts +60 -3
- package/skill/workflow-nodes.md +245 -0
- package/skill/workflow-references.md +140 -0
- package/skill/workflow-references.test.ts +137 -0
- package/skill/workflow-references.ts +99 -0
- package/skill/workflow-run-builders.ts +14 -4
- package/skill/workflow-spec-builders.ts +15 -112
- package/skill/workflow-updates.ts +168 -0
- package/skill/workflow.md +176 -167
- package/skill/workflows.test.ts +809 -0
- package/skill/workflows.ts +746 -147
- package/skill/workflow-nodes-v2-v3.md +0 -746
- package/skill/workflow-references-v2-v3.md +0 -169
- package/skill/workflow-v2-create-update.md +0 -54
- package/skill/workflow-v3-create-update.md +0 -65
package/README.md
CHANGED
|
@@ -42,27 +42,33 @@ The CLI (`cli.ts`) is an interactive installer that sets up Tela Skills for Clau
|
|
|
42
42
|
|
|
43
43
|
### What it does
|
|
44
44
|
|
|
45
|
-
1. **Environment selection** — Choose between Production (`api.tela.com`)
|
|
45
|
+
1. **Environment selection** — Choose between Production (`api.tela.com`), Staging (`api.telastaging.com`), or Localhost (Docker-based local development).
|
|
46
46
|
2. **Skill installation** — Copies skill source files from `skill/` to `~/.claude/skills/tela/`.
|
|
47
47
|
3. **Agent deployment** — Copies agent definitions from `agents/` to `~/.claude/agents/` (if available).
|
|
48
48
|
4. **Dependency installation** — Installs `@meistrari/vault-sdk` into the skills directory via Bun.
|
|
49
|
-
5. **Authentication** — Browser auth
|
|
50
|
-
6. **Session persistence** — Saves credentials to `~/.tela/session.production
|
|
49
|
+
5. **Authentication** — Browser auth for Production/Staging; Docker discovery + JWT generation for Localhost.
|
|
50
|
+
6. **Session persistence** — Saves credentials to `~/.tela/session.production`, `~/.tela/session.staging`, or `~/.tela/session.local` and environment config to `~/.tela/environment` and `~/.tela/.env`.
|
|
51
51
|
|
|
52
52
|
### Usage
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
# Install and configure
|
|
55
|
+
# Install and configure (interactive)
|
|
56
56
|
bunx @meistrari/tela-skills
|
|
57
57
|
|
|
58
|
+
# Non-interactive localhost setup
|
|
59
|
+
bunx @meistrari/tela-skills --env localhost --repos-path /path/to/tela --workspace <slug-or-id> --yes
|
|
60
|
+
|
|
58
61
|
# Test locally during development
|
|
59
62
|
bun run cli.ts
|
|
60
63
|
|
|
61
64
|
# Verify skill loads correctly
|
|
62
65
|
bun --preload skill/preload.ts -e "console.log(Object.keys(tela))"
|
|
63
66
|
|
|
64
|
-
#
|
|
67
|
+
# Run commands against localhost
|
|
65
68
|
bun localhost.ts "console.log(await tela.listProjects())"
|
|
69
|
+
|
|
70
|
+
# List available localhost workspaces
|
|
71
|
+
bun list-workspaces.ts
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
### File layout after installation
|
|
@@ -75,10 +81,22 @@ bun localhost.ts "console.log(await tela.listProjects())"
|
|
|
75
81
|
~/.tela/
|
|
76
82
|
session.production # API token (production)
|
|
77
83
|
session.staging # API token (staging)
|
|
78
|
-
|
|
84
|
+
session.local # JWT token (localhost, 30-day expiry)
|
|
85
|
+
environment # "production", "staging", or "localhost"
|
|
79
86
|
.env # TELA_API_URL, TELA_APP_URL
|
|
87
|
+
tela-repo-path # Path to tela monorepo (localhost only, for key discovery)
|
|
80
88
|
```
|
|
81
89
|
|
|
90
|
+
### Localhost environment
|
|
91
|
+
|
|
92
|
+
For internal development against locally running Tela services:
|
|
93
|
+
|
|
94
|
+
1. Start Docker services in the tela monorepo (`make dev`)
|
|
95
|
+
2. Run `bunx @meistrari/tela-skills` and select "Localhost"
|
|
96
|
+
3. The CLI discovers Docker ports, reads `AUTH_API_SECRET` from `packages/api/.env`, loads auth settings from `.repositories/auth-api/.env`, fetches workspaces, and generates a JWT through the local auth-api JWKS signing flow
|
|
97
|
+
|
|
98
|
+
The localhost JWT has a 30-day expiry and is auto-regenerated when expired. Workspace selection is required during setup — use `bun list-workspaces.ts` to see available workspaces.
|
|
99
|
+
|
|
82
100
|
---
|
|
83
101
|
|
|
84
102
|
## Skills
|
|
@@ -100,14 +118,14 @@ bun --preload ~/.claude/skills/tela/preload.ts -e "
|
|
|
100
118
|
|
|
101
119
|
**File:** `skill/canvas.ts`
|
|
102
120
|
|
|
103
|
-
Canvas is Tela's core concept — a versioned AI prompt with
|
|
121
|
+
Canvas is Tela's core concept — a versioned AI prompt with a message, variables, configuration, and optional structured output.
|
|
104
122
|
|
|
105
123
|
#### Types
|
|
106
124
|
|
|
107
125
|
| Type | Description |
|
|
108
126
|
|------|-------------|
|
|
109
127
|
| `Canvas` | Top-level prompt object with metadata |
|
|
110
|
-
| `CanvasVersion` | Versioned snapshot:
|
|
128
|
+
| `CanvasVersion` | Versioned snapshot: message, variables, config |
|
|
111
129
|
| `Variable` | Input/output definition (text, file, or mixed) |
|
|
112
130
|
| `StructuredOutput` | JSON schema for enforcing response format |
|
|
113
131
|
| `CanvasMessage` | Chat message (system, user, assistant, function) |
|
|
@@ -122,7 +140,7 @@ tela.createCanvas({
|
|
|
122
140
|
title: "My Prompt",
|
|
123
141
|
projectId: "proj-uuid",
|
|
124
142
|
version: {
|
|
125
|
-
|
|
143
|
+
message: tela.createMessage("system", "You are a helpful assistant."),
|
|
126
144
|
variables: [tela.createVariable("input", "text", { variableType: "input" })],
|
|
127
145
|
configuration: { model: "gpt-5", temperature: 0 }
|
|
128
146
|
}
|
|
@@ -294,7 +312,7 @@ Upload, download, and manage files using immutable `vault://` references.
|
|
|
294
312
|
|
|
295
313
|
| Function | Description |
|
|
296
314
|
|----------|-------------|
|
|
297
|
-
| `loadApiKey()` | Reads token from `~/.tela/session.production
|
|
315
|
+
| `loadApiKey()` | Reads token from `~/.tela/session.production`, `~/.tela/session.staging`, or `~/.tela/session.local` |
|
|
298
316
|
| `getApiBaseUrl()` | Returns API URL from globals or defaults |
|
|
299
317
|
| `getAppBaseUrl()` | Returns app URL for constructing viewer links |
|
|
300
318
|
| `apiRequest<T>(method, path, body?)` | Generic HTTP client with Bearer auth |
|
|
@@ -375,7 +393,8 @@ Runtime:
|
|
|
375
393
|
├─→ prompts.ts (list/get prompt metadata)
|
|
376
394
|
├─→ test-cases.ts (create/run/manage evaluations)
|
|
377
395
|
├─→ tasks.ts (human-in-the-loop validation)
|
|
378
|
-
|
|
396
|
+
├─→ vault.ts (file upload/download/manage)
|
|
397
|
+
└─→ localhost-utils.ts (Docker discovery, JWT signing, workspace fetching)
|
|
379
398
|
```
|
|
380
399
|
|
|
381
400
|
---
|
|
@@ -388,7 +407,7 @@ Every message and variable has both **markdown** and **HTML** representations. T
|
|
|
388
407
|
|
|
389
408
|
### Vault references
|
|
390
409
|
|
|
391
|
-
All files are referenced by immutable `vault://UUID` identifiers — never local paths. Upload files to Vault first, then use the returned reference in canvas
|
|
410
|
+
All files are referenced by immutable `vault://UUID` identifiers — never local paths. Upload files to Vault first, then use the returned reference in a canvas message, test cases, or task inputs.
|
|
392
411
|
|
|
393
412
|
### Smart merging
|
|
394
413
|
|
package/cli.ts
CHANGED
|
@@ -7,10 +7,9 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import open from 'open'
|
|
8
8
|
import {
|
|
9
9
|
discoverLocalhostServices,
|
|
10
|
-
parseApiGatewayKeys,
|
|
11
10
|
readAuthApiSecret,
|
|
12
|
-
signLocalhostJwt,
|
|
13
11
|
fetchWorkspaces,
|
|
12
|
+
generateLocalhostJwt,
|
|
14
13
|
saveLocalhostSession,
|
|
15
14
|
writeLocalhostEnv,
|
|
16
15
|
readSavedReposPath,
|
|
@@ -23,7 +22,7 @@ const OPEN_ISSUE_SKILLS_DIR = join(homedir(), '.claude', 'skills', 'tela-open-is
|
|
|
23
22
|
const LOCALHOST_SKILLS_DIR = join(homedir(), '.claude', 'skills', 'tela-localhost')
|
|
24
23
|
const SESSION_DIR = join(homedir(), '.tela')
|
|
25
24
|
const BACKOFFICE_URL = 'https://backoffice.tela.com'
|
|
26
|
-
const STAGING_BACKOFFICE_URL = 'https://
|
|
25
|
+
const STAGING_BACKOFFICE_URL = 'https://backoffice.telastaging.com'
|
|
27
26
|
|
|
28
27
|
// --- CLI Flag Parsing ---
|
|
29
28
|
function parseFlags(argv: string[]): Record<string, string | true> {
|
|
@@ -126,8 +125,13 @@ async function handleLocalhostSetup() {
|
|
|
126
125
|
p.log.error('Could not find tela-api Docker container. Make sure local services are running.')
|
|
127
126
|
process.exit(1)
|
|
128
127
|
}
|
|
128
|
+
if (!services.authApiDbPort) {
|
|
129
|
+
discoverSpinner.stop('auth-postgres container not found')
|
|
130
|
+
p.log.error('Could not find auth-postgres Docker container. Make sure local auth services are running.')
|
|
131
|
+
process.exit(1)
|
|
132
|
+
}
|
|
129
133
|
|
|
130
|
-
discoverSpinner.stop(`Found services — auth-api :${services.authApiPort}, tela-api :${services.telaApiPort}${services.telaAppPort ? `, tela-app :${services.telaAppPort}` : ''}`)
|
|
134
|
+
discoverSpinner.stop(`Found services — auth-api :${services.authApiPort}, auth-postgres :${services.authApiDbPort}, tela-api :${services.telaApiPort}${services.telaAppPort ? `, tela-app :${services.telaAppPort}` : ''}`)
|
|
131
135
|
|
|
132
136
|
// 2. Ask for meistrari repos path
|
|
133
137
|
const savedPath = readSavedReposPath()
|
|
@@ -161,18 +165,16 @@ async function handleLocalhostSetup() {
|
|
|
161
165
|
const resolvedPath = reposPath.replace(/^~/, homedir())
|
|
162
166
|
saveReposPath(resolvedPath)
|
|
163
167
|
|
|
164
|
-
// 3. Read
|
|
168
|
+
// 3. Read auth-api access secret
|
|
165
169
|
const keysSpinner = p.spinner()
|
|
166
|
-
keysSpinner.start('Reading
|
|
170
|
+
keysSpinner.start('Reading auth-api configuration...')
|
|
167
171
|
|
|
168
|
-
let keys: ReturnType<typeof parseApiGatewayKeys>
|
|
169
172
|
let authApiSecret: string
|
|
170
173
|
try {
|
|
171
|
-
keys = parseApiGatewayKeys(resolvedPath)
|
|
172
174
|
authApiSecret = readAuthApiSecret(resolvedPath)
|
|
173
|
-
keysSpinner.stop('
|
|
175
|
+
keysSpinner.stop('Auth-api configuration loaded')
|
|
174
176
|
} catch (err) {
|
|
175
|
-
keysSpinner.stop('Failed to read
|
|
177
|
+
keysSpinner.stop('Failed to read auth-api configuration')
|
|
176
178
|
p.log.error(err instanceof Error ? err.message : String(err))
|
|
177
179
|
process.exit(1)
|
|
178
180
|
}
|
|
@@ -234,16 +236,22 @@ async function handleLocalhostSetup() {
|
|
|
234
236
|
const tokenSpinner = p.spinner()
|
|
235
237
|
tokenSpinner.start('Generating authentication token...')
|
|
236
238
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
try {
|
|
240
|
+
const token = generateLocalhostJwt({
|
|
241
|
+
reposPath: resolvedPath,
|
|
242
|
+
workspace: { id: workspace.id, title: workspace.title },
|
|
243
|
+
authApiDbPort: services.authApiDbPort,
|
|
244
|
+
})
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
saveLocalhostSession(token)
|
|
247
|
+
writeLocalhostEnv(services.telaApiPort, services.telaAppPort)
|
|
245
248
|
|
|
246
|
-
|
|
249
|
+
tokenSpinner.stop('Token generated and saved')
|
|
250
|
+
} catch (err) {
|
|
251
|
+
tokenSpinner.stop('Failed to generate token')
|
|
252
|
+
p.log.error(err instanceof Error ? err.message : String(err))
|
|
253
|
+
process.exit(1)
|
|
254
|
+
}
|
|
247
255
|
|
|
248
256
|
// 7. Install skills (reuse existing logic)
|
|
249
257
|
if (YES) {
|
|
@@ -474,8 +482,8 @@ async function main() {
|
|
|
474
482
|
|
|
475
483
|
const ENV_PATH = join(SESSION_DIR, '.env')
|
|
476
484
|
const isStaging = env === 'staging'
|
|
477
|
-
const apiUrl = isStaging ? 'https://
|
|
478
|
-
const appUrl = isStaging ? 'https://
|
|
485
|
+
const apiUrl = isStaging ? 'https://api.telastaging.com' : 'https://api.tela.com'
|
|
486
|
+
const appUrl = isStaging ? 'https://app.telastaging.com' : 'https://app.tela.com'
|
|
479
487
|
|
|
480
488
|
mkdirSync(SESSION_DIR, { recursive: true })
|
|
481
489
|
writeFileSync(ENV_PATH, `TELA_API_URL=${apiUrl}\nTELA_APP_URL=${appUrl}\n`, { mode: 0o600 })
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ Manage your local Tela development environment. Switch between workspaces, check
|
|
|
9
9
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
|
-
- Docker running with tela services (auth-api, tela-api)
|
|
12
|
+
- Docker running with tela services (auth-api, auth-postgres, tela-api)
|
|
13
13
|
- Localhost environment configured via `bunx @meistrari/tela-skills` (select "Localhost")
|
|
14
14
|
- Tela monorepo path saved at `~/.tela/tela-repo-path`
|
|
15
15
|
|
|
@@ -31,7 +31,7 @@ bun list-workspaces.ts
|
|
|
31
31
|
|
|
32
32
|
### Switch Workspace
|
|
33
33
|
|
|
34
|
-
Switch to a different workspace by generating a new JWT token.
|
|
34
|
+
Switch to a different workspace by generating a new JWT token through the local auth-api signing flow.
|
|
35
35
|
|
|
36
36
|
Replace `WORKSPACE_ID` and `WORKSPACE_TITLE` with the target workspace values from "List Workspaces".
|
|
37
37
|
|
|
@@ -42,12 +42,13 @@ const { homedir } = await import('node:os')
|
|
|
42
42
|
const lu = await import(join(homedir(), '.claude', 'skills', 'tela', 'localhost-utils.ts'))
|
|
43
43
|
const reposPath = lu.readSavedReposPath()
|
|
44
44
|
if (!reposPath) { console.error('No tela repo path saved. Run CLI setup first.'); process.exit(1) }
|
|
45
|
+
const services = lu.discoverLocalhostServices()
|
|
46
|
+
if (!services.authApiDbPort) { console.error('auth-postgres Docker container not found.'); process.exit(1) }
|
|
45
47
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
+
const token = lu.generateLocalhostJwt({
|
|
49
|
+
reposPath,
|
|
48
50
|
workspace: { id: 'WORKSPACE_ID', title: 'WORKSPACE_TITLE' },
|
|
49
|
-
|
|
50
|
-
kid: keys.kid,
|
|
51
|
+
authApiDbPort: services.authApiDbPort,
|
|
51
52
|
})
|
|
52
53
|
lu.saveLocalhostSession(token)
|
|
53
54
|
console.log('Switched to workspace: WORKSPACE_TITLE')
|
|
@@ -101,6 +102,7 @@ try {
|
|
|
101
102
|
## Troubleshooting
|
|
102
103
|
|
|
103
104
|
- **"auth-api not running"**: Start Docker services in the tela monorepo (`make dev`)
|
|
105
|
+
- **"auth-postgres not running"**: Make sure the local auth database container is up and exposes its host port
|
|
104
106
|
- **"No tela repo path saved"**: Run `bunx @meistrari/tela-skills` and select Localhost to configure
|
|
105
107
|
- **"Connection: FAILED"**: Check that tela-api Docker container is running (`docker ps | grep tela-api`)
|
|
106
108
|
- **Token expired**: Tokens are auto-regenerated when expired. If auto-regeneration fails, use "Switch Workspace" to regenerate manually
|
package/skill/TELA_SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tela
|
|
3
|
-
description: Skills for interacting with Tela - view, create, and update prompts/canvas. Upload and download files via Vault (vault:// references). Use this skill when user shares a Tela URL (app.tela.com or
|
|
3
|
+
description: Skills for interacting with Tela - view, create, and update prompts/canvas. Upload and download files via Vault (vault:// references). Use this skill when user shares a Tela URL (app.tela.com or app.telastaging.com) or asks about canvas/prompts. Do NOT use WebFetch for Tela URLs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Tela API Skills
|
|
@@ -9,7 +9,7 @@ Skills for interacting with the Tela API. Note: Prompts are called "Canvas" in T
|
|
|
9
9
|
|
|
10
10
|
**IMPORTANT**: When the user shares a Tela URL like `https://app.tela.com/prompt/{id}/craft`, use these skills to fetch and modify the canvas - do NOT use WebFetch.
|
|
11
11
|
|
|
12
|
-
**Workflows**: For workflow authoring and execution, use the `workflow.md` skill
|
|
12
|
+
**Workflows**: For workflow authoring and execution, use the `workflow.md` skill. Current graph authoring is the only supported path.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -19,7 +19,13 @@ bunx @meistrari/tela-skills
|
|
|
19
19
|
|
|
20
20
|
## Authentication
|
|
21
21
|
|
|
22
|
-
The API uses
|
|
22
|
+
The API uses token authentication via the `x-data-token` header. The token is stored at:
|
|
23
|
+
|
|
24
|
+
- `~/.tela/session.production` — Production environment
|
|
25
|
+
- `~/.tela/session.staging` — Staging environment
|
|
26
|
+
- `~/.tela/session.local` — Localhost environment (JWT, 30-day expiry, auto-regenerated when expired)
|
|
27
|
+
|
|
28
|
+
The active environment is determined by the `TELA_API_URL` in `~/.tela/.env`. When the URL contains `localhost`, the localhost session is used automatically. All session files are created with `0o600` permissions (user read/write only).
|
|
23
29
|
|
|
24
30
|
## Using These Skills
|
|
25
31
|
|
|
@@ -50,10 +56,8 @@ bun --preload ~/.claude/skills/tela/preload.ts -e "console.log(await tela.listPr
|
|
|
50
56
|
|
|
51
57
|
### Workflow Operations
|
|
52
58
|
|
|
53
|
-
- `tela.
|
|
54
|
-
- `tela.
|
|
55
|
-
- `tela.updateWorkflowVersionV2(versionId, payload)` - Update workflow prompt-version in v2 compatibility mode
|
|
56
|
-
- `tela.updateWorkflowVersionV3(versionId, payload)` - Update workflow prompt-version in v3 compatibility mode
|
|
59
|
+
- `tela.createWorkflowVersion(payload)` - Create workflow prompt-version with graph validation
|
|
60
|
+
- `tela.updateWorkflowVersion(versionId, payload)` - Update workflow prompt-version with graph validation
|
|
57
61
|
- `tela.runWorkflow(payload)` - Run workflow via `/workflow/test`
|
|
58
62
|
- `tela.getWorkflowRun(runId)` - Get workflow run by ID
|
|
59
63
|
- `tela.waitForWorkflowRun(runId, options?)` - Poll until terminal status
|
|
@@ -61,10 +65,14 @@ bun --preload ~/.claude/skills/tela/preload.ts -e "console.log(await tela.listPr
|
|
|
61
65
|
- `tela.listWorkflowRuns(options)` - List workflow runs by prompt
|
|
62
66
|
- `tela.getWorkflowVariables(promptId, payload?)` - Get available variables for graph/input context
|
|
63
67
|
- `tela.getWorkflowVariablesByStep(promptId, stepId, payload?)` - Get step-targeted compatibility variables
|
|
64
|
-
- `tela.validateWorkflowGraph(graph
|
|
65
|
-
- `tela.assertWorkflowGraphValid(graph, { version })` - Throw on validation errors
|
|
66
|
-
- `tela.
|
|
67
|
-
- `tela.
|
|
68
|
+
- `tela.validateWorkflowGraph(graph)` - Local pre-save graph shape checks (synchronous)
|
|
69
|
+
- `tela.assertWorkflowGraphValid(graph, { version })` - Throw on local validation errors
|
|
70
|
+
- `tela.validateWorkflowGraphServer(promptId, graph)` - Server-side validation with full inference (compatibility, condition branch visibility) — requires the prompt to exist
|
|
71
|
+
- `tela.getWorkflowActions()` - Catalog of all workflow actions with config/output schemas, reference field metadata, and public methods
|
|
72
|
+
- `tela.getWorkflowDependencies(promptId, versionId)` - All variables visible across a saved workflow version, with schemas
|
|
73
|
+
- `tela.getStepDependencies(promptId, versionId, stepId)` - Variables visible at a specific step, including per-reference-field format compatibility — use this instead of guessing what `?format=` values an action supports
|
|
74
|
+
- `tela.getStepTypes(promptId, versionId, stepId)` - TypeScript declarations (`.d.ts`) for the `TelaInput` shape inside a `codeExecutionNode` — use to author `input["..."]` accesses with confidence
|
|
75
|
+
- `tela.getWorkflowUrl(promptId)` - Get workflow UI URL (`/workflows/:id`)
|
|
68
76
|
|
|
69
77
|
### Export
|
|
70
78
|
|
|
@@ -153,9 +161,7 @@ const canvas = await tela.createCanvas({
|
|
|
153
161
|
keyPoints: { type: 'array', items: { type: 'string' } }
|
|
154
162
|
})
|
|
155
163
|
},
|
|
156
|
-
|
|
157
|
-
tela.createMessage('system', '## Task\n\nAnalyze the document.\n\n## Input\n\n{document}', 0)
|
|
158
|
-
],
|
|
164
|
+
message: tela.createMessage('system', '## Task\n\nAnalyze the document.\n\n## Input\n\n{document}', 0),
|
|
159
165
|
variables: [tela.createVariable('document', 'file', { allowMultimodal: true })]
|
|
160
166
|
}
|
|
161
167
|
})
|
|
@@ -283,29 +289,51 @@ Referenced in content as `{variableName}` (single braces)
|
|
|
283
289
|
}
|
|
284
290
|
```
|
|
285
291
|
|
|
286
|
-
###
|
|
292
|
+
### Message
|
|
287
293
|
```typescript
|
|
288
294
|
{ role: 'system' | 'user' | 'assistant', content: 'Message content', index: 0 }
|
|
289
295
|
```
|
|
290
296
|
|
|
291
297
|
## Environment Variables
|
|
292
298
|
|
|
293
|
-
|
|
294
|
-
|
|
299
|
+
Configured automatically by the CLI installer in `~/.tela/.env`:
|
|
300
|
+
|
|
301
|
+
- `TELA_API_URL` - API base URL (production: `https://api.tela.com`, staging: `https://api.telastaging.com`, localhost: `http://localhost:<port>`)
|
|
302
|
+
- `TELA_APP_URL` - App base URL (production: `https://app.tela.com`, staging: `https://app.telastaging.com`, localhost: `http://localhost:<port>`)
|
|
303
|
+
|
|
304
|
+
For localhost, ports are auto-discovered from Docker containers (`auth-api`, `tela-api`, `tela-app`). If `tela-app` is not running, `TELA_APP_URL` defaults to `http://localhost:3000`.
|
|
305
|
+
|
|
306
|
+
## Localhost Environment
|
|
307
|
+
|
|
308
|
+
For developing against locally running Tela services, use the localhost environment:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
bunx @meistrari/tela-skills # Select "Localhost" during setup
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Prerequisites: Docker running with tela services (`auth-api`, `auth-postgres`, `tela-api`), with auth settings available in `.repositories/auth-api/.env` and `AUTH_API_SECRET` in `packages/api/.env`. The CLI discovers ports, reads the local auth configuration, and generates a JWT token through the auth-api JWKS signing flow.
|
|
315
|
+
|
|
316
|
+
For workspace management after setup, see the `tela-localhost` skill (switch workspaces, check status, regenerate tokens).
|
|
317
|
+
|
|
318
|
+
Run commands against localhost with the helper script:
|
|
319
|
+
```bash
|
|
320
|
+
bun localhost.ts "console.log(await tela.listProjects())"
|
|
321
|
+
```
|
|
295
322
|
|
|
296
323
|
## File Structure
|
|
297
324
|
|
|
298
325
|
```
|
|
299
326
|
~/.claude/skills/tela/
|
|
300
|
-
├── SKILL.md
|
|
301
|
-
├── preload.ts
|
|
302
|
-
├── index.ts
|
|
303
|
-
├── common.ts
|
|
304
|
-
├── projects.ts
|
|
305
|
-
├── prompts.ts
|
|
306
|
-
├── canvas.ts
|
|
307
|
-
├── workflows.ts
|
|
308
|
-
├── test-cases.ts
|
|
309
|
-
├── tasks.ts
|
|
310
|
-
|
|
327
|
+
├── SKILL.md # This file
|
|
328
|
+
├── preload.ts # Preload script - makes `tela` globally available
|
|
329
|
+
├── index.ts # Barrel export
|
|
330
|
+
├── common.ts # Shared utilities (loadApiKey, apiRequest, env detection)
|
|
331
|
+
├── projects.ts # Project functions
|
|
332
|
+
├── prompts.ts # Prompt/Canvas list functions
|
|
333
|
+
├── canvas.ts # Canvas create/update functions
|
|
334
|
+
├── workflows.ts # Workflow create/update/run/validation functions
|
|
335
|
+
├── test-cases.ts # Test case functions
|
|
336
|
+
├── tasks.ts # Task management functions
|
|
337
|
+
├── vault.ts # Vault file management functions
|
|
338
|
+
└── localhost-utils.ts # Docker discovery, JWT signing, workspace fetching (internal, not on tela global)
|
|
311
339
|
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const apiRequest = mock(async (_endpoint: string, _options?: RequestInit): Promise<unknown> => ({ id: 'canvas-id' }))
|
|
4
|
+
|
|
5
|
+
void mock.module('./common.ts', () => ({
|
|
6
|
+
apiRequest,
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
describe('canvas', () => {
|
|
10
|
+
const legacyMessagesError = 'Use message instead of messages (pass a single CanvasMessage object, not an array)'
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
apiRequest.mockClear()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('creates canvases with the canvas layout version', async () => {
|
|
17
|
+
const { createCanvas } = await import('./canvas.ts')
|
|
18
|
+
|
|
19
|
+
await createCanvas({
|
|
20
|
+
title: 'Canvas',
|
|
21
|
+
projectId: 'project-id',
|
|
22
|
+
workspaceId: 'workspace-id',
|
|
23
|
+
version: {
|
|
24
|
+
title: 'v1',
|
|
25
|
+
configuration: { model: 'gpt-5', type: 'chat' },
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const [, options] = apiRequest.mock.calls[0]!
|
|
30
|
+
const body = JSON.parse(String(options?.body))
|
|
31
|
+
|
|
32
|
+
expect(body.layoutVersion).toBe('v1')
|
|
33
|
+
expect(body.version.isWorkflow).toBe(false)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('rejects legacy message arrays when creating a canvas', async () => {
|
|
37
|
+
const { createCanvas } = await import('./canvas.ts')
|
|
38
|
+
|
|
39
|
+
const create = createCanvas({
|
|
40
|
+
title: 'Canvas',
|
|
41
|
+
projectId: 'project-id',
|
|
42
|
+
workspaceId: 'workspace-id',
|
|
43
|
+
version: {
|
|
44
|
+
title: 'v1',
|
|
45
|
+
configuration: { model: 'gpt-5', type: 'chat' },
|
|
46
|
+
// @ts-expect-error Legacy JavaScript callers can still send this field at runtime.
|
|
47
|
+
messages: [{ role: 'system', content: 'Legacy', index: 0 }],
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
let error: Error | undefined
|
|
52
|
+
try {
|
|
53
|
+
await create
|
|
54
|
+
}
|
|
55
|
+
catch (caught) {
|
|
56
|
+
error = caught as Error
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
expect(error?.message).toContain(legacyMessagesError)
|
|
60
|
+
expect(apiRequest).not.toHaveBeenCalled()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('rejects legacy message arrays when updating a canvas version', async () => {
|
|
64
|
+
const { updateCanvasVersion } = await import('./canvas.ts')
|
|
65
|
+
|
|
66
|
+
const update = updateCanvasVersion('version-id', {
|
|
67
|
+
title: 'v2',
|
|
68
|
+
// @ts-expect-error Legacy JavaScript callers can still send this field at runtime.
|
|
69
|
+
messages: [{ role: 'system', content: 'Legacy', index: 0 }],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
let error: Error | undefined
|
|
73
|
+
try {
|
|
74
|
+
await update
|
|
75
|
+
}
|
|
76
|
+
catch (caught) {
|
|
77
|
+
error = caught as Error
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
expect(error?.message).toContain(legacyMessagesError)
|
|
81
|
+
expect(apiRequest).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('rejects legacy message arrays when creating a canvas version', async () => {
|
|
85
|
+
const { createCanvasVersion } = await import('./canvas.ts')
|
|
86
|
+
|
|
87
|
+
const create = createCanvasVersion('canvas-id', {
|
|
88
|
+
title: 'v2',
|
|
89
|
+
configuration: { model: 'gpt-5', type: 'chat' },
|
|
90
|
+
// @ts-expect-error Legacy JavaScript callers can still send this field at runtime.
|
|
91
|
+
messages: [{ role: 'system', content: 'Legacy', index: 0 }],
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
let error: Error | undefined
|
|
95
|
+
try {
|
|
96
|
+
await create
|
|
97
|
+
}
|
|
98
|
+
catch (caught) {
|
|
99
|
+
error = caught as Error
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
expect(error?.message).toContain(legacyMessagesError)
|
|
103
|
+
expect(apiRequest).not.toHaveBeenCalled()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('preserves the current message when updating a canvas', async () => {
|
|
107
|
+
const { updateCanvas } = await import('./canvas.ts')
|
|
108
|
+
|
|
109
|
+
apiRequest.mockImplementation(async (endpoint: string) => {
|
|
110
|
+
if (endpoint === '/prompt/canvas-id') {
|
|
111
|
+
return { id: 'canvas-id', title: 'Canvas' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (endpoint === '/prompt-version?promptId=canvas-id') {
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
id: 'version-id',
|
|
118
|
+
promptId: 'canvas-id',
|
|
119
|
+
title: 'v1',
|
|
120
|
+
content: null,
|
|
121
|
+
markdownContent: null,
|
|
122
|
+
configuration: { model: 'gpt-5', type: 'chat' },
|
|
123
|
+
variables: [],
|
|
124
|
+
promoted: false,
|
|
125
|
+
draft: false,
|
|
126
|
+
isWorkflow: false,
|
|
127
|
+
workspaceId: 'workspace-id',
|
|
128
|
+
createdBy: 'user-id',
|
|
129
|
+
updatedBy: 'user-id',
|
|
130
|
+
createdAt: '2026-06-22T10:00:00.000Z',
|
|
131
|
+
updatedAt: '2026-06-22T10:00:00.000Z',
|
|
132
|
+
deletedAt: null,
|
|
133
|
+
message: { role: 'system', content: 'Current', markdownContent: 'Current', index: 0 },
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { id: 'new-version-id' }
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
await updateCanvas('canvas-id', { title: 'v2' })
|
|
142
|
+
|
|
143
|
+
const [, options] = apiRequest.mock.calls[2]!
|
|
144
|
+
const body = JSON.parse(String(options?.body))
|
|
145
|
+
|
|
146
|
+
expect(body.message).toEqual({ role: 'system', content: 'Current', markdownContent: 'Current', index: 0 })
|
|
147
|
+
expect('messages' in body).toBe(false)
|
|
148
|
+
})
|
|
149
|
+
})
|