# JmpKit Maker > JmpKit Maker is a browser app for finding, making, remixing, and publishing > JmpKit projects from prompts. ## Key Pages - [Home](https://make.jmpkit.com/): Start from the main prompt box. - [Search](https://make.jmpkit.com/search): Find public projects and apps. - [Craft](https://make.jmpkit.com/craft): Make a new project from a prompt. - [Developer](https://make.jmpkit.com/dev): View the current developer workspace. ## Related Links - [JmpKit](https://jmpkit.com/): Companion agent-operable cloud infra. TL;DR: agents use it to host apps, publish Ziphost bundles, and add backend primitives like queues, realtime WebSockets, and TURN. For Maker, treat `jmpkit.com/ziphost10/p/...` as live app URLs that can be opened, inspected, and submitted back to `make.jmpkit.com` through the inbox starter kit below. ## Agent Guide For discovery requests such as "Find me a calculator app on make.jmpkit.com", start with the Search page. Prefer structured project data and public project URLs when available. Treat the visible search UI as a human-facing surface and the underlying API as the future stable agent surface. ## Current Agent-Readable Notes - The V3 Maker app shell serves `/search`, `/craft`, `/dev`, and `/boosts`. - Project search is currently UI-oriented. - A proposed first agent API is documented internally as agent-operable search: `POST /api/agent/tasks` for task creation and `GET /api/agent/tasks/:taskId` for status/results. - The existing browser-scoped event stream is `GET /api/events?browserRef=...`. ## Suggested Search Intent Shape ```json { "kind": "search", "prompt": "Find me a calculator app on make.jmpkit.com", "scope": { "site": "make.jmpkit.com", "visibility": "public" } } ``` ## Mini Starter Kit: Public Project Search The current agent-readable search entry point is: ```text GET /api/all-projects ``` This returns a public project index used by the Maker search UI. It is not a server-side query endpoint yet: fetch the index, then filter/rank the returned `items` client-side. The endpoint currently includes projects that have completed makes, visible feed entries, and an HTTP public link. For a prompt such as "Find me a calculator app on make.jmpkit.com", use `/api/all-projects`, score each item against terms like `calculator`, `calculate`, `math`, `numbers`, and inspect fields such as `displayTitle`, `initialPrompt`, `metaDescription`, `topics`, `projectKind`, and `publicLink`. ### Curl Fetch the full public project index: ```bash curl -sS https://make.jmpkit.com/api/all-projects ``` Local development server: ```bash curl -sS http://127.0.0.1:8000/api/all-projects ``` Summarize titles and links with `jq`: ```bash curl -sS https://make.jmpkit.com/api/all-projects \ | jq '.items[] | {title: .displayTitle, url: (.publicLink // .jmpkitLink), topics, summary: .metaDescription}' ``` Find likely calculator projects: ```bash curl -sS https://make.jmpkit.com/api/all-projects \ | jq '.items[] | select(((.displayTitle // "") + " " + (.initialPrompt // "") + " " + (.metaDescription // "") + " " + ((.topics // []) | join(" "))) | test("calculator|calculate|math|number"; "i")) | {title: .displayTitle, url: (.publicLink // .jmpkitLink), summary: .metaDescription}' ``` ### Node.js Fetch Example ```js const endpoint = "https://make.jmpkit.com/api/all-projects"; const query = "calculator app"; const stopWords = new Set(["a", "an", "app", "find", "for", "me", "project", "the"]); const terms = query .toLowerCase() .split(/\s+/) .filter((term) => term && !stopWords.has(term)); const response = await fetch(endpoint, { headers: { accept: "application/json" }, }); if (!response.ok) { throw new Error(`Project index request failed: ${response.status}`); } const payload = await response.json(); const items = Array.isArray(payload.items) ? payload.items : []; const searchableText = (item) => [ item.displayTitle, item.htmlTitle, item.generatedTitle, item.initialPrompt, item.metaDescription, item.projectKind, item.sourceName, item.ownerAliasName, ...(Array.isArray(item.topics) ? item.topics : []), ].filter(Boolean).join(" ").toLowerCase(); const scored = items .map((item) => { const text = searchableText(item); const score = terms.reduce((total, term) => ( total + (text.includes(term) ? 1 : 0) ), 0); return { item, score }; }) .filter((entry) => entry.score > 0) .sort((a, b) => ( b.score - a.score || String(b.item.feedUpdatedAt || "").localeCompare(String(a.item.feedUpdatedAt || "")) )); const results = scored.slice(0, 5).map(({ item, score }) => ({ score, title: item.displayTitle, url: item.publicLink || item.jmpkitLink || item.publicPath, summary: item.metaDescription || item.initialPrompt || "", topics: item.topics || [], })); console.log(JSON.stringify({ query, resultCount: results.length, results }, null, 2)); ``` ### Result Shape Top-level response: ```json { "ok": true, "makerCount": 12, "itemCount": 38, "items": [ { "projectPublicRef": "jpl_example", "publicPath": "/p/jpl_example/", "publicLink": "https://jmpkit.com/ziphost10/p/example/", "jmpkitLink": "https://jmpkit.com/ziphost10/p/example/", "displayTitle": "Simple Calculator", "initialPrompt": "Make a calculator app", "metaDescription": "A basic calculator with keypad, arithmetic operators, and a result display.", "topics": ["utility", "math"], "projectKind": "app", "sourceName": "JmpKit feed", "ownerAliasName": "Maker", "feedPath": "/f/fd_example", "feedLink": "/f/fd_example", "createdAt": "2026-06-26T14:40:49.637Z", "updatedAt": "2026-06-26T15:28:39.374Z", "feedUpdatedAt": "2026-06-26T15:28:39.374Z", "feedListed": true, "completedMakeCount": 1, "collectionCount": 0, "screenshots": { "thumb": { "url": "/screenshots/jpl_example-thumb.png", "width": 640, "height": 640 } } } ] } ``` Useful fields: - `displayTitle`: best human title for the project. - `publicLink` or `jmpkitLink`: live app URL when available. - `publicPath`: Maker project detail path. - `initialPrompt`: original prompt or import description. - `metaDescription`: generated page/app summary. - `topics`: deterministic topic labels. - `projectKind`: usually `app`, but may vary as classification improves. - `feedListed`: `false` means hidden from public feed/search. - `screenshots`: optional preview images served by Maker. ### Source Excerpts Server route in `src/server.js`: ```js if (url.pathname === '/api/all-projects') { if (req.method !== 'GET') { sendJson(res, 405, { error: 'method not allowed' }); return; } sendJson(res, 200, orchestrator.getAllProjects()); return; } ``` Project index assembly in `src/lib/orchestrator.js`: ```js getAllProjects() { const makerRecords = [...browsersByRef.values()]; for (const record of makerRecords) { ensureFeedRef(record); } const items = makerRecords .flatMap((record) => feedItemsFor(record)) .filter((item) => /^https?:\/\//i.test(item.jmpkitLink || item.publicLink || '')) .sort((a, b) => String(b.feedUpdatedAt).localeCompare(String(a.feedUpdatedAt))); return { ok: true, makerCount: makerRecords.length, itemCount: items.length, items, }; } ``` Search UI fetch path in `public/index.html`: ```js const response = await fetch('/api/all-projects', { headers: { accept: 'application/json' }, }); const payload = await response.json(); if (!response.ok) throw new Error(payload?.error || `projects request failed: ${response.status}`); const publicItems = Array.isArray(payload.items) ? payload.items : []; ``` When a browser identity is present, the UI also fetches local owned builds and merges them with public results: ```js const ownedResponse = await fetch(`/api/builds?browserRef=${encodeURIComponent(currentBrowserRef)}`, { headers: { accept: 'application/json' }, }); ``` That means `/api/all-projects` is the best public discovery endpoint today, while `/api/builds?browserRef=...` is for the current browser's own workspace. ## Mini Starter Kit: Submit a Ziphost Project Through the Inbox The current lightweight submission entry point for adding an existing JmpKit Ziphost project to the Maker index is: ```text POST /api/inbox ``` Post a single JmpKit Ziphost 10 project URL as plain text. The server stores the raw request and returns an `inboxRef` receipt immediately. A background inbox processor then reads the item, validates the URL, imports it as a remote project, queues screenshot/meta indexing jobs, and makes it discoverable through `GET /api/all-projects`. Accepted body forms today: - `content-type: text/plain` with exactly one URL and no whitespace other than surrounding trim/newline. - `content-type: text/uri-list` with exactly one non-comment URL line. - URL must normalize to `https://jmpkit.com/ziphost10/p/{readCode}/`. This is not a JSON submission API yet. Do not send title, description, metadata, or arbitrary app URLs to this endpoint. The current inbox importer only accepts JmpKit Ziphost 10 project URLs. ### Curl Submit a Ziphost project URL: ```bash curl -sS https://make.jmpkit.com/api/inbox \ -H 'content-type: text/plain; charset=utf-8' \ --data-binary 'https://jmpkit.com/ziphost10/p/example123/' ``` Submit with `text/uri-list`: ```bash curl -sS https://make.jmpkit.com/api/inbox \ -H 'content-type: text/uri-list' \ --data-binary $'# one URL only\nhttps://jmpkit.com/ziphost10/p/example123/\n' ``` Local development server: ```bash curl -sS http://127.0.0.1:8000/api/inbox \ -H 'content-type: text/plain; charset=utf-8' \ --data-binary 'https://jmpkit.com/ziphost10/p/example123/' ``` Verify that the imported project reached the public index: ```bash curl -sS https://make.jmpkit.com/api/all-projects \ | jq '.items[] | select(.remoteUrl == "https://jmpkit.com/ziphost10/p/example123/" or .jmpkitLink == "https://jmpkit.com/ziphost10/p/example123/") | {title: .displayTitle, sourceKind, owner: .ownerAliasName, url: .jmpkitLink, projectPublicRef}' ``` ### Node.js Fetch Example ```js const makeOrigin = "https://make.jmpkit.com"; const ziphostUrl = "https://jmpkit.com/ziphost10/p/example123/"; const inboxResponse = await fetch(`${makeOrigin}/api/inbox`, { method: "POST", headers: { accept: "application/json", "content-type": "text/plain; charset=utf-8", }, body: `${ziphostUrl}\n`, }); const receipt = await inboxResponse.json(); if (!inboxResponse.ok) { throw new Error(receipt?.error || `Inbox request failed: ${inboxResponse.status}`); } console.log("Inbox receipt:", receipt.inboxRef); const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); let imported = null; for (let attempt = 0; attempt < 12; attempt += 1) { const indexResponse = await fetch(`${makeOrigin}/api/all-projects`, { headers: { accept: "application/json" }, }); const index = await indexResponse.json(); if (!indexResponse.ok) { throw new Error(index?.error || `Index request failed: ${indexResponse.status}`); } imported = (Array.isArray(index.items) ? index.items : []).find((item) => item.remoteUrl === ziphostUrl || item.jmpkitLink === ziphostUrl || item.publicLink === ziphostUrl); if (imported) break; await wait(2500); } if (!imported) { throw new Error(`Inbox item ${receipt.inboxRef} was accepted but is not indexed yet`); } console.log(JSON.stringify({ inboxRef: receipt.inboxRef, projectPublicRef: imported.projectPublicRef, title: imported.displayTitle, url: imported.jmpkitLink || imported.publicLink, sourceKind: imported.sourceKind, }, null, 2)); ``` ### Response Shapes Inbox receipt response: ```json { "ok": true, "inboxRef": "inb_example", "receivedAt": "2026-07-03T13:20:00.000Z", "sizeBytes": 44, "sha256": "..." } ``` Internal processor result shape, written under the server inbox store: ```json { "schemaVersion": 1, "processedAt": "2026-07-03T13:20:03.000Z", "ok": true, "status": "imported", "inboxRef": "inb_example", "url": "https://jmpkit.com/ziphost10/p/example123/", "ziphostReadCode": "example123", "projectPublicRef": "jpl_example", "projectRef": "prj_example", "screenshotQueued": true, "metaQueued": true } ``` Public index item after import: ```json { "projectPublicRef": "jpl_example", "publicPath": "/p/jpl_example/", "publicLink": "https://jmpkit.com/ziphost10/p/example123/", "jmpkitLink": "https://jmpkit.com/ziphost10/p/example123/", "displayTitle": "Remote JmpKit Project", "initialPrompt": "Remote JmpKit Ziphost project example123", "sourceKind": "remote", "remoteUrl": "https://jmpkit.com/ziphost10/p/example123/", "sourceName": "Remote", "ownerAliasName": "Remote", "feedListed": true, "completedMakeCount": 1 } ``` If the same Ziphost read code is submitted again, the processor returns `status: "already_imported"` internally and refreshes index jobs for the existing remote project. ### Source Excerpts Inbox route in `src/server.js`: ```js if (url.pathname === '/api/inbox') { if (req.method !== 'POST') { sendJson(res, 405, { error: 'method not allowed' }); return; } const body = await readRawBody(req, { maxBytes: httpInboxMaxBytes }); const stored = await httpInboxStore.receive({ body, method: req.method, requestUrl: req.url, pathname: url.pathname, headers: req.headers, rawHeaders: req.rawHeaders, }); httpInboxProcessorSupervisor?.trigger(); sendJson(res, 200, { ok: true, inboxRef: stored.inboxRef, receivedAt: stored.receivedAt, sizeBytes: stored.sizeBytes, sha256: stored.sha256, }); return; } ``` Inbox processor in `src/lib/http-inbox-processor.js`: ```js const contentType = textContentType(envelope.contentType || envelope.headers?.['content-type']); if (!isPlainTextInboxItem(contentType)) { throw new Error('inbox item is not plain text'); } const text = decodeUtf8(body); const candidateUrl = oneUrlFromText({ text, contentType }); const remote = normalizeZiphost10ProjectUrl(candidateUrl); const imported = orchestrator.importRemoteZiphostProject({ url: remote.href, inboxRef, }); ``` Remote project import in `src/lib/orchestrator.js`: ```js importRemoteZiphostProject({ url, inboxRef = null } = {}) { const remote = normalizeZiphost10ProjectUrl(url); const importedAt = new Date().toISOString(); const projectRef = newProjectRef(); const projectPublicRef = newProjectPublicRef(); commitEvent({ type: 'project_created', projectRef, projectPublicRef, publicLink: remote.href, jmpkitLink: remote.href, ziphostReadCode: remote.readCode, sourceKind: 'remote', remoteUrl: remote.href, ...(inboxRef ? { importedFromInboxRef: inboxRef } : {}), initialPrompt: `Remote JmpKit Ziphost project ${remote.readCode}`, generatedTitle: 'Remote JmpKit Project', feedListed: true, updatedAt: importedAt, }); } ```