From 92a46d4e1c6a4ca15efbff3272f523e507f46bf6 Mon Sep 17 00:00:00 2001 From: mega123-art Date: Sat, 20 Jun 2026 15:44:29 +0530 Subject: [PATCH] feat(market): publish workflows from skill form --- .../core/src/skill-market/ingest/env.spec.ts | 113 ++++++++++++++++++ packages/core/src/skill-market/ingest/env.ts | 41 +++++++ 2 files changed, 154 insertions(+) create mode 100644 packages/core/src/skill-market/ingest/env.spec.ts diff --git a/packages/core/src/skill-market/ingest/env.spec.ts b/packages/core/src/skill-market/ingest/env.spec.ts new file mode 100644 index 0000000..c06a8e9 --- /dev/null +++ b/packages/core/src/skill-market/ingest/env.spec.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { marketplaceEnv } from "./env.js"; +import { publishSkill as corePublishSkill } from "../../nft/skill.js"; +import { publishWorkflow as corePublishWorkflow } from "../../nft/workflow.js"; + +vi.mock("../../nft/skill.js", () => ({ + publishSkill: vi.fn().mockResolvedValue("mockSkillMint"), + buySkill: vi.fn(), +})); + +vi.mock("../../nft/workflow.js", () => ({ + publishWorkflow: vi.fn().mockResolvedValue("mockWorkflowMint"), +})); + +vi.mock("../../core/chain.js", () => ({ + init: vi.fn(), + signerAddress: vi.fn().mockResolvedValue("mockAddress"), +})); + +vi.mock("../../core/rpc.js", () => ({ + resolveRpcUrl: vi.fn().mockResolvedValue("http://localhost:8899"), +})); + +describe("skill-market/ingest/env publish", () => { + const mockWallet = { address: "mockWalletAddress" } as any; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("routes workflow frontmatter to publishWorkflow", async () => { + const text = `--- +type: workflow +requiredSkills: [skillMint1, skillMint2] +--- +Some workflow body`; + + const env = await marketplaceEnv(mockWallet); + const result = await env.publishSkill({ + name: "My Workflow", + description: "A workflow", + text, + category: "testing", + hashtags: ["test", "workflow"], + priceSol: "0.25", + }); + + expect(result).toEqual({ ok: true, mint: "mockWorkflowMint" }); + expect(corePublishWorkflow).toHaveBeenCalledWith(expect.any(Object), mockWallet, { + name: "My Workflow", + description: "A workflow", + text, + requiredSkills: ["skillMint1", "skillMint2"], + category: "testing", + hashtags: ["test", "workflow"], + price: 250000000n, + }); + expect(corePublishSkill).not.toHaveBeenCalled(); + }); + + it("routes skill frontmatter to publishSkill", async () => { + const text = `--- +type: skill +--- +Some skill body`; + + const env = await marketplaceEnv(mockWallet); + const result = await env.publishSkill({ + name: "My Skill", + description: "A skill", + text, + category: "testing", + hashtags: ["test"], + priceSol: "0.1", + }); + + expect(result).toEqual({ ok: true, mint: "mockSkillMint" }); + expect(corePublishSkill).toHaveBeenCalledWith(expect.any(Object), mockWallet, { + name: "My Skill", + description: "A skill", + text, + category: "testing", + hashtags: ["test"], + price: 100000000n, + image: undefined, + }); + expect(corePublishWorkflow).not.toHaveBeenCalled(); + }); + + it("routes missing frontmatter to publishSkill", async () => { + const text = "Pure markdown without frontmatter"; + + const env = await marketplaceEnv(mockWallet); + const result = await env.publishSkill({ + name: "My Skill", + description: "A skill", + text, + priceSol: "1", + }); + + expect(result).toEqual({ ok: true, mint: "mockSkillMint" }); + expect(corePublishSkill).toHaveBeenCalledWith(expect.any(Object), mockWallet, { + name: "My Skill", + description: "A skill", + text, + category: undefined, + hashtags: undefined, + price: 1000000000n, + image: undefined, + }); + expect(corePublishWorkflow).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/skill-market/ingest/env.ts b/packages/core/src/skill-market/ingest/env.ts index adf2a46..e769a23 100644 --- a/packages/core/src/skill-market/ingest/env.ts +++ b/packages/core/src/skill-market/ingest/env.ts @@ -15,6 +15,7 @@ import type { Wallet } from "../../runtime/contract.js"; import { searchSkills } from "../../search/index.js"; import { dasSource, indexerSource, ownedSkillMints } from "../../core/skillSource.js"; import { buySkill, publishSkill as corePublishSkill } from "../../nft/skill.js"; +import { publishWorkflow as corePublishWorkflow } from "../../nft/workflow.js"; import { getSolBalance } from "../../notes/index.js"; import { readSkillText, readSkillMintMetadata } from "../../nft/token2022.js"; import { heldSkillCreators } from "../../notes/holdings.js"; @@ -87,6 +88,33 @@ export function solToLamports(sol: string): bigint | null { return BigInt(whole) * LAMPORTS_PER_SOL + BigInt(frac.padEnd(9, "0") || "0"); } +function publishFrontmatter(text: string): { type?: string; requiredSkills?: string[] } { + const lines = text.split("\n"); + if (lines[0]?.trim() !== "---") return {}; + let closeIdx = -1; + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === "---") { closeIdx = i; break; } + } + if (closeIdx === -1) return {}; + + const out: { type?: string; requiredSkills?: string[] } = {}; + for (const line of lines.slice(1, closeIdx)) { + const colon = line.indexOf(":"); + if (colon === -1) continue; + const key = line.slice(0, colon).trim(); + const raw = line.slice(colon + 1).trim(); + if (key === "type") out.type = raw.replace(/^['"]|['"]$/g, ""); + if (key === "requiredSkills" && raw.startsWith("[") && raw.endsWith("]")) { + out.requiredSkills = raw + .slice(1, -1) + .split(",") + .map((s) => s.trim().replace(/^['"]|['"]$/g, "")) + .filter(Boolean); + } + } + return out; +} + export async function marketplaceEnv(wallet: Wallet) { const conn = new Connection(await resolveRpcUrl(), "confirmed"); // Wire the chain layer's module-level connection. Writes (publishSkill -> codeIn, @@ -222,6 +250,19 @@ export async function marketplaceEnv(wallet: Wallet) { try { const lamports = solToLamports(input.priceSol); if (lamports === null) return { ok: false, error: "Enter a valid price in SOL (e.g. 0.1)" }; + const frontmatter = publishFrontmatter(input.text); + if (frontmatter.type === "workflow") { + const mint = await corePublishWorkflow(conn, wallet, { + name: input.name, + description: input.description, + text: input.text, + requiredSkills: frontmatter.requiredSkills ?? [], + category: input.category, + hashtags: input.hashtags, + price: lamports, + }); + return { ok: true, mint }; + } const mint = await corePublishSkill(conn, wallet, { name: input.name, description: input.description,