mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
feat(plugins): add specVersion and version fields to plugin and marketplace schemas
- Introduced `specVersion` and `version` fields to the plugin and marketplace schemas, ensuring better versioning and compatibility tracking. - Updated various components and functions to handle the new fields, including database migrations, plugin snapshots, and marketplace management. - Enhanced tests to validate the presence and correctness of the new fields in plugin manifests and marketplace entries. - Improved documentation to reflect the changes in schema requirements and provide guidance on the new versioning system. This update strengthens the plugin ecosystem by providing clear versioning, enhancing the reliability and maintainability of plugins and marketplaces.
This commit is contained in:
parent
fbcbd4a643
commit
0edbf38171
455 changed files with 736 additions and 83 deletions
|
|
@ -1218,7 +1218,7 @@ Common options:
|
|||
return;
|
||||
}
|
||||
for (const m of rows) {
|
||||
console.log(`${m.id} trust=${m.trust} url=${m.url}`);
|
||||
console.log(`${m.id} version=${m.version ?? 'unknown'} spec=${m.specVersion ?? 'unknown'} trust=${m.trust} url=${m.url}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -1249,7 +1249,9 @@ Common options:
|
|||
matches.push({
|
||||
marketplaceId: mp.id,
|
||||
marketplaceUrl: mp.url,
|
||||
marketplaceVersion: mp.version,
|
||||
name: p.name,
|
||||
version: p.version,
|
||||
source: p.source,
|
||||
description: p.description ?? '',
|
||||
tags: p.tags ?? [],
|
||||
|
|
@ -1265,7 +1267,7 @@ Common options:
|
|||
return;
|
||||
}
|
||||
for (const m of matches) {
|
||||
console.log(`${m.name}\t${m.source}\t${m.marketplaceId}\t${m.description}`);
|
||||
console.log(`${m.name}@${m.version}\t${m.source}\t${m.marketplaceId}@${m.marketplaceVersion}\t${m.description}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export function applyPlugin(input: ApplyInput): ApplyComputed {
|
|||
const snapshot: AppliedPluginSnapshot = {
|
||||
snapshotId: '',
|
||||
pluginId: input.plugin.id,
|
||||
pluginSpecVersion: manifest.specVersion,
|
||||
pluginVersion: input.plugin.version,
|
||||
manifestSourceDigest: digest,
|
||||
sourceMarketplaceId: input.plugin.sourceMarketplaceId,
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ async function readSkillBody(
|
|||
function buildPortableManifest(snapshot: AppliedPluginSnapshot): Record<string, unknown> {
|
||||
return {
|
||||
$schema: 'https://open-design.ai/schemas/plugin.v1.json',
|
||||
specVersion: snapshot.pluginSpecVersion ?? '1.0.0',
|
||||
name: snapshot.pluginId,
|
||||
title: snapshot.pluginTitle ?? snapshot.pluginId,
|
||||
version: snapshot.pluginVersion,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ import {
|
|||
parseMarketplace,
|
||||
type MarketplaceParseResult,
|
||||
} from '@open-design/plugin-runtime';
|
||||
import type { MarketplaceManifest } from '@open-design/contracts';
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
type MarketplaceManifest,
|
||||
} from '@open-design/contracts';
|
||||
|
||||
type SqliteDb = Database.Database;
|
||||
|
||||
|
|
@ -28,6 +31,8 @@ export type MarketplaceTrustTier = 'official' | 'trusted' | 'restricted';
|
|||
export interface MarketplaceRow {
|
||||
id: string;
|
||||
url: string;
|
||||
specVersion: string;
|
||||
version: string;
|
||||
trust: MarketplaceTrustTier;
|
||||
manifest: MarketplaceManifest;
|
||||
addedAt: number;
|
||||
|
|
@ -100,56 +105,77 @@ export async function addMarketplace(
|
|||
const now = Date.now();
|
||||
const trust = input.trust ?? 'restricted';
|
||||
db.prepare(
|
||||
`INSERT INTO plugin_marketplaces (id, url, trust, manifest_json, added_at, refreshed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
).run(id, input.url, trust, text, now, now);
|
||||
`INSERT INTO plugin_marketplaces (id, url, spec_version, version, trust, manifest_json, added_at, refreshed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(id, input.url, parsed.manifest.specVersion, parsed.manifest.version, trust, text, now, now);
|
||||
return {
|
||||
ok: true,
|
||||
row: { id, url: input.url, trust, manifest: parsed.manifest, addedAt: now, refreshedAt: now },
|
||||
row: {
|
||||
id,
|
||||
url: input.url,
|
||||
specVersion: parsed.manifest.specVersion,
|
||||
version: parsed.manifest.version,
|
||||
trust,
|
||||
manifest: parsed.manifest,
|
||||
addedAt: now,
|
||||
refreshedAt: now,
|
||||
},
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function listMarketplaces(db: SqliteDb): MarketplaceRow[] {
|
||||
const rows = db
|
||||
.prepare(`SELECT id, url, trust, manifest_json, added_at, refreshed_at FROM plugin_marketplaces ORDER BY added_at ASC`)
|
||||
.prepare(`SELECT id, url, spec_version, version, trust, manifest_json, added_at, refreshed_at FROM plugin_marketplaces ORDER BY added_at ASC`)
|
||||
.all() as Array<{
|
||||
id: string;
|
||||
url: string;
|
||||
spec_version: string;
|
||||
version: string;
|
||||
trust: MarketplaceTrustTier;
|
||||
manifest_json: string;
|
||||
added_at: number;
|
||||
refreshed_at: number;
|
||||
}>;
|
||||
return rows.map((r) => ({
|
||||
id: r.id,
|
||||
url: r.url,
|
||||
trust: r.trust,
|
||||
manifest: safeParseManifest(r.manifest_json),
|
||||
addedAt: r.added_at,
|
||||
refreshedAt: r.refreshed_at,
|
||||
}));
|
||||
return rows.map((r) => {
|
||||
const manifest = safeParseManifest(r.manifest_json);
|
||||
return {
|
||||
id: r.id,
|
||||
url: r.url,
|
||||
specVersion: r.spec_version || manifest.specVersion,
|
||||
version: r.version === '0.0.0' ? manifest.version : r.version,
|
||||
trust: r.trust,
|
||||
manifest,
|
||||
addedAt: r.added_at,
|
||||
refreshedAt: r.refreshed_at,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getMarketplace(db: SqliteDb, id: string): MarketplaceRow | null {
|
||||
const row = db
|
||||
.prepare(`SELECT id, url, trust, manifest_json, added_at, refreshed_at FROM plugin_marketplaces WHERE id = ?`)
|
||||
.prepare(`SELECT id, url, spec_version, version, trust, manifest_json, added_at, refreshed_at FROM plugin_marketplaces WHERE id = ?`)
|
||||
.get(id) as
|
||||
| undefined
|
||||
| {
|
||||
id: string;
|
||||
url: string;
|
||||
spec_version: string;
|
||||
version: string;
|
||||
trust: MarketplaceTrustTier;
|
||||
manifest_json: string;
|
||||
added_at: number;
|
||||
refreshed_at: number;
|
||||
};
|
||||
if (!row) return null;
|
||||
const manifest = safeParseManifest(row.manifest_json);
|
||||
return {
|
||||
id: row.id,
|
||||
url: row.url,
|
||||
specVersion: row.spec_version || manifest.specVersion,
|
||||
version: row.version === '0.0.0' ? manifest.version : row.version,
|
||||
trust: row.trust,
|
||||
manifest: safeParseManifest(row.manifest_json),
|
||||
manifest,
|
||||
addedAt: row.added_at,
|
||||
refreshedAt: row.refreshed_at,
|
||||
};
|
||||
|
|
@ -198,11 +224,17 @@ export async function refreshMarketplace(
|
|||
return { ok: false, status: 422, message: 'marketplace manifest failed validation', errors: parsed.errors };
|
||||
}
|
||||
const now = Date.now();
|
||||
db.prepare(`UPDATE plugin_marketplaces SET manifest_json = ?, refreshed_at = ? WHERE id = ?`)
|
||||
.run(text, now, id);
|
||||
db.prepare(`UPDATE plugin_marketplaces SET spec_version = ?, version = ?, manifest_json = ?, refreshed_at = ? WHERE id = ?`)
|
||||
.run(parsed.manifest.specVersion, parsed.manifest.version, text, now, id);
|
||||
return {
|
||||
ok: true,
|
||||
row: { ...existing, manifest: parsed.manifest, refreshedAt: now },
|
||||
row: {
|
||||
...existing,
|
||||
specVersion: parsed.manifest.specVersion,
|
||||
version: parsed.manifest.version,
|
||||
manifest: parsed.manifest,
|
||||
refreshedAt: now,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -222,10 +254,54 @@ function safeParseManifest(raw: string): MarketplaceManifest {
|
|||
} catch {
|
||||
// fall through
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error('legacy marketplace manifest is not an object');
|
||||
}
|
||||
const legacy = parsed as Record<string, unknown>;
|
||||
const metadata = typeof legacy['metadata'] === 'object' && legacy['metadata'] !== null
|
||||
? legacy['metadata'] as Record<string, unknown>
|
||||
: {};
|
||||
const plugins = Array.isArray(legacy?.['plugins'])
|
||||
? (legacy['plugins'] as unknown[]).flatMap((entry) => {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return [];
|
||||
const obj = entry as Record<string, unknown>;
|
||||
const name = typeof obj['name'] === 'string' ? obj['name'] : '';
|
||||
const source = typeof obj['source'] === 'string' ? obj['source'] : '';
|
||||
if (!name || !source) return [];
|
||||
return [{
|
||||
...obj,
|
||||
name,
|
||||
source,
|
||||
version: typeof obj['version'] === 'string' && obj['version'].length > 0
|
||||
? obj['version']
|
||||
: '0.0.0',
|
||||
}];
|
||||
})
|
||||
: [];
|
||||
return {
|
||||
...legacy,
|
||||
specVersion: typeof legacy['specVersion'] === 'string'
|
||||
? legacy['specVersion'] as string
|
||||
: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
name: typeof legacy['name'] === 'string' ? legacy['name'] as string : 'unknown',
|
||||
version: typeof legacy['version'] === 'string' && (legacy['version'] as string).length > 0
|
||||
? legacy['version'] as string
|
||||
: typeof metadata['version'] === 'string' && metadata['version'].length > 0
|
||||
? metadata['version']
|
||||
: '0.0.0',
|
||||
plugins,
|
||||
} as MarketplaceManifest;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
// Last-resort fallback: return a minimal shape so the caller doesn't
|
||||
// explode if a database row was stored before a schema patch.
|
||||
return {
|
||||
specVersion: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
name: 'unknown',
|
||||
version: '0.0.0',
|
||||
plugins: [],
|
||||
} as MarketplaceManifest;
|
||||
}
|
||||
|
|
@ -245,7 +321,10 @@ export interface ResolvedPluginEntry {
|
|||
marketplaceId: string;
|
||||
marketplaceUrl: string;
|
||||
marketplaceTrust: MarketplaceTrustTier;
|
||||
marketplaceSpecVersion: string;
|
||||
marketplaceVersion: string;
|
||||
pluginName: string;
|
||||
pluginVersion: string;
|
||||
source: string;
|
||||
description?: string;
|
||||
}
|
||||
|
|
@ -265,7 +344,10 @@ export function resolvePluginInMarketplaces(
|
|||
marketplaceId: row.id,
|
||||
marketplaceUrl: row.url,
|
||||
marketplaceTrust: row.trust,
|
||||
marketplaceSpecVersion: row.specVersion,
|
||||
marketplaceVersion: row.version,
|
||||
pluginName: entry.name,
|
||||
pluginVersion: entry.version,
|
||||
source: entry.source,
|
||||
};
|
||||
if (entry.description) result.description = entry.description;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ export function migratePlugins(db: SqliteDb): void {
|
|||
CREATE TABLE IF NOT EXISTS plugin_marketplaces (
|
||||
id TEXT PRIMARY KEY,
|
||||
url TEXT NOT NULL,
|
||||
spec_version TEXT NOT NULL DEFAULT '1.0.0',
|
||||
version TEXT NOT NULL DEFAULT '0.0.0',
|
||||
trust TEXT NOT NULL,
|
||||
manifest_json TEXT NOT NULL,
|
||||
added_at INTEGER NOT NULL,
|
||||
|
|
@ -51,6 +53,7 @@ export function migratePlugins(db: SqliteDb): void {
|
|||
conversation_id TEXT,
|
||||
run_id TEXT,
|
||||
plugin_id TEXT NOT NULL,
|
||||
plugin_spec_version TEXT NOT NULL DEFAULT '1.0.0',
|
||||
plugin_version TEXT NOT NULL,
|
||||
manifest_source_digest TEXT NOT NULL,
|
||||
source_marketplace_id TEXT,
|
||||
|
|
@ -128,6 +131,20 @@ export function migratePlugins(db: SqliteDb): void {
|
|||
CREATE INDEX IF NOT EXISTS idx_genui_run ON genui_surfaces(run_id);
|
||||
`);
|
||||
|
||||
const marketplaceCols = db.prepare(`PRAGMA table_info(plugin_marketplaces)`).all() as DbRow[];
|
||||
if (!marketplaceCols.some((c) => c['name'] === 'spec_version')) {
|
||||
db.exec(`ALTER TABLE plugin_marketplaces ADD COLUMN spec_version TEXT NOT NULL DEFAULT '1.0.0'`);
|
||||
}
|
||||
if (!marketplaceCols.some((c) => c['name'] === 'version')) {
|
||||
db.exec(`ALTER TABLE plugin_marketplaces ADD COLUMN version TEXT NOT NULL DEFAULT '0.0.0'`);
|
||||
}
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_marketplaces_version ON plugin_marketplaces(version)`);
|
||||
|
||||
const snapshotCols = db.prepare(`PRAGMA table_info(applied_plugin_snapshots)`).all() as DbRow[];
|
||||
if (!snapshotCols.some((c) => c['name'] === 'plugin_spec_version')) {
|
||||
db.exec(`ALTER TABLE applied_plugin_snapshots ADD COLUMN plugin_spec_version TEXT NOT NULL DEFAULT '1.0.0'`);
|
||||
}
|
||||
|
||||
// Back-reference columns. SQLite has no IF NOT EXISTS for ALTER; check
|
||||
// pragma_table_info first. Mirrors the upstream pattern in db.ts.
|
||||
const projectCols = db.prepare(`PRAGMA table_info(projects)`).all() as DbRow[];
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ export function resolvePluginSnapshot(input: ResolveSnapshotInput): ResolveSnaps
|
|||
conversationId: input.conversationId ?? null,
|
||||
runId: input.runId ?? null,
|
||||
pluginId: result.appliedPlugin.pluginId,
|
||||
pluginSpecVersion: result.appliedPlugin.pluginSpecVersion ?? plugin.manifest.specVersion,
|
||||
pluginVersion: result.appliedPlugin.pluginVersion,
|
||||
pluginTitle: result.appliedPlugin.pluginTitle,
|
||||
pluginDescription: result.appliedPlugin.pluginDescription,
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export async function scaffoldPlugin(input: ScaffoldInput): Promise<ScaffoldResu
|
|||
|
||||
const manifest: Record<string, unknown> = {
|
||||
$schema: 'https://open-design.ai/schemas/plugin.v1.json',
|
||||
specVersion: '1.0.0',
|
||||
name: input.id,
|
||||
title,
|
||||
version: '0.1.0',
|
||||
|
|
@ -142,7 +143,7 @@ export async function scaffoldPlugin(input: ScaffoldInput): Promise<ScaffoldResu
|
|||
'## Files',
|
||||
'',
|
||||
'- `SKILL.md` — the canonical agent skill body.',
|
||||
'- `open-design.json` — the Open Design marketplace sidecar.',
|
||||
'- `open-design.json` — the versioned Open Design marketplace sidecar.',
|
||||
'',
|
||||
'Edit `SKILL.md` to teach the agent how to perform the workflow.',
|
||||
'Edit `open-design.json` to refine the marketplace card and inputs.',
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export function diffSnapshots(input: DiffSnapshotsInput): SnapshotDiffReport {
|
|||
// Identity + lineage.
|
||||
diffScalar(entries, 'snapshotId', a.snapshotId, b.snapshotId);
|
||||
diffScalar(entries, 'pluginId', a.pluginId, b.pluginId);
|
||||
diffScalar(entries, 'pluginSpecVersion', a.pluginSpecVersion, b.pluginSpecVersion);
|
||||
diffScalar(entries, 'pluginVersion', a.pluginVersion, b.pluginVersion);
|
||||
diffScalar(entries, 'manifestSourceDigest', a.manifestSourceDigest, b.manifestSourceDigest);
|
||||
diffScalar(entries, 'sourceMarketplaceId', a.sourceMarketplaceId, b.sourceMarketplaceId);
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import type Database from 'better-sqlite3';
|
||||
import { readPluginEnvKnobs } from '../app-config.js';
|
||||
import type {
|
||||
AppliedPluginSnapshot,
|
||||
GenUISurfaceSpec,
|
||||
McpServerSpec,
|
||||
PluginAssetRef,
|
||||
PluginConnectorBinding,
|
||||
PluginConnectorRef,
|
||||
PluginPipeline,
|
||||
ResolvedContext,
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
type AppliedPluginSnapshot,
|
||||
type GenUISurfaceSpec,
|
||||
type McpServerSpec,
|
||||
type PluginAssetRef,
|
||||
type PluginConnectorBinding,
|
||||
type PluginConnectorRef,
|
||||
type PluginPipeline,
|
||||
type ResolvedContext,
|
||||
} from '@open-design/contracts';
|
||||
|
||||
type SqliteDb = Database.Database;
|
||||
|
|
@ -34,6 +35,7 @@ export interface CreateSnapshotInput {
|
|||
conversationId?: string | null | undefined;
|
||||
runId?: string | null | undefined;
|
||||
pluginId: string;
|
||||
pluginSpecVersion?: string | null | undefined;
|
||||
pluginVersion: string;
|
||||
pluginTitle?: string | undefined;
|
||||
pluginDescription?: string | undefined;
|
||||
|
|
@ -69,7 +71,7 @@ export function createSnapshot(db: SqliteDb, input: CreateSnapshotInput): Applie
|
|||
|
||||
db.prepare(`
|
||||
INSERT INTO applied_plugin_snapshots (
|
||||
id, project_id, conversation_id, run_id, plugin_id, plugin_version,
|
||||
id, project_id, conversation_id, run_id, plugin_id, plugin_spec_version, plugin_version,
|
||||
manifest_source_digest, source_marketplace_id, pinned_ref, task_kind,
|
||||
inputs_json, resolved_context_json, pipeline_json, genui_surfaces_json,
|
||||
capabilities_granted, capabilities_required, assets_staged_json,
|
||||
|
|
@ -77,13 +79,14 @@ export function createSnapshot(db: SqliteDb, input: CreateSnapshotInput): Applie
|
|||
plugin_title, plugin_description, query_text,
|
||||
status, applied_at, expires_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'fresh', ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'fresh', ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
input.projectId,
|
||||
input.conversationId ?? null,
|
||||
input.runId ?? null,
|
||||
input.pluginId,
|
||||
input.pluginSpecVersion ?? OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
input.pluginVersion,
|
||||
input.manifestSourceDigest,
|
||||
input.sourceMarketplaceId ?? null,
|
||||
|
|
@ -280,6 +283,7 @@ function buildSnapshot(args: {
|
|||
const snapshot: AppliedPluginSnapshot = {
|
||||
snapshotId: id,
|
||||
pluginId: input.pluginId,
|
||||
pluginSpecVersion: input.pluginSpecVersion ?? OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
pluginVersion: input.pluginVersion,
|
||||
manifestSourceDigest: input.manifestSourceDigest,
|
||||
sourceMarketplaceId: input.sourceMarketplaceId ?? undefined,
|
||||
|
|
@ -309,6 +313,7 @@ export function rowToSnapshot(row: DbRow): AppliedPluginSnapshot {
|
|||
const snapshot: AppliedPluginSnapshot = {
|
||||
snapshotId: String(row['id']),
|
||||
pluginId: String(row['plugin_id']),
|
||||
pluginSpecVersion: row['plugin_spec_version'] != null ? String(row['plugin_spec_version']) : undefined,
|
||||
pluginVersion: String(row['plugin_version']),
|
||||
manifestSourceDigest: String(row['manifest_source_digest']),
|
||||
sourceMarketplaceId: row['source_marketplace_id'] != null ? String(row['source_marketplace_id']) : undefined,
|
||||
|
|
|
|||
|
|
@ -4821,7 +4821,11 @@ export async function startServer({
|
|||
recordPluginEvent({
|
||||
kind: 'plugin.marketplace-refreshed',
|
||||
pluginId: '',
|
||||
details: { marketplaceId: req.params.id },
|
||||
details: {
|
||||
marketplaceId: req.params.id,
|
||||
marketplaceVersion: result.row.version,
|
||||
specVersion: result.row.specVersion,
|
||||
},
|
||||
});
|
||||
} catch { /* best-effort */ }
|
||||
res.json(result.row);
|
||||
|
|
@ -5154,6 +5158,7 @@ export async function startServer({
|
|||
// the digest match guarantees byte-equality (§8.2.1).
|
||||
rerun: {
|
||||
pluginId: snapshot.pluginId,
|
||||
pluginSpecVersion: snapshot.pluginSpecVersion,
|
||||
pluginVersion: snapshot.pluginVersion,
|
||||
inputs: snapshot.inputs,
|
||||
manifestSourceDigest: snapshot.manifestSourceDigest,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "sample-plugin",
|
||||
"title": "Sample Plugin",
|
||||
"version": "1.0.0",
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ let db: Database.Database;
|
|||
let tmpDir: string;
|
||||
|
||||
const VALID_MANIFEST = JSON.stringify({
|
||||
specVersion: '1.0.0',
|
||||
name: 'test-marketplace',
|
||||
version: '1.0.0',
|
||||
metadata: { description: 'fixture', version: '1.0.0' },
|
||||
plugins: [
|
||||
{ name: 'sample-plugin', source: 'github:open-design/sample-plugin' },
|
||||
{ name: 'sample-plugin', source: 'github:open-design/sample-plugin', version: '0.1.0' },
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -65,6 +67,8 @@ describe('marketplaces', () => {
|
|||
throw new Error(`expected ok: ${JSON.stringify(result)}`);
|
||||
}
|
||||
expect(result.row.url).toBe('https://example.com/marketplace.json');
|
||||
expect(result.row.specVersion).toBe('1.0.0');
|
||||
expect(result.row.version).toBe('1.0.0');
|
||||
expect(result.row.trust).toBe('restricted');
|
||||
expect(result.row.manifest.plugins).toHaveLength(1);
|
||||
expect(listMarketplaces(db)).toHaveLength(1);
|
||||
|
|
@ -103,13 +107,16 @@ describe('marketplaces', () => {
|
|||
updatedManifest.plugins.push({
|
||||
name: 'new-plugin',
|
||||
source: 'github:open-design/new-plugin',
|
||||
version: '0.2.0',
|
||||
});
|
||||
updatedManifest.version = '1.0.1';
|
||||
const refreshed = await refreshMarketplace(
|
||||
db,
|
||||
added.row.id,
|
||||
fixtureFetcher(JSON.stringify(updatedManifest)),
|
||||
);
|
||||
if (!refreshed.ok) throw new Error('refresh failed');
|
||||
expect(refreshed.row.version).toBe('1.0.1');
|
||||
expect(refreshed.row.manifest.plugins).toHaveLength(2);
|
||||
expect(refreshed.row.refreshedAt).toBeGreaterThanOrEqual(added.row.refreshedAt);
|
||||
});
|
||||
|
|
@ -136,6 +143,8 @@ describe('resolvePluginInMarketplaces', () => {
|
|||
const resolved = resolvePluginInMarketplaces(db, 'sample-plugin');
|
||||
expect(resolved).not.toBeNull();
|
||||
expect(resolved!.source).toBe('github:open-design/sample-plugin');
|
||||
expect(resolved!.pluginVersion).toBe('0.1.0');
|
||||
expect(resolved!.marketplaceVersion).toBe('1.0.0');
|
||||
expect(resolved!.marketplaceTrust).toBe('restricted');
|
||||
});
|
||||
|
||||
|
|
@ -159,8 +168,10 @@ describe('resolvePluginInMarketplaces', () => {
|
|||
|
||||
it('walks marketplaces in registration order, first hit wins', async () => {
|
||||
const otherManifest = JSON.stringify({
|
||||
specVersion: '1.0.0',
|
||||
name: 'other',
|
||||
plugins: [{ name: 'sample-plugin', source: 'github:other/sample' }],
|
||||
version: '1.0.0',
|
||||
plugins: [{ name: 'sample-plugin', source: 'github:other/sample', version: '0.9.0' }],
|
||||
});
|
||||
const first = await addMarketplace(db, {
|
||||
url: 'https://first.example/marketplace.json',
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export function PluginMetaSections({ record, omit, compact, heading }: Props) {
|
|||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const manifest: PluginManifest = record.manifest ?? ({} as PluginManifest);
|
||||
const specVersion = typeof manifest.specVersion === 'string' ? manifest.specVersion : '';
|
||||
const od = manifest.od ?? {};
|
||||
const description = manifest.description ?? '';
|
||||
const query = resolvePluginQueryFallback(od.useCase?.query);
|
||||
|
|
@ -514,6 +515,14 @@ export function PluginMetaSections({ record, omit, compact, heading }: Props) {
|
|||
<code>v{record.version}</code>
|
||||
</dd>
|
||||
</div>
|
||||
{specVersion ? (
|
||||
<div>
|
||||
<dt>Spec</dt>
|
||||
<dd>
|
||||
<code>v{specVersion}</code>
|
||||
</dd>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<dt>Trust</dt>
|
||||
<dd>
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ Rules of authorship:
|
|||
```json
|
||||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "make-a-deck",
|
||||
"title": "Make a deck",
|
||||
"version": "1.0.0",
|
||||
|
|
@ -346,6 +347,8 @@ Rules of authorship:
|
|||
### 5.1 Field reference
|
||||
|
||||
- `compat.*` — relative paths to inherited files. The loader concatenates their content into the OD prompt stack assembled by [`composeSystemPrompt()`](../apps/daemon/src/prompts/system.ts).
|
||||
- `specVersion` — the Open Design plugin spec version used to interpret the manifest. This is distinct from plugin `version` and is frozen into apply snapshots for replay.
|
||||
- `version` — the plugin package version. Bump it whenever behavior, metadata, pipeline, inputs, or bundled assets change in a way users may need to audit.
|
||||
- `od.kind` — registry classification (`skill` / `scenario` / `atom` / `bundle`).
|
||||
- `od.taskKind` — one of the four product scenarios (`new-generation` / `code-migration` / `figma-migration` / `tune-collab`, see §1 "Four product scenarios"). Drives marketplace filters, default input templates, and the recommended pipeline starting point.
|
||||
- `od.preview` — drives the marketplace card and detail page. `entry` is served sandboxed via the daemon (the existing `/api/skills/:id/example` plumbing extended to plugins).
|
||||
|
|
@ -422,16 +425,21 @@ Mirrors [`anthropics/skills/.claude-plugin/marketplace.json`](https://raw.github
|
|||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://open-design.ai/schemas/marketplace.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "open-design-official",
|
||||
"version": "1.0.0",
|
||||
"owner": { "name": "Open Design", "url": "https://open-design.ai" },
|
||||
"metadata": { "description": "First-party plugins", "version": "1.0.0" },
|
||||
"plugins": [
|
||||
{ "name": "make-a-deck", "source": "github:open-design/plugins/make-a-deck", "tags": ["deck"] },
|
||||
{ "name": "tweet-card", "source": "https://files.../tweet-card-1.0.0.tgz", "tags": ["marketing"] }
|
||||
{ "name": "make-a-deck", "version": "1.0.0", "source": "github:open-design/plugins/make-a-deck", "tags": ["deck"] },
|
||||
{ "name": "tweet-card", "version": "1.0.0", "source": "https://files.../tweet-card-1.0.0.tgz", "tags": ["marketing"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The marketplace top-level `version` is the catalog snapshot version; every `plugins[]` entry also declares the listed plugin version. Installers still verify the target folder's own `open-design.json` after fetching, but registry search, audit logs, and marketplace refresh events can now reason about catalog and plugin versions before install.
|
||||
|
||||
Multiple marketplaces coexist — the user runs `od marketplace add <url>` to register additional indexes (Vercel's, OpenClaw's clawhub, an enterprise team's private catalog). By default, a user-added marketplace is only a discovery source and plugins from it still install as `restricted`; only the built-in official marketplace or a marketplace explicitly trusted through `od marketplace add <url> --trust` / `od marketplace trust <id>` can pass through default `trusted` status.
|
||||
|
||||
## 7. Discovery and install
|
||||
|
|
@ -550,6 +558,7 @@ export interface ApplyResult {
|
|||
export interface AppliedPluginSnapshot {
|
||||
snapshotId: string;
|
||||
pluginId: string;
|
||||
pluginSpecVersion: string;
|
||||
pluginVersion: string;
|
||||
manifestSourceDigest: string;
|
||||
sourceMarketplaceId?: string;
|
||||
|
|
@ -610,7 +619,7 @@ Lives in `packages/contracts/src/plugins/apply.ts`. Re-exported from [`packages/
|
|||
|
||||
The daemon therefore must:
|
||||
|
||||
1. **At apply time** — hash the hydrated manifest plus inputs into `manifestSourceDigest`, then write `pluginVersion`, `pinnedRef`, `sourceMarketplaceId`, `resolvedContext`, `capabilitiesGranted`, `assetsStaged`, **`connectorsRequired` / `connectorsResolved` (cross-checked against the connector subsystem's current `status`)**, and **`mcpServers` (the MCP server set active at apply time)** into `appliedPlugin` and return it to the caller.
|
||||
1. **At apply time** — hash the hydrated manifest plus inputs into `manifestSourceDigest`, then write `pluginSpecVersion`, `pluginVersion`, `pinnedRef`, `sourceMarketplaceId`, `resolvedContext`, `capabilitiesGranted`, `assetsStaged`, **`connectorsRequired` / `connectorsResolved` (cross-checked against the connector subsystem's current `status`)**, and **`mcpServers` (the MCP server set active at apply time)** into `appliedPlugin` and return it to the caller.
|
||||
2. **At project create / run start** — write the client-supplied `appliedPlugin` (or the daemon's server-side re-resolved snapshot) into the SQLite `applied_plugin_snapshots` table (§11.4) and FK-link it from `runs` / `conversations`.
|
||||
3. **Replay** — `od run replay <runId>` and `od plugin export <runId>` must reconstruct prompt and assets from the snapshot rather than the live manifest, so old runs remain reproducible after plugin upgrades.
|
||||
4. **Audit** — UI ProjectView shows snapshot id + version + digest at the top; artifact provenance (§11.5 ArtifactManifest) reverse-resolves plugin source via the snapshot id.
|
||||
|
|
@ -664,6 +673,7 @@ A `restricted` plugin can never reach P3/P4/P5 unless the user grants the capabi
|
|||
Trust records must bind to provenance, not just a name:
|
||||
|
||||
- `pluginId`
|
||||
- `specVersion`
|
||||
- `version` or resolved git SHA / archive digest
|
||||
- source marketplace id, if any
|
||||
- granted capabilities
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ my-plugin/
|
|||
```json
|
||||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "make-a-deck",
|
||||
"title": "Make a deck",
|
||||
"version": "1.0.0",
|
||||
|
|
@ -346,6 +347,8 @@ my-plugin/
|
|||
### 5.1 字段说明
|
||||
|
||||
- `compat.*`:指向继承格式文件的相对路径。loader 会把它们的内容合并进 [`composeSystemPrompt()`](../apps/daemon/src/prompts/system.ts) 组装出的 OD prompt stack。
|
||||
- `specVersion`:解释此 manifest 时使用的 Open Design 插件规范版本。它独立于插件 `version`,并会冻结到 apply snapshot,便于 replay。
|
||||
- `version`:插件包自身版本。只要行为、元数据、pipeline、inputs 或随包 assets 出现用户需要审计的变化,就应该 bump。
|
||||
- `od.kind`:registry 里的分类(`skill` / `scenario` / `atom` / `bundle`)。
|
||||
- `od.taskKind`:四类产品场景之一(`new-generation` / `code-migration` / `figma-migration` / `tune-collab`,§1「四类产品场景」)。决定 marketplace filter、初始 inputs 模板、推荐 pipeline 起点。
|
||||
- `od.preview`:驱动 marketplace 卡片和详情页。`entry` 通过 daemon 以 sandboxed 方式服务(扩展现有 `/api/skills/:id/example` plumbing)。
|
||||
|
|
@ -422,16 +425,21 @@ export type ContextItem =
|
|||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://open-design.ai/schemas/marketplace.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "open-design-official",
|
||||
"version": "1.0.0",
|
||||
"owner": { "name": "Open Design", "url": "https://open-design.ai" },
|
||||
"metadata": { "description": "First-party plugins", "version": "1.0.0" },
|
||||
"plugins": [
|
||||
{ "name": "make-a-deck", "source": "github:open-design/plugins/make-a-deck", "tags": ["deck"] },
|
||||
{ "name": "tweet-card", "source": "https://files.../tweet-card-1.0.0.tgz", "tags": ["marketing"] }
|
||||
{ "name": "make-a-deck", "version": "1.0.0", "source": "github:open-design/plugins/make-a-deck", "tags": ["deck"] },
|
||||
{ "name": "tweet-card", "version": "1.0.0", "source": "https://files.../tweet-card-1.0.0.tgz", "tags": ["marketing"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Marketplace 顶层 `version` 是 catalog snapshot 版本;每个 `plugins[]` entry 也声明被列入的插件版本。Installer 抓取后仍会校验目标文件夹自己的 `open-design.json`,但 registry search、审计日志和 marketplace refresh events 可以在安装前就理解 catalog 与插件版本。
|
||||
|
||||
可以同时存在多个 marketplaces。用户通过 `od marketplace add <url>` 注册额外 index(Vercel 的、OpenClaw 的 clawhub、企业私有 catalog)。默认情况下,用户添加的 marketplace 只是 discovery source,它里面的插件仍然以 `restricted` 安装;只有官方内置 marketplace 或用户显式执行 `od marketplace add <url> --trust` / `od marketplace trust <id>` 后,来自该 marketplace 的插件才可以默认继承 `trusted`。
|
||||
|
||||
## 7. 发现与安装
|
||||
|
|
@ -550,6 +558,7 @@ export interface ApplyResult {
|
|||
export interface AppliedPluginSnapshot {
|
||||
snapshotId: string;
|
||||
pluginId: string;
|
||||
pluginSpecVersion: string;
|
||||
pluginVersion: string;
|
||||
manifestSourceDigest: string;
|
||||
sourceMarketplaceId?: string;
|
||||
|
|
@ -609,7 +618,7 @@ export interface InputFieldSpec {
|
|||
|
||||
因此 daemon 必须:
|
||||
|
||||
1. **Apply 时**:把 hydrated manifest 与 inputs 一起 hash 成 `manifestSourceDigest`,连同 `pluginVersion`、`pinnedRef`、`sourceMarketplaceId`、`resolvedContext`、`capabilitiesGranted`、`assetsStaged`、**`connectorsRequired` / `connectorsResolved`(参考 connector 子系统当前 `status`)**、**`mcpServers`(apply 时启用的 MCP server set)** 写入 `appliedPlugin`,返回给 caller。
|
||||
1. **Apply 时**:把 hydrated manifest 与 inputs 一起 hash 成 `manifestSourceDigest`,连同 `pluginSpecVersion`、`pluginVersion`、`pinnedRef`、`sourceMarketplaceId`、`resolvedContext`、`capabilitiesGranted`、`assetsStaged`、**`connectorsRequired` / `connectorsResolved`(参考 connector 子系统当前 `status`)**、**`mcpServers`(apply 时启用的 MCP server set)** 写入 `appliedPlugin`,返回给 caller。
|
||||
2. **Project create / run start 时**:把客户端提交的 `appliedPlugin`(或 daemon 在 server-side 重新解析得到的 snapshot)写入 SQLite `applied_plugin_snapshots` 表(§11.4),并在 `runs` / `conversations` 表中以 FK 指向。
|
||||
3. **Replay**:`od run replay <runId>` 与 `od plugin export <runId>` 必须从 snapshot 而非 live manifest 还原 prompt 与 assets,使老 run 在插件升级后仍可复现。
|
||||
4. **Audit**:UI 的 ProjectView 顶端展示 snapshot id + version + digest;artifact provenance(§11.5 ArtifactManifest)通过 snapshot id 反查 plugin source。
|
||||
|
|
@ -663,6 +672,7 @@ flowchart LR
|
|||
信任记录必须绑定 provenance,而不是只绑定名称:
|
||||
|
||||
- `pluginId`
|
||||
- `specVersion`
|
||||
- `version` 或 resolved git SHA / archive digest
|
||||
- source marketplace id(如果有)
|
||||
- granted capabilities
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
"title": "Open Design plugin marketplace index (v1)",
|
||||
"description": "Schema for `open-design-marketplace.json` federated catalog files. See docs/plugins-spec.md §6.",
|
||||
"type": "object",
|
||||
"required": ["name", "plugins"],
|
||||
"required": ["specVersion", "name", "version", "plugins"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"$schema": { "type": "string", "format": "uri" },
|
||||
"specVersion": { "type": "string", "minLength": 1 },
|
||||
"name": { "type": "string", "minLength": 1 },
|
||||
"version": { "type": "string", "minLength": 1 },
|
||||
"owner": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -29,11 +31,11 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "source"],
|
||||
"required": ["name", "source", "version"],
|
||||
"properties": {
|
||||
"name": { "type": "string", "minLength": 1 },
|
||||
"source": { "type": "string", "minLength": 1 },
|
||||
"version": { "type": "string" },
|
||||
"version": { "type": "string", "minLength": 1 },
|
||||
"ref": { "type": "string" },
|
||||
"tags": { "type": "array", "items": { "type": "string" } },
|
||||
"title": { "type": "string" },
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@
|
|||
"title": "Open Design plugin manifest (v1)",
|
||||
"description": "Schema for `open-design.json` sidecar files. Companion of `SKILL.md`; never duplicates skill body content. See docs/plugins-spec.md §5.",
|
||||
"type": "object",
|
||||
"required": ["name", "version"],
|
||||
"required": ["specVersion", "name", "version"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"$schema": { "type": "string", "format": "uri" },
|
||||
"specVersion": { "type": "string", "minLength": 1 },
|
||||
"name": { "type": "string", "minLength": 1, "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
||||
"title": { "type": "string", "minLength": 1 },
|
||||
"version": { "type": "string", "minLength": 1 },
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export type PluginConnectorBinding = z.infer<typeof PluginConnectorBindingSchema
|
|||
export const AppliedPluginSnapshotSchema = z.object({
|
||||
snapshotId: z.string(),
|
||||
pluginId: z.string(),
|
||||
pluginSpecVersion: z.string().optional(),
|
||||
pluginVersion: z.string(),
|
||||
manifestSourceDigest: z.string(),
|
||||
sourceMarketplaceId: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import { z } from 'zod';
|
|||
// outputs (synthesized PluginManifest from SKILL.md frontmatter or claude
|
||||
// plugin.json) parse cleanly without losing forward-compatible fields.
|
||||
|
||||
export const OPEN_DESIGN_PLUGIN_SPEC_VERSION = '1.0.0';
|
||||
|
||||
export const OpenDesignSpecVersionSchema = z.string().min(1);
|
||||
|
||||
export const ReferenceSchema = z.object({
|
||||
ref: z.string().optional(),
|
||||
path: z.string().optional(),
|
||||
|
|
@ -134,6 +138,7 @@ export type PluginConnectorRef = z.infer<typeof PluginConnectorRefSchema>;
|
|||
|
||||
export const PluginManifestSchema = z.object({
|
||||
$schema: z.string().optional(),
|
||||
specVersion: OpenDesignSpecVersionSchema.optional(),
|
||||
name: z.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/),
|
||||
title: z.string().optional(),
|
||||
version: z.string().min(1),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
OpenDesignSpecVersionSchema,
|
||||
} from './manifest.js';
|
||||
|
||||
// `open-design-marketplace.json` schema (v1). Mirrors
|
||||
// `docs/schemas/open-design.marketplace.v1.json`. The federated catalog
|
||||
|
|
@ -7,7 +11,7 @@ import { z } from 'zod';
|
|||
export const MarketplacePluginEntrySchema = z.object({
|
||||
name: z.string().min(1),
|
||||
source: z.string().min(1),
|
||||
version: z.string().optional(),
|
||||
version: z.string().min(1),
|
||||
ref: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
title: z.string().optional(),
|
||||
|
|
@ -18,8 +22,10 @@ export const MarketplacePluginEntrySchema = z.object({
|
|||
export type MarketplacePluginEntry = z.infer<typeof MarketplacePluginEntrySchema>;
|
||||
|
||||
export const MarketplaceManifestSchema = z.object({
|
||||
$schema: z.string().optional(),
|
||||
name: z.string().min(1),
|
||||
$schema: z.string().optional(),
|
||||
specVersion: OpenDesignSpecVersionSchema.default(OPEN_DESIGN_PLUGIN_SPEC_VERSION),
|
||||
name: z.string().min(1),
|
||||
version: z.string().min(1),
|
||||
owner: z.object({
|
||||
name: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
PluginManifestSchema,
|
||||
resolveLocalizedText,
|
||||
} from '../src/plugins/manifest.js';
|
||||
|
||||
describe('plugin manifest localized text', () => {
|
||||
it('exports the current plugin spec version for manifests and registries', () => {
|
||||
expect(OPEN_DESIGN_PLUGIN_SPEC_VERSION).toBe('1.0.0');
|
||||
});
|
||||
|
||||
it('accepts legacy string use-case queries', () => {
|
||||
const manifest = PluginManifestSchema.parse({
|
||||
name: 'sample-plugin',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
InputField,
|
||||
PluginManifest,
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
type InputField,
|
||||
type PluginManifest,
|
||||
} from '@open-design/contracts';
|
||||
import { parseFrontmatter, type FrontmatterObject, type FrontmatterValue } from '../parsers/frontmatter.js';
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ export function adaptAgentSkill(
|
|||
: undefined;
|
||||
|
||||
const manifest: PluginManifest = {
|
||||
specVersion: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
name,
|
||||
title,
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import type { PluginManifest } from '@open-design/contracts';
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
type PluginManifest,
|
||||
} from '@open-design/contracts';
|
||||
|
||||
// Adapter from a `.claude-plugin/plugin.json` file to a synthesized
|
||||
// PluginManifest. Phase 1 keeps the mapping minimal — name / version /
|
||||
|
|
@ -54,6 +57,7 @@ export function adaptClaudePlugin(
|
|||
warnings.push(`claude-plugin declares ${commands} command(s); v1 OD apply does not auto-register hooks. Add them via od.context.claudePlugins[].`);
|
||||
}
|
||||
const manifest: PluginManifest = {
|
||||
specVersion: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
name: safeName,
|
||||
title: typeof obj['title'] === 'string' ? obj['title'] : safeName,
|
||||
version,
|
||||
|
|
@ -69,6 +73,7 @@ export function adaptClaudePlugin(
|
|||
|
||||
function synthesizeFallback(folderId: string, compatPath: string): PluginManifest {
|
||||
return {
|
||||
specVersion: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
name: folderId,
|
||||
title: folderId,
|
||||
version: '0.0.0',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { PluginManifestSchema, type PluginManifest } from '@open-design/contracts';
|
||||
import {
|
||||
OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
PluginManifestSchema,
|
||||
type PluginManifest,
|
||||
} from '@open-design/contracts';
|
||||
|
||||
export interface ManifestParseSuccess {
|
||||
ok: true;
|
||||
|
|
@ -41,5 +45,12 @@ export function parseManifestObject(value: unknown): ManifestParseResult {
|
|||
errors: result.error.issues.map((issue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`),
|
||||
};
|
||||
}
|
||||
return { ok: true, manifest: result.data, warnings: [] };
|
||||
return {
|
||||
ok: true,
|
||||
manifest: {
|
||||
specVersion: OPEN_DESIGN_PLUGIN_SPEC_VERSION,
|
||||
...result.data,
|
||||
},
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,14 +57,33 @@ describe('parseManifest', () => {
|
|||
describe('parseMarketplace', () => {
|
||||
it('accepts a tiny catalog', () => {
|
||||
const result = parseMarketplace(JSON.stringify({
|
||||
specVersion: '1.0.0',
|
||||
name: 'open-design-official',
|
||||
plugins: [{ name: 'make-a-deck', source: 'github:open-design/plugins/make-a-deck' }],
|
||||
version: '1.0.0',
|
||||
plugins: [{ name: 'make-a-deck', source: 'github:open-design/plugins/make-a-deck', version: '0.1.0' }],
|
||||
}));
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects when catalog version is missing', () => {
|
||||
const result = parseMarketplace(JSON.stringify({
|
||||
name: 'no-version',
|
||||
plugins: [{ name: 'make-a-deck', source: 'github:open-design/plugins/make-a-deck', version: '0.1.0' }],
|
||||
}));
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when plugin entry version is missing', () => {
|
||||
const result = parseMarketplace(JSON.stringify({
|
||||
name: 'missing-plugin-version',
|
||||
version: '1.0.0',
|
||||
plugins: [{ name: 'make-a-deck', source: 'github:open-design/plugins/make-a-deck' }],
|
||||
}));
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when plugins is missing', () => {
|
||||
const result = parseMarketplace(JSON.stringify({ name: 'no-plugins' }));
|
||||
const result = parseMarketplace(JSON.stringify({ name: 'no-plugins', version: '1.0.0' }));
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ This directory has two different jobs:
|
|||
- `_official/` - first-party plugins bundled with Open Design. The daemon scans this tree at startup and registers these plugins as official.
|
||||
- `spec/` - the portable plugin specification, templates, examples, and agent handoff kit for building, testing, publishing, or opening a PR back to Open Design.
|
||||
|
||||
The common contract is the same everywhere: a plugin is a portable agent skill folder with a `SKILL.md`, plus an optional `open-design.json` sidecar that gives Open Design marketplace metadata, inputs, previews, pipelines, and trust/capability hints.
|
||||
The common contract is the same everywhere: a plugin is a portable agent skill folder with a `SKILL.md`, plus an optional versioned `open-design.json` sidecar that gives Open Design marketplace metadata, inputs, previews, pipelines, and trust/capability hints.
|
||||
|
||||
Start here:
|
||||
|
||||
|
|
@ -17,3 +17,4 @@ Start here:
|
|||
- Registry publishing strategy: [`spec/PUBLISHING-REGISTRIES.md`](spec/PUBLISHING-REGISTRIES.md)
|
||||
- Full product spec: [`../docs/plugins-spec.md`](../docs/plugins-spec.md)
|
||||
- Manifest schema: [`../docs/schemas/open-design.plugin.v1.json`](../docs/schemas/open-design.plugin.v1.json)
|
||||
- Marketplace schema: [`../docs/schemas/open-design.marketplace.v1.json`](../docs/schemas/open-design.marketplace.v1.json)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- `_official/` - Open Design 随包发布的一方插件。daemon 启动时会扫描这个目录,并把这些插件注册为 official。
|
||||
- `spec/` - 可移植插件规范、模板、示例和 agent handoff 包,用于构建、测试、发布插件,或向 Open Design 提交 PR。
|
||||
|
||||
所有插件共享同一个基础契约:插件是一个可移植的 agent skill 文件夹,包含 `SKILL.md`,并可选添加 `open-design.json` sidecar。`open-design.json` 负责 Open Design marketplace 元数据、输入项、预览、pipeline、信任与能力声明。
|
||||
所有插件共享同一个基础契约:插件是一个可移植的 agent skill 文件夹,包含 `SKILL.md`,并可选添加带版本的 `open-design.json` sidecar。`open-design.json` 负责 Open Design marketplace 元数据、输入项、预览、pipeline、信任与能力声明。
|
||||
|
||||
从这里开始:
|
||||
|
||||
|
|
@ -17,3 +17,4 @@
|
|||
- Registry 发布策略:[`spec/PUBLISHING-REGISTRIES.zh-CN.md`](spec/PUBLISHING-REGISTRIES.zh-CN.md)
|
||||
- 完整产品 spec:[`../docs/plugins-spec.zh-CN.md`](../docs/plugins-spec.zh-CN.md)
|
||||
- Manifest schema:[`../docs/schemas/open-design.plugin.v1.json`](../docs/schemas/open-design.plugin.v1.json)
|
||||
- Marketplace schema:[`../docs/schemas/open-design.marketplace.v1.json`](../docs/schemas/open-design.marketplace.v1.json)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "build-test",
|
||||
"title": "Build test",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "code-import",
|
||||
"title": "Code import",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "critique-theater",
|
||||
"title": "Critique theater",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-extract",
|
||||
"title": "Design extract",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "diff-review",
|
||||
"title": "Diff review",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "direction-picker",
|
||||
"title": "Direction picker",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "discovery-question-form",
|
||||
"title": "Discovery question form",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "figma-extract",
|
||||
"title": "Figma extract",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "handoff",
|
||||
"title": "Handoff",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "patch-edit",
|
||||
"title": "Patch edit",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "rewrite-plan",
|
||||
"title": "Rewrite plan",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "todo-write",
|
||||
"title": "Todo write",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "token-map",
|
||||
"title": "Token map",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-agentic",
|
||||
"title": "Agentic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-airbnb",
|
||||
"title": "Airbnb",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-airtable",
|
||||
"title": "Airtable",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-ant",
|
||||
"title": "Ant",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-apple",
|
||||
"title": "Apple",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-application",
|
||||
"title": "Application",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-arc",
|
||||
"title": "Arc Browser",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-artistic",
|
||||
"title": "Artistic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-atelier-zero",
|
||||
"title": "Atelier Zero",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-bento",
|
||||
"title": "Bento",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-binance",
|
||||
"title": "Binance.US",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-bmw-m",
|
||||
"title": "BMW M",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-bmw",
|
||||
"title": "BMW",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-bold",
|
||||
"title": "Bold",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-brutalism",
|
||||
"title": "Brutalism",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-bugatti",
|
||||
"title": "Bugatti",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-cafe",
|
||||
"title": "Cafe",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-cal",
|
||||
"title": "Cal.com",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-canva",
|
||||
"title": "Canva",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-claude",
|
||||
"title": "Claude (Anthropic)",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-clay",
|
||||
"title": "Clay",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-claymorphism",
|
||||
"title": "Claymorphism",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-clean",
|
||||
"title": "Clean",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-clickhouse",
|
||||
"title": "ClickHouse",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-cohere",
|
||||
"title": "Cohere",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-coinbase",
|
||||
"title": "Coinbase",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-colorful",
|
||||
"title": "Colorful",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-composio",
|
||||
"title": "Composio",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-contemporary",
|
||||
"title": "Contemporary",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-corporate",
|
||||
"title": "Corporate",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-cosmic",
|
||||
"title": "Cosmic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-creative",
|
||||
"title": "Creative",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-cursor",
|
||||
"title": "Cursor",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-dashboard",
|
||||
"title": "Dashboard",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-default",
|
||||
"title": "Neutral Modern",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-discord",
|
||||
"title": "Discord",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-dithered",
|
||||
"title": "Dithered",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-doodle",
|
||||
"title": "Doodle",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-dramatic",
|
||||
"title": "Dramatic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-duolingo",
|
||||
"title": "Duolingo",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-editorial",
|
||||
"title": "Editorial",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-elegant",
|
||||
"title": "Elegant",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-elevenlabs",
|
||||
"title": "ElevenLabs",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-energetic",
|
||||
"title": "Energetic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-enterprise",
|
||||
"title": "Enterprise",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-expo",
|
||||
"title": "Expo",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-expressive",
|
||||
"title": "Expressive",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-fantasy",
|
||||
"title": "Fantasy",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-ferrari",
|
||||
"title": "Ferrari",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-figma",
|
||||
"title": "Figma",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-flat",
|
||||
"title": "Flat",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-framer",
|
||||
"title": "Framer",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-friendly",
|
||||
"title": "Friendly",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-futuristic",
|
||||
"title": "Futuristic",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-github",
|
||||
"title": "GitHub",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-glassmorphism",
|
||||
"title": "Glassmorphism",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-gradient",
|
||||
"title": "Gradient",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-hashicorp",
|
||||
"title": "HashiCorp",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-huggingface",
|
||||
"title": "Hugging Face",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
|
||||
"specVersion": "1.0.0",
|
||||
"name": "design-system-ibm",
|
||||
"title": "IBM",
|
||||
"version": "0.1.0",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue