diff --git a/.claude/skills/credential-create/SKILL.md b/.claude/skills/credential-create/SKILL.md index 53bfb71..dcf07a1 100644 --- a/.claude/skills/credential-create/SKILL.md +++ b/.claude/skills/credential-create/SKILL.md @@ -45,6 +45,14 @@ Naming rules: - Function: `NewCredential` (PascalCase). For OAuth2/OAuth1 the suffix is `OAuth2` / `OAuth1`, not `Oauth2`. - Credential `ID`: `_` (e.g., `mistral_api`, `notion_oauth2`) +## Icon + +A credential wears its provider's plain brand mark; the glyph badge belongs to +individual nodes, so `CredentialIcon` carries only `Brand`. The value names the +artwork under `apps/platform/public/assets/icons/brands//{light,dark}.svg` +— reuse the provider's existing brand, or drop the two files in for a provider +that has none. Nothing registers a brand in code. + ## Templates ### API key credential @@ -64,8 +72,7 @@ func NewApiCredential() *domain.Credential { Name: "", Description: "", Icon: domain.CredentialIcon{ - Light: "", - Dark: "", + Brand: "", }, Schema: &gjs.Schema{ Type: "object", @@ -117,8 +124,7 @@ func NewOAuth2Credential(redirectURL string) *domain.Credential { Name: "", Description: "", Icon: domain.CredentialIcon{ - Light: "", - Dark: "", + Brand: "", }, Schema: &gjs.Schema{ Type: "object", diff --git a/.claude/skills/credential-update/SKILL.md b/.claude/skills/credential-update/SKILL.md index 71797fa..3ceee9c 100644 --- a/.claude/skills/credential-update/SKILL.md +++ b/.claude/skills/credential-update/SKILL.md @@ -135,7 +135,7 @@ grep -rn 'SupportedCredentials.*""' internal/nodeengine/nodes/ #### Update display metadata -`Name`, `Description`, `Icon` are independent and non-breaking. +`Name`, `Description`, `Icon` are independent and non-breaking. `CredentialIcon` carries only a `Brand` — the provider's plain mark, named after a folder under the platform UI's `public/assets/icons/brands/`; the glyph badge belongs to individual nodes. #### Disable / enable diff --git a/.claude/skills/node-create/SKILL.md b/.claude/skills/node-create/SKILL.md index 048fb02..259089f 100644 --- a/.claude/skills/node-create/SKILL.md +++ b/.claude/skills/node-create/SKILL.md @@ -53,6 +53,50 @@ When **adding a new action to an existing provider**, only create `/node When **adding a new provider from scratch**, create the entire tree above, then add the provider's `Register()` call to `internal/nodeengine/nodes/nodes.go`. +## Icon and handles + +A node states what it looks like and how it connects; nothing is derived from +its name or category. + +**Icon** is a brand plus a glyph. `Brand` names the artwork under +`apps/platform/public/assets/icons/brands//{light,dark}.svg` — reuse the +provider's existing brand (`gmail`, `slack`, `x`). A provider that has no mark +yet needs one dropped in that folder; no code registers it. `Glyph` names the +action badge under `.../glyphs/.svg` — reuse an existing one where it +fits (`send`, `search`, `create`, `list`, `eye`, `trash`, `pencil`, `image`, +`film`, `speaker`, `music`, `chat`, `folder`, `file`, `table`, `record`, +`branch`, `clock`, `play`, `note`, `story`, `upload`, `organize`, `translate`, +`trending`, `target`). Two nodes of the same provider must not share a glyph — +that is the only thing telling them apart in a list. + +A system primitive with no brand behind it declares only `Glyph`. + +**Handles** are the connection points, and every node lists its own; there is no +default. Almost every node is one in, one out: + +```go +Inputs: []nodes.NodeHandle{ + {Key: "in"}, +}, +Outputs: []nodes.NodeHandle{ + {Key: "out"}, +}, +``` + +A node with no way in declares `Inputs: []nodes.NodeHandle{}` (an empty, non-nil +slice — `nil` would mean "not declared"). A node that branches declares one +output per branch with a `Label`, which the canvas prints next to the dot: + +```go +Outputs: []nodes.NodeHandle{ + {Key: "true", Label: "True"}, + {Key: "false", Label: "False"}, +}, +``` + +Edges record which output they leave from, so a key is part of the saved +workflow — renaming one breaks every flow that uses it. + ## Annotations classification Every node carries `Annotations: nodes.NodeAnnotations{...}` describing how the tool affects its environment. MCP clients (Claude Desktop, Cursor, etc.) use these hints to decide when to ask the user for confirmation. Pick one of these groups: @@ -145,8 +189,14 @@ func NewNode(nodeID string) *Node { Name: "", Description: "", Icon: nodes.NodeIcon{ - Light: nodeID, - Dark: nodeID, + Brand: "", + Glyph: "", + }, + Inputs: []nodes.NodeHandle{ + {Key: "in"}, + }, + Outputs: []nodes.NodeHandle{ + {Key: "out"}, }, Categories: []string{""}, SubCategories: []string{""}, diff --git a/.claude/skills/node-update/SKILL.md b/.claude/skills/node-update/SKILL.md index 7e45c36..11b5b21 100644 --- a/.claude/skills/node-update/SKILL.md +++ b/.claude/skills/node-update/SKILL.md @@ -144,6 +144,10 @@ Verify the Go type still matches the new values (e.g., changing from `int` to `f `Name`, `Description`, `Icon`, `Categories`, `SubCategories`, `Tags` are independent. Tags must remain lowercase; categories must use one of the existing top-level values (memory: `node-create` skill lists them). +`Icon` is a `Brand` (the provider's mark) plus a `Glyph` (the action badge), both naming files under the platform UI's `public/assets/icons/`; see the `node-create` skill for the glyph vocabulary and the rule that two nodes of one provider must not share a glyph. + +`Inputs`/`Outputs` are a different matter — they are not metadata. A handle `Key` is written into every edge of every saved workflow, so renaming or removing one breaks each flow that uses it. Adding an output is safe; changing an existing key is a migration, not an update. + #### Add / remove a supported credential ```go diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 166b528..14028b3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -131,7 +131,7 @@ The mechanics behind the headline features are documented next to the code: | --- | --- | | The canvas model — `$references`, credential references, deliberate simplicity | [`workflows`](apps/platform-api/internal/workflows/README.md) | | AI workflow generation from chat (SSE streaming) | [`workflows`](apps/platform-api/internal/workflows/README.md) | -| One node descriptor → UI form, validation, function calling, MCP tool | [`nodeengine`](apps/platform-api/internal/nodeengine/README.md) | +| One node descriptor → UI form, icon, connection points, validation, function calling, MCP tool | [`nodeengine`](apps/platform-api/internal/nodeengine/README.md) | | How a node becomes an MCP tool | [`mcp`](apps/platform-api/internal/mcp/README.md) | | Task scheduling, DAG execution order, prompt layering | [`taskrunner`](apps/platform-api/internal/taskrunner/README.md) | | Trigger types, webhook adapters, runtime config overlay | [`triggers`](apps/platform-api/internal/triggers/README.md) | diff --git a/apps/platform-api/internal/nodeengine/README.md b/apps/platform-api/internal/nodeengine/README.md index 32054ca..b7d3e6e 100644 --- a/apps/platform-api/internal/nodeengine/README.md +++ b/apps/platform-api/internal/nodeengine/README.md @@ -8,7 +8,9 @@ The node engine owns the **definitions** of everything a workflow can do: each n ## Core concepts -- **Node** (`domain/nodes/node.go`): a declarative action descriptor — `ID`, `Version`, name/description, `InputSchema`/`OutputSchema` (`jsonschema-go`), `Categories`/`SubCategories`/`Tags`, `SupportedCredentials`, `Annotations`, `HasNaturalLanguage`, `Disabled`. Implements `NodeManager` (getter interface). +- **Node** (`domain/nodes/node.go`): a declarative action descriptor — `ID`, `Version`, name/description, `Icon` (a `Brand` naming the provider's mark plus a `Glyph` naming the action badge), `Inputs`/`Outputs` (the connection points, each a `Key` and an optional `Label`; every node states its own, there is no default), `InputSchema`/`OutputSchema` (`jsonschema-go`), `Categories`/`SubCategories`/`Tags`, `SupportedCredentials`, `Annotations`, `HasNaturalLanguage`, `Disabled`. Implements `NodeManager` (getter interface). + + A node describes how it looks and how it connects, so the UI derives nothing: the artwork lives under `apps/platform/public/assets/icons/{brands,glyphs}/` and is composed at render time. `system_starter` declares an empty `Inputs` slice (no way in), `system_condition` declares two outputs labelled True and False. Credentials (`CredentialIcon`) and MCP servers (`ServerIcon`) name a `Brand` only — the glyph badge belongs to individual nodes. - **Executor** (`domain/executors/executor.go`): the behavior for a node, keyed by the same ID. `ExecutorManager.ExecuteWithContext(ctx, credentials, data)` is the execution entry point. - **Credential** (`domain/credentials/credential.go`): a provider auth descriptor — schema, `IsOAuth1`/`IsOAuth2`/`IsSupportPlatform`, `SupportedNodes`. OAuth credentials may implement `RefreshableCredential`. `IsSupportPlatform` is auto-derived at load (host-provided OAuth apps). - **Adapter** (`domain/adapters/adapter.go`): a trigger adapter that maps a raw webhook payload into a `TriggerContext` (`source`/`sender`/`prompt`/`payload`, exposed as `$trigger.*` variables).