Skip to content

Commit 511fbb9

Browse files
authored
Merge pull request #16 from JoAiHQ/dev
Dev
2 parents 2dbd078 + b9d21fa commit 511fbb9

File tree

96 files changed

+6763
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+6763
-102
lines changed

.github/workflows/sync-catalog.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,15 @@ jobs:
6060
env:
6161
WARP_GITHUB_SYNC_TOKEN: ${{ secrets.WARP_GITHUB_SYNC_TOKEN }}
6262
run: node --import tsx scripts/catalog/sync-to-api.ts --branch "${GITHUB_REF_NAME}" --commit "${GITHUB_SHA}" --repo "${GITHUB_REPOSITORY}"
63+
64+
- name: Dispatch plugin sync
65+
env:
66+
JOAI_PLUGIN_SYNC_TOKEN: ${{ secrets.JOAI_PLUGIN_SYNC_TOKEN }}
67+
if: ${{ env.JOAI_PLUGIN_SYNC_TOKEN != '' }}
68+
uses: peter-evans/repository-dispatch@v3
69+
with:
70+
token: ${{ env.JOAI_PLUGIN_SYNC_TOKEN }}
71+
repository: JoAiHQ/joai--plugins
72+
event-type: warps_catalog_updated
73+
client-payload: >-
74+
{"warps_ref":"${{ github.ref_name }}","warps_sha":"${{ github.sha }}"}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules
22
dist
33
.DS_Store
44
.env
5+
.firecrawl

agents.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,62 @@
3232

3333
- Never use formal "Sie/Ihre" in German translations. Always use informal "du/dein" (lowercase) or rephrase to avoid the pronoun entirely.
3434
- Lowercase "sie/ihre" referring to things (not users) is fine — e.g. "einer Transaktion anhand ihres Hashes" (its hash).
35+
36+
## When Creating or Updating Warps
37+
38+
Every warp must meet these requirements before it can be considered complete:
39+
40+
### 1. Warp JSON — Descriptions
41+
42+
- The `description` field must be 2-3 sentences, optimized for SEO and LLM search discoverability.
43+
- Include relevant keywords naturally — mention the brand/service name, what the action does, and who benefits.
44+
- If a description is only 1 short sentence, it is not ready — expand it.
45+
- All descriptions must have both `en` and `de` translations.
46+
- German translations must sound natural (informal "du" form, not robotic/literal). See the German Localization section.
47+
- Never expose the internal term "warp" in user-facing text. Users know these as "actions".
48+
49+
### 2. meta.ts — SEO Extras (Required)
50+
51+
Every brand that has warps must have a `meta.ts` file in its directory. When adding or updating warps, always update the corresponding `meta.ts`.
52+
53+
The file exports SEO extras per warp, keyed by warp filename (without `.json`) or subdirectory name:
54+
55+
```ts
56+
import type { WarpExtras } from '../types'
57+
58+
export const meta: Record<string, WarpExtras> = {
59+
'warp-name': {
60+
keywords: {
61+
en: ['relevant search term', 'another keyword'],
62+
de: ['relevanter Suchbegriff', 'weiteres Keyword'],
63+
},
64+
useCases: {
65+
en: ['Specific scenario 1', 'Specific scenario 2', 'Specific scenario 3'],
66+
de: ['Spezifisches Szenario 1', 'Spezifisches Szenario 2', 'Spezifisches Szenario 3'],
67+
},
68+
category: 'productivity',
69+
faq: {
70+
en: [
71+
{ question: 'A real question users would ask?', answer: 'Concise 1-2 sentence answer.' },
72+
],
73+
de: [
74+
{ question: 'Eine echte Frage, die Nutzer stellen würden?', answer: 'Knappe Antwort in 1-2 Sätzen.' },
75+
],
76+
},
77+
},
78+
}
79+
```
80+
81+
**Field requirements:**
82+
83+
- `keywords`: relevant search terms users would type. Include brand name, action name, and related terms. Both `en` and `de`.
84+
- `useCases`: 3-4 short, specific scenario strings describing who benefits or what can be done. Not generic platitudes — be concrete. Both `en` and `de`.
85+
- `category`: exactly one of: `'productivity'`, `'communication'`, `'developer'`, `'defi'`, `'staking'`, `'nft'`, `'analytics'`, `'commerce'`, `'social'`, `'security'`, `'infrastructure'`.
86+
- `faq`: 2-3 question/answer pairs per warp. Questions should be things users actually search for. Answers must be concise (1-2 sentences). Both `en` and `de`.
87+
88+
**Content rules:**
89+
90+
- Never reference internal identifiers, technical names, or the word "warp" in any user-facing text.
91+
- German translations must be natural and fluent — not word-for-word translations. Use informal "du" form.
92+
- Keywords should include terms in the user's language, not just English terms translated literally. German crypto/tech terms often stay in English (e.g. "staken", "swappen", "DEX").
93+
- FAQ answers should describe what the user can do, not how the system works internally.

