@nebutra/play-marketplace 0.1.1
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/.nebutra/debug/content-store.jsonl +4 -0
- package/.nebutra/debug/event-log.jsonl +2 -0
- package/.nebutra/debug/play-marketplace.jsonl +3 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +16 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +676 -0
- package/README.md +28 -0
- package/dist/chunk-2SUUBIJC.js +189 -0
- package/dist/chunk-2SUUBIJC.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +36 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/examples/doctor.ts +11 -0
- package/examples/paid-gate.ts +31 -0
- package/examples/quickstart.ts +33 -0
- package/package.json +62 -0
- package/plays/publish_play/SKILL.md +24 -0
- package/src/cli.ts +28 -0
- package/src/index.test.ts +84 -0
- package/src/index.ts +281 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @nebutra/play-marketplace
|
|
2
|
+
|
|
3
|
+
Status: **WIP**
|
|
4
|
+
|
|
5
|
+
Controlled Play registry surface for validating, publishing, searching, and
|
|
6
|
+
installing SKILL.md Plays.
|
|
7
|
+
|
|
8
|
+
## Boundary
|
|
9
|
+
|
|
10
|
+
SKILL.md parsing belongs to `@nebutra/play-loader`. This package consumes that
|
|
11
|
+
parser and writes local registry/install artifacts. It does not own billing
|
|
12
|
+
deduction, remote registry transport, or verified review operations.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm play-marketplace:doctor
|
|
18
|
+
pnpm play-marketplace:quickstart
|
|
19
|
+
pnpm play-marketplace:debug
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Current Closure
|
|
23
|
+
|
|
24
|
+
- Play validation through play-loader.
|
|
25
|
+
- Controlled paid publishing gate.
|
|
26
|
+
- Local signed registry artifact.
|
|
27
|
+
- Search and install handoff.
|
|
28
|
+
- Quality metrics placeholders kept honest.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
|
+
import { ContentStore } from "@nebutra/content-store";
|
|
6
|
+
import { CapabilityError as CapabilityError2 } from "@nebutra/errors";
|
|
7
|
+
import { EventLog } from "@nebutra/event-log";
|
|
8
|
+
|
|
9
|
+
// ../generation-context/src/index.ts
|
|
10
|
+
import { CapabilityError } from "@nebutra/errors";
|
|
11
|
+
function assetId(prefix, seed) {
|
|
12
|
+
return `${prefix}_${seed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40)}_${Date.now().toString(36)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/index.ts
|
|
16
|
+
import { parsePlayMarkdown } from "@nebutra/play-loader";
|
|
17
|
+
function requireTenant(value) {
|
|
18
|
+
if (!value?.trim()) {
|
|
19
|
+
throw new CapabilityError2("play-marketplace", "Play Marketplace requires tenant context", {
|
|
20
|
+
suggestion: "Pass tenantId so registry and install artifacts stay tenant-scoped.",
|
|
21
|
+
statusCode: 400
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function isPaid(pricing) {
|
|
27
|
+
return pricing.model !== "free";
|
|
28
|
+
}
|
|
29
|
+
function validatePlayPackage(request) {
|
|
30
|
+
const play = parsePlayMarkdown(request.skillMarkdown);
|
|
31
|
+
return {
|
|
32
|
+
ok: true,
|
|
33
|
+
playName: play.meta.name,
|
|
34
|
+
version: play.meta.version,
|
|
35
|
+
requiredSkills: play.requiredSkills,
|
|
36
|
+
subAgents: play.subAgents.map((agent) => agent.role)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var PlayMarketplace = class _PlayMarketplace {
|
|
40
|
+
#tenantId;
|
|
41
|
+
#debugRoot;
|
|
42
|
+
#contentStore;
|
|
43
|
+
#eventLog;
|
|
44
|
+
#plays = /* @__PURE__ */ new Map();
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.#tenantId = requireTenant(options.tenantId);
|
|
47
|
+
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
48
|
+
this.#contentStore = options.contentStore;
|
|
49
|
+
this.#eventLog = options.eventLog;
|
|
50
|
+
}
|
|
51
|
+
static async open(root = ".nebutra/play-marketplace", options = {}) {
|
|
52
|
+
const tenantId = options.tenantId ?? "local";
|
|
53
|
+
await mkdir(root, { recursive: true });
|
|
54
|
+
const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
|
|
55
|
+
const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
|
|
56
|
+
return new _PlayMarketplace({ ...options, tenantId, contentStore, eventLog });
|
|
57
|
+
}
|
|
58
|
+
async publish(request) {
|
|
59
|
+
if (isPaid(request.pricing) && !request.invitedAuthor) {
|
|
60
|
+
throw new CapabilityError2(
|
|
61
|
+
"play-marketplace",
|
|
62
|
+
"Paid Play publishing is limited during controlled launch",
|
|
63
|
+
{
|
|
64
|
+
suggestion: "Use an invited author account or publish a free Play until paid publishing opens.",
|
|
65
|
+
statusCode: 403
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const validation = validatePlayPackage({ skillMarkdown: request.skillMarkdown });
|
|
70
|
+
const playId = validation.playName;
|
|
71
|
+
const registryPath = `marketplace/plays/${playId}@${validation.version}.json`;
|
|
72
|
+
const record = {
|
|
73
|
+
playId,
|
|
74
|
+
version: validation.version,
|
|
75
|
+
visibility: request.visibility,
|
|
76
|
+
pricing: request.pricing,
|
|
77
|
+
quality: {
|
|
78
|
+
verified: false,
|
|
79
|
+
successRate: 0,
|
|
80
|
+
avgCostUsd: 0,
|
|
81
|
+
avgDurationMinutes: 0
|
|
82
|
+
},
|
|
83
|
+
requiredSkills: validation.requiredSkills,
|
|
84
|
+
subAgents: validation.subAgents,
|
|
85
|
+
signature: assetId("play_sig", `${playId}_${validation.version}`)
|
|
86
|
+
};
|
|
87
|
+
const snapshot = `${JSON.stringify(record, null, 2)}
|
|
88
|
+
`;
|
|
89
|
+
await this.#contentStore.write(registryPath, snapshot);
|
|
90
|
+
const eventId = await this.#eventLog.commit({
|
|
91
|
+
traceId: assetId("play_marketplace_publish", `${playId}_${validation.version}`),
|
|
92
|
+
kind: "content_write",
|
|
93
|
+
affected: [registryPath],
|
|
94
|
+
parent: null,
|
|
95
|
+
snapshot: { [registryPath]: snapshot }
|
|
96
|
+
});
|
|
97
|
+
const published = {
|
|
98
|
+
playId,
|
|
99
|
+
version: validation.version,
|
|
100
|
+
registryPath,
|
|
101
|
+
visibility: request.visibility,
|
|
102
|
+
pricing: request.pricing,
|
|
103
|
+
quality: record.quality,
|
|
104
|
+
eventId,
|
|
105
|
+
skillMarkdown: request.skillMarkdown
|
|
106
|
+
};
|
|
107
|
+
this.#plays.set(`${playId}@${validation.version}`, published);
|
|
108
|
+
await this.#debug({
|
|
109
|
+
type: "publish",
|
|
110
|
+
tenantId: this.#tenantId,
|
|
111
|
+
playId,
|
|
112
|
+
version: validation.version
|
|
113
|
+
});
|
|
114
|
+
return published;
|
|
115
|
+
}
|
|
116
|
+
async search(request) {
|
|
117
|
+
const probe = request.query.toLowerCase();
|
|
118
|
+
const items = [...this.#plays.values()].filter(
|
|
119
|
+
(play) => play.playId.toLowerCase().includes(probe) || play.skillMarkdown.toLowerCase().includes(probe)
|
|
120
|
+
).sort(
|
|
121
|
+
(left, right) => request.sort === "success_rate" ? right.quality.successRate - left.quality.successRate || left.playId.localeCompare(right.playId) : right.eventId.localeCompare(left.eventId)
|
|
122
|
+
);
|
|
123
|
+
await this.#debug({
|
|
124
|
+
type: "search",
|
|
125
|
+
tenantId: this.#tenantId,
|
|
126
|
+
query: request.query,
|
|
127
|
+
hits: items.length
|
|
128
|
+
});
|
|
129
|
+
return { items };
|
|
130
|
+
}
|
|
131
|
+
async install(playId, version) {
|
|
132
|
+
const key = `${playId}@${version}`;
|
|
133
|
+
const play = this.#plays.get(key);
|
|
134
|
+
if (!play) {
|
|
135
|
+
throw new CapabilityError2("play-marketplace", "Play registry record not found", {
|
|
136
|
+
suggestion: "Search the marketplace and install an available version.",
|
|
137
|
+
metadata: { playId, version },
|
|
138
|
+
statusCode: 404
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const installPath = `marketplace/installed/${playId}@${version}.json`;
|
|
142
|
+
const install = {
|
|
143
|
+
playId,
|
|
144
|
+
version,
|
|
145
|
+
status: "installed",
|
|
146
|
+
registeredSkill: playId,
|
|
147
|
+
installPath
|
|
148
|
+
};
|
|
149
|
+
const snapshot = `${JSON.stringify(install, null, 2)}
|
|
150
|
+
`;
|
|
151
|
+
await this.#contentStore.write(installPath, snapshot);
|
|
152
|
+
await this.#eventLog.commit({
|
|
153
|
+
traceId: assetId("play_marketplace_install", `${playId}_${version}`),
|
|
154
|
+
kind: "content_write",
|
|
155
|
+
affected: [installPath],
|
|
156
|
+
parent: play.eventId,
|
|
157
|
+
snapshot: { [installPath]: snapshot }
|
|
158
|
+
});
|
|
159
|
+
await this.#debug({ type: "install", tenantId: this.#tenantId, playId, version });
|
|
160
|
+
return install;
|
|
161
|
+
}
|
|
162
|
+
async doctor() {
|
|
163
|
+
const report = {
|
|
164
|
+
capability: "play-marketplace",
|
|
165
|
+
ok: true,
|
|
166
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
features: ["validate-skill", "publish", "search", "install", "paid-launch-gate"],
|
|
168
|
+
suggestion: "Local registry artifacts are active; remote registry, billing, and verified review queues remain handoffs."
|
|
169
|
+
};
|
|
170
|
+
await this.#debug({ type: "doctor", tenantId: this.#tenantId, plays: this.#plays.size });
|
|
171
|
+
return report;
|
|
172
|
+
}
|
|
173
|
+
async close() {
|
|
174
|
+
await this.#contentStore.close();
|
|
175
|
+
}
|
|
176
|
+
async #debug(entry) {
|
|
177
|
+
await appendCapabilityDebug("play-marketplace", entry, { root: this.#debugRoot });
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
async function readPlayMarketplaceDebug(root = process.cwd(), limit = 20) {
|
|
181
|
+
return readCapabilityDebug("play-marketplace", { root, limit });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
validatePlayPackage,
|
|
186
|
+
PlayMarketplace,
|
|
187
|
+
readPlayMarketplaceDebug
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=chunk-2SUUBIJC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../generation-context/src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\nimport { parsePlayMarkdown } from \"@nebutra/play-loader\";\n\nexport type Visibility = \"private\" | \"public\";\nexport type Pricing =\n | { readonly model: \"free\" }\n | { readonly model: \"one_time\"; readonly amountUsd: number }\n | {\n readonly model: \"subscription\";\n readonly amountUsd: number;\n readonly interval: \"month\" | \"year\";\n }\n | { readonly model: \"per_run\"; readonly amountUsd: number };\n\nexport interface ValidatePlayPackageRequest {\n readonly skillMarkdown: string;\n}\n\nexport interface PlayValidationReport {\n readonly ok: boolean;\n readonly playName: string;\n readonly version: string;\n readonly requiredSkills: readonly string[];\n readonly subAgents: readonly string[];\n}\n\nexport interface PublishPlayRequest {\n readonly skillMarkdown: string;\n readonly visibility: Visibility;\n readonly pricing: Pricing;\n readonly invitedAuthor: boolean;\n}\n\nexport interface PublishedPlay {\n readonly playId: string;\n readonly version: string;\n readonly registryPath: string;\n readonly visibility: Visibility;\n readonly pricing: Pricing;\n readonly quality: {\n readonly verified: boolean;\n readonly successRate: number;\n readonly avgCostUsd: number;\n readonly avgDurationMinutes: number;\n };\n readonly eventId: string;\n}\n\nexport interface MarketplaceSearchRequest {\n readonly query: string;\n readonly sort: \"success_rate\" | \"new\";\n}\n\nexport interface MarketplaceSearchResult {\n readonly items: readonly PublishedPlay[];\n}\n\nexport interface InstalledPlay {\n readonly playId: string;\n readonly version: string;\n readonly status: \"installed\";\n readonly registeredSkill: string;\n readonly installPath: string;\n}\n\nexport interface PlayMarketplaceDoctorReport {\n readonly capability: \"play-marketplace\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly features: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface PlayMarketplaceOptions {\n readonly tenantId?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction requireTenant(value: string | undefined): string {\n if (!value?.trim()) {\n throw new CapabilityError(\"play-marketplace\", \"Play Marketplace requires tenant context\", {\n suggestion: \"Pass tenantId so registry and install artifacts stay tenant-scoped.\",\n statusCode: 400,\n });\n }\n return value;\n}\n\nfunction isPaid(pricing: Pricing): boolean {\n return pricing.model !== \"free\";\n}\n\nexport function validatePlayPackage(request: ValidatePlayPackageRequest): PlayValidationReport {\n const play = parsePlayMarkdown(request.skillMarkdown);\n return {\n ok: true,\n playName: play.meta.name,\n version: play.meta.version,\n requiredSkills: play.requiredSkills,\n subAgents: play.subAgents.map((agent) => agent.role),\n };\n}\n\nexport class PlayMarketplace {\n readonly #tenantId: string;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n readonly #plays = new Map<string, PublishedPlay & { readonly skillMarkdown: string }>();\n\n private constructor(\n options: Required<Pick<PlayMarketplaceOptions, \"tenantId\" | \"contentStore\" | \"eventLog\">> &\n Pick<PlayMarketplaceOptions, \"debugRoot\">,\n ) {\n this.#tenantId = requireTenant(options.tenantId);\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/play-marketplace\",\n options: Omit<PlayMarketplaceOptions, \"contentStore\" | \"eventLog\"> = {},\n ): Promise<PlayMarketplace> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new PlayMarketplace({ ...options, tenantId, contentStore, eventLog });\n }\n\n async publish(request: PublishPlayRequest): Promise<PublishedPlay> {\n if (isPaid(request.pricing) && !request.invitedAuthor) {\n throw new CapabilityError(\n \"play-marketplace\",\n \"Paid Play publishing is limited during controlled launch\",\n {\n suggestion:\n \"Use an invited author account or publish a free Play until paid publishing opens.\",\n statusCode: 403,\n },\n );\n }\n\n const validation = validatePlayPackage({ skillMarkdown: request.skillMarkdown });\n const playId = validation.playName;\n const registryPath = `marketplace/plays/${playId}@${validation.version}.json`;\n const record = {\n playId,\n version: validation.version,\n visibility: request.visibility,\n pricing: request.pricing,\n quality: {\n verified: false,\n successRate: 0,\n avgCostUsd: 0,\n avgDurationMinutes: 0,\n },\n requiredSkills: validation.requiredSkills,\n subAgents: validation.subAgents,\n signature: assetId(\"play_sig\", `${playId}_${validation.version}`),\n };\n const snapshot = `${JSON.stringify(record, null, 2)}\\n`;\n await this.#contentStore.write(registryPath, snapshot);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"play_marketplace_publish\", `${playId}_${validation.version}`),\n kind: \"content_write\",\n affected: [registryPath],\n parent: null,\n snapshot: { [registryPath]: snapshot },\n });\n const published: PublishedPlay & { readonly skillMarkdown: string } = {\n playId,\n version: validation.version,\n registryPath,\n visibility: request.visibility,\n pricing: request.pricing,\n quality: record.quality,\n eventId,\n skillMarkdown: request.skillMarkdown,\n };\n this.#plays.set(`${playId}@${validation.version}`, published);\n await this.#debug({\n type: \"publish\",\n tenantId: this.#tenantId,\n playId,\n version: validation.version,\n });\n return published;\n }\n\n async search(request: MarketplaceSearchRequest): Promise<MarketplaceSearchResult> {\n const probe = request.query.toLowerCase();\n const items = [...this.#plays.values()]\n .filter(\n (play) =>\n play.playId.toLowerCase().includes(probe) ||\n play.skillMarkdown.toLowerCase().includes(probe),\n )\n .sort((left, right) =>\n request.sort === \"success_rate\"\n ? right.quality.successRate - left.quality.successRate ||\n left.playId.localeCompare(right.playId)\n : right.eventId.localeCompare(left.eventId),\n );\n await this.#debug({\n type: \"search\",\n tenantId: this.#tenantId,\n query: request.query,\n hits: items.length,\n });\n return { items };\n }\n\n async install(playId: string, version: string): Promise<InstalledPlay> {\n const key = `${playId}@${version}`;\n const play = this.#plays.get(key);\n if (!play) {\n throw new CapabilityError(\"play-marketplace\", \"Play registry record not found\", {\n suggestion: \"Search the marketplace and install an available version.\",\n metadata: { playId, version },\n statusCode: 404,\n });\n }\n const installPath = `marketplace/installed/${playId}@${version}.json`;\n const install: InstalledPlay = {\n playId,\n version,\n status: \"installed\",\n registeredSkill: playId,\n installPath,\n };\n const snapshot = `${JSON.stringify(install, null, 2)}\\n`;\n await this.#contentStore.write(installPath, snapshot);\n await this.#eventLog.commit({\n traceId: assetId(\"play_marketplace_install\", `${playId}_${version}`),\n kind: \"content_write\",\n affected: [installPath],\n parent: play.eventId,\n snapshot: { [installPath]: snapshot },\n });\n await this.#debug({ type: \"install\", tenantId: this.#tenantId, playId, version });\n return install;\n }\n\n async doctor(): Promise<PlayMarketplaceDoctorReport> {\n const report: PlayMarketplaceDoctorReport = {\n capability: \"play-marketplace\",\n ok: true,\n checkedAt: new Date().toISOString(),\n features: [\"validate-skill\", \"publish\", \"search\", \"install\", \"paid-launch-gate\"],\n suggestion:\n \"Local registry artifacts are active; remote registry, billing, and verified review queues remain handoffs.\",\n };\n await this.#debug({ type: \"doctor\", tenantId: this.#tenantId, plays: this.#plays.size });\n return report;\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"play-marketplace\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readPlayMarketplaceDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"play-marketplace\", { root, limit });\n}\n","import { CapabilityError } from \"@nebutra/errors\";\n\nexport interface PathRef {\n readonly path: string;\n readonly tenantId?: string;\n readonly mediaType?: string;\n}\n\nexport interface BrandColor {\n readonly name: string;\n readonly hex: string;\n readonly role?: \"primary\" | \"secondary\" | \"accent\" | \"neutral\" | \"background\";\n}\n\nexport interface TypographySpec {\n readonly heading: string;\n readonly body: string;\n readonly accent?: string;\n}\n\nexport interface VisualStyle {\n readonly name: string;\n readonly keywords: readonly string[];\n readonly avoid?: readonly string[];\n}\n\nexport interface BrandContext {\n readonly tenantId: string;\n readonly brandId: string;\n readonly name: string;\n readonly palette: readonly BrandColor[];\n readonly typography: TypographySpec;\n readonly visualStyle: VisualStyle;\n readonly referenceImages: readonly PathRef[];\n readonly forbidden: readonly string[];\n readonly toneKeywords: readonly string[];\n readonly sourcePath: string;\n}\n\nexport type LicenseStatus = \"commercial-ok\" | \"non-commercial\" | \"requires-attribution\" | \"unknown\";\n\nexport interface LicenseMetadata {\n readonly status: LicenseStatus;\n readonly source: string;\n readonly suggestion?: string;\n}\n\nexport interface GeneratedAsset {\n readonly id: string;\n readonly tenantId: string;\n readonly kind: \"image\" | \"video\" | \"audio\" | \"voice\" | \"mesh\";\n readonly path: string;\n readonly brandId: string;\n readonly provider: string;\n readonly model: string;\n readonly createdAt: string;\n readonly license: LicenseMetadata;\n readonly metadata?: Record<string, unknown>;\n}\n\nexport function requireBrandContext(\n brand: BrandContext | undefined,\n capability: string,\n): BrandContext {\n if (!brand) {\n throw new CapabilityError(capability, \"BrandContext is required for generation\", {\n suggestion:\n \"Load company/BRAND.md through content-store and pass the parsed BrandContext into this call.\",\n statusCode: 400,\n });\n }\n\n const missing: string[] = [];\n if (!brand.tenantId) missing.push(\"tenantId\");\n if (!brand.brandId) missing.push(\"brandId\");\n if (!brand.name) missing.push(\"name\");\n if (brand.palette.length === 0) missing.push(\"palette\");\n if (!brand.typography.heading || !brand.typography.body) missing.push(\"typography\");\n if (!brand.visualStyle.name || brand.visualStyle.keywords.length === 0) {\n missing.push(\"visualStyle\");\n }\n if (brand.toneKeywords.length === 0) missing.push(\"toneKeywords\");\n if (!brand.sourcePath) missing.push(\"sourcePath\");\n\n if (missing.length > 0) {\n throw new CapabilityError(capability, \"BrandContext is incomplete\", {\n suggestion: `Fill these BrandContext fields before generation: ${missing.join(\", \")}.`,\n metadata: { missing },\n statusCode: 400,\n });\n }\n\n return brand;\n}\n\nexport function summarizeBrandContext(brand: BrandContext): string {\n return [\n brand.name,\n `style=${brand.visualStyle.name}`,\n `tone=${brand.toneKeywords.join(\", \")}`,\n `palette=${brand.palette.map((color) => `${color.name}:${color.hex}`).join(\", \")}`,\n ].join(\"; \");\n}\n\nexport function createDemoBrandContext(overrides: Partial<BrandContext> = {}): BrandContext {\n return {\n tenantId: \"demo_tenant\",\n brandId: \"loop\",\n name: \"Loop\",\n palette: [\n { name: \"signal\", hex: \"#00A693\", role: \"primary\" },\n { name: \"ink\", hex: \"#151719\", role: \"neutral\" },\n { name: \"spark\", hex: \"#F3C14B\", role: \"accent\" },\n ],\n typography: { heading: \"Geist Sans\", body: \"Geist Sans Mono\" },\n visualStyle: {\n name: \"focused technical optimism\",\n keywords: [\"precise\", \"calm\", \"developer-native\"],\n avoid: [\"dark horror\", \"generic stock\"],\n },\n referenceImages: [],\n forbidden: [\"dark horror\", \"skeletal imagery\"],\n toneKeywords: [\"technical\", \"warm\", \"direct\"],\n sourcePath: \"company/BRAND.md\",\n ...overrides,\n };\n}\n\nexport function assetId(prefix: string, seed: string): string {\n return `${prefix}_${seed\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 40)}_${Date.now().toString(36)}`;\n}\n"],"mappings":"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","names":["CapabilityError","CapabilityError"]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlayMarketplace,
|
|
3
|
+
readPlayMarketplaceDebug
|
|
4
|
+
} from "./chunk-2SUUBIJC.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var command = process.argv[2] ?? "doctor";
|
|
8
|
+
var root = process.env.PLAY_MARKETPLACE_ROOT ?? ".nebutra/play-marketplace";
|
|
9
|
+
var tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
10
|
+
if (command === "doctor") {
|
|
11
|
+
const marketplace = await PlayMarketplace.open(root, { tenantId });
|
|
12
|
+
try {
|
|
13
|
+
process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}
|
|
14
|
+
`);
|
|
15
|
+
} finally {
|
|
16
|
+
await marketplace.close();
|
|
17
|
+
}
|
|
18
|
+
} else if (command === "quickstart") {
|
|
19
|
+
const marketplace = await PlayMarketplace.open(root, { tenantId });
|
|
20
|
+
try {
|
|
21
|
+
process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}
|
|
22
|
+
`);
|
|
23
|
+
} finally {
|
|
24
|
+
await marketplace.close();
|
|
25
|
+
}
|
|
26
|
+
} else if (command === "debug") {
|
|
27
|
+
process.stdout.write(
|
|
28
|
+
`${JSON.stringify({ capability: "play-marketplace", entries: await readPlayMarketplaceDebug() }, null, 2)}
|
|
29
|
+
`
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
process.stderr.write(`Unknown play-marketplace command: ${command}
|
|
33
|
+
`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { PlayMarketplace, readPlayMarketplaceDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst root = process.env.PLAY_MARKETPLACE_ROOT ?? \".nebutra/play-marketplace\";\nconst tenantId = process.env.NEBUTRA_TENANT_ID ?? \"local\";\n\nif (command === \"doctor\") {\n const marketplace = await PlayMarketplace.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}\\n`);\n } finally {\n await marketplace.close();\n }\n} else if (command === \"quickstart\") {\n const marketplace = await PlayMarketplace.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}\\n`);\n } finally {\n await marketplace.close();\n }\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"play-marketplace\", entries: await readPlayMarketplaceDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown play-marketplace command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,yBAAyB;AAClD,IAAM,WAAW,QAAQ,IAAI,qBAAqB;AAElD,IAAI,YAAY,UAAU;AACxB,QAAM,cAAc,MAAM,gBAAgB,KAAK,MAAM,EAAE,SAAS,CAAC;AACjE,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,YAAY,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EACjF,UAAE;AACA,UAAM,YAAY,MAAM;AAAA,EAC1B;AACF,WAAW,YAAY,cAAc;AACnC,QAAM,cAAc,MAAM,gBAAgB,KAAK,MAAM,EAAE,SAAS,CAAC;AACjE,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,YAAY,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EACjF,UAAE;AACA,UAAM,YAAY,MAAM;AAAA,EAC1B;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,oBAAoB,SAAS,MAAM,yBAAyB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3G;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,qCAAqC,OAAO;AAAA,CAAI;AACrE,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ContentStore } from '@nebutra/content-store';
|
|
2
|
+
import { EventLog } from '@nebutra/event-log';
|
|
3
|
+
|
|
4
|
+
type Visibility = "private" | "public";
|
|
5
|
+
type Pricing = {
|
|
6
|
+
readonly model: "free";
|
|
7
|
+
} | {
|
|
8
|
+
readonly model: "one_time";
|
|
9
|
+
readonly amountUsd: number;
|
|
10
|
+
} | {
|
|
11
|
+
readonly model: "subscription";
|
|
12
|
+
readonly amountUsd: number;
|
|
13
|
+
readonly interval: "month" | "year";
|
|
14
|
+
} | {
|
|
15
|
+
readonly model: "per_run";
|
|
16
|
+
readonly amountUsd: number;
|
|
17
|
+
};
|
|
18
|
+
interface ValidatePlayPackageRequest {
|
|
19
|
+
readonly skillMarkdown: string;
|
|
20
|
+
}
|
|
21
|
+
interface PlayValidationReport {
|
|
22
|
+
readonly ok: boolean;
|
|
23
|
+
readonly playName: string;
|
|
24
|
+
readonly version: string;
|
|
25
|
+
readonly requiredSkills: readonly string[];
|
|
26
|
+
readonly subAgents: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
interface PublishPlayRequest {
|
|
29
|
+
readonly skillMarkdown: string;
|
|
30
|
+
readonly visibility: Visibility;
|
|
31
|
+
readonly pricing: Pricing;
|
|
32
|
+
readonly invitedAuthor: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface PublishedPlay {
|
|
35
|
+
readonly playId: string;
|
|
36
|
+
readonly version: string;
|
|
37
|
+
readonly registryPath: string;
|
|
38
|
+
readonly visibility: Visibility;
|
|
39
|
+
readonly pricing: Pricing;
|
|
40
|
+
readonly quality: {
|
|
41
|
+
readonly verified: boolean;
|
|
42
|
+
readonly successRate: number;
|
|
43
|
+
readonly avgCostUsd: number;
|
|
44
|
+
readonly avgDurationMinutes: number;
|
|
45
|
+
};
|
|
46
|
+
readonly eventId: string;
|
|
47
|
+
}
|
|
48
|
+
interface MarketplaceSearchRequest {
|
|
49
|
+
readonly query: string;
|
|
50
|
+
readonly sort: "success_rate" | "new";
|
|
51
|
+
}
|
|
52
|
+
interface MarketplaceSearchResult {
|
|
53
|
+
readonly items: readonly PublishedPlay[];
|
|
54
|
+
}
|
|
55
|
+
interface InstalledPlay {
|
|
56
|
+
readonly playId: string;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
readonly status: "installed";
|
|
59
|
+
readonly registeredSkill: string;
|
|
60
|
+
readonly installPath: string;
|
|
61
|
+
}
|
|
62
|
+
interface PlayMarketplaceDoctorReport {
|
|
63
|
+
readonly capability: "play-marketplace";
|
|
64
|
+
readonly ok: boolean;
|
|
65
|
+
readonly checkedAt: string;
|
|
66
|
+
readonly features: readonly string[];
|
|
67
|
+
readonly suggestion?: string;
|
|
68
|
+
}
|
|
69
|
+
interface PlayMarketplaceOptions {
|
|
70
|
+
readonly tenantId?: string;
|
|
71
|
+
readonly debugRoot?: string;
|
|
72
|
+
readonly contentStore?: ContentStore;
|
|
73
|
+
readonly eventLog?: EventLog;
|
|
74
|
+
}
|
|
75
|
+
declare function validatePlayPackage(request: ValidatePlayPackageRequest): PlayValidationReport;
|
|
76
|
+
declare class PlayMarketplace {
|
|
77
|
+
#private;
|
|
78
|
+
private constructor();
|
|
79
|
+
static open(root?: string, options?: Omit<PlayMarketplaceOptions, "contentStore" | "eventLog">): Promise<PlayMarketplace>;
|
|
80
|
+
publish(request: PublishPlayRequest): Promise<PublishedPlay>;
|
|
81
|
+
search(request: MarketplaceSearchRequest): Promise<MarketplaceSearchResult>;
|
|
82
|
+
install(playId: string, version: string): Promise<InstalledPlay>;
|
|
83
|
+
doctor(): Promise<PlayMarketplaceDoctorReport>;
|
|
84
|
+
close(): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
declare function readPlayMarketplaceDebug(root?: string, limit?: number): Promise<unknown[]>;
|
|
87
|
+
|
|
88
|
+
export { type InstalledPlay, type MarketplaceSearchRequest, type MarketplaceSearchResult, PlayMarketplace, type PlayMarketplaceDoctorReport, type PlayMarketplaceOptions, type PlayValidationReport, type Pricing, type PublishPlayRequest, type PublishedPlay, type ValidatePlayPackageRequest, type Visibility, readPlayMarketplaceDebug, validatePlayPackage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PlayMarketplace } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const marketplace = await PlayMarketplace.open(".nebutra/play-marketplace-example", {
|
|
4
|
+
tenantId: "local",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}\n`);
|
|
9
|
+
} finally {
|
|
10
|
+
await marketplace.close();
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PlayMarketplace } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const skillMarkdown = `---
|
|
4
|
+
name: paid_demo
|
|
5
|
+
kind: play
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
description: Paid demo play
|
|
8
|
+
required_skills: []
|
|
9
|
+
sub_agents: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What this play does
|
|
13
|
+
|
|
14
|
+
Demonstrates the paid publish gate.`;
|
|
15
|
+
|
|
16
|
+
const marketplace = await PlayMarketplace.open(".nebutra/play-marketplace-paid-gate-example", {
|
|
17
|
+
tenantId: "local",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await marketplace.publish({
|
|
22
|
+
skillMarkdown,
|
|
23
|
+
visibility: "public",
|
|
24
|
+
pricing: { model: "subscription", amountUsd: 29, interval: "month" },
|
|
25
|
+
invitedAuthor: false,
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
process.stdout.write(`${JSON.stringify(error, null, 2)}\n`);
|
|
29
|
+
} finally {
|
|
30
|
+
await marketplace.close();
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PlayMarketplace } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const skillMarkdown = `---
|
|
4
|
+
name: cold_email_pro
|
|
5
|
+
kind: play
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
description: Cold email draft play
|
|
8
|
+
required_skills:
|
|
9
|
+
- content_store.write
|
|
10
|
+
sub_agents: []
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What this play does
|
|
14
|
+
|
|
15
|
+
Drafts a reviewed campaign.`;
|
|
16
|
+
|
|
17
|
+
const marketplace = await PlayMarketplace.open(".nebutra/play-marketplace-example", {
|
|
18
|
+
tenantId: "local",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const play = await marketplace.publish({
|
|
23
|
+
skillMarkdown,
|
|
24
|
+
visibility: "public",
|
|
25
|
+
pricing: { model: "free" },
|
|
26
|
+
invitedAuthor: true,
|
|
27
|
+
});
|
|
28
|
+
process.stdout.write(
|
|
29
|
+
`${JSON.stringify(await marketplace.install(play.playId, play.version), null, 2)}\n`,
|
|
30
|
+
);
|
|
31
|
+
} finally {
|
|
32
|
+
await marketplace.close();
|
|
33
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebutra/play-marketplace",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Local registry, publishing, install, metrics, and billing handoff surface for SKILL.md Plays",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"nebutra": {
|
|
9
|
+
"status": "wip",
|
|
10
|
+
"productionReady": false,
|
|
11
|
+
"surface": "ecosystem-product",
|
|
12
|
+
"requires": [
|
|
13
|
+
"@nebutra/play-loader for SKILL.md validation",
|
|
14
|
+
"@nebutra/content-store for registry artifacts",
|
|
15
|
+
"@nebutra/event-log for publish and install checkpoints"
|
|
16
|
+
],
|
|
17
|
+
"gaps": [
|
|
18
|
+
"Remote registry transport is represented as a local signed index",
|
|
19
|
+
"Paid billing is represented as a provider-neutral handoff manifest",
|
|
20
|
+
"Verified review queues are metadata-only until marketplace operations exist"
|
|
21
|
+
],
|
|
22
|
+
"featureId": "play-marketplace",
|
|
23
|
+
"category": "ai",
|
|
24
|
+
"summary": "SKILL.md Play marketplace surface with quality gates and real usage stats"
|
|
25
|
+
},
|
|
26
|
+
"main": "./src/index.ts",
|
|
27
|
+
"types": "./src/index.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/index.ts"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@nebutra/capability-kit": "0.2.0",
|
|
33
|
+
"@nebutra/content-store": "0.1.0",
|
|
34
|
+
"@nebutra/errors": "0.1.0",
|
|
35
|
+
"@nebutra/event-log": "0.1.0",
|
|
36
|
+
"@nebutra/play-loader": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.19.15",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"tsx": "^4.21.0",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^4.1.4"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/play-marketplace#readme",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
|
|
49
|
+
"directory": "packages/ai/play-marketplace"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish_play
|
|
3
|
+
kind: play
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Validate and publish a SKILL.md Play into the controlled marketplace registry
|
|
6
|
+
required_skills:
|
|
7
|
+
- play_loader.parse
|
|
8
|
+
- content_store.write
|
|
9
|
+
- event_log.commit
|
|
10
|
+
sub_agents:
|
|
11
|
+
- role: registry_reviewer
|
|
12
|
+
allowed_skills: [play_loader.parse, content_store.write]
|
|
13
|
+
depends_on_plays: []
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What this play does
|
|
17
|
+
|
|
18
|
+
Validates a Play declaration, writes a signed registry artifact, and prepares install metadata.
|
|
19
|
+
|
|
20
|
+
## Boundaries
|
|
21
|
+
|
|
22
|
+
- SKILL.md parsing belongs to play-loader.
|
|
23
|
+
- Paid publishing stays gated during controlled launch.
|
|
24
|
+
- Billing and remote registry transport are explicit handoffs.
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PlayMarketplace, readPlayMarketplaceDebug } from "./index";
|
|
2
|
+
|
|
3
|
+
const command = process.argv[2] ?? "doctor";
|
|
4
|
+
const root = process.env.PLAY_MARKETPLACE_ROOT ?? ".nebutra/play-marketplace";
|
|
5
|
+
const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
6
|
+
|
|
7
|
+
if (command === "doctor") {
|
|
8
|
+
const marketplace = await PlayMarketplace.open(root, { tenantId });
|
|
9
|
+
try {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}\n`);
|
|
11
|
+
} finally {
|
|
12
|
+
await marketplace.close();
|
|
13
|
+
}
|
|
14
|
+
} else if (command === "quickstart") {
|
|
15
|
+
const marketplace = await PlayMarketplace.open(root, { tenantId });
|
|
16
|
+
try {
|
|
17
|
+
process.stdout.write(`${JSON.stringify(await marketplace.doctor(), null, 2)}\n`);
|
|
18
|
+
} finally {
|
|
19
|
+
await marketplace.close();
|
|
20
|
+
}
|
|
21
|
+
} else if (command === "debug") {
|
|
22
|
+
process.stdout.write(
|
|
23
|
+
`${JSON.stringify({ capability: "play-marketplace", entries: await readPlayMarketplaceDebug() }, null, 2)}\n`,
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
process.stderr.write(`Unknown play-marketplace command: ${command}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|