@proveanything/smartlinks 1.16.7 → 1.17.4

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.
@@ -0,0 +1,168 @@
1
+ # AI Tools & Skills
2
+
3
+ The platform's AI can research the web, extract structured data, screenshot pages, and
4
+ generate images — through a **capability registry**. This page is the catalog: what the
5
+ AI can do, and how an app reaches for it. You should not need to call an API to find
6
+ this out — it's documented here so that when you build an app (or an AI assistant helps
7
+ you), you *know* these capabilities exist and can shape your app to use them.
8
+
9
+ ## Two layers: tools vs skills
10
+
11
+ - **Skills** are the app-facing verbs — named, composed capabilities with the
12
+ orchestration and prompt **baked in**. You invoke a skill by name with structured
13
+ input and get structured output back. **You never write a prompt.** Example:
14
+ `research.brand`.
15
+ - **Tools** are the atomic building blocks (fetch a page, generate an image). The AI
16
+ reaches for these *itself* during a skill or agent run — you rarely call them directly.
17
+
18
+ Rule of thumb: **if a skill exists for what you want, call the skill.** Drop to the
19
+ agent loop (below) only for open-ended tasks with no matching skill.
20
+
21
+ ## Using a skill
22
+
23
+ ```ts
24
+ import { ai } from '@proveanything/smartlinks'
25
+
26
+ // Research a client's brand from their website — no prompt, just input.
27
+ const { profile, sources } = await ai.skills.run(collectionId, 'research.brand', {
28
+ url: 'https://acme.com',
29
+ })
30
+ // profile → { name, description, tagline, palette:[{hex}], logoUrl, tone, keyProducts, socials }
31
+ // sources → which signals were available (markdown, branding, schema.org)
32
+
33
+ // Discover skills at runtime too (this catalog, live):
34
+ const { skills } = await ai.skills.list(collectionId)
35
+ ```
36
+
37
+ ## Deterministic extraction (no AI)
38
+
39
+ For structured pages, skip the LLM entirely — `research.fetch` returns schema.org
40
+ JSON-LD deterministically:
41
+
42
+ ```ts
43
+ const res = await ai./* research */ // see the `research` namespace
44
+ // or the tool directly inside an agent run: web.extractSchema
45
+ ```
46
+ (See the **Integrations / research** doc for `research.fetch`, used e.g. by the Recipes
47
+ app to pull a recipe's schema.org data without any AI.)
48
+
49
+ ## Open-ended tasks: the agent loop
50
+
51
+ When no skill fits, run the agent — it's given the tool catalog and reaches for tools
52
+ as your prompt warrants:
53
+
54
+ ```ts
55
+ const result = await ai.agent.run(collectionId, {
56
+ prompt: 'Research acme.com and draft a one-paragraph brand summary with 3 hero image ideas.',
57
+ allowCapabilities: ['web:read', 'ai:image'], // cap blast radius to these capabilities
58
+ })
59
+ // result.finalText + result.toolResults (the trace of tools the AI called)
60
+
61
+ const { tools } = await ai.agent.listTools(collectionId) // what the AI could reach for
62
+ ```
63
+
64
+ `allowCapabilities` gates which tools a run may use (e.g. omit `ai:image` to forbid
65
+ image generation). Capability tags are listed against each tool below.
66
+
67
+ ## How the AI discovers tools
68
+
69
+ Within a skill or `ai.agent.run`, the tool definitions (names, descriptions, JSON
70
+ schemas) are passed to the model, so it discovers and calls them automatically. Outside
71
+ a run — e.g. the plain chat endpoints — tools are **not** auto-injected; use a skill or
72
+ the agent loop to give the AI tool access.
73
+
74
+ ---
75
+
76
+ <!-- The section below is GENERATED from the server registry (single source of truth),
77
+ also served live at GET /admin/collection/:collectionId/ai/catalog.
78
+ Regenerate with `node scripts/gen-ai-catalog.js` in prove/server. -->
79
+
80
+ ## Skills
81
+
82
+ Named, composed capabilities an app invokes **by name** with structured input — no prompt-shaping. Call `SL.ai.skills.run(collectionId, name, input)`.
83
+
84
+ ### `research.brand`
85
+
86
+ Research a brand or company from its website URL into a structured brand profile (name, description, palette, logo, tone, key products, socials). Gathers page content + schema.org + branding deterministically, then synthesises with AI.
87
+
88
+ _Capabilities: web:read, ai:text_
89
+
90
+ **Input**
91
+ - `url` _(required)_ — string: The brand's website URL (https).
92
+ - `instructions` — string: Optional extra guidance for the researcher.
93
+
94
+ ## Tools
95
+
96
+ Atomic building blocks the AI reaches for **during** an agent/skill run — you rarely call these directly. Enumerable via `SL.ai.agent.listTools(collectionId)`; capability tags cap what a run may use.
97
+
98
+ ### `web.fetchPage`
99
+
100
+ Fetch a web page by URL and return clean markdown, page metadata, and any structured schema.org/JSON-LD data. Use to research a brand or product website.
101
+
102
+ _Capabilities: web:read_
103
+
104
+ **Parameters**
105
+ - `url` _(required)_ — string: Absolute URL to fetch (https).
106
+ - `type` — string: Optional schema.org @type filter for the returned JSON-LD, e.g. "Product" or "Recipe".
107
+ - `forceRefresh` — boolean: Bypass the cache and re-fetch.
108
+
109
+ ### `web.extractSchema`
110
+
111
+ Fetch a URL and return only its schema.org structured data (JSON-LD) of the given @type, e.g. "Recipe" or "Product". Deterministic — no AI.
112
+
113
+ _Capabilities: web:read_
114
+
115
+ **Parameters**
116
+ - `url` _(required)_ — string: Absolute URL to fetch (https).
117
+ - `schemaType` — string: schema.org @type to extract, e.g. "Recipe" or "Product".
118
+ - `forceRefresh` — boolean
119
+
120
+ ### `web.screenshot`
121
+
122
+ Capture a screenshot of a web page. Returns a stable hosted image URL (screenshotUrl) you can then read with image.describe.
123
+
124
+ _Capabilities: web:read_
125
+
126
+ **Parameters**
127
+ - `url` _(required)_ — string: Absolute URL to screenshot (https).
128
+
129
+ ### `image.describe`
130
+
131
+ Describe an image at a URL, or read text from it (image-to-text / vision). Use on a screenshot or photo to extract what it shows or says.
132
+
133
+ _Capabilities: ai:vision_
134
+
135
+ **Parameters**
136
+ - `imageUrl` _(required)_ — string: URL of the image to analyse.
137
+ - `prompt` — string: What to extract or describe (default: describe + transcribe visible text).
138
+
139
+ ### `brand.assets`
140
+
141
+ Extract a website's brand elements — logo, colours, design — plus page metadata. Use to research a brand's visual identity.
142
+
143
+ _Capabilities: web:read_
144
+
145
+ **Parameters**
146
+ - `url` _(required)_ — string: The brand's website URL (https).
147
+
148
+ ### `image.generate`
149
+
150
+ Generate a new image from a text prompt. Returns the generated image (url or base64).
151
+
152
+ _Capabilities: ai:image_
153
+
154
+ **Parameters**
155
+ - `prompt` _(required)_ — string: Description of the image to generate.
156
+ - `size` — string: e.g. "1024x1024".
157
+ - `provider` — `openai` | `gemini`: Image model provider.
158
+
159
+ ### `image.searchStock`
160
+
161
+ Search stock photography (Unsplash) for real photos matching a query. Returns candidate image URLs.
162
+
163
+ _Capabilities: web:read_
164
+
165
+ **Parameters**
166
+ - `query` _(required)_ — string: What to search for.
167
+ - `per_page` — number: How many results (default 10).
168
+ - `orientation` — `landscape` | `portrait` | `squarish`
@@ -0,0 +1,141 @@
1
+ # Integrations
2
+
3
+ An **integration flow** is one input/output pipeline between SmartLinks and an external
4
+ system. There are two directions:
5
+
6
+ - **outbound** — read a SmartLinks entity (v1: a product), transform it with field
7
+ mappings, and send it to an external endpoint.
8
+ - **inbound** — fetch from an external system and write a SmartLinks entity. *(Executor is
9
+ outbound-first; inbound lands in a later increment.)*
10
+
11
+ Flows are triggered three ways, all converging on the same executor:
12
+
13
+ - **manual** — `integrations.runFlow(...)`, inline (returns a run summary) or enqueued.
14
+ - **event** — an outbound flow subscribed to an event type (e.g. `product.updated`) fires
15
+ automatically when that entity changes.
16
+ - **schedule** — a flow carrying a cron/interval `schedule` is run by the scan job. *(next)*
17
+
18
+ Credentials are **never** stored on the flow. The connection holds an opaque
19
+ `credentialRef` into the **sealed-secret store** (`secrets` namespace); the value is sealed
20
+ at rest and resolved server-side only, at execution.
21
+
22
+ ---
23
+
24
+ ## The flow model
25
+
26
+ ```ts
27
+ interface IntegrationFlow {
28
+ id: string
29
+ direction: 'inbound' | 'outbound'
30
+ name: string
31
+ status: 'draft' | 'active' | 'paused' | 'error' // only 'active' flows fire on events/schedule
32
+ eventTypes: string[] // e.g. ['product.updated']
33
+ schedule: string | null // cron/interval for scheduled flows
34
+ sourceEntity: string | null // outbound source, v1: 'product'
35
+ targetEntity: string | null // inbound target
36
+ config: {
37
+ connection?: {
38
+ baseUrl?: string
39
+ sendEndpoint?: string // outbound: appended to baseUrl
40
+ defaultHeaders?: Record<string, string>
41
+ auth?: { method: 'api_key' | 'bearer' | 'basic' | ..., headerName?: string, credentialRef?: string }
42
+ }
43
+ fieldMappings?: FieldMapping[]
44
+ }
45
+ // ...run watermark/telemetry: lastRunAt, lastRunStatus, lastRunCount, totalSynced
46
+ }
47
+ ```
48
+
49
+ ### Field mappings (transform)
50
+
51
+ Each mapping produces one field on the target payload:
52
+
53
+ | transformType | uses | meaning |
54
+ |---|---|---|
55
+ | `direct` | `sourcePath` | copy the value at that dot-path |
56
+ | `static` | `transformExpression` | a constant |
57
+ | `template` | `transformExpression` | a Liquid template rendered against the source record |
58
+ | `jsonata` / `ai` | — | recognised but not yet executed; reported as a per-field error |
59
+
60
+ A single field's failure is collected and the rest continue (partial success) — it never
61
+ aborts the whole record.
62
+
63
+ ---
64
+
65
+ ## Secrets (write-only)
66
+
67
+ The secret store is **write-only from the client**: you can set, rotate, list (refs +
68
+ masked hints + metadata) and delete — but a value never comes back over the API.
69
+
70
+ ```ts
71
+ import { secrets, integrations } from '@proveanything/smartlinks'
72
+
73
+ // 1. Store the destination credential — keep the returned ref.
74
+ const { ref } = await secrets.set(collectionId, {
75
+ name: 'Acme API key',
76
+ purpose: 'integration',
77
+ value: 'sk_live_…', // sent once; never retrievable
78
+ })
79
+
80
+ // list shows refs + masked hints only (safe to render)
81
+ const { secrets: list } = await secrets.list(collectionId)
82
+ // → [{ ref, name: 'Acme API key', hint: '…live_1a2b', purpose, createdAt, ... }]
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Creating and running a flow
88
+
89
+ ```ts
90
+ // 2. Create an outbound flow that pushes products to Acme, authed by the secret above.
91
+ const flow = await integrations.createFlow(collectionId, {
92
+ appId: 'my-integration-app',
93
+ direction: 'outbound',
94
+ name: 'Push products to Acme',
95
+ status: 'active',
96
+ eventTypes: ['product.updated'], // fire whenever a product changes
97
+ sourceEntity: 'product',
98
+ config: {
99
+ connection: {
100
+ baseUrl: 'https://api.acme.example',
101
+ sendEndpoint: '/v1/products',
102
+ auth: { method: 'api_key', headerName: 'X-API-Key', credentialRef: ref },
103
+ },
104
+ fieldMappings: [
105
+ { targetPath: 'sku', sourcePath: 'sku', transformType: 'direct' },
106
+ { targetPath: 'name', sourcePath: 'name', transformType: 'direct' },
107
+ { targetPath: 'label', transformType: 'template', transformExpression: '{{name}} ({{sku}})' },
108
+ ],
109
+ },
110
+ })
111
+
112
+ // 3a. Test it now against one product — inline, returns a summary.
113
+ const result = await integrations.runFlow(collectionId, flow.id, { entityId: 'P1045716' })
114
+ if (integrations.isRunSummary(result)) {
115
+ console.log(result) // { records: 1, sent: 1, failed: 0, status: 'success' }
116
+ }
117
+
118
+ // 3b. Or enqueue on the worker (returns immediately).
119
+ await integrations.runFlow(collectionId, flow.id, { entityId: 'P1045716', async: true })
120
+ ```
121
+
122
+ Once `status: 'active'` with `eventTypes: ['product.updated']`, editing that product in the
123
+ admin API fires the flow automatically — no manual run needed.
124
+
125
+ ---
126
+
127
+ ## Reference
128
+
129
+ | Function | HTTP |
130
+ |---|---|
131
+ | `integrations.listFlows(collectionId, query?)` | `GET /integrations/flows` |
132
+ | `integrations.createFlow(collectionId, input)` | `POST /integrations/flows` |
133
+ | `integrations.getFlow(collectionId, id)` | `GET /integrations/flows/:id` |
134
+ | `integrations.updateFlow(collectionId, id, input)` | `PUT /integrations/flows/:id` |
135
+ | `integrations.deleteFlow(collectionId, id)` | `DELETE /integrations/flows/:id` |
136
+ | `integrations.runFlow(collectionId, id, opts?)` | `POST /integrations/flows/:id/run` |
137
+ | `secrets.list(collectionId, query?)` | `GET /secrets` |
138
+ | `secrets.set(collectionId, input)` | `POST /secrets` |
139
+ | `secrets.get(collectionId, ref)` | `GET /secrets/:ref` |
140
+ | `secrets.rotate(collectionId, ref, input)` | `PUT /secrets/:ref` |
141
+ | `secrets.remove(collectionId, ref)` | `DELETE /secrets/:ref` |