@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.
- package/dist/api/ai.d.ts +34 -1
- package/dist/api/ai.js +64 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +3 -0
- package/dist/api/integrations.d.ts +28 -0
- package/dist/api/integrations.js +82 -0
- package/dist/api/research.d.ts +9 -0
- package/dist/api/research.js +21 -0
- package/dist/api/secrets.d.ts +15 -0
- package/dist/api/secrets.js +52 -0
- package/dist/docs/API_SUMMARY.md +374 -1
- package/dist/docs/ai-tools-and-skills.md +168 -0
- package/dist/docs/integrations.md +141 -0
- package/dist/openapi.yaml +617 -0
- package/dist/types/ai.d.ts +57 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/integrations.d.ts +136 -0
- package/dist/types/integrations.js +10 -0
- package/dist/types/research.d.ts +22 -0
- package/dist/types/research.js +6 -0
- package/docs/API_SUMMARY.md +374 -1
- package/docs/ai-tools-and-skills.md +168 -0
- package/docs/integrations.md +141 -0
- package/openapi.yaml +617 -0
- package/package.json +2 -2
|
@@ -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` |
|