scripts/catalog/build-catalog.test.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// @vitest-environment node
22
import { describe, it, expect } from 'vitest'
3+
import path from 'path'
4+
import { fileURLToPath } from 'url'
35
import { isDraftFile, isPrivateFile, getAliasFromFileName } from './build-catalog.js'
6+
import { buildDistributionCatalog, validateDistributionCatalog } from './distribution.js'
7+
8+
const __filename = fileURLToPath(import.meta.url)
9+
const __dirname = path.dirname(__filename)
10+
const REPO_ROOT = path.resolve(__dirname, '../../')
411

512
describe('isPrivateFile', () => {
613
it('returns true for #-prefixed filenames', () => {
@@ -79,3 +86,167 @@ describe('sync-to-api: listed filtering', () => {
7986
expect(filtered).toHaveLength(2)
8087
})
8188
})
89+
90+
describe('distribution catalog', () => {
91+
it('builds a public app catalog from listed brand warps', async () => {
92+
const catalog = await buildDistributionCatalog(
93+
REPO_ROOT,
94+
{
95+
schemaVersion: 1,
96+
source: 'github',
97+
repo: 'JoAiHQ/warps',
98+
branch: 'main',
99+
network: 'mainnet',
100+
commitSha: 'test',
101+
generatedAt: '2026-04-01T00:00:00.000Z',
102+
warps: [
103+
{
104+
key: 'multiversx:joai-agent-create',
105+
identifier: '@multiversx:joai-agent-create',
106+
alias: 'joai-agent-create',
107+
chain: 'multiversx',
108+
hash: 'warp-hash-1',
109+
checksum: 'warp-hash-1',
110+
name: 'JoAi: Create Agent',
111+
title: { en: 'Create Agent' },
112+
description: { en: 'Create a JoAi agent.' },
113+
preview: null,
114+
creator: 'github:JoAiHQ/warps',
115+
privileges: [],
116+
listed: true,
117+
primaryAddress: null,
118+
primaryFunc: null,
119+
brand: {
120+
hash: 'brand-hash-1',
121+
slug: 'joai',
122+
active: true,
123+
protocol: 'brand:1.0.0',
124+
name: 'JoAi',
125+
description: { en: 'JoAi brand' },
126+
logo: { default: 'https://example.com/logo.svg' },
127+
urls: { web: 'https://joai.ai' },
128+
colors: { primary: '#98FF98' },
129+
},
130+
warp: {
131+
actions: [{ type: 'collect' }],
132+
},
133+
extras: null,
134+
},
135+
{
136+
key: 'multiversx:joai-private-warp',
137+
identifier: '@multiversx:joai-private-warp',
138+
alias: 'joai-private-warp',
139+
chain: 'multiversx',
140+
hash: 'warp-hash-2',
141+
checksum: 'warp-hash-2',
142+
name: 'JoAi: Private Warp',
143+
title: { en: 'Private Warp' },
144+
description: { en: 'Private warp' },
145+
preview: null,
146+
creator: 'github:JoAiHQ/warps',
147+
privileges: [],
148+
listed: false,
149+
primaryAddress: null,
150+
primaryFunc: null,
151+
brand: {
152+
hash: 'brand-hash-1',
153+
slug: 'joai',
154+
active: true,
155+
protocol: 'brand:1.0.0',
156+
name: 'JoAi',
157+
description: { en: 'JoAi brand' },
158+
logo: { default: 'https://example.com/logo.svg' },
159+
urls: { web: 'https://joai.ai' },
160+
colors: { primary: '#98FF98' },
161+
},
162+
warp: {
163+
actions: [{ type: 'contract' }],
164+
},
165+
extras: null,
166+
},
167+
],
168+
},
169+
new Map(),
170+
)
171+
172+
expect(catalog.apps).toHaveLength(1)
173+
expect(catalog.apps[0]).toMatchObject({
174+
slug: 'joai',
175+
mcpUrl: 'https://cortex.joai.ai/mcp/apps/joai',
176+
providers: {
177+
claude: { enabled: true, status: 'ready' },
178+
codex: { enabled: true, status: 'ready' },
179+
openai: { enabled: true, status: 'runtime_ready' },
180+
},
181+
})
182+
expect(catalog.apps[0].actions).toEqual([
183+
{
184+
alias: 'joai-agent-create',
185+
identifier: '@multiversx:joai-agent-create',
186+
chain: 'multiversx',
187+
name: 'JoAi: Create Agent',
188+
title: { en: 'Create Agent' },
189+
description: { en: 'Create a JoAi agent.' },
190+
actionTypes: ['collect'],
191+
},
192+
])
193+
expect(validateDistributionCatalog(catalog)).toEqual([])
194+
})
195+
196+
it('requires screenshots only for submission-ready OpenAI apps', () => {
197+
const errors = validateDistributionCatalog({
198+
schemaVersion: 1,
199+
source: 'github',
200+
repo: 'JoAiHQ/warps',
201+
branch: 'main',
202+
network: 'mainnet',
203+
commitSha: 'test',
204+
generatedAt: '2026-04-01T00:00:00.000Z',
205+
apps: [
206+
{
207+
slug: 'joai',
208+
name: 'JoAi',
209+
description: { en: 'JoAi' },
210+
logo: { default: 'https://example.com/logo.svg' },
211+
urls: { web: 'https://joai.ai' },
212+
hash: 'brand-hash',
213+
mcpUrl: 'https://cortex.joai.ai/mcp/apps/joai',
214+
install: {
215+
summary: 'summary',
216+
examplePrompts: ['prompt'],
217+
usageNotes: [],
218+
authPrerequisites: [],
219+
},
220+
legal: {
221+
privacyUrl: 'https://legal.vleap.ai/policies/privacy.html',
222+
supportEmail: 'support@joai.ai',
223+
},
224+
review: {
225+
screenshots: [],
226+
reviewerNotes: ['note'],
227+
testPrompts: ['prompt'],
228+
},
229+
ui: {
230+
prefersBorder: true,
231+
csp: {
232+
connectDomains: [],
233+
resourceDomains: [],
234+
frameDomains: [],
235+
baseUriDomains: [],
236+
},
237+
permissions: {},
238+
domain: undefined,
239+
},
240+
providers: {
241+
claude: { provider: 'claude', enabled: true, status: 'ready', notes: [] },
242+
codex: { provider: 'codex', enabled: true, status: 'ready', notes: [] },
243+
openai: { provider: 'openai', enabled: true, status: 'submission_ready', notes: [] },
244+
},
245+
actions: [],
246+
},
247+
],
248+
})
249+
250+
expect(errors).toContain('joai: openai marked submission_ready without screenshots')
251+
})
252+
})

0 commit comments

Comments
 (0)