Skip to main content
Glama
README.md
# Product Studio

Local **Product Owner** workspace: a browser chat UI, your company OpenAI-compatible LLM (no Cursor), and optional MCP tools for Jira, Confluence, GitLab, Workflow, Database, and Solver.

## Requirements

- Node.js **22.13+**
- Access to your LLM endpoint
- Optional: Jira / Confluence / GitLab — the app still runs if any are missing

## Install

```bash
npm install
npm run build
```

## Run (chat UI)

```bash
npm start
# → http://127.0.0.1:8787
```

Or:

```bash
npx tsx src/index.ts serve --port 8787
```

### Login / members

Access requires a Studio account (httpOnly session cookie). Passwords are stored as **scrypt** hashes in SQLite — never plaintext.

**First admin (pick one):**

1. Open the UI with no users → **Create admin** setup screen  
2. Or CLI:

```bash
npx tsx src/index.ts user bootstrap --username admin --password "your-secure-password"
```

**Add a limited member (after admin exists):**

```bash
npx tsx src/index.ts user create --username alice --password "alice-pass-here" --role limited
npx tsx src/index.ts user list
```

Or in the UI: **Settings → Members** (admin only).

| Role | Access |
|------|--------|
| **admin** | Everything + member management |
| **full** | Chat, Workflows (create/edit/delete), Status, Settings integrations |
| **limited** | Chat, run/view workflows + pauses, Status — no Settings, no workflow delete/disable/create |

## Configure

Configs persist in SQLite (`data/product-studio.sqlite`).

**In the UI:** send `/config` and follow the menu.

**CLI:**

```bash
npx tsx src/index.ts config llm --base-url "https://your-llm/v1" --api-key "..." --model "..."
npx tsx src/index.ts config jira --base-url "..." --email "..." --api-token "..."
npx tsx src/index.ts config database --dialect sqlite --file-path data/app-data.sqlite --name default
npx tsx src/index.ts status
```

## UI workspaces

- **Chat** — agent conversation (`New chat` keeps past threads; `/start` clears only the current chat; last 20 messages are sent as context)
- **Workflows** — create, edit, run, and visualize HTTP/SQL sequences; approve pending SQL here
- **Settings** — LLM / Jira / Confluence / GitLab / Database forms
- **Status** — connection state and active MCP tools

## Workflows

```bash
npx tsx src/index.ts workflow create --name "demo" --description "sample" --definition-file data/sample-workflow.json
npx tsx src/index.ts workflow visualize demo
```

HTTP nodes support **GET / POST / PUT / PATCH / DELETE** with templated `headers`, `query` (object or query string), and `body` (JSON object or raw string). Body is **not** sent on GET/HEAD by default. Use `{{nodeId.path}}` and `{{secrets.*}}` in URL/headers/query/body. SQL nodes use `type: "sql"` (or `"database"`) with arbitrary `sql` (and optional `params` / `connectionId`). **Every SQL statement pauses for approval** (including SELECT) until `POST /api/approvals/:id/approve`.

### Return mapping (`returns`)

Optional top-level `returns` declares the **Output** shape of a successful run. Values are templates against node outputs (`{{nodeId.path}}`). A single template keeps the typed value; missing paths become `null` and are listed in `meta.returnWarnings` (set `returnsStrict: true` to fail instead).

When `returns` is present, MCP `workflow_run` / `POST /api/workflows/:id/run` include `returns` and omit full `outputs` unless you pass `includeNodes: true` (Studio Run always sets this for debugging). Workflows without `returns` keep the previous full-`outputs` response.

```json
{
  "nodes": [ /* ... */ ],
  "returns": {
    "response": "{{get_profile.body}}",
    "token": "{{exchangeToken.body.access_token}}",
    "userId": "{{profile.body.id}}"
  }
}
```

HTTP node outputs always include `body` (parsed JSON or raw text), plus `status`, `ok`, `text`, and response `headers`. Use `{{nodeId.body}}` for the full payload object, `{{nodeId}}` for the entire node bag, or `{{nodeId.body.field}}` for a leaf. Body object fields are also promoted to the top level for older templates (`{{nodeId.field}}`), except when they collide with envelope keys (`body`, `status`, `ok`, `text`, `headers`).

Example GET (query) and POST (JSON body + headers):

```json
{
  "nodes": [
    {
      "id": "listItems",
      "type": "http",
      "name": "List items",
      "method": "GET",
      "url": "https://httpbin.org/get",
      "headers": { "Accept": "application/json" },
      "query": { "page": "1", "q": "{{input.search}}" }
    },
    {
      "id": "createItem",
      "type": "http",
      "name": "Create item",
      "method": "POST",
      "url": "https://httpbin.org/post",
      "headers": {
        "Content-Type": "application/json",
        "Authorization": "Bearer {{secrets.jira.apiToken}}"
      },
      "body": { "title": "{{input.title}}", "from": "{{listItems.url}}" }
    }
  ]
}
```

Smoke-check HTTP request construction: `npx tsx scripts/smoke-http-node.ts`  
Smoke-check workflow engine (transform, choice, parallel, auth): `npm run smoke:workflow`

### Node cheat-sheet (extras)

| Capability | Snippet |
|------------|---------|
| **Transform** `merge_arrays` / `merge_object` / `parse_json` / `map` / `format_table` | `{ "type":"transform", "op":"format_table", "source":"{{list.body}}", "columns":["id","name"] }` → `{{node.markdown}}` |
| **Choice input** | `{ "type":"input", "inputType":"choice", "optionsFrom":"{{table.items}}", "valueField":"id", "labelFields":"name,tag" }` |
| **HTTP auth helper** | `{ "type":"http", "auth":{ "type":"bearer", "from":"{{token.accessToken}}" }, "headers":{...} }` (raw `Authorization` overrides) |
| **Parallel HTTP** | `{ "type":"parallel", "requests":[{ "id":"a", "method":"GET", "url":"..." }, { "id":"b", ... }] }` → `{{node.results.a.body}}` |
| **Approval** | `{ "type":"approval", "prompt":"Confirm POST?", "summary":"{{body.result}}" }` — reply yes/بله or no/خیر in Chat |

Example SQL node:

```json
{
  "id": "q1",
  "type": "sql",
  "name": "list users",
  "operation": "sql",
  "sql": "SELECT id, email FROM users LIMIT 20",
  "connectionId": "default"
}
```

Approvals API: `GET /api/approvals?status=pending`, `POST /api/approvals/:id/approve`, `POST /api/approvals/:id/reject`.

## MCP servers

| Server | When enabled |
|--------|----------------|
| Jira | baseUrl + (bearer **or** email+apiToken) |
| Confluence | same pattern |
| GitLab | baseUrl + token |
| Workflow | always |
| Database | always (connections optional); schema inspect needs a connection |
| Solver | when LLM is configured |

Database MCP tools (English names): `database_list_connections`, `database_upsert_connection`, `database_delete_connection`, `database_test_connection`, `database_list_databases`, `database_list_schemas`, `database_list_tables`, `database_describe_table`, `database_list_indexes`, `database_list_foreign_keys`, `database_request_sql` (queues approval), `database_list_approvals`.