@synap-core/workspace-templates 0.12.0 → 0.24.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 +509 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +1496 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +494 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-DqoItRiR.d.ts +1370 -0
- package/dist/index.d.ts +174 -900
- package/dist/index.js +26447 -24075
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +27 -4
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +105 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +138 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.yaml +85 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +779 -91
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +296 -49
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +66 -2
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +349 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1873 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +246 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1563 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +146 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +78 -38
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +46 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +51 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +81 -0
- package/src/retired-goal-kinds.test.ts +52 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +1 -6
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +40 -2
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/stripe-capability.test.ts +192 -0
- package/src/task-intents.test.ts +293 -0
- package/src/template-documents.test.ts +158 -0
- package/src/templates.ts +20907 -21464
- package/src/types.ts +475 -61
- package/src/unipile-linkedin-find-profile.test.ts +183 -0
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +22 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +71 -4
- package/src/validate.ts +1416 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/views/bookmark-views-pack.view.json +39 -0
- package/views/event-views-pack.view.json +45 -0
- package/views/task-views-pack.view.json +53 -0
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- package/src/the-arch.yaml +0 -201
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": "synap/v1",
|
|
3
|
+
"kind": "capability",
|
|
4
|
+
"meta": {
|
|
5
|
+
"slug": "dropbox",
|
|
6
|
+
"name": "Dropbox — Files & Storage",
|
|
7
|
+
"description": "Dropbox file operations through one Nango OAuth connection (API v2, RPC style): list a folder, search, get metadata, mint a temporary download link, create a folder, and delete. Read verbs auto-run; writes route through the capability-execution gate. Connect your Dropbox account to use it.",
|
|
8
|
+
"tags": [
|
|
9
|
+
"capability"
|
|
10
|
+
],
|
|
11
|
+
"isPublic": true,
|
|
12
|
+
"syncByDefault": true
|
|
13
|
+
},
|
|
14
|
+
"spec": {
|
|
15
|
+
"key": "dropbox",
|
|
16
|
+
"name": "Dropbox — Files & Storage",
|
|
17
|
+
"description": "Dropbox file operations through ONE Nango OAuth connection (Dropbox API v2 — RPC style: every verb POSTs a JSON body to api.dropboxapi.com/2). READ your files — list a folder's entries, search names and content, inspect one item's metadata, and mint a temporary (4-hour) download link. WRITE — create a folder, delete a file or folder. PRECONDITION: the 'dropbox' integration must be declared + configured on the pod's Nango (scopes: files.metadata.read, files.content.read, and files.content.write for create_folder/delete) and the acting user must connect their Dropbox account via Connectors; the dispatcher resolves the most-recent dropbox connection automatically. Paths are RELATIVE to the Nango dropbox integration's base URL (api.dropboxapi.com/2) — no baseUrlOverride, so no provider-proxy-host allowlist entry is needed. Reads (list/search/get/get_temporary_link) auto-run; writes (create_folder, delete) are side-effecting and route through the capability-execution gate. BODY TYPING — IMPORTANT: the declarative engine interpolates `{{param}}` into a JSON body as STRINGS ONLY, and Dropbox type-checks its RPC bodies (a string where a uint32/boolean is expected returns 400 'expected uint64, got string'). So NO numeric/boolean body params are declared here: `limit` and `recursive` are omitted from list_folder and `max_results` from search_v2, letting Dropbox apply its own defaults; declaring them would ship a verb that always errors. A typed numeric body param needs substrate support before it can be added. NOT INCLUDED: raw binary download/upload — `files/download` and `files/upload` carry their argument in the `Dropbox-API-Arg` HEADER (which the engine cannot interpolate), live on the separate content.dropboxapi.com host, and return raw bytes with the metadata in a response header (which responseShape cannot read). Use `dropbox_get_temporary_link` to obtain a download URL instead. Two shaping limits worth knowing: Dropbox's `.tag` union discriminator (a key that literally begins with a dot) is not addressable through the engine's dot-path getter — a segment named `.tag` splits to an empty key — so list/search entries carry no explicit file-vs-folder mark (a folder is an entry whose `size` is null); and an unscoped search sends `options.path` as an empty string rather than omitting the key, because only QUERY keys are dropped when empty, never BODY keys. Response-field shaping is best-effort and confirmed against the live API at first run.",
|
|
18
|
+
"params": [],
|
|
19
|
+
"vault": [],
|
|
20
|
+
"tools": [
|
|
21
|
+
{
|
|
22
|
+
"name": "dropbox",
|
|
23
|
+
"kind": "provider",
|
|
24
|
+
"description": "Dropbox API v2 access through Nango. credentialRef nango://dropbox resolves the user's Dropbox connection (Connection-Id + Provider-Config-Key) and proxies to the integration's base URL (api.dropboxapi.com/2).",
|
|
25
|
+
"credentialRef": "nango://dropbox",
|
|
26
|
+
"executor": "is-agent",
|
|
27
|
+
"config": {
|
|
28
|
+
"providerConfigKey": "dropbox"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"skills": [
|
|
33
|
+
{
|
|
34
|
+
"name": "dropbox_list_folder",
|
|
35
|
+
"intent": "list_records",
|
|
36
|
+
"kind": "declarative",
|
|
37
|
+
"scope": "pod",
|
|
38
|
+
"description": "Lists the entries (files and folders) in a Dropbox folder. Args: { path?: string (Dropbox path, e.g. \"/Projects\"; omit or pass \"\" for the root) }.",
|
|
39
|
+
"requires": [
|
|
40
|
+
"dropbox"
|
|
41
|
+
],
|
|
42
|
+
"executionMode": "sync",
|
|
43
|
+
"timeoutSeconds": 60,
|
|
44
|
+
"parameters": {
|
|
45
|
+
"path": "string?"
|
|
46
|
+
},
|
|
47
|
+
"providerSpec": {
|
|
48
|
+
"tool": "dropbox",
|
|
49
|
+
"method": "POST",
|
|
50
|
+
"pathTemplate": "/files/list_folder",
|
|
51
|
+
"body": {
|
|
52
|
+
"path": "{{path}}"
|
|
53
|
+
},
|
|
54
|
+
"paramMapping": {
|
|
55
|
+
"path": {
|
|
56
|
+
"default": ""
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"responseShape": {
|
|
60
|
+
"collectionPath": "entries",
|
|
61
|
+
"collectionAs": "entries",
|
|
62
|
+
"item": {
|
|
63
|
+
"name": "name",
|
|
64
|
+
"path": "path_display",
|
|
65
|
+
"id": "id",
|
|
66
|
+
"size": "size",
|
|
67
|
+
"clientModified": "client_modified",
|
|
68
|
+
"serverModified": "server_modified"
|
|
69
|
+
},
|
|
70
|
+
"scalar": {
|
|
71
|
+
"hasMore": "has_more",
|
|
72
|
+
"cursor": "cursor"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "dropbox_search",
|
|
79
|
+
"intent": "search_external",
|
|
80
|
+
"kind": "declarative",
|
|
81
|
+
"scope": "pod",
|
|
82
|
+
"description": "Searches the user's Dropbox by file name and content. Args: { query: string (max 1000 chars), path?: string (folder to scope the search to; omit or pass \"\" to search everywhere) }.",
|
|
83
|
+
"requires": [
|
|
84
|
+
"dropbox"
|
|
85
|
+
],
|
|
86
|
+
"executionMode": "sync",
|
|
87
|
+
"timeoutSeconds": 60,
|
|
88
|
+
"parameters": {
|
|
89
|
+
"query": "string",
|
|
90
|
+
"path": "string?"
|
|
91
|
+
},
|
|
92
|
+
"providerSpec": {
|
|
93
|
+
"tool": "dropbox",
|
|
94
|
+
"method": "POST",
|
|
95
|
+
"pathTemplate": "/files/search_v2",
|
|
96
|
+
"body": {
|
|
97
|
+
"query": "{{query}}",
|
|
98
|
+
"options": {
|
|
99
|
+
"path": "{{path}}"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"paramMapping": {
|
|
103
|
+
"query": {
|
|
104
|
+
"required": true
|
|
105
|
+
},
|
|
106
|
+
"path": {
|
|
107
|
+
"default": ""
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"responseShape": {
|
|
111
|
+
"collectionPath": "matches",
|
|
112
|
+
"collectionAs": "matches",
|
|
113
|
+
"item": {
|
|
114
|
+
"name": "metadata.metadata.name",
|
|
115
|
+
"path": "metadata.metadata.path_display",
|
|
116
|
+
"id": "metadata.metadata.id",
|
|
117
|
+
"size": "metadata.metadata.size",
|
|
118
|
+
"serverModified": "metadata.metadata.server_modified"
|
|
119
|
+
},
|
|
120
|
+
"scalar": {
|
|
121
|
+
"hasMore": "has_more"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "dropbox_get_metadata",
|
|
128
|
+
"intent": "fetch_record",
|
|
129
|
+
"kind": "declarative",
|
|
130
|
+
"scope": "pod",
|
|
131
|
+
"description": "Gets metadata (name, path, id, size, timestamps, rev) for one file or folder. Args: { path: string (e.g. \"/Docs/report.pdf\"; id:… / rev:… refs are also accepted) }.",
|
|
132
|
+
"requires": [
|
|
133
|
+
"dropbox"
|
|
134
|
+
],
|
|
135
|
+
"executionMode": "sync",
|
|
136
|
+
"timeoutSeconds": 60,
|
|
137
|
+
"parameters": {
|
|
138
|
+
"path": "string"
|
|
139
|
+
},
|
|
140
|
+
"providerSpec": {
|
|
141
|
+
"tool": "dropbox",
|
|
142
|
+
"method": "POST",
|
|
143
|
+
"pathTemplate": "/files/get_metadata",
|
|
144
|
+
"body": {
|
|
145
|
+
"path": "{{path}}"
|
|
146
|
+
},
|
|
147
|
+
"paramMapping": {
|
|
148
|
+
"path": {
|
|
149
|
+
"required": true
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"responseShape": {
|
|
153
|
+
"scalar": {
|
|
154
|
+
"name": "name",
|
|
155
|
+
"path": "path_display",
|
|
156
|
+
"id": "id",
|
|
157
|
+
"size": "size",
|
|
158
|
+
"rev": "rev",
|
|
159
|
+
"clientModified": "client_modified",
|
|
160
|
+
"serverModified": "server_modified"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "dropbox_get_temporary_link",
|
|
167
|
+
"intent": "manage_file",
|
|
168
|
+
"kind": "declarative",
|
|
169
|
+
"scope": "pod",
|
|
170
|
+
"description": "Returns a temporary download URL for a file — the way to get at a Dropbox file's bytes without streaming them. The link expires after 4 hours. Args: { path: string }.",
|
|
171
|
+
"requires": [
|
|
172
|
+
"dropbox"
|
|
173
|
+
],
|
|
174
|
+
"executionMode": "sync",
|
|
175
|
+
"timeoutSeconds": 60,
|
|
176
|
+
"parameters": {
|
|
177
|
+
"path": "string"
|
|
178
|
+
},
|
|
179
|
+
"providerSpec": {
|
|
180
|
+
"tool": "dropbox",
|
|
181
|
+
"method": "POST",
|
|
182
|
+
"pathTemplate": "/files/get_temporary_link",
|
|
183
|
+
"body": {
|
|
184
|
+
"path": "{{path}}"
|
|
185
|
+
},
|
|
186
|
+
"paramMapping": {
|
|
187
|
+
"path": {
|
|
188
|
+
"required": true
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"responseShape": {
|
|
192
|
+
"scalar": {
|
|
193
|
+
"link": "link",
|
|
194
|
+
"name": "metadata.name",
|
|
195
|
+
"path": "metadata.path_display",
|
|
196
|
+
"size": "metadata.size"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"name": "dropbox_create_folder",
|
|
203
|
+
"intent": "manage_file",
|
|
204
|
+
"kind": "declarative",
|
|
205
|
+
"scope": "pod",
|
|
206
|
+
"description": "Creates a folder at the given Dropbox path. Side-effecting write — routes through the capability-execution gate. Args: { path: string (e.g. \"/Projects/2026\") }.",
|
|
207
|
+
"requires": [
|
|
208
|
+
"dropbox"
|
|
209
|
+
],
|
|
210
|
+
"executionMode": "sync",
|
|
211
|
+
"timeoutSeconds": 60,
|
|
212
|
+
"parameters": {
|
|
213
|
+
"path": "string"
|
|
214
|
+
},
|
|
215
|
+
"providerSpec": {
|
|
216
|
+
"tool": "dropbox",
|
|
217
|
+
"method": "POST",
|
|
218
|
+
"pathTemplate": "/files/create_folder_v2",
|
|
219
|
+
"body": {
|
|
220
|
+
"path": "{{path}}",
|
|
221
|
+
"autorename": false
|
|
222
|
+
},
|
|
223
|
+
"paramMapping": {
|
|
224
|
+
"path": {
|
|
225
|
+
"required": true
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"responseShape": {
|
|
229
|
+
"scalar": {
|
|
230
|
+
"name": "metadata.name",
|
|
231
|
+
"path": "metadata.path_display",
|
|
232
|
+
"id": "metadata.id"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "dropbox_delete",
|
|
239
|
+
"intent": "manage_file",
|
|
240
|
+
"kind": "declarative",
|
|
241
|
+
"scope": "pod",
|
|
242
|
+
"description": "Deletes a file or folder at the given Dropbox path (a folder deletes its contents too). Side-effecting write — routes through the capability-execution gate. Args: { path: string }.",
|
|
243
|
+
"requires": [
|
|
244
|
+
"dropbox"
|
|
245
|
+
],
|
|
246
|
+
"executionMode": "sync",
|
|
247
|
+
"timeoutSeconds": 60,
|
|
248
|
+
"parameters": {
|
|
249
|
+
"path": "string"
|
|
250
|
+
},
|
|
251
|
+
"providerSpec": {
|
|
252
|
+
"tool": "dropbox",
|
|
253
|
+
"method": "POST",
|
|
254
|
+
"pathTemplate": "/files/delete_v2",
|
|
255
|
+
"body": {
|
|
256
|
+
"path": "{{path}}"
|
|
257
|
+
},
|
|
258
|
+
"paramMapping": {
|
|
259
|
+
"path": {
|
|
260
|
+
"required": true
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"responseShape": {
|
|
264
|
+
"scalar": {
|
|
265
|
+
"name": "metadata.name",
|
|
266
|
+
"path": "metadata.path_display",
|
|
267
|
+
"id": "metadata.id"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
"provides": [
|
|
274
|
+
"fetch_record",
|
|
275
|
+
"list_records",
|
|
276
|
+
"manage_file",
|
|
277
|
+
"search_external"
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
"dependencies": []
|
|
281
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": "synap/v1",
|
|
3
|
+
"kind": "capability",
|
|
4
|
+
"meta": {
|
|
5
|
+
"slug": "exa",
|
|
6
|
+
"name": "Exa — Web Search",
|
|
7
|
+
"description": "Search the web with Exa to discover companies, people, news and articles you don't know about yet, or find pages similar to one you have. Search only; results are saved only if you choose to. Needs an Exa API key.",
|
|
8
|
+
"tags": [
|
|
9
|
+
"capability"
|
|
10
|
+
],
|
|
11
|
+
"isPublic": true,
|
|
12
|
+
"syncByDefault": true
|
|
13
|
+
},
|
|
14
|
+
"spec": {
|
|
15
|
+
"key": "exa",
|
|
16
|
+
"name": "Exa — Neural Web Search",
|
|
17
|
+
"description": "AI-native web search and discovery — the DISCOVERY pillar the scrape/act connectors can't provide. Apify scrapes sources you already name, Unipile acts in accounts you own, Nango calls APIs you're authed to; Exa answers the question none of them can: 'find things across the web I don't know about yet' (new competitors, people voicing a problem, fresh content on a topic). This is what makes a kind-bound radar SELF-GENERATING — it discovers new entities rather than only re-scanning known ones. Verified against Exa's live API docs (POST https://api.exa.ai/search, x-api-key header, category filter incl. company/people/news, /findSimilar), not guessed. Skills are READ-ONLY search primitives with no side effects: a radar playbook composes them (fan-out, then resolve-or-create deduplicated entities via the pod's identity-resolution door) — the search skills themselves never create entities, keeping WHAT-to-search separate from WHAT-to-do-with-results. PRECONDITION: an Exa account API key (dashboard.exa.ai -> API Keys).",
|
|
18
|
+
"params": [
|
|
19
|
+
{
|
|
20
|
+
"name": "exaApiKey",
|
|
21
|
+
"label": "Exa API key",
|
|
22
|
+
"type": "text",
|
|
23
|
+
"required": true,
|
|
24
|
+
"description": "The Exa API key (dashboard.exa.ai -> API Keys). Stored server-encrypted in the vault; never persisted in the template."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"vault": [
|
|
28
|
+
{
|
|
29
|
+
"ref": "exaApiKeySecret",
|
|
30
|
+
"name": "{{name}} Exa API key",
|
|
31
|
+
"value": "{{exaApiKey}}",
|
|
32
|
+
"type": "api_key",
|
|
33
|
+
"description": "Exa API key for the exa_api connector."
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "exa_api",
|
|
39
|
+
"kind": "api",
|
|
40
|
+
"description": "Authenticated HTTP access to the Exa REST API (https://api.exa.ai). The applier injects the vaulted API key into the `x-api-key` header (raw key, no prefix — Exa's auth scheme); its credentialRef is remapped to the runtime vault://<id> at apply time.",
|
|
41
|
+
"credentialRef": "exaApiKeySecret",
|
|
42
|
+
"executor": "is-agent",
|
|
43
|
+
"config": {
|
|
44
|
+
"baseUrl": "https://api.exa.ai",
|
|
45
|
+
"auth": {
|
|
46
|
+
"in": "header",
|
|
47
|
+
"name": "x-api-key",
|
|
48
|
+
"prefix": ""
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"skills": [
|
|
54
|
+
{
|
|
55
|
+
"name": "exa_search",
|
|
56
|
+
"intent": "search_web",
|
|
57
|
+
"kind": "code",
|
|
58
|
+
"scope": "pod",
|
|
59
|
+
"description": "Neural web search — finds pages semantically matching a query (not just keyword-matching), the discovery engine for radars. Read-only, no side effects. Use `category` to narrow to a kind of result: 'company' (find competitors/vendors), 'people' (find leads/prospects voicing a need), 'news', 'research paper', 'financial report'. Returns normalized results { title, url, publishedDate, author, text, summary, entities }. Args: { query: string, numResults?: number (default 10, max 100), category?: string, type?: string ('auto'|'fast'|'deep'), includeDomains?: string[], excludeDomains?: string[], startPublishedDate?: ISO8601, endPublishedDate?: ISO8601, summary?: boolean }.",
|
|
60
|
+
"requires": [
|
|
61
|
+
"exa_api"
|
|
62
|
+
],
|
|
63
|
+
"executionMode": "sync",
|
|
64
|
+
"timeoutSeconds": 45,
|
|
65
|
+
"parameters": {
|
|
66
|
+
"query": "string",
|
|
67
|
+
"numResults": "number?",
|
|
68
|
+
"category": "string?",
|
|
69
|
+
"type": "string?",
|
|
70
|
+
"includeDomains": "array?",
|
|
71
|
+
"excludeDomains": "array?",
|
|
72
|
+
"startPublishedDate": "string?",
|
|
73
|
+
"endPublishedDate": "string?",
|
|
74
|
+
"summary": "boolean?"
|
|
75
|
+
},
|
|
76
|
+
"code": "// Neural web search via Exa. Executor wraps this body as (async (args, context) => { <code> });\n// use args directly and `return`. callProvider('exa_api','POST','/search',body) resolves the\n// exa_api tool by NAME and injects the vaulted key as `x-api-key: <key>` (config.auth).\n// Build the body omitting undefined optional fields — Exa rejects empty enum values.\nconst query = String(args?.query ?? '').trim();\nif (!query) throw new Error('exa_search: query is required');\n\nconst n = Number(args?.numResults);\nconst numResults = Number.isFinite(n) ? Math.max(1, Math.min(100, Math.trunc(n))) : 10;\n\nconst contents = { text: { maxCharacters: 1500 } };\nif (args?.summary === true) contents.summary = true;\n\nconst body = { query, numResults, contents };\nif (typeof args?.category === 'string' && args.category.trim()) body.category = args.category.trim();\nif (typeof args?.type === 'string' && args.type.trim()) body.type = args.type.trim();\nif (Array.isArray(args?.includeDomains) && args.includeDomains.length) body.includeDomains = args.includeDomains;\nif (Array.isArray(args?.excludeDomains) && args.excludeDomains.length) body.excludeDomains = args.excludeDomains;\nif (typeof args?.startPublishedDate === 'string' && args.startPublishedDate.trim()) body.startPublishedDate = args.startPublishedDate.trim();\nif (typeof args?.endPublishedDate === 'string' && args.endPublishedDate.trim()) body.endPublishedDate = args.endPublishedDate.trim();\n\nconst res = await callProvider('exa_api', 'POST', '/search', body);\nconst payload = res?.body ?? res;\nconst rows = Array.isArray(payload?.results) ? payload.results : [];\nconst results = rows.map((r) => ({\n title: r?.title ?? null,\n url: r?.url ?? null,\n publishedDate: r?.publishedDate ?? null,\n author: r?.author ?? null,\n text: r?.text ?? null,\n summary: r?.summary ?? null,\n entities: r?.entities ?? null,\n}));\nreturn { query, count: results.length, results, costDollars: payload?.costDollars ?? null };",
|
|
77
|
+
"metadata": {
|
|
78
|
+
"readOnly": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "exa_find_similar",
|
|
83
|
+
"intent": "search_external",
|
|
84
|
+
"kind": "code",
|
|
85
|
+
"scope": "pod",
|
|
86
|
+
"description": "Finds web pages semantically SIMILAR to a given URL — 'more sites like this competitor / this article'. Read-only, no side effects. Great for expanding a radar from one known entity to its neighbours (e.g. from one competitor's site to other companies in the same space). Returns normalized results { title, url, publishedDate, author, text }. Args: { url: string, numResults?: number (default 10, max 100), excludeSourceDomain?: boolean (default true — don't return the input site itself) }.",
|
|
87
|
+
"requires": [
|
|
88
|
+
"exa_api"
|
|
89
|
+
],
|
|
90
|
+
"executionMode": "sync",
|
|
91
|
+
"timeoutSeconds": 45,
|
|
92
|
+
"parameters": {
|
|
93
|
+
"url": "string",
|
|
94
|
+
"numResults": "number?",
|
|
95
|
+
"excludeSourceDomain": "boolean?"
|
|
96
|
+
},
|
|
97
|
+
"code": "// Find pages similar to a URL via Exa's /findSimilar. Executor wraps this body as\n// (async (args, context) => { <code> }); use args directly and `return`.\n// callProvider('exa_api','POST','/findSimilar',body) injects `x-api-key` via config.auth.\nconst url = String(args?.url ?? '').trim();\nif (!url) throw new Error('exa_find_similar: url is required');\n\nconst n = Number(args?.numResults);\nconst numResults = Number.isFinite(n) ? Math.max(1, Math.min(100, Math.trunc(n))) : 10;\n\nconst body = {\n url,\n numResults,\n excludeSourceDomain: args?.excludeSourceDomain !== false,\n contents: { text: { maxCharacters: 1500 } },\n};\n\nconst res = await callProvider('exa_api', 'POST', '/findSimilar', body);\nconst payload = res?.body ?? res;\nconst rows = Array.isArray(payload?.results) ? payload.results : [];\nconst results = rows.map((r) => ({\n title: r?.title ?? null,\n url: r?.url ?? null,\n publishedDate: r?.publishedDate ?? null,\n author: r?.author ?? null,\n text: r?.text ?? null,\n}));\nreturn { url, count: results.length, results, costDollars: payload?.costDollars ?? null };",
|
|
98
|
+
"metadata": {
|
|
99
|
+
"readOnly": true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"provides": [
|
|
104
|
+
"search_web",
|
|
105
|
+
"search_external"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"dependencies": []
|
|
109
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": "synap/v1",
|
|
3
|
+
"kind": "capability",
|
|
4
|
+
"meta": {
|
|
5
|
+
"slug": "fal-ai",
|
|
6
|
+
"name": "fal.ai — Image Generation",
|
|
7
|
+
"description": "Generate images from a text prompt with fal.ai models such as FLUX, and save each result as a bookmark once you approve it. Needs a fal.ai API key.",
|
|
8
|
+
"tags": [
|
|
9
|
+
"capability"
|
|
10
|
+
],
|
|
11
|
+
"isPublic": true,
|
|
12
|
+
"syncByDefault": true
|
|
13
|
+
},
|
|
14
|
+
"spec": {
|
|
15
|
+
"key": "fal-ai",
|
|
16
|
+
"name": "fal.ai Image Generation",
|
|
17
|
+
"description": "AI image generation via the fal.ai synchronous REST API (base https://fal.run/<model-id>) using API-key auth. Stores the fal.ai key in the vault, registers one HTTP API tool that authenticates with `Authorization: Key <fal_key>`, and seeds one write skill (fal_generate_image) that calls the model, then proposes the result as a bookmark entity carrying the image URL (proposal-gated). GOTCHA: fal.run blocks the request until generation finishes (fast models like flux/schnell: ~2-6s; heavier models can take much longer) — this is the SYNCHRONOUS endpoint, not the async queue.fal.run submit/poll flow (which this template does NOT implement; a queue+poll variant would need a code skill polling `status_url`, since ProviderVerbSpec has no poll primitive). The `model` arg is the fal model id path (e.g. 'fal-ai/flux/schnell', 'fal-ai/flux/dev', 'fal-ai/recraft-v3') appended directly to the base URL — different fal models can take different request/response shapes; this skill sends the common contract ({ prompt, num_images, image_size } plus, when given, image_urls / aspect_ratio / seed in; { images: [{ url, width, height }] } out), which covers the flux family, most fal text-to-image models and the image_urls edit/reference models (e.g. fal-ai/nano-banana/edit), but is not universal. Only those declared inputs are forwarded, each validated and capped. GOTCHA: there is no first-class 'generated image / document' entity in Synap reachable from a capability skill (propose.entity has no document-creation counterpart, and the 'file'/'capture' profiles are hideFromCreate — reserved for system-driven creation via the document-upload pipeline) — this template proposes a 'bookmark' entity (Saved URL) holding the image URL as the closest legitimate fit. PRECONDITION: a fal.ai account with an API key (https://fal.ai/dashboard/keys). Pass it as `falApiKey`.",
|
|
18
|
+
"params": [
|
|
19
|
+
{
|
|
20
|
+
"name": "falApiKey",
|
|
21
|
+
"label": "fal.ai API key",
|
|
22
|
+
"type": "text",
|
|
23
|
+
"required": true,
|
|
24
|
+
"description": "The fal.ai API key (https://fal.ai/dashboard/keys). Stored server-encrypted in the vault; never persisted in the template."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"vault": [
|
|
28
|
+
{
|
|
29
|
+
"ref": "falApiKeySecret",
|
|
30
|
+
"name": "{{name}} fal.ai API key",
|
|
31
|
+
"value": "{{falApiKey}}",
|
|
32
|
+
"type": "api_key",
|
|
33
|
+
"description": "fal.ai API key for the fal_api connector."
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "fal_api",
|
|
39
|
+
"kind": "api",
|
|
40
|
+
"description": "Authenticated HTTP access to the fal.ai synchronous run API (https://fal.run/<model-id>). The applier injects the vaulted API key into the Authorization header as `Key <key>`; its credentialRef is remapped to the runtime vault://<id> at apply time.",
|
|
41
|
+
"credentialRef": "falApiKeySecret",
|
|
42
|
+
"executor": "is-agent",
|
|
43
|
+
"config": {
|
|
44
|
+
"baseUrl": "https://fal.run",
|
|
45
|
+
"auth": {
|
|
46
|
+
"in": "header",
|
|
47
|
+
"name": "Authorization",
|
|
48
|
+
"prefix": "Key "
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"skills": [
|
|
54
|
+
{
|
|
55
|
+
"name": "fal_generate_image",
|
|
56
|
+
"intent": "generate_media",
|
|
57
|
+
"kind": "code",
|
|
58
|
+
"scope": "pod",
|
|
59
|
+
"description": "Generates images from a text prompt via fal.ai (proposal-gated) and proposes each result as a bookmark entity holding its image URL. Args: { prompt: string, model?: string (fal model id, default 'fal-ai/flux/schnell'; edit/reference models such as 'fal-ai/nano-banana/edit' need imageUrls), imageSize?: string (default 'square_hd' when no aspectRatio, e.g. 'square'|'portrait_4_3'|'landscape_16_9'), numImages?: string (default '1', max 4), imageUrls?: string[] (1-8 public http(s) reference or source image URLs, sent as image_urls), images?: pod file entity ids (reference images from the pod; the pod signs them at run time and they join imageUrls in image_urls, 8 at most in all), aspectRatio?: string (e.g. '16:9', '1:1', 'auto'; sent as aspect_ratio, used by nano-banana), seed?: string (whole number, for reproducible results) }. Only these args reach fal. Returns { imageUrl (first), imageUrls (all), imageCount, seed, bookmarks: [{ url, status, proposalId?, error? }], bookmarkFailures }; a failed bookmark write is reported there and never fails the run.",
|
|
60
|
+
"requires": [
|
|
61
|
+
"fal_api"
|
|
62
|
+
],
|
|
63
|
+
"executionMode": "async",
|
|
64
|
+
"timeoutSeconds": 120,
|
|
65
|
+
"parameters": {
|
|
66
|
+
"prompt": "string",
|
|
67
|
+
"model": "string?",
|
|
68
|
+
"imageSize": "string?",
|
|
69
|
+
"numImages": "string?",
|
|
70
|
+
"imageUrls": "array?",
|
|
71
|
+
"images": "file[]:url?",
|
|
72
|
+
"aspectRatio": "string?",
|
|
73
|
+
"seed": "string?"
|
|
74
|
+
},
|
|
75
|
+
"code": "// Generate an image via fal.ai's synchronous run endpoint (side-effecting write:\n// calls a billed external API). Executor wraps this body as\n// (async (args, context) => { <code> }); use args directly and `return`.\n// callProvider(tool, method, path, body?, opts?) resolves the fal_api tool by\n// NAME, injects the vaulted key as Authorization: Key <key>, and uses the\n// tool's config.baseUrl = https://fal.run. The model id becomes the path.\n//\n// MODEL INPUTS: only the DECLARED args reach fal, each validated and capped:\n// imageUrls -> image_urls (edit / reference models such as\n// fal-ai/nano-banana/edit), aspectRatio -> aspect_ratio, seed -> seed. Nothing\n// else is forwarded, so a caller cannot smuggle a key or an arbitrary field\n// into the billed request. A fal-style snake_case spelling of a declared arg\n// is REFUSED with the name to use, never silently dropped (a dropped\n// image_urls is what turned an edit call into a fal 422).\nconst prompt = String(args?.prompt ?? '').trim();\nif (!prompt) throw new Error('fal_generate_image: prompt is required');\nconst SNAKE = { image_urls: 'imageUrls', aspect_ratio: 'aspectRatio', image_size: 'imageSize', num_images: 'numImages' };\nfor (const k of Object.keys(SNAKE)) {\n if (args && args[k] !== undefined) throw new Error('fal_generate_image: pass ' + SNAKE[k] + ', not ' + k);\n}\nconst model = String(args?.model ?? 'fal-ai/flux/schnell').trim() || 'fal-ai/flux/schnell';\nif (!/^[a-z0-9][a-z0-9._-]*(\\/[a-z0-9][a-z0-9._-]*){1,4}$/i.test(model)) {\n throw new Error('fal_generate_image: model must be a fal model id such as fal-ai/flux/schnell');\n}\nconst numImagesRaw = Number(args?.numImages ?? 1);\nconst numImages = Number.isFinite(numImagesRaw) && numImagesRaw > 0 ? Math.min(Math.floor(numImagesRaw), 4) : 1;\n\nconst payload = { prompt, num_images: numImages };\nlet imageUrls = [];\nif (args?.imageUrls !== undefined) {\n if (!Array.isArray(args.imageUrls)) throw new Error('fal_generate_image: imageUrls must be an array of URLs');\n imageUrls = args.imageUrls.map((u) => String(u ?? '').trim()).filter((u) => u.length > 0);\n if (imageUrls.length === 0) throw new Error('fal_generate_image: imageUrls is empty');\n if (imageUrls.length > 8) throw new Error('fal_generate_image: imageUrls takes at most 8 URLs');\n for (const u of imageUrls) {\n // Public http(s) only, no userinfo (user:pass@) and no data:/file:/vault: refs.\n if (u.length > 2048 || !/^https?:\\/\\/[^\\s\\/?#@]+([\\/?#][^\\s]*)?$/i.test(u)) {\n throw new Error('fal_generate_image: imageUrls must be public http(s) URLs without credentials (got ' + u.slice(0, 80) + ')');\n }\n }\n}\n// `images` are POD FILES (declared \"file[]:url?\", backend #469): the caller passes\n// file entity ids and the pod swaps each for a fresh signed URL at run time (6 h\n// for this async verb). Those URLs are sent to fal and never stored or echoed.\nlet podImages = [];\nif (args?.images !== undefined && args?.images !== null) {\n if (!Array.isArray(args.images)) throw new Error('fal_generate_image: images must be a list of pod file ids');\n podImages = args.images.map((u) => String(u ?? '').trim()).filter((u) => u.length > 0);\n for (const u of podImages) {\n if (u.length > 4096 || !/^https:\\/\\/[^\\s\\/?#@]+([\\/?#][^\\s]*)?$/i.test(u)) {\n throw new Error('fal_generate_image: images did not arrive as signed pod file URLs; pass pod file entity ids');\n }\n }\n}\nconst references = imageUrls.concat(podImages);\nif (references.length > 8) throw new Error('fal_generate_image: imageUrls and images take at most 8 images together');\nif (references.length > 0) payload.image_urls = references;\nconst aspectRatio = args?.aspectRatio === undefined ? '' : String(args.aspectRatio).trim();\nif (aspectRatio) {\n if (!/^(auto|\\d{1,2}:\\d{1,2})$/.test(aspectRatio)) throw new Error('fal_generate_image: aspectRatio must be like 16:9, 1:1 or auto');\n payload.aspect_ratio = aspectRatio;\n}\n// image_size: sent when asked for, or as the default when no aspectRatio\n// is given (flux-family models size by image_size; nano-banana ignores it\n// and sizes by aspect_ratio).\nconst imageSize = args?.imageSize === undefined ? '' : String(args.imageSize).trim();\nif (imageSize || !aspectRatio) payload.image_size = imageSize || 'square_hd';\nif (args?.seed !== undefined && String(args.seed).trim() !== '') {\n const seed = Number(args.seed);\n if (!Number.isSafeInteger(seed) || seed < 0 || seed > 4294967295) throw new Error('fal_generate_image: seed must be a whole number from 0 to 4294967295');\n payload.seed = seed;\n}\n\nconst res = await callProvider('fal_api', 'POST', '/' + model, payload);\n// The capability-execution gate may return a PROPOSAL envelope ({ proposed,\n// proposalId }) instead of executing — that proposal IS the record; return early\n// WITHOUT also filing a bookmark (no double-proposal).\nif (res?.proposed === true) {\n return { success: false, proposed: true, proposalId: res?.proposalId ?? null, prompt, model };\n}\nconst ok = typeof res?.status === 'number' && res.status >= 200 && res.status < 300;\nconst body = res?.body ?? res;\nconst images = (Array.isArray(body?.images) ? body.images : []).filter((im) => typeof im?.url === 'string' && im.url);\nif (!ok || images.length === 0) {\n throw new Error('fal_generate_image: generation failed or returned no image (status ' + (res?.status ?? 'unknown') + ')');\n}\nconst urls = images.map((im) => im.url);\nconst outSeed = typeof body?.seed === 'number' ? body.seed : (payload.seed ?? null);\n\n// One bookmark per image: each variant is kept or rejected on its own.\n// TITLE = prompt + the image's own fal file id. A bookmark's identity is its\n// url, but the duplicate door's name leg compares titles: a title made of the\n// truncated prompt alone was the SAME for every variant and every re-run, so\n// each image after the first was filed as POSSIBLE_DUPLICATE, sat pending,\n// and ate the agent's pending-proposal budget until generation itself failed.\n// A failed bookmark write never fails the run: the images are generated and\n// billed, so their URLs are returned and the failure is reported beside them.\nconst shortPrompt = prompt.length > 60 ? prompt.slice(0, 57) + '...' : prompt;\nconst bookmarks = [];\nfor (let i = 0; i < images.length; i++) {\n const im = images[i];\n const file = String(im.url.split('?')[0].split('/').pop() || '').replace(/\\.[a-z0-9]+$/i, '');\n const tag = file ? file.slice(0, 32) : String(i + 1);\n try {\n const r = await propose.entity({\n profileSlug: 'bookmark',\n title: 'AI image: ' + shortPrompt + ' #' + tag,\n properties: { url: im.url, prompt, model, provenance: 'connector', provider: 'fal.ai', width: im.width ?? null, height: im.height ?? null, imageCount: images.length, imageIndex: i + 1, seed: outSeed, referenceUrls: imageUrls.length ? imageUrls : null, referencePodFiles: podImages.length || null },\n reasoning: 'Recorded the fal.ai-generated image so it is findable in the pod.'\n });\n bookmarks.push({ url: im.url, status: r?.status ?? 'unknown', proposalId: r?.proposalId ?? null, entityId: r?.proposedEntityId ?? null });\n } catch (err) {\n bookmarks.push({ url: im.url, status: 'failed', error: String(err?.message ?? err).slice(0, 300) });\n }\n}\nconst bookmarkFailures = bookmarks.filter((b) => b.status === 'failed').length;\nreturn { success: true, proposed: false, imageUrl: urls[0], imageUrls: urls, model, prompt, imageCount: urls.length, seed: outSeed, bookmarks, bookmarkFailures };"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"provides": [
|
|
79
|
+
"generate_media"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"dependencies": []
|
|
83
|
+
}
